21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
32 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
33 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
34 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
36 extern struct config config;
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
74 * This function is self explanatory.
75 * (What can I say, I'm in a weird mood today...)
77 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
81 for (i = 0; i < strlen(name); ++i)
84 if (isspace(name[i - 1])) {
85 strcpy(&name[i - 1], &name[i]);
88 while (isspace(name[i + 1])) {
89 strcpy(&name[i + 1], &name[i + 2]);
96 * Aliasing for network mail.
97 * (Error messages have been commented out, because this is a server.)
100 { /* process alias and routing info for mail */
103 char aaa[300], bbb[300];
105 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
107 fp = fopen("network/mail.aliases", "r");
109 fp = fopen("/dev/null", "r");
114 while (fgets(aaa, sizeof aaa, fp) != NULL) {
115 while (isspace(name[0]))
116 strcpy(name, &name[1]);
117 aaa[strlen(aaa) - 1] = 0;
119 for (a = 0; a < strlen(aaa); ++a) {
121 strcpy(bbb, &aaa[a + 1]);
125 if (!strcasecmp(name, aaa))
129 lprintf(7, "Mail is being forwarded to %s\n", name);
131 /* determine local or remote type, see citadel.h */
132 for (a = 0; a < strlen(name); ++a)
134 return (MES_INTERNET);
135 for (a = 0; a < strlen(name); ++a)
137 for (b = a; b < strlen(name); ++b)
139 return (MES_INTERNET);
141 for (a = 0; a < strlen(name); ++a)
145 lprintf(7, "Too many @'s in address\n");
149 for (a = 0; a < strlen(name); ++a)
151 strcpy(bbb, &name[a + 1]);
153 strcpy(bbb, &bbb[1]);
154 fp = fopen("network/mail.sysinfo", "r");
158 a = getstring(fp, aaa);
159 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
160 a = getstring(fp, aaa);
161 if (!strncmp(aaa, "use ", 4)) {
162 strcpy(bbb, &aaa[4]);
167 if (!strncmp(aaa, "uum", 3)) {
169 for (a = 0; a < strlen(bbb); ++a) {
175 while (bbb[strlen(bbb) - 1] == '_')
176 bbb[strlen(bbb) - 1] = 0;
177 sprintf(name, &aaa[4], bbb);
178 return (MES_INTERNET);
180 if (!strncmp(aaa, "bin", 3)) {
183 while (aaa[strlen(aaa) - 1] != '@')
184 aaa[strlen(aaa) - 1] = 0;
185 aaa[strlen(aaa) - 1] = 0;
186 while (aaa[strlen(aaa) - 1] == ' ')
187 aaa[strlen(aaa) - 1] = 0;
188 while (bbb[0] != '@')
189 strcpy(bbb, &bbb[1]);
190 strcpy(bbb, &bbb[1]);
191 while (bbb[0] == ' ')
192 strcpy(bbb, &bbb[1]);
193 sprintf(name, "%s @%s", aaa, bbb);
206 fp = fopen("citadel.control", "r");
207 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
213 void simple_listing(long msgnum)
215 cprintf("%ld\n", msgnum);
220 /* Determine if a given message matches the fields in a message template.
221 * Return 0 for a successful match.
223 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
226 /* If there aren't any fields in the template, all messages will
229 if (template == NULL) return(0);
231 /* Null messages are bogus. */
232 if (msg == NULL) return(1);
234 for (i='A'; i<='Z'; ++i) {
235 if (template->cm_fields[i] != NULL) {
236 if (msg->cm_fields[i] == NULL) {
239 if (strcasecmp(msg->cm_fields[i],
240 template->cm_fields[i])) return 1;
244 /* All compares succeeded: we have a match! */
252 * API function to perform an operation for each qualifying message in the
255 void CtdlForEachMessage(int mode, long ref,
257 struct CtdlMessage *compare,
258 void (*CallBack) (long msgnum))
263 struct cdbdata *cdbfr;
264 long *msglist = NULL;
267 struct SuppMsgInfo smi;
268 struct CtdlMessage *msg;
270 /* Learn about the user and room in question */
272 getuser(&CC->usersupp, CC->curr_user);
273 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
275 /* Load the message list */
276 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
278 msglist = mallok(cdbfr->len);
279 memcpy(msglist, cdbfr->ptr, cdbfr->len);
280 num_msgs = cdbfr->len / sizeof(long);
283 return; /* No messages at all? No further action. */
287 /* If the caller is looking for a specific MIME type, then filter
288 * out all messages which are not of the type requested.
291 if (content_type != NULL)
292 if (strlen(content_type) > 0)
293 for (a = 0; a < num_msgs; ++a) {
294 GetSuppMsgInfo(&smi, msglist[a]);
295 if (strcasecmp(smi.smi_content_type, content_type)) {
300 num_msgs = sort_msglist(msglist, num_msgs);
302 /* If a template was supplied, filter out the messages which
303 * don't match. (This could induce some delays!)
306 if (compare != NULL) {
307 for (a = 0; a < num_msgs; ++a) {
308 msg = CtdlFetchMessage(msglist[a]);
310 if (CtdlMsgCmp(msg, compare)) {
313 CtdlFreeMessage(msg);
321 * Now iterate through the message list, according to the
322 * criteria supplied by the caller.
325 for (a = 0; a < num_msgs; ++a) {
326 thismsg = msglist[a];
331 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
332 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
333 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
334 && (CC->usersupp.flags & US_LASTOLD))
335 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
336 || ((mode == MSGS_FIRST) && (a < ref))
337 || ((mode == MSGS_GT) && (thismsg > ref))
343 phree(msglist); /* Clean up */
349 * cmd_msgs() - get list of message #'s in this room
350 * implements the MSGS server command using CtdlForEachMessage()
352 void cmd_msgs(char *cmdbuf)
361 int with_template = 0;
362 struct CtdlMessage *template = NULL;
364 extract(which, cmdbuf, 0);
365 cm_ref = extract_int(cmdbuf, 1);
366 with_template = extract_int(cmdbuf, 2);
370 if (!strncasecmp(which, "OLD", 3))
372 else if (!strncasecmp(which, "NEW", 3))
374 else if (!strncasecmp(which, "FIRST", 5))
376 else if (!strncasecmp(which, "LAST", 4))
378 else if (!strncasecmp(which, "GT", 2))
381 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
382 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
387 cprintf("%d Send template then receive message list\n",
389 template = (struct CtdlMessage *)
390 mallok(sizeof(struct CtdlMessage));
391 memset(template, 0, sizeof(struct CtdlMessage));
392 while(client_gets(buf), strcmp(buf,"000")) {
393 extract(tfield, buf, 0);
394 extract(tvalue, buf, 1);
395 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
396 if (!strcasecmp(tfield, msgkeys[i])) {
397 template->cm_fields[i] =
404 cprintf("%d Message list...\n", LISTING_FOLLOWS);
407 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
408 if (template != NULL) CtdlFreeMessage(template);
416 * help_subst() - support routine for help file viewer
418 void help_subst(char *strbuf, char *source, char *dest)
423 while (p = pattern2(strbuf, source), (p >= 0)) {
424 strcpy(workbuf, &strbuf[p + strlen(source)]);
425 strcpy(&strbuf[p], dest);
426 strcat(strbuf, workbuf);
431 void do_help_subst(char *buffer)
435 help_subst(buffer, "^nodename", config.c_nodename);
436 help_subst(buffer, "^humannode", config.c_humannode);
437 help_subst(buffer, "^fqdn", config.c_fqdn);
438 help_subst(buffer, "^username", CC->usersupp.fullname);
439 sprintf(buf2, "%ld", CC->usersupp.usernum);
440 help_subst(buffer, "^usernum", buf2);
441 help_subst(buffer, "^sysadm", config.c_sysadm);
442 help_subst(buffer, "^variantname", CITADEL);
443 sprintf(buf2, "%d", config.c_maxsessions);
444 help_subst(buffer, "^maxsessions", buf2);
450 * memfmout() - Citadel text formatter and paginator.
451 * Although the original purpose of this routine was to format
452 * text to the reader's screen width, all we're really using it
453 * for here is to format text out to 80 columns before sending it
454 * to the client. The client software may reformat it again.
457 int width, /* screen width to use */
458 char *mptr, /* where are we going to get our text from? */
459 char subst) /* nonzero if we should do substitutions */
471 c = 1; /* c is the current pos */
474 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
476 buffer[strlen(buffer) + 1] = 0;
477 buffer[strlen(buffer)] = ch;
480 if (buffer[0] == '^')
481 do_help_subst(buffer);
483 buffer[strlen(buffer) + 1] = 0;
485 strcpy(buffer, &buffer[1]);
495 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
497 if (((old == 13) || (old == 10)) && (isspace(real))) {
505 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
506 cprintf("\n%s", aaa);
515 if ((strlen(aaa) + c) > (width - 5)) {
525 if ((ch == 13) || (ch == 10)) {
526 cprintf("%s\n", aaa);
533 FMTEND: cprintf("%s\n", aaa);
539 * Callback function for mime parser that simply lists the part
541 void list_this_part(char *name, char *filename, char *partnum, char *disp,
542 void *content, char *cbtype, size_t length)
545 cprintf("part=%s|%s|%s|%s|%s|%d\n",
546 name, filename, partnum, disp, cbtype, length);
551 * Callback function for mime parser that opens a section for downloading
553 void mime_download(char *name, char *filename, char *partnum, char *disp,
554 void *content, char *cbtype, size_t length)
557 /* Silently go away if there's already a download open... */
558 if (CC->download_fp != NULL)
561 /* ...or if this is not the desired section */
562 if (strcasecmp(desired_section, partnum))
565 CC->download_fp = tmpfile();
566 if (CC->download_fp == NULL)
569 fwrite(content, length, 1, CC->download_fp);
570 fflush(CC->download_fp);
571 rewind(CC->download_fp);
573 OpenCmdResult(filename, cbtype);
579 * Load a message from disk into memory.
580 * This is used by CtdlOutputMsg() and other fetch functions.
582 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
583 * using the CtdlMessageFree() function.
585 struct CtdlMessage *CtdlFetchMessage(long msgnum)
587 struct cdbdata *dmsgtext;
588 struct CtdlMessage *ret = NULL;
591 CIT_UBYTE field_header;
594 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
595 if (dmsgtext == NULL) {
598 mptr = dmsgtext->ptr;
600 /* Parse the three bytes that begin EVERY message on disk.
601 * The first is always 0xFF, the on-disk magic number.
602 * The second is the anonymous/public type byte.
603 * The third is the format type byte (vari, fixed, or MIME).
607 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
611 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
612 memset(ret, 0, sizeof(struct CtdlMessage));
614 ret->cm_magic = CTDLMESSAGE_MAGIC;
615 ret->cm_anon_type = *mptr++; /* Anon type byte */
616 ret->cm_format_type = *mptr++; /* Format type byte */
619 * The rest is zero or more arbitrary fields. Load them in.
620 * We're done when we encounter either a zero-length field or
621 * have just processed the 'M' (message text) field.
624 field_length = strlen(mptr);
625 if (field_length == 0)
627 field_header = *mptr++;
628 ret->cm_fields[field_header] = mallok(field_length);
629 strcpy(ret->cm_fields[field_header], mptr);
631 while (*mptr++ != 0); /* advance to next field */
633 } while ((field_length > 0) && (field_header != 'M'));
637 /* Always make sure there's something in the msg text field */
638 if (ret->cm_fields['M'] == NULL)
639 ret->cm_fields['M'] = strdoop("<no text>\n");
641 /* Perform "before read" hooks (aborting if any return nonzero) */
642 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
643 CtdlFreeMessage(ret);
652 * Returns 1 if the supplied pointer points to a valid Citadel message.
653 * If the pointer is NULL or the magic number check fails, returns 0.
655 int is_valid_message(struct CtdlMessage *msg) {
658 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
659 lprintf(3, "is_valid_message() -- self-check failed\n");
667 * 'Destructor' for struct CtdlMessage
669 void CtdlFreeMessage(struct CtdlMessage *msg)
673 if (is_valid_message(msg) == 0) return;
675 for (i = 0; i < 256; ++i)
676 if (msg->cm_fields[i] != NULL)
677 phree(msg->cm_fields[i]);
679 msg->cm_magic = 0; /* just in case */
685 * Callback function for mime parser that wants to display text
687 void fixed_output(char *name, char *filename, char *partnum, char *disp,
688 void *content, char *cbtype, size_t length)
695 if (!strcasecmp(cbtype, "multipart/alternative")) {
696 strcpy(ma->prefix, partnum);
697 strcat(ma->prefix, ".");
703 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
705 && (ma->did_print == 1) ) {
706 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
712 if ( (!strcasecmp(cbtype, "text/plain"))
713 || (strlen(cbtype)==0) ) {
718 if (ch==10) cprintf("\r\n");
719 else cprintf("%c", ch);
722 else if (!strcasecmp(cbtype, "text/html")) {
723 ptr = html_to_ascii(content, 80, 0);
728 if (ch==10) cprintf("\r\n");
729 else cprintf("%c", ch);
733 else if (strncasecmp(cbtype, "multipart/", 10)) {
734 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
735 partnum, filename, cbtype, length);
741 * Get a message off disk. (returns om_* values found in msgbase.h)
744 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
745 int mode, /* how would you like that message? */
746 int headers_only, /* eschew the message body? */
747 int do_proto, /* do Citadel protocol responses? */
748 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 */
769 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
773 sprintf(mid, "%ld", msg_num);
774 nl = (crlf ? "\r\n" : "\n");
776 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
777 if (do_proto) cprintf("%d Not logged in.\n",
778 ERROR + NOT_LOGGED_IN);
779 return(om_not_logged_in);
782 /* FIX ... small security issue
783 * We need to check to make sure the requested message is actually
784 * in the current room, and set msg_ok to 1 only if it is. This
785 * functionality is currently missing because I'm in a hurry to replace
786 * broken production code with nonbroken pre-beta code. :( -- ajc
789 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
791 return(om_no_such_msg);
796 * Fetch the message from disk
798 TheMessage = CtdlFetchMessage(msg_num);
799 if (TheMessage == NULL) {
800 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
802 return(om_no_such_msg);
805 /* Are we downloading a MIME component? */
806 if (mode == MT_DOWNLOAD) {
807 if (TheMessage->cm_format_type != FMT_RFC822) {
809 cprintf("%d This is not a MIME message.\n",
811 } else if (CC->download_fp != NULL) {
812 if (do_proto) cprintf(
813 "%d You already have a download open.\n",
816 /* Parse the message text component */
817 mptr = TheMessage->cm_fields['M'];
818 mime_parser(mptr, NULL, *mime_download);
819 /* If there's no file open by this time, the requested
820 * section wasn't found, so print an error
822 if (CC->download_fp == NULL) {
823 if (do_proto) cprintf(
824 "%d Section %s not found.\n",
825 ERROR + FILE_NOT_FOUND,
829 CtdlFreeMessage(TheMessage);
830 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
833 /* now for the user-mode message reading loops */
834 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
836 /* Tell the client which format type we're using. If this is a
837 * MIME message, *lie* about it and tell the user it's fixed-format.
839 if (mode == MT_CITADEL) {
840 if (TheMessage->cm_format_type == FMT_RFC822) {
841 if (do_proto) cprintf("type=1\n");
844 if (do_proto) cprintf("type=%d\n",
845 TheMessage->cm_format_type);
849 /* nhdr=yes means that we're only displaying headers, no body */
850 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
851 if (do_proto) cprintf("nhdr=yes\n");
854 /* begin header processing loop for Citadel message format */
856 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
858 strcpy(display_name, "<unknown>");
859 if (TheMessage->cm_fields['A']) {
860 strcpy(buf, TheMessage->cm_fields['A']);
861 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
862 if (TheMessage->cm_anon_type == MES_ANON)
863 strcpy(display_name, "****");
864 else if (TheMessage->cm_anon_type == MES_AN2)
865 strcpy(display_name, "anonymous");
867 strcpy(display_name, buf);
869 && ((TheMessage->cm_anon_type == MES_ANON)
870 || (TheMessage->cm_anon_type == MES_AN2))) {
871 sprintf(&display_name[strlen(display_name)],
876 strcpy(allkeys, FORDER);
877 for (i=0; i<strlen(allkeys); ++i) {
878 k = (int) allkeys[i];
880 if (TheMessage->cm_fields[k] != NULL) {
882 if (do_proto) cprintf("%s=%s\n",
887 if (do_proto) cprintf("%s=%s\n",
889 TheMessage->cm_fields[k]
898 /* begin header processing loop for RFC822 transfer format */
903 strcpy(snode, NODENAME);
904 strcpy(lnode, HUMANNODE);
905 if (mode == MT_RFC822) {
906 cprintf("X-UIDL: %ld%s", msg_num, nl);
907 for (i = 0; i < 256; ++i) {
908 if (TheMessage->cm_fields[i]) {
909 mptr = TheMessage->cm_fields[i];
913 } else if (i == 'P') {
914 cprintf("Path: %s%s", mptr, nl);
915 for (a = 0; a < strlen(mptr); ++a) {
916 if (mptr[a] == '!') {
917 strcpy(mptr, &mptr[a + 1]);
923 cprintf("Subject: %s%s", mptr, nl);
929 cprintf("X-Citadel-Room: %s%s",
934 cprintf("To: %s%s", mptr, nl);
937 cprintf("Date: %s", asctime(localtime(&xtime)));
943 if (mode == MT_RFC822) {
944 if (!strcasecmp(snode, NODENAME)) {
947 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
948 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
950 if (strlen(fuser) > 0) {
951 cprintf("From: %s (%s)%s", fuser, luser, nl);
954 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
957 cprintf("Organization: %s%s", lnode, nl);
960 /* end header processing loop ... at this point, we're in the text */
962 mptr = TheMessage->cm_fields['M'];
964 /* Tell the client about the MIME parts in this message */
965 if (TheMessage->cm_format_type == FMT_RFC822) {
966 if (mode == MT_CITADEL) {
967 mime_parser(mptr, NULL, *list_this_part);
969 else if (mode == MT_MIME) { /* list parts only */
970 mime_parser(mptr, NULL, *list_this_part);
971 if (do_proto) cprintf("000\n");
972 CtdlFreeMessage(TheMessage);
975 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
976 /* FIX ... we have to put some code in here to avoid
977 * printing duplicate header information when both
978 * Citadel and RFC822 headers exist. Preference should
979 * probably be given to the RFC822 headers.
981 while (ch=*(mptr++), ch!=0) {
983 else if (ch==10) cprintf("%s", nl);
984 else cprintf("%c", ch);
986 if (do_proto) cprintf("000\n");
987 CtdlFreeMessage(TheMessage);
993 if (do_proto) cprintf("000\n");
994 CtdlFreeMessage(TheMessage);
998 /* signify start of msg text */
999 if (mode == MT_CITADEL)
1000 if (do_proto) cprintf("text\n");
1001 if (mode == MT_RFC822) {
1005 /* If the format type on disk is 1 (fixed-format), then we want
1006 * everything to be output completely literally ... regardless of
1007 * what message transfer format is in use.
1009 if (TheMessage->cm_format_type == FMT_FIXED) {
1011 while (ch = *mptr++, ch > 0) {
1014 if ((ch == 10) || (strlen(buf) > 250)) {
1015 cprintf("%s%s", buf, nl);
1018 buf[strlen(buf) + 1] = 0;
1019 buf[strlen(buf)] = ch;
1022 if (strlen(buf) > 0)
1023 cprintf("%s%s", buf, nl);
1026 /* If the message on disk is format 0 (Citadel vari-format), we
1027 * output using the formatter at 80 columns. This is the final output
1028 * form if the transfer format is RFC822, but if the transfer format
1029 * is Citadel proprietary, it'll still work, because the indentation
1030 * for new paragraphs is correct and the client will reformat the
1031 * message to the reader's screen width.
1033 if (TheMessage->cm_format_type == FMT_CITADEL) {
1034 memfmout(80, mptr, 0);
1037 /* If the message on disk is format 4 (MIME), we've gotta hand it
1038 * off to the MIME parser. The client has already been told that
1039 * this message is format 1 (fixed format), so the callback function
1040 * we use will display those parts as-is.
1042 if (TheMessage->cm_format_type == FMT_RFC822) {
1043 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1044 memset(ma, 0, sizeof(struct ma_info));
1045 mime_parser(mptr, NULL, *fixed_output);
1048 /* now we're done */
1049 if (do_proto) cprintf("000\n");
1050 CtdlFreeMessage(TheMessage);
1057 * display a message (mode 0 - Citadel proprietary)
1059 void cmd_msg0(char *cmdbuf)
1062 int headers_only = 0;
1064 msgid = extract_long(cmdbuf, 0);
1065 headers_only = extract_int(cmdbuf, 1);
1067 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1073 * display a message (mode 2 - RFC822)
1075 void cmd_msg2(char *cmdbuf)
1078 int headers_only = 0;
1080 msgid = extract_long(cmdbuf, 0);
1081 headers_only = extract_int(cmdbuf, 1);
1083 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1089 * display a message (mode 3 - IGnet raw format - internal programs only)
1091 void cmd_msg3(char *cmdbuf)
1094 struct CtdlMessage *msg;
1097 if (CC->internal_pgm == 0) {
1098 cprintf("%d This command is for internal programs only.\n",
1103 msgnum = extract_long(cmdbuf, 0);
1104 msg = CtdlFetchMessage(msgnum);
1106 cprintf("%d Message %ld not found.\n",
1111 serialize_message(&smr, msg);
1112 CtdlFreeMessage(msg);
1115 cprintf("%d Unable to serialize message\n",
1116 ERROR+INTERNAL_ERROR);
1120 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1121 client_write(smr.ser, smr.len);
1128 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1130 void cmd_msg4(char *cmdbuf)
1134 msgid = extract_long(cmdbuf, 0);
1135 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1139 * Open a component of a MIME message as a download file
1141 void cmd_opna(char *cmdbuf)
1145 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1147 msgid = extract_long(cmdbuf, 0);
1148 extract(desired_section, cmdbuf, 1);
1150 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1155 * Save a message pointer into a specified room
1156 * (Returns 0 for success, nonzero for failure)
1157 * roomname may be NULL to use the current room
1159 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1161 char hold_rm[ROOMNAMELEN];
1162 struct cdbdata *cdbfr;
1165 long highest_msg = 0L;
1166 struct CtdlMessage *msg = NULL;
1168 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1169 roomname, msgid, flags);
1171 strcpy(hold_rm, CC->quickroom.QRname);
1173 /* We may need to check to see if this message is real */
1174 if ( (flags & SM_VERIFY_GOODNESS)
1175 || (flags & SM_DO_REPL_CHECK)
1177 msg = CtdlFetchMessage(msgid);
1178 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1181 /* Perform replication checks if necessary */
1182 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1184 if (getroom(&CC->quickroom,
1185 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1187 lprintf(9, "No such room <%s>\n", roomname);
1188 if (msg != NULL) CtdlFreeMessage(msg);
1189 return(ERROR + ROOM_NOT_FOUND);
1192 if (ReplicationChecks(msg) != 0) {
1193 getroom(&CC->quickroom, hold_rm);
1194 if (msg != NULL) CtdlFreeMessage(msg);
1195 lprintf(9, "Did replication, and newer exists\n");
1200 /* Now the regular stuff */
1201 if (lgetroom(&CC->quickroom,
1202 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1204 lprintf(9, "No such room <%s>\n", roomname);
1205 if (msg != NULL) CtdlFreeMessage(msg);
1206 return(ERROR + ROOM_NOT_FOUND);
1209 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1210 if (cdbfr == NULL) {
1214 msglist = mallok(cdbfr->len);
1215 if (msglist == NULL)
1216 lprintf(3, "ERROR malloc msglist!\n");
1217 num_msgs = cdbfr->len / sizeof(long);
1218 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1223 /* Make sure the message doesn't already exist in this room. It
1224 * is absolutely taboo to have more than one reference to the same
1225 * message in a room.
1227 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1228 if (msglist[i] == msgid) {
1229 lputroom(&CC->quickroom); /* unlock the room */
1230 getroom(&CC->quickroom, hold_rm);
1231 if (msg != NULL) CtdlFreeMessage(msg);
1232 return(ERROR + ALREADY_EXISTS);
1236 /* Now add the new message */
1238 msglist = reallok(msglist,
1239 (num_msgs * sizeof(long)));
1241 if (msglist == NULL) {
1242 lprintf(3, "ERROR: can't realloc message list!\n");
1244 msglist[num_msgs - 1] = msgid;
1246 /* Sort the message list, so all the msgid's are in order */
1247 num_msgs = sort_msglist(msglist, num_msgs);
1249 /* Determine the highest message number */
1250 highest_msg = msglist[num_msgs - 1];
1252 /* Write it back to disk. */
1253 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1254 msglist, num_msgs * sizeof(long));
1256 /* Free up the memory we used. */
1259 /* Update the highest-message pointer and unlock the room. */
1260 CC->quickroom.QRhighest = highest_msg;
1261 lputroom(&CC->quickroom);
1262 getroom(&CC->quickroom, hold_rm);
1264 /* Bump the reference count for this message. */
1265 if ((flags & SM_DONT_BUMP_REF)==0) {
1266 AdjRefCount(msgid, +1);
1269 /* Return success. */
1270 if (msg != NULL) CtdlFreeMessage(msg);
1277 * Message base operation to send a message to the master file
1278 * (returns new message number)
1280 * This is the back end for CtdlSaveMsg() and should not be directly
1281 * called by server-side modules.
1284 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1285 int generate_id, /* generate 'I' field? */
1286 FILE *save_a_copy) /* save a copy to disk? */
1293 /* Get a new message number */
1294 newmsgid = get_new_message_number();
1295 sprintf(msgidbuf, "%ld", newmsgid);
1298 msg->cm_fields['I'] = strdoop(msgidbuf);
1301 serialize_message(&smr, msg);
1304 cprintf("%d Unable to serialize message\n",
1305 ERROR+INTERNAL_ERROR);
1309 /* Write our little bundle of joy into the message base */
1310 begin_critical_section(S_MSGMAIN);
1311 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1312 smr.ser, smr.len) < 0) {
1313 lprintf(2, "Can't store message\n");
1318 end_critical_section(S_MSGMAIN);
1320 /* If the caller specified that a copy should be saved to a particular
1321 * file handle, do that now too.
1323 if (save_a_copy != NULL) {
1324 fwrite(smr.ser, smr.len, 1, save_a_copy);
1327 /* Free the memory we used for the serialized message */
1330 /* Return the *local* message ID to the caller
1331 * (even if we're storing an incoming network message)
1339 * Serialize a struct CtdlMessage into the format used on disk and network.
1341 * This function loads up a "struct ser_ret" (defined in server.h) which
1342 * contains the length of the serialized message and a pointer to the
1343 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1345 void serialize_message(struct ser_ret *ret, /* return values */
1346 struct CtdlMessage *msg) /* unserialized msg */
1350 static char *forder = FORDER;
1352 if (is_valid_message(msg) == 0) return; /* self check */
1355 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1356 ret->len = ret->len +
1357 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1359 lprintf(9, "calling malloc\n");
1360 ret->ser = mallok(ret->len);
1361 if (ret->ser == NULL) {
1367 ret->ser[1] = msg->cm_anon_type;
1368 ret->ser[2] = msg->cm_format_type;
1371 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1372 ret->ser[wlen++] = (char)forder[i];
1373 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1374 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1376 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1385 * Back end for the ReplicationChecks() function
1387 void check_repl(long msgnum) {
1388 struct CtdlMessage *msg;
1389 time_t timestamp = (-1L);
1391 lprintf(9, "check_repl() found message %ld\n", msgnum);
1392 msg = CtdlFetchMessage(msgnum);
1393 if (msg == NULL) return;
1394 if (msg->cm_fields['T'] != NULL) {
1395 timestamp = atol(msg->cm_fields['T']);
1397 CtdlFreeMessage(msg);
1399 if (timestamp > msg_repl->highest) {
1400 msg_repl->highest = timestamp; /* newer! */
1401 lprintf(9, "newer!\n");
1404 lprintf(9, "older!\n");
1406 /* Existing isn't newer? Then delete the old one(s). */
1407 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1412 * Check to see if any messages already exist which carry the same Extended ID
1416 * -> With older timestamps: delete them and return 0. Message will be saved.
1417 * -> With newer timestamps: return 1. Message save will be aborted.
1419 int ReplicationChecks(struct CtdlMessage *msg) {
1420 struct CtdlMessage *template;
1423 lprintf(9, "ReplicationChecks() started\n");
1424 /* No extended id? Don't do anything. */
1425 if (msg->cm_fields['E'] == NULL) return 0;
1426 if (strlen(msg->cm_fields['E']) == 0) return 0;
1427 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1429 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1430 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1431 msg_repl->highest = atol(msg->cm_fields['T']);
1433 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1434 memset(template, 0, sizeof(struct CtdlMessage));
1435 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1437 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1439 /* If a newer message exists with the same Extended ID, abort
1442 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1446 CtdlFreeMessage(template);
1447 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1455 * Save a message to disk
1457 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1458 char *rec, /* Recipient (mail) */
1459 char *force, /* force a particular room? */
1460 int mailtype, /* local or remote type */
1461 int generate_id) /* 1 = generate 'I' field */
1464 char hold_rm[ROOMNAMELEN];
1465 char actual_rm[ROOMNAMELEN];
1466 char force_room[ROOMNAMELEN];
1467 char content_type[256]; /* We have to learn this */
1468 char recipient[256];
1471 struct usersupp userbuf;
1473 struct SuppMsgInfo smi;
1474 FILE *network_fp = NULL;
1475 static int seqnum = 1;
1476 struct CtdlMessage *imsg;
1479 lprintf(9, "CtdlSaveMsg() called\n");
1480 if (is_valid_message(msg) == 0) return(-1); /* self check */
1482 /* If this message has no timestamp, we take the liberty of
1483 * giving it one, right now.
1485 if (msg->cm_fields['T'] == NULL) {
1486 lprintf(9, "Generating timestamp\n");
1487 sprintf(aaa, "%ld", time(NULL));
1488 msg->cm_fields['T'] = strdoop(aaa);
1491 /* If this message has no path, we generate one.
1493 if (msg->cm_fields['P'] == NULL) {
1494 lprintf(9, "Generating path\n");
1495 if (msg->cm_fields['A'] != NULL) {
1496 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1497 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1498 if (isspace(msg->cm_fields['P'][a])) {
1499 msg->cm_fields['P'][a] = ' ';
1504 msg->cm_fields['P'] = strdoop("unknown");
1508 strcpy(force_room, force);
1510 /* Strip non-printable characters out of the recipient name */
1511 strcpy(recipient, rec);
1512 for (a = 0; a < strlen(recipient); ++a)
1513 if (!isprint(recipient[a]))
1514 strcpy(&recipient[a], &recipient[a + 1]);
1516 /* Learn about what's inside, because it's what's inside that counts */
1517 lprintf(9, "Learning what's inside\n");
1518 if (msg->cm_fields['M'] == NULL) {
1519 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1522 switch (msg->cm_format_type) {
1524 strcpy(content_type, "text/x-citadel-variformat");
1527 strcpy(content_type, "text/plain");
1530 strcpy(content_type, "text/plain");
1531 /* advance past header fields */
1532 mptr = msg->cm_fields['M'];
1535 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1536 safestrncpy(content_type, mptr,
1537 sizeof(content_type));
1538 strcpy(content_type, &content_type[14]);
1539 for (a = 0; a < strlen(content_type); ++a)
1540 if ((content_type[a] == ';')
1541 || (content_type[a] == ' ')
1542 || (content_type[a] == 13)
1543 || (content_type[a] == 10))
1544 content_type[a] = 0;
1551 /* Goto the correct room */
1552 lprintf(9, "Switching rooms\n");
1553 strcpy(hold_rm, CC->quickroom.QRname);
1554 strcpy(actual_rm, CC->quickroom.QRname);
1556 /* If the user is a twit, move to the twit room for posting */
1557 lprintf(9, "Handling twit stuff\n");
1559 if (CC->usersupp.axlevel == 2) {
1560 strcpy(hold_rm, actual_rm);
1561 strcpy(actual_rm, config.c_twitroom);
1565 /* ...or if this message is destined for Aide> then go there. */
1566 if (strlen(force_room) > 0) {
1567 strcpy(actual_rm, force_room);
1570 lprintf(9, "Possibly relocating\n");
1571 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1572 getroom(&CC->quickroom, actual_rm);
1576 * If this message has no O (room) field, generate one.
1578 if (msg->cm_fields['O'] == NULL) {
1579 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1582 /* Perform "before save" hooks (aborting if any return nonzero) */
1583 lprintf(9, "Performing before-save hooks\n");
1584 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1586 /* If this message has an Extended ID, perform replication checks */
1587 lprintf(9, "Performing replication checks\n");
1588 if (ReplicationChecks(msg) > 0) return(-1);
1590 /* Network mail - send a copy to the network program. */
1591 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1592 lprintf(9, "Sending network spool\n");
1593 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1594 (long) getpid(), CC->cs_pid, ++seqnum);
1595 lprintf(9, "Saving a copy to %s\n", aaa);
1596 network_fp = fopen(aaa, "ab+");
1597 if (network_fp == NULL)
1598 lprintf(2, "ERROR: %s\n", strerror(errno));
1601 /* Save it to disk */
1602 lprintf(9, "Saving to disk\n");
1603 newmsgid = send_message(msg, generate_id, network_fp);
1604 if (network_fp != NULL) {
1606 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1609 if (newmsgid <= 0L) return(-1);
1611 /* Write a supplemental message info record. This doesn't have to
1612 * be a critical section because nobody else knows about this message
1615 lprintf(9, "Creating SuppMsgInfo record\n");
1616 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1617 smi.smi_msgnum = newmsgid;
1618 smi.smi_refcount = 0;
1619 safestrncpy(smi.smi_content_type, content_type, 64);
1620 PutSuppMsgInfo(&smi);
1622 /* Now figure out where to store the pointers */
1623 lprintf(9, "Storing pointers\n");
1625 /* If this is being done by the networker delivering a private
1626 * message, we want to BYPASS saving the sender's copy (because there
1627 * is no local sender; it would otherwise go to the Trashcan).
1629 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1630 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1633 /* For internet mail, drop a copy in the outbound queue room */
1634 if (mailtype == MES_INTERNET) {
1635 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1638 /* Bump this user's messages posted counter. */
1639 lprintf(9, "Updating user\n");
1640 lgetuser(&CC->usersupp, CC->curr_user);
1641 CC->usersupp.posted = CC->usersupp.posted + 1;
1642 lputuser(&CC->usersupp);
1644 /* If this is private, local mail, make a copy in the
1645 * recipient's mailbox and bump the reference count.
1647 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1648 if (getuser(&userbuf, recipient) == 0) {
1649 lprintf(9, "Delivering private mail\n");
1650 MailboxName(actual_rm, &userbuf, MAILROOM);
1651 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1655 /* Perform "after save" hooks */
1656 lprintf(9, "Performing after-save hooks\n");
1657 PerformMessageHooks(msg, EVT_AFTERSAVE);
1660 lprintf(9, "Returning to original room\n");
1661 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1662 getroom(&CC->quickroom, hold_rm);
1664 /* For internet mail, generate delivery instructions
1665 * (Yes, this is recursive! Deal with it!)
1667 if (mailtype == MES_INTERNET) {
1668 lprintf(9, "Generating delivery instructions\n");
1669 instr = mallok(2048);
1671 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1673 SPOOLMIME, newmsgid, time(NULL), recipient );
1675 imsg = mallok(sizeof(struct CtdlMessage));
1676 memset(imsg, 0, sizeof(struct CtdlMessage));
1677 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1678 imsg->cm_anon_type = MES_NORMAL;
1679 imsg->cm_format_type = FMT_RFC822;
1680 imsg->cm_fields['A'] = strdoop("Citadel");
1681 imsg->cm_fields['M'] = instr;
1682 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1683 CtdlFreeMessage(imsg);
1692 * Convenience function for generating small administrative messages.
1694 void quickie_message(char *from, char *to, char *room, char *text)
1696 struct CtdlMessage *msg;
1698 msg = mallok(sizeof(struct CtdlMessage));
1699 memset(msg, 0, sizeof(struct CtdlMessage));
1700 msg->cm_magic = CTDLMESSAGE_MAGIC;
1701 msg->cm_anon_type = MES_NORMAL;
1702 msg->cm_format_type = 0;
1703 msg->cm_fields['A'] = strdoop(from);
1704 msg->cm_fields['O'] = strdoop(room);
1705 msg->cm_fields['N'] = strdoop(NODENAME);
1707 msg->cm_fields['R'] = strdoop(to);
1708 msg->cm_fields['M'] = strdoop(text);
1710 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1711 CtdlFreeMessage(msg);
1712 syslog(LOG_NOTICE, text);
1718 * Back end function used by make_message() and similar functions
1720 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1721 size_t maxlen, /* maximum message length */
1722 char *exist /* if non-null, append to it;
1723 exist is ALWAYS freed */
1726 size_t message_len = 0;
1727 size_t buffer_len = 0;
1731 if (exist == NULL) {
1735 m = reallok(exist, strlen(exist) + 4096);
1736 if (m == NULL) phree(exist);
1739 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1746 /* read in the lines of message text one by one */
1748 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1750 /* augment the buffer if we have to */
1751 if ((message_len + strlen(buf) + 2) > buffer_len) {
1752 lprintf(9, "realloking\n");
1753 ptr = reallok(m, (buffer_len * 2) );
1754 if (ptr == NULL) { /* flush if can't allocate */
1755 while ( (client_gets(buf)>0) &&
1756 strcmp(buf, terminator)) ;;
1759 buffer_len = (buffer_len * 2);
1762 lprintf(9, "buffer_len is %d\n", buffer_len);
1766 if (append == NULL) append = m;
1767 while (strlen(append) > 0) ++append;
1768 strcpy(append, buf);
1769 strcat(append, "\n");
1770 message_len = message_len + strlen(buf) + 1;
1772 /* if we've hit the max msg length, flush the rest */
1773 if (message_len >= maxlen) {
1774 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1785 * Build a binary message to be saved on disk.
1788 struct CtdlMessage *make_message(
1789 struct usersupp *author, /* author's usersupp structure */
1790 char *recipient, /* NULL if it's not mail */
1791 char *room, /* room where it's going */
1792 int type, /* see MES_ types in header file */
1793 int net_type, /* see MES_ types in header file */
1794 int format_type, /* local or remote (see citadel.h) */
1795 char *fake_name) /* who we're masquerading as */
1801 struct CtdlMessage *msg;
1803 msg = mallok(sizeof(struct CtdlMessage));
1804 memset(msg, 0, sizeof(struct CtdlMessage));
1805 msg->cm_magic = CTDLMESSAGE_MAGIC;
1806 msg->cm_anon_type = type;
1807 msg->cm_format_type = format_type;
1809 /* Don't confuse the poor folks if it's not routed mail. */
1810 strcpy(dest_node, "");
1812 /* If net_type is MES_BINARY, split out the destination node. */
1813 if (net_type == MES_BINARY) {
1814 strcpy(dest_node, NODENAME);
1815 for (a = 0; a < strlen(recipient); ++a) {
1816 if (recipient[a] == '@') {
1818 strcpy(dest_node, &recipient[a + 1]);
1823 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1824 if (net_type == MES_INTERNET) {
1825 strcpy(dest_node, "internet");
1828 while (isspace(recipient[strlen(recipient) - 1]))
1829 recipient[strlen(recipient) - 1] = 0;
1831 sprintf(buf, "cit%ld", author->usernum); /* Path */
1832 msg->cm_fields['P'] = strdoop(buf);
1834 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1835 msg->cm_fields['T'] = strdoop(buf);
1837 if (fake_name[0]) /* author */
1838 msg->cm_fields['A'] = strdoop(fake_name);
1840 msg->cm_fields['A'] = strdoop(author->fullname);
1842 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1843 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1845 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1847 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1848 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1850 if (recipient[0] != 0)
1851 msg->cm_fields['R'] = strdoop(recipient);
1852 if (dest_node[0] != 0)
1853 msg->cm_fields['D'] = strdoop(dest_node);
1856 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1857 config.c_maxmsglen, NULL);
1868 * message entry - mode 0 (normal)
1870 void cmd_ent0(char *entargs)
1873 char recipient[256];
1875 int format_type = 0;
1876 char newusername[256];
1877 struct CtdlMessage *msg;
1881 struct usersupp tempUS;
1884 post = extract_int(entargs, 0);
1885 extract(recipient, entargs, 1);
1886 anon_flag = extract_int(entargs, 2);
1887 format_type = extract_int(entargs, 3);
1889 /* first check to make sure the request is valid. */
1891 if (!(CC->logged_in)) {
1892 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1895 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1896 cprintf("%d Need to be validated to enter ",
1897 ERROR + HIGHER_ACCESS_REQUIRED);
1898 cprintf("(except in %s> to sysop)\n", MAILROOM);
1901 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1902 cprintf("%d Need net privileges to enter here.\n",
1903 ERROR + HIGHER_ACCESS_REQUIRED);
1906 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1907 cprintf("%d Sorry, this is a read-only room.\n",
1908 ERROR + HIGHER_ACCESS_REQUIRED);
1915 if (CC->usersupp.axlevel < 6) {
1916 cprintf("%d You don't have permission to masquerade.\n",
1917 ERROR + HIGHER_ACCESS_REQUIRED);
1920 extract(newusername, entargs, 4);
1921 memset(CC->fake_postname, 0, 32);
1922 strcpy(CC->fake_postname, newusername);
1923 cprintf("%d Ok\n", OK);
1926 CC->cs_flags |= CS_POSTING;
1929 if (CC->quickroom.QRflags & QR_MAILBOX) {
1930 if (CC->usersupp.axlevel >= 2) {
1931 strcpy(buf, recipient);
1933 strcpy(buf, "sysop");
1934 e = alias(buf); /* alias and mail type */
1935 if ((buf[0] == 0) || (e == MES_ERROR)) {
1936 cprintf("%d Unknown address - cannot send message.\n",
1937 ERROR + NO_SUCH_USER);
1940 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1941 cprintf("%d Net privileges required for network mail.\n",
1942 ERROR + HIGHER_ACCESS_REQUIRED);
1945 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1946 && ((CC->usersupp.flags & US_INTERNET) == 0)
1947 && (!CC->internal_pgm)) {
1948 cprintf("%d You don't have access to Internet mail.\n",
1949 ERROR + HIGHER_ACCESS_REQUIRED);
1952 if (!strcasecmp(buf, "sysop")) {
1957 goto SKFALL; /* don't search local file */
1958 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1959 cprintf("%d Can't send mail to yourself!\n",
1960 ERROR + NO_SUCH_USER);
1963 /* Check to make sure the user exists; also get the correct
1964 * upper/lower casing of the name.
1966 a = getuser(&tempUS, buf);
1968 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1971 strcpy(buf, tempUS.fullname);
1974 SKFALL: b = MES_NORMAL;
1975 if (CC->quickroom.QRflags & QR_ANONONLY)
1977 if (CC->quickroom.QRflags & QR_ANONOPT) {
1981 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1984 /* If we're only checking the validity of the request, return
1985 * success without creating the message.
1988 cprintf("%d %s\n", OK, buf);
1992 cprintf("%d send message\n", SEND_LISTING);
1994 /* Read in the message from the client. */
1995 if (CC->fake_postname[0])
1996 msg = make_message(&CC->usersupp, buf,
1997 CC->quickroom.QRname, b, e, format_type,
1999 else if (CC->fake_username[0])
2000 msg = make_message(&CC->usersupp, buf,
2001 CC->quickroom.QRname, b, e, format_type,
2004 msg = make_message(&CC->usersupp, buf,
2005 CC->quickroom.QRname, b, e, format_type, "");
2008 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2009 CtdlFreeMessage(msg);
2010 CC->fake_postname[0] = '\0';
2017 * message entry - mode 3 (raw)
2019 void cmd_ent3(char *entargs)
2025 unsigned char ch, which_field;
2026 struct usersupp tempUS;
2028 struct CtdlMessage *msg;
2031 if (CC->internal_pgm == 0) {
2032 cprintf("%d This command is for internal programs only.\n",
2037 /* See if there's a recipient, but make sure it's a real one */
2038 extract(recp, entargs, 1);
2039 for (a = 0; a < strlen(recp); ++a)
2040 if (!isprint(recp[a]))
2041 strcpy(&recp[a], &recp[a + 1]);
2042 while (isspace(recp[0]))
2043 strcpy(recp, &recp[1]);
2044 while (isspace(recp[strlen(recp) - 1]))
2045 recp[strlen(recp) - 1] = 0;
2047 /* If we're in Mail, check the recipient */
2048 if (strlen(recp) > 0) {
2049 e = alias(recp); /* alias and mail type */
2050 if ((recp[0] == 0) || (e == MES_ERROR)) {
2051 cprintf("%d Unknown address - cannot send message.\n",
2052 ERROR + NO_SUCH_USER);
2055 if (e == MES_LOCAL) {
2056 a = getuser(&tempUS, recp);
2058 cprintf("%d No such user.\n",
2059 ERROR + NO_SUCH_USER);
2065 /* At this point, message has been approved. */
2066 if (extract_int(entargs, 0) == 0) {
2067 cprintf("%d OK to send\n", OK);
2071 msglen = extract_long(entargs, 2);
2072 msg = mallok(sizeof(struct CtdlMessage));
2074 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2078 memset(msg, 0, sizeof(struct CtdlMessage));
2079 tempbuf = mallok(msglen);
2080 if (tempbuf == NULL) {
2081 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2086 cprintf("%d %ld\n", SEND_BINARY, msglen);
2088 client_read(&ch, 1); /* 0xFF magic number */
2089 msg->cm_magic = CTDLMESSAGE_MAGIC;
2090 client_read(&ch, 1); /* anon type */
2091 msg->cm_anon_type = ch;
2092 client_read(&ch, 1); /* format type */
2093 msg->cm_format_type = ch;
2094 msglen = msglen - 3;
2096 while (msglen > 0) {
2097 client_read(&which_field, 1);
2098 if (!isalpha(which_field)) valid_msg = 0;
2102 client_read(&ch, 1);
2104 a = strlen(tempbuf);
2107 } while ( (ch != 0) && (msglen > 0) );
2109 msg->cm_fields[which_field] = strdoop(tempbuf);
2112 msg->cm_flags = CM_SKIP_HOOKS;
2113 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2114 CtdlFreeMessage(msg);
2120 * API function to delete messages which match a set of criteria
2121 * (returns the actual number of messages deleted)
2123 int CtdlDeleteMessages(char *room_name, /* which room */
2124 long dmsgnum, /* or "0" for any */
2125 char *content_type /* or NULL for any */
2129 struct quickroom qrbuf;
2130 struct cdbdata *cdbfr;
2131 long *msglist = NULL;
2134 int num_deleted = 0;
2136 struct SuppMsgInfo smi;
2138 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2139 room_name, dmsgnum, content_type);
2141 /* get room record, obtaining a lock... */
2142 if (lgetroom(&qrbuf, room_name) != 0) {
2143 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2145 return (0); /* room not found */
2147 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2149 if (cdbfr != NULL) {
2150 msglist = mallok(cdbfr->len);
2151 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2152 num_msgs = cdbfr->len / sizeof(long);
2156 for (i = 0; i < num_msgs; ++i) {
2159 /* Set/clear a bit for each criterion */
2161 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2162 delete_this |= 0x01;
2164 if (content_type == NULL) {
2165 delete_this |= 0x02;
2167 GetSuppMsgInfo(&smi, msglist[i]);
2168 if (!strcasecmp(smi.smi_content_type,
2170 delete_this |= 0x02;
2174 /* Delete message only if all bits are set */
2175 if (delete_this == 0x03) {
2176 AdjRefCount(msglist[i], -1);
2182 num_msgs = sort_msglist(msglist, num_msgs);
2183 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2184 msglist, (num_msgs * sizeof(long)));
2186 qrbuf.QRhighest = msglist[num_msgs - 1];
2190 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2191 return (num_deleted);
2197 * Delete message from current room
2199 void cmd_dele(char *delstr)
2204 getuser(&CC->usersupp, CC->curr_user);
2205 if ((CC->usersupp.axlevel < 6)
2206 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2207 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2208 && (!(CC->internal_pgm))) {
2209 cprintf("%d Higher access required.\n",
2210 ERROR + HIGHER_ACCESS_REQUIRED);
2213 delnum = extract_long(delstr, 0);
2215 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2218 cprintf("%d %d message%s deleted.\n", OK,
2219 num_deleted, ((num_deleted != 1) ? "s" : ""));
2221 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2227 * move or copy a message to another room
2229 void cmd_move(char *args)
2233 struct quickroom qtemp;
2237 num = extract_long(args, 0);
2238 extract(targ, args, 1);
2239 targ[ROOMNAMELEN - 1] = 0;
2240 is_copy = extract_int(args, 2);
2242 getuser(&CC->usersupp, CC->curr_user);
2243 if ((CC->usersupp.axlevel < 6)
2244 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2245 cprintf("%d Higher access required.\n",
2246 ERROR + HIGHER_ACCESS_REQUIRED);
2250 if (getroom(&qtemp, targ) != 0) {
2251 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2255 err = CtdlSaveMsgPointerInRoom(targ, num,
2256 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2258 cprintf("%d Cannot store message in %s: error %d\n",
2263 /* Now delete the message from the source room,
2264 * if this is a 'move' rather than a 'copy' operation.
2266 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2268 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2274 * GetSuppMsgInfo() - Get the supplementary record for a message
2276 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2279 struct cdbdata *cdbsmi;
2282 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2283 smibuf->smi_msgnum = msgnum;
2284 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2286 /* Use the negative of the message number for its supp record index */
2287 TheIndex = (0L - msgnum);
2289 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2290 if (cdbsmi == NULL) {
2291 return; /* record not found; go with defaults */
2293 memcpy(smibuf, cdbsmi->ptr,
2294 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2295 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2302 * PutSuppMsgInfo() - (re)write supplementary record for a message
2304 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2308 /* Use the negative of the message number for its supp record index */
2309 TheIndex = (0L - smibuf->smi_msgnum);
2311 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2312 smibuf->smi_msgnum, smibuf->smi_refcount);
2314 cdb_store(CDB_MSGMAIN,
2315 &TheIndex, sizeof(long),
2316 smibuf, sizeof(struct SuppMsgInfo));
2321 * AdjRefCount - change the reference count for a message;
2322 * delete the message if it reaches zero
2324 void AdjRefCount(long msgnum, int incr)
2327 struct SuppMsgInfo smi;
2330 /* This is a *tight* critical section; please keep it that way, as
2331 * it may get called while nested in other critical sections.
2332 * Complicating this any further will surely cause deadlock!
2334 begin_critical_section(S_SUPPMSGMAIN);
2335 GetSuppMsgInfo(&smi, msgnum);
2336 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2337 msgnum, smi.smi_refcount);
2338 smi.smi_refcount += incr;
2339 PutSuppMsgInfo(&smi);
2340 end_critical_section(S_SUPPMSGMAIN);
2341 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2342 msgnum, smi.smi_refcount);
2344 /* If the reference count is now zero, delete the message
2345 * (and its supplementary record as well).
2347 if (smi.smi_refcount == 0) {
2348 lprintf(9, "Deleting message <%ld>\n", msgnum);
2350 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2351 delnum = (0L - msgnum);
2352 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2357 * Write a generic object to this room
2359 * Note: this could be much more efficient. Right now we use two temporary
2360 * files, and still pull the message into memory as with all others.
2362 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2363 char *content_type, /* MIME type of this object */
2364 char *tempfilename, /* Where to fetch it from */
2365 struct usersupp *is_mailbox, /* Mailbox room? */
2366 int is_binary, /* Is encoding necessary? */
2367 int is_unique, /* Del others of this type? */
2368 unsigned int flags /* Internal save flags */
2373 char filename[PATH_MAX];
2376 struct quickroom qrbuf;
2377 char roomname[ROOMNAMELEN];
2378 struct CtdlMessage *msg;
2381 if (is_mailbox != NULL)
2382 MailboxName(roomname, is_mailbox, req_room);
2384 safestrncpy(roomname, req_room, sizeof(roomname));
2385 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2387 strcpy(filename, tmpnam(NULL));
2388 fp = fopen(filename, "w");
2392 tempfp = fopen(tempfilename, "r");
2393 if (tempfp == NULL) {
2399 fprintf(fp, "Content-type: %s\n", content_type);
2400 lprintf(9, "Content-type: %s\n", content_type);
2402 if (is_binary == 0) {
2403 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2404 while (ch = getc(tempfp), ch > 0)
2410 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2413 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2414 tempfilename, filename);
2418 lprintf(9, "Allocating\n");
2419 msg = mallok(sizeof(struct CtdlMessage));
2420 memset(msg, 0, sizeof(struct CtdlMessage));
2421 msg->cm_magic = CTDLMESSAGE_MAGIC;
2422 msg->cm_anon_type = MES_NORMAL;
2423 msg->cm_format_type = 4;
2424 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2425 msg->cm_fields['O'] = strdoop(req_room);
2426 msg->cm_fields['N'] = strdoop(config.c_nodename);
2427 msg->cm_fields['H'] = strdoop(config.c_humannode);
2428 msg->cm_flags = flags;
2430 lprintf(9, "Loading\n");
2431 fp = fopen(filename, "rb");
2432 fseek(fp, 0L, SEEK_END);
2435 msg->cm_fields['M'] = mallok(len);
2436 fread(msg->cm_fields['M'], len, 1, fp);
2440 /* Create the requested room if we have to. */
2441 if (getroom(&qrbuf, roomname) != 0) {
2442 create_room(roomname,
2443 ( (is_mailbox != NULL) ? 4 : 3 ),
2446 /* If the caller specified this object as unique, delete all
2447 * other objects of this type that are currently in the room.
2450 lprintf(9, "Deleted %d other msgs of this type\n",
2451 CtdlDeleteMessages(roomname, 0L, content_type));
2453 /* Now write the data */
2454 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2455 CtdlFreeMessage(msg);
2463 void CtdlGetSysConfigBackend(long msgnum) {
2464 config_msgnum = msgnum;
2468 char *CtdlGetSysConfig(char *sysconfname) {
2469 char hold_rm[ROOMNAMELEN];
2472 struct CtdlMessage *msg;
2475 strcpy(hold_rm, CC->quickroom.QRname);
2476 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2477 getroom(&CC->quickroom, hold_rm);
2482 /* We want the last (and probably only) config in this room */
2483 begin_critical_section(S_CONFIG);
2484 config_msgnum = (-1L);
2485 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2486 CtdlGetSysConfigBackend);
2487 msgnum = config_msgnum;
2488 end_critical_section(S_CONFIG);
2494 msg = CtdlFetchMessage(msgnum);
2496 conf = strdoop(msg->cm_fields['M']);
2497 CtdlFreeMessage(msg);
2504 getroom(&CC->quickroom, hold_rm);
2506 lprintf(9, "eggstracting...\n");
2507 if (conf != NULL) do {
2508 extract_token(buf, conf, 0, '\n');
2509 lprintf(9, "eggstracted <%s>\n", buf);
2510 strcpy(conf, &conf[strlen(buf)+1]);
2511 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2516 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2517 char temp[PATH_MAX];
2520 strcpy(temp, tmpnam(NULL));
2522 fp = fopen(temp, "w");
2523 if (fp == NULL) return;
2524 fprintf(fp, "%s", sysconfdata);
2527 /* this handy API function does all the work for us */
2528 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);