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;
533 int need_to_free_re = 0;
536 if (content_type) if (strlen(content_type) > 0) {
537 regcomp(&re, content_type, 0);
541 /* Learn about the user and room in question */
542 getuser(&CC->user, CC->curr_user);
543 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
545 /* Load the message list */
546 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
548 msglist = (long *) cdbfr->ptr;
549 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
550 num_msgs = cdbfr->len / sizeof(long);
553 if (need_to_free_re) regfree(&re);
554 return 0; /* No messages at all? No further action. */
559 * Now begin the traversal.
561 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
563 /* If the caller is looking for a specific MIME type, filter
564 * out all messages which are not of the type requested.
566 if (content_type != NULL) if (strlen(content_type) > 0) {
568 /* This call to GetMetaData() sits inside this loop
569 * so that we only do the extra database read per msg
570 * if we need to. Doing the extra read all the time
571 * really kills the server. If we ever need to use
572 * metadata for another search criterion, we need to
573 * move the read somewhere else -- but still be smart
574 * enough to only do the read if the caller has
575 * specified something that will need it.
577 GetMetaData(&smi, msglist[a]);
579 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
580 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
586 num_msgs = sort_msglist(msglist, num_msgs);
588 /* If a template was supplied, filter out the messages which
589 * don't match. (This could induce some delays!)
592 if (compare != NULL) {
593 for (a = 0; a < num_msgs; ++a) {
594 msg = CtdlFetchMessage(msglist[a], 1);
596 if (CtdlMsgCmp(msg, compare)) {
599 CtdlFreeMessage(msg);
605 /* If a search string was specified, get a message list from
606 * the full text index and remove messages which aren't on both
610 * Since the lists are sorted and strictly ascending, and the
611 * output list is guaranteed to be shorter than or equal to the
612 * input list, we overwrite the bottom of the input list. This
613 * eliminates the need to memmove big chunks of the list over and
616 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
617 ft_search(&num_search_msgs, &search_msgs, search_string);
618 if (num_search_msgs > 0) {
622 orig_num_msgs = num_msgs;
624 for (i=0; i<orig_num_msgs; ++i) {
625 for (j=0; j<num_search_msgs; ++j) {
626 if (msglist[i] == search_msgs[j]) {
627 msglist[num_msgs++] = msglist[i];
633 num_msgs = 0; /* No messages qualify */
635 if (search_msgs != NULL) free(search_msgs);
637 /* Now that we've purged messages which don't contain the search
638 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
645 * Now iterate through the message list, according to the
646 * criteria supplied by the caller.
649 for (a = 0; a < num_msgs; ++a) {
650 thismsg = msglist[a];
651 if (mode == MSGS_ALL) {
655 is_seen = is_msg_in_sequence_set(
656 vbuf.v_seen, thismsg);
657 if (is_seen) lastold = thismsg;
663 || ((mode == MSGS_OLD) && (is_seen))
664 || ((mode == MSGS_NEW) && (!is_seen))
665 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
666 || ((mode == MSGS_FIRST) && (a < ref))
667 || ((mode == MSGS_GT) && (thismsg > ref))
668 || ((mode == MSGS_EQ) && (thismsg == ref))
671 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
673 CallBack(lastold, userdata);
677 if (CallBack) CallBack(thismsg, userdata);
681 free(msglist); /* Clean up */
682 if (need_to_free_re) regfree(&re);
683 return num_processed;
689 * cmd_msgs() - get list of message #'s in this room
690 * implements the MSGS server command using CtdlForEachMessage()
692 void cmd_msgs(char *cmdbuf)
701 int with_template = 0;
702 struct CtdlMessage *template = NULL;
703 int with_headers = 0;
704 char search_string[1024];
706 extract_token(which, cmdbuf, 0, '|', sizeof which);
707 cm_ref = extract_int(cmdbuf, 1);
708 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
709 with_template = extract_int(cmdbuf, 2);
710 with_headers = extract_int(cmdbuf, 3);
713 if (!strncasecmp(which, "OLD", 3))
715 else if (!strncasecmp(which, "NEW", 3))
717 else if (!strncasecmp(which, "FIRST", 5))
719 else if (!strncasecmp(which, "LAST", 4))
721 else if (!strncasecmp(which, "GT", 2))
723 else if (!strncasecmp(which, "SEARCH", 6))
728 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
729 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
733 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
734 cprintf("%d Full text index is not enabled on this server.\n",
735 ERROR + CMD_NOT_SUPPORTED);
741 cprintf("%d Send template then receive message list\n",
743 template = (struct CtdlMessage *)
744 malloc(sizeof(struct CtdlMessage));
745 memset(template, 0, sizeof(struct CtdlMessage));
746 template->cm_magic = CTDLMESSAGE_MAGIC;
747 template->cm_anon_type = MES_NORMAL;
749 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
750 extract_token(tfield, buf, 0, '|', sizeof tfield);
751 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
752 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
753 if (!strcasecmp(tfield, msgkeys[i])) {
754 template->cm_fields[i] =
762 cprintf("%d \n", LISTING_FOLLOWS);
765 CtdlForEachMessage(mode,
766 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
767 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
770 (with_headers ? headers_listing : simple_listing),
773 if (template != NULL) CtdlFreeMessage(template);
781 * help_subst() - support routine for help file viewer
783 void help_subst(char *strbuf, char *source, char *dest)
788 while (p = pattern2(strbuf, source), (p >= 0)) {
789 strcpy(workbuf, &strbuf[p + strlen(source)]);
790 strcpy(&strbuf[p], dest);
791 strcat(strbuf, workbuf);
796 void do_help_subst(char *buffer)
800 help_subst(buffer, "^nodename", config.c_nodename);
801 help_subst(buffer, "^humannode", config.c_humannode);
802 help_subst(buffer, "^fqdn", config.c_fqdn);
803 help_subst(buffer, "^username", CC->user.fullname);
804 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
805 help_subst(buffer, "^usernum", buf2);
806 help_subst(buffer, "^sysadm", config.c_sysadm);
807 help_subst(buffer, "^variantname", CITADEL);
808 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
809 help_subst(buffer, "^maxsessions", buf2);
810 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
816 * memfmout() - Citadel text formatter and paginator.
817 * Although the original purpose of this routine was to format
818 * text to the reader's screen width, all we're really using it
819 * for here is to format text out to 80 columns before sending it
820 * to the client. The client software may reformat it again.
823 char *mptr, /* where are we going to get our text from? */
824 char subst, /* nonzero if we should do substitutions */
825 char *nl) /* string to terminate lines with */
833 static int width = 80;
838 c = 1; /* c is the current pos */
842 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
844 buffer[strlen(buffer) + 1] = 0;
845 buffer[strlen(buffer)] = ch;
848 if (buffer[0] == '^')
849 do_help_subst(buffer);
851 buffer[strlen(buffer) + 1] = 0;
853 strcpy(buffer, &buffer[1]);
861 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
864 if (((old == 13) || (old == 10)) && (isspace(real))) {
869 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
870 cprintf("%s%s", nl, aaa);
879 if ((strlen(aaa) + c) > (width - 5)) {
888 if ((ch == 13) || (ch == 10)) {
889 cprintf("%s%s", aaa, nl);
896 cprintf("%s%s", aaa, nl);
902 * Callback function for mime parser that simply lists the part
904 void list_this_part(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
910 ma = (struct ma_info *)cbuserdata;
911 if (ma->is_ma == 0) {
912 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
913 name, filename, partnum, disp, cbtype, (long)length);
918 * Callback function for multipart prefix
920 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
921 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
926 ma = (struct ma_info *)cbuserdata;
927 if (!strcasecmp(cbtype, "multipart/alternative")) {
931 if (ma->is_ma == 0) {
932 cprintf("pref=%s|%s\n", partnum, cbtype);
937 * Callback function for multipart sufffix
939 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
940 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
945 ma = (struct ma_info *)cbuserdata;
946 if (ma->is_ma == 0) {
947 cprintf("suff=%s|%s\n", partnum, cbtype);
949 if (!strcasecmp(cbtype, "multipart/alternative")) {
956 * Callback function for mime parser that opens a section for downloading
958 void mime_download(char *name, char *filename, char *partnum, char *disp,
959 void *content, char *cbtype, char *cbcharset, size_t length,
960 char *encoding, void *cbuserdata)
963 /* Silently go away if there's already a download open... */
964 if (CC->download_fp != NULL)
967 /* ...or if this is not the desired section */
968 if (strcasecmp(CC->download_desired_section, partnum))
971 CC->download_fp = tmpfile();
972 if (CC->download_fp == NULL)
975 fwrite(content, length, 1, CC->download_fp);
976 fflush(CC->download_fp);
977 rewind(CC->download_fp);
979 OpenCmdResult(filename, cbtype);
985 * Callback function for mime parser that outputs a section all at once
987 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
988 void *content, char *cbtype, char *cbcharset, size_t length,
989 char *encoding, void *cbuserdata)
991 int *found_it = (int *)cbuserdata;
993 /* ...or if this is not the desired section */
994 if (strcasecmp(CC->download_desired_section, partnum))
999 cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
1000 client_write(content, length);
1006 * Load a message from disk into memory.
1007 * This is used by CtdlOutputMsg() and other fetch functions.
1009 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1010 * using the CtdlMessageFree() function.
1012 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1014 struct cdbdata *dmsgtext;
1015 struct CtdlMessage *ret = NULL;
1019 cit_uint8_t field_header;
1021 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1023 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1024 if (dmsgtext == NULL) {
1027 mptr = dmsgtext->ptr;
1028 upper_bound = mptr + dmsgtext->len;
1030 /* Parse the three bytes that begin EVERY message on disk.
1031 * The first is always 0xFF, the on-disk magic number.
1032 * The second is the anonymous/public type byte.
1033 * The third is the format type byte (vari, fixed, or MIME).
1038 "Message %ld appears to be corrupted.\n",
1043 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1044 memset(ret, 0, sizeof(struct CtdlMessage));
1046 ret->cm_magic = CTDLMESSAGE_MAGIC;
1047 ret->cm_anon_type = *mptr++; /* Anon type byte */
1048 ret->cm_format_type = *mptr++; /* Format type byte */
1051 * The rest is zero or more arbitrary fields. Load them in.
1052 * We're done when we encounter either a zero-length field or
1053 * have just processed the 'M' (message text) field.
1056 if (mptr >= upper_bound) {
1059 field_header = *mptr++;
1060 ret->cm_fields[field_header] = strdup(mptr);
1062 while (*mptr++ != 0); /* advance to next field */
1064 } while ((mptr < upper_bound) && (field_header != 'M'));
1068 /* Always make sure there's something in the msg text field. If
1069 * it's NULL, the message text is most likely stored separately,
1070 * so go ahead and fetch that. Failing that, just set a dummy
1071 * body so other code doesn't barf.
1073 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1074 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1075 if (dmsgtext != NULL) {
1076 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1080 if (ret->cm_fields['M'] == NULL) {
1081 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1084 /* Perform "before read" hooks (aborting if any return nonzero) */
1085 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1086 CtdlFreeMessage(ret);
1095 * Returns 1 if the supplied pointer points to a valid Citadel message.
1096 * If the pointer is NULL or the magic number check fails, returns 0.
1098 int is_valid_message(struct CtdlMessage *msg) {
1101 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1102 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1110 * 'Destructor' for struct CtdlMessage
1112 void CtdlFreeMessage(struct CtdlMessage *msg)
1116 if (is_valid_message(msg) == 0)
1118 if (msg != NULL) free (msg);
1122 for (i = 0; i < 256; ++i)
1123 if (msg->cm_fields[i] != NULL) {
1124 free(msg->cm_fields[i]);
1127 msg->cm_magic = 0; /* just in case */
1133 * Pre callback function for multipart/alternative
1135 * NOTE: this differs from the standard behavior for a reason. Normally when
1136 * displaying multipart/alternative you want to show the _last_ usable
1137 * format in the message. Here we show the _first_ one, because it's
1138 * usually text/plain. Since this set of functions is designed for text
1139 * output to non-MIME-aware clients, this is the desired behavior.
1142 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1143 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1148 ma = (struct ma_info *)cbuserdata;
1149 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1150 if (!strcasecmp(cbtype, "multipart/alternative")) {
1154 if (!strcasecmp(cbtype, "message/rfc822")) {
1160 * Post callback function for multipart/alternative
1162 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1163 void *content, char *cbtype, char *cbcharset, size_t length,
1164 char *encoding, void *cbuserdata)
1168 ma = (struct ma_info *)cbuserdata;
1169 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1170 if (!strcasecmp(cbtype, "multipart/alternative")) {
1174 if (!strcasecmp(cbtype, "message/rfc822")) {
1180 * Inline callback function for mime parser that wants to display text
1182 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1183 void *content, char *cbtype, char *cbcharset, size_t length,
1184 char *encoding, void *cbuserdata)
1191 ma = (struct ma_info *)cbuserdata;
1194 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1195 partnum, filename, cbtype, (long)length);
1198 * If we're in the middle of a multipart/alternative scope and
1199 * we've already printed another section, skip this one.
1201 if ( (ma->is_ma) && (ma->did_print) ) {
1202 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1208 if ( (!strcasecmp(cbtype, "text/plain"))
1209 || (strlen(cbtype)==0) ) {
1212 client_write(wptr, length);
1213 if (wptr[length-1] != '\n') {
1220 if (!strcasecmp(cbtype, "text/html")) {
1221 ptr = html_to_ascii(content, length, 80, 0);
1223 client_write(ptr, wlen);
1224 if (ptr[wlen-1] != '\n') {
1231 if (ma->use_fo_hooks) {
1232 if (PerformFixedOutputHooks(cbtype, content, length)) {
1233 /* above function returns nonzero if it handled the part */
1238 if (strncasecmp(cbtype, "multipart/", 10)) {
1239 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1240 partnum, filename, cbtype, (long)length);
1246 * The client is elegant and sophisticated and wants to be choosy about
1247 * MIME content types, so figure out which multipart/alternative part
1248 * we're going to send.
1250 * We use a system of weights. When we find a part that matches one of the
1251 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1252 * and then set ma->chosen_pref to that MIME type's position in our preference
1253 * list. If we then hit another match, we only replace the first match if
1254 * the preference value is lower.
1256 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1257 void *content, char *cbtype, char *cbcharset, size_t length,
1258 char *encoding, void *cbuserdata)
1264 ma = (struct ma_info *)cbuserdata;
1266 if (ma->is_ma > 0) {
1267 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1268 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1269 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1270 if (i < ma->chosen_pref) {
1271 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1272 ma->chosen_pref = i;
1280 * Now that we've chosen our preferred part, output it.
1282 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1283 void *content, char *cbtype, char *cbcharset, size_t length,
1284 char *encoding, void *cbuserdata)
1288 int add_newline = 0;
1292 ma = (struct ma_info *)cbuserdata;
1294 /* This is not the MIME part you're looking for... */
1295 if (strcasecmp(partnum, ma->chosen_part)) return;
1297 /* If the content-type of this part is in our preferred formats
1298 * list, we can simply output it verbatim.
1300 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1301 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1302 if (!strcasecmp(buf, cbtype)) {
1303 /* Yeah! Go! W00t!! */
1305 text_content = (char *)content;
1306 if (text_content[length-1] != '\n') {
1309 cprintf("Content-type: %s", cbtype);
1310 if (strlen(cbcharset) > 0) {
1311 cprintf("; charset=%s", cbcharset);
1313 cprintf("\nContent-length: %d\n",
1314 (int)(length + add_newline) );
1315 if (strlen(encoding) > 0) {
1316 cprintf("Content-transfer-encoding: %s\n", encoding);
1319 cprintf("Content-transfer-encoding: 7bit\n");
1321 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1323 client_write(content, length);
1324 if (add_newline) cprintf("\n");
1329 /* No translations required or possible: output as text/plain */
1330 cprintf("Content-type: text/plain\n\n");
1331 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1332 length, encoding, cbuserdata);
1337 char desired_section[64];
1344 * Callback function for
1346 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1347 void *content, char *cbtype, char *cbcharset, size_t length,
1348 char *encoding, void *cbuserdata)
1350 struct encapmsg *encap;
1352 encap = (struct encapmsg *)cbuserdata;
1354 /* Only proceed if this is the desired section... */
1355 if (!strcasecmp(encap->desired_section, partnum)) {
1356 encap->msglen = length;
1357 encap->msg = malloc(length + 2);
1358 memcpy(encap->msg, content, length);
1368 * Get a message off disk. (returns om_* values found in msgbase.h)
1371 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1372 int mode, /* how would you like that message? */
1373 int headers_only, /* eschew the message body? */
1374 int do_proto, /* do Citadel protocol responses? */
1375 int crlf, /* Use CRLF newlines instead of LF? */
1376 char *section /* NULL or a message/rfc822 section */
1378 struct CtdlMessage *TheMessage = NULL;
1379 int retcode = om_no_such_msg;
1380 struct encapmsg encap;
1382 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1384 (section ? section : "<>")
1387 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1388 if (do_proto) cprintf("%d Not logged in.\n",
1389 ERROR + NOT_LOGGED_IN);
1390 return(om_not_logged_in);
1393 /* FIXME: check message id against msglist for this room */
1396 * Fetch the message from disk. If we're in any sort of headers
1397 * only mode, request that we don't even bother loading the body
1400 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1401 TheMessage = CtdlFetchMessage(msg_num, 0);
1404 TheMessage = CtdlFetchMessage(msg_num, 1);
1407 if (TheMessage == NULL) {
1408 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1409 ERROR + MESSAGE_NOT_FOUND, msg_num);
1410 return(om_no_such_msg);
1413 /* Here is the weird form of this command, to process only an
1414 * encapsulated message/rfc822 section.
1416 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1417 memset(&encap, 0, sizeof encap);
1418 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1419 mime_parser(TheMessage->cm_fields['M'],
1421 *extract_encapsulated_message,
1422 NULL, NULL, (void *)&encap, 0
1424 CtdlFreeMessage(TheMessage);
1428 encap.msg[encap.msglen] = 0;
1429 TheMessage = convert_internet_message(encap.msg);
1430 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1432 /* Now we let it fall through to the bottom of this
1433 * function, because TheMessage now contains the
1434 * encapsulated message instead of the top-level
1435 * message. Isn't that neat?
1440 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1441 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1442 retcode = om_no_such_msg;
1447 /* Ok, output the message now */
1448 retcode = CtdlOutputPreLoadedMsg(
1450 headers_only, do_proto, crlf);
1451 CtdlFreeMessage(TheMessage);
1458 * Get a message off disk. (returns om_* values found in msgbase.h)
1461 int CtdlOutputPreLoadedMsg(
1462 struct CtdlMessage *TheMessage,
1463 int mode, /* how would you like that message? */
1464 int headers_only, /* eschew the message body? */
1465 int do_proto, /* do Citadel protocol responses? */
1466 int crlf /* Use CRLF newlines instead of LF? */
1472 char display_name[256];
1474 char *nl; /* newline string */
1476 int subject_found = 0;
1479 /* Buffers needed for RFC822 translation. These are all filled
1480 * using functions that are bounds-checked, and therefore we can
1481 * make them substantially smaller than SIZ.
1489 char datestamp[100];
1491 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1492 ((TheMessage == NULL) ? "NULL" : "not null"),
1493 mode, headers_only, do_proto, crlf);
1495 strcpy(mid, "unknown");
1496 nl = (crlf ? "\r\n" : "\n");
1498 if (!is_valid_message(TheMessage)) {
1500 "ERROR: invalid preloaded message for output\n");
1501 return(om_no_such_msg);
1504 /* Are we downloading a MIME component? */
1505 if (mode == MT_DOWNLOAD) {
1506 if (TheMessage->cm_format_type != FMT_RFC822) {
1508 cprintf("%d This is not a MIME message.\n",
1509 ERROR + ILLEGAL_VALUE);
1510 } else if (CC->download_fp != NULL) {
1511 if (do_proto) cprintf(
1512 "%d You already have a download open.\n",
1513 ERROR + RESOURCE_BUSY);
1515 /* Parse the message text component */
1516 mptr = TheMessage->cm_fields['M'];
1517 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1518 /* If there's no file open by this time, the requested
1519 * section wasn't found, so print an error
1521 if (CC->download_fp == NULL) {
1522 if (do_proto) cprintf(
1523 "%d Section %s not found.\n",
1524 ERROR + FILE_NOT_FOUND,
1525 CC->download_desired_section);
1528 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1531 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1532 * in a single server operation instead of opening a download file.
1534 if (mode == MT_SPEW_SECTION) {
1535 if (TheMessage->cm_format_type != FMT_RFC822) {
1537 cprintf("%d This is not a MIME message.\n",
1538 ERROR + ILLEGAL_VALUE);
1540 /* Parse the message text component */
1543 mptr = TheMessage->cm_fields['M'];
1544 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1545 /* If section wasn't found, print an error
1548 if (do_proto) cprintf(
1549 "%d Section %s not found.\n",
1550 ERROR + FILE_NOT_FOUND,
1551 CC->download_desired_section);
1554 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1557 /* now for the user-mode message reading loops */
1558 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1560 /* Does the caller want to skip the headers? */
1561 if (headers_only == HEADERS_NONE) goto START_TEXT;
1563 /* Tell the client which format type we're using. */
1564 if ( (mode == MT_CITADEL) && (do_proto) ) {
1565 cprintf("type=%d\n", TheMessage->cm_format_type);
1568 /* nhdr=yes means that we're only displaying headers, no body */
1569 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1570 && (mode == MT_CITADEL)
1573 cprintf("nhdr=yes\n");
1576 /* begin header processing loop for Citadel message format */
1578 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1580 safestrncpy(display_name, "<unknown>", sizeof display_name);
1581 if (TheMessage->cm_fields['A']) {
1582 strcpy(buf, TheMessage->cm_fields['A']);
1583 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1584 safestrncpy(display_name, "****", sizeof display_name);
1586 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1587 safestrncpy(display_name, "anonymous", sizeof display_name);
1590 safestrncpy(display_name, buf, sizeof display_name);
1592 if ((is_room_aide())
1593 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1594 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1595 size_t tmp = strlen(display_name);
1596 snprintf(&display_name[tmp],
1597 sizeof display_name - tmp,
1602 /* Don't show Internet address for users on the
1603 * local Citadel network.
1606 if (TheMessage->cm_fields['N'] != NULL)
1607 if (strlen(TheMessage->cm_fields['N']) > 0)
1608 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1612 /* Now spew the header fields in the order we like them. */
1613 safestrncpy(allkeys, FORDER, sizeof allkeys);
1614 for (i=0; i<strlen(allkeys); ++i) {
1615 k = (int) allkeys[i];
1617 if ( (TheMessage->cm_fields[k] != NULL)
1618 && (msgkeys[k] != NULL) ) {
1620 if (do_proto) cprintf("%s=%s\n",
1624 else if ((k == 'F') && (suppress_f)) {
1627 /* Masquerade display name if needed */
1629 if (do_proto) cprintf("%s=%s\n",
1631 TheMessage->cm_fields[k]
1640 /* begin header processing loop for RFC822 transfer format */
1645 strcpy(snode, NODENAME);
1646 strcpy(lnode, HUMANNODE);
1647 if (mode == MT_RFC822) {
1648 for (i = 0; i < 256; ++i) {
1649 if (TheMessage->cm_fields[i]) {
1650 mptr = TheMessage->cm_fields[i];
1653 safestrncpy(luser, mptr, sizeof luser);
1654 safestrncpy(suser, mptr, sizeof suser);
1656 else if (i == 'Y') {
1657 cprintf("CC: %s%s", mptr, nl);
1659 else if (i == 'P') {
1660 cprintf("Return-Path: %s%s", mptr, nl);
1662 else if (i == 'V') {
1663 cprintf("Envelope-To: %s%s", mptr, nl);
1665 else if (i == 'U') {
1666 cprintf("Subject: %s%s", mptr, nl);
1670 safestrncpy(mid, mptr, sizeof mid);
1672 safestrncpy(lnode, mptr, sizeof lnode);
1674 safestrncpy(fuser, mptr, sizeof fuser);
1675 /* else if (i == 'O')
1676 cprintf("X-Citadel-Room: %s%s",
1679 safestrncpy(snode, mptr, sizeof snode);
1681 cprintf("To: %s%s", mptr, nl);
1682 else if (i == 'T') {
1683 datestring(datestamp, sizeof datestamp,
1684 atol(mptr), DATESTRING_RFC822);
1685 cprintf("Date: %s%s", datestamp, nl);
1689 if (subject_found == 0) {
1690 cprintf("Subject: (no subject)%s", nl);
1694 for (i=0; i<strlen(suser); ++i) {
1695 suser[i] = tolower(suser[i]);
1696 if (!isalnum(suser[i])) suser[i]='_';
1699 if (mode == MT_RFC822) {
1700 if (!strcasecmp(snode, NODENAME)) {
1701 safestrncpy(snode, FQDN, sizeof snode);
1704 /* Construct a fun message id */
1705 cprintf("Message-ID: <%s", mid);
1706 if (strchr(mid, '@')==NULL) {
1707 cprintf("@%s", snode);
1711 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1712 cprintf("From: \"----\" <x@x.org>%s", nl);
1714 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1715 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1717 else if (strlen(fuser) > 0) {
1718 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1721 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1724 cprintf("Organization: %s%s", lnode, nl);
1726 /* Blank line signifying RFC822 end-of-headers */
1727 if (TheMessage->cm_format_type != FMT_RFC822) {
1732 /* end header processing loop ... at this point, we're in the text */
1734 if (headers_only == HEADERS_FAST) goto DONE;
1735 mptr = TheMessage->cm_fields['M'];
1737 /* Tell the client about the MIME parts in this message */
1738 if (TheMessage->cm_format_type == FMT_RFC822) {
1739 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1740 memset(&ma, 0, sizeof(struct ma_info));
1741 mime_parser(mptr, NULL,
1742 (do_proto ? *list_this_part : NULL),
1743 (do_proto ? *list_this_pref : NULL),
1744 (do_proto ? *list_this_suff : NULL),
1747 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1748 char *start_of_text = NULL;
1749 start_of_text = strstr(mptr, "\n\r\n");
1750 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1751 if (start_of_text == NULL) start_of_text = mptr;
1753 start_of_text = strstr(start_of_text, "\n");
1758 int nllen = strlen(nl);
1759 while (ch=*mptr, ch!=0) {
1765 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1766 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1767 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1770 sprintf(&outbuf[outlen], "%s", nl);
1774 outbuf[outlen++] = ch;
1779 if (outlen > 1000) {
1780 client_write(outbuf, outlen);
1785 client_write(outbuf, outlen);
1793 if (headers_only == HEADERS_ONLY) {
1797 /* signify start of msg text */
1798 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1799 if (do_proto) cprintf("text\n");
1802 /* If the format type on disk is 1 (fixed-format), then we want
1803 * everything to be output completely literally ... regardless of
1804 * what message transfer format is in use.
1806 if (TheMessage->cm_format_type == FMT_FIXED) {
1807 if (mode == MT_MIME) {
1808 cprintf("Content-type: text/plain\n\n");
1811 while (ch = *mptr++, ch > 0) {
1814 if ((ch == 10) || (strlen(buf) > 250)) {
1815 cprintf("%s%s", buf, nl);
1818 buf[strlen(buf) + 1] = 0;
1819 buf[strlen(buf)] = ch;
1822 if (strlen(buf) > 0)
1823 cprintf("%s%s", buf, nl);
1826 /* If the message on disk is format 0 (Citadel vari-format), we
1827 * output using the formatter at 80 columns. This is the final output
1828 * form if the transfer format is RFC822, but if the transfer format
1829 * is Citadel proprietary, it'll still work, because the indentation
1830 * for new paragraphs is correct and the client will reformat the
1831 * message to the reader's screen width.
1833 if (TheMessage->cm_format_type == FMT_CITADEL) {
1834 if (mode == MT_MIME) {
1835 cprintf("Content-type: text/x-citadel-variformat\n\n");
1837 memfmout(mptr, 0, nl);
1840 /* If the message on disk is format 4 (MIME), we've gotta hand it
1841 * off to the MIME parser. The client has already been told that
1842 * this message is format 1 (fixed format), so the callback function
1843 * we use will display those parts as-is.
1845 if (TheMessage->cm_format_type == FMT_RFC822) {
1846 memset(&ma, 0, sizeof(struct ma_info));
1848 if (mode == MT_MIME) {
1849 ma.use_fo_hooks = 0;
1850 strcpy(ma.chosen_part, "1");
1851 ma.chosen_pref = 9999;
1852 mime_parser(mptr, NULL,
1853 *choose_preferred, *fixed_output_pre,
1854 *fixed_output_post, (void *)&ma, 0);
1855 mime_parser(mptr, NULL,
1856 *output_preferred, NULL, NULL, (void *)&ma, 0);
1859 ma.use_fo_hooks = 1;
1860 mime_parser(mptr, NULL,
1861 *fixed_output, *fixed_output_pre,
1862 *fixed_output_post, (void *)&ma, 0);
1867 DONE: /* now we're done */
1868 if (do_proto) cprintf("000\n");
1875 * display a message (mode 0 - Citadel proprietary)
1877 void cmd_msg0(char *cmdbuf)
1880 int headers_only = HEADERS_ALL;
1882 msgid = extract_long(cmdbuf, 0);
1883 headers_only = extract_int(cmdbuf, 1);
1885 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1891 * display a message (mode 2 - RFC822)
1893 void cmd_msg2(char *cmdbuf)
1896 int headers_only = HEADERS_ALL;
1898 msgid = extract_long(cmdbuf, 0);
1899 headers_only = extract_int(cmdbuf, 1);
1901 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1907 * display a message (mode 3 - IGnet raw format - internal programs only)
1909 void cmd_msg3(char *cmdbuf)
1912 struct CtdlMessage *msg = NULL;
1915 if (CC->internal_pgm == 0) {
1916 cprintf("%d This command is for internal programs only.\n",
1917 ERROR + HIGHER_ACCESS_REQUIRED);
1921 msgnum = extract_long(cmdbuf, 0);
1922 msg = CtdlFetchMessage(msgnum, 1);
1924 cprintf("%d Message %ld not found.\n",
1925 ERROR + MESSAGE_NOT_FOUND, msgnum);
1929 serialize_message(&smr, msg);
1930 CtdlFreeMessage(msg);
1933 cprintf("%d Unable to serialize message\n",
1934 ERROR + INTERNAL_ERROR);
1938 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1939 client_write((char *)smr.ser, (int)smr.len);
1946 * Display a message using MIME content types
1948 void cmd_msg4(char *cmdbuf)
1953 msgid = extract_long(cmdbuf, 0);
1954 extract_token(section, cmdbuf, 1, '|', sizeof section);
1955 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1961 * Client tells us its preferred message format(s)
1963 void cmd_msgp(char *cmdbuf)
1965 safestrncpy(CC->preferred_formats, cmdbuf,
1966 sizeof(CC->preferred_formats));
1967 cprintf("%d ok\n", CIT_OK);
1972 * Open a component of a MIME message as a download file
1974 void cmd_opna(char *cmdbuf)
1977 char desired_section[128];
1979 msgid = extract_long(cmdbuf, 0);
1980 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1981 safestrncpy(CC->download_desired_section, desired_section,
1982 sizeof CC->download_desired_section);
1983 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1988 * Open a component of a MIME message and transmit it all at once
1990 void cmd_dlat(char *cmdbuf)
1993 char desired_section[128];
1995 msgid = extract_long(cmdbuf, 0);
1996 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1997 safestrncpy(CC->download_desired_section, desired_section,
1998 sizeof CC->download_desired_section);
1999 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2004 * Save one or more message pointers into a specified room
2005 * (Returns 0 for success, nonzero for failure)
2006 * roomname may be NULL to use the current room
2008 * Note that the 'supplied_msg' field may be set to NULL, in which case
2009 * the message will be fetched from disk, by number, if we need to perform
2010 * replication checks. This adds an additional database read, so if the
2011 * caller already has the message in memory then it should be supplied. (Obviously
2012 * this mode of operation only works if we're saving a single message.)
2014 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2015 int do_repl_check, struct CtdlMessage *supplied_msg)
2018 char hold_rm[ROOMNAMELEN];
2019 struct cdbdata *cdbfr;
2022 long highest_msg = 0L;
2025 struct CtdlMessage *msg = NULL;
2027 long *msgs_to_be_merged = NULL;
2028 int num_msgs_to_be_merged = 0;
2031 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2032 roomname, num_newmsgs, do_repl_check);
2034 strcpy(hold_rm, CC->room.QRname);
2037 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2038 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2039 if (num_newmsgs > 1) supplied_msg = NULL;
2041 /* Now the regular stuff */
2042 if (lgetroom(&CC->room,
2043 ((roomname != NULL) ? roomname : CC->room.QRname) )
2045 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2046 return(ERROR + ROOM_NOT_FOUND);
2050 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2051 num_msgs_to_be_merged = 0;
2054 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2055 if (cdbfr == NULL) {
2059 msglist = (long *) cdbfr->ptr;
2060 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2061 num_msgs = cdbfr->len / sizeof(long);
2066 /* Create a list of msgid's which were supplied by the caller, but do
2067 * not already exist in the target room. It is absolutely taboo to
2068 * have more than one reference to the same message in a room.
2070 for (i=0; i<num_newmsgs; ++i) {
2072 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2073 if (msglist[j] == newmsgidlist[i]) {
2078 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2082 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2085 * Now merge the new messages
2087 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2088 if (msglist == NULL) {
2089 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2091 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2092 num_msgs += num_msgs_to_be_merged;
2094 /* Sort the message list, so all the msgid's are in order */
2095 num_msgs = sort_msglist(msglist, num_msgs);
2097 /* Determine the highest message number */
2098 highest_msg = msglist[num_msgs - 1];
2100 /* Write it back to disk. */
2101 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2102 msglist, (int)(num_msgs * sizeof(long)));
2104 /* Free up the memory we used. */
2107 /* Update the highest-message pointer and unlock the room. */
2108 CC->room.QRhighest = highest_msg;
2109 lputroom(&CC->room);
2111 /* Perform replication checks if necessary */
2112 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2113 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2115 for (i=0; i<num_msgs_to_be_merged; ++i) {
2116 msgid = msgs_to_be_merged[i];
2118 if (supplied_msg != NULL) {
2122 msg = CtdlFetchMessage(msgid, 0);
2126 ReplicationChecks(msg);
2128 /* If the message has an Exclusive ID, index that... */
2129 if (msg->cm_fields['E'] != NULL) {
2130 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2133 /* Free up the memory we may have allocated */
2134 if (msg != supplied_msg) {
2135 CtdlFreeMessage(msg);
2143 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2146 /* Submit this room for net processing */
2147 network_queue_room(&CC->room, NULL);
2149 #ifdef HAVE_LIBSIEVE
2150 /* If this is someone's inbox, submit the room for sieve processing */
2151 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2152 sieve_queue_room(&CC->room);
2154 #endif /* HAVE_LIBSIEVE */
2156 /* Go back to the room we were in before we wandered here... */
2157 getroom(&CC->room, hold_rm);
2159 /* Bump the reference count for all messages which were merged */
2160 for (i=0; i<num_msgs_to_be_merged; ++i) {
2161 AdjRefCount(msgs_to_be_merged[i], +1);
2164 /* Free up memory... */
2165 if (msgs_to_be_merged != NULL) {
2166 free(msgs_to_be_merged);
2169 /* Return success. */
2175 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2178 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2179 int do_repl_check, struct CtdlMessage *supplied_msg)
2181 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2188 * Message base operation to save a new message to the message store
2189 * (returns new message number)
2191 * This is the back end for CtdlSubmitMsg() and should not be directly
2192 * called by server-side modules.
2195 long send_message(struct CtdlMessage *msg) {
2203 /* Get a new message number */
2204 newmsgid = get_new_message_number();
2205 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2207 /* Generate an ID if we don't have one already */
2208 if (msg->cm_fields['I']==NULL) {
2209 msg->cm_fields['I'] = strdup(msgidbuf);
2212 /* If the message is big, set its body aside for storage elsewhere */
2213 if (msg->cm_fields['M'] != NULL) {
2214 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2216 holdM = msg->cm_fields['M'];
2217 msg->cm_fields['M'] = NULL;
2221 /* Serialize our data structure for storage in the database */
2222 serialize_message(&smr, msg);
2225 msg->cm_fields['M'] = holdM;
2229 cprintf("%d Unable to serialize message\n",
2230 ERROR + INTERNAL_ERROR);
2234 /* Write our little bundle of joy into the message base */
2235 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2236 smr.ser, smr.len) < 0) {
2237 lprintf(CTDL_ERR, "Can't store message\n");
2241 cdb_store(CDB_BIGMSGS,
2251 /* Free the memory we used for the serialized message */
2254 /* Return the *local* message ID to the caller
2255 * (even if we're storing an incoming network message)
2263 * Serialize a struct CtdlMessage into the format used on disk and network.
2265 * This function loads up a "struct ser_ret" (defined in server.h) which
2266 * contains the length of the serialized message and a pointer to the
2267 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2269 void serialize_message(struct ser_ret *ret, /* return values */
2270 struct CtdlMessage *msg) /* unserialized msg */
2272 size_t wlen, fieldlen;
2274 static char *forder = FORDER;
2277 * Check for valid message format
2279 if (is_valid_message(msg) == 0) {
2280 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2287 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2288 ret->len = ret->len +
2289 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2291 ret->ser = malloc(ret->len);
2292 if (ret->ser == NULL) {
2293 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2294 (long)ret->len, strerror(errno));
2301 ret->ser[1] = msg->cm_anon_type;
2302 ret->ser[2] = msg->cm_format_type;
2305 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2306 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2307 ret->ser[wlen++] = (char)forder[i];
2308 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2309 wlen = wlen + fieldlen + 1;
2311 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2312 (long)ret->len, (long)wlen);
2320 * Check to see if any messages already exist in the current room which
2321 * carry the same Exclusive ID as this one. If any are found, delete them.
2323 void ReplicationChecks(struct CtdlMessage *msg) {
2324 long old_msgnum = (-1L);
2326 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2328 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2331 /* No exclusive id? Don't do anything. */
2332 if (msg == NULL) return;
2333 if (msg->cm_fields['E'] == NULL) return;
2334 if (strlen(msg->cm_fields['E']) == 0) return;
2335 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2336 msg->cm_fields['E'], CC->room.QRname);*/
2338 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2339 if (old_msgnum > 0L) {
2340 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2341 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2348 * Save a message to disk and submit it into the delivery system.
2350 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2351 struct recptypes *recps, /* recipients (if mail) */
2352 char *force /* force a particular room? */
2354 char submit_filename[128];
2355 char generated_timestamp[32];
2356 char hold_rm[ROOMNAMELEN];
2357 char actual_rm[ROOMNAMELEN];
2358 char force_room[ROOMNAMELEN];
2359 char content_type[SIZ]; /* We have to learn this */
2360 char recipient[SIZ];
2363 struct ctdluser userbuf;
2365 struct MetaData smi;
2366 FILE *network_fp = NULL;
2367 static int seqnum = 1;
2368 struct CtdlMessage *imsg = NULL;
2370 size_t instr_alloc = 0;
2372 char *hold_R, *hold_D;
2373 char *collected_addresses = NULL;
2374 struct addresses_to_be_filed *aptr = NULL;
2375 char *saved_rfc822_version = NULL;
2376 int qualified_for_journaling = 0;
2378 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2379 if (is_valid_message(msg) == 0) return(-1); /* self check */
2381 /* If this message has no timestamp, we take the liberty of
2382 * giving it one, right now.
2384 if (msg->cm_fields['T'] == NULL) {
2385 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2386 msg->cm_fields['T'] = strdup(generated_timestamp);
2389 /* If this message has no path, we generate one.
2391 if (msg->cm_fields['P'] == NULL) {
2392 if (msg->cm_fields['A'] != NULL) {
2393 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2394 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2395 if (isspace(msg->cm_fields['P'][a])) {
2396 msg->cm_fields['P'][a] = ' ';
2401 msg->cm_fields['P'] = strdup("unknown");
2405 if (force == NULL) {
2406 strcpy(force_room, "");
2409 strcpy(force_room, force);
2412 /* Learn about what's inside, because it's what's inside that counts */
2413 if (msg->cm_fields['M'] == NULL) {
2414 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2418 switch (msg->cm_format_type) {
2420 strcpy(content_type, "text/x-citadel-variformat");
2423 strcpy(content_type, "text/plain");
2426 strcpy(content_type, "text/plain");
2427 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2429 safestrncpy(content_type, &mptr[13], sizeof content_type);
2430 striplt(content_type);
2431 for (a = 0; a < strlen(content_type); ++a) {
2432 if ((content_type[a] == ';')
2433 || (content_type[a] == ' ')
2434 || (content_type[a] == 13)
2435 || (content_type[a] == 10)) {
2436 content_type[a] = 0;
2442 /* Goto the correct room */
2443 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2444 strcpy(hold_rm, CC->room.QRname);
2445 strcpy(actual_rm, CC->room.QRname);
2446 if (recps != NULL) {
2447 strcpy(actual_rm, SENTITEMS);
2450 /* If the user is a twit, move to the twit room for posting */
2452 if (CC->user.axlevel == 2) {
2453 strcpy(hold_rm, actual_rm);
2454 strcpy(actual_rm, config.c_twitroom);
2455 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2459 /* ...or if this message is destined for Aide> then go there. */
2460 if (strlen(force_room) > 0) {
2461 strcpy(actual_rm, force_room);
2464 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2465 if (strcasecmp(actual_rm, CC->room.QRname)) {
2466 /* getroom(&CC->room, actual_rm); */
2467 usergoto(actual_rm, 0, 1, NULL, NULL);
2471 * If this message has no O (room) field, generate one.
2473 if (msg->cm_fields['O'] == NULL) {
2474 msg->cm_fields['O'] = strdup(CC->room.QRname);
2477 /* Perform "before save" hooks (aborting if any return nonzero) */
2478 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2479 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2482 * If this message has an Exclusive ID, and the room is replication
2483 * checking enabled, then do replication checks.
2485 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2486 ReplicationChecks(msg);
2489 /* Save it to disk */
2490 lprintf(CTDL_DEBUG, "Saving to disk\n");
2491 newmsgid = send_message(msg);
2492 if (newmsgid <= 0L) return(-5);
2494 /* Write a supplemental message info record. This doesn't have to
2495 * be a critical section because nobody else knows about this message
2498 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2499 memset(&smi, 0, sizeof(struct MetaData));
2500 smi.meta_msgnum = newmsgid;
2501 smi.meta_refcount = 0;
2502 safestrncpy(smi.meta_content_type, content_type,
2503 sizeof smi.meta_content_type);
2506 * Measure how big this message will be when rendered as RFC822.
2507 * We do this for two reasons:
2508 * 1. We need the RFC822 length for the new metadata record, so the
2509 * POP and IMAP services don't have to calculate message lengths
2510 * while the user is waiting (multiplied by potentially hundreds
2511 * or thousands of messages).
2512 * 2. If journaling is enabled, we will need an RFC822 version of the
2513 * message to attach to the journalized copy.
2515 if (CC->redirect_buffer != NULL) {
2516 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2519 CC->redirect_buffer = malloc(SIZ);
2520 CC->redirect_len = 0;
2521 CC->redirect_alloc = SIZ;
2522 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2523 smi.meta_rfc822_length = CC->redirect_len;
2524 saved_rfc822_version = CC->redirect_buffer;
2525 CC->redirect_buffer = NULL;
2526 CC->redirect_len = 0;
2527 CC->redirect_alloc = 0;
2531 /* Now figure out where to store the pointers */
2532 lprintf(CTDL_DEBUG, "Storing pointers\n");
2534 /* If this is being done by the networker delivering a private
2535 * message, we want to BYPASS saving the sender's copy (because there
2536 * is no local sender; it would otherwise go to the Trashcan).
2538 if ((!CC->internal_pgm) || (recps == NULL)) {
2539 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2540 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2541 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2545 /* For internet mail, drop a copy in the outbound queue room */
2547 if (recps->num_internet > 0) {
2548 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2551 /* If other rooms are specified, drop them there too. */
2553 if (recps->num_room > 0)
2554 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2555 extract_token(recipient, recps->recp_room, i,
2556 '|', sizeof recipient);
2557 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2558 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2561 /* Bump this user's messages posted counter. */
2562 lprintf(CTDL_DEBUG, "Updating user\n");
2563 lgetuser(&CC->user, CC->curr_user);
2564 CC->user.posted = CC->user.posted + 1;
2565 lputuser(&CC->user);
2567 /* If this is private, local mail, make a copy in the
2568 * recipient's mailbox and bump the reference count.
2571 if (recps->num_local > 0)
2572 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2573 extract_token(recipient, recps->recp_local, i,
2574 '|', sizeof recipient);
2575 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2577 if (getuser(&userbuf, recipient) == 0) {
2578 // Add a flag so the Funambol module knows its mail
2579 msg->cm_fields['W'] = strdup(recipient);
2580 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2581 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2582 BumpNewMailCounter(userbuf.usernum);
2583 if (strlen(config.c_funambol_host) > 0) {
2584 /* Generate a instruction message for the Funambol notification
2585 * server, in the same style as the SMTP queue
2588 instr = malloc(instr_alloc);
2589 snprintf(instr, instr_alloc,
2590 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2592 SPOOLMIME, newmsgid, (long)time(NULL),
2593 msg->cm_fields['A'], msg->cm_fields['N']
2596 imsg = malloc(sizeof(struct CtdlMessage));
2597 memset(imsg, 0, sizeof(struct CtdlMessage));
2598 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2599 imsg->cm_anon_type = MES_NORMAL;
2600 imsg->cm_format_type = FMT_RFC822;
2601 imsg->cm_fields['A'] = strdup("Citadel");
2602 imsg->cm_fields['J'] = strdup("do not journal");
2603 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2604 imsg->cm_fields['W'] = strdup(recipient);
2605 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2606 CtdlFreeMessage(imsg);
2610 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2611 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2616 /* Perform "after save" hooks */
2617 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2618 PerformMessageHooks(msg, EVT_AFTERSAVE);
2620 /* For IGnet mail, we have to save a new copy into the spooler for
2621 * each recipient, with the R and D fields set to the recipient and
2622 * destination-node. This has two ugly side effects: all other
2623 * recipients end up being unlisted in this recipient's copy of the
2624 * message, and it has to deliver multiple messages to the same
2625 * node. We'll revisit this again in a year or so when everyone has
2626 * a network spool receiver that can handle the new style messages.
2629 if (recps->num_ignet > 0)
2630 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2631 extract_token(recipient, recps->recp_ignet, i,
2632 '|', sizeof recipient);
2634 hold_R = msg->cm_fields['R'];
2635 hold_D = msg->cm_fields['D'];
2636 msg->cm_fields['R'] = malloc(SIZ);
2637 msg->cm_fields['D'] = malloc(128);
2638 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2639 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2641 serialize_message(&smr, msg);
2643 snprintf(submit_filename, sizeof submit_filename,
2644 "%s/netmail.%04lx.%04x.%04x",
2646 (long) getpid(), CC->cs_pid, ++seqnum);
2647 network_fp = fopen(submit_filename, "wb+");
2648 if (network_fp != NULL) {
2649 fwrite(smr.ser, smr.len, 1, network_fp);
2655 free(msg->cm_fields['R']);
2656 free(msg->cm_fields['D']);
2657 msg->cm_fields['R'] = hold_R;
2658 msg->cm_fields['D'] = hold_D;
2661 /* Go back to the room we started from */
2662 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2663 if (strcasecmp(hold_rm, CC->room.QRname))
2664 usergoto(hold_rm, 0, 1, NULL, NULL);
2666 /* For internet mail, generate delivery instructions.
2667 * Yes, this is recursive. Deal with it. Infinite recursion does
2668 * not happen because the delivery instructions message does not
2669 * contain a recipient.
2672 if (recps->num_internet > 0) {
2673 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2675 instr = malloc(instr_alloc);
2676 snprintf(instr, instr_alloc,
2677 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2679 SPOOLMIME, newmsgid, (long)time(NULL),
2680 msg->cm_fields['A'], msg->cm_fields['N']
2683 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2684 size_t tmp = strlen(instr);
2685 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2686 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2687 instr_alloc = instr_alloc * 2;
2688 instr = realloc(instr, instr_alloc);
2690 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2693 imsg = malloc(sizeof(struct CtdlMessage));
2694 memset(imsg, 0, sizeof(struct CtdlMessage));
2695 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2696 imsg->cm_anon_type = MES_NORMAL;
2697 imsg->cm_format_type = FMT_RFC822;
2698 imsg->cm_fields['A'] = strdup("Citadel");
2699 imsg->cm_fields['J'] = strdup("do not journal");
2700 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2701 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2702 CtdlFreeMessage(imsg);
2706 * Any addresses to harvest for someone's address book?
2708 if ( (CC->logged_in) && (recps != NULL) ) {
2709 collected_addresses = harvest_collected_addresses(msg);
2712 if (collected_addresses != NULL) {
2713 begin_critical_section(S_ATBF);
2714 aptr = (struct addresses_to_be_filed *)
2715 malloc(sizeof(struct addresses_to_be_filed));
2717 MailboxName(actual_rm, sizeof actual_rm,
2718 &CC->user, USERCONTACTSROOM);
2719 aptr->roomname = strdup(actual_rm);
2720 aptr->collected_addresses = collected_addresses;
2722 end_critical_section(S_ATBF);
2726 * Determine whether this message qualifies for journaling.
2728 if (msg->cm_fields['J'] != NULL) {
2729 qualified_for_journaling = 0;
2732 if (recps == NULL) {
2733 qualified_for_journaling = config.c_journal_pubmsgs;
2735 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2736 qualified_for_journaling = config.c_journal_email;
2739 qualified_for_journaling = config.c_journal_pubmsgs;
2744 * Do we have to perform journaling? If so, hand off the saved
2745 * RFC822 version will be handed off to the journaler for background
2746 * submit. Otherwise, we have to free the memory ourselves.
2748 if (saved_rfc822_version != NULL) {
2749 if (qualified_for_journaling) {
2750 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2753 free(saved_rfc822_version);
2766 * Convenience function for generating small administrative messages.
2768 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2769 int format_type, char *subject)
2771 struct CtdlMessage *msg;
2772 struct recptypes *recp = NULL;
2774 msg = malloc(sizeof(struct CtdlMessage));
2775 memset(msg, 0, sizeof(struct CtdlMessage));
2776 msg->cm_magic = CTDLMESSAGE_MAGIC;
2777 msg->cm_anon_type = MES_NORMAL;
2778 msg->cm_format_type = format_type;
2781 msg->cm_fields['A'] = strdup(from);
2783 else if (fromaddr != NULL) {
2784 msg->cm_fields['A'] = strdup(fromaddr);
2785 if (strchr(msg->cm_fields['A'], '@')) {
2786 *strchr(msg->cm_fields['A'], '@') = 0;
2790 msg->cm_fields['A'] = strdup("Citadel");
2793 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2794 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2795 msg->cm_fields['N'] = strdup(NODENAME);
2797 msg->cm_fields['R'] = strdup(to);
2798 recp = validate_recipients(to);
2800 if (subject != NULL) {
2801 msg->cm_fields['U'] = strdup(subject);
2803 msg->cm_fields['M'] = strdup(text);
2805 CtdlSubmitMsg(msg, recp, room);
2806 CtdlFreeMessage(msg);
2807 if (recp != NULL) free_recipients(recp);
2813 * Back end function used by CtdlMakeMessage() and similar functions
2815 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2816 size_t maxlen, /* maximum message length */
2817 char *exist, /* if non-null, append to it;
2818 exist is ALWAYS freed */
2819 int crlf /* CRLF newlines instead of LF */
2823 size_t message_len = 0;
2824 size_t buffer_len = 0;
2831 if (exist == NULL) {
2838 message_len = strlen(exist);
2839 buffer_len = message_len + 4096;
2840 m = realloc(exist, buffer_len);
2847 /* Do we need to change leading ".." to "." for SMTP escaping? */
2848 if (!strcmp(terminator, ".")) {
2852 /* flush the input if we have nowhere to store it */
2857 /* read in the lines of message text one by one */
2859 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2860 if (!strcmp(buf, terminator)) finished = 1;
2862 strcat(buf, "\r\n");
2868 /* Unescape SMTP-style input of two dots at the beginning of the line */
2870 if (!strncmp(buf, "..", 2)) {
2871 strcpy(buf, &buf[1]);
2875 if ( (!flushing) && (!finished) ) {
2876 /* Measure the line */
2877 linelen = strlen(buf);
2879 /* augment the buffer if we have to */
2880 if ((message_len + linelen) >= buffer_len) {
2881 ptr = realloc(m, (buffer_len * 2) );
2882 if (ptr == NULL) { /* flush if can't allocate */
2885 buffer_len = (buffer_len * 2);
2887 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2891 /* Add the new line to the buffer. NOTE: this loop must avoid
2892 * using functions like strcat() and strlen() because they
2893 * traverse the entire buffer upon every call, and doing that
2894 * for a multi-megabyte message slows it down beyond usability.
2896 strcpy(&m[message_len], buf);
2897 message_len += linelen;
2900 /* if we've hit the max msg length, flush the rest */
2901 if (message_len >= maxlen) flushing = 1;
2903 } while (!finished);
2911 * Build a binary message to be saved on disk.
2912 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2913 * will become part of the message. This means you are no longer
2914 * responsible for managing that memory -- it will be freed along with
2915 * the rest of the fields when CtdlFreeMessage() is called.)
2918 struct CtdlMessage *CtdlMakeMessage(
2919 struct ctdluser *author, /* author's user structure */
2920 char *recipient, /* NULL if it's not mail */
2921 char *recp_cc, /* NULL if it's not mail */
2922 char *room, /* room where it's going */
2923 int type, /* see MES_ types in header file */
2924 int format_type, /* variformat, plain text, MIME... */
2925 char *fake_name, /* who we're masquerading as */
2926 char *my_email, /* which of my email addresses to use (empty is ok) */
2927 char *subject, /* Subject (optional) */
2928 char *supplied_euid, /* ...or NULL if this is irrelevant */
2929 char *preformatted_text /* ...or NULL to read text from client */
2931 char dest_node[256];
2933 struct CtdlMessage *msg;
2936 msg = malloc(sizeof(struct CtdlMessage));
2937 memset(msg, 0, sizeof(struct CtdlMessage));
2938 msg->cm_magic = CTDLMESSAGE_MAGIC;
2939 msg->cm_anon_type = type;
2940 msg->cm_format_type = format_type;
2942 /* Don't confuse the poor folks if it's not routed mail. */
2943 strcpy(dest_node, "");
2948 /* Path or Return-Path */
2949 if (my_email == NULL) my_email = "";
2951 if (strlen(my_email) > 0) {
2952 msg->cm_fields['P'] = strdup(my_email);
2955 snprintf(buf, sizeof buf, "%s", author->fullname);
2956 msg->cm_fields['P'] = strdup(buf);
2958 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
2959 if (isspace(msg->cm_fields['P'][i])) {
2960 msg->cm_fields['P'][i] = '_';
2964 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2965 msg->cm_fields['T'] = strdup(buf);
2967 if (fake_name[0]) /* author */
2968 msg->cm_fields['A'] = strdup(fake_name);
2970 msg->cm_fields['A'] = strdup(author->fullname);
2972 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2973 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2976 msg->cm_fields['O'] = strdup(CC->room.QRname);
2979 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2980 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2982 if (recipient[0] != 0) {
2983 msg->cm_fields['R'] = strdup(recipient);
2985 if (recp_cc[0] != 0) {
2986 msg->cm_fields['Y'] = strdup(recp_cc);
2988 if (dest_node[0] != 0) {
2989 msg->cm_fields['D'] = strdup(dest_node);
2992 if (strlen(my_email) > 0) {
2993 msg->cm_fields['F'] = strdup(my_email);
2995 else if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2996 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2999 if (subject != NULL) {
3002 length = strlen(subject);
3008 while ((subject[i] != '\0') &&
3009 (IsAscii = isascii(subject[i]) != 0 ))
3012 msg->cm_fields['U'] = strdup(subject);
3013 else /* ok, we've got utf8 in the string. */
3015 msg->cm_fields['U'] = rfc2047encode(subject, length);
3021 if (supplied_euid != NULL) {
3022 msg->cm_fields['E'] = strdup(supplied_euid);
3025 if (preformatted_text != NULL) {
3026 msg->cm_fields['M'] = preformatted_text;
3029 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
3037 * Check to see whether we have permission to post a message in the current
3038 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3039 * returns 0 on success.
3041 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3044 if (!(CC->logged_in)) {
3045 snprintf(errmsgbuf, n, "Not logged in.");
3046 return (ERROR + NOT_LOGGED_IN);
3049 if ((CC->user.axlevel < 2)
3050 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3051 snprintf(errmsgbuf, n, "Need to be validated to enter "
3052 "(except in %s> to sysop)", MAILROOM);
3053 return (ERROR + HIGHER_ACCESS_REQUIRED);
3056 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3057 if (!(ra & UA_POSTALLOWED)) {
3058 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3059 return (ERROR + HIGHER_ACCESS_REQUIRED);
3062 strcpy(errmsgbuf, "Ok");
3068 * Check to see if the specified user has Internet mail permission
3069 * (returns nonzero if permission is granted)
3071 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3073 /* Do not allow twits to send Internet mail */
3074 if (who->axlevel <= 2) return(0);
3076 /* Globally enabled? */
3077 if (config.c_restrict == 0) return(1);
3079 /* User flagged ok? */
3080 if (who->flags & US_INTERNET) return(2);
3082 /* Aide level access? */
3083 if (who->axlevel >= 6) return(3);
3085 /* No mail for you! */
3091 * Validate recipients, count delivery types and errors, and handle aliasing
3092 * FIXME check for dupes!!!!!
3094 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3095 * were specified, or the number of addresses found invalid.
3097 * Caller needs to free the result using free_recipients()
3099 struct recptypes *validate_recipients(char *supplied_recipients) {
3100 struct recptypes *ret;
3101 char *recipients = NULL;
3102 char this_recp[256];
3103 char this_recp_cooked[256];
3109 struct ctdluser tempUS;
3110 struct ctdlroom tempQR;
3114 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3115 if (ret == NULL) return(NULL);
3117 /* Set all strings to null and numeric values to zero */
3118 memset(ret, 0, sizeof(struct recptypes));
3120 if (supplied_recipients == NULL) {
3121 recipients = strdup("");
3124 recipients = strdup(supplied_recipients);
3127 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3128 * actually need, but it's healthier for the heap than doing lots of tiny
3129 * realloc() calls instead.
3132 ret->errormsg = malloc(strlen(recipients) + 1024);
3133 ret->recp_local = malloc(strlen(recipients) + 1024);
3134 ret->recp_internet = malloc(strlen(recipients) + 1024);
3135 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3136 ret->recp_room = malloc(strlen(recipients) + 1024);
3137 ret->display_recp = malloc(strlen(recipients) + 1024);
3139 ret->errormsg[0] = 0;
3140 ret->recp_local[0] = 0;
3141 ret->recp_internet[0] = 0;
3142 ret->recp_ignet[0] = 0;
3143 ret->recp_room[0] = 0;
3144 ret->display_recp[0] = 0;
3146 ret->recptypes_magic = RECPTYPES_MAGIC;
3148 /* Change all valid separator characters to commas */
3149 for (i=0; i<strlen(recipients); ++i) {
3150 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3151 recipients[i] = ',';
3155 /* Now start extracting recipients... */
3157 while (strlen(recipients) > 0) {
3159 for (i=0; i<=strlen(recipients); ++i) {
3160 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3161 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3162 safestrncpy(this_recp, recipients, i+1);
3164 if (recipients[i] == ',') {
3165 strcpy(recipients, &recipients[i+1]);
3168 strcpy(recipients, "");
3175 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3177 mailtype = alias(this_recp);
3178 mailtype = alias(this_recp);
3179 mailtype = alias(this_recp);
3180 for (j=0; j<=strlen(this_recp); ++j) {
3181 if (this_recp[j]=='_') {
3182 this_recp_cooked[j] = ' ';
3185 this_recp_cooked[j] = this_recp[j];
3191 if (!strcasecmp(this_recp, "sysop")) {
3193 strcpy(this_recp, config.c_aideroom);
3194 if (strlen(ret->recp_room) > 0) {
3195 strcat(ret->recp_room, "|");
3197 strcat(ret->recp_room, this_recp);
3199 else if (getuser(&tempUS, this_recp) == 0) {
3201 strcpy(this_recp, tempUS.fullname);
3202 if (strlen(ret->recp_local) > 0) {
3203 strcat(ret->recp_local, "|");
3205 strcat(ret->recp_local, this_recp);
3207 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3209 strcpy(this_recp, tempUS.fullname);
3210 if (strlen(ret->recp_local) > 0) {
3211 strcat(ret->recp_local, "|");
3213 strcat(ret->recp_local, this_recp);
3215 else if ( (!strncasecmp(this_recp, "room_", 5))
3216 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3218 if (strlen(ret->recp_room) > 0) {
3219 strcat(ret->recp_room, "|");
3221 strcat(ret->recp_room, &this_recp_cooked[5]);
3229 /* Yes, you're reading this correctly: if the target
3230 * domain points back to the local system or an attached
3231 * Citadel directory, the address is invalid. That's
3232 * because if the address were valid, we would have
3233 * already translated it to a local address by now.
3235 if (IsDirectory(this_recp, 0)) {
3240 ++ret->num_internet;
3241 if (strlen(ret->recp_internet) > 0) {
3242 strcat(ret->recp_internet, "|");
3244 strcat(ret->recp_internet, this_recp);
3249 if (strlen(ret->recp_ignet) > 0) {
3250 strcat(ret->recp_ignet, "|");
3252 strcat(ret->recp_ignet, this_recp);
3260 if (strlen(ret->errormsg) == 0) {
3261 snprintf(append, sizeof append,
3262 "Invalid recipient: %s",
3266 snprintf(append, sizeof append, ", %s", this_recp);
3268 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3269 strcat(ret->errormsg, append);
3273 if (strlen(ret->display_recp) == 0) {
3274 strcpy(append, this_recp);
3277 snprintf(append, sizeof append, ", %s", this_recp);
3279 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3280 strcat(ret->display_recp, append);
3285 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3286 ret->num_room + ret->num_error) == 0) {
3287 ret->num_error = (-1);
3288 strcpy(ret->errormsg, "No recipients specified.");
3291 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3292 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3293 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3294 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3295 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3296 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3304 * Destructor for struct recptypes
3306 void free_recipients(struct recptypes *valid) {
3308 if (valid == NULL) {
3312 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3313 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3317 if (valid->errormsg != NULL) free(valid->errormsg);
3318 if (valid->recp_local != NULL) free(valid->recp_local);
3319 if (valid->recp_internet != NULL) free(valid->recp_internet);
3320 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3321 if (valid->recp_room != NULL) free(valid->recp_room);
3322 if (valid->display_recp != NULL) free(valid->display_recp);
3329 * message entry - mode 0 (normal)
3331 void cmd_ent0(char *entargs)
3337 char supplied_euid[128];
3339 int format_type = 0;
3340 char newusername[256];
3341 char newuseremail[256];
3342 struct CtdlMessage *msg;
3346 struct recptypes *valid = NULL;
3347 struct recptypes *valid_to = NULL;
3348 struct recptypes *valid_cc = NULL;
3349 struct recptypes *valid_bcc = NULL;
3351 int subject_required = 0;
3356 int newuseremail_ok = 0;
3360 post = extract_int(entargs, 0);
3361 extract_token(recp, entargs, 1, '|', sizeof recp);
3362 anon_flag = extract_int(entargs, 2);
3363 format_type = extract_int(entargs, 3);
3364 extract_token(subject, entargs, 4, '|', sizeof subject);
3365 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3366 do_confirm = extract_int(entargs, 6);
3367 extract_token(cc, entargs, 7, '|', sizeof cc);
3368 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3369 switch(CC->room.QRdefaultview) {
3372 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3375 supplied_euid[0] = 0;
3378 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3380 /* first check to make sure the request is valid. */
3382 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3385 cprintf("%d %s\n", err, errmsg);
3389 /* Check some other permission type things. */
3391 if (strlen(newusername) == 0) {
3392 strcpy(newusername, CC->user.fullname);
3394 if ( (CC->user.axlevel < 6)
3395 && (strcasecmp(newusername, CC->user.fullname))
3396 && (strcasecmp(newusername, CC->cs_inet_fn))
3398 cprintf("%d You don't have permission to author messages as '%s'.\n",
3399 ERROR + HIGHER_ACCESS_REQUIRED,
3406 if (strlen(newuseremail) == 0) {
3407 newuseremail_ok = 1;
3410 if (strlen(newuseremail) > 0) {
3411 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3412 newuseremail_ok = 1;
3414 else if (strlen(CC->cs_inet_other_emails) > 0) {
3415 j = num_tokens(CC->cs_inet_other_emails, '|');
3416 for (i=0; i<j; ++i) {
3417 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3418 if (!strcasecmp(newuseremail, buf)) {
3419 newuseremail_ok = 1;
3425 if (!newuseremail_ok) {
3426 cprintf("%d You don't have permission to author messages as '%s'.\n",
3427 ERROR + HIGHER_ACCESS_REQUIRED,
3433 CC->cs_flags |= CS_POSTING;
3435 /* In mailbox rooms we have to behave a little differently --
3436 * make sure the user has specified at least one recipient. Then
3437 * validate the recipient(s). We do this for the Mail> room, as
3438 * well as any room which has the "Mailbox" view set.
3441 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3442 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3444 if (CC->user.axlevel < 2) {
3445 strcpy(recp, "sysop");
3450 valid_to = validate_recipients(recp);
3451 if (valid_to->num_error > 0) {
3452 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3453 free_recipients(valid_to);
3457 valid_cc = validate_recipients(cc);
3458 if (valid_cc->num_error > 0) {
3459 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3460 free_recipients(valid_to);
3461 free_recipients(valid_cc);
3465 valid_bcc = validate_recipients(bcc);
3466 if (valid_bcc->num_error > 0) {
3467 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3468 free_recipients(valid_to);
3469 free_recipients(valid_cc);
3470 free_recipients(valid_bcc);
3474 /* Recipient required, but none were specified */
3475 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3476 free_recipients(valid_to);
3477 free_recipients(valid_cc);
3478 free_recipients(valid_bcc);
3479 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3483 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3484 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3485 cprintf("%d You do not have permission "
3486 "to send Internet mail.\n",
3487 ERROR + HIGHER_ACCESS_REQUIRED);
3488 free_recipients(valid_to);
3489 free_recipients(valid_cc);
3490 free_recipients(valid_bcc);
3495 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)
3496 && (CC->user.axlevel < 4) ) {
3497 cprintf("%d Higher access required for network mail.\n",
3498 ERROR + HIGHER_ACCESS_REQUIRED);
3499 free_recipients(valid_to);
3500 free_recipients(valid_cc);
3501 free_recipients(valid_bcc);
3505 if ((RESTRICT_INTERNET == 1)
3506 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3507 && ((CC->user.flags & US_INTERNET) == 0)
3508 && (!CC->internal_pgm)) {
3509 cprintf("%d You don't have access to Internet mail.\n",
3510 ERROR + HIGHER_ACCESS_REQUIRED);
3511 free_recipients(valid_to);
3512 free_recipients(valid_cc);
3513 free_recipients(valid_bcc);
3519 /* Is this a room which has anonymous-only or anonymous-option? */
3520 anonymous = MES_NORMAL;
3521 if (CC->room.QRflags & QR_ANONONLY) {
3522 anonymous = MES_ANONONLY;
3524 if (CC->room.QRflags & QR_ANONOPT) {
3525 if (anon_flag == 1) { /* only if the user requested it */
3526 anonymous = MES_ANONOPT;
3530 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3534 /* Recommend to the client that the use of a message subject is
3535 * strongly recommended in this room, if either the SUBJECTREQ flag
3536 * is set, or if there is one or more Internet email recipients.
3538 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3539 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3540 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3541 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3543 /* If we're only checking the validity of the request, return
3544 * success without creating the message.
3547 cprintf("%d %s|%d\n", CIT_OK,
3548 ((valid_to != NULL) ? valid_to->display_recp : ""),
3550 free_recipients(valid_to);
3551 free_recipients(valid_cc);
3552 free_recipients(valid_bcc);
3556 /* We don't need these anymore because we'll do it differently below */
3557 free_recipients(valid_to);
3558 free_recipients(valid_cc);
3559 free_recipients(valid_bcc);
3561 /* Read in the message from the client. */
3563 cprintf("%d send message\n", START_CHAT_MODE);
3565 cprintf("%d send message\n", SEND_LISTING);
3568 msg = CtdlMakeMessage(&CC->user, recp, cc,
3569 CC->room.QRname, anonymous, format_type,
3570 newusername, newuseremail, subject,
3571 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3574 /* Put together one big recipients struct containing to/cc/bcc all in
3575 * one. This is for the envelope.
3577 char *all_recps = malloc(SIZ * 3);
3578 strcpy(all_recps, recp);
3579 if (strlen(cc) > 0) {
3580 if (strlen(all_recps) > 0) {
3581 strcat(all_recps, ",");
3583 strcat(all_recps, cc);
3585 if (strlen(bcc) > 0) {
3586 if (strlen(all_recps) > 0) {
3587 strcat(all_recps, ",");
3589 strcat(all_recps, bcc);
3591 if (strlen(all_recps) > 0) {
3592 valid = validate_recipients(all_recps);
3600 msgnum = CtdlSubmitMsg(msg, valid, "");
3603 cprintf("%ld\n", msgnum);
3605 cprintf("Message accepted.\n");
3608 cprintf("Internal error.\n");
3610 if (msg->cm_fields['E'] != NULL) {
3611 cprintf("%s\n", msg->cm_fields['E']);
3618 CtdlFreeMessage(msg);
3620 if (valid != NULL) {
3621 free_recipients(valid);
3629 * API function to delete messages which match a set of criteria
3630 * (returns the actual number of messages deleted)
3632 int CtdlDeleteMessages(char *room_name, /* which room */
3633 long *dmsgnums, /* array of msg numbers to be deleted */
3634 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3635 char *content_type /* or "" for any. regular expressions expected. */
3638 struct ctdlroom qrbuf;
3639 struct cdbdata *cdbfr;
3640 long *msglist = NULL;
3641 long *dellist = NULL;
3644 int num_deleted = 0;
3646 struct MetaData smi;
3649 int need_to_free_re = 0;
3651 if (content_type) if (strlen(content_type) > 0) {
3652 regcomp(&re, content_type, 0);
3653 need_to_free_re = 1;
3655 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3656 room_name, num_dmsgnums, content_type);
3658 /* get room record, obtaining a lock... */
3659 if (lgetroom(&qrbuf, room_name) != 0) {
3660 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3662 if (need_to_free_re) regfree(&re);
3663 return (0); /* room not found */
3665 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3667 if (cdbfr != NULL) {
3668 dellist = malloc(cdbfr->len);
3669 msglist = (long *) cdbfr->ptr;
3670 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3671 num_msgs = cdbfr->len / sizeof(long);
3675 for (i = 0; i < num_msgs; ++i) {
3678 /* Set/clear a bit for each criterion */
3680 /* 0 messages in the list or a null list means that we are
3681 * interested in deleting any messages which meet the other criteria.
3683 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3684 delete_this |= 0x01;
3687 for (j=0; j<num_dmsgnums; ++j) {
3688 if (msglist[i] == dmsgnums[j]) {
3689 delete_this |= 0x01;
3694 if (strlen(content_type) == 0) {
3695 delete_this |= 0x02;
3697 GetMetaData(&smi, msglist[i]);
3698 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3699 delete_this |= 0x02;
3703 /* Delete message only if all bits are set */
3704 if (delete_this == 0x03) {
3705 dellist[num_deleted++] = msglist[i];
3710 num_msgs = sort_msglist(msglist, num_msgs);
3711 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3712 msglist, (int)(num_msgs * sizeof(long)));
3714 qrbuf.QRhighest = msglist[num_msgs - 1];
3718 /* Go through the messages we pulled out of the index, and decrement
3719 * their reference counts by 1. If this is the only room the message
3720 * was in, the reference count will reach zero and the message will
3721 * automatically be deleted from the database. We do this in a
3722 * separate pass because there might be plug-in hooks getting called,
3723 * and we don't want that happening during an S_ROOMS critical
3726 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3727 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3728 AdjRefCount(dellist[i], -1);
3731 /* Now free the memory we used, and go away. */
3732 if (msglist != NULL) free(msglist);
3733 if (dellist != NULL) free(dellist);
3734 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3735 if (need_to_free_re) regfree(&re);
3736 return (num_deleted);
3742 * Check whether the current user has permission to delete messages from
3743 * the current room (returns 1 for yes, 0 for no)
3745 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3747 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3748 if (ra & UA_DELETEALLOWED) return(1);
3756 * Delete message from current room
3758 void cmd_dele(char *args)
3767 extract_token(msgset, args, 0, '|', sizeof msgset);
3768 num_msgs = num_tokens(msgset, ',');
3770 cprintf("%d Nothing to do.\n", CIT_OK);
3774 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3775 cprintf("%d Higher access required.\n",
3776 ERROR + HIGHER_ACCESS_REQUIRED);
3781 * Build our message set to be moved/copied
3783 msgs = malloc(num_msgs * sizeof(long));
3784 for (i=0; i<num_msgs; ++i) {
3785 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3786 msgs[i] = atol(msgtok);
3789 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3793 cprintf("%d %d message%s deleted.\n", CIT_OK,
3794 num_deleted, ((num_deleted != 1) ? "s" : ""));
3796 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3802 * Back end API function for moves and deletes (multiple messages)
3804 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3807 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3808 if (err != 0) return(err);
3817 * move or copy a message to another room
3819 void cmd_move(char *args)
3826 char targ[ROOMNAMELEN];
3827 struct ctdlroom qtemp;
3834 extract_token(msgset, args, 0, '|', sizeof msgset);
3835 num_msgs = num_tokens(msgset, ',');
3837 cprintf("%d Nothing to do.\n", CIT_OK);
3841 extract_token(targ, args, 1, '|', sizeof targ);
3842 convert_room_name_macros(targ, sizeof targ);
3843 targ[ROOMNAMELEN - 1] = 0;
3844 is_copy = extract_int(args, 2);
3846 if (getroom(&qtemp, targ) != 0) {
3847 cprintf("%d '%s' does not exist.\n",
3848 ERROR + ROOM_NOT_FOUND, targ);
3852 getuser(&CC->user, CC->curr_user);
3853 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3855 /* Check for permission to perform this operation.
3856 * Remember: "CC->room" is source, "qtemp" is target.
3860 /* Aides can move/copy */
3861 if (CC->user.axlevel >= 6) permit = 1;
3863 /* Room aides can move/copy */
3864 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3866 /* Permit move/copy from personal rooms */
3867 if ((CC->room.QRflags & QR_MAILBOX)
3868 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3870 /* Permit only copy from public to personal room */
3872 && (!(CC->room.QRflags & QR_MAILBOX))
3873 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3875 /* Permit message removal from collaborative delete rooms */
3876 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3878 /* User must have access to target room */
3879 if (!(ra & UA_KNOWN)) permit = 0;
3882 cprintf("%d Higher access required.\n",
3883 ERROR + HIGHER_ACCESS_REQUIRED);
3888 * Build our message set to be moved/copied
3890 msgs = malloc(num_msgs * sizeof(long));
3891 for (i=0; i<num_msgs; ++i) {
3892 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3893 msgs[i] = atol(msgtok);
3899 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3901 cprintf("%d Cannot store message(s) in %s: error %d\n",
3907 /* Now delete the message from the source room,
3908 * if this is a 'move' rather than a 'copy' operation.
3911 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3915 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3921 * GetMetaData() - Get the supplementary record for a message
3923 void GetMetaData(struct MetaData *smibuf, long msgnum)
3926 struct cdbdata *cdbsmi;
3929 memset(smibuf, 0, sizeof(struct MetaData));
3930 smibuf->meta_msgnum = msgnum;
3931 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3933 /* Use the negative of the message number for its supp record index */
3934 TheIndex = (0L - msgnum);
3936 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3937 if (cdbsmi == NULL) {
3938 return; /* record not found; go with defaults */
3940 memcpy(smibuf, cdbsmi->ptr,
3941 ((cdbsmi->len > sizeof(struct MetaData)) ?
3942 sizeof(struct MetaData) : cdbsmi->len));
3949 * PutMetaData() - (re)write supplementary record for a message
3951 void PutMetaData(struct MetaData *smibuf)
3955 /* Use the negative of the message number for the metadata db index */
3956 TheIndex = (0L - smibuf->meta_msgnum);
3958 cdb_store(CDB_MSGMAIN,
3959 &TheIndex, (int)sizeof(long),
3960 smibuf, (int)sizeof(struct MetaData));
3965 * AdjRefCount - submit an adjustment to the reference count for a message.
3966 * (These are just queued -- we actually process them later.)
3968 void AdjRefCount(long msgnum, int incr)
3970 struct arcq new_arcq;
3972 begin_critical_section(S_SUPPMSGMAIN);
3973 if (arcfp == NULL) {
3974 arcfp = fopen(file_arcq, "ab+");
3976 end_critical_section(S_SUPPMSGMAIN);
3978 /* msgnum < 0 means that we're trying to close the file */
3980 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3981 begin_critical_section(S_SUPPMSGMAIN);
3982 if (arcfp != NULL) {
3986 end_critical_section(S_SUPPMSGMAIN);
3991 * If we can't open the queue, perform the operation synchronously.
3993 if (arcfp == NULL) {
3994 TDAP_AdjRefCount(msgnum, incr);
3998 new_arcq.arcq_msgnum = msgnum;
3999 new_arcq.arcq_delta = incr;
4000 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4008 * TDAP_ProcessAdjRefCountQueue()
4010 * Process the queue of message count adjustments that was created by calls
4011 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4012 * for each one. This should be an "off hours" operation.
4014 int TDAP_ProcessAdjRefCountQueue(void)
4016 char file_arcq_temp[PATH_MAX];
4019 struct arcq arcq_rec;
4020 int num_records_processed = 0;
4022 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4024 begin_critical_section(S_SUPPMSGMAIN);
4025 if (arcfp != NULL) {
4030 r = link(file_arcq, file_arcq_temp);
4032 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4033 end_critical_section(S_SUPPMSGMAIN);
4034 return(num_records_processed);
4038 end_critical_section(S_SUPPMSGMAIN);
4040 fp = fopen(file_arcq_temp, "rb");
4042 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4043 return(num_records_processed);
4046 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4047 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4048 ++num_records_processed;
4052 r = unlink(file_arcq_temp);
4054 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4057 return(num_records_processed);
4063 * TDAP_AdjRefCount - adjust the reference count for a message.
4064 * This one does it "for real" because it's called by
4065 * the autopurger function that processes the queue
4066 * created by AdjRefCount(). If a message's reference
4067 * count becomes zero, we also delete the message from
4068 * disk and de-index it.
4070 void TDAP_AdjRefCount(long msgnum, int incr)
4073 struct MetaData smi;
4076 /* This is a *tight* critical section; please keep it that way, as
4077 * it may get called while nested in other critical sections.
4078 * Complicating this any further will surely cause deadlock!
4080 begin_critical_section(S_SUPPMSGMAIN);
4081 GetMetaData(&smi, msgnum);
4082 smi.meta_refcount += incr;
4084 end_critical_section(S_SUPPMSGMAIN);
4085 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4086 msgnum, incr, smi.meta_refcount);
4088 /* If the reference count is now zero, delete the message
4089 * (and its supplementary record as well).
4091 if (smi.meta_refcount == 0) {
4092 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4094 /* Remove from fulltext index */
4095 if (config.c_enable_fulltext) {
4096 ft_index_message(msgnum, 0);
4099 /* Remove from message base */
4101 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4102 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4104 /* Remove metadata record */
4105 delnum = (0L - msgnum);
4106 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4112 * Write a generic object to this room
4114 * Note: this could be much more efficient. Right now we use two temporary
4115 * files, and still pull the message into memory as with all others.
4117 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4118 char *content_type, /* MIME type of this object */
4119 char *tempfilename, /* Where to fetch it from */
4120 struct ctdluser *is_mailbox, /* Mailbox room? */
4121 int is_binary, /* Is encoding necessary? */
4122 int is_unique, /* Del others of this type? */
4123 unsigned int flags /* Internal save flags */
4128 struct ctdlroom qrbuf;
4129 char roomname[ROOMNAMELEN];
4130 struct CtdlMessage *msg;
4132 char *raw_message = NULL;
4133 char *encoded_message = NULL;
4134 off_t raw_length = 0;
4136 if (is_mailbox != NULL) {
4137 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4140 safestrncpy(roomname, req_room, sizeof(roomname));
4143 fp = fopen(tempfilename, "rb");
4145 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4146 tempfilename, strerror(errno));
4149 fseek(fp, 0L, SEEK_END);
4150 raw_length = ftell(fp);
4152 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4154 raw_message = malloc((size_t)raw_length + 2);
4155 fread(raw_message, (size_t)raw_length, 1, fp);
4159 encoded_message = malloc((size_t)
4160 (((raw_length * 134) / 100) + 4096 ) );
4163 encoded_message = malloc((size_t)(raw_length + 4096));
4166 sprintf(encoded_message, "Content-type: %s\n", content_type);
4169 sprintf(&encoded_message[strlen(encoded_message)],
4170 "Content-transfer-encoding: base64\n\n"
4174 sprintf(&encoded_message[strlen(encoded_message)],
4175 "Content-transfer-encoding: 7bit\n\n"
4181 &encoded_message[strlen(encoded_message)],
4187 raw_message[raw_length] = 0;
4189 &encoded_message[strlen(encoded_message)],
4197 lprintf(CTDL_DEBUG, "Allocating\n");
4198 msg = malloc(sizeof(struct CtdlMessage));
4199 memset(msg, 0, sizeof(struct CtdlMessage));
4200 msg->cm_magic = CTDLMESSAGE_MAGIC;
4201 msg->cm_anon_type = MES_NORMAL;
4202 msg->cm_format_type = 4;
4203 msg->cm_fields['A'] = strdup(CC->user.fullname);
4204 msg->cm_fields['O'] = strdup(req_room);
4205 msg->cm_fields['N'] = strdup(config.c_nodename);
4206 msg->cm_fields['H'] = strdup(config.c_humannode);
4207 msg->cm_flags = flags;
4209 msg->cm_fields['M'] = encoded_message;
4211 /* Create the requested room if we have to. */
4212 if (getroom(&qrbuf, roomname) != 0) {
4213 create_room(roomname,
4214 ( (is_mailbox != NULL) ? 5 : 3 ),
4215 "", 0, 1, 0, VIEW_BBS);
4217 /* If the caller specified this object as unique, delete all
4218 * other objects of this type that are currently in the room.
4221 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4222 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4225 /* Now write the data */
4226 CtdlSubmitMsg(msg, NULL, roomname);
4227 CtdlFreeMessage(msg);
4235 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4236 config_msgnum = msgnum;
4240 char *CtdlGetSysConfig(char *sysconfname) {
4241 char hold_rm[ROOMNAMELEN];
4244 struct CtdlMessage *msg;
4247 strcpy(hold_rm, CC->room.QRname);
4248 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4249 getroom(&CC->room, hold_rm);
4254 /* We want the last (and probably only) config in this room */
4255 begin_critical_section(S_CONFIG);
4256 config_msgnum = (-1L);
4257 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4258 CtdlGetSysConfigBackend, NULL);
4259 msgnum = config_msgnum;
4260 end_critical_section(S_CONFIG);
4266 msg = CtdlFetchMessage(msgnum, 1);
4268 conf = strdup(msg->cm_fields['M']);
4269 CtdlFreeMessage(msg);
4276 getroom(&CC->room, hold_rm);
4278 if (conf != NULL) do {
4279 extract_token(buf, conf, 0, '\n', sizeof buf);
4280 strcpy(conf, &conf[strlen(buf)+1]);
4281 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4286 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4287 char temp[PATH_MAX];
4290 CtdlMakeTempFileName(temp, sizeof temp);
4292 fp = fopen(temp, "w");
4293 if (fp == NULL) return;
4294 fprintf(fp, "%s", sysconfdata);
4297 /* this handy API function does all the work for us */
4298 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4304 * Determine whether a given Internet address belongs to the current user
4306 int CtdlIsMe(char *addr, int addr_buf_len)
4308 struct recptypes *recp;
4311 recp = validate_recipients(addr);
4312 if (recp == NULL) return(0);
4314 if (recp->num_local == 0) {
4315 free_recipients(recp);
4319 for (i=0; i<recp->num_local; ++i) {
4320 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4321 if (!strcasecmp(addr, CC->user.fullname)) {
4322 free_recipients(recp);
4327 free_recipients(recp);
4333 * Citadel protocol command to do the same
4335 void cmd_isme(char *argbuf) {
4338 if (CtdlAccessCheck(ac_logged_in)) return;
4339 extract_token(addr, argbuf, 0, '|', sizeof addr);
4341 if (CtdlIsMe(addr, sizeof addr)) {
4342 cprintf("%d %s\n", CIT_OK, addr);
4345 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);