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 stov: for (i = 0; i < strlen(name); ++i) {
91 if (isspace(name[i - 1])) {
92 strcpy(&name[i - 1], &name[i]);
93 goto stov; /* start over */
95 while (isspace(name[i + 1])) {
96 strcpy(&name[i + 1], &name[i + 2]);
104 * Aliasing for network mail.
105 * (Error messages have been commented out, because this is a server.)
107 int alias(char *name)
108 { /* process alias and routing info for mail */
111 char aaa[300], bbb[300];
113 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
115 fp = fopen("network/mail.aliases", "r");
117 fp = fopen("/dev/null", "r");
122 while (fgets(aaa, sizeof aaa, fp) != NULL) {
123 while (isspace(name[0]))
124 strcpy(name, &name[1]);
125 aaa[strlen(aaa) - 1] = 0;
127 for (a = 0; a < strlen(aaa); ++a) {
129 strcpy(bbb, &aaa[a + 1]);
133 if (!strcasecmp(name, aaa))
137 lprintf(7, "Mail is being forwarded to %s\n", name);
139 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
140 for (a=0; a<strlen(name); ++a) {
141 if (name[a] == '@') {
142 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
144 lprintf(7, "Changed to <%s>\n", name);
149 /* determine local or remote type, see citadel.h */
150 for (a = 0; a < strlen(name); ++a)
152 return (MES_INTERNET);
153 for (a = 0; a < strlen(name); ++a)
155 for (b = a; b < strlen(name); ++b)
157 return (MES_INTERNET);
159 for (a = 0; a < strlen(name); ++a)
163 lprintf(7, "Too many @'s in address\n");
167 for (a = 0; a < strlen(name); ++a)
169 strcpy(bbb, &name[a + 1]);
171 strcpy(bbb, &bbb[1]);
172 fp = fopen("network/mail.sysinfo", "r");
176 a = getstring(fp, aaa);
177 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
178 a = getstring(fp, aaa);
179 if (!strncmp(aaa, "use ", 4)) {
180 strcpy(bbb, &aaa[4]);
185 if (!strncmp(aaa, "uum", 3)) {
187 for (a = 0; a < strlen(bbb); ++a) {
193 while (bbb[strlen(bbb) - 1] == '_')
194 bbb[strlen(bbb) - 1] = 0;
195 sprintf(name, &aaa[4], bbb);
196 lprintf(9, "returning MES_INTERNET\n");
197 return (MES_INTERNET);
199 if (!strncmp(aaa, "bin", 3)) {
202 while (aaa[strlen(aaa) - 1] != '@')
203 aaa[strlen(aaa) - 1] = 0;
204 aaa[strlen(aaa) - 1] = 0;
205 while (aaa[strlen(aaa) - 1] == ' ')
206 aaa[strlen(aaa) - 1] = 0;
207 while (bbb[0] != '@')
208 strcpy(bbb, &bbb[1]);
209 strcpy(bbb, &bbb[1]);
210 while (bbb[0] == ' ')
211 strcpy(bbb, &bbb[1]);
212 sprintf(name, "%s @%s", aaa, bbb);
213 lprintf(9, "returning MES_BINARY\n");
218 lprintf(9, "returning MES_LOCAL\n");
227 fp = fopen("citadel.control", "r");
228 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
234 void simple_listing(long msgnum, void *userdata)
236 cprintf("%ld\n", msgnum);
241 /* Determine if a given message matches the fields in a message template.
242 * Return 0 for a successful match.
244 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
247 /* If there aren't any fields in the template, all messages will
250 if (template == NULL) return(0);
252 /* Null messages are bogus. */
253 if (msg == NULL) return(1);
255 for (i='A'; i<='Z'; ++i) {
256 if (template->cm_fields[i] != NULL) {
257 if (msg->cm_fields[i] == NULL) {
260 if (strcasecmp(msg->cm_fields[i],
261 template->cm_fields[i])) return 1;
265 /* All compares succeeded: we have a match! */
273 * API function to perform an operation for each qualifying message in the
274 * current room. (Returns the number of messages processed.)
276 int CtdlForEachMessage(int mode, long ref,
277 int moderation_level,
279 struct CtdlMessage *compare,
280 void (*CallBack) (long, void *),
286 struct cdbdata *cdbfr;
287 long *msglist = NULL;
289 int num_processed = 0;
291 struct SuppMsgInfo smi;
292 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];
362 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
363 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
364 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
365 && (CC->usersupp.flags & US_LASTOLD))
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 */
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]);
532 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
534 if (((old == 13) || (old == 10)) && (isspace(real))) {
542 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
543 cprintf("%s%s", nl, aaa);
552 if ((strlen(aaa) + c) > (width - 5)) {
562 if ((ch == 13) || (ch == 10)) {
563 cprintf("%s%s", aaa, nl);
570 FMTEND: cprintf("%s%s", aaa, nl);
576 * Callback function for mime parser that simply lists the part
578 void list_this_part(char *name, char *filename, char *partnum, char *disp,
579 void *content, char *cbtype, size_t length)
582 cprintf("part=%s|%s|%s|%s|%s|%d\n",
583 name, filename, partnum, disp, cbtype, length);
588 * Callback function for mime parser that opens a section for downloading
590 void mime_download(char *name, char *filename, char *partnum, char *disp,
591 void *content, char *cbtype, size_t length)
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)
733 if (!strcasecmp(cbtype, "multipart/alternative")) {
734 strcpy(ma->prefix, partnum);
735 strcat(ma->prefix, ".");
741 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
743 && (ma->did_print == 1) ) {
744 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
750 if ( (!strcasecmp(cbtype, "text/plain"))
751 || (strlen(cbtype)==0) ) {
756 if (ch==10) cprintf("\r\n");
757 else cprintf("%c", ch);
760 else if (!strcasecmp(cbtype, "text/html")) {
761 ptr = html_to_ascii(content, 80, 0);
766 if (ch==10) cprintf("\r\n");
767 else cprintf("%c", ch);
771 else if (strncasecmp(cbtype, "multipart/", 10)) {
772 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
773 partnum, filename, cbtype, length);
779 * Get a message off disk. (returns om_* values found in msgbase.h)
782 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
783 int mode, /* how would you like that message? */
784 int headers_only, /* eschew the message body? */
785 int do_proto, /* do Citadel protocol responses? */
786 int crlf /* Use CRLF newlines instead of LF? */
792 char display_name[256];
793 struct CtdlMessage *TheMessage;
795 char *nl; /* newline string */
797 /* buffers needed for RFC822 translation */
807 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
811 sprintf(mid, "%ld", msg_num);
812 nl = (crlf ? "\r\n" : "\n");
814 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
815 if (do_proto) cprintf("%d Not logged in.\n",
816 ERROR + NOT_LOGGED_IN);
817 return(om_not_logged_in);
820 /* FIXME ... small security issue
821 * We need to check to make sure the requested message is actually
822 * in the current room, and set msg_ok to 1 only if it is. This
823 * functionality is currently missing because I'm in a hurry to replace
824 * broken production code with nonbroken pre-beta code. :( -- ajc
827 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
829 return(om_no_such_msg);
834 * Fetch the message from disk
836 TheMessage = CtdlFetchMessage(msg_num);
837 if (TheMessage == NULL) {
838 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
840 return(om_no_such_msg);
843 /* Are we downloading a MIME component? */
844 if (mode == MT_DOWNLOAD) {
845 if (TheMessage->cm_format_type != FMT_RFC822) {
847 cprintf("%d This is not a MIME message.\n",
849 } else if (CC->download_fp != NULL) {
850 if (do_proto) cprintf(
851 "%d You already have a download open.\n",
854 /* Parse the message text component */
855 mptr = TheMessage->cm_fields['M'];
856 mime_parser(mptr, NULL, *mime_download);
857 /* If there's no file open by this time, the requested
858 * section wasn't found, so print an error
860 if (CC->download_fp == NULL) {
861 if (do_proto) cprintf(
862 "%d Section %s not found.\n",
863 ERROR + FILE_NOT_FOUND,
867 CtdlFreeMessage(TheMessage);
868 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
871 /* now for the user-mode message reading loops */
872 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
874 /* Tell the client which format type we're using. If this is a
875 * MIME message, *lie* about it and tell the user it's fixed-format.
877 if (mode == MT_CITADEL) {
878 if (TheMessage->cm_format_type == FMT_RFC822) {
879 if (do_proto) cprintf("type=1\n");
882 if (do_proto) cprintf("type=%d\n",
883 TheMessage->cm_format_type);
887 /* nhdr=yes means that we're only displaying headers, no body */
888 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
889 if (do_proto) cprintf("nhdr=yes\n");
892 /* begin header processing loop for Citadel message format */
894 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
896 strcpy(display_name, "<unknown>");
897 if (TheMessage->cm_fields['A']) {
898 strcpy(buf, TheMessage->cm_fields['A']);
899 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
900 if (TheMessage->cm_anon_type == MES_ANON)
901 strcpy(display_name, "****");
902 else if (TheMessage->cm_anon_type == MES_AN2)
903 strcpy(display_name, "anonymous");
905 strcpy(display_name, buf);
907 && ((TheMessage->cm_anon_type == MES_ANON)
908 || (TheMessage->cm_anon_type == MES_AN2))) {
909 sprintf(&display_name[strlen(display_name)],
914 strcpy(allkeys, FORDER);
915 for (i=0; i<strlen(allkeys); ++i) {
916 k = (int) allkeys[i];
918 if (TheMessage->cm_fields[k] != NULL) {
920 if (do_proto) cprintf("%s=%s\n",
925 if (do_proto) cprintf("%s=%s\n",
927 TheMessage->cm_fields[k]
936 /* begin header processing loop for RFC822 transfer format */
941 strcpy(snode, NODENAME);
942 strcpy(lnode, HUMANNODE);
943 if (mode == MT_RFC822) {
944 cprintf("X-UIDL: %ld%s", msg_num, nl);
945 for (i = 0; i < 256; ++i) {
946 if (TheMessage->cm_fields[i]) {
947 mptr = TheMessage->cm_fields[i];
954 cprintf("Path: %s%s", mptr, nl);
957 cprintf("Subject: %s%s", mptr, nl);
963 cprintf("X-Citadel-Room: %s%s",
968 cprintf("To: %s%s", mptr, nl);
970 generate_rfc822_datestamp(datestamp,
972 cprintf("Date: %s%s", datestamp, nl);
978 for (i=0; i<strlen(suser); ++i) {
979 suser[i] = tolower(suser[i]);
980 if (!isalnum(suser[i])) suser[i]='_';
983 if (mode == MT_RFC822) {
984 if (!strcasecmp(snode, NODENAME)) {
988 /* Construct a fun message id */
989 cprintf("Message-ID: <%s", mid);
990 if (strchr(mid, '@')==NULL) {
991 cprintf("@%s", snode);
995 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
997 if (strlen(fuser) > 0) {
998 cprintf("From: %s (%s)%s", fuser, luser, nl);
1001 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1004 cprintf("Organization: %s%s", lnode, nl);
1007 /* end header processing loop ... at this point, we're in the text */
1009 mptr = TheMessage->cm_fields['M'];
1011 /* Tell the client about the MIME parts in this message */
1012 if (TheMessage->cm_format_type == FMT_RFC822) {
1013 if (mode == MT_CITADEL) {
1014 mime_parser(mptr, NULL, *list_this_part);
1016 else if (mode == MT_MIME) { /* list parts only */
1017 mime_parser(mptr, NULL, *list_this_part);
1018 if (do_proto) cprintf("000\n");
1019 CtdlFreeMessage(TheMessage);
1022 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1023 /* FIXME ... we have to put some code in here to avoid
1024 * printing duplicate header information when both
1025 * Citadel and RFC822 headers exist. Preference should
1026 * probably be given to the RFC822 headers.
1028 while (ch=*(mptr++), ch!=0) {
1030 else if (ch==10) cprintf("%s", nl);
1031 else cprintf("%c", ch);
1033 if (do_proto) cprintf("000\n");
1034 CtdlFreeMessage(TheMessage);
1040 if (do_proto) cprintf("000\n");
1041 CtdlFreeMessage(TheMessage);
1045 /* signify start of msg text */
1046 if (mode == MT_CITADEL)
1047 if (do_proto) cprintf("text\n");
1048 if (mode == MT_RFC822) {
1049 if (TheMessage->cm_fields['U'] == NULL) {
1050 cprintf("Subject: (no subject)%s", nl);
1055 /* If the format type on disk is 1 (fixed-format), then we want
1056 * everything to be output completely literally ... regardless of
1057 * what message transfer format is in use.
1059 if (TheMessage->cm_format_type == FMT_FIXED) {
1061 while (ch = *mptr++, ch > 0) {
1064 if ((ch == 10) || (strlen(buf) > 250)) {
1065 cprintf("%s%s", buf, nl);
1068 buf[strlen(buf) + 1] = 0;
1069 buf[strlen(buf)] = ch;
1072 if (strlen(buf) > 0)
1073 cprintf("%s%s", buf, nl);
1076 /* If the message on disk is format 0 (Citadel vari-format), we
1077 * output using the formatter at 80 columns. This is the final output
1078 * form if the transfer format is RFC822, but if the transfer format
1079 * is Citadel proprietary, it'll still work, because the indentation
1080 * for new paragraphs is correct and the client will reformat the
1081 * message to the reader's screen width.
1083 if (TheMessage->cm_format_type == FMT_CITADEL) {
1084 memfmout(80, mptr, 0, nl);
1087 /* If the message on disk is format 4 (MIME), we've gotta hand it
1088 * off to the MIME parser. The client has already been told that
1089 * this message is format 1 (fixed format), so the callback function
1090 * we use will display those parts as-is.
1092 if (TheMessage->cm_format_type == FMT_RFC822) {
1093 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1094 memset(ma, 0, sizeof(struct ma_info));
1095 mime_parser(mptr, NULL, *fixed_output);
1098 /* now we're done */
1099 if (do_proto) cprintf("000\n");
1100 CtdlFreeMessage(TheMessage);
1107 * display a message (mode 0 - Citadel proprietary)
1109 void cmd_msg0(char *cmdbuf)
1112 int headers_only = 0;
1114 msgid = extract_long(cmdbuf, 0);
1115 headers_only = extract_int(cmdbuf, 1);
1117 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1123 * display a message (mode 2 - RFC822)
1125 void cmd_msg2(char *cmdbuf)
1128 int headers_only = 0;
1130 msgid = extract_long(cmdbuf, 0);
1131 headers_only = extract_int(cmdbuf, 1);
1133 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1139 * display a message (mode 3 - IGnet raw format - internal programs only)
1141 void cmd_msg3(char *cmdbuf)
1144 struct CtdlMessage *msg;
1147 if (CC->internal_pgm == 0) {
1148 cprintf("%d This command is for internal programs only.\n",
1153 msgnum = extract_long(cmdbuf, 0);
1154 msg = CtdlFetchMessage(msgnum);
1156 cprintf("%d Message %ld not found.\n",
1161 serialize_message(&smr, msg);
1162 CtdlFreeMessage(msg);
1165 cprintf("%d Unable to serialize message\n",
1166 ERROR+INTERNAL_ERROR);
1170 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1171 client_write(smr.ser, smr.len);
1178 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1180 void cmd_msg4(char *cmdbuf)
1184 msgid = extract_long(cmdbuf, 0);
1185 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1189 * Open a component of a MIME message as a download file
1191 void cmd_opna(char *cmdbuf)
1195 CtdlAllocUserData(SYM_DESIRED_SECTION, 256);
1197 msgid = extract_long(cmdbuf, 0);
1198 extract(desired_section, cmdbuf, 1);
1200 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1205 * Save a message pointer into a specified room
1206 * (Returns 0 for success, nonzero for failure)
1207 * roomname may be NULL to use the current room
1209 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1211 char hold_rm[ROOMNAMELEN];
1212 struct cdbdata *cdbfr;
1215 long highest_msg = 0L;
1216 struct CtdlMessage *msg = NULL;
1218 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1219 roomname, msgid, flags);
1221 strcpy(hold_rm, CC->quickroom.QRname);
1223 /* We may need to check to see if this message is real */
1224 if ( (flags & SM_VERIFY_GOODNESS)
1225 || (flags & SM_DO_REPL_CHECK)
1227 msg = CtdlFetchMessage(msgid);
1228 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1231 /* Perform replication checks if necessary */
1232 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1234 if (getroom(&CC->quickroom,
1235 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1237 lprintf(9, "No such room <%s>\n", roomname);
1238 if (msg != NULL) CtdlFreeMessage(msg);
1239 return(ERROR + ROOM_NOT_FOUND);
1242 if (ReplicationChecks(msg) != 0) {
1243 getroom(&CC->quickroom, hold_rm);
1244 if (msg != NULL) CtdlFreeMessage(msg);
1245 lprintf(9, "Did replication, and newer exists\n");
1250 /* Now the regular stuff */
1251 if (lgetroom(&CC->quickroom,
1252 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1254 lprintf(9, "No such room <%s>\n", roomname);
1255 if (msg != NULL) CtdlFreeMessage(msg);
1256 return(ERROR + ROOM_NOT_FOUND);
1259 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1260 if (cdbfr == NULL) {
1264 msglist = mallok(cdbfr->len);
1265 if (msglist == NULL)
1266 lprintf(3, "ERROR malloc msglist!\n");
1267 num_msgs = cdbfr->len / sizeof(long);
1268 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1273 /* Make sure the message doesn't already exist in this room. It
1274 * is absolutely taboo to have more than one reference to the same
1275 * message in a room.
1277 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1278 if (msglist[i] == msgid) {
1279 lputroom(&CC->quickroom); /* unlock the room */
1280 getroom(&CC->quickroom, hold_rm);
1281 if (msg != NULL) CtdlFreeMessage(msg);
1282 return(ERROR + ALREADY_EXISTS);
1286 /* Now add the new message */
1288 msglist = reallok(msglist,
1289 (num_msgs * sizeof(long)));
1291 if (msglist == NULL) {
1292 lprintf(3, "ERROR: can't realloc message list!\n");
1294 msglist[num_msgs - 1] = msgid;
1296 /* Sort the message list, so all the msgid's are in order */
1297 num_msgs = sort_msglist(msglist, num_msgs);
1299 /* Determine the highest message number */
1300 highest_msg = msglist[num_msgs - 1];
1302 /* Write it back to disk. */
1303 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1304 msglist, num_msgs * sizeof(long));
1306 /* Free up the memory we used. */
1309 /* Update the highest-message pointer and unlock the room. */
1310 CC->quickroom.QRhighest = highest_msg;
1311 lputroom(&CC->quickroom);
1312 getroom(&CC->quickroom, hold_rm);
1314 /* Bump the reference count for this message. */
1315 if ((flags & SM_DONT_BUMP_REF)==0) {
1316 AdjRefCount(msgid, +1);
1319 /* Return success. */
1320 if (msg != NULL) CtdlFreeMessage(msg);
1327 * Message base operation to send a message to the master file
1328 * (returns new message number)
1330 * This is the back end for CtdlSaveMsg() and should not be directly
1331 * called by server-side modules.
1334 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1335 FILE *save_a_copy) /* save a copy to disk? */
1342 /* Get a new message number */
1343 newmsgid = get_new_message_number();
1344 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1346 /* Generate an ID if we don't have one already */
1347 if (msg->cm_fields['I']==NULL) {
1348 msg->cm_fields['I'] = strdoop(msgidbuf);
1351 serialize_message(&smr, msg);
1354 cprintf("%d Unable to serialize message\n",
1355 ERROR+INTERNAL_ERROR);
1359 /* Write our little bundle of joy into the message base */
1360 begin_critical_section(S_MSGMAIN);
1361 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1362 smr.ser, smr.len) < 0) {
1363 lprintf(2, "Can't store message\n");
1368 end_critical_section(S_MSGMAIN);
1370 /* If the caller specified that a copy should be saved to a particular
1371 * file handle, do that now too.
1373 if (save_a_copy != NULL) {
1374 fwrite(smr.ser, smr.len, 1, save_a_copy);
1377 /* Free the memory we used for the serialized message */
1380 /* Return the *local* message ID to the caller
1381 * (even if we're storing an incoming network message)
1389 * Serialize a struct CtdlMessage into the format used on disk and network.
1391 * This function loads up a "struct ser_ret" (defined in server.h) which
1392 * contains the length of the serialized message and a pointer to the
1393 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1395 void serialize_message(struct ser_ret *ret, /* return values */
1396 struct CtdlMessage *msg) /* unserialized msg */
1400 static char *forder = FORDER;
1402 if (is_valid_message(msg) == 0) return; /* self check */
1405 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1406 ret->len = ret->len +
1407 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1409 lprintf(9, "calling malloc(%d)\n", ret->len);
1410 ret->ser = mallok(ret->len);
1411 if (ret->ser == NULL) {
1417 ret->ser[1] = msg->cm_anon_type;
1418 ret->ser[2] = msg->cm_format_type;
1421 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1422 ret->ser[wlen++] = (char)forder[i];
1423 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1424 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1426 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1435 * Back end for the ReplicationChecks() function
1437 void check_repl(long msgnum, void *userdata) {
1438 struct CtdlMessage *msg;
1439 time_t timestamp = (-1L);
1441 lprintf(9, "check_repl() found message %ld\n", msgnum);
1442 msg = CtdlFetchMessage(msgnum);
1443 if (msg == NULL) return;
1444 if (msg->cm_fields['T'] != NULL) {
1445 timestamp = atol(msg->cm_fields['T']);
1447 CtdlFreeMessage(msg);
1449 if (timestamp > msg_repl->highest) {
1450 msg_repl->highest = timestamp; /* newer! */
1451 lprintf(9, "newer!\n");
1454 lprintf(9, "older!\n");
1456 /* Existing isn't newer? Then delete the old one(s). */
1457 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1462 * Check to see if any messages already exist which carry the same Extended ID
1466 * -> With older timestamps: delete them and return 0. Message will be saved.
1467 * -> With newer timestamps: return 1. Message save will be aborted.
1469 int ReplicationChecks(struct CtdlMessage *msg) {
1470 struct CtdlMessage *template;
1473 lprintf(9, "ReplicationChecks() started\n");
1474 /* No extended id? Don't do anything. */
1475 if (msg->cm_fields['E'] == NULL) return 0;
1476 if (strlen(msg->cm_fields['E']) == 0) return 0;
1477 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1479 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1480 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1481 msg_repl->highest = atol(msg->cm_fields['T']);
1483 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1484 memset(template, 0, sizeof(struct CtdlMessage));
1485 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1487 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1490 /* If a newer message exists with the same Extended ID, abort
1493 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1497 CtdlFreeMessage(template);
1498 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1506 * Save a message to disk
1508 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1509 char *rec, /* Recipient (mail) */
1510 char *force, /* force a particular room? */
1511 int supplied_mailtype) /* local or remote type */
1514 char hold_rm[ROOMNAMELEN];
1515 char actual_rm[ROOMNAMELEN];
1516 char force_room[ROOMNAMELEN];
1517 char content_type[256]; /* We have to learn this */
1518 char recipient[256];
1521 struct usersupp userbuf;
1523 struct SuppMsgInfo smi;
1524 FILE *network_fp = NULL;
1525 static int seqnum = 1;
1526 struct CtdlMessage *imsg;
1530 lprintf(9, "CtdlSaveMsg() called\n");
1531 if (is_valid_message(msg) == 0) return(-1); /* self check */
1532 mailtype = supplied_mailtype;
1534 /* If this message has no timestamp, we take the liberty of
1535 * giving it one, right now.
1537 if (msg->cm_fields['T'] == NULL) {
1538 lprintf(9, "Generating timestamp\n");
1539 sprintf(aaa, "%ld", time(NULL));
1540 msg->cm_fields['T'] = strdoop(aaa);
1543 /* If this message has no path, we generate one.
1545 if (msg->cm_fields['P'] == NULL) {
1546 lprintf(9, "Generating path\n");
1547 if (msg->cm_fields['A'] != NULL) {
1548 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1549 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1550 if (isspace(msg->cm_fields['P'][a])) {
1551 msg->cm_fields['P'][a] = ' ';
1556 msg->cm_fields['P'] = strdoop("unknown");
1560 strcpy(force_room, force);
1562 /* Strip non-printable characters out of the recipient name */
1563 lprintf(9, "Checking recipient (if present)\n");
1564 strcpy(recipient, rec);
1565 for (a = 0; a < strlen(recipient); ++a)
1566 if (!isprint(recipient[a]))
1567 strcpy(&recipient[a], &recipient[a + 1]);
1569 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1570 for (a=0; a<strlen(recipient); ++a) {
1571 if (recipient[a] == '@') {
1572 if (CtdlHostAlias(&recipient[a+1])
1573 == hostalias_localhost) {
1575 lprintf(7, "Changed to <%s>\n", recipient);
1576 mailtype = MES_LOCAL;
1581 lprintf(9, "Recipient is <%s>\n", recipient);
1583 /* Learn about what's inside, because it's what's inside that counts */
1584 lprintf(9, "Learning what's inside\n");
1585 if (msg->cm_fields['M'] == NULL) {
1586 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1589 switch (msg->cm_format_type) {
1591 strcpy(content_type, "text/x-citadel-variformat");
1594 strcpy(content_type, "text/plain");
1597 strcpy(content_type, "text/plain");
1598 /* advance past header fields */
1599 mptr = msg->cm_fields['M'];
1602 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1603 safestrncpy(content_type, mptr,
1604 sizeof(content_type));
1605 strcpy(content_type, &content_type[14]);
1606 for (a = 0; a < strlen(content_type); ++a)
1607 if ((content_type[a] == ';')
1608 || (content_type[a] == ' ')
1609 || (content_type[a] == 13)
1610 || (content_type[a] == 10))
1611 content_type[a] = 0;
1618 /* Goto the correct room */
1619 lprintf(9, "Switching rooms\n");
1620 strcpy(hold_rm, CC->quickroom.QRname);
1621 strcpy(actual_rm, CC->quickroom.QRname);
1623 /* If the user is a twit, move to the twit room for posting */
1624 lprintf(9, "Handling twit stuff\n");
1626 if (CC->usersupp.axlevel == 2) {
1627 strcpy(hold_rm, actual_rm);
1628 strcpy(actual_rm, config.c_twitroom);
1632 /* ...or if this message is destined for Aide> then go there. */
1633 if (strlen(force_room) > 0) {
1634 strcpy(actual_rm, force_room);
1637 lprintf(9, "Possibly relocating\n");
1638 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1639 getroom(&CC->quickroom, actual_rm);
1643 * If this message has no O (room) field, generate one.
1645 if (msg->cm_fields['O'] == NULL) {
1646 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1649 /* Perform "before save" hooks (aborting if any return nonzero) */
1650 lprintf(9, "Performing before-save hooks\n");
1651 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1653 /* If this message has an Extended ID, perform replication checks */
1654 lprintf(9, "Performing replication checks\n");
1655 if (ReplicationChecks(msg) > 0) return(-1);
1657 /* Network mail - send a copy to the network program. */
1658 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1659 lprintf(9, "Sending network spool\n");
1660 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1661 (long) getpid(), CC->cs_pid, ++seqnum);
1662 lprintf(9, "Saving a copy to %s\n", aaa);
1663 network_fp = fopen(aaa, "ab+");
1664 if (network_fp == NULL)
1665 lprintf(2, "ERROR: %s\n", strerror(errno));
1668 /* Save it to disk */
1669 lprintf(9, "Saving to disk\n");
1670 newmsgid = send_message(msg, network_fp);
1671 if (network_fp != NULL) {
1673 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1676 if (newmsgid <= 0L) return(-1);
1678 /* Write a supplemental message info record. This doesn't have to
1679 * be a critical section because nobody else knows about this message
1682 lprintf(9, "Creating SuppMsgInfo record\n");
1683 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1684 smi.smi_msgnum = newmsgid;
1685 smi.smi_refcount = 0;
1686 safestrncpy(smi.smi_content_type, content_type, 64);
1687 PutSuppMsgInfo(&smi);
1689 /* Now figure out where to store the pointers */
1690 lprintf(9, "Storing pointers\n");
1692 /* If this is being done by the networker delivering a private
1693 * message, we want to BYPASS saving the sender's copy (because there
1694 * is no local sender; it would otherwise go to the Trashcan).
1696 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1697 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1698 lprintf(3, "ERROR saving message pointer!\n");
1699 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1703 /* For internet mail, drop a copy in the outbound queue room */
1704 if (mailtype == MES_INTERNET) {
1705 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1708 /* Bump this user's messages posted counter. */
1709 lprintf(9, "Updating user\n");
1710 lgetuser(&CC->usersupp, CC->curr_user);
1711 CC->usersupp.posted = CC->usersupp.posted + 1;
1712 lputuser(&CC->usersupp);
1714 /* If this is private, local mail, make a copy in the
1715 * recipient's mailbox and bump the reference count.
1717 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1718 if (getuser(&userbuf, recipient) == 0) {
1719 lprintf(9, "Delivering private mail\n");
1720 MailboxName(actual_rm, &userbuf, MAILROOM);
1721 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1724 lprintf(9, "No user <%s>, saving in %s> instead\n",
1725 recipient, AIDEROOM);
1726 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1730 /* Perform "after save" hooks */
1731 lprintf(9, "Performing after-save hooks\n");
1732 PerformMessageHooks(msg, EVT_AFTERSAVE);
1735 lprintf(9, "Returning to original room\n");
1736 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1737 getroom(&CC->quickroom, hold_rm);
1739 /* For internet mail, generate delivery instructions
1740 * (Yes, this is recursive! Deal with it!)
1742 if (mailtype == MES_INTERNET) {
1743 lprintf(9, "Generating delivery instructions\n");
1744 instr = mallok(2048);
1746 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1749 SPOOLMIME, newmsgid, time(NULL),
1750 msg->cm_fields['A'], msg->cm_fields['N'],
1753 imsg = mallok(sizeof(struct CtdlMessage));
1754 memset(imsg, 0, sizeof(struct CtdlMessage));
1755 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1756 imsg->cm_anon_type = MES_NORMAL;
1757 imsg->cm_format_type = FMT_RFC822;
1758 imsg->cm_fields['A'] = strdoop("Citadel");
1759 imsg->cm_fields['M'] = instr;
1760 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1761 CtdlFreeMessage(imsg);
1770 * Convenience function for generating small administrative messages.
1772 void quickie_message(char *from, char *to, char *room, char *text)
1774 struct CtdlMessage *msg;
1776 msg = mallok(sizeof(struct CtdlMessage));
1777 memset(msg, 0, sizeof(struct CtdlMessage));
1778 msg->cm_magic = CTDLMESSAGE_MAGIC;
1779 msg->cm_anon_type = MES_NORMAL;
1780 msg->cm_format_type = 0;
1781 msg->cm_fields['A'] = strdoop(from);
1782 msg->cm_fields['O'] = strdoop(room);
1783 msg->cm_fields['N'] = strdoop(NODENAME);
1785 msg->cm_fields['R'] = strdoop(to);
1786 msg->cm_fields['M'] = strdoop(text);
1788 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1789 CtdlFreeMessage(msg);
1790 syslog(LOG_NOTICE, text);
1796 * Back end function used by make_message() and similar functions
1798 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1799 size_t maxlen, /* maximum message length */
1800 char *exist /* if non-null, append to it;
1801 exist is ALWAYS freed */
1804 size_t message_len = 0;
1805 size_t buffer_len = 0;
1809 if (exist == NULL) {
1813 m = reallok(exist, strlen(exist) + 4096);
1814 if (m == NULL) phree(exist);
1817 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1824 /* read in the lines of message text one by one */
1826 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1828 /* augment the buffer if we have to */
1829 if ((message_len + strlen(buf) + 2) > buffer_len) {
1830 lprintf(9, "realloking\n");
1831 ptr = reallok(m, (buffer_len * 2) );
1832 if (ptr == NULL) { /* flush if can't allocate */
1833 while ( (client_gets(buf)>0) &&
1834 strcmp(buf, terminator)) ;;
1837 buffer_len = (buffer_len * 2);
1840 lprintf(9, "buffer_len is %d\n", buffer_len);
1844 if (append == NULL) append = m;
1845 while (strlen(append) > 0) ++append;
1846 strcpy(append, buf);
1847 strcat(append, "\n");
1848 message_len = message_len + strlen(buf) + 1;
1850 /* if we've hit the max msg length, flush the rest */
1851 if (message_len >= maxlen) {
1852 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1863 * Build a binary message to be saved on disk.
1866 struct CtdlMessage *make_message(
1867 struct usersupp *author, /* author's usersupp structure */
1868 char *recipient, /* NULL if it's not mail */
1869 char *room, /* room where it's going */
1870 int type, /* see MES_ types in header file */
1871 int net_type, /* see MES_ types in header file */
1872 int format_type, /* local or remote (see citadel.h) */
1873 char *fake_name) /* who we're masquerading as */
1879 struct CtdlMessage *msg;
1881 msg = mallok(sizeof(struct CtdlMessage));
1882 memset(msg, 0, sizeof(struct CtdlMessage));
1883 msg->cm_magic = CTDLMESSAGE_MAGIC;
1884 msg->cm_anon_type = type;
1885 msg->cm_format_type = format_type;
1887 /* Don't confuse the poor folks if it's not routed mail. */
1888 strcpy(dest_node, "");
1890 /* If net_type is MES_BINARY, split out the destination node. */
1891 if (net_type == MES_BINARY) {
1892 strcpy(dest_node, NODENAME);
1893 for (a = 0; a < strlen(recipient); ++a) {
1894 if (recipient[a] == '@') {
1896 strcpy(dest_node, &recipient[a + 1]);
1901 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1902 if (net_type == MES_INTERNET) {
1903 strcpy(dest_node, "internet");
1906 while (isspace(recipient[strlen(recipient) - 1]))
1907 recipient[strlen(recipient) - 1] = 0;
1909 sprintf(buf, "cit%ld", author->usernum); /* Path */
1910 msg->cm_fields['P'] = strdoop(buf);
1912 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1913 msg->cm_fields['T'] = strdoop(buf);
1915 if (fake_name[0]) /* author */
1916 msg->cm_fields['A'] = strdoop(fake_name);
1918 msg->cm_fields['A'] = strdoop(author->fullname);
1920 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1921 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1923 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1925 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1926 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1928 if (recipient[0] != 0)
1929 msg->cm_fields['R'] = strdoop(recipient);
1930 if (dest_node[0] != 0)
1931 msg->cm_fields['D'] = strdoop(dest_node);
1934 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1935 config.c_maxmsglen, NULL);
1946 * message entry - mode 0 (normal)
1948 void cmd_ent0(char *entargs)
1951 char recipient[256];
1953 int format_type = 0;
1954 char newusername[256];
1955 struct CtdlMessage *msg;
1959 struct usersupp tempUS;
1962 post = extract_int(entargs, 0);
1963 extract(recipient, entargs, 1);
1964 anon_flag = extract_int(entargs, 2);
1965 format_type = extract_int(entargs, 3);
1967 /* first check to make sure the request is valid. */
1969 if (!(CC->logged_in)) {
1970 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1973 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1974 cprintf("%d Need to be validated to enter ",
1975 ERROR + HIGHER_ACCESS_REQUIRED);
1976 cprintf("(except in %s> to sysop)\n", MAILROOM);
1979 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1980 cprintf("%d Need net privileges to enter here.\n",
1981 ERROR + HIGHER_ACCESS_REQUIRED);
1984 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1985 cprintf("%d Sorry, this is a read-only room.\n",
1986 ERROR + HIGHER_ACCESS_REQUIRED);
1993 if (CC->usersupp.axlevel < 6) {
1994 cprintf("%d You don't have permission to masquerade.\n",
1995 ERROR + HIGHER_ACCESS_REQUIRED);
1998 extract(newusername, entargs, 4);
1999 memset(CC->fake_postname, 0, 32);
2000 strcpy(CC->fake_postname, newusername);
2001 cprintf("%d Ok\n", OK);
2004 CC->cs_flags |= CS_POSTING;
2007 if (CC->quickroom.QRflags & QR_MAILBOX) {
2008 if (CC->usersupp.axlevel >= 2) {
2009 strcpy(buf, recipient);
2011 strcpy(buf, "sysop");
2012 e = alias(buf); /* alias and mail type */
2013 if ((buf[0] == 0) || (e == MES_ERROR)) {
2014 cprintf("%d Unknown address - cannot send message.\n",
2015 ERROR + NO_SUCH_USER);
2018 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2019 cprintf("%d Net privileges required for network mail.\n",
2020 ERROR + HIGHER_ACCESS_REQUIRED);
2023 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2024 && ((CC->usersupp.flags & US_INTERNET) == 0)
2025 && (!CC->internal_pgm)) {
2026 cprintf("%d You don't have access to Internet mail.\n",
2027 ERROR + HIGHER_ACCESS_REQUIRED);
2030 if (!strcasecmp(buf, "sysop")) {
2035 goto SKFALL; /* don't search local file */
2036 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2037 cprintf("%d Can't send mail to yourself!\n",
2038 ERROR + NO_SUCH_USER);
2041 /* Check to make sure the user exists; also get the correct
2042 * upper/lower casing of the name.
2044 a = getuser(&tempUS, buf);
2046 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2049 strcpy(buf, tempUS.fullname);
2052 SKFALL: b = MES_NORMAL;
2053 if (CC->quickroom.QRflags & QR_ANONONLY)
2055 if (CC->quickroom.QRflags & QR_ANONOPT) {
2059 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2062 /* If we're only checking the validity of the request, return
2063 * success without creating the message.
2066 cprintf("%d %s\n", OK, buf);
2070 cprintf("%d send message\n", SEND_LISTING);
2072 /* Read in the message from the client. */
2073 if (CC->fake_postname[0])
2074 msg = make_message(&CC->usersupp, buf,
2075 CC->quickroom.QRname, b, e, format_type,
2077 else if (CC->fake_username[0])
2078 msg = make_message(&CC->usersupp, buf,
2079 CC->quickroom.QRname, b, e, format_type,
2082 msg = make_message(&CC->usersupp, buf,
2083 CC->quickroom.QRname, b, e, format_type, "");
2086 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2087 CtdlFreeMessage(msg);
2088 CC->fake_postname[0] = '\0';
2095 * message entry - mode 3 (raw)
2097 void cmd_ent3(char *entargs)
2103 unsigned char ch, which_field;
2104 struct usersupp tempUS;
2106 struct CtdlMessage *msg;
2109 if (CC->internal_pgm == 0) {
2110 cprintf("%d This command is for internal programs only.\n",
2115 /* See if there's a recipient, but make sure it's a real one */
2116 extract(recp, entargs, 1);
2117 for (a = 0; a < strlen(recp); ++a)
2118 if (!isprint(recp[a]))
2119 strcpy(&recp[a], &recp[a + 1]);
2120 while (isspace(recp[0]))
2121 strcpy(recp, &recp[1]);
2122 while (isspace(recp[strlen(recp) - 1]))
2123 recp[strlen(recp) - 1] = 0;
2125 /* If we're in Mail, check the recipient */
2126 if (strlen(recp) > 0) {
2127 e = alias(recp); /* alias and mail type */
2128 if ((recp[0] == 0) || (e == MES_ERROR)) {
2129 cprintf("%d Unknown address - cannot send message.\n",
2130 ERROR + NO_SUCH_USER);
2133 if (e == MES_LOCAL) {
2134 a = getuser(&tempUS, recp);
2136 cprintf("%d No such user.\n",
2137 ERROR + NO_SUCH_USER);
2143 /* At this point, message has been approved. */
2144 if (extract_int(entargs, 0) == 0) {
2145 cprintf("%d OK to send\n", OK);
2149 msglen = extract_long(entargs, 2);
2150 msg = mallok(sizeof(struct CtdlMessage));
2152 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2156 memset(msg, 0, sizeof(struct CtdlMessage));
2157 tempbuf = mallok(msglen);
2158 if (tempbuf == NULL) {
2159 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2164 cprintf("%d %ld\n", SEND_BINARY, msglen);
2166 client_read(&ch, 1); /* 0xFF magic number */
2167 msg->cm_magic = CTDLMESSAGE_MAGIC;
2168 client_read(&ch, 1); /* anon type */
2169 msg->cm_anon_type = ch;
2170 client_read(&ch, 1); /* format type */
2171 msg->cm_format_type = ch;
2172 msglen = msglen - 3;
2174 while (msglen > 0) {
2175 client_read(&which_field, 1);
2176 if (!isalpha(which_field)) valid_msg = 0;
2180 client_read(&ch, 1);
2182 a = strlen(tempbuf);
2185 } while ( (ch != 0) && (msglen > 0) );
2187 msg->cm_fields[which_field] = strdoop(tempbuf);
2190 msg->cm_flags = CM_SKIP_HOOKS;
2191 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2192 CtdlFreeMessage(msg);
2198 * API function to delete messages which match a set of criteria
2199 * (returns the actual number of messages deleted)
2201 int CtdlDeleteMessages(char *room_name, /* which room */
2202 long dmsgnum, /* or "0" for any */
2203 char *content_type /* or "" for any */
2207 struct quickroom qrbuf;
2208 struct cdbdata *cdbfr;
2209 long *msglist = NULL;
2212 int num_deleted = 0;
2214 struct SuppMsgInfo smi;
2216 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2217 room_name, dmsgnum, content_type);
2219 /* get room record, obtaining a lock... */
2220 if (lgetroom(&qrbuf, room_name) != 0) {
2221 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2223 return (0); /* room not found */
2225 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2227 if (cdbfr != NULL) {
2228 msglist = mallok(cdbfr->len);
2229 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2230 num_msgs = cdbfr->len / sizeof(long);
2234 for (i = 0; i < num_msgs; ++i) {
2237 /* Set/clear a bit for each criterion */
2239 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2240 delete_this |= 0x01;
2242 if (strlen(content_type) == 0) {
2243 delete_this |= 0x02;
2245 GetSuppMsgInfo(&smi, msglist[i]);
2246 if (!strcasecmp(smi.smi_content_type,
2248 delete_this |= 0x02;
2252 /* Delete message only if all bits are set */
2253 if (delete_this == 0x03) {
2254 AdjRefCount(msglist[i], -1);
2260 num_msgs = sort_msglist(msglist, num_msgs);
2261 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2262 msglist, (num_msgs * sizeof(long)));
2264 qrbuf.QRhighest = msglist[num_msgs - 1];
2268 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2269 return (num_deleted);
2275 * Delete message from current room
2277 void cmd_dele(char *delstr)
2282 getuser(&CC->usersupp, CC->curr_user);
2283 if ((CC->usersupp.axlevel < 6)
2284 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2285 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2286 && (!(CC->internal_pgm))) {
2287 cprintf("%d Higher access required.\n",
2288 ERROR + HIGHER_ACCESS_REQUIRED);
2291 delnum = extract_long(delstr, 0);
2293 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2296 cprintf("%d %d message%s deleted.\n", OK,
2297 num_deleted, ((num_deleted != 1) ? "s" : ""));
2299 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2305 * move or copy a message to another room
2307 void cmd_move(char *args)
2311 struct quickroom qtemp;
2315 num = extract_long(args, 0);
2316 extract(targ, args, 1);
2317 targ[ROOMNAMELEN - 1] = 0;
2318 is_copy = extract_int(args, 2);
2320 getuser(&CC->usersupp, CC->curr_user);
2321 if ((CC->usersupp.axlevel < 6)
2322 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2323 cprintf("%d Higher access required.\n",
2324 ERROR + HIGHER_ACCESS_REQUIRED);
2328 if (getroom(&qtemp, targ) != 0) {
2329 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2333 err = CtdlSaveMsgPointerInRoom(targ, num,
2334 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2336 cprintf("%d Cannot store message in %s: error %d\n",
2341 /* Now delete the message from the source room,
2342 * if this is a 'move' rather than a 'copy' operation.
2344 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2346 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2352 * GetSuppMsgInfo() - Get the supplementary record for a message
2354 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2357 struct cdbdata *cdbsmi;
2360 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2361 smibuf->smi_msgnum = msgnum;
2362 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2364 /* Use the negative of the message number for its supp record index */
2365 TheIndex = (0L - msgnum);
2367 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2368 if (cdbsmi == NULL) {
2369 return; /* record not found; go with defaults */
2371 memcpy(smibuf, cdbsmi->ptr,
2372 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2373 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2380 * PutSuppMsgInfo() - (re)write supplementary record for a message
2382 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2386 /* Use the negative of the message number for its supp record index */
2387 TheIndex = (0L - smibuf->smi_msgnum);
2389 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2390 smibuf->smi_msgnum, smibuf->smi_refcount);
2392 cdb_store(CDB_MSGMAIN,
2393 &TheIndex, sizeof(long),
2394 smibuf, sizeof(struct SuppMsgInfo));
2399 * AdjRefCount - change the reference count for a message;
2400 * delete the message if it reaches zero
2402 void AdjRefCount(long msgnum, int incr)
2405 struct SuppMsgInfo smi;
2408 /* This is a *tight* critical section; please keep it that way, as
2409 * it may get called while nested in other critical sections.
2410 * Complicating this any further will surely cause deadlock!
2412 begin_critical_section(S_SUPPMSGMAIN);
2413 GetSuppMsgInfo(&smi, msgnum);
2414 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2415 msgnum, smi.smi_refcount);
2416 smi.smi_refcount += incr;
2417 PutSuppMsgInfo(&smi);
2418 end_critical_section(S_SUPPMSGMAIN);
2419 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2420 msgnum, smi.smi_refcount);
2422 /* If the reference count is now zero, delete the message
2423 * (and its supplementary record as well).
2425 if (smi.smi_refcount == 0) {
2426 lprintf(9, "Deleting message <%ld>\n", msgnum);
2428 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2429 delnum = (0L - msgnum);
2430 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2435 * Write a generic object to this room
2437 * Note: this could be much more efficient. Right now we use two temporary
2438 * files, and still pull the message into memory as with all others.
2440 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2441 char *content_type, /* MIME type of this object */
2442 char *tempfilename, /* Where to fetch it from */
2443 struct usersupp *is_mailbox, /* Mailbox room? */
2444 int is_binary, /* Is encoding necessary? */
2445 int is_unique, /* Del others of this type? */
2446 unsigned int flags /* Internal save flags */
2451 char filename[PATH_MAX];
2454 struct quickroom qrbuf;
2455 char roomname[ROOMNAMELEN];
2456 struct CtdlMessage *msg;
2459 if (is_mailbox != NULL)
2460 MailboxName(roomname, is_mailbox, req_room);
2462 safestrncpy(roomname, req_room, sizeof(roomname));
2463 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2465 strcpy(filename, tmpnam(NULL));
2466 fp = fopen(filename, "w");
2470 tempfp = fopen(tempfilename, "r");
2471 if (tempfp == NULL) {
2477 fprintf(fp, "Content-type: %s\n", content_type);
2478 lprintf(9, "Content-type: %s\n", content_type);
2480 if (is_binary == 0) {
2481 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2482 while (ch = getc(tempfp), ch > 0)
2488 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2491 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2492 tempfilename, filename);
2496 lprintf(9, "Allocating\n");
2497 msg = mallok(sizeof(struct CtdlMessage));
2498 memset(msg, 0, sizeof(struct CtdlMessage));
2499 msg->cm_magic = CTDLMESSAGE_MAGIC;
2500 msg->cm_anon_type = MES_NORMAL;
2501 msg->cm_format_type = 4;
2502 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2503 msg->cm_fields['O'] = strdoop(req_room);
2504 msg->cm_fields['N'] = strdoop(config.c_nodename);
2505 msg->cm_fields['H'] = strdoop(config.c_humannode);
2506 msg->cm_flags = flags;
2508 lprintf(9, "Loading\n");
2509 fp = fopen(filename, "rb");
2510 fseek(fp, 0L, SEEK_END);
2513 msg->cm_fields['M'] = mallok(len);
2514 fread(msg->cm_fields['M'], len, 1, fp);
2518 /* Create the requested room if we have to. */
2519 if (getroom(&qrbuf, roomname) != 0) {
2520 create_room(roomname,
2521 ( (is_mailbox != NULL) ? 4 : 3 ),
2524 /* If the caller specified this object as unique, delete all
2525 * other objects of this type that are currently in the room.
2528 lprintf(9, "Deleted %d other msgs of this type\n",
2529 CtdlDeleteMessages(roomname, 0L, content_type));
2531 /* Now write the data */
2532 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2533 CtdlFreeMessage(msg);
2541 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2542 config_msgnum = msgnum;
2546 char *CtdlGetSysConfig(char *sysconfname) {
2547 char hold_rm[ROOMNAMELEN];
2550 struct CtdlMessage *msg;
2553 strcpy(hold_rm, CC->quickroom.QRname);
2554 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2555 getroom(&CC->quickroom, hold_rm);
2560 /* We want the last (and probably only) config in this room */
2561 begin_critical_section(S_CONFIG);
2562 config_msgnum = (-1L);
2563 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2564 CtdlGetSysConfigBackend, NULL);
2565 msgnum = config_msgnum;
2566 end_critical_section(S_CONFIG);
2572 msg = CtdlFetchMessage(msgnum);
2574 conf = strdoop(msg->cm_fields['M']);
2575 CtdlFreeMessage(msg);
2582 getroom(&CC->quickroom, hold_rm);
2584 lprintf(9, "eggstracting...\n");
2585 if (conf != NULL) do {
2586 extract_token(buf, conf, 0, '\n');
2587 lprintf(9, "eggstracted <%s>\n", buf);
2588 strcpy(conf, &conf[strlen(buf)+1]);
2589 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2594 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2595 char temp[PATH_MAX];
2598 strcpy(temp, tmpnam(NULL));
2600 fp = fopen(temp, "w");
2601 if (fp == NULL) return;
2602 fprintf(fp, "%s", sysconfdata);
2605 /* this handy API function does all the work for us */
2606 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);