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 freeing the returned CtdlMessage struct
487 * using the CtdlMessageFree() function.
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 on-disk magic number.
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_magic = CTDLMESSAGE_MAGIC;
522 ret->cm_anon_type = *mptr++; /* Anon type byte */
523 ret->cm_format_type = *mptr++; /* Format type byte */
526 * The rest is zero or more arbitrary fields. Load them in.
527 * We're done when we encounter either a zero-length field or
528 * have just processed the 'M' (message text) field.
531 field_length = strlen(mptr);
532 if (field_length == 0) break;
533 field_header = *mptr++;
534 ret->cm_fields[field_header] = mallok(field_length);
535 strcpy(ret->cm_fields[field_header], mptr);
537 while (*mptr++ != 0) ; /* advance to next field */
539 } while ( (field_length > 0) && (field_header != 'M') );
546 * 'Destructor' for struct CtdlMessage
548 void CtdlFreeMessage(struct CtdlMessage *msg) {
551 if (msg == NULL) return;
552 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
553 lprintf(3, "CtdlFreeMessage() -- self-check failed\n");
557 for (i=0; i<256; ++i)
558 if (msg->cm_fields[i] != NULL)
559 phree(msg->cm_fields[i]);
567 * Get a message off disk. (return value is the message's timestamp)
570 void output_message(char *msgid, int mode, int headers_only)
575 CIT_UBYTE format_type, anon_flag;
580 struct cdbdata *dmsgtext;
583 /* buffers needed for RFC822 translation */
591 msg_num = atol(msgid);
593 if ( (!(CC->logged_in)) && (!(CC->internal_pgm)) ) {
594 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
598 /* FIX ... small security issue
599 * We need to check to make sure the requested message is actually
600 * in the current room, and set msg_ok to 1 only if it is. This
601 * functionality is currently missing because I'm in a hurry to replace
602 * broken production code with nonbroken pre-beta code. :( -- ajc
605 cprintf("%d Message %ld is not in this room.\n",
612 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msg_num, sizeof(long));
614 if (dmsgtext == NULL) {
615 cprintf("%d Can't find message %ld\n",
616 (ERROR + INTERNAL_ERROR), msg_num);
619 msg_len = (long) dmsgtext->len;
620 mptr = dmsgtext->ptr;
622 /* this loop spews out the whole message if we're doing raw format */
623 if (mode == MT_RAW) {
624 cprintf("%d %ld\n", BINARY_FOLLOWS, msg_len);
625 client_write(dmsgtext->ptr, (int) msg_len);
629 /* Otherwise, we'll start parsing it field by field... */
632 cprintf("%d Illegal message format on disk\n",
633 ERROR + INTERNAL_ERROR);
638 format_type = *mptr++;
640 /* Are we downloading a MIME component? */
641 if (mode == MT_DOWNLOAD) {
642 if (format_type != 4) {
643 cprintf("%d This is not a MIME message.\n",
645 } else if (CC->download_fp != NULL) {
646 cprintf("%d You already have a download open.\n",
649 /* Skip to the message body */
650 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
653 buf[strlen(buf) + 1] = 0;
655 buf[strlen(buf)] = rch;
659 mime_parser(mptr, NULL, *mime_download);
660 /* If there's no file open by this time, the requested
661 * section wasn't found, so print an error
663 if (CC->download_fp == NULL) {
664 cprintf("%d Section %s not found.\n",
665 ERROR + FILE_NOT_FOUND,
673 /* now for the user-mode message reading loops */
674 cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
676 if (mode == MT_CITADEL)
677 cprintf("type=%d\n", format_type);
679 if ((anon_flag == MES_ANON) && (mode == MT_CITADEL)) {
680 cprintf("nhdr=yes\n");
682 /* begin header processing loop for Citadel message format */
684 if ((mode == MT_CITADEL) || (mode == MT_MIME))
685 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
688 buf[strlen(buf) + 1] = 0;
690 buf[strlen(buf)] = rch;
694 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
695 if (anon_flag == MES_ANON)
696 cprintf("from=****");
697 else if (anon_flag == MES_AN2)
698 cprintf("from=anonymous");
700 cprintf("from=%s", buf);
701 if ((is_room_aide()) && ((anon_flag == MES_ANON)
702 || (anon_flag == MES_AN2)))
703 cprintf(" [%s]", buf);
705 } else if (ch == 'P')
706 cprintf("path=%s\n", buf);
708 cprintf("subj=%s\n", buf);
710 cprintf("msgn=%s\n", buf);
712 cprintf("hnod=%s\n", buf);
714 cprintf("room=%s\n", buf);
716 cprintf("node=%s\n", buf);
718 cprintf("rcpt=%s\n", buf);
720 cprintf("time=%s\n", buf);
721 /* else cprintf("fld%c=%s\n",ch,buf); */
723 /* begin header processing loop for RFC822 transfer format */
727 strcpy(snode, NODENAME);
728 strcpy(lnode, HUMANNODE);
729 if (mode == MT_RFC822)
730 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
733 buf[strlen(buf) + 1] = 0;
735 buf[strlen(buf)] = rch;
740 else if (ch == 'P') {
741 cprintf("Path: %s\n", buf);
742 for (a = 0; a < strlen(buf); ++a) {
744 strcpy(buf, &buf[a + 1]);
749 } else if (ch == 'U')
750 cprintf("Subject: %s\n", buf);
756 cprintf("X-Citadel-Room: %s\n", buf);
760 cprintf("To: %s\n", buf);
761 else if (ch == 'T') {
763 cprintf("Date: %s", asctime(localtime(&xtime)));
766 if (mode == MT_RFC822) {
767 if (!strcasecmp(snode, NODENAME)) {
770 cprintf("Message-ID: <%s@%s>\n", mid, snode);
771 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
772 cprintf("From: %s@%s (%s)\n",
773 suser, snode, luser);
774 cprintf("Organization: %s\n", lnode);
776 /* end header processing loop ... at this point, we're in the text */
779 cprintf("text\n*** ?Message truncated\n000\n");
783 /* do some sort of MIME output */
784 if (format_type == 4) {
785 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
786 mime_parser(mptr, NULL, *list_this_part);
788 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
795 /* give 'em a length */
797 while (ch = *mptr++, ch > 0) {
800 cprintf("mlen=%ld\n", msg_len);
805 /* signify start of msg text */
806 if (mode == MT_CITADEL)
808 if ((mode == MT_RFC822) && (format_type != 4))
811 /* If the format type on disk is 1 (fixed-format), then we want
812 * everything to be output completely literally ... regardless of
813 * what message transfer format is in use.
815 if (format_type == 1) {
817 while (ch = *mptr++, ch > 0) {
820 if ((ch == 10) || (strlen(buf) > 250)) {
821 cprintf("%s\n", buf);
824 buf[strlen(buf) + 1] = 0;
825 buf[strlen(buf)] = ch;
829 cprintf("%s\n", buf);
831 /* If the message on disk is format 0 (Citadel vari-format), we
832 * output using the formatter at 80 columns. This is the final output
833 * form if the transfer format is RFC822, but if the transfer format
834 * is Citadel proprietary, it'll still work, because the indentation
835 * for new paragraphs is correct and the client will reformat the
836 * message to the reader's screen width.
838 if (format_type == 0) {
839 memfmout(80, mptr, 0);
841 /* If the message on disk is format 4 (MIME), we've gotta hand it
842 * off to the MIME parser. The client has already been told that
843 * this message is format 1 (fixed format), so the callback function
844 * we use will display those parts as-is.
846 if (format_type == 4) {
847 mime_parser(mptr, NULL, *fixed_output);
857 * display a message (mode 0 - Citadel proprietary)
859 void cmd_msg0(char *cmdbuf)
862 int headers_only = 0;
864 extract(msgid, cmdbuf, 0);
865 headers_only = extract_int(cmdbuf, 1);
867 output_message(msgid, MT_CITADEL, headers_only);
873 * display a message (mode 2 - RFC822)
875 void cmd_msg2(char *cmdbuf)
878 int headers_only = 0;
880 extract(msgid, cmdbuf, 0);
881 headers_only = extract_int(cmdbuf, 1);
883 output_message(msgid, MT_RFC822, headers_only);
887 * display a message (mode 3 - IGnet raw format - internal programs only)
889 void cmd_msg3(char *cmdbuf)
892 int headers_only = 0;
894 if (CC->internal_pgm == 0) {
895 cprintf("%d This command is for internal programs only.\n",
899 extract(msgid, cmdbuf, 0);
900 headers_only = extract_int(cmdbuf, 1);
902 output_message(msgid, MT_RAW, headers_only);
906 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
908 void cmd_msg4(char *cmdbuf)
912 extract(msgid, cmdbuf, 0);
914 output_message(msgid, MT_MIME, 0);
920 * Open a component of a MIME message as a download file
922 void cmd_opna(char *cmdbuf)
926 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
928 extract(msgid, cmdbuf, 0);
929 extract(desired_section, cmdbuf, 1);
931 output_message(msgid, MT_DOWNLOAD, 0);
937 * Message base operation to send a message to the master file
938 * (returns new message number)
940 long send_message(char *message_in_memory, /* pointer to buffer */
941 size_t message_length, /* length of buffer */
943 { /* 1 to generate an I field */
946 char *actual_message;
947 size_t actual_length;
951 /* Get a new message number */
952 newmsgid = get_new_message_number();
955 sprintf(msgidbuf, "I%ld", newmsgid);
956 actual_length = message_length + strlen(msgidbuf) + 1;
957 actual_message = mallok(actual_length);
958 memcpy(actual_message, message_in_memory, 3);
959 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1));
960 memcpy(&actual_message[strlen(msgidbuf) + 4],
961 &message_in_memory[3], message_length - 3);
963 actual_message = message_in_memory;
964 actual_length = message_length;
967 /* Write our little bundle of joy into the message base */
968 begin_critical_section(S_MSGMAIN);
969 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
970 actual_message, actual_length) < 0) {
971 lprintf(2, "Can't store message\n");
976 end_critical_section(S_MSGMAIN);
979 phree(actual_message);
981 /* Finally, return the pointers */
988 * this is a simple file copy routine.
990 void copy_file(char *from, char *to)
995 ffp = fopen(from, "r");
998 tfp = fopen(to, "w");
1003 while (a = getc(ffp), a >= 0) {
1014 * message base operation to save a message and install its pointers
1016 void save_message(char *mtmp, /* file containing proper message */
1017 char *rec, /* Recipient (if mail) */
1018 char *force, /* if non-zero length, force a room */
1019 int mailtype, /* local or remote type, see citadel.h */
1021 { /* set to 1 to generate an 'I' field */
1023 char hold_rm[ROOMNAMELEN];
1024 char actual_rm[ROOMNAMELEN];
1025 char force_room[ROOMNAMELEN];
1026 char content_type[256]; /* We have to learn this */
1028 char recipient[256];
1030 char *message_in_memory;
1032 struct stat statbuf;
1035 struct usersupp userbuf;
1037 static int seqnum = 0;
1038 int successful_local_recipients = 0;
1039 struct quickroom qtemp;
1040 struct SuppMsgInfo smi;
1042 lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
1043 mtmp, rec, force, mailtype, generate_id);
1045 strcpy(force_room, force);
1047 /* Strip non-printable characters out of the recipient name */
1048 strcpy(recipient, rec);
1049 for (a = 0; a < strlen(recipient); ++a)
1050 if (!isprint(recipient[a]))
1051 strcpy(&recipient[a], &recipient[a + 1]);
1053 /* Measure the message */
1054 stat(mtmp, &statbuf);
1055 templen = statbuf.st_size;
1057 /* Now read it into memory */
1058 message_in_memory = (char *) mallok(templen);
1059 if (message_in_memory == NULL) {
1060 lprintf(2, "Can't allocate memory to save message!\n");
1063 fp = fopen(mtmp, "rb");
1064 fread(message_in_memory, templen, 1, fp);
1067 /* Learn about what's inside, because it's what's inside that counts */
1068 mptr = message_in_memory;
1069 ++mptr; /* advance past 0xFF header */
1070 ++mptr; /* advance past anon flag */
1074 strcpy(content_type, "text/x-citadel-variformat");
1077 strcpy(content_type, "text/plain");
1080 strcpy(content_type, "text/plain");
1081 /* advance past header fields */
1082 while (ch = *mptr++, (ch != 'M' && ch != 0)) {
1089 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1090 safestrncpy(content_type, mptr,
1091 sizeof(content_type));
1092 lprintf(9, "%s\n", content_type);
1093 strcpy(content_type, &content_type[14]);
1094 for (a=0; a<strlen(content_type); ++a)
1095 if ( (content_type[a]==';')
1096 || (content_type[a]==' ')
1097 || (content_type[a]==13)
1098 || (content_type[a]==10) )
1099 content_type[a] = 0;
1105 lprintf(9, "Content type is <%s>\n", content_type);
1107 /* Save it to disk */
1108 newmsgid = send_message(message_in_memory, templen, generate_id);
1109 phree(message_in_memory);
1113 strcpy(actual_rm, CC->quickroom.QRname);
1114 strcpy(hold_rm, "");
1116 /* If this is being done by the networker delivering a private
1117 * message, we want to BYPASS saving the sender's copy (because there
1118 * is no local sender; it would otherwise go to the Trashcan).
1120 if ( (!CC->internal_pgm) || (strlen(recipient)==0) ) {
1121 /* If the user is a twit, move to the twit room for posting */
1123 if (CC->usersupp.axlevel == 2) {
1124 strcpy(hold_rm, actual_rm);
1125 strcpy(actual_rm, config.c_twitroom);
1127 /* ...or if this message is destined for Aide> then go there. */
1128 lprintf(9, "actual room forcing loop\n");
1129 if (strlen(force_room) > 0) {
1130 strcpy(hold_rm, actual_rm);
1131 strcpy(actual_rm, force_room);
1133 /* This call to usergoto() changes rooms if necessary. It also
1134 * causes the latest message list to be read into memory.
1136 usergoto(actual_rm, 0);
1138 /* read in the quickroom record, obtaining a lock... */
1139 lgetroom(&CC->quickroom, actual_rm);
1141 /* Fix an obscure bug */
1142 if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
1143 CC->quickroom.QRflags =
1144 CC->quickroom.QRflags & ~QR_MAILBOX;
1147 /* Add the message pointer to the room */
1148 CC->quickroom.QRhighest =
1149 AddMessageToRoom(&CC->quickroom, newmsgid);
1151 /* update quickroom */
1152 lputroom(&CC->quickroom);
1153 ++successful_local_recipients;
1156 /* Network mail - send a copy to the network program. */
1157 if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) {
1158 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1159 (long) getpid(), CC->cs_pid, ++seqnum);
1160 copy_file(mtmp, aaa);
1161 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1163 /* Bump this user's messages posted counter. */
1164 lgetuser(&CC->usersupp, CC->curr_user);
1165 CC->usersupp.posted = CC->usersupp.posted + 1;
1166 lputuser(&CC->usersupp);
1168 /* If this is private, local mail, make a copy in the
1169 * recipient's mailbox and bump the reference count.
1171 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1172 if (getuser(&userbuf, recipient) == 0) {
1173 MailboxName(actual_rm, &userbuf, MAILROOM);
1174 lprintf(9, "Targeting mailbox: <%s>\n", actual_rm);
1175 if (lgetroom(&qtemp, actual_rm) == 0) {
1177 AddMessageToRoom(&qtemp, newmsgid);
1179 ++successful_local_recipients;
1183 /* If we've posted in a room other than the current room, then we
1184 * have to now go back to the current room...
1186 if (strlen(hold_rm) > 0) {
1187 usergoto(hold_rm, 0);
1189 unlink(mtmp); /* delete the temporary file */
1191 /* Write a supplemental message info record. This doesn't have to
1192 * be a critical section because nobody else knows about this message
1195 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1196 smi.smi_msgnum = newmsgid;
1197 smi.smi_refcount = successful_local_recipients;
1198 safestrncpy(smi.smi_content_type, content_type, 64);
1199 PutSuppMsgInfo(&smi);
1204 * Generate an administrative message and post it in the Aide> room.
1206 void aide_message(char *text)
1210 fp = fopen(CC->temp, "wb");
1211 fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0);
1212 fprintf(fp, "Psysop%c", 0);
1213 fprintf(fp, "T%ld%c", (long) time(NULL), 0);
1214 fprintf(fp, "ACitadel%c", 0);
1215 fprintf(fp, "OAide%c", 0);
1216 fprintf(fp, "N%s%c", NODENAME, 0);
1217 fprintf(fp, "M%s\n%c", text, 0);
1219 save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1);
1220 syslog(LOG_NOTICE, text);
1226 * Build a binary message to be saved on disk.
1229 char *filename, /* temporary file name */
1230 struct usersupp *author, /* author's usersupp structure */
1231 char *recipient, /* NULL if it's not mail */
1232 char *room, /* room where it's going */
1233 int type, /* see MES_ types in header file */
1234 int net_type, /* see MES_ types in header file */
1235 int format_type, /* local or remote (see citadel.h) */
1237 { /* who we're masquerading as */
1245 /* Don't confuse the poor folks if it's not routed mail. */
1246 strcpy(dest_node, "");
1249 /* If net_type is MES_BINARY, split out the destination node. */
1250 if (net_type == MES_BINARY) {
1251 strcpy(dest_node, NODENAME);
1252 for (a = 0; a < strlen(recipient); ++a) {
1253 if (recipient[a] == '@') {
1255 strcpy(dest_node, &recipient[a + 1]);
1259 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1260 if (net_type == MES_INTERNET) {
1261 strcpy(dest_node, "internet");
1263 while (isspace(recipient[strlen(recipient) - 1]))
1264 recipient[strlen(recipient) - 1] = 0;
1267 fp = fopen(filename, "w");
1269 putc(type, fp); /* Normal or anonymous, see MES_ flags */
1270 putc(format_type, fp); /* Formatted or unformatted */
1271 fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */
1272 fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */
1274 fprintf(fp, "A%s%c", fake_name, 0);
1276 fprintf(fp, "A%s%c", author->fullname, 0); /* author */
1278 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
1279 fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0);
1281 fprintf(fp, "O%s%c", CC->quickroom.QRname, 0);
1284 fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */
1285 fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */
1287 if (recipient[0] != 0)
1288 fprintf(fp, "R%s%c", recipient, 0);
1289 if (dest_node[0] != 0)
1290 fprintf(fp, "D%s%c", dest_node, 0);
1294 while (client_gets(buf), strcmp(buf, "000")) {
1295 fprintf(fp, "%s\n", buf);
1306 * message entry - mode 0 (normal) <bc>
1308 void cmd_ent0(char *entargs)
1311 char recipient[256];
1313 int format_type = 0;
1314 char newusername[256]; /* <bc> */
1319 struct usersupp tempUS;
1322 post = extract_int(entargs, 0);
1323 extract(recipient, entargs, 1);
1324 anon_flag = extract_int(entargs, 2);
1325 format_type = extract_int(entargs, 3);
1327 /* first check to make sure the request is valid. */
1329 if (!(CC->logged_in)) {
1330 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1333 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1334 cprintf("%d Need to be validated to enter ",
1335 ERROR + HIGHER_ACCESS_REQUIRED);
1336 cprintf("(except in %s> to sysop)\n", MAILROOM);
1339 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1340 cprintf("%d Need net privileges to enter here.\n",
1341 ERROR + HIGHER_ACCESS_REQUIRED);
1344 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1345 cprintf("%d Sorry, this is a read-only room.\n",
1346 ERROR + HIGHER_ACCESS_REQUIRED);
1352 if (post == 2) { /* <bc> */
1353 if (CC->usersupp.axlevel < 6) {
1354 cprintf("%d You don't have permission to do an aide post.\n",
1355 ERROR + HIGHER_ACCESS_REQUIRED);
1358 extract(newusername, entargs, 4);
1359 memset(CC->fake_postname, 0, 32);
1360 strcpy(CC->fake_postname, newusername);
1361 cprintf("%d Ok\n", OK);
1364 CC->cs_flags |= CS_POSTING;
1367 if (CC->quickroom.QRflags & QR_MAILBOX) {
1368 if (CC->usersupp.axlevel >= 2) {
1369 strcpy(buf, recipient);
1371 strcpy(buf, "sysop");
1372 lprintf(9, "calling alias()\n");
1373 e = alias(buf); /* alias and mail type */
1374 lprintf(9, "alias() returned %d\n", e);
1375 if ((buf[0] == 0) || (e == MES_ERROR)) {
1376 cprintf("%d Unknown address - cannot send message.\n",
1377 ERROR + NO_SUCH_USER);
1380 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1381 cprintf("%d Net privileges required for network mail.\n",
1382 ERROR + HIGHER_ACCESS_REQUIRED);
1385 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1386 && ((CC->usersupp.flags & US_INTERNET) == 0)
1387 && (!CC->internal_pgm)) {
1388 cprintf("%d You don't have access to Internet mail.\n",
1389 ERROR + HIGHER_ACCESS_REQUIRED);
1392 if (!strcasecmp(buf, "sysop")) {
1397 goto SKFALL; /* don't search local file */
1398 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1399 cprintf("%d Can't send mail to yourself!\n",
1400 ERROR + NO_SUCH_USER);
1403 /* Check to make sure the user exists; also get the correct
1404 * upper/lower casing of the name.
1406 a = getuser(&tempUS, buf);
1408 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1411 strcpy(buf, tempUS.fullname);
1413 SKFALL:b = MES_NORMAL;
1414 if (CC->quickroom.QRflags & QR_ANONONLY)
1416 if (CC->quickroom.QRflags & QR_ANONOPT) {
1420 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1423 /* If we're only checking the validity of the request, return
1424 * success without creating the message.
1427 cprintf("%d %s\n", OK, buf);
1430 cprintf("%d send message\n", SEND_LISTING);
1431 if (CC->fake_postname[0])
1432 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname);
1433 else if (CC->fake_username[0])
1434 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username);
1436 make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, "");
1437 save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1438 CC->fake_postname[0] = '\0';
1445 * message entry - mode 3 (raw)
1447 void cmd_ent3(char *entargs)
1453 struct usersupp tempUS;
1458 if (CC->internal_pgm == 0) {
1459 cprintf("%d This command is for internal programs only.\n",
1463 /* See if there's a recipient, but make sure it's a real one */
1464 extract(recp, entargs, 1);
1465 for (a = 0; a < strlen(recp); ++a)
1466 if (!isprint(recp[a]))
1467 strcpy(&recp[a], &recp[a + 1]);
1468 while (isspace(recp[0]))
1469 strcpy(recp, &recp[1]);
1470 while (isspace(recp[strlen(recp) - 1]))
1471 recp[strlen(recp) - 1] = 0;
1473 /* If we're in Mail, check the recipient */
1474 if (strlen(recp) > 0) {
1475 e = alias(recp); /* alias and mail type */
1476 if ((recp[0] == 0) || (e == MES_ERROR)) {
1477 cprintf("%d Unknown address - cannot send message.\n",
1478 ERROR + NO_SUCH_USER);
1481 if (e == MES_LOCAL) {
1482 a = getuser(&tempUS, recp);
1484 cprintf("%d No such user.\n",
1485 ERROR + NO_SUCH_USER);
1490 /* At this point, message has been approved. */
1491 if (extract_int(entargs, 0) == 0) {
1492 cprintf("%d OK to send\n", OK);
1495 /* open a temp file to hold the message */
1496 fp = fopen(CC->temp, "wb");
1498 cprintf("%d Cannot open %s: %s\n",
1499 ERROR + INTERNAL_ERROR,
1500 CC->temp, strerror(errno));
1503 msglen = extract_long(entargs, 2);
1504 cprintf("%d %ld\n", SEND_BINARY, msglen);
1505 while (msglen > 0L) {
1506 bloklen = ((msglen >= 255L) ? 255 : msglen);
1507 client_read(buf, (int) bloklen);
1508 fwrite(buf, (int) bloklen, 1, fp);
1509 msglen = msglen - bloklen;
1513 save_message(CC->temp, recp, "", e, 0);
1518 * API function to delete messages which match a set of criteria
1519 * (returns the actual number of messages deleted)
1520 * FIX ... still need to implement delete by content type
1522 int CtdlDeleteMessages( char *room_name, /* which room */
1523 long dmsgnum, /* or "0" for any */
1524 char *content_type /* or NULL for any */
1527 struct quickroom qrbuf;
1528 struct cdbdata *cdbfr;
1529 long *msglist = NULL;
1532 int num_deleted = 0;
1534 struct SuppMsgInfo smi;
1536 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
1537 room_name, dmsgnum, content_type);
1539 /* get room record, obtaining a lock... */
1540 if (lgetroom(&qrbuf, room_name) != 0) {
1541 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
1543 return(0); /* room not found */
1546 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
1548 lprintf(9, "doing mallok/memcpy loop\n");
1549 if (cdbfr != NULL) {
1550 msglist = mallok(cdbfr->len);
1551 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1552 num_msgs = cdbfr->len / sizeof(long);
1557 for (i=0; i<num_msgs; ++i) {
1560 /* Set/clear a bit for each criterion */
1562 if ( (dmsgnum == 0L) || (msglist[i]==dmsgnum) ) {
1563 delete_this |= 0x01;
1566 if (content_type == NULL) {
1567 delete_this |= 0x02;
1569 GetSuppMsgInfo(&smi, msglist[i]);
1570 if (!strcasecmp(smi.smi_content_type,
1572 delete_this |= 0x02;
1576 /* Delete message only if all bits are set */
1577 if (delete_this == 0x03) {
1578 AdjRefCount(msglist[i], -1);
1584 num_msgs = sort_msglist(msglist, num_msgs);
1585 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
1586 msglist, (num_msgs * sizeof(long)) );
1588 qrbuf.QRhighest = msglist[num_msgs - 1];
1592 lprintf(9, "%d message(s) deleted.\n", num_deleted);
1593 return(num_deleted);
1599 * Delete message from current room
1601 void cmd_dele(char *delstr)
1606 getuser(&CC->usersupp, CC->curr_user);
1607 if ((CC->usersupp.axlevel < 6)
1608 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1609 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1610 cprintf("%d Higher access required.\n",
1611 ERROR + HIGHER_ACCESS_REQUIRED);
1614 delnum = extract_long(delstr, 0);
1616 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
1619 cprintf("%d %d message%s deleted.\n", OK,
1620 num_deleted, ((num_deleted!=1) ? "s" : "") );
1622 cprintf("%d Message %ld not found.\n", ERROR, delnum);
1628 * move a message to another room
1630 void cmd_move(char *args)
1634 struct quickroom qtemp;
1637 num = extract_long(args, 0);
1638 extract(targ, args, 1);
1640 getuser(&CC->usersupp, CC->curr_user);
1641 if ((CC->usersupp.axlevel < 6)
1642 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1643 cprintf("%d Higher access required.\n",
1644 ERROR + HIGHER_ACCESS_REQUIRED);
1647 if (getroom(&qtemp, targ) != 0) {
1648 cprintf("%d '%s' does not exist.\n", ERROR, targ);
1652 /* Bump the reference count, otherwise the message will be deleted
1653 * from disk when we remove it from the source room.
1655 AdjRefCount(num, 1);
1657 /* yank the message out of the current room... */
1658 foundit = CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
1661 /* put the message into the target room */
1662 lgetroom(&qtemp, targ);
1663 qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1665 cprintf("%d Message moved.\n", OK);
1667 AdjRefCount(num, (-1)); /* oops */
1668 cprintf("%d msg %ld does not exist.\n", ERROR, num);
1675 * GetSuppMsgInfo() - Get the supplementary record for a message
1677 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
1680 struct cdbdata *cdbsmi;
1683 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
1684 smibuf->smi_msgnum = msgnum;
1685 smibuf->smi_refcount = 1; /* Default reference count is 1 */
1687 /* Use the negative of the message number for its supp record index */
1688 TheIndex = (0L - msgnum);
1690 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
1691 if (cdbsmi == NULL) {
1692 return; /* record not found; go with defaults */
1694 memcpy(smibuf, cdbsmi->ptr,
1695 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
1696 sizeof(struct SuppMsgInfo) : cdbsmi->len));
1703 * PutSuppMsgInfo() - (re)write supplementary record for a message
1705 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
1709 /* Use the negative of the message number for its supp record index */
1710 TheIndex = (0L - smibuf->smi_msgnum);
1712 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
1713 smibuf->smi_msgnum, smibuf->smi_refcount);
1715 cdb_store(CDB_MSGMAIN,
1716 &TheIndex, sizeof(long),
1717 smibuf, sizeof(struct SuppMsgInfo));
1724 * AdjRefCount - change the reference count for a message;
1725 * delete the message if it reaches zero
1727 void AdjRefCount(long msgnum, int incr)
1730 struct SuppMsgInfo smi;
1733 /* This is a *tight* critical section; please keep it that way, as
1734 * it may get called while nested in other critical sections.
1735 * Complicating this any further will surely cause deadlock!
1737 begin_critical_section(S_SUPPMSGMAIN);
1738 GetSuppMsgInfo(&smi, msgnum);
1739 smi.smi_refcount += incr;
1740 PutSuppMsgInfo(&smi);
1741 end_critical_section(S_SUPPMSGMAIN);
1743 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
1744 msgnum, smi.smi_refcount);
1746 /* If the reference count is now zero, delete the message
1747 * (and its supplementary record as well).
1749 if (smi.smi_refcount == 0) {
1750 lprintf(9, "Deleting message <%ld>\n", msgnum);
1752 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1753 delnum = (0L - msgnum);
1754 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1760 * Write a generic object to this room
1762 void CtdlWriteObject( char *req_room, /* Room to stuff it in */
1763 char *content_type, /* MIME type of this object */
1764 char *tempfilename, /* Where to fetch it from */
1765 int is_mailbox, /* Private mailbox room? */
1766 int is_binary, /* Is encoding necessary? */
1767 int is_unique /* Del others of this type? */
1771 char filename[PATH_MAX];
1774 struct quickroom qrbuf;
1775 char roomname[ROOMNAMELEN];
1777 if (is_mailbox) MailboxName(roomname, &CC->usersupp, req_room);
1778 else safestrncpy(roomname, req_room, sizeof(roomname));
1780 strcpy(filename, tmpnam(NULL));
1781 fp = fopen(filename, "w");
1782 if (fp == NULL) return;
1784 fprintf(fp, "%c%c%c", 0xFF, MES_NORMAL, 4);
1785 fprintf(fp, "T%ld%c", time(NULL), 0);
1786 fprintf(fp, "A%s%c", CC->usersupp.fullname, 0);
1787 fprintf(fp, "O%s%c", roomname, 0);
1788 fprintf(fp, "N%s%c", config.c_nodename, 0);
1789 fprintf(fp, "MContent-type: %s\n", content_type);
1791 tempfp = fopen(tempfilename, "r");
1792 if (tempfp == NULL) {
1798 if (is_binary == 0) {
1799 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
1800 while (ch=getc(tempfp), ch>0) putc(ch, fp);
1805 fprintf(fp, "Content-transfer-encoding: base64\n\n");
1808 sprintf(cmdbuf, "./base64 -e <%s >>%s",
1809 tempfilename, filename);
1813 /* Create the requested room if we have to. */
1814 if (getroom(&qrbuf, roomname) != 0) {
1815 create_room(roomname, 4, "", 0);
1818 /* If the caller specified this object as unique, delete all
1819 * other objects of this type that are currently in the room.
1822 lprintf(9, "Deleted %d other msgs of this type\n",
1823 CtdlDeleteMessages(roomname, 0L, content_type) );
1826 /* Now write the data */
1827 save_message(filename, "", roomname, MES_LOCAL, 1);