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[256];
851 char *nl; /* newline string */
853 /* buffers needed for RFC822 translation */
863 sprintf(mid, "%ld", msg_num);
864 nl = (crlf ? "\r\n" : "\n");
866 /* Are we downloading a MIME component? */
867 if (mode == MT_DOWNLOAD) {
868 if (TheMessage->cm_format_type != FMT_RFC822) {
870 cprintf("%d This is not a MIME message.\n",
872 } else if (CC->download_fp != NULL) {
873 if (do_proto) cprintf(
874 "%d You already have a download open.\n",
877 /* Parse the message text component */
878 mptr = TheMessage->cm_fields['M'];
879 mime_parser(mptr, NULL, *mime_download, NULL, 0);
880 /* If there's no file open by this time, the requested
881 * section wasn't found, so print an error
883 if (CC->download_fp == NULL) {
884 if (do_proto) cprintf(
885 "%d Section %s not found.\n",
886 ERROR + FILE_NOT_FOUND,
890 CtdlFreeMessage(TheMessage);
891 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
894 /* now for the user-mode message reading loops */
895 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
897 /* Tell the client which format type we're using. If this is a
898 * MIME message, *lie* about it and tell the user it's fixed-format.
900 if (mode == MT_CITADEL) {
901 if (TheMessage->cm_format_type == FMT_RFC822) {
902 if (do_proto) cprintf("type=1\n");
905 if (do_proto) cprintf("type=%d\n",
906 TheMessage->cm_format_type);
910 /* nhdr=yes means that we're only displaying headers, no body */
911 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
912 if (do_proto) cprintf("nhdr=yes\n");
915 /* begin header processing loop for Citadel message format */
917 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
919 strcpy(display_name, "<unknown>");
920 if (TheMessage->cm_fields['A']) {
921 strcpy(buf, TheMessage->cm_fields['A']);
922 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
923 if (TheMessage->cm_anon_type == MES_ANON)
924 strcpy(display_name, "****");
925 else if (TheMessage->cm_anon_type == MES_AN2)
926 strcpy(display_name, "anonymous");
928 strcpy(display_name, buf);
930 && ((TheMessage->cm_anon_type == MES_ANON)
931 || (TheMessage->cm_anon_type == MES_AN2))) {
932 sprintf(&display_name[strlen(display_name)],
937 strcpy(allkeys, FORDER);
938 for (i=0; i<strlen(allkeys); ++i) {
939 k = (int) allkeys[i];
941 if (TheMessage->cm_fields[k] != NULL) {
943 if (do_proto) cprintf("%s=%s\n",
948 if (do_proto) cprintf("%s=%s\n",
950 TheMessage->cm_fields[k]
959 /* begin header processing loop for RFC822 transfer format */
964 strcpy(snode, NODENAME);
965 strcpy(lnode, HUMANNODE);
966 if (mode == MT_RFC822) {
967 cprintf("X-UIDL: %ld%s", msg_num, nl);
968 for (i = 0; i < 256; ++i) {
969 if (TheMessage->cm_fields[i]) {
970 mptr = TheMessage->cm_fields[i];
977 cprintf("Path: %s%s", mptr, nl);
980 cprintf("Subject: %s%s", mptr, nl);
986 cprintf("X-Citadel-Room: %s%s",
991 cprintf("To: %s%s", mptr, nl);
993 datestring(datestamp, atol(mptr),
995 cprintf("Date: %s%s", datestamp, nl);
1001 for (i=0; i<strlen(suser); ++i) {
1002 suser[i] = tolower(suser[i]);
1003 if (!isalnum(suser[i])) suser[i]='_';
1006 if (mode == MT_RFC822) {
1007 if (!strcasecmp(snode, NODENAME)) {
1008 strcpy(snode, FQDN);
1011 /* Construct a fun message id */
1012 cprintf("Message-ID: <%s", mid);
1013 if (strchr(mid, '@')==NULL) {
1014 cprintf("@%s", snode);
1018 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1020 if (strlen(fuser) > 0) {
1021 cprintf("From: %s (%s)%s", fuser, luser, nl);
1024 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1027 cprintf("Organization: %s%s", lnode, nl);
1030 /* end header processing loop ... at this point, we're in the text */
1032 mptr = TheMessage->cm_fields['M'];
1034 /* Tell the client about the MIME parts in this message */
1035 if (TheMessage->cm_format_type == FMT_RFC822) {
1036 if (mode == MT_CITADEL) {
1037 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1039 else if (mode == MT_MIME) { /* list parts only */
1040 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1041 if (do_proto) cprintf("000\n");
1042 CtdlFreeMessage(TheMessage);
1045 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1046 /* FIXME ... we have to put some code in here to avoid
1047 * printing duplicate header information when both
1048 * Citadel and RFC822 headers exist. Preference should
1049 * probably be given to the RFC822 headers.
1051 while (ch=*(mptr++), ch!=0) {
1053 else if (ch==10) cprintf("%s", nl);
1054 else cprintf("%c", ch);
1056 if (do_proto) cprintf("000\n");
1057 CtdlFreeMessage(TheMessage);
1063 if (do_proto) cprintf("000\n");
1064 CtdlFreeMessage(TheMessage);
1068 /* signify start of msg text */
1069 if (mode == MT_CITADEL)
1070 if (do_proto) cprintf("text\n");
1071 if (mode == MT_RFC822) {
1072 if (TheMessage->cm_fields['U'] == NULL) {
1073 cprintf("Subject: (no subject)%s", nl);
1078 /* If the format type on disk is 1 (fixed-format), then we want
1079 * everything to be output completely literally ... regardless of
1080 * what message transfer format is in use.
1082 if (TheMessage->cm_format_type == FMT_FIXED) {
1084 while (ch = *mptr++, ch > 0) {
1087 if ((ch == 10) || (strlen(buf) > 250)) {
1088 cprintf("%s%s", buf, nl);
1091 buf[strlen(buf) + 1] = 0;
1092 buf[strlen(buf)] = ch;
1095 if (strlen(buf) > 0)
1096 cprintf("%s%s", buf, nl);
1099 /* If the message on disk is format 0 (Citadel vari-format), we
1100 * output using the formatter at 80 columns. This is the final output
1101 * form if the transfer format is RFC822, but if the transfer format
1102 * is Citadel proprietary, it'll still work, because the indentation
1103 * for new paragraphs is correct and the client will reformat the
1104 * message to the reader's screen width.
1106 if (TheMessage->cm_format_type == FMT_CITADEL) {
1107 memfmout(80, mptr, 0, nl);
1110 /* If the message on disk is format 4 (MIME), we've gotta hand it
1111 * off to the MIME parser. The client has already been told that
1112 * this message is format 1 (fixed format), so the callback function
1113 * we use will display those parts as-is.
1115 if (TheMessage->cm_format_type == FMT_RFC822) {
1116 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1117 memset(ma, 0, sizeof(struct ma_info));
1118 mime_parser(mptr, NULL, *fixed_output, NULL, 0);
1121 /* now we're done */
1122 if (do_proto) cprintf("000\n");
1129 * display a message (mode 0 - Citadel proprietary)
1131 void cmd_msg0(char *cmdbuf)
1134 int headers_only = 0;
1136 msgid = extract_long(cmdbuf, 0);
1137 headers_only = extract_int(cmdbuf, 1);
1139 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1145 * display a message (mode 2 - RFC822)
1147 void cmd_msg2(char *cmdbuf)
1150 int headers_only = 0;
1152 msgid = extract_long(cmdbuf, 0);
1153 headers_only = extract_int(cmdbuf, 1);
1155 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1161 * display a message (mode 3 - IGnet raw format - internal programs only)
1163 void cmd_msg3(char *cmdbuf)
1166 struct CtdlMessage *msg;
1169 if (CC->internal_pgm == 0) {
1170 cprintf("%d This command is for internal programs only.\n",
1175 msgnum = extract_long(cmdbuf, 0);
1176 msg = CtdlFetchMessage(msgnum);
1178 cprintf("%d Message %ld not found.\n",
1183 serialize_message(&smr, msg);
1184 CtdlFreeMessage(msg);
1187 cprintf("%d Unable to serialize message\n",
1188 ERROR+INTERNAL_ERROR);
1192 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1193 client_write(smr.ser, smr.len);
1200 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1202 void cmd_msg4(char *cmdbuf)
1206 msgid = extract_long(cmdbuf, 0);
1207 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1211 * Open a component of a MIME message as a download file
1213 void cmd_opna(char *cmdbuf)
1217 CtdlAllocUserData(SYM_DESIRED_SECTION, 256);
1219 msgid = extract_long(cmdbuf, 0);
1220 extract(desired_section, cmdbuf, 1);
1222 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1227 * Save a message pointer into a specified room
1228 * (Returns 0 for success, nonzero for failure)
1229 * roomname may be NULL to use the current room
1231 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1233 char hold_rm[ROOMNAMELEN];
1234 struct cdbdata *cdbfr;
1237 long highest_msg = 0L;
1238 struct CtdlMessage *msg = NULL;
1240 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1241 roomname, msgid, flags);
1243 strcpy(hold_rm, CC->quickroom.QRname);
1245 /* We may need to check to see if this message is real */
1246 if ( (flags & SM_VERIFY_GOODNESS)
1247 || (flags & SM_DO_REPL_CHECK)
1249 msg = CtdlFetchMessage(msgid);
1250 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1253 /* Perform replication checks if necessary */
1254 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1256 if (getroom(&CC->quickroom,
1257 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1259 lprintf(9, "No such room <%s>\n", roomname);
1260 if (msg != NULL) CtdlFreeMessage(msg);
1261 return(ERROR + ROOM_NOT_FOUND);
1264 if (ReplicationChecks(msg) != 0) {
1265 getroom(&CC->quickroom, hold_rm);
1266 if (msg != NULL) CtdlFreeMessage(msg);
1267 lprintf(9, "Did replication, and newer exists\n");
1272 /* Now the regular stuff */
1273 if (lgetroom(&CC->quickroom,
1274 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1276 lprintf(9, "No such room <%s>\n", roomname);
1277 if (msg != NULL) CtdlFreeMessage(msg);
1278 return(ERROR + ROOM_NOT_FOUND);
1281 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1282 if (cdbfr == NULL) {
1286 msglist = mallok(cdbfr->len);
1287 if (msglist == NULL)
1288 lprintf(3, "ERROR malloc msglist!\n");
1289 num_msgs = cdbfr->len / sizeof(long);
1290 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1295 /* Make sure the message doesn't already exist in this room. It
1296 * is absolutely taboo to have more than one reference to the same
1297 * message in a room.
1299 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1300 if (msglist[i] == msgid) {
1301 lputroom(&CC->quickroom); /* unlock the room */
1302 getroom(&CC->quickroom, hold_rm);
1303 if (msg != NULL) CtdlFreeMessage(msg);
1304 return(ERROR + ALREADY_EXISTS);
1308 /* Now add the new message */
1310 msglist = reallok(msglist,
1311 (num_msgs * sizeof(long)));
1313 if (msglist == NULL) {
1314 lprintf(3, "ERROR: can't realloc message list!\n");
1316 msglist[num_msgs - 1] = msgid;
1318 /* Sort the message list, so all the msgid's are in order */
1319 num_msgs = sort_msglist(msglist, num_msgs);
1321 /* Determine the highest message number */
1322 highest_msg = msglist[num_msgs - 1];
1324 /* Write it back to disk. */
1325 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1326 msglist, num_msgs * sizeof(long));
1328 /* Free up the memory we used. */
1331 /* Update the highest-message pointer and unlock the room. */
1332 CC->quickroom.QRhighest = highest_msg;
1333 lputroom(&CC->quickroom);
1334 getroom(&CC->quickroom, hold_rm);
1336 /* Bump the reference count for this message. */
1337 if ((flags & SM_DONT_BUMP_REF)==0) {
1338 AdjRefCount(msgid, +1);
1341 /* Return success. */
1342 if (msg != NULL) CtdlFreeMessage(msg);
1349 * Message base operation to send a message to the master file
1350 * (returns new message number)
1352 * This is the back end for CtdlSaveMsg() and should not be directly
1353 * called by server-side modules.
1356 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1357 FILE *save_a_copy) /* save a copy to disk? */
1364 /* Get a new message number */
1365 newmsgid = get_new_message_number();
1366 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1368 /* Generate an ID if we don't have one already */
1369 if (msg->cm_fields['I']==NULL) {
1370 msg->cm_fields['I'] = strdoop(msgidbuf);
1373 serialize_message(&smr, msg);
1376 cprintf("%d Unable to serialize message\n",
1377 ERROR+INTERNAL_ERROR);
1381 /* Write our little bundle of joy into the message base */
1382 begin_critical_section(S_MSGMAIN);
1383 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1384 smr.ser, smr.len) < 0) {
1385 lprintf(2, "Can't store message\n");
1390 end_critical_section(S_MSGMAIN);
1392 /* If the caller specified that a copy should be saved to a particular
1393 * file handle, do that now too.
1395 if (save_a_copy != NULL) {
1396 fwrite(smr.ser, smr.len, 1, save_a_copy);
1399 /* Free the memory we used for the serialized message */
1402 /* Return the *local* message ID to the caller
1403 * (even if we're storing an incoming network message)
1411 * Serialize a struct CtdlMessage into the format used on disk and network.
1413 * This function loads up a "struct ser_ret" (defined in server.h) which
1414 * contains the length of the serialized message and a pointer to the
1415 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1417 void serialize_message(struct ser_ret *ret, /* return values */
1418 struct CtdlMessage *msg) /* unserialized msg */
1422 static char *forder = FORDER;
1424 if (is_valid_message(msg) == 0) return; /* self check */
1427 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1428 ret->len = ret->len +
1429 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1431 lprintf(9, "calling malloc(%d)\n", ret->len);
1432 ret->ser = mallok(ret->len);
1433 if (ret->ser == NULL) {
1439 ret->ser[1] = msg->cm_anon_type;
1440 ret->ser[2] = msg->cm_format_type;
1443 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1444 ret->ser[wlen++] = (char)forder[i];
1445 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1446 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1448 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1457 * Back end for the ReplicationChecks() function
1459 void check_repl(long msgnum, void *userdata) {
1460 struct CtdlMessage *msg;
1461 time_t timestamp = (-1L);
1463 lprintf(9, "check_repl() found message %ld\n", msgnum);
1464 msg = CtdlFetchMessage(msgnum);
1465 if (msg == NULL) return;
1466 if (msg->cm_fields['T'] != NULL) {
1467 timestamp = atol(msg->cm_fields['T']);
1469 CtdlFreeMessage(msg);
1471 if (timestamp > msg_repl->highest) {
1472 msg_repl->highest = timestamp; /* newer! */
1473 lprintf(9, "newer!\n");
1476 lprintf(9, "older!\n");
1478 /* Existing isn't newer? Then delete the old one(s). */
1479 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1484 * Check to see if any messages already exist which carry the same Extended ID
1488 * -> With older timestamps: delete them and return 0. Message will be saved.
1489 * -> With newer timestamps: return 1. Message save will be aborted.
1491 int ReplicationChecks(struct CtdlMessage *msg) {
1492 struct CtdlMessage *template;
1495 lprintf(9, "ReplicationChecks() started\n");
1496 /* No extended id? Don't do anything. */
1497 if (msg->cm_fields['E'] == NULL) return 0;
1498 if (strlen(msg->cm_fields['E']) == 0) return 0;
1499 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1501 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1502 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1503 msg_repl->highest = atol(msg->cm_fields['T']);
1505 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1506 memset(template, 0, sizeof(struct CtdlMessage));
1507 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1509 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1512 /* If a newer message exists with the same Extended ID, abort
1515 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1519 CtdlFreeMessage(template);
1520 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1528 * Save a message to disk
1530 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1531 char *rec, /* Recipient (mail) */
1532 char *force, /* force a particular room? */
1533 int supplied_mailtype) /* local or remote type */
1536 char hold_rm[ROOMNAMELEN];
1537 char actual_rm[ROOMNAMELEN];
1538 char force_room[ROOMNAMELEN];
1539 char content_type[256]; /* We have to learn this */
1540 char recipient[256];
1543 struct usersupp userbuf;
1545 struct SuppMsgInfo smi;
1546 FILE *network_fp = NULL;
1547 static int seqnum = 1;
1548 struct CtdlMessage *imsg;
1552 lprintf(9, "CtdlSaveMsg() called\n");
1553 if (is_valid_message(msg) == 0) return(-1); /* self check */
1554 mailtype = supplied_mailtype;
1556 /* If this message has no timestamp, we take the liberty of
1557 * giving it one, right now.
1559 if (msg->cm_fields['T'] == NULL) {
1560 lprintf(9, "Generating timestamp\n");
1561 sprintf(aaa, "%ld", time(NULL));
1562 msg->cm_fields['T'] = strdoop(aaa);
1565 /* If this message has no path, we generate one.
1567 if (msg->cm_fields['P'] == NULL) {
1568 lprintf(9, "Generating path\n");
1569 if (msg->cm_fields['A'] != NULL) {
1570 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1571 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1572 if (isspace(msg->cm_fields['P'][a])) {
1573 msg->cm_fields['P'][a] = ' ';
1578 msg->cm_fields['P'] = strdoop("unknown");
1582 strcpy(force_room, force);
1584 /* Strip non-printable characters out of the recipient name */
1585 lprintf(9, "Checking recipient (if present)\n");
1586 strcpy(recipient, rec);
1587 for (a = 0; a < strlen(recipient); ++a)
1588 if (!isprint(recipient[a]))
1589 strcpy(&recipient[a], &recipient[a + 1]);
1591 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1592 for (a=0; a<strlen(recipient); ++a) {
1593 if (recipient[a] == '@') {
1594 if (CtdlHostAlias(&recipient[a+1])
1595 == hostalias_localhost) {
1597 lprintf(7, "Changed to <%s>\n", recipient);
1598 mailtype = MES_LOCAL;
1603 lprintf(9, "Recipient is <%s>\n", recipient);
1605 /* Learn about what's inside, because it's what's inside that counts */
1606 lprintf(9, "Learning what's inside\n");
1607 if (msg->cm_fields['M'] == NULL) {
1608 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1611 switch (msg->cm_format_type) {
1613 strcpy(content_type, "text/x-citadel-variformat");
1616 strcpy(content_type, "text/plain");
1619 strcpy(content_type, "text/plain");
1620 /* advance past header fields */
1621 mptr = msg->cm_fields['M'];
1624 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1625 safestrncpy(content_type, mptr,
1626 sizeof(content_type));
1627 strcpy(content_type, &content_type[14]);
1628 for (a = 0; a < strlen(content_type); ++a)
1629 if ((content_type[a] == ';')
1630 || (content_type[a] == ' ')
1631 || (content_type[a] == 13)
1632 || (content_type[a] == 10))
1633 content_type[a] = 0;
1640 /* Goto the correct room */
1641 lprintf(9, "Switching rooms\n");
1642 strcpy(hold_rm, CC->quickroom.QRname);
1643 strcpy(actual_rm, CC->quickroom.QRname);
1645 /* If the user is a twit, move to the twit room for posting */
1646 lprintf(9, "Handling twit stuff\n");
1648 if (CC->usersupp.axlevel == 2) {
1649 strcpy(hold_rm, actual_rm);
1650 strcpy(actual_rm, config.c_twitroom);
1654 /* ...or if this message is destined for Aide> then go there. */
1655 if (strlen(force_room) > 0) {
1656 strcpy(actual_rm, force_room);
1659 lprintf(9, "Possibly relocating\n");
1660 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1661 getroom(&CC->quickroom, actual_rm);
1665 * If this message has no O (room) field, generate one.
1667 if (msg->cm_fields['O'] == NULL) {
1668 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1671 /* Perform "before save" hooks (aborting if any return nonzero) */
1672 lprintf(9, "Performing before-save hooks\n");
1673 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1675 /* If this message has an Extended ID, perform replication checks */
1676 lprintf(9, "Performing replication checks\n");
1677 if (ReplicationChecks(msg) > 0) return(-1);
1679 /* Network mail - send a copy to the network program. */
1680 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1681 lprintf(9, "Sending network spool\n");
1682 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1683 (long) getpid(), CC->cs_pid, ++seqnum);
1684 lprintf(9, "Saving a copy to %s\n", aaa);
1685 network_fp = fopen(aaa, "ab+");
1686 if (network_fp == NULL)
1687 lprintf(2, "ERROR: %s\n", strerror(errno));
1690 /* Save it to disk */
1691 lprintf(9, "Saving to disk\n");
1692 newmsgid = send_message(msg, network_fp);
1693 if (network_fp != NULL) {
1695 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1698 if (newmsgid <= 0L) return(-1);
1700 /* Write a supplemental message info record. This doesn't have to
1701 * be a critical section because nobody else knows about this message
1704 lprintf(9, "Creating SuppMsgInfo record\n");
1705 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1706 smi.smi_msgnum = newmsgid;
1707 smi.smi_refcount = 0;
1708 safestrncpy(smi.smi_content_type, content_type, 64);
1709 PutSuppMsgInfo(&smi);
1711 /* Now figure out where to store the pointers */
1712 lprintf(9, "Storing pointers\n");
1714 /* If this is being done by the networker delivering a private
1715 * message, we want to BYPASS saving the sender's copy (because there
1716 * is no local sender; it would otherwise go to the Trashcan).
1718 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1719 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1720 lprintf(3, "ERROR saving message pointer!\n");
1721 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1725 /* For internet mail, drop a copy in the outbound queue room */
1726 if (mailtype == MES_INTERNET) {
1727 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1730 /* Bump this user's messages posted counter. */
1731 lprintf(9, "Updating user\n");
1732 lgetuser(&CC->usersupp, CC->curr_user);
1733 CC->usersupp.posted = CC->usersupp.posted + 1;
1734 lputuser(&CC->usersupp);
1736 /* If this is private, local mail, make a copy in the
1737 * recipient's mailbox and bump the reference count.
1739 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1740 if (getuser(&userbuf, recipient) == 0) {
1741 lprintf(9, "Delivering private mail\n");
1742 MailboxName(actual_rm, &userbuf, MAILROOM);
1743 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1746 lprintf(9, "No user <%s>, saving in %s> instead\n",
1747 recipient, AIDEROOM);
1748 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1752 /* Perform "after save" hooks */
1753 lprintf(9, "Performing after-save hooks\n");
1754 PerformMessageHooks(msg, EVT_AFTERSAVE);
1757 lprintf(9, "Returning to original room\n");
1758 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1759 getroom(&CC->quickroom, hold_rm);
1761 /* For internet mail, generate delivery instructions
1762 * (Yes, this is recursive! Deal with it!)
1764 if (mailtype == MES_INTERNET) {
1765 lprintf(9, "Generating delivery instructions\n");
1766 instr = mallok(2048);
1768 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1771 SPOOLMIME, newmsgid, time(NULL),
1772 msg->cm_fields['A'], msg->cm_fields['N'],
1775 imsg = mallok(sizeof(struct CtdlMessage));
1776 memset(imsg, 0, sizeof(struct CtdlMessage));
1777 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1778 imsg->cm_anon_type = MES_NORMAL;
1779 imsg->cm_format_type = FMT_RFC822;
1780 imsg->cm_fields['A'] = strdoop("Citadel");
1781 imsg->cm_fields['M'] = instr;
1782 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1783 CtdlFreeMessage(imsg);
1792 * Convenience function for generating small administrative messages.
1794 void quickie_message(char *from, char *to, char *room, char *text)
1796 struct CtdlMessage *msg;
1798 msg = mallok(sizeof(struct CtdlMessage));
1799 memset(msg, 0, sizeof(struct CtdlMessage));
1800 msg->cm_magic = CTDLMESSAGE_MAGIC;
1801 msg->cm_anon_type = MES_NORMAL;
1802 msg->cm_format_type = 0;
1803 msg->cm_fields['A'] = strdoop(from);
1804 msg->cm_fields['O'] = strdoop(room);
1805 msg->cm_fields['N'] = strdoop(NODENAME);
1807 msg->cm_fields['R'] = strdoop(to);
1808 msg->cm_fields['M'] = strdoop(text);
1810 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1811 CtdlFreeMessage(msg);
1812 syslog(LOG_NOTICE, text);
1818 * Back end function used by make_message() and similar functions
1820 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1821 size_t maxlen, /* maximum message length */
1822 char *exist /* if non-null, append to it;
1823 exist is ALWAYS freed */
1827 size_t message_len = 0;
1828 size_t buffer_len = 0;
1832 if (exist == NULL) {
1836 m = reallok(exist, strlen(exist) + 4096);
1837 if (m == NULL) phree(exist);
1840 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1847 /* read in the lines of message text one by one */
1848 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1850 /* strip trailing newline type stuff */
1851 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1852 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1854 linelen = strlen(buf);
1856 /* augment the buffer if we have to */
1857 if ((message_len + linelen + 2) > buffer_len) {
1858 lprintf(9, "realloking\n");
1859 ptr = reallok(m, (buffer_len * 2) );
1860 if (ptr == NULL) { /* flush if can't allocate */
1861 while ( (client_gets(buf)>0) &&
1862 strcmp(buf, terminator)) ;;
1865 buffer_len = (buffer_len * 2);
1867 lprintf(9, "buffer_len is %d\n", buffer_len);
1871 /* Add the new line to the buffer. We avoid using strcat()
1872 * because that would involve traversing the entire message
1873 * after each line, and this function needs to run fast.
1875 strcpy(&m[message_len], buf);
1876 m[message_len + linelen] = '\n';
1877 m[message_len + linelen + 1] = 0;
1878 message_len = message_len + linelen + 1;
1880 /* if we've hit the max msg length, flush the rest */
1881 if (message_len >= maxlen) {
1882 while ( (client_gets(buf)>0)
1883 && strcmp(buf, terminator)) ;;
1894 * Build a binary message to be saved on disk.
1897 struct CtdlMessage *make_message(
1898 struct usersupp *author, /* author's usersupp structure */
1899 char *recipient, /* NULL if it's not mail */
1900 char *room, /* room where it's going */
1901 int type, /* see MES_ types in header file */
1902 int net_type, /* see MES_ types in header file */
1903 int format_type, /* local or remote (see citadel.h) */
1904 char *fake_name) /* who we're masquerading as */
1910 struct CtdlMessage *msg;
1912 msg = mallok(sizeof(struct CtdlMessage));
1913 memset(msg, 0, sizeof(struct CtdlMessage));
1914 msg->cm_magic = CTDLMESSAGE_MAGIC;
1915 msg->cm_anon_type = type;
1916 msg->cm_format_type = format_type;
1918 /* Don't confuse the poor folks if it's not routed mail. */
1919 strcpy(dest_node, "");
1921 /* If net_type is MES_BINARY, split out the destination node. */
1922 if (net_type == MES_BINARY) {
1923 strcpy(dest_node, NODENAME);
1924 for (a = 0; a < strlen(recipient); ++a) {
1925 if (recipient[a] == '@') {
1927 strcpy(dest_node, &recipient[a + 1]);
1932 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1933 if (net_type == MES_INTERNET) {
1934 strcpy(dest_node, "internet");
1937 while (isspace(recipient[strlen(recipient) - 1]))
1938 recipient[strlen(recipient) - 1] = 0;
1940 sprintf(buf, "cit%ld", author->usernum); /* Path */
1941 msg->cm_fields['P'] = strdoop(buf);
1943 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1944 msg->cm_fields['T'] = strdoop(buf);
1946 if (fake_name[0]) /* author */
1947 msg->cm_fields['A'] = strdoop(fake_name);
1949 msg->cm_fields['A'] = strdoop(author->fullname);
1951 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1952 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1954 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1956 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1957 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1959 if (recipient[0] != 0)
1960 msg->cm_fields['R'] = strdoop(recipient);
1961 if (dest_node[0] != 0)
1962 msg->cm_fields['D'] = strdoop(dest_node);
1965 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1966 config.c_maxmsglen, NULL);
1977 * message entry - mode 0 (normal)
1979 void cmd_ent0(char *entargs)
1982 char recipient[256];
1984 int format_type = 0;
1985 char newusername[256];
1986 struct CtdlMessage *msg;
1990 struct usersupp tempUS;
1993 post = extract_int(entargs, 0);
1994 extract(recipient, entargs, 1);
1995 anon_flag = extract_int(entargs, 2);
1996 format_type = extract_int(entargs, 3);
1998 /* first check to make sure the request is valid. */
2000 if (!(CC->logged_in)) {
2001 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
2004 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2005 cprintf("%d Need to be validated to enter ",
2006 ERROR + HIGHER_ACCESS_REQUIRED);
2007 cprintf("(except in %s> to sysop)\n", MAILROOM);
2010 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
2011 cprintf("%d Need net privileges to enter here.\n",
2012 ERROR + HIGHER_ACCESS_REQUIRED);
2015 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
2016 cprintf("%d Sorry, this is a read-only room.\n",
2017 ERROR + HIGHER_ACCESS_REQUIRED);
2024 if (CC->usersupp.axlevel < 6) {
2025 cprintf("%d You don't have permission to masquerade.\n",
2026 ERROR + HIGHER_ACCESS_REQUIRED);
2029 extract(newusername, entargs, 4);
2030 memset(CC->fake_postname, 0, 32);
2031 strcpy(CC->fake_postname, newusername);
2032 cprintf("%d Ok\n", OK);
2035 CC->cs_flags |= CS_POSTING;
2038 if (CC->quickroom.QRflags & QR_MAILBOX) {
2039 if (CC->usersupp.axlevel >= 2) {
2040 strcpy(buf, recipient);
2042 strcpy(buf, "sysop");
2043 e = alias(buf); /* alias and mail type */
2044 if ((buf[0] == 0) || (e == MES_ERROR)) {
2045 cprintf("%d Unknown address - cannot send message.\n",
2046 ERROR + NO_SUCH_USER);
2049 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2050 cprintf("%d Net privileges required for network mail.\n",
2051 ERROR + HIGHER_ACCESS_REQUIRED);
2054 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2055 && ((CC->usersupp.flags & US_INTERNET) == 0)
2056 && (!CC->internal_pgm)) {
2057 cprintf("%d You don't have access to Internet mail.\n",
2058 ERROR + HIGHER_ACCESS_REQUIRED);
2061 if (!strcasecmp(buf, "sysop")) {
2064 else if (e == MES_LOCAL) { /* don't search local file */
2065 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2066 cprintf("%d Can't send mail to yourself!\n",
2067 ERROR + NO_SUCH_USER);
2070 /* Check to make sure the user exists; also get the correct
2071 * upper/lower casing of the name.
2073 a = getuser(&tempUS, buf);
2075 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2078 strcpy(buf, tempUS.fullname);
2083 if (CC->quickroom.QRflags & QR_ANONONLY)
2085 if (CC->quickroom.QRflags & QR_ANONOPT) {
2089 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2092 /* If we're only checking the validity of the request, return
2093 * success without creating the message.
2096 cprintf("%d %s\n", OK, buf);
2100 cprintf("%d send message\n", SEND_LISTING);
2102 /* Read in the message from the client. */
2103 if (CC->fake_postname[0])
2104 msg = make_message(&CC->usersupp, buf,
2105 CC->quickroom.QRname, b, e, format_type,
2107 else if (CC->fake_username[0])
2108 msg = make_message(&CC->usersupp, buf,
2109 CC->quickroom.QRname, b, e, format_type,
2112 msg = make_message(&CC->usersupp, buf,
2113 CC->quickroom.QRname, b, e, format_type, "");
2116 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2117 CtdlFreeMessage(msg);
2118 CC->fake_postname[0] = '\0';
2125 * message entry - mode 3 (raw)
2127 void cmd_ent3(char *entargs)
2133 unsigned char ch, which_field;
2134 struct usersupp tempUS;
2136 struct CtdlMessage *msg;
2139 if (CC->internal_pgm == 0) {
2140 cprintf("%d This command is for internal programs only.\n",
2145 /* See if there's a recipient, but make sure it's a real one */
2146 extract(recp, entargs, 1);
2147 for (a = 0; a < strlen(recp); ++a)
2148 if (!isprint(recp[a]))
2149 strcpy(&recp[a], &recp[a + 1]);
2150 while (isspace(recp[0]))
2151 strcpy(recp, &recp[1]);
2152 while (isspace(recp[strlen(recp) - 1]))
2153 recp[strlen(recp) - 1] = 0;
2155 /* If we're in Mail, check the recipient */
2156 if (strlen(recp) > 0) {
2157 e = alias(recp); /* alias and mail type */
2158 if ((recp[0] == 0) || (e == MES_ERROR)) {
2159 cprintf("%d Unknown address - cannot send message.\n",
2160 ERROR + NO_SUCH_USER);
2163 if (e == MES_LOCAL) {
2164 a = getuser(&tempUS, recp);
2166 cprintf("%d No such user.\n",
2167 ERROR + NO_SUCH_USER);
2173 /* At this point, message has been approved. */
2174 if (extract_int(entargs, 0) == 0) {
2175 cprintf("%d OK to send\n", OK);
2179 msglen = extract_long(entargs, 2);
2180 msg = mallok(sizeof(struct CtdlMessage));
2182 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2186 memset(msg, 0, sizeof(struct CtdlMessage));
2187 tempbuf = mallok(msglen);
2188 if (tempbuf == NULL) {
2189 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2194 cprintf("%d %ld\n", SEND_BINARY, msglen);
2196 client_read(&ch, 1); /* 0xFF magic number */
2197 msg->cm_magic = CTDLMESSAGE_MAGIC;
2198 client_read(&ch, 1); /* anon type */
2199 msg->cm_anon_type = ch;
2200 client_read(&ch, 1); /* format type */
2201 msg->cm_format_type = ch;
2202 msglen = msglen - 3;
2204 while (msglen > 0) {
2205 client_read(&which_field, 1);
2206 if (!isalpha(which_field)) valid_msg = 0;
2210 client_read(&ch, 1);
2212 a = strlen(tempbuf);
2215 } while ( (ch != 0) && (msglen > 0) );
2217 msg->cm_fields[which_field] = strdoop(tempbuf);
2220 msg->cm_flags = CM_SKIP_HOOKS;
2221 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2222 CtdlFreeMessage(msg);
2228 * API function to delete messages which match a set of criteria
2229 * (returns the actual number of messages deleted)
2231 int CtdlDeleteMessages(char *room_name, /* which room */
2232 long dmsgnum, /* or "0" for any */
2233 char *content_type /* or "" for any */
2237 struct quickroom qrbuf;
2238 struct cdbdata *cdbfr;
2239 long *msglist = NULL;
2242 int num_deleted = 0;
2244 struct SuppMsgInfo smi;
2246 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2247 room_name, dmsgnum, content_type);
2249 /* get room record, obtaining a lock... */
2250 if (lgetroom(&qrbuf, room_name) != 0) {
2251 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2253 return (0); /* room not found */
2255 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2257 if (cdbfr != NULL) {
2258 msglist = mallok(cdbfr->len);
2259 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2260 num_msgs = cdbfr->len / sizeof(long);
2264 for (i = 0; i < num_msgs; ++i) {
2267 /* Set/clear a bit for each criterion */
2269 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2270 delete_this |= 0x01;
2272 if (strlen(content_type) == 0) {
2273 delete_this |= 0x02;
2275 GetSuppMsgInfo(&smi, msglist[i]);
2276 if (!strcasecmp(smi.smi_content_type,
2278 delete_this |= 0x02;
2282 /* Delete message only if all bits are set */
2283 if (delete_this == 0x03) {
2284 AdjRefCount(msglist[i], -1);
2290 num_msgs = sort_msglist(msglist, num_msgs);
2291 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2292 msglist, (num_msgs * sizeof(long)));
2294 qrbuf.QRhighest = msglist[num_msgs - 1];
2298 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2299 return (num_deleted);
2305 * Delete message from current room
2307 void cmd_dele(char *delstr)
2312 getuser(&CC->usersupp, CC->curr_user);
2313 if ((CC->usersupp.axlevel < 6)
2314 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2315 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2316 && (!(CC->internal_pgm))) {
2317 cprintf("%d Higher access required.\n",
2318 ERROR + HIGHER_ACCESS_REQUIRED);
2321 delnum = extract_long(delstr, 0);
2323 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2326 cprintf("%d %d message%s deleted.\n", OK,
2327 num_deleted, ((num_deleted != 1) ? "s" : ""));
2329 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2335 * move or copy a message to another room
2337 void cmd_move(char *args)
2341 struct quickroom qtemp;
2345 num = extract_long(args, 0);
2346 extract(targ, args, 1);
2347 targ[ROOMNAMELEN - 1] = 0;
2348 is_copy = extract_int(args, 2);
2350 getuser(&CC->usersupp, CC->curr_user);
2351 if ((CC->usersupp.axlevel < 6)
2352 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2353 cprintf("%d Higher access required.\n",
2354 ERROR + HIGHER_ACCESS_REQUIRED);
2358 if (getroom(&qtemp, targ) != 0) {
2359 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2363 err = CtdlSaveMsgPointerInRoom(targ, num,
2364 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2366 cprintf("%d Cannot store message in %s: error %d\n",
2371 /* Now delete the message from the source room,
2372 * if this is a 'move' rather than a 'copy' operation.
2374 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2376 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2382 * GetSuppMsgInfo() - Get the supplementary record for a message
2384 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2387 struct cdbdata *cdbsmi;
2390 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2391 smibuf->smi_msgnum = msgnum;
2392 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2394 /* Use the negative of the message number for its supp record index */
2395 TheIndex = (0L - msgnum);
2397 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2398 if (cdbsmi == NULL) {
2399 return; /* record not found; go with defaults */
2401 memcpy(smibuf, cdbsmi->ptr,
2402 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2403 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2410 * PutSuppMsgInfo() - (re)write supplementary record for a message
2412 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2416 /* Use the negative of the message number for its supp record index */
2417 TheIndex = (0L - smibuf->smi_msgnum);
2419 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2420 smibuf->smi_msgnum, smibuf->smi_refcount);
2422 cdb_store(CDB_MSGMAIN,
2423 &TheIndex, sizeof(long),
2424 smibuf, sizeof(struct SuppMsgInfo));
2429 * AdjRefCount - change the reference count for a message;
2430 * delete the message if it reaches zero
2432 void AdjRefCount(long msgnum, int incr)
2435 struct SuppMsgInfo smi;
2438 /* This is a *tight* critical section; please keep it that way, as
2439 * it may get called while nested in other critical sections.
2440 * Complicating this any further will surely cause deadlock!
2442 begin_critical_section(S_SUPPMSGMAIN);
2443 GetSuppMsgInfo(&smi, msgnum);
2444 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2445 msgnum, smi.smi_refcount);
2446 smi.smi_refcount += incr;
2447 PutSuppMsgInfo(&smi);
2448 end_critical_section(S_SUPPMSGMAIN);
2449 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2450 msgnum, smi.smi_refcount);
2452 /* If the reference count is now zero, delete the message
2453 * (and its supplementary record as well).
2455 if (smi.smi_refcount == 0) {
2456 lprintf(9, "Deleting message <%ld>\n", msgnum);
2458 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2459 delnum = (0L - msgnum);
2460 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2465 * Write a generic object to this room
2467 * Note: this could be much more efficient. Right now we use two temporary
2468 * files, and still pull the message into memory as with all others.
2470 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2471 char *content_type, /* MIME type of this object */
2472 char *tempfilename, /* Where to fetch it from */
2473 struct usersupp *is_mailbox, /* Mailbox room? */
2474 int is_binary, /* Is encoding necessary? */
2475 int is_unique, /* Del others of this type? */
2476 unsigned int flags /* Internal save flags */
2481 char filename[PATH_MAX];
2484 struct quickroom qrbuf;
2485 char roomname[ROOMNAMELEN];
2486 struct CtdlMessage *msg;
2489 if (is_mailbox != NULL)
2490 MailboxName(roomname, is_mailbox, req_room);
2492 safestrncpy(roomname, req_room, sizeof(roomname));
2493 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2495 strcpy(filename, tmpnam(NULL));
2496 fp = fopen(filename, "w");
2500 tempfp = fopen(tempfilename, "r");
2501 if (tempfp == NULL) {
2507 fprintf(fp, "Content-type: %s\n", content_type);
2508 lprintf(9, "Content-type: %s\n", content_type);
2510 if (is_binary == 0) {
2511 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2512 while (ch = getc(tempfp), ch > 0)
2518 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2521 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2522 tempfilename, filename);
2526 lprintf(9, "Allocating\n");
2527 msg = mallok(sizeof(struct CtdlMessage));
2528 memset(msg, 0, sizeof(struct CtdlMessage));
2529 msg->cm_magic = CTDLMESSAGE_MAGIC;
2530 msg->cm_anon_type = MES_NORMAL;
2531 msg->cm_format_type = 4;
2532 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2533 msg->cm_fields['O'] = strdoop(req_room);
2534 msg->cm_fields['N'] = strdoop(config.c_nodename);
2535 msg->cm_fields['H'] = strdoop(config.c_humannode);
2536 msg->cm_flags = flags;
2538 lprintf(9, "Loading\n");
2539 fp = fopen(filename, "rb");
2540 fseek(fp, 0L, SEEK_END);
2543 msg->cm_fields['M'] = mallok(len);
2544 fread(msg->cm_fields['M'], len, 1, fp);
2548 /* Create the requested room if we have to. */
2549 if (getroom(&qrbuf, roomname) != 0) {
2550 create_room(roomname,
2551 ( (is_mailbox != NULL) ? 4 : 3 ),
2554 /* If the caller specified this object as unique, delete all
2555 * other objects of this type that are currently in the room.
2558 lprintf(9, "Deleted %d other msgs of this type\n",
2559 CtdlDeleteMessages(roomname, 0L, content_type));
2561 /* Now write the data */
2562 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2563 CtdlFreeMessage(msg);
2571 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2572 config_msgnum = msgnum;
2576 char *CtdlGetSysConfig(char *sysconfname) {
2577 char hold_rm[ROOMNAMELEN];
2580 struct CtdlMessage *msg;
2583 strcpy(hold_rm, CC->quickroom.QRname);
2584 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2585 getroom(&CC->quickroom, hold_rm);
2590 /* We want the last (and probably only) config in this room */
2591 begin_critical_section(S_CONFIG);
2592 config_msgnum = (-1L);
2593 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2594 CtdlGetSysConfigBackend, NULL);
2595 msgnum = config_msgnum;
2596 end_critical_section(S_CONFIG);
2602 msg = CtdlFetchMessage(msgnum);
2604 conf = strdoop(msg->cm_fields['M']);
2605 CtdlFreeMessage(msg);
2612 getroom(&CC->quickroom, hold_rm);
2614 lprintf(9, "eggstracting...\n");
2615 if (conf != NULL) do {
2616 extract_token(buf, conf, 0, '\n');
2617 lprintf(9, "eggstracted <%s>\n", buf);
2618 strcpy(conf, &conf[strlen(buf)+1]);
2619 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2624 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2625 char temp[PATH_MAX];
2628 strcpy(temp, tmpnam(NULL));
2630 fp = fopen(temp, "w");
2631 if (fp == NULL) return;
2632 fprintf(fp, "%s", sysconfdata);
2635 /* this handy API function does all the work for us */
2636 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);