NTTList: move into networking module
[citadel.git] / citadel / file_ops.c
1 /* 
2  * Server functions which handle file transfers and room directories.
3  */
4
5 #include "sysdep.h"
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <stdio.h>
9 #include <fcntl.h>
10 #include <sys/stat.h>
11 #include <errno.h>
12 #include <ctype.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <sys/mman.h>
16
17 #if TIME_WITH_SYS_TIME
18 # include <sys/time.h>
19 # include <time.h>
20 #else
21 # if HAVE_SYS_TIME_H
22 #  include <sys/time.h>
23 # else
24 #  include <time.h>
25 # endif
26 #endif
27
28 #include <limits.h>
29 #include <libcitadel.h>
30 #include "citadel.h"
31 #include "server.h"
32 #include "config.h"
33 #include "file_ops.h"
34 #include "sysdep_decls.h"
35 #include "support.h"
36 #include "room_ops.h"
37 #include "msgbase.h"
38 #include "citserver.h"
39 #include "threads.h"
40
41 #ifndef HAVE_SNPRINTF
42 #include "snprintf.h"
43 #endif
44
45 #include "ctdl_module.h"
46 #include "user_ops.h"
47
48
49
50 /*
51  * Server command to delete a file from a room's directory
52  */
53 void cmd_delf(char *filename)
54 {
55         char pathname[64];
56         int a;
57
58         if (CtdlAccessCheck(ac_room_aide))
59                 return;
60
61         if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
62                 cprintf("%d No directory in this room.\n",
63                         ERROR + NOT_HERE);
64                 return;
65         }
66
67         if (IsEmptyStr(filename)) {
68                 cprintf("%d You must specify a file name.\n",
69                         ERROR + FILE_NOT_FOUND);
70                 return;
71         }
72         for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
73                 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
74                         filename[a] = '_';
75                 }
76         }
77         snprintf(pathname, sizeof pathname,
78                          "%s/%s/%s",
79                          ctdl_file_dir,
80                          CC->room.QRdirname, filename);
81         a = unlink(pathname);
82         if (a == 0) {
83                 cprintf("%d File '%s' deleted.\n", CIT_OK, pathname);
84         }
85         else {
86                 cprintf("%d File '%s' not found.\n",
87                         ERROR + FILE_NOT_FOUND, pathname);
88         }
89 }
90
91
92
93
94 /*
95  * move a file from one room directory to another
96  */
97 void cmd_movf(char *cmdbuf)
98 {
99         char filename[PATH_MAX];
100         char pathname[PATH_MAX];
101         char newpath[PATH_MAX];
102         char newroom[ROOMNAMELEN];
103         char buf[PATH_MAX];
104         int a;
105         struct ctdlroom qrbuf;
106
107         extract_token(filename, cmdbuf, 0, '|', sizeof filename);
108         extract_token(newroom, cmdbuf, 1, '|', sizeof newroom);
109
110         if (CtdlAccessCheck(ac_room_aide)) return;
111
112         if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
113                 cprintf("%d No directory in this room.\n",
114                         ERROR + NOT_HERE);
115                 return;
116         }
117
118         if (IsEmptyStr(filename)) {
119                 cprintf("%d You must specify a file name.\n",
120                         ERROR + FILE_NOT_FOUND);
121                 return;
122         }
123
124         for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
125                 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
126                         filename[a] = '_';
127                 }
128         }
129         snprintf(pathname, sizeof pathname, "./files/%s/%s",
130                  CC->room.QRdirname, filename);
131         if (access(pathname, 0) != 0) {
132                 cprintf("%d File '%s' not found.\n",
133                         ERROR + FILE_NOT_FOUND, pathname);
134                 return;
135         }
136
137         if (CtdlGetRoom(&qrbuf, newroom) != 0) {
138                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom);
139                 return;
140         }
141         if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
142                 cprintf("%d '%s' is not a directory room.\n",
143                         ERROR + NOT_HERE, qrbuf.QRname);
144                 return;
145         }
146         snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname,
147                  filename);
148         if (link(pathname, newpath) != 0) {
149                 cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR,
150                         strerror(errno));
151                 return;
152         }
153         unlink(pathname);
154
155         /* this is a crude method of copying the file description */
156         snprintf(buf, sizeof buf,
157                  "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir",
158                  CC->room.QRdirname, filename, qrbuf.QRdirname);
159         system(buf);
160         cprintf("%d File '%s' has been moved.\n", CIT_OK, filename);
161 }
162
163
164 /*
165  * This code is common to all commands which open a file for downloading,
166  * regardless of whether it's a file from the directory, an image, a network
167  * spool file, a MIME attachment, etc.
168  * It examines the file and displays the OK result code and some information
169  * about the file.  NOTE: this stuff is Unix dependent.
170  */
171 void OpenCmdResult(char *filename, const char *mime_type)
172 {
173         struct stat statbuf;
174         time_t modtime;
175         long filesize;
176
177         fstat(fileno(CC->download_fp), &statbuf);
178         CC->download_fp_total = statbuf.st_size;
179         filesize = (long) statbuf.st_size;
180         modtime = (time_t) statbuf.st_mtime;
181
182         cprintf("%d %ld|%ld|%s|%s\n",
183                 CIT_OK, filesize, (long)modtime, filename, mime_type);
184 }
185
186
187 /*
188  * open a file for downloading
189  */
190 void cmd_open(char *cmdbuf)
191 {
192         char filename[256];
193         char pathname[PATH_MAX];
194         int a;
195
196         extract_token(filename, cmdbuf, 0, '|', sizeof filename);
197
198         if (CtdlAccessCheck(ac_logged_in)) return;
199
200         if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
201                 cprintf("%d No directory in this room.\n",
202                         ERROR + NOT_HERE);
203                 return;
204         }
205
206         if (IsEmptyStr(filename)) {
207                 cprintf("%d You must specify a file name.\n",
208                         ERROR + FILE_NOT_FOUND);
209                 return;
210         }
211
212         if (CC->download_fp != NULL) {
213                 cprintf("%d You already have a download file open.\n",
214                         ERROR + RESOURCE_BUSY);
215                 return;
216         }
217
218         for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
219                 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
220                         filename[a] = '_';
221                 }
222         }
223
224         snprintf(pathname, sizeof pathname,
225                          "%s/%s/%s",
226                          ctdl_file_dir,
227                          CC->room.QRdirname, filename);
228         CC->download_fp = fopen(pathname, "r");
229
230         if (CC->download_fp == NULL) {
231                 cprintf("%d cannot open %s: %s\n",
232                         ERROR + INTERNAL_ERROR, pathname, strerror(errno));
233                 return;
234         }
235
236         OpenCmdResult(filename, "application/octet-stream");
237 }
238
239 /*
240  * open an image file
241  */
242 void cmd_oimg(char *cmdbuf)
243 {
244         char filename[256];
245         char pathname[PATH_MAX];
246         char MimeTestBuf[32];
247         struct ctdluser usbuf;
248         char which_user[USERNAME_SIZE];
249         int which_floor;
250         int a;
251         int rv;
252
253         extract_token(filename, cmdbuf, 0, '|', sizeof filename);
254
255         if (IsEmptyStr(filename)) {
256                 cprintf("%d You must specify a file name.\n",
257                         ERROR + FILE_NOT_FOUND);
258                 return;
259         }
260
261         if (CC->download_fp != NULL) {
262                 cprintf("%d You already have a download file open.\n",
263                         ERROR + RESOURCE_BUSY);
264                 return;
265         }
266
267         if (!strcasecmp(filename, "_userpic_")) {
268                 extract_token(which_user, cmdbuf, 1, '|', sizeof which_user);
269                 if (CtdlGetUser(&usbuf, which_user) != 0) {
270                         cprintf("%d No such user.\n",
271                                 ERROR + NO_SUCH_USER);
272                         return;
273                 }
274                 snprintf(pathname, sizeof pathname, 
275                                  "%s/%ld",
276                                  ctdl_usrpic_dir,
277                                  usbuf.usernum);
278         } else if (!strcasecmp(filename, "_floorpic_")) {
279                 which_floor = extract_int(cmdbuf, 1);
280                 snprintf(pathname, sizeof pathname,
281                                  "%s/floor.%d",
282                                  ctdl_image_dir, which_floor);
283         } else if (!strcasecmp(filename, "_roompic_")) {
284                 assoc_file_name(pathname, sizeof pathname, &CC->room, ctdl_image_dir);
285         } else {
286                 for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
287                         filename[a] = tolower(filename[a]);
288                         if ( (filename[a] == '/') || (filename[a] == '\\') ) {
289                                 filename[a] = '_';
290                         }
291                 }
292                 snprintf(pathname, sizeof pathname,
293                                  "%s/%s",
294                                  ctdl_image_dir,
295                                  filename);
296         }
297
298         CC->download_fp = fopen(pathname, "rb");
299         if (CC->download_fp == NULL) {
300                 strcat(pathname, ".gif");
301                 CC->download_fp = fopen(pathname, "rb");
302         }
303         if (CC->download_fp == NULL) {
304                 cprintf("%d Cannot open %s: %s\n",
305                         ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
306                 return;
307         }
308         rv = fread(&MimeTestBuf[0], 1, 32, CC->download_fp);
309         if (rv == -1) {
310                 cprintf("%d Cannot access %s: %s\n",
311                         ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
312                 return;
313         }
314
315         rewind (CC->download_fp);
316         OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32));
317 }
318
319 /*
320  * open a file for uploading
321  */
322 void cmd_uopn(char *cmdbuf)
323 {
324         int a;
325
326         extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file);
327         extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
328         extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment);
329
330         if (CtdlAccessCheck(ac_logged_in)) return;
331
332         if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
333                 cprintf("%d No directory in this room.\n",
334                         ERROR + NOT_HERE);
335                 return;
336         }
337
338         if (IsEmptyStr(CC->upl_file)) {
339                 cprintf("%d You must specify a file name.\n",
340                         ERROR + FILE_NOT_FOUND);
341                 return;
342         }
343
344         if (CC->upload_fp != NULL) {
345                 cprintf("%d You already have a upload file open.\n",
346                         ERROR + RESOURCE_BUSY);
347                 return;
348         }
349
350         for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) {
351                 if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) {
352                         CC->upl_file[a] = '_';
353                 }
354         }
355         snprintf(CC->upl_path, sizeof CC->upl_path, 
356                          "%s/%s/%s",
357                          ctdl_file_dir,
358                          CC->room.QRdirname, CC->upl_file);
359         snprintf(CC->upl_filedir, sizeof CC->upl_filedir,
360                          "%s/%s/filedir", 
361                          ctdl_file_dir,
362                          CC->room.QRdirname);
363
364         CC->upload_fp = fopen(CC->upl_path, "r");
365         if (CC->upload_fp != NULL) {
366                 fclose(CC->upload_fp);
367                 CC->upload_fp = NULL;
368                 cprintf("%d '%s' already exists\n",
369                         ERROR + ALREADY_EXISTS, CC->upl_path);
370                 return;
371         }
372
373         CC->upload_fp = fopen(CC->upl_path, "wb");
374         if (CC->upload_fp == NULL) {
375                 cprintf("%d Cannot open %s: %s\n",
376                         ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
377                 return;
378         }
379         cprintf("%d Ok\n", CIT_OK);
380 }
381
382
383
384 /*
385  * open an image file for uploading
386  */
387 void cmd_uimg(char *cmdbuf)
388 {
389         int is_this_for_real;
390         char basenm[256];
391         int which_floor;
392         int a;
393
394         if (num_parms(cmdbuf) < 2) {
395                 cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
396                 return;
397         }
398
399         is_this_for_real = extract_int(cmdbuf, 0);
400         extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
401         extract_token(basenm, cmdbuf, 2, '|', sizeof basenm);
402         if (CC->upload_fp != NULL) {
403                 cprintf("%d You already have an upload file open.\n",
404                         ERROR + RESOURCE_BUSY);
405                 return;
406         }
407
408         strcpy(CC->upl_path, "");
409
410         for (a = 0; !IsEmptyStr(&basenm[a]); ++a) {
411                 basenm[a] = tolower(basenm[a]);
412                 if ( (basenm[a] == '/') || (basenm[a] == '\\') ) {
413                         basenm[a] = '_';
414                 }
415         }
416
417         if (CC->user.axlevel >= AxAideU) {
418                 snprintf(CC->upl_path, sizeof CC->upl_path, 
419                                  "%s/%s",
420                                  ctdl_image_dir,
421                                  basenm);
422         }
423
424         if (!strcasecmp(basenm, "_userpic_")) {
425                 snprintf(CC->upl_path, sizeof CC->upl_path,
426                                  "%s/%ld.gif",
427                                  ctdl_usrpic_dir,
428                                  CC->user.usernum);
429         }
430
431         if ((!strcasecmp(basenm, "_floorpic_"))
432             && (CC->user.axlevel >= AxAideU)) {
433                 which_floor = extract_int(cmdbuf, 2);
434                 snprintf(CC->upl_path, sizeof CC->upl_path,
435                                  "%s/floor.%d.gif",
436                                  ctdl_image_dir,
437                                  which_floor);
438         }
439
440         if ((!strcasecmp(basenm, "_roompic_")) && (is_room_aide())) {
441                 assoc_file_name(CC->upl_path, sizeof CC->upl_path, &CC->room, ctdl_image_dir);
442         }
443
444         if (IsEmptyStr(CC->upl_path)) {
445                 cprintf("%d Higher access required.\n",
446                         ERROR + HIGHER_ACCESS_REQUIRED);
447                 return;
448         }
449
450         if (is_this_for_real == 0) {
451                 cprintf("%d Ok to send image\n", CIT_OK);
452                 return;
453         }
454
455         CC->upload_fp = fopen(CC->upl_path, "wb");
456         if (CC->upload_fp == NULL) {
457                 cprintf("%d Cannot open %s: %s\n",
458                         ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
459                 return;
460         }
461         cprintf("%d Ok\n", CIT_OK);
462         CC->upload_type = UPL_IMAGE;
463 }
464
465
466 /*
467  * close the download file
468  */
469 void cmd_clos(char *cmdbuf)
470 {
471         char buf[256];
472
473         if (CC->download_fp == NULL) {
474                 cprintf("%d You don't have a download file open.\n",
475                         ERROR + RESOURCE_NOT_OPEN);
476                 return;
477         }
478
479         fclose(CC->download_fp);
480         CC->download_fp = NULL;
481
482         if (CC->dl_is_net == 1) {
483                 CC->dl_is_net = 0;
484                 snprintf(buf, sizeof buf, 
485                                  "%s/%s",
486                                  ctdl_netout_dir,
487                                  CC->net_node);
488                 unlink(buf);
489         }
490
491         cprintf("%d Ok\n", CIT_OK);
492 }
493
494
495 /*
496  * abort an upload
497  */
498 void abort_upl(CitContext *who)
499 {
500         if (who->upload_fp != NULL) {
501                 fclose(who->upload_fp);
502                 who->upload_fp = NULL;
503                 unlink(CC->upl_path);
504         }
505 }
506
507
508
509 /*
510  * close the upload file
511  */
512 void cmd_ucls(char *cmd)
513 {
514         FILE *fp;
515         char upload_notice[512];
516         static int seq = 0;
517
518         if (CC->upload_fp == NULL) {
519                 cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
520                 return;
521         }
522
523         fclose(CC->upload_fp);
524         CC->upload_fp = NULL;
525
526         if ((!strcasecmp(cmd, "1")) && (CC->upload_type != UPL_FILE)) {
527                 cprintf("%d Upload completed.\n", CIT_OK);
528
529                 if (CC->upload_type == UPL_NET) {
530                         char final_filename[PATH_MAX];
531                         snprintf(final_filename, sizeof final_filename,
532                                 "%s/%s.%04lx.%04x",
533                                 ctdl_netin_dir,
534                                 CC->net_node,
535                                 (long)getpid(),
536                                 ++seq
537                         );
538
539                         if (link(CC->upl_path, final_filename) == 0) {
540                                 unlink(CC->upl_path);
541                         }
542                         else {
543                                 syslog(LOG_ALERT, "Cannot link %s to %s: %s\n",
544                                         CC->upl_path, final_filename, strerror(errno)
545                                 );
546                         }
547
548                         /* FIXME ... here we need to trigger a network run */
549                 }
550
551                 CC->upload_type = UPL_FILE;
552                 return;
553         }
554
555         if (!strcasecmp(cmd, "1")) {
556                 cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path);
557                 fp = fopen(CC->upl_filedir, "a");
558                 if (fp == NULL) {
559                         fp = fopen(CC->upl_filedir, "w");
560                 }
561                 if (fp != NULL) {
562                         fprintf(fp, "%s %s %s\n", CC->upl_file,
563                                 CC->upl_mimetype,
564                                 CC->upl_comment);
565                         fclose(fp);
566                 }
567
568                 /* put together an upload notice */
569                 snprintf(upload_notice, sizeof upload_notice,
570                         "NEW UPLOAD: '%s'\n %s\n%s\n",
571                          CC->upl_file, 
572                          CC->upl_comment, 
573                          CC->upl_mimetype);
574                 quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname,
575                                 upload_notice, 0, NULL);
576         } else {
577                 abort_upl(CC);
578                 cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path);
579         }
580 }
581
582
583 /*
584  * read from the download file
585  */
586 void cmd_read(char *cmdbuf)
587 {
588         long start_pos;
589         size_t bytes;
590         char buf[SIZ];
591
592         /* The client will transmit its requested offset and byte count */
593         start_pos = extract_long(cmdbuf, 0);
594         bytes = extract_int(cmdbuf, 1);
595
596         if (CC->download_fp == NULL) {
597                 cprintf("%d You don't have a download file open.\n",
598                         ERROR + RESOURCE_NOT_OPEN);
599                 return;
600         }
601
602         /* If necessary, reduce the byte count to the size of our buffer */
603         if (bytes > sizeof(buf)) {
604                 bytes = sizeof(buf);
605         }
606
607         fseek(CC->download_fp, start_pos, 0);
608         bytes = fread(buf, 1, bytes, CC->download_fp);
609         if (bytes > 0) {
610                 /* Tell the client the actual byte count and transmit it */
611                 cprintf("%d %d\n", BINARY_FOLLOWS, (int)bytes);
612                 client_write(buf, bytes);
613         }
614         else {
615                 cprintf("%d %s\n", ERROR, strerror(errno));
616         }
617 }
618
619
620 /*
621  * write to the upload file
622  */
623 void cmd_writ(char *cmdbuf)
624 {
625         int bytes;
626         char *buf;
627         int rv;
628
629         unbuffer_output();
630
631         bytes = extract_int(cmdbuf, 0);
632
633         if (CC->upload_fp == NULL) {
634                 cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
635                 return;
636         }
637
638         if (bytes > 100000) {
639                 bytes = 100000;
640         }
641
642         cprintf("%d %d\n", SEND_BINARY, bytes);
643         buf = malloc(bytes + 1);
644         client_read(buf, bytes);
645         rv = fwrite(buf, bytes, 1, CC->upload_fp);
646         if (rv == -1) {
647                 syslog(LOG_EMERG, "Couldn't write: %s\n",
648                        strerror(errno));
649         }
650         free(buf);
651 }
652
653
654
655
656 /*
657  * cmd_ndop() - open a network spool file for downloading
658  */
659 void cmd_ndop(char *cmdbuf)
660 {
661         char pathname[256];
662         struct stat statbuf;
663
664         if (IsEmptyStr(CC->net_node)) {
665                 cprintf("%d Not authenticated as a network node.\n",
666                         ERROR + NOT_LOGGED_IN);
667                 return;
668         }
669
670         if (CC->download_fp != NULL) {
671                 cprintf("%d You already have a download file open.\n",
672                         ERROR + RESOURCE_BUSY);
673                 return;
674         }
675
676         snprintf(pathname, sizeof pathname, 
677                          "%s/%s",
678                          ctdl_netout_dir,
679                          CC->net_node);
680
681         /* first open the file in append mode in order to create a
682          * zero-length file if it doesn't already exist 
683          */
684         CC->download_fp = fopen(pathname, "a");
685         if (CC->download_fp != NULL)
686                 fclose(CC->download_fp);
687
688         /* now open it */
689         CC->download_fp = fopen(pathname, "r");
690         if (CC->download_fp == NULL) {
691                 cprintf("%d cannot open %s: %s\n",
692                         ERROR + INTERNAL_ERROR, pathname, strerror(errno));
693                 return;
694         }
695
696
697         /* set this flag so other routines know that the download file
698          * currently open is a network spool file 
699          */
700         CC->dl_is_net = 1;
701
702         stat(pathname, &statbuf);
703         CC->download_fp_total = statbuf.st_size;
704         cprintf("%d %ld\n", CIT_OK, (long)statbuf.st_size);
705 }
706
707 /*
708  * cmd_nuop() - open a network spool file for uploading
709  */
710 void cmd_nuop(char *cmdbuf)
711 {
712         static int seq = 1;
713
714         if (IsEmptyStr(CC->net_node)) {
715                 cprintf("%d Not authenticated as a network node.\n",
716                         ERROR + NOT_LOGGED_IN);
717                 return;
718         }
719
720         if (CC->upload_fp != NULL) {
721                 cprintf("%d You already have an upload file open.\n",
722                         ERROR + RESOURCE_BUSY);
723                 return;
724         }
725
726         snprintf(CC->upl_path, sizeof CC->upl_path,
727                          "%s/%s.%04lx.%04x",
728                          ctdl_nettmp_dir,
729                          CC->net_node, 
730                          (long)getpid(), 
731                          ++seq);
732
733         CC->upload_fp = fopen(CC->upl_path, "r");
734         if (CC->upload_fp != NULL) {
735                 fclose(CC->upload_fp);
736                 CC->upload_fp = NULL;
737                 cprintf("%d '%s' already exists\n",
738                         ERROR + ALREADY_EXISTS, CC->upl_path);
739                 return;
740         }
741
742         CC->upload_fp = fopen(CC->upl_path, "w");
743         if (CC->upload_fp == NULL) {
744                 cprintf("%d Cannot open %s: %s\n",
745                         ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
746                 return;
747         }
748
749         CC->upload_type = UPL_NET;
750         cprintf("%d Ok\n", CIT_OK);
751 }
752
753
754 /*****************************************************************************/
755 /*                      MODULE INITIALIZATION STUFF                          */
756 /*****************************************************************************/
757
758 CTDL_MODULE_INIT(file_ops)
759 {
760         if (!threading) {
761
762                 CtdlRegisterProtoHook(cmd_delf, "DELF", "Delete a file");
763                 CtdlRegisterProtoHook(cmd_movf, "MOVF", "Move a file");
764                 CtdlRegisterProtoHook(cmd_open, "OPEN", "Open a download file transfer");
765                 CtdlRegisterProtoHook(cmd_clos, "CLOS", "Close a download file transfer");
766                 CtdlRegisterProtoHook(cmd_uopn, "UOPN", "Open an upload file transfer");
767                 CtdlRegisterProtoHook(cmd_ucls, "UCLS", "Close an upload file transfer");
768                 CtdlRegisterProtoHook(cmd_read, "READ", "File transfer read operation");
769                 CtdlRegisterProtoHook(cmd_writ, "WRIT", "File transfer write operation");
770                 CtdlRegisterProtoHook(cmd_ndop, "NDOP", "Open a network spool file for download");
771                 CtdlRegisterProtoHook(cmd_nuop, "NUOP", "Open a network spool file for upload");
772                 CtdlRegisterProtoHook(cmd_oimg, "OIMG", "Open an image file for download");
773                 CtdlRegisterProtoHook(cmd_uimg, "UIMG", "Upload an image file");
774         }
775         /* return our Subversion id for the Log */
776         return "file_ops";
777 }