21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
33 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
34 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
35 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
37 extern struct config config;
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
75 * This function is self explanatory.
76 * (What can I say, I'm in a weird mood today...)
78 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
82 for (i = 0; i < strlen(name); ++i)
85 if (isspace(name[i - 1])) {
86 strcpy(&name[i - 1], &name[i]);
89 while (isspace(name[i + 1])) {
90 strcpy(&name[i + 1], &name[i + 2]);
97 * Aliasing for network mail.
98 * (Error messages have been commented out, because this is a server.)
100 int alias(char *name)
101 { /* process alias and routing info for mail */
104 char aaa[300], bbb[300];
106 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
108 fp = fopen("network/mail.aliases", "r");
110 fp = fopen("/dev/null", "r");
115 while (fgets(aaa, sizeof aaa, fp) != NULL) {
116 while (isspace(name[0]))
117 strcpy(name, &name[1]);
118 aaa[strlen(aaa) - 1] = 0;
120 for (a = 0; a < strlen(aaa); ++a) {
122 strcpy(bbb, &aaa[a + 1]);
126 if (!strcasecmp(name, aaa))
130 lprintf(7, "Mail is being forwarded to %s\n", name);
132 /* determine local or remote type, see citadel.h */
133 for (a = 0; a < strlen(name); ++a)
135 return (MES_INTERNET);
136 for (a = 0; a < strlen(name); ++a)
138 for (b = a; b < strlen(name); ++b)
140 return (MES_INTERNET);
142 for (a = 0; a < strlen(name); ++a)
146 lprintf(7, "Too many @'s in address\n");
150 for (a = 0; a < strlen(name); ++a)
152 strcpy(bbb, &name[a + 1]);
154 strcpy(bbb, &bbb[1]);
155 fp = fopen("network/mail.sysinfo", "r");
159 a = getstring(fp, aaa);
160 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
161 a = getstring(fp, aaa);
162 if (!strncmp(aaa, "use ", 4)) {
163 strcpy(bbb, &aaa[4]);
168 if (!strncmp(aaa, "uum", 3)) {
170 for (a = 0; a < strlen(bbb); ++a) {
176 while (bbb[strlen(bbb) - 1] == '_')
177 bbb[strlen(bbb) - 1] = 0;
178 sprintf(name, &aaa[4], bbb);
179 return (MES_INTERNET);
181 if (!strncmp(aaa, "bin", 3)) {
184 while (aaa[strlen(aaa) - 1] != '@')
185 aaa[strlen(aaa) - 1] = 0;
186 aaa[strlen(aaa) - 1] = 0;
187 while (aaa[strlen(aaa) - 1] == ' ')
188 aaa[strlen(aaa) - 1] = 0;
189 while (bbb[0] != '@')
190 strcpy(bbb, &bbb[1]);
191 strcpy(bbb, &bbb[1]);
192 while (bbb[0] == ' ')
193 strcpy(bbb, &bbb[1]);
194 sprintf(name, "%s @%s", aaa, bbb);
207 fp = fopen("citadel.control", "r");
208 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
214 void simple_listing(long msgnum)
216 cprintf("%ld\n", msgnum);
221 /* Determine if a given message matches the fields in a message template.
222 * Return 0 for a successful match.
224 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
227 /* If there aren't any fields in the template, all messages will
230 if (template == NULL) return(0);
232 /* Null messages are bogus. */
233 if (msg == NULL) return(1);
235 for (i='A'; i<='Z'; ++i) {
236 if (template->cm_fields[i] != NULL) {
237 if (msg->cm_fields[i] == NULL) {
240 if (strcasecmp(msg->cm_fields[i],
241 template->cm_fields[i])) return 1;
245 /* All compares succeeded: we have a match! */
253 * API function to perform an operation for each qualifying message in the
256 void CtdlForEachMessage(int mode, long ref,
258 struct CtdlMessage *compare,
259 void (*CallBack) (long msgnum))
264 struct cdbdata *cdbfr;
265 long *msglist = NULL;
268 struct SuppMsgInfo smi;
269 struct CtdlMessage *msg;
271 /* Learn about the user and room in question */
273 getuser(&CC->usersupp, CC->curr_user);
274 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
276 /* Load the message list */
277 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
279 msglist = mallok(cdbfr->len);
280 memcpy(msglist, cdbfr->ptr, cdbfr->len);
281 num_msgs = cdbfr->len / sizeof(long);
284 return; /* No messages at all? No further action. */
288 /* If the caller is looking for a specific MIME type, then filter
289 * out all messages which are not of the type requested.
292 if (content_type != NULL)
293 if (strlen(content_type) > 0)
294 for (a = 0; a < num_msgs; ++a) {
295 GetSuppMsgInfo(&smi, msglist[a]);
296 if (strcasecmp(smi.smi_content_type, content_type)) {
301 num_msgs = sort_msglist(msglist, num_msgs);
303 /* If a template was supplied, filter out the messages which
304 * don't match. (This could induce some delays!)
307 if (compare != NULL) {
308 for (a = 0; a < num_msgs; ++a) {
309 msg = CtdlFetchMessage(msglist[a]);
311 if (CtdlMsgCmp(msg, compare)) {
314 CtdlFreeMessage(msg);
322 * Now iterate through the message list, according to the
323 * criteria supplied by the caller.
326 for (a = 0; a < num_msgs; ++a) {
327 thismsg = msglist[a];
332 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
333 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
334 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
335 && (CC->usersupp.flags & US_LASTOLD))
336 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
337 || ((mode == MSGS_FIRST) && (a < ref))
338 || ((mode == MSGS_GT) && (thismsg > ref))
344 phree(msglist); /* Clean up */
350 * cmd_msgs() - get list of message #'s in this room
351 * implements the MSGS server command using CtdlForEachMessage()
353 void cmd_msgs(char *cmdbuf)
362 int with_template = 0;
363 struct CtdlMessage *template = NULL;
365 extract(which, cmdbuf, 0);
366 cm_ref = extract_int(cmdbuf, 1);
367 with_template = extract_int(cmdbuf, 2);
371 if (!strncasecmp(which, "OLD", 3))
373 else if (!strncasecmp(which, "NEW", 3))
375 else if (!strncasecmp(which, "FIRST", 5))
377 else if (!strncasecmp(which, "LAST", 4))
379 else if (!strncasecmp(which, "GT", 2))
382 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
383 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
388 cprintf("%d Send template then receive message list\n",
390 template = (struct CtdlMessage *)
391 mallok(sizeof(struct CtdlMessage));
392 memset(template, 0, sizeof(struct CtdlMessage));
393 while(client_gets(buf), strcmp(buf,"000")) {
394 extract(tfield, buf, 0);
395 extract(tvalue, buf, 1);
396 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
397 if (!strcasecmp(tfield, msgkeys[i])) {
398 template->cm_fields[i] =
405 cprintf("%d Message list...\n", LISTING_FOLLOWS);
408 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
409 if (template != NULL) CtdlFreeMessage(template);
417 * help_subst() - support routine for help file viewer
419 void help_subst(char *strbuf, char *source, char *dest)
424 while (p = pattern2(strbuf, source), (p >= 0)) {
425 strcpy(workbuf, &strbuf[p + strlen(source)]);
426 strcpy(&strbuf[p], dest);
427 strcat(strbuf, workbuf);
432 void do_help_subst(char *buffer)
436 help_subst(buffer, "^nodename", config.c_nodename);
437 help_subst(buffer, "^humannode", config.c_humannode);
438 help_subst(buffer, "^fqdn", config.c_fqdn);
439 help_subst(buffer, "^username", CC->usersupp.fullname);
440 sprintf(buf2, "%ld", CC->usersupp.usernum);
441 help_subst(buffer, "^usernum", buf2);
442 help_subst(buffer, "^sysadm", config.c_sysadm);
443 help_subst(buffer, "^variantname", CITADEL);
444 sprintf(buf2, "%d", config.c_maxsessions);
445 help_subst(buffer, "^maxsessions", buf2);
451 * memfmout() - Citadel text formatter and paginator.
452 * Although the original purpose of this routine was to format
453 * text to the reader's screen width, all we're really using it
454 * for here is to format text out to 80 columns before sending it
455 * to the client. The client software may reformat it again.
458 int width, /* screen width to use */
459 char *mptr, /* where are we going to get our text from? */
460 char subst) /* nonzero if we should do substitutions */
472 c = 1; /* c is the current pos */
475 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
477 buffer[strlen(buffer) + 1] = 0;
478 buffer[strlen(buffer)] = ch;
481 if (buffer[0] == '^')
482 do_help_subst(buffer);
484 buffer[strlen(buffer) + 1] = 0;
486 strcpy(buffer, &buffer[1]);
496 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
498 if (((old == 13) || (old == 10)) && (isspace(real))) {
506 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
507 cprintf("\n%s", aaa);
516 if ((strlen(aaa) + c) > (width - 5)) {
526 if ((ch == 13) || (ch == 10)) {
527 cprintf("%s\n", aaa);
534 FMTEND: cprintf("%s\n", aaa);
540 * Callback function for mime parser that simply lists the part
542 void list_this_part(char *name, char *filename, char *partnum, char *disp,
543 void *content, char *cbtype, size_t length)
546 cprintf("part=%s|%s|%s|%s|%s|%d\n",
547 name, filename, partnum, disp, cbtype, length);
552 * Callback function for mime parser that opens a section for downloading
554 void mime_download(char *name, char *filename, char *partnum, char *disp,
555 void *content, char *cbtype, size_t length)
558 /* Silently go away if there's already a download open... */
559 if (CC->download_fp != NULL)
562 /* ...or if this is not the desired section */
563 if (strcasecmp(desired_section, partnum))
566 CC->download_fp = tmpfile();
567 if (CC->download_fp == NULL)
570 fwrite(content, length, 1, CC->download_fp);
571 fflush(CC->download_fp);
572 rewind(CC->download_fp);
574 OpenCmdResult(filename, cbtype);
580 * Load a message from disk into memory.
581 * This is used by CtdlOutputMsg() and other fetch functions.
583 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
584 * using the CtdlMessageFree() function.
586 struct CtdlMessage *CtdlFetchMessage(long msgnum)
588 struct cdbdata *dmsgtext;
589 struct CtdlMessage *ret = NULL;
592 CIT_UBYTE field_header;
595 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
596 if (dmsgtext == NULL) {
599 mptr = dmsgtext->ptr;
601 /* Parse the three bytes that begin EVERY message on disk.
602 * The first is always 0xFF, the on-disk magic number.
603 * The second is the anonymous/public type byte.
604 * The third is the format type byte (vari, fixed, or MIME).
608 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
612 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
613 memset(ret, 0, sizeof(struct CtdlMessage));
615 ret->cm_magic = CTDLMESSAGE_MAGIC;
616 ret->cm_anon_type = *mptr++; /* Anon type byte */
617 ret->cm_format_type = *mptr++; /* Format type byte */
620 * The rest is zero or more arbitrary fields. Load them in.
621 * We're done when we encounter either a zero-length field or
622 * have just processed the 'M' (message text) field.
625 field_length = strlen(mptr);
626 if (field_length == 0)
628 field_header = *mptr++;
629 ret->cm_fields[field_header] = mallok(field_length);
630 strcpy(ret->cm_fields[field_header], mptr);
632 while (*mptr++ != 0); /* advance to next field */
634 } while ((field_length > 0) && (field_header != 'M'));
638 /* Always make sure there's something in the msg text field */
639 if (ret->cm_fields['M'] == NULL)
640 ret->cm_fields['M'] = strdoop("<no text>\n");
642 /* Perform "before read" hooks (aborting if any return nonzero) */
643 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
644 CtdlFreeMessage(ret);
653 * Returns 1 if the supplied pointer points to a valid Citadel message.
654 * If the pointer is NULL or the magic number check fails, returns 0.
656 int is_valid_message(struct CtdlMessage *msg) {
659 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
660 lprintf(3, "is_valid_message() -- self-check failed\n");
668 * 'Destructor' for struct CtdlMessage
670 void CtdlFreeMessage(struct CtdlMessage *msg)
674 if (is_valid_message(msg) == 0) return;
676 for (i = 0; i < 256; ++i)
677 if (msg->cm_fields[i] != NULL)
678 phree(msg->cm_fields[i]);
680 msg->cm_magic = 0; /* just in case */
686 * Callback function for mime parser that wants to display text
688 void fixed_output(char *name, char *filename, char *partnum, char *disp,
689 void *content, char *cbtype, size_t length)
696 if (!strcasecmp(cbtype, "multipart/alternative")) {
697 strcpy(ma->prefix, partnum);
698 strcat(ma->prefix, ".");
704 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
706 && (ma->did_print == 1) ) {
707 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
713 if ( (!strcasecmp(cbtype, "text/plain"))
714 || (strlen(cbtype)==0) ) {
719 if (ch==10) cprintf("\r\n");
720 else cprintf("%c", ch);
723 else if (!strcasecmp(cbtype, "text/html")) {
724 ptr = html_to_ascii(content, 80, 0);
729 if (ch==10) cprintf("\r\n");
730 else cprintf("%c", ch);
734 else if (strncasecmp(cbtype, "multipart/", 10)) {
735 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
736 partnum, filename, cbtype, length);
742 * Get a message off disk. (returns om_* values found in msgbase.h)
745 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
746 int mode, /* how would you like that message? */
747 int headers_only, /* eschew the message body? */
748 int do_proto, /* do Citadel protocol responses? */
749 int crlf /* Use CRLF newlines instead of LF? */
755 char display_name[256];
756 struct CtdlMessage *TheMessage;
758 char *nl; /* newline string */
760 /* buffers needed for RFC822 translation */
770 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
774 sprintf(mid, "%ld", msg_num);
775 nl = (crlf ? "\r\n" : "\n");
777 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
778 if (do_proto) cprintf("%d Not logged in.\n",
779 ERROR + NOT_LOGGED_IN);
780 return(om_not_logged_in);
783 /* FIX ... small security issue
784 * We need to check to make sure the requested message is actually
785 * in the current room, and set msg_ok to 1 only if it is. This
786 * functionality is currently missing because I'm in a hurry to replace
787 * broken production code with nonbroken pre-beta code. :( -- ajc
790 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
792 return(om_no_such_msg);
797 * Fetch the message from disk
799 TheMessage = CtdlFetchMessage(msg_num);
800 if (TheMessage == NULL) {
801 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
803 return(om_no_such_msg);
806 /* Are we downloading a MIME component? */
807 if (mode == MT_DOWNLOAD) {
808 if (TheMessage->cm_format_type != FMT_RFC822) {
810 cprintf("%d This is not a MIME message.\n",
812 } else if (CC->download_fp != NULL) {
813 if (do_proto) cprintf(
814 "%d You already have a download open.\n",
817 /* Parse the message text component */
818 mptr = TheMessage->cm_fields['M'];
819 mime_parser(mptr, NULL, *mime_download);
820 /* If there's no file open by this time, the requested
821 * section wasn't found, so print an error
823 if (CC->download_fp == NULL) {
824 if (do_proto) cprintf(
825 "%d Section %s not found.\n",
826 ERROR + FILE_NOT_FOUND,
830 CtdlFreeMessage(TheMessage);
831 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
834 /* now for the user-mode message reading loops */
835 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
837 /* Tell the client which format type we're using. If this is a
838 * MIME message, *lie* about it and tell the user it's fixed-format.
840 if (mode == MT_CITADEL) {
841 if (TheMessage->cm_format_type == FMT_RFC822) {
842 if (do_proto) cprintf("type=1\n");
845 if (do_proto) cprintf("type=%d\n",
846 TheMessage->cm_format_type);
850 /* nhdr=yes means that we're only displaying headers, no body */
851 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
852 if (do_proto) cprintf("nhdr=yes\n");
855 /* begin header processing loop for Citadel message format */
857 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
859 strcpy(display_name, "<unknown>");
860 if (TheMessage->cm_fields['A']) {
861 strcpy(buf, TheMessage->cm_fields['A']);
862 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
863 if (TheMessage->cm_anon_type == MES_ANON)
864 strcpy(display_name, "****");
865 else if (TheMessage->cm_anon_type == MES_AN2)
866 strcpy(display_name, "anonymous");
868 strcpy(display_name, buf);
870 && ((TheMessage->cm_anon_type == MES_ANON)
871 || (TheMessage->cm_anon_type == MES_AN2))) {
872 sprintf(&display_name[strlen(display_name)],
877 strcpy(allkeys, FORDER);
878 for (i=0; i<strlen(allkeys); ++i) {
879 k = (int) allkeys[i];
881 if (TheMessage->cm_fields[k] != NULL) {
883 if (do_proto) cprintf("%s=%s\n",
888 if (do_proto) cprintf("%s=%s\n",
890 TheMessage->cm_fields[k]
899 /* begin header processing loop for RFC822 transfer format */
904 strcpy(snode, NODENAME);
905 strcpy(lnode, HUMANNODE);
906 if (mode == MT_RFC822) {
907 cprintf("X-UIDL: %ld%s", msg_num, nl);
908 for (i = 0; i < 256; ++i) {
909 if (TheMessage->cm_fields[i]) {
910 mptr = TheMessage->cm_fields[i];
917 cprintf("Path: %s%s", mptr, nl);
920 cprintf("Subject: %s%s", mptr, nl);
926 cprintf("X-Citadel-Room: %s%s",
931 cprintf("To: %s%s", mptr, nl);
933 generate_rfc822_datestamp(datestamp,
935 cprintf("Date: %s%s", datestamp, nl);
941 for (i=0; i<strlen(suser); ++i) {
942 suser[i] = tolower(suser[i]);
943 if (!isalnum(suser[i])) suser[i]='_';
946 if (mode == MT_RFC822) {
947 if (!strcasecmp(snode, NODENAME)) {
950 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
951 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
953 if (strlen(fuser) > 0) {
954 cprintf("From: %s (%s)%s", fuser, luser, nl);
957 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
960 cprintf("Organization: %s%s", lnode, nl);
963 /* end header processing loop ... at this point, we're in the text */
965 mptr = TheMessage->cm_fields['M'];
967 /* Tell the client about the MIME parts in this message */
968 if (TheMessage->cm_format_type == FMT_RFC822) {
969 if (mode == MT_CITADEL) {
970 mime_parser(mptr, NULL, *list_this_part);
972 else if (mode == MT_MIME) { /* list parts only */
973 mime_parser(mptr, NULL, *list_this_part);
974 if (do_proto) cprintf("000\n");
975 CtdlFreeMessage(TheMessage);
978 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
979 /* FIX ... we have to put some code in here to avoid
980 * printing duplicate header information when both
981 * Citadel and RFC822 headers exist. Preference should
982 * probably be given to the RFC822 headers.
984 while (ch=*(mptr++), ch!=0) {
986 else if (ch==10) cprintf("%s", nl);
987 else cprintf("%c", ch);
989 if (do_proto) cprintf("000\n");
990 CtdlFreeMessage(TheMessage);
996 if (do_proto) cprintf("000\n");
997 CtdlFreeMessage(TheMessage);
1001 /* signify start of msg text */
1002 if (mode == MT_CITADEL)
1003 if (do_proto) cprintf("text\n");
1004 if (mode == MT_RFC822) {
1005 if (TheMessage->cm_fields['U'] == NULL) {
1006 cprintf("Subject: (no subject)%s", nl);
1011 /* If the format type on disk is 1 (fixed-format), then we want
1012 * everything to be output completely literally ... regardless of
1013 * what message transfer format is in use.
1015 if (TheMessage->cm_format_type == FMT_FIXED) {
1017 while (ch = *mptr++, ch > 0) {
1020 if ((ch == 10) || (strlen(buf) > 250)) {
1021 cprintf("%s%s", buf, nl);
1024 buf[strlen(buf) + 1] = 0;
1025 buf[strlen(buf)] = ch;
1028 if (strlen(buf) > 0)
1029 cprintf("%s%s", buf, nl);
1032 /* If the message on disk is format 0 (Citadel vari-format), we
1033 * output using the formatter at 80 columns. This is the final output
1034 * form if the transfer format is RFC822, but if the transfer format
1035 * is Citadel proprietary, it'll still work, because the indentation
1036 * for new paragraphs is correct and the client will reformat the
1037 * message to the reader's screen width.
1039 if (TheMessage->cm_format_type == FMT_CITADEL) {
1040 memfmout(80, mptr, 0);
1043 /* If the message on disk is format 4 (MIME), we've gotta hand it
1044 * off to the MIME parser. The client has already been told that
1045 * this message is format 1 (fixed format), so the callback function
1046 * we use will display those parts as-is.
1048 if (TheMessage->cm_format_type == FMT_RFC822) {
1049 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1050 memset(ma, 0, sizeof(struct ma_info));
1051 mime_parser(mptr, NULL, *fixed_output);
1054 /* now we're done */
1055 if (do_proto) cprintf("000\n");
1056 CtdlFreeMessage(TheMessage);
1063 * display a message (mode 0 - Citadel proprietary)
1065 void cmd_msg0(char *cmdbuf)
1068 int headers_only = 0;
1070 msgid = extract_long(cmdbuf, 0);
1071 headers_only = extract_int(cmdbuf, 1);
1073 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1079 * display a message (mode 2 - RFC822)
1081 void cmd_msg2(char *cmdbuf)
1084 int headers_only = 0;
1086 msgid = extract_long(cmdbuf, 0);
1087 headers_only = extract_int(cmdbuf, 1);
1089 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1095 * display a message (mode 3 - IGnet raw format - internal programs only)
1097 void cmd_msg3(char *cmdbuf)
1100 struct CtdlMessage *msg;
1103 if (CC->internal_pgm == 0) {
1104 cprintf("%d This command is for internal programs only.\n",
1109 msgnum = extract_long(cmdbuf, 0);
1110 msg = CtdlFetchMessage(msgnum);
1112 cprintf("%d Message %ld not found.\n",
1117 serialize_message(&smr, msg);
1118 CtdlFreeMessage(msg);
1121 cprintf("%d Unable to serialize message\n",
1122 ERROR+INTERNAL_ERROR);
1126 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1127 client_write(smr.ser, smr.len);
1134 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1136 void cmd_msg4(char *cmdbuf)
1140 msgid = extract_long(cmdbuf, 0);
1141 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1145 * Open a component of a MIME message as a download file
1147 void cmd_opna(char *cmdbuf)
1151 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1153 msgid = extract_long(cmdbuf, 0);
1154 extract(desired_section, cmdbuf, 1);
1156 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1161 * Save a message pointer into a specified room
1162 * (Returns 0 for success, nonzero for failure)
1163 * roomname may be NULL to use the current room
1165 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1167 char hold_rm[ROOMNAMELEN];
1168 struct cdbdata *cdbfr;
1171 long highest_msg = 0L;
1172 struct CtdlMessage *msg = NULL;
1174 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1175 roomname, msgid, flags);
1177 strcpy(hold_rm, CC->quickroom.QRname);
1179 /* We may need to check to see if this message is real */
1180 if ( (flags & SM_VERIFY_GOODNESS)
1181 || (flags & SM_DO_REPL_CHECK)
1183 msg = CtdlFetchMessage(msgid);
1184 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1187 /* Perform replication checks if necessary */
1188 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1190 if (getroom(&CC->quickroom,
1191 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1193 lprintf(9, "No such room <%s>\n", roomname);
1194 if (msg != NULL) CtdlFreeMessage(msg);
1195 return(ERROR + ROOM_NOT_FOUND);
1198 if (ReplicationChecks(msg) != 0) {
1199 getroom(&CC->quickroom, hold_rm);
1200 if (msg != NULL) CtdlFreeMessage(msg);
1201 lprintf(9, "Did replication, and newer exists\n");
1206 /* Now the regular stuff */
1207 if (lgetroom(&CC->quickroom,
1208 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1210 lprintf(9, "No such room <%s>\n", roomname);
1211 if (msg != NULL) CtdlFreeMessage(msg);
1212 return(ERROR + ROOM_NOT_FOUND);
1215 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1216 if (cdbfr == NULL) {
1220 msglist = mallok(cdbfr->len);
1221 if (msglist == NULL)
1222 lprintf(3, "ERROR malloc msglist!\n");
1223 num_msgs = cdbfr->len / sizeof(long);
1224 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1229 /* Make sure the message doesn't already exist in this room. It
1230 * is absolutely taboo to have more than one reference to the same
1231 * message in a room.
1233 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1234 if (msglist[i] == msgid) {
1235 lputroom(&CC->quickroom); /* unlock the room */
1236 getroom(&CC->quickroom, hold_rm);
1237 if (msg != NULL) CtdlFreeMessage(msg);
1238 return(ERROR + ALREADY_EXISTS);
1242 /* Now add the new message */
1244 msglist = reallok(msglist,
1245 (num_msgs * sizeof(long)));
1247 if (msglist == NULL) {
1248 lprintf(3, "ERROR: can't realloc message list!\n");
1250 msglist[num_msgs - 1] = msgid;
1252 /* Sort the message list, so all the msgid's are in order */
1253 num_msgs = sort_msglist(msglist, num_msgs);
1255 /* Determine the highest message number */
1256 highest_msg = msglist[num_msgs - 1];
1258 /* Write it back to disk. */
1259 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1260 msglist, num_msgs * sizeof(long));
1262 /* Free up the memory we used. */
1265 /* Update the highest-message pointer and unlock the room. */
1266 CC->quickroom.QRhighest = highest_msg;
1267 lputroom(&CC->quickroom);
1268 getroom(&CC->quickroom, hold_rm);
1270 /* Bump the reference count for this message. */
1271 if ((flags & SM_DONT_BUMP_REF)==0) {
1272 AdjRefCount(msgid, +1);
1275 /* Return success. */
1276 if (msg != NULL) CtdlFreeMessage(msg);
1283 * Message base operation to send a message to the master file
1284 * (returns new message number)
1286 * This is the back end for CtdlSaveMsg() and should not be directly
1287 * called by server-side modules.
1290 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1291 int generate_id, /* generate 'I' field? */
1292 FILE *save_a_copy) /* save a copy to disk? */
1299 /* Get a new message number */
1300 newmsgid = get_new_message_number();
1301 sprintf(msgidbuf, "%ld", newmsgid);
1304 msg->cm_fields['I'] = strdoop(msgidbuf);
1307 serialize_message(&smr, msg);
1310 cprintf("%d Unable to serialize message\n",
1311 ERROR+INTERNAL_ERROR);
1315 /* Write our little bundle of joy into the message base */
1316 begin_critical_section(S_MSGMAIN);
1317 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1318 smr.ser, smr.len) < 0) {
1319 lprintf(2, "Can't store message\n");
1324 end_critical_section(S_MSGMAIN);
1326 /* If the caller specified that a copy should be saved to a particular
1327 * file handle, do that now too.
1329 if (save_a_copy != NULL) {
1330 fwrite(smr.ser, smr.len, 1, save_a_copy);
1333 /* Free the memory we used for the serialized message */
1336 /* Return the *local* message ID to the caller
1337 * (even if we're storing an incoming network message)
1345 * Serialize a struct CtdlMessage into the format used on disk and network.
1347 * This function loads up a "struct ser_ret" (defined in server.h) which
1348 * contains the length of the serialized message and a pointer to the
1349 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1351 void serialize_message(struct ser_ret *ret, /* return values */
1352 struct CtdlMessage *msg) /* unserialized msg */
1356 static char *forder = FORDER;
1358 if (is_valid_message(msg) == 0) return; /* self check */
1361 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1362 ret->len = ret->len +
1363 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1365 lprintf(9, "calling malloc\n");
1366 ret->ser = mallok(ret->len);
1367 if (ret->ser == NULL) {
1373 ret->ser[1] = msg->cm_anon_type;
1374 ret->ser[2] = msg->cm_format_type;
1377 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1378 ret->ser[wlen++] = (char)forder[i];
1379 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1380 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1382 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1391 * Back end for the ReplicationChecks() function
1393 void check_repl(long msgnum) {
1394 struct CtdlMessage *msg;
1395 time_t timestamp = (-1L);
1397 lprintf(9, "check_repl() found message %ld\n", msgnum);
1398 msg = CtdlFetchMessage(msgnum);
1399 if (msg == NULL) return;
1400 if (msg->cm_fields['T'] != NULL) {
1401 timestamp = atol(msg->cm_fields['T']);
1403 CtdlFreeMessage(msg);
1405 if (timestamp > msg_repl->highest) {
1406 msg_repl->highest = timestamp; /* newer! */
1407 lprintf(9, "newer!\n");
1410 lprintf(9, "older!\n");
1412 /* Existing isn't newer? Then delete the old one(s). */
1413 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1418 * Check to see if any messages already exist which carry the same Extended ID
1422 * -> With older timestamps: delete them and return 0. Message will be saved.
1423 * -> With newer timestamps: return 1. Message save will be aborted.
1425 int ReplicationChecks(struct CtdlMessage *msg) {
1426 struct CtdlMessage *template;
1429 lprintf(9, "ReplicationChecks() started\n");
1430 /* No extended id? Don't do anything. */
1431 if (msg->cm_fields['E'] == NULL) return 0;
1432 if (strlen(msg->cm_fields['E']) == 0) return 0;
1433 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1435 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1436 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1437 msg_repl->highest = atol(msg->cm_fields['T']);
1439 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1440 memset(template, 0, sizeof(struct CtdlMessage));
1441 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1443 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1445 /* If a newer message exists with the same Extended ID, abort
1448 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1452 CtdlFreeMessage(template);
1453 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1461 * Save a message to disk
1463 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1464 char *rec, /* Recipient (mail) */
1465 char *force, /* force a particular room? */
1466 int mailtype, /* local or remote type */
1467 int generate_id) /* 1 = generate 'I' field */
1470 char hold_rm[ROOMNAMELEN];
1471 char actual_rm[ROOMNAMELEN];
1472 char force_room[ROOMNAMELEN];
1473 char content_type[256]; /* We have to learn this */
1474 char recipient[256];
1477 struct usersupp userbuf;
1479 struct SuppMsgInfo smi;
1480 FILE *network_fp = NULL;
1481 static int seqnum = 1;
1482 struct CtdlMessage *imsg;
1485 lprintf(9, "CtdlSaveMsg() called\n");
1486 if (is_valid_message(msg) == 0) return(-1); /* self check */
1488 /* If this message has no timestamp, we take the liberty of
1489 * giving it one, right now.
1491 if (msg->cm_fields['T'] == NULL) {
1492 lprintf(9, "Generating timestamp\n");
1493 sprintf(aaa, "%ld", time(NULL));
1494 msg->cm_fields['T'] = strdoop(aaa);
1497 /* If this message has no path, we generate one.
1499 if (msg->cm_fields['P'] == NULL) {
1500 lprintf(9, "Generating path\n");
1501 if (msg->cm_fields['A'] != NULL) {
1502 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1503 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1504 if (isspace(msg->cm_fields['P'][a])) {
1505 msg->cm_fields['P'][a] = ' ';
1510 msg->cm_fields['P'] = strdoop("unknown");
1514 strcpy(force_room, force);
1516 /* Strip non-printable characters out of the recipient name */
1517 strcpy(recipient, rec);
1518 for (a = 0; a < strlen(recipient); ++a)
1519 if (!isprint(recipient[a]))
1520 strcpy(&recipient[a], &recipient[a + 1]);
1522 /* Learn about what's inside, because it's what's inside that counts */
1523 lprintf(9, "Learning what's inside\n");
1524 if (msg->cm_fields['M'] == NULL) {
1525 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1528 switch (msg->cm_format_type) {
1530 strcpy(content_type, "text/x-citadel-variformat");
1533 strcpy(content_type, "text/plain");
1536 strcpy(content_type, "text/plain");
1537 /* advance past header fields */
1538 mptr = msg->cm_fields['M'];
1541 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1542 safestrncpy(content_type, mptr,
1543 sizeof(content_type));
1544 strcpy(content_type, &content_type[14]);
1545 for (a = 0; a < strlen(content_type); ++a)
1546 if ((content_type[a] == ';')
1547 || (content_type[a] == ' ')
1548 || (content_type[a] == 13)
1549 || (content_type[a] == 10))
1550 content_type[a] = 0;
1557 /* Goto the correct room */
1558 lprintf(9, "Switching rooms\n");
1559 strcpy(hold_rm, CC->quickroom.QRname);
1560 strcpy(actual_rm, CC->quickroom.QRname);
1562 /* If the user is a twit, move to the twit room for posting */
1563 lprintf(9, "Handling twit stuff\n");
1565 if (CC->usersupp.axlevel == 2) {
1566 strcpy(hold_rm, actual_rm);
1567 strcpy(actual_rm, config.c_twitroom);
1571 /* ...or if this message is destined for Aide> then go there. */
1572 if (strlen(force_room) > 0) {
1573 strcpy(actual_rm, force_room);
1576 lprintf(9, "Possibly relocating\n");
1577 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1578 getroom(&CC->quickroom, actual_rm);
1582 * If this message has no O (room) field, generate one.
1584 if (msg->cm_fields['O'] == NULL) {
1585 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1588 /* Perform "before save" hooks (aborting if any return nonzero) */
1589 lprintf(9, "Performing before-save hooks\n");
1590 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1592 /* If this message has an Extended ID, perform replication checks */
1593 lprintf(9, "Performing replication checks\n");
1594 if (ReplicationChecks(msg) > 0) return(-1);
1596 /* Network mail - send a copy to the network program. */
1597 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1598 lprintf(9, "Sending network spool\n");
1599 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1600 (long) getpid(), CC->cs_pid, ++seqnum);
1601 lprintf(9, "Saving a copy to %s\n", aaa);
1602 network_fp = fopen(aaa, "ab+");
1603 if (network_fp == NULL)
1604 lprintf(2, "ERROR: %s\n", strerror(errno));
1607 /* Save it to disk */
1608 lprintf(9, "Saving to disk\n");
1609 newmsgid = send_message(msg, generate_id, network_fp);
1610 if (network_fp != NULL) {
1612 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1615 if (newmsgid <= 0L) return(-1);
1617 /* Write a supplemental message info record. This doesn't have to
1618 * be a critical section because nobody else knows about this message
1621 lprintf(9, "Creating SuppMsgInfo record\n");
1622 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1623 smi.smi_msgnum = newmsgid;
1624 smi.smi_refcount = 0;
1625 safestrncpy(smi.smi_content_type, content_type, 64);
1626 PutSuppMsgInfo(&smi);
1628 /* Now figure out where to store the pointers */
1629 lprintf(9, "Storing pointers\n");
1631 /* If this is being done by the networker delivering a private
1632 * message, we want to BYPASS saving the sender's copy (because there
1633 * is no local sender; it would otherwise go to the Trashcan).
1635 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1636 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1639 /* For internet mail, drop a copy in the outbound queue room */
1640 if (mailtype == MES_INTERNET) {
1641 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1644 /* Bump this user's messages posted counter. */
1645 lprintf(9, "Updating user\n");
1646 lgetuser(&CC->usersupp, CC->curr_user);
1647 CC->usersupp.posted = CC->usersupp.posted + 1;
1648 lputuser(&CC->usersupp);
1650 /* If this is private, local mail, make a copy in the
1651 * recipient's mailbox and bump the reference count.
1653 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1654 if (getuser(&userbuf, recipient) == 0) {
1655 lprintf(9, "Delivering private mail\n");
1656 MailboxName(actual_rm, &userbuf, MAILROOM);
1657 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1661 /* Perform "after save" hooks */
1662 lprintf(9, "Performing after-save hooks\n");
1663 PerformMessageHooks(msg, EVT_AFTERSAVE);
1666 lprintf(9, "Returning to original room\n");
1667 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1668 getroom(&CC->quickroom, hold_rm);
1670 /* For internet mail, generate delivery instructions
1671 * (Yes, this is recursive! Deal with it!)
1673 if (mailtype == MES_INTERNET) {
1674 lprintf(9, "Generating delivery instructions\n");
1675 instr = mallok(2048);
1677 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1679 SPOOLMIME, newmsgid, time(NULL), recipient );
1681 imsg = mallok(sizeof(struct CtdlMessage));
1682 memset(imsg, 0, sizeof(struct CtdlMessage));
1683 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1684 imsg->cm_anon_type = MES_NORMAL;
1685 imsg->cm_format_type = FMT_RFC822;
1686 imsg->cm_fields['A'] = strdoop("Citadel");
1687 imsg->cm_fields['M'] = instr;
1688 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1689 CtdlFreeMessage(imsg);
1698 * Convenience function for generating small administrative messages.
1700 void quickie_message(char *from, char *to, char *room, char *text)
1702 struct CtdlMessage *msg;
1704 msg = mallok(sizeof(struct CtdlMessage));
1705 memset(msg, 0, sizeof(struct CtdlMessage));
1706 msg->cm_magic = CTDLMESSAGE_MAGIC;
1707 msg->cm_anon_type = MES_NORMAL;
1708 msg->cm_format_type = 0;
1709 msg->cm_fields['A'] = strdoop(from);
1710 msg->cm_fields['O'] = strdoop(room);
1711 msg->cm_fields['N'] = strdoop(NODENAME);
1713 msg->cm_fields['R'] = strdoop(to);
1714 msg->cm_fields['M'] = strdoop(text);
1716 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1717 CtdlFreeMessage(msg);
1718 syslog(LOG_NOTICE, text);
1724 * Back end function used by make_message() and similar functions
1726 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1727 size_t maxlen, /* maximum message length */
1728 char *exist /* if non-null, append to it;
1729 exist is ALWAYS freed */
1732 size_t message_len = 0;
1733 size_t buffer_len = 0;
1737 if (exist == NULL) {
1741 m = reallok(exist, strlen(exist) + 4096);
1742 if (m == NULL) phree(exist);
1745 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1752 /* read in the lines of message text one by one */
1754 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1756 /* augment the buffer if we have to */
1757 if ((message_len + strlen(buf) + 2) > buffer_len) {
1758 lprintf(9, "realloking\n");
1759 ptr = reallok(m, (buffer_len * 2) );
1760 if (ptr == NULL) { /* flush if can't allocate */
1761 while ( (client_gets(buf)>0) &&
1762 strcmp(buf, terminator)) ;;
1765 buffer_len = (buffer_len * 2);
1768 lprintf(9, "buffer_len is %d\n", buffer_len);
1772 if (append == NULL) append = m;
1773 while (strlen(append) > 0) ++append;
1774 strcpy(append, buf);
1775 strcat(append, "\n");
1776 message_len = message_len + strlen(buf) + 1;
1778 /* if we've hit the max msg length, flush the rest */
1779 if (message_len >= maxlen) {
1780 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1791 * Build a binary message to be saved on disk.
1794 struct CtdlMessage *make_message(
1795 struct usersupp *author, /* author's usersupp structure */
1796 char *recipient, /* NULL if it's not mail */
1797 char *room, /* room where it's going */
1798 int type, /* see MES_ types in header file */
1799 int net_type, /* see MES_ types in header file */
1800 int format_type, /* local or remote (see citadel.h) */
1801 char *fake_name) /* who we're masquerading as */
1807 struct CtdlMessage *msg;
1809 msg = mallok(sizeof(struct CtdlMessage));
1810 memset(msg, 0, sizeof(struct CtdlMessage));
1811 msg->cm_magic = CTDLMESSAGE_MAGIC;
1812 msg->cm_anon_type = type;
1813 msg->cm_format_type = format_type;
1815 /* Don't confuse the poor folks if it's not routed mail. */
1816 strcpy(dest_node, "");
1818 /* If net_type is MES_BINARY, split out the destination node. */
1819 if (net_type == MES_BINARY) {
1820 strcpy(dest_node, NODENAME);
1821 for (a = 0; a < strlen(recipient); ++a) {
1822 if (recipient[a] == '@') {
1824 strcpy(dest_node, &recipient[a + 1]);
1829 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1830 if (net_type == MES_INTERNET) {
1831 strcpy(dest_node, "internet");
1834 while (isspace(recipient[strlen(recipient) - 1]))
1835 recipient[strlen(recipient) - 1] = 0;
1837 sprintf(buf, "cit%ld", author->usernum); /* Path */
1838 msg->cm_fields['P'] = strdoop(buf);
1840 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1841 msg->cm_fields['T'] = strdoop(buf);
1843 if (fake_name[0]) /* author */
1844 msg->cm_fields['A'] = strdoop(fake_name);
1846 msg->cm_fields['A'] = strdoop(author->fullname);
1848 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1849 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1851 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1853 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1854 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1856 if (recipient[0] != 0)
1857 msg->cm_fields['R'] = strdoop(recipient);
1858 if (dest_node[0] != 0)
1859 msg->cm_fields['D'] = strdoop(dest_node);
1862 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1863 config.c_maxmsglen, NULL);
1874 * message entry - mode 0 (normal)
1876 void cmd_ent0(char *entargs)
1879 char recipient[256];
1881 int format_type = 0;
1882 char newusername[256];
1883 struct CtdlMessage *msg;
1887 struct usersupp tempUS;
1890 post = extract_int(entargs, 0);
1891 extract(recipient, entargs, 1);
1892 anon_flag = extract_int(entargs, 2);
1893 format_type = extract_int(entargs, 3);
1895 /* first check to make sure the request is valid. */
1897 if (!(CC->logged_in)) {
1898 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1901 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1902 cprintf("%d Need to be validated to enter ",
1903 ERROR + HIGHER_ACCESS_REQUIRED);
1904 cprintf("(except in %s> to sysop)\n", MAILROOM);
1907 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1908 cprintf("%d Need net privileges to enter here.\n",
1909 ERROR + HIGHER_ACCESS_REQUIRED);
1912 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1913 cprintf("%d Sorry, this is a read-only room.\n",
1914 ERROR + HIGHER_ACCESS_REQUIRED);
1921 if (CC->usersupp.axlevel < 6) {
1922 cprintf("%d You don't have permission to masquerade.\n",
1923 ERROR + HIGHER_ACCESS_REQUIRED);
1926 extract(newusername, entargs, 4);
1927 memset(CC->fake_postname, 0, 32);
1928 strcpy(CC->fake_postname, newusername);
1929 cprintf("%d Ok\n", OK);
1932 CC->cs_flags |= CS_POSTING;
1935 if (CC->quickroom.QRflags & QR_MAILBOX) {
1936 if (CC->usersupp.axlevel >= 2) {
1937 strcpy(buf, recipient);
1939 strcpy(buf, "sysop");
1940 e = alias(buf); /* alias and mail type */
1941 if ((buf[0] == 0) || (e == MES_ERROR)) {
1942 cprintf("%d Unknown address - cannot send message.\n",
1943 ERROR + NO_SUCH_USER);
1946 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1947 cprintf("%d Net privileges required for network mail.\n",
1948 ERROR + HIGHER_ACCESS_REQUIRED);
1951 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1952 && ((CC->usersupp.flags & US_INTERNET) == 0)
1953 && (!CC->internal_pgm)) {
1954 cprintf("%d You don't have access to Internet mail.\n",
1955 ERROR + HIGHER_ACCESS_REQUIRED);
1958 if (!strcasecmp(buf, "sysop")) {
1963 goto SKFALL; /* don't search local file */
1964 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1965 cprintf("%d Can't send mail to yourself!\n",
1966 ERROR + NO_SUCH_USER);
1969 /* Check to make sure the user exists; also get the correct
1970 * upper/lower casing of the name.
1972 a = getuser(&tempUS, buf);
1974 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1977 strcpy(buf, tempUS.fullname);
1980 SKFALL: b = MES_NORMAL;
1981 if (CC->quickroom.QRflags & QR_ANONONLY)
1983 if (CC->quickroom.QRflags & QR_ANONOPT) {
1987 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1990 /* If we're only checking the validity of the request, return
1991 * success without creating the message.
1994 cprintf("%d %s\n", OK, buf);
1998 cprintf("%d send message\n", SEND_LISTING);
2000 /* Read in the message from the client. */
2001 if (CC->fake_postname[0])
2002 msg = make_message(&CC->usersupp, buf,
2003 CC->quickroom.QRname, b, e, format_type,
2005 else if (CC->fake_username[0])
2006 msg = make_message(&CC->usersupp, buf,
2007 CC->quickroom.QRname, b, e, format_type,
2010 msg = make_message(&CC->usersupp, buf,
2011 CC->quickroom.QRname, b, e, format_type, "");
2014 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2015 CtdlFreeMessage(msg);
2016 CC->fake_postname[0] = '\0';
2023 * message entry - mode 3 (raw)
2025 void cmd_ent3(char *entargs)
2031 unsigned char ch, which_field;
2032 struct usersupp tempUS;
2034 struct CtdlMessage *msg;
2037 if (CC->internal_pgm == 0) {
2038 cprintf("%d This command is for internal programs only.\n",
2043 /* See if there's a recipient, but make sure it's a real one */
2044 extract(recp, entargs, 1);
2045 for (a = 0; a < strlen(recp); ++a)
2046 if (!isprint(recp[a]))
2047 strcpy(&recp[a], &recp[a + 1]);
2048 while (isspace(recp[0]))
2049 strcpy(recp, &recp[1]);
2050 while (isspace(recp[strlen(recp) - 1]))
2051 recp[strlen(recp) - 1] = 0;
2053 /* If we're in Mail, check the recipient */
2054 if (strlen(recp) > 0) {
2055 e = alias(recp); /* alias and mail type */
2056 if ((recp[0] == 0) || (e == MES_ERROR)) {
2057 cprintf("%d Unknown address - cannot send message.\n",
2058 ERROR + NO_SUCH_USER);
2061 if (e == MES_LOCAL) {
2062 a = getuser(&tempUS, recp);
2064 cprintf("%d No such user.\n",
2065 ERROR + NO_SUCH_USER);
2071 /* At this point, message has been approved. */
2072 if (extract_int(entargs, 0) == 0) {
2073 cprintf("%d OK to send\n", OK);
2077 msglen = extract_long(entargs, 2);
2078 msg = mallok(sizeof(struct CtdlMessage));
2080 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2084 memset(msg, 0, sizeof(struct CtdlMessage));
2085 tempbuf = mallok(msglen);
2086 if (tempbuf == NULL) {
2087 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2092 cprintf("%d %ld\n", SEND_BINARY, msglen);
2094 client_read(&ch, 1); /* 0xFF magic number */
2095 msg->cm_magic = CTDLMESSAGE_MAGIC;
2096 client_read(&ch, 1); /* anon type */
2097 msg->cm_anon_type = ch;
2098 client_read(&ch, 1); /* format type */
2099 msg->cm_format_type = ch;
2100 msglen = msglen - 3;
2102 while (msglen > 0) {
2103 client_read(&which_field, 1);
2104 if (!isalpha(which_field)) valid_msg = 0;
2108 client_read(&ch, 1);
2110 a = strlen(tempbuf);
2113 } while ( (ch != 0) && (msglen > 0) );
2115 msg->cm_fields[which_field] = strdoop(tempbuf);
2118 msg->cm_flags = CM_SKIP_HOOKS;
2119 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2120 CtdlFreeMessage(msg);
2126 * API function to delete messages which match a set of criteria
2127 * (returns the actual number of messages deleted)
2129 int CtdlDeleteMessages(char *room_name, /* which room */
2130 long dmsgnum, /* or "0" for any */
2131 char *content_type /* or NULL for any */
2135 struct quickroom qrbuf;
2136 struct cdbdata *cdbfr;
2137 long *msglist = NULL;
2140 int num_deleted = 0;
2142 struct SuppMsgInfo smi;
2144 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2145 room_name, dmsgnum, content_type);
2147 /* get room record, obtaining a lock... */
2148 if (lgetroom(&qrbuf, room_name) != 0) {
2149 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2151 return (0); /* room not found */
2153 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2155 if (cdbfr != NULL) {
2156 msglist = mallok(cdbfr->len);
2157 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2158 num_msgs = cdbfr->len / sizeof(long);
2162 for (i = 0; i < num_msgs; ++i) {
2165 /* Set/clear a bit for each criterion */
2167 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2168 delete_this |= 0x01;
2170 if (content_type == NULL) {
2171 delete_this |= 0x02;
2173 GetSuppMsgInfo(&smi, msglist[i]);
2174 if (!strcasecmp(smi.smi_content_type,
2176 delete_this |= 0x02;
2180 /* Delete message only if all bits are set */
2181 if (delete_this == 0x03) {
2182 AdjRefCount(msglist[i], -1);
2188 num_msgs = sort_msglist(msglist, num_msgs);
2189 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2190 msglist, (num_msgs * sizeof(long)));
2192 qrbuf.QRhighest = msglist[num_msgs - 1];
2196 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2197 return (num_deleted);
2203 * Delete message from current room
2205 void cmd_dele(char *delstr)
2210 getuser(&CC->usersupp, CC->curr_user);
2211 if ((CC->usersupp.axlevel < 6)
2212 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2213 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2214 && (!(CC->internal_pgm))) {
2215 cprintf("%d Higher access required.\n",
2216 ERROR + HIGHER_ACCESS_REQUIRED);
2219 delnum = extract_long(delstr, 0);
2221 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2224 cprintf("%d %d message%s deleted.\n", OK,
2225 num_deleted, ((num_deleted != 1) ? "s" : ""));
2227 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2233 * move or copy a message to another room
2235 void cmd_move(char *args)
2239 struct quickroom qtemp;
2243 num = extract_long(args, 0);
2244 extract(targ, args, 1);
2245 targ[ROOMNAMELEN - 1] = 0;
2246 is_copy = extract_int(args, 2);
2248 getuser(&CC->usersupp, CC->curr_user);
2249 if ((CC->usersupp.axlevel < 6)
2250 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2251 cprintf("%d Higher access required.\n",
2252 ERROR + HIGHER_ACCESS_REQUIRED);
2256 if (getroom(&qtemp, targ) != 0) {
2257 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2261 err = CtdlSaveMsgPointerInRoom(targ, num,
2262 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2264 cprintf("%d Cannot store message in %s: error %d\n",
2269 /* Now delete the message from the source room,
2270 * if this is a 'move' rather than a 'copy' operation.
2272 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2274 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2280 * GetSuppMsgInfo() - Get the supplementary record for a message
2282 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2285 struct cdbdata *cdbsmi;
2288 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2289 smibuf->smi_msgnum = msgnum;
2290 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2292 /* Use the negative of the message number for its supp record index */
2293 TheIndex = (0L - msgnum);
2295 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2296 if (cdbsmi == NULL) {
2297 return; /* record not found; go with defaults */
2299 memcpy(smibuf, cdbsmi->ptr,
2300 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2301 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2308 * PutSuppMsgInfo() - (re)write supplementary record for a message
2310 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2314 /* Use the negative of the message number for its supp record index */
2315 TheIndex = (0L - smibuf->smi_msgnum);
2317 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2318 smibuf->smi_msgnum, smibuf->smi_refcount);
2320 cdb_store(CDB_MSGMAIN,
2321 &TheIndex, sizeof(long),
2322 smibuf, sizeof(struct SuppMsgInfo));
2327 * AdjRefCount - change the reference count for a message;
2328 * delete the message if it reaches zero
2330 void AdjRefCount(long msgnum, int incr)
2333 struct SuppMsgInfo smi;
2336 /* This is a *tight* critical section; please keep it that way, as
2337 * it may get called while nested in other critical sections.
2338 * Complicating this any further will surely cause deadlock!
2340 begin_critical_section(S_SUPPMSGMAIN);
2341 GetSuppMsgInfo(&smi, msgnum);
2342 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2343 msgnum, smi.smi_refcount);
2344 smi.smi_refcount += incr;
2345 PutSuppMsgInfo(&smi);
2346 end_critical_section(S_SUPPMSGMAIN);
2347 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2348 msgnum, smi.smi_refcount);
2350 /* If the reference count is now zero, delete the message
2351 * (and its supplementary record as well).
2353 if (smi.smi_refcount == 0) {
2354 lprintf(9, "Deleting message <%ld>\n", msgnum);
2356 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2357 delnum = (0L - msgnum);
2358 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2363 * Write a generic object to this room
2365 * Note: this could be much more efficient. Right now we use two temporary
2366 * files, and still pull the message into memory as with all others.
2368 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2369 char *content_type, /* MIME type of this object */
2370 char *tempfilename, /* Where to fetch it from */
2371 struct usersupp *is_mailbox, /* Mailbox room? */
2372 int is_binary, /* Is encoding necessary? */
2373 int is_unique, /* Del others of this type? */
2374 unsigned int flags /* Internal save flags */
2379 char filename[PATH_MAX];
2382 struct quickroom qrbuf;
2383 char roomname[ROOMNAMELEN];
2384 struct CtdlMessage *msg;
2387 if (is_mailbox != NULL)
2388 MailboxName(roomname, is_mailbox, req_room);
2390 safestrncpy(roomname, req_room, sizeof(roomname));
2391 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2393 strcpy(filename, tmpnam(NULL));
2394 fp = fopen(filename, "w");
2398 tempfp = fopen(tempfilename, "r");
2399 if (tempfp == NULL) {
2405 fprintf(fp, "Content-type: %s\n", content_type);
2406 lprintf(9, "Content-type: %s\n", content_type);
2408 if (is_binary == 0) {
2409 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2410 while (ch = getc(tempfp), ch > 0)
2416 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2419 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2420 tempfilename, filename);
2424 lprintf(9, "Allocating\n");
2425 msg = mallok(sizeof(struct CtdlMessage));
2426 memset(msg, 0, sizeof(struct CtdlMessage));
2427 msg->cm_magic = CTDLMESSAGE_MAGIC;
2428 msg->cm_anon_type = MES_NORMAL;
2429 msg->cm_format_type = 4;
2430 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2431 msg->cm_fields['O'] = strdoop(req_room);
2432 msg->cm_fields['N'] = strdoop(config.c_nodename);
2433 msg->cm_fields['H'] = strdoop(config.c_humannode);
2434 msg->cm_flags = flags;
2436 lprintf(9, "Loading\n");
2437 fp = fopen(filename, "rb");
2438 fseek(fp, 0L, SEEK_END);
2441 msg->cm_fields['M'] = mallok(len);
2442 fread(msg->cm_fields['M'], len, 1, fp);
2446 /* Create the requested room if we have to. */
2447 if (getroom(&qrbuf, roomname) != 0) {
2448 create_room(roomname,
2449 ( (is_mailbox != NULL) ? 4 : 3 ),
2452 /* If the caller specified this object as unique, delete all
2453 * other objects of this type that are currently in the room.
2456 lprintf(9, "Deleted %d other msgs of this type\n",
2457 CtdlDeleteMessages(roomname, 0L, content_type));
2459 /* Now write the data */
2460 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2461 CtdlFreeMessage(msg);
2469 void CtdlGetSysConfigBackend(long msgnum) {
2470 config_msgnum = msgnum;
2474 char *CtdlGetSysConfig(char *sysconfname) {
2475 char hold_rm[ROOMNAMELEN];
2478 struct CtdlMessage *msg;
2481 strcpy(hold_rm, CC->quickroom.QRname);
2482 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2483 getroom(&CC->quickroom, hold_rm);
2488 /* We want the last (and probably only) config in this room */
2489 begin_critical_section(S_CONFIG);
2490 config_msgnum = (-1L);
2491 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2492 CtdlGetSysConfigBackend);
2493 msgnum = config_msgnum;
2494 end_critical_section(S_CONFIG);
2500 msg = CtdlFetchMessage(msgnum);
2502 conf = strdoop(msg->cm_fields['M']);
2503 CtdlFreeMessage(msg);
2510 getroom(&CC->quickroom, hold_rm);
2512 lprintf(9, "eggstracting...\n");
2513 if (conf != NULL) do {
2514 extract_token(buf, conf, 0, '\n');
2515 lprintf(9, "eggstracted <%s>\n", buf);
2516 strcpy(conf, &conf[strlen(buf)+1]);
2517 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2522 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2523 char temp[PATH_MAX];
2526 strcpy(temp, tmpnam(NULL));
2528 fp = fopen(temp, "w");
2529 if (fp == NULL) return;
2530 fprintf(fp, "%s", sysconfdata);
2533 /* this handy API function does all the work for us */
2534 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);