20 #include "sysdep_decls.h"
21 #include "citserver.h"
26 #include "dynloader.h"
28 #include "mime_parser.h"
31 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
32 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
33 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
35 extern struct config config;
39 "", "", "", "", "", "", "", "",
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
72 * This function is self explanatory.
73 * (What can I say, I'm in a weird mood today...)
75 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
79 for (i = 0; i < strlen(name); ++i)
82 if (isspace(name[i - 1])) {
83 strcpy(&name[i - 1], &name[i]);
86 while (isspace(name[i + 1])) {
87 strcpy(&name[i + 1], &name[i + 2]);
94 * Aliasing for network mail.
95 * (Error messages have been commented out, because this is a server.)
98 { /* process alias and routing info for mail */
101 char aaa[300], bbb[300];
103 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
105 fp = fopen("network/mail.aliases", "r");
107 fp = fopen("/dev/null", "r");
112 while (fgets(aaa, sizeof aaa, fp) != NULL) {
113 while (isspace(name[0]))
114 strcpy(name, &name[1]);
115 aaa[strlen(aaa) - 1] = 0;
117 for (a = 0; a < strlen(aaa); ++a) {
119 strcpy(bbb, &aaa[a + 1]);
123 if (!strcasecmp(name, aaa))
127 lprintf(7, "Mail is being forwarded to %s\n", name);
129 /* determine local or remote type, see citadel.h */
130 for (a = 0; a < strlen(name); ++a)
132 return (MES_INTERNET);
133 for (a = 0; a < strlen(name); ++a)
135 for (b = a; b < strlen(name); ++b)
137 return (MES_INTERNET);
139 for (a = 0; a < strlen(name); ++a)
143 lprintf(7, "Too many @'s in address\n");
147 for (a = 0; a < strlen(name); ++a)
149 strcpy(bbb, &name[a + 1]);
151 strcpy(bbb, &bbb[1]);
152 fp = fopen("network/mail.sysinfo", "r");
156 a = getstring(fp, aaa);
157 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
158 a = getstring(fp, aaa);
159 if (!strncmp(aaa, "use ", 4)) {
160 strcpy(bbb, &aaa[4]);
165 if (!strncmp(aaa, "uum", 3)) {
167 for (a = 0; a < strlen(bbb); ++a) {
173 while (bbb[strlen(bbb) - 1] == '_')
174 bbb[strlen(bbb) - 1] = 0;
175 sprintf(name, &aaa[4], bbb);
176 return (MES_INTERNET);
178 if (!strncmp(aaa, "bin", 3)) {
181 while (aaa[strlen(aaa) - 1] != '@')
182 aaa[strlen(aaa) - 1] = 0;
183 aaa[strlen(aaa) - 1] = 0;
184 while (aaa[strlen(aaa) - 1] == ' ')
185 aaa[strlen(aaa) - 1] = 0;
186 while (bbb[0] != '@')
187 strcpy(bbb, &bbb[1]);
188 strcpy(bbb, &bbb[1]);
189 while (bbb[0] == ' ')
190 strcpy(bbb, &bbb[1]);
191 sprintf(name, "%s @%s", aaa, bbb);
204 fp = fopen("citadel.control", "r");
205 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
211 void simple_listing(long msgnum)
213 cprintf("%ld\n", msgnum);
218 /* Determine if a given message matches the fields in a message template.
219 * Return 0 for a successful match.
221 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
224 /* If there aren't any fields in the template, all messages will
227 if (template == NULL) return(0);
229 /* Null messages are bogus. */
230 if (msg == NULL) return(1);
232 for (i='A'; i<='Z'; ++i) {
233 if (template->cm_fields[i] != NULL) {
234 if (msg->cm_fields[i] == NULL) {
237 if (strcasecmp(msg->cm_fields[i],
238 template->cm_fields[i])) return 1;
242 /* All compares succeeded: we have a match! */
250 * API function to perform an operation for each qualifying message in the
253 void CtdlForEachMessage(int mode, long ref,
255 struct CtdlMessage *compare,
256 void (*CallBack) (long msgnum))
261 struct cdbdata *cdbfr;
262 long *msglist = NULL;
265 struct SuppMsgInfo smi;
266 struct CtdlMessage *msg;
268 /* Learn about the user and room in question */
270 getuser(&CC->usersupp, CC->curr_user);
271 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
273 /* Load the message list */
274 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
276 msglist = mallok(cdbfr->len);
277 memcpy(msglist, cdbfr->ptr, cdbfr->len);
278 num_msgs = cdbfr->len / sizeof(long);
281 return; /* No messages at all? No further action. */
285 /* If the caller is looking for a specific MIME type, then filter
286 * out all messages which are not of the type requested.
289 if (content_type != NULL)
290 if (strlen(content_type) > 0)
291 for (a = 0; a < num_msgs; ++a) {
292 GetSuppMsgInfo(&smi, msglist[a]);
293 if (strcasecmp(smi.smi_content_type, content_type)) {
298 num_msgs = sort_msglist(msglist, num_msgs);
300 /* If a template was supplied, filter out the messages which
301 * don't match. (This could induce some delays!)
304 if (compare != NULL) {
305 for (a = 0; a < num_msgs; ++a) {
306 msg = CtdlFetchMessage(msglist[a]);
308 if (CtdlMsgCmp(msg, compare)) {
311 CtdlFreeMessage(msg);
319 * Now iterate through the message list, according to the
320 * criteria supplied by the caller.
323 for (a = 0; a < num_msgs; ++a) {
324 thismsg = msglist[a];
329 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
330 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
331 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
332 && (CC->usersupp.flags & US_LASTOLD))
333 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
334 || ((mode == MSGS_FIRST) && (a < ref))
335 || ((mode == MSGS_GT) && (thismsg > ref))
341 phree(msglist); /* Clean up */
347 * cmd_msgs() - get list of message #'s in this room
348 * implements the MSGS server command using CtdlForEachMessage()
350 void cmd_msgs(char *cmdbuf)
359 int with_template = 0;
360 struct CtdlMessage *template = NULL;
362 extract(which, cmdbuf, 0);
363 cm_ref = extract_int(cmdbuf, 1);
364 with_template = extract_int(cmdbuf, 2);
368 if (!strncasecmp(which, "OLD", 3))
370 else if (!strncasecmp(which, "NEW", 3))
372 else if (!strncasecmp(which, "FIRST", 5))
374 else if (!strncasecmp(which, "LAST", 4))
376 else if (!strncasecmp(which, "GT", 2))
379 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
380 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
385 cprintf("%d Send template then receive message list\n",
387 template = (struct CtdlMessage *)
388 mallok(sizeof(struct CtdlMessage));
389 memset(template, 0, sizeof(struct CtdlMessage));
390 while(client_gets(buf), strcmp(buf,"000")) {
391 extract(tfield, buf, 0);
392 extract(tvalue, buf, 1);
393 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
394 if (!strcasecmp(tfield, msgkeys[i])) {
395 template->cm_fields[i] =
402 cprintf("%d Message list...\n", LISTING_FOLLOWS);
405 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
406 if (template != NULL) CtdlFreeMessage(template);
414 * help_subst() - support routine for help file viewer
416 void help_subst(char *strbuf, char *source, char *dest)
421 while (p = pattern2(strbuf, source), (p >= 0)) {
422 strcpy(workbuf, &strbuf[p + strlen(source)]);
423 strcpy(&strbuf[p], dest);
424 strcat(strbuf, workbuf);
429 void do_help_subst(char *buffer)
433 help_subst(buffer, "^nodename", config.c_nodename);
434 help_subst(buffer, "^humannode", config.c_humannode);
435 help_subst(buffer, "^fqdn", config.c_fqdn);
436 help_subst(buffer, "^username", CC->usersupp.fullname);
437 sprintf(buf2, "%ld", CC->usersupp.usernum);
438 help_subst(buffer, "^usernum", buf2);
439 help_subst(buffer, "^sysadm", config.c_sysadm);
440 help_subst(buffer, "^variantname", CITADEL);
441 sprintf(buf2, "%d", config.c_maxsessions);
442 help_subst(buffer, "^maxsessions", buf2);
448 * memfmout() - Citadel text formatter and paginator.
449 * Although the original purpose of this routine was to format
450 * text to the reader's screen width, all we're really using it
451 * for here is to format text out to 80 columns before sending it
452 * to the client. The client software may reformat it again.
455 int width, /* screen width to use */
456 char *mptr, /* where are we going to get our text from? */
457 char subst) /* nonzero if we should do substitutions */
469 c = 1; /* c is the current pos */
472 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
474 buffer[strlen(buffer) + 1] = 0;
475 buffer[strlen(buffer)] = ch;
478 if (buffer[0] == '^')
479 do_help_subst(buffer);
481 buffer[strlen(buffer) + 1] = 0;
483 strcpy(buffer, &buffer[1]);
493 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
495 if (((old == 13) || (old == 10)) && (isspace(real))) {
503 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
504 cprintf("\n%s", aaa);
513 if ((strlen(aaa) + c) > (width - 5)) {
523 if ((ch == 13) || (ch == 10)) {
524 cprintf("%s\n", aaa);
531 FMTEND: cprintf("%s\n", aaa);
537 * Callback function for mime parser that simply lists the part
539 void list_this_part(char *name, char *filename, char *partnum, char *disp,
540 void *content, char *cbtype, size_t length)
543 cprintf("part=%s|%s|%s|%s|%s|%d\n",
544 name, filename, partnum, disp, cbtype, length);
549 * Callback function for mime parser that wants to display text
551 void fixed_output(char *name, char *filename, char *partnum, char *disp,
552 void *content, char *cbtype, size_t length)
556 if (!strcasecmp(cbtype, "multipart/alternative")) {
557 strcpy(ma->prefix, partnum);
558 strcat(ma->prefix, ".");
564 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
566 && (ma->did_print == 1) ) {
567 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
573 if (!strcasecmp(cbtype, "text/plain")) {
574 client_write(content, length);
576 else if (!strcasecmp(cbtype, "text/html")) {
577 ptr = html_to_ascii(content, 80, 0);
578 client_write(ptr, strlen(ptr));
581 else if (strncasecmp(cbtype, "multipart/", 10)) {
582 cprintf("Part %s: %s (%s) (%d bytes)\n",
583 partnum, filename, cbtype, length);
589 * Callback function for mime parser that opens a section for downloading
591 void mime_download(char *name, char *filename, char *partnum, char *disp,
592 void *content, char *cbtype, size_t length)
595 /* Silently go away if there's already a download open... */
596 if (CC->download_fp != NULL)
599 /* ...or if this is not the desired section */
600 if (strcasecmp(desired_section, partnum))
603 CC->download_fp = tmpfile();
604 if (CC->download_fp == NULL)
607 fwrite(content, length, 1, CC->download_fp);
608 fflush(CC->download_fp);
609 rewind(CC->download_fp);
611 OpenCmdResult(filename, cbtype);
617 * Load a message from disk into memory.
618 * This is used by CtdlOutputMsg() and other fetch functions.
620 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
621 * using the CtdlMessageFree() function.
623 struct CtdlMessage *CtdlFetchMessage(long msgnum)
625 struct cdbdata *dmsgtext;
626 struct CtdlMessage *ret = NULL;
629 CIT_UBYTE field_header;
632 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
633 if (dmsgtext == NULL) {
636 mptr = dmsgtext->ptr;
638 /* Parse the three bytes that begin EVERY message on disk.
639 * The first is always 0xFF, the on-disk magic number.
640 * The second is the anonymous/public type byte.
641 * The third is the format type byte (vari, fixed, or MIME).
645 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
649 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
650 memset(ret, 0, sizeof(struct CtdlMessage));
652 ret->cm_magic = CTDLMESSAGE_MAGIC;
653 ret->cm_anon_type = *mptr++; /* Anon type byte */
654 ret->cm_format_type = *mptr++; /* Format type byte */
657 * The rest is zero or more arbitrary fields. Load them in.
658 * We're done when we encounter either a zero-length field or
659 * have just processed the 'M' (message text) field.
662 field_length = strlen(mptr);
663 if (field_length == 0)
665 field_header = *mptr++;
666 ret->cm_fields[field_header] = mallok(field_length);
667 strcpy(ret->cm_fields[field_header], mptr);
669 while (*mptr++ != 0); /* advance to next field */
671 } while ((field_length > 0) && (field_header != 'M'));
675 /* Always make sure there's something in the msg text field */
676 if (ret->cm_fields['M'] == NULL)
677 ret->cm_fields['M'] = strdoop("<no text>\n");
679 /* Perform "before read" hooks (aborting if any return nonzero) */
680 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
681 CtdlFreeMessage(ret);
690 * Returns 1 if the supplied pointer points to a valid Citadel message.
691 * If the pointer is NULL or the magic number check fails, returns 0.
693 int is_valid_message(struct CtdlMessage *msg) {
696 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
697 lprintf(3, "is_valid_message() -- self-check failed\n");
705 * 'Destructor' for struct CtdlMessage
707 void CtdlFreeMessage(struct CtdlMessage *msg)
711 if (is_valid_message(msg) == 0) return;
713 for (i = 0; i < 256; ++i)
714 if (msg->cm_fields[i] != NULL)
715 phree(msg->cm_fields[i]);
717 msg->cm_magic = 0; /* just in case */
723 * Get a message off disk. (return value is the message's timestamp)
726 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
727 int mode, /* how would you like that message? */
728 int headers_only, /* eschew the message body? */
729 int do_proto, /* do Citadel protocol responses? */
738 char display_name[256];
739 struct CtdlMessage *TheMessage;
742 /* buffers needed for RFC822 translation */
751 /* BEGIN NESTED FUNCTION omprintf() */
752 void omprintf(const char *format, ...) {
756 va_start(arg_ptr, format);
757 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
758 buf[sizeof buf - 2] = '\n';
760 fwrite(buf, strlen(buf), 1, outfp);
762 else if (outsock >= 0) {
763 write(outsock, buf, strlen(buf));
766 client_write(buf, strlen(buf));
770 /* END NESTED FUNCTION omprintf() */
772 /* BEGIN NESTED FUNCTION omfmout() */
773 void omfmout(char *mptr) {
784 c = 1; /* c is the current pos */
792 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
794 if (((old == 13) || (old == 10)) && (isspace(real))) {
802 if (((strlen(aaa) + c) > (75)) && (strlen(aaa) > (75))) {
803 omprintf("\n%s", aaa);
812 if ((strlen(aaa) + c) > (75)) {
816 omprintf("%s ", aaa);
822 if ((ch == 13) || (ch == 10)) {
823 omprintf("%s\n", aaa);
830 FMTEND: omprintf("%s\n", aaa);
832 /* END NESTED FUNCTION omfmout() */
835 sprintf(mid, "%ld", msg_num);
837 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
838 if (do_proto) cprintf("%d Not logged in.\n",
839 ERROR + NOT_LOGGED_IN);
840 return(om_not_logged_in);
843 /* FIX ... small security issue
844 * We need to check to make sure the requested message is actually
845 * in the current room, and set msg_ok to 1 only if it is. This
846 * functionality is currently missing because I'm in a hurry to replace
847 * broken production code with nonbroken pre-beta code. :( -- ajc
850 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
852 return(om_no_such_msg);
857 * Fetch the message from disk
859 TheMessage = CtdlFetchMessage(msg_num);
860 if (TheMessage == NULL) {
861 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
863 return(om_no_such_msg);
866 /* Are we downloading a MIME component? */
867 if (mode == MT_DOWNLOAD) {
868 if (TheMessage->cm_format_type != FMT_RFC822) {
870 cprintf("%d This is not a MIME message.\n",
872 } else if (CC->download_fp != NULL) {
873 if (do_proto) cprintf(
874 "%d You already have a download open.\n",
877 /* Parse the message text component */
878 mptr = TheMessage->cm_fields['M'];
879 mime_parser(mptr, NULL, *mime_download);
880 /* If there's no file open by this time, the requested
881 * section wasn't found, so print an error
883 if (CC->download_fp == NULL) {
884 if (do_proto) cprintf(
885 "%d Section %s not found.\n",
886 ERROR + FILE_NOT_FOUND,
890 CtdlFreeMessage(TheMessage);
891 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
894 /* now for the user-mode message reading loops */
895 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
897 /* Tell the client which format type we're using. If this is a
898 * MIME message, *lie* about it and tell the user it's fixed-format.
900 if (mode == MT_CITADEL) {
901 if (TheMessage->cm_format_type == FMT_RFC822) {
902 if (do_proto) cprintf("type=1\n");
905 if (do_proto) cprintf("type=%d\n",
906 TheMessage->cm_format_type);
910 /* nhdr=yes means that we're only displaying headers, no body */
911 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
912 if (do_proto) cprintf("nhdr=yes\n");
915 /* begin header processing loop for Citadel message format */
917 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
919 strcpy(display_name, "<unknown>");
920 if (TheMessage->cm_fields['A']) {
921 strcpy(buf, TheMessage->cm_fields['A']);
922 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
923 if (TheMessage->cm_anon_type == MES_ANON)
924 strcpy(display_name, "****");
925 else if (TheMessage->cm_anon_type == MES_AN2)
926 strcpy(display_name, "anonymous");
928 strcpy(display_name, buf);
930 && ((TheMessage->cm_anon_type == MES_ANON)
931 || (TheMessage->cm_anon_type == MES_AN2))) {
932 sprintf(&display_name[strlen(display_name)],
937 strcpy(allkeys, FORDER);
938 for (i=0; i<strlen(allkeys); ++i) {
939 k = (int) allkeys[i];
941 if (TheMessage->cm_fields[k] != NULL) {
943 if (do_proto) cprintf("%s=%s\n",
948 if (do_proto) cprintf("%s=%s\n",
950 TheMessage->cm_fields[k]
959 /* begin header processing loop for RFC822 transfer format */
963 strcpy(snode, NODENAME);
964 strcpy(lnode, HUMANNODE);
965 if (mode == MT_RFC822) {
966 for (i = 0; i < 256; ++i) {
967 if (TheMessage->cm_fields[i]) {
968 mptr = TheMessage->cm_fields[i];
972 } else if (i == 'P') {
973 omprintf("Path: %s\n", mptr);
974 for (a = 0; a < strlen(mptr); ++a) {
975 if (mptr[a] == '!') {
976 strcpy(mptr, &mptr[a + 1]);
982 omprintf("Subject: %s\n", mptr);
988 omprintf("X-Citadel-Room: %s\n", mptr);
992 omprintf("To: %s\n", mptr);
995 omprintf("Date: %s", asctime(localtime(&xtime)));
1001 if (mode == MT_RFC822) {
1002 if (!strcasecmp(snode, NODENAME)) {
1003 strcpy(snode, FQDN);
1005 omprintf("Message-ID: <%s@%s>\n", mid, snode);
1006 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1007 omprintf("From: %s@%s (%s)\n", suser, snode, luser);
1008 omprintf("Organization: %s\n", lnode);
1011 /* end header processing loop ... at this point, we're in the text */
1013 mptr = TheMessage->cm_fields['M'];
1015 /* Tell the client about the MIME parts in this message */
1016 if (TheMessage->cm_format_type == FMT_RFC822) { /* legacy text dump */
1017 if (mode == MT_CITADEL) {
1018 mime_parser(mptr, NULL, *list_this_part);
1020 else if (mode == MT_MIME) { /* list parts only */
1021 mime_parser(mptr, NULL, *list_this_part);
1022 if (do_proto) cprintf("000\n");
1023 CtdlFreeMessage(TheMessage);
1029 if (do_proto) cprintf("000\n");
1030 CtdlFreeMessage(TheMessage);
1034 /* signify start of msg text */
1035 if (mode == MT_CITADEL)
1036 if (do_proto) cprintf("text\n");
1037 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != FMT_RFC822))
1040 /* If the format type on disk is 1 (fixed-format), then we want
1041 * everything to be output completely literally ... regardless of
1042 * what message transfer format is in use.
1044 if (TheMessage->cm_format_type == FMT_FIXED) {
1046 while (ch = *mptr++, ch > 0) {
1049 if ((ch == 10) || (strlen(buf) > 250)) {
1050 omprintf("%s\n", buf);
1053 buf[strlen(buf) + 1] = 0;
1054 buf[strlen(buf)] = ch;
1057 if (strlen(buf) > 0)
1058 omprintf("%s\n", buf);
1061 /* If the message on disk is format 0 (Citadel vari-format), we
1062 * output using the formatter at 80 columns. This is the final output
1063 * form if the transfer format is RFC822, but if the transfer format
1064 * is Citadel proprietary, it'll still work, because the indentation
1065 * for new paragraphs is correct and the client will reformat the
1066 * message to the reader's screen width.
1068 if (TheMessage->cm_format_type == FMT_CITADEL) {
1072 /* If the message on disk is format 4 (MIME), we've gotta hand it
1073 * off to the MIME parser. The client has already been told that
1074 * this message is format 1 (fixed format), so the callback function
1075 * we use will display those parts as-is.
1077 if (TheMessage->cm_format_type == FMT_RFC822) {
1078 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1079 memset(ma, 0, sizeof(struct ma_info));
1080 mime_parser(mptr, NULL, *fixed_output);
1083 /* now we're done */
1084 if (do_proto) cprintf("000\n");
1085 CtdlFreeMessage(TheMessage);
1092 * display a message (mode 0 - Citadel proprietary)
1094 void cmd_msg0(char *cmdbuf)
1097 int headers_only = 0;
1099 msgid = extract_long(cmdbuf, 0);
1100 headers_only = extract_int(cmdbuf, 1);
1102 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1);
1108 * display a message (mode 2 - RFC822)
1110 void cmd_msg2(char *cmdbuf)
1113 int headers_only = 0;
1115 msgid = extract_long(cmdbuf, 0);
1116 headers_only = extract_int(cmdbuf, 1);
1118 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1);
1124 * display a message (mode 3 - IGnet raw format - internal programs only)
1126 void cmd_msg3(char *cmdbuf)
1129 struct CtdlMessage *msg;
1132 if (CC->internal_pgm == 0) {
1133 cprintf("%d This command is for internal programs only.\n",
1138 msgnum = extract_long(cmdbuf, 0);
1139 msg = CtdlFetchMessage(msgnum);
1141 cprintf("%d Message %ld not found.\n",
1146 serialize_message(&smr, msg);
1147 CtdlFreeMessage(msg);
1150 cprintf("%d Unable to serialize message\n",
1151 ERROR+INTERNAL_ERROR);
1155 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1156 client_write(smr.ser, smr.len);
1163 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1165 void cmd_msg4(char *cmdbuf)
1169 msgid = extract_long(cmdbuf, 0);
1170 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1);
1174 * Open a component of a MIME message as a download file
1176 void cmd_opna(char *cmdbuf)
1180 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1182 msgid = extract_long(cmdbuf, 0);
1183 extract(desired_section, cmdbuf, 1);
1185 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1);
1190 * Save a message pointer into a specified room
1191 * (Returns 0 for success, nonzero for failure)
1192 * roomname may be NULL to use the current room
1194 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1196 char hold_rm[ROOMNAMELEN];
1197 struct cdbdata *cdbfr;
1200 long highest_msg = 0L;
1201 struct CtdlMessage *msg = NULL;
1203 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1204 roomname, msgid, flags);
1206 strcpy(hold_rm, CC->quickroom.QRname);
1208 /* We may need to check to see if this message is real */
1209 if ( (flags & SM_VERIFY_GOODNESS)
1210 || (flags & SM_DO_REPL_CHECK)
1212 msg = CtdlFetchMessage(msgid);
1213 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1216 /* Perform replication checks if necessary */
1217 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1219 if (getroom(&CC->quickroom,
1220 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1222 lprintf(9, "No such room <%s>\n", roomname);
1223 if (msg != NULL) CtdlFreeMessage(msg);
1224 return(ERROR + ROOM_NOT_FOUND);
1227 if (ReplicationChecks(msg) != 0) {
1228 getroom(&CC->quickroom, hold_rm);
1229 if (msg != NULL) CtdlFreeMessage(msg);
1230 lprintf(9, "Did replication, and newer exists\n");
1235 /* Now the regular stuff */
1236 if (lgetroom(&CC->quickroom,
1237 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1239 lprintf(9, "No such room <%s>\n", roomname);
1240 if (msg != NULL) CtdlFreeMessage(msg);
1241 return(ERROR + ROOM_NOT_FOUND);
1244 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1245 if (cdbfr == NULL) {
1249 msglist = mallok(cdbfr->len);
1250 if (msglist == NULL)
1251 lprintf(3, "ERROR malloc msglist!\n");
1252 num_msgs = cdbfr->len / sizeof(long);
1253 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1258 /* Make sure the message doesn't already exist in this room. It
1259 * is absolutely taboo to have more than one reference to the same
1260 * message in a room.
1262 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1263 if (msglist[i] == msgid) {
1264 lputroom(&CC->quickroom); /* unlock the room */
1265 getroom(&CC->quickroom, hold_rm);
1266 if (msg != NULL) CtdlFreeMessage(msg);
1267 return(ERROR + ALREADY_EXISTS);
1271 /* Now add the new message */
1273 msglist = reallok(msglist,
1274 (num_msgs * sizeof(long)));
1276 if (msglist == NULL) {
1277 lprintf(3, "ERROR: can't realloc message list!\n");
1279 msglist[num_msgs - 1] = msgid;
1281 /* Sort the message list, so all the msgid's are in order */
1282 num_msgs = sort_msglist(msglist, num_msgs);
1284 /* Determine the highest message number */
1285 highest_msg = msglist[num_msgs - 1];
1287 /* Write it back to disk. */
1288 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1289 msglist, num_msgs * sizeof(long));
1291 /* Free up the memory we used. */
1294 /* Update the highest-message pointer and unlock the room. */
1295 CC->quickroom.QRhighest = highest_msg;
1296 lputroom(&CC->quickroom);
1297 getroom(&CC->quickroom, hold_rm);
1299 /* Bump the reference count for this message. */
1300 AdjRefCount(msgid, +1);
1302 /* Return success. */
1303 if (msg != NULL) CtdlFreeMessage(msg);
1310 * Message base operation to send a message to the master file
1311 * (returns new message number)
1313 * This is the back end for CtdlSaveMsg() and should not be directly
1314 * called by server-side modules.
1317 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1318 int generate_id, /* generate 'I' field? */
1319 FILE *save_a_copy) /* save a copy to disk? */
1326 /* Get a new message number */
1327 newmsgid = get_new_message_number();
1328 sprintf(msgidbuf, "%ld", newmsgid);
1331 msg->cm_fields['I'] = strdoop(msgidbuf);
1334 serialize_message(&smr, msg);
1337 cprintf("%d Unable to serialize message\n",
1338 ERROR+INTERNAL_ERROR);
1342 /* Write our little bundle of joy into the message base */
1343 begin_critical_section(S_MSGMAIN);
1344 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1345 smr.ser, smr.len) < 0) {
1346 lprintf(2, "Can't store message\n");
1351 end_critical_section(S_MSGMAIN);
1353 /* If the caller specified that a copy should be saved to a particular
1354 * file handle, do that now too.
1356 if (save_a_copy != NULL) {
1357 fwrite(smr.ser, smr.len, 1, save_a_copy);
1360 /* Free the memory we used for the serialized message */
1363 /* Return the *local* message ID to the caller
1364 * (even if we're storing an incoming network message)
1372 * Serialize a struct CtdlMessage into the format used on disk and network.
1374 * This function loads up a "struct ser_ret" (defined in server.h) which
1375 * contains the length of the serialized message and a pointer to the
1376 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1378 void serialize_message(struct ser_ret *ret, /* return values */
1379 struct CtdlMessage *msg) /* unserialized msg */
1383 static char *forder = FORDER;
1385 if (is_valid_message(msg) == 0) return; /* self check */
1388 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1389 ret->len = ret->len +
1390 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1392 lprintf(9, "calling malloc\n");
1393 ret->ser = mallok(ret->len);
1394 if (ret->ser == NULL) {
1400 ret->ser[1] = msg->cm_anon_type;
1401 ret->ser[2] = msg->cm_format_type;
1404 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1405 ret->ser[wlen++] = (char)forder[i];
1406 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1407 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1409 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1418 * Back end for the ReplicationChecks() function
1420 void check_repl(long msgnum) {
1421 struct CtdlMessage *msg;
1422 time_t timestamp = (-1L);
1424 lprintf(9, "check_repl() found message %ld\n", msgnum);
1425 msg = CtdlFetchMessage(msgnum);
1426 if (msg == NULL) return;
1427 if (msg->cm_fields['T'] != NULL) {
1428 timestamp = atol(msg->cm_fields['T']);
1430 CtdlFreeMessage(msg);
1432 if (timestamp > msg_repl->highest) {
1433 msg_repl->highest = timestamp; /* newer! */
1434 lprintf(9, "newer!\n");
1437 lprintf(9, "older!\n");
1439 /* Existing isn't newer? Then delete the old one(s). */
1440 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1445 * Check to see if any messages already exist which carry the same Extended ID
1449 * -> With older timestamps: delete them and return 0. Message will be saved.
1450 * -> With newer timestamps: return 1. Message save will be aborted.
1452 int ReplicationChecks(struct CtdlMessage *msg) {
1453 struct CtdlMessage *template;
1456 lprintf(9, "ReplicationChecks() started\n");
1457 /* No extended id? Don't do anything. */
1458 if (msg->cm_fields['E'] == NULL) return 0;
1459 if (strlen(msg->cm_fields['E']) == 0) return 0;
1460 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1462 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1463 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1464 msg_repl->highest = atol(msg->cm_fields['T']);
1466 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1467 memset(template, 0, sizeof(struct CtdlMessage));
1468 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1470 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1472 /* If a newer message exists with the same Extended ID, abort
1475 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1479 CtdlFreeMessage(template);
1480 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1488 * Save a message to disk
1490 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1491 char *rec, /* Recipient (mail) */
1492 char *force, /* force a particular room? */
1493 int mailtype, /* local or remote type */
1494 int generate_id) /* 1 = generate 'I' field */
1497 char hold_rm[ROOMNAMELEN];
1498 char actual_rm[ROOMNAMELEN];
1499 char force_room[ROOMNAMELEN];
1500 char content_type[256]; /* We have to learn this */
1501 char recipient[256];
1504 struct usersupp userbuf;
1506 struct SuppMsgInfo smi;
1507 FILE *network_fp = NULL;
1508 static int seqnum = 1;
1510 lprintf(9, "CtdlSaveMsg() called\n");
1511 if (is_valid_message(msg) == 0) return(-1); /* self check */
1513 /* If this message has no timestamp, we take the liberty of
1514 * giving it one, right now.
1516 if (msg->cm_fields['T'] == NULL) {
1517 lprintf(9, "Generating timestamp\n");
1518 sprintf(aaa, "%ld", time(NULL));
1519 msg->cm_fields['T'] = strdoop(aaa);
1522 /* If this message has no path, we generate one.
1524 if (msg->cm_fields['P'] == NULL) {
1525 lprintf(9, "Generating path\n");
1526 if (msg->cm_fields['A'] != NULL) {
1527 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1528 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1529 if (isspace(msg->cm_fields['P'][a])) {
1530 msg->cm_fields['P'][a] = ' ';
1535 msg->cm_fields['P'] = strdoop("unknown");
1539 strcpy(force_room, force);
1541 /* Strip non-printable characters out of the recipient name */
1542 strcpy(recipient, rec);
1543 for (a = 0; a < strlen(recipient); ++a)
1544 if (!isprint(recipient[a]))
1545 strcpy(&recipient[a], &recipient[a + 1]);
1547 /* Learn about what's inside, because it's what's inside that counts */
1548 lprintf(9, "Learning what's inside\n");
1549 if (msg->cm_fields['M'] == NULL) {
1550 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1553 switch (msg->cm_format_type) {
1555 strcpy(content_type, "text/x-citadel-variformat");
1558 strcpy(content_type, "text/plain");
1561 strcpy(content_type, "text/plain");
1562 /* advance past header fields */
1563 mptr = msg->cm_fields['M'];
1566 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1567 safestrncpy(content_type, mptr,
1568 sizeof(content_type));
1569 strcpy(content_type, &content_type[14]);
1570 for (a = 0; a < strlen(content_type); ++a)
1571 if ((content_type[a] == ';')
1572 || (content_type[a] == ' ')
1573 || (content_type[a] == 13)
1574 || (content_type[a] == 10))
1575 content_type[a] = 0;
1582 /* Goto the correct room */
1583 lprintf(9, "Switching rooms\n");
1584 strcpy(hold_rm, CC->quickroom.QRname);
1585 strcpy(actual_rm, CC->quickroom.QRname);
1587 /* If the user is a twit, move to the twit room for posting */
1588 lprintf(9, "Handling twit stuff\n");
1590 if (CC->usersupp.axlevel == 2) {
1591 strcpy(hold_rm, actual_rm);
1592 strcpy(actual_rm, config.c_twitroom);
1596 /* ...or if this message is destined for Aide> then go there. */
1597 if (strlen(force_room) > 0) {
1598 strcpy(actual_rm, force_room);
1601 lprintf(9, "Possibly relocating\n");
1602 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1603 getroom(&CC->quickroom, actual_rm);
1605 /* Perform "before save" hooks (aborting if any return nonzero) */
1606 lprintf(9, "Performing before-save hooks\n");
1607 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1609 /* If this message has an Extended ID, perform replication checks */
1610 lprintf(9, "Performing replication checks\n");
1611 if (ReplicationChecks(msg) > 0) return(-1);
1613 /* Network mail - send a copy to the network program. */
1614 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1615 lprintf(9, "Sending network spool\n");
1616 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1617 (long) getpid(), CC->cs_pid, ++seqnum);
1618 lprintf(9, "Saving a copy to %s\n", aaa);
1619 network_fp = fopen(aaa, "ab+");
1620 if (network_fp == NULL)
1621 lprintf(2, "ERROR: %s\n", strerror(errno));
1624 /* Save it to disk */
1625 lprintf(9, "Saving to disk\n");
1626 newmsgid = send_message(msg, generate_id, network_fp);
1627 if (network_fp != NULL) {
1629 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1632 if (newmsgid <= 0L) return(-1);
1634 /* Write a supplemental message info record. This doesn't have to
1635 * be a critical section because nobody else knows about this message
1638 lprintf(9, "Creating SuppMsgInfo record\n");
1639 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1640 smi.smi_msgnum = newmsgid;
1641 smi.smi_refcount = 0;
1642 safestrncpy(smi.smi_content_type, content_type, 64);
1643 PutSuppMsgInfo(&smi);
1645 /* Now figure out where to store the pointers */
1646 lprintf(9, "Storing pointers\n");
1649 /* If this is being done by the networker delivering a private
1650 * message, we want to BYPASS saving the sender's copy (because there
1651 * is no local sender; it would otherwise go to the Trashcan).
1653 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1654 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1657 /* For internet mail, drop a copy in the outbound queue room */
1658 /* FIX ... nothing's going to get delivered until we add
1659 some delivery instructions!!! */
1660 if (mailtype == MES_INTERNET) {
1661 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1664 /* Bump this user's messages posted counter. */
1665 lprintf(9, "Updating user\n");
1666 lgetuser(&CC->usersupp, CC->curr_user);
1667 CC->usersupp.posted = CC->usersupp.posted + 1;
1668 lputuser(&CC->usersupp);
1670 /* If this is private, local mail, make a copy in the
1671 * recipient's mailbox and bump the reference count.
1673 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1674 if (getuser(&userbuf, recipient) == 0) {
1675 lprintf(9, "Delivering private mail\n");
1676 MailboxName(actual_rm, &userbuf, MAILROOM);
1677 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1681 /* Perform "after save" hooks */
1682 lprintf(9, "Performing after-save hooks\n");
1683 PerformMessageHooks(msg, EVT_AFTERSAVE);
1686 lprintf(9, "Returning to original room\n");
1687 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1688 getroom(&CC->quickroom, hold_rm);
1696 * Convenience function for generating small administrative messages.
1698 void quickie_message(char *from, char *to, char *room, char *text)
1700 struct CtdlMessage *msg;
1702 msg = mallok(sizeof(struct CtdlMessage));
1703 memset(msg, 0, sizeof(struct CtdlMessage));
1704 msg->cm_magic = CTDLMESSAGE_MAGIC;
1705 msg->cm_anon_type = MES_NORMAL;
1706 msg->cm_format_type = 0;
1707 msg->cm_fields['A'] = strdoop(from);
1708 msg->cm_fields['O'] = strdoop(room);
1709 msg->cm_fields['N'] = strdoop(NODENAME);
1711 msg->cm_fields['R'] = strdoop(to);
1712 msg->cm_fields['M'] = strdoop(text);
1714 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1715 CtdlFreeMessage(msg);
1716 syslog(LOG_NOTICE, text);
1722 * Back end function used by make_message() and similar functions
1724 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1725 size_t maxlen, /* maximum message length */
1726 char *exist /* if non-null, append to it;
1727 exist is ALWAYS freed */
1730 size_t message_len = 0;
1731 size_t buffer_len = 0;
1735 if (exist == NULL) {
1739 m = reallok(exist, strlen(exist) + 4096);
1740 if (m == NULL) phree(exist);
1743 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1750 /* read in the lines of message text one by one */
1752 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1754 /* augment the buffer if we have to */
1755 if ((message_len + strlen(buf) + 2) > buffer_len) {
1756 lprintf(9, "realloking\n");
1757 ptr = reallok(m, (buffer_len * 2) );
1758 if (ptr == NULL) { /* flush if can't allocate */
1759 while ( (client_gets(buf)>0) &&
1760 strcmp(buf, terminator)) ;;
1763 buffer_len = (buffer_len * 2);
1766 lprintf(9, "buffer_len is %d\n", buffer_len);
1770 if (append == NULL) append = m;
1771 while (strlen(append) > 0) ++append;
1772 strcpy(append, buf);
1773 strcat(append, "\n");
1774 message_len = message_len + strlen(buf) + 1;
1776 /* if we've hit the max msg length, flush the rest */
1777 if (message_len >= maxlen) {
1778 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1789 * Build a binary message to be saved on disk.
1792 struct CtdlMessage *make_message(
1793 struct usersupp *author, /* author's usersupp structure */
1794 char *recipient, /* NULL if it's not mail */
1795 char *room, /* room where it's going */
1796 int type, /* see MES_ types in header file */
1797 int net_type, /* see MES_ types in header file */
1798 int format_type, /* local or remote (see citadel.h) */
1799 char *fake_name) /* who we're masquerading as */
1805 struct CtdlMessage *msg;
1807 msg = mallok(sizeof(struct CtdlMessage));
1808 memset(msg, 0, sizeof(struct CtdlMessage));
1809 msg->cm_magic = CTDLMESSAGE_MAGIC;
1810 msg->cm_anon_type = type;
1811 msg->cm_format_type = format_type;
1813 /* Don't confuse the poor folks if it's not routed mail. */
1814 strcpy(dest_node, "");
1816 /* If net_type is MES_BINARY, split out the destination node. */
1817 if (net_type == MES_BINARY) {
1818 strcpy(dest_node, NODENAME);
1819 for (a = 0; a < strlen(recipient); ++a) {
1820 if (recipient[a] == '@') {
1822 strcpy(dest_node, &recipient[a + 1]);
1827 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1828 if (net_type == MES_INTERNET) {
1829 strcpy(dest_node, "internet");
1832 while (isspace(recipient[strlen(recipient) - 1]))
1833 recipient[strlen(recipient) - 1] = 0;
1835 sprintf(buf, "cit%ld", author->usernum); /* Path */
1836 msg->cm_fields['P'] = strdoop(buf);
1838 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1839 msg->cm_fields['T'] = strdoop(buf);
1841 if (fake_name[0]) /* author */
1842 msg->cm_fields['A'] = strdoop(fake_name);
1844 msg->cm_fields['A'] = strdoop(author->fullname);
1846 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1847 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1849 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1851 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1852 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1854 if (recipient[0] != 0)
1855 msg->cm_fields['R'] = strdoop(recipient);
1856 if (dest_node[0] != 0)
1857 msg->cm_fields['D'] = strdoop(dest_node);
1860 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1861 config.c_maxmsglen, NULL);
1872 * message entry - mode 0 (normal)
1874 void cmd_ent0(char *entargs)
1877 char recipient[256];
1879 int format_type = 0;
1880 char newusername[256];
1881 struct CtdlMessage *msg;
1885 struct usersupp tempUS;
1888 post = extract_int(entargs, 0);
1889 extract(recipient, entargs, 1);
1890 anon_flag = extract_int(entargs, 2);
1891 format_type = extract_int(entargs, 3);
1893 /* first check to make sure the request is valid. */
1895 if (!(CC->logged_in)) {
1896 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1899 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1900 cprintf("%d Need to be validated to enter ",
1901 ERROR + HIGHER_ACCESS_REQUIRED);
1902 cprintf("(except in %s> to sysop)\n", MAILROOM);
1905 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1906 cprintf("%d Need net privileges to enter here.\n",
1907 ERROR + HIGHER_ACCESS_REQUIRED);
1910 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1911 cprintf("%d Sorry, this is a read-only room.\n",
1912 ERROR + HIGHER_ACCESS_REQUIRED);
1919 if (CC->usersupp.axlevel < 6) {
1920 cprintf("%d You don't have permission to masquerade.\n",
1921 ERROR + HIGHER_ACCESS_REQUIRED);
1924 extract(newusername, entargs, 4);
1925 memset(CC->fake_postname, 0, 32);
1926 strcpy(CC->fake_postname, newusername);
1927 cprintf("%d Ok\n", OK);
1930 CC->cs_flags |= CS_POSTING;
1933 if (CC->quickroom.QRflags & QR_MAILBOX) {
1934 if (CC->usersupp.axlevel >= 2) {
1935 strcpy(buf, recipient);
1937 strcpy(buf, "sysop");
1938 e = alias(buf); /* alias and mail type */
1939 if ((buf[0] == 0) || (e == MES_ERROR)) {
1940 cprintf("%d Unknown address - cannot send message.\n",
1941 ERROR + NO_SUCH_USER);
1944 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1945 cprintf("%d Net privileges required for network mail.\n",
1946 ERROR + HIGHER_ACCESS_REQUIRED);
1949 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1950 && ((CC->usersupp.flags & US_INTERNET) == 0)
1951 && (!CC->internal_pgm)) {
1952 cprintf("%d You don't have access to Internet mail.\n",
1953 ERROR + HIGHER_ACCESS_REQUIRED);
1956 if (!strcasecmp(buf, "sysop")) {
1961 goto SKFALL; /* don't search local file */
1962 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1963 cprintf("%d Can't send mail to yourself!\n",
1964 ERROR + NO_SUCH_USER);
1967 /* Check to make sure the user exists; also get the correct
1968 * upper/lower casing of the name.
1970 a = getuser(&tempUS, buf);
1972 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1975 strcpy(buf, tempUS.fullname);
1978 SKFALL: b = MES_NORMAL;
1979 if (CC->quickroom.QRflags & QR_ANONONLY)
1981 if (CC->quickroom.QRflags & QR_ANONOPT) {
1985 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1988 /* If we're only checking the validity of the request, return
1989 * success without creating the message.
1992 cprintf("%d %s\n", OK, buf);
1996 cprintf("%d send message\n", SEND_LISTING);
1998 /* Read in the message from the client. */
1999 if (CC->fake_postname[0])
2000 msg = make_message(&CC->usersupp, buf,
2001 CC->quickroom.QRname, b, e, format_type,
2003 else if (CC->fake_username[0])
2004 msg = make_message(&CC->usersupp, buf,
2005 CC->quickroom.QRname, b, e, format_type,
2008 msg = make_message(&CC->usersupp, buf,
2009 CC->quickroom.QRname, b, e, format_type, "");
2012 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2013 CtdlFreeMessage(msg);
2014 CC->fake_postname[0] = '\0';
2021 * message entry - mode 3 (raw)
2023 void cmd_ent3(char *entargs)
2029 unsigned char ch, which_field;
2030 struct usersupp tempUS;
2032 struct CtdlMessage *msg;
2035 if (CC->internal_pgm == 0) {
2036 cprintf("%d This command is for internal programs only.\n",
2041 /* See if there's a recipient, but make sure it's a real one */
2042 extract(recp, entargs, 1);
2043 for (a = 0; a < strlen(recp); ++a)
2044 if (!isprint(recp[a]))
2045 strcpy(&recp[a], &recp[a + 1]);
2046 while (isspace(recp[0]))
2047 strcpy(recp, &recp[1]);
2048 while (isspace(recp[strlen(recp) - 1]))
2049 recp[strlen(recp) - 1] = 0;
2051 /* If we're in Mail, check the recipient */
2052 if (strlen(recp) > 0) {
2053 e = alias(recp); /* alias and mail type */
2054 if ((recp[0] == 0) || (e == MES_ERROR)) {
2055 cprintf("%d Unknown address - cannot send message.\n",
2056 ERROR + NO_SUCH_USER);
2059 if (e == MES_LOCAL) {
2060 a = getuser(&tempUS, recp);
2062 cprintf("%d No such user.\n",
2063 ERROR + NO_SUCH_USER);
2069 /* At this point, message has been approved. */
2070 if (extract_int(entargs, 0) == 0) {
2071 cprintf("%d OK to send\n", OK);
2075 msglen = extract_long(entargs, 2);
2076 msg = mallok(sizeof(struct CtdlMessage));
2078 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2082 memset(msg, 0, sizeof(struct CtdlMessage));
2083 tempbuf = mallok(msglen);
2084 if (tempbuf == NULL) {
2085 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2090 cprintf("%d %ld\n", SEND_BINARY, msglen);
2092 client_read(&ch, 1); /* 0xFF magic number */
2093 msg->cm_magic = CTDLMESSAGE_MAGIC;
2094 client_read(&ch, 1); /* anon type */
2095 msg->cm_anon_type = ch;
2096 client_read(&ch, 1); /* format type */
2097 msg->cm_format_type = ch;
2098 msglen = msglen - 3;
2100 while (msglen > 0) {
2101 client_read(&which_field, 1);
2102 if (!isalpha(which_field)) valid_msg = 0;
2106 client_read(&ch, 1);
2108 a = strlen(tempbuf);
2111 } while ( (ch != 0) && (msglen > 0) );
2113 msg->cm_fields[which_field] = strdoop(tempbuf);
2116 msg->cm_flags = CM_SKIP_HOOKS;
2117 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2118 CtdlFreeMessage(msg);
2124 * API function to delete messages which match a set of criteria
2125 * (returns the actual number of messages deleted)
2127 int CtdlDeleteMessages(char *room_name, /* which room */
2128 long dmsgnum, /* or "0" for any */
2129 char *content_type /* or NULL for any */
2133 struct quickroom qrbuf;
2134 struct cdbdata *cdbfr;
2135 long *msglist = NULL;
2138 int num_deleted = 0;
2140 struct SuppMsgInfo smi;
2142 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2143 room_name, dmsgnum, content_type);
2145 /* get room record, obtaining a lock... */
2146 if (lgetroom(&qrbuf, room_name) != 0) {
2147 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2149 return (0); /* room not found */
2151 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2153 if (cdbfr != NULL) {
2154 msglist = mallok(cdbfr->len);
2155 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2156 num_msgs = cdbfr->len / sizeof(long);
2160 for (i = 0; i < num_msgs; ++i) {
2163 /* Set/clear a bit for each criterion */
2165 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2166 delete_this |= 0x01;
2168 if (content_type == NULL) {
2169 delete_this |= 0x02;
2171 GetSuppMsgInfo(&smi, msglist[i]);
2172 if (!strcasecmp(smi.smi_content_type,
2174 delete_this |= 0x02;
2178 /* Delete message only if all bits are set */
2179 if (delete_this == 0x03) {
2180 AdjRefCount(msglist[i], -1);
2186 num_msgs = sort_msglist(msglist, num_msgs);
2187 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2188 msglist, (num_msgs * sizeof(long)));
2190 qrbuf.QRhighest = msglist[num_msgs - 1];
2194 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2195 return (num_deleted);
2201 * Delete message from current room
2203 void cmd_dele(char *delstr)
2208 getuser(&CC->usersupp, CC->curr_user);
2209 if ((CC->usersupp.axlevel < 6)
2210 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2211 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2212 && (!(CC->internal_pgm))) {
2213 cprintf("%d Higher access required.\n",
2214 ERROR + HIGHER_ACCESS_REQUIRED);
2217 delnum = extract_long(delstr, 0);
2219 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2222 cprintf("%d %d message%s deleted.\n", OK,
2223 num_deleted, ((num_deleted != 1) ? "s" : ""));
2225 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2231 * move or copy a message to another room
2233 void cmd_move(char *args)
2237 struct quickroom qtemp;
2241 num = extract_long(args, 0);
2242 extract(targ, args, 1);
2243 targ[ROOMNAMELEN - 1] = 0;
2244 is_copy = extract_int(args, 2);
2246 getuser(&CC->usersupp, CC->curr_user);
2247 if ((CC->usersupp.axlevel < 6)
2248 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2249 cprintf("%d Higher access required.\n",
2250 ERROR + HIGHER_ACCESS_REQUIRED);
2254 if (getroom(&qtemp, targ) != 0) {
2255 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2259 err = CtdlSaveMsgPointerInRoom(targ, num,
2260 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2262 cprintf("%d Cannot store message in %s: error %d\n",
2267 /* Now delete the message from the source room,
2268 * if this is a 'move' rather than a 'copy' operation.
2270 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2272 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2278 * GetSuppMsgInfo() - Get the supplementary record for a message
2280 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2283 struct cdbdata *cdbsmi;
2286 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2287 smibuf->smi_msgnum = msgnum;
2288 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2290 /* Use the negative of the message number for its supp record index */
2291 TheIndex = (0L - msgnum);
2293 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2294 if (cdbsmi == NULL) {
2295 return; /* record not found; go with defaults */
2297 memcpy(smibuf, cdbsmi->ptr,
2298 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2299 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2306 * PutSuppMsgInfo() - (re)write supplementary record for a message
2308 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2312 /* Use the negative of the message number for its supp record index */
2313 TheIndex = (0L - smibuf->smi_msgnum);
2315 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2316 smibuf->smi_msgnum, smibuf->smi_refcount);
2318 cdb_store(CDB_MSGMAIN,
2319 &TheIndex, sizeof(long),
2320 smibuf, sizeof(struct SuppMsgInfo));
2325 * AdjRefCount - change the reference count for a message;
2326 * delete the message if it reaches zero
2328 void AdjRefCount(long msgnum, int incr)
2331 struct SuppMsgInfo smi;
2334 /* This is a *tight* critical section; please keep it that way, as
2335 * it may get called while nested in other critical sections.
2336 * Complicating this any further will surely cause deadlock!
2338 begin_critical_section(S_SUPPMSGMAIN);
2339 GetSuppMsgInfo(&smi, msgnum);
2340 smi.smi_refcount += incr;
2341 PutSuppMsgInfo(&smi);
2342 end_critical_section(S_SUPPMSGMAIN);
2344 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2345 msgnum, smi.smi_refcount);
2347 /* If the reference count is now zero, delete the message
2348 * (and its supplementary record as well).
2350 if (smi.smi_refcount == 0) {
2351 lprintf(9, "Deleting message <%ld>\n", msgnum);
2353 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2354 delnum = (0L - msgnum);
2355 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2360 * Write a generic object to this room
2362 * Note: this could be much more efficient. Right now we use two temporary
2363 * files, and still pull the message into memory as with all others.
2365 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2366 char *content_type, /* MIME type of this object */
2367 char *tempfilename, /* Where to fetch it from */
2368 struct usersupp *is_mailbox, /* Mailbox room? */
2369 int is_binary, /* Is encoding necessary? */
2370 int is_unique, /* Del others of this type? */
2371 unsigned int flags /* Internal save flags */
2376 char filename[PATH_MAX];
2379 struct quickroom qrbuf;
2380 char roomname[ROOMNAMELEN];
2381 struct CtdlMessage *msg;
2384 if (is_mailbox != NULL)
2385 MailboxName(roomname, is_mailbox, req_room);
2387 safestrncpy(roomname, req_room, sizeof(roomname));
2388 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2390 strcpy(filename, tmpnam(NULL));
2391 fp = fopen(filename, "w");
2395 tempfp = fopen(tempfilename, "r");
2396 if (tempfp == NULL) {
2402 fprintf(fp, "Content-type: %s\n", content_type);
2403 lprintf(9, "Content-type: %s\n", content_type);
2405 if (is_binary == 0) {
2406 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2407 while (ch = getc(tempfp), ch > 0)
2413 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2416 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2417 tempfilename, filename);
2421 lprintf(9, "Allocating\n");
2422 msg = mallok(sizeof(struct CtdlMessage));
2423 memset(msg, 0, sizeof(struct CtdlMessage));
2424 msg->cm_magic = CTDLMESSAGE_MAGIC;
2425 msg->cm_anon_type = MES_NORMAL;
2426 msg->cm_format_type = 4;
2427 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2428 msg->cm_fields['O'] = strdoop(req_room);
2429 msg->cm_fields['N'] = strdoop(config.c_nodename);
2430 msg->cm_fields['H'] = strdoop(config.c_humannode);
2431 msg->cm_flags = flags;
2433 lprintf(9, "Loading\n");
2434 fp = fopen(filename, "rb");
2435 fseek(fp, 0L, SEEK_END);
2438 msg->cm_fields['M'] = mallok(len);
2439 fread(msg->cm_fields['M'], len, 1, fp);
2443 /* Create the requested room if we have to. */
2444 if (getroom(&qrbuf, roomname) != 0) {
2445 create_room(roomname,
2446 ( (is_mailbox != NULL) ? 4 : 3 ),
2449 /* If the caller specified this object as unique, delete all
2450 * other objects of this type that are currently in the room.
2453 lprintf(9, "Deleted %d other msgs of this type\n",
2454 CtdlDeleteMessages(roomname, 0L, content_type));
2456 /* Now write the data */
2457 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2458 CtdlFreeMessage(msg);
2466 void CtdlGetSysConfigBackend(long msgnum) {
2467 config_msgnum = msgnum;
2471 char *CtdlGetSysConfig(char *sysconfname) {
2472 char hold_rm[ROOMNAMELEN];
2475 struct CtdlMessage *msg;
2478 strcpy(hold_rm, CC->quickroom.QRname);
2479 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2480 getroom(&CC->quickroom, hold_rm);
2485 /* We want the last (and probably only) config in this room */
2486 begin_critical_section(S_CONFIG);
2487 config_msgnum = (-1L);
2488 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2489 CtdlGetSysConfigBackend);
2490 msgnum = config_msgnum;
2491 end_critical_section(S_CONFIG);
2497 msg = CtdlFetchMessage(msgnum);
2499 conf = strdoop(msg->cm_fields['M']);
2500 CtdlFreeMessage(msg);
2507 getroom(&CC->quickroom, hold_rm);
2509 lprintf(9, "eggstracting...\n");
2510 if (conf != NULL) do {
2511 extract_token(buf, conf, 0, '\n');
2512 lprintf(9, "eggstracted <%s>\n", buf);
2513 strcpy(conf, &conf[strlen(buf)+1]);
2514 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2519 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2520 char temp[PATH_MAX];
2523 strcpy(temp, tmpnam(NULL));
2525 fp = fopen(temp, "w");
2526 if (fp == NULL) return;
2527 fprintf(fp, "%s", sysconfdata);
2530 /* this handy API function does all the work for us */
2531 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);