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
270 void 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;
283 struct SuppMsgInfo smi;
284 struct CtdlMessage *msg;
286 /* Learn about the user and room in question */
288 getuser(&CC->usersupp, CC->curr_user);
289 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
291 /* Load the message list */
292 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
294 msglist = mallok(cdbfr->len);
295 memcpy(msglist, cdbfr->ptr, cdbfr->len);
296 num_msgs = cdbfr->len / sizeof(long);
299 return; /* No messages at all? No further action. */
303 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
304 GetSuppMsgInfo(&smi, msglist[a]);
306 /* Filter out messages that are moderated below the level
307 * currently being viewed at.
309 if (smi.smi_mod < moderation_level) {
313 /* If the caller is looking for a specific MIME type, filter
314 * out all messages which are not of the type requested.
316 if (content_type != NULL) if (strlen(content_type) > 0) {
317 if (strcasecmp(smi.smi_content_type, content_type)) {
323 num_msgs = sort_msglist(msglist, num_msgs);
325 /* If a template was supplied, filter out the messages which
326 * don't match. (This could induce some delays!)
329 if (compare != NULL) {
330 for (a = 0; a < num_msgs; ++a) {
331 msg = CtdlFetchMessage(msglist[a]);
333 if (CtdlMsgCmp(msg, compare)) {
336 CtdlFreeMessage(msg);
344 * Now iterate through the message list, according to the
345 * criteria supplied by the caller.
348 for (a = 0; a < num_msgs; ++a) {
349 thismsg = msglist[a];
354 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
355 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
356 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
357 && (CC->usersupp.flags & US_LASTOLD))
358 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
359 || ((mode == MSGS_FIRST) && (a < ref))
360 || ((mode == MSGS_GT) && (thismsg > ref))
366 phree(msglist); /* Clean up */
372 * cmd_msgs() - get list of message #'s in this room
373 * implements the MSGS server command using CtdlForEachMessage()
375 void cmd_msgs(char *cmdbuf)
384 int with_template = 0;
385 struct CtdlMessage *template = NULL;
387 extract(which, cmdbuf, 0);
388 cm_ref = extract_int(cmdbuf, 1);
389 with_template = extract_int(cmdbuf, 2);
393 if (!strncasecmp(which, "OLD", 3))
395 else if (!strncasecmp(which, "NEW", 3))
397 else if (!strncasecmp(which, "FIRST", 5))
399 else if (!strncasecmp(which, "LAST", 4))
401 else if (!strncasecmp(which, "GT", 2))
404 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
405 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
410 cprintf("%d Send template then receive message list\n",
412 template = (struct CtdlMessage *)
413 mallok(sizeof(struct CtdlMessage));
414 memset(template, 0, sizeof(struct CtdlMessage));
415 while(client_gets(buf), strcmp(buf,"000")) {
416 extract(tfield, buf, 0);
417 extract(tvalue, buf, 1);
418 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
419 if (!strcasecmp(tfield, msgkeys[i])) {
420 template->cm_fields[i] =
427 cprintf("%d Message list...\n", LISTING_FOLLOWS);
430 CtdlForEachMessage(mode, cm_ref,
431 CC->usersupp.moderation_filter,
432 NULL, template, simple_listing);
433 if (template != NULL) CtdlFreeMessage(template);
441 * help_subst() - support routine for help file viewer
443 void help_subst(char *strbuf, char *source, char *dest)
448 while (p = pattern2(strbuf, source), (p >= 0)) {
449 strcpy(workbuf, &strbuf[p + strlen(source)]);
450 strcpy(&strbuf[p], dest);
451 strcat(strbuf, workbuf);
456 void do_help_subst(char *buffer)
460 help_subst(buffer, "^nodename", config.c_nodename);
461 help_subst(buffer, "^humannode", config.c_humannode);
462 help_subst(buffer, "^fqdn", config.c_fqdn);
463 help_subst(buffer, "^username", CC->usersupp.fullname);
464 sprintf(buf2, "%ld", CC->usersupp.usernum);
465 help_subst(buffer, "^usernum", buf2);
466 help_subst(buffer, "^sysadm", config.c_sysadm);
467 help_subst(buffer, "^variantname", CITADEL);
468 sprintf(buf2, "%d", config.c_maxsessions);
469 help_subst(buffer, "^maxsessions", buf2);
475 * memfmout() - Citadel text formatter and paginator.
476 * Although the original purpose of this routine was to format
477 * text to the reader's screen width, all we're really using it
478 * for here is to format text out to 80 columns before sending it
479 * to the client. The client software may reformat it again.
482 int width, /* screen width to use */
483 char *mptr, /* where are we going to get our text from? */
484 char subst, /* nonzero if we should do substitutions */
485 char *nl) /* string to terminate lines with */
497 c = 1; /* c is the current pos */
500 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
502 buffer[strlen(buffer) + 1] = 0;
503 buffer[strlen(buffer)] = ch;
506 if (buffer[0] == '^')
507 do_help_subst(buffer);
509 buffer[strlen(buffer) + 1] = 0;
511 strcpy(buffer, &buffer[1]);
521 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
523 if (((old == 13) || (old == 10)) && (isspace(real))) {
531 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
532 cprintf("%s%s", nl, aaa);
541 if ((strlen(aaa) + c) > (width - 5)) {
551 if ((ch == 13) || (ch == 10)) {
552 cprintf("%s%s", aaa, nl);
559 FMTEND: cprintf("%s%s", aaa, nl);
565 * Callback function for mime parser that simply lists the part
567 void list_this_part(char *name, char *filename, char *partnum, char *disp,
568 void *content, char *cbtype, size_t length)
571 cprintf("part=%s|%s|%s|%s|%s|%d\n",
572 name, filename, partnum, disp, cbtype, length);
577 * Callback function for mime parser that opens a section for downloading
579 void mime_download(char *name, char *filename, char *partnum, char *disp,
580 void *content, char *cbtype, size_t length)
583 /* Silently go away if there's already a download open... */
584 if (CC->download_fp != NULL)
587 /* ...or if this is not the desired section */
588 if (strcasecmp(desired_section, partnum))
591 CC->download_fp = tmpfile();
592 if (CC->download_fp == NULL)
595 fwrite(content, length, 1, CC->download_fp);
596 fflush(CC->download_fp);
597 rewind(CC->download_fp);
599 OpenCmdResult(filename, cbtype);
605 * Load a message from disk into memory.
606 * This is used by CtdlOutputMsg() and other fetch functions.
608 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
609 * using the CtdlMessageFree() function.
611 struct CtdlMessage *CtdlFetchMessage(long msgnum)
613 struct cdbdata *dmsgtext;
614 struct CtdlMessage *ret = NULL;
617 CIT_UBYTE field_header;
620 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
621 if (dmsgtext == NULL) {
624 mptr = dmsgtext->ptr;
626 /* Parse the three bytes that begin EVERY message on disk.
627 * The first is always 0xFF, the on-disk magic number.
628 * The second is the anonymous/public type byte.
629 * The third is the format type byte (vari, fixed, or MIME).
633 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
637 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
638 memset(ret, 0, sizeof(struct CtdlMessage));
640 ret->cm_magic = CTDLMESSAGE_MAGIC;
641 ret->cm_anon_type = *mptr++; /* Anon type byte */
642 ret->cm_format_type = *mptr++; /* Format type byte */
645 * The rest is zero or more arbitrary fields. Load them in.
646 * We're done when we encounter either a zero-length field or
647 * have just processed the 'M' (message text) field.
650 field_length = strlen(mptr);
651 if (field_length == 0)
653 field_header = *mptr++;
654 ret->cm_fields[field_header] = mallok(field_length);
655 strcpy(ret->cm_fields[field_header], mptr);
657 while (*mptr++ != 0); /* advance to next field */
659 } while ((field_length > 0) && (field_header != 'M'));
663 /* Always make sure there's something in the msg text field */
664 if (ret->cm_fields['M'] == NULL)
665 ret->cm_fields['M'] = strdoop("<no text>\n");
667 /* Perform "before read" hooks (aborting if any return nonzero) */
668 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
669 CtdlFreeMessage(ret);
678 * Returns 1 if the supplied pointer points to a valid Citadel message.
679 * If the pointer is NULL or the magic number check fails, returns 0.
681 int is_valid_message(struct CtdlMessage *msg) {
684 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
685 lprintf(3, "is_valid_message() -- self-check failed\n");
693 * 'Destructor' for struct CtdlMessage
695 void CtdlFreeMessage(struct CtdlMessage *msg)
699 if (is_valid_message(msg) == 0) return;
701 for (i = 0; i < 256; ++i)
702 if (msg->cm_fields[i] != NULL) {
703 phree(msg->cm_fields[i]);
706 msg->cm_magic = 0; /* just in case */
712 * Callback function for mime parser that wants to display text
714 void fixed_output(char *name, char *filename, char *partnum, char *disp,
715 void *content, char *cbtype, size_t length)
722 if (!strcasecmp(cbtype, "multipart/alternative")) {
723 strcpy(ma->prefix, partnum);
724 strcat(ma->prefix, ".");
730 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
732 && (ma->did_print == 1) ) {
733 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
739 if ( (!strcasecmp(cbtype, "text/plain"))
740 || (strlen(cbtype)==0) ) {
745 if (ch==10) cprintf("\r\n");
746 else cprintf("%c", ch);
749 else if (!strcasecmp(cbtype, "text/html")) {
750 ptr = html_to_ascii(content, 80, 0);
755 if (ch==10) cprintf("\r\n");
756 else cprintf("%c", ch);
760 else if (strncasecmp(cbtype, "multipart/", 10)) {
761 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
762 partnum, filename, cbtype, length);
768 * Get a message off disk. (returns om_* values found in msgbase.h)
771 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
772 int mode, /* how would you like that message? */
773 int headers_only, /* eschew the message body? */
774 int do_proto, /* do Citadel protocol responses? */
775 int crlf /* Use CRLF newlines instead of LF? */
781 char display_name[256];
782 struct CtdlMessage *TheMessage;
784 char *nl; /* newline string */
786 /* buffers needed for RFC822 translation */
796 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
800 sprintf(mid, "%ld", msg_num);
801 nl = (crlf ? "\r\n" : "\n");
803 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
804 if (do_proto) cprintf("%d Not logged in.\n",
805 ERROR + NOT_LOGGED_IN);
806 return(om_not_logged_in);
809 /* FIXME ... small security issue
810 * We need to check to make sure the requested message is actually
811 * in the current room, and set msg_ok to 1 only if it is. This
812 * functionality is currently missing because I'm in a hurry to replace
813 * broken production code with nonbroken pre-beta code. :( -- ajc
816 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
818 return(om_no_such_msg);
823 * Fetch the message from disk
825 TheMessage = CtdlFetchMessage(msg_num);
826 if (TheMessage == NULL) {
827 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
829 return(om_no_such_msg);
832 /* Are we downloading a MIME component? */
833 if (mode == MT_DOWNLOAD) {
834 if (TheMessage->cm_format_type != FMT_RFC822) {
836 cprintf("%d This is not a MIME message.\n",
838 } else if (CC->download_fp != NULL) {
839 if (do_proto) cprintf(
840 "%d You already have a download open.\n",
843 /* Parse the message text component */
844 mptr = TheMessage->cm_fields['M'];
845 mime_parser(mptr, NULL, *mime_download);
846 /* If there's no file open by this time, the requested
847 * section wasn't found, so print an error
849 if (CC->download_fp == NULL) {
850 if (do_proto) cprintf(
851 "%d Section %s not found.\n",
852 ERROR + FILE_NOT_FOUND,
856 CtdlFreeMessage(TheMessage);
857 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
860 /* now for the user-mode message reading loops */
861 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
863 /* Tell the client which format type we're using. If this is a
864 * MIME message, *lie* about it and tell the user it's fixed-format.
866 if (mode == MT_CITADEL) {
867 if (TheMessage->cm_format_type == FMT_RFC822) {
868 if (do_proto) cprintf("type=1\n");
871 if (do_proto) cprintf("type=%d\n",
872 TheMessage->cm_format_type);
876 /* nhdr=yes means that we're only displaying headers, no body */
877 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
878 if (do_proto) cprintf("nhdr=yes\n");
881 /* begin header processing loop for Citadel message format */
883 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
885 strcpy(display_name, "<unknown>");
886 if (TheMessage->cm_fields['A']) {
887 strcpy(buf, TheMessage->cm_fields['A']);
888 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
889 if (TheMessage->cm_anon_type == MES_ANON)
890 strcpy(display_name, "****");
891 else if (TheMessage->cm_anon_type == MES_AN2)
892 strcpy(display_name, "anonymous");
894 strcpy(display_name, buf);
896 && ((TheMessage->cm_anon_type == MES_ANON)
897 || (TheMessage->cm_anon_type == MES_AN2))) {
898 sprintf(&display_name[strlen(display_name)],
903 strcpy(allkeys, FORDER);
904 for (i=0; i<strlen(allkeys); ++i) {
905 k = (int) allkeys[i];
907 if (TheMessage->cm_fields[k] != NULL) {
909 if (do_proto) cprintf("%s=%s\n",
914 if (do_proto) cprintf("%s=%s\n",
916 TheMessage->cm_fields[k]
925 /* begin header processing loop for RFC822 transfer format */
930 strcpy(snode, NODENAME);
931 strcpy(lnode, HUMANNODE);
932 if (mode == MT_RFC822) {
933 cprintf("X-UIDL: %ld%s", msg_num, nl);
934 for (i = 0; i < 256; ++i) {
935 if (TheMessage->cm_fields[i]) {
936 mptr = TheMessage->cm_fields[i];
943 cprintf("Path: %s%s", mptr, nl);
946 cprintf("Subject: %s%s", mptr, nl);
952 cprintf("X-Citadel-Room: %s%s",
957 cprintf("To: %s%s", mptr, nl);
959 generate_rfc822_datestamp(datestamp,
961 cprintf("Date: %s%s", datestamp, nl);
967 for (i=0; i<strlen(suser); ++i) {
968 suser[i] = tolower(suser[i]);
969 if (!isalnum(suser[i])) suser[i]='_';
972 if (mode == MT_RFC822) {
973 if (!strcasecmp(snode, NODENAME)) {
977 /* Construct a fun message id */
978 cprintf("Message-ID: <%s", mid);
979 if (strchr(mid, '@')==NULL) {
980 cprintf("@%s", snode);
984 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
986 if (strlen(fuser) > 0) {
987 cprintf("From: %s (%s)%s", fuser, luser, nl);
990 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
993 cprintf("Organization: %s%s", lnode, nl);
996 /* end header processing loop ... at this point, we're in the text */
998 mptr = TheMessage->cm_fields['M'];
1000 /* Tell the client about the MIME parts in this message */
1001 if (TheMessage->cm_format_type == FMT_RFC822) {
1002 if (mode == MT_CITADEL) {
1003 mime_parser(mptr, NULL, *list_this_part);
1005 else if (mode == MT_MIME) { /* list parts only */
1006 mime_parser(mptr, NULL, *list_this_part);
1007 if (do_proto) cprintf("000\n");
1008 CtdlFreeMessage(TheMessage);
1011 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1012 /* FIXME ... we have to put some code in here to avoid
1013 * printing duplicate header information when both
1014 * Citadel and RFC822 headers exist. Preference should
1015 * probably be given to the RFC822 headers.
1017 while (ch=*(mptr++), ch!=0) {
1019 else if (ch==10) cprintf("%s", nl);
1020 else cprintf("%c", ch);
1022 if (do_proto) cprintf("000\n");
1023 CtdlFreeMessage(TheMessage);
1029 if (do_proto) cprintf("000\n");
1030 CtdlFreeMessage(TheMessage);
1034 /* signify start of msg text */
1035 if (mode == MT_CITADEL)
1036 if (do_proto) cprintf("text\n");
1037 if (mode == MT_RFC822) {
1038 if (TheMessage->cm_fields['U'] == NULL) {
1039 cprintf("Subject: (no subject)%s", nl);
1044 /* If the format type on disk is 1 (fixed-format), then we want
1045 * everything to be output completely literally ... regardless of
1046 * what message transfer format is in use.
1048 if (TheMessage->cm_format_type == FMT_FIXED) {
1050 while (ch = *mptr++, ch > 0) {
1053 if ((ch == 10) || (strlen(buf) > 250)) {
1054 cprintf("%s%s", buf, nl);
1057 buf[strlen(buf) + 1] = 0;
1058 buf[strlen(buf)] = ch;
1061 if (strlen(buf) > 0)
1062 cprintf("%s%s", buf, nl);
1065 /* If the message on disk is format 0 (Citadel vari-format), we
1066 * output using the formatter at 80 columns. This is the final output
1067 * form if the transfer format is RFC822, but if the transfer format
1068 * is Citadel proprietary, it'll still work, because the indentation
1069 * for new paragraphs is correct and the client will reformat the
1070 * message to the reader's screen width.
1072 if (TheMessage->cm_format_type == FMT_CITADEL) {
1073 memfmout(80, mptr, 0, nl);
1076 /* If the message on disk is format 4 (MIME), we've gotta hand it
1077 * off to the MIME parser. The client has already been told that
1078 * this message is format 1 (fixed format), so the callback function
1079 * we use will display those parts as-is.
1081 if (TheMessage->cm_format_type == FMT_RFC822) {
1082 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1083 memset(ma, 0, sizeof(struct ma_info));
1084 mime_parser(mptr, NULL, *fixed_output);
1087 /* now we're done */
1088 if (do_proto) cprintf("000\n");
1089 CtdlFreeMessage(TheMessage);
1096 * display a message (mode 0 - Citadel proprietary)
1098 void cmd_msg0(char *cmdbuf)
1101 int headers_only = 0;
1103 msgid = extract_long(cmdbuf, 0);
1104 headers_only = extract_int(cmdbuf, 1);
1106 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1112 * display a message (mode 2 - RFC822)
1114 void cmd_msg2(char *cmdbuf)
1117 int headers_only = 0;
1119 msgid = extract_long(cmdbuf, 0);
1120 headers_only = extract_int(cmdbuf, 1);
1122 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1128 * display a message (mode 3 - IGnet raw format - internal programs only)
1130 void cmd_msg3(char *cmdbuf)
1133 struct CtdlMessage *msg;
1136 if (CC->internal_pgm == 0) {
1137 cprintf("%d This command is for internal programs only.\n",
1142 msgnum = extract_long(cmdbuf, 0);
1143 msg = CtdlFetchMessage(msgnum);
1145 cprintf("%d Message %ld not found.\n",
1150 serialize_message(&smr, msg);
1151 CtdlFreeMessage(msg);
1154 cprintf("%d Unable to serialize message\n",
1155 ERROR+INTERNAL_ERROR);
1159 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1160 client_write(smr.ser, smr.len);
1167 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1169 void cmd_msg4(char *cmdbuf)
1173 msgid = extract_long(cmdbuf, 0);
1174 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1178 * Open a component of a MIME message as a download file
1180 void cmd_opna(char *cmdbuf)
1184 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1186 msgid = extract_long(cmdbuf, 0);
1187 extract(desired_section, cmdbuf, 1);
1189 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1194 * Save a message pointer into a specified room
1195 * (Returns 0 for success, nonzero for failure)
1196 * roomname may be NULL to use the current room
1198 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1200 char hold_rm[ROOMNAMELEN];
1201 struct cdbdata *cdbfr;
1204 long highest_msg = 0L;
1205 struct CtdlMessage *msg = NULL;
1207 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1208 roomname, msgid, flags);
1210 strcpy(hold_rm, CC->quickroom.QRname);
1212 /* We may need to check to see if this message is real */
1213 if ( (flags & SM_VERIFY_GOODNESS)
1214 || (flags & SM_DO_REPL_CHECK)
1216 msg = CtdlFetchMessage(msgid);
1217 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1220 /* Perform replication checks if necessary */
1221 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1223 if (getroom(&CC->quickroom,
1224 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1226 lprintf(9, "No such room <%s>\n", roomname);
1227 if (msg != NULL) CtdlFreeMessage(msg);
1228 return(ERROR + ROOM_NOT_FOUND);
1231 if (ReplicationChecks(msg) != 0) {
1232 getroom(&CC->quickroom, hold_rm);
1233 if (msg != NULL) CtdlFreeMessage(msg);
1234 lprintf(9, "Did replication, and newer exists\n");
1239 /* Now the regular stuff */
1240 if (lgetroom(&CC->quickroom,
1241 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1243 lprintf(9, "No such room <%s>\n", roomname);
1244 if (msg != NULL) CtdlFreeMessage(msg);
1245 return(ERROR + ROOM_NOT_FOUND);
1248 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1249 if (cdbfr == NULL) {
1253 msglist = mallok(cdbfr->len);
1254 if (msglist == NULL)
1255 lprintf(3, "ERROR malloc msglist!\n");
1256 num_msgs = cdbfr->len / sizeof(long);
1257 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1262 /* Make sure the message doesn't already exist in this room. It
1263 * is absolutely taboo to have more than one reference to the same
1264 * message in a room.
1266 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1267 if (msglist[i] == msgid) {
1268 lputroom(&CC->quickroom); /* unlock the room */
1269 getroom(&CC->quickroom, hold_rm);
1270 if (msg != NULL) CtdlFreeMessage(msg);
1271 return(ERROR + ALREADY_EXISTS);
1275 /* Now add the new message */
1277 msglist = reallok(msglist,
1278 (num_msgs * sizeof(long)));
1280 if (msglist == NULL) {
1281 lprintf(3, "ERROR: can't realloc message list!\n");
1283 msglist[num_msgs - 1] = msgid;
1285 /* Sort the message list, so all the msgid's are in order */
1286 num_msgs = sort_msglist(msglist, num_msgs);
1288 /* Determine the highest message number */
1289 highest_msg = msglist[num_msgs - 1];
1291 /* Write it back to disk. */
1292 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1293 msglist, num_msgs * sizeof(long));
1295 /* Free up the memory we used. */
1298 /* Update the highest-message pointer and unlock the room. */
1299 CC->quickroom.QRhighest = highest_msg;
1300 lputroom(&CC->quickroom);
1301 getroom(&CC->quickroom, hold_rm);
1303 /* Bump the reference count for this message. */
1304 if ((flags & SM_DONT_BUMP_REF)==0) {
1305 AdjRefCount(msgid, +1);
1308 /* Return success. */
1309 if (msg != NULL) CtdlFreeMessage(msg);
1316 * Message base operation to send a message to the master file
1317 * (returns new message number)
1319 * This is the back end for CtdlSaveMsg() and should not be directly
1320 * called by server-side modules.
1323 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1324 FILE *save_a_copy) /* save a copy to disk? */
1331 /* Get a new message number */
1332 newmsgid = get_new_message_number();
1333 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1335 /* Generate an ID if we don't have one already */
1336 if (msg->cm_fields['I']==NULL) {
1337 msg->cm_fields['I'] = strdoop(msgidbuf);
1340 serialize_message(&smr, msg);
1343 cprintf("%d Unable to serialize message\n",
1344 ERROR+INTERNAL_ERROR);
1348 /* Write our little bundle of joy into the message base */
1349 begin_critical_section(S_MSGMAIN);
1350 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1351 smr.ser, smr.len) < 0) {
1352 lprintf(2, "Can't store message\n");
1357 end_critical_section(S_MSGMAIN);
1359 /* If the caller specified that a copy should be saved to a particular
1360 * file handle, do that now too.
1362 if (save_a_copy != NULL) {
1363 fwrite(smr.ser, smr.len, 1, save_a_copy);
1366 /* Free the memory we used for the serialized message */
1369 /* Return the *local* message ID to the caller
1370 * (even if we're storing an incoming network message)
1378 * Serialize a struct CtdlMessage into the format used on disk and network.
1380 * This function loads up a "struct ser_ret" (defined in server.h) which
1381 * contains the length of the serialized message and a pointer to the
1382 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1384 void serialize_message(struct ser_ret *ret, /* return values */
1385 struct CtdlMessage *msg) /* unserialized msg */
1389 static char *forder = FORDER;
1391 if (is_valid_message(msg) == 0) return; /* self check */
1394 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1395 ret->len = ret->len +
1396 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1398 lprintf(9, "calling malloc(%d)\n", ret->len);
1399 ret->ser = mallok(ret->len);
1400 if (ret->ser == NULL) {
1406 ret->ser[1] = msg->cm_anon_type;
1407 ret->ser[2] = msg->cm_format_type;
1410 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1411 ret->ser[wlen++] = (char)forder[i];
1412 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1413 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1415 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1424 * Back end for the ReplicationChecks() function
1426 void check_repl(long msgnum) {
1427 struct CtdlMessage *msg;
1428 time_t timestamp = (-1L);
1430 lprintf(9, "check_repl() found message %ld\n", msgnum);
1431 msg = CtdlFetchMessage(msgnum);
1432 if (msg == NULL) return;
1433 if (msg->cm_fields['T'] != NULL) {
1434 timestamp = atol(msg->cm_fields['T']);
1436 CtdlFreeMessage(msg);
1438 if (timestamp > msg_repl->highest) {
1439 msg_repl->highest = timestamp; /* newer! */
1440 lprintf(9, "newer!\n");
1443 lprintf(9, "older!\n");
1445 /* Existing isn't newer? Then delete the old one(s). */
1446 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1451 * Check to see if any messages already exist which carry the same Extended ID
1455 * -> With older timestamps: delete them and return 0. Message will be saved.
1456 * -> With newer timestamps: return 1. Message save will be aborted.
1458 int ReplicationChecks(struct CtdlMessage *msg) {
1459 struct CtdlMessage *template;
1462 lprintf(9, "ReplicationChecks() started\n");
1463 /* No extended id? Don't do anything. */
1464 if (msg->cm_fields['E'] == NULL) return 0;
1465 if (strlen(msg->cm_fields['E']) == 0) return 0;
1466 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1468 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1469 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1470 msg_repl->highest = atol(msg->cm_fields['T']);
1472 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1473 memset(template, 0, sizeof(struct CtdlMessage));
1474 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1476 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template, check_repl);
1478 /* If a newer message exists with the same Extended ID, abort
1481 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1485 CtdlFreeMessage(template);
1486 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1494 * Save a message to disk
1496 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1497 char *rec, /* Recipient (mail) */
1498 char *force, /* force a particular room? */
1499 int supplied_mailtype) /* local or remote type */
1502 char hold_rm[ROOMNAMELEN];
1503 char actual_rm[ROOMNAMELEN];
1504 char force_room[ROOMNAMELEN];
1505 char content_type[256]; /* We have to learn this */
1506 char recipient[256];
1509 struct usersupp userbuf;
1511 struct SuppMsgInfo smi;
1512 FILE *network_fp = NULL;
1513 static int seqnum = 1;
1514 struct CtdlMessage *imsg;
1518 lprintf(9, "CtdlSaveMsg() called\n");
1519 if (is_valid_message(msg) == 0) return(-1); /* self check */
1520 mailtype = supplied_mailtype;
1522 /* If this message has no timestamp, we take the liberty of
1523 * giving it one, right now.
1525 if (msg->cm_fields['T'] == NULL) {
1526 lprintf(9, "Generating timestamp\n");
1527 sprintf(aaa, "%ld", time(NULL));
1528 msg->cm_fields['T'] = strdoop(aaa);
1531 /* If this message has no path, we generate one.
1533 if (msg->cm_fields['P'] == NULL) {
1534 lprintf(9, "Generating path\n");
1535 if (msg->cm_fields['A'] != NULL) {
1536 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1537 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1538 if (isspace(msg->cm_fields['P'][a])) {
1539 msg->cm_fields['P'][a] = ' ';
1544 msg->cm_fields['P'] = strdoop("unknown");
1548 strcpy(force_room, force);
1550 /* Strip non-printable characters out of the recipient name */
1551 lprintf(9, "Checking recipient (if present)\n");
1552 strcpy(recipient, rec);
1553 for (a = 0; a < strlen(recipient); ++a)
1554 if (!isprint(recipient[a]))
1555 strcpy(&recipient[a], &recipient[a + 1]);
1557 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1558 for (a=0; a<strlen(recipient); ++a) {
1559 if (recipient[a] == '@') {
1560 if (CtdlHostAlias(&recipient[a+1])
1561 == hostalias_localhost) {
1563 lprintf(7, "Changed to <%s>\n", recipient);
1564 mailtype = MES_LOCAL;
1569 lprintf(9, "Recipient is <%s>\n", recipient);
1571 /* Learn about what's inside, because it's what's inside that counts */
1572 lprintf(9, "Learning what's inside\n");
1573 if (msg->cm_fields['M'] == NULL) {
1574 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1577 switch (msg->cm_format_type) {
1579 strcpy(content_type, "text/x-citadel-variformat");
1582 strcpy(content_type, "text/plain");
1585 strcpy(content_type, "text/plain");
1586 /* advance past header fields */
1587 mptr = msg->cm_fields['M'];
1590 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1591 safestrncpy(content_type, mptr,
1592 sizeof(content_type));
1593 strcpy(content_type, &content_type[14]);
1594 for (a = 0; a < strlen(content_type); ++a)
1595 if ((content_type[a] == ';')
1596 || (content_type[a] == ' ')
1597 || (content_type[a] == 13)
1598 || (content_type[a] == 10))
1599 content_type[a] = 0;
1606 /* Goto the correct room */
1607 lprintf(9, "Switching rooms\n");
1608 strcpy(hold_rm, CC->quickroom.QRname);
1609 strcpy(actual_rm, CC->quickroom.QRname);
1611 /* If the user is a twit, move to the twit room for posting */
1612 lprintf(9, "Handling twit stuff\n");
1614 if (CC->usersupp.axlevel == 2) {
1615 strcpy(hold_rm, actual_rm);
1616 strcpy(actual_rm, config.c_twitroom);
1620 /* ...or if this message is destined for Aide> then go there. */
1621 if (strlen(force_room) > 0) {
1622 strcpy(actual_rm, force_room);
1625 lprintf(9, "Possibly relocating\n");
1626 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1627 getroom(&CC->quickroom, actual_rm);
1631 * If this message has no O (room) field, generate one.
1633 if (msg->cm_fields['O'] == NULL) {
1634 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1637 /* Perform "before save" hooks (aborting if any return nonzero) */
1638 lprintf(9, "Performing before-save hooks\n");
1639 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1641 /* If this message has an Extended ID, perform replication checks */
1642 lprintf(9, "Performing replication checks\n");
1643 if (ReplicationChecks(msg) > 0) return(-1);
1645 /* Network mail - send a copy to the network program. */
1646 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1647 lprintf(9, "Sending network spool\n");
1648 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1649 (long) getpid(), CC->cs_pid, ++seqnum);
1650 lprintf(9, "Saving a copy to %s\n", aaa);
1651 network_fp = fopen(aaa, "ab+");
1652 if (network_fp == NULL)
1653 lprintf(2, "ERROR: %s\n", strerror(errno));
1656 /* Save it to disk */
1657 lprintf(9, "Saving to disk\n");
1658 newmsgid = send_message(msg, network_fp);
1659 if (network_fp != NULL) {
1661 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1664 if (newmsgid <= 0L) return(-1);
1666 /* Write a supplemental message info record. This doesn't have to
1667 * be a critical section because nobody else knows about this message
1670 lprintf(9, "Creating SuppMsgInfo record\n");
1671 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1672 smi.smi_msgnum = newmsgid;
1673 smi.smi_refcount = 0;
1674 safestrncpy(smi.smi_content_type, content_type, 64);
1675 PutSuppMsgInfo(&smi);
1677 /* Now figure out where to store the pointers */
1678 lprintf(9, "Storing pointers\n");
1680 /* If this is being done by the networker delivering a private
1681 * message, we want to BYPASS saving the sender's copy (because there
1682 * is no local sender; it would otherwise go to the Trashcan).
1684 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1685 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1686 lprintf(3, "ERROR saving message pointer!\n");
1687 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1691 /* For internet mail, drop a copy in the outbound queue room */
1692 if (mailtype == MES_INTERNET) {
1693 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1696 /* Bump this user's messages posted counter. */
1697 lprintf(9, "Updating user\n");
1698 lgetuser(&CC->usersupp, CC->curr_user);
1699 CC->usersupp.posted = CC->usersupp.posted + 1;
1700 lputuser(&CC->usersupp);
1702 /* If this is private, local mail, make a copy in the
1703 * recipient's mailbox and bump the reference count.
1705 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1706 if (getuser(&userbuf, recipient) == 0) {
1707 lprintf(9, "Delivering private mail\n");
1708 MailboxName(actual_rm, &userbuf, MAILROOM);
1709 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1712 lprintf(9, "No user <%s>, saving in %s> instead\n",
1713 recipient, AIDEROOM);
1714 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1718 /* Perform "after save" hooks */
1719 lprintf(9, "Performing after-save hooks\n");
1720 PerformMessageHooks(msg, EVT_AFTERSAVE);
1723 lprintf(9, "Returning to original room\n");
1724 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1725 getroom(&CC->quickroom, hold_rm);
1727 /* For internet mail, generate delivery instructions
1728 * (Yes, this is recursive! Deal with it!)
1730 if (mailtype == MES_INTERNET) {
1731 lprintf(9, "Generating delivery instructions\n");
1732 instr = mallok(2048);
1734 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1737 SPOOLMIME, newmsgid, time(NULL),
1738 msg->cm_fields['A'], msg->cm_fields['N'],
1741 imsg = mallok(sizeof(struct CtdlMessage));
1742 memset(imsg, 0, sizeof(struct CtdlMessage));
1743 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1744 imsg->cm_anon_type = MES_NORMAL;
1745 imsg->cm_format_type = FMT_RFC822;
1746 imsg->cm_fields['A'] = strdoop("Citadel");
1747 imsg->cm_fields['M'] = instr;
1748 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1749 CtdlFreeMessage(imsg);
1758 * Convenience function for generating small administrative messages.
1760 void quickie_message(char *from, char *to, char *room, char *text)
1762 struct CtdlMessage *msg;
1764 msg = mallok(sizeof(struct CtdlMessage));
1765 memset(msg, 0, sizeof(struct CtdlMessage));
1766 msg->cm_magic = CTDLMESSAGE_MAGIC;
1767 msg->cm_anon_type = MES_NORMAL;
1768 msg->cm_format_type = 0;
1769 msg->cm_fields['A'] = strdoop(from);
1770 msg->cm_fields['O'] = strdoop(room);
1771 msg->cm_fields['N'] = strdoop(NODENAME);
1773 msg->cm_fields['R'] = strdoop(to);
1774 msg->cm_fields['M'] = strdoop(text);
1776 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1777 CtdlFreeMessage(msg);
1778 syslog(LOG_NOTICE, text);
1784 * Back end function used by make_message() and similar functions
1786 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1787 size_t maxlen, /* maximum message length */
1788 char *exist /* if non-null, append to it;
1789 exist is ALWAYS freed */
1792 size_t message_len = 0;
1793 size_t buffer_len = 0;
1797 if (exist == NULL) {
1801 m = reallok(exist, strlen(exist) + 4096);
1802 if (m == NULL) phree(exist);
1805 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1812 /* read in the lines of message text one by one */
1814 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1816 /* augment the buffer if we have to */
1817 if ((message_len + strlen(buf) + 2) > buffer_len) {
1818 lprintf(9, "realloking\n");
1819 ptr = reallok(m, (buffer_len * 2) );
1820 if (ptr == NULL) { /* flush if can't allocate */
1821 while ( (client_gets(buf)>0) &&
1822 strcmp(buf, terminator)) ;;
1825 buffer_len = (buffer_len * 2);
1828 lprintf(9, "buffer_len is %d\n", buffer_len);
1832 if (append == NULL) append = m;
1833 while (strlen(append) > 0) ++append;
1834 strcpy(append, buf);
1835 strcat(append, "\n");
1836 message_len = message_len + strlen(buf) + 1;
1838 /* if we've hit the max msg length, flush the rest */
1839 if (message_len >= maxlen) {
1840 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1851 * Build a binary message to be saved on disk.
1854 struct CtdlMessage *make_message(
1855 struct usersupp *author, /* author's usersupp structure */
1856 char *recipient, /* NULL if it's not mail */
1857 char *room, /* room where it's going */
1858 int type, /* see MES_ types in header file */
1859 int net_type, /* see MES_ types in header file */
1860 int format_type, /* local or remote (see citadel.h) */
1861 char *fake_name) /* who we're masquerading as */
1867 struct CtdlMessage *msg;
1869 msg = mallok(sizeof(struct CtdlMessage));
1870 memset(msg, 0, sizeof(struct CtdlMessage));
1871 msg->cm_magic = CTDLMESSAGE_MAGIC;
1872 msg->cm_anon_type = type;
1873 msg->cm_format_type = format_type;
1875 /* Don't confuse the poor folks if it's not routed mail. */
1876 strcpy(dest_node, "");
1878 /* If net_type is MES_BINARY, split out the destination node. */
1879 if (net_type == MES_BINARY) {
1880 strcpy(dest_node, NODENAME);
1881 for (a = 0; a < strlen(recipient); ++a) {
1882 if (recipient[a] == '@') {
1884 strcpy(dest_node, &recipient[a + 1]);
1889 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1890 if (net_type == MES_INTERNET) {
1891 strcpy(dest_node, "internet");
1894 while (isspace(recipient[strlen(recipient) - 1]))
1895 recipient[strlen(recipient) - 1] = 0;
1897 sprintf(buf, "cit%ld", author->usernum); /* Path */
1898 msg->cm_fields['P'] = strdoop(buf);
1900 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1901 msg->cm_fields['T'] = strdoop(buf);
1903 if (fake_name[0]) /* author */
1904 msg->cm_fields['A'] = strdoop(fake_name);
1906 msg->cm_fields['A'] = strdoop(author->fullname);
1908 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1909 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1911 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1913 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1914 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1916 if (recipient[0] != 0)
1917 msg->cm_fields['R'] = strdoop(recipient);
1918 if (dest_node[0] != 0)
1919 msg->cm_fields['D'] = strdoop(dest_node);
1922 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1923 config.c_maxmsglen, NULL);
1934 * message entry - mode 0 (normal)
1936 void cmd_ent0(char *entargs)
1939 char recipient[256];
1941 int format_type = 0;
1942 char newusername[256];
1943 struct CtdlMessage *msg;
1947 struct usersupp tempUS;
1950 post = extract_int(entargs, 0);
1951 extract(recipient, entargs, 1);
1952 anon_flag = extract_int(entargs, 2);
1953 format_type = extract_int(entargs, 3);
1955 /* first check to make sure the request is valid. */
1957 if (!(CC->logged_in)) {
1958 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1961 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1962 cprintf("%d Need to be validated to enter ",
1963 ERROR + HIGHER_ACCESS_REQUIRED);
1964 cprintf("(except in %s> to sysop)\n", MAILROOM);
1967 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1968 cprintf("%d Need net privileges to enter here.\n",
1969 ERROR + HIGHER_ACCESS_REQUIRED);
1972 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1973 cprintf("%d Sorry, this is a read-only room.\n",
1974 ERROR + HIGHER_ACCESS_REQUIRED);
1981 if (CC->usersupp.axlevel < 6) {
1982 cprintf("%d You don't have permission to masquerade.\n",
1983 ERROR + HIGHER_ACCESS_REQUIRED);
1986 extract(newusername, entargs, 4);
1987 memset(CC->fake_postname, 0, 32);
1988 strcpy(CC->fake_postname, newusername);
1989 cprintf("%d Ok\n", OK);
1992 CC->cs_flags |= CS_POSTING;
1995 if (CC->quickroom.QRflags & QR_MAILBOX) {
1996 if (CC->usersupp.axlevel >= 2) {
1997 strcpy(buf, recipient);
1999 strcpy(buf, "sysop");
2000 e = alias(buf); /* alias and mail type */
2001 if ((buf[0] == 0) || (e == MES_ERROR)) {
2002 cprintf("%d Unknown address - cannot send message.\n",
2003 ERROR + NO_SUCH_USER);
2006 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2007 cprintf("%d Net privileges required for network mail.\n",
2008 ERROR + HIGHER_ACCESS_REQUIRED);
2011 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2012 && ((CC->usersupp.flags & US_INTERNET) == 0)
2013 && (!CC->internal_pgm)) {
2014 cprintf("%d You don't have access to Internet mail.\n",
2015 ERROR + HIGHER_ACCESS_REQUIRED);
2018 if (!strcasecmp(buf, "sysop")) {
2023 goto SKFALL; /* don't search local file */
2024 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2025 cprintf("%d Can't send mail to yourself!\n",
2026 ERROR + NO_SUCH_USER);
2029 /* Check to make sure the user exists; also get the correct
2030 * upper/lower casing of the name.
2032 a = getuser(&tempUS, buf);
2034 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2037 strcpy(buf, tempUS.fullname);
2040 SKFALL: b = MES_NORMAL;
2041 if (CC->quickroom.QRflags & QR_ANONONLY)
2043 if (CC->quickroom.QRflags & QR_ANONOPT) {
2047 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2050 /* If we're only checking the validity of the request, return
2051 * success without creating the message.
2054 cprintf("%d %s\n", OK, buf);
2058 cprintf("%d send message\n", SEND_LISTING);
2060 /* Read in the message from the client. */
2061 if (CC->fake_postname[0])
2062 msg = make_message(&CC->usersupp, buf,
2063 CC->quickroom.QRname, b, e, format_type,
2065 else if (CC->fake_username[0])
2066 msg = make_message(&CC->usersupp, buf,
2067 CC->quickroom.QRname, b, e, format_type,
2070 msg = make_message(&CC->usersupp, buf,
2071 CC->quickroom.QRname, b, e, format_type, "");
2074 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2075 CtdlFreeMessage(msg);
2076 CC->fake_postname[0] = '\0';
2083 * message entry - mode 3 (raw)
2085 void cmd_ent3(char *entargs)
2091 unsigned char ch, which_field;
2092 struct usersupp tempUS;
2094 struct CtdlMessage *msg;
2097 if (CC->internal_pgm == 0) {
2098 cprintf("%d This command is for internal programs only.\n",
2103 /* See if there's a recipient, but make sure it's a real one */
2104 extract(recp, entargs, 1);
2105 for (a = 0; a < strlen(recp); ++a)
2106 if (!isprint(recp[a]))
2107 strcpy(&recp[a], &recp[a + 1]);
2108 while (isspace(recp[0]))
2109 strcpy(recp, &recp[1]);
2110 while (isspace(recp[strlen(recp) - 1]))
2111 recp[strlen(recp) - 1] = 0;
2113 /* If we're in Mail, check the recipient */
2114 if (strlen(recp) > 0) {
2115 e = alias(recp); /* alias and mail type */
2116 if ((recp[0] == 0) || (e == MES_ERROR)) {
2117 cprintf("%d Unknown address - cannot send message.\n",
2118 ERROR + NO_SUCH_USER);
2121 if (e == MES_LOCAL) {
2122 a = getuser(&tempUS, recp);
2124 cprintf("%d No such user.\n",
2125 ERROR + NO_SUCH_USER);
2131 /* At this point, message has been approved. */
2132 if (extract_int(entargs, 0) == 0) {
2133 cprintf("%d OK to send\n", OK);
2137 msglen = extract_long(entargs, 2);
2138 msg = mallok(sizeof(struct CtdlMessage));
2140 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2144 memset(msg, 0, sizeof(struct CtdlMessage));
2145 tempbuf = mallok(msglen);
2146 if (tempbuf == NULL) {
2147 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2152 cprintf("%d %ld\n", SEND_BINARY, msglen);
2154 client_read(&ch, 1); /* 0xFF magic number */
2155 msg->cm_magic = CTDLMESSAGE_MAGIC;
2156 client_read(&ch, 1); /* anon type */
2157 msg->cm_anon_type = ch;
2158 client_read(&ch, 1); /* format type */
2159 msg->cm_format_type = ch;
2160 msglen = msglen - 3;
2162 while (msglen > 0) {
2163 client_read(&which_field, 1);
2164 if (!isalpha(which_field)) valid_msg = 0;
2168 client_read(&ch, 1);
2170 a = strlen(tempbuf);
2173 } while ( (ch != 0) && (msglen > 0) );
2175 msg->cm_fields[which_field] = strdoop(tempbuf);
2178 msg->cm_flags = CM_SKIP_HOOKS;
2179 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2180 CtdlFreeMessage(msg);
2186 * API function to delete messages which match a set of criteria
2187 * (returns the actual number of messages deleted)
2189 int CtdlDeleteMessages(char *room_name, /* which room */
2190 long dmsgnum, /* or "0" for any */
2191 char *content_type /* or "" for any */
2195 struct quickroom qrbuf;
2196 struct cdbdata *cdbfr;
2197 long *msglist = NULL;
2200 int num_deleted = 0;
2202 struct SuppMsgInfo smi;
2204 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2205 room_name, dmsgnum, content_type);
2207 /* get room record, obtaining a lock... */
2208 if (lgetroom(&qrbuf, room_name) != 0) {
2209 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2211 return (0); /* room not found */
2213 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2215 if (cdbfr != NULL) {
2216 msglist = mallok(cdbfr->len);
2217 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2218 num_msgs = cdbfr->len / sizeof(long);
2222 for (i = 0; i < num_msgs; ++i) {
2225 /* Set/clear a bit for each criterion */
2227 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2228 delete_this |= 0x01;
2230 if (strlen(content_type) == 0) {
2231 delete_this |= 0x02;
2233 GetSuppMsgInfo(&smi, msglist[i]);
2234 if (!strcasecmp(smi.smi_content_type,
2236 delete_this |= 0x02;
2240 /* Delete message only if all bits are set */
2241 if (delete_this == 0x03) {
2242 AdjRefCount(msglist[i], -1);
2248 num_msgs = sort_msglist(msglist, num_msgs);
2249 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2250 msglist, (num_msgs * sizeof(long)));
2252 qrbuf.QRhighest = msglist[num_msgs - 1];
2256 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2257 return (num_deleted);
2263 * Delete message from current room
2265 void cmd_dele(char *delstr)
2270 getuser(&CC->usersupp, CC->curr_user);
2271 if ((CC->usersupp.axlevel < 6)
2272 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2273 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2274 && (!(CC->internal_pgm))) {
2275 cprintf("%d Higher access required.\n",
2276 ERROR + HIGHER_ACCESS_REQUIRED);
2279 delnum = extract_long(delstr, 0);
2281 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2284 cprintf("%d %d message%s deleted.\n", OK,
2285 num_deleted, ((num_deleted != 1) ? "s" : ""));
2287 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2293 * move or copy a message to another room
2295 void cmd_move(char *args)
2299 struct quickroom qtemp;
2303 num = extract_long(args, 0);
2304 extract(targ, args, 1);
2305 targ[ROOMNAMELEN - 1] = 0;
2306 is_copy = extract_int(args, 2);
2308 getuser(&CC->usersupp, CC->curr_user);
2309 if ((CC->usersupp.axlevel < 6)
2310 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2311 cprintf("%d Higher access required.\n",
2312 ERROR + HIGHER_ACCESS_REQUIRED);
2316 if (getroom(&qtemp, targ) != 0) {
2317 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2321 err = CtdlSaveMsgPointerInRoom(targ, num,
2322 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2324 cprintf("%d Cannot store message in %s: error %d\n",
2329 /* Now delete the message from the source room,
2330 * if this is a 'move' rather than a 'copy' operation.
2332 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2334 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2340 * GetSuppMsgInfo() - Get the supplementary record for a message
2342 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2345 struct cdbdata *cdbsmi;
2348 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2349 smibuf->smi_msgnum = msgnum;
2350 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2352 /* Use the negative of the message number for its supp record index */
2353 TheIndex = (0L - msgnum);
2355 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2356 if (cdbsmi == NULL) {
2357 return; /* record not found; go with defaults */
2359 memcpy(smibuf, cdbsmi->ptr,
2360 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2361 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2368 * PutSuppMsgInfo() - (re)write supplementary record for a message
2370 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2374 /* Use the negative of the message number for its supp record index */
2375 TheIndex = (0L - smibuf->smi_msgnum);
2377 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2378 smibuf->smi_msgnum, smibuf->smi_refcount);
2380 cdb_store(CDB_MSGMAIN,
2381 &TheIndex, sizeof(long),
2382 smibuf, sizeof(struct SuppMsgInfo));
2387 * AdjRefCount - change the reference count for a message;
2388 * delete the message if it reaches zero
2390 void AdjRefCount(long msgnum, int incr)
2393 struct SuppMsgInfo smi;
2396 /* This is a *tight* critical section; please keep it that way, as
2397 * it may get called while nested in other critical sections.
2398 * Complicating this any further will surely cause deadlock!
2400 begin_critical_section(S_SUPPMSGMAIN);
2401 GetSuppMsgInfo(&smi, msgnum);
2402 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2403 msgnum, smi.smi_refcount);
2404 smi.smi_refcount += incr;
2405 PutSuppMsgInfo(&smi);
2406 end_critical_section(S_SUPPMSGMAIN);
2407 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2408 msgnum, smi.smi_refcount);
2410 /* If the reference count is now zero, delete the message
2411 * (and its supplementary record as well).
2413 if (smi.smi_refcount == 0) {
2414 lprintf(9, "Deleting message <%ld>\n", msgnum);
2416 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2417 delnum = (0L - msgnum);
2418 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2423 * Write a generic object to this room
2425 * Note: this could be much more efficient. Right now we use two temporary
2426 * files, and still pull the message into memory as with all others.
2428 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2429 char *content_type, /* MIME type of this object */
2430 char *tempfilename, /* Where to fetch it from */
2431 struct usersupp *is_mailbox, /* Mailbox room? */
2432 int is_binary, /* Is encoding necessary? */
2433 int is_unique, /* Del others of this type? */
2434 unsigned int flags /* Internal save flags */
2439 char filename[PATH_MAX];
2442 struct quickroom qrbuf;
2443 char roomname[ROOMNAMELEN];
2444 struct CtdlMessage *msg;
2447 if (is_mailbox != NULL)
2448 MailboxName(roomname, is_mailbox, req_room);
2450 safestrncpy(roomname, req_room, sizeof(roomname));
2451 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2453 strcpy(filename, tmpnam(NULL));
2454 fp = fopen(filename, "w");
2458 tempfp = fopen(tempfilename, "r");
2459 if (tempfp == NULL) {
2465 fprintf(fp, "Content-type: %s\n", content_type);
2466 lprintf(9, "Content-type: %s\n", content_type);
2468 if (is_binary == 0) {
2469 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2470 while (ch = getc(tempfp), ch > 0)
2476 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2479 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2480 tempfilename, filename);
2484 lprintf(9, "Allocating\n");
2485 msg = mallok(sizeof(struct CtdlMessage));
2486 memset(msg, 0, sizeof(struct CtdlMessage));
2487 msg->cm_magic = CTDLMESSAGE_MAGIC;
2488 msg->cm_anon_type = MES_NORMAL;
2489 msg->cm_format_type = 4;
2490 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2491 msg->cm_fields['O'] = strdoop(req_room);
2492 msg->cm_fields['N'] = strdoop(config.c_nodename);
2493 msg->cm_fields['H'] = strdoop(config.c_humannode);
2494 msg->cm_flags = flags;
2496 lprintf(9, "Loading\n");
2497 fp = fopen(filename, "rb");
2498 fseek(fp, 0L, SEEK_END);
2501 msg->cm_fields['M'] = mallok(len);
2502 fread(msg->cm_fields['M'], len, 1, fp);
2506 /* Create the requested room if we have to. */
2507 if (getroom(&qrbuf, roomname) != 0) {
2508 create_room(roomname,
2509 ( (is_mailbox != NULL) ? 4 : 3 ),
2512 /* If the caller specified this object as unique, delete all
2513 * other objects of this type that are currently in the room.
2516 lprintf(9, "Deleted %d other msgs of this type\n",
2517 CtdlDeleteMessages(roomname, 0L, content_type));
2519 /* Now write the data */
2520 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2521 CtdlFreeMessage(msg);
2529 void CtdlGetSysConfigBackend(long msgnum) {
2530 config_msgnum = msgnum;
2534 char *CtdlGetSysConfig(char *sysconfname) {
2535 char hold_rm[ROOMNAMELEN];
2538 struct CtdlMessage *msg;
2541 strcpy(hold_rm, CC->quickroom.QRname);
2542 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2543 getroom(&CC->quickroom, hold_rm);
2548 /* We want the last (and probably only) config in this room */
2549 begin_critical_section(S_CONFIG);
2550 config_msgnum = (-1L);
2551 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2552 CtdlGetSysConfigBackend);
2553 msgnum = config_msgnum;
2554 end_critical_section(S_CONFIG);
2560 msg = CtdlFetchMessage(msgnum);
2562 conf = strdoop(msg->cm_fields['M']);
2563 CtdlFreeMessage(msg);
2570 getroom(&CC->quickroom, hold_rm);
2572 lprintf(9, "eggstracting...\n");
2573 if (conf != NULL) do {
2574 extract_token(buf, conf, 0, '\n');
2575 lprintf(9, "eggstracted <%s>\n", buf);
2576 strcpy(conf, &conf[strlen(buf)+1]);
2577 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2582 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2583 char temp[PATH_MAX];
2586 strcpy(temp, tmpnam(NULL));
2588 fp = fopen(temp, "w");
2589 if (fp == NULL) return;
2590 fprintf(fp, "%s", sysconfdata);
2593 /* this handy API function does all the work for us */
2594 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);