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;
294 /* Learn about the user and room in question */
296 getuser(&CC->usersupp, CC->curr_user);
297 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
299 /* Load the message list */
300 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
302 msglist = mallok(cdbfr->len);
303 memcpy(msglist, cdbfr->ptr, cdbfr->len);
304 num_msgs = cdbfr->len / sizeof(long);
307 return 0; /* No messages at all? No further action. */
311 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
312 GetSuppMsgInfo(&smi, msglist[a]);
314 /* Filter out messages that are moderated below the level
315 * currently being viewed at.
317 if (smi.smi_mod < moderation_level) {
321 /* If the caller is looking for a specific MIME type, filter
322 * out all messages which are not of the type requested.
324 if (content_type != NULL) if (strlen(content_type) > 0) {
325 if (strcasecmp(smi.smi_content_type, content_type)) {
331 num_msgs = sort_msglist(msglist, num_msgs);
333 /* If a template was supplied, filter out the messages which
334 * don't match. (This could induce some delays!)
337 if (compare != NULL) {
338 for (a = 0; a < num_msgs; ++a) {
339 msg = CtdlFetchMessage(msglist[a]);
341 if (CtdlMsgCmp(msg, compare)) {
344 CtdlFreeMessage(msg);
352 * Now iterate through the message list, according to the
353 * criteria supplied by the caller.
356 for (a = 0; a < num_msgs; ++a) {
357 thismsg = msglist[a];
358 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
363 || ((mode == MSGS_OLD) && (is_seen))
364 || ((mode == MSGS_NEW) && (!is_seen))
365 /* FIXME handle lastold mode */
366 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
367 || ((mode == MSGS_FIRST) && (a < ref))
368 || ((mode == MSGS_GT) && (thismsg > ref))
369 || ((mode == MSGS_EQ) && (thismsg == ref))
372 if (CallBack) CallBack(thismsg, userdata);
376 phree(msglist); /* Clean up */
377 return num_processed;
383 * cmd_msgs() - get list of message #'s in this room
384 * implements the MSGS server command using CtdlForEachMessage()
386 void cmd_msgs(char *cmdbuf)
395 int with_template = 0;
396 struct CtdlMessage *template = NULL;
398 extract(which, cmdbuf, 0);
399 cm_ref = extract_int(cmdbuf, 1);
400 with_template = extract_int(cmdbuf, 2);
404 if (!strncasecmp(which, "OLD", 3))
406 else if (!strncasecmp(which, "NEW", 3))
408 else if (!strncasecmp(which, "FIRST", 5))
410 else if (!strncasecmp(which, "LAST", 4))
412 else if (!strncasecmp(which, "GT", 2))
415 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
416 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
421 cprintf("%d Send template then receive message list\n",
423 template = (struct CtdlMessage *)
424 mallok(sizeof(struct CtdlMessage));
425 memset(template, 0, sizeof(struct CtdlMessage));
426 while(client_gets(buf), strcmp(buf,"000")) {
427 extract(tfield, buf, 0);
428 extract(tvalue, buf, 1);
429 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
430 if (!strcasecmp(tfield, msgkeys[i])) {
431 template->cm_fields[i] =
438 cprintf("%d Message list...\n", LISTING_FOLLOWS);
441 CtdlForEachMessage(mode, cm_ref,
442 CC->usersupp.moderation_filter,
443 NULL, template, simple_listing, NULL);
444 if (template != NULL) CtdlFreeMessage(template);
452 * help_subst() - support routine for help file viewer
454 void help_subst(char *strbuf, char *source, char *dest)
459 while (p = pattern2(strbuf, source), (p >= 0)) {
460 strcpy(workbuf, &strbuf[p + strlen(source)]);
461 strcpy(&strbuf[p], dest);
462 strcat(strbuf, workbuf);
467 void do_help_subst(char *buffer)
471 help_subst(buffer, "^nodename", config.c_nodename);
472 help_subst(buffer, "^humannode", config.c_humannode);
473 help_subst(buffer, "^fqdn", config.c_fqdn);
474 help_subst(buffer, "^username", CC->usersupp.fullname);
475 sprintf(buf2, "%ld", CC->usersupp.usernum);
476 help_subst(buffer, "^usernum", buf2);
477 help_subst(buffer, "^sysadm", config.c_sysadm);
478 help_subst(buffer, "^variantname", CITADEL);
479 sprintf(buf2, "%d", config.c_maxsessions);
480 help_subst(buffer, "^maxsessions", buf2);
486 * memfmout() - Citadel text formatter and paginator.
487 * Although the original purpose of this routine was to format
488 * text to the reader's screen width, all we're really using it
489 * for here is to format text out to 80 columns before sending it
490 * to the client. The client software may reformat it again.
493 int width, /* screen width to use */
494 char *mptr, /* where are we going to get our text from? */
495 char subst, /* nonzero if we should do substitutions */
496 char *nl) /* string to terminate lines with */
508 c = 1; /* c is the current pos */
512 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
514 buffer[strlen(buffer) + 1] = 0;
515 buffer[strlen(buffer)] = ch;
518 if (buffer[0] == '^')
519 do_help_subst(buffer);
521 buffer[strlen(buffer) + 1] = 0;
523 strcpy(buffer, &buffer[1]);
531 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
533 if (((old == 13) || (old == 10)) && (isspace(real))) {
541 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
542 cprintf("%s%s", nl, aaa);
551 if ((strlen(aaa) + c) > (width - 5)) {
560 if ((ch == 13) || (ch == 10)) {
561 cprintf("%s%s", aaa, nl);
568 cprintf("%s%s", aaa, nl);
574 * Callback function for mime parser that simply lists the part
576 void list_this_part(char *name, char *filename, char *partnum, char *disp,
577 void *content, char *cbtype, size_t length, char *encoding,
581 cprintf("part=%s|%s|%s|%s|%s|%d\n",
582 name, filename, partnum, disp, cbtype, length);
587 * Callback function for mime parser that opens a section for downloading
589 void mime_download(char *name, char *filename, char *partnum, char *disp,
590 void *content, char *cbtype, size_t length, char *encoding,
594 /* Silently go away if there's already a download open... */
595 if (CC->download_fp != NULL)
598 /* ...or if this is not the desired section */
599 if (strcasecmp(desired_section, partnum))
602 CC->download_fp = tmpfile();
603 if (CC->download_fp == NULL)
606 fwrite(content, length, 1, CC->download_fp);
607 fflush(CC->download_fp);
608 rewind(CC->download_fp);
610 OpenCmdResult(filename, cbtype);
616 * Load a message from disk into memory.
617 * This is used by CtdlOutputMsg() and other fetch functions.
619 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
620 * using the CtdlMessageFree() function.
622 struct CtdlMessage *CtdlFetchMessage(long msgnum)
624 struct cdbdata *dmsgtext;
625 struct CtdlMessage *ret = NULL;
628 CIT_UBYTE field_header;
631 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
632 if (dmsgtext == NULL) {
635 mptr = dmsgtext->ptr;
637 /* Parse the three bytes that begin EVERY message on disk.
638 * The first is always 0xFF, the on-disk magic number.
639 * The second is the anonymous/public type byte.
640 * The third is the format type byte (vari, fixed, or MIME).
644 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
648 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
649 memset(ret, 0, sizeof(struct CtdlMessage));
651 ret->cm_magic = CTDLMESSAGE_MAGIC;
652 ret->cm_anon_type = *mptr++; /* Anon type byte */
653 ret->cm_format_type = *mptr++; /* Format type byte */
656 * The rest is zero or more arbitrary fields. Load them in.
657 * We're done when we encounter either a zero-length field or
658 * have just processed the 'M' (message text) field.
661 field_length = strlen(mptr);
662 if (field_length == 0)
664 field_header = *mptr++;
665 ret->cm_fields[field_header] = mallok(field_length);
666 strcpy(ret->cm_fields[field_header], mptr);
668 while (*mptr++ != 0); /* advance to next field */
670 } while ((field_length > 0) && (field_header != 'M'));
674 /* Always make sure there's something in the msg text field */
675 if (ret->cm_fields['M'] == NULL)
676 ret->cm_fields['M'] = strdoop("<no text>\n");
678 /* Perform "before read" hooks (aborting if any return nonzero) */
679 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
680 CtdlFreeMessage(ret);
689 * Returns 1 if the supplied pointer points to a valid Citadel message.
690 * If the pointer is NULL or the magic number check fails, returns 0.
692 int is_valid_message(struct CtdlMessage *msg) {
695 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
696 lprintf(3, "is_valid_message() -- self-check failed\n");
704 * 'Destructor' for struct CtdlMessage
706 void CtdlFreeMessage(struct CtdlMessage *msg)
710 if (is_valid_message(msg) == 0) return;
712 for (i = 0; i < 256; ++i)
713 if (msg->cm_fields[i] != NULL) {
714 phree(msg->cm_fields[i]);
717 msg->cm_magic = 0; /* just in case */
723 * Callback function for mime parser that wants to display text
725 void fixed_output(char *name, char *filename, char *partnum, char *disp,
726 void *content, char *cbtype, size_t length, char *encoding,
734 if (!strcasecmp(cbtype, "multipart/alternative")) {
735 strcpy(ma->prefix, partnum);
736 strcat(ma->prefix, ".");
742 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
744 && (ma->did_print == 1) ) {
745 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
751 if ( (!strcasecmp(cbtype, "text/plain"))
752 || (strlen(cbtype)==0) ) {
757 if (ch==10) cprintf("\r\n");
758 else cprintf("%c", ch);
761 else if (!strcasecmp(cbtype, "text/html")) {
762 ptr = html_to_ascii(content, 80, 0);
767 if (ch==10) cprintf("\r\n");
768 else cprintf("%c", ch);
772 else if (strncasecmp(cbtype, "multipart/", 10)) {
773 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
774 partnum, filename, cbtype, length);
780 * Get a message off disk. (returns om_* values found in msgbase.h)
783 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
784 int mode, /* how would you like that message? */
785 int headers_only, /* eschew the message body? */
786 int do_proto, /* do Citadel protocol responses? */
787 int crlf /* Use CRLF newlines instead of LF? */
789 struct CtdlMessage *TheMessage;
792 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
797 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
798 if (do_proto) cprintf("%d Not logged in.\n",
799 ERROR + NOT_LOGGED_IN);
800 return(om_not_logged_in);
803 /* FIXME ... small security issue
804 * We need to check to make sure the requested message is actually
805 * in the current room, and set msg_ok to 1 only if it is. This
806 * functionality is currently missing because I'm in a hurry to replace
807 * broken production code with nonbroken pre-beta code. :( -- ajc
810 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
812 return(om_no_such_msg);
817 * Fetch the message from disk
819 TheMessage = CtdlFetchMessage(msg_num);
820 if (TheMessage == NULL) {
821 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
823 return(om_no_such_msg);
826 retcode = CtdlOutputPreLoadedMsg(
827 TheMessage, msg_num, mode,
828 headers_only, do_proto, crlf);
830 CtdlFreeMessage(TheMessage);
836 * Get a message off disk. (returns om_* values found in msgbase.h)
839 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
841 int mode, /* how would you like that message? */
842 int headers_only, /* eschew the message body? */
843 int do_proto, /* do Citadel protocol responses? */
844 int crlf /* Use CRLF newlines instead of LF? */
850 char display_name[SIZ];
852 char *nl; /* newline string */
854 /* buffers needed for RFC822 translation */
864 sprintf(mid, "%ld", msg_num);
865 nl = (crlf ? "\r\n" : "\n");
867 if (!is_valid_message(TheMessage)) {
868 lprintf(1, "ERROR: invalid preloaded message for output\n");
869 return(om_no_such_msg);
872 /* Are we downloading a MIME component? */
873 if (mode == MT_DOWNLOAD) {
874 if (TheMessage->cm_format_type != FMT_RFC822) {
876 cprintf("%d This is not a MIME message.\n",
878 } else if (CC->download_fp != NULL) {
879 if (do_proto) cprintf(
880 "%d You already have a download open.\n",
883 /* Parse the message text component */
884 mptr = TheMessage->cm_fields['M'];
885 mime_parser(mptr, NULL,
886 *mime_download, NULL, NULL,
888 /* If there's no file open by this time, the requested
889 * section wasn't found, so print an error
891 if (CC->download_fp == NULL) {
892 if (do_proto) cprintf(
893 "%d Section %s not found.\n",
894 ERROR + FILE_NOT_FOUND,
898 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
901 /* now for the user-mode message reading loops */
902 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
904 /* Tell the client which format type we're using. If this is a
905 * MIME message, *lie* about it and tell the user it's fixed-format.
907 if (mode == MT_CITADEL) {
908 if (TheMessage->cm_format_type == FMT_RFC822) {
909 if (do_proto) cprintf("type=1\n");
912 if (do_proto) cprintf("type=%d\n",
913 TheMessage->cm_format_type);
917 /* nhdr=yes means that we're only displaying headers, no body */
918 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
919 if (do_proto) cprintf("nhdr=yes\n");
922 /* begin header processing loop for Citadel message format */
924 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
926 strcpy(display_name, "<unknown>");
927 if (TheMessage->cm_fields['A']) {
928 strcpy(buf, TheMessage->cm_fields['A']);
929 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
930 if (TheMessage->cm_anon_type == MES_ANON)
931 strcpy(display_name, "****");
932 else if (TheMessage->cm_anon_type == MES_AN2)
933 strcpy(display_name, "anonymous");
935 strcpy(display_name, buf);
937 && ((TheMessage->cm_anon_type == MES_ANON)
938 || (TheMessage->cm_anon_type == MES_AN2))) {
939 sprintf(&display_name[strlen(display_name)],
944 strcpy(allkeys, FORDER);
945 for (i=0; i<strlen(allkeys); ++i) {
946 k = (int) allkeys[i];
948 if (TheMessage->cm_fields[k] != NULL) {
950 if (do_proto) cprintf("%s=%s\n",
955 if (do_proto) cprintf("%s=%s\n",
957 TheMessage->cm_fields[k]
966 /* begin header processing loop for RFC822 transfer format */
971 strcpy(snode, NODENAME);
972 strcpy(lnode, HUMANNODE);
973 if (mode == MT_RFC822) {
974 cprintf("X-UIDL: %ld%s", msg_num, nl);
975 for (i = 0; i < 256; ++i) {
976 if (TheMessage->cm_fields[i]) {
977 mptr = TheMessage->cm_fields[i];
984 "Path:" removed for now because it confuses brain-dead Microsoft shitware
985 into thinking that mail messages are newsgroup messages instead. When we
986 add NNTP support back into Citadel we'll have to add code to only output
987 this field when appropriate.
989 cprintf("Path: %s%s", mptr, nl);
993 cprintf("Subject: %s%s", mptr, nl);
999 cprintf("X-Citadel-Room: %s%s",
1002 strcpy(snode, mptr);
1004 cprintf("To: %s%s", mptr, nl);
1005 else if (i == 'T') {
1006 datestring(datestamp, atol(mptr),
1007 DATESTRING_RFC822 );
1008 cprintf("Date: %s%s", datestamp, nl);
1014 for (i=0; i<strlen(suser); ++i) {
1015 suser[i] = tolower(suser[i]);
1016 if (!isalnum(suser[i])) suser[i]='_';
1019 if (mode == MT_RFC822) {
1020 if (!strcasecmp(snode, NODENAME)) {
1021 strcpy(snode, FQDN);
1024 /* Construct a fun message id */
1025 cprintf("Message-ID: <%s", mid);
1026 if (strchr(mid, '@')==NULL) {
1027 cprintf("@%s", snode);
1031 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1033 if (strlen(fuser) > 0) {
1034 cprintf("From: %s (%s)%s", fuser, luser, nl);
1037 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1040 cprintf("Organization: %s%s", lnode, nl);
1043 /* end header processing loop ... at this point, we're in the text */
1045 mptr = TheMessage->cm_fields['M'];
1047 /* Tell the client about the MIME parts in this message */
1048 if (TheMessage->cm_format_type == FMT_RFC822) {
1049 if (mode == MT_CITADEL) {
1050 mime_parser(mptr, NULL,
1051 *list_this_part, NULL, NULL,
1054 else if (mode == MT_MIME) { /* list parts only */
1055 mime_parser(mptr, NULL,
1056 *list_this_part, NULL, NULL,
1058 if (do_proto) cprintf("000\n");
1061 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1062 /* FIXME ... we have to put some code in here to avoid
1063 * printing duplicate header information when both
1064 * Citadel and RFC822 headers exist. Preference should
1065 * probably be given to the RFC822 headers.
1067 while (ch=*(mptr++), ch!=0) {
1069 else if (ch==10) cprintf("%s", nl);
1070 else cprintf("%c", ch);
1072 if (do_proto) cprintf("000\n");
1078 if (do_proto) cprintf("000\n");
1082 /* signify start of msg text */
1083 if (mode == MT_CITADEL)
1084 if (do_proto) cprintf("text\n");
1085 if (mode == MT_RFC822) {
1086 if (TheMessage->cm_fields['U'] == NULL) {
1087 cprintf("Subject: (no subject)%s", nl);
1092 /* If the format type on disk is 1 (fixed-format), then we want
1093 * everything to be output completely literally ... regardless of
1094 * what message transfer format is in use.
1096 if (TheMessage->cm_format_type == FMT_FIXED) {
1098 while (ch = *mptr++, ch > 0) {
1101 if ((ch == 10) || (strlen(buf) > 250)) {
1102 cprintf("%s%s", buf, nl);
1105 buf[strlen(buf) + 1] = 0;
1106 buf[strlen(buf)] = ch;
1109 if (strlen(buf) > 0)
1110 cprintf("%s%s", buf, nl);
1113 /* If the message on disk is format 0 (Citadel vari-format), we
1114 * output using the formatter at 80 columns. This is the final output
1115 * form if the transfer format is RFC822, but if the transfer format
1116 * is Citadel proprietary, it'll still work, because the indentation
1117 * for new paragraphs is correct and the client will reformat the
1118 * message to the reader's screen width.
1120 if (TheMessage->cm_format_type == FMT_CITADEL) {
1121 memfmout(80, mptr, 0, nl);
1124 /* If the message on disk is format 4 (MIME), we've gotta hand it
1125 * off to the MIME parser. The client has already been told that
1126 * this message is format 1 (fixed format), so the callback function
1127 * we use will display those parts as-is.
1129 if (TheMessage->cm_format_type == FMT_RFC822) {
1130 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1131 memset(ma, 0, sizeof(struct ma_info));
1132 mime_parser(mptr, NULL,
1133 *fixed_output, NULL, NULL,
1137 /* now we're done */
1138 if (do_proto) cprintf("000\n");
1145 * display a message (mode 0 - Citadel proprietary)
1147 void cmd_msg0(char *cmdbuf)
1150 int headers_only = 0;
1152 msgid = extract_long(cmdbuf, 0);
1153 headers_only = extract_int(cmdbuf, 1);
1155 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1161 * display a message (mode 2 - RFC822)
1163 void cmd_msg2(char *cmdbuf)
1166 int headers_only = 0;
1168 msgid = extract_long(cmdbuf, 0);
1169 headers_only = extract_int(cmdbuf, 1);
1171 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1177 * display a message (mode 3 - IGnet raw format - internal programs only)
1179 void cmd_msg3(char *cmdbuf)
1182 struct CtdlMessage *msg;
1185 if (CC->internal_pgm == 0) {
1186 cprintf("%d This command is for internal programs only.\n",
1191 msgnum = extract_long(cmdbuf, 0);
1192 msg = CtdlFetchMessage(msgnum);
1194 cprintf("%d Message %ld not found.\n",
1199 serialize_message(&smr, msg);
1200 CtdlFreeMessage(msg);
1203 cprintf("%d Unable to serialize message\n",
1204 ERROR+INTERNAL_ERROR);
1208 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1209 client_write(smr.ser, smr.len);
1216 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1218 void cmd_msg4(char *cmdbuf)
1222 msgid = extract_long(cmdbuf, 0);
1223 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1227 * Open a component of a MIME message as a download file
1229 void cmd_opna(char *cmdbuf)
1233 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1235 msgid = extract_long(cmdbuf, 0);
1236 extract(desired_section, cmdbuf, 1);
1238 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1243 * Save a message pointer into a specified room
1244 * (Returns 0 for success, nonzero for failure)
1245 * roomname may be NULL to use the current room
1247 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1249 char hold_rm[ROOMNAMELEN];
1250 struct cdbdata *cdbfr;
1253 long highest_msg = 0L;
1254 struct CtdlMessage *msg = NULL;
1256 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1257 roomname, msgid, flags);
1259 strcpy(hold_rm, CC->quickroom.QRname);
1261 /* We may need to check to see if this message is real */
1262 if ( (flags & SM_VERIFY_GOODNESS)
1263 || (flags & SM_DO_REPL_CHECK)
1265 msg = CtdlFetchMessage(msgid);
1266 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1269 /* Perform replication checks if necessary */
1270 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1272 if (getroom(&CC->quickroom,
1273 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1275 lprintf(9, "No such room <%s>\n", roomname);
1276 if (msg != NULL) CtdlFreeMessage(msg);
1277 return(ERROR + ROOM_NOT_FOUND);
1280 if (ReplicationChecks(msg) != 0) {
1281 getroom(&CC->quickroom, hold_rm);
1282 if (msg != NULL) CtdlFreeMessage(msg);
1283 lprintf(9, "Did replication, and newer exists\n");
1288 /* Now the regular stuff */
1289 if (lgetroom(&CC->quickroom,
1290 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1292 lprintf(9, "No such room <%s>\n", roomname);
1293 if (msg != NULL) CtdlFreeMessage(msg);
1294 return(ERROR + ROOM_NOT_FOUND);
1297 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1298 if (cdbfr == NULL) {
1302 msglist = mallok(cdbfr->len);
1303 if (msglist == NULL)
1304 lprintf(3, "ERROR malloc msglist!\n");
1305 num_msgs = cdbfr->len / sizeof(long);
1306 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1311 /* Make sure the message doesn't already exist in this room. It
1312 * is absolutely taboo to have more than one reference to the same
1313 * message in a room.
1315 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1316 if (msglist[i] == msgid) {
1317 lputroom(&CC->quickroom); /* unlock the room */
1318 getroom(&CC->quickroom, hold_rm);
1319 if (msg != NULL) CtdlFreeMessage(msg);
1320 return(ERROR + ALREADY_EXISTS);
1324 /* Now add the new message */
1326 msglist = reallok(msglist,
1327 (num_msgs * sizeof(long)));
1329 if (msglist == NULL) {
1330 lprintf(3, "ERROR: can't realloc message list!\n");
1332 msglist[num_msgs - 1] = msgid;
1334 /* Sort the message list, so all the msgid's are in order */
1335 num_msgs = sort_msglist(msglist, num_msgs);
1337 /* Determine the highest message number */
1338 highest_msg = msglist[num_msgs - 1];
1340 /* Write it back to disk. */
1341 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1342 msglist, num_msgs * sizeof(long));
1344 /* Free up the memory we used. */
1347 /* Update the highest-message pointer and unlock the room. */
1348 CC->quickroom.QRhighest = highest_msg;
1349 lputroom(&CC->quickroom);
1350 getroom(&CC->quickroom, hold_rm);
1352 /* Bump the reference count for this message. */
1353 if ((flags & SM_DONT_BUMP_REF)==0) {
1354 AdjRefCount(msgid, +1);
1357 /* Return success. */
1358 if (msg != NULL) CtdlFreeMessage(msg);
1365 * Message base operation to send a message to the master file
1366 * (returns new message number)
1368 * This is the back end for CtdlSaveMsg() and should not be directly
1369 * called by server-side modules.
1372 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1373 FILE *save_a_copy) /* save a copy to disk? */
1380 /* Get a new message number */
1381 newmsgid = get_new_message_number();
1382 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1384 /* Generate an ID if we don't have one already */
1385 if (msg->cm_fields['I']==NULL) {
1386 msg->cm_fields['I'] = strdoop(msgidbuf);
1389 serialize_message(&smr, msg);
1392 cprintf("%d Unable to serialize message\n",
1393 ERROR+INTERNAL_ERROR);
1397 /* Write our little bundle of joy into the message base */
1398 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1399 smr.ser, smr.len) < 0) {
1400 lprintf(2, "Can't store message\n");
1406 /* If the caller specified that a copy should be saved to a particular
1407 * file handle, do that now too.
1409 if (save_a_copy != NULL) {
1410 fwrite(smr.ser, smr.len, 1, save_a_copy);
1413 /* Free the memory we used for the serialized message */
1416 /* Return the *local* message ID to the caller
1417 * (even if we're storing an incoming network message)
1425 * Serialize a struct CtdlMessage into the format used on disk and network.
1427 * This function loads up a "struct ser_ret" (defined in server.h) which
1428 * contains the length of the serialized message and a pointer to the
1429 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1431 void serialize_message(struct ser_ret *ret, /* return values */
1432 struct CtdlMessage *msg) /* unserialized msg */
1436 static char *forder = FORDER;
1438 if (is_valid_message(msg) == 0) return; /* self check */
1441 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1442 ret->len = ret->len +
1443 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1445 lprintf(9, "calling malloc(%d)\n", ret->len);
1446 ret->ser = mallok(ret->len);
1447 if (ret->ser == NULL) {
1453 ret->ser[1] = msg->cm_anon_type;
1454 ret->ser[2] = msg->cm_format_type;
1457 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1458 ret->ser[wlen++] = (char)forder[i];
1459 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1460 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1462 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1471 * Back end for the ReplicationChecks() function
1473 void check_repl(long msgnum, void *userdata) {
1474 struct CtdlMessage *msg;
1475 time_t timestamp = (-1L);
1477 lprintf(9, "check_repl() found message %ld\n", msgnum);
1478 msg = CtdlFetchMessage(msgnum);
1479 if (msg == NULL) return;
1480 if (msg->cm_fields['T'] != NULL) {
1481 timestamp = atol(msg->cm_fields['T']);
1483 CtdlFreeMessage(msg);
1485 if (timestamp > msg_repl->highest) {
1486 msg_repl->highest = timestamp; /* newer! */
1487 lprintf(9, "newer!\n");
1490 lprintf(9, "older!\n");
1492 /* Existing isn't newer? Then delete the old one(s). */
1493 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1498 * Check to see if any messages already exist which carry the same Extended ID
1502 * -> With older timestamps: delete them and return 0. Message will be saved.
1503 * -> With newer timestamps: return 1. Message save will be aborted.
1505 int ReplicationChecks(struct CtdlMessage *msg) {
1506 struct CtdlMessage *template;
1509 lprintf(9, "ReplicationChecks() started\n");
1510 /* No extended id? Don't do anything. */
1511 if (msg->cm_fields['E'] == NULL) return 0;
1512 if (strlen(msg->cm_fields['E']) == 0) return 0;
1513 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1515 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1516 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1517 msg_repl->highest = atol(msg->cm_fields['T']);
1519 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1520 memset(template, 0, sizeof(struct CtdlMessage));
1521 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1523 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1526 /* If a newer message exists with the same Extended ID, abort
1529 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1533 CtdlFreeMessage(template);
1534 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1542 * Save a message to disk
1544 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1545 char *rec, /* Recipient (mail) */
1546 char *force, /* force a particular room? */
1547 int supplied_mailtype) /* local or remote type */
1550 char hold_rm[ROOMNAMELEN];
1551 char actual_rm[ROOMNAMELEN];
1552 char force_room[ROOMNAMELEN];
1553 char content_type[SIZ]; /* We have to learn this */
1554 char recipient[SIZ];
1557 struct usersupp userbuf;
1559 struct SuppMsgInfo smi;
1560 FILE *network_fp = NULL;
1561 static int seqnum = 1;
1562 struct CtdlMessage *imsg;
1566 lprintf(9, "CtdlSaveMsg() called\n");
1567 if (is_valid_message(msg) == 0) return(-1); /* self check */
1568 mailtype = supplied_mailtype;
1570 /* If this message has no timestamp, we take the liberty of
1571 * giving it one, right now.
1573 if (msg->cm_fields['T'] == NULL) {
1574 lprintf(9, "Generating timestamp\n");
1575 sprintf(aaa, "%ld", time(NULL));
1576 msg->cm_fields['T'] = strdoop(aaa);
1579 /* If this message has no path, we generate one.
1581 if (msg->cm_fields['P'] == NULL) {
1582 lprintf(9, "Generating path\n");
1583 if (msg->cm_fields['A'] != NULL) {
1584 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1585 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1586 if (isspace(msg->cm_fields['P'][a])) {
1587 msg->cm_fields['P'][a] = ' ';
1592 msg->cm_fields['P'] = strdoop("unknown");
1596 strcpy(force_room, force);
1598 /* Strip non-printable characters out of the recipient name */
1599 lprintf(9, "Checking recipient (if present)\n");
1600 strcpy(recipient, rec);
1601 for (a = 0; a < strlen(recipient); ++a)
1602 if (!isprint(recipient[a]))
1603 strcpy(&recipient[a], &recipient[a + 1]);
1605 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1606 for (a=0; a<strlen(recipient); ++a) {
1607 if (recipient[a] == '@') {
1608 if (CtdlHostAlias(&recipient[a+1])
1609 == hostalias_localhost) {
1611 lprintf(7, "Changed to <%s>\n", recipient);
1612 mailtype = MES_LOCAL;
1617 lprintf(9, "Recipient is <%s>\n", recipient);
1619 /* Learn about what's inside, because it's what's inside that counts */
1620 lprintf(9, "Learning what's inside\n");
1621 if (msg->cm_fields['M'] == NULL) {
1622 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1625 switch (msg->cm_format_type) {
1627 strcpy(content_type, "text/x-citadel-variformat");
1630 strcpy(content_type, "text/plain");
1633 strcpy(content_type, "text/plain");
1634 /* advance past header fields */
1635 mptr = msg->cm_fields['M'];
1638 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1639 safestrncpy(content_type, mptr,
1640 sizeof(content_type));
1641 strcpy(content_type, &content_type[14]);
1642 for (a = 0; a < strlen(content_type); ++a)
1643 if ((content_type[a] == ';')
1644 || (content_type[a] == ' ')
1645 || (content_type[a] == 13)
1646 || (content_type[a] == 10))
1647 content_type[a] = 0;
1654 /* Goto the correct room */
1655 lprintf(9, "Switching rooms\n");
1656 strcpy(hold_rm, CC->quickroom.QRname);
1657 strcpy(actual_rm, CC->quickroom.QRname);
1659 /* If the user is a twit, move to the twit room for posting */
1660 lprintf(9, "Handling twit stuff\n");
1662 if (CC->usersupp.axlevel == 2) {
1663 strcpy(hold_rm, actual_rm);
1664 strcpy(actual_rm, config.c_twitroom);
1668 /* ...or if this message is destined for Aide> then go there. */
1669 if (strlen(force_room) > 0) {
1670 strcpy(actual_rm, force_room);
1673 lprintf(9, "Possibly relocating\n");
1674 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1675 getroom(&CC->quickroom, actual_rm);
1679 * If this message has no O (room) field, generate one.
1681 if (msg->cm_fields['O'] == NULL) {
1682 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1685 /* Perform "before save" hooks (aborting if any return nonzero) */
1686 lprintf(9, "Performing before-save hooks\n");
1687 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1689 /* If this message has an Extended ID, perform replication checks */
1690 lprintf(9, "Performing replication checks\n");
1691 if (ReplicationChecks(msg) > 0) return(-1);
1693 /* Network mail - send a copy to the network program. */
1694 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1695 lprintf(9, "Sending network spool\n");
1696 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1697 (long) getpid(), CC->cs_pid, ++seqnum);
1698 lprintf(9, "Saving a copy to %s\n", aaa);
1699 network_fp = fopen(aaa, "ab+");
1700 if (network_fp == NULL)
1701 lprintf(2, "ERROR: %s\n", strerror(errno));
1704 /* Save it to disk */
1705 lprintf(9, "Saving to disk\n");
1706 newmsgid = send_message(msg, network_fp);
1707 if (network_fp != NULL) {
1709 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1712 if (newmsgid <= 0L) return(-1);
1714 /* Write a supplemental message info record. This doesn't have to
1715 * be a critical section because nobody else knows about this message
1718 lprintf(9, "Creating SuppMsgInfo record\n");
1719 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1720 smi.smi_msgnum = newmsgid;
1721 smi.smi_refcount = 0;
1722 safestrncpy(smi.smi_content_type, content_type, 64);
1723 PutSuppMsgInfo(&smi);
1725 /* Now figure out where to store the pointers */
1726 lprintf(9, "Storing pointers\n");
1728 /* If this is being done by the networker delivering a private
1729 * message, we want to BYPASS saving the sender's copy (because there
1730 * is no local sender; it would otherwise go to the Trashcan).
1732 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1733 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1734 lprintf(3, "ERROR saving message pointer!\n");
1735 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1739 /* For internet mail, drop a copy in the outbound queue room */
1740 if (mailtype == MES_INTERNET) {
1741 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1744 /* Bump this user's messages posted counter. */
1745 lprintf(9, "Updating user\n");
1746 lgetuser(&CC->usersupp, CC->curr_user);
1747 CC->usersupp.posted = CC->usersupp.posted + 1;
1748 lputuser(&CC->usersupp);
1750 /* If this is private, local mail, make a copy in the
1751 * recipient's mailbox and bump the reference count.
1753 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1754 if (getuser(&userbuf, recipient) == 0) {
1755 lprintf(9, "Delivering private mail\n");
1756 MailboxName(actual_rm, &userbuf, MAILROOM);
1757 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1760 lprintf(9, "No user <%s>, saving in %s> instead\n",
1761 recipient, AIDEROOM);
1762 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1766 /* Perform "after save" hooks */
1767 lprintf(9, "Performing after-save hooks\n");
1768 PerformMessageHooks(msg, EVT_AFTERSAVE);
1771 lprintf(9, "Returning to original room\n");
1772 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1773 getroom(&CC->quickroom, hold_rm);
1775 /* For internet mail, generate delivery instructions
1776 * (Yes, this is recursive! Deal with it!)
1778 if (mailtype == MES_INTERNET) {
1779 lprintf(9, "Generating delivery instructions\n");
1780 instr = mallok(2048);
1782 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1785 SPOOLMIME, newmsgid, time(NULL),
1786 msg->cm_fields['A'], msg->cm_fields['N'],
1789 imsg = mallok(sizeof(struct CtdlMessage));
1790 memset(imsg, 0, sizeof(struct CtdlMessage));
1791 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1792 imsg->cm_anon_type = MES_NORMAL;
1793 imsg->cm_format_type = FMT_RFC822;
1794 imsg->cm_fields['A'] = strdoop("Citadel");
1795 imsg->cm_fields['M'] = instr;
1796 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1797 CtdlFreeMessage(imsg);
1806 * Convenience function for generating small administrative messages.
1808 void quickie_message(char *from, char *to, char *room, char *text)
1810 struct CtdlMessage *msg;
1812 msg = mallok(sizeof(struct CtdlMessage));
1813 memset(msg, 0, sizeof(struct CtdlMessage));
1814 msg->cm_magic = CTDLMESSAGE_MAGIC;
1815 msg->cm_anon_type = MES_NORMAL;
1816 msg->cm_format_type = 0;
1817 msg->cm_fields['A'] = strdoop(from);
1818 msg->cm_fields['O'] = strdoop(room);
1819 msg->cm_fields['N'] = strdoop(NODENAME);
1821 msg->cm_fields['R'] = strdoop(to);
1822 msg->cm_fields['M'] = strdoop(text);
1824 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1825 CtdlFreeMessage(msg);
1826 syslog(LOG_NOTICE, text);
1832 * Back end function used by make_message() and similar functions
1834 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1835 size_t maxlen, /* maximum message length */
1836 char *exist /* if non-null, append to it;
1837 exist is ALWAYS freed */
1841 size_t message_len = 0;
1842 size_t buffer_len = 0;
1846 if (exist == NULL) {
1850 m = reallok(exist, strlen(exist) + 4096);
1851 if (m == NULL) phree(exist);
1854 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1861 /* read in the lines of message text one by one */
1862 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1864 /* strip trailing newline type stuff */
1865 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1866 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1868 linelen = strlen(buf);
1870 /* augment the buffer if we have to */
1871 if ((message_len + linelen + 2) > buffer_len) {
1872 lprintf(9, "realloking\n");
1873 ptr = reallok(m, (buffer_len * 2) );
1874 if (ptr == NULL) { /* flush if can't allocate */
1875 while ( (client_gets(buf)>0) &&
1876 strcmp(buf, terminator)) ;;
1879 buffer_len = (buffer_len * 2);
1881 lprintf(9, "buffer_len is %d\n", buffer_len);
1885 /* Add the new line to the buffer. We avoid using strcat()
1886 * because that would involve traversing the entire message
1887 * after each line, and this function needs to run fast.
1889 strcpy(&m[message_len], buf);
1890 m[message_len + linelen] = '\n';
1891 m[message_len + linelen + 1] = 0;
1892 message_len = message_len + linelen + 1;
1894 /* if we've hit the max msg length, flush the rest */
1895 if (message_len >= maxlen) {
1896 while ( (client_gets(buf)>0)
1897 && strcmp(buf, terminator)) ;;
1908 * Build a binary message to be saved on disk.
1911 struct CtdlMessage *make_message(
1912 struct usersupp *author, /* author's usersupp structure */
1913 char *recipient, /* NULL if it's not mail */
1914 char *room, /* room where it's going */
1915 int type, /* see MES_ types in header file */
1916 int net_type, /* see MES_ types in header file */
1917 int format_type, /* local or remote (see citadel.h) */
1918 char *fake_name) /* who we're masquerading as */
1924 struct CtdlMessage *msg;
1926 msg = mallok(sizeof(struct CtdlMessage));
1927 memset(msg, 0, sizeof(struct CtdlMessage));
1928 msg->cm_magic = CTDLMESSAGE_MAGIC;
1929 msg->cm_anon_type = type;
1930 msg->cm_format_type = format_type;
1932 /* Don't confuse the poor folks if it's not routed mail. */
1933 strcpy(dest_node, "");
1935 /* If net_type is MES_BINARY, split out the destination node. */
1936 if (net_type == MES_BINARY) {
1937 strcpy(dest_node, NODENAME);
1938 for (a = 0; a < strlen(recipient); ++a) {
1939 if (recipient[a] == '@') {
1941 strcpy(dest_node, &recipient[a + 1]);
1946 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1947 if (net_type == MES_INTERNET) {
1948 strcpy(dest_node, "internet");
1951 while (isspace(recipient[strlen(recipient) - 1]))
1952 recipient[strlen(recipient) - 1] = 0;
1954 sprintf(buf, "cit%ld", author->usernum); /* Path */
1955 msg->cm_fields['P'] = strdoop(buf);
1957 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1958 msg->cm_fields['T'] = strdoop(buf);
1960 if (fake_name[0]) /* author */
1961 msg->cm_fields['A'] = strdoop(fake_name);
1963 msg->cm_fields['A'] = strdoop(author->fullname);
1965 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1966 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1968 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1970 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1971 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1973 if (recipient[0] != 0)
1974 msg->cm_fields['R'] = strdoop(recipient);
1975 if (dest_node[0] != 0)
1976 msg->cm_fields['D'] = strdoop(dest_node);
1979 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1980 config.c_maxmsglen, NULL);
1988 * Check to see whether we have permission to post a message in the current
1989 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
1990 * returns 0 on success.
1992 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
1994 if (!(CC->logged_in)) {
1995 sprintf(errmsgbuf, "Not logged in.");
1996 return (ERROR + NOT_LOGGED_IN);
1999 if ((CC->usersupp.axlevel < 2)
2000 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2001 sprintf(errmsgbuf, "Need to be validated to enter "
2002 "(except in %s> to sysop)", MAILROOM);
2003 return (ERROR + HIGHER_ACCESS_REQUIRED);
2006 if ((CC->usersupp.axlevel < 4)
2007 && (CC->quickroom.QRflags & QR_NETWORK)) {
2008 sprintf(errmsgbuf, "Need net privileges to enter here.");
2009 return (ERROR + HIGHER_ACCESS_REQUIRED);
2012 if ((CC->usersupp.axlevel < 6)
2013 && (CC->quickroom.QRflags & QR_READONLY)) {
2014 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2015 return (ERROR + HIGHER_ACCESS_REQUIRED);
2018 strcpy(errmsgbuf, "Ok");
2026 * message entry - mode 0 (normal)
2028 void cmd_ent0(char *entargs)
2031 char recipient[SIZ];
2033 int format_type = 0;
2034 char newusername[SIZ];
2035 struct CtdlMessage *msg;
2039 struct usersupp tempUS;
2043 post = extract_int(entargs, 0);
2044 extract(recipient, entargs, 1);
2045 anon_flag = extract_int(entargs, 2);
2046 format_type = extract_int(entargs, 3);
2048 /* first check to make sure the request is valid. */
2050 err = CtdlDoIHavePermissionToPostInThisRoom(buf);
2052 cprintf("%d %s\n", err, buf);
2056 /* Check some other permission type things. */
2059 if (CC->usersupp.axlevel < 6) {
2060 cprintf("%d You don't have permission to masquerade.\n",
2061 ERROR + HIGHER_ACCESS_REQUIRED);
2064 extract(newusername, entargs, 4);
2065 memset(CC->fake_postname, 0, 32);
2066 strcpy(CC->fake_postname, newusername);
2067 cprintf("%d Ok\n", OK);
2070 CC->cs_flags |= CS_POSTING;
2073 if (CC->quickroom.QRflags & QR_MAILBOX) {
2074 if (CC->usersupp.axlevel >= 2) {
2075 strcpy(buf, recipient);
2077 strcpy(buf, "sysop");
2078 e = alias(buf); /* alias and mail type */
2079 if ((buf[0] == 0) || (e == MES_ERROR)) {
2080 cprintf("%d Unknown address - cannot send message.\n",
2081 ERROR + NO_SUCH_USER);
2084 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2085 cprintf("%d Net privileges required for network mail.\n",
2086 ERROR + HIGHER_ACCESS_REQUIRED);
2089 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2090 && ((CC->usersupp.flags & US_INTERNET) == 0)
2091 && (!CC->internal_pgm)) {
2092 cprintf("%d You don't have access to Internet mail.\n",
2093 ERROR + HIGHER_ACCESS_REQUIRED);
2096 if (!strcasecmp(buf, "sysop")) {
2099 else if (e == MES_LOCAL) { /* don't search local file */
2100 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2101 cprintf("%d Can't send mail to yourself!\n",
2102 ERROR + NO_SUCH_USER);
2105 /* Check to make sure the user exists; also get the correct
2106 * upper/lower casing of the name.
2108 a = getuser(&tempUS, buf);
2110 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2113 strcpy(buf, tempUS.fullname);
2118 if (CC->quickroom.QRflags & QR_ANONONLY)
2120 if (CC->quickroom.QRflags & QR_ANONOPT) {
2124 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2127 /* If we're only checking the validity of the request, return
2128 * success without creating the message.
2131 cprintf("%d %s\n", OK, buf);
2135 cprintf("%d send message\n", SEND_LISTING);
2137 /* Read in the message from the client. */
2138 if (CC->fake_postname[0])
2139 msg = make_message(&CC->usersupp, buf,
2140 CC->quickroom.QRname, b, e, format_type,
2142 else if (CC->fake_username[0])
2143 msg = make_message(&CC->usersupp, buf,
2144 CC->quickroom.QRname, b, e, format_type,
2147 msg = make_message(&CC->usersupp, buf,
2148 CC->quickroom.QRname, b, e, format_type, "");
2151 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2152 CtdlFreeMessage(msg);
2153 CC->fake_postname[0] = '\0';
2160 * message entry - mode 3 (raw)
2162 void cmd_ent3(char *entargs)
2168 unsigned char ch, which_field;
2169 struct usersupp tempUS;
2171 struct CtdlMessage *msg;
2174 if (CC->internal_pgm == 0) {
2175 cprintf("%d This command is for internal programs only.\n",
2180 /* See if there's a recipient, but make sure it's a real one */
2181 extract(recp, entargs, 1);
2182 for (a = 0; a < strlen(recp); ++a)
2183 if (!isprint(recp[a]))
2184 strcpy(&recp[a], &recp[a + 1]);
2185 while (isspace(recp[0]))
2186 strcpy(recp, &recp[1]);
2187 while (isspace(recp[strlen(recp) - 1]))
2188 recp[strlen(recp) - 1] = 0;
2190 /* If we're in Mail, check the recipient */
2191 if (strlen(recp) > 0) {
2192 e = alias(recp); /* alias and mail type */
2193 if ((recp[0] == 0) || (e == MES_ERROR)) {
2194 cprintf("%d Unknown address - cannot send message.\n",
2195 ERROR + NO_SUCH_USER);
2198 if (e == MES_LOCAL) {
2199 a = getuser(&tempUS, recp);
2201 cprintf("%d No such user.\n",
2202 ERROR + NO_SUCH_USER);
2208 /* At this point, message has been approved. */
2209 if (extract_int(entargs, 0) == 0) {
2210 cprintf("%d OK to send\n", OK);
2214 msglen = extract_long(entargs, 2);
2215 msg = mallok(sizeof(struct CtdlMessage));
2217 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2221 memset(msg, 0, sizeof(struct CtdlMessage));
2222 tempbuf = mallok(msglen);
2223 if (tempbuf == NULL) {
2224 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2229 cprintf("%d %ld\n", SEND_BINARY, msglen);
2231 client_read(&ch, 1); /* 0xFF magic number */
2232 msg->cm_magic = CTDLMESSAGE_MAGIC;
2233 client_read(&ch, 1); /* anon type */
2234 msg->cm_anon_type = ch;
2235 client_read(&ch, 1); /* format type */
2236 msg->cm_format_type = ch;
2237 msglen = msglen - 3;
2239 while (msglen > 0) {
2240 client_read(&which_field, 1);
2241 if (!isalpha(which_field)) valid_msg = 0;
2245 client_read(&ch, 1);
2247 a = strlen(tempbuf);
2250 } while ( (ch != 0) && (msglen > 0) );
2252 msg->cm_fields[which_field] = strdoop(tempbuf);
2255 msg->cm_flags = CM_SKIP_HOOKS;
2256 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2257 CtdlFreeMessage(msg);
2263 * API function to delete messages which match a set of criteria
2264 * (returns the actual number of messages deleted)
2266 int CtdlDeleteMessages(char *room_name, /* which room */
2267 long dmsgnum, /* or "0" for any */
2268 char *content_type /* or "" for any */
2272 struct quickroom qrbuf;
2273 struct cdbdata *cdbfr;
2274 long *msglist = NULL;
2277 int num_deleted = 0;
2279 struct SuppMsgInfo smi;
2281 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2282 room_name, dmsgnum, content_type);
2284 /* get room record, obtaining a lock... */
2285 if (lgetroom(&qrbuf, room_name) != 0) {
2286 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2288 return (0); /* room not found */
2290 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2292 if (cdbfr != NULL) {
2293 msglist = mallok(cdbfr->len);
2294 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2295 num_msgs = cdbfr->len / sizeof(long);
2299 for (i = 0; i < num_msgs; ++i) {
2302 /* Set/clear a bit for each criterion */
2304 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2305 delete_this |= 0x01;
2307 if (strlen(content_type) == 0) {
2308 delete_this |= 0x02;
2310 GetSuppMsgInfo(&smi, msglist[i]);
2311 if (!strcasecmp(smi.smi_content_type,
2313 delete_this |= 0x02;
2317 /* Delete message only if all bits are set */
2318 if (delete_this == 0x03) {
2319 AdjRefCount(msglist[i], -1);
2325 num_msgs = sort_msglist(msglist, num_msgs);
2326 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2327 msglist, (num_msgs * sizeof(long)));
2329 qrbuf.QRhighest = msglist[num_msgs - 1];
2333 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2334 return (num_deleted);
2340 * Check whether the current user has permission to delete messages from
2341 * the current room (returns 1 for yes, 0 for no)
2343 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2344 getuser(&CC->usersupp, CC->curr_user);
2345 if ((CC->usersupp.axlevel < 6)
2346 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2347 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2348 && (!(CC->internal_pgm))) {
2357 * Delete message from current room
2359 void cmd_dele(char *delstr)
2364 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2365 cprintf("%d Higher access required.\n",
2366 ERROR + HIGHER_ACCESS_REQUIRED);
2369 delnum = extract_long(delstr, 0);
2371 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2374 cprintf("%d %d message%s deleted.\n", OK,
2375 num_deleted, ((num_deleted != 1) ? "s" : ""));
2377 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2383 * Back end API function for moves and deletes
2385 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2388 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2389 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2390 if (err != 0) return(err);
2398 * move or copy a message to another room
2400 void cmd_move(char *args)
2404 struct quickroom qtemp;
2408 num = extract_long(args, 0);
2409 extract(targ, args, 1);
2410 targ[ROOMNAMELEN - 1] = 0;
2411 is_copy = extract_int(args, 2);
2413 getuser(&CC->usersupp, CC->curr_user);
2414 if ((CC->usersupp.axlevel < 6)
2415 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2416 cprintf("%d Higher access required.\n",
2417 ERROR + HIGHER_ACCESS_REQUIRED);
2421 if (getroom(&qtemp, targ) != 0) {
2422 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2426 err = CtdlCopyMsgToRoom(num, targ);
2428 cprintf("%d Cannot store message in %s: error %d\n",
2433 /* Now delete the message from the source room,
2434 * if this is a 'move' rather than a 'copy' operation.
2436 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2438 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2444 * GetSuppMsgInfo() - Get the supplementary record for a message
2446 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2449 struct cdbdata *cdbsmi;
2452 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2453 smibuf->smi_msgnum = msgnum;
2454 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2456 /* Use the negative of the message number for its supp record index */
2457 TheIndex = (0L - msgnum);
2459 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2460 if (cdbsmi == NULL) {
2461 return; /* record not found; go with defaults */
2463 memcpy(smibuf, cdbsmi->ptr,
2464 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2465 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2472 * PutSuppMsgInfo() - (re)write supplementary record for a message
2474 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2478 /* Use the negative of the message number for its supp record index */
2479 TheIndex = (0L - smibuf->smi_msgnum);
2481 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2482 smibuf->smi_msgnum, smibuf->smi_refcount);
2484 cdb_store(CDB_MSGMAIN,
2485 &TheIndex, sizeof(long),
2486 smibuf, sizeof(struct SuppMsgInfo));
2491 * AdjRefCount - change the reference count for a message;
2492 * delete the message if it reaches zero
2494 void AdjRefCount(long msgnum, int incr)
2497 struct SuppMsgInfo smi;
2500 /* This is a *tight* critical section; please keep it that way, as
2501 * it may get called while nested in other critical sections.
2502 * Complicating this any further will surely cause deadlock!
2504 begin_critical_section(S_SUPPMSGMAIN);
2505 GetSuppMsgInfo(&smi, msgnum);
2506 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2507 msgnum, smi.smi_refcount);
2508 smi.smi_refcount += incr;
2509 PutSuppMsgInfo(&smi);
2510 end_critical_section(S_SUPPMSGMAIN);
2511 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2512 msgnum, smi.smi_refcount);
2514 /* If the reference count is now zero, delete the message
2515 * (and its supplementary record as well).
2517 if (smi.smi_refcount == 0) {
2518 lprintf(9, "Deleting message <%ld>\n", msgnum);
2520 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2521 delnum = (0L - msgnum);
2522 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2527 * Write a generic object to this room
2529 * Note: this could be much more efficient. Right now we use two temporary
2530 * files, and still pull the message into memory as with all others.
2532 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2533 char *content_type, /* MIME type of this object */
2534 char *tempfilename, /* Where to fetch it from */
2535 struct usersupp *is_mailbox, /* Mailbox room? */
2536 int is_binary, /* Is encoding necessary? */
2537 int is_unique, /* Del others of this type? */
2538 unsigned int flags /* Internal save flags */
2543 char filename[PATH_MAX];
2546 struct quickroom qrbuf;
2547 char roomname[ROOMNAMELEN];
2548 struct CtdlMessage *msg;
2551 if (is_mailbox != NULL)
2552 MailboxName(roomname, is_mailbox, req_room);
2554 safestrncpy(roomname, req_room, sizeof(roomname));
2555 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2557 strcpy(filename, tmpnam(NULL));
2558 fp = fopen(filename, "w");
2562 tempfp = fopen(tempfilename, "r");
2563 if (tempfp == NULL) {
2569 fprintf(fp, "Content-type: %s\n", content_type);
2570 lprintf(9, "Content-type: %s\n", content_type);
2572 if (is_binary == 0) {
2573 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2574 while (ch = getc(tempfp), ch > 0)
2580 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2583 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2584 tempfilename, filename);
2588 lprintf(9, "Allocating\n");
2589 msg = mallok(sizeof(struct CtdlMessage));
2590 memset(msg, 0, sizeof(struct CtdlMessage));
2591 msg->cm_magic = CTDLMESSAGE_MAGIC;
2592 msg->cm_anon_type = MES_NORMAL;
2593 msg->cm_format_type = 4;
2594 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2595 msg->cm_fields['O'] = strdoop(req_room);
2596 msg->cm_fields['N'] = strdoop(config.c_nodename);
2597 msg->cm_fields['H'] = strdoop(config.c_humannode);
2598 msg->cm_flags = flags;
2600 lprintf(9, "Loading\n");
2601 fp = fopen(filename, "rb");
2602 fseek(fp, 0L, SEEK_END);
2605 msg->cm_fields['M'] = mallok(len);
2606 fread(msg->cm_fields['M'], len, 1, fp);
2610 /* Create the requested room if we have to. */
2611 if (getroom(&qrbuf, roomname) != 0) {
2612 create_room(roomname,
2613 ( (is_mailbox != NULL) ? 5 : 3 ),
2616 /* If the caller specified this object as unique, delete all
2617 * other objects of this type that are currently in the room.
2620 lprintf(9, "Deleted %d other msgs of this type\n",
2621 CtdlDeleteMessages(roomname, 0L, content_type));
2623 /* Now write the data */
2624 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2625 CtdlFreeMessage(msg);
2633 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2634 config_msgnum = msgnum;
2638 char *CtdlGetSysConfig(char *sysconfname) {
2639 char hold_rm[ROOMNAMELEN];
2642 struct CtdlMessage *msg;
2645 strcpy(hold_rm, CC->quickroom.QRname);
2646 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2647 getroom(&CC->quickroom, hold_rm);
2652 /* We want the last (and probably only) config in this room */
2653 begin_critical_section(S_CONFIG);
2654 config_msgnum = (-1L);
2655 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2656 CtdlGetSysConfigBackend, NULL);
2657 msgnum = config_msgnum;
2658 end_critical_section(S_CONFIG);
2664 msg = CtdlFetchMessage(msgnum);
2666 conf = strdoop(msg->cm_fields['M']);
2667 CtdlFreeMessage(msg);
2674 getroom(&CC->quickroom, hold_rm);
2676 lprintf(9, "eggstracting...\n");
2677 if (conf != NULL) do {
2678 extract_token(buf, conf, 0, '\n');
2679 lprintf(9, "eggstracted <%s>\n", buf);
2680 strcpy(conf, &conf[strlen(buf)+1]);
2681 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2686 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2687 char temp[PATH_MAX];
2690 strcpy(temp, tmpnam(NULL));
2692 fp = fopen(temp, "w");
2693 if (fp == NULL) return;
2694 fprintf(fp, "%s", sysconfdata);
2697 /* this handy API function does all the work for us */
2698 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);