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