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 lprintf(9, "phreeing %c\n", i);
679 phree(msg->cm_fields[i]);
682 msg->cm_magic = 0; /* just in case */
683 lprintf(9, "phreeing msg\n");
689 * Callback function for mime parser that wants to display text
691 void fixed_output(char *name, char *filename, char *partnum, char *disp,
692 void *content, char *cbtype, size_t length)
699 if (!strcasecmp(cbtype, "multipart/alternative")) {
700 strcpy(ma->prefix, partnum);
701 strcat(ma->prefix, ".");
707 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
709 && (ma->did_print == 1) ) {
710 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
716 if ( (!strcasecmp(cbtype, "text/plain"))
717 || (strlen(cbtype)==0) ) {
722 if (ch==10) cprintf("\r\n");
723 else cprintf("%c", ch);
726 else if (!strcasecmp(cbtype, "text/html")) {
727 ptr = html_to_ascii(content, 80, 0);
732 if (ch==10) cprintf("\r\n");
733 else cprintf("%c", ch);
737 else if (strncasecmp(cbtype, "multipart/", 10)) {
738 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
739 partnum, filename, cbtype, length);
745 * Get a message off disk. (returns om_* values found in msgbase.h)
748 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
749 int mode, /* how would you like that message? */
750 int headers_only, /* eschew the message body? */
751 int do_proto, /* do Citadel protocol responses? */
752 int crlf /* Use CRLF newlines instead of LF? */
758 char display_name[256];
759 struct CtdlMessage *TheMessage;
761 char *nl; /* newline string */
763 /* buffers needed for RFC822 translation */
773 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
777 sprintf(mid, "%ld", msg_num);
778 nl = (crlf ? "\r\n" : "\n");
780 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
781 if (do_proto) cprintf("%d Not logged in.\n",
782 ERROR + NOT_LOGGED_IN);
783 return(om_not_logged_in);
786 /* FIX ... small security issue
787 * We need to check to make sure the requested message is actually
788 * in the current room, and set msg_ok to 1 only if it is. This
789 * functionality is currently missing because I'm in a hurry to replace
790 * broken production code with nonbroken pre-beta code. :( -- ajc
793 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
795 return(om_no_such_msg);
800 * Fetch the message from disk
802 TheMessage = CtdlFetchMessage(msg_num);
803 if (TheMessage == NULL) {
804 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
806 return(om_no_such_msg);
809 /* Are we downloading a MIME component? */
810 if (mode == MT_DOWNLOAD) {
811 if (TheMessage->cm_format_type != FMT_RFC822) {
813 cprintf("%d This is not a MIME message.\n",
815 } else if (CC->download_fp != NULL) {
816 if (do_proto) cprintf(
817 "%d You already have a download open.\n",
820 /* Parse the message text component */
821 mptr = TheMessage->cm_fields['M'];
822 mime_parser(mptr, NULL, *mime_download);
823 /* If there's no file open by this time, the requested
824 * section wasn't found, so print an error
826 if (CC->download_fp == NULL) {
827 if (do_proto) cprintf(
828 "%d Section %s not found.\n",
829 ERROR + FILE_NOT_FOUND,
833 CtdlFreeMessage(TheMessage);
834 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
837 /* now for the user-mode message reading loops */
838 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
840 /* Tell the client which format type we're using. If this is a
841 * MIME message, *lie* about it and tell the user it's fixed-format.
843 if (mode == MT_CITADEL) {
844 if (TheMessage->cm_format_type == FMT_RFC822) {
845 if (do_proto) cprintf("type=1\n");
848 if (do_proto) cprintf("type=%d\n",
849 TheMessage->cm_format_type);
853 /* nhdr=yes means that we're only displaying headers, no body */
854 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
855 if (do_proto) cprintf("nhdr=yes\n");
858 /* begin header processing loop for Citadel message format */
860 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
862 strcpy(display_name, "<unknown>");
863 if (TheMessage->cm_fields['A']) {
864 strcpy(buf, TheMessage->cm_fields['A']);
865 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
866 if (TheMessage->cm_anon_type == MES_ANON)
867 strcpy(display_name, "****");
868 else if (TheMessage->cm_anon_type == MES_AN2)
869 strcpy(display_name, "anonymous");
871 strcpy(display_name, buf);
873 && ((TheMessage->cm_anon_type == MES_ANON)
874 || (TheMessage->cm_anon_type == MES_AN2))) {
875 sprintf(&display_name[strlen(display_name)],
880 strcpy(allkeys, FORDER);
881 for (i=0; i<strlen(allkeys); ++i) {
882 k = (int) allkeys[i];
884 if (TheMessage->cm_fields[k] != NULL) {
886 if (do_proto) cprintf("%s=%s\n",
891 if (do_proto) cprintf("%s=%s\n",
893 TheMessage->cm_fields[k]
902 /* begin header processing loop for RFC822 transfer format */
907 strcpy(snode, NODENAME);
908 strcpy(lnode, HUMANNODE);
909 if (mode == MT_RFC822) {
910 cprintf("X-UIDL: %ld%s", msg_num, nl);
911 for (i = 0; i < 256; ++i) {
912 if (TheMessage->cm_fields[i]) {
913 mptr = TheMessage->cm_fields[i];
920 cprintf("Path: %s%s", mptr, nl);
923 cprintf("Subject: %s%s", mptr, nl);
929 cprintf("X-Citadel-Room: %s%s",
934 cprintf("To: %s%s", mptr, nl);
936 generate_rfc822_datestamp(datestamp,
938 cprintf("Date: %s%s", datestamp, nl);
944 for (i=0; i<strlen(suser); ++i) {
945 suser[i] = tolower(suser[i]);
946 if (!isalnum(suser[i])) suser[i]='_';
949 if (mode == MT_RFC822) {
950 if (!strcasecmp(snode, NODENAME)) {
953 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
954 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
956 if (strlen(fuser) > 0) {
957 cprintf("From: %s (%s)%s", fuser, luser, nl);
960 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
963 cprintf("Organization: %s%s", lnode, nl);
966 /* end header processing loop ... at this point, we're in the text */
968 mptr = TheMessage->cm_fields['M'];
970 /* Tell the client about the MIME parts in this message */
971 if (TheMessage->cm_format_type == FMT_RFC822) {
972 if (mode == MT_CITADEL) {
973 mime_parser(mptr, NULL, *list_this_part);
975 else if (mode == MT_MIME) { /* list parts only */
976 mime_parser(mptr, NULL, *list_this_part);
977 if (do_proto) cprintf("000\n");
978 CtdlFreeMessage(TheMessage);
981 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
982 /* FIX ... we have to put some code in here to avoid
983 * printing duplicate header information when both
984 * Citadel and RFC822 headers exist. Preference should
985 * probably be given to the RFC822 headers.
987 while (ch=*(mptr++), ch!=0) {
989 else if (ch==10) cprintf("%s", nl);
990 else cprintf("%c", ch);
992 if (do_proto) cprintf("000\n");
993 CtdlFreeMessage(TheMessage);
999 if (do_proto) cprintf("000\n");
1000 CtdlFreeMessage(TheMessage);
1004 /* signify start of msg text */
1005 if (mode == MT_CITADEL)
1006 if (do_proto) cprintf("text\n");
1007 if (mode == MT_RFC822) {
1008 if (TheMessage->cm_fields['U'] == NULL) {
1009 cprintf("Subject: (no subject)%s", nl);
1014 /* If the format type on disk is 1 (fixed-format), then we want
1015 * everything to be output completely literally ... regardless of
1016 * what message transfer format is in use.
1018 if (TheMessage->cm_format_type == FMT_FIXED) {
1020 while (ch = *mptr++, ch > 0) {
1023 if ((ch == 10) || (strlen(buf) > 250)) {
1024 cprintf("%s%s", buf, nl);
1027 buf[strlen(buf) + 1] = 0;
1028 buf[strlen(buf)] = ch;
1031 if (strlen(buf) > 0)
1032 cprintf("%s%s", buf, nl);
1035 /* If the message on disk is format 0 (Citadel vari-format), we
1036 * output using the formatter at 80 columns. This is the final output
1037 * form if the transfer format is RFC822, but if the transfer format
1038 * is Citadel proprietary, it'll still work, because the indentation
1039 * for new paragraphs is correct and the client will reformat the
1040 * message to the reader's screen width.
1042 if (TheMessage->cm_format_type == FMT_CITADEL) {
1043 memfmout(80, mptr, 0);
1046 /* If the message on disk is format 4 (MIME), we've gotta hand it
1047 * off to the MIME parser. The client has already been told that
1048 * this message is format 1 (fixed format), so the callback function
1049 * we use will display those parts as-is.
1051 if (TheMessage->cm_format_type == FMT_RFC822) {
1052 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1053 memset(ma, 0, sizeof(struct ma_info));
1054 mime_parser(mptr, NULL, *fixed_output);
1057 /* now we're done */
1058 if (do_proto) cprintf("000\n");
1059 CtdlFreeMessage(TheMessage);
1066 * display a message (mode 0 - Citadel proprietary)
1068 void cmd_msg0(char *cmdbuf)
1071 int headers_only = 0;
1073 msgid = extract_long(cmdbuf, 0);
1074 headers_only = extract_int(cmdbuf, 1);
1076 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1082 * display a message (mode 2 - RFC822)
1084 void cmd_msg2(char *cmdbuf)
1087 int headers_only = 0;
1089 msgid = extract_long(cmdbuf, 0);
1090 headers_only = extract_int(cmdbuf, 1);
1092 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1098 * display a message (mode 3 - IGnet raw format - internal programs only)
1100 void cmd_msg3(char *cmdbuf)
1103 struct CtdlMessage *msg;
1106 if (CC->internal_pgm == 0) {
1107 cprintf("%d This command is for internal programs only.\n",
1112 msgnum = extract_long(cmdbuf, 0);
1113 msg = CtdlFetchMessage(msgnum);
1115 cprintf("%d Message %ld not found.\n",
1120 serialize_message(&smr, msg);
1121 CtdlFreeMessage(msg);
1124 cprintf("%d Unable to serialize message\n",
1125 ERROR+INTERNAL_ERROR);
1129 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1130 client_write(smr.ser, smr.len);
1137 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1139 void cmd_msg4(char *cmdbuf)
1143 msgid = extract_long(cmdbuf, 0);
1144 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1148 * Open a component of a MIME message as a download file
1150 void cmd_opna(char *cmdbuf)
1154 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1156 msgid = extract_long(cmdbuf, 0);
1157 extract(desired_section, cmdbuf, 1);
1159 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1164 * Save a message pointer into a specified room
1165 * (Returns 0 for success, nonzero for failure)
1166 * roomname may be NULL to use the current room
1168 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1170 char hold_rm[ROOMNAMELEN];
1171 struct cdbdata *cdbfr;
1174 long highest_msg = 0L;
1175 struct CtdlMessage *msg = NULL;
1177 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1178 roomname, msgid, flags);
1180 strcpy(hold_rm, CC->quickroom.QRname);
1182 /* We may need to check to see if this message is real */
1183 if ( (flags & SM_VERIFY_GOODNESS)
1184 || (flags & SM_DO_REPL_CHECK)
1186 msg = CtdlFetchMessage(msgid);
1187 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1190 /* Perform replication checks if necessary */
1191 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1193 if (getroom(&CC->quickroom,
1194 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1196 lprintf(9, "No such room <%s>\n", roomname);
1197 if (msg != NULL) CtdlFreeMessage(msg);
1198 return(ERROR + ROOM_NOT_FOUND);
1201 if (ReplicationChecks(msg) != 0) {
1202 getroom(&CC->quickroom, hold_rm);
1203 if (msg != NULL) CtdlFreeMessage(msg);
1204 lprintf(9, "Did replication, and newer exists\n");
1209 /* Now the regular stuff */
1210 if (lgetroom(&CC->quickroom,
1211 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1213 lprintf(9, "No such room <%s>\n", roomname);
1214 if (msg != NULL) CtdlFreeMessage(msg);
1215 return(ERROR + ROOM_NOT_FOUND);
1218 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1219 if (cdbfr == NULL) {
1223 msglist = mallok(cdbfr->len);
1224 if (msglist == NULL)
1225 lprintf(3, "ERROR malloc msglist!\n");
1226 num_msgs = cdbfr->len / sizeof(long);
1227 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1232 /* Make sure the message doesn't already exist in this room. It
1233 * is absolutely taboo to have more than one reference to the same
1234 * message in a room.
1236 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1237 if (msglist[i] == msgid) {
1238 lputroom(&CC->quickroom); /* unlock the room */
1239 getroom(&CC->quickroom, hold_rm);
1240 if (msg != NULL) CtdlFreeMessage(msg);
1241 return(ERROR + ALREADY_EXISTS);
1245 /* Now add the new message */
1247 msglist = reallok(msglist,
1248 (num_msgs * sizeof(long)));
1250 if (msglist == NULL) {
1251 lprintf(3, "ERROR: can't realloc message list!\n");
1253 msglist[num_msgs - 1] = msgid;
1255 /* Sort the message list, so all the msgid's are in order */
1256 num_msgs = sort_msglist(msglist, num_msgs);
1258 /* Determine the highest message number */
1259 highest_msg = msglist[num_msgs - 1];
1261 /* Write it back to disk. */
1262 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1263 msglist, num_msgs * sizeof(long));
1265 /* Free up the memory we used. */
1268 /* Update the highest-message pointer and unlock the room. */
1269 CC->quickroom.QRhighest = highest_msg;
1270 lputroom(&CC->quickroom);
1271 getroom(&CC->quickroom, hold_rm);
1273 /* Bump the reference count for this message. */
1274 if ((flags & SM_DONT_BUMP_REF)==0) {
1275 AdjRefCount(msgid, +1);
1278 /* Return success. */
1279 if (msg != NULL) CtdlFreeMessage(msg);
1286 * Message base operation to send a message to the master file
1287 * (returns new message number)
1289 * This is the back end for CtdlSaveMsg() and should not be directly
1290 * called by server-side modules.
1293 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1294 int generate_id, /* generate 'I' field? */
1295 FILE *save_a_copy) /* save a copy to disk? */
1302 /* Get a new message number */
1303 newmsgid = get_new_message_number();
1304 sprintf(msgidbuf, "%ld", newmsgid);
1307 msg->cm_fields['I'] = strdoop(msgidbuf);
1310 serialize_message(&smr, msg);
1313 cprintf("%d Unable to serialize message\n",
1314 ERROR+INTERNAL_ERROR);
1318 /* Write our little bundle of joy into the message base */
1319 begin_critical_section(S_MSGMAIN);
1320 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1321 smr.ser, smr.len) < 0) {
1322 lprintf(2, "Can't store message\n");
1327 end_critical_section(S_MSGMAIN);
1329 /* If the caller specified that a copy should be saved to a particular
1330 * file handle, do that now too.
1332 if (save_a_copy != NULL) {
1333 fwrite(smr.ser, smr.len, 1, save_a_copy);
1336 /* Free the memory we used for the serialized message */
1339 /* Return the *local* message ID to the caller
1340 * (even if we're storing an incoming network message)
1348 * Serialize a struct CtdlMessage into the format used on disk and network.
1350 * This function loads up a "struct ser_ret" (defined in server.h) which
1351 * contains the length of the serialized message and a pointer to the
1352 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1354 void serialize_message(struct ser_ret *ret, /* return values */
1355 struct CtdlMessage *msg) /* unserialized msg */
1359 static char *forder = FORDER;
1361 if (is_valid_message(msg) == 0) return; /* self check */
1364 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1365 ret->len = ret->len +
1366 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1368 lprintf(9, "calling malloc\n");
1369 ret->ser = mallok(ret->len);
1370 if (ret->ser == NULL) {
1376 ret->ser[1] = msg->cm_anon_type;
1377 ret->ser[2] = msg->cm_format_type;
1380 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1381 ret->ser[wlen++] = (char)forder[i];
1382 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1383 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1385 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1394 * Back end for the ReplicationChecks() function
1396 void check_repl(long msgnum) {
1397 struct CtdlMessage *msg;
1398 time_t timestamp = (-1L);
1400 lprintf(9, "check_repl() found message %ld\n", msgnum);
1401 msg = CtdlFetchMessage(msgnum);
1402 if (msg == NULL) return;
1403 if (msg->cm_fields['T'] != NULL) {
1404 timestamp = atol(msg->cm_fields['T']);
1406 CtdlFreeMessage(msg);
1408 if (timestamp > msg_repl->highest) {
1409 msg_repl->highest = timestamp; /* newer! */
1410 lprintf(9, "newer!\n");
1413 lprintf(9, "older!\n");
1415 /* Existing isn't newer? Then delete the old one(s). */
1416 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1421 * Check to see if any messages already exist which carry the same Extended ID
1425 * -> With older timestamps: delete them and return 0. Message will be saved.
1426 * -> With newer timestamps: return 1. Message save will be aborted.
1428 int ReplicationChecks(struct CtdlMessage *msg) {
1429 struct CtdlMessage *template;
1432 lprintf(9, "ReplicationChecks() started\n");
1433 /* No extended id? Don't do anything. */
1434 if (msg->cm_fields['E'] == NULL) return 0;
1435 if (strlen(msg->cm_fields['E']) == 0) return 0;
1436 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1438 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1439 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1440 msg_repl->highest = atol(msg->cm_fields['T']);
1442 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1443 memset(template, 0, sizeof(struct CtdlMessage));
1444 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1446 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1448 /* If a newer message exists with the same Extended ID, abort
1451 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1455 CtdlFreeMessage(template);
1456 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1464 * Save a message to disk
1466 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1467 char *rec, /* Recipient (mail) */
1468 char *force, /* force a particular room? */
1469 int mailtype, /* local or remote type */
1470 int generate_id) /* 1 = generate 'I' field */
1473 char hold_rm[ROOMNAMELEN];
1474 char actual_rm[ROOMNAMELEN];
1475 char force_room[ROOMNAMELEN];
1476 char content_type[256]; /* We have to learn this */
1477 char recipient[256];
1480 struct usersupp userbuf;
1482 struct SuppMsgInfo smi;
1483 FILE *network_fp = NULL;
1484 static int seqnum = 1;
1485 struct CtdlMessage *imsg;
1488 lprintf(9, "CtdlSaveMsg() called\n");
1489 if (is_valid_message(msg) == 0) return(-1); /* self check */
1491 /* If this message has no timestamp, we take the liberty of
1492 * giving it one, right now.
1494 if (msg->cm_fields['T'] == NULL) {
1495 lprintf(9, "Generating timestamp\n");
1496 sprintf(aaa, "%ld", time(NULL));
1497 msg->cm_fields['T'] = strdoop(aaa);
1500 /* If this message has no path, we generate one.
1502 if (msg->cm_fields['P'] == NULL) {
1503 lprintf(9, "Generating path\n");
1504 if (msg->cm_fields['A'] != NULL) {
1505 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1506 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1507 if (isspace(msg->cm_fields['P'][a])) {
1508 msg->cm_fields['P'][a] = ' ';
1513 msg->cm_fields['P'] = strdoop("unknown");
1517 strcpy(force_room, force);
1519 /* Strip non-printable characters out of the recipient name */
1520 strcpy(recipient, rec);
1521 for (a = 0; a < strlen(recipient); ++a)
1522 if (!isprint(recipient[a]))
1523 strcpy(&recipient[a], &recipient[a + 1]);
1525 /* Learn about what's inside, because it's what's inside that counts */
1526 lprintf(9, "Learning what's inside\n");
1527 if (msg->cm_fields['M'] == NULL) {
1528 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1531 switch (msg->cm_format_type) {
1533 strcpy(content_type, "text/x-citadel-variformat");
1536 strcpy(content_type, "text/plain");
1539 strcpy(content_type, "text/plain");
1540 /* advance past header fields */
1541 mptr = msg->cm_fields['M'];
1544 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1545 safestrncpy(content_type, mptr,
1546 sizeof(content_type));
1547 strcpy(content_type, &content_type[14]);
1548 for (a = 0; a < strlen(content_type); ++a)
1549 if ((content_type[a] == ';')
1550 || (content_type[a] == ' ')
1551 || (content_type[a] == 13)
1552 || (content_type[a] == 10))
1553 content_type[a] = 0;
1560 /* Goto the correct room */
1561 lprintf(9, "Switching rooms\n");
1562 strcpy(hold_rm, CC->quickroom.QRname);
1563 strcpy(actual_rm, CC->quickroom.QRname);
1565 /* If the user is a twit, move to the twit room for posting */
1566 lprintf(9, "Handling twit stuff\n");
1568 if (CC->usersupp.axlevel == 2) {
1569 strcpy(hold_rm, actual_rm);
1570 strcpy(actual_rm, config.c_twitroom);
1574 /* ...or if this message is destined for Aide> then go there. */
1575 if (strlen(force_room) > 0) {
1576 strcpy(actual_rm, force_room);
1579 lprintf(9, "Possibly relocating\n");
1580 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1581 getroom(&CC->quickroom, actual_rm);
1585 * If this message has no O (room) field, generate one.
1587 if (msg->cm_fields['O'] == NULL) {
1588 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1591 /* Perform "before save" hooks (aborting if any return nonzero) */
1592 lprintf(9, "Performing before-save hooks\n");
1593 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1595 /* If this message has an Extended ID, perform replication checks */
1596 lprintf(9, "Performing replication checks\n");
1597 if (ReplicationChecks(msg) > 0) return(-1);
1599 /* Network mail - send a copy to the network program. */
1600 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1601 lprintf(9, "Sending network spool\n");
1602 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1603 (long) getpid(), CC->cs_pid, ++seqnum);
1604 lprintf(9, "Saving a copy to %s\n", aaa);
1605 network_fp = fopen(aaa, "ab+");
1606 if (network_fp == NULL)
1607 lprintf(2, "ERROR: %s\n", strerror(errno));
1610 /* Save it to disk */
1611 lprintf(9, "Saving to disk\n");
1612 newmsgid = send_message(msg, generate_id, network_fp);
1613 if (network_fp != NULL) {
1615 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1618 if (newmsgid <= 0L) return(-1);
1620 /* Write a supplemental message info record. This doesn't have to
1621 * be a critical section because nobody else knows about this message
1624 lprintf(9, "Creating SuppMsgInfo record\n");
1625 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1626 smi.smi_msgnum = newmsgid;
1627 smi.smi_refcount = 0;
1628 safestrncpy(smi.smi_content_type, content_type, 64);
1629 PutSuppMsgInfo(&smi);
1631 /* Now figure out where to store the pointers */
1632 lprintf(9, "Storing pointers\n");
1634 /* If this is being done by the networker delivering a private
1635 * message, we want to BYPASS saving the sender's copy (because there
1636 * is no local sender; it would otherwise go to the Trashcan).
1638 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1639 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1642 /* For internet mail, drop a copy in the outbound queue room */
1643 if (mailtype == MES_INTERNET) {
1644 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1647 /* Bump this user's messages posted counter. */
1648 lprintf(9, "Updating user\n");
1649 lgetuser(&CC->usersupp, CC->curr_user);
1650 CC->usersupp.posted = CC->usersupp.posted + 1;
1651 lputuser(&CC->usersupp);
1653 /* If this is private, local mail, make a copy in the
1654 * recipient's mailbox and bump the reference count.
1656 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1657 if (getuser(&userbuf, recipient) == 0) {
1658 lprintf(9, "Delivering private mail\n");
1659 MailboxName(actual_rm, &userbuf, MAILROOM);
1660 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1664 /* Perform "after save" hooks */
1665 lprintf(9, "Performing after-save hooks\n");
1666 PerformMessageHooks(msg, EVT_AFTERSAVE);
1669 lprintf(9, "Returning to original room\n");
1670 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1671 getroom(&CC->quickroom, hold_rm);
1673 /* For internet mail, generate delivery instructions
1674 * (Yes, this is recursive! Deal with it!)
1676 if (mailtype == MES_INTERNET) {
1677 lprintf(9, "Generating delivery instructions\n");
1678 instr = mallok(2048);
1680 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1683 SPOOLMIME, newmsgid, time(NULL),
1684 msg->cm_fields['A'], msg->cm_fields['N'],
1687 imsg = mallok(sizeof(struct CtdlMessage));
1688 memset(imsg, 0, sizeof(struct CtdlMessage));
1689 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1690 imsg->cm_anon_type = MES_NORMAL;
1691 imsg->cm_format_type = FMT_RFC822;
1692 imsg->cm_fields['A'] = strdoop("Citadel");
1693 imsg->cm_fields['M'] = instr;
1694 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1695 CtdlFreeMessage(imsg);
1704 * Convenience function for generating small administrative messages.
1706 void quickie_message(char *from, char *to, char *room, char *text)
1708 struct CtdlMessage *msg;
1710 msg = mallok(sizeof(struct CtdlMessage));
1711 memset(msg, 0, sizeof(struct CtdlMessage));
1712 msg->cm_magic = CTDLMESSAGE_MAGIC;
1713 msg->cm_anon_type = MES_NORMAL;
1714 msg->cm_format_type = 0;
1715 msg->cm_fields['A'] = strdoop(from);
1716 msg->cm_fields['O'] = strdoop(room);
1717 msg->cm_fields['N'] = strdoop(NODENAME);
1719 msg->cm_fields['R'] = strdoop(to);
1720 msg->cm_fields['M'] = strdoop(text);
1722 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1723 CtdlFreeMessage(msg);
1724 syslog(LOG_NOTICE, text);
1730 * Back end function used by make_message() and similar functions
1732 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1733 size_t maxlen, /* maximum message length */
1734 char *exist /* if non-null, append to it;
1735 exist is ALWAYS freed */
1738 size_t message_len = 0;
1739 size_t buffer_len = 0;
1743 if (exist == NULL) {
1747 m = reallok(exist, strlen(exist) + 4096);
1748 if (m == NULL) phree(exist);
1751 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1758 /* read in the lines of message text one by one */
1760 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1762 /* augment the buffer if we have to */
1763 if ((message_len + strlen(buf) + 2) > buffer_len) {
1764 lprintf(9, "realloking\n");
1765 ptr = reallok(m, (buffer_len * 2) );
1766 if (ptr == NULL) { /* flush if can't allocate */
1767 while ( (client_gets(buf)>0) &&
1768 strcmp(buf, terminator)) ;;
1771 buffer_len = (buffer_len * 2);
1774 lprintf(9, "buffer_len is %d\n", buffer_len);
1778 if (append == NULL) append = m;
1779 while (strlen(append) > 0) ++append;
1780 strcpy(append, buf);
1781 strcat(append, "\n");
1782 message_len = message_len + strlen(buf) + 1;
1784 /* if we've hit the max msg length, flush the rest */
1785 if (message_len >= maxlen) {
1786 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1797 * Build a binary message to be saved on disk.
1800 struct CtdlMessage *make_message(
1801 struct usersupp *author, /* author's usersupp structure */
1802 char *recipient, /* NULL if it's not mail */
1803 char *room, /* room where it's going */
1804 int type, /* see MES_ types in header file */
1805 int net_type, /* see MES_ types in header file */
1806 int format_type, /* local or remote (see citadel.h) */
1807 char *fake_name) /* who we're masquerading as */
1813 struct CtdlMessage *msg;
1815 msg = mallok(sizeof(struct CtdlMessage));
1816 memset(msg, 0, sizeof(struct CtdlMessage));
1817 msg->cm_magic = CTDLMESSAGE_MAGIC;
1818 msg->cm_anon_type = type;
1819 msg->cm_format_type = format_type;
1821 /* Don't confuse the poor folks if it's not routed mail. */
1822 strcpy(dest_node, "");
1824 /* If net_type is MES_BINARY, split out the destination node. */
1825 if (net_type == MES_BINARY) {
1826 strcpy(dest_node, NODENAME);
1827 for (a = 0; a < strlen(recipient); ++a) {
1828 if (recipient[a] == '@') {
1830 strcpy(dest_node, &recipient[a + 1]);
1835 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1836 if (net_type == MES_INTERNET) {
1837 strcpy(dest_node, "internet");
1840 while (isspace(recipient[strlen(recipient) - 1]))
1841 recipient[strlen(recipient) - 1] = 0;
1843 sprintf(buf, "cit%ld", author->usernum); /* Path */
1844 msg->cm_fields['P'] = strdoop(buf);
1846 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1847 msg->cm_fields['T'] = strdoop(buf);
1849 if (fake_name[0]) /* author */
1850 msg->cm_fields['A'] = strdoop(fake_name);
1852 msg->cm_fields['A'] = strdoop(author->fullname);
1854 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1855 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1857 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1859 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1860 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1862 if (recipient[0] != 0)
1863 msg->cm_fields['R'] = strdoop(recipient);
1864 if (dest_node[0] != 0)
1865 msg->cm_fields['D'] = strdoop(dest_node);
1868 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1869 config.c_maxmsglen, NULL);
1880 * message entry - mode 0 (normal)
1882 void cmd_ent0(char *entargs)
1885 char recipient[256];
1887 int format_type = 0;
1888 char newusername[256];
1889 struct CtdlMessage *msg;
1893 struct usersupp tempUS;
1896 post = extract_int(entargs, 0);
1897 extract(recipient, entargs, 1);
1898 anon_flag = extract_int(entargs, 2);
1899 format_type = extract_int(entargs, 3);
1901 /* first check to make sure the request is valid. */
1903 if (!(CC->logged_in)) {
1904 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1907 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1908 cprintf("%d Need to be validated to enter ",
1909 ERROR + HIGHER_ACCESS_REQUIRED);
1910 cprintf("(except in %s> to sysop)\n", MAILROOM);
1913 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1914 cprintf("%d Need net privileges to enter here.\n",
1915 ERROR + HIGHER_ACCESS_REQUIRED);
1918 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1919 cprintf("%d Sorry, this is a read-only room.\n",
1920 ERROR + HIGHER_ACCESS_REQUIRED);
1927 if (CC->usersupp.axlevel < 6) {
1928 cprintf("%d You don't have permission to masquerade.\n",
1929 ERROR + HIGHER_ACCESS_REQUIRED);
1932 extract(newusername, entargs, 4);
1933 memset(CC->fake_postname, 0, 32);
1934 strcpy(CC->fake_postname, newusername);
1935 cprintf("%d Ok\n", OK);
1938 CC->cs_flags |= CS_POSTING;
1941 if (CC->quickroom.QRflags & QR_MAILBOX) {
1942 if (CC->usersupp.axlevel >= 2) {
1943 strcpy(buf, recipient);
1945 strcpy(buf, "sysop");
1946 e = alias(buf); /* alias and mail type */
1947 if ((buf[0] == 0) || (e == MES_ERROR)) {
1948 cprintf("%d Unknown address - cannot send message.\n",
1949 ERROR + NO_SUCH_USER);
1952 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1953 cprintf("%d Net privileges required for network mail.\n",
1954 ERROR + HIGHER_ACCESS_REQUIRED);
1957 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1958 && ((CC->usersupp.flags & US_INTERNET) == 0)
1959 && (!CC->internal_pgm)) {
1960 cprintf("%d You don't have access to Internet mail.\n",
1961 ERROR + HIGHER_ACCESS_REQUIRED);
1964 if (!strcasecmp(buf, "sysop")) {
1969 goto SKFALL; /* don't search local file */
1970 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1971 cprintf("%d Can't send mail to yourself!\n",
1972 ERROR + NO_SUCH_USER);
1975 /* Check to make sure the user exists; also get the correct
1976 * upper/lower casing of the name.
1978 a = getuser(&tempUS, buf);
1980 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1983 strcpy(buf, tempUS.fullname);
1986 SKFALL: b = MES_NORMAL;
1987 if (CC->quickroom.QRflags & QR_ANONONLY)
1989 if (CC->quickroom.QRflags & QR_ANONOPT) {
1993 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1996 /* If we're only checking the validity of the request, return
1997 * success without creating the message.
2000 cprintf("%d %s\n", OK, buf);
2004 cprintf("%d send message\n", SEND_LISTING);
2006 /* Read in the message from the client. */
2007 if (CC->fake_postname[0])
2008 msg = make_message(&CC->usersupp, buf,
2009 CC->quickroom.QRname, b, e, format_type,
2011 else if (CC->fake_username[0])
2012 msg = make_message(&CC->usersupp, buf,
2013 CC->quickroom.QRname, b, e, format_type,
2016 msg = make_message(&CC->usersupp, buf,
2017 CC->quickroom.QRname, b, e, format_type, "");
2020 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2021 CtdlFreeMessage(msg);
2022 CC->fake_postname[0] = '\0';
2029 * message entry - mode 3 (raw)
2031 void cmd_ent3(char *entargs)
2037 unsigned char ch, which_field;
2038 struct usersupp tempUS;
2040 struct CtdlMessage *msg;
2043 if (CC->internal_pgm == 0) {
2044 cprintf("%d This command is for internal programs only.\n",
2049 /* See if there's a recipient, but make sure it's a real one */
2050 extract(recp, entargs, 1);
2051 for (a = 0; a < strlen(recp); ++a)
2052 if (!isprint(recp[a]))
2053 strcpy(&recp[a], &recp[a + 1]);
2054 while (isspace(recp[0]))
2055 strcpy(recp, &recp[1]);
2056 while (isspace(recp[strlen(recp) - 1]))
2057 recp[strlen(recp) - 1] = 0;
2059 /* If we're in Mail, check the recipient */
2060 if (strlen(recp) > 0) {
2061 e = alias(recp); /* alias and mail type */
2062 if ((recp[0] == 0) || (e == MES_ERROR)) {
2063 cprintf("%d Unknown address - cannot send message.\n",
2064 ERROR + NO_SUCH_USER);
2067 if (e == MES_LOCAL) {
2068 a = getuser(&tempUS, recp);
2070 cprintf("%d No such user.\n",
2071 ERROR + NO_SUCH_USER);
2077 /* At this point, message has been approved. */
2078 if (extract_int(entargs, 0) == 0) {
2079 cprintf("%d OK to send\n", OK);
2083 msglen = extract_long(entargs, 2);
2084 msg = mallok(sizeof(struct CtdlMessage));
2086 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2090 memset(msg, 0, sizeof(struct CtdlMessage));
2091 tempbuf = mallok(msglen);
2092 if (tempbuf == NULL) {
2093 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2098 cprintf("%d %ld\n", SEND_BINARY, msglen);
2100 client_read(&ch, 1); /* 0xFF magic number */
2101 msg->cm_magic = CTDLMESSAGE_MAGIC;
2102 client_read(&ch, 1); /* anon type */
2103 msg->cm_anon_type = ch;
2104 client_read(&ch, 1); /* format type */
2105 msg->cm_format_type = ch;
2106 msglen = msglen - 3;
2108 while (msglen > 0) {
2109 client_read(&which_field, 1);
2110 if (!isalpha(which_field)) valid_msg = 0;
2114 client_read(&ch, 1);
2116 a = strlen(tempbuf);
2119 } while ( (ch != 0) && (msglen > 0) );
2121 msg->cm_fields[which_field] = strdoop(tempbuf);
2124 msg->cm_flags = CM_SKIP_HOOKS;
2125 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2126 CtdlFreeMessage(msg);
2132 * API function to delete messages which match a set of criteria
2133 * (returns the actual number of messages deleted)
2135 int CtdlDeleteMessages(char *room_name, /* which room */
2136 long dmsgnum, /* or "0" for any */
2137 char *content_type /* or NULL for any */
2141 struct quickroom qrbuf;
2142 struct cdbdata *cdbfr;
2143 long *msglist = NULL;
2146 int num_deleted = 0;
2148 struct SuppMsgInfo smi;
2150 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2151 room_name, dmsgnum, content_type);
2153 /* get room record, obtaining a lock... */
2154 if (lgetroom(&qrbuf, room_name) != 0) {
2155 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2157 return (0); /* room not found */
2159 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2161 if (cdbfr != NULL) {
2162 msglist = mallok(cdbfr->len);
2163 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2164 num_msgs = cdbfr->len / sizeof(long);
2168 for (i = 0; i < num_msgs; ++i) {
2171 /* Set/clear a bit for each criterion */
2173 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2174 delete_this |= 0x01;
2176 if (content_type == NULL) {
2177 delete_this |= 0x02;
2179 GetSuppMsgInfo(&smi, msglist[i]);
2180 if (!strcasecmp(smi.smi_content_type,
2182 delete_this |= 0x02;
2186 /* Delete message only if all bits are set */
2187 if (delete_this == 0x03) {
2188 AdjRefCount(msglist[i], -1);
2194 num_msgs = sort_msglist(msglist, num_msgs);
2195 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2196 msglist, (num_msgs * sizeof(long)));
2198 qrbuf.QRhighest = msglist[num_msgs - 1];
2202 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2203 return (num_deleted);
2209 * Delete message from current room
2211 void cmd_dele(char *delstr)
2216 getuser(&CC->usersupp, CC->curr_user);
2217 if ((CC->usersupp.axlevel < 6)
2218 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2219 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2220 && (!(CC->internal_pgm))) {
2221 cprintf("%d Higher access required.\n",
2222 ERROR + HIGHER_ACCESS_REQUIRED);
2225 delnum = extract_long(delstr, 0);
2227 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2230 cprintf("%d %d message%s deleted.\n", OK,
2231 num_deleted, ((num_deleted != 1) ? "s" : ""));
2233 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2239 * move or copy a message to another room
2241 void cmd_move(char *args)
2245 struct quickroom qtemp;
2249 num = extract_long(args, 0);
2250 extract(targ, args, 1);
2251 targ[ROOMNAMELEN - 1] = 0;
2252 is_copy = extract_int(args, 2);
2254 getuser(&CC->usersupp, CC->curr_user);
2255 if ((CC->usersupp.axlevel < 6)
2256 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2257 cprintf("%d Higher access required.\n",
2258 ERROR + HIGHER_ACCESS_REQUIRED);
2262 if (getroom(&qtemp, targ) != 0) {
2263 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2267 err = CtdlSaveMsgPointerInRoom(targ, num,
2268 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2270 cprintf("%d Cannot store message in %s: error %d\n",
2275 /* Now delete the message from the source room,
2276 * if this is a 'move' rather than a 'copy' operation.
2278 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2280 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2286 * GetSuppMsgInfo() - Get the supplementary record for a message
2288 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2291 struct cdbdata *cdbsmi;
2294 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2295 smibuf->smi_msgnum = msgnum;
2296 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2298 /* Use the negative of the message number for its supp record index */
2299 TheIndex = (0L - msgnum);
2301 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2302 if (cdbsmi == NULL) {
2303 return; /* record not found; go with defaults */
2305 memcpy(smibuf, cdbsmi->ptr,
2306 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2307 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2314 * PutSuppMsgInfo() - (re)write supplementary record for a message
2316 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2320 /* Use the negative of the message number for its supp record index */
2321 TheIndex = (0L - smibuf->smi_msgnum);
2323 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2324 smibuf->smi_msgnum, smibuf->smi_refcount);
2326 cdb_store(CDB_MSGMAIN,
2327 &TheIndex, sizeof(long),
2328 smibuf, sizeof(struct SuppMsgInfo));
2333 * AdjRefCount - change the reference count for a message;
2334 * delete the message if it reaches zero
2336 void AdjRefCount(long msgnum, int incr)
2339 struct SuppMsgInfo smi;
2342 /* This is a *tight* critical section; please keep it that way, as
2343 * it may get called while nested in other critical sections.
2344 * Complicating this any further will surely cause deadlock!
2346 begin_critical_section(S_SUPPMSGMAIN);
2347 GetSuppMsgInfo(&smi, msgnum);
2348 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2349 msgnum, smi.smi_refcount);
2350 smi.smi_refcount += incr;
2351 PutSuppMsgInfo(&smi);
2352 end_critical_section(S_SUPPMSGMAIN);
2353 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2354 msgnum, smi.smi_refcount);
2356 /* If the reference count is now zero, delete the message
2357 * (and its supplementary record as well).
2359 if (smi.smi_refcount == 0) {
2360 lprintf(9, "Deleting message <%ld>\n", msgnum);
2362 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2363 delnum = (0L - msgnum);
2364 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2369 * Write a generic object to this room
2371 * Note: this could be much more efficient. Right now we use two temporary
2372 * files, and still pull the message into memory as with all others.
2374 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2375 char *content_type, /* MIME type of this object */
2376 char *tempfilename, /* Where to fetch it from */
2377 struct usersupp *is_mailbox, /* Mailbox room? */
2378 int is_binary, /* Is encoding necessary? */
2379 int is_unique, /* Del others of this type? */
2380 unsigned int flags /* Internal save flags */
2385 char filename[PATH_MAX];
2388 struct quickroom qrbuf;
2389 char roomname[ROOMNAMELEN];
2390 struct CtdlMessage *msg;
2393 if (is_mailbox != NULL)
2394 MailboxName(roomname, is_mailbox, req_room);
2396 safestrncpy(roomname, req_room, sizeof(roomname));
2397 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2399 strcpy(filename, tmpnam(NULL));
2400 fp = fopen(filename, "w");
2404 tempfp = fopen(tempfilename, "r");
2405 if (tempfp == NULL) {
2411 fprintf(fp, "Content-type: %s\n", content_type);
2412 lprintf(9, "Content-type: %s\n", content_type);
2414 if (is_binary == 0) {
2415 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2416 while (ch = getc(tempfp), ch > 0)
2422 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2425 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2426 tempfilename, filename);
2430 lprintf(9, "Allocating\n");
2431 msg = mallok(sizeof(struct CtdlMessage));
2432 memset(msg, 0, sizeof(struct CtdlMessage));
2433 msg->cm_magic = CTDLMESSAGE_MAGIC;
2434 msg->cm_anon_type = MES_NORMAL;
2435 msg->cm_format_type = 4;
2436 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2437 msg->cm_fields['O'] = strdoop(req_room);
2438 msg->cm_fields['N'] = strdoop(config.c_nodename);
2439 msg->cm_fields['H'] = strdoop(config.c_humannode);
2440 msg->cm_flags = flags;
2442 lprintf(9, "Loading\n");
2443 fp = fopen(filename, "rb");
2444 fseek(fp, 0L, SEEK_END);
2447 msg->cm_fields['M'] = mallok(len);
2448 fread(msg->cm_fields['M'], len, 1, fp);
2452 /* Create the requested room if we have to. */
2453 if (getroom(&qrbuf, roomname) != 0) {
2454 create_room(roomname,
2455 ( (is_mailbox != NULL) ? 4 : 3 ),
2458 /* If the caller specified this object as unique, delete all
2459 * other objects of this type that are currently in the room.
2462 lprintf(9, "Deleted %d other msgs of this type\n",
2463 CtdlDeleteMessages(roomname, 0L, content_type));
2465 /* Now write the data */
2466 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2467 CtdlFreeMessage(msg);
2475 void CtdlGetSysConfigBackend(long msgnum) {
2476 config_msgnum = msgnum;
2480 char *CtdlGetSysConfig(char *sysconfname) {
2481 char hold_rm[ROOMNAMELEN];
2484 struct CtdlMessage *msg;
2487 strcpy(hold_rm, CC->quickroom.QRname);
2488 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2489 getroom(&CC->quickroom, hold_rm);
2494 /* We want the last (and probably only) config in this room */
2495 begin_critical_section(S_CONFIG);
2496 config_msgnum = (-1L);
2497 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2498 CtdlGetSysConfigBackend);
2499 msgnum = config_msgnum;
2500 end_critical_section(S_CONFIG);
2506 msg = CtdlFetchMessage(msgnum);
2508 conf = strdoop(msg->cm_fields['M']);
2509 CtdlFreeMessage(msg);
2516 getroom(&CC->quickroom, hold_rm);
2518 lprintf(9, "eggstracting...\n");
2519 if (conf != NULL) do {
2520 extract_token(buf, conf, 0, '\n');
2521 lprintf(9, "eggstracted <%s>\n", buf);
2522 strcpy(conf, &conf[strlen(buf)+1]);
2523 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2528 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2529 char temp[PATH_MAX];
2532 strcpy(temp, tmpnam(NULL));
2534 fp = fopen(temp, "w");
2535 if (fp == NULL) return;
2536 fprintf(fp, "%s", sysconfdata);
2539 /* this handy API function does all the work for us */
2540 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);