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