* Fixed a multiselect bug in the mailbox view. Ctrl-click was selecting a message...
[citadel.git] / citadel / file_ops.c
blob1424e33e18aec2b64572befe4cf276502d6b9dcf
1 /*
2 * $Id$
4 * Server functions which handle file transfers and room directories.
6 */
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <sys/stat.h>
14 #include <errno.h>
15 #include <ctype.h>
16 #include <string.h>
17 #include <sys/stat.h>
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
21 # include <time.h>
22 #else
23 # if HAVE_SYS_TIME_H
24 # include <sys/time.h>
25 # else
26 # include <time.h>
27 # endif
28 #endif
30 #include <limits.h>
31 #include <libcitadel.h>
32 #include "citadel.h"
33 #include "server.h"
34 #include "config.h"
35 #include "file_ops.h"
36 #include "sysdep_decls.h"
37 #include "user_ops.h"
38 #include "support.h"
39 #include "room_ops.h"
40 #include "msgbase.h"
41 #include "citserver.h"
42 #include "threads.h"
44 #ifndef HAVE_SNPRINTF
45 #include "snprintf.h"
46 #endif
49 * network_talking_to() -- concurrency checker
51 int network_talking_to(char *nodename, int operation) {
53 static char *nttlist = NULL;
54 char *ptr = NULL;
55 int i;
56 char buf[SIZ];
57 int retval = 0;
59 begin_critical_section(S_NTTLIST);
61 switch(operation) {
63 case NTT_ADD:
64 if (nttlist == NULL) nttlist = strdup("");
65 if (nttlist == NULL) break;
66 nttlist = (char *)realloc(nttlist,
67 (strlen(nttlist) + strlen(nodename) + 3) );
68 strcat(nttlist, "|");
69 strcat(nttlist, nodename);
70 break;
72 case NTT_REMOVE:
73 if (nttlist == NULL) break;
74 if (IsEmptyStr(nttlist)) break;
75 ptr = malloc(strlen(nttlist));
76 if (ptr == NULL) break;
77 strcpy(ptr, "");
78 for (i = 0; i < num_tokens(nttlist, '|'); ++i) {
79 extract_token(buf, nttlist, i, '|', sizeof buf);
80 if ( (!IsEmptyStr(buf))
81 && (strcasecmp(buf, nodename)) ) {
82 strcat(ptr, buf);
83 strcat(ptr, "|");
86 free(nttlist);
87 nttlist = ptr;
88 break;
90 case NTT_CHECK:
91 if (nttlist == NULL) break;
92 if (IsEmptyStr(nttlist)) break;
93 for (i = 0; i < num_tokens(nttlist, '|'); ++i) {
94 extract_token(buf, nttlist, i, '|', sizeof buf);
95 if (!strcasecmp(buf, nodename)) ++retval;
97 break;
100 if (nttlist != NULL) CtdlLogPrintf(CTDL_DEBUG, "nttlist=<%s>\n", nttlist);
101 end_critical_section(S_NTTLIST);
102 return(retval);
109 * Server command to delete a file from a room's directory
111 void cmd_delf(char *filename)
113 char pathname[64];
114 int a;
116 if (CtdlAccessCheck(ac_room_aide))
117 return;
119 if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
120 cprintf("%d No directory in this room.\n",
121 ERROR + NOT_HERE);
122 return;
125 if (IsEmptyStr(filename)) {
126 cprintf("%d You must specify a file name.\n",
127 ERROR + FILE_NOT_FOUND);
128 return;
130 for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
131 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
132 filename[a] = '_';
135 snprintf(pathname, sizeof pathname,
136 "%s/%s/%s",
137 ctdl_file_dir,
138 CC->room.QRdirname, filename);
139 a = unlink(pathname);
140 if (a == 0) {
141 cprintf("%d File '%s' deleted.\n", CIT_OK, pathname);
143 else {
144 cprintf("%d File '%s' not found.\n",
145 ERROR + FILE_NOT_FOUND, pathname);
153 * move a file from one room directory to another
155 void cmd_movf(char *cmdbuf)
157 char filename[PATH_MAX];
158 char pathname[PATH_MAX];
159 char newpath[PATH_MAX];
160 char newroom[ROOMNAMELEN];
161 char buf[PATH_MAX];
162 int a;
163 struct ctdlroom qrbuf;
165 extract_token(filename, cmdbuf, 0, '|', sizeof filename);
166 extract_token(newroom, cmdbuf, 1, '|', sizeof newroom);
168 if (CtdlAccessCheck(ac_room_aide)) return;
170 if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
171 cprintf("%d No directory in this room.\n",
172 ERROR + NOT_HERE);
173 return;
176 if (IsEmptyStr(filename)) {
177 cprintf("%d You must specify a file name.\n",
178 ERROR + FILE_NOT_FOUND);
179 return;
182 for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
183 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
184 filename[a] = '_';
187 snprintf(pathname, sizeof pathname, "./files/%s/%s",
188 CC->room.QRdirname, filename);
189 if (access(pathname, 0) != 0) {
190 cprintf("%d File '%s' not found.\n",
191 ERROR + FILE_NOT_FOUND, pathname);
192 return;
195 if (getroom(&qrbuf, newroom) != 0) {
196 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom);
197 return;
199 if ((qrbuf.QRflags & QR_DIRECTORY) == 0) {
200 cprintf("%d '%s' is not a directory room.\n",
201 ERROR + NOT_HERE, qrbuf.QRname);
202 return;
204 snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname,
205 filename);
206 if (link(pathname, newpath) != 0) {
207 cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR,
208 strerror(errno));
209 return;
211 unlink(pathname);
213 /* this is a crude method of copying the file description */
214 snprintf(buf, sizeof buf,
215 "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir",
216 CC->room.QRdirname, filename, qrbuf.QRdirname);
217 system(buf);
218 cprintf("%d File '%s' has been moved.\n", CIT_OK, filename);
223 * This code is common to all commands which open a file for downloading,
224 * regardless of whether it's a file from the directory, an image, a network
225 * spool file, a MIME attachment, etc.
226 * It examines the file and displays the OK result code and some information
227 * about the file. NOTE: this stuff is Unix dependent.
229 void OpenCmdResult(char *filename, const char *mime_type)
231 struct stat statbuf;
232 time_t modtime;
233 long filesize;
235 fstat(fileno(CC->download_fp), &statbuf);
236 filesize = (long) statbuf.st_size;
237 modtime = (time_t) statbuf.st_mtime;
239 cprintf("%d %ld|%ld|%s|%s\n",
240 CIT_OK, filesize, (long)modtime, filename, mime_type);
245 * open a file for downloading
247 void cmd_open(char *cmdbuf)
249 char filename[256];
250 char pathname[PATH_MAX];
251 int a;
253 extract_token(filename, cmdbuf, 0, '|', sizeof filename);
255 if (CtdlAccessCheck(ac_logged_in)) return;
257 if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
258 cprintf("%d No directory in this room.\n",
259 ERROR + NOT_HERE);
260 return;
263 if (IsEmptyStr(filename)) {
264 cprintf("%d You must specify a file name.\n",
265 ERROR + FILE_NOT_FOUND);
266 return;
269 if (CC->download_fp != NULL) {
270 cprintf("%d You already have a download file open.\n",
271 ERROR + RESOURCE_BUSY);
272 return;
275 for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
276 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
277 filename[a] = '_';
281 snprintf(pathname, sizeof pathname,
282 "%s/%s/%s",
283 ctdl_file_dir,
284 CC->room.QRdirname, filename);
285 CC->download_fp = fopen(pathname, "r");
287 if (CC->download_fp == NULL) {
288 cprintf("%d cannot open %s: %s\n",
289 ERROR + INTERNAL_ERROR, pathname, strerror(errno));
290 return;
293 OpenCmdResult(filename, "application/octet-stream");
297 * open an image file
299 void cmd_oimg(char *cmdbuf)
301 char filename[256];
302 char pathname[PATH_MAX];
303 char MimeTestBuf[32];
304 struct ctdluser usbuf;
305 char which_user[USERNAME_SIZE];
306 int which_floor;
307 int a;
309 extract_token(filename, cmdbuf, 0, '|', sizeof filename);
311 if (IsEmptyStr(filename)) {
312 cprintf("%d You must specify a file name.\n",
313 ERROR + FILE_NOT_FOUND);
314 return;
317 if (CC->download_fp != NULL) {
318 cprintf("%d You already have a download file open.\n",
319 ERROR + RESOURCE_BUSY);
320 return;
323 if (!strcasecmp(filename, "_userpic_")) {
324 extract_token(which_user, cmdbuf, 1, '|', sizeof which_user);
325 if (getuser(&usbuf, which_user) != 0) {
326 cprintf("%d No such user.\n",
327 ERROR + NO_SUCH_USER);
328 return;
330 snprintf(pathname, sizeof pathname,
331 "%s/%ld",
332 ctdl_usrpic_dir,
333 usbuf.usernum);
334 } else if (!strcasecmp(filename, "_floorpic_")) {
335 which_floor = extract_int(cmdbuf, 1);
336 snprintf(pathname, sizeof pathname,
337 "%s/floor.%d",
338 ctdl_image_dir, which_floor);
339 } else if (!strcasecmp(filename, "_roompic_")) {
340 assoc_file_name(pathname, sizeof pathname, &CC->room, ctdl_image_dir);
341 } else {
342 for (a = 0; !IsEmptyStr(&filename[a]); ++a) {
343 filename[a] = tolower(filename[a]);
344 if ( (filename[a] == '/') || (filename[a] == '\\') ) {
345 filename[a] = '_';
348 snprintf(pathname, sizeof pathname,
349 "%s/%s",
350 ctdl_image_dir,
351 filename);
354 CC->download_fp = fopen(pathname, "rb");
355 if (CC->download_fp == NULL) {
356 strcat(pathname, ".gif");
357 CC->download_fp = fopen(pathname, "rb");
359 if (CC->download_fp == NULL) {
360 cprintf("%d Cannot open %s: %s\n",
361 ERROR + FILE_NOT_FOUND, pathname, strerror(errno));
362 return;
364 fread(&MimeTestBuf[0], 1, 32, CC->download_fp);
365 rewind (CC->download_fp);
366 OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32));
370 * open a file for uploading
372 void cmd_uopn(char *cmdbuf)
374 int a;
376 extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file);
377 extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
378 extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment);
380 if (CtdlAccessCheck(ac_logged_in)) return;
382 if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
383 cprintf("%d No directory in this room.\n",
384 ERROR + NOT_HERE);
385 return;
388 if (IsEmptyStr(CC->upl_file)) {
389 cprintf("%d You must specify a file name.\n",
390 ERROR + FILE_NOT_FOUND);
391 return;
394 if (CC->upload_fp != NULL) {
395 cprintf("%d You already have a upload file open.\n",
396 ERROR + RESOURCE_BUSY);
397 return;
400 for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) {
401 if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) {
402 CC->upl_file[a] = '_';
405 snprintf(CC->upl_path, sizeof CC->upl_path,
406 "%s/%s/%s",
407 ctdl_file_dir,
408 CC->room.QRdirname, CC->upl_file);
409 snprintf(CC->upl_filedir, sizeof CC->upl_filedir,
410 "%s/%s/filedir",
411 ctdl_file_dir,
412 CC->room.QRdirname);
414 CC->upload_fp = fopen(CC->upl_path, "r");
415 if (CC->upload_fp != NULL) {
416 fclose(CC->upload_fp);
417 CC->upload_fp = NULL;
418 cprintf("%d '%s' already exists\n",
419 ERROR + ALREADY_EXISTS, CC->upl_path);
420 return;
423 CC->upload_fp = fopen(CC->upl_path, "wb");
424 if (CC->upload_fp == NULL) {
425 cprintf("%d Cannot open %s: %s\n",
426 ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
427 return;
429 cprintf("%d Ok\n", CIT_OK);
435 * open an image file for uploading
437 void cmd_uimg(char *cmdbuf)
439 int is_this_for_real;
440 char basenm[256];
441 int which_floor;
442 int a;
444 if (num_parms(cmdbuf) < 2) {
445 cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
446 return;
449 is_this_for_real = extract_int(cmdbuf, 0);
450 extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype);
451 extract_token(basenm, cmdbuf, 2, '|', sizeof basenm);
452 if (CC->upload_fp != NULL) {
453 cprintf("%d You already have an upload file open.\n",
454 ERROR + RESOURCE_BUSY);
455 return;
458 strcpy(CC->upl_path, "");
460 for (a = 0; !IsEmptyStr(&basenm[a]); ++a) {
461 basenm[a] = tolower(basenm[a]);
462 if ( (basenm[a] == '/') || (basenm[a] == '\\') ) {
463 basenm[a] = '_';
467 if (CC->user.axlevel >= 6) {
468 snprintf(CC->upl_path, sizeof CC->upl_path,
469 "%s/%s",
470 ctdl_image_dir,
471 basenm);
474 if (!strcasecmp(basenm, "_userpic_")) {
475 snprintf(CC->upl_path, sizeof CC->upl_path,
476 "%s/%ld.gif",
477 ctdl_usrpic_dir,
478 CC->user.usernum);
481 if ((!strcasecmp(basenm, "_floorpic_"))
482 && (CC->user.axlevel >= 6)) {
483 which_floor = extract_int(cmdbuf, 2);
484 snprintf(CC->upl_path, sizeof CC->upl_path,
485 "%s/floor.%d.gif",
486 ctdl_image_dir,
487 which_floor);
490 if ((!strcasecmp(basenm, "_roompic_")) && (is_room_aide())) {
491 assoc_file_name(CC->upl_path, sizeof CC->upl_path, &CC->room, ctdl_image_dir);
494 if (IsEmptyStr(CC->upl_path)) {
495 cprintf("%d Higher access required.\n",
496 ERROR + HIGHER_ACCESS_REQUIRED);
497 return;
500 if (is_this_for_real == 0) {
501 cprintf("%d Ok to send image\n", CIT_OK);
502 return;
505 CC->upload_fp = fopen(CC->upl_path, "wb");
506 if (CC->upload_fp == NULL) {
507 cprintf("%d Cannot open %s: %s\n",
508 ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
509 return;
511 cprintf("%d Ok\n", CIT_OK);
512 CC->upload_type = UPL_IMAGE;
517 * close the download file
519 void cmd_clos(void)
521 char buf[256];
523 if (CC->download_fp == NULL) {
524 cprintf("%d You don't have a download file open.\n",
525 ERROR + RESOURCE_NOT_OPEN);
526 return;
529 fclose(CC->download_fp);
530 CC->download_fp = NULL;
532 if (CC->dl_is_net == 1) {
533 CC->dl_is_net = 0;
534 snprintf(buf, sizeof buf,
535 "%s/%s",
536 ctdl_netout_dir,
537 CC->net_node);
538 unlink(buf);
541 cprintf("%d Ok\n", CIT_OK);
546 * abort an upload
548 void abort_upl(struct CitContext *who)
550 if (who->upload_fp != NULL) {
551 fclose(who->upload_fp);
552 who->upload_fp = NULL;
553 unlink(CC->upl_path);
560 * close the upload file
562 void cmd_ucls(char *cmd)
564 FILE *fp;
565 char upload_notice[512];
567 if (CC->upload_fp == NULL) {
568 cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
569 return;
572 fclose(CC->upload_fp);
573 CC->upload_fp = NULL;
575 if ((!strcasecmp(cmd, "1")) && (CC->upload_type != UPL_FILE)) {
576 CC->upload_type = UPL_FILE;
577 cprintf("%d Upload completed.\n", CIT_OK);
579 /* FIXME ... here we need to trigger a network run */
581 return;
584 if (!strcasecmp(cmd, "1")) {
585 cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path);
586 fp = fopen(CC->upl_filedir, "a");
587 if (fp == NULL) {
588 fp = fopen(CC->upl_filedir, "w");
590 if (fp != NULL) {
591 fprintf(fp, "%s %s %s\n", CC->upl_file,
592 CC->upl_mimetype,
593 CC->upl_comment);
594 fclose(fp);
597 /* put together an upload notice */
598 snprintf(upload_notice, sizeof upload_notice,
599 "NEW UPLOAD: '%s'\n %s\n%s\n",
600 CC->upl_file,
601 CC->upl_comment,
602 CC->upl_mimetype);
603 quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname,
604 upload_notice, 0, NULL);
605 } else {
606 abort_upl(CC);
607 cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path);
614 * read from the download file
616 void cmd_read(char *cmdbuf)
618 long start_pos;
619 size_t bytes;
620 size_t actual_bytes;
621 char *buf = NULL;
623 start_pos = extract_long(cmdbuf, 0);
624 bytes = extract_int(cmdbuf, 1);
626 if (CC->download_fp == NULL) {
627 cprintf("%d You don't have a download file open.\n",
628 ERROR + RESOURCE_NOT_OPEN);
629 return;
632 if (bytes > 100000) bytes = 100000;
633 buf = malloc(bytes + 1);
635 fseek(CC->download_fp, start_pos, 0);
636 actual_bytes = fread(buf, 1, bytes, CC->download_fp);
637 cprintf("%d %d\n", BINARY_FOLLOWS, (int)actual_bytes);
638 client_write(buf, actual_bytes);
639 free(buf);
645 * write to the upload file
647 void cmd_writ(char *cmdbuf)
649 int bytes;
650 char *buf;
652 unbuffer_output();
654 bytes = extract_int(cmdbuf, 0);
656 if (CC->upload_fp == NULL) {
657 cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN);
658 return;
661 if (bytes > 100000) {
662 cprintf("%d You may not write more than 100000 bytes.\n",
663 ERROR + TOO_BIG);
664 return;
667 cprintf("%d %d\n", SEND_BINARY, bytes);
668 buf = malloc(bytes + 1);
669 client_read(buf, bytes);
670 fwrite(buf, bytes, 1, CC->upload_fp);
671 free(buf);
678 * cmd_ndop() - open a network spool file for downloading
680 void cmd_ndop(char *cmdbuf)
682 char pathname[256];
683 struct stat statbuf;
685 if (IsEmptyStr(CC->net_node)) {
686 cprintf("%d Not authenticated as a network node.\n",
687 ERROR + NOT_LOGGED_IN);
688 return;
691 if (CC->download_fp != NULL) {
692 cprintf("%d You already have a download file open.\n",
693 ERROR + RESOURCE_BUSY);
694 return;
697 snprintf(pathname, sizeof pathname,
698 "%s/%s",
699 ctdl_netout_dir,
700 CC->net_node);
702 /* first open the file in append mode in order to create a
703 * zero-length file if it doesn't already exist
705 CC->download_fp = fopen(pathname, "a");
706 if (CC->download_fp != NULL)
707 fclose(CC->download_fp);
709 /* now open it */
710 CC->download_fp = fopen(pathname, "r");
711 if (CC->download_fp == NULL) {
712 cprintf("%d cannot open %s: %s\n",
713 ERROR + INTERNAL_ERROR, pathname, strerror(errno));
714 return;
718 /* set this flag so other routines know that the download file
719 * currently open is a network spool file
721 CC->dl_is_net = 1;
723 stat(pathname, &statbuf);
724 cprintf("%d %ld\n", CIT_OK, (long)statbuf.st_size);
728 * cmd_nuop() - open a network spool file for uploading
730 void cmd_nuop(char *cmdbuf)
732 static int seq = 1;
734 if (IsEmptyStr(CC->net_node)) {
735 cprintf("%d Not authenticated as a network node.\n",
736 ERROR + NOT_LOGGED_IN);
737 return;
740 if (CC->upload_fp != NULL) {
741 cprintf("%d You already have an upload file open.\n",
742 ERROR + RESOURCE_BUSY);
743 return;
746 snprintf(CC->upl_path, sizeof CC->upl_path,
747 "%s/%s.%04lx.%04x",
748 ctdl_netin_dir,
749 CC->net_node,
750 (long)getpid(),
751 ++seq);
753 CC->upload_fp = fopen(CC->upl_path, "r");
754 if (CC->upload_fp != NULL) {
755 fclose(CC->upload_fp);
756 CC->upload_fp = NULL;
757 cprintf("%d '%s' already exists\n",
758 ERROR + ALREADY_EXISTS, CC->upl_path);
759 return;
762 CC->upload_fp = fopen(CC->upl_path, "w");
763 if (CC->upload_fp == NULL) {
764 cprintf("%d Cannot open %s: %s\n",
765 ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno));
766 return;
769 CC->upload_type = UPL_NET;
770 cprintf("%d Ok\n", CIT_OK);