4 * Implements the message store.
26 #include "sysdep_decls.h"
27 #include "citserver.h"
32 #include "dynloader.h"
34 #include "mime_parser.h"
37 #include "internet_addressing.h"
39 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
40 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
41 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
43 extern struct config config;
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
49 "", "", "", "", "", "", "", "",
50 "", "", "", "", "", "", "", "",
51 "", "", "", "", "", "", "", "",
52 "", "", "", "", "", "", "", "",
53 "", "", "", "", "", "", "", "",
54 "", "", "", "", "", "", "", "",
81 * This function is self explanatory.
82 * (What can I say, I'm in a weird mood today...)
84 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
88 for (i = 0; i < strlen(name); ++i) {
90 while (isspace(name[i - 1]) && i > 0) {
91 strcpy(&name[i - 1], &name[i]);
94 while (isspace(name[i + 1])) {
95 strcpy(&name[i + 1], &name[i + 2]);
103 * Aliasing for network mail.
104 * (Error messages have been commented out, because this is a server.)
106 int alias(char *name)
107 { /* process alias and routing info for mail */
110 char aaa[300], bbb[300];
112 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
114 fp = fopen("network/mail.aliases", "r");
116 fp = fopen("/dev/null", "r");
121 while (fgets(aaa, sizeof aaa, fp) != NULL) {
122 while (isspace(name[0]))
123 strcpy(name, &name[1]);
124 aaa[strlen(aaa) - 1] = 0;
126 for (a = 0; a < strlen(aaa); ++a) {
128 strcpy(bbb, &aaa[a + 1]);
132 if (!strcasecmp(name, aaa))
136 lprintf(7, "Mail is being forwarded to %s\n", name);
138 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
139 for (a=0; a<strlen(name); ++a) {
140 if (name[a] == '@') {
141 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
143 lprintf(7, "Changed to <%s>\n", name);
148 /* determine local or remote type, see citadel.h */
149 for (a = 0; a < strlen(name); ++a)
151 return (MES_INTERNET);
152 for (a = 0; a < strlen(name); ++a)
154 for (b = a; b < strlen(name); ++b)
156 return (MES_INTERNET);
158 for (a = 0; a < strlen(name); ++a)
162 lprintf(7, "Too many @'s in address\n");
166 for (a = 0; a < strlen(name); ++a)
168 strcpy(bbb, &name[a + 1]);
170 strcpy(bbb, &bbb[1]);
171 fp = fopen("network/mail.sysinfo", "r");
175 a = getstring(fp, aaa);
176 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
177 a = getstring(fp, aaa);
178 if (!strncmp(aaa, "use ", 4)) {
179 strcpy(bbb, &aaa[4]);
184 if (!strncmp(aaa, "uum", 3)) {
186 for (a = 0; a < strlen(bbb); ++a) {
192 while (bbb[strlen(bbb) - 1] == '_')
193 bbb[strlen(bbb) - 1] = 0;
194 sprintf(name, &aaa[4], bbb);
195 lprintf(9, "returning MES_INTERNET\n");
196 return (MES_INTERNET);
198 if (!strncmp(aaa, "bin", 3)) {
201 while (aaa[strlen(aaa) - 1] != '@')
202 aaa[strlen(aaa) - 1] = 0;
203 aaa[strlen(aaa) - 1] = 0;
204 while (aaa[strlen(aaa) - 1] == ' ')
205 aaa[strlen(aaa) - 1] = 0;
206 while (bbb[0] != '@')
207 strcpy(bbb, &bbb[1]);
208 strcpy(bbb, &bbb[1]);
209 while (bbb[0] == ' ')
210 strcpy(bbb, &bbb[1]);
211 sprintf(name, "%s @%s", aaa, bbb);
212 lprintf(9, "returning MES_BINARY\n");
217 lprintf(9, "returning MES_LOCAL\n");
226 fp = fopen("citadel.control", "r");
227 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
233 void simple_listing(long msgnum, void *userdata)
235 cprintf("%ld\n", msgnum);
240 /* Determine if a given message matches the fields in a message template.
241 * Return 0 for a successful match.
243 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
246 /* If there aren't any fields in the template, all messages will
249 if (template == NULL) return(0);
251 /* Null messages are bogus. */
252 if (msg == NULL) return(1);
254 for (i='A'; i<='Z'; ++i) {
255 if (template->cm_fields[i] != NULL) {
256 if (msg->cm_fields[i] == NULL) {
259 if (strcasecmp(msg->cm_fields[i],
260 template->cm_fields[i])) return 1;
264 /* All compares succeeded: we have a match! */
272 * API function to perform an operation for each qualifying message in the
273 * current room. (Returns the number of messages processed.)
275 int CtdlForEachMessage(int mode, long ref,
276 int moderation_level,
278 struct CtdlMessage *compare,
279 void (*CallBack) (long, void *),
285 struct cdbdata *cdbfr;
286 long *msglist = NULL;
288 int num_processed = 0;
290 struct SuppMsgInfo smi;
291 struct CtdlMessage *msg;
293 /* Learn about the user and room in question */
295 getuser(&CC->usersupp, CC->curr_user);
296 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
298 /* Load the message list */
299 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
301 msglist = mallok(cdbfr->len);
302 memcpy(msglist, cdbfr->ptr, cdbfr->len);
303 num_msgs = cdbfr->len / sizeof(long);
306 return 0; /* No messages at all? No further action. */
310 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
311 GetSuppMsgInfo(&smi, msglist[a]);
313 /* Filter out messages that are moderated below the level
314 * currently being viewed at.
316 if (smi.smi_mod < moderation_level) {
320 /* If the caller is looking for a specific MIME type, filter
321 * out all messages which are not of the type requested.
323 if (content_type != NULL) if (strlen(content_type) > 0) {
324 if (strcasecmp(smi.smi_content_type, content_type)) {
330 num_msgs = sort_msglist(msglist, num_msgs);
332 /* If a template was supplied, filter out the messages which
333 * don't match. (This could induce some delays!)
336 if (compare != NULL) {
337 for (a = 0; a < num_msgs; ++a) {
338 msg = CtdlFetchMessage(msglist[a]);
340 if (CtdlMsgCmp(msg, compare)) {
343 CtdlFreeMessage(msg);
351 * Now iterate through the message list, according to the
352 * criteria supplied by the caller.
355 for (a = 0; a < num_msgs; ++a) {
356 thismsg = msglist[a];
361 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
362 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
363 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
364 && (CC->usersupp.flags & US_LASTOLD))
365 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
366 || ((mode == MSGS_FIRST) && (a < ref))
367 || ((mode == MSGS_GT) && (thismsg > ref))
368 || ((mode == MSGS_EQ) && (thismsg == ref))
371 if (CallBack) CallBack(thismsg, userdata);
375 phree(msglist); /* Clean up */
376 return num_processed;
382 * cmd_msgs() - get list of message #'s in this room
383 * implements the MSGS server command using CtdlForEachMessage()
385 void cmd_msgs(char *cmdbuf)
394 int with_template = 0;
395 struct CtdlMessage *template = NULL;
397 extract(which, cmdbuf, 0);
398 cm_ref = extract_int(cmdbuf, 1);
399 with_template = extract_int(cmdbuf, 2);
403 if (!strncasecmp(which, "OLD", 3))
405 else if (!strncasecmp(which, "NEW", 3))
407 else if (!strncasecmp(which, "FIRST", 5))
409 else if (!strncasecmp(which, "LAST", 4))
411 else if (!strncasecmp(which, "GT", 2))
414 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
415 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
420 cprintf("%d Send template then receive message list\n",
422 template = (struct CtdlMessage *)
423 mallok(sizeof(struct CtdlMessage));
424 memset(template, 0, sizeof(struct CtdlMessage));
425 while(client_gets(buf), strcmp(buf,"000")) {
426 extract(tfield, buf, 0);
427 extract(tvalue, buf, 1);
428 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
429 if (!strcasecmp(tfield, msgkeys[i])) {
430 template->cm_fields[i] =
437 cprintf("%d Message list...\n", LISTING_FOLLOWS);
440 CtdlForEachMessage(mode, cm_ref,
441 CC->usersupp.moderation_filter,
442 NULL, template, simple_listing, NULL);
443 if (template != NULL) CtdlFreeMessage(template);
451 * help_subst() - support routine for help file viewer
453 void help_subst(char *strbuf, char *source, char *dest)
458 while (p = pattern2(strbuf, source), (p >= 0)) {
459 strcpy(workbuf, &strbuf[p + strlen(source)]);
460 strcpy(&strbuf[p], dest);
461 strcat(strbuf, workbuf);
466 void do_help_subst(char *buffer)
470 help_subst(buffer, "^nodename", config.c_nodename);
471 help_subst(buffer, "^humannode", config.c_humannode);
472 help_subst(buffer, "^fqdn", config.c_fqdn);
473 help_subst(buffer, "^username", CC->usersupp.fullname);
474 sprintf(buf2, "%ld", CC->usersupp.usernum);
475 help_subst(buffer, "^usernum", buf2);
476 help_subst(buffer, "^sysadm", config.c_sysadm);
477 help_subst(buffer, "^variantname", CITADEL);
478 sprintf(buf2, "%d", config.c_maxsessions);
479 help_subst(buffer, "^maxsessions", buf2);
485 * memfmout() - Citadel text formatter and paginator.
486 * Although the original purpose of this routine was to format
487 * text to the reader's screen width, all we're really using it
488 * for here is to format text out to 80 columns before sending it
489 * to the client. The client software may reformat it again.
492 int width, /* screen width to use */
493 char *mptr, /* where are we going to get our text from? */
494 char subst, /* nonzero if we should do substitutions */
495 char *nl) /* string to terminate lines with */
507 c = 1; /* c is the current pos */
511 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
513 buffer[strlen(buffer) + 1] = 0;
514 buffer[strlen(buffer)] = ch;
517 if (buffer[0] == '^')
518 do_help_subst(buffer);
520 buffer[strlen(buffer) + 1] = 0;
522 strcpy(buffer, &buffer[1]);
530 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
532 if (((old == 13) || (old == 10)) && (isspace(real))) {
540 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
541 cprintf("%s%s", nl, aaa);
550 if ((strlen(aaa) + c) > (width - 5)) {
559 if ((ch == 13) || (ch == 10)) {
560 cprintf("%s%s", aaa, nl);
567 cprintf("%s%s", aaa, nl);
573 * Callback function for mime parser that simply lists the part
575 void list_this_part(char *name, char *filename, char *partnum, char *disp,
576 void *content, char *cbtype, size_t length, char *encoding,
580 cprintf("part=%s|%s|%s|%s|%s|%d\n",
581 name, filename, partnum, disp, cbtype, length);
586 * Callback function for mime parser that opens a section for downloading
588 void mime_download(char *name, char *filename, char *partnum, char *disp,
589 void *content, char *cbtype, size_t length, char *encoding,
593 /* Silently go away if there's already a download open... */
594 if (CC->download_fp != NULL)
597 /* ...or if this is not the desired section */
598 if (strcasecmp(desired_section, partnum))
601 CC->download_fp = tmpfile();
602 if (CC->download_fp == NULL)
605 fwrite(content, length, 1, CC->download_fp);
606 fflush(CC->download_fp);
607 rewind(CC->download_fp);
609 OpenCmdResult(filename, cbtype);
615 * Load a message from disk into memory.
616 * This is used by CtdlOutputMsg() and other fetch functions.
618 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
619 * using the CtdlMessageFree() function.
621 struct CtdlMessage *CtdlFetchMessage(long msgnum)
623 struct cdbdata *dmsgtext;
624 struct CtdlMessage *ret = NULL;
627 CIT_UBYTE field_header;
630 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
631 if (dmsgtext == NULL) {
634 mptr = dmsgtext->ptr;
636 /* Parse the three bytes that begin EVERY message on disk.
637 * The first is always 0xFF, the on-disk magic number.
638 * The second is the anonymous/public type byte.
639 * The third is the format type byte (vari, fixed, or MIME).
643 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
647 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
648 memset(ret, 0, sizeof(struct CtdlMessage));
650 ret->cm_magic = CTDLMESSAGE_MAGIC;
651 ret->cm_anon_type = *mptr++; /* Anon type byte */
652 ret->cm_format_type = *mptr++; /* Format type byte */
655 * The rest is zero or more arbitrary fields. Load them in.
656 * We're done when we encounter either a zero-length field or
657 * have just processed the 'M' (message text) field.
660 field_length = strlen(mptr);
661 if (field_length == 0)
663 field_header = *mptr++;
664 ret->cm_fields[field_header] = mallok(field_length);
665 strcpy(ret->cm_fields[field_header], mptr);
667 while (*mptr++ != 0); /* advance to next field */
669 } while ((field_length > 0) && (field_header != 'M'));
673 /* Always make sure there's something in the msg text field */
674 if (ret->cm_fields['M'] == NULL)
675 ret->cm_fields['M'] = strdoop("<no text>\n");
677 /* Perform "before read" hooks (aborting if any return nonzero) */
678 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
679 CtdlFreeMessage(ret);
688 * Returns 1 if the supplied pointer points to a valid Citadel message.
689 * If the pointer is NULL or the magic number check fails, returns 0.
691 int is_valid_message(struct CtdlMessage *msg) {
694 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
695 lprintf(3, "is_valid_message() -- self-check failed\n");
703 * 'Destructor' for struct CtdlMessage
705 void CtdlFreeMessage(struct CtdlMessage *msg)
709 if (is_valid_message(msg) == 0) return;
711 for (i = 0; i < 256; ++i)
712 if (msg->cm_fields[i] != NULL) {
713 phree(msg->cm_fields[i]);
716 msg->cm_magic = 0; /* just in case */
722 * Callback function for mime parser that wants to display text
724 void fixed_output(char *name, char *filename, char *partnum, char *disp,
725 void *content, char *cbtype, size_t length, char *encoding,
733 if (!strcasecmp(cbtype, "multipart/alternative")) {
734 strcpy(ma->prefix, partnum);
735 strcat(ma->prefix, ".");
741 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
743 && (ma->did_print == 1) ) {
744 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
750 if ( (!strcasecmp(cbtype, "text/plain"))
751 || (strlen(cbtype)==0) ) {
756 if (ch==10) cprintf("\r\n");
757 else cprintf("%c", ch);
760 else if (!strcasecmp(cbtype, "text/html")) {
761 ptr = html_to_ascii(content, 80, 0);
766 if (ch==10) cprintf("\r\n");
767 else cprintf("%c", ch);
771 else if (strncasecmp(cbtype, "multipart/", 10)) {
772 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
773 partnum, filename, cbtype, length);
779 * Get a message off disk. (returns om_* values found in msgbase.h)
782 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
783 int mode, /* how would you like that message? */
784 int headers_only, /* eschew the message body? */
785 int do_proto, /* do Citadel protocol responses? */
786 int crlf /* Use CRLF newlines instead of LF? */
788 struct CtdlMessage *TheMessage;
791 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
796 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
797 if (do_proto) cprintf("%d Not logged in.\n",
798 ERROR + NOT_LOGGED_IN);
799 return(om_not_logged_in);
802 /* FIXME ... small security issue
803 * We need to check to make sure the requested message is actually
804 * in the current room, and set msg_ok to 1 only if it is. This
805 * functionality is currently missing because I'm in a hurry to replace
806 * broken production code with nonbroken pre-beta code. :( -- ajc
809 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
811 return(om_no_such_msg);
816 * Fetch the message from disk
818 TheMessage = CtdlFetchMessage(msg_num);
819 if (TheMessage == NULL) {
820 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
822 return(om_no_such_msg);
825 retcode = CtdlOutputPreLoadedMsg(
826 TheMessage, msg_num, mode,
827 headers_only, do_proto, crlf);
829 CtdlFreeMessage(TheMessage);
835 * Get a message off disk. (returns om_* values found in msgbase.h)
838 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
840 int mode, /* how would you like that message? */
841 int headers_only, /* eschew the message body? */
842 int do_proto, /* do Citadel protocol responses? */
843 int crlf /* Use CRLF newlines instead of LF? */
849 char display_name[SIZ];
851 char *nl; /* newline string */
853 /* buffers needed for RFC822 translation */
863 sprintf(mid, "%ld", msg_num);
864 nl = (crlf ? "\r\n" : "\n");
866 if (!is_valid_message(TheMessage)) {
867 lprintf(1, "ERROR: invalid preloaded message for output\n");
868 return(om_no_such_msg);
871 /* Are we downloading a MIME component? */
872 if (mode == MT_DOWNLOAD) {
873 if (TheMessage->cm_format_type != FMT_RFC822) {
875 cprintf("%d This is not a MIME message.\n",
877 } else if (CC->download_fp != NULL) {
878 if (do_proto) cprintf(
879 "%d You already have a download open.\n",
882 /* Parse the message text component */
883 mptr = TheMessage->cm_fields['M'];
884 mime_parser(mptr, NULL, *mime_download, NULL, 0);
885 /* If there's no file open by this time, the requested
886 * section wasn't found, so print an error
888 if (CC->download_fp == NULL) {
889 if (do_proto) cprintf(
890 "%d Section %s not found.\n",
891 ERROR + FILE_NOT_FOUND,
895 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
898 /* now for the user-mode message reading loops */
899 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
901 /* Tell the client which format type we're using. If this is a
902 * MIME message, *lie* about it and tell the user it's fixed-format.
904 if (mode == MT_CITADEL) {
905 if (TheMessage->cm_format_type == FMT_RFC822) {
906 if (do_proto) cprintf("type=1\n");
909 if (do_proto) cprintf("type=%d\n",
910 TheMessage->cm_format_type);
914 /* nhdr=yes means that we're only displaying headers, no body */
915 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
916 if (do_proto) cprintf("nhdr=yes\n");
919 /* begin header processing loop for Citadel message format */
921 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
923 strcpy(display_name, "<unknown>");
924 if (TheMessage->cm_fields['A']) {
925 strcpy(buf, TheMessage->cm_fields['A']);
926 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
927 if (TheMessage->cm_anon_type == MES_ANON)
928 strcpy(display_name, "****");
929 else if (TheMessage->cm_anon_type == MES_AN2)
930 strcpy(display_name, "anonymous");
932 strcpy(display_name, buf);
934 && ((TheMessage->cm_anon_type == MES_ANON)
935 || (TheMessage->cm_anon_type == MES_AN2))) {
936 sprintf(&display_name[strlen(display_name)],
941 strcpy(allkeys, FORDER);
942 for (i=0; i<strlen(allkeys); ++i) {
943 k = (int) allkeys[i];
945 if (TheMessage->cm_fields[k] != NULL) {
947 if (do_proto) cprintf("%s=%s\n",
952 if (do_proto) cprintf("%s=%s\n",
954 TheMessage->cm_fields[k]
963 /* begin header processing loop for RFC822 transfer format */
968 strcpy(snode, NODENAME);
969 strcpy(lnode, HUMANNODE);
970 if (mode == MT_RFC822) {
971 cprintf("X-UIDL: %ld%s", msg_num, nl);
972 for (i = 0; i < 256; ++i) {
973 if (TheMessage->cm_fields[i]) {
974 mptr = TheMessage->cm_fields[i];
981 "Path:" removed for now because it confuses brain-dead Microsoft shitware
982 into thinking that mail messages are newsgroup messages instead. When we
983 add NNTP support back into Citadel we'll have to add code to only output
984 this field when appropriate.
986 cprintf("Path: %s%s", mptr, nl);
990 cprintf("Subject: %s%s", mptr, nl);
996 cprintf("X-Citadel-Room: %s%s",
1001 cprintf("To: %s%s", mptr, nl);
1002 else if (i == 'T') {
1003 datestring(datestamp, atol(mptr),
1004 DATESTRING_RFC822 );
1005 cprintf("Date: %s%s", datestamp, nl);
1011 for (i=0; i<strlen(suser); ++i) {
1012 suser[i] = tolower(suser[i]);
1013 if (!isalnum(suser[i])) suser[i]='_';
1016 if (mode == MT_RFC822) {
1017 if (!strcasecmp(snode, NODENAME)) {
1018 strcpy(snode, FQDN);
1021 /* Construct a fun message id */
1022 cprintf("Message-ID: <%s", mid);
1023 if (strchr(mid, '@')==NULL) {
1024 cprintf("@%s", snode);
1028 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1030 if (strlen(fuser) > 0) {
1031 cprintf("From: %s (%s)%s", fuser, luser, nl);
1034 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1037 cprintf("Organization: %s%s", lnode, nl);
1040 /* end header processing loop ... at this point, we're in the text */
1042 mptr = TheMessage->cm_fields['M'];
1044 /* Tell the client about the MIME parts in this message */
1045 if (TheMessage->cm_format_type == FMT_RFC822) {
1046 if (mode == MT_CITADEL) {
1047 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1049 else if (mode == MT_MIME) { /* list parts only */
1050 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1051 if (do_proto) cprintf("000\n");
1054 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1055 /* FIXME ... we have to put some code in here to avoid
1056 * printing duplicate header information when both
1057 * Citadel and RFC822 headers exist. Preference should
1058 * probably be given to the RFC822 headers.
1060 while (ch=*(mptr++), ch!=0) {
1062 else if (ch==10) cprintf("%s", nl);
1063 else cprintf("%c", ch);
1065 if (do_proto) cprintf("000\n");
1071 if (do_proto) cprintf("000\n");
1075 /* signify start of msg text */
1076 if (mode == MT_CITADEL)
1077 if (do_proto) cprintf("text\n");
1078 if (mode == MT_RFC822) {
1079 if (TheMessage->cm_fields['U'] == NULL) {
1080 cprintf("Subject: (no subject)%s", nl);
1085 /* If the format type on disk is 1 (fixed-format), then we want
1086 * everything to be output completely literally ... regardless of
1087 * what message transfer format is in use.
1089 if (TheMessage->cm_format_type == FMT_FIXED) {
1091 while (ch = *mptr++, ch > 0) {
1094 if ((ch == 10) || (strlen(buf) > 250)) {
1095 cprintf("%s%s", buf, nl);
1098 buf[strlen(buf) + 1] = 0;
1099 buf[strlen(buf)] = ch;
1102 if (strlen(buf) > 0)
1103 cprintf("%s%s", buf, nl);
1106 /* If the message on disk is format 0 (Citadel vari-format), we
1107 * output using the formatter at 80 columns. This is the final output
1108 * form if the transfer format is RFC822, but if the transfer format
1109 * is Citadel proprietary, it'll still work, because the indentation
1110 * for new paragraphs is correct and the client will reformat the
1111 * message to the reader's screen width.
1113 if (TheMessage->cm_format_type == FMT_CITADEL) {
1114 memfmout(80, mptr, 0, nl);
1117 /* If the message on disk is format 4 (MIME), we've gotta hand it
1118 * off to the MIME parser. The client has already been told that
1119 * this message is format 1 (fixed format), so the callback function
1120 * we use will display those parts as-is.
1122 if (TheMessage->cm_format_type == FMT_RFC822) {
1123 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1124 memset(ma, 0, sizeof(struct ma_info));
1125 mime_parser(mptr, NULL, *fixed_output, NULL, 0);
1128 /* now we're done */
1129 if (do_proto) cprintf("000\n");
1136 * display a message (mode 0 - Citadel proprietary)
1138 void cmd_msg0(char *cmdbuf)
1141 int headers_only = 0;
1143 msgid = extract_long(cmdbuf, 0);
1144 headers_only = extract_int(cmdbuf, 1);
1146 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1152 * display a message (mode 2 - RFC822)
1154 void cmd_msg2(char *cmdbuf)
1157 int headers_only = 0;
1159 msgid = extract_long(cmdbuf, 0);
1160 headers_only = extract_int(cmdbuf, 1);
1162 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1168 * display a message (mode 3 - IGnet raw format - internal programs only)
1170 void cmd_msg3(char *cmdbuf)
1173 struct CtdlMessage *msg;
1176 if (CC->internal_pgm == 0) {
1177 cprintf("%d This command is for internal programs only.\n",
1182 msgnum = extract_long(cmdbuf, 0);
1183 msg = CtdlFetchMessage(msgnum);
1185 cprintf("%d Message %ld not found.\n",
1190 serialize_message(&smr, msg);
1191 CtdlFreeMessage(msg);
1194 cprintf("%d Unable to serialize message\n",
1195 ERROR+INTERNAL_ERROR);
1199 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1200 client_write(smr.ser, smr.len);
1207 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1209 void cmd_msg4(char *cmdbuf)
1213 msgid = extract_long(cmdbuf, 0);
1214 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1218 * Open a component of a MIME message as a download file
1220 void cmd_opna(char *cmdbuf)
1224 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1226 msgid = extract_long(cmdbuf, 0);
1227 extract(desired_section, cmdbuf, 1);
1229 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1234 * Save a message pointer into a specified room
1235 * (Returns 0 for success, nonzero for failure)
1236 * roomname may be NULL to use the current room
1238 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1240 char hold_rm[ROOMNAMELEN];
1241 struct cdbdata *cdbfr;
1244 long highest_msg = 0L;
1245 struct CtdlMessage *msg = NULL;
1247 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1248 roomname, msgid, flags);
1250 strcpy(hold_rm, CC->quickroom.QRname);
1252 /* We may need to check to see if this message is real */
1253 if ( (flags & SM_VERIFY_GOODNESS)
1254 || (flags & SM_DO_REPL_CHECK)
1256 msg = CtdlFetchMessage(msgid);
1257 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1260 /* Perform replication checks if necessary */
1261 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1263 if (getroom(&CC->quickroom,
1264 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1266 lprintf(9, "No such room <%s>\n", roomname);
1267 if (msg != NULL) CtdlFreeMessage(msg);
1268 return(ERROR + ROOM_NOT_FOUND);
1271 if (ReplicationChecks(msg) != 0) {
1272 getroom(&CC->quickroom, hold_rm);
1273 if (msg != NULL) CtdlFreeMessage(msg);
1274 lprintf(9, "Did replication, and newer exists\n");
1279 /* Now the regular stuff */
1280 if (lgetroom(&CC->quickroom,
1281 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1283 lprintf(9, "No such room <%s>\n", roomname);
1284 if (msg != NULL) CtdlFreeMessage(msg);
1285 return(ERROR + ROOM_NOT_FOUND);
1288 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1289 if (cdbfr == NULL) {
1293 msglist = mallok(cdbfr->len);
1294 if (msglist == NULL)
1295 lprintf(3, "ERROR malloc msglist!\n");
1296 num_msgs = cdbfr->len / sizeof(long);
1297 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1302 /* Make sure the message doesn't already exist in this room. It
1303 * is absolutely taboo to have more than one reference to the same
1304 * message in a room.
1306 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1307 if (msglist[i] == msgid) {
1308 lputroom(&CC->quickroom); /* unlock the room */
1309 getroom(&CC->quickroom, hold_rm);
1310 if (msg != NULL) CtdlFreeMessage(msg);
1311 return(ERROR + ALREADY_EXISTS);
1315 /* Now add the new message */
1317 msglist = reallok(msglist,
1318 (num_msgs * sizeof(long)));
1320 if (msglist == NULL) {
1321 lprintf(3, "ERROR: can't realloc message list!\n");
1323 msglist[num_msgs - 1] = msgid;
1325 /* Sort the message list, so all the msgid's are in order */
1326 num_msgs = sort_msglist(msglist, num_msgs);
1328 /* Determine the highest message number */
1329 highest_msg = msglist[num_msgs - 1];
1331 /* Write it back to disk. */
1332 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1333 msglist, num_msgs * sizeof(long));
1335 /* Free up the memory we used. */
1338 /* Update the highest-message pointer and unlock the room. */
1339 CC->quickroom.QRhighest = highest_msg;
1340 lputroom(&CC->quickroom);
1341 getroom(&CC->quickroom, hold_rm);
1343 /* Bump the reference count for this message. */
1344 if ((flags & SM_DONT_BUMP_REF)==0) {
1345 AdjRefCount(msgid, +1);
1348 /* Return success. */
1349 if (msg != NULL) CtdlFreeMessage(msg);
1356 * Message base operation to send a message to the master file
1357 * (returns new message number)
1359 * This is the back end for CtdlSaveMsg() and should not be directly
1360 * called by server-side modules.
1363 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1364 FILE *save_a_copy) /* save a copy to disk? */
1371 /* Get a new message number */
1372 newmsgid = get_new_message_number();
1373 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1375 /* Generate an ID if we don't have one already */
1376 if (msg->cm_fields['I']==NULL) {
1377 msg->cm_fields['I'] = strdoop(msgidbuf);
1380 serialize_message(&smr, msg);
1383 cprintf("%d Unable to serialize message\n",
1384 ERROR+INTERNAL_ERROR);
1388 /* Write our little bundle of joy into the message base */
1389 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1390 smr.ser, smr.len) < 0) {
1391 lprintf(2, "Can't store message\n");
1397 /* If the caller specified that a copy should be saved to a particular
1398 * file handle, do that now too.
1400 if (save_a_copy != NULL) {
1401 fwrite(smr.ser, smr.len, 1, save_a_copy);
1404 /* Free the memory we used for the serialized message */
1407 /* Return the *local* message ID to the caller
1408 * (even if we're storing an incoming network message)
1416 * Serialize a struct CtdlMessage into the format used on disk and network.
1418 * This function loads up a "struct ser_ret" (defined in server.h) which
1419 * contains the length of the serialized message and a pointer to the
1420 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1422 void serialize_message(struct ser_ret *ret, /* return values */
1423 struct CtdlMessage *msg) /* unserialized msg */
1427 static char *forder = FORDER;
1429 if (is_valid_message(msg) == 0) return; /* self check */
1432 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1433 ret->len = ret->len +
1434 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1436 lprintf(9, "calling malloc(%d)\n", ret->len);
1437 ret->ser = mallok(ret->len);
1438 if (ret->ser == NULL) {
1444 ret->ser[1] = msg->cm_anon_type;
1445 ret->ser[2] = msg->cm_format_type;
1448 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1449 ret->ser[wlen++] = (char)forder[i];
1450 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1451 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1453 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1462 * Back end for the ReplicationChecks() function
1464 void check_repl(long msgnum, void *userdata) {
1465 struct CtdlMessage *msg;
1466 time_t timestamp = (-1L);
1468 lprintf(9, "check_repl() found message %ld\n", msgnum);
1469 msg = CtdlFetchMessage(msgnum);
1470 if (msg == NULL) return;
1471 if (msg->cm_fields['T'] != NULL) {
1472 timestamp = atol(msg->cm_fields['T']);
1474 CtdlFreeMessage(msg);
1476 if (timestamp > msg_repl->highest) {
1477 msg_repl->highest = timestamp; /* newer! */
1478 lprintf(9, "newer!\n");
1481 lprintf(9, "older!\n");
1483 /* Existing isn't newer? Then delete the old one(s). */
1484 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1489 * Check to see if any messages already exist which carry the same Extended ID
1493 * -> With older timestamps: delete them and return 0. Message will be saved.
1494 * -> With newer timestamps: return 1. Message save will be aborted.
1496 int ReplicationChecks(struct CtdlMessage *msg) {
1497 struct CtdlMessage *template;
1500 lprintf(9, "ReplicationChecks() started\n");
1501 /* No extended id? Don't do anything. */
1502 if (msg->cm_fields['E'] == NULL) return 0;
1503 if (strlen(msg->cm_fields['E']) == 0) return 0;
1504 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1506 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1507 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1508 msg_repl->highest = atol(msg->cm_fields['T']);
1510 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1511 memset(template, 0, sizeof(struct CtdlMessage));
1512 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1514 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1517 /* If a newer message exists with the same Extended ID, abort
1520 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1524 CtdlFreeMessage(template);
1525 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1533 * Save a message to disk
1535 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1536 char *rec, /* Recipient (mail) */
1537 char *force, /* force a particular room? */
1538 int supplied_mailtype) /* local or remote type */
1541 char hold_rm[ROOMNAMELEN];
1542 char actual_rm[ROOMNAMELEN];
1543 char force_room[ROOMNAMELEN];
1544 char content_type[SIZ]; /* We have to learn this */
1545 char recipient[SIZ];
1548 struct usersupp userbuf;
1550 struct SuppMsgInfo smi;
1551 FILE *network_fp = NULL;
1552 static int seqnum = 1;
1553 struct CtdlMessage *imsg;
1557 lprintf(9, "CtdlSaveMsg() called\n");
1558 if (is_valid_message(msg) == 0) return(-1); /* self check */
1559 mailtype = supplied_mailtype;
1561 /* If this message has no timestamp, we take the liberty of
1562 * giving it one, right now.
1564 if (msg->cm_fields['T'] == NULL) {
1565 lprintf(9, "Generating timestamp\n");
1566 sprintf(aaa, "%ld", time(NULL));
1567 msg->cm_fields['T'] = strdoop(aaa);
1570 /* If this message has no path, we generate one.
1572 if (msg->cm_fields['P'] == NULL) {
1573 lprintf(9, "Generating path\n");
1574 if (msg->cm_fields['A'] != NULL) {
1575 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1576 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1577 if (isspace(msg->cm_fields['P'][a])) {
1578 msg->cm_fields['P'][a] = ' ';
1583 msg->cm_fields['P'] = strdoop("unknown");
1587 strcpy(force_room, force);
1589 /* Strip non-printable characters out of the recipient name */
1590 lprintf(9, "Checking recipient (if present)\n");
1591 strcpy(recipient, rec);
1592 for (a = 0; a < strlen(recipient); ++a)
1593 if (!isprint(recipient[a]))
1594 strcpy(&recipient[a], &recipient[a + 1]);
1596 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1597 for (a=0; a<strlen(recipient); ++a) {
1598 if (recipient[a] == '@') {
1599 if (CtdlHostAlias(&recipient[a+1])
1600 == hostalias_localhost) {
1602 lprintf(7, "Changed to <%s>\n", recipient);
1603 mailtype = MES_LOCAL;
1608 lprintf(9, "Recipient is <%s>\n", recipient);
1610 /* Learn about what's inside, because it's what's inside that counts */
1611 lprintf(9, "Learning what's inside\n");
1612 if (msg->cm_fields['M'] == NULL) {
1613 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1616 switch (msg->cm_format_type) {
1618 strcpy(content_type, "text/x-citadel-variformat");
1621 strcpy(content_type, "text/plain");
1624 strcpy(content_type, "text/plain");
1625 /* advance past header fields */
1626 mptr = msg->cm_fields['M'];
1629 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1630 safestrncpy(content_type, mptr,
1631 sizeof(content_type));
1632 strcpy(content_type, &content_type[14]);
1633 for (a = 0; a < strlen(content_type); ++a)
1634 if ((content_type[a] == ';')
1635 || (content_type[a] == ' ')
1636 || (content_type[a] == 13)
1637 || (content_type[a] == 10))
1638 content_type[a] = 0;
1645 /* Goto the correct room */
1646 lprintf(9, "Switching rooms\n");
1647 strcpy(hold_rm, CC->quickroom.QRname);
1648 strcpy(actual_rm, CC->quickroom.QRname);
1650 /* If the user is a twit, move to the twit room for posting */
1651 lprintf(9, "Handling twit stuff\n");
1653 if (CC->usersupp.axlevel == 2) {
1654 strcpy(hold_rm, actual_rm);
1655 strcpy(actual_rm, config.c_twitroom);
1659 /* ...or if this message is destined for Aide> then go there. */
1660 if (strlen(force_room) > 0) {
1661 strcpy(actual_rm, force_room);
1664 lprintf(9, "Possibly relocating\n");
1665 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1666 getroom(&CC->quickroom, actual_rm);
1670 * If this message has no O (room) field, generate one.
1672 if (msg->cm_fields['O'] == NULL) {
1673 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1676 /* Perform "before save" hooks (aborting if any return nonzero) */
1677 lprintf(9, "Performing before-save hooks\n");
1678 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1680 /* If this message has an Extended ID, perform replication checks */
1681 lprintf(9, "Performing replication checks\n");
1682 if (ReplicationChecks(msg) > 0) return(-1);
1684 /* Network mail - send a copy to the network program. */
1685 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1686 lprintf(9, "Sending network spool\n");
1687 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1688 (long) getpid(), CC->cs_pid, ++seqnum);
1689 lprintf(9, "Saving a copy to %s\n", aaa);
1690 network_fp = fopen(aaa, "ab+");
1691 if (network_fp == NULL)
1692 lprintf(2, "ERROR: %s\n", strerror(errno));
1695 /* Save it to disk */
1696 lprintf(9, "Saving to disk\n");
1697 newmsgid = send_message(msg, network_fp);
1698 if (network_fp != NULL) {
1700 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1703 if (newmsgid <= 0L) return(-1);
1705 /* Write a supplemental message info record. This doesn't have to
1706 * be a critical section because nobody else knows about this message
1709 lprintf(9, "Creating SuppMsgInfo record\n");
1710 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1711 smi.smi_msgnum = newmsgid;
1712 smi.smi_refcount = 0;
1713 safestrncpy(smi.smi_content_type, content_type, 64);
1714 PutSuppMsgInfo(&smi);
1716 /* Now figure out where to store the pointers */
1717 lprintf(9, "Storing pointers\n");
1719 /* If this is being done by the networker delivering a private
1720 * message, we want to BYPASS saving the sender's copy (because there
1721 * is no local sender; it would otherwise go to the Trashcan).
1723 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1724 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1725 lprintf(3, "ERROR saving message pointer!\n");
1726 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1730 /* For internet mail, drop a copy in the outbound queue room */
1731 if (mailtype == MES_INTERNET) {
1732 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1735 /* Bump this user's messages posted counter. */
1736 lprintf(9, "Updating user\n");
1737 lgetuser(&CC->usersupp, CC->curr_user);
1738 CC->usersupp.posted = CC->usersupp.posted + 1;
1739 lputuser(&CC->usersupp);
1741 /* If this is private, local mail, make a copy in the
1742 * recipient's mailbox and bump the reference count.
1744 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1745 if (getuser(&userbuf, recipient) == 0) {
1746 lprintf(9, "Delivering private mail\n");
1747 MailboxName(actual_rm, &userbuf, MAILROOM);
1748 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1751 lprintf(9, "No user <%s>, saving in %s> instead\n",
1752 recipient, AIDEROOM);
1753 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1757 /* Perform "after save" hooks */
1758 lprintf(9, "Performing after-save hooks\n");
1759 PerformMessageHooks(msg, EVT_AFTERSAVE);
1762 lprintf(9, "Returning to original room\n");
1763 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1764 getroom(&CC->quickroom, hold_rm);
1766 /* For internet mail, generate delivery instructions
1767 * (Yes, this is recursive! Deal with it!)
1769 if (mailtype == MES_INTERNET) {
1770 lprintf(9, "Generating delivery instructions\n");
1771 instr = mallok(2048);
1773 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1776 SPOOLMIME, newmsgid, time(NULL),
1777 msg->cm_fields['A'], msg->cm_fields['N'],
1780 imsg = mallok(sizeof(struct CtdlMessage));
1781 memset(imsg, 0, sizeof(struct CtdlMessage));
1782 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1783 imsg->cm_anon_type = MES_NORMAL;
1784 imsg->cm_format_type = FMT_RFC822;
1785 imsg->cm_fields['A'] = strdoop("Citadel");
1786 imsg->cm_fields['M'] = instr;
1787 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1788 CtdlFreeMessage(imsg);
1797 * Convenience function for generating small administrative messages.
1799 void quickie_message(char *from, char *to, char *room, char *text)
1801 struct CtdlMessage *msg;
1803 msg = mallok(sizeof(struct CtdlMessage));
1804 memset(msg, 0, sizeof(struct CtdlMessage));
1805 msg->cm_magic = CTDLMESSAGE_MAGIC;
1806 msg->cm_anon_type = MES_NORMAL;
1807 msg->cm_format_type = 0;
1808 msg->cm_fields['A'] = strdoop(from);
1809 msg->cm_fields['O'] = strdoop(room);
1810 msg->cm_fields['N'] = strdoop(NODENAME);
1812 msg->cm_fields['R'] = strdoop(to);
1813 msg->cm_fields['M'] = strdoop(text);
1815 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1816 CtdlFreeMessage(msg);
1817 syslog(LOG_NOTICE, text);
1823 * Back end function used by make_message() and similar functions
1825 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1826 size_t maxlen, /* maximum message length */
1827 char *exist /* if non-null, append to it;
1828 exist is ALWAYS freed */
1832 size_t message_len = 0;
1833 size_t buffer_len = 0;
1837 if (exist == NULL) {
1841 m = reallok(exist, strlen(exist) + 4096);
1842 if (m == NULL) phree(exist);
1845 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1852 /* read in the lines of message text one by one */
1853 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1855 /* strip trailing newline type stuff */
1856 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1857 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1859 linelen = strlen(buf);
1861 /* augment the buffer if we have to */
1862 if ((message_len + linelen + 2) > buffer_len) {
1863 lprintf(9, "realloking\n");
1864 ptr = reallok(m, (buffer_len * 2) );
1865 if (ptr == NULL) { /* flush if can't allocate */
1866 while ( (client_gets(buf)>0) &&
1867 strcmp(buf, terminator)) ;;
1870 buffer_len = (buffer_len * 2);
1872 lprintf(9, "buffer_len is %d\n", buffer_len);
1876 /* Add the new line to the buffer. We avoid using strcat()
1877 * because that would involve traversing the entire message
1878 * after each line, and this function needs to run fast.
1880 strcpy(&m[message_len], buf);
1881 m[message_len + linelen] = '\n';
1882 m[message_len + linelen + 1] = 0;
1883 message_len = message_len + linelen + 1;
1885 /* if we've hit the max msg length, flush the rest */
1886 if (message_len >= maxlen) {
1887 while ( (client_gets(buf)>0)
1888 && strcmp(buf, terminator)) ;;
1899 * Build a binary message to be saved on disk.
1902 struct CtdlMessage *make_message(
1903 struct usersupp *author, /* author's usersupp structure */
1904 char *recipient, /* NULL if it's not mail */
1905 char *room, /* room where it's going */
1906 int type, /* see MES_ types in header file */
1907 int net_type, /* see MES_ types in header file */
1908 int format_type, /* local or remote (see citadel.h) */
1909 char *fake_name) /* who we're masquerading as */
1915 struct CtdlMessage *msg;
1917 msg = mallok(sizeof(struct CtdlMessage));
1918 memset(msg, 0, sizeof(struct CtdlMessage));
1919 msg->cm_magic = CTDLMESSAGE_MAGIC;
1920 msg->cm_anon_type = type;
1921 msg->cm_format_type = format_type;
1923 /* Don't confuse the poor folks if it's not routed mail. */
1924 strcpy(dest_node, "");
1926 /* If net_type is MES_BINARY, split out the destination node. */
1927 if (net_type == MES_BINARY) {
1928 strcpy(dest_node, NODENAME);
1929 for (a = 0; a < strlen(recipient); ++a) {
1930 if (recipient[a] == '@') {
1932 strcpy(dest_node, &recipient[a + 1]);
1937 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1938 if (net_type == MES_INTERNET) {
1939 strcpy(dest_node, "internet");
1942 while (isspace(recipient[strlen(recipient) - 1]))
1943 recipient[strlen(recipient) - 1] = 0;
1945 sprintf(buf, "cit%ld", author->usernum); /* Path */
1946 msg->cm_fields['P'] = strdoop(buf);
1948 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1949 msg->cm_fields['T'] = strdoop(buf);
1951 if (fake_name[0]) /* author */
1952 msg->cm_fields['A'] = strdoop(fake_name);
1954 msg->cm_fields['A'] = strdoop(author->fullname);
1956 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1957 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1959 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1961 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1962 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1964 if (recipient[0] != 0)
1965 msg->cm_fields['R'] = strdoop(recipient);
1966 if (dest_node[0] != 0)
1967 msg->cm_fields['D'] = strdoop(dest_node);
1970 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1971 config.c_maxmsglen, NULL);
1982 * message entry - mode 0 (normal)
1984 void cmd_ent0(char *entargs)
1987 char recipient[SIZ];
1989 int format_type = 0;
1990 char newusername[SIZ];
1991 struct CtdlMessage *msg;
1995 struct usersupp tempUS;
1998 post = extract_int(entargs, 0);
1999 extract(recipient, entargs, 1);
2000 anon_flag = extract_int(entargs, 2);
2001 format_type = extract_int(entargs, 3);
2003 /* first check to make sure the request is valid. */
2005 if (!(CC->logged_in)) {
2006 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
2009 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2010 cprintf("%d Need to be validated to enter ",
2011 ERROR + HIGHER_ACCESS_REQUIRED);
2012 cprintf("(except in %s> to sysop)\n", MAILROOM);
2015 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
2016 cprintf("%d Need net privileges to enter here.\n",
2017 ERROR + HIGHER_ACCESS_REQUIRED);
2020 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
2021 cprintf("%d Sorry, this is a read-only room.\n",
2022 ERROR + HIGHER_ACCESS_REQUIRED);
2029 if (CC->usersupp.axlevel < 6) {
2030 cprintf("%d You don't have permission to masquerade.\n",
2031 ERROR + HIGHER_ACCESS_REQUIRED);
2034 extract(newusername, entargs, 4);
2035 memset(CC->fake_postname, 0, 32);
2036 strcpy(CC->fake_postname, newusername);
2037 cprintf("%d Ok\n", OK);
2040 CC->cs_flags |= CS_POSTING;
2043 if (CC->quickroom.QRflags & QR_MAILBOX) {
2044 if (CC->usersupp.axlevel >= 2) {
2045 strcpy(buf, recipient);
2047 strcpy(buf, "sysop");
2048 e = alias(buf); /* alias and mail type */
2049 if ((buf[0] == 0) || (e == MES_ERROR)) {
2050 cprintf("%d Unknown address - cannot send message.\n",
2051 ERROR + NO_SUCH_USER);
2054 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2055 cprintf("%d Net privileges required for network mail.\n",
2056 ERROR + HIGHER_ACCESS_REQUIRED);
2059 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2060 && ((CC->usersupp.flags & US_INTERNET) == 0)
2061 && (!CC->internal_pgm)) {
2062 cprintf("%d You don't have access to Internet mail.\n",
2063 ERROR + HIGHER_ACCESS_REQUIRED);
2066 if (!strcasecmp(buf, "sysop")) {
2069 else if (e == MES_LOCAL) { /* don't search local file */
2070 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2071 cprintf("%d Can't send mail to yourself!\n",
2072 ERROR + NO_SUCH_USER);
2075 /* Check to make sure the user exists; also get the correct
2076 * upper/lower casing of the name.
2078 a = getuser(&tempUS, buf);
2080 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2083 strcpy(buf, tempUS.fullname);
2088 if (CC->quickroom.QRflags & QR_ANONONLY)
2090 if (CC->quickroom.QRflags & QR_ANONOPT) {
2094 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2097 /* If we're only checking the validity of the request, return
2098 * success without creating the message.
2101 cprintf("%d %s\n", OK, buf);
2105 cprintf("%d send message\n", SEND_LISTING);
2107 /* Read in the message from the client. */
2108 if (CC->fake_postname[0])
2109 msg = make_message(&CC->usersupp, buf,
2110 CC->quickroom.QRname, b, e, format_type,
2112 else if (CC->fake_username[0])
2113 msg = make_message(&CC->usersupp, buf,
2114 CC->quickroom.QRname, b, e, format_type,
2117 msg = make_message(&CC->usersupp, buf,
2118 CC->quickroom.QRname, b, e, format_type, "");
2121 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2122 CtdlFreeMessage(msg);
2123 CC->fake_postname[0] = '\0';
2130 * message entry - mode 3 (raw)
2132 void cmd_ent3(char *entargs)
2138 unsigned char ch, which_field;
2139 struct usersupp tempUS;
2141 struct CtdlMessage *msg;
2144 if (CC->internal_pgm == 0) {
2145 cprintf("%d This command is for internal programs only.\n",
2150 /* See if there's a recipient, but make sure it's a real one */
2151 extract(recp, entargs, 1);
2152 for (a = 0; a < strlen(recp); ++a)
2153 if (!isprint(recp[a]))
2154 strcpy(&recp[a], &recp[a + 1]);
2155 while (isspace(recp[0]))
2156 strcpy(recp, &recp[1]);
2157 while (isspace(recp[strlen(recp) - 1]))
2158 recp[strlen(recp) - 1] = 0;
2160 /* If we're in Mail, check the recipient */
2161 if (strlen(recp) > 0) {
2162 e = alias(recp); /* alias and mail type */
2163 if ((recp[0] == 0) || (e == MES_ERROR)) {
2164 cprintf("%d Unknown address - cannot send message.\n",
2165 ERROR + NO_SUCH_USER);
2168 if (e == MES_LOCAL) {
2169 a = getuser(&tempUS, recp);
2171 cprintf("%d No such user.\n",
2172 ERROR + NO_SUCH_USER);
2178 /* At this point, message has been approved. */
2179 if (extract_int(entargs, 0) == 0) {
2180 cprintf("%d OK to send\n", OK);
2184 msglen = extract_long(entargs, 2);
2185 msg = mallok(sizeof(struct CtdlMessage));
2187 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2191 memset(msg, 0, sizeof(struct CtdlMessage));
2192 tempbuf = mallok(msglen);
2193 if (tempbuf == NULL) {
2194 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2199 cprintf("%d %ld\n", SEND_BINARY, msglen);
2201 client_read(&ch, 1); /* 0xFF magic number */
2202 msg->cm_magic = CTDLMESSAGE_MAGIC;
2203 client_read(&ch, 1); /* anon type */
2204 msg->cm_anon_type = ch;
2205 client_read(&ch, 1); /* format type */
2206 msg->cm_format_type = ch;
2207 msglen = msglen - 3;
2209 while (msglen > 0) {
2210 client_read(&which_field, 1);
2211 if (!isalpha(which_field)) valid_msg = 0;
2215 client_read(&ch, 1);
2217 a = strlen(tempbuf);
2220 } while ( (ch != 0) && (msglen > 0) );
2222 msg->cm_fields[which_field] = strdoop(tempbuf);
2225 msg->cm_flags = CM_SKIP_HOOKS;
2226 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2227 CtdlFreeMessage(msg);
2233 * API function to delete messages which match a set of criteria
2234 * (returns the actual number of messages deleted)
2236 int CtdlDeleteMessages(char *room_name, /* which room */
2237 long dmsgnum, /* or "0" for any */
2238 char *content_type /* or "" for any */
2242 struct quickroom qrbuf;
2243 struct cdbdata *cdbfr;
2244 long *msglist = NULL;
2247 int num_deleted = 0;
2249 struct SuppMsgInfo smi;
2251 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2252 room_name, dmsgnum, content_type);
2254 /* get room record, obtaining a lock... */
2255 if (lgetroom(&qrbuf, room_name) != 0) {
2256 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2258 return (0); /* room not found */
2260 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2262 if (cdbfr != NULL) {
2263 msglist = mallok(cdbfr->len);
2264 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2265 num_msgs = cdbfr->len / sizeof(long);
2269 for (i = 0; i < num_msgs; ++i) {
2272 /* Set/clear a bit for each criterion */
2274 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2275 delete_this |= 0x01;
2277 if (strlen(content_type) == 0) {
2278 delete_this |= 0x02;
2280 GetSuppMsgInfo(&smi, msglist[i]);
2281 if (!strcasecmp(smi.smi_content_type,
2283 delete_this |= 0x02;
2287 /* Delete message only if all bits are set */
2288 if (delete_this == 0x03) {
2289 AdjRefCount(msglist[i], -1);
2295 num_msgs = sort_msglist(msglist, num_msgs);
2296 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2297 msglist, (num_msgs * sizeof(long)));
2299 qrbuf.QRhighest = msglist[num_msgs - 1];
2303 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2304 return (num_deleted);
2310 * Delete message from current room
2312 void cmd_dele(char *delstr)
2317 getuser(&CC->usersupp, CC->curr_user);
2318 if ((CC->usersupp.axlevel < 6)
2319 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2320 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2321 && (!(CC->internal_pgm))) {
2322 cprintf("%d Higher access required.\n",
2323 ERROR + HIGHER_ACCESS_REQUIRED);
2326 delnum = extract_long(delstr, 0);
2328 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2331 cprintf("%d %d message%s deleted.\n", OK,
2332 num_deleted, ((num_deleted != 1) ? "s" : ""));
2334 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2340 * move or copy a message to another room
2342 void cmd_move(char *args)
2346 struct quickroom qtemp;
2350 num = extract_long(args, 0);
2351 extract(targ, args, 1);
2352 targ[ROOMNAMELEN - 1] = 0;
2353 is_copy = extract_int(args, 2);
2355 getuser(&CC->usersupp, CC->curr_user);
2356 if ((CC->usersupp.axlevel < 6)
2357 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2358 cprintf("%d Higher access required.\n",
2359 ERROR + HIGHER_ACCESS_REQUIRED);
2363 if (getroom(&qtemp, targ) != 0) {
2364 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2368 err = CtdlSaveMsgPointerInRoom(targ, num,
2369 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2371 cprintf("%d Cannot store message in %s: error %d\n",
2376 /* Now delete the message from the source room,
2377 * if this is a 'move' rather than a 'copy' operation.
2379 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2381 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2387 * GetSuppMsgInfo() - Get the supplementary record for a message
2389 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2392 struct cdbdata *cdbsmi;
2395 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2396 smibuf->smi_msgnum = msgnum;
2397 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2399 /* Use the negative of the message number for its supp record index */
2400 TheIndex = (0L - msgnum);
2402 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2403 if (cdbsmi == NULL) {
2404 return; /* record not found; go with defaults */
2406 memcpy(smibuf, cdbsmi->ptr,
2407 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2408 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2415 * PutSuppMsgInfo() - (re)write supplementary record for a message
2417 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2421 /* Use the negative of the message number for its supp record index */
2422 TheIndex = (0L - smibuf->smi_msgnum);
2424 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2425 smibuf->smi_msgnum, smibuf->smi_refcount);
2427 cdb_store(CDB_MSGMAIN,
2428 &TheIndex, sizeof(long),
2429 smibuf, sizeof(struct SuppMsgInfo));
2434 * AdjRefCount - change the reference count for a message;
2435 * delete the message if it reaches zero
2437 void AdjRefCount(long msgnum, int incr)
2440 struct SuppMsgInfo smi;
2443 /* This is a *tight* critical section; please keep it that way, as
2444 * it may get called while nested in other critical sections.
2445 * Complicating this any further will surely cause deadlock!
2447 begin_critical_section(S_SUPPMSGMAIN);
2448 GetSuppMsgInfo(&smi, msgnum);
2449 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2450 msgnum, smi.smi_refcount);
2451 smi.smi_refcount += incr;
2452 PutSuppMsgInfo(&smi);
2453 end_critical_section(S_SUPPMSGMAIN);
2454 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2455 msgnum, smi.smi_refcount);
2457 /* If the reference count is now zero, delete the message
2458 * (and its supplementary record as well).
2460 if (smi.smi_refcount == 0) {
2461 lprintf(9, "Deleting message <%ld>\n", msgnum);
2463 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2464 delnum = (0L - msgnum);
2465 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2470 * Write a generic object to this room
2472 * Note: this could be much more efficient. Right now we use two temporary
2473 * files, and still pull the message into memory as with all others.
2475 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2476 char *content_type, /* MIME type of this object */
2477 char *tempfilename, /* Where to fetch it from */
2478 struct usersupp *is_mailbox, /* Mailbox room? */
2479 int is_binary, /* Is encoding necessary? */
2480 int is_unique, /* Del others of this type? */
2481 unsigned int flags /* Internal save flags */
2486 char filename[PATH_MAX];
2489 struct quickroom qrbuf;
2490 char roomname[ROOMNAMELEN];
2491 struct CtdlMessage *msg;
2494 if (is_mailbox != NULL)
2495 MailboxName(roomname, is_mailbox, req_room);
2497 safestrncpy(roomname, req_room, sizeof(roomname));
2498 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2500 strcpy(filename, tmpnam(NULL));
2501 fp = fopen(filename, "w");
2505 tempfp = fopen(tempfilename, "r");
2506 if (tempfp == NULL) {
2512 fprintf(fp, "Content-type: %s\n", content_type);
2513 lprintf(9, "Content-type: %s\n", content_type);
2515 if (is_binary == 0) {
2516 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2517 while (ch = getc(tempfp), ch > 0)
2523 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2526 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2527 tempfilename, filename);
2531 lprintf(9, "Allocating\n");
2532 msg = mallok(sizeof(struct CtdlMessage));
2533 memset(msg, 0, sizeof(struct CtdlMessage));
2534 msg->cm_magic = CTDLMESSAGE_MAGIC;
2535 msg->cm_anon_type = MES_NORMAL;
2536 msg->cm_format_type = 4;
2537 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2538 msg->cm_fields['O'] = strdoop(req_room);
2539 msg->cm_fields['N'] = strdoop(config.c_nodename);
2540 msg->cm_fields['H'] = strdoop(config.c_humannode);
2541 msg->cm_flags = flags;
2543 lprintf(9, "Loading\n");
2544 fp = fopen(filename, "rb");
2545 fseek(fp, 0L, SEEK_END);
2548 msg->cm_fields['M'] = mallok(len);
2549 fread(msg->cm_fields['M'], len, 1, fp);
2553 /* Create the requested room if we have to. */
2554 if (getroom(&qrbuf, roomname) != 0) {
2555 create_room(roomname,
2556 ( (is_mailbox != NULL) ? 4 : 3 ),
2559 /* If the caller specified this object as unique, delete all
2560 * other objects of this type that are currently in the room.
2563 lprintf(9, "Deleted %d other msgs of this type\n",
2564 CtdlDeleteMessages(roomname, 0L, content_type));
2566 /* Now write the data */
2567 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2568 CtdlFreeMessage(msg);
2576 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2577 config_msgnum = msgnum;
2581 char *CtdlGetSysConfig(char *sysconfname) {
2582 char hold_rm[ROOMNAMELEN];
2585 struct CtdlMessage *msg;
2588 strcpy(hold_rm, CC->quickroom.QRname);
2589 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2590 getroom(&CC->quickroom, hold_rm);
2595 /* We want the last (and probably only) config in this room */
2596 begin_critical_section(S_CONFIG);
2597 config_msgnum = (-1L);
2598 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2599 CtdlGetSysConfigBackend, NULL);
2600 msgnum = config_msgnum;
2601 end_critical_section(S_CONFIG);
2607 msg = CtdlFetchMessage(msgnum);
2609 conf = strdoop(msg->cm_fields['M']);
2610 CtdlFreeMessage(msg);
2617 getroom(&CC->quickroom, hold_rm);
2619 lprintf(9, "eggstracting...\n");
2620 if (conf != NULL) do {
2621 extract_token(buf, conf, 0, '\n');
2622 lprintf(9, "eggstracted <%s>\n", buf);
2623 strcpy(conf, &conf[strlen(buf)+1]);
2624 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2629 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2630 char temp[PATH_MAX];
2633 strcpy(temp, tmpnam(NULL));
2635 fp = fopen(temp, "w");
2636 if (fp == NULL) return;
2637 fprintf(fp, "%s", sysconfdata);
2640 /* this handy API function does all the work for us */
2641 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);