4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
36 #include "serv_extensions.h"
40 #include "sysdep_decls.h"
41 #include "citserver.h"
48 #include "mime_parser.h"
51 #include "internet_addressing.h"
52 #include "serv_fulltext.h"
54 #include "euidindex.h"
55 #include "journaling.h"
56 #include "citadel_dirs.h"
57 #include "serv_network.h"
60 # include "serv_sieve.h"
61 #endif /* HAVE_LIBSIEVE */
64 struct addresses_to_be_filed *atbf = NULL;
66 /* This temp file holds the queue of operations for AdjRefCount() */
67 static FILE *arcfp = NULL;
70 * This really belongs in serv_network.c, but I don't know how to export
71 * symbols between modules.
73 struct FilterList *filterlist = NULL;
77 * These are the four-character field headers we use when outputting
78 * messages in Citadel format (as opposed to RFC822 format).
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
85 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
86 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
87 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
88 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
116 * This function is self explanatory.
117 * (What can I say, I'm in a weird mood today...)
119 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
123 for (i = 0; i < strlen(name); ++i) {
124 if (name[i] == '@') {
125 while (isspace(name[i - 1]) && i > 0) {
126 strcpy(&name[i - 1], &name[i]);
129 while (isspace(name[i + 1])) {
130 strcpy(&name[i + 1], &name[i + 2]);
138 * Aliasing for network mail.
139 * (Error messages have been commented out, because this is a server.)
141 int alias(char *name)
142 { /* process alias and routing info for mail */
145 char aaa[SIZ], bbb[SIZ];
146 char *ignetcfg = NULL;
147 char *ignetmap = NULL;
153 char original_name[256];
154 safestrncpy(original_name, name, sizeof original_name);
157 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
158 stripallbut(name, '<', '>');
160 fp = fopen(file_mail_aliases, "r");
162 fp = fopen("/dev/null", "r");
169 while (fgets(aaa, sizeof aaa, fp) != NULL) {
170 while (isspace(name[0]))
171 strcpy(name, &name[1]);
172 aaa[strlen(aaa) - 1] = 0;
174 for (a = 0; a < strlen(aaa); ++a) {
176 strcpy(bbb, &aaa[a + 1]);
180 if (!strcasecmp(name, aaa))
185 /* Hit the Global Address Book */
186 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
190 if (strcasecmp(original_name, name)) {
191 lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
194 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
195 for (a=0; a<strlen(name); ++a) {
196 if (name[a] == '@') {
197 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
199 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
204 /* determine local or remote type, see citadel.h */
205 at = haschar(name, '@');
206 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
207 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
208 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
210 /* figure out the delivery mode */
211 extract_token(node, name, 1, '@', sizeof node);
213 /* If there are one or more dots in the nodename, we assume that it
214 * is an FQDN and will attempt SMTP delivery to the Internet.
216 if (haschar(node, '.') > 0) {
217 return(MES_INTERNET);
220 /* Otherwise we look in the IGnet maps for a valid Citadel node.
221 * Try directly-connected nodes first...
223 ignetcfg = CtdlGetSysConfig(IGNETCFG);
224 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
225 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
226 extract_token(testnode, buf, 0, '|', sizeof testnode);
227 if (!strcasecmp(node, testnode)) {
235 * Then try nodes that are two or more hops away.
237 ignetmap = CtdlGetSysConfig(IGNETMAP);
238 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
239 extract_token(buf, ignetmap, i, '\n', sizeof buf);
240 extract_token(testnode, buf, 0, '|', sizeof testnode);
241 if (!strcasecmp(node, testnode)) {
248 /* If we get to this point it's an invalid node name */
254 * Back end for the MSGS command: output message number only.
256 void simple_listing(long msgnum, void *userdata)
258 cprintf("%ld\n", msgnum);
264 * Back end for the MSGS command: output header summary.
266 void headers_listing(long msgnum, void *userdata)
268 struct CtdlMessage *msg;
270 msg = CtdlFetchMessage(msgnum, 0);
272 cprintf("%ld|0|||||\n", msgnum);
276 cprintf("%ld|%s|%s|%s|%s|%s|\n",
278 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
279 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
280 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
281 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
282 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
284 CtdlFreeMessage(msg);
289 /* Determine if a given message matches the fields in a message template.
290 * Return 0 for a successful match.
292 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
295 /* If there aren't any fields in the template, all messages will
298 if (template == NULL) return(0);
300 /* Null messages are bogus. */
301 if (msg == NULL) return(1);
303 for (i='A'; i<='Z'; ++i) {
304 if (template->cm_fields[i] != NULL) {
305 if (msg->cm_fields[i] == NULL) {
308 if (strcasecmp(msg->cm_fields[i],
309 template->cm_fields[i])) return 1;
313 /* All compares succeeded: we have a match! */
320 * Retrieve the "seen" message list for the current room.
322 void CtdlGetSeen(char *buf, int which_set) {
325 /* Learn about the user and room in question */
326 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
328 if (which_set == ctdlsetseen_seen)
329 safestrncpy(buf, vbuf.v_seen, SIZ);
330 if (which_set == ctdlsetseen_answered)
331 safestrncpy(buf, vbuf.v_answered, SIZ);
337 * Manipulate the "seen msgs" string (or other message set strings)
339 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
340 int target_setting, int which_set,
341 struct ctdluser *which_user, struct ctdlroom *which_room) {
342 struct cdbdata *cdbfr;
354 char *is_set; /* actually an array of booleans */
357 char setstr[SIZ], lostr[SIZ], histr[SIZ];
360 /* Don't bother doing *anything* if we were passed a list of zero messages */
361 if (num_target_msgnums < 1) {
365 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
366 num_target_msgnums, target_msgnums[0],
367 target_setting, which_set);
369 /* Learn about the user and room in question */
370 CtdlGetRelationship(&vbuf,
371 ((which_user != NULL) ? which_user : &CC->user),
372 ((which_room != NULL) ? which_room : &CC->room)
375 /* Load the message list */
376 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
378 msglist = (long *) cdbfr->ptr;
379 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
380 num_msgs = cdbfr->len / sizeof(long);
383 return; /* No messages at all? No further action. */
386 is_set = malloc(num_msgs * sizeof(char));
387 memset(is_set, 0, (num_msgs * sizeof(char)) );
389 /* Decide which message set we're manipulating */
391 case ctdlsetseen_seen:
392 safestrncpy(vset, vbuf.v_seen, sizeof vset);
394 case ctdlsetseen_answered:
395 safestrncpy(vset, vbuf.v_answered, sizeof vset);
399 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
401 /* Translate the existing sequence set into an array of booleans */
402 num_sets = num_tokens(vset, ',');
403 for (s=0; s<num_sets; ++s) {
404 extract_token(setstr, vset, s, ',', sizeof setstr);
406 extract_token(lostr, setstr, 0, ':', sizeof lostr);
407 if (num_tokens(setstr, ':') >= 2) {
408 extract_token(histr, setstr, 1, ':', sizeof histr);
409 if (!strcmp(histr, "*")) {
410 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
414 strcpy(histr, lostr);
419 for (i = 0; i < num_msgs; ++i) {
420 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
426 /* Now translate the array of booleans back into a sequence set */
431 for (i=0; i<num_msgs; ++i) {
433 is_seen = is_set[i]; /* Default to existing setting */
435 for (k=0; k<num_target_msgnums; ++k) {
436 if (msglist[i] == target_msgnums[k]) {
437 is_seen = target_setting;
442 if (lo < 0L) lo = msglist[i];
446 if ( ((is_seen == 0) && (was_seen == 1))
447 || ((is_seen == 1) && (i == num_msgs-1)) ) {
449 /* begin trim-o-matic code */
452 while ( (strlen(vset) + 20) > sizeof vset) {
453 remove_token(vset, 0, ',');
455 if (j--) break; /* loop no more than 9 times */
457 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
461 snprintf(lostr, sizeof lostr,
462 "1:%ld,%s", t, vset);
463 safestrncpy(vset, lostr, sizeof vset);
465 /* end trim-o-matic code */
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
477 snprintf(&vset[tmp], (sizeof vset) - tmp,
486 /* Decide which message set we're manipulating */
488 case ctdlsetseen_seen:
489 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
491 case ctdlsetseen_answered:
492 safestrncpy(vbuf.v_answered, vset,
493 sizeof vbuf.v_answered);
498 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
500 CtdlSetRelationship(&vbuf,
501 ((which_user != NULL) ? which_user : &CC->user),
502 ((which_room != NULL) ? which_room : &CC->room)
508 * API function to perform an operation for each qualifying message in the
509 * current room. (Returns the number of messages processed.)
511 int CtdlForEachMessage(int mode, long ref, char *search_string,
513 struct CtdlMessage *compare,
514 void (*CallBack) (long, void *),
520 struct cdbdata *cdbfr;
521 long *msglist = NULL;
523 int num_processed = 0;
526 struct CtdlMessage *msg = NULL;
529 int printed_lastold = 0;
530 int num_search_msgs = 0;
531 long *search_msgs = NULL;
535 if (content_type) if (strlen(content_type) > 0) {
536 regcomp(&re, content_type, 0);
539 /* Learn about the user and room in question */
540 getuser(&CC->user, CC->curr_user);
541 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
543 /* Load the message list */
544 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
546 msglist = (long *) cdbfr->ptr;
547 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
548 num_msgs = cdbfr->len / sizeof(long);
551 return 0; /* No messages at all? No further action. */
556 * Now begin the traversal.
558 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
560 /* If the caller is looking for a specific MIME type, filter
561 * out all messages which are not of the type requested.
563 if (content_type != NULL) if (strlen(content_type) > 0) {
565 /* This call to GetMetaData() sits inside this loop
566 * so that we only do the extra database read per msg
567 * if we need to. Doing the extra read all the time
568 * really kills the server. If we ever need to use
569 * metadata for another search criterion, we need to
570 * move the read somewhere else -- but still be smart
571 * enough to only do the read if the caller has
572 * specified something that will need it.
574 GetMetaData(&smi, msglist[a]);
576 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
577 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
583 num_msgs = sort_msglist(msglist, num_msgs);
585 /* If a template was supplied, filter out the messages which
586 * don't match. (This could induce some delays!)
589 if (compare != NULL) {
590 for (a = 0; a < num_msgs; ++a) {
591 msg = CtdlFetchMessage(msglist[a], 1);
593 if (CtdlMsgCmp(msg, compare)) {
596 CtdlFreeMessage(msg);
602 /* If a search string was specified, get a message list from
603 * the full text index and remove messages which aren't on both
607 * Since the lists are sorted and strictly ascending, and the
608 * output list is guaranteed to be shorter than or equal to the
609 * input list, we overwrite the bottom of the input list. This
610 * eliminates the need to memmove big chunks of the list over and
613 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
614 ft_search(&num_search_msgs, &search_msgs, search_string);
615 if (num_search_msgs > 0) {
619 orig_num_msgs = num_msgs;
621 for (i=0; i<orig_num_msgs; ++i) {
622 for (j=0; j<num_search_msgs; ++j) {
623 if (msglist[i] == search_msgs[j]) {
624 msglist[num_msgs++] = msglist[i];
630 num_msgs = 0; /* No messages qualify */
632 if (search_msgs != NULL) free(search_msgs);
634 /* Now that we've purged messages which don't contain the search
635 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
642 * Now iterate through the message list, according to the
643 * criteria supplied by the caller.
646 for (a = 0; a < num_msgs; ++a) {
647 thismsg = msglist[a];
648 if (mode == MSGS_ALL) {
652 is_seen = is_msg_in_sequence_set(
653 vbuf.v_seen, thismsg);
654 if (is_seen) lastold = thismsg;
660 || ((mode == MSGS_OLD) && (is_seen))
661 || ((mode == MSGS_NEW) && (!is_seen))
662 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
663 || ((mode == MSGS_FIRST) && (a < ref))
664 || ((mode == MSGS_GT) && (thismsg > ref))
665 || ((mode == MSGS_EQ) && (thismsg == ref))
668 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
670 CallBack(lastold, userdata);
674 if (CallBack) CallBack(thismsg, userdata);
678 free(msglist); /* Clean up */
679 return num_processed;
685 * cmd_msgs() - get list of message #'s in this room
686 * implements the MSGS server command using CtdlForEachMessage()
688 void cmd_msgs(char *cmdbuf)
697 int with_template = 0;
698 struct CtdlMessage *template = NULL;
699 int with_headers = 0;
700 char search_string[1024];
702 extract_token(which, cmdbuf, 0, '|', sizeof which);
703 cm_ref = extract_int(cmdbuf, 1);
704 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
705 with_template = extract_int(cmdbuf, 2);
706 with_headers = extract_int(cmdbuf, 3);
709 if (!strncasecmp(which, "OLD", 3))
711 else if (!strncasecmp(which, "NEW", 3))
713 else if (!strncasecmp(which, "FIRST", 5))
715 else if (!strncasecmp(which, "LAST", 4))
717 else if (!strncasecmp(which, "GT", 2))
719 else if (!strncasecmp(which, "SEARCH", 6))
724 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
725 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
729 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
730 cprintf("%d Full text index is not enabled on this server.\n",
731 ERROR + CMD_NOT_SUPPORTED);
737 cprintf("%d Send template then receive message list\n",
739 template = (struct CtdlMessage *)
740 malloc(sizeof(struct CtdlMessage));
741 memset(template, 0, sizeof(struct CtdlMessage));
742 template->cm_magic = CTDLMESSAGE_MAGIC;
743 template->cm_anon_type = MES_NORMAL;
745 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
746 extract_token(tfield, buf, 0, '|', sizeof tfield);
747 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
748 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
749 if (!strcasecmp(tfield, msgkeys[i])) {
750 template->cm_fields[i] =
758 cprintf("%d \n", LISTING_FOLLOWS);
761 CtdlForEachMessage(mode,
762 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
763 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
766 (with_headers ? headers_listing : simple_listing),
769 if (template != NULL) CtdlFreeMessage(template);
777 * help_subst() - support routine for help file viewer
779 void help_subst(char *strbuf, char *source, char *dest)
784 while (p = pattern2(strbuf, source), (p >= 0)) {
785 strcpy(workbuf, &strbuf[p + strlen(source)]);
786 strcpy(&strbuf[p], dest);
787 strcat(strbuf, workbuf);
792 void do_help_subst(char *buffer)
796 help_subst(buffer, "^nodename", config.c_nodename);
797 help_subst(buffer, "^humannode", config.c_humannode);
798 help_subst(buffer, "^fqdn", config.c_fqdn);
799 help_subst(buffer, "^username", CC->user.fullname);
800 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
801 help_subst(buffer, "^usernum", buf2);
802 help_subst(buffer, "^sysadm", config.c_sysadm);
803 help_subst(buffer, "^variantname", CITADEL);
804 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
805 help_subst(buffer, "^maxsessions", buf2);
806 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
812 * memfmout() - Citadel text formatter and paginator.
813 * Although the original purpose of this routine was to format
814 * text to the reader's screen width, all we're really using it
815 * for here is to format text out to 80 columns before sending it
816 * to the client. The client software may reformat it again.
819 char *mptr, /* where are we going to get our text from? */
820 char subst, /* nonzero if we should do substitutions */
821 char *nl) /* string to terminate lines with */
829 static int width = 80;
834 c = 1; /* c is the current pos */
838 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
840 buffer[strlen(buffer) + 1] = 0;
841 buffer[strlen(buffer)] = ch;
844 if (buffer[0] == '^')
845 do_help_subst(buffer);
847 buffer[strlen(buffer) + 1] = 0;
849 strcpy(buffer, &buffer[1]);
857 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
860 if (((old == 13) || (old == 10)) && (isspace(real))) {
865 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
866 cprintf("%s%s", nl, aaa);
875 if ((strlen(aaa) + c) > (width - 5)) {
884 if ((ch == 13) || (ch == 10)) {
885 cprintf("%s%s", aaa, nl);
892 cprintf("%s%s", aaa, nl);
898 * Callback function for mime parser that simply lists the part
900 void list_this_part(char *name, char *filename, char *partnum, char *disp,
901 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
906 ma = (struct ma_info *)cbuserdata;
907 if (ma->is_ma == 0) {
908 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
909 name, filename, partnum, disp, cbtype, (long)length);
914 * Callback function for multipart prefix
916 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
917 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
922 ma = (struct ma_info *)cbuserdata;
923 if (!strcasecmp(cbtype, "multipart/alternative")) {
927 if (ma->is_ma == 0) {
928 cprintf("pref=%s|%s\n", partnum, cbtype);
933 * Callback function for multipart sufffix
935 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
936 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
941 ma = (struct ma_info *)cbuserdata;
942 if (ma->is_ma == 0) {
943 cprintf("suff=%s|%s\n", partnum, cbtype);
945 if (!strcasecmp(cbtype, "multipart/alternative")) {
952 * Callback function for mime parser that opens a section for downloading
954 void mime_download(char *name, char *filename, char *partnum, char *disp,
955 void *content, char *cbtype, char *cbcharset, size_t length,
956 char *encoding, void *cbuserdata)
959 /* Silently go away if there's already a download open... */
960 if (CC->download_fp != NULL)
963 /* ...or if this is not the desired section */
964 if (strcasecmp(CC->download_desired_section, partnum))
967 CC->download_fp = tmpfile();
968 if (CC->download_fp == NULL)
971 fwrite(content, length, 1, CC->download_fp);
972 fflush(CC->download_fp);
973 rewind(CC->download_fp);
975 OpenCmdResult(filename, cbtype);
981 * Callback function for mime parser that outputs a section all at once
983 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
984 void *content, char *cbtype, char *cbcharset, size_t length,
985 char *encoding, void *cbuserdata)
987 int *found_it = (int *)cbuserdata;
989 /* ...or if this is not the desired section */
990 if (strcasecmp(CC->download_desired_section, partnum))
995 cprintf("%d %d\n", BINARY_FOLLOWS, length);
996 client_write(content, length);
1002 * Load a message from disk into memory.
1003 * This is used by CtdlOutputMsg() and other fetch functions.
1005 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1006 * using the CtdlMessageFree() function.
1008 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1010 struct cdbdata *dmsgtext;
1011 struct CtdlMessage *ret = NULL;
1015 cit_uint8_t field_header;
1017 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1019 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1020 if (dmsgtext == NULL) {
1023 mptr = dmsgtext->ptr;
1024 upper_bound = mptr + dmsgtext->len;
1026 /* Parse the three bytes that begin EVERY message on disk.
1027 * The first is always 0xFF, the on-disk magic number.
1028 * The second is the anonymous/public type byte.
1029 * The third is the format type byte (vari, fixed, or MIME).
1034 "Message %ld appears to be corrupted.\n",
1039 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1040 memset(ret, 0, sizeof(struct CtdlMessage));
1042 ret->cm_magic = CTDLMESSAGE_MAGIC;
1043 ret->cm_anon_type = *mptr++; /* Anon type byte */
1044 ret->cm_format_type = *mptr++; /* Format type byte */
1047 * The rest is zero or more arbitrary fields. Load them in.
1048 * We're done when we encounter either a zero-length field or
1049 * have just processed the 'M' (message text) field.
1052 if (mptr >= upper_bound) {
1055 field_header = *mptr++;
1056 ret->cm_fields[field_header] = strdup(mptr);
1058 while (*mptr++ != 0); /* advance to next field */
1060 } while ((mptr < upper_bound) && (field_header != 'M'));
1064 /* Always make sure there's something in the msg text field. If
1065 * it's NULL, the message text is most likely stored separately,
1066 * so go ahead and fetch that. Failing that, just set a dummy
1067 * body so other code doesn't barf.
1069 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1070 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1071 if (dmsgtext != NULL) {
1072 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1076 if (ret->cm_fields['M'] == NULL) {
1077 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1080 /* Perform "before read" hooks (aborting if any return nonzero) */
1081 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1082 CtdlFreeMessage(ret);
1091 * Returns 1 if the supplied pointer points to a valid Citadel message.
1092 * If the pointer is NULL or the magic number check fails, returns 0.
1094 int is_valid_message(struct CtdlMessage *msg) {
1097 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1098 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1106 * 'Destructor' for struct CtdlMessage
1108 void CtdlFreeMessage(struct CtdlMessage *msg)
1112 if (is_valid_message(msg) == 0)
1114 if (msg != NULL) free (msg);
1118 for (i = 0; i < 256; ++i)
1119 if (msg->cm_fields[i] != NULL) {
1120 free(msg->cm_fields[i]);
1123 msg->cm_magic = 0; /* just in case */
1129 * Pre callback function for multipart/alternative
1131 * NOTE: this differs from the standard behavior for a reason. Normally when
1132 * displaying multipart/alternative you want to show the _last_ usable
1133 * format in the message. Here we show the _first_ one, because it's
1134 * usually text/plain. Since this set of functions is designed for text
1135 * output to non-MIME-aware clients, this is the desired behavior.
1138 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1139 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1144 ma = (struct ma_info *)cbuserdata;
1145 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1146 if (!strcasecmp(cbtype, "multipart/alternative")) {
1150 if (!strcasecmp(cbtype, "message/rfc822")) {
1156 * Post callback function for multipart/alternative
1158 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1159 void *content, char *cbtype, char *cbcharset, size_t length,
1160 char *encoding, void *cbuserdata)
1164 ma = (struct ma_info *)cbuserdata;
1165 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1166 if (!strcasecmp(cbtype, "multipart/alternative")) {
1170 if (!strcasecmp(cbtype, "message/rfc822")) {
1176 * Inline callback function for mime parser that wants to display text
1178 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1179 void *content, char *cbtype, char *cbcharset, size_t length,
1180 char *encoding, void *cbuserdata)
1187 ma = (struct ma_info *)cbuserdata;
1190 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1191 partnum, filename, cbtype, (long)length);
1194 * If we're in the middle of a multipart/alternative scope and
1195 * we've already printed another section, skip this one.
1197 if ( (ma->is_ma) && (ma->did_print) ) {
1198 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1204 if ( (!strcasecmp(cbtype, "text/plain"))
1205 || (strlen(cbtype)==0) ) {
1208 client_write(wptr, length);
1209 if (wptr[length-1] != '\n') {
1216 if (!strcasecmp(cbtype, "text/html")) {
1217 ptr = html_to_ascii(content, length, 80, 0);
1219 client_write(ptr, wlen);
1220 if (ptr[wlen-1] != '\n') {
1227 if (ma->use_fo_hooks) {
1228 if (PerformFixedOutputHooks(cbtype, content, length)) {
1229 /* above function returns nonzero if it handled the part */
1234 if (strncasecmp(cbtype, "multipart/", 10)) {
1235 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1236 partnum, filename, cbtype, (long)length);
1242 * The client is elegant and sophisticated and wants to be choosy about
1243 * MIME content types, so figure out which multipart/alternative part
1244 * we're going to send.
1246 * We use a system of weights. When we find a part that matches one of the
1247 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1248 * and then set ma->chosen_pref to that MIME type's position in our preference
1249 * list. If we then hit another match, we only replace the first match if
1250 * the preference value is lower.
1252 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1253 void *content, char *cbtype, char *cbcharset, size_t length,
1254 char *encoding, void *cbuserdata)
1260 ma = (struct ma_info *)cbuserdata;
1262 if (ma->is_ma > 0) {
1263 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1264 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1265 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1266 if (i < ma->chosen_pref) {
1267 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1268 ma->chosen_pref = i;
1276 * Now that we've chosen our preferred part, output it.
1278 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1279 void *content, char *cbtype, char *cbcharset, size_t length,
1280 char *encoding, void *cbuserdata)
1284 int add_newline = 0;
1288 ma = (struct ma_info *)cbuserdata;
1290 /* This is not the MIME part you're looking for... */
1291 if (strcasecmp(partnum, ma->chosen_part)) return;
1293 /* If the content-type of this part is in our preferred formats
1294 * list, we can simply output it verbatim.
1296 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1297 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1298 if (!strcasecmp(buf, cbtype)) {
1299 /* Yeah! Go! W00t!! */
1301 text_content = (char *)content;
1302 if (text_content[length-1] != '\n') {
1305 cprintf("Content-type: %s", cbtype);
1306 if (strlen(cbcharset) > 0) {
1307 cprintf("; charset=%s", cbcharset);
1309 cprintf("\nContent-length: %d\n",
1310 (int)(length + add_newline) );
1311 if (strlen(encoding) > 0) {
1312 cprintf("Content-transfer-encoding: %s\n", encoding);
1315 cprintf("Content-transfer-encoding: 7bit\n");
1317 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1319 client_write(content, length);
1320 if (add_newline) cprintf("\n");
1325 /* No translations required or possible: output as text/plain */
1326 cprintf("Content-type: text/plain\n\n");
1327 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1328 length, encoding, cbuserdata);
1333 char desired_section[64];
1340 * Callback function for
1342 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1343 void *content, char *cbtype, char *cbcharset, size_t length,
1344 char *encoding, void *cbuserdata)
1346 struct encapmsg *encap;
1348 encap = (struct encapmsg *)cbuserdata;
1350 /* Only proceed if this is the desired section... */
1351 if (!strcasecmp(encap->desired_section, partnum)) {
1352 encap->msglen = length;
1353 encap->msg = malloc(length + 2);
1354 memcpy(encap->msg, content, length);
1364 * Get a message off disk. (returns om_* values found in msgbase.h)
1367 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1368 int mode, /* how would you like that message? */
1369 int headers_only, /* eschew the message body? */
1370 int do_proto, /* do Citadel protocol responses? */
1371 int crlf, /* Use CRLF newlines instead of LF? */
1372 char *section /* NULL or a message/rfc822 section */
1374 struct CtdlMessage *TheMessage = NULL;
1375 int retcode = om_no_such_msg;
1376 struct encapmsg encap;
1378 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1380 (section ? section : "<>")
1383 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1384 if (do_proto) cprintf("%d Not logged in.\n",
1385 ERROR + NOT_LOGGED_IN);
1386 return(om_not_logged_in);
1389 /* FIXME: check message id against msglist for this room */
1392 * Fetch the message from disk. If we're in any sort of headers
1393 * only mode, request that we don't even bother loading the body
1396 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1397 TheMessage = CtdlFetchMessage(msg_num, 0);
1400 TheMessage = CtdlFetchMessage(msg_num, 1);
1403 if (TheMessage == NULL) {
1404 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1405 ERROR + MESSAGE_NOT_FOUND, msg_num);
1406 return(om_no_such_msg);
1409 /* Here is the weird form of this command, to process only an
1410 * encapsulated message/rfc822 section.
1412 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1413 memset(&encap, 0, sizeof encap);
1414 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1415 mime_parser(TheMessage->cm_fields['M'],
1417 *extract_encapsulated_message,
1418 NULL, NULL, (void *)&encap, 0
1420 CtdlFreeMessage(TheMessage);
1424 encap.msg[encap.msglen] = 0;
1425 TheMessage = convert_internet_message(encap.msg);
1426 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1428 /* Now we let it fall through to the bottom of this
1429 * function, because TheMessage now contains the
1430 * encapsulated message instead of the top-level
1431 * message. Isn't that neat?
1436 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1437 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1438 retcode = om_no_such_msg;
1443 /* Ok, output the message now */
1444 retcode = CtdlOutputPreLoadedMsg(
1446 headers_only, do_proto, crlf);
1447 CtdlFreeMessage(TheMessage);
1454 * Get a message off disk. (returns om_* values found in msgbase.h)
1457 int CtdlOutputPreLoadedMsg(
1458 struct CtdlMessage *TheMessage,
1459 int mode, /* how would you like that message? */
1460 int headers_only, /* eschew the message body? */
1461 int do_proto, /* do Citadel protocol responses? */
1462 int crlf /* Use CRLF newlines instead of LF? */
1468 char display_name[256];
1470 char *nl; /* newline string */
1472 int subject_found = 0;
1475 /* Buffers needed for RFC822 translation. These are all filled
1476 * using functions that are bounds-checked, and therefore we can
1477 * make them substantially smaller than SIZ.
1485 char datestamp[100];
1487 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1488 ((TheMessage == NULL) ? "NULL" : "not null"),
1489 mode, headers_only, do_proto, crlf);
1491 strcpy(mid, "unknown");
1492 nl = (crlf ? "\r\n" : "\n");
1494 if (!is_valid_message(TheMessage)) {
1496 "ERROR: invalid preloaded message for output\n");
1497 return(om_no_such_msg);
1500 /* Are we downloading a MIME component? */
1501 if (mode == MT_DOWNLOAD) {
1502 if (TheMessage->cm_format_type != FMT_RFC822) {
1504 cprintf("%d This is not a MIME message.\n",
1505 ERROR + ILLEGAL_VALUE);
1506 } else if (CC->download_fp != NULL) {
1507 if (do_proto) cprintf(
1508 "%d You already have a download open.\n",
1509 ERROR + RESOURCE_BUSY);
1511 /* Parse the message text component */
1512 mptr = TheMessage->cm_fields['M'];
1513 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1514 /* If there's no file open by this time, the requested
1515 * section wasn't found, so print an error
1517 if (CC->download_fp == NULL) {
1518 if (do_proto) cprintf(
1519 "%d Section %s not found.\n",
1520 ERROR + FILE_NOT_FOUND,
1521 CC->download_desired_section);
1524 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1527 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1528 * in a single server operation instead of opening a download file.
1530 if (mode == MT_SPEW_SECTION) {
1531 if (TheMessage->cm_format_type != FMT_RFC822) {
1533 cprintf("%d This is not a MIME message.\n",
1534 ERROR + ILLEGAL_VALUE);
1536 /* Parse the message text component */
1539 mptr = TheMessage->cm_fields['M'];
1540 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1541 /* If section wasn't found, print an error
1544 if (do_proto) cprintf(
1545 "%d Section %s not found.\n",
1546 ERROR + FILE_NOT_FOUND,
1547 CC->download_desired_section);
1550 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1553 /* now for the user-mode message reading loops */
1554 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1556 /* Does the caller want to skip the headers? */
1557 if (headers_only == HEADERS_NONE) goto START_TEXT;
1559 /* Tell the client which format type we're using. */
1560 if ( (mode == MT_CITADEL) && (do_proto) ) {
1561 cprintf("type=%d\n", TheMessage->cm_format_type);
1564 /* nhdr=yes means that we're only displaying headers, no body */
1565 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1566 && (mode == MT_CITADEL)
1569 cprintf("nhdr=yes\n");
1572 /* begin header processing loop for Citadel message format */
1574 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1576 safestrncpy(display_name, "<unknown>", sizeof display_name);
1577 if (TheMessage->cm_fields['A']) {
1578 strcpy(buf, TheMessage->cm_fields['A']);
1579 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1580 safestrncpy(display_name, "****", sizeof display_name);
1582 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1583 safestrncpy(display_name, "anonymous", sizeof display_name);
1586 safestrncpy(display_name, buf, sizeof display_name);
1588 if ((is_room_aide())
1589 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1590 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1591 size_t tmp = strlen(display_name);
1592 snprintf(&display_name[tmp],
1593 sizeof display_name - tmp,
1598 /* Don't show Internet address for users on the
1599 * local Citadel network.
1602 if (TheMessage->cm_fields['N'] != NULL)
1603 if (strlen(TheMessage->cm_fields['N']) > 0)
1604 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1608 /* Now spew the header fields in the order we like them. */
1609 safestrncpy(allkeys, FORDER, sizeof allkeys);
1610 for (i=0; i<strlen(allkeys); ++i) {
1611 k = (int) allkeys[i];
1613 if ( (TheMessage->cm_fields[k] != NULL)
1614 && (msgkeys[k] != NULL) ) {
1616 if (do_proto) cprintf("%s=%s\n",
1620 else if ((k == 'F') && (suppress_f)) {
1623 /* Masquerade display name if needed */
1625 if (do_proto) cprintf("%s=%s\n",
1627 TheMessage->cm_fields[k]
1636 /* begin header processing loop for RFC822 transfer format */
1641 strcpy(snode, NODENAME);
1642 strcpy(lnode, HUMANNODE);
1643 if (mode == MT_RFC822) {
1644 for (i = 0; i < 256; ++i) {
1645 if (TheMessage->cm_fields[i]) {
1646 mptr = TheMessage->cm_fields[i];
1649 safestrncpy(luser, mptr, sizeof luser);
1650 safestrncpy(suser, mptr, sizeof suser);
1652 else if (i == 'Y') {
1653 cprintf("CC: %s%s", mptr, nl);
1655 else if (i == 'P') {
1656 cprintf("Return-Path: %s%s", mptr, nl);
1658 else if (i == 'V') {
1659 cprintf("Envelope-To: %s%s", mptr, nl);
1661 else if (i == 'U') {
1662 cprintf("Subject: %s%s", mptr, nl);
1666 safestrncpy(mid, mptr, sizeof mid);
1668 safestrncpy(lnode, mptr, sizeof lnode);
1670 safestrncpy(fuser, mptr, sizeof fuser);
1671 /* else if (i == 'O')
1672 cprintf("X-Citadel-Room: %s%s",
1675 safestrncpy(snode, mptr, sizeof snode);
1677 cprintf("To: %s%s", mptr, nl);
1678 else if (i == 'T') {
1679 datestring(datestamp, sizeof datestamp,
1680 atol(mptr), DATESTRING_RFC822);
1681 cprintf("Date: %s%s", datestamp, nl);
1685 if (subject_found == 0) {
1686 cprintf("Subject: (no subject)%s", nl);
1690 for (i=0; i<strlen(suser); ++i) {
1691 suser[i] = tolower(suser[i]);
1692 if (!isalnum(suser[i])) suser[i]='_';
1695 if (mode == MT_RFC822) {
1696 if (!strcasecmp(snode, NODENAME)) {
1697 safestrncpy(snode, FQDN, sizeof snode);
1700 /* Construct a fun message id */
1701 cprintf("Message-ID: <%s", mid);
1702 if (strchr(mid, '@')==NULL) {
1703 cprintf("@%s", snode);
1707 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1708 cprintf("From: \"----\" <x@x.org>%s", nl);
1710 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1711 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1713 else if (strlen(fuser) > 0) {
1714 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1717 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1720 cprintf("Organization: %s%s", lnode, nl);
1722 /* Blank line signifying RFC822 end-of-headers */
1723 if (TheMessage->cm_format_type != FMT_RFC822) {
1728 /* end header processing loop ... at this point, we're in the text */
1730 if (headers_only == HEADERS_FAST) goto DONE;
1731 mptr = TheMessage->cm_fields['M'];
1733 /* Tell the client about the MIME parts in this message */
1734 if (TheMessage->cm_format_type == FMT_RFC822) {
1735 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1736 memset(&ma, 0, sizeof(struct ma_info));
1737 mime_parser(mptr, NULL,
1738 (do_proto ? *list_this_part : NULL),
1739 (do_proto ? *list_this_pref : NULL),
1740 (do_proto ? *list_this_suff : NULL),
1743 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1744 char *start_of_text = NULL;
1745 start_of_text = strstr(mptr, "\n\r\n");
1746 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1747 if (start_of_text == NULL) start_of_text = mptr;
1749 start_of_text = strstr(start_of_text, "\n");
1754 int nllen = strlen(nl);
1755 while (ch=*mptr, ch!=0) {
1761 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1762 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1763 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1766 sprintf(&outbuf[outlen], "%s", nl);
1770 outbuf[outlen++] = ch;
1775 if (outlen > 1000) {
1776 client_write(outbuf, outlen);
1781 client_write(outbuf, outlen);
1789 if (headers_only == HEADERS_ONLY) {
1793 /* signify start of msg text */
1794 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1795 if (do_proto) cprintf("text\n");
1798 /* If the format type on disk is 1 (fixed-format), then we want
1799 * everything to be output completely literally ... regardless of
1800 * what message transfer format is in use.
1802 if (TheMessage->cm_format_type == FMT_FIXED) {
1803 if (mode == MT_MIME) {
1804 cprintf("Content-type: text/plain\n\n");
1807 while (ch = *mptr++, ch > 0) {
1810 if ((ch == 10) || (strlen(buf) > 250)) {
1811 cprintf("%s%s", buf, nl);
1814 buf[strlen(buf) + 1] = 0;
1815 buf[strlen(buf)] = ch;
1818 if (strlen(buf) > 0)
1819 cprintf("%s%s", buf, nl);
1822 /* If the message on disk is format 0 (Citadel vari-format), we
1823 * output using the formatter at 80 columns. This is the final output
1824 * form if the transfer format is RFC822, but if the transfer format
1825 * is Citadel proprietary, it'll still work, because the indentation
1826 * for new paragraphs is correct and the client will reformat the
1827 * message to the reader's screen width.
1829 if (TheMessage->cm_format_type == FMT_CITADEL) {
1830 if (mode == MT_MIME) {
1831 cprintf("Content-type: text/x-citadel-variformat\n\n");
1833 memfmout(mptr, 0, nl);
1836 /* If the message on disk is format 4 (MIME), we've gotta hand it
1837 * off to the MIME parser. The client has already been told that
1838 * this message is format 1 (fixed format), so the callback function
1839 * we use will display those parts as-is.
1841 if (TheMessage->cm_format_type == FMT_RFC822) {
1842 memset(&ma, 0, sizeof(struct ma_info));
1844 if (mode == MT_MIME) {
1845 ma.use_fo_hooks = 0;
1846 strcpy(ma.chosen_part, "1");
1847 ma.chosen_pref = 9999;
1848 mime_parser(mptr, NULL,
1849 *choose_preferred, *fixed_output_pre,
1850 *fixed_output_post, (void *)&ma, 0);
1851 mime_parser(mptr, NULL,
1852 *output_preferred, NULL, NULL, (void *)&ma, 0);
1855 ma.use_fo_hooks = 1;
1856 mime_parser(mptr, NULL,
1857 *fixed_output, *fixed_output_pre,
1858 *fixed_output_post, (void *)&ma, 0);
1863 DONE: /* now we're done */
1864 if (do_proto) cprintf("000\n");
1871 * display a message (mode 0 - Citadel proprietary)
1873 void cmd_msg0(char *cmdbuf)
1876 int headers_only = HEADERS_ALL;
1878 msgid = extract_long(cmdbuf, 0);
1879 headers_only = extract_int(cmdbuf, 1);
1881 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1887 * display a message (mode 2 - RFC822)
1889 void cmd_msg2(char *cmdbuf)
1892 int headers_only = HEADERS_ALL;
1894 msgid = extract_long(cmdbuf, 0);
1895 headers_only = extract_int(cmdbuf, 1);
1897 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1903 * display a message (mode 3 - IGnet raw format - internal programs only)
1905 void cmd_msg3(char *cmdbuf)
1908 struct CtdlMessage *msg = NULL;
1911 if (CC->internal_pgm == 0) {
1912 cprintf("%d This command is for internal programs only.\n",
1913 ERROR + HIGHER_ACCESS_REQUIRED);
1917 msgnum = extract_long(cmdbuf, 0);
1918 msg = CtdlFetchMessage(msgnum, 1);
1920 cprintf("%d Message %ld not found.\n",
1921 ERROR + MESSAGE_NOT_FOUND, msgnum);
1925 serialize_message(&smr, msg);
1926 CtdlFreeMessage(msg);
1929 cprintf("%d Unable to serialize message\n",
1930 ERROR + INTERNAL_ERROR);
1934 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1935 client_write((char *)smr.ser, (int)smr.len);
1942 * Display a message using MIME content types
1944 void cmd_msg4(char *cmdbuf)
1949 msgid = extract_long(cmdbuf, 0);
1950 extract_token(section, cmdbuf, 1, '|', sizeof section);
1951 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1957 * Client tells us its preferred message format(s)
1959 void cmd_msgp(char *cmdbuf)
1961 safestrncpy(CC->preferred_formats, cmdbuf,
1962 sizeof(CC->preferred_formats));
1963 cprintf("%d ok\n", CIT_OK);
1968 * Open a component of a MIME message as a download file
1970 void cmd_opna(char *cmdbuf)
1973 char desired_section[128];
1975 msgid = extract_long(cmdbuf, 0);
1976 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1977 safestrncpy(CC->download_desired_section, desired_section,
1978 sizeof CC->download_desired_section);
1979 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1984 * Open a component of a MIME message and transmit it all at once
1986 void cmd_dlat(char *cmdbuf)
1989 char desired_section[128];
1991 msgid = extract_long(cmdbuf, 0);
1992 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1993 safestrncpy(CC->download_desired_section, desired_section,
1994 sizeof CC->download_desired_section);
1995 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2000 * Save one or more message pointers into a specified room
2001 * (Returns 0 for success, nonzero for failure)
2002 * roomname may be NULL to use the current room
2004 * Note that the 'supplied_msg' field may be set to NULL, in which case
2005 * the message will be fetched from disk, by number, if we need to perform
2006 * replication checks. This adds an additional database read, so if the
2007 * caller already has the message in memory then it should be supplied. (Obviously
2008 * this mode of operation only works if we're saving a single message.)
2010 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2011 int do_repl_check, struct CtdlMessage *supplied_msg)
2014 char hold_rm[ROOMNAMELEN];
2015 struct cdbdata *cdbfr;
2018 long highest_msg = 0L;
2021 struct CtdlMessage *msg = NULL;
2023 long *msgs_to_be_merged = NULL;
2024 int num_msgs_to_be_merged = 0;
2027 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2028 roomname, num_newmsgs, do_repl_check);
2030 strcpy(hold_rm, CC->room.QRname);
2033 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2034 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2035 if (num_newmsgs > 1) supplied_msg = NULL;
2037 /* Now the regular stuff */
2038 if (lgetroom(&CC->room,
2039 ((roomname != NULL) ? roomname : CC->room.QRname) )
2041 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2042 return(ERROR + ROOM_NOT_FOUND);
2046 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2047 num_msgs_to_be_merged = 0;
2050 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2051 if (cdbfr == NULL) {
2055 msglist = (long *) cdbfr->ptr;
2056 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2057 num_msgs = cdbfr->len / sizeof(long);
2062 /* Create a list of msgid's which were supplied by the caller, but do
2063 * not already exist in the target room. It is absolutely taboo to
2064 * have more than one reference to the same message in a room.
2066 for (i=0; i<num_newmsgs; ++i) {
2068 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2069 if (msglist[j] == newmsgidlist[i]) {
2074 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2078 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2081 * Now merge the new messages
2083 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2084 if (msglist == NULL) {
2085 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2087 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2088 num_msgs += num_msgs_to_be_merged;
2090 /* Sort the message list, so all the msgid's are in order */
2091 num_msgs = sort_msglist(msglist, num_msgs);
2093 /* Determine the highest message number */
2094 highest_msg = msglist[num_msgs - 1];
2096 /* Write it back to disk. */
2097 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2098 msglist, (int)(num_msgs * sizeof(long)));
2100 /* Free up the memory we used. */
2103 /* Update the highest-message pointer and unlock the room. */
2104 CC->room.QRhighest = highest_msg;
2105 lputroom(&CC->room);
2107 /* Perform replication checks if necessary */
2108 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2109 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2111 for (i=0; i<num_msgs_to_be_merged; ++i) {
2112 msgid = msgs_to_be_merged[i];
2114 if (supplied_msg != NULL) {
2118 msg = CtdlFetchMessage(msgid, 0);
2122 ReplicationChecks(msg);
2124 /* If the message has an Exclusive ID, index that... */
2125 if (msg->cm_fields['E'] != NULL) {
2126 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2129 /* Free up the memory we may have allocated */
2130 if (msg != supplied_msg) {
2131 CtdlFreeMessage(msg);
2139 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2142 /* Submit this room for net processing */
2143 network_queue_room(&CC->room, NULL);
2145 #ifdef HAVE_LIBSIEVE
2146 /* If this is someone's inbox, submit the room for sieve processing */
2147 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2148 sieve_queue_room(&CC->room);
2150 #endif /* HAVE_LIBSIEVE */
2152 /* Go back to the room we were in before we wandered here... */
2153 getroom(&CC->room, hold_rm);
2155 /* Bump the reference count for all messages which were merged */
2156 for (i=0; i<num_msgs_to_be_merged; ++i) {
2157 AdjRefCount(msgs_to_be_merged[i], +1);
2160 /* Free up memory... */
2161 if (msgs_to_be_merged != NULL) {
2162 free(msgs_to_be_merged);
2165 /* Return success. */
2171 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2174 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2175 int do_repl_check, struct CtdlMessage *supplied_msg)
2177 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2184 * Message base operation to save a new message to the message store
2185 * (returns new message number)
2187 * This is the back end for CtdlSubmitMsg() and should not be directly
2188 * called by server-side modules.
2191 long send_message(struct CtdlMessage *msg) {
2199 /* Get a new message number */
2200 newmsgid = get_new_message_number();
2201 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2203 /* Generate an ID if we don't have one already */
2204 if (msg->cm_fields['I']==NULL) {
2205 msg->cm_fields['I'] = strdup(msgidbuf);
2208 /* If the message is big, set its body aside for storage elsewhere */
2209 if (msg->cm_fields['M'] != NULL) {
2210 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2212 holdM = msg->cm_fields['M'];
2213 msg->cm_fields['M'] = NULL;
2217 /* Serialize our data structure for storage in the database */
2218 serialize_message(&smr, msg);
2221 msg->cm_fields['M'] = holdM;
2225 cprintf("%d Unable to serialize message\n",
2226 ERROR + INTERNAL_ERROR);
2230 /* Write our little bundle of joy into the message base */
2231 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2232 smr.ser, smr.len) < 0) {
2233 lprintf(CTDL_ERR, "Can't store message\n");
2237 cdb_store(CDB_BIGMSGS,
2247 /* Free the memory we used for the serialized message */
2250 /* Return the *local* message ID to the caller
2251 * (even if we're storing an incoming network message)
2259 * Serialize a struct CtdlMessage into the format used on disk and network.
2261 * This function loads up a "struct ser_ret" (defined in server.h) which
2262 * contains the length of the serialized message and a pointer to the
2263 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2265 void serialize_message(struct ser_ret *ret, /* return values */
2266 struct CtdlMessage *msg) /* unserialized msg */
2268 size_t wlen, fieldlen;
2270 static char *forder = FORDER;
2273 * Check for valid message format
2275 if (is_valid_message(msg) == 0) {
2276 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2283 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2284 ret->len = ret->len +
2285 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2287 ret->ser = malloc(ret->len);
2288 if (ret->ser == NULL) {
2289 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2290 (long)ret->len, strerror(errno));
2297 ret->ser[1] = msg->cm_anon_type;
2298 ret->ser[2] = msg->cm_format_type;
2301 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2302 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2303 ret->ser[wlen++] = (char)forder[i];
2304 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2305 wlen = wlen + fieldlen + 1;
2307 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2308 (long)ret->len, (long)wlen);
2316 * Check to see if any messages already exist in the current room which
2317 * carry the same Exclusive ID as this one. If any are found, delete them.
2319 void ReplicationChecks(struct CtdlMessage *msg) {
2320 long old_msgnum = (-1L);
2322 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2324 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2327 /* No exclusive id? Don't do anything. */
2328 if (msg == NULL) return;
2329 if (msg->cm_fields['E'] == NULL) return;
2330 if (strlen(msg->cm_fields['E']) == 0) return;
2331 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2332 msg->cm_fields['E'], CC->room.QRname);*/
2334 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2335 if (old_msgnum > 0L) {
2336 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2337 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2344 * Save a message to disk and submit it into the delivery system.
2346 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2347 struct recptypes *recps, /* recipients (if mail) */
2348 char *force /* force a particular room? */
2350 char submit_filename[128];
2351 char generated_timestamp[32];
2352 char hold_rm[ROOMNAMELEN];
2353 char actual_rm[ROOMNAMELEN];
2354 char force_room[ROOMNAMELEN];
2355 char content_type[SIZ]; /* We have to learn this */
2356 char recipient[SIZ];
2359 struct ctdluser userbuf;
2361 struct MetaData smi;
2362 FILE *network_fp = NULL;
2363 static int seqnum = 1;
2364 struct CtdlMessage *imsg = NULL;
2366 size_t instr_alloc = 0;
2368 char *hold_R, *hold_D;
2369 char *collected_addresses = NULL;
2370 struct addresses_to_be_filed *aptr = NULL;
2371 char *saved_rfc822_version = NULL;
2372 int qualified_for_journaling = 0;
2374 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2375 if (is_valid_message(msg) == 0) return(-1); /* self check */
2377 /* If this message has no timestamp, we take the liberty of
2378 * giving it one, right now.
2380 if (msg->cm_fields['T'] == NULL) {
2381 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2382 msg->cm_fields['T'] = strdup(generated_timestamp);
2385 /* If this message has no path, we generate one.
2387 if (msg->cm_fields['P'] == NULL) {
2388 if (msg->cm_fields['A'] != NULL) {
2389 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2390 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2391 if (isspace(msg->cm_fields['P'][a])) {
2392 msg->cm_fields['P'][a] = ' ';
2397 msg->cm_fields['P'] = strdup("unknown");
2401 if (force == NULL) {
2402 strcpy(force_room, "");
2405 strcpy(force_room, force);
2408 /* Learn about what's inside, because it's what's inside that counts */
2409 if (msg->cm_fields['M'] == NULL) {
2410 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2414 switch (msg->cm_format_type) {
2416 strcpy(content_type, "text/x-citadel-variformat");
2419 strcpy(content_type, "text/plain");
2422 strcpy(content_type, "text/plain");
2423 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2425 safestrncpy(content_type, &mptr[13], sizeof content_type);
2426 striplt(content_type);
2427 for (a = 0; a < strlen(content_type); ++a) {
2428 if ((content_type[a] == ';')
2429 || (content_type[a] == ' ')
2430 || (content_type[a] == 13)
2431 || (content_type[a] == 10)) {
2432 content_type[a] = 0;
2438 /* Goto the correct room */
2439 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2440 strcpy(hold_rm, CC->room.QRname);
2441 strcpy(actual_rm, CC->room.QRname);
2442 if (recps != NULL) {
2443 strcpy(actual_rm, SENTITEMS);
2446 /* If the user is a twit, move to the twit room for posting */
2448 if (CC->user.axlevel == 2) {
2449 strcpy(hold_rm, actual_rm);
2450 strcpy(actual_rm, config.c_twitroom);
2451 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2455 /* ...or if this message is destined for Aide> then go there. */
2456 if (strlen(force_room) > 0) {
2457 strcpy(actual_rm, force_room);
2460 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2461 if (strcasecmp(actual_rm, CC->room.QRname)) {
2462 /* getroom(&CC->room, actual_rm); */
2463 usergoto(actual_rm, 0, 1, NULL, NULL);
2467 * If this message has no O (room) field, generate one.
2469 if (msg->cm_fields['O'] == NULL) {
2470 msg->cm_fields['O'] = strdup(CC->room.QRname);
2473 /* Perform "before save" hooks (aborting if any return nonzero) */
2474 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2475 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2478 * If this message has an Exclusive ID, and the room is replication
2479 * checking enabled, then do replication checks.
2481 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2482 ReplicationChecks(msg);
2485 /* Save it to disk */
2486 lprintf(CTDL_DEBUG, "Saving to disk\n");
2487 newmsgid = send_message(msg);
2488 if (newmsgid <= 0L) return(-5);
2490 /* Write a supplemental message info record. This doesn't have to
2491 * be a critical section because nobody else knows about this message
2494 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2495 memset(&smi, 0, sizeof(struct MetaData));
2496 smi.meta_msgnum = newmsgid;
2497 smi.meta_refcount = 0;
2498 safestrncpy(smi.meta_content_type, content_type,
2499 sizeof smi.meta_content_type);
2502 * Measure how big this message will be when rendered as RFC822.
2503 * We do this for two reasons:
2504 * 1. We need the RFC822 length for the new metadata record, so the
2505 * POP and IMAP services don't have to calculate message lengths
2506 * while the user is waiting (multiplied by potentially hundreds
2507 * or thousands of messages).
2508 * 2. If journaling is enabled, we will need an RFC822 version of the
2509 * message to attach to the journalized copy.
2511 if (CC->redirect_buffer != NULL) {
2512 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2515 CC->redirect_buffer = malloc(SIZ);
2516 CC->redirect_len = 0;
2517 CC->redirect_alloc = SIZ;
2518 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2519 smi.meta_rfc822_length = CC->redirect_len;
2520 saved_rfc822_version = CC->redirect_buffer;
2521 CC->redirect_buffer = NULL;
2522 CC->redirect_len = 0;
2523 CC->redirect_alloc = 0;
2527 /* Now figure out where to store the pointers */
2528 lprintf(CTDL_DEBUG, "Storing pointers\n");
2530 /* If this is being done by the networker delivering a private
2531 * message, we want to BYPASS saving the sender's copy (because there
2532 * is no local sender; it would otherwise go to the Trashcan).
2534 if ((!CC->internal_pgm) || (recps == NULL)) {
2535 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2536 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2537 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2541 /* For internet mail, drop a copy in the outbound queue room */
2543 if (recps->num_internet > 0) {
2544 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2547 /* If other rooms are specified, drop them there too. */
2549 if (recps->num_room > 0)
2550 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2551 extract_token(recipient, recps->recp_room, i,
2552 '|', sizeof recipient);
2553 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2554 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2557 /* Bump this user's messages posted counter. */
2558 lprintf(CTDL_DEBUG, "Updating user\n");
2559 lgetuser(&CC->user, CC->curr_user);
2560 CC->user.posted = CC->user.posted + 1;
2561 lputuser(&CC->user);
2563 /* If this is private, local mail, make a copy in the
2564 * recipient's mailbox and bump the reference count.
2567 if (recps->num_local > 0)
2568 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2569 extract_token(recipient, recps->recp_local, i,
2570 '|', sizeof recipient);
2571 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2573 if (getuser(&userbuf, recipient) == 0) {
2574 // Add a flag so the Funambol module knows its mail
2575 msg->cm_fields['W'] = strdup(recipient);
2576 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2577 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2578 BumpNewMailCounter(userbuf.usernum);
2579 if (strlen(config.c_funambol_host) > 0) {
2580 /* Generate a instruction message for the Funambol notification
2581 * server, in the same style as the SMTP queue
2584 instr = malloc(instr_alloc);
2585 snprintf(instr, instr_alloc,
2586 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2588 SPOOLMIME, newmsgid, (long)time(NULL),
2589 msg->cm_fields['A'], msg->cm_fields['N']
2592 imsg = malloc(sizeof(struct CtdlMessage));
2593 memset(imsg, 0, sizeof(struct CtdlMessage));
2594 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2595 imsg->cm_anon_type = MES_NORMAL;
2596 imsg->cm_format_type = FMT_RFC822;
2597 imsg->cm_fields['A'] = strdup("Citadel");
2598 imsg->cm_fields['J'] = strdup("do not journal");
2599 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2600 imsg->cm_fields['W'] = strdup(recipient);
2601 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2602 CtdlFreeMessage(imsg);
2606 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2607 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2612 /* Perform "after save" hooks */
2613 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2614 PerformMessageHooks(msg, EVT_AFTERSAVE);
2616 /* For IGnet mail, we have to save a new copy into the spooler for
2617 * each recipient, with the R and D fields set to the recipient and
2618 * destination-node. This has two ugly side effects: all other
2619 * recipients end up being unlisted in this recipient's copy of the
2620 * message, and it has to deliver multiple messages to the same
2621 * node. We'll revisit this again in a year or so when everyone has
2622 * a network spool receiver that can handle the new style messages.
2625 if (recps->num_ignet > 0)
2626 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2627 extract_token(recipient, recps->recp_ignet, i,
2628 '|', sizeof recipient);
2630 hold_R = msg->cm_fields['R'];
2631 hold_D = msg->cm_fields['D'];
2632 msg->cm_fields['R'] = malloc(SIZ);
2633 msg->cm_fields['D'] = malloc(128);
2634 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2635 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2637 serialize_message(&smr, msg);
2639 snprintf(submit_filename, sizeof submit_filename,
2640 "%s/netmail.%04lx.%04x.%04x",
2642 (long) getpid(), CC->cs_pid, ++seqnum);
2643 network_fp = fopen(submit_filename, "wb+");
2644 if (network_fp != NULL) {
2645 fwrite(smr.ser, smr.len, 1, network_fp);
2651 free(msg->cm_fields['R']);
2652 free(msg->cm_fields['D']);
2653 msg->cm_fields['R'] = hold_R;
2654 msg->cm_fields['D'] = hold_D;
2657 /* Go back to the room we started from */
2658 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2659 if (strcasecmp(hold_rm, CC->room.QRname))
2660 usergoto(hold_rm, 0, 1, NULL, NULL);
2662 /* For internet mail, generate delivery instructions.
2663 * Yes, this is recursive. Deal with it. Infinite recursion does
2664 * not happen because the delivery instructions message does not
2665 * contain a recipient.
2668 if (recps->num_internet > 0) {
2669 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2671 instr = malloc(instr_alloc);
2672 snprintf(instr, instr_alloc,
2673 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2675 SPOOLMIME, newmsgid, (long)time(NULL),
2676 msg->cm_fields['A'], msg->cm_fields['N']
2679 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2680 size_t tmp = strlen(instr);
2681 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2682 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2683 instr_alloc = instr_alloc * 2;
2684 instr = realloc(instr, instr_alloc);
2686 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2689 imsg = malloc(sizeof(struct CtdlMessage));
2690 memset(imsg, 0, sizeof(struct CtdlMessage));
2691 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2692 imsg->cm_anon_type = MES_NORMAL;
2693 imsg->cm_format_type = FMT_RFC822;
2694 imsg->cm_fields['A'] = strdup("Citadel");
2695 imsg->cm_fields['J'] = strdup("do not journal");
2696 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2697 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2698 CtdlFreeMessage(imsg);
2702 * Any addresses to harvest for someone's address book?
2704 if ( (CC->logged_in) && (recps != NULL) ) {
2705 collected_addresses = harvest_collected_addresses(msg);
2708 if (collected_addresses != NULL) {
2709 begin_critical_section(S_ATBF);
2710 aptr = (struct addresses_to_be_filed *)
2711 malloc(sizeof(struct addresses_to_be_filed));
2713 MailboxName(actual_rm, sizeof actual_rm,
2714 &CC->user, USERCONTACTSROOM);
2715 aptr->roomname = strdup(actual_rm);
2716 aptr->collected_addresses = collected_addresses;
2718 end_critical_section(S_ATBF);
2722 * Determine whether this message qualifies for journaling.
2724 if (msg->cm_fields['J'] != NULL) {
2725 qualified_for_journaling = 0;
2728 if (recps == NULL) {
2729 qualified_for_journaling = config.c_journal_pubmsgs;
2731 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2732 qualified_for_journaling = config.c_journal_email;
2735 qualified_for_journaling = config.c_journal_pubmsgs;
2740 * Do we have to perform journaling? If so, hand off the saved
2741 * RFC822 version will be handed off to the journaler for background
2742 * submit. Otherwise, we have to free the memory ourselves.
2744 if (saved_rfc822_version != NULL) {
2745 if (qualified_for_journaling) {
2746 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2749 free(saved_rfc822_version);
2762 * Convenience function for generating small administrative messages.
2764 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2765 int format_type, char *subject)
2767 struct CtdlMessage *msg;
2768 struct recptypes *recp = NULL;
2770 msg = malloc(sizeof(struct CtdlMessage));
2771 memset(msg, 0, sizeof(struct CtdlMessage));
2772 msg->cm_magic = CTDLMESSAGE_MAGIC;
2773 msg->cm_anon_type = MES_NORMAL;
2774 msg->cm_format_type = format_type;
2777 msg->cm_fields['A'] = strdup(from);
2779 else if (fromaddr != NULL) {
2780 msg->cm_fields['A'] = strdup(fromaddr);
2781 if (strchr(msg->cm_fields['A'], '@')) {
2782 *strchr(msg->cm_fields['A'], '@') = 0;
2786 msg->cm_fields['A'] = strdup("Citadel");
2789 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2790 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2791 msg->cm_fields['N'] = strdup(NODENAME);
2793 msg->cm_fields['R'] = strdup(to);
2794 recp = validate_recipients(to);
2796 if (subject != NULL) {
2797 msg->cm_fields['U'] = strdup(subject);
2799 msg->cm_fields['M'] = strdup(text);
2801 CtdlSubmitMsg(msg, recp, room);
2802 CtdlFreeMessage(msg);
2803 if (recp != NULL) free_recipients(recp);
2809 * Back end function used by CtdlMakeMessage() and similar functions
2811 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2812 size_t maxlen, /* maximum message length */
2813 char *exist, /* if non-null, append to it;
2814 exist is ALWAYS freed */
2815 int crlf /* CRLF newlines instead of LF */
2819 size_t message_len = 0;
2820 size_t buffer_len = 0;
2827 if (exist == NULL) {
2834 message_len = strlen(exist);
2835 buffer_len = message_len + 4096;
2836 m = realloc(exist, buffer_len);
2843 /* Do we need to change leading ".." to "." for SMTP escaping? */
2844 if (!strcmp(terminator, ".")) {
2848 /* flush the input if we have nowhere to store it */
2853 /* read in the lines of message text one by one */
2855 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2856 if (!strcmp(buf, terminator)) finished = 1;
2858 strcat(buf, "\r\n");
2864 /* Unescape SMTP-style input of two dots at the beginning of the line */
2866 if (!strncmp(buf, "..", 2)) {
2867 strcpy(buf, &buf[1]);
2871 if ( (!flushing) && (!finished) ) {
2872 /* Measure the line */
2873 linelen = strlen(buf);
2875 /* augment the buffer if we have to */
2876 if ((message_len + linelen) >= buffer_len) {
2877 ptr = realloc(m, (buffer_len * 2) );
2878 if (ptr == NULL) { /* flush if can't allocate */
2881 buffer_len = (buffer_len * 2);
2883 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2887 /* Add the new line to the buffer. NOTE: this loop must avoid
2888 * using functions like strcat() and strlen() because they
2889 * traverse the entire buffer upon every call, and doing that
2890 * for a multi-megabyte message slows it down beyond usability.
2892 strcpy(&m[message_len], buf);
2893 message_len += linelen;
2896 /* if we've hit the max msg length, flush the rest */
2897 if (message_len >= maxlen) flushing = 1;
2899 } while (!finished);
2907 * Build a binary message to be saved on disk.
2908 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2909 * will become part of the message. This means you are no longer
2910 * responsible for managing that memory -- it will be freed along with
2911 * the rest of the fields when CtdlFreeMessage() is called.)
2914 struct CtdlMessage *CtdlMakeMessage(
2915 struct ctdluser *author, /* author's user structure */
2916 char *recipient, /* NULL if it's not mail */
2917 char *recp_cc, /* NULL if it's not mail */
2918 char *room, /* room where it's going */
2919 int type, /* see MES_ types in header file */
2920 int format_type, /* variformat, plain text, MIME... */
2921 char *fake_name, /* who we're masquerading as */
2922 char *my_email, /* which of my email addresses to use (empty is ok) */
2923 char *subject, /* Subject (optional) */
2924 char *supplied_euid, /* ...or NULL if this is irrelevant */
2925 char *preformatted_text /* ...or NULL to read text from client */
2927 char dest_node[256];
2929 struct CtdlMessage *msg;
2931 msg = malloc(sizeof(struct CtdlMessage));
2932 memset(msg, 0, sizeof(struct CtdlMessage));
2933 msg->cm_magic = CTDLMESSAGE_MAGIC;
2934 msg->cm_anon_type = type;
2935 msg->cm_format_type = format_type;
2937 /* Don't confuse the poor folks if it's not routed mail. */
2938 strcpy(dest_node, "");
2943 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2944 msg->cm_fields['P'] = strdup(buf);
2946 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2947 msg->cm_fields['T'] = strdup(buf);
2949 if (fake_name[0]) /* author */
2950 msg->cm_fields['A'] = strdup(fake_name);
2952 msg->cm_fields['A'] = strdup(author->fullname);
2954 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2955 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2958 msg->cm_fields['O'] = strdup(CC->room.QRname);
2961 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2962 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2964 if (recipient[0] != 0) {
2965 msg->cm_fields['R'] = strdup(recipient);
2967 if (recp_cc[0] != 0) {
2968 msg->cm_fields['Y'] = strdup(recp_cc);
2970 if (dest_node[0] != 0) {
2971 msg->cm_fields['D'] = strdup(dest_node);
2974 if (strlen(my_email) > 0) {
2975 msg->cm_fields['F'] = strdup(my_email);
2977 else if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2978 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2981 if (subject != NULL) {
2984 length = strlen(subject);
2990 while ((subject[i] != '\0') &&
2991 (IsAscii = isascii(subject[i]) != 0 ))
2994 msg->cm_fields['U'] = strdup(subject);
2995 else /* ok, we've got utf8 in the string. */
2997 msg->cm_fields['U'] = rfc2047encode(subject, length);
3003 if (supplied_euid != NULL) {
3004 msg->cm_fields['E'] = strdup(supplied_euid);
3007 if (preformatted_text != NULL) {
3008 msg->cm_fields['M'] = preformatted_text;
3011 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
3019 * Check to see whether we have permission to post a message in the current
3020 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3021 * returns 0 on success.
3023 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3026 if (!(CC->logged_in)) {
3027 snprintf(errmsgbuf, n, "Not logged in.");
3028 return (ERROR + NOT_LOGGED_IN);
3031 if ((CC->user.axlevel < 2)
3032 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3033 snprintf(errmsgbuf, n, "Need to be validated to enter "
3034 "(except in %s> to sysop)", MAILROOM);
3035 return (ERROR + HIGHER_ACCESS_REQUIRED);
3038 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3039 if (!(ra & UA_POSTALLOWED)) {
3040 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3041 return (ERROR + HIGHER_ACCESS_REQUIRED);
3044 strcpy(errmsgbuf, "Ok");
3050 * Check to see if the specified user has Internet mail permission
3051 * (returns nonzero if permission is granted)
3053 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3055 /* Do not allow twits to send Internet mail */
3056 if (who->axlevel <= 2) return(0);
3058 /* Globally enabled? */
3059 if (config.c_restrict == 0) return(1);
3061 /* User flagged ok? */
3062 if (who->flags & US_INTERNET) return(2);
3064 /* Aide level access? */
3065 if (who->axlevel >= 6) return(3);
3067 /* No mail for you! */
3073 * Validate recipients, count delivery types and errors, and handle aliasing
3074 * FIXME check for dupes!!!!!
3076 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3077 * were specified, or the number of addresses found invalid.
3079 * Caller needs to free the result using free_recipients()
3081 struct recptypes *validate_recipients(char *supplied_recipients) {
3082 struct recptypes *ret;
3083 char *recipients = NULL;
3084 char this_recp[256];
3085 char this_recp_cooked[256];
3091 struct ctdluser tempUS;
3092 struct ctdlroom tempQR;
3096 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3097 if (ret == NULL) return(NULL);
3099 /* Set all strings to null and numeric values to zero */
3100 memset(ret, 0, sizeof(struct recptypes));
3102 if (supplied_recipients == NULL) {
3103 recipients = strdup("");
3106 recipients = strdup(supplied_recipients);
3109 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3110 * actually need, but it's healthier for the heap than doing lots of tiny
3111 * realloc() calls instead.
3114 ret->errormsg = malloc(strlen(recipients) + 1024);
3115 ret->recp_local = malloc(strlen(recipients) + 1024);
3116 ret->recp_internet = malloc(strlen(recipients) + 1024);
3117 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3118 ret->recp_room = malloc(strlen(recipients) + 1024);
3119 ret->display_recp = malloc(strlen(recipients) + 1024);
3121 ret->errormsg[0] = 0;
3122 ret->recp_local[0] = 0;
3123 ret->recp_internet[0] = 0;
3124 ret->recp_ignet[0] = 0;
3125 ret->recp_room[0] = 0;
3126 ret->display_recp[0] = 0;
3128 ret->recptypes_magic = RECPTYPES_MAGIC;
3130 /* Change all valid separator characters to commas */
3131 for (i=0; i<strlen(recipients); ++i) {
3132 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3133 recipients[i] = ',';
3137 /* Now start extracting recipients... */
3139 while (strlen(recipients) > 0) {
3141 for (i=0; i<=strlen(recipients); ++i) {
3142 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3143 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3144 safestrncpy(this_recp, recipients, i+1);
3146 if (recipients[i] == ',') {
3147 strcpy(recipients, &recipients[i+1]);
3150 strcpy(recipients, "");
3157 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3159 mailtype = alias(this_recp);
3160 mailtype = alias(this_recp);
3161 mailtype = alias(this_recp);
3162 for (j=0; j<=strlen(this_recp); ++j) {
3163 if (this_recp[j]=='_') {
3164 this_recp_cooked[j] = ' ';
3167 this_recp_cooked[j] = this_recp[j];
3173 if (!strcasecmp(this_recp, "sysop")) {
3175 strcpy(this_recp, config.c_aideroom);
3176 if (strlen(ret->recp_room) > 0) {
3177 strcat(ret->recp_room, "|");
3179 strcat(ret->recp_room, this_recp);
3181 else if (getuser(&tempUS, this_recp) == 0) {
3183 strcpy(this_recp, tempUS.fullname);
3184 if (strlen(ret->recp_local) > 0) {
3185 strcat(ret->recp_local, "|");
3187 strcat(ret->recp_local, this_recp);
3189 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3191 strcpy(this_recp, tempUS.fullname);
3192 if (strlen(ret->recp_local) > 0) {
3193 strcat(ret->recp_local, "|");
3195 strcat(ret->recp_local, this_recp);
3197 else if ( (!strncasecmp(this_recp, "room_", 5))
3198 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3200 if (strlen(ret->recp_room) > 0) {
3201 strcat(ret->recp_room, "|");
3203 strcat(ret->recp_room, &this_recp_cooked[5]);
3211 /* Yes, you're reading this correctly: if the target
3212 * domain points back to the local system or an attached
3213 * Citadel directory, the address is invalid. That's
3214 * because if the address were valid, we would have
3215 * already translated it to a local address by now.
3217 if (IsDirectory(this_recp)) {
3222 ++ret->num_internet;
3223 if (strlen(ret->recp_internet) > 0) {
3224 strcat(ret->recp_internet, "|");
3226 strcat(ret->recp_internet, this_recp);
3231 if (strlen(ret->recp_ignet) > 0) {
3232 strcat(ret->recp_ignet, "|");
3234 strcat(ret->recp_ignet, this_recp);
3242 if (strlen(ret->errormsg) == 0) {
3243 snprintf(append, sizeof append,
3244 "Invalid recipient: %s",
3248 snprintf(append, sizeof append, ", %s", this_recp);
3250 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3251 strcat(ret->errormsg, append);
3255 if (strlen(ret->display_recp) == 0) {
3256 strcpy(append, this_recp);
3259 snprintf(append, sizeof append, ", %s", this_recp);
3261 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3262 strcat(ret->display_recp, append);
3267 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3268 ret->num_room + ret->num_error) == 0) {
3269 ret->num_error = (-1);
3270 strcpy(ret->errormsg, "No recipients specified.");
3273 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3274 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3275 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3276 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3277 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3278 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3286 * Destructor for struct recptypes
3288 void free_recipients(struct recptypes *valid) {
3290 if (valid == NULL) {
3294 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3295 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3299 if (valid->errormsg != NULL) free(valid->errormsg);
3300 if (valid->recp_local != NULL) free(valid->recp_local);
3301 if (valid->recp_internet != NULL) free(valid->recp_internet);
3302 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3303 if (valid->recp_room != NULL) free(valid->recp_room);
3304 if (valid->display_recp != NULL) free(valid->display_recp);
3311 * message entry - mode 0 (normal)
3313 void cmd_ent0(char *entargs)
3319 char supplied_euid[128];
3321 int format_type = 0;
3322 char newusername[256];
3323 char newuseremail[256];
3324 struct CtdlMessage *msg;
3328 struct recptypes *valid = NULL;
3329 struct recptypes *valid_to = NULL;
3330 struct recptypes *valid_cc = NULL;
3331 struct recptypes *valid_bcc = NULL;
3337 int newuseremail_ok = 0;
3341 post = extract_int(entargs, 0);
3342 extract_token(recp, entargs, 1, '|', sizeof recp);
3343 anon_flag = extract_int(entargs, 2);
3344 format_type = extract_int(entargs, 3);
3345 extract_token(subject, entargs, 4, '|', sizeof subject);
3346 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3347 do_confirm = extract_int(entargs, 6);
3348 extract_token(cc, entargs, 7, '|', sizeof cc);
3349 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3350 switch(CC->room.QRdefaultview) {
3353 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3356 supplied_euid[0] = 0;
3359 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3361 /* first check to make sure the request is valid. */
3363 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3366 cprintf("%d %s\n", err, errmsg);
3370 /* Check some other permission type things. */
3372 if (strlen(newusername) == 0) {
3373 strcpy(newusername, CC->user.fullname);
3375 if ( (CC->user.axlevel < 6)
3376 && (strcasecmp(newusername, CC->user.fullname))
3377 && (strcasecmp(newusername, CC->cs_inet_fn))
3379 cprintf("%d You don't have permission to author messages as '%s'.\n",
3380 ERROR + HIGHER_ACCESS_REQUIRED,
3387 if (strlen(newuseremail) == 0) {
3388 newuseremail_ok = 1;
3391 if (strlen(newuseremail) > 0) {
3392 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3393 newuseremail_ok = 1;
3395 else if (strlen(CC->cs_inet_other_emails) > 0) {
3396 j = num_tokens(CC->cs_inet_other_emails, '|');
3397 for (i=0; i<j; ++i) {
3398 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3399 if (!strcasecmp(newuseremail, buf)) {
3400 newuseremail_ok = 1;
3406 if (!newuseremail_ok) {
3407 cprintf("%d You don't have permission to author messages as '%s'.\n",
3408 ERROR + HIGHER_ACCESS_REQUIRED,
3414 CC->cs_flags |= CS_POSTING;
3416 /* In mailbox rooms we have to behave a little differently --
3417 * make sure the user has specified at least one recipient. Then
3418 * validate the recipient(s). We do this for the Mail> room, as
3419 * well as any room which has the "Mailbox" view set.
3422 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3423 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3425 if (CC->user.axlevel < 2) {
3426 strcpy(recp, "sysop");
3431 valid_to = validate_recipients(recp);
3432 if (valid_to->num_error > 0) {
3433 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3434 free_recipients(valid_to);
3438 valid_cc = validate_recipients(cc);
3439 if (valid_cc->num_error > 0) {
3440 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3441 free_recipients(valid_to);
3442 free_recipients(valid_cc);
3446 valid_bcc = validate_recipients(bcc);
3447 if (valid_bcc->num_error > 0) {
3448 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3449 free_recipients(valid_to);
3450 free_recipients(valid_cc);
3451 free_recipients(valid_bcc);
3455 /* Recipient required, but none were specified */
3456 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3457 free_recipients(valid_to);
3458 free_recipients(valid_cc);
3459 free_recipients(valid_bcc);
3460 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3464 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3465 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3466 cprintf("%d You do not have permission "
3467 "to send Internet mail.\n",
3468 ERROR + HIGHER_ACCESS_REQUIRED);
3469 free_recipients(valid_to);
3470 free_recipients(valid_cc);
3471 free_recipients(valid_bcc);
3476 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
3477 && (CC->user.axlevel < 4) ) {
3478 cprintf("%d Higher access required for network mail.\n",
3479 ERROR + HIGHER_ACCESS_REQUIRED);
3480 free_recipients(valid_to);
3481 free_recipients(valid_cc);
3482 free_recipients(valid_bcc);
3486 if ((RESTRICT_INTERNET == 1)
3487 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3488 && ((CC->user.flags & US_INTERNET) == 0)
3489 && (!CC->internal_pgm)) {
3490 cprintf("%d You don't have access to Internet mail.\n",
3491 ERROR + HIGHER_ACCESS_REQUIRED);
3492 free_recipients(valid_to);
3493 free_recipients(valid_cc);
3494 free_recipients(valid_bcc);
3500 /* Is this a room which has anonymous-only or anonymous-option? */
3501 anonymous = MES_NORMAL;
3502 if (CC->room.QRflags & QR_ANONONLY) {
3503 anonymous = MES_ANONONLY;
3505 if (CC->room.QRflags & QR_ANONOPT) {
3506 if (anon_flag == 1) { /* only if the user requested it */
3507 anonymous = MES_ANONOPT;
3511 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3515 /* If we're only checking the validity of the request, return
3516 * success without creating the message.
3519 cprintf("%d %s\n", CIT_OK,
3520 ((valid_to != NULL) ? valid_to->display_recp : "") );
3521 free_recipients(valid_to);
3522 free_recipients(valid_cc);
3523 free_recipients(valid_bcc);
3527 /* We don't need these anymore because we'll do it differently below */
3528 free_recipients(valid_to);
3529 free_recipients(valid_cc);
3530 free_recipients(valid_bcc);
3532 /* Read in the message from the client. */
3534 cprintf("%d send message\n", START_CHAT_MODE);
3536 cprintf("%d send message\n", SEND_LISTING);
3539 msg = CtdlMakeMessage(&CC->user, recp, cc,
3540 CC->room.QRname, anonymous, format_type,
3541 newusername, newuseremail, subject,
3542 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3545 /* Put together one big recipients struct containing to/cc/bcc all in
3546 * one. This is for the envelope.
3548 char *all_recps = malloc(SIZ * 3);
3549 strcpy(all_recps, recp);
3550 if (strlen(cc) > 0) {
3551 if (strlen(all_recps) > 0) {
3552 strcat(all_recps, ",");
3554 strcat(all_recps, cc);
3556 if (strlen(bcc) > 0) {
3557 if (strlen(all_recps) > 0) {
3558 strcat(all_recps, ",");
3560 strcat(all_recps, bcc);
3562 if (strlen(all_recps) > 0) {
3563 valid = validate_recipients(all_recps);
3571 msgnum = CtdlSubmitMsg(msg, valid, "");
3574 cprintf("%ld\n", msgnum);
3576 cprintf("Message accepted.\n");
3579 cprintf("Internal error.\n");
3581 if (msg->cm_fields['E'] != NULL) {
3582 cprintf("%s\n", msg->cm_fields['E']);
3589 CtdlFreeMessage(msg);
3591 if (valid != NULL) {
3592 free_recipients(valid);
3600 * API function to delete messages which match a set of criteria
3601 * (returns the actual number of messages deleted)
3603 int CtdlDeleteMessages(char *room_name, /* which room */
3604 long *dmsgnums, /* array of msg numbers to be deleted */
3605 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3606 char *content_type /* or "" for any. regular expressions expected. */
3609 struct ctdlroom qrbuf;
3610 struct cdbdata *cdbfr;
3611 long *msglist = NULL;
3612 long *dellist = NULL;
3615 int num_deleted = 0;
3617 struct MetaData smi;
3621 if (content_type) if (strlen(content_type) > 0) {
3622 regcomp(&re, content_type, 0);
3624 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3625 room_name, num_dmsgnums, content_type);
3627 /* get room record, obtaining a lock... */
3628 if (lgetroom(&qrbuf, room_name) != 0) {
3629 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3631 return (0); /* room not found */
3633 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3635 if (cdbfr != NULL) {
3636 dellist = malloc(cdbfr->len);
3637 msglist = (long *) cdbfr->ptr;
3638 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3639 num_msgs = cdbfr->len / sizeof(long);
3643 for (i = 0; i < num_msgs; ++i) {
3646 /* Set/clear a bit for each criterion */
3648 /* 0 messages in the list or a null list means that we are
3649 * interested in deleting any messages which meet the other criteria.
3651 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3652 delete_this |= 0x01;
3655 for (j=0; j<num_dmsgnums; ++j) {
3656 if (msglist[i] == dmsgnums[j]) {
3657 delete_this |= 0x01;
3662 if (strlen(content_type) == 0) {
3663 delete_this |= 0x02;
3665 GetMetaData(&smi, msglist[i]);
3666 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3667 delete_this |= 0x02;
3671 /* Delete message only if all bits are set */
3672 if (delete_this == 0x03) {
3673 dellist[num_deleted++] = msglist[i];
3678 num_msgs = sort_msglist(msglist, num_msgs);
3679 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3680 msglist, (int)(num_msgs * sizeof(long)));
3682 qrbuf.QRhighest = msglist[num_msgs - 1];
3686 /* Go through the messages we pulled out of the index, and decrement
3687 * their reference counts by 1. If this is the only room the message
3688 * was in, the reference count will reach zero and the message will
3689 * automatically be deleted from the database. We do this in a
3690 * separate pass because there might be plug-in hooks getting called,
3691 * and we don't want that happening during an S_ROOMS critical
3694 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3695 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3696 AdjRefCount(dellist[i], -1);
3699 /* Now free the memory we used, and go away. */
3700 if (msglist != NULL) free(msglist);
3701 if (dellist != NULL) free(dellist);
3702 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3703 return (num_deleted);
3709 * Check whether the current user has permission to delete messages from
3710 * the current room (returns 1 for yes, 0 for no)
3712 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3714 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3715 if (ra & UA_DELETEALLOWED) return(1);
3723 * Delete message from current room
3725 void cmd_dele(char *args)
3734 extract_token(msgset, args, 0, '|', sizeof msgset);
3735 num_msgs = num_tokens(msgset, ',');
3737 cprintf("%d Nothing to do.\n", CIT_OK);
3741 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3742 cprintf("%d Higher access required.\n",
3743 ERROR + HIGHER_ACCESS_REQUIRED);
3748 * Build our message set to be moved/copied
3750 msgs = malloc(num_msgs * sizeof(long));
3751 for (i=0; i<num_msgs; ++i) {
3752 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3753 msgs[i] = atol(msgtok);
3756 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3760 cprintf("%d %d message%s deleted.\n", CIT_OK,
3761 num_deleted, ((num_deleted != 1) ? "s" : ""));
3763 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3769 * Back end API function for moves and deletes (multiple messages)
3771 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3774 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3775 if (err != 0) return(err);
3784 * move or copy a message to another room
3786 void cmd_move(char *args)
3793 char targ[ROOMNAMELEN];
3794 struct ctdlroom qtemp;
3801 extract_token(msgset, args, 0, '|', sizeof msgset);
3802 num_msgs = num_tokens(msgset, ',');
3804 cprintf("%d Nothing to do.\n", CIT_OK);
3808 extract_token(targ, args, 1, '|', sizeof targ);
3809 convert_room_name_macros(targ, sizeof targ);
3810 targ[ROOMNAMELEN - 1] = 0;
3811 is_copy = extract_int(args, 2);
3813 if (getroom(&qtemp, targ) != 0) {
3814 cprintf("%d '%s' does not exist.\n",
3815 ERROR + ROOM_NOT_FOUND, targ);
3819 getuser(&CC->user, CC->curr_user);
3820 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3822 /* Check for permission to perform this operation.
3823 * Remember: "CC->room" is source, "qtemp" is target.
3827 /* Aides can move/copy */
3828 if (CC->user.axlevel >= 6) permit = 1;
3830 /* Room aides can move/copy */
3831 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3833 /* Permit move/copy from personal rooms */
3834 if ((CC->room.QRflags & QR_MAILBOX)
3835 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3837 /* Permit only copy from public to personal room */
3839 && (!(CC->room.QRflags & QR_MAILBOX))
3840 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3842 /* Permit message removal from collaborative delete rooms */
3843 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3845 /* User must have access to target room */
3846 if (!(ra & UA_KNOWN)) permit = 0;
3849 cprintf("%d Higher access required.\n",
3850 ERROR + HIGHER_ACCESS_REQUIRED);
3855 * Build our message set to be moved/copied
3857 msgs = malloc(num_msgs * sizeof(long));
3858 for (i=0; i<num_msgs; ++i) {
3859 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3860 msgs[i] = atol(msgtok);
3866 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3868 cprintf("%d Cannot store message(s) in %s: error %d\n",
3874 /* Now delete the message from the source room,
3875 * if this is a 'move' rather than a 'copy' operation.
3878 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3882 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3888 * GetMetaData() - Get the supplementary record for a message
3890 void GetMetaData(struct MetaData *smibuf, long msgnum)
3893 struct cdbdata *cdbsmi;
3896 memset(smibuf, 0, sizeof(struct MetaData));
3897 smibuf->meta_msgnum = msgnum;
3898 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3900 /* Use the negative of the message number for its supp record index */
3901 TheIndex = (0L - msgnum);
3903 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3904 if (cdbsmi == NULL) {
3905 return; /* record not found; go with defaults */
3907 memcpy(smibuf, cdbsmi->ptr,
3908 ((cdbsmi->len > sizeof(struct MetaData)) ?
3909 sizeof(struct MetaData) : cdbsmi->len));
3916 * PutMetaData() - (re)write supplementary record for a message
3918 void PutMetaData(struct MetaData *smibuf)
3922 /* Use the negative of the message number for the metadata db index */
3923 TheIndex = (0L - smibuf->meta_msgnum);
3925 cdb_store(CDB_MSGMAIN,
3926 &TheIndex, (int)sizeof(long),
3927 smibuf, (int)sizeof(struct MetaData));
3932 * AdjRefCount - submit an adjustment to the reference count for a message.
3933 * (These are just queued -- we actually process them later.)
3935 void AdjRefCount(long msgnum, int incr)
3937 struct arcq new_arcq;
3939 begin_critical_section(S_SUPPMSGMAIN);
3940 if (arcfp == NULL) {
3941 arcfp = fopen(file_arcq, "ab+");
3943 end_critical_section(S_SUPPMSGMAIN);
3945 /* msgnum < 0 means that we're trying to close the file */
3947 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3948 begin_critical_section(S_SUPPMSGMAIN);
3949 if (arcfp != NULL) {
3953 end_critical_section(S_SUPPMSGMAIN);
3958 * If we can't open the queue, perform the operation synchronously.
3960 if (arcfp == NULL) {
3961 TDAP_AdjRefCount(msgnum, incr);
3965 new_arcq.arcq_msgnum = msgnum;
3966 new_arcq.arcq_delta = incr;
3967 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3975 * TDAP_ProcessAdjRefCountQueue()
3977 * Process the queue of message count adjustments that was created by calls
3978 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3979 * for each one. This should be an "off hours" operation.
3981 int TDAP_ProcessAdjRefCountQueue(void)
3983 char file_arcq_temp[PATH_MAX];
3986 struct arcq arcq_rec;
3987 int num_records_processed = 0;
3989 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
3991 begin_critical_section(S_SUPPMSGMAIN);
3992 if (arcfp != NULL) {
3997 r = link(file_arcq, file_arcq_temp);
3999 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4000 end_critical_section(S_SUPPMSGMAIN);
4001 return(num_records_processed);
4005 end_critical_section(S_SUPPMSGMAIN);
4007 fp = fopen(file_arcq_temp, "rb");
4009 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4010 return(num_records_processed);
4013 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4014 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4015 ++num_records_processed;
4019 r = unlink(file_arcq_temp);
4021 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4024 return(num_records_processed);
4030 * TDAP_AdjRefCount - adjust the reference count for a message.
4031 * This one does it "for real" because it's called by
4032 * the autopurger function that processes the queue
4033 * created by AdjRefCount(). If a message's reference
4034 * count becomes zero, we also delete the message from
4035 * disk and de-index it.
4037 void TDAP_AdjRefCount(long msgnum, int incr)
4040 struct MetaData smi;
4043 /* This is a *tight* critical section; please keep it that way, as
4044 * it may get called while nested in other critical sections.
4045 * Complicating this any further will surely cause deadlock!
4047 begin_critical_section(S_SUPPMSGMAIN);
4048 GetMetaData(&smi, msgnum);
4049 smi.meta_refcount += incr;
4051 end_critical_section(S_SUPPMSGMAIN);
4052 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4053 msgnum, incr, smi.meta_refcount);
4055 /* If the reference count is now zero, delete the message
4056 * (and its supplementary record as well).
4058 if (smi.meta_refcount == 0) {
4059 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4061 /* Remove from fulltext index */
4062 if (config.c_enable_fulltext) {
4063 ft_index_message(msgnum, 0);
4066 /* Remove from message base */
4068 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4069 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4071 /* Remove metadata record */
4072 delnum = (0L - msgnum);
4073 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4079 * Write a generic object to this room
4081 * Note: this could be much more efficient. Right now we use two temporary
4082 * files, and still pull the message into memory as with all others.
4084 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4085 char *content_type, /* MIME type of this object */
4086 char *tempfilename, /* Where to fetch it from */
4087 struct ctdluser *is_mailbox, /* Mailbox room? */
4088 int is_binary, /* Is encoding necessary? */
4089 int is_unique, /* Del others of this type? */
4090 unsigned int flags /* Internal save flags */
4095 struct ctdlroom qrbuf;
4096 char roomname[ROOMNAMELEN];
4097 struct CtdlMessage *msg;
4099 char *raw_message = NULL;
4100 char *encoded_message = NULL;
4101 off_t raw_length = 0;
4103 if (is_mailbox != NULL) {
4104 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4107 safestrncpy(roomname, req_room, sizeof(roomname));
4110 fp = fopen(tempfilename, "rb");
4112 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4113 tempfilename, strerror(errno));
4116 fseek(fp, 0L, SEEK_END);
4117 raw_length = ftell(fp);
4119 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4121 raw_message = malloc((size_t)raw_length + 2);
4122 fread(raw_message, (size_t)raw_length, 1, fp);
4126 encoded_message = malloc((size_t)
4127 (((raw_length * 134) / 100) + 4096 ) );
4130 encoded_message = malloc((size_t)(raw_length + 4096));
4133 sprintf(encoded_message, "Content-type: %s\n", content_type);
4136 sprintf(&encoded_message[strlen(encoded_message)],
4137 "Content-transfer-encoding: base64\n\n"
4141 sprintf(&encoded_message[strlen(encoded_message)],
4142 "Content-transfer-encoding: 7bit\n\n"
4148 &encoded_message[strlen(encoded_message)],
4154 raw_message[raw_length] = 0;
4156 &encoded_message[strlen(encoded_message)],
4164 lprintf(CTDL_DEBUG, "Allocating\n");
4165 msg = malloc(sizeof(struct CtdlMessage));
4166 memset(msg, 0, sizeof(struct CtdlMessage));
4167 msg->cm_magic = CTDLMESSAGE_MAGIC;
4168 msg->cm_anon_type = MES_NORMAL;
4169 msg->cm_format_type = 4;
4170 msg->cm_fields['A'] = strdup(CC->user.fullname);
4171 msg->cm_fields['O'] = strdup(req_room);
4172 msg->cm_fields['N'] = strdup(config.c_nodename);
4173 msg->cm_fields['H'] = strdup(config.c_humannode);
4174 msg->cm_flags = flags;
4176 msg->cm_fields['M'] = encoded_message;
4178 /* Create the requested room if we have to. */
4179 if (getroom(&qrbuf, roomname) != 0) {
4180 create_room(roomname,
4181 ( (is_mailbox != NULL) ? 5 : 3 ),
4182 "", 0, 1, 0, VIEW_BBS);
4184 /* If the caller specified this object as unique, delete all
4185 * other objects of this type that are currently in the room.
4188 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4189 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4192 /* Now write the data */
4193 CtdlSubmitMsg(msg, NULL, roomname);
4194 CtdlFreeMessage(msg);
4202 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4203 config_msgnum = msgnum;
4207 char *CtdlGetSysConfig(char *sysconfname) {
4208 char hold_rm[ROOMNAMELEN];
4211 struct CtdlMessage *msg;
4214 strcpy(hold_rm, CC->room.QRname);
4215 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4216 getroom(&CC->room, hold_rm);
4221 /* We want the last (and probably only) config in this room */
4222 begin_critical_section(S_CONFIG);
4223 config_msgnum = (-1L);
4224 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4225 CtdlGetSysConfigBackend, NULL);
4226 msgnum = config_msgnum;
4227 end_critical_section(S_CONFIG);
4233 msg = CtdlFetchMessage(msgnum, 1);
4235 conf = strdup(msg->cm_fields['M']);
4236 CtdlFreeMessage(msg);
4243 getroom(&CC->room, hold_rm);
4245 if (conf != NULL) do {
4246 extract_token(buf, conf, 0, '\n', sizeof buf);
4247 strcpy(conf, &conf[strlen(buf)+1]);
4248 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4253 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4254 char temp[PATH_MAX];
4257 CtdlMakeTempFileName(temp, sizeof temp);
4259 fp = fopen(temp, "w");
4260 if (fp == NULL) return;
4261 fprintf(fp, "%s", sysconfdata);
4264 /* this handy API function does all the work for us */
4265 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4271 * Determine whether a given Internet address belongs to the current user
4273 int CtdlIsMe(char *addr, int addr_buf_len)
4275 struct recptypes *recp;
4278 recp = validate_recipients(addr);
4279 if (recp == NULL) return(0);
4281 if (recp->num_local == 0) {
4282 free_recipients(recp);
4286 for (i=0; i<recp->num_local; ++i) {
4287 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4288 if (!strcasecmp(addr, CC->user.fullname)) {
4289 free_recipients(recp);
4294 free_recipients(recp);
4300 * Citadel protocol command to do the same
4302 void cmd_isme(char *argbuf) {
4305 if (CtdlAccessCheck(ac_logged_in)) return;
4306 extract_token(addr, argbuf, 0, '|', sizeof addr);
4308 if (CtdlIsMe(addr, sizeof addr)) {
4309 cprintf("%d %s\n", CIT_OK, addr);
4312 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);