4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
39 #include "dynloader.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
59 extern struct config config;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList *filterlist = NULL;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
109 * This function is self explanatory.
110 * (What can I say, I'm in a weird mood today...)
112 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
116 for (i = 0; i < strlen(name); ++i) {
117 if (name[i] == '@') {
118 while (isspace(name[i - 1]) && i > 0) {
119 strcpy(&name[i - 1], &name[i]);
122 while (isspace(name[i + 1])) {
123 strcpy(&name[i + 1], &name[i + 2]);
131 * Aliasing for network mail.
132 * (Error messages have been commented out, because this is a server.)
134 int alias(char *name)
135 { /* process alias and routing info for mail */
138 char aaa[SIZ], bbb[SIZ];
139 char *ignetcfg = NULL;
140 char *ignetmap = NULL;
147 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
149 fp = fopen("network/mail.aliases", "r");
151 fp = fopen("/dev/null", "r");
158 while (fgets(aaa, sizeof aaa, fp) != NULL) {
159 while (isspace(name[0]))
160 strcpy(name, &name[1]);
161 aaa[strlen(aaa) - 1] = 0;
163 for (a = 0; a < strlen(aaa); ++a) {
165 strcpy(bbb, &aaa[a + 1]);
169 if (!strcasecmp(name, aaa))
174 /* Hit the Global Address Book */
175 if (CtdlDirectoryLookup(aaa, name) == 0) {
179 lprintf(7, "Mail is being forwarded to %s\n", name);
181 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
182 for (a=0; a<strlen(name); ++a) {
183 if (name[a] == '@') {
184 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
186 lprintf(7, "Changed to <%s>\n", name);
191 /* determine local or remote type, see citadel.h */
192 at = haschar(name, '@');
193 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
194 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
195 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
197 /* figure out the delivery mode */
198 extract_token(node, name, 1, '@');
200 /* If there are one or more dots in the nodename, we assume that it
201 * is an FQDN and will attempt SMTP delivery to the Internet.
203 if (haschar(node, '.') > 0) {
204 return(MES_INTERNET);
207 /* Otherwise we look in the IGnet maps for a valid Citadel node.
208 * Try directly-connected nodes first...
210 ignetcfg = CtdlGetSysConfig(IGNETCFG);
211 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
212 extract_token(buf, ignetcfg, i, '\n');
213 extract_token(testnode, buf, 0, '|');
214 if (!strcasecmp(node, testnode)) {
222 * Then try nodes that are two or more hops away.
224 ignetmap = CtdlGetSysConfig(IGNETMAP);
225 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
226 extract_token(buf, ignetmap, i, '\n');
227 extract_token(testnode, buf, 0, '|');
228 if (!strcasecmp(node, testnode)) {
235 /* If we get to this point it's an invalid node name */
244 fp = fopen("citadel.control", "r");
245 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
258 /* Determine if a given message matches the fields in a message template.
259 * Return 0 for a successful match.
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
264 /* If there aren't any fields in the template, all messages will
267 if (template == NULL) return(0);
269 /* Null messages are bogus. */
270 if (msg == NULL) return(1);
272 for (i='A'; i<='Z'; ++i) {
273 if (template->cm_fields[i] != NULL) {
274 if (msg->cm_fields[i] == NULL) {
277 if (strcasecmp(msg->cm_fields[i],
278 template->cm_fields[i])) return 1;
282 /* All compares succeeded: we have a match! */
288 * Manipulate the "seen msgs" string.
290 void CtdlSetSeen(long target_msgnum, int target_setting) {
292 struct cdbdata *cdbfr;
302 /* Learn about the user and room in question */
304 getuser(&CC->usersupp, CC->curr_user);
305 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
307 /* Load the message list */
308 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
310 msglist = mallok(cdbfr->len);
311 memcpy(msglist, cdbfr->ptr, cdbfr->len);
312 num_msgs = cdbfr->len / sizeof(long);
315 return; /* No messages at all? No further action. */
318 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
321 for (i=0; i<num_msgs; ++i) {
324 if (msglist[i] == target_msgnum) {
325 is_seen = target_setting;
328 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
334 if (lo < 0L) lo = msglist[i];
337 if ( ((is_seen == 0) && (was_seen == 1))
338 || ((is_seen == 1) && (i == num_msgs-1)) ) {
341 if ( (strlen(newseen) + 20) > SIZ) {
342 strcpy(newseen, &newseen[20]);
345 tmp = strlen(newseen);
347 strcat(newseen, ",");
351 snprintf(&newseen[tmp], sizeof newseen - tmp,
355 snprintf(&newseen[tmp], sizeof newseen - tmp,
364 safestrncpy(vbuf.v_seen, newseen, SIZ);
365 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
367 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
372 * API function to perform an operation for each qualifying message in the
373 * current room. (Returns the number of messages processed.)
375 int CtdlForEachMessage(int mode, long ref,
376 int moderation_level,
378 struct CtdlMessage *compare,
379 void (*CallBack) (long, void *),
385 struct cdbdata *cdbfr;
386 long *msglist = NULL;
388 int num_processed = 0;
391 struct CtdlMessage *msg;
394 int printed_lastold = 0;
396 /* Learn about the user and room in question */
398 getuser(&CC->usersupp, CC->curr_user);
399 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
401 /* Load the message list */
402 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
404 msglist = mallok(cdbfr->len);
405 memcpy(msglist, cdbfr->ptr, cdbfr->len);
406 num_msgs = cdbfr->len / sizeof(long);
409 return 0; /* No messages at all? No further action. */
414 * Now begin the traversal.
416 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
417 GetMetaData(&smi, msglist[a]);
419 /* Filter out messages that are moderated below the level
420 * currently being viewed at.
422 if (smi.meta_mod < moderation_level) {
426 /* If the caller is looking for a specific MIME type, filter
427 * out all messages which are not of the type requested.
429 if (content_type != NULL) if (strlen(content_type) > 0) {
430 if (strcasecmp(smi.meta_content_type, content_type)) {
436 num_msgs = sort_msglist(msglist, num_msgs);
438 /* If a template was supplied, filter out the messages which
439 * don't match. (This could induce some delays!)
442 if (compare != NULL) {
443 for (a = 0; a < num_msgs; ++a) {
444 msg = CtdlFetchMessage(msglist[a]);
446 if (CtdlMsgCmp(msg, compare)) {
449 CtdlFreeMessage(msg);
457 * Now iterate through the message list, according to the
458 * criteria supplied by the caller.
461 for (a = 0; a < num_msgs; ++a) {
462 thismsg = msglist[a];
463 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
464 if (is_seen) lastold = thismsg;
469 || ((mode == MSGS_OLD) && (is_seen))
470 || ((mode == MSGS_NEW) && (!is_seen))
471 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
472 || ((mode == MSGS_FIRST) && (a < ref))
473 || ((mode == MSGS_GT) && (thismsg > ref))
474 || ((mode == MSGS_EQ) && (thismsg == ref))
477 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
479 CallBack(lastold, userdata);
483 if (CallBack) CallBack(thismsg, userdata);
487 phree(msglist); /* Clean up */
488 return num_processed;
494 * cmd_msgs() - get list of message #'s in this room
495 * implements the MSGS server command using CtdlForEachMessage()
497 void cmd_msgs(char *cmdbuf)
506 int with_template = 0;
507 struct CtdlMessage *template = NULL;
509 extract(which, cmdbuf, 0);
510 cm_ref = extract_int(cmdbuf, 1);
511 with_template = extract_int(cmdbuf, 2);
515 if (!strncasecmp(which, "OLD", 3))
517 else if (!strncasecmp(which, "NEW", 3))
519 else if (!strncasecmp(which, "FIRST", 5))
521 else if (!strncasecmp(which, "LAST", 4))
523 else if (!strncasecmp(which, "GT", 2))
526 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
527 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
532 cprintf("%d Send template then receive message list\n",
534 template = (struct CtdlMessage *)
535 mallok(sizeof(struct CtdlMessage));
536 memset(template, 0, sizeof(struct CtdlMessage));
537 while(client_gets(buf), strcmp(buf,"000")) {
538 extract(tfield, buf, 0);
539 extract(tvalue, buf, 1);
540 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
541 if (!strcasecmp(tfield, msgkeys[i])) {
542 template->cm_fields[i] =
549 cprintf("%d Message list...\n", LISTING_FOLLOWS);
552 CtdlForEachMessage(mode, cm_ref,
553 CC->usersupp.moderation_filter,
554 NULL, template, simple_listing, NULL);
555 if (template != NULL) CtdlFreeMessage(template);
563 * help_subst() - support routine for help file viewer
565 void help_subst(char *strbuf, char *source, char *dest)
570 while (p = pattern2(strbuf, source), (p >= 0)) {
571 strcpy(workbuf, &strbuf[p + strlen(source)]);
572 strcpy(&strbuf[p], dest);
573 strcat(strbuf, workbuf);
578 void do_help_subst(char *buffer)
582 help_subst(buffer, "^nodename", config.c_nodename);
583 help_subst(buffer, "^humannode", config.c_humannode);
584 help_subst(buffer, "^fqdn", config.c_fqdn);
585 help_subst(buffer, "^username", CC->usersupp.fullname);
586 snprintf(buf2, sizeof buf2, "%ld", CC->usersupp.usernum);
587 help_subst(buffer, "^usernum", buf2);
588 help_subst(buffer, "^sysadm", config.c_sysadm);
589 help_subst(buffer, "^variantname", CITADEL);
590 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
591 help_subst(buffer, "^maxsessions", buf2);
597 * memfmout() - Citadel text formatter and paginator.
598 * Although the original purpose of this routine was to format
599 * text to the reader's screen width, all we're really using it
600 * for here is to format text out to 80 columns before sending it
601 * to the client. The client software may reformat it again.
604 int width, /* screen width to use */
605 char *mptr, /* where are we going to get our text from? */
606 char subst, /* nonzero if we should do substitutions */
607 char *nl) /* string to terminate lines with */
619 c = 1; /* c is the current pos */
623 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
625 buffer[strlen(buffer) + 1] = 0;
626 buffer[strlen(buffer)] = ch;
629 if (buffer[0] == '^')
630 do_help_subst(buffer);
632 buffer[strlen(buffer) + 1] = 0;
634 strcpy(buffer, &buffer[1]);
642 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
644 if (((old == 13) || (old == 10)) && (isspace(real))) {
652 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
653 cprintf("%s%s", nl, aaa);
662 if ((strlen(aaa) + c) > (width - 5)) {
671 if ((ch == 13) || (ch == 10)) {
672 cprintf("%s%s", aaa, nl);
679 cprintf("%s%s", aaa, nl);
685 * Callback function for mime parser that simply lists the part
687 void list_this_part(char *name, char *filename, char *partnum, char *disp,
688 void *content, char *cbtype, size_t length, char *encoding,
692 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
693 name, filename, partnum, disp, cbtype, (long)length);
698 * Callback function for mime parser that opens a section for downloading
700 void mime_download(char *name, char *filename, char *partnum, char *disp,
701 void *content, char *cbtype, size_t length, char *encoding,
705 /* Silently go away if there's already a download open... */
706 if (CC->download_fp != NULL)
709 /* ...or if this is not the desired section */
710 if (strcasecmp(desired_section, partnum))
713 CC->download_fp = tmpfile();
714 if (CC->download_fp == NULL)
717 fwrite(content, length, 1, CC->download_fp);
718 fflush(CC->download_fp);
719 rewind(CC->download_fp);
721 OpenCmdResult(filename, cbtype);
727 * Load a message from disk into memory.
728 * This is used by CtdlOutputMsg() and other fetch functions.
730 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
731 * using the CtdlMessageFree() function.
733 struct CtdlMessage *CtdlFetchMessage(long msgnum)
735 struct cdbdata *dmsgtext;
736 struct CtdlMessage *ret = NULL;
739 CIT_UBYTE field_header;
742 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
743 if (dmsgtext == NULL) {
746 mptr = dmsgtext->ptr;
748 /* Parse the three bytes that begin EVERY message on disk.
749 * The first is always 0xFF, the on-disk magic number.
750 * The second is the anonymous/public type byte.
751 * The third is the format type byte (vari, fixed, or MIME).
755 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
759 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
760 memset(ret, 0, sizeof(struct CtdlMessage));
762 ret->cm_magic = CTDLMESSAGE_MAGIC;
763 ret->cm_anon_type = *mptr++; /* Anon type byte */
764 ret->cm_format_type = *mptr++; /* Format type byte */
767 * The rest is zero or more arbitrary fields. Load them in.
768 * We're done when we encounter either a zero-length field or
769 * have just processed the 'M' (message text) field.
772 field_length = strlen(mptr);
773 if (field_length == 0)
775 field_header = *mptr++;
776 ret->cm_fields[field_header] = mallok(field_length);
777 strcpy(ret->cm_fields[field_header], mptr);
779 while (*mptr++ != 0); /* advance to next field */
781 } while ((field_length > 0) && (field_header != 'M'));
785 /* Always make sure there's something in the msg text field */
786 if (ret->cm_fields['M'] == NULL)
787 ret->cm_fields['M'] = strdoop("<no text>\n");
789 /* Perform "before read" hooks (aborting if any return nonzero) */
790 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
791 CtdlFreeMessage(ret);
800 * Returns 1 if the supplied pointer points to a valid Citadel message.
801 * If the pointer is NULL or the magic number check fails, returns 0.
803 int is_valid_message(struct CtdlMessage *msg) {
806 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
807 lprintf(3, "is_valid_message() -- self-check failed\n");
815 * 'Destructor' for struct CtdlMessage
817 void CtdlFreeMessage(struct CtdlMessage *msg)
821 if (is_valid_message(msg) == 0) return;
823 for (i = 0; i < 256; ++i)
824 if (msg->cm_fields[i] != NULL) {
825 phree(msg->cm_fields[i]);
828 msg->cm_magic = 0; /* just in case */
834 * Pre callback function for multipart/alternative
836 * NOTE: this differs from the standard behavior for a reason. Normally when
837 * displaying multipart/alternative you want to show the _last_ usable
838 * format in the message. Here we show the _first_ one, because it's
839 * usually text/plain. Since this set of functions is designed for text
840 * output to non-MIME-aware clients, this is the desired behavior.
843 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
844 void *content, char *cbtype, size_t length, char *encoding,
847 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
848 if (!strcasecmp(cbtype, "multipart/alternative")) {
856 * Post callback function for multipart/alternative
858 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
859 void *content, char *cbtype, size_t length, char *encoding,
862 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
863 if (!strcasecmp(cbtype, "multipart/alternative")) {
871 * Inline callback function for mime parser that wants to display text
873 void fixed_output(char *name, char *filename, char *partnum, char *disp,
874 void *content, char *cbtype, size_t length, char *encoding,
882 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
885 * If we're in the middle of a multipart/alternative scope and
886 * we've already printed another section, skip this one.
888 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
889 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
894 if ( (!strcasecmp(cbtype, "text/plain"))
895 || (strlen(cbtype)==0) ) {
901 if (ch==10) cprintf("\r\n");
902 else cprintf("%c", ch);
906 if (ch != '\n') cprintf("\n");
908 else if (!strcasecmp(cbtype, "text/html")) {
909 ptr = html_to_ascii(content, 80, 0);
914 if (ch==10) cprintf("\r\n");
915 else cprintf("%c", ch);
919 else if (strncasecmp(cbtype, "multipart/", 10)) {
920 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
921 partnum, filename, cbtype, (long)length);
927 * Get a message off disk. (returns om_* values found in msgbase.h)
930 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
931 int mode, /* how would you like that message? */
932 int headers_only, /* eschew the message body? */
933 int do_proto, /* do Citadel protocol responses? */
934 int crlf /* Use CRLF newlines instead of LF? */
936 struct CtdlMessage *TheMessage;
939 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
944 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
945 if (do_proto) cprintf("%d Not logged in.\n",
946 ERROR + NOT_LOGGED_IN);
947 return(om_not_logged_in);
950 /* FIXME ... small security issue
951 * We need to check to make sure the requested message is actually
952 * in the current room, and set msg_ok to 1 only if it is. This
953 * functionality is currently missing because I'm in a hurry to replace
954 * broken production code with nonbroken pre-beta code. :( -- ajc
957 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
959 return(om_no_such_msg);
964 * Fetch the message from disk
966 TheMessage = CtdlFetchMessage(msg_num);
967 if (TheMessage == NULL) {
968 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
970 return(om_no_such_msg);
973 retcode = CtdlOutputPreLoadedMsg(
974 TheMessage, msg_num, mode,
975 headers_only, do_proto, crlf);
977 CtdlFreeMessage(TheMessage);
983 * Get a message off disk. (returns om_* values found in msgbase.h)
986 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
988 int mode, /* how would you like that message? */
989 int headers_only, /* eschew the message body? */
990 int do_proto, /* do Citadel protocol responses? */
991 int crlf /* Use CRLF newlines instead of LF? */
997 char display_name[SIZ];
999 char *nl; /* newline string */
1002 /* buffers needed for RFC822 translation */
1009 char datestamp[SIZ];
1012 snprintf(mid, sizeof mid, "%ld", msg_num);
1013 nl = (crlf ? "\r\n" : "\n");
1015 if (!is_valid_message(TheMessage)) {
1016 lprintf(1, "ERROR: invalid preloaded message for output\n");
1017 return(om_no_such_msg);
1020 /* Are we downloading a MIME component? */
1021 if (mode == MT_DOWNLOAD) {
1022 if (TheMessage->cm_format_type != FMT_RFC822) {
1024 cprintf("%d This is not a MIME message.\n",
1026 } else if (CC->download_fp != NULL) {
1027 if (do_proto) cprintf(
1028 "%d You already have a download open.\n",
1031 /* Parse the message text component */
1032 mptr = TheMessage->cm_fields['M'];
1033 mime_parser(mptr, NULL,
1034 *mime_download, NULL, NULL,
1036 /* If there's no file open by this time, the requested
1037 * section wasn't found, so print an error
1039 if (CC->download_fp == NULL) {
1040 if (do_proto) cprintf(
1041 "%d Section %s not found.\n",
1042 ERROR + FILE_NOT_FOUND,
1046 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1049 /* now for the user-mode message reading loops */
1050 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1052 /* Tell the client which format type we're using. If this is a
1053 * MIME message, *lie* about it and tell the user it's fixed-format.
1055 if (mode == MT_CITADEL) {
1056 if (TheMessage->cm_format_type == FMT_RFC822) {
1057 if (do_proto) cprintf("type=1\n");
1060 if (do_proto) cprintf("type=%d\n",
1061 TheMessage->cm_format_type);
1065 /* nhdr=yes means that we're only displaying headers, no body */
1066 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1067 if (do_proto) cprintf("nhdr=yes\n");
1070 /* begin header processing loop for Citadel message format */
1072 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1074 strcpy(display_name, "<unknown>");
1075 if (TheMessage->cm_fields['A']) {
1076 strcpy(buf, TheMessage->cm_fields['A']);
1077 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1078 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1079 strcpy(display_name, "****");
1081 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1082 strcpy(display_name, "anonymous");
1085 strcpy(display_name, buf);
1087 if ((is_room_aide())
1088 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1089 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1090 size_t tmp = strlen(display_name);
1091 snprintf(&display_name[tmp],
1092 sizeof display_name - tmp,
1097 /* Don't show Internet address for users on the
1098 * local Citadel network.
1101 if (TheMessage->cm_fields['N'] != NULL)
1102 if (strlen(TheMessage->cm_fields['N']) > 0)
1103 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1107 /* Now spew the header fields in the order we like them. */
1108 strcpy(allkeys, FORDER);
1109 for (i=0; i<strlen(allkeys); ++i) {
1110 k = (int) allkeys[i];
1112 if ( (TheMessage->cm_fields[k] != NULL)
1113 && (msgkeys[k] != NULL) ) {
1115 if (do_proto) cprintf("%s=%s\n",
1119 else if ((k == 'F') && (suppress_f)) {
1122 /* Masquerade display name if needed */
1124 if (do_proto) cprintf("%s=%s\n",
1126 TheMessage->cm_fields[k]
1135 /* begin header processing loop for RFC822 transfer format */
1140 strcpy(snode, NODENAME);
1141 strcpy(lnode, HUMANNODE);
1142 if (mode == MT_RFC822) {
1143 cprintf("X-UIDL: %ld%s", msg_num, nl);
1144 for (i = 0; i < 256; ++i) {
1145 if (TheMessage->cm_fields[i]) {
1146 mptr = TheMessage->cm_fields[i];
1149 strcpy(luser, mptr);
1150 strcpy(suser, mptr);
1153 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1154 into thinking that mail messages are newsgroup messages instead. When we
1155 add NNTP support back into Citadel we'll have to add code to only output
1156 this field when appropriate.
1157 else if (i == 'P') {
1158 cprintf("Path: %s%s", mptr, nl);
1162 cprintf("Subject: %s%s", mptr, nl);
1164 safestrncpy(mid, mptr, sizeof mid);
1166 safestrncpy(lnode, mptr, sizeof lnode);
1168 safestrncpy(fuser, mptr, sizeof fuser);
1170 cprintf("X-Citadel-Room: %s%s",
1173 safestrncpy(snode, mptr, sizeof snode);
1175 cprintf("To: %s%s", mptr, nl);
1176 else if (i == 'T') {
1177 datestring(datestamp, sizeof datestamp, atol(mptr),
1178 DATESTRING_RFC822 );
1179 cprintf("Date: %s%s", datestamp, nl);
1185 for (i=0; i<strlen(suser); ++i) {
1186 suser[i] = tolower(suser[i]);
1187 if (!isalnum(suser[i])) suser[i]='_';
1190 if (mode == MT_RFC822) {
1191 if (!strcasecmp(snode, NODENAME)) {
1192 strcpy(snode, FQDN);
1195 /* Construct a fun message id */
1196 cprintf("Message-ID: <%s", mid);
1197 if (strchr(mid, '@')==NULL) {
1198 cprintf("@%s", snode);
1202 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1204 if (strlen(fuser) > 0) {
1205 cprintf("From: %s (%s)%s", fuser, luser, nl);
1208 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1211 cprintf("Organization: %s%s", lnode, nl);
1214 /* end header processing loop ... at this point, we're in the text */
1216 mptr = TheMessage->cm_fields['M'];
1218 /* Tell the client about the MIME parts in this message */
1219 if (TheMessage->cm_format_type == FMT_RFC822) {
1220 if (mode == MT_CITADEL) {
1221 mime_parser(mptr, NULL,
1222 *list_this_part, NULL, NULL,
1225 else if (mode == MT_MIME) { /* list parts only */
1226 mime_parser(mptr, NULL,
1227 *list_this_part, NULL, NULL,
1229 if (do_proto) cprintf("000\n");
1232 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1233 /* FIXME ... we have to put some code in here to avoid
1234 * printing duplicate header information when both
1235 * Citadel and RFC822 headers exist. Preference should
1236 * probably be given to the RFC822 headers.
1238 while (ch=*(mptr++), ch!=0) {
1240 else if (ch==10) cprintf("%s", nl);
1241 else cprintf("%c", ch);
1243 if (do_proto) cprintf("000\n");
1249 if (do_proto) cprintf("000\n");
1253 /* signify start of msg text */
1254 if (mode == MT_CITADEL)
1255 if (do_proto) cprintf("text\n");
1256 if (mode == MT_RFC822) {
1257 if (TheMessage->cm_fields['U'] == NULL) {
1258 cprintf("Subject: (no subject)%s", nl);
1263 /* If the format type on disk is 1 (fixed-format), then we want
1264 * everything to be output completely literally ... regardless of
1265 * what message transfer format is in use.
1267 if (TheMessage->cm_format_type == FMT_FIXED) {
1269 while (ch = *mptr++, ch > 0) {
1272 if ((ch == 10) || (strlen(buf) > 250)) {
1273 cprintf("%s%s", buf, nl);
1276 buf[strlen(buf) + 1] = 0;
1277 buf[strlen(buf)] = ch;
1280 if (strlen(buf) > 0)
1281 cprintf("%s%s", buf, nl);
1284 /* If the message on disk is format 0 (Citadel vari-format), we
1285 * output using the formatter at 80 columns. This is the final output
1286 * form if the transfer format is RFC822, but if the transfer format
1287 * is Citadel proprietary, it'll still work, because the indentation
1288 * for new paragraphs is correct and the client will reformat the
1289 * message to the reader's screen width.
1291 if (TheMessage->cm_format_type == FMT_CITADEL) {
1292 memfmout(80, mptr, 0, nl);
1295 /* If the message on disk is format 4 (MIME), we've gotta hand it
1296 * off to the MIME parser. The client has already been told that
1297 * this message is format 1 (fixed format), so the callback function
1298 * we use will display those parts as-is.
1300 if (TheMessage->cm_format_type == FMT_RFC822) {
1301 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1302 memset(ma, 0, sizeof(struct ma_info));
1303 mime_parser(mptr, NULL,
1304 *fixed_output, *fixed_output_pre, *fixed_output_post,
1308 /* now we're done */
1309 if (do_proto) cprintf("000\n");
1316 * display a message (mode 0 - Citadel proprietary)
1318 void cmd_msg0(char *cmdbuf)
1321 int headers_only = 0;
1323 msgid = extract_long(cmdbuf, 0);
1324 headers_only = extract_int(cmdbuf, 1);
1326 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1332 * display a message (mode 2 - RFC822)
1334 void cmd_msg2(char *cmdbuf)
1337 int headers_only = 0;
1339 msgid = extract_long(cmdbuf, 0);
1340 headers_only = extract_int(cmdbuf, 1);
1342 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1348 * display a message (mode 3 - IGnet raw format - internal programs only)
1350 void cmd_msg3(char *cmdbuf)
1353 struct CtdlMessage *msg;
1356 if (CC->internal_pgm == 0) {
1357 cprintf("%d This command is for internal programs only.\n",
1362 msgnum = extract_long(cmdbuf, 0);
1363 msg = CtdlFetchMessage(msgnum);
1365 cprintf("%d Message %ld not found.\n",
1370 serialize_message(&smr, msg);
1371 CtdlFreeMessage(msg);
1374 cprintf("%d Unable to serialize message\n",
1375 ERROR+INTERNAL_ERROR);
1379 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1380 client_write(smr.ser, smr.len);
1387 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1389 void cmd_msg4(char *cmdbuf)
1393 msgid = extract_long(cmdbuf, 0);
1394 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1398 * Open a component of a MIME message as a download file
1400 void cmd_opna(char *cmdbuf)
1404 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1406 msgid = extract_long(cmdbuf, 0);
1407 extract(desired_section, cmdbuf, 1);
1409 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1414 * Save a message pointer into a specified room
1415 * (Returns 0 for success, nonzero for failure)
1416 * roomname may be NULL to use the current room
1418 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1420 char hold_rm[ROOMNAMELEN];
1421 struct cdbdata *cdbfr;
1424 long highest_msg = 0L;
1425 struct CtdlMessage *msg = NULL;
1427 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1428 roomname, msgid, flags);
1430 strcpy(hold_rm, CC->quickroom.QRname);
1432 /* We may need to check to see if this message is real */
1433 if ( (flags & SM_VERIFY_GOODNESS)
1434 || (flags & SM_DO_REPL_CHECK)
1436 msg = CtdlFetchMessage(msgid);
1437 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1440 /* Perform replication checks if necessary */
1441 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1443 if (getroom(&CC->quickroom,
1444 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1446 lprintf(9, "No such room <%s>\n", roomname);
1447 if (msg != NULL) CtdlFreeMessage(msg);
1448 return(ERROR + ROOM_NOT_FOUND);
1451 if (ReplicationChecks(msg) != 0) {
1452 getroom(&CC->quickroom, hold_rm);
1453 if (msg != NULL) CtdlFreeMessage(msg);
1454 lprintf(9, "Did replication, and newer exists\n");
1459 /* Now the regular stuff */
1460 if (lgetroom(&CC->quickroom,
1461 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1463 lprintf(9, "No such room <%s>\n", roomname);
1464 if (msg != NULL) CtdlFreeMessage(msg);
1465 return(ERROR + ROOM_NOT_FOUND);
1468 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1469 if (cdbfr == NULL) {
1473 msglist = mallok(cdbfr->len);
1474 if (msglist == NULL)
1475 lprintf(3, "ERROR malloc msglist!\n");
1476 num_msgs = cdbfr->len / sizeof(long);
1477 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1482 /* Make sure the message doesn't already exist in this room. It
1483 * is absolutely taboo to have more than one reference to the same
1484 * message in a room.
1486 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1487 if (msglist[i] == msgid) {
1488 lputroom(&CC->quickroom); /* unlock the room */
1489 getroom(&CC->quickroom, hold_rm);
1490 if (msg != NULL) CtdlFreeMessage(msg);
1491 return(ERROR + ALREADY_EXISTS);
1495 /* Now add the new message */
1497 msglist = reallok(msglist,
1498 (num_msgs * sizeof(long)));
1500 if (msglist == NULL) {
1501 lprintf(3, "ERROR: can't realloc message list!\n");
1503 msglist[num_msgs - 1] = msgid;
1505 /* Sort the message list, so all the msgid's are in order */
1506 num_msgs = sort_msglist(msglist, num_msgs);
1508 /* Determine the highest message number */
1509 highest_msg = msglist[num_msgs - 1];
1511 /* Write it back to disk. */
1512 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1513 msglist, num_msgs * sizeof(long));
1515 /* Free up the memory we used. */
1518 /* Update the highest-message pointer and unlock the room. */
1519 CC->quickroom.QRhighest = highest_msg;
1520 lputroom(&CC->quickroom);
1521 getroom(&CC->quickroom, hold_rm);
1523 /* Bump the reference count for this message. */
1524 if ((flags & SM_DONT_BUMP_REF)==0) {
1525 AdjRefCount(msgid, +1);
1528 /* Return success. */
1529 if (msg != NULL) CtdlFreeMessage(msg);
1536 * Message base operation to send a message to the master file
1537 * (returns new message number)
1539 * This is the back end for CtdlSubmitMsg() and should not be directly
1540 * called by server-side modules.
1543 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1544 FILE *save_a_copy) /* save a copy to disk? */
1551 /* Get a new message number */
1552 newmsgid = get_new_message_number();
1553 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1555 /* Generate an ID if we don't have one already */
1556 if (msg->cm_fields['I']==NULL) {
1557 msg->cm_fields['I'] = strdoop(msgidbuf);
1560 serialize_message(&smr, msg);
1563 cprintf("%d Unable to serialize message\n",
1564 ERROR+INTERNAL_ERROR);
1568 /* Write our little bundle of joy into the message base */
1569 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1570 smr.ser, smr.len) < 0) {
1571 lprintf(2, "Can't store message\n");
1577 /* If the caller specified that a copy should be saved to a particular
1578 * file handle, do that now too.
1580 if (save_a_copy != NULL) {
1581 fwrite(smr.ser, smr.len, 1, save_a_copy);
1584 /* Free the memory we used for the serialized message */
1587 /* Return the *local* message ID to the caller
1588 * (even if we're storing an incoming network message)
1596 * Serialize a struct CtdlMessage into the format used on disk and network.
1598 * This function loads up a "struct ser_ret" (defined in server.h) which
1599 * contains the length of the serialized message and a pointer to the
1600 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1602 void serialize_message(struct ser_ret *ret, /* return values */
1603 struct CtdlMessage *msg) /* unserialized msg */
1607 static char *forder = FORDER;
1609 if (is_valid_message(msg) == 0) return; /* self check */
1612 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1613 ret->len = ret->len +
1614 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1616 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1617 ret->ser = mallok(ret->len);
1618 if (ret->ser == NULL) {
1624 ret->ser[1] = msg->cm_anon_type;
1625 ret->ser[2] = msg->cm_format_type;
1628 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1629 ret->ser[wlen++] = (char)forder[i];
1630 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1631 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1633 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1634 (long)ret->len, (long)wlen);
1642 * Back end for the ReplicationChecks() function
1644 void check_repl(long msgnum, void *userdata) {
1645 struct CtdlMessage *msg;
1646 time_t timestamp = (-1L);
1648 lprintf(9, "check_repl() found message %ld\n", msgnum);
1649 msg = CtdlFetchMessage(msgnum);
1650 if (msg == NULL) return;
1651 if (msg->cm_fields['T'] != NULL) {
1652 timestamp = atol(msg->cm_fields['T']);
1654 CtdlFreeMessage(msg);
1656 if (timestamp > msg_repl->highest) {
1657 msg_repl->highest = timestamp; /* newer! */
1658 lprintf(9, "newer!\n");
1661 lprintf(9, "older!\n");
1663 /* Existing isn't newer? Then delete the old one(s). */
1664 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1669 * Check to see if any messages already exist which carry the same Extended ID
1673 * -> With older timestamps: delete them and return 0. Message will be saved.
1674 * -> With newer timestamps: return 1. Message save will be aborted.
1676 int ReplicationChecks(struct CtdlMessage *msg) {
1677 struct CtdlMessage *template;
1680 lprintf(9, "ReplicationChecks() started\n");
1681 /* No extended id? Don't do anything. */
1682 if (msg->cm_fields['E'] == NULL) return 0;
1683 if (strlen(msg->cm_fields['E']) == 0) return 0;
1684 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1686 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1687 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1688 msg_repl->highest = atol(msg->cm_fields['T']);
1690 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1691 memset(template, 0, sizeof(struct CtdlMessage));
1692 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1694 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1697 /* If a newer message exists with the same Extended ID, abort
1700 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1704 CtdlFreeMessage(template);
1705 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1713 * Save a message to disk and submit it into the delivery system.
1715 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1716 struct recptypes *recps, /* recipients (if mail) */
1717 char *force /* force a particular room? */
1720 char hold_rm[ROOMNAMELEN];
1721 char actual_rm[ROOMNAMELEN];
1722 char force_room[ROOMNAMELEN];
1723 char content_type[SIZ]; /* We have to learn this */
1724 char recipient[SIZ];
1727 struct usersupp userbuf;
1729 struct MetaData smi;
1730 FILE *network_fp = NULL;
1731 static int seqnum = 1;
1732 struct CtdlMessage *imsg = NULL;
1735 char *hold_R, *hold_D;
1737 lprintf(9, "CtdlSubmitMsg() called\n");
1738 if (is_valid_message(msg) == 0) return(-1); /* self check */
1740 /* If this message has no timestamp, we take the liberty of
1741 * giving it one, right now.
1743 if (msg->cm_fields['T'] == NULL) {
1744 lprintf(9, "Generating timestamp\n");
1745 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1746 msg->cm_fields['T'] = strdoop(aaa);
1749 /* If this message has no path, we generate one.
1751 if (msg->cm_fields['P'] == NULL) {
1752 lprintf(9, "Generating path\n");
1753 if (msg->cm_fields['A'] != NULL) {
1754 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1755 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1756 if (isspace(msg->cm_fields['P'][a])) {
1757 msg->cm_fields['P'][a] = ' ';
1762 msg->cm_fields['P'] = strdoop("unknown");
1766 strcpy(force_room, force);
1768 /* Learn about what's inside, because it's what's inside that counts */
1769 lprintf(9, "Learning what's inside\n");
1770 if (msg->cm_fields['M'] == NULL) {
1771 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1774 switch (msg->cm_format_type) {
1776 strcpy(content_type, "text/x-citadel-variformat");
1779 strcpy(content_type, "text/plain");
1782 strcpy(content_type, "text/plain");
1783 /* advance past header fields */
1784 mptr = msg->cm_fields['M'];
1787 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1788 safestrncpy(content_type, mptr,
1789 sizeof(content_type));
1790 strcpy(content_type, &content_type[14]);
1791 for (a = 0; a < strlen(content_type); ++a)
1792 if ((content_type[a] == ';')
1793 || (content_type[a] == ' ')
1794 || (content_type[a] == 13)
1795 || (content_type[a] == 10))
1796 content_type[a] = 0;
1803 /* Goto the correct room */
1804 lprintf(9, "Switching rooms\n");
1805 strcpy(hold_rm, CC->quickroom.QRname);
1806 strcpy(actual_rm, CC->quickroom.QRname);
1807 if (recps != NULL) {
1808 strcpy(actual_rm, SENTITEMS);
1811 /* If the user is a twit, move to the twit room for posting */
1812 lprintf(9, "Handling twit stuff\n");
1814 if (CC->usersupp.axlevel == 2) {
1815 strcpy(hold_rm, actual_rm);
1816 strcpy(actual_rm, config.c_twitroom);
1820 /* ...or if this message is destined for Aide> then go there. */
1821 if (strlen(force_room) > 0) {
1822 strcpy(actual_rm, force_room);
1825 lprintf(9, "Possibly relocating\n");
1826 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1827 getroom(&CC->quickroom, actual_rm);
1831 * If this message has no O (room) field, generate one.
1833 if (msg->cm_fields['O'] == NULL) {
1834 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1837 /* Perform "before save" hooks (aborting if any return nonzero) */
1838 lprintf(9, "Performing before-save hooks\n");
1839 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1841 /* If this message has an Extended ID, perform replication checks */
1842 lprintf(9, "Performing replication checks\n");
1843 if (ReplicationChecks(msg) > 0) return(-1);
1845 /* Save it to disk */
1846 lprintf(9, "Saving to disk\n");
1847 newmsgid = send_message(msg, NULL);
1848 if (newmsgid <= 0L) return(-1);
1850 /* Write a supplemental message info record. This doesn't have to
1851 * be a critical section because nobody else knows about this message
1854 lprintf(9, "Creating MetaData record\n");
1855 memset(&smi, 0, sizeof(struct MetaData));
1856 smi.meta_msgnum = newmsgid;
1857 smi.meta_refcount = 0;
1858 safestrncpy(smi.meta_content_type, content_type, 64);
1861 /* Now figure out where to store the pointers */
1862 lprintf(9, "Storing pointers\n");
1864 /* If this is being done by the networker delivering a private
1865 * message, we want to BYPASS saving the sender's copy (because there
1866 * is no local sender; it would otherwise go to the Trashcan).
1868 if ((!CC->internal_pgm) || (recps == NULL)) {
1869 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1870 lprintf(3, "ERROR saving message pointer!\n");
1871 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1875 /* For internet mail, drop a copy in the outbound queue room */
1877 if (recps->num_internet > 0) {
1878 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1881 /* If other rooms are specified, drop them there too. */
1883 if (recps->num_room > 0)
1884 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1885 extract(recipient, recps->recp_room, i);
1886 lprintf(9, "Delivering to local room <%s>\n", recipient);
1887 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1890 /* Bump this user's messages posted counter. */
1891 lprintf(9, "Updating user\n");
1892 lgetuser(&CC->usersupp, CC->curr_user);
1893 CC->usersupp.posted = CC->usersupp.posted + 1;
1894 lputuser(&CC->usersupp);
1896 /* If this is private, local mail, make a copy in the
1897 * recipient's mailbox and bump the reference count.
1900 if (recps->num_local > 0)
1901 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1902 extract(recipient, recps->recp_local, i);
1903 lprintf(9, "Delivering private local mail to <%s>\n",
1905 if (getuser(&userbuf, recipient) == 0) {
1906 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
1907 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1910 lprintf(9, "No user <%s>\n", recipient);
1911 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1915 /* Perform "after save" hooks */
1916 lprintf(9, "Performing after-save hooks\n");
1917 PerformMessageHooks(msg, EVT_AFTERSAVE);
1919 /* For IGnet mail, we have to save a new copy into the spooler for
1920 * each recipient, with the R and D fields set to the recipient and
1921 * destination-node. This has two ugly side effects: all other
1922 * recipients end up being unlisted in this recipient's copy of the
1923 * message, and it has to deliver multiple messages to the same
1924 * node. We'll revisit this again in a year or so when everyone has
1925 * a network spool receiver that can handle the new style messages.
1928 if (recps->num_ignet > 0)
1929 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1930 extract(recipient, recps->recp_ignet, i);
1932 hold_R = msg->cm_fields['R'];
1933 hold_D = msg->cm_fields['D'];
1934 msg->cm_fields['R'] = mallok(SIZ);
1935 msg->cm_fields['D'] = mallok(SIZ);
1936 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1937 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1939 serialize_message(&smr, msg);
1941 snprintf(aaa, sizeof aaa,
1942 "./network/spoolin/netmail.%04lx.%04x.%04x",
1943 (long) getpid(), CC->cs_pid, ++seqnum);
1944 network_fp = fopen(aaa, "wb+");
1945 if (network_fp != NULL) {
1946 fwrite(smr.ser, smr.len, 1, network_fp);
1952 phree(msg->cm_fields['R']);
1953 phree(msg->cm_fields['D']);
1954 msg->cm_fields['R'] = hold_R;
1955 msg->cm_fields['D'] = hold_D;
1958 /* Go back to the room we started from */
1959 lprintf(9, "Returning to original room\n");
1960 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1961 getroom(&CC->quickroom, hold_rm);
1963 /* For internet mail, generate delivery instructions.
1964 * Yes, this is recursive. Deal with it. Infinite recursion does
1965 * not happen because the delivery instructions message does not
1966 * contain a recipient.
1969 if (recps->num_internet > 0) {
1970 lprintf(9, "Generating delivery instructions\n");
1971 instr = mallok(SIZ * 2);
1972 snprintf(instr, SIZ * 2,
1973 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1975 SPOOLMIME, newmsgid, (long)time(NULL),
1976 msg->cm_fields['A'], msg->cm_fields['N']
1979 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1980 size_t tmp = strlen(instr);
1981 extract(recipient, recps->recp_internet, i);
1982 snprintf(&instr[tmp], SIZ * 2 - tmp,
1983 "remote|%s|0||\n", recipient);
1986 imsg = mallok(sizeof(struct CtdlMessage));
1987 memset(imsg, 0, sizeof(struct CtdlMessage));
1988 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1989 imsg->cm_anon_type = MES_NORMAL;
1990 imsg->cm_format_type = FMT_RFC822;
1991 imsg->cm_fields['A'] = strdoop("Citadel");
1992 imsg->cm_fields['M'] = instr;
1993 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1994 CtdlFreeMessage(imsg);
2003 * Convenience function for generating small administrative messages.
2005 void quickie_message(char *from, char *to, char *room, char *text)
2007 struct CtdlMessage *msg;
2009 msg = mallok(sizeof(struct CtdlMessage));
2010 memset(msg, 0, sizeof(struct CtdlMessage));
2011 msg->cm_magic = CTDLMESSAGE_MAGIC;
2012 msg->cm_anon_type = MES_NORMAL;
2013 msg->cm_format_type = 0;
2014 msg->cm_fields['A'] = strdoop(from);
2015 msg->cm_fields['O'] = strdoop(room);
2016 msg->cm_fields['N'] = strdoop(NODENAME);
2018 msg->cm_fields['R'] = strdoop(to);
2019 msg->cm_fields['M'] = strdoop(text);
2021 CtdlSubmitMsg(msg, NULL, room);
2022 CtdlFreeMessage(msg);
2023 syslog(LOG_NOTICE, text);
2029 * Back end function used by make_message() and similar functions
2031 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2032 size_t maxlen, /* maximum message length */
2033 char *exist /* if non-null, append to it;
2034 exist is ALWAYS freed */
2038 size_t message_len = 0;
2039 size_t buffer_len = 0;
2043 if (exist == NULL) {
2050 message_len = strlen(exist);
2051 buffer_len = message_len + 4096;
2052 m = reallok(exist, buffer_len);
2059 /* flush the input if we have nowhere to store it */
2061 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2065 /* read in the lines of message text one by one */
2066 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2068 /* strip trailing newline type stuff */
2069 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2070 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2072 linelen = strlen(buf);
2074 /* augment the buffer if we have to */
2075 if ((message_len + linelen + 2) > buffer_len) {
2076 lprintf(9, "realloking\n");
2077 ptr = reallok(m, (buffer_len * 2) );
2078 if (ptr == NULL) { /* flush if can't allocate */
2079 while ( (client_gets(buf)>0) &&
2080 strcmp(buf, terminator)) ;;
2083 buffer_len = (buffer_len * 2);
2085 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2089 /* Add the new line to the buffer. NOTE: this loop must avoid
2090 * using functions like strcat() and strlen() because they
2091 * traverse the entire buffer upon every call, and doing that
2092 * for a multi-megabyte message slows it down beyond usability.
2094 strcpy(&m[message_len], buf);
2095 m[message_len + linelen] = '\n';
2096 m[message_len + linelen + 1] = 0;
2097 message_len = message_len + linelen + 1;
2099 /* if we've hit the max msg length, flush the rest */
2100 if (message_len >= maxlen) {
2101 while ( (client_gets(buf)>0)
2102 && strcmp(buf, terminator)) ;;
2113 * Build a binary message to be saved on disk.
2116 static struct CtdlMessage *make_message(
2117 struct usersupp *author, /* author's usersupp structure */
2118 char *recipient, /* NULL if it's not mail */
2119 char *room, /* room where it's going */
2120 int type, /* see MES_ types in header file */
2121 int format_type, /* variformat, plain text, MIME... */
2122 char *fake_name, /* who we're masquerading as */
2123 char *subject /* Subject (optional) */
2125 char dest_node[SIZ];
2127 struct CtdlMessage *msg;
2129 msg = mallok(sizeof(struct CtdlMessage));
2130 memset(msg, 0, sizeof(struct CtdlMessage));
2131 msg->cm_magic = CTDLMESSAGE_MAGIC;
2132 msg->cm_anon_type = type;
2133 msg->cm_format_type = format_type;
2135 /* Don't confuse the poor folks if it's not routed mail. */
2136 strcpy(dest_node, "");
2140 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2141 msg->cm_fields['P'] = strdoop(buf);
2143 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2144 msg->cm_fields['T'] = strdoop(buf);
2146 if (fake_name[0]) /* author */
2147 msg->cm_fields['A'] = strdoop(fake_name);
2149 msg->cm_fields['A'] = strdoop(author->fullname);
2151 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2152 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2155 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2158 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2159 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2161 if (recipient[0] != 0) {
2162 msg->cm_fields['R'] = strdoop(recipient);
2164 if (dest_node[0] != 0) {
2165 msg->cm_fields['D'] = strdoop(dest_node);
2168 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2169 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2172 if (subject != NULL) {
2174 if (strlen(subject) > 0) {
2175 msg->cm_fields['U'] = strdoop(subject);
2179 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2180 config.c_maxmsglen, NULL);
2187 * Check to see whether we have permission to post a message in the current
2188 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2189 * returns 0 on success.
2191 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2193 if (!(CC->logged_in)) {
2194 snprintf(errmsgbuf, n, "Not logged in.");
2195 return (ERROR + NOT_LOGGED_IN);
2198 if ((CC->usersupp.axlevel < 2)
2199 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2200 snprintf(errmsgbuf, n, "Need to be validated to enter "
2201 "(except in %s> to sysop)", MAILROOM);
2202 return (ERROR + HIGHER_ACCESS_REQUIRED);
2205 if ((CC->usersupp.axlevel < 4)
2206 && (CC->quickroom.QRflags & QR_NETWORK)) {
2207 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2208 return (ERROR + HIGHER_ACCESS_REQUIRED);
2211 if ((CC->usersupp.axlevel < 6)
2212 && (CC->quickroom.QRflags & QR_READONLY)) {
2213 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2214 return (ERROR + HIGHER_ACCESS_REQUIRED);
2217 strcpy(errmsgbuf, "Ok");
2223 * Validate recipients, count delivery types and errors, and handle aliasing
2224 * FIXME check for dupes!!!!!
2226 struct recptypes *validate_recipients(char *recipients) {
2227 struct recptypes *ret;
2228 char this_recp[SIZ];
2229 char this_recp_cooked[SIZ];
2235 struct usersupp tempUS;
2236 struct quickroom tempQR;
2239 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2240 if (ret == NULL) return(NULL);
2241 memset(ret, 0, sizeof(struct recptypes));
2244 ret->num_internet = 0;
2249 if (recipients == NULL) {
2252 else if (strlen(recipients) == 0) {
2256 /* Change all valid separator characters to commas */
2257 for (i=0; i<strlen(recipients); ++i) {
2258 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2259 recipients[i] = ',';
2264 num_recps = num_tokens(recipients, ',');
2267 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2268 extract_token(this_recp, recipients, i, ',');
2270 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2271 mailtype = alias(this_recp);
2272 mailtype = alias(this_recp);
2273 mailtype = alias(this_recp);
2274 for (j=0; j<=strlen(this_recp); ++j) {
2275 if (this_recp[j]=='_') {
2276 this_recp_cooked[j] = ' ';
2279 this_recp_cooked[j] = this_recp[j];
2285 if (!strcasecmp(this_recp, "sysop")) {
2287 strcpy(this_recp, AIDEROOM);
2288 if (strlen(ret->recp_room) > 0) {
2289 strcat(ret->recp_room, "|");
2291 strcat(ret->recp_room, this_recp);
2293 else if (getuser(&tempUS, this_recp) == 0) {
2295 strcpy(this_recp, tempUS.fullname);
2296 if (strlen(ret->recp_local) > 0) {
2297 strcat(ret->recp_local, "|");
2299 strcat(ret->recp_local, this_recp);
2301 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2303 strcpy(this_recp, tempUS.fullname);
2304 if (strlen(ret->recp_local) > 0) {
2305 strcat(ret->recp_local, "|");
2307 strcat(ret->recp_local, this_recp);
2309 else if ( (!strncasecmp(this_recp, "room_", 5))
2310 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2312 if (strlen(ret->recp_room) > 0) {
2313 strcat(ret->recp_room, "|");
2315 strcat(ret->recp_room, &this_recp_cooked[5]);
2323 ++ret->num_internet;
2324 if (strlen(ret->recp_internet) > 0) {
2325 strcat(ret->recp_internet, "|");
2327 strcat(ret->recp_internet, this_recp);
2331 if (strlen(ret->recp_ignet) > 0) {
2332 strcat(ret->recp_ignet, "|");
2334 strcat(ret->recp_ignet, this_recp);
2342 if (strlen(ret->errormsg) == 0) {
2343 snprintf(append, sizeof append,
2344 "Invalid recipient: %s",
2348 snprintf(append, sizeof append,
2351 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2352 strcat(ret->errormsg, append);
2356 if (strlen(ret->display_recp) == 0) {
2357 strcpy(append, this_recp);
2360 snprintf(append, sizeof append, ", %s",
2363 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2364 strcat(ret->display_recp, append);
2369 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2370 ret->num_room + ret->num_error) == 0) {
2372 strcpy(ret->errormsg, "No recipients specified.");
2375 lprintf(9, "validate_recipients()\n");
2376 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2377 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2378 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2379 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2380 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2388 * message entry - mode 0 (normal)
2390 void cmd_ent0(char *entargs)
2394 char masquerade_as[SIZ];
2396 int format_type = 0;
2397 char newusername[SIZ];
2398 struct CtdlMessage *msg;
2402 struct recptypes *valid = NULL;
2405 post = extract_int(entargs, 0);
2406 extract(recp, entargs, 1);
2407 anon_flag = extract_int(entargs, 2);
2408 format_type = extract_int(entargs, 3);
2409 extract(subject, entargs, 4);
2411 /* first check to make sure the request is valid. */
2413 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2415 cprintf("%d %s\n", err, errmsg);
2419 /* Check some other permission type things. */
2422 if (CC->usersupp.axlevel < 6) {
2423 cprintf("%d You don't have permission to masquerade.\n",
2424 ERROR + HIGHER_ACCESS_REQUIRED);
2427 extract(newusername, entargs, 4);
2428 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2429 safestrncpy(CC->fake_postname, newusername,
2430 sizeof(CC->fake_postname) );
2431 cprintf("%d ok\n", CIT_OK);
2434 CC->cs_flags |= CS_POSTING;
2436 /* In the Mail> room we have to behave a little differently --
2437 * make sure the user has specified at least one recipient. Then
2438 * validate the recipient(s).
2440 if ( (CC->quickroom.QRflags & QR_MAILBOX)
2441 && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2443 if (CC->usersupp.axlevel < 2) {
2444 strcpy(recp, "sysop");
2447 valid = validate_recipients(recp);
2448 if (valid->num_error > 0) {
2450 ERROR + NO_SUCH_USER, valid->errormsg);
2455 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2456 && (CC->usersupp.axlevel < 4) ) {
2457 cprintf("%d Higher access required for network mail.\n",
2458 ERROR + HIGHER_ACCESS_REQUIRED);
2463 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2464 && ((CC->usersupp.flags & US_INTERNET) == 0)
2465 && (!CC->internal_pgm)) {
2466 cprintf("%d You don't have access to Internet mail.\n",
2467 ERROR + HIGHER_ACCESS_REQUIRED);
2474 /* Is this a room which has anonymous-only or anonymous-option? */
2475 anonymous = MES_NORMAL;
2476 if (CC->quickroom.QRflags & QR_ANONONLY) {
2477 anonymous = MES_ANONONLY;
2479 if (CC->quickroom.QRflags & QR_ANONOPT) {
2480 if (anon_flag == 1) { /* only if the user requested it */
2481 anonymous = MES_ANONOPT;
2485 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2489 /* If we're only checking the validity of the request, return
2490 * success without creating the message.
2493 cprintf("%d %s\n", CIT_OK,
2494 ((valid != NULL) ? valid->display_recp : "") );
2499 /* Handle author masquerading */
2500 if (CC->fake_postname[0]) {
2501 strcpy(masquerade_as, CC->fake_postname);
2503 else if (CC->fake_username[0]) {
2504 strcpy(masquerade_as, CC->fake_username);
2507 strcpy(masquerade_as, "");
2510 /* Read in the message from the client. */
2511 cprintf("%d send message\n", SEND_LISTING);
2512 msg = make_message(&CC->usersupp, recp,
2513 CC->quickroom.QRname, anonymous, format_type,
2514 masquerade_as, subject);
2517 CtdlSubmitMsg(msg, valid, "");
2518 CtdlFreeMessage(msg);
2520 CC->fake_postname[0] = '\0';
2528 * API function to delete messages which match a set of criteria
2529 * (returns the actual number of messages deleted)
2531 int CtdlDeleteMessages(char *room_name, /* which room */
2532 long dmsgnum, /* or "0" for any */
2533 char *content_type /* or "" for any */
2537 struct quickroom qrbuf;
2538 struct cdbdata *cdbfr;
2539 long *msglist = NULL;
2540 long *dellist = NULL;
2543 int num_deleted = 0;
2545 struct MetaData smi;
2547 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2548 room_name, dmsgnum, content_type);
2550 /* get room record, obtaining a lock... */
2551 if (lgetroom(&qrbuf, room_name) != 0) {
2552 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2554 return (0); /* room not found */
2556 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2558 if (cdbfr != NULL) {
2559 msglist = mallok(cdbfr->len);
2560 dellist = mallok(cdbfr->len);
2561 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2562 num_msgs = cdbfr->len / sizeof(long);
2566 for (i = 0; i < num_msgs; ++i) {
2569 /* Set/clear a bit for each criterion */
2571 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2572 delete_this |= 0x01;
2574 if (strlen(content_type) == 0) {
2575 delete_this |= 0x02;
2577 GetMetaData(&smi, msglist[i]);
2578 if (!strcasecmp(smi.meta_content_type,
2580 delete_this |= 0x02;
2584 /* Delete message only if all bits are set */
2585 if (delete_this == 0x03) {
2586 dellist[num_deleted++] = msglist[i];
2591 num_msgs = sort_msglist(msglist, num_msgs);
2592 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2593 msglist, (num_msgs * sizeof(long)));
2595 qrbuf.QRhighest = msglist[num_msgs - 1];
2599 /* Go through the messages we pulled out of the index, and decrement
2600 * their reference counts by 1. If this is the only room the message
2601 * was in, the reference count will reach zero and the message will
2602 * automatically be deleted from the database. We do this in a
2603 * separate pass because there might be plug-in hooks getting called,
2604 * and we don't want that happening during an S_QUICKROOM critical
2607 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2608 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2609 AdjRefCount(dellist[i], -1);
2612 /* Now free the memory we used, and go away. */
2613 if (msglist != NULL) phree(msglist);
2614 if (dellist != NULL) phree(dellist);
2615 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2616 return (num_deleted);
2622 * Check whether the current user has permission to delete messages from
2623 * the current room (returns 1 for yes, 0 for no)
2625 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2626 getuser(&CC->usersupp, CC->curr_user);
2627 if ((CC->usersupp.axlevel < 6)
2628 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2629 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2630 && (!(CC->internal_pgm))) {
2639 * Delete message from current room
2641 void cmd_dele(char *delstr)
2646 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2647 cprintf("%d Higher access required.\n",
2648 ERROR + HIGHER_ACCESS_REQUIRED);
2651 delnum = extract_long(delstr, 0);
2653 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2656 cprintf("%d %d message%s deleted.\n", CIT_OK,
2657 num_deleted, ((num_deleted != 1) ? "s" : ""));
2659 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2665 * Back end API function for moves and deletes
2667 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2670 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2671 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2672 if (err != 0) return(err);
2680 * move or copy a message to another room
2682 void cmd_move(char *args)
2686 struct quickroom qtemp;
2690 num = extract_long(args, 0);
2691 extract(targ, args, 1);
2692 targ[ROOMNAMELEN - 1] = 0;
2693 is_copy = extract_int(args, 2);
2695 if (getroom(&qtemp, targ) != 0) {
2696 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2700 getuser(&CC->usersupp, CC->curr_user);
2701 /* Aides can move/copy */
2702 if ((CC->usersupp.axlevel < 6)
2703 /* Roomaides can move/copy */
2704 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2705 /* Permit move/copy to/from personal rooms */
2706 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2707 && (qtemp.QRflags & QR_MAILBOX)))
2708 /* Permit only copy from public to personal room */
2709 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2710 && (qtemp.QRflags & QR_MAILBOX)))) {
2711 cprintf("%d Higher access required.\n",
2712 ERROR + HIGHER_ACCESS_REQUIRED);
2716 err = CtdlCopyMsgToRoom(num, targ);
2718 cprintf("%d Cannot store message in %s: error %d\n",
2723 /* Now delete the message from the source room,
2724 * if this is a 'move' rather than a 'copy' operation.
2727 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2730 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2736 * GetMetaData() - Get the supplementary record for a message
2738 void GetMetaData(struct MetaData *smibuf, long msgnum)
2741 struct cdbdata *cdbsmi;
2744 memset(smibuf, 0, sizeof(struct MetaData));
2745 smibuf->meta_msgnum = msgnum;
2746 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2748 /* Use the negative of the message number for its supp record index */
2749 TheIndex = (0L - msgnum);
2751 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2752 if (cdbsmi == NULL) {
2753 return; /* record not found; go with defaults */
2755 memcpy(smibuf, cdbsmi->ptr,
2756 ((cdbsmi->len > sizeof(struct MetaData)) ?
2757 sizeof(struct MetaData) : cdbsmi->len));
2764 * PutMetaData() - (re)write supplementary record for a message
2766 void PutMetaData(struct MetaData *smibuf)
2770 /* Use the negative of the message number for the metadata db index */
2771 TheIndex = (0L - smibuf->meta_msgnum);
2773 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2774 smibuf->meta_msgnum, smibuf->meta_refcount);
2776 cdb_store(CDB_MSGMAIN,
2777 &TheIndex, sizeof(long),
2778 smibuf, sizeof(struct MetaData));
2783 * AdjRefCount - change the reference count for a message;
2784 * delete the message if it reaches zero
2786 void AdjRefCount(long msgnum, int incr)
2789 struct MetaData smi;
2792 /* This is a *tight* critical section; please keep it that way, as
2793 * it may get called while nested in other critical sections.
2794 * Complicating this any further will surely cause deadlock!
2796 begin_critical_section(S_SUPPMSGMAIN);
2797 GetMetaData(&smi, msgnum);
2798 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2799 msgnum, smi.meta_refcount);
2800 smi.meta_refcount += incr;
2802 end_critical_section(S_SUPPMSGMAIN);
2803 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2804 msgnum, smi.meta_refcount);
2806 /* If the reference count is now zero, delete the message
2807 * (and its supplementary record as well).
2809 if (smi.meta_refcount == 0) {
2810 lprintf(9, "Deleting message <%ld>\n", msgnum);
2812 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2814 /* We have to delete the metadata record too! */
2815 delnum = (0L - msgnum);
2816 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2821 * Write a generic object to this room
2823 * Note: this could be much more efficient. Right now we use two temporary
2824 * files, and still pull the message into memory as with all others.
2826 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2827 char *content_type, /* MIME type of this object */
2828 char *tempfilename, /* Where to fetch it from */
2829 struct usersupp *is_mailbox, /* Mailbox room? */
2830 int is_binary, /* Is encoding necessary? */
2831 int is_unique, /* Del others of this type? */
2832 unsigned int flags /* Internal save flags */
2837 char filename[PATH_MAX];
2840 struct quickroom qrbuf;
2841 char roomname[ROOMNAMELEN];
2842 struct CtdlMessage *msg;
2845 if (is_mailbox != NULL)
2846 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
2848 safestrncpy(roomname, req_room, sizeof(roomname));
2849 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2851 strcpy(filename, tmpnam(NULL));
2852 fp = fopen(filename, "w");
2856 tempfp = fopen(tempfilename, "r");
2857 if (tempfp == NULL) {
2863 fprintf(fp, "Content-type: %s\n", content_type);
2864 lprintf(9, "Content-type: %s\n", content_type);
2866 if (is_binary == 0) {
2867 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2868 while (ch = getc(tempfp), ch > 0)
2874 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2877 snprintf(cmdbuf, sizeof cmdbuf, "./base64 -e <%s >>%s",
2878 tempfilename, filename);
2882 lprintf(9, "Allocating\n");
2883 msg = mallok(sizeof(struct CtdlMessage));
2884 memset(msg, 0, sizeof(struct CtdlMessage));
2885 msg->cm_magic = CTDLMESSAGE_MAGIC;
2886 msg->cm_anon_type = MES_NORMAL;
2887 msg->cm_format_type = 4;
2888 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2889 msg->cm_fields['O'] = strdoop(req_room);
2890 msg->cm_fields['N'] = strdoop(config.c_nodename);
2891 msg->cm_fields['H'] = strdoop(config.c_humannode);
2892 msg->cm_flags = flags;
2894 lprintf(9, "Loading\n");
2895 fp = fopen(filename, "rb");
2896 fseek(fp, 0L, SEEK_END);
2899 msg->cm_fields['M'] = mallok(len);
2900 fread(msg->cm_fields['M'], len, 1, fp);
2904 /* Create the requested room if we have to. */
2905 if (getroom(&qrbuf, roomname) != 0) {
2906 create_room(roomname,
2907 ( (is_mailbox != NULL) ? 5 : 3 ),
2910 /* If the caller specified this object as unique, delete all
2911 * other objects of this type that are currently in the room.
2914 lprintf(9, "Deleted %d other msgs of this type\n",
2915 CtdlDeleteMessages(roomname, 0L, content_type));
2917 /* Now write the data */
2918 CtdlSubmitMsg(msg, NULL, roomname);
2919 CtdlFreeMessage(msg);
2927 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2928 config_msgnum = msgnum;
2932 char *CtdlGetSysConfig(char *sysconfname) {
2933 char hold_rm[ROOMNAMELEN];
2936 struct CtdlMessage *msg;
2939 strcpy(hold_rm, CC->quickroom.QRname);
2940 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2941 getroom(&CC->quickroom, hold_rm);
2946 /* We want the last (and probably only) config in this room */
2947 begin_critical_section(S_CONFIG);
2948 config_msgnum = (-1L);
2949 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2950 CtdlGetSysConfigBackend, NULL);
2951 msgnum = config_msgnum;
2952 end_critical_section(S_CONFIG);
2958 msg = CtdlFetchMessage(msgnum);
2960 conf = strdoop(msg->cm_fields['M']);
2961 CtdlFreeMessage(msg);
2968 getroom(&CC->quickroom, hold_rm);
2970 if (conf != NULL) do {
2971 extract_token(buf, conf, 0, '\n');
2972 strcpy(conf, &conf[strlen(buf)+1]);
2973 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2978 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2979 char temp[PATH_MAX];
2982 strcpy(temp, tmpnam(NULL));
2984 fp = fopen(temp, "w");
2985 if (fp == NULL) return;
2986 fprintf(fp, "%s", sysconfdata);
2989 /* this handy API function does all the work for us */
2990 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);