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? */
792 char display_name[256];
793 struct CtdlMessage *TheMessage;
795 char *nl; /* newline string */
797 /* buffers needed for RFC822 translation */
807 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
811 sprintf(mid, "%ld", msg_num);
812 nl = (crlf ? "\r\n" : "\n");
814 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
815 if (do_proto) cprintf("%d Not logged in.\n",
816 ERROR + NOT_LOGGED_IN);
817 return(om_not_logged_in);
820 /* FIXME ... small security issue
821 * We need to check to make sure the requested message is actually
822 * in the current room, and set msg_ok to 1 only if it is. This
823 * functionality is currently missing because I'm in a hurry to replace
824 * broken production code with nonbroken pre-beta code. :( -- ajc
827 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
829 return(om_no_such_msg);
834 * Fetch the message from disk
836 TheMessage = CtdlFetchMessage(msg_num);
837 if (TheMessage == NULL) {
838 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
840 return(om_no_such_msg);
843 /* Are we downloading a MIME component? */
844 if (mode == MT_DOWNLOAD) {
845 if (TheMessage->cm_format_type != FMT_RFC822) {
847 cprintf("%d This is not a MIME message.\n",
849 } else if (CC->download_fp != NULL) {
850 if (do_proto) cprintf(
851 "%d You already have a download open.\n",
854 /* Parse the message text component */
855 mptr = TheMessage->cm_fields['M'];
856 mime_parser(mptr, NULL, *mime_download, NULL, 0);
857 /* If there's no file open by this time, the requested
858 * section wasn't found, so print an error
860 if (CC->download_fp == NULL) {
861 if (do_proto) cprintf(
862 "%d Section %s not found.\n",
863 ERROR + FILE_NOT_FOUND,
867 CtdlFreeMessage(TheMessage);
868 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
871 /* now for the user-mode message reading loops */
872 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
874 /* Tell the client which format type we're using. If this is a
875 * MIME message, *lie* about it and tell the user it's fixed-format.
877 if (mode == MT_CITADEL) {
878 if (TheMessage->cm_format_type == FMT_RFC822) {
879 if (do_proto) cprintf("type=1\n");
882 if (do_proto) cprintf("type=%d\n",
883 TheMessage->cm_format_type);
887 /* nhdr=yes means that we're only displaying headers, no body */
888 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
889 if (do_proto) cprintf("nhdr=yes\n");
892 /* begin header processing loop for Citadel message format */
894 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
896 strcpy(display_name, "<unknown>");
897 if (TheMessage->cm_fields['A']) {
898 strcpy(buf, TheMessage->cm_fields['A']);
899 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
900 if (TheMessage->cm_anon_type == MES_ANON)
901 strcpy(display_name, "****");
902 else if (TheMessage->cm_anon_type == MES_AN2)
903 strcpy(display_name, "anonymous");
905 strcpy(display_name, buf);
907 && ((TheMessage->cm_anon_type == MES_ANON)
908 || (TheMessage->cm_anon_type == MES_AN2))) {
909 sprintf(&display_name[strlen(display_name)],
914 strcpy(allkeys, FORDER);
915 for (i=0; i<strlen(allkeys); ++i) {
916 k = (int) allkeys[i];
918 if (TheMessage->cm_fields[k] != NULL) {
920 if (do_proto) cprintf("%s=%s\n",
925 if (do_proto) cprintf("%s=%s\n",
927 TheMessage->cm_fields[k]
936 /* begin header processing loop for RFC822 transfer format */
941 strcpy(snode, NODENAME);
942 strcpy(lnode, HUMANNODE);
943 if (mode == MT_RFC822) {
944 cprintf("X-UIDL: %ld%s", msg_num, nl);
945 for (i = 0; i < 256; ++i) {
946 if (TheMessage->cm_fields[i]) {
947 mptr = TheMessage->cm_fields[i];
954 cprintf("Path: %s%s", mptr, nl);
957 cprintf("Subject: %s%s", mptr, nl);
963 cprintf("X-Citadel-Room: %s%s",
968 cprintf("To: %s%s", mptr, nl);
970 datestring(datestamp, atol(mptr),
972 cprintf("Date: %s%s", datestamp, nl);
978 for (i=0; i<strlen(suser); ++i) {
979 suser[i] = tolower(suser[i]);
980 if (!isalnum(suser[i])) suser[i]='_';
983 if (mode == MT_RFC822) {
984 if (!strcasecmp(snode, NODENAME)) {
988 /* Construct a fun message id */
989 cprintf("Message-ID: <%s", mid);
990 if (strchr(mid, '@')==NULL) {
991 cprintf("@%s", snode);
995 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
997 if (strlen(fuser) > 0) {
998 cprintf("From: %s (%s)%s", fuser, luser, nl);
1001 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1004 cprintf("Organization: %s%s", lnode, nl);
1007 /* end header processing loop ... at this point, we're in the text */
1009 mptr = TheMessage->cm_fields['M'];
1011 /* Tell the client about the MIME parts in this message */
1012 if (TheMessage->cm_format_type == FMT_RFC822) {
1013 if (mode == MT_CITADEL) {
1014 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1016 else if (mode == MT_MIME) { /* list parts only */
1017 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1018 if (do_proto) cprintf("000\n");
1019 CtdlFreeMessage(TheMessage);
1022 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1023 /* FIXME ... we have to put some code in here to avoid
1024 * printing duplicate header information when both
1025 * Citadel and RFC822 headers exist. Preference should
1026 * probably be given to the RFC822 headers.
1028 while (ch=*(mptr++), ch!=0) {
1030 else if (ch==10) cprintf("%s", nl);
1031 else cprintf("%c", ch);
1033 if (do_proto) cprintf("000\n");
1034 CtdlFreeMessage(TheMessage);
1040 if (do_proto) cprintf("000\n");
1041 CtdlFreeMessage(TheMessage);
1045 /* signify start of msg text */
1046 if (mode == MT_CITADEL)
1047 if (do_proto) cprintf("text\n");
1048 if (mode == MT_RFC822) {
1049 if (TheMessage->cm_fields['U'] == NULL) {
1050 cprintf("Subject: (no subject)%s", nl);
1055 /* If the format type on disk is 1 (fixed-format), then we want
1056 * everything to be output completely literally ... regardless of
1057 * what message transfer format is in use.
1059 if (TheMessage->cm_format_type == FMT_FIXED) {
1061 while (ch = *mptr++, ch > 0) {
1064 if ((ch == 10) || (strlen(buf) > 250)) {
1065 cprintf("%s%s", buf, nl);
1068 buf[strlen(buf) + 1] = 0;
1069 buf[strlen(buf)] = ch;
1072 if (strlen(buf) > 0)
1073 cprintf("%s%s", buf, nl);
1076 /* If the message on disk is format 0 (Citadel vari-format), we
1077 * output using the formatter at 80 columns. This is the final output
1078 * form if the transfer format is RFC822, but if the transfer format
1079 * is Citadel proprietary, it'll still work, because the indentation
1080 * for new paragraphs is correct and the client will reformat the
1081 * message to the reader's screen width.
1083 if (TheMessage->cm_format_type == FMT_CITADEL) {
1084 memfmout(80, mptr, 0, nl);
1087 /* If the message on disk is format 4 (MIME), we've gotta hand it
1088 * off to the MIME parser. The client has already been told that
1089 * this message is format 1 (fixed format), so the callback function
1090 * we use will display those parts as-is.
1092 if (TheMessage->cm_format_type == FMT_RFC822) {
1093 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1094 memset(ma, 0, sizeof(struct ma_info));
1095 mime_parser(mptr, NULL, *fixed_output, NULL, 0);
1098 /* now we're done */
1099 if (do_proto) cprintf("000\n");
1100 CtdlFreeMessage(TheMessage);
1107 * display a message (mode 0 - Citadel proprietary)
1109 void cmd_msg0(char *cmdbuf)
1112 int headers_only = 0;
1114 msgid = extract_long(cmdbuf, 0);
1115 headers_only = extract_int(cmdbuf, 1);
1117 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1123 * display a message (mode 2 - RFC822)
1125 void cmd_msg2(char *cmdbuf)
1128 int headers_only = 0;
1130 msgid = extract_long(cmdbuf, 0);
1131 headers_only = extract_int(cmdbuf, 1);
1133 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1139 * display a message (mode 3 - IGnet raw format - internal programs only)
1141 void cmd_msg3(char *cmdbuf)
1144 struct CtdlMessage *msg;
1147 if (CC->internal_pgm == 0) {
1148 cprintf("%d This command is for internal programs only.\n",
1153 msgnum = extract_long(cmdbuf, 0);
1154 msg = CtdlFetchMessage(msgnum);
1156 cprintf("%d Message %ld not found.\n",
1161 serialize_message(&smr, msg);
1162 CtdlFreeMessage(msg);
1165 cprintf("%d Unable to serialize message\n",
1166 ERROR+INTERNAL_ERROR);
1170 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1171 client_write(smr.ser, smr.len);
1178 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1180 void cmd_msg4(char *cmdbuf)
1184 msgid = extract_long(cmdbuf, 0);
1185 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1189 * Open a component of a MIME message as a download file
1191 void cmd_opna(char *cmdbuf)
1195 CtdlAllocUserData(SYM_DESIRED_SECTION, 256);
1197 msgid = extract_long(cmdbuf, 0);
1198 extract(desired_section, cmdbuf, 1);
1200 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1205 * Save a message pointer into a specified room
1206 * (Returns 0 for success, nonzero for failure)
1207 * roomname may be NULL to use the current room
1209 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1211 char hold_rm[ROOMNAMELEN];
1212 struct cdbdata *cdbfr;
1215 long highest_msg = 0L;
1216 struct CtdlMessage *msg = NULL;
1218 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1219 roomname, msgid, flags);
1221 strcpy(hold_rm, CC->quickroom.QRname);
1223 /* We may need to check to see if this message is real */
1224 if ( (flags & SM_VERIFY_GOODNESS)
1225 || (flags & SM_DO_REPL_CHECK)
1227 msg = CtdlFetchMessage(msgid);
1228 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1231 /* Perform replication checks if necessary */
1232 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1234 if (getroom(&CC->quickroom,
1235 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1237 lprintf(9, "No such room <%s>\n", roomname);
1238 if (msg != NULL) CtdlFreeMessage(msg);
1239 return(ERROR + ROOM_NOT_FOUND);
1242 if (ReplicationChecks(msg) != 0) {
1243 getroom(&CC->quickroom, hold_rm);
1244 if (msg != NULL) CtdlFreeMessage(msg);
1245 lprintf(9, "Did replication, and newer exists\n");
1250 /* Now the regular stuff */
1251 if (lgetroom(&CC->quickroom,
1252 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1254 lprintf(9, "No such room <%s>\n", roomname);
1255 if (msg != NULL) CtdlFreeMessage(msg);
1256 return(ERROR + ROOM_NOT_FOUND);
1259 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1260 if (cdbfr == NULL) {
1264 msglist = mallok(cdbfr->len);
1265 if (msglist == NULL)
1266 lprintf(3, "ERROR malloc msglist!\n");
1267 num_msgs = cdbfr->len / sizeof(long);
1268 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1273 /* Make sure the message doesn't already exist in this room. It
1274 * is absolutely taboo to have more than one reference to the same
1275 * message in a room.
1277 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1278 if (msglist[i] == msgid) {
1279 lputroom(&CC->quickroom); /* unlock the room */
1280 getroom(&CC->quickroom, hold_rm);
1281 if (msg != NULL) CtdlFreeMessage(msg);
1282 return(ERROR + ALREADY_EXISTS);
1286 /* Now add the new message */
1288 msglist = reallok(msglist,
1289 (num_msgs * sizeof(long)));
1291 if (msglist == NULL) {
1292 lprintf(3, "ERROR: can't realloc message list!\n");
1294 msglist[num_msgs - 1] = msgid;
1296 /* Sort the message list, so all the msgid's are in order */
1297 num_msgs = sort_msglist(msglist, num_msgs);
1299 /* Determine the highest message number */
1300 highest_msg = msglist[num_msgs - 1];
1302 /* Write it back to disk. */
1303 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1304 msglist, num_msgs * sizeof(long));
1306 /* Free up the memory we used. */
1309 /* Update the highest-message pointer and unlock the room. */
1310 CC->quickroom.QRhighest = highest_msg;
1311 lputroom(&CC->quickroom);
1312 getroom(&CC->quickroom, hold_rm);
1314 /* Bump the reference count for this message. */
1315 if ((flags & SM_DONT_BUMP_REF)==0) {
1316 AdjRefCount(msgid, +1);
1319 /* Return success. */
1320 if (msg != NULL) CtdlFreeMessage(msg);
1327 * Message base operation to send a message to the master file
1328 * (returns new message number)
1330 * This is the back end for CtdlSaveMsg() and should not be directly
1331 * called by server-side modules.
1334 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1335 FILE *save_a_copy) /* save a copy to disk? */
1342 /* Get a new message number */
1343 newmsgid = get_new_message_number();
1344 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1346 /* Generate an ID if we don't have one already */
1347 if (msg->cm_fields['I']==NULL) {
1348 msg->cm_fields['I'] = strdoop(msgidbuf);
1351 serialize_message(&smr, msg);
1354 cprintf("%d Unable to serialize message\n",
1355 ERROR+INTERNAL_ERROR);
1359 /* Write our little bundle of joy into the message base */
1360 begin_critical_section(S_MSGMAIN);
1361 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1362 smr.ser, smr.len) < 0) {
1363 lprintf(2, "Can't store message\n");
1368 end_critical_section(S_MSGMAIN);
1370 /* If the caller specified that a copy should be saved to a particular
1371 * file handle, do that now too.
1373 if (save_a_copy != NULL) {
1374 fwrite(smr.ser, smr.len, 1, save_a_copy);
1377 /* Free the memory we used for the serialized message */
1380 /* Return the *local* message ID to the caller
1381 * (even if we're storing an incoming network message)
1389 * Serialize a struct CtdlMessage into the format used on disk and network.
1391 * This function loads up a "struct ser_ret" (defined in server.h) which
1392 * contains the length of the serialized message and a pointer to the
1393 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1395 void serialize_message(struct ser_ret *ret, /* return values */
1396 struct CtdlMessage *msg) /* unserialized msg */
1400 static char *forder = FORDER;
1402 if (is_valid_message(msg) == 0) return; /* self check */
1405 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1406 ret->len = ret->len +
1407 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1409 lprintf(9, "calling malloc(%d)\n", ret->len);
1410 ret->ser = mallok(ret->len);
1411 if (ret->ser == NULL) {
1417 ret->ser[1] = msg->cm_anon_type;
1418 ret->ser[2] = msg->cm_format_type;
1421 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1422 ret->ser[wlen++] = (char)forder[i];
1423 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1424 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1426 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1435 * Back end for the ReplicationChecks() function
1437 void check_repl(long msgnum, void *userdata) {
1438 struct CtdlMessage *msg;
1439 time_t timestamp = (-1L);
1441 lprintf(9, "check_repl() found message %ld\n", msgnum);
1442 msg = CtdlFetchMessage(msgnum);
1443 if (msg == NULL) return;
1444 if (msg->cm_fields['T'] != NULL) {
1445 timestamp = atol(msg->cm_fields['T']);
1447 CtdlFreeMessage(msg);
1449 if (timestamp > msg_repl->highest) {
1450 msg_repl->highest = timestamp; /* newer! */
1451 lprintf(9, "newer!\n");
1454 lprintf(9, "older!\n");
1456 /* Existing isn't newer? Then delete the old one(s). */
1457 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1462 * Check to see if any messages already exist which carry the same Extended ID
1466 * -> With older timestamps: delete them and return 0. Message will be saved.
1467 * -> With newer timestamps: return 1. Message save will be aborted.
1469 int ReplicationChecks(struct CtdlMessage *msg) {
1470 struct CtdlMessage *template;
1473 lprintf(9, "ReplicationChecks() started\n");
1474 /* No extended id? Don't do anything. */
1475 if (msg->cm_fields['E'] == NULL) return 0;
1476 if (strlen(msg->cm_fields['E']) == 0) return 0;
1477 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1479 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1480 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1481 msg_repl->highest = atol(msg->cm_fields['T']);
1483 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1484 memset(template, 0, sizeof(struct CtdlMessage));
1485 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1487 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1490 /* If a newer message exists with the same Extended ID, abort
1493 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1497 CtdlFreeMessage(template);
1498 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1506 * Save a message to disk
1508 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1509 char *rec, /* Recipient (mail) */
1510 char *force, /* force a particular room? */
1511 int supplied_mailtype) /* local or remote type */
1514 char hold_rm[ROOMNAMELEN];
1515 char actual_rm[ROOMNAMELEN];
1516 char force_room[ROOMNAMELEN];
1517 char content_type[256]; /* We have to learn this */
1518 char recipient[256];
1521 struct usersupp userbuf;
1523 struct SuppMsgInfo smi;
1524 FILE *network_fp = NULL;
1525 static int seqnum = 1;
1526 struct CtdlMessage *imsg;
1530 lprintf(9, "CtdlSaveMsg() called\n");
1531 if (is_valid_message(msg) == 0) return(-1); /* self check */
1532 mailtype = supplied_mailtype;
1534 /* If this message has no timestamp, we take the liberty of
1535 * giving it one, right now.
1537 if (msg->cm_fields['T'] == NULL) {
1538 lprintf(9, "Generating timestamp\n");
1539 sprintf(aaa, "%ld", time(NULL));
1540 msg->cm_fields['T'] = strdoop(aaa);
1543 /* If this message has no path, we generate one.
1545 if (msg->cm_fields['P'] == NULL) {
1546 lprintf(9, "Generating path\n");
1547 if (msg->cm_fields['A'] != NULL) {
1548 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1549 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1550 if (isspace(msg->cm_fields['P'][a])) {
1551 msg->cm_fields['P'][a] = ' ';
1556 msg->cm_fields['P'] = strdoop("unknown");
1560 strcpy(force_room, force);
1562 /* Strip non-printable characters out of the recipient name */
1563 lprintf(9, "Checking recipient (if present)\n");
1564 strcpy(recipient, rec);
1565 for (a = 0; a < strlen(recipient); ++a)
1566 if (!isprint(recipient[a]))
1567 strcpy(&recipient[a], &recipient[a + 1]);
1569 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1570 for (a=0; a<strlen(recipient); ++a) {
1571 if (recipient[a] == '@') {
1572 if (CtdlHostAlias(&recipient[a+1])
1573 == hostalias_localhost) {
1575 lprintf(7, "Changed to <%s>\n", recipient);
1576 mailtype = MES_LOCAL;
1581 lprintf(9, "Recipient is <%s>\n", recipient);
1583 /* Learn about what's inside, because it's what's inside that counts */
1584 lprintf(9, "Learning what's inside\n");
1585 if (msg->cm_fields['M'] == NULL) {
1586 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1589 switch (msg->cm_format_type) {
1591 strcpy(content_type, "text/x-citadel-variformat");
1594 strcpy(content_type, "text/plain");
1597 strcpy(content_type, "text/plain");
1598 /* advance past header fields */
1599 mptr = msg->cm_fields['M'];
1602 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1603 safestrncpy(content_type, mptr,
1604 sizeof(content_type));
1605 strcpy(content_type, &content_type[14]);
1606 for (a = 0; a < strlen(content_type); ++a)
1607 if ((content_type[a] == ';')
1608 || (content_type[a] == ' ')
1609 || (content_type[a] == 13)
1610 || (content_type[a] == 10))
1611 content_type[a] = 0;
1618 /* Goto the correct room */
1619 lprintf(9, "Switching rooms\n");
1620 strcpy(hold_rm, CC->quickroom.QRname);
1621 strcpy(actual_rm, CC->quickroom.QRname);
1623 /* If the user is a twit, move to the twit room for posting */
1624 lprintf(9, "Handling twit stuff\n");
1626 if (CC->usersupp.axlevel == 2) {
1627 strcpy(hold_rm, actual_rm);
1628 strcpy(actual_rm, config.c_twitroom);
1632 /* ...or if this message is destined for Aide> then go there. */
1633 if (strlen(force_room) > 0) {
1634 strcpy(actual_rm, force_room);
1637 lprintf(9, "Possibly relocating\n");
1638 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1639 getroom(&CC->quickroom, actual_rm);
1643 * If this message has no O (room) field, generate one.
1645 if (msg->cm_fields['O'] == NULL) {
1646 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1649 /* Perform "before save" hooks (aborting if any return nonzero) */
1650 lprintf(9, "Performing before-save hooks\n");
1651 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1653 /* If this message has an Extended ID, perform replication checks */
1654 lprintf(9, "Performing replication checks\n");
1655 if (ReplicationChecks(msg) > 0) return(-1);
1657 /* Network mail - send a copy to the network program. */
1658 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1659 lprintf(9, "Sending network spool\n");
1660 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1661 (long) getpid(), CC->cs_pid, ++seqnum);
1662 lprintf(9, "Saving a copy to %s\n", aaa);
1663 network_fp = fopen(aaa, "ab+");
1664 if (network_fp == NULL)
1665 lprintf(2, "ERROR: %s\n", strerror(errno));
1668 /* Save it to disk */
1669 lprintf(9, "Saving to disk\n");
1670 newmsgid = send_message(msg, network_fp);
1671 if (network_fp != NULL) {
1673 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1676 if (newmsgid <= 0L) return(-1);
1678 /* Write a supplemental message info record. This doesn't have to
1679 * be a critical section because nobody else knows about this message
1682 lprintf(9, "Creating SuppMsgInfo record\n");
1683 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1684 smi.smi_msgnum = newmsgid;
1685 smi.smi_refcount = 0;
1686 safestrncpy(smi.smi_content_type, content_type, 64);
1687 PutSuppMsgInfo(&smi);
1689 /* Now figure out where to store the pointers */
1690 lprintf(9, "Storing pointers\n");
1692 /* If this is being done by the networker delivering a private
1693 * message, we want to BYPASS saving the sender's copy (because there
1694 * is no local sender; it would otherwise go to the Trashcan).
1696 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1697 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1698 lprintf(3, "ERROR saving message pointer!\n");
1699 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1703 /* For internet mail, drop a copy in the outbound queue room */
1704 if (mailtype == MES_INTERNET) {
1705 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1708 /* Bump this user's messages posted counter. */
1709 lprintf(9, "Updating user\n");
1710 lgetuser(&CC->usersupp, CC->curr_user);
1711 CC->usersupp.posted = CC->usersupp.posted + 1;
1712 lputuser(&CC->usersupp);
1714 /* If this is private, local mail, make a copy in the
1715 * recipient's mailbox and bump the reference count.
1717 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1718 if (getuser(&userbuf, recipient) == 0) {
1719 lprintf(9, "Delivering private mail\n");
1720 MailboxName(actual_rm, &userbuf, MAILROOM);
1721 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1724 lprintf(9, "No user <%s>, saving in %s> instead\n",
1725 recipient, AIDEROOM);
1726 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1730 /* Perform "after save" hooks */
1731 lprintf(9, "Performing after-save hooks\n");
1732 PerformMessageHooks(msg, EVT_AFTERSAVE);
1735 lprintf(9, "Returning to original room\n");
1736 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1737 getroom(&CC->quickroom, hold_rm);
1739 /* For internet mail, generate delivery instructions
1740 * (Yes, this is recursive! Deal with it!)
1742 if (mailtype == MES_INTERNET) {
1743 lprintf(9, "Generating delivery instructions\n");
1744 instr = mallok(2048);
1746 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1749 SPOOLMIME, newmsgid, time(NULL),
1750 msg->cm_fields['A'], msg->cm_fields['N'],
1753 imsg = mallok(sizeof(struct CtdlMessage));
1754 memset(imsg, 0, sizeof(struct CtdlMessage));
1755 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1756 imsg->cm_anon_type = MES_NORMAL;
1757 imsg->cm_format_type = FMT_RFC822;
1758 imsg->cm_fields['A'] = strdoop("Citadel");
1759 imsg->cm_fields['M'] = instr;
1760 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1761 CtdlFreeMessage(imsg);
1770 * Convenience function for generating small administrative messages.
1772 void quickie_message(char *from, char *to, char *room, char *text)
1774 struct CtdlMessage *msg;
1776 msg = mallok(sizeof(struct CtdlMessage));
1777 memset(msg, 0, sizeof(struct CtdlMessage));
1778 msg->cm_magic = CTDLMESSAGE_MAGIC;
1779 msg->cm_anon_type = MES_NORMAL;
1780 msg->cm_format_type = 0;
1781 msg->cm_fields['A'] = strdoop(from);
1782 msg->cm_fields['O'] = strdoop(room);
1783 msg->cm_fields['N'] = strdoop(NODENAME);
1785 msg->cm_fields['R'] = strdoop(to);
1786 msg->cm_fields['M'] = strdoop(text);
1788 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1789 CtdlFreeMessage(msg);
1790 syslog(LOG_NOTICE, text);
1796 * Back end function used by make_message() and similar functions
1798 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1799 size_t maxlen, /* maximum message length */
1800 char *exist /* if non-null, append to it;
1801 exist is ALWAYS freed */
1805 size_t message_len = 0;
1806 size_t buffer_len = 0;
1810 if (exist == NULL) {
1814 m = reallok(exist, strlen(exist) + 4096);
1815 if (m == NULL) phree(exist);
1818 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1825 /* read in the lines of message text one by one */
1826 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1828 /* strip trailing newline type stuff */
1829 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1830 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1832 linelen = strlen(buf);
1834 /* augment the buffer if we have to */
1835 if ((message_len + linelen + 2) > buffer_len) {
1836 lprintf(9, "realloking\n");
1837 ptr = reallok(m, (buffer_len * 2) );
1838 if (ptr == NULL) { /* flush if can't allocate */
1839 while ( (client_gets(buf)>0) &&
1840 strcmp(buf, terminator)) ;;
1843 buffer_len = (buffer_len * 2);
1845 lprintf(9, "buffer_len is %d\n", buffer_len);
1849 /* Add the new line to the buffer. We avoid using strcat()
1850 * because that would involve traversing the entire message
1851 * after each line, and this function needs to run fast.
1853 strcpy(&m[message_len], buf);
1854 m[message_len + linelen] = '\n';
1855 m[message_len + linelen + 1] = 0;
1856 message_len = message_len + linelen + 1;
1858 /* if we've hit the max msg length, flush the rest */
1859 if (message_len >= maxlen) {
1860 while ( (client_gets(buf)>0)
1861 && strcmp(buf, terminator)) ;;
1872 * Build a binary message to be saved on disk.
1875 struct CtdlMessage *make_message(
1876 struct usersupp *author, /* author's usersupp structure */
1877 char *recipient, /* NULL if it's not mail */
1878 char *room, /* room where it's going */
1879 int type, /* see MES_ types in header file */
1880 int net_type, /* see MES_ types in header file */
1881 int format_type, /* local or remote (see citadel.h) */
1882 char *fake_name) /* who we're masquerading as */
1888 struct CtdlMessage *msg;
1890 msg = mallok(sizeof(struct CtdlMessage));
1891 memset(msg, 0, sizeof(struct CtdlMessage));
1892 msg->cm_magic = CTDLMESSAGE_MAGIC;
1893 msg->cm_anon_type = type;
1894 msg->cm_format_type = format_type;
1896 /* Don't confuse the poor folks if it's not routed mail. */
1897 strcpy(dest_node, "");
1899 /* If net_type is MES_BINARY, split out the destination node. */
1900 if (net_type == MES_BINARY) {
1901 strcpy(dest_node, NODENAME);
1902 for (a = 0; a < strlen(recipient); ++a) {
1903 if (recipient[a] == '@') {
1905 strcpy(dest_node, &recipient[a + 1]);
1910 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1911 if (net_type == MES_INTERNET) {
1912 strcpy(dest_node, "internet");
1915 while (isspace(recipient[strlen(recipient) - 1]))
1916 recipient[strlen(recipient) - 1] = 0;
1918 sprintf(buf, "cit%ld", author->usernum); /* Path */
1919 msg->cm_fields['P'] = strdoop(buf);
1921 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1922 msg->cm_fields['T'] = strdoop(buf);
1924 if (fake_name[0]) /* author */
1925 msg->cm_fields['A'] = strdoop(fake_name);
1927 msg->cm_fields['A'] = strdoop(author->fullname);
1929 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1930 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1932 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1934 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1935 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1937 if (recipient[0] != 0)
1938 msg->cm_fields['R'] = strdoop(recipient);
1939 if (dest_node[0] != 0)
1940 msg->cm_fields['D'] = strdoop(dest_node);
1943 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1944 config.c_maxmsglen, NULL);
1955 * message entry - mode 0 (normal)
1957 void cmd_ent0(char *entargs)
1960 char recipient[256];
1962 int format_type = 0;
1963 char newusername[256];
1964 struct CtdlMessage *msg;
1968 struct usersupp tempUS;
1971 post = extract_int(entargs, 0);
1972 extract(recipient, entargs, 1);
1973 anon_flag = extract_int(entargs, 2);
1974 format_type = extract_int(entargs, 3);
1976 /* first check to make sure the request is valid. */
1978 if (!(CC->logged_in)) {
1979 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1982 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1983 cprintf("%d Need to be validated to enter ",
1984 ERROR + HIGHER_ACCESS_REQUIRED);
1985 cprintf("(except in %s> to sysop)\n", MAILROOM);
1988 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1989 cprintf("%d Need net privileges to enter here.\n",
1990 ERROR + HIGHER_ACCESS_REQUIRED);
1993 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1994 cprintf("%d Sorry, this is a read-only room.\n",
1995 ERROR + HIGHER_ACCESS_REQUIRED);
2002 if (CC->usersupp.axlevel < 6) {
2003 cprintf("%d You don't have permission to masquerade.\n",
2004 ERROR + HIGHER_ACCESS_REQUIRED);
2007 extract(newusername, entargs, 4);
2008 memset(CC->fake_postname, 0, 32);
2009 strcpy(CC->fake_postname, newusername);
2010 cprintf("%d Ok\n", OK);
2013 CC->cs_flags |= CS_POSTING;
2016 if (CC->quickroom.QRflags & QR_MAILBOX) {
2017 if (CC->usersupp.axlevel >= 2) {
2018 strcpy(buf, recipient);
2020 strcpy(buf, "sysop");
2021 e = alias(buf); /* alias and mail type */
2022 if ((buf[0] == 0) || (e == MES_ERROR)) {
2023 cprintf("%d Unknown address - cannot send message.\n",
2024 ERROR + NO_SUCH_USER);
2027 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2028 cprintf("%d Net privileges required for network mail.\n",
2029 ERROR + HIGHER_ACCESS_REQUIRED);
2032 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2033 && ((CC->usersupp.flags & US_INTERNET) == 0)
2034 && (!CC->internal_pgm)) {
2035 cprintf("%d You don't have access to Internet mail.\n",
2036 ERROR + HIGHER_ACCESS_REQUIRED);
2039 if (!strcasecmp(buf, "sysop")) {
2042 else if (e == MES_LOCAL) { /* don't search local file */
2043 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2044 cprintf("%d Can't send mail to yourself!\n",
2045 ERROR + NO_SUCH_USER);
2048 /* Check to make sure the user exists; also get the correct
2049 * upper/lower casing of the name.
2051 a = getuser(&tempUS, buf);
2053 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2056 strcpy(buf, tempUS.fullname);
2061 if (CC->quickroom.QRflags & QR_ANONONLY)
2063 if (CC->quickroom.QRflags & QR_ANONOPT) {
2067 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2070 /* If we're only checking the validity of the request, return
2071 * success without creating the message.
2074 cprintf("%d %s\n", OK, buf);
2078 cprintf("%d send message\n", SEND_LISTING);
2080 /* Read in the message from the client. */
2081 if (CC->fake_postname[0])
2082 msg = make_message(&CC->usersupp, buf,
2083 CC->quickroom.QRname, b, e, format_type,
2085 else if (CC->fake_username[0])
2086 msg = make_message(&CC->usersupp, buf,
2087 CC->quickroom.QRname, b, e, format_type,
2090 msg = make_message(&CC->usersupp, buf,
2091 CC->quickroom.QRname, b, e, format_type, "");
2094 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2095 CtdlFreeMessage(msg);
2096 CC->fake_postname[0] = '\0';
2103 * message entry - mode 3 (raw)
2105 void cmd_ent3(char *entargs)
2111 unsigned char ch, which_field;
2112 struct usersupp tempUS;
2114 struct CtdlMessage *msg;
2117 if (CC->internal_pgm == 0) {
2118 cprintf("%d This command is for internal programs only.\n",
2123 /* See if there's a recipient, but make sure it's a real one */
2124 extract(recp, entargs, 1);
2125 for (a = 0; a < strlen(recp); ++a)
2126 if (!isprint(recp[a]))
2127 strcpy(&recp[a], &recp[a + 1]);
2128 while (isspace(recp[0]))
2129 strcpy(recp, &recp[1]);
2130 while (isspace(recp[strlen(recp) - 1]))
2131 recp[strlen(recp) - 1] = 0;
2133 /* If we're in Mail, check the recipient */
2134 if (strlen(recp) > 0) {
2135 e = alias(recp); /* alias and mail type */
2136 if ((recp[0] == 0) || (e == MES_ERROR)) {
2137 cprintf("%d Unknown address - cannot send message.\n",
2138 ERROR + NO_SUCH_USER);
2141 if (e == MES_LOCAL) {
2142 a = getuser(&tempUS, recp);
2144 cprintf("%d No such user.\n",
2145 ERROR + NO_SUCH_USER);
2151 /* At this point, message has been approved. */
2152 if (extract_int(entargs, 0) == 0) {
2153 cprintf("%d OK to send\n", OK);
2157 msglen = extract_long(entargs, 2);
2158 msg = mallok(sizeof(struct CtdlMessage));
2160 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2164 memset(msg, 0, sizeof(struct CtdlMessage));
2165 tempbuf = mallok(msglen);
2166 if (tempbuf == NULL) {
2167 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2172 cprintf("%d %ld\n", SEND_BINARY, msglen);
2174 client_read(&ch, 1); /* 0xFF magic number */
2175 msg->cm_magic = CTDLMESSAGE_MAGIC;
2176 client_read(&ch, 1); /* anon type */
2177 msg->cm_anon_type = ch;
2178 client_read(&ch, 1); /* format type */
2179 msg->cm_format_type = ch;
2180 msglen = msglen - 3;
2182 while (msglen > 0) {
2183 client_read(&which_field, 1);
2184 if (!isalpha(which_field)) valid_msg = 0;
2188 client_read(&ch, 1);
2190 a = strlen(tempbuf);
2193 } while ( (ch != 0) && (msglen > 0) );
2195 msg->cm_fields[which_field] = strdoop(tempbuf);
2198 msg->cm_flags = CM_SKIP_HOOKS;
2199 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2200 CtdlFreeMessage(msg);
2206 * API function to delete messages which match a set of criteria
2207 * (returns the actual number of messages deleted)
2209 int CtdlDeleteMessages(char *room_name, /* which room */
2210 long dmsgnum, /* or "0" for any */
2211 char *content_type /* or "" for any */
2215 struct quickroom qrbuf;
2216 struct cdbdata *cdbfr;
2217 long *msglist = NULL;
2220 int num_deleted = 0;
2222 struct SuppMsgInfo smi;
2224 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2225 room_name, dmsgnum, content_type);
2227 /* get room record, obtaining a lock... */
2228 if (lgetroom(&qrbuf, room_name) != 0) {
2229 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2231 return (0); /* room not found */
2233 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2235 if (cdbfr != NULL) {
2236 msglist = mallok(cdbfr->len);
2237 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2238 num_msgs = cdbfr->len / sizeof(long);
2242 for (i = 0; i < num_msgs; ++i) {
2245 /* Set/clear a bit for each criterion */
2247 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2248 delete_this |= 0x01;
2250 if (strlen(content_type) == 0) {
2251 delete_this |= 0x02;
2253 GetSuppMsgInfo(&smi, msglist[i]);
2254 if (!strcasecmp(smi.smi_content_type,
2256 delete_this |= 0x02;
2260 /* Delete message only if all bits are set */
2261 if (delete_this == 0x03) {
2262 AdjRefCount(msglist[i], -1);
2268 num_msgs = sort_msglist(msglist, num_msgs);
2269 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2270 msglist, (num_msgs * sizeof(long)));
2272 qrbuf.QRhighest = msglist[num_msgs - 1];
2276 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2277 return (num_deleted);
2283 * Delete message from current room
2285 void cmd_dele(char *delstr)
2290 getuser(&CC->usersupp, CC->curr_user);
2291 if ((CC->usersupp.axlevel < 6)
2292 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2293 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2294 && (!(CC->internal_pgm))) {
2295 cprintf("%d Higher access required.\n",
2296 ERROR + HIGHER_ACCESS_REQUIRED);
2299 delnum = extract_long(delstr, 0);
2301 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2304 cprintf("%d %d message%s deleted.\n", OK,
2305 num_deleted, ((num_deleted != 1) ? "s" : ""));
2307 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2313 * move or copy a message to another room
2315 void cmd_move(char *args)
2319 struct quickroom qtemp;
2323 num = extract_long(args, 0);
2324 extract(targ, args, 1);
2325 targ[ROOMNAMELEN - 1] = 0;
2326 is_copy = extract_int(args, 2);
2328 getuser(&CC->usersupp, CC->curr_user);
2329 if ((CC->usersupp.axlevel < 6)
2330 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2331 cprintf("%d Higher access required.\n",
2332 ERROR + HIGHER_ACCESS_REQUIRED);
2336 if (getroom(&qtemp, targ) != 0) {
2337 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2341 err = CtdlSaveMsgPointerInRoom(targ, num,
2342 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2344 cprintf("%d Cannot store message in %s: error %d\n",
2349 /* Now delete the message from the source room,
2350 * if this is a 'move' rather than a 'copy' operation.
2352 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2354 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2360 * GetSuppMsgInfo() - Get the supplementary record for a message
2362 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2365 struct cdbdata *cdbsmi;
2368 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2369 smibuf->smi_msgnum = msgnum;
2370 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2372 /* Use the negative of the message number for its supp record index */
2373 TheIndex = (0L - msgnum);
2375 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2376 if (cdbsmi == NULL) {
2377 return; /* record not found; go with defaults */
2379 memcpy(smibuf, cdbsmi->ptr,
2380 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2381 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2388 * PutSuppMsgInfo() - (re)write supplementary record for a message
2390 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2394 /* Use the negative of the message number for its supp record index */
2395 TheIndex = (0L - smibuf->smi_msgnum);
2397 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2398 smibuf->smi_msgnum, smibuf->smi_refcount);
2400 cdb_store(CDB_MSGMAIN,
2401 &TheIndex, sizeof(long),
2402 smibuf, sizeof(struct SuppMsgInfo));
2407 * AdjRefCount - change the reference count for a message;
2408 * delete the message if it reaches zero
2410 void AdjRefCount(long msgnum, int incr)
2413 struct SuppMsgInfo smi;
2416 /* This is a *tight* critical section; please keep it that way, as
2417 * it may get called while nested in other critical sections.
2418 * Complicating this any further will surely cause deadlock!
2420 begin_critical_section(S_SUPPMSGMAIN);
2421 GetSuppMsgInfo(&smi, msgnum);
2422 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2423 msgnum, smi.smi_refcount);
2424 smi.smi_refcount += incr;
2425 PutSuppMsgInfo(&smi);
2426 end_critical_section(S_SUPPMSGMAIN);
2427 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2428 msgnum, smi.smi_refcount);
2430 /* If the reference count is now zero, delete the message
2431 * (and its supplementary record as well).
2433 if (smi.smi_refcount == 0) {
2434 lprintf(9, "Deleting message <%ld>\n", msgnum);
2436 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2437 delnum = (0L - msgnum);
2438 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2443 * Write a generic object to this room
2445 * Note: this could be much more efficient. Right now we use two temporary
2446 * files, and still pull the message into memory as with all others.
2448 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2449 char *content_type, /* MIME type of this object */
2450 char *tempfilename, /* Where to fetch it from */
2451 struct usersupp *is_mailbox, /* Mailbox room? */
2452 int is_binary, /* Is encoding necessary? */
2453 int is_unique, /* Del others of this type? */
2454 unsigned int flags /* Internal save flags */
2459 char filename[PATH_MAX];
2462 struct quickroom qrbuf;
2463 char roomname[ROOMNAMELEN];
2464 struct CtdlMessage *msg;
2467 if (is_mailbox != NULL)
2468 MailboxName(roomname, is_mailbox, req_room);
2470 safestrncpy(roomname, req_room, sizeof(roomname));
2471 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2473 strcpy(filename, tmpnam(NULL));
2474 fp = fopen(filename, "w");
2478 tempfp = fopen(tempfilename, "r");
2479 if (tempfp == NULL) {
2485 fprintf(fp, "Content-type: %s\n", content_type);
2486 lprintf(9, "Content-type: %s\n", content_type);
2488 if (is_binary == 0) {
2489 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2490 while (ch = getc(tempfp), ch > 0)
2496 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2499 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2500 tempfilename, filename);
2504 lprintf(9, "Allocating\n");
2505 msg = mallok(sizeof(struct CtdlMessage));
2506 memset(msg, 0, sizeof(struct CtdlMessage));
2507 msg->cm_magic = CTDLMESSAGE_MAGIC;
2508 msg->cm_anon_type = MES_NORMAL;
2509 msg->cm_format_type = 4;
2510 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2511 msg->cm_fields['O'] = strdoop(req_room);
2512 msg->cm_fields['N'] = strdoop(config.c_nodename);
2513 msg->cm_fields['H'] = strdoop(config.c_humannode);
2514 msg->cm_flags = flags;
2516 lprintf(9, "Loading\n");
2517 fp = fopen(filename, "rb");
2518 fseek(fp, 0L, SEEK_END);
2521 msg->cm_fields['M'] = mallok(len);
2522 fread(msg->cm_fields['M'], len, 1, fp);
2526 /* Create the requested room if we have to. */
2527 if (getroom(&qrbuf, roomname) != 0) {
2528 create_room(roomname,
2529 ( (is_mailbox != NULL) ? 4 : 3 ),
2532 /* If the caller specified this object as unique, delete all
2533 * other objects of this type that are currently in the room.
2536 lprintf(9, "Deleted %d other msgs of this type\n",
2537 CtdlDeleteMessages(roomname, 0L, content_type));
2539 /* Now write the data */
2540 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2541 CtdlFreeMessage(msg);
2549 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2550 config_msgnum = msgnum;
2554 char *CtdlGetSysConfig(char *sysconfname) {
2555 char hold_rm[ROOMNAMELEN];
2558 struct CtdlMessage *msg;
2561 strcpy(hold_rm, CC->quickroom.QRname);
2562 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2563 getroom(&CC->quickroom, hold_rm);
2568 /* We want the last (and probably only) config in this room */
2569 begin_critical_section(S_CONFIG);
2570 config_msgnum = (-1L);
2571 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2572 CtdlGetSysConfigBackend, NULL);
2573 msgnum = config_msgnum;
2574 end_critical_section(S_CONFIG);
2580 msg = CtdlFetchMessage(msgnum);
2582 conf = strdoop(msg->cm_fields['M']);
2583 CtdlFreeMessage(msg);
2590 getroom(&CC->quickroom, hold_rm);
2592 lprintf(9, "eggstracting...\n");
2593 if (conf != NULL) do {
2594 extract_token(buf, conf, 0, '\n');
2595 lprintf(9, "eggstracted <%s>\n", buf);
2596 strcpy(conf, &conf[strlen(buf)+1]);
2597 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2602 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2603 char temp[PATH_MAX];
2606 strcpy(temp, tmpnam(NULL));
2608 fp = fopen(temp, "w");
2609 if (fp == NULL) return;
2610 fprintf(fp, "%s", sysconfdata);
2613 /* this handy API function does all the work for us */
2614 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);