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)
579 cprintf("part=%s|%s|%s|%s|%s|%d\n",
580 name, filename, partnum, disp, cbtype, length);
585 * Callback function for mime parser that opens a section for downloading
587 void mime_download(char *name, char *filename, char *partnum, char *disp,
588 void *content, char *cbtype, size_t length)
591 /* Silently go away if there's already a download open... */
592 if (CC->download_fp != NULL)
595 /* ...or if this is not the desired section */
596 if (strcasecmp(desired_section, partnum))
599 CC->download_fp = tmpfile();
600 if (CC->download_fp == NULL)
603 fwrite(content, length, 1, CC->download_fp);
604 fflush(CC->download_fp);
605 rewind(CC->download_fp);
607 OpenCmdResult(filename, cbtype);
613 * Load a message from disk into memory.
614 * This is used by CtdlOutputMsg() and other fetch functions.
616 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
617 * using the CtdlMessageFree() function.
619 struct CtdlMessage *CtdlFetchMessage(long msgnum)
621 struct cdbdata *dmsgtext;
622 struct CtdlMessage *ret = NULL;
625 CIT_UBYTE field_header;
628 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
629 if (dmsgtext == NULL) {
632 mptr = dmsgtext->ptr;
634 /* Parse the three bytes that begin EVERY message on disk.
635 * The first is always 0xFF, the on-disk magic number.
636 * The second is the anonymous/public type byte.
637 * The third is the format type byte (vari, fixed, or MIME).
641 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
645 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
646 memset(ret, 0, sizeof(struct CtdlMessage));
648 ret->cm_magic = CTDLMESSAGE_MAGIC;
649 ret->cm_anon_type = *mptr++; /* Anon type byte */
650 ret->cm_format_type = *mptr++; /* Format type byte */
653 * The rest is zero or more arbitrary fields. Load them in.
654 * We're done when we encounter either a zero-length field or
655 * have just processed the 'M' (message text) field.
658 field_length = strlen(mptr);
659 if (field_length == 0)
661 field_header = *mptr++;
662 ret->cm_fields[field_header] = mallok(field_length);
663 strcpy(ret->cm_fields[field_header], mptr);
665 while (*mptr++ != 0); /* advance to next field */
667 } while ((field_length > 0) && (field_header != 'M'));
671 /* Always make sure there's something in the msg text field */
672 if (ret->cm_fields['M'] == NULL)
673 ret->cm_fields['M'] = strdoop("<no text>\n");
675 /* Perform "before read" hooks (aborting if any return nonzero) */
676 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
677 CtdlFreeMessage(ret);
686 * Returns 1 if the supplied pointer points to a valid Citadel message.
687 * If the pointer is NULL or the magic number check fails, returns 0.
689 int is_valid_message(struct CtdlMessage *msg) {
692 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
693 lprintf(3, "is_valid_message() -- self-check failed\n");
701 * 'Destructor' for struct CtdlMessage
703 void CtdlFreeMessage(struct CtdlMessage *msg)
707 if (is_valid_message(msg) == 0) return;
709 for (i = 0; i < 256; ++i)
710 if (msg->cm_fields[i] != NULL) {
711 phree(msg->cm_fields[i]);
714 msg->cm_magic = 0; /* just in case */
720 * Callback function for mime parser that wants to display text
722 void fixed_output(char *name, char *filename, char *partnum, char *disp,
723 void *content, char *cbtype, size_t length)
730 if (!strcasecmp(cbtype, "multipart/alternative")) {
731 strcpy(ma->prefix, partnum);
732 strcat(ma->prefix, ".");
738 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
740 && (ma->did_print == 1) ) {
741 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
747 if ( (!strcasecmp(cbtype, "text/plain"))
748 || (strlen(cbtype)==0) ) {
753 if (ch==10) cprintf("\r\n");
754 else cprintf("%c", ch);
757 else if (!strcasecmp(cbtype, "text/html")) {
758 ptr = html_to_ascii(content, 80, 0);
763 if (ch==10) cprintf("\r\n");
764 else cprintf("%c", ch);
768 else if (strncasecmp(cbtype, "multipart/", 10)) {
769 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
770 partnum, filename, cbtype, length);
776 * Get a message off disk. (returns om_* values found in msgbase.h)
779 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
780 int mode, /* how would you like that message? */
781 int headers_only, /* eschew the message body? */
782 int do_proto, /* do Citadel protocol responses? */
783 int crlf /* Use CRLF newlines instead of LF? */
789 char display_name[256];
790 struct CtdlMessage *TheMessage;
792 char *nl; /* newline string */
794 /* buffers needed for RFC822 translation */
804 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
808 sprintf(mid, "%ld", msg_num);
809 nl = (crlf ? "\r\n" : "\n");
811 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
812 if (do_proto) cprintf("%d Not logged in.\n",
813 ERROR + NOT_LOGGED_IN);
814 return(om_not_logged_in);
817 /* FIXME ... small security issue
818 * We need to check to make sure the requested message is actually
819 * in the current room, and set msg_ok to 1 only if it is. This
820 * functionality is currently missing because I'm in a hurry to replace
821 * broken production code with nonbroken pre-beta code. :( -- ajc
824 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
826 return(om_no_such_msg);
831 * Fetch the message from disk
833 TheMessage = CtdlFetchMessage(msg_num);
834 if (TheMessage == NULL) {
835 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
837 return(om_no_such_msg);
840 /* Are we downloading a MIME component? */
841 if (mode == MT_DOWNLOAD) {
842 if (TheMessage->cm_format_type != FMT_RFC822) {
844 cprintf("%d This is not a MIME message.\n",
846 } else if (CC->download_fp != NULL) {
847 if (do_proto) cprintf(
848 "%d You already have a download open.\n",
851 /* Parse the message text component */
852 mptr = TheMessage->cm_fields['M'];
853 mime_parser(mptr, NULL, *mime_download);
854 /* If there's no file open by this time, the requested
855 * section wasn't found, so print an error
857 if (CC->download_fp == NULL) {
858 if (do_proto) cprintf(
859 "%d Section %s not found.\n",
860 ERROR + FILE_NOT_FOUND,
864 CtdlFreeMessage(TheMessage);
865 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
868 /* now for the user-mode message reading loops */
869 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
871 /* Tell the client which format type we're using. If this is a
872 * MIME message, *lie* about it and tell the user it's fixed-format.
874 if (mode == MT_CITADEL) {
875 if (TheMessage->cm_format_type == FMT_RFC822) {
876 if (do_proto) cprintf("type=1\n");
879 if (do_proto) cprintf("type=%d\n",
880 TheMessage->cm_format_type);
884 /* nhdr=yes means that we're only displaying headers, no body */
885 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
886 if (do_proto) cprintf("nhdr=yes\n");
889 /* begin header processing loop for Citadel message format */
891 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
893 strcpy(display_name, "<unknown>");
894 if (TheMessage->cm_fields['A']) {
895 strcpy(buf, TheMessage->cm_fields['A']);
896 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
897 if (TheMessage->cm_anon_type == MES_ANON)
898 strcpy(display_name, "****");
899 else if (TheMessage->cm_anon_type == MES_AN2)
900 strcpy(display_name, "anonymous");
902 strcpy(display_name, buf);
904 && ((TheMessage->cm_anon_type == MES_ANON)
905 || (TheMessage->cm_anon_type == MES_AN2))) {
906 sprintf(&display_name[strlen(display_name)],
911 strcpy(allkeys, FORDER);
912 for (i=0; i<strlen(allkeys); ++i) {
913 k = (int) allkeys[i];
915 if (TheMessage->cm_fields[k] != NULL) {
917 if (do_proto) cprintf("%s=%s\n",
922 if (do_proto) cprintf("%s=%s\n",
924 TheMessage->cm_fields[k]
933 /* begin header processing loop for RFC822 transfer format */
938 strcpy(snode, NODENAME);
939 strcpy(lnode, HUMANNODE);
940 if (mode == MT_RFC822) {
941 cprintf("X-UIDL: %ld%s", msg_num, nl);
942 for (i = 0; i < 256; ++i) {
943 if (TheMessage->cm_fields[i]) {
944 mptr = TheMessage->cm_fields[i];
951 cprintf("Path: %s%s", mptr, nl);
954 cprintf("Subject: %s%s", mptr, nl);
960 cprintf("X-Citadel-Room: %s%s",
965 cprintf("To: %s%s", mptr, nl);
967 datestring(datestamp, atol(mptr),
969 cprintf("Date: %s%s", datestamp, nl);
975 for (i=0; i<strlen(suser); ++i) {
976 suser[i] = tolower(suser[i]);
977 if (!isalnum(suser[i])) suser[i]='_';
980 if (mode == MT_RFC822) {
981 if (!strcasecmp(snode, NODENAME)) {
985 /* Construct a fun message id */
986 cprintf("Message-ID: <%s", mid);
987 if (strchr(mid, '@')==NULL) {
988 cprintf("@%s", snode);
992 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
994 if (strlen(fuser) > 0) {
995 cprintf("From: %s (%s)%s", fuser, luser, nl);
998 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1001 cprintf("Organization: %s%s", lnode, nl);
1004 /* end header processing loop ... at this point, we're in the text */
1006 mptr = TheMessage->cm_fields['M'];
1008 /* Tell the client about the MIME parts in this message */
1009 if (TheMessage->cm_format_type == FMT_RFC822) {
1010 if (mode == MT_CITADEL) {
1011 mime_parser(mptr, NULL, *list_this_part);
1013 else if (mode == MT_MIME) { /* list parts only */
1014 mime_parser(mptr, NULL, *list_this_part);
1015 if (do_proto) cprintf("000\n");
1016 CtdlFreeMessage(TheMessage);
1019 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1020 /* FIXME ... we have to put some code in here to avoid
1021 * printing duplicate header information when both
1022 * Citadel and RFC822 headers exist. Preference should
1023 * probably be given to the RFC822 headers.
1025 while (ch=*(mptr++), ch!=0) {
1027 else if (ch==10) cprintf("%s", nl);
1028 else cprintf("%c", ch);
1030 if (do_proto) cprintf("000\n");
1031 CtdlFreeMessage(TheMessage);
1037 if (do_proto) cprintf("000\n");
1038 CtdlFreeMessage(TheMessage);
1042 /* signify start of msg text */
1043 if (mode == MT_CITADEL)
1044 if (do_proto) cprintf("text\n");
1045 if (mode == MT_RFC822) {
1046 if (TheMessage->cm_fields['U'] == NULL) {
1047 cprintf("Subject: (no subject)%s", nl);
1052 /* If the format type on disk is 1 (fixed-format), then we want
1053 * everything to be output completely literally ... regardless of
1054 * what message transfer format is in use.
1056 if (TheMessage->cm_format_type == FMT_FIXED) {
1058 while (ch = *mptr++, ch > 0) {
1061 if ((ch == 10) || (strlen(buf) > 250)) {
1062 cprintf("%s%s", buf, nl);
1065 buf[strlen(buf) + 1] = 0;
1066 buf[strlen(buf)] = ch;
1069 if (strlen(buf) > 0)
1070 cprintf("%s%s", buf, nl);
1073 /* If the message on disk is format 0 (Citadel vari-format), we
1074 * output using the formatter at 80 columns. This is the final output
1075 * form if the transfer format is RFC822, but if the transfer format
1076 * is Citadel proprietary, it'll still work, because the indentation
1077 * for new paragraphs is correct and the client will reformat the
1078 * message to the reader's screen width.
1080 if (TheMessage->cm_format_type == FMT_CITADEL) {
1081 memfmout(80, mptr, 0, nl);
1084 /* If the message on disk is format 4 (MIME), we've gotta hand it
1085 * off to the MIME parser. The client has already been told that
1086 * this message is format 1 (fixed format), so the callback function
1087 * we use will display those parts as-is.
1089 if (TheMessage->cm_format_type == FMT_RFC822) {
1090 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1091 memset(ma, 0, sizeof(struct ma_info));
1092 mime_parser(mptr, NULL, *fixed_output);
1095 /* now we're done */
1096 if (do_proto) cprintf("000\n");
1097 CtdlFreeMessage(TheMessage);
1104 * display a message (mode 0 - Citadel proprietary)
1106 void cmd_msg0(char *cmdbuf)
1109 int headers_only = 0;
1111 msgid = extract_long(cmdbuf, 0);
1112 headers_only = extract_int(cmdbuf, 1);
1114 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1120 * display a message (mode 2 - RFC822)
1122 void cmd_msg2(char *cmdbuf)
1125 int headers_only = 0;
1127 msgid = extract_long(cmdbuf, 0);
1128 headers_only = extract_int(cmdbuf, 1);
1130 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1136 * display a message (mode 3 - IGnet raw format - internal programs only)
1138 void cmd_msg3(char *cmdbuf)
1141 struct CtdlMessage *msg;
1144 if (CC->internal_pgm == 0) {
1145 cprintf("%d This command is for internal programs only.\n",
1150 msgnum = extract_long(cmdbuf, 0);
1151 msg = CtdlFetchMessage(msgnum);
1153 cprintf("%d Message %ld not found.\n",
1158 serialize_message(&smr, msg);
1159 CtdlFreeMessage(msg);
1162 cprintf("%d Unable to serialize message\n",
1163 ERROR+INTERNAL_ERROR);
1167 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1168 client_write(smr.ser, smr.len);
1175 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1177 void cmd_msg4(char *cmdbuf)
1181 msgid = extract_long(cmdbuf, 0);
1182 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1186 * Open a component of a MIME message as a download file
1188 void cmd_opna(char *cmdbuf)
1192 CtdlAllocUserData(SYM_DESIRED_SECTION, 256);
1194 msgid = extract_long(cmdbuf, 0);
1195 extract(desired_section, cmdbuf, 1);
1197 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1202 * Save a message pointer into a specified room
1203 * (Returns 0 for success, nonzero for failure)
1204 * roomname may be NULL to use the current room
1206 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1208 char hold_rm[ROOMNAMELEN];
1209 struct cdbdata *cdbfr;
1212 long highest_msg = 0L;
1213 struct CtdlMessage *msg = NULL;
1215 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1216 roomname, msgid, flags);
1218 strcpy(hold_rm, CC->quickroom.QRname);
1220 /* We may need to check to see if this message is real */
1221 if ( (flags & SM_VERIFY_GOODNESS)
1222 || (flags & SM_DO_REPL_CHECK)
1224 msg = CtdlFetchMessage(msgid);
1225 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1228 /* Perform replication checks if necessary */
1229 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1231 if (getroom(&CC->quickroom,
1232 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1234 lprintf(9, "No such room <%s>\n", roomname);
1235 if (msg != NULL) CtdlFreeMessage(msg);
1236 return(ERROR + ROOM_NOT_FOUND);
1239 if (ReplicationChecks(msg) != 0) {
1240 getroom(&CC->quickroom, hold_rm);
1241 if (msg != NULL) CtdlFreeMessage(msg);
1242 lprintf(9, "Did replication, and newer exists\n");
1247 /* Now the regular stuff */
1248 if (lgetroom(&CC->quickroom,
1249 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1251 lprintf(9, "No such room <%s>\n", roomname);
1252 if (msg != NULL) CtdlFreeMessage(msg);
1253 return(ERROR + ROOM_NOT_FOUND);
1256 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1257 if (cdbfr == NULL) {
1261 msglist = mallok(cdbfr->len);
1262 if (msglist == NULL)
1263 lprintf(3, "ERROR malloc msglist!\n");
1264 num_msgs = cdbfr->len / sizeof(long);
1265 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1270 /* Make sure the message doesn't already exist in this room. It
1271 * is absolutely taboo to have more than one reference to the same
1272 * message in a room.
1274 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1275 if (msglist[i] == msgid) {
1276 lputroom(&CC->quickroom); /* unlock the room */
1277 getroom(&CC->quickroom, hold_rm);
1278 if (msg != NULL) CtdlFreeMessage(msg);
1279 return(ERROR + ALREADY_EXISTS);
1283 /* Now add the new message */
1285 msglist = reallok(msglist,
1286 (num_msgs * sizeof(long)));
1288 if (msglist == NULL) {
1289 lprintf(3, "ERROR: can't realloc message list!\n");
1291 msglist[num_msgs - 1] = msgid;
1293 /* Sort the message list, so all the msgid's are in order */
1294 num_msgs = sort_msglist(msglist, num_msgs);
1296 /* Determine the highest message number */
1297 highest_msg = msglist[num_msgs - 1];
1299 /* Write it back to disk. */
1300 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1301 msglist, num_msgs * sizeof(long));
1303 /* Free up the memory we used. */
1306 /* Update the highest-message pointer and unlock the room. */
1307 CC->quickroom.QRhighest = highest_msg;
1308 lputroom(&CC->quickroom);
1309 getroom(&CC->quickroom, hold_rm);
1311 /* Bump the reference count for this message. */
1312 if ((flags & SM_DONT_BUMP_REF)==0) {
1313 AdjRefCount(msgid, +1);
1316 /* Return success. */
1317 if (msg != NULL) CtdlFreeMessage(msg);
1324 * Message base operation to send a message to the master file
1325 * (returns new message number)
1327 * This is the back end for CtdlSaveMsg() and should not be directly
1328 * called by server-side modules.
1331 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1332 FILE *save_a_copy) /* save a copy to disk? */
1339 /* Get a new message number */
1340 newmsgid = get_new_message_number();
1341 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1343 /* Generate an ID if we don't have one already */
1344 if (msg->cm_fields['I']==NULL) {
1345 msg->cm_fields['I'] = strdoop(msgidbuf);
1348 serialize_message(&smr, msg);
1351 cprintf("%d Unable to serialize message\n",
1352 ERROR+INTERNAL_ERROR);
1356 /* Write our little bundle of joy into the message base */
1357 begin_critical_section(S_MSGMAIN);
1358 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1359 smr.ser, smr.len) < 0) {
1360 lprintf(2, "Can't store message\n");
1365 end_critical_section(S_MSGMAIN);
1367 /* If the caller specified that a copy should be saved to a particular
1368 * file handle, do that now too.
1370 if (save_a_copy != NULL) {
1371 fwrite(smr.ser, smr.len, 1, save_a_copy);
1374 /* Free the memory we used for the serialized message */
1377 /* Return the *local* message ID to the caller
1378 * (even if we're storing an incoming network message)
1386 * Serialize a struct CtdlMessage into the format used on disk and network.
1388 * This function loads up a "struct ser_ret" (defined in server.h) which
1389 * contains the length of the serialized message and a pointer to the
1390 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1392 void serialize_message(struct ser_ret *ret, /* return values */
1393 struct CtdlMessage *msg) /* unserialized msg */
1397 static char *forder = FORDER;
1399 if (is_valid_message(msg) == 0) return; /* self check */
1402 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1403 ret->len = ret->len +
1404 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1406 lprintf(9, "calling malloc(%d)\n", ret->len);
1407 ret->ser = mallok(ret->len);
1408 if (ret->ser == NULL) {
1414 ret->ser[1] = msg->cm_anon_type;
1415 ret->ser[2] = msg->cm_format_type;
1418 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1419 ret->ser[wlen++] = (char)forder[i];
1420 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1421 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1423 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1432 * Back end for the ReplicationChecks() function
1434 void check_repl(long msgnum, void *userdata) {
1435 struct CtdlMessage *msg;
1436 time_t timestamp = (-1L);
1438 lprintf(9, "check_repl() found message %ld\n", msgnum);
1439 msg = CtdlFetchMessage(msgnum);
1440 if (msg == NULL) return;
1441 if (msg->cm_fields['T'] != NULL) {
1442 timestamp = atol(msg->cm_fields['T']);
1444 CtdlFreeMessage(msg);
1446 if (timestamp > msg_repl->highest) {
1447 msg_repl->highest = timestamp; /* newer! */
1448 lprintf(9, "newer!\n");
1451 lprintf(9, "older!\n");
1453 /* Existing isn't newer? Then delete the old one(s). */
1454 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1459 * Check to see if any messages already exist which carry the same Extended ID
1463 * -> With older timestamps: delete them and return 0. Message will be saved.
1464 * -> With newer timestamps: return 1. Message save will be aborted.
1466 int ReplicationChecks(struct CtdlMessage *msg) {
1467 struct CtdlMessage *template;
1470 lprintf(9, "ReplicationChecks() started\n");
1471 /* No extended id? Don't do anything. */
1472 if (msg->cm_fields['E'] == NULL) return 0;
1473 if (strlen(msg->cm_fields['E']) == 0) return 0;
1474 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1476 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1477 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1478 msg_repl->highest = atol(msg->cm_fields['T']);
1480 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1481 memset(template, 0, sizeof(struct CtdlMessage));
1482 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1484 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1487 /* If a newer message exists with the same Extended ID, abort
1490 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1494 CtdlFreeMessage(template);
1495 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1503 * Save a message to disk
1505 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1506 char *rec, /* Recipient (mail) */
1507 char *force, /* force a particular room? */
1508 int supplied_mailtype) /* local or remote type */
1511 char hold_rm[ROOMNAMELEN];
1512 char actual_rm[ROOMNAMELEN];
1513 char force_room[ROOMNAMELEN];
1514 char content_type[256]; /* We have to learn this */
1515 char recipient[256];
1518 struct usersupp userbuf;
1520 struct SuppMsgInfo smi;
1521 FILE *network_fp = NULL;
1522 static int seqnum = 1;
1523 struct CtdlMessage *imsg;
1527 lprintf(9, "CtdlSaveMsg() called\n");
1528 if (is_valid_message(msg) == 0) return(-1); /* self check */
1529 mailtype = supplied_mailtype;
1531 /* If this message has no timestamp, we take the liberty of
1532 * giving it one, right now.
1534 if (msg->cm_fields['T'] == NULL) {
1535 lprintf(9, "Generating timestamp\n");
1536 sprintf(aaa, "%ld", time(NULL));
1537 msg->cm_fields['T'] = strdoop(aaa);
1540 /* If this message has no path, we generate one.
1542 if (msg->cm_fields['P'] == NULL) {
1543 lprintf(9, "Generating path\n");
1544 if (msg->cm_fields['A'] != NULL) {
1545 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1546 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1547 if (isspace(msg->cm_fields['P'][a])) {
1548 msg->cm_fields['P'][a] = ' ';
1553 msg->cm_fields['P'] = strdoop("unknown");
1557 strcpy(force_room, force);
1559 /* Strip non-printable characters out of the recipient name */
1560 lprintf(9, "Checking recipient (if present)\n");
1561 strcpy(recipient, rec);
1562 for (a = 0; a < strlen(recipient); ++a)
1563 if (!isprint(recipient[a]))
1564 strcpy(&recipient[a], &recipient[a + 1]);
1566 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1567 for (a=0; a<strlen(recipient); ++a) {
1568 if (recipient[a] == '@') {
1569 if (CtdlHostAlias(&recipient[a+1])
1570 == hostalias_localhost) {
1572 lprintf(7, "Changed to <%s>\n", recipient);
1573 mailtype = MES_LOCAL;
1578 lprintf(9, "Recipient is <%s>\n", recipient);
1580 /* Learn about what's inside, because it's what's inside that counts */
1581 lprintf(9, "Learning what's inside\n");
1582 if (msg->cm_fields['M'] == NULL) {
1583 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1586 switch (msg->cm_format_type) {
1588 strcpy(content_type, "text/x-citadel-variformat");
1591 strcpy(content_type, "text/plain");
1594 strcpy(content_type, "text/plain");
1595 /* advance past header fields */
1596 mptr = msg->cm_fields['M'];
1599 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1600 safestrncpy(content_type, mptr,
1601 sizeof(content_type));
1602 strcpy(content_type, &content_type[14]);
1603 for (a = 0; a < strlen(content_type); ++a)
1604 if ((content_type[a] == ';')
1605 || (content_type[a] == ' ')
1606 || (content_type[a] == 13)
1607 || (content_type[a] == 10))
1608 content_type[a] = 0;
1615 /* Goto the correct room */
1616 lprintf(9, "Switching rooms\n");
1617 strcpy(hold_rm, CC->quickroom.QRname);
1618 strcpy(actual_rm, CC->quickroom.QRname);
1620 /* If the user is a twit, move to the twit room for posting */
1621 lprintf(9, "Handling twit stuff\n");
1623 if (CC->usersupp.axlevel == 2) {
1624 strcpy(hold_rm, actual_rm);
1625 strcpy(actual_rm, config.c_twitroom);
1629 /* ...or if this message is destined for Aide> then go there. */
1630 if (strlen(force_room) > 0) {
1631 strcpy(actual_rm, force_room);
1634 lprintf(9, "Possibly relocating\n");
1635 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1636 getroom(&CC->quickroom, actual_rm);
1640 * If this message has no O (room) field, generate one.
1642 if (msg->cm_fields['O'] == NULL) {
1643 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1646 /* Perform "before save" hooks (aborting if any return nonzero) */
1647 lprintf(9, "Performing before-save hooks\n");
1648 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1650 /* If this message has an Extended ID, perform replication checks */
1651 lprintf(9, "Performing replication checks\n");
1652 if (ReplicationChecks(msg) > 0) return(-1);
1654 /* Network mail - send a copy to the network program. */
1655 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1656 lprintf(9, "Sending network spool\n");
1657 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1658 (long) getpid(), CC->cs_pid, ++seqnum);
1659 lprintf(9, "Saving a copy to %s\n", aaa);
1660 network_fp = fopen(aaa, "ab+");
1661 if (network_fp == NULL)
1662 lprintf(2, "ERROR: %s\n", strerror(errno));
1665 /* Save it to disk */
1666 lprintf(9, "Saving to disk\n");
1667 newmsgid = send_message(msg, network_fp);
1668 if (network_fp != NULL) {
1670 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1673 if (newmsgid <= 0L) return(-1);
1675 /* Write a supplemental message info record. This doesn't have to
1676 * be a critical section because nobody else knows about this message
1679 lprintf(9, "Creating SuppMsgInfo record\n");
1680 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1681 smi.smi_msgnum = newmsgid;
1682 smi.smi_refcount = 0;
1683 safestrncpy(smi.smi_content_type, content_type, 64);
1684 PutSuppMsgInfo(&smi);
1686 /* Now figure out where to store the pointers */
1687 lprintf(9, "Storing pointers\n");
1689 /* If this is being done by the networker delivering a private
1690 * message, we want to BYPASS saving the sender's copy (because there
1691 * is no local sender; it would otherwise go to the Trashcan).
1693 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1694 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1695 lprintf(3, "ERROR saving message pointer!\n");
1696 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1700 /* For internet mail, drop a copy in the outbound queue room */
1701 if (mailtype == MES_INTERNET) {
1702 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1705 /* Bump this user's messages posted counter. */
1706 lprintf(9, "Updating user\n");
1707 lgetuser(&CC->usersupp, CC->curr_user);
1708 CC->usersupp.posted = CC->usersupp.posted + 1;
1709 lputuser(&CC->usersupp);
1711 /* If this is private, local mail, make a copy in the
1712 * recipient's mailbox and bump the reference count.
1714 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1715 if (getuser(&userbuf, recipient) == 0) {
1716 lprintf(9, "Delivering private mail\n");
1717 MailboxName(actual_rm, &userbuf, MAILROOM);
1718 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1721 lprintf(9, "No user <%s>, saving in %s> instead\n",
1722 recipient, AIDEROOM);
1723 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1727 /* Perform "after save" hooks */
1728 lprintf(9, "Performing after-save hooks\n");
1729 PerformMessageHooks(msg, EVT_AFTERSAVE);
1732 lprintf(9, "Returning to original room\n");
1733 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1734 getroom(&CC->quickroom, hold_rm);
1736 /* For internet mail, generate delivery instructions
1737 * (Yes, this is recursive! Deal with it!)
1739 if (mailtype == MES_INTERNET) {
1740 lprintf(9, "Generating delivery instructions\n");
1741 instr = mallok(2048);
1743 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1746 SPOOLMIME, newmsgid, time(NULL),
1747 msg->cm_fields['A'], msg->cm_fields['N'],
1750 imsg = mallok(sizeof(struct CtdlMessage));
1751 memset(imsg, 0, sizeof(struct CtdlMessage));
1752 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1753 imsg->cm_anon_type = MES_NORMAL;
1754 imsg->cm_format_type = FMT_RFC822;
1755 imsg->cm_fields['A'] = strdoop("Citadel");
1756 imsg->cm_fields['M'] = instr;
1757 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1758 CtdlFreeMessage(imsg);
1767 * Convenience function for generating small administrative messages.
1769 void quickie_message(char *from, char *to, char *room, char *text)
1771 struct CtdlMessage *msg;
1773 msg = mallok(sizeof(struct CtdlMessage));
1774 memset(msg, 0, sizeof(struct CtdlMessage));
1775 msg->cm_magic = CTDLMESSAGE_MAGIC;
1776 msg->cm_anon_type = MES_NORMAL;
1777 msg->cm_format_type = 0;
1778 msg->cm_fields['A'] = strdoop(from);
1779 msg->cm_fields['O'] = strdoop(room);
1780 msg->cm_fields['N'] = strdoop(NODENAME);
1782 msg->cm_fields['R'] = strdoop(to);
1783 msg->cm_fields['M'] = strdoop(text);
1785 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1786 CtdlFreeMessage(msg);
1787 syslog(LOG_NOTICE, text);
1793 * Back end function used by make_message() and similar functions
1795 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1796 size_t maxlen, /* maximum message length */
1797 char *exist /* if non-null, append to it;
1798 exist is ALWAYS freed */
1801 size_t message_len = 0;
1802 size_t buffer_len = 0;
1806 if (exist == NULL) {
1810 m = reallok(exist, strlen(exist) + 4096);
1811 if (m == NULL) phree(exist);
1814 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1821 /* read in the lines of message text one by one */
1823 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1825 /* augment the buffer if we have to */
1826 if ((message_len + strlen(buf) + 2) > buffer_len) {
1827 lprintf(9, "realloking\n");
1828 ptr = reallok(m, (buffer_len * 2) );
1829 if (ptr == NULL) { /* flush if can't allocate */
1830 while ( (client_gets(buf)>0) &&
1831 strcmp(buf, terminator)) ;;
1834 buffer_len = (buffer_len * 2);
1837 lprintf(9, "buffer_len is %d\n", buffer_len);
1841 if (append == NULL) append = m;
1842 while (strlen(append) > 0) ++append;
1843 strcpy(append, buf);
1844 strcat(append, "\n");
1845 message_len = message_len + strlen(buf) + 1;
1847 /* if we've hit the max msg length, flush the rest */
1848 if (message_len >= maxlen) {
1849 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1860 * Build a binary message to be saved on disk.
1863 struct CtdlMessage *make_message(
1864 struct usersupp *author, /* author's usersupp structure */
1865 char *recipient, /* NULL if it's not mail */
1866 char *room, /* room where it's going */
1867 int type, /* see MES_ types in header file */
1868 int net_type, /* see MES_ types in header file */
1869 int format_type, /* local or remote (see citadel.h) */
1870 char *fake_name) /* who we're masquerading as */
1876 struct CtdlMessage *msg;
1878 msg = mallok(sizeof(struct CtdlMessage));
1879 memset(msg, 0, sizeof(struct CtdlMessage));
1880 msg->cm_magic = CTDLMESSAGE_MAGIC;
1881 msg->cm_anon_type = type;
1882 msg->cm_format_type = format_type;
1884 /* Don't confuse the poor folks if it's not routed mail. */
1885 strcpy(dest_node, "");
1887 /* If net_type is MES_BINARY, split out the destination node. */
1888 if (net_type == MES_BINARY) {
1889 strcpy(dest_node, NODENAME);
1890 for (a = 0; a < strlen(recipient); ++a) {
1891 if (recipient[a] == '@') {
1893 strcpy(dest_node, &recipient[a + 1]);
1898 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1899 if (net_type == MES_INTERNET) {
1900 strcpy(dest_node, "internet");
1903 while (isspace(recipient[strlen(recipient) - 1]))
1904 recipient[strlen(recipient) - 1] = 0;
1906 sprintf(buf, "cit%ld", author->usernum); /* Path */
1907 msg->cm_fields['P'] = strdoop(buf);
1909 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1910 msg->cm_fields['T'] = strdoop(buf);
1912 if (fake_name[0]) /* author */
1913 msg->cm_fields['A'] = strdoop(fake_name);
1915 msg->cm_fields['A'] = strdoop(author->fullname);
1917 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1918 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1920 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1922 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1923 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1925 if (recipient[0] != 0)
1926 msg->cm_fields['R'] = strdoop(recipient);
1927 if (dest_node[0] != 0)
1928 msg->cm_fields['D'] = strdoop(dest_node);
1931 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1932 config.c_maxmsglen, NULL);
1943 * message entry - mode 0 (normal)
1945 void cmd_ent0(char *entargs)
1948 char recipient[256];
1950 int format_type = 0;
1951 char newusername[256];
1952 struct CtdlMessage *msg;
1956 struct usersupp tempUS;
1959 post = extract_int(entargs, 0);
1960 extract(recipient, entargs, 1);
1961 anon_flag = extract_int(entargs, 2);
1962 format_type = extract_int(entargs, 3);
1964 /* first check to make sure the request is valid. */
1966 if (!(CC->logged_in)) {
1967 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1970 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1971 cprintf("%d Need to be validated to enter ",
1972 ERROR + HIGHER_ACCESS_REQUIRED);
1973 cprintf("(except in %s> to sysop)\n", MAILROOM);
1976 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1977 cprintf("%d Need net privileges to enter here.\n",
1978 ERROR + HIGHER_ACCESS_REQUIRED);
1981 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1982 cprintf("%d Sorry, this is a read-only room.\n",
1983 ERROR + HIGHER_ACCESS_REQUIRED);
1990 if (CC->usersupp.axlevel < 6) {
1991 cprintf("%d You don't have permission to masquerade.\n",
1992 ERROR + HIGHER_ACCESS_REQUIRED);
1995 extract(newusername, entargs, 4);
1996 memset(CC->fake_postname, 0, 32);
1997 strcpy(CC->fake_postname, newusername);
1998 cprintf("%d Ok\n", OK);
2001 CC->cs_flags |= CS_POSTING;
2004 if (CC->quickroom.QRflags & QR_MAILBOX) {
2005 if (CC->usersupp.axlevel >= 2) {
2006 strcpy(buf, recipient);
2008 strcpy(buf, "sysop");
2009 e = alias(buf); /* alias and mail type */
2010 if ((buf[0] == 0) || (e == MES_ERROR)) {
2011 cprintf("%d Unknown address - cannot send message.\n",
2012 ERROR + NO_SUCH_USER);
2015 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2016 cprintf("%d Net privileges required for network mail.\n",
2017 ERROR + HIGHER_ACCESS_REQUIRED);
2020 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2021 && ((CC->usersupp.flags & US_INTERNET) == 0)
2022 && (!CC->internal_pgm)) {
2023 cprintf("%d You don't have access to Internet mail.\n",
2024 ERROR + HIGHER_ACCESS_REQUIRED);
2027 if (!strcasecmp(buf, "sysop")) {
2030 else if (e == MES_LOCAL) { /* don't search local file */
2031 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2032 cprintf("%d Can't send mail to yourself!\n",
2033 ERROR + NO_SUCH_USER);
2036 /* Check to make sure the user exists; also get the correct
2037 * upper/lower casing of the name.
2039 a = getuser(&tempUS, buf);
2041 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2044 strcpy(buf, tempUS.fullname);
2049 if (CC->quickroom.QRflags & QR_ANONONLY)
2051 if (CC->quickroom.QRflags & QR_ANONOPT) {
2055 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2058 /* If we're only checking the validity of the request, return
2059 * success without creating the message.
2062 cprintf("%d %s\n", OK, buf);
2066 cprintf("%d send message\n", SEND_LISTING);
2068 /* Read in the message from the client. */
2069 if (CC->fake_postname[0])
2070 msg = make_message(&CC->usersupp, buf,
2071 CC->quickroom.QRname, b, e, format_type,
2073 else if (CC->fake_username[0])
2074 msg = make_message(&CC->usersupp, buf,
2075 CC->quickroom.QRname, b, e, format_type,
2078 msg = make_message(&CC->usersupp, buf,
2079 CC->quickroom.QRname, b, e, format_type, "");
2082 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2083 CtdlFreeMessage(msg);
2084 CC->fake_postname[0] = '\0';
2091 * message entry - mode 3 (raw)
2093 void cmd_ent3(char *entargs)
2099 unsigned char ch, which_field;
2100 struct usersupp tempUS;
2102 struct CtdlMessage *msg;
2105 if (CC->internal_pgm == 0) {
2106 cprintf("%d This command is for internal programs only.\n",
2111 /* See if there's a recipient, but make sure it's a real one */
2112 extract(recp, entargs, 1);
2113 for (a = 0; a < strlen(recp); ++a)
2114 if (!isprint(recp[a]))
2115 strcpy(&recp[a], &recp[a + 1]);
2116 while (isspace(recp[0]))
2117 strcpy(recp, &recp[1]);
2118 while (isspace(recp[strlen(recp) - 1]))
2119 recp[strlen(recp) - 1] = 0;
2121 /* If we're in Mail, check the recipient */
2122 if (strlen(recp) > 0) {
2123 e = alias(recp); /* alias and mail type */
2124 if ((recp[0] == 0) || (e == MES_ERROR)) {
2125 cprintf("%d Unknown address - cannot send message.\n",
2126 ERROR + NO_SUCH_USER);
2129 if (e == MES_LOCAL) {
2130 a = getuser(&tempUS, recp);
2132 cprintf("%d No such user.\n",
2133 ERROR + NO_SUCH_USER);
2139 /* At this point, message has been approved. */
2140 if (extract_int(entargs, 0) == 0) {
2141 cprintf("%d OK to send\n", OK);
2145 msglen = extract_long(entargs, 2);
2146 msg = mallok(sizeof(struct CtdlMessage));
2148 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2152 memset(msg, 0, sizeof(struct CtdlMessage));
2153 tempbuf = mallok(msglen);
2154 if (tempbuf == NULL) {
2155 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2160 cprintf("%d %ld\n", SEND_BINARY, msglen);
2162 client_read(&ch, 1); /* 0xFF magic number */
2163 msg->cm_magic = CTDLMESSAGE_MAGIC;
2164 client_read(&ch, 1); /* anon type */
2165 msg->cm_anon_type = ch;
2166 client_read(&ch, 1); /* format type */
2167 msg->cm_format_type = ch;
2168 msglen = msglen - 3;
2170 while (msglen > 0) {
2171 client_read(&which_field, 1);
2172 if (!isalpha(which_field)) valid_msg = 0;
2176 client_read(&ch, 1);
2178 a = strlen(tempbuf);
2181 } while ( (ch != 0) && (msglen > 0) );
2183 msg->cm_fields[which_field] = strdoop(tempbuf);
2186 msg->cm_flags = CM_SKIP_HOOKS;
2187 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2188 CtdlFreeMessage(msg);
2194 * API function to delete messages which match a set of criteria
2195 * (returns the actual number of messages deleted)
2197 int CtdlDeleteMessages(char *room_name, /* which room */
2198 long dmsgnum, /* or "0" for any */
2199 char *content_type /* or "" for any */
2203 struct quickroom qrbuf;
2204 struct cdbdata *cdbfr;
2205 long *msglist = NULL;
2208 int num_deleted = 0;
2210 struct SuppMsgInfo smi;
2212 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2213 room_name, dmsgnum, content_type);
2215 /* get room record, obtaining a lock... */
2216 if (lgetroom(&qrbuf, room_name) != 0) {
2217 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2219 return (0); /* room not found */
2221 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2223 if (cdbfr != NULL) {
2224 msglist = mallok(cdbfr->len);
2225 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2226 num_msgs = cdbfr->len / sizeof(long);
2230 for (i = 0; i < num_msgs; ++i) {
2233 /* Set/clear a bit for each criterion */
2235 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2236 delete_this |= 0x01;
2238 if (strlen(content_type) == 0) {
2239 delete_this |= 0x02;
2241 GetSuppMsgInfo(&smi, msglist[i]);
2242 if (!strcasecmp(smi.smi_content_type,
2244 delete_this |= 0x02;
2248 /* Delete message only if all bits are set */
2249 if (delete_this == 0x03) {
2250 AdjRefCount(msglist[i], -1);
2256 num_msgs = sort_msglist(msglist, num_msgs);
2257 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2258 msglist, (num_msgs * sizeof(long)));
2260 qrbuf.QRhighest = msglist[num_msgs - 1];
2264 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2265 return (num_deleted);
2271 * Delete message from current room
2273 void cmd_dele(char *delstr)
2278 getuser(&CC->usersupp, CC->curr_user);
2279 if ((CC->usersupp.axlevel < 6)
2280 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2281 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2282 && (!(CC->internal_pgm))) {
2283 cprintf("%d Higher access required.\n",
2284 ERROR + HIGHER_ACCESS_REQUIRED);
2287 delnum = extract_long(delstr, 0);
2289 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2292 cprintf("%d %d message%s deleted.\n", OK,
2293 num_deleted, ((num_deleted != 1) ? "s" : ""));
2295 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2301 * move or copy a message to another room
2303 void cmd_move(char *args)
2307 struct quickroom qtemp;
2311 num = extract_long(args, 0);
2312 extract(targ, args, 1);
2313 targ[ROOMNAMELEN - 1] = 0;
2314 is_copy = extract_int(args, 2);
2316 getuser(&CC->usersupp, CC->curr_user);
2317 if ((CC->usersupp.axlevel < 6)
2318 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2319 cprintf("%d Higher access required.\n",
2320 ERROR + HIGHER_ACCESS_REQUIRED);
2324 if (getroom(&qtemp, targ) != 0) {
2325 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2329 err = CtdlSaveMsgPointerInRoom(targ, num,
2330 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2332 cprintf("%d Cannot store message in %s: error %d\n",
2337 /* Now delete the message from the source room,
2338 * if this is a 'move' rather than a 'copy' operation.
2340 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2342 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2348 * GetSuppMsgInfo() - Get the supplementary record for a message
2350 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2353 struct cdbdata *cdbsmi;
2356 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2357 smibuf->smi_msgnum = msgnum;
2358 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2360 /* Use the negative of the message number for its supp record index */
2361 TheIndex = (0L - msgnum);
2363 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2364 if (cdbsmi == NULL) {
2365 return; /* record not found; go with defaults */
2367 memcpy(smibuf, cdbsmi->ptr,
2368 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2369 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2376 * PutSuppMsgInfo() - (re)write supplementary record for a message
2378 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2382 /* Use the negative of the message number for its supp record index */
2383 TheIndex = (0L - smibuf->smi_msgnum);
2385 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2386 smibuf->smi_msgnum, smibuf->smi_refcount);
2388 cdb_store(CDB_MSGMAIN,
2389 &TheIndex, sizeof(long),
2390 smibuf, sizeof(struct SuppMsgInfo));
2395 * AdjRefCount - change the reference count for a message;
2396 * delete the message if it reaches zero
2398 void AdjRefCount(long msgnum, int incr)
2401 struct SuppMsgInfo smi;
2404 /* This is a *tight* critical section; please keep it that way, as
2405 * it may get called while nested in other critical sections.
2406 * Complicating this any further will surely cause deadlock!
2408 begin_critical_section(S_SUPPMSGMAIN);
2409 GetSuppMsgInfo(&smi, msgnum);
2410 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2411 msgnum, smi.smi_refcount);
2412 smi.smi_refcount += incr;
2413 PutSuppMsgInfo(&smi);
2414 end_critical_section(S_SUPPMSGMAIN);
2415 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2416 msgnum, smi.smi_refcount);
2418 /* If the reference count is now zero, delete the message
2419 * (and its supplementary record as well).
2421 if (smi.smi_refcount == 0) {
2422 lprintf(9, "Deleting message <%ld>\n", msgnum);
2424 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2425 delnum = (0L - msgnum);
2426 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2431 * Write a generic object to this room
2433 * Note: this could be much more efficient. Right now we use two temporary
2434 * files, and still pull the message into memory as with all others.
2436 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2437 char *content_type, /* MIME type of this object */
2438 char *tempfilename, /* Where to fetch it from */
2439 struct usersupp *is_mailbox, /* Mailbox room? */
2440 int is_binary, /* Is encoding necessary? */
2441 int is_unique, /* Del others of this type? */
2442 unsigned int flags /* Internal save flags */
2447 char filename[PATH_MAX];
2450 struct quickroom qrbuf;
2451 char roomname[ROOMNAMELEN];
2452 struct CtdlMessage *msg;
2455 if (is_mailbox != NULL)
2456 MailboxName(roomname, is_mailbox, req_room);
2458 safestrncpy(roomname, req_room, sizeof(roomname));
2459 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2461 strcpy(filename, tmpnam(NULL));
2462 fp = fopen(filename, "w");
2466 tempfp = fopen(tempfilename, "r");
2467 if (tempfp == NULL) {
2473 fprintf(fp, "Content-type: %s\n", content_type);
2474 lprintf(9, "Content-type: %s\n", content_type);
2476 if (is_binary == 0) {
2477 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2478 while (ch = getc(tempfp), ch > 0)
2484 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2487 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2488 tempfilename, filename);
2492 lprintf(9, "Allocating\n");
2493 msg = mallok(sizeof(struct CtdlMessage));
2494 memset(msg, 0, sizeof(struct CtdlMessage));
2495 msg->cm_magic = CTDLMESSAGE_MAGIC;
2496 msg->cm_anon_type = MES_NORMAL;
2497 msg->cm_format_type = 4;
2498 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2499 msg->cm_fields['O'] = strdoop(req_room);
2500 msg->cm_fields['N'] = strdoop(config.c_nodename);
2501 msg->cm_fields['H'] = strdoop(config.c_humannode);
2502 msg->cm_flags = flags;
2504 lprintf(9, "Loading\n");
2505 fp = fopen(filename, "rb");
2506 fseek(fp, 0L, SEEK_END);
2509 msg->cm_fields['M'] = mallok(len);
2510 fread(msg->cm_fields['M'], len, 1, fp);
2514 /* Create the requested room if we have to. */
2515 if (getroom(&qrbuf, roomname) != 0) {
2516 create_room(roomname,
2517 ( (is_mailbox != NULL) ? 4 : 3 ),
2520 /* If the caller specified this object as unique, delete all
2521 * other objects of this type that are currently in the room.
2524 lprintf(9, "Deleted %d other msgs of this type\n",
2525 CtdlDeleteMessages(roomname, 0L, content_type));
2527 /* Now write the data */
2528 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2529 CtdlFreeMessage(msg);
2537 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2538 config_msgnum = msgnum;
2542 char *CtdlGetSysConfig(char *sysconfname) {
2543 char hold_rm[ROOMNAMELEN];
2546 struct CtdlMessage *msg;
2549 strcpy(hold_rm, CC->quickroom.QRname);
2550 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2551 getroom(&CC->quickroom, hold_rm);
2556 /* We want the last (and probably only) config in this room */
2557 begin_critical_section(S_CONFIG);
2558 config_msgnum = (-1L);
2559 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2560 CtdlGetSysConfigBackend, NULL);
2561 msgnum = config_msgnum;
2562 end_critical_section(S_CONFIG);
2568 msg = CtdlFetchMessage(msgnum);
2570 conf = strdoop(msg->cm_fields['M']);
2571 CtdlFreeMessage(msg);
2578 getroom(&CC->quickroom, hold_rm);
2580 lprintf(9, "eggstracting...\n");
2581 if (conf != NULL) do {
2582 extract_token(buf, conf, 0, '\n');
2583 lprintf(9, "eggstracted <%s>\n", buf);
2584 strcpy(conf, &conf[strlen(buf)+1]);
2585 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2590 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2591 char temp[PATH_MAX];
2594 strcpy(temp, tmpnam(NULL));
2596 fp = fopen(temp, "w");
2597 if (fp == NULL) return;
2598 fprintf(fp, "%s", sysconfdata);
2601 /* this handy API function does all the work for us */
2602 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);