21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
32 #include "internet_addressing.h"
34 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
35 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
36 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
38 extern struct config config;
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
49 "", "", "", "", "", "", "", "",
76 * This function is self explanatory.
77 * (What can I say, I'm in a weird mood today...)
79 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
83 for (i = 0; i < strlen(name); ++i)
86 if (isspace(name[i - 1])) {
87 strcpy(&name[i - 1], &name[i]);
90 while (isspace(name[i + 1])) {
91 strcpy(&name[i + 1], &name[i + 2]);
98 * Aliasing for network mail.
99 * (Error messages have been commented out, because this is a server.)
101 int alias(char *name)
102 { /* process alias and routing info for mail */
105 char aaa[300], bbb[300];
107 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
109 fp = fopen("network/mail.aliases", "r");
111 fp = fopen("/dev/null", "r");
116 while (fgets(aaa, sizeof aaa, fp) != NULL) {
117 while (isspace(name[0]))
118 strcpy(name, &name[1]);
119 aaa[strlen(aaa) - 1] = 0;
121 for (a = 0; a < strlen(aaa); ++a) {
123 strcpy(bbb, &aaa[a + 1]);
127 if (!strcasecmp(name, aaa))
131 lprintf(7, "Mail is being forwarded to %s\n", name);
133 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
134 for (a=0; a<strlen(name); ++a) {
135 if (name[a] == '@') {
136 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
138 lprintf(7, "Changed to <%s>\n", name);
143 /* determine local or remote type, see citadel.h */
144 for (a = 0; a < strlen(name); ++a)
146 return (MES_INTERNET);
147 for (a = 0; a < strlen(name); ++a)
149 for (b = a; b < strlen(name); ++b)
151 return (MES_INTERNET);
153 for (a = 0; a < strlen(name); ++a)
157 lprintf(7, "Too many @'s in address\n");
161 for (a = 0; a < strlen(name); ++a)
163 strcpy(bbb, &name[a + 1]);
165 strcpy(bbb, &bbb[1]);
166 fp = fopen("network/mail.sysinfo", "r");
170 a = getstring(fp, aaa);
171 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
172 a = getstring(fp, aaa);
173 if (!strncmp(aaa, "use ", 4)) {
174 strcpy(bbb, &aaa[4]);
179 if (!strncmp(aaa, "uum", 3)) {
181 for (a = 0; a < strlen(bbb); ++a) {
187 while (bbb[strlen(bbb) - 1] == '_')
188 bbb[strlen(bbb) - 1] = 0;
189 sprintf(name, &aaa[4], bbb);
190 lprintf(9, "returning MES_INTERNET\n");
191 return (MES_INTERNET);
193 if (!strncmp(aaa, "bin", 3)) {
196 while (aaa[strlen(aaa) - 1] != '@')
197 aaa[strlen(aaa) - 1] = 0;
198 aaa[strlen(aaa) - 1] = 0;
199 while (aaa[strlen(aaa) - 1] == ' ')
200 aaa[strlen(aaa) - 1] = 0;
201 while (bbb[0] != '@')
202 strcpy(bbb, &bbb[1]);
203 strcpy(bbb, &bbb[1]);
204 while (bbb[0] == ' ')
205 strcpy(bbb, &bbb[1]);
206 sprintf(name, "%s @%s", aaa, bbb);
207 lprintf(9, "returning MES_BINARY\n");
212 lprintf(9, "returning MES_LOCAL\n");
221 fp = fopen("citadel.control", "r");
222 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
228 void simple_listing(long msgnum)
230 cprintf("%ld\n", msgnum);
235 /* Determine if a given message matches the fields in a message template.
236 * Return 0 for a successful match.
238 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
241 /* If there aren't any fields in the template, all messages will
244 if (template == NULL) return(0);
246 /* Null messages are bogus. */
247 if (msg == NULL) return(1);
249 for (i='A'; i<='Z'; ++i) {
250 if (template->cm_fields[i] != NULL) {
251 if (msg->cm_fields[i] == NULL) {
254 if (strcasecmp(msg->cm_fields[i],
255 template->cm_fields[i])) return 1;
259 /* All compares succeeded: we have a match! */
267 * API function to perform an operation for each qualifying message in the
268 * current room. (Returns the number of messages processed.)
270 int CtdlForEachMessage(int mode, long ref,
271 int moderation_level,
273 struct CtdlMessage *compare,
274 void (*CallBack) (long msgnum))
279 struct cdbdata *cdbfr;
280 long *msglist = NULL;
282 int num_processed = 0;
284 struct SuppMsgInfo smi;
285 struct CtdlMessage *msg;
287 /* Learn about the user and room in question */
289 getuser(&CC->usersupp, CC->curr_user);
290 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
292 /* Load the message list */
293 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
295 msglist = mallok(cdbfr->len);
296 memcpy(msglist, cdbfr->ptr, cdbfr->len);
297 num_msgs = cdbfr->len / sizeof(long);
300 return 0; /* No messages at all? No further action. */
304 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
305 GetSuppMsgInfo(&smi, msglist[a]);
307 /* Filter out messages that are moderated below the level
308 * currently being viewed at.
310 if (smi.smi_mod < moderation_level) {
314 /* If the caller is looking for a specific MIME type, filter
315 * out all messages which are not of the type requested.
317 if (content_type != NULL) if (strlen(content_type) > 0) {
318 if (strcasecmp(smi.smi_content_type, content_type)) {
324 num_msgs = sort_msglist(msglist, num_msgs);
326 /* If a template was supplied, filter out the messages which
327 * don't match. (This could induce some delays!)
330 if (compare != NULL) {
331 for (a = 0; a < num_msgs; ++a) {
332 msg = CtdlFetchMessage(msglist[a]);
334 if (CtdlMsgCmp(msg, compare)) {
337 CtdlFreeMessage(msg);
345 * Now iterate through the message list, according to the
346 * criteria supplied by the caller.
349 for (a = 0; a < num_msgs; ++a) {
350 thismsg = msglist[a];
355 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
356 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
357 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
358 && (CC->usersupp.flags & US_LASTOLD))
359 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
360 || ((mode == MSGS_FIRST) && (a < ref))
361 || ((mode == MSGS_GT) && (thismsg > ref))
362 || ((mode == MSGS_EQ) && (thismsg == ref))
365 if (CallBack) CallBack(thismsg);
369 phree(msglist); /* Clean up */
370 return num_processed;
376 * cmd_msgs() - get list of message #'s in this room
377 * implements the MSGS server command using CtdlForEachMessage()
379 void cmd_msgs(char *cmdbuf)
388 int with_template = 0;
389 struct CtdlMessage *template = NULL;
391 extract(which, cmdbuf, 0);
392 cm_ref = extract_int(cmdbuf, 1);
393 with_template = extract_int(cmdbuf, 2);
397 if (!strncasecmp(which, "OLD", 3))
399 else if (!strncasecmp(which, "NEW", 3))
401 else if (!strncasecmp(which, "FIRST", 5))
403 else if (!strncasecmp(which, "LAST", 4))
405 else if (!strncasecmp(which, "GT", 2))
408 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
409 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
414 cprintf("%d Send template then receive message list\n",
416 template = (struct CtdlMessage *)
417 mallok(sizeof(struct CtdlMessage));
418 memset(template, 0, sizeof(struct CtdlMessage));
419 while(client_gets(buf), strcmp(buf,"000")) {
420 extract(tfield, buf, 0);
421 extract(tvalue, buf, 1);
422 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
423 if (!strcasecmp(tfield, msgkeys[i])) {
424 template->cm_fields[i] =
431 cprintf("%d Message list...\n", LISTING_FOLLOWS);
434 CtdlForEachMessage(mode, cm_ref,
435 CC->usersupp.moderation_filter,
436 NULL, template, simple_listing);
437 if (template != NULL) CtdlFreeMessage(template);
445 * help_subst() - support routine for help file viewer
447 void help_subst(char *strbuf, char *source, char *dest)
452 while (p = pattern2(strbuf, source), (p >= 0)) {
453 strcpy(workbuf, &strbuf[p + strlen(source)]);
454 strcpy(&strbuf[p], dest);
455 strcat(strbuf, workbuf);
460 void do_help_subst(char *buffer)
464 help_subst(buffer, "^nodename", config.c_nodename);
465 help_subst(buffer, "^humannode", config.c_humannode);
466 help_subst(buffer, "^fqdn", config.c_fqdn);
467 help_subst(buffer, "^username", CC->usersupp.fullname);
468 sprintf(buf2, "%ld", CC->usersupp.usernum);
469 help_subst(buffer, "^usernum", buf2);
470 help_subst(buffer, "^sysadm", config.c_sysadm);
471 help_subst(buffer, "^variantname", CITADEL);
472 sprintf(buf2, "%d", config.c_maxsessions);
473 help_subst(buffer, "^maxsessions", buf2);
479 * memfmout() - Citadel text formatter and paginator.
480 * Although the original purpose of this routine was to format
481 * text to the reader's screen width, all we're really using it
482 * for here is to format text out to 80 columns before sending it
483 * to the client. The client software may reformat it again.
486 int width, /* screen width to use */
487 char *mptr, /* where are we going to get our text from? */
488 char subst, /* nonzero if we should do substitutions */
489 char *nl) /* string to terminate lines with */
501 c = 1; /* c is the current pos */
504 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
506 buffer[strlen(buffer) + 1] = 0;
507 buffer[strlen(buffer)] = ch;
510 if (buffer[0] == '^')
511 do_help_subst(buffer);
513 buffer[strlen(buffer) + 1] = 0;
515 strcpy(buffer, &buffer[1]);
525 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
527 if (((old == 13) || (old == 10)) && (isspace(real))) {
535 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
536 cprintf("%s%s", nl, aaa);
545 if ((strlen(aaa) + c) > (width - 5)) {
555 if ((ch == 13) || (ch == 10)) {
556 cprintf("%s%s", aaa, nl);
563 FMTEND: cprintf("%s%s", aaa, nl);
569 * Callback function for mime parser that simply lists the part
571 void list_this_part(char *name, char *filename, char *partnum, char *disp,
572 void *content, char *cbtype, size_t length)
575 cprintf("part=%s|%s|%s|%s|%s|%d\n",
576 name, filename, partnum, disp, cbtype, length);
581 * Callback function for mime parser that opens a section for downloading
583 void mime_download(char *name, char *filename, char *partnum, char *disp,
584 void *content, char *cbtype, size_t length)
587 /* Silently go away if there's already a download open... */
588 if (CC->download_fp != NULL)
591 /* ...or if this is not the desired section */
592 if (strcasecmp(desired_section, partnum))
595 CC->download_fp = tmpfile();
596 if (CC->download_fp == NULL)
599 fwrite(content, length, 1, CC->download_fp);
600 fflush(CC->download_fp);
601 rewind(CC->download_fp);
603 OpenCmdResult(filename, cbtype);
609 * Load a message from disk into memory.
610 * This is used by CtdlOutputMsg() and other fetch functions.
612 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
613 * using the CtdlMessageFree() function.
615 struct CtdlMessage *CtdlFetchMessage(long msgnum)
617 struct cdbdata *dmsgtext;
618 struct CtdlMessage *ret = NULL;
621 CIT_UBYTE field_header;
624 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
625 if (dmsgtext == NULL) {
628 mptr = dmsgtext->ptr;
630 /* Parse the three bytes that begin EVERY message on disk.
631 * The first is always 0xFF, the on-disk magic number.
632 * The second is the anonymous/public type byte.
633 * The third is the format type byte (vari, fixed, or MIME).
637 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
641 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
642 memset(ret, 0, sizeof(struct CtdlMessage));
644 ret->cm_magic = CTDLMESSAGE_MAGIC;
645 ret->cm_anon_type = *mptr++; /* Anon type byte */
646 ret->cm_format_type = *mptr++; /* Format type byte */
649 * The rest is zero or more arbitrary fields. Load them in.
650 * We're done when we encounter either a zero-length field or
651 * have just processed the 'M' (message text) field.
654 field_length = strlen(mptr);
655 if (field_length == 0)
657 field_header = *mptr++;
658 ret->cm_fields[field_header] = mallok(field_length);
659 strcpy(ret->cm_fields[field_header], mptr);
661 while (*mptr++ != 0); /* advance to next field */
663 } while ((field_length > 0) && (field_header != 'M'));
667 /* Always make sure there's something in the msg text field */
668 if (ret->cm_fields['M'] == NULL)
669 ret->cm_fields['M'] = strdoop("<no text>\n");
671 /* Perform "before read" hooks (aborting if any return nonzero) */
672 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
673 CtdlFreeMessage(ret);
682 * Returns 1 if the supplied pointer points to a valid Citadel message.
683 * If the pointer is NULL or the magic number check fails, returns 0.
685 int is_valid_message(struct CtdlMessage *msg) {
688 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
689 lprintf(3, "is_valid_message() -- self-check failed\n");
697 * 'Destructor' for struct CtdlMessage
699 void CtdlFreeMessage(struct CtdlMessage *msg)
703 if (is_valid_message(msg) == 0) return;
705 for (i = 0; i < 256; ++i)
706 if (msg->cm_fields[i] != NULL) {
707 phree(msg->cm_fields[i]);
710 msg->cm_magic = 0; /* just in case */
716 * Callback function for mime parser that wants to display text
718 void fixed_output(char *name, char *filename, char *partnum, char *disp,
719 void *content, char *cbtype, size_t length)
726 if (!strcasecmp(cbtype, "multipart/alternative")) {
727 strcpy(ma->prefix, partnum);
728 strcat(ma->prefix, ".");
734 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
736 && (ma->did_print == 1) ) {
737 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
743 if ( (!strcasecmp(cbtype, "text/plain"))
744 || (strlen(cbtype)==0) ) {
749 if (ch==10) cprintf("\r\n");
750 else cprintf("%c", ch);
753 else if (!strcasecmp(cbtype, "text/html")) {
754 ptr = html_to_ascii(content, 80, 0);
759 if (ch==10) cprintf("\r\n");
760 else cprintf("%c", ch);
764 else if (strncasecmp(cbtype, "multipart/", 10)) {
765 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
766 partnum, filename, cbtype, length);
772 * Get a message off disk. (returns om_* values found in msgbase.h)
775 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
776 int mode, /* how would you like that message? */
777 int headers_only, /* eschew the message body? */
778 int do_proto, /* do Citadel protocol responses? */
779 int crlf /* Use CRLF newlines instead of LF? */
785 char display_name[256];
786 struct CtdlMessage *TheMessage;
788 char *nl; /* newline string */
790 /* buffers needed for RFC822 translation */
800 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
804 sprintf(mid, "%ld", msg_num);
805 nl = (crlf ? "\r\n" : "\n");
807 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
808 if (do_proto) cprintf("%d Not logged in.\n",
809 ERROR + NOT_LOGGED_IN);
810 return(om_not_logged_in);
813 /* FIXME ... small security issue
814 * We need to check to make sure the requested message is actually
815 * in the current room, and set msg_ok to 1 only if it is. This
816 * functionality is currently missing because I'm in a hurry to replace
817 * broken production code with nonbroken pre-beta code. :( -- ajc
820 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
822 return(om_no_such_msg);
827 * Fetch the message from disk
829 TheMessage = CtdlFetchMessage(msg_num);
830 if (TheMessage == NULL) {
831 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
833 return(om_no_such_msg);
836 /* Are we downloading a MIME component? */
837 if (mode == MT_DOWNLOAD) {
838 if (TheMessage->cm_format_type != FMT_RFC822) {
840 cprintf("%d This is not a MIME message.\n",
842 } else if (CC->download_fp != NULL) {
843 if (do_proto) cprintf(
844 "%d You already have a download open.\n",
847 /* Parse the message text component */
848 mptr = TheMessage->cm_fields['M'];
849 mime_parser(mptr, NULL, *mime_download);
850 /* If there's no file open by this time, the requested
851 * section wasn't found, so print an error
853 if (CC->download_fp == NULL) {
854 if (do_proto) cprintf(
855 "%d Section %s not found.\n",
856 ERROR + FILE_NOT_FOUND,
860 CtdlFreeMessage(TheMessage);
861 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
864 /* now for the user-mode message reading loops */
865 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
867 /* Tell the client which format type we're using. If this is a
868 * MIME message, *lie* about it and tell the user it's fixed-format.
870 if (mode == MT_CITADEL) {
871 if (TheMessage->cm_format_type == FMT_RFC822) {
872 if (do_proto) cprintf("type=1\n");
875 if (do_proto) cprintf("type=%d\n",
876 TheMessage->cm_format_type);
880 /* nhdr=yes means that we're only displaying headers, no body */
881 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
882 if (do_proto) cprintf("nhdr=yes\n");
885 /* begin header processing loop for Citadel message format */
887 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
889 strcpy(display_name, "<unknown>");
890 if (TheMessage->cm_fields['A']) {
891 strcpy(buf, TheMessage->cm_fields['A']);
892 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
893 if (TheMessage->cm_anon_type == MES_ANON)
894 strcpy(display_name, "****");
895 else if (TheMessage->cm_anon_type == MES_AN2)
896 strcpy(display_name, "anonymous");
898 strcpy(display_name, buf);
900 && ((TheMessage->cm_anon_type == MES_ANON)
901 || (TheMessage->cm_anon_type == MES_AN2))) {
902 sprintf(&display_name[strlen(display_name)],
907 strcpy(allkeys, FORDER);
908 for (i=0; i<strlen(allkeys); ++i) {
909 k = (int) allkeys[i];
911 if (TheMessage->cm_fields[k] != NULL) {
913 if (do_proto) cprintf("%s=%s\n",
918 if (do_proto) cprintf("%s=%s\n",
920 TheMessage->cm_fields[k]
929 /* begin header processing loop for RFC822 transfer format */
934 strcpy(snode, NODENAME);
935 strcpy(lnode, HUMANNODE);
936 if (mode == MT_RFC822) {
937 cprintf("X-UIDL: %ld%s", msg_num, nl);
938 for (i = 0; i < 256; ++i) {
939 if (TheMessage->cm_fields[i]) {
940 mptr = TheMessage->cm_fields[i];
947 cprintf("Path: %s%s", mptr, nl);
950 cprintf("Subject: %s%s", mptr, nl);
956 cprintf("X-Citadel-Room: %s%s",
961 cprintf("To: %s%s", mptr, nl);
963 generate_rfc822_datestamp(datestamp,
965 cprintf("Date: %s%s", datestamp, nl);
971 for (i=0; i<strlen(suser); ++i) {
972 suser[i] = tolower(suser[i]);
973 if (!isalnum(suser[i])) suser[i]='_';
976 if (mode == MT_RFC822) {
977 if (!strcasecmp(snode, NODENAME)) {
981 /* Construct a fun message id */
982 cprintf("Message-ID: <%s", mid);
983 if (strchr(mid, '@')==NULL) {
984 cprintf("@%s", snode);
988 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
990 if (strlen(fuser) > 0) {
991 cprintf("From: %s (%s)%s", fuser, luser, nl);
994 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
997 cprintf("Organization: %s%s", lnode, nl);
1000 /* end header processing loop ... at this point, we're in the text */
1002 mptr = TheMessage->cm_fields['M'];
1004 /* Tell the client about the MIME parts in this message */
1005 if (TheMessage->cm_format_type == FMT_RFC822) {
1006 if (mode == MT_CITADEL) {
1007 mime_parser(mptr, NULL, *list_this_part);
1009 else if (mode == MT_MIME) { /* list parts only */
1010 mime_parser(mptr, NULL, *list_this_part);
1011 if (do_proto) cprintf("000\n");
1012 CtdlFreeMessage(TheMessage);
1015 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1016 /* FIXME ... we have to put some code in here to avoid
1017 * printing duplicate header information when both
1018 * Citadel and RFC822 headers exist. Preference should
1019 * probably be given to the RFC822 headers.
1021 while (ch=*(mptr++), ch!=0) {
1023 else if (ch==10) cprintf("%s", nl);
1024 else cprintf("%c", ch);
1026 if (do_proto) cprintf("000\n");
1027 CtdlFreeMessage(TheMessage);
1033 if (do_proto) cprintf("000\n");
1034 CtdlFreeMessage(TheMessage);
1038 /* signify start of msg text */
1039 if (mode == MT_CITADEL)
1040 if (do_proto) cprintf("text\n");
1041 if (mode == MT_RFC822) {
1042 if (TheMessage->cm_fields['U'] == NULL) {
1043 cprintf("Subject: (no subject)%s", nl);
1048 /* If the format type on disk is 1 (fixed-format), then we want
1049 * everything to be output completely literally ... regardless of
1050 * what message transfer format is in use.
1052 if (TheMessage->cm_format_type == FMT_FIXED) {
1054 while (ch = *mptr++, ch > 0) {
1057 if ((ch == 10) || (strlen(buf) > 250)) {
1058 cprintf("%s%s", buf, nl);
1061 buf[strlen(buf) + 1] = 0;
1062 buf[strlen(buf)] = ch;
1065 if (strlen(buf) > 0)
1066 cprintf("%s%s", buf, nl);
1069 /* If the message on disk is format 0 (Citadel vari-format), we
1070 * output using the formatter at 80 columns. This is the final output
1071 * form if the transfer format is RFC822, but if the transfer format
1072 * is Citadel proprietary, it'll still work, because the indentation
1073 * for new paragraphs is correct and the client will reformat the
1074 * message to the reader's screen width.
1076 if (TheMessage->cm_format_type == FMT_CITADEL) {
1077 memfmout(80, mptr, 0, nl);
1080 /* If the message on disk is format 4 (MIME), we've gotta hand it
1081 * off to the MIME parser. The client has already been told that
1082 * this message is format 1 (fixed format), so the callback function
1083 * we use will display those parts as-is.
1085 if (TheMessage->cm_format_type == FMT_RFC822) {
1086 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1087 memset(ma, 0, sizeof(struct ma_info));
1088 mime_parser(mptr, NULL, *fixed_output);
1091 /* now we're done */
1092 if (do_proto) cprintf("000\n");
1093 CtdlFreeMessage(TheMessage);
1100 * display a message (mode 0 - Citadel proprietary)
1102 void cmd_msg0(char *cmdbuf)
1105 int headers_only = 0;
1107 msgid = extract_long(cmdbuf, 0);
1108 headers_only = extract_int(cmdbuf, 1);
1110 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1116 * display a message (mode 2 - RFC822)
1118 void cmd_msg2(char *cmdbuf)
1121 int headers_only = 0;
1123 msgid = extract_long(cmdbuf, 0);
1124 headers_only = extract_int(cmdbuf, 1);
1126 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1132 * display a message (mode 3 - IGnet raw format - internal programs only)
1134 void cmd_msg3(char *cmdbuf)
1137 struct CtdlMessage *msg;
1140 if (CC->internal_pgm == 0) {
1141 cprintf("%d This command is for internal programs only.\n",
1146 msgnum = extract_long(cmdbuf, 0);
1147 msg = CtdlFetchMessage(msgnum);
1149 cprintf("%d Message %ld not found.\n",
1154 serialize_message(&smr, msg);
1155 CtdlFreeMessage(msg);
1158 cprintf("%d Unable to serialize message\n",
1159 ERROR+INTERNAL_ERROR);
1163 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1164 client_write(smr.ser, smr.len);
1171 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1173 void cmd_msg4(char *cmdbuf)
1177 msgid = extract_long(cmdbuf, 0);
1178 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1182 * Open a component of a MIME message as a download file
1184 void cmd_opna(char *cmdbuf)
1188 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1190 msgid = extract_long(cmdbuf, 0);
1191 extract(desired_section, cmdbuf, 1);
1193 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1198 * Save a message pointer into a specified room
1199 * (Returns 0 for success, nonzero for failure)
1200 * roomname may be NULL to use the current room
1202 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1204 char hold_rm[ROOMNAMELEN];
1205 struct cdbdata *cdbfr;
1208 long highest_msg = 0L;
1209 struct CtdlMessage *msg = NULL;
1211 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1212 roomname, msgid, flags);
1214 strcpy(hold_rm, CC->quickroom.QRname);
1216 /* We may need to check to see if this message is real */
1217 if ( (flags & SM_VERIFY_GOODNESS)
1218 || (flags & SM_DO_REPL_CHECK)
1220 msg = CtdlFetchMessage(msgid);
1221 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1224 /* Perform replication checks if necessary */
1225 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1227 if (getroom(&CC->quickroom,
1228 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1230 lprintf(9, "No such room <%s>\n", roomname);
1231 if (msg != NULL) CtdlFreeMessage(msg);
1232 return(ERROR + ROOM_NOT_FOUND);
1235 if (ReplicationChecks(msg) != 0) {
1236 getroom(&CC->quickroom, hold_rm);
1237 if (msg != NULL) CtdlFreeMessage(msg);
1238 lprintf(9, "Did replication, and newer exists\n");
1243 /* Now the regular stuff */
1244 if (lgetroom(&CC->quickroom,
1245 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1247 lprintf(9, "No such room <%s>\n", roomname);
1248 if (msg != NULL) CtdlFreeMessage(msg);
1249 return(ERROR + ROOM_NOT_FOUND);
1252 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1253 if (cdbfr == NULL) {
1257 msglist = mallok(cdbfr->len);
1258 if (msglist == NULL)
1259 lprintf(3, "ERROR malloc msglist!\n");
1260 num_msgs = cdbfr->len / sizeof(long);
1261 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1266 /* Make sure the message doesn't already exist in this room. It
1267 * is absolutely taboo to have more than one reference to the same
1268 * message in a room.
1270 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1271 if (msglist[i] == msgid) {
1272 lputroom(&CC->quickroom); /* unlock the room */
1273 getroom(&CC->quickroom, hold_rm);
1274 if (msg != NULL) CtdlFreeMessage(msg);
1275 return(ERROR + ALREADY_EXISTS);
1279 /* Now add the new message */
1281 msglist = reallok(msglist,
1282 (num_msgs * sizeof(long)));
1284 if (msglist == NULL) {
1285 lprintf(3, "ERROR: can't realloc message list!\n");
1287 msglist[num_msgs - 1] = msgid;
1289 /* Sort the message list, so all the msgid's are in order */
1290 num_msgs = sort_msglist(msglist, num_msgs);
1292 /* Determine the highest message number */
1293 highest_msg = msglist[num_msgs - 1];
1295 /* Write it back to disk. */
1296 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1297 msglist, num_msgs * sizeof(long));
1299 /* Free up the memory we used. */
1302 /* Update the highest-message pointer and unlock the room. */
1303 CC->quickroom.QRhighest = highest_msg;
1304 lputroom(&CC->quickroom);
1305 getroom(&CC->quickroom, hold_rm);
1307 /* Bump the reference count for this message. */
1308 if ((flags & SM_DONT_BUMP_REF)==0) {
1309 AdjRefCount(msgid, +1);
1312 /* Return success. */
1313 if (msg != NULL) CtdlFreeMessage(msg);
1320 * Message base operation to send a message to the master file
1321 * (returns new message number)
1323 * This is the back end for CtdlSaveMsg() and should not be directly
1324 * called by server-side modules.
1327 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1328 FILE *save_a_copy) /* save a copy to disk? */
1335 /* Get a new message number */
1336 newmsgid = get_new_message_number();
1337 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1339 /* Generate an ID if we don't have one already */
1340 if (msg->cm_fields['I']==NULL) {
1341 msg->cm_fields['I'] = strdoop(msgidbuf);
1344 serialize_message(&smr, msg);
1347 cprintf("%d Unable to serialize message\n",
1348 ERROR+INTERNAL_ERROR);
1352 /* Write our little bundle of joy into the message base */
1353 begin_critical_section(S_MSGMAIN);
1354 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1355 smr.ser, smr.len) < 0) {
1356 lprintf(2, "Can't store message\n");
1361 end_critical_section(S_MSGMAIN);
1363 /* If the caller specified that a copy should be saved to a particular
1364 * file handle, do that now too.
1366 if (save_a_copy != NULL) {
1367 fwrite(smr.ser, smr.len, 1, save_a_copy);
1370 /* Free the memory we used for the serialized message */
1373 /* Return the *local* message ID to the caller
1374 * (even if we're storing an incoming network message)
1382 * Serialize a struct CtdlMessage into the format used on disk and network.
1384 * This function loads up a "struct ser_ret" (defined in server.h) which
1385 * contains the length of the serialized message and a pointer to the
1386 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1388 void serialize_message(struct ser_ret *ret, /* return values */
1389 struct CtdlMessage *msg) /* unserialized msg */
1393 static char *forder = FORDER;
1395 if (is_valid_message(msg) == 0) return; /* self check */
1398 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1399 ret->len = ret->len +
1400 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1402 lprintf(9, "calling malloc(%d)\n", ret->len);
1403 ret->ser = mallok(ret->len);
1404 if (ret->ser == NULL) {
1410 ret->ser[1] = msg->cm_anon_type;
1411 ret->ser[2] = msg->cm_format_type;
1414 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1415 ret->ser[wlen++] = (char)forder[i];
1416 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1417 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1419 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1428 * Back end for the ReplicationChecks() function
1430 void check_repl(long msgnum) {
1431 struct CtdlMessage *msg;
1432 time_t timestamp = (-1L);
1434 lprintf(9, "check_repl() found message %ld\n", msgnum);
1435 msg = CtdlFetchMessage(msgnum);
1436 if (msg == NULL) return;
1437 if (msg->cm_fields['T'] != NULL) {
1438 timestamp = atol(msg->cm_fields['T']);
1440 CtdlFreeMessage(msg);
1442 if (timestamp > msg_repl->highest) {
1443 msg_repl->highest = timestamp; /* newer! */
1444 lprintf(9, "newer!\n");
1447 lprintf(9, "older!\n");
1449 /* Existing isn't newer? Then delete the old one(s). */
1450 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1455 * Check to see if any messages already exist which carry the same Extended ID
1459 * -> With older timestamps: delete them and return 0. Message will be saved.
1460 * -> With newer timestamps: return 1. Message save will be aborted.
1462 int ReplicationChecks(struct CtdlMessage *msg) {
1463 struct CtdlMessage *template;
1466 lprintf(9, "ReplicationChecks() started\n");
1467 /* No extended id? Don't do anything. */
1468 if (msg->cm_fields['E'] == NULL) return 0;
1469 if (strlen(msg->cm_fields['E']) == 0) return 0;
1470 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1472 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1473 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1474 msg_repl->highest = atol(msg->cm_fields['T']);
1476 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1477 memset(template, 0, sizeof(struct CtdlMessage));
1478 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1480 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template, check_repl);
1482 /* If a newer message exists with the same Extended ID, abort
1485 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1489 CtdlFreeMessage(template);
1490 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1498 * Save a message to disk
1500 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1501 char *rec, /* Recipient (mail) */
1502 char *force, /* force a particular room? */
1503 int supplied_mailtype) /* local or remote type */
1506 char hold_rm[ROOMNAMELEN];
1507 char actual_rm[ROOMNAMELEN];
1508 char force_room[ROOMNAMELEN];
1509 char content_type[256]; /* We have to learn this */
1510 char recipient[256];
1513 struct usersupp userbuf;
1515 struct SuppMsgInfo smi;
1516 FILE *network_fp = NULL;
1517 static int seqnum = 1;
1518 struct CtdlMessage *imsg;
1522 lprintf(9, "CtdlSaveMsg() called\n");
1523 if (is_valid_message(msg) == 0) return(-1); /* self check */
1524 mailtype = supplied_mailtype;
1526 /* If this message has no timestamp, we take the liberty of
1527 * giving it one, right now.
1529 if (msg->cm_fields['T'] == NULL) {
1530 lprintf(9, "Generating timestamp\n");
1531 sprintf(aaa, "%ld", time(NULL));
1532 msg->cm_fields['T'] = strdoop(aaa);
1535 /* If this message has no path, we generate one.
1537 if (msg->cm_fields['P'] == NULL) {
1538 lprintf(9, "Generating path\n");
1539 if (msg->cm_fields['A'] != NULL) {
1540 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1541 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1542 if (isspace(msg->cm_fields['P'][a])) {
1543 msg->cm_fields['P'][a] = ' ';
1548 msg->cm_fields['P'] = strdoop("unknown");
1552 strcpy(force_room, force);
1554 /* Strip non-printable characters out of the recipient name */
1555 lprintf(9, "Checking recipient (if present)\n");
1556 strcpy(recipient, rec);
1557 for (a = 0; a < strlen(recipient); ++a)
1558 if (!isprint(recipient[a]))
1559 strcpy(&recipient[a], &recipient[a + 1]);
1561 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1562 for (a=0; a<strlen(recipient); ++a) {
1563 if (recipient[a] == '@') {
1564 if (CtdlHostAlias(&recipient[a+1])
1565 == hostalias_localhost) {
1567 lprintf(7, "Changed to <%s>\n", recipient);
1568 mailtype = MES_LOCAL;
1573 lprintf(9, "Recipient is <%s>\n", recipient);
1575 /* Learn about what's inside, because it's what's inside that counts */
1576 lprintf(9, "Learning what's inside\n");
1577 if (msg->cm_fields['M'] == NULL) {
1578 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1581 switch (msg->cm_format_type) {
1583 strcpy(content_type, "text/x-citadel-variformat");
1586 strcpy(content_type, "text/plain");
1589 strcpy(content_type, "text/plain");
1590 /* advance past header fields */
1591 mptr = msg->cm_fields['M'];
1594 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1595 safestrncpy(content_type, mptr,
1596 sizeof(content_type));
1597 strcpy(content_type, &content_type[14]);
1598 for (a = 0; a < strlen(content_type); ++a)
1599 if ((content_type[a] == ';')
1600 || (content_type[a] == ' ')
1601 || (content_type[a] == 13)
1602 || (content_type[a] == 10))
1603 content_type[a] = 0;
1610 /* Goto the correct room */
1611 lprintf(9, "Switching rooms\n");
1612 strcpy(hold_rm, CC->quickroom.QRname);
1613 strcpy(actual_rm, CC->quickroom.QRname);
1615 /* If the user is a twit, move to the twit room for posting */
1616 lprintf(9, "Handling twit stuff\n");
1618 if (CC->usersupp.axlevel == 2) {
1619 strcpy(hold_rm, actual_rm);
1620 strcpy(actual_rm, config.c_twitroom);
1624 /* ...or if this message is destined for Aide> then go there. */
1625 if (strlen(force_room) > 0) {
1626 strcpy(actual_rm, force_room);
1629 lprintf(9, "Possibly relocating\n");
1630 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1631 getroom(&CC->quickroom, actual_rm);
1635 * If this message has no O (room) field, generate one.
1637 if (msg->cm_fields['O'] == NULL) {
1638 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1641 /* Perform "before save" hooks (aborting if any return nonzero) */
1642 lprintf(9, "Performing before-save hooks\n");
1643 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1645 /* If this message has an Extended ID, perform replication checks */
1646 lprintf(9, "Performing replication checks\n");
1647 if (ReplicationChecks(msg) > 0) return(-1);
1649 /* Network mail - send a copy to the network program. */
1650 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1651 lprintf(9, "Sending network spool\n");
1652 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1653 (long) getpid(), CC->cs_pid, ++seqnum);
1654 lprintf(9, "Saving a copy to %s\n", aaa);
1655 network_fp = fopen(aaa, "ab+");
1656 if (network_fp == NULL)
1657 lprintf(2, "ERROR: %s\n", strerror(errno));
1660 /* Save it to disk */
1661 lprintf(9, "Saving to disk\n");
1662 newmsgid = send_message(msg, network_fp);
1663 if (network_fp != NULL) {
1665 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1668 if (newmsgid <= 0L) return(-1);
1670 /* Write a supplemental message info record. This doesn't have to
1671 * be a critical section because nobody else knows about this message
1674 lprintf(9, "Creating SuppMsgInfo record\n");
1675 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1676 smi.smi_msgnum = newmsgid;
1677 smi.smi_refcount = 0;
1678 safestrncpy(smi.smi_content_type, content_type, 64);
1679 PutSuppMsgInfo(&smi);
1681 /* Now figure out where to store the pointers */
1682 lprintf(9, "Storing pointers\n");
1684 /* If this is being done by the networker delivering a private
1685 * message, we want to BYPASS saving the sender's copy (because there
1686 * is no local sender; it would otherwise go to the Trashcan).
1688 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1689 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1690 lprintf(3, "ERROR saving message pointer!\n");
1691 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1695 /* For internet mail, drop a copy in the outbound queue room */
1696 if (mailtype == MES_INTERNET) {
1697 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1700 /* Bump this user's messages posted counter. */
1701 lprintf(9, "Updating user\n");
1702 lgetuser(&CC->usersupp, CC->curr_user);
1703 CC->usersupp.posted = CC->usersupp.posted + 1;
1704 lputuser(&CC->usersupp);
1706 /* If this is private, local mail, make a copy in the
1707 * recipient's mailbox and bump the reference count.
1709 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1710 if (getuser(&userbuf, recipient) == 0) {
1711 lprintf(9, "Delivering private mail\n");
1712 MailboxName(actual_rm, &userbuf, MAILROOM);
1713 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1716 lprintf(9, "No user <%s>, saving in %s> instead\n",
1717 recipient, AIDEROOM);
1718 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1722 /* Perform "after save" hooks */
1723 lprintf(9, "Performing after-save hooks\n");
1724 PerformMessageHooks(msg, EVT_AFTERSAVE);
1727 lprintf(9, "Returning to original room\n");
1728 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1729 getroom(&CC->quickroom, hold_rm);
1731 /* For internet mail, generate delivery instructions
1732 * (Yes, this is recursive! Deal with it!)
1734 if (mailtype == MES_INTERNET) {
1735 lprintf(9, "Generating delivery instructions\n");
1736 instr = mallok(2048);
1738 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1741 SPOOLMIME, newmsgid, time(NULL),
1742 msg->cm_fields['A'], msg->cm_fields['N'],
1745 imsg = mallok(sizeof(struct CtdlMessage));
1746 memset(imsg, 0, sizeof(struct CtdlMessage));
1747 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1748 imsg->cm_anon_type = MES_NORMAL;
1749 imsg->cm_format_type = FMT_RFC822;
1750 imsg->cm_fields['A'] = strdoop("Citadel");
1751 imsg->cm_fields['M'] = instr;
1752 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1753 CtdlFreeMessage(imsg);
1762 * Convenience function for generating small administrative messages.
1764 void quickie_message(char *from, char *to, char *room, char *text)
1766 struct CtdlMessage *msg;
1768 msg = mallok(sizeof(struct CtdlMessage));
1769 memset(msg, 0, sizeof(struct CtdlMessage));
1770 msg->cm_magic = CTDLMESSAGE_MAGIC;
1771 msg->cm_anon_type = MES_NORMAL;
1772 msg->cm_format_type = 0;
1773 msg->cm_fields['A'] = strdoop(from);
1774 msg->cm_fields['O'] = strdoop(room);
1775 msg->cm_fields['N'] = strdoop(NODENAME);
1777 msg->cm_fields['R'] = strdoop(to);
1778 msg->cm_fields['M'] = strdoop(text);
1780 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1781 CtdlFreeMessage(msg);
1782 syslog(LOG_NOTICE, text);
1788 * Back end function used by make_message() and similar functions
1790 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1791 size_t maxlen, /* maximum message length */
1792 char *exist /* if non-null, append to it;
1793 exist is ALWAYS freed */
1796 size_t message_len = 0;
1797 size_t buffer_len = 0;
1801 if (exist == NULL) {
1805 m = reallok(exist, strlen(exist) + 4096);
1806 if (m == NULL) phree(exist);
1809 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1816 /* read in the lines of message text one by one */
1818 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1820 /* augment the buffer if we have to */
1821 if ((message_len + strlen(buf) + 2) > buffer_len) {
1822 lprintf(9, "realloking\n");
1823 ptr = reallok(m, (buffer_len * 2) );
1824 if (ptr == NULL) { /* flush if can't allocate */
1825 while ( (client_gets(buf)>0) &&
1826 strcmp(buf, terminator)) ;;
1829 buffer_len = (buffer_len * 2);
1832 lprintf(9, "buffer_len is %d\n", buffer_len);
1836 if (append == NULL) append = m;
1837 while (strlen(append) > 0) ++append;
1838 strcpy(append, buf);
1839 strcat(append, "\n");
1840 message_len = message_len + strlen(buf) + 1;
1842 /* if we've hit the max msg length, flush the rest */
1843 if (message_len >= maxlen) {
1844 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1855 * Build a binary message to be saved on disk.
1858 struct CtdlMessage *make_message(
1859 struct usersupp *author, /* author's usersupp structure */
1860 char *recipient, /* NULL if it's not mail */
1861 char *room, /* room where it's going */
1862 int type, /* see MES_ types in header file */
1863 int net_type, /* see MES_ types in header file */
1864 int format_type, /* local or remote (see citadel.h) */
1865 char *fake_name) /* who we're masquerading as */
1871 struct CtdlMessage *msg;
1873 msg = mallok(sizeof(struct CtdlMessage));
1874 memset(msg, 0, sizeof(struct CtdlMessage));
1875 msg->cm_magic = CTDLMESSAGE_MAGIC;
1876 msg->cm_anon_type = type;
1877 msg->cm_format_type = format_type;
1879 /* Don't confuse the poor folks if it's not routed mail. */
1880 strcpy(dest_node, "");
1882 /* If net_type is MES_BINARY, split out the destination node. */
1883 if (net_type == MES_BINARY) {
1884 strcpy(dest_node, NODENAME);
1885 for (a = 0; a < strlen(recipient); ++a) {
1886 if (recipient[a] == '@') {
1888 strcpy(dest_node, &recipient[a + 1]);
1893 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1894 if (net_type == MES_INTERNET) {
1895 strcpy(dest_node, "internet");
1898 while (isspace(recipient[strlen(recipient) - 1]))
1899 recipient[strlen(recipient) - 1] = 0;
1901 sprintf(buf, "cit%ld", author->usernum); /* Path */
1902 msg->cm_fields['P'] = strdoop(buf);
1904 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1905 msg->cm_fields['T'] = strdoop(buf);
1907 if (fake_name[0]) /* author */
1908 msg->cm_fields['A'] = strdoop(fake_name);
1910 msg->cm_fields['A'] = strdoop(author->fullname);
1912 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1913 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1915 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1917 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1918 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1920 if (recipient[0] != 0)
1921 msg->cm_fields['R'] = strdoop(recipient);
1922 if (dest_node[0] != 0)
1923 msg->cm_fields['D'] = strdoop(dest_node);
1926 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1927 config.c_maxmsglen, NULL);
1938 * message entry - mode 0 (normal)
1940 void cmd_ent0(char *entargs)
1943 char recipient[256];
1945 int format_type = 0;
1946 char newusername[256];
1947 struct CtdlMessage *msg;
1951 struct usersupp tempUS;
1954 post = extract_int(entargs, 0);
1955 extract(recipient, entargs, 1);
1956 anon_flag = extract_int(entargs, 2);
1957 format_type = extract_int(entargs, 3);
1959 /* first check to make sure the request is valid. */
1961 if (!(CC->logged_in)) {
1962 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1965 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1966 cprintf("%d Need to be validated to enter ",
1967 ERROR + HIGHER_ACCESS_REQUIRED);
1968 cprintf("(except in %s> to sysop)\n", MAILROOM);
1971 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1972 cprintf("%d Need net privileges to enter here.\n",
1973 ERROR + HIGHER_ACCESS_REQUIRED);
1976 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1977 cprintf("%d Sorry, this is a read-only room.\n",
1978 ERROR + HIGHER_ACCESS_REQUIRED);
1985 if (CC->usersupp.axlevel < 6) {
1986 cprintf("%d You don't have permission to masquerade.\n",
1987 ERROR + HIGHER_ACCESS_REQUIRED);
1990 extract(newusername, entargs, 4);
1991 memset(CC->fake_postname, 0, 32);
1992 strcpy(CC->fake_postname, newusername);
1993 cprintf("%d Ok\n", OK);
1996 CC->cs_flags |= CS_POSTING;
1999 if (CC->quickroom.QRflags & QR_MAILBOX) {
2000 if (CC->usersupp.axlevel >= 2) {
2001 strcpy(buf, recipient);
2003 strcpy(buf, "sysop");
2004 e = alias(buf); /* alias and mail type */
2005 if ((buf[0] == 0) || (e == MES_ERROR)) {
2006 cprintf("%d Unknown address - cannot send message.\n",
2007 ERROR + NO_SUCH_USER);
2010 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2011 cprintf("%d Net privileges required for network mail.\n",
2012 ERROR + HIGHER_ACCESS_REQUIRED);
2015 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2016 && ((CC->usersupp.flags & US_INTERNET) == 0)
2017 && (!CC->internal_pgm)) {
2018 cprintf("%d You don't have access to Internet mail.\n",
2019 ERROR + HIGHER_ACCESS_REQUIRED);
2022 if (!strcasecmp(buf, "sysop")) {
2027 goto SKFALL; /* don't search local file */
2028 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2029 cprintf("%d Can't send mail to yourself!\n",
2030 ERROR + NO_SUCH_USER);
2033 /* Check to make sure the user exists; also get the correct
2034 * upper/lower casing of the name.
2036 a = getuser(&tempUS, buf);
2038 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2041 strcpy(buf, tempUS.fullname);
2044 SKFALL: b = MES_NORMAL;
2045 if (CC->quickroom.QRflags & QR_ANONONLY)
2047 if (CC->quickroom.QRflags & QR_ANONOPT) {
2051 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2054 /* If we're only checking the validity of the request, return
2055 * success without creating the message.
2058 cprintf("%d %s\n", OK, buf);
2062 cprintf("%d send message\n", SEND_LISTING);
2064 /* Read in the message from the client. */
2065 if (CC->fake_postname[0])
2066 msg = make_message(&CC->usersupp, buf,
2067 CC->quickroom.QRname, b, e, format_type,
2069 else if (CC->fake_username[0])
2070 msg = make_message(&CC->usersupp, buf,
2071 CC->quickroom.QRname, b, e, format_type,
2074 msg = make_message(&CC->usersupp, buf,
2075 CC->quickroom.QRname, b, e, format_type, "");
2078 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2079 CtdlFreeMessage(msg);
2080 CC->fake_postname[0] = '\0';
2087 * message entry - mode 3 (raw)
2089 void cmd_ent3(char *entargs)
2095 unsigned char ch, which_field;
2096 struct usersupp tempUS;
2098 struct CtdlMessage *msg;
2101 if (CC->internal_pgm == 0) {
2102 cprintf("%d This command is for internal programs only.\n",
2107 /* See if there's a recipient, but make sure it's a real one */
2108 extract(recp, entargs, 1);
2109 for (a = 0; a < strlen(recp); ++a)
2110 if (!isprint(recp[a]))
2111 strcpy(&recp[a], &recp[a + 1]);
2112 while (isspace(recp[0]))
2113 strcpy(recp, &recp[1]);
2114 while (isspace(recp[strlen(recp) - 1]))
2115 recp[strlen(recp) - 1] = 0;
2117 /* If we're in Mail, check the recipient */
2118 if (strlen(recp) > 0) {
2119 e = alias(recp); /* alias and mail type */
2120 if ((recp[0] == 0) || (e == MES_ERROR)) {
2121 cprintf("%d Unknown address - cannot send message.\n",
2122 ERROR + NO_SUCH_USER);
2125 if (e == MES_LOCAL) {
2126 a = getuser(&tempUS, recp);
2128 cprintf("%d No such user.\n",
2129 ERROR + NO_SUCH_USER);
2135 /* At this point, message has been approved. */
2136 if (extract_int(entargs, 0) == 0) {
2137 cprintf("%d OK to send\n", OK);
2141 msglen = extract_long(entargs, 2);
2142 msg = mallok(sizeof(struct CtdlMessage));
2144 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2148 memset(msg, 0, sizeof(struct CtdlMessage));
2149 tempbuf = mallok(msglen);
2150 if (tempbuf == NULL) {
2151 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2156 cprintf("%d %ld\n", SEND_BINARY, msglen);
2158 client_read(&ch, 1); /* 0xFF magic number */
2159 msg->cm_magic = CTDLMESSAGE_MAGIC;
2160 client_read(&ch, 1); /* anon type */
2161 msg->cm_anon_type = ch;
2162 client_read(&ch, 1); /* format type */
2163 msg->cm_format_type = ch;
2164 msglen = msglen - 3;
2166 while (msglen > 0) {
2167 client_read(&which_field, 1);
2168 if (!isalpha(which_field)) valid_msg = 0;
2172 client_read(&ch, 1);
2174 a = strlen(tempbuf);
2177 } while ( (ch != 0) && (msglen > 0) );
2179 msg->cm_fields[which_field] = strdoop(tempbuf);
2182 msg->cm_flags = CM_SKIP_HOOKS;
2183 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2184 CtdlFreeMessage(msg);
2190 * API function to delete messages which match a set of criteria
2191 * (returns the actual number of messages deleted)
2193 int CtdlDeleteMessages(char *room_name, /* which room */
2194 long dmsgnum, /* or "0" for any */
2195 char *content_type /* or "" for any */
2199 struct quickroom qrbuf;
2200 struct cdbdata *cdbfr;
2201 long *msglist = NULL;
2204 int num_deleted = 0;
2206 struct SuppMsgInfo smi;
2208 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2209 room_name, dmsgnum, content_type);
2211 /* get room record, obtaining a lock... */
2212 if (lgetroom(&qrbuf, room_name) != 0) {
2213 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2215 return (0); /* room not found */
2217 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2219 if (cdbfr != NULL) {
2220 msglist = mallok(cdbfr->len);
2221 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2222 num_msgs = cdbfr->len / sizeof(long);
2226 for (i = 0; i < num_msgs; ++i) {
2229 /* Set/clear a bit for each criterion */
2231 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2232 delete_this |= 0x01;
2234 if (strlen(content_type) == 0) {
2235 delete_this |= 0x02;
2237 GetSuppMsgInfo(&smi, msglist[i]);
2238 if (!strcasecmp(smi.smi_content_type,
2240 delete_this |= 0x02;
2244 /* Delete message only if all bits are set */
2245 if (delete_this == 0x03) {
2246 AdjRefCount(msglist[i], -1);
2252 num_msgs = sort_msglist(msglist, num_msgs);
2253 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2254 msglist, (num_msgs * sizeof(long)));
2256 qrbuf.QRhighest = msglist[num_msgs - 1];
2260 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2261 return (num_deleted);
2267 * Delete message from current room
2269 void cmd_dele(char *delstr)
2274 getuser(&CC->usersupp, CC->curr_user);
2275 if ((CC->usersupp.axlevel < 6)
2276 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2277 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2278 && (!(CC->internal_pgm))) {
2279 cprintf("%d Higher access required.\n",
2280 ERROR + HIGHER_ACCESS_REQUIRED);
2283 delnum = extract_long(delstr, 0);
2285 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2288 cprintf("%d %d message%s deleted.\n", OK,
2289 num_deleted, ((num_deleted != 1) ? "s" : ""));
2291 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2297 * move or copy a message to another room
2299 void cmd_move(char *args)
2303 struct quickroom qtemp;
2307 num = extract_long(args, 0);
2308 extract(targ, args, 1);
2309 targ[ROOMNAMELEN - 1] = 0;
2310 is_copy = extract_int(args, 2);
2312 getuser(&CC->usersupp, CC->curr_user);
2313 if ((CC->usersupp.axlevel < 6)
2314 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2315 cprintf("%d Higher access required.\n",
2316 ERROR + HIGHER_ACCESS_REQUIRED);
2320 if (getroom(&qtemp, targ) != 0) {
2321 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2325 err = CtdlSaveMsgPointerInRoom(targ, num,
2326 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2328 cprintf("%d Cannot store message in %s: error %d\n",
2333 /* Now delete the message from the source room,
2334 * if this is a 'move' rather than a 'copy' operation.
2336 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2338 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2344 * GetSuppMsgInfo() - Get the supplementary record for a message
2346 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2349 struct cdbdata *cdbsmi;
2352 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2353 smibuf->smi_msgnum = msgnum;
2354 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2356 /* Use the negative of the message number for its supp record index */
2357 TheIndex = (0L - msgnum);
2359 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2360 if (cdbsmi == NULL) {
2361 return; /* record not found; go with defaults */
2363 memcpy(smibuf, cdbsmi->ptr,
2364 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2365 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2372 * PutSuppMsgInfo() - (re)write supplementary record for a message
2374 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2378 /* Use the negative of the message number for its supp record index */
2379 TheIndex = (0L - smibuf->smi_msgnum);
2381 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2382 smibuf->smi_msgnum, smibuf->smi_refcount);
2384 cdb_store(CDB_MSGMAIN,
2385 &TheIndex, sizeof(long),
2386 smibuf, sizeof(struct SuppMsgInfo));
2391 * AdjRefCount - change the reference count for a message;
2392 * delete the message if it reaches zero
2394 void AdjRefCount(long msgnum, int incr)
2397 struct SuppMsgInfo smi;
2400 /* This is a *tight* critical section; please keep it that way, as
2401 * it may get called while nested in other critical sections.
2402 * Complicating this any further will surely cause deadlock!
2404 begin_critical_section(S_SUPPMSGMAIN);
2405 GetSuppMsgInfo(&smi, msgnum);
2406 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2407 msgnum, smi.smi_refcount);
2408 smi.smi_refcount += incr;
2409 PutSuppMsgInfo(&smi);
2410 end_critical_section(S_SUPPMSGMAIN);
2411 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2412 msgnum, smi.smi_refcount);
2414 /* If the reference count is now zero, delete the message
2415 * (and its supplementary record as well).
2417 if (smi.smi_refcount == 0) {
2418 lprintf(9, "Deleting message <%ld>\n", msgnum);
2420 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2421 delnum = (0L - msgnum);
2422 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2427 * Write a generic object to this room
2429 * Note: this could be much more efficient. Right now we use two temporary
2430 * files, and still pull the message into memory as with all others.
2432 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2433 char *content_type, /* MIME type of this object */
2434 char *tempfilename, /* Where to fetch it from */
2435 struct usersupp *is_mailbox, /* Mailbox room? */
2436 int is_binary, /* Is encoding necessary? */
2437 int is_unique, /* Del others of this type? */
2438 unsigned int flags /* Internal save flags */
2443 char filename[PATH_MAX];
2446 struct quickroom qrbuf;
2447 char roomname[ROOMNAMELEN];
2448 struct CtdlMessage *msg;
2451 if (is_mailbox != NULL)
2452 MailboxName(roomname, is_mailbox, req_room);
2454 safestrncpy(roomname, req_room, sizeof(roomname));
2455 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2457 strcpy(filename, tmpnam(NULL));
2458 fp = fopen(filename, "w");
2462 tempfp = fopen(tempfilename, "r");
2463 if (tempfp == NULL) {
2469 fprintf(fp, "Content-type: %s\n", content_type);
2470 lprintf(9, "Content-type: %s\n", content_type);
2472 if (is_binary == 0) {
2473 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2474 while (ch = getc(tempfp), ch > 0)
2480 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2483 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2484 tempfilename, filename);
2488 lprintf(9, "Allocating\n");
2489 msg = mallok(sizeof(struct CtdlMessage));
2490 memset(msg, 0, sizeof(struct CtdlMessage));
2491 msg->cm_magic = CTDLMESSAGE_MAGIC;
2492 msg->cm_anon_type = MES_NORMAL;
2493 msg->cm_format_type = 4;
2494 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2495 msg->cm_fields['O'] = strdoop(req_room);
2496 msg->cm_fields['N'] = strdoop(config.c_nodename);
2497 msg->cm_fields['H'] = strdoop(config.c_humannode);
2498 msg->cm_flags = flags;
2500 lprintf(9, "Loading\n");
2501 fp = fopen(filename, "rb");
2502 fseek(fp, 0L, SEEK_END);
2505 msg->cm_fields['M'] = mallok(len);
2506 fread(msg->cm_fields['M'], len, 1, fp);
2510 /* Create the requested room if we have to. */
2511 if (getroom(&qrbuf, roomname) != 0) {
2512 create_room(roomname,
2513 ( (is_mailbox != NULL) ? 4 : 3 ),
2516 /* If the caller specified this object as unique, delete all
2517 * other objects of this type that are currently in the room.
2520 lprintf(9, "Deleted %d other msgs of this type\n",
2521 CtdlDeleteMessages(roomname, 0L, content_type));
2523 /* Now write the data */
2524 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2525 CtdlFreeMessage(msg);
2533 void CtdlGetSysConfigBackend(long msgnum) {
2534 config_msgnum = msgnum;
2538 char *CtdlGetSysConfig(char *sysconfname) {
2539 char hold_rm[ROOMNAMELEN];
2542 struct CtdlMessage *msg;
2545 strcpy(hold_rm, CC->quickroom.QRname);
2546 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2547 getroom(&CC->quickroom, hold_rm);
2552 /* We want the last (and probably only) config in this room */
2553 begin_critical_section(S_CONFIG);
2554 config_msgnum = (-1L);
2555 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2556 CtdlGetSysConfigBackend);
2557 msgnum = config_msgnum;
2558 end_critical_section(S_CONFIG);
2564 msg = CtdlFetchMessage(msgnum);
2566 conf = strdoop(msg->cm_fields['M']);
2567 CtdlFreeMessage(msg);
2574 getroom(&CC->quickroom, hold_rm);
2576 lprintf(9, "eggstracting...\n");
2577 if (conf != NULL) do {
2578 extract_token(buf, conf, 0, '\n');
2579 lprintf(9, "eggstracted <%s>\n", buf);
2580 strcpy(conf, &conf[strlen(buf)+1]);
2581 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2586 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2587 char temp[PATH_MAX];
2590 strcpy(temp, tmpnam(NULL));
2592 fp = fopen(temp, "w");
2593 if (fp == NULL) return;
2594 fprintf(fp, "%s", sysconfdata);
2597 /* this handy API function does all the work for us */
2598 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);