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) {
1008 /* If the format type on disk is 1 (fixed-format), then we want
1009 * everything to be output completely literally ... regardless of
1010 * what message transfer format is in use.
1012 if (TheMessage->cm_format_type == FMT_FIXED) {
1014 while (ch = *mptr++, ch > 0) {
1017 if ((ch == 10) || (strlen(buf) > 250)) {
1018 cprintf("%s%s", buf, nl);
1021 buf[strlen(buf) + 1] = 0;
1022 buf[strlen(buf)] = ch;
1025 if (strlen(buf) > 0)
1026 cprintf("%s%s", buf, nl);
1029 /* If the message on disk is format 0 (Citadel vari-format), we
1030 * output using the formatter at 80 columns. This is the final output
1031 * form if the transfer format is RFC822, but if the transfer format
1032 * is Citadel proprietary, it'll still work, because the indentation
1033 * for new paragraphs is correct and the client will reformat the
1034 * message to the reader's screen width.
1036 if (TheMessage->cm_format_type == FMT_CITADEL) {
1037 memfmout(80, mptr, 0);
1040 /* If the message on disk is format 4 (MIME), we've gotta hand it
1041 * off to the MIME parser. The client has already been told that
1042 * this message is format 1 (fixed format), so the callback function
1043 * we use will display those parts as-is.
1045 if (TheMessage->cm_format_type == FMT_RFC822) {
1046 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1047 memset(ma, 0, sizeof(struct ma_info));
1048 mime_parser(mptr, NULL, *fixed_output);
1051 /* now we're done */
1052 if (do_proto) cprintf("000\n");
1053 CtdlFreeMessage(TheMessage);
1060 * display a message (mode 0 - Citadel proprietary)
1062 void cmd_msg0(char *cmdbuf)
1065 int headers_only = 0;
1067 msgid = extract_long(cmdbuf, 0);
1068 headers_only = extract_int(cmdbuf, 1);
1070 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1076 * display a message (mode 2 - RFC822)
1078 void cmd_msg2(char *cmdbuf)
1081 int headers_only = 0;
1083 msgid = extract_long(cmdbuf, 0);
1084 headers_only = extract_int(cmdbuf, 1);
1086 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1092 * display a message (mode 3 - IGnet raw format - internal programs only)
1094 void cmd_msg3(char *cmdbuf)
1097 struct CtdlMessage *msg;
1100 if (CC->internal_pgm == 0) {
1101 cprintf("%d This command is for internal programs only.\n",
1106 msgnum = extract_long(cmdbuf, 0);
1107 msg = CtdlFetchMessage(msgnum);
1109 cprintf("%d Message %ld not found.\n",
1114 serialize_message(&smr, msg);
1115 CtdlFreeMessage(msg);
1118 cprintf("%d Unable to serialize message\n",
1119 ERROR+INTERNAL_ERROR);
1123 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1124 client_write(smr.ser, smr.len);
1131 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1133 void cmd_msg4(char *cmdbuf)
1137 msgid = extract_long(cmdbuf, 0);
1138 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1142 * Open a component of a MIME message as a download file
1144 void cmd_opna(char *cmdbuf)
1148 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1150 msgid = extract_long(cmdbuf, 0);
1151 extract(desired_section, cmdbuf, 1);
1153 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1158 * Save a message pointer into a specified room
1159 * (Returns 0 for success, nonzero for failure)
1160 * roomname may be NULL to use the current room
1162 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1164 char hold_rm[ROOMNAMELEN];
1165 struct cdbdata *cdbfr;
1168 long highest_msg = 0L;
1169 struct CtdlMessage *msg = NULL;
1171 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1172 roomname, msgid, flags);
1174 strcpy(hold_rm, CC->quickroom.QRname);
1176 /* We may need to check to see if this message is real */
1177 if ( (flags & SM_VERIFY_GOODNESS)
1178 || (flags & SM_DO_REPL_CHECK)
1180 msg = CtdlFetchMessage(msgid);
1181 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1184 /* Perform replication checks if necessary */
1185 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1187 if (getroom(&CC->quickroom,
1188 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1190 lprintf(9, "No such room <%s>\n", roomname);
1191 if (msg != NULL) CtdlFreeMessage(msg);
1192 return(ERROR + ROOM_NOT_FOUND);
1195 if (ReplicationChecks(msg) != 0) {
1196 getroom(&CC->quickroom, hold_rm);
1197 if (msg != NULL) CtdlFreeMessage(msg);
1198 lprintf(9, "Did replication, and newer exists\n");
1203 /* Now the regular stuff */
1204 if (lgetroom(&CC->quickroom,
1205 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1207 lprintf(9, "No such room <%s>\n", roomname);
1208 if (msg != NULL) CtdlFreeMessage(msg);
1209 return(ERROR + ROOM_NOT_FOUND);
1212 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1213 if (cdbfr == NULL) {
1217 msglist = mallok(cdbfr->len);
1218 if (msglist == NULL)
1219 lprintf(3, "ERROR malloc msglist!\n");
1220 num_msgs = cdbfr->len / sizeof(long);
1221 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1226 /* Make sure the message doesn't already exist in this room. It
1227 * is absolutely taboo to have more than one reference to the same
1228 * message in a room.
1230 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1231 if (msglist[i] == msgid) {
1232 lputroom(&CC->quickroom); /* unlock the room */
1233 getroom(&CC->quickroom, hold_rm);
1234 if (msg != NULL) CtdlFreeMessage(msg);
1235 return(ERROR + ALREADY_EXISTS);
1239 /* Now add the new message */
1241 msglist = reallok(msglist,
1242 (num_msgs * sizeof(long)));
1244 if (msglist == NULL) {
1245 lprintf(3, "ERROR: can't realloc message list!\n");
1247 msglist[num_msgs - 1] = msgid;
1249 /* Sort the message list, so all the msgid's are in order */
1250 num_msgs = sort_msglist(msglist, num_msgs);
1252 /* Determine the highest message number */
1253 highest_msg = msglist[num_msgs - 1];
1255 /* Write it back to disk. */
1256 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1257 msglist, num_msgs * sizeof(long));
1259 /* Free up the memory we used. */
1262 /* Update the highest-message pointer and unlock the room. */
1263 CC->quickroom.QRhighest = highest_msg;
1264 lputroom(&CC->quickroom);
1265 getroom(&CC->quickroom, hold_rm);
1267 /* Bump the reference count for this message. */
1268 if ((flags & SM_DONT_BUMP_REF)==0) {
1269 AdjRefCount(msgid, +1);
1272 /* Return success. */
1273 if (msg != NULL) CtdlFreeMessage(msg);
1280 * Message base operation to send a message to the master file
1281 * (returns new message number)
1283 * This is the back end for CtdlSaveMsg() and should not be directly
1284 * called by server-side modules.
1287 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1288 int generate_id, /* generate 'I' field? */
1289 FILE *save_a_copy) /* save a copy to disk? */
1296 /* Get a new message number */
1297 newmsgid = get_new_message_number();
1298 sprintf(msgidbuf, "%ld", newmsgid);
1301 msg->cm_fields['I'] = strdoop(msgidbuf);
1304 serialize_message(&smr, msg);
1307 cprintf("%d Unable to serialize message\n",
1308 ERROR+INTERNAL_ERROR);
1312 /* Write our little bundle of joy into the message base */
1313 begin_critical_section(S_MSGMAIN);
1314 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1315 smr.ser, smr.len) < 0) {
1316 lprintf(2, "Can't store message\n");
1321 end_critical_section(S_MSGMAIN);
1323 /* If the caller specified that a copy should be saved to a particular
1324 * file handle, do that now too.
1326 if (save_a_copy != NULL) {
1327 fwrite(smr.ser, smr.len, 1, save_a_copy);
1330 /* Free the memory we used for the serialized message */
1333 /* Return the *local* message ID to the caller
1334 * (even if we're storing an incoming network message)
1342 * Serialize a struct CtdlMessage into the format used on disk and network.
1344 * This function loads up a "struct ser_ret" (defined in server.h) which
1345 * contains the length of the serialized message and a pointer to the
1346 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1348 void serialize_message(struct ser_ret *ret, /* return values */
1349 struct CtdlMessage *msg) /* unserialized msg */
1353 static char *forder = FORDER;
1355 if (is_valid_message(msg) == 0) return; /* self check */
1358 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1359 ret->len = ret->len +
1360 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1362 lprintf(9, "calling malloc\n");
1363 ret->ser = mallok(ret->len);
1364 if (ret->ser == NULL) {
1370 ret->ser[1] = msg->cm_anon_type;
1371 ret->ser[2] = msg->cm_format_type;
1374 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1375 ret->ser[wlen++] = (char)forder[i];
1376 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1377 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1379 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1388 * Back end for the ReplicationChecks() function
1390 void check_repl(long msgnum) {
1391 struct CtdlMessage *msg;
1392 time_t timestamp = (-1L);
1394 lprintf(9, "check_repl() found message %ld\n", msgnum);
1395 msg = CtdlFetchMessage(msgnum);
1396 if (msg == NULL) return;
1397 if (msg->cm_fields['T'] != NULL) {
1398 timestamp = atol(msg->cm_fields['T']);
1400 CtdlFreeMessage(msg);
1402 if (timestamp > msg_repl->highest) {
1403 msg_repl->highest = timestamp; /* newer! */
1404 lprintf(9, "newer!\n");
1407 lprintf(9, "older!\n");
1409 /* Existing isn't newer? Then delete the old one(s). */
1410 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1415 * Check to see if any messages already exist which carry the same Extended ID
1419 * -> With older timestamps: delete them and return 0. Message will be saved.
1420 * -> With newer timestamps: return 1. Message save will be aborted.
1422 int ReplicationChecks(struct CtdlMessage *msg) {
1423 struct CtdlMessage *template;
1426 lprintf(9, "ReplicationChecks() started\n");
1427 /* No extended id? Don't do anything. */
1428 if (msg->cm_fields['E'] == NULL) return 0;
1429 if (strlen(msg->cm_fields['E']) == 0) return 0;
1430 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1432 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1433 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1434 msg_repl->highest = atol(msg->cm_fields['T']);
1436 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1437 memset(template, 0, sizeof(struct CtdlMessage));
1438 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1440 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1442 /* If a newer message exists with the same Extended ID, abort
1445 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1449 CtdlFreeMessage(template);
1450 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1458 * Save a message to disk
1460 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1461 char *rec, /* Recipient (mail) */
1462 char *force, /* force a particular room? */
1463 int mailtype, /* local or remote type */
1464 int generate_id) /* 1 = generate 'I' field */
1467 char hold_rm[ROOMNAMELEN];
1468 char actual_rm[ROOMNAMELEN];
1469 char force_room[ROOMNAMELEN];
1470 char content_type[256]; /* We have to learn this */
1471 char recipient[256];
1474 struct usersupp userbuf;
1476 struct SuppMsgInfo smi;
1477 FILE *network_fp = NULL;
1478 static int seqnum = 1;
1479 struct CtdlMessage *imsg;
1482 lprintf(9, "CtdlSaveMsg() called\n");
1483 if (is_valid_message(msg) == 0) return(-1); /* self check */
1485 /* If this message has no timestamp, we take the liberty of
1486 * giving it one, right now.
1488 if (msg->cm_fields['T'] == NULL) {
1489 lprintf(9, "Generating timestamp\n");
1490 sprintf(aaa, "%ld", time(NULL));
1491 msg->cm_fields['T'] = strdoop(aaa);
1494 /* If this message has no path, we generate one.
1496 if (msg->cm_fields['P'] == NULL) {
1497 lprintf(9, "Generating path\n");
1498 if (msg->cm_fields['A'] != NULL) {
1499 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1500 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1501 if (isspace(msg->cm_fields['P'][a])) {
1502 msg->cm_fields['P'][a] = ' ';
1507 msg->cm_fields['P'] = strdoop("unknown");
1511 strcpy(force_room, force);
1513 /* Strip non-printable characters out of the recipient name */
1514 strcpy(recipient, rec);
1515 for (a = 0; a < strlen(recipient); ++a)
1516 if (!isprint(recipient[a]))
1517 strcpy(&recipient[a], &recipient[a + 1]);
1519 /* Learn about what's inside, because it's what's inside that counts */
1520 lprintf(9, "Learning what's inside\n");
1521 if (msg->cm_fields['M'] == NULL) {
1522 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1525 switch (msg->cm_format_type) {
1527 strcpy(content_type, "text/x-citadel-variformat");
1530 strcpy(content_type, "text/plain");
1533 strcpy(content_type, "text/plain");
1534 /* advance past header fields */
1535 mptr = msg->cm_fields['M'];
1538 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1539 safestrncpy(content_type, mptr,
1540 sizeof(content_type));
1541 strcpy(content_type, &content_type[14]);
1542 for (a = 0; a < strlen(content_type); ++a)
1543 if ((content_type[a] == ';')
1544 || (content_type[a] == ' ')
1545 || (content_type[a] == 13)
1546 || (content_type[a] == 10))
1547 content_type[a] = 0;
1554 /* Goto the correct room */
1555 lprintf(9, "Switching rooms\n");
1556 strcpy(hold_rm, CC->quickroom.QRname);
1557 strcpy(actual_rm, CC->quickroom.QRname);
1559 /* If the user is a twit, move to the twit room for posting */
1560 lprintf(9, "Handling twit stuff\n");
1562 if (CC->usersupp.axlevel == 2) {
1563 strcpy(hold_rm, actual_rm);
1564 strcpy(actual_rm, config.c_twitroom);
1568 /* ...or if this message is destined for Aide> then go there. */
1569 if (strlen(force_room) > 0) {
1570 strcpy(actual_rm, force_room);
1573 lprintf(9, "Possibly relocating\n");
1574 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1575 getroom(&CC->quickroom, actual_rm);
1579 * If this message has no O (room) field, generate one.
1581 if (msg->cm_fields['O'] == NULL) {
1582 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1585 /* Perform "before save" hooks (aborting if any return nonzero) */
1586 lprintf(9, "Performing before-save hooks\n");
1587 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1589 /* If this message has an Extended ID, perform replication checks */
1590 lprintf(9, "Performing replication checks\n");
1591 if (ReplicationChecks(msg) > 0) return(-1);
1593 /* Network mail - send a copy to the network program. */
1594 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1595 lprintf(9, "Sending network spool\n");
1596 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1597 (long) getpid(), CC->cs_pid, ++seqnum);
1598 lprintf(9, "Saving a copy to %s\n", aaa);
1599 network_fp = fopen(aaa, "ab+");
1600 if (network_fp == NULL)
1601 lprintf(2, "ERROR: %s\n", strerror(errno));
1604 /* Save it to disk */
1605 lprintf(9, "Saving to disk\n");
1606 newmsgid = send_message(msg, generate_id, network_fp);
1607 if (network_fp != NULL) {
1609 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1612 if (newmsgid <= 0L) return(-1);
1614 /* Write a supplemental message info record. This doesn't have to
1615 * be a critical section because nobody else knows about this message
1618 lprintf(9, "Creating SuppMsgInfo record\n");
1619 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1620 smi.smi_msgnum = newmsgid;
1621 smi.smi_refcount = 0;
1622 safestrncpy(smi.smi_content_type, content_type, 64);
1623 PutSuppMsgInfo(&smi);
1625 /* Now figure out where to store the pointers */
1626 lprintf(9, "Storing pointers\n");
1628 /* If this is being done by the networker delivering a private
1629 * message, we want to BYPASS saving the sender's copy (because there
1630 * is no local sender; it would otherwise go to the Trashcan).
1632 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1633 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1636 /* For internet mail, drop a copy in the outbound queue room */
1637 if (mailtype == MES_INTERNET) {
1638 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1641 /* Bump this user's messages posted counter. */
1642 lprintf(9, "Updating user\n");
1643 lgetuser(&CC->usersupp, CC->curr_user);
1644 CC->usersupp.posted = CC->usersupp.posted + 1;
1645 lputuser(&CC->usersupp);
1647 /* If this is private, local mail, make a copy in the
1648 * recipient's mailbox and bump the reference count.
1650 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1651 if (getuser(&userbuf, recipient) == 0) {
1652 lprintf(9, "Delivering private mail\n");
1653 MailboxName(actual_rm, &userbuf, MAILROOM);
1654 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1658 /* Perform "after save" hooks */
1659 lprintf(9, "Performing after-save hooks\n");
1660 PerformMessageHooks(msg, EVT_AFTERSAVE);
1663 lprintf(9, "Returning to original room\n");
1664 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1665 getroom(&CC->quickroom, hold_rm);
1667 /* For internet mail, generate delivery instructions
1668 * (Yes, this is recursive! Deal with it!)
1670 if (mailtype == MES_INTERNET) {
1671 lprintf(9, "Generating delivery instructions\n");
1672 instr = mallok(2048);
1674 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1676 SPOOLMIME, newmsgid, time(NULL), recipient );
1678 imsg = mallok(sizeof(struct CtdlMessage));
1679 memset(imsg, 0, sizeof(struct CtdlMessage));
1680 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1681 imsg->cm_anon_type = MES_NORMAL;
1682 imsg->cm_format_type = FMT_RFC822;
1683 imsg->cm_fields['A'] = strdoop("Citadel");
1684 imsg->cm_fields['M'] = instr;
1685 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1686 CtdlFreeMessage(imsg);
1695 * Convenience function for generating small administrative messages.
1697 void quickie_message(char *from, char *to, char *room, char *text)
1699 struct CtdlMessage *msg;
1701 msg = mallok(sizeof(struct CtdlMessage));
1702 memset(msg, 0, sizeof(struct CtdlMessage));
1703 msg->cm_magic = CTDLMESSAGE_MAGIC;
1704 msg->cm_anon_type = MES_NORMAL;
1705 msg->cm_format_type = 0;
1706 msg->cm_fields['A'] = strdoop(from);
1707 msg->cm_fields['O'] = strdoop(room);
1708 msg->cm_fields['N'] = strdoop(NODENAME);
1710 msg->cm_fields['R'] = strdoop(to);
1711 msg->cm_fields['M'] = strdoop(text);
1713 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1714 CtdlFreeMessage(msg);
1715 syslog(LOG_NOTICE, text);
1721 * Back end function used by make_message() and similar functions
1723 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1724 size_t maxlen, /* maximum message length */
1725 char *exist /* if non-null, append to it;
1726 exist is ALWAYS freed */
1729 size_t message_len = 0;
1730 size_t buffer_len = 0;
1734 if (exist == NULL) {
1738 m = reallok(exist, strlen(exist) + 4096);
1739 if (m == NULL) phree(exist);
1742 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1749 /* read in the lines of message text one by one */
1751 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1753 /* augment the buffer if we have to */
1754 if ((message_len + strlen(buf) + 2) > buffer_len) {
1755 lprintf(9, "realloking\n");
1756 ptr = reallok(m, (buffer_len * 2) );
1757 if (ptr == NULL) { /* flush if can't allocate */
1758 while ( (client_gets(buf)>0) &&
1759 strcmp(buf, terminator)) ;;
1762 buffer_len = (buffer_len * 2);
1765 lprintf(9, "buffer_len is %d\n", buffer_len);
1769 if (append == NULL) append = m;
1770 while (strlen(append) > 0) ++append;
1771 strcpy(append, buf);
1772 strcat(append, "\n");
1773 message_len = message_len + strlen(buf) + 1;
1775 /* if we've hit the max msg length, flush the rest */
1776 if (message_len >= maxlen) {
1777 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1788 * Build a binary message to be saved on disk.
1791 struct CtdlMessage *make_message(
1792 struct usersupp *author, /* author's usersupp structure */
1793 char *recipient, /* NULL if it's not mail */
1794 char *room, /* room where it's going */
1795 int type, /* see MES_ types in header file */
1796 int net_type, /* see MES_ types in header file */
1797 int format_type, /* local or remote (see citadel.h) */
1798 char *fake_name) /* who we're masquerading as */
1804 struct CtdlMessage *msg;
1806 msg = mallok(sizeof(struct CtdlMessage));
1807 memset(msg, 0, sizeof(struct CtdlMessage));
1808 msg->cm_magic = CTDLMESSAGE_MAGIC;
1809 msg->cm_anon_type = type;
1810 msg->cm_format_type = format_type;
1812 /* Don't confuse the poor folks if it's not routed mail. */
1813 strcpy(dest_node, "");
1815 /* If net_type is MES_BINARY, split out the destination node. */
1816 if (net_type == MES_BINARY) {
1817 strcpy(dest_node, NODENAME);
1818 for (a = 0; a < strlen(recipient); ++a) {
1819 if (recipient[a] == '@') {
1821 strcpy(dest_node, &recipient[a + 1]);
1826 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1827 if (net_type == MES_INTERNET) {
1828 strcpy(dest_node, "internet");
1831 while (isspace(recipient[strlen(recipient) - 1]))
1832 recipient[strlen(recipient) - 1] = 0;
1834 sprintf(buf, "cit%ld", author->usernum); /* Path */
1835 msg->cm_fields['P'] = strdoop(buf);
1837 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1838 msg->cm_fields['T'] = strdoop(buf);
1840 if (fake_name[0]) /* author */
1841 msg->cm_fields['A'] = strdoop(fake_name);
1843 msg->cm_fields['A'] = strdoop(author->fullname);
1845 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1846 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1848 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1850 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1851 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1853 if (recipient[0] != 0)
1854 msg->cm_fields['R'] = strdoop(recipient);
1855 if (dest_node[0] != 0)
1856 msg->cm_fields['D'] = strdoop(dest_node);
1859 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1860 config.c_maxmsglen, NULL);
1871 * message entry - mode 0 (normal)
1873 void cmd_ent0(char *entargs)
1876 char recipient[256];
1878 int format_type = 0;
1879 char newusername[256];
1880 struct CtdlMessage *msg;
1884 struct usersupp tempUS;
1887 post = extract_int(entargs, 0);
1888 extract(recipient, entargs, 1);
1889 anon_flag = extract_int(entargs, 2);
1890 format_type = extract_int(entargs, 3);
1892 /* first check to make sure the request is valid. */
1894 if (!(CC->logged_in)) {
1895 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1898 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1899 cprintf("%d Need to be validated to enter ",
1900 ERROR + HIGHER_ACCESS_REQUIRED);
1901 cprintf("(except in %s> to sysop)\n", MAILROOM);
1904 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1905 cprintf("%d Need net privileges to enter here.\n",
1906 ERROR + HIGHER_ACCESS_REQUIRED);
1909 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1910 cprintf("%d Sorry, this is a read-only room.\n",
1911 ERROR + HIGHER_ACCESS_REQUIRED);
1918 if (CC->usersupp.axlevel < 6) {
1919 cprintf("%d You don't have permission to masquerade.\n",
1920 ERROR + HIGHER_ACCESS_REQUIRED);
1923 extract(newusername, entargs, 4);
1924 memset(CC->fake_postname, 0, 32);
1925 strcpy(CC->fake_postname, newusername);
1926 cprintf("%d Ok\n", OK);
1929 CC->cs_flags |= CS_POSTING;
1932 if (CC->quickroom.QRflags & QR_MAILBOX) {
1933 if (CC->usersupp.axlevel >= 2) {
1934 strcpy(buf, recipient);
1936 strcpy(buf, "sysop");
1937 e = alias(buf); /* alias and mail type */
1938 if ((buf[0] == 0) || (e == MES_ERROR)) {
1939 cprintf("%d Unknown address - cannot send message.\n",
1940 ERROR + NO_SUCH_USER);
1943 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1944 cprintf("%d Net privileges required for network mail.\n",
1945 ERROR + HIGHER_ACCESS_REQUIRED);
1948 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1949 && ((CC->usersupp.flags & US_INTERNET) == 0)
1950 && (!CC->internal_pgm)) {
1951 cprintf("%d You don't have access to Internet mail.\n",
1952 ERROR + HIGHER_ACCESS_REQUIRED);
1955 if (!strcasecmp(buf, "sysop")) {
1960 goto SKFALL; /* don't search local file */
1961 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1962 cprintf("%d Can't send mail to yourself!\n",
1963 ERROR + NO_SUCH_USER);
1966 /* Check to make sure the user exists; also get the correct
1967 * upper/lower casing of the name.
1969 a = getuser(&tempUS, buf);
1971 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1974 strcpy(buf, tempUS.fullname);
1977 SKFALL: b = MES_NORMAL;
1978 if (CC->quickroom.QRflags & QR_ANONONLY)
1980 if (CC->quickroom.QRflags & QR_ANONOPT) {
1984 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1987 /* If we're only checking the validity of the request, return
1988 * success without creating the message.
1991 cprintf("%d %s\n", OK, buf);
1995 cprintf("%d send message\n", SEND_LISTING);
1997 /* Read in the message from the client. */
1998 if (CC->fake_postname[0])
1999 msg = make_message(&CC->usersupp, buf,
2000 CC->quickroom.QRname, b, e, format_type,
2002 else if (CC->fake_username[0])
2003 msg = make_message(&CC->usersupp, buf,
2004 CC->quickroom.QRname, b, e, format_type,
2007 msg = make_message(&CC->usersupp, buf,
2008 CC->quickroom.QRname, b, e, format_type, "");
2011 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2012 CtdlFreeMessage(msg);
2013 CC->fake_postname[0] = '\0';
2020 * message entry - mode 3 (raw)
2022 void cmd_ent3(char *entargs)
2028 unsigned char ch, which_field;
2029 struct usersupp tempUS;
2031 struct CtdlMessage *msg;
2034 if (CC->internal_pgm == 0) {
2035 cprintf("%d This command is for internal programs only.\n",
2040 /* See if there's a recipient, but make sure it's a real one */
2041 extract(recp, entargs, 1);
2042 for (a = 0; a < strlen(recp); ++a)
2043 if (!isprint(recp[a]))
2044 strcpy(&recp[a], &recp[a + 1]);
2045 while (isspace(recp[0]))
2046 strcpy(recp, &recp[1]);
2047 while (isspace(recp[strlen(recp) - 1]))
2048 recp[strlen(recp) - 1] = 0;
2050 /* If we're in Mail, check the recipient */
2051 if (strlen(recp) > 0) {
2052 e = alias(recp); /* alias and mail type */
2053 if ((recp[0] == 0) || (e == MES_ERROR)) {
2054 cprintf("%d Unknown address - cannot send message.\n",
2055 ERROR + NO_SUCH_USER);
2058 if (e == MES_LOCAL) {
2059 a = getuser(&tempUS, recp);
2061 cprintf("%d No such user.\n",
2062 ERROR + NO_SUCH_USER);
2068 /* At this point, message has been approved. */
2069 if (extract_int(entargs, 0) == 0) {
2070 cprintf("%d OK to send\n", OK);
2074 msglen = extract_long(entargs, 2);
2075 msg = mallok(sizeof(struct CtdlMessage));
2077 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2081 memset(msg, 0, sizeof(struct CtdlMessage));
2082 tempbuf = mallok(msglen);
2083 if (tempbuf == NULL) {
2084 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2089 cprintf("%d %ld\n", SEND_BINARY, msglen);
2091 client_read(&ch, 1); /* 0xFF magic number */
2092 msg->cm_magic = CTDLMESSAGE_MAGIC;
2093 client_read(&ch, 1); /* anon type */
2094 msg->cm_anon_type = ch;
2095 client_read(&ch, 1); /* format type */
2096 msg->cm_format_type = ch;
2097 msglen = msglen - 3;
2099 while (msglen > 0) {
2100 client_read(&which_field, 1);
2101 if (!isalpha(which_field)) valid_msg = 0;
2105 client_read(&ch, 1);
2107 a = strlen(tempbuf);
2110 } while ( (ch != 0) && (msglen > 0) );
2112 msg->cm_fields[which_field] = strdoop(tempbuf);
2115 msg->cm_flags = CM_SKIP_HOOKS;
2116 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2117 CtdlFreeMessage(msg);
2123 * API function to delete messages which match a set of criteria
2124 * (returns the actual number of messages deleted)
2126 int CtdlDeleteMessages(char *room_name, /* which room */
2127 long dmsgnum, /* or "0" for any */
2128 char *content_type /* or NULL for any */
2132 struct quickroom qrbuf;
2133 struct cdbdata *cdbfr;
2134 long *msglist = NULL;
2137 int num_deleted = 0;
2139 struct SuppMsgInfo smi;
2141 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2142 room_name, dmsgnum, content_type);
2144 /* get room record, obtaining a lock... */
2145 if (lgetroom(&qrbuf, room_name) != 0) {
2146 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2148 return (0); /* room not found */
2150 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2152 if (cdbfr != NULL) {
2153 msglist = mallok(cdbfr->len);
2154 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2155 num_msgs = cdbfr->len / sizeof(long);
2159 for (i = 0; i < num_msgs; ++i) {
2162 /* Set/clear a bit for each criterion */
2164 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2165 delete_this |= 0x01;
2167 if (content_type == NULL) {
2168 delete_this |= 0x02;
2170 GetSuppMsgInfo(&smi, msglist[i]);
2171 if (!strcasecmp(smi.smi_content_type,
2173 delete_this |= 0x02;
2177 /* Delete message only if all bits are set */
2178 if (delete_this == 0x03) {
2179 AdjRefCount(msglist[i], -1);
2185 num_msgs = sort_msglist(msglist, num_msgs);
2186 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2187 msglist, (num_msgs * sizeof(long)));
2189 qrbuf.QRhighest = msglist[num_msgs - 1];
2193 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2194 return (num_deleted);
2200 * Delete message from current room
2202 void cmd_dele(char *delstr)
2207 getuser(&CC->usersupp, CC->curr_user);
2208 if ((CC->usersupp.axlevel < 6)
2209 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2210 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2211 && (!(CC->internal_pgm))) {
2212 cprintf("%d Higher access required.\n",
2213 ERROR + HIGHER_ACCESS_REQUIRED);
2216 delnum = extract_long(delstr, 0);
2218 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2221 cprintf("%d %d message%s deleted.\n", OK,
2222 num_deleted, ((num_deleted != 1) ? "s" : ""));
2224 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2230 * move or copy a message to another room
2232 void cmd_move(char *args)
2236 struct quickroom qtemp;
2240 num = extract_long(args, 0);
2241 extract(targ, args, 1);
2242 targ[ROOMNAMELEN - 1] = 0;
2243 is_copy = extract_int(args, 2);
2245 getuser(&CC->usersupp, CC->curr_user);
2246 if ((CC->usersupp.axlevel < 6)
2247 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2248 cprintf("%d Higher access required.\n",
2249 ERROR + HIGHER_ACCESS_REQUIRED);
2253 if (getroom(&qtemp, targ) != 0) {
2254 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2258 err = CtdlSaveMsgPointerInRoom(targ, num,
2259 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2261 cprintf("%d Cannot store message in %s: error %d\n",
2266 /* Now delete the message from the source room,
2267 * if this is a 'move' rather than a 'copy' operation.
2269 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2271 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2277 * GetSuppMsgInfo() - Get the supplementary record for a message
2279 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2282 struct cdbdata *cdbsmi;
2285 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2286 smibuf->smi_msgnum = msgnum;
2287 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2289 /* Use the negative of the message number for its supp record index */
2290 TheIndex = (0L - msgnum);
2292 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2293 if (cdbsmi == NULL) {
2294 return; /* record not found; go with defaults */
2296 memcpy(smibuf, cdbsmi->ptr,
2297 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2298 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2305 * PutSuppMsgInfo() - (re)write supplementary record for a message
2307 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2311 /* Use the negative of the message number for its supp record index */
2312 TheIndex = (0L - smibuf->smi_msgnum);
2314 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2315 smibuf->smi_msgnum, smibuf->smi_refcount);
2317 cdb_store(CDB_MSGMAIN,
2318 &TheIndex, sizeof(long),
2319 smibuf, sizeof(struct SuppMsgInfo));
2324 * AdjRefCount - change the reference count for a message;
2325 * delete the message if it reaches zero
2327 void AdjRefCount(long msgnum, int incr)
2330 struct SuppMsgInfo smi;
2333 /* This is a *tight* critical section; please keep it that way, as
2334 * it may get called while nested in other critical sections.
2335 * Complicating this any further will surely cause deadlock!
2337 begin_critical_section(S_SUPPMSGMAIN);
2338 GetSuppMsgInfo(&smi, msgnum);
2339 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2340 msgnum, smi.smi_refcount);
2341 smi.smi_refcount += incr;
2342 PutSuppMsgInfo(&smi);
2343 end_critical_section(S_SUPPMSGMAIN);
2344 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2345 msgnum, smi.smi_refcount);
2347 /* If the reference count is now zero, delete the message
2348 * (and its supplementary record as well).
2350 if (smi.smi_refcount == 0) {
2351 lprintf(9, "Deleting message <%ld>\n", msgnum);
2353 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2354 delnum = (0L - msgnum);
2355 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2360 * Write a generic object to this room
2362 * Note: this could be much more efficient. Right now we use two temporary
2363 * files, and still pull the message into memory as with all others.
2365 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2366 char *content_type, /* MIME type of this object */
2367 char *tempfilename, /* Where to fetch it from */
2368 struct usersupp *is_mailbox, /* Mailbox room? */
2369 int is_binary, /* Is encoding necessary? */
2370 int is_unique, /* Del others of this type? */
2371 unsigned int flags /* Internal save flags */
2376 char filename[PATH_MAX];
2379 struct quickroom qrbuf;
2380 char roomname[ROOMNAMELEN];
2381 struct CtdlMessage *msg;
2384 if (is_mailbox != NULL)
2385 MailboxName(roomname, is_mailbox, req_room);
2387 safestrncpy(roomname, req_room, sizeof(roomname));
2388 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2390 strcpy(filename, tmpnam(NULL));
2391 fp = fopen(filename, "w");
2395 tempfp = fopen(tempfilename, "r");
2396 if (tempfp == NULL) {
2402 fprintf(fp, "Content-type: %s\n", content_type);
2403 lprintf(9, "Content-type: %s\n", content_type);
2405 if (is_binary == 0) {
2406 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2407 while (ch = getc(tempfp), ch > 0)
2413 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2416 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2417 tempfilename, filename);
2421 lprintf(9, "Allocating\n");
2422 msg = mallok(sizeof(struct CtdlMessage));
2423 memset(msg, 0, sizeof(struct CtdlMessage));
2424 msg->cm_magic = CTDLMESSAGE_MAGIC;
2425 msg->cm_anon_type = MES_NORMAL;
2426 msg->cm_format_type = 4;
2427 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2428 msg->cm_fields['O'] = strdoop(req_room);
2429 msg->cm_fields['N'] = strdoop(config.c_nodename);
2430 msg->cm_fields['H'] = strdoop(config.c_humannode);
2431 msg->cm_flags = flags;
2433 lprintf(9, "Loading\n");
2434 fp = fopen(filename, "rb");
2435 fseek(fp, 0L, SEEK_END);
2438 msg->cm_fields['M'] = mallok(len);
2439 fread(msg->cm_fields['M'], len, 1, fp);
2443 /* Create the requested room if we have to. */
2444 if (getroom(&qrbuf, roomname) != 0) {
2445 create_room(roomname,
2446 ( (is_mailbox != NULL) ? 4 : 3 ),
2449 /* If the caller specified this object as unique, delete all
2450 * other objects of this type that are currently in the room.
2453 lprintf(9, "Deleted %d other msgs of this type\n",
2454 CtdlDeleteMessages(roomname, 0L, content_type));
2456 /* Now write the data */
2457 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2458 CtdlFreeMessage(msg);
2466 void CtdlGetSysConfigBackend(long msgnum) {
2467 config_msgnum = msgnum;
2471 char *CtdlGetSysConfig(char *sysconfname) {
2472 char hold_rm[ROOMNAMELEN];
2475 struct CtdlMessage *msg;
2478 strcpy(hold_rm, CC->quickroom.QRname);
2479 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2480 getroom(&CC->quickroom, hold_rm);
2485 /* We want the last (and probably only) config in this room */
2486 begin_critical_section(S_CONFIG);
2487 config_msgnum = (-1L);
2488 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2489 CtdlGetSysConfigBackend);
2490 msgnum = config_msgnum;
2491 end_critical_section(S_CONFIG);
2497 msg = CtdlFetchMessage(msgnum);
2499 conf = strdoop(msg->cm_fields['M']);
2500 CtdlFreeMessage(msg);
2507 getroom(&CC->quickroom, hold_rm);
2509 lprintf(9, "eggstracting...\n");
2510 if (conf != NULL) do {
2511 extract_token(buf, conf, 0, '\n');
2512 lprintf(9, "eggstracted <%s>\n", buf);
2513 strcpy(conf, &conf[strlen(buf)+1]);
2514 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2519 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2520 char temp[PATH_MAX];
2523 strcpy(temp, tmpnam(NULL));
2525 fp = fopen(temp, "w");
2526 if (fp == NULL) return;
2527 fprintf(fp, "%s", sysconfdata);
2530 /* this handy API function does all the work for us */
2531 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);