22 #include "sysdep_decls.h"
23 #include "citserver.h"
28 #include "dynloader.h"
30 #include "mime_parser.h"
39 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
41 extern struct config config;
45 * This function is self explanatory.
46 * (What can I say, I'm in a weird mood today...)
48 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
52 for (i = 0; i < strlen(name); ++i)
55 if (isspace(name[i - 1])) {
56 strcpy(&name[i - 1], &name[i]);
59 while (isspace(name[i + 1])) {
60 strcpy(&name[i + 1], &name[i + 2]);
67 * Aliasing for network mail.
68 * (Error messages have been commented out, because this is a server.)
71 { /* process alias and routing info for mail */
74 char aaa[300], bbb[300];
76 lprintf(9, "alias() called for <%s>\n", name);
78 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
80 fp = fopen("network/mail.aliases", "r");
82 fp = fopen("/dev/null", "r");
87 while (fgets(aaa, sizeof aaa, fp) != NULL) {
88 while (isspace(name[0]))
89 strcpy(name, &name[1]);
90 aaa[strlen(aaa) - 1] = 0;
92 for (a = 0; a < strlen(aaa); ++a) {
94 strcpy(bbb, &aaa[a + 1]);
98 if (!strcasecmp(name, aaa))
102 lprintf(7, "Mail is being forwarded to %s\n", name);
104 /* determine local or remote type, see citadel.h */
105 for (a = 0; a < strlen(name); ++a)
107 return (MES_INTERNET);
108 for (a = 0; a < strlen(name); ++a)
110 for (b = a; b < strlen(name); ++b)
112 return (MES_INTERNET);
114 for (a = 0; a < strlen(name); ++a)
118 lprintf(7, "Too many @'s in address\n");
122 for (a = 0; a < strlen(name); ++a)
124 strcpy(bbb, &name[a + 1]);
126 strcpy(bbb, &bbb[1]);
127 fp = fopen("network/mail.sysinfo", "r");
131 a = getstring(fp, aaa);
132 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
133 a = getstring(fp, aaa);
134 if (!strncmp(aaa, "use ", 4)) {
135 strcpy(bbb, &aaa[4]);
140 if (!strncmp(aaa, "uum", 3)) {
142 for (a = 0; a < strlen(bbb); ++a) {
148 while (bbb[strlen(bbb) - 1] == '_')
149 bbb[strlen(bbb) - 1] = 0;
150 sprintf(name, &aaa[4], bbb);
151 return (MES_INTERNET);
153 if (!strncmp(aaa, "bin", 3)) {
156 while (aaa[strlen(aaa) - 1] != '@')
157 aaa[strlen(aaa) - 1] = 0;
158 aaa[strlen(aaa) - 1] = 0;
159 while (aaa[strlen(aaa) - 1] == ' ')
160 aaa[strlen(aaa) - 1] = 0;
161 while (bbb[0] != '@')
162 strcpy(bbb, &bbb[1]);
163 strcpy(bbb, &bbb[1]);
164 while (bbb[0] == ' ')
165 strcpy(bbb, &bbb[1]);
166 sprintf(name, "%s @%s", aaa, bbb);
179 fp = fopen("citadel.control", "r");
180 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
186 void simple_listing(long msgnum) {
187 cprintf("%ld\n", msgnum);
192 * API function to perform an operation for each qualifying message in the
195 void CtdlForEachMessage(int mode, long ref,
197 void (*CallBack) (long msgnum) ) {
201 struct cdbdata *cdbfr;
202 long *msglist = NULL;
205 struct SuppMsgInfo smi;
207 /* Learn about the user and room in question */
209 getuser(&CC->usersupp, CC->curr_user);
210 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
212 /* Load the message list */
213 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
215 msglist = mallok(cdbfr->len);
216 memcpy(msglist, cdbfr->ptr, cdbfr->len);
217 num_msgs = cdbfr->len / sizeof(long);
220 return; /* No messages at all? No further action. */
224 /* If the caller is looking for a specific MIME type, then filter
225 * out all messages which are not of the type requested.
228 if (content_type != NULL)
229 if (strlen(content_type) > 0) for (a = 0; a < num_msgs; ++a) {
230 GetSuppMsgInfo(&smi, msglist[a]);
231 if (strcasecmp(smi.smi_content_type, content_type)) {
238 * Now iterate through the message list, according to the
239 * criteria supplied by the caller.
241 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
242 thismsg = msglist[a];
247 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
248 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
249 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
250 && (CC->usersupp.flags & US_LASTOLD))
251 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
252 || ((mode == MSGS_FIRST) && (a < ref))
253 || ((mode == MSGS_GT) && (thismsg > ref))
260 phree(msglist); /* Clean up */
266 * cmd_msgs() - get list of message #'s in this room
267 * implements the MSGS server command using CtdlForEachMessage()
269 void cmd_msgs(char *cmdbuf)
275 extract(which, cmdbuf, 0);
276 cm_ref = extract_int(cmdbuf, 1);
280 if (!strncasecmp(which, "OLD", 3))
282 else if (!strncasecmp(which, "NEW", 3))
284 else if (!strncasecmp(which, "FIRST", 5))
286 else if (!strncasecmp(which, "LAST", 4))
288 else if (!strncasecmp(which, "GT", 2))
291 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
292 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
296 cprintf("%d Message list...\n", LISTING_FOLLOWS);
297 CtdlForEachMessage(mode, cm_ref, NULL, simple_listing);
305 * help_subst() - support routine for help file viewer
307 void help_subst(char *strbuf, char *source, char *dest)
312 while (p = pattern2(strbuf, source), (p >= 0)) {
313 strcpy(workbuf, &strbuf[p + strlen(source)]);
314 strcpy(&strbuf[p], dest);
315 strcat(strbuf, workbuf);
320 void do_help_subst(char *buffer)
324 help_subst(buffer, "^nodename", config.c_nodename);
325 help_subst(buffer, "^humannode", config.c_humannode);
326 help_subst(buffer, "^fqdn", config.c_fqdn);
327 help_subst(buffer, "^username", CC->usersupp.fullname);
328 sprintf(buf2, "%ld", CC->usersupp.usernum);
329 help_subst(buffer, "^usernum", buf2);
330 help_subst(buffer, "^sysadm", config.c_sysadm);
331 help_subst(buffer, "^variantname", CITADEL);
332 sprintf(buf2, "%d", config.c_maxsessions);
333 help_subst(buffer, "^maxsessions", buf2);
339 * memfmout() - Citadel text formatter and paginator.
340 * Although the original purpose of this routine was to format
341 * text to the reader's screen width, all we're really using it
342 * for here is to format text out to 80 columns before sending it
343 * to the client. The client software may reformat it again.
345 void memfmout(int width, char *mptr, char subst)
346 /* screen width to use */
347 /* where are we going to get our text from? */
348 /* nonzero if we should use hypertext mode */
360 c = 1; /* c is the current pos */
363 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
365 buffer[strlen(buffer) + 1] = 0;
366 buffer[strlen(buffer)] = ch;
369 if (buffer[0] == '^')
370 do_help_subst(buffer);
372 buffer[strlen(buffer) + 1] = 0;
374 strcpy(buffer, &buffer[1]);
383 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
385 if (((old == 13) || (old == 10)) && (isspace(real))) {
393 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
394 cprintf("\n%s", aaa);
403 if ((strlen(aaa) + c) > (width - 5)) {
413 if ((ch == 13) || (ch == 10)) {
414 cprintf("%s\n", aaa);
421 FMTEND: cprintf("%s\n", aaa);
427 * Callback function for mime parser that simply lists the part
429 void list_this_part(char *name, char *filename, char *partnum, char *disp,
430 void *content, char *cbtype, size_t length)
433 cprintf("part=%s|%s|%s|%s|%s|%d\n",
434 name, filename, partnum, disp, cbtype, length);
439 * Callback function for mime parser that wants to display text
441 void fixed_output(char *name, char *filename, char *partnum, char *disp,
442 void *content, char *cbtype, size_t length)
445 if (!strcasecmp(cbtype, "text/plain")) {
446 client_write(content, length);
448 cprintf("Part %s: %s (%s) (%d bytes)\n",
449 partnum, filename, cbtype, length);
455 * Callback function for mime parser that opens a section for downloading
457 void mime_download(char *name, char *filename, char *partnum, char *disp,
458 void *content, char *cbtype, size_t length)
461 /* Silently go away if there's already a download open... */
462 if (CC->download_fp != NULL)
465 /* ...or if this is not the desired section */
466 if (strcasecmp(desired_section, partnum))
469 CC->download_fp = tmpfile();
470 if (CC->download_fp == NULL)
473 fwrite(content, length, 1, CC->download_fp);
474 fflush(CC->download_fp);
475 rewind(CC->download_fp);
477 OpenCmdResult(filename, cbtype);
483 * Load a message from disk into memory.
484 * (This will replace a big piece of output_message() eventually)
486 * NOTE: Caller is responsible for _recursively_ freeing the returned
487 * CtdlMessage data structure!
489 struct CtdlMessage *CtdlFetchMessage(long msgnum) {
490 struct cdbdata *dmsgtext;
491 struct CtdlMessage *ret = NULL;
494 CIT_UBYTE field_header;
498 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
499 if (dmsgtext == NULL) {
500 lprintf(9, "CtdlMessage(%ld) failed.\n");
504 mptr = dmsgtext->ptr;
506 /* Parse the three bytes that begin EVERY message on disk.
507 * The first is always 0xFF, the universal start-of-message byte.
508 * The second is the anonymous/public type byte.
509 * The third is the format type byte (vari, fixed, or MIME).
513 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
518 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
519 memset(ret, 0, sizeof(struct CtdlMessage));
521 ret->cm_anon_type = *mptr++; /* Anon type byte */
522 ret->cm_format_type = *mptr++; /* Format type byte */
525 * The rest is zero or more arbitrary fields. Load them in.
526 * We're done when we encounter either a zero-length field or
527 * have just processed the 'M' (message text) field.
530 field_length = strlen(mptr);
531 if (field_length == 0) break;
532 field_header = *mptr++;
533 ret->cm_fields[field_header] = mallok(field_length);
534 strcpy(ret->cm_fields[field_header], mptr);
536 while (*mptr++ != 0) ; /* advance to next field */
538 } while ( (field_length > 0) && (field_header != 'M') );
548 * Get a message off disk. (return value is the message's timestamp)
551 time_t output_message(char *msgid, int mode, int headers_only)
556 CIT_UBYTE format_type, anon_flag;
561 struct cdbdata *dmsgtext;
564 /* buffers needed for RFC822 translation */
573 msg_num = atol(msgid);
576 if ((!(CC->logged_in)) && (!(CC->internal_pgm)) && (mode != MT_DATE)) {
577 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
581 /* FIX ... small security issue
582 * We need to check to make sure the requested message is actually
583 * in the current room, and set msg_ok to 1 only if it is. This
584 * functionality is currently missing because I'm in a hurry to replace
585 * broken production code with nonbroken pre-beta code. :( -- ajc
591 cprintf("%d Message %ld is not in this room.\n",
595 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msg_num, sizeof(long));
597 if (dmsgtext == NULL) {
599 cprintf("%d Can't find message %ld\n",
600 (ERROR + INTERNAL_ERROR), msg_num);
603 msg_len = (long) dmsgtext->len;
604 mptr = dmsgtext->ptr;
606 /* this loop spews out the whole message if we're doing raw format */
607 if (mode == MT_RAW) {
608 cprintf("%d %ld\n", BINARY_FOLLOWS, msg_len);
609 client_write(dmsgtext->ptr, (int) msg_len);
613 /* Otherwise, we'll start parsing it field by field... */
616 cprintf("%d Illegal message format on disk\n",
617 ERROR + INTERNAL_ERROR);
622 format_type = *mptr++;
624 /* Are we downloading a MIME component? */
625 if (mode == MT_DOWNLOAD) {
626 if (format_type != 4) {
627 cprintf("%d This is not a MIME message.\n",
629 } else if (CC->download_fp != NULL) {
630 cprintf("%d You already have a download open.\n",
633 /* Skip to the message body */
634 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
637 buf[strlen(buf) + 1] = 0;
639 buf[strlen(buf)] = rch;
643 mime_parser(mptr, NULL, *mime_download);
644 /* If there's no file open by this time, the requested
645 * section wasn't found, so print an error
647 if (CC->download_fp == NULL) {
648 cprintf("%d Section %s not found.\n",
649 ERROR + FILE_NOT_FOUND,
656 /* Are we just looking for the message date? */
658 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
661 buf[strlen(buf) + 1] = 0;
663 buf[strlen(buf)] = rch;
672 /* now for the user-mode message reading loops */
673 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
675 if (mode == MT_CITADEL)
676 cprintf("type=%d\n", format_type);
678 if ((anon_flag == MES_ANON) && (mode == MT_CITADEL)) {
679 cprintf("nhdr=yes\n");
681 /* begin header processing loop for Citadel message format */
683 if ((mode == MT_CITADEL) || (mode == MT_MIME))
684 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
687 buf[strlen(buf) + 1] = 0;
689 buf[strlen(buf)] = rch;
693 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
694 if (anon_flag == MES_ANON)
695 cprintf("from=****");
696 else if (anon_flag == MES_AN2)
697 cprintf("from=anonymous");
699 cprintf("from=%s", buf);
700 if ((is_room_aide()) && ((anon_flag == MES_ANON)
701 || (anon_flag == MES_AN2)))
702 cprintf(" [%s]", buf);
704 } else if (ch == 'P')
705 cprintf("path=%s\n", buf);
707 cprintf("subj=%s\n", buf);
709 cprintf("msgn=%s\n", buf);
711 cprintf("hnod=%s\n", buf);
713 cprintf("room=%s\n", buf);
715 cprintf("node=%s\n", buf);
717 cprintf("rcpt=%s\n", buf);
719 cprintf("time=%s\n", buf);
720 /* else cprintf("fld%c=%s\n",ch,buf); */
722 /* begin header processing loop for RFC822 transfer format */
726 strcpy(snode, NODENAME);
727 strcpy(lnode, HUMANNODE);
728 if (mode == MT_RFC822)
729 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
732 buf[strlen(buf) + 1] = 0;
734 buf[strlen(buf)] = rch;
739 else if (ch == 'P') {
740 cprintf("Path: %s\n", buf);
741 for (a = 0; a < strlen(buf); ++a) {
743 strcpy(buf, &buf[a + 1]);
748 } else if (ch == 'U')
749 cprintf("Subject: %s\n", buf);
755 cprintf("X-Citadel-Room: %s\n", buf);
759 cprintf("To: %s\n", buf);
760 else if (ch == 'T') {
762 cprintf("Date: %s", asctime(localtime(&xtime)));
765 if (mode == MT_RFC822) {
766 if (!strcasecmp(snode, NODENAME)) {
769 cprintf("Message-ID: <%s@%s>\n", mid, snode);
770 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
771 cprintf("From: %s@%s (%s)\n",
772 suser, snode, luser);
773 cprintf("Organization: %s\n", lnode);
775 /* end header processing loop ... at this point, we're in the text */
778 cprintf("text\n*** ?Message truncated\n000\n");
782 /* do some sort of MIME output */
783 if (format_type == 4) {
784 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
785 mime_parser(mptr, NULL, *list_this_part);
787 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
794 /* give 'em a length */
796 while (ch = *mptr++, ch > 0) {
799 cprintf("mlen=%ld\n", msg_len);
804 /* signify start of msg text */
805 if (mode == MT_CITADEL)
807 if ((mode == MT_RFC822) && (format_type != 4))
810 /* If the format type on disk is 1 (fixed-format), then we want
811 * everything to be output completely literally ... regardless of
812 * what message transfer format is in use.
814 if (format_type == 1) {
816 while (ch = *mptr++, ch > 0) {
819 if ((ch == 10) || (strlen(buf) > 250)) {
820 cprintf("%s\n", buf);
823 buf[strlen(buf) + 1] = 0;
824 buf[strlen(buf)] = ch;
828 cprintf("%s\n", buf);
830 /* If the message on disk is format 0 (Citadel vari-format), we
831 * output using the formatter at 80 columns. This is the final output
832 * form if the transfer format is RFC822, but if the transfer format
833 * is Citadel proprietary, it'll still work, because the indentation
834 * for new paragraphs is correct and the client will reformat the
835 * message to the reader's screen width.
837 if (format_type == 0) {
838 memfmout(80, mptr, 0);
840 /* If the message on disk is format 4 (MIME), we've gotta hand it
841 * off to the MIME parser. The client has already been told that
842 * this message is format 1 (fixed format), so the callback function
843 * we use will display those parts as-is.
845 if (format_type == 4) {
846 mime_parser(mptr, NULL, *fixed_output);
856 * display a message (mode 0 - Citadel proprietary)
858 void cmd_msg0(char *cmdbuf)
861 int headers_only = 0;
863 extract(msgid, cmdbuf, 0);
864 headers_only = extract_int(cmdbuf, 1);
866 output_message(msgid, MT_CITADEL, headers_only);
872 * display a message (mode 2 - RFC822)
874 void cmd_msg2(char *cmdbuf)
877 int headers_only = 0;
879 extract(msgid, cmdbuf, 0);
880 headers_only = extract_int(cmdbuf, 1);
882 output_message(msgid, MT_RFC822, headers_only);
886 * display a message (mode 3 - IGnet raw format - internal programs only)
888 void cmd_msg3(char *cmdbuf)
891 int headers_only = 0;
893 if (CC->internal_pgm == 0) {
894 cprintf("%d This command is for internal programs only.\n",
898 extract(msgid, cmdbuf, 0);
899 headers_only = extract_int(cmdbuf, 1);
901 output_message(msgid, MT_RAW, headers_only);
905 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
907 void cmd_msg4(char *cmdbuf)
911 extract(msgid, cmdbuf, 0);
913 output_message(msgid, MT_MIME, 0);
919 * Open a component of a MIME message as a download file
921 void cmd_opna(char *cmdbuf)
925 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
927 extract(msgid, cmdbuf, 0);
928 extract(desired_section, cmdbuf, 1);
930 output_message(msgid, MT_DOWNLOAD, 0);
936 * Message base operation to send a message to the master file
937 * (returns new message number)
939 long send_message(char *message_in_memory, /* pointer to buffer */
940 size_t message_length, /* length of buffer */
942 { /* 1 to generate an I field */
945 char *actual_message;
946 size_t actual_length;
950 /* Get a new message number */
951 newmsgid = get_new_message_number();
954 sprintf(msgidbuf, "I%ld", newmsgid);
955 actual_length = message_length + strlen(msgidbuf) + 1;
956 actual_message = mallok(actual_length);
957 memcpy(actual_message, message_in_memory, 3);
958 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
959 memcpy(&actual_message[strlen(msgidbuf) + 4],
960 &message_in_memory[3], message_length - 3);
962 actual_message = message_in_memory;
963 actual_length = message_length;
966 /* Write our little bundle of joy into the message base */
967 begin_critical_section(S_MSGMAIN);
968 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
969 actual_message, actual_length) < 0) {
970 lprintf(2, "Can't store message\n");
975 end_critical_section(S_MSGMAIN);
978 phree(actual_message);
980 /* Finally, return the pointers */
987 * this is a simple file copy routine.
989 void copy_file(char *from, char *to)
994 ffp = fopen(from, "r");
997 tfp = fopen(to, "w");
1002 while (a = getc(ffp), a >= 0) {
1013 * message base operation to save a message and install its pointers
1015 void save_message(char *mtmp, /* file containing proper message */
1016 char *rec, /* Recipient (if mail) */
1017 char *force, /* if non-zero length, force a room */
1018 int mailtype, /* local or remote type, see citadel.h */
1020 { /* set to 1 to generate an 'I' field */
1022 char hold_rm[ROOMNAMELEN];
1023 char actual_rm[ROOMNAMELEN];
1024 char force_room[ROOMNAMELEN];
1025 char content_type[256]; /* We have to learn this */
1027 char recipient[256];
1029 char *message_in_memory;
1031 struct stat statbuf;
1034 struct usersupp userbuf;
1036 static int seqnum = 0;
1037 int successful_local_recipients = 0;
1038 struct quickroom qtemp;
1039 struct SuppMsgInfo smi;
1041 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1042 mtmp, rec, force, mailtype, generate_id);
1044 strcpy(force_room, force);
1046 /* Strip non-printable characters out of the recipient name */
1047 strcpy(recipient, rec);
1048 for (a = 0; a < strlen(recipient); ++a)
1049 if (!isprint(recipient[a]))
1050 strcpy(&recipient[a], &recipient[a + 1]);
1052 /* Measure the message */
1053 stat(mtmp, &statbuf);
1054 templen = statbuf.st_size;
1056 /* Now read it into memory */
1057 message_in_memory = (char *) mallok(templen);
1058 if (message_in_memory == NULL) {
1059 lprintf(2, "Can't allocate memory to save message!\n");
1062 fp = fopen(mtmp, "rb");
1063 fread(message_in_memory, templen, 1, fp);
1066 /* Learn about what's inside, because it's what's inside that counts */
1067 mptr = message_in_memory;
1068 ++mptr; /* advance past 0xFF header */
1069 ++mptr; /* advance past anon flag */
1073 strcpy(content_type, "text/x-citadel-variformat");
1076 strcpy(content_type, "text/plain");
1079 strcpy(content_type, "text/plain");
1080 /* advance past header fields */
1081 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1088 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1089 safestrncpy(content_type, mptr,
1090 sizeof(content_type));
1091 lprintf(9, "%s\n", content_type);
1092 strcpy(content_type, &content_type[14]);
1093 for (a=0; a<strlen(content_type); ++a)
1094 if ( (content_type[a]==';')
1095 || (content_type[a]==' ')
1096 || (content_type[a]==13)
1097 || (content_type[a]==10) )
1098 content_type[a] = 0;
1104 lprintf(9, "Content type is <%s>\n", content_type);
1106 /* Save it to disk */
1107 newmsgid = send_message(message_in_memory, templen, generate_id);
1108 phree(message_in_memory);
1112 strcpy(actual_rm, CC->quickroom.QRname);
1113 strcpy(hold_rm, "");
1115 /* If this is being done by the networker delivering a private
1116 * message, we want to BYPASS saving the sender's copy (because there
1117 * is no local sender; it would otherwise go to the Trashcan).
1119 if ( (!CC->internal_pgm) || (strlen(recipient)==0) ) {
1120 /* If the user is a twit, move to the twit room for posting */
1122 if (CC->usersupp.axlevel == 2) {
1123 strcpy(hold_rm, actual_rm);
1124 strcpy(actual_rm, config.c_twitroom);
1126 /* ...or if this message is destined for Aide> then go there. */
1127 lprintf(9, "actual room forcing loop\n");
1128 if (strlen(force_room) > 0) {
1129 strcpy(hold_rm, actual_rm);
1130 strcpy(actual_rm, force_room);
1132 /* This call to usergoto() changes rooms if necessary. It also
1133 * causes the latest message list to be read into memory.
1135 usergoto(actual_rm, 0);
1137 /* read in the quickroom record, obtaining a lock... */
1138 lgetroom(&CC->quickroom, actual_rm);
1140 /* Fix an obscure bug */
1141 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1142 CC->quickroom.QRflags =
1143 CC->quickroom.QRflags & ~QR_MAILBOX;
1146 /* Add the message pointer to the room */
1147 CC->quickroom.QRhighest =
1148 AddMessageToRoom(&CC->quickroom, newmsgid);
1150 /* update quickroom */
1151 lputroom(&CC->quickroom);
1152 ++successful_local_recipients;
1155 /* Network mail - send a copy to the network program. */
1156 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1157 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1158 (long) getpid(), CC->cs_pid, ++seqnum);
1159 copy_file(mtmp, aaa);
1160 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1162 /* Bump this user's messages posted counter. */
1163 lgetuser(&CC->usersupp, CC->curr_user);
1164 CC->usersupp.posted = CC->usersupp.posted + 1;
1165 lputuser(&CC->usersupp);
1167 /* If this is private, local mail, make a copy in the
1168 * recipient's mailbox and bump the reference count.
1170 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1171 if (getuser(&userbuf, recipient) == 0) {
1172 MailboxName(actual_rm, &userbuf, MAILROOM);
1173 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1174 if (lgetroom(&qtemp, actual_rm) == 0) {
1176 AddMessageToRoom(&qtemp, newmsgid);
1178 ++successful_local_recipients;
1182 /* If we've posted in a room other than the current room, then we
1183 * have to now go back to the current room...
1185 if (strlen(hold_rm) > 0) {
1186 usergoto(hold_rm, 0);
1188 unlink(mtmp); /* delete the temporary file */
1190 /* Write a supplemental message info record. This doesn't have to
1191 * be a critical section because nobody else knows about this message
1194 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1195 smi.smi_msgnum = newmsgid;
1196 smi.smi_refcount = successful_local_recipients;
1197 safestrncpy(smi.smi_content_type, content_type, 64);
1198 PutSuppMsgInfo(&smi);
1203 * Generate an administrative message and post it in the Aide> room.
1205 void aide_message(char *text)
1209 fp = fopen(CC->temp, "wb");
1210 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1211 fprintf(fp, "Psysop%c", 0);
1212 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1213 fprintf(fp, "ACitadel%c", 0);
1214 fprintf(fp, "OAide%c", 0);
1215 fprintf(fp, "N%s%c", NODENAME, 0);
1216 fprintf(fp, "M%s\n%c", text, 0);
1218 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1219 syslog(LOG_NOTICE, text);
1225 * Build a binary message to be saved on disk.
1228 char *filename, /* temporary file name */
1229 struct usersupp *author, /* author's usersupp structure */
1230 char *recipient, /* NULL if it's not mail */
1231 char *room, /* room where it's going */
1232 int type, /* see MES_ types in header file */
1233 int net_type, /* see MES_ types in header file */
1234 int format_type, /* local or remote (see citadel.h) */
1236 { /* who we're masquerading as */
1244 /* Don't confuse the poor folks if it's not routed mail. */
1245 strcpy(dest_node, "");
1248 /* If net_type is MES_BINARY, split out the destination node. */
1249 if (net_type == MES_BINARY) {
1250 strcpy(dest_node, NODENAME);
1251 for (a = 0; a < strlen(recipient); ++a) {
1252 if (recipient[a] == '@') {
1254 strcpy(dest_node, &recipient[a + 1]);
1258 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1259 if (net_type == MES_INTERNET) {
1260 strcpy(dest_node, "internet");
1262 while (isspace(recipient[strlen(recipient) - 1]))
1263 recipient[strlen(recipient) - 1] = 0;
1266 fp = fopen(filename, "w");
1268 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1269 putc(format_type, fp); /* Formatted or unformatted */
1270 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1271 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1273 fprintf(fp, "A%s%c", fake_name, 0);
1275 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1277 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1278 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1280 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1283 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1284 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1286 if (recipient[0] != 0)
1287 fprintf(fp, "R%s%c", recipient, 0);
1288 if (dest_node[0] != 0)
1289 fprintf(fp, "D%s%c", dest_node, 0);
1293 while (client_gets(buf), strcmp(buf, "000")) {
1294 fprintf(fp, "%s\n", buf);
1305 * message entry - mode 0 (normal) <bc>
1307 void cmd_ent0(char *entargs)
1310 char recipient[256];
1312 int format_type = 0;
1313 char newusername[256]; /* <bc> */
1318 struct usersupp tempUS;
1321 post = extract_int(entargs, 0);
1322 extract(recipient, entargs, 1);
1323 anon_flag = extract_int(entargs, 2);
1324 format_type = extract_int(entargs, 3);
1326 /* first check to make sure the request is valid. */
1328 if (!(CC->logged_in)) {
1329 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1332 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1333 cprintf("%d Need to be validated to enter ",
1334 ERROR + HIGHER_ACCESS_REQUIRED);
1335 cprintf("(except in %s> to sysop)\n", MAILROOM);
1338 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1339 cprintf("%d Need net privileges to enter here.\n",
1340 ERROR + HIGHER_ACCESS_REQUIRED);
1343 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1344 cprintf("%d Sorry, this is a read-only room.\n",
1345 ERROR + HIGHER_ACCESS_REQUIRED);
1351 if (post == 2) { /* <bc> */
1352 if (CC->usersupp.axlevel < 6) {
1353 cprintf("%d You don't have permission to do an aide post.\n",
1354 ERROR + HIGHER_ACCESS_REQUIRED);
1357 extract(newusername, entargs, 4);
1358 memset(CC->fake_postname, 0, 32);
1359 strcpy(CC->fake_postname, newusername);
1360 cprintf("%d Ok\n", OK);
1363 CC->cs_flags |= CS_POSTING;
1366 if (CC->quickroom.QRflags & QR_MAILBOX) {
1367 if (CC->usersupp.axlevel >= 2) {
1368 strcpy(buf, recipient);
1370 strcpy(buf, "sysop");
1371 lprintf(9, "calling alias()\n");
1372 e = alias(buf); /* alias and mail type */
1373 lprintf(9, "alias() returned %d\n", e);
1374 if ((buf[0] == 0) || (e == MES_ERROR)) {
1375 cprintf("%d Unknown address - cannot send message.\n",
1376 ERROR + NO_SUCH_USER);
1379 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1380 cprintf("%d Net privileges required for network mail.\n",
1381 ERROR + HIGHER_ACCESS_REQUIRED);
1384 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1385 && ((CC->usersupp.flags & US_INTERNET) == 0)
1386 && (!CC->internal_pgm)) {
1387 cprintf("%d You don't have access to Internet mail.\n",
1388 ERROR + HIGHER_ACCESS_REQUIRED);
1391 if (!strcasecmp(buf, "sysop")) {
1396 goto SKFALL; /* don't search local file */
1397 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1398 cprintf("%d Can't send mail to yourself!\n",
1399 ERROR + NO_SUCH_USER);
1402 /* Check to make sure the user exists; also get the correct
1403 * upper/lower casing of the name.
1405 a = getuser(&tempUS, buf);
1407 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1410 strcpy(buf, tempUS.fullname);
1412 SKFALL:b = MES_NORMAL;
1413 if (CC->quickroom.QRflags & QR_ANONONLY)
1415 if (CC->quickroom.QRflags & QR_ANONOPT) {
1419 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1422 /* If we're only checking the validity of the request, return
1423 * success without creating the message.
1426 cprintf("%d %s\n", OK, buf);
1429 cprintf("%d send message\n", SEND_LISTING);
1430 if (CC->fake_postname[0])
1431 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1432 else if (CC->fake_username[0])
1433 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1435 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1436 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1437 CC->fake_postname[0] = '\0';
1444 * message entry - mode 3 (raw)
1446 void cmd_ent3(char *entargs)
1452 struct usersupp tempUS;
1457 if (CC->internal_pgm == 0) {
1458 cprintf("%d This command is for internal programs only.\n",
1462 /* See if there's a recipient, but make sure it's a real one */
1463 extract(recp, entargs, 1);
1464 for (a = 0; a < strlen(recp); ++a)
1465 if (!isprint(recp[a]))
1466 strcpy(&recp[a], &recp[a + 1]);
1467 while (isspace(recp[0]))
1468 strcpy(recp, &recp[1]);
1469 while (isspace(recp[strlen(recp) - 1]))
1470 recp[strlen(recp) - 1] = 0;
1472 /* If we're in Mail, check the recipient */
1473 if (strlen(recp) > 0) {
1474 e = alias(recp); /* alias and mail type */
1475 if ((recp[0] == 0) || (e == MES_ERROR)) {
1476 cprintf("%d Unknown address - cannot send message.\n",
1477 ERROR + NO_SUCH_USER);
1480 if (e == MES_LOCAL) {
1481 a = getuser(&tempUS, recp);
1483 cprintf("%d No such user.\n",
1484 ERROR + NO_SUCH_USER);
1489 /* At this point, message has been approved. */
1490 if (extract_int(entargs, 0) == 0) {
1491 cprintf("%d OK to send\n", OK);
1494 /* open a temp file to hold the message */
1495 fp = fopen(CC->temp, "wb");
1497 cprintf("%d Cannot open %s: %s\n",
1498 ERROR + INTERNAL_ERROR,
1499 CC->temp, strerror(errno));
1502 msglen = extract_long(entargs, 2);
1503 cprintf("%d %ld\n", SEND_BINARY, msglen);
1504 while (msglen > 0L) {
1505 bloklen = ((msglen >= 255L) ? 255 : msglen);
1506 client_read(buf, (int) bloklen);
1507 fwrite(buf, (int) bloklen, 1, fp);
1508 msglen = msglen - bloklen;
1512 save_message(CC->temp, recp, "", e, 0);
1517 * API function to delete messages which match a set of criteria
1518 * (returns the actual number of messages deleted)
1519 * FIX ... still need to implement delete by content type
1521 int CtdlDeleteMessages( char *room_name, /* which room */
1522 long dmsgnum, /* or "0" for any */
1523 char *content_type /* or NULL for any */
1526 struct quickroom qrbuf;
1527 struct cdbdata *cdbfr;
1528 long *msglist = NULL;
1531 int num_deleted = 0;
1533 struct SuppMsgInfo smi;
1535 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1536 room_name, dmsgnum, content_type);
1538 /* get room record, obtaining a lock... */
1539 if (lgetroom(&qrbuf, room_name) != 0) {
1540 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1542 return(0); /* room not found */
1545 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1547 lprintf(9, "doing mallok/memcpy loop\n");
1548 if (cdbfr != NULL) {
1549 msglist = mallok(cdbfr->len);
1550 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1551 num_msgs = cdbfr->len / sizeof(long);
1556 for (i=0; i<num_msgs; ++i) {
1559 /* Set/clear a bit for each criterion */
1561 if ( (dmsgnum == 0L) || (msglist[i]==dmsgnum) ) {
1562 delete_this |= 0x01;
1565 if (content_type == NULL) {
1566 delete_this |= 0x02;
1568 GetSuppMsgInfo(&smi, msglist[i]);
1569 if (!strcasecmp(smi.smi_content_type,
1571 delete_this |= 0x02;
1575 /* Delete message only if all bits are set */
1576 if (delete_this == 0x03) {
1577 AdjRefCount(msglist[i], -1);
1583 num_msgs = sort_msglist(msglist, num_msgs);
1584 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1585 msglist, (num_msgs * sizeof(long)) );
1587 qrbuf.QRhighest = msglist[num_msgs - 1];
1591 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1592 return(num_deleted);
1598 * Delete message from current room
1600 void cmd_dele(char *delstr)
1605 getuser(&CC->usersupp, CC->curr_user);
1606 if ((CC->usersupp.axlevel < 6)
1607 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1608 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1609 cprintf("%d Higher access required.\n",
1610 ERROR + HIGHER_ACCESS_REQUIRED);
1613 delnum = extract_long(delstr, 0);
1615 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1618 cprintf("%d %d message%s deleted.\n", OK,
1619 num_deleted, ((num_deleted!=1) ? "s" : "") );
1621 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1627 * move a message to another room
1629 void cmd_move(char *args)
1633 struct quickroom qtemp;
1636 num = extract_long(args, 0);
1637 extract(targ, args, 1);
1639 getuser(&CC->usersupp, CC->curr_user);
1640 if ((CC->usersupp.axlevel < 6)
1641 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1642 cprintf("%d Higher access required.\n",
1643 ERROR + HIGHER_ACCESS_REQUIRED);
1646 if (getroom(&qtemp, targ) != 0) {
1647 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1651 /* Bump the reference count, otherwise the message will be deleted
1652 * from disk when we remove it from the source room.
1654 AdjRefCount(num, 1);
1656 /* yank the message out of the current room... */
1657 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1660 /* put the message into the target room */
1661 lgetroom(&qtemp, targ);
1662 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1664 cprintf("%d Message moved.\n", OK);
1666 AdjRefCount(num, (-1)); /* oops */
1667 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1674 * GetSuppMsgInfo() - Get the supplementary record for a message
1676 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1679 struct cdbdata *cdbsmi;
1682 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1683 smibuf->smi_msgnum = msgnum;
1684 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1686 /* Use the negative of the message number for its supp record index */
1687 TheIndex = (0L - msgnum);
1689 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1690 if (cdbsmi == NULL) {
1691 return; /* record not found; go with defaults */
1693 memcpy(smibuf, cdbsmi->ptr,
1694 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1695 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1702 * PutSuppMsgInfo() - (re)write supplementary record for a message
1704 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1708 /* Use the negative of the message number for its supp record index */
1709 TheIndex = (0L - smibuf->smi_msgnum);
1711 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1712 smibuf->smi_msgnum, smibuf->smi_refcount);
1714 cdb_store(CDB_MSGMAIN,
1715 &TheIndex, sizeof(long),
1716 smibuf, sizeof(struct SuppMsgInfo));
1723 * AdjRefCount - change the reference count for a message;
1724 * delete the message if it reaches zero
1726 void AdjRefCount(long msgnum, int incr)
1729 struct SuppMsgInfo smi;
1732 /* This is a *tight* critical section; please keep it that way, as
1733 * it may get called while nested in other critical sections.
1734 * Complicating this any further will surely cause deadlock!
1736 begin_critical_section(S_SUPPMSGMAIN);
1737 GetSuppMsgInfo(&smi, msgnum);
1738 smi.smi_refcount += incr;
1739 PutSuppMsgInfo(&smi);
1740 end_critical_section(S_SUPPMSGMAIN);
1742 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1743 msgnum, smi.smi_refcount);
1745 /* If the reference count is now zero, delete the message
1746 * (and its supplementary record as well).
1748 if (smi.smi_refcount == 0) {
1749 lprintf(9, "Deleting message <%ld>\n", msgnum);
1751 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1752 delnum = (0L - msgnum);
1753 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1759 * Write a generic object to this room
1761 void CtdlWriteObject( char *req_room, /* Room to stuff it in */
1762 char *content_type, /* MIME type of this object */
1763 char *tempfilename, /* Where to fetch it from */
1764 int is_mailbox, /* Private mailbox room? */
1765 int is_binary, /* Is encoding necessary? */
1766 int is_unique /* Del others of this type? */
1770 char filename[PATH_MAX];
1773 struct quickroom qrbuf;
1774 char roomname[ROOMNAMELEN];
1776 if (is_mailbox) MailboxName(roomname, &CC->usersupp, req_room);
1777 else safestrncpy(roomname, req_room, sizeof(roomname));
1779 strcpy(filename, tmpnam(NULL));
1780 fp = fopen(filename, "w");
1781 if (fp == NULL) return;
1783 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1784 fprintf(fp, "T%ld%c", time(NULL), 0);
1785 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1786 fprintf(fp, "O%s%c", roomname, 0);
1787 fprintf(fp, "N%s%c", config.c_nodename, 0);
1788 fprintf(fp, "MContent-type: %s\n", content_type);
1790 tempfp = fopen(tempfilename, "r");
1791 if (tempfp == NULL) {
1797 if (is_binary == 0) {
1798 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1799 while (ch=getc(tempfp), ch>0) putc(ch, fp);
1804 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1807 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1808 tempfilename, filename);
1812 /* Create the requested room if we have to. */
1813 if (getroom(&qrbuf, roomname) != 0) {
1814 create_room(roomname, 4, "", 0);
1817 /* If the caller specified this object as unique, delete all
1818 * other objects of this type that are currently in the room.
1821 lprintf(9, "Deleted %d other msgs of this type\n",
1822 CtdlDeleteMessages(roomname, 0L, content_type) );
1825 /* Now write the data */
1826 save_message(filename, "", roomname, MES_LOCAL, 1);