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");
2147 /* Submit this room for net processing */
2148 network_queue_room(&CC->room, NULL);
2151 /* Submit this room for processing by hooks */
2152 PerformRoomHooks(&CC->room);
2155 #ifdef HAVE_LIBSIEVE
2156 /* If this is someone's inbox, submit the room for sieve processing */
2157 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
2158 sieve_queue_room(&CC->room);
2160 #endif /* HAVE_LIBSIEVE */
2163 /* Go back to the room we were in before we wandered here... */
2164 getroom(&CC->room, hold_rm);
2166 /* Bump the reference count for all messages which were merged */
2167 for (i=0; i<num_msgs_to_be_merged; ++i) {
2168 AdjRefCount(msgs_to_be_merged[i], +1);
2171 /* Free up memory... */
2172 if (msgs_to_be_merged != NULL) {
2173 free(msgs_to_be_merged);
2176 /* Return success. */
2182 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2185 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2186 int do_repl_check, struct CtdlMessage *supplied_msg)
2188 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2195 * Message base operation to save a new message to the message store
2196 * (returns new message number)
2198 * This is the back end for CtdlSubmitMsg() and should not be directly
2199 * called by server-side modules.
2202 long send_message(struct CtdlMessage *msg) {
2210 /* Get a new message number */
2211 newmsgid = get_new_message_number();
2212 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2214 /* Generate an ID if we don't have one already */
2215 if (msg->cm_fields['I']==NULL) {
2216 msg->cm_fields['I'] = strdup(msgidbuf);
2219 /* If the message is big, set its body aside for storage elsewhere */
2220 if (msg->cm_fields['M'] != NULL) {
2221 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2223 holdM = msg->cm_fields['M'];
2224 msg->cm_fields['M'] = NULL;
2228 /* Serialize our data structure for storage in the database */
2229 serialize_message(&smr, msg);
2232 msg->cm_fields['M'] = holdM;
2236 cprintf("%d Unable to serialize message\n",
2237 ERROR + INTERNAL_ERROR);
2241 /* Write our little bundle of joy into the message base */
2242 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2243 smr.ser, smr.len) < 0) {
2244 lprintf(CTDL_ERR, "Can't store message\n");
2248 cdb_store(CDB_BIGMSGS,
2258 /* Free the memory we used for the serialized message */
2261 /* Return the *local* message ID to the caller
2262 * (even if we're storing an incoming network message)
2270 * Serialize a struct CtdlMessage into the format used on disk and network.
2272 * This function loads up a "struct ser_ret" (defined in server.h) which
2273 * contains the length of the serialized message and a pointer to the
2274 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2276 void serialize_message(struct ser_ret *ret, /* return values */
2277 struct CtdlMessage *msg) /* unserialized msg */
2279 size_t wlen, fieldlen;
2281 static char *forder = FORDER;
2284 * Check for valid message format
2286 if (is_valid_message(msg) == 0) {
2287 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2294 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2295 ret->len = ret->len +
2296 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2298 ret->ser = malloc(ret->len);
2299 if (ret->ser == NULL) {
2300 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2301 (long)ret->len, strerror(errno));
2308 ret->ser[1] = msg->cm_anon_type;
2309 ret->ser[2] = msg->cm_format_type;
2312 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2313 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2314 ret->ser[wlen++] = (char)forder[i];
2315 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2316 wlen = wlen + fieldlen + 1;
2318 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2319 (long)ret->len, (long)wlen);
2327 * Check to see if any messages already exist in the current room which
2328 * carry the same Exclusive ID as this one. If any are found, delete them.
2330 void ReplicationChecks(struct CtdlMessage *msg) {
2331 long old_msgnum = (-1L);
2333 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2335 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2338 /* No exclusive id? Don't do anything. */
2339 if (msg == NULL) return;
2340 if (msg->cm_fields['E'] == NULL) return;
2341 if (strlen(msg->cm_fields['E']) == 0) return;
2342 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2343 msg->cm_fields['E'], CC->room.QRname);*/
2345 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2346 if (old_msgnum > 0L) {
2347 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2348 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2355 * Save a message to disk and submit it into the delivery system.
2357 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2358 struct recptypes *recps, /* recipients (if mail) */
2359 char *force /* force a particular room? */
2361 char submit_filename[128];
2362 char generated_timestamp[32];
2363 char hold_rm[ROOMNAMELEN];
2364 char actual_rm[ROOMNAMELEN];
2365 char force_room[ROOMNAMELEN];
2366 char content_type[SIZ]; /* We have to learn this */
2367 char recipient[SIZ];
2370 struct ctdluser userbuf;
2372 struct MetaData smi;
2373 FILE *network_fp = NULL;
2374 static int seqnum = 1;
2375 struct CtdlMessage *imsg = NULL;
2377 size_t instr_alloc = 0;
2379 char *hold_R, *hold_D;
2380 char *collected_addresses = NULL;
2381 struct addresses_to_be_filed *aptr = NULL;
2382 char *saved_rfc822_version = NULL;
2383 int qualified_for_journaling = 0;
2385 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2386 if (is_valid_message(msg) == 0) return(-1); /* self check */
2388 /* If this message has no timestamp, we take the liberty of
2389 * giving it one, right now.
2391 if (msg->cm_fields['T'] == NULL) {
2392 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2393 msg->cm_fields['T'] = strdup(generated_timestamp);
2396 /* If this message has no path, we generate one.
2398 if (msg->cm_fields['P'] == NULL) {
2399 if (msg->cm_fields['A'] != NULL) {
2400 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2401 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2402 if (isspace(msg->cm_fields['P'][a])) {
2403 msg->cm_fields['P'][a] = ' ';
2408 msg->cm_fields['P'] = strdup("unknown");
2412 if (force == NULL) {
2413 strcpy(force_room, "");
2416 strcpy(force_room, force);
2419 /* Learn about what's inside, because it's what's inside that counts */
2420 if (msg->cm_fields['M'] == NULL) {
2421 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2425 switch (msg->cm_format_type) {
2427 strcpy(content_type, "text/x-citadel-variformat");
2430 strcpy(content_type, "text/plain");
2433 strcpy(content_type, "text/plain");
2434 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2436 safestrncpy(content_type, &mptr[13], sizeof content_type);
2437 striplt(content_type);
2438 for (a = 0; a < strlen(content_type); ++a) {
2439 if ((content_type[a] == ';')
2440 || (content_type[a] == ' ')
2441 || (content_type[a] == 13)
2442 || (content_type[a] == 10)) {
2443 content_type[a] = 0;
2449 /* Goto the correct room */
2450 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2451 strcpy(hold_rm, CC->room.QRname);
2452 strcpy(actual_rm, CC->room.QRname);
2453 if (recps != NULL) {
2454 strcpy(actual_rm, SENTITEMS);
2457 /* If the user is a twit, move to the twit room for posting */
2459 if (CC->user.axlevel == 2) {
2460 strcpy(hold_rm, actual_rm);
2461 strcpy(actual_rm, config.c_twitroom);
2462 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2466 /* ...or if this message is destined for Aide> then go there. */
2467 if (strlen(force_room) > 0) {
2468 strcpy(actual_rm, force_room);
2471 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2472 if (strcasecmp(actual_rm, CC->room.QRname)) {
2473 /* getroom(&CC->room, actual_rm); */
2474 usergoto(actual_rm, 0, 1, NULL, NULL);
2478 * If this message has no O (room) field, generate one.
2480 if (msg->cm_fields['O'] == NULL) {
2481 msg->cm_fields['O'] = strdup(CC->room.QRname);
2484 /* Perform "before save" hooks (aborting if any return nonzero) */
2485 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2486 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2489 * If this message has an Exclusive ID, and the room is replication
2490 * checking enabled, then do replication checks.
2492 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2493 ReplicationChecks(msg);
2496 /* Save it to disk */
2497 lprintf(CTDL_DEBUG, "Saving to disk\n");
2498 newmsgid = send_message(msg);
2499 if (newmsgid <= 0L) return(-5);
2501 /* Write a supplemental message info record. This doesn't have to
2502 * be a critical section because nobody else knows about this message
2505 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2506 memset(&smi, 0, sizeof(struct MetaData));
2507 smi.meta_msgnum = newmsgid;
2508 smi.meta_refcount = 0;
2509 safestrncpy(smi.meta_content_type, content_type,
2510 sizeof smi.meta_content_type);
2513 * Measure how big this message will be when rendered as RFC822.
2514 * We do this for two reasons:
2515 * 1. We need the RFC822 length for the new metadata record, so the
2516 * POP and IMAP services don't have to calculate message lengths
2517 * while the user is waiting (multiplied by potentially hundreds
2518 * or thousands of messages).
2519 * 2. If journaling is enabled, we will need an RFC822 version of the
2520 * message to attach to the journalized copy.
2522 if (CC->redirect_buffer != NULL) {
2523 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2526 CC->redirect_buffer = malloc(SIZ);
2527 CC->redirect_len = 0;
2528 CC->redirect_alloc = SIZ;
2529 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2530 smi.meta_rfc822_length = CC->redirect_len;
2531 saved_rfc822_version = CC->redirect_buffer;
2532 CC->redirect_buffer = NULL;
2533 CC->redirect_len = 0;
2534 CC->redirect_alloc = 0;
2538 /* Now figure out where to store the pointers */
2539 lprintf(CTDL_DEBUG, "Storing pointers\n");
2541 /* If this is being done by the networker delivering a private
2542 * message, we want to BYPASS saving the sender's copy (because there
2543 * is no local sender; it would otherwise go to the Trashcan).
2545 if ((!CC->internal_pgm) || (recps == NULL)) {
2546 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2547 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2548 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2552 /* For internet mail, drop a copy in the outbound queue room */
2554 if (recps->num_internet > 0) {
2555 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2558 /* If other rooms are specified, drop them there too. */
2560 if (recps->num_room > 0)
2561 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2562 extract_token(recipient, recps->recp_room, i,
2563 '|', sizeof recipient);
2564 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2565 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2568 /* Bump this user's messages posted counter. */
2569 lprintf(CTDL_DEBUG, "Updating user\n");
2570 lgetuser(&CC->user, CC->curr_user);
2571 CC->user.posted = CC->user.posted + 1;
2572 lputuser(&CC->user);
2574 /* If this is private, local mail, make a copy in the
2575 * recipient's mailbox and bump the reference count.
2578 if (recps->num_local > 0)
2579 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2580 extract_token(recipient, recps->recp_local, i,
2581 '|', sizeof recipient);
2582 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2584 if (getuser(&userbuf, recipient) == 0) {
2585 // Add a flag so the Funambol module knows its mail
2586 msg->cm_fields['W'] = strdup(recipient);
2587 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2588 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2589 BumpNewMailCounter(userbuf.usernum);
2590 if (strlen(config.c_funambol_host) > 0) {
2591 /* Generate a instruction message for the Funambol notification
2592 * server, in the same style as the SMTP queue
2595 instr = malloc(instr_alloc);
2596 snprintf(instr, instr_alloc,
2597 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2599 SPOOLMIME, newmsgid, (long)time(NULL),
2600 msg->cm_fields['A'], msg->cm_fields['N']
2603 imsg = malloc(sizeof(struct CtdlMessage));
2604 memset(imsg, 0, sizeof(struct CtdlMessage));
2605 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2606 imsg->cm_anon_type = MES_NORMAL;
2607 imsg->cm_format_type = FMT_RFC822;
2608 imsg->cm_fields['A'] = strdup("Citadel");
2609 imsg->cm_fields['J'] = strdup("do not journal");
2610 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2611 imsg->cm_fields['W'] = strdup(recipient);
2612 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2613 CtdlFreeMessage(imsg);
2617 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2618 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2623 /* Perform "after save" hooks */
2624 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2625 PerformMessageHooks(msg, EVT_AFTERSAVE);
2627 /* For IGnet mail, we have to save a new copy into the spooler for
2628 * each recipient, with the R and D fields set to the recipient and
2629 * destination-node. This has two ugly side effects: all other
2630 * recipients end up being unlisted in this recipient's copy of the
2631 * message, and it has to deliver multiple messages to the same
2632 * node. We'll revisit this again in a year or so when everyone has
2633 * a network spool receiver that can handle the new style messages.
2636 if (recps->num_ignet > 0)
2637 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2638 extract_token(recipient, recps->recp_ignet, i,
2639 '|', sizeof recipient);
2641 hold_R = msg->cm_fields['R'];
2642 hold_D = msg->cm_fields['D'];
2643 msg->cm_fields['R'] = malloc(SIZ);
2644 msg->cm_fields['D'] = malloc(128);
2645 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2646 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2648 serialize_message(&smr, msg);
2650 snprintf(submit_filename, sizeof submit_filename,
2651 "%s/netmail.%04lx.%04x.%04x",
2653 (long) getpid(), CC->cs_pid, ++seqnum);
2654 network_fp = fopen(submit_filename, "wb+");
2655 if (network_fp != NULL) {
2656 fwrite(smr.ser, smr.len, 1, network_fp);
2662 free(msg->cm_fields['R']);
2663 free(msg->cm_fields['D']);
2664 msg->cm_fields['R'] = hold_R;
2665 msg->cm_fields['D'] = hold_D;
2668 /* Go back to the room we started from */
2669 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2670 if (strcasecmp(hold_rm, CC->room.QRname))
2671 usergoto(hold_rm, 0, 1, NULL, NULL);
2673 /* For internet mail, generate delivery instructions.
2674 * Yes, this is recursive. Deal with it. Infinite recursion does
2675 * not happen because the delivery instructions message does not
2676 * contain a recipient.
2679 if (recps->num_internet > 0) {
2680 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2682 instr = malloc(instr_alloc);
2683 snprintf(instr, instr_alloc,
2684 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2686 SPOOLMIME, newmsgid, (long)time(NULL),
2687 msg->cm_fields['A'], msg->cm_fields['N']
2690 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2691 size_t tmp = strlen(instr);
2692 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2693 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2694 instr_alloc = instr_alloc * 2;
2695 instr = realloc(instr, instr_alloc);
2697 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2700 imsg = malloc(sizeof(struct CtdlMessage));
2701 memset(imsg, 0, sizeof(struct CtdlMessage));
2702 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2703 imsg->cm_anon_type = MES_NORMAL;
2704 imsg->cm_format_type = FMT_RFC822;
2705 imsg->cm_fields['A'] = strdup("Citadel");
2706 imsg->cm_fields['J'] = strdup("do not journal");
2707 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2708 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2709 CtdlFreeMessage(imsg);
2713 * Any addresses to harvest for someone's address book?
2715 if ( (CC->logged_in) && (recps != NULL) ) {
2716 collected_addresses = harvest_collected_addresses(msg);
2719 if (collected_addresses != NULL) {
2720 begin_critical_section(S_ATBF);
2721 aptr = (struct addresses_to_be_filed *)
2722 malloc(sizeof(struct addresses_to_be_filed));
2724 MailboxName(actual_rm, sizeof actual_rm,
2725 &CC->user, USERCONTACTSROOM);
2726 aptr->roomname = strdup(actual_rm);
2727 aptr->collected_addresses = collected_addresses;
2729 end_critical_section(S_ATBF);
2733 * Determine whether this message qualifies for journaling.
2735 if (msg->cm_fields['J'] != NULL) {
2736 qualified_for_journaling = 0;
2739 if (recps == NULL) {
2740 qualified_for_journaling = config.c_journal_pubmsgs;
2742 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2743 qualified_for_journaling = config.c_journal_email;
2746 qualified_for_journaling = config.c_journal_pubmsgs;
2751 * Do we have to perform journaling? If so, hand off the saved
2752 * RFC822 version will be handed off to the journaler for background
2753 * submit. Otherwise, we have to free the memory ourselves.
2755 if (saved_rfc822_version != NULL) {
2756 if (qualified_for_journaling) {
2757 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2760 free(saved_rfc822_version);
2773 * Convenience function for generating small administrative messages.
2775 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2776 int format_type, char *subject)
2778 struct CtdlMessage *msg;
2779 struct recptypes *recp = NULL;
2781 msg = malloc(sizeof(struct CtdlMessage));
2782 memset(msg, 0, sizeof(struct CtdlMessage));
2783 msg->cm_magic = CTDLMESSAGE_MAGIC;
2784 msg->cm_anon_type = MES_NORMAL;
2785 msg->cm_format_type = format_type;
2788 msg->cm_fields['A'] = strdup(from);
2790 else if (fromaddr != NULL) {
2791 msg->cm_fields['A'] = strdup(fromaddr);
2792 if (strchr(msg->cm_fields['A'], '@')) {
2793 *strchr(msg->cm_fields['A'], '@') = 0;
2797 msg->cm_fields['A'] = strdup("Citadel");
2800 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2801 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2802 msg->cm_fields['N'] = strdup(NODENAME);
2804 msg->cm_fields['R'] = strdup(to);
2805 recp = validate_recipients(to);
2807 if (subject != NULL) {
2808 msg->cm_fields['U'] = strdup(subject);
2810 msg->cm_fields['M'] = strdup(text);
2812 CtdlSubmitMsg(msg, recp, room);
2813 CtdlFreeMessage(msg);
2814 if (recp != NULL) free_recipients(recp);
2820 * Back end function used by CtdlMakeMessage() and similar functions
2822 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2823 size_t maxlen, /* maximum message length */
2824 char *exist, /* if non-null, append to it;
2825 exist is ALWAYS freed */
2826 int crlf /* CRLF newlines instead of LF */
2830 size_t message_len = 0;
2831 size_t buffer_len = 0;
2838 if (exist == NULL) {
2845 message_len = strlen(exist);
2846 buffer_len = message_len + 4096;
2847 m = realloc(exist, buffer_len);
2854 /* Do we need to change leading ".." to "." for SMTP escaping? */
2855 if (!strcmp(terminator, ".")) {
2859 /* flush the input if we have nowhere to store it */
2864 /* read in the lines of message text one by one */
2866 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2867 if (!strcmp(buf, terminator)) finished = 1;
2869 strcat(buf, "\r\n");
2875 /* Unescape SMTP-style input of two dots at the beginning of the line */
2877 if (!strncmp(buf, "..", 2)) {
2878 strcpy(buf, &buf[1]);
2882 if ( (!flushing) && (!finished) ) {
2883 /* Measure the line */
2884 linelen = strlen(buf);
2886 /* augment the buffer if we have to */
2887 if ((message_len + linelen) >= buffer_len) {
2888 ptr = realloc(m, (buffer_len * 2) );
2889 if (ptr == NULL) { /* flush if can't allocate */
2892 buffer_len = (buffer_len * 2);
2894 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2898 /* Add the new line to the buffer. NOTE: this loop must avoid
2899 * using functions like strcat() and strlen() because they
2900 * traverse the entire buffer upon every call, and doing that
2901 * for a multi-megabyte message slows it down beyond usability.
2903 strcpy(&m[message_len], buf);
2904 message_len += linelen;
2907 /* if we've hit the max msg length, flush the rest */
2908 if (message_len >= maxlen) flushing = 1;
2910 } while (!finished);
2918 * Build a binary message to be saved on disk.
2919 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2920 * will become part of the message. This means you are no longer
2921 * responsible for managing that memory -- it will be freed along with
2922 * the rest of the fields when CtdlFreeMessage() is called.)
2925 struct CtdlMessage *CtdlMakeMessage(
2926 struct ctdluser *author, /* author's user structure */
2927 char *recipient, /* NULL if it's not mail */
2928 char *recp_cc, /* NULL if it's not mail */
2929 char *room, /* room where it's going */
2930 int type, /* see MES_ types in header file */
2931 int format_type, /* variformat, plain text, MIME... */
2932 char *fake_name, /* who we're masquerading as */
2933 char *my_email, /* which of my email addresses to use (empty is ok) */
2934 char *subject, /* Subject (optional) */
2935 char *supplied_euid, /* ...or NULL if this is irrelevant */
2936 char *preformatted_text /* ...or NULL to read text from client */
2938 char dest_node[256];
2940 struct CtdlMessage *msg;
2943 msg = malloc(sizeof(struct CtdlMessage));
2944 memset(msg, 0, sizeof(struct CtdlMessage));
2945 msg->cm_magic = CTDLMESSAGE_MAGIC;
2946 msg->cm_anon_type = type;
2947 msg->cm_format_type = format_type;
2949 /* Don't confuse the poor folks if it's not routed mail. */
2950 strcpy(dest_node, "");
2955 /* Path or Return-Path */
2956 if (my_email == NULL) my_email = "";
2958 if (strlen(my_email) > 0) {
2959 msg->cm_fields['P'] = strdup(my_email);
2962 snprintf(buf, sizeof buf, "%s", author->fullname);
2963 msg->cm_fields['P'] = strdup(buf);
2965 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
2966 if (isspace(msg->cm_fields['P'][i])) {
2967 msg->cm_fields['P'][i] = '_';
2971 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2972 msg->cm_fields['T'] = strdup(buf);
2974 if (fake_name[0]) /* author */
2975 msg->cm_fields['A'] = strdup(fake_name);
2977 msg->cm_fields['A'] = strdup(author->fullname);
2979 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2980 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2983 msg->cm_fields['O'] = strdup(CC->room.QRname);
2986 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2987 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2989 if (recipient[0] != 0) {
2990 msg->cm_fields['R'] = strdup(recipient);
2992 if (recp_cc[0] != 0) {
2993 msg->cm_fields['Y'] = strdup(recp_cc);
2995 if (dest_node[0] != 0) {
2996 msg->cm_fields['D'] = strdup(dest_node);
2999 if (strlen(my_email) > 0) {
3000 msg->cm_fields['F'] = strdup(my_email);
3002 else if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
3003 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3006 if (subject != NULL) {
3009 length = strlen(subject);
3015 while ((subject[i] != '\0') &&
3016 (IsAscii = isascii(subject[i]) != 0 ))
3019 msg->cm_fields['U'] = strdup(subject);
3020 else /* ok, we've got utf8 in the string. */
3022 msg->cm_fields['U'] = rfc2047encode(subject, length);
3028 if (supplied_euid != NULL) {
3029 msg->cm_fields['E'] = strdup(supplied_euid);
3032 if (preformatted_text != NULL) {
3033 msg->cm_fields['M'] = preformatted_text;
3036 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
3044 * Check to see whether we have permission to post a message in the current
3045 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3046 * returns 0 on success.
3048 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3051 if (!(CC->logged_in)) {
3052 snprintf(errmsgbuf, n, "Not logged in.");
3053 return (ERROR + NOT_LOGGED_IN);
3056 if ((CC->user.axlevel < 2)
3057 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3058 snprintf(errmsgbuf, n, "Need to be validated to enter "
3059 "(except in %s> to sysop)", MAILROOM);
3060 return (ERROR + HIGHER_ACCESS_REQUIRED);
3063 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3064 if (!(ra & UA_POSTALLOWED)) {
3065 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3066 return (ERROR + HIGHER_ACCESS_REQUIRED);
3069 strcpy(errmsgbuf, "Ok");
3075 * Check to see if the specified user has Internet mail permission
3076 * (returns nonzero if permission is granted)
3078 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3080 /* Do not allow twits to send Internet mail */
3081 if (who->axlevel <= 2) return(0);
3083 /* Globally enabled? */
3084 if (config.c_restrict == 0) return(1);
3086 /* User flagged ok? */
3087 if (who->flags & US_INTERNET) return(2);
3089 /* Aide level access? */
3090 if (who->axlevel >= 6) return(3);
3092 /* No mail for you! */
3098 * Validate recipients, count delivery types and errors, and handle aliasing
3099 * FIXME check for dupes!!!!!
3101 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3102 * were specified, or the number of addresses found invalid.
3104 * Caller needs to free the result using free_recipients()
3106 struct recptypes *validate_recipients(char *supplied_recipients) {
3107 struct recptypes *ret;
3108 char *recipients = NULL;
3109 char this_recp[256];
3110 char this_recp_cooked[256];
3116 struct ctdluser tempUS;
3117 struct ctdlroom tempQR;
3121 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3122 if (ret == NULL) return(NULL);
3124 /* Set all strings to null and numeric values to zero */
3125 memset(ret, 0, sizeof(struct recptypes));
3127 if (supplied_recipients == NULL) {
3128 recipients = strdup("");
3131 recipients = strdup(supplied_recipients);
3134 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3135 * actually need, but it's healthier for the heap than doing lots of tiny
3136 * realloc() calls instead.
3139 ret->errormsg = malloc(strlen(recipients) + 1024);
3140 ret->recp_local = malloc(strlen(recipients) + 1024);
3141 ret->recp_internet = malloc(strlen(recipients) + 1024);
3142 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3143 ret->recp_room = malloc(strlen(recipients) + 1024);
3144 ret->display_recp = malloc(strlen(recipients) + 1024);
3146 ret->errormsg[0] = 0;
3147 ret->recp_local[0] = 0;
3148 ret->recp_internet[0] = 0;
3149 ret->recp_ignet[0] = 0;
3150 ret->recp_room[0] = 0;
3151 ret->display_recp[0] = 0;
3153 ret->recptypes_magic = RECPTYPES_MAGIC;
3155 /* Change all valid separator characters to commas */
3156 for (i=0; i<strlen(recipients); ++i) {
3157 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3158 recipients[i] = ',';
3162 /* Now start extracting recipients... */
3164 while (strlen(recipients) > 0) {
3166 for (i=0; i<=strlen(recipients); ++i) {
3167 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3168 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3169 safestrncpy(this_recp, recipients, i+1);
3171 if (recipients[i] == ',') {
3172 strcpy(recipients, &recipients[i+1]);
3175 strcpy(recipients, "");
3182 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3184 mailtype = alias(this_recp);
3185 mailtype = alias(this_recp);
3186 mailtype = alias(this_recp);
3187 for (j=0; j<=strlen(this_recp); ++j) {
3188 if (this_recp[j]=='_') {
3189 this_recp_cooked[j] = ' ';
3192 this_recp_cooked[j] = this_recp[j];
3198 if (!strcasecmp(this_recp, "sysop")) {
3200 strcpy(this_recp, config.c_aideroom);
3201 if (strlen(ret->recp_room) > 0) {
3202 strcat(ret->recp_room, "|");
3204 strcat(ret->recp_room, this_recp);
3206 else if (getuser(&tempUS, this_recp) == 0) {
3208 strcpy(this_recp, tempUS.fullname);
3209 if (strlen(ret->recp_local) > 0) {
3210 strcat(ret->recp_local, "|");
3212 strcat(ret->recp_local, this_recp);
3214 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3216 strcpy(this_recp, tempUS.fullname);
3217 if (strlen(ret->recp_local) > 0) {
3218 strcat(ret->recp_local, "|");
3220 strcat(ret->recp_local, this_recp);
3222 else if ( (!strncasecmp(this_recp, "room_", 5))
3223 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3225 if (strlen(ret->recp_room) > 0) {
3226 strcat(ret->recp_room, "|");
3228 strcat(ret->recp_room, &this_recp_cooked[5]);
3236 /* Yes, you're reading this correctly: if the target
3237 * domain points back to the local system or an attached
3238 * Citadel directory, the address is invalid. That's
3239 * because if the address were valid, we would have
3240 * already translated it to a local address by now.
3242 if (IsDirectory(this_recp, 0)) {
3247 ++ret->num_internet;
3248 if (strlen(ret->recp_internet) > 0) {
3249 strcat(ret->recp_internet, "|");
3251 strcat(ret->recp_internet, this_recp);
3256 if (strlen(ret->recp_ignet) > 0) {
3257 strcat(ret->recp_ignet, "|");
3259 strcat(ret->recp_ignet, this_recp);
3267 if (strlen(ret->errormsg) == 0) {
3268 snprintf(append, sizeof append,
3269 "Invalid recipient: %s",
3273 snprintf(append, sizeof append, ", %s", this_recp);
3275 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3276 strcat(ret->errormsg, append);
3280 if (strlen(ret->display_recp) == 0) {
3281 strcpy(append, this_recp);
3284 snprintf(append, sizeof append, ", %s", this_recp);
3286 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3287 strcat(ret->display_recp, append);
3292 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3293 ret->num_room + ret->num_error) == 0) {
3294 ret->num_error = (-1);
3295 strcpy(ret->errormsg, "No recipients specified.");
3298 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3299 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3300 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3301 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3302 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3303 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3311 * Destructor for struct recptypes
3313 void free_recipients(struct recptypes *valid) {
3315 if (valid == NULL) {
3319 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3320 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3324 if (valid->errormsg != NULL) free(valid->errormsg);
3325 if (valid->recp_local != NULL) free(valid->recp_local);
3326 if (valid->recp_internet != NULL) free(valid->recp_internet);
3327 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3328 if (valid->recp_room != NULL) free(valid->recp_room);
3329 if (valid->display_recp != NULL) free(valid->display_recp);
3336 * message entry - mode 0 (normal)
3338 void cmd_ent0(char *entargs)
3344 char supplied_euid[128];
3346 int format_type = 0;
3347 char newusername[256];
3348 char newuseremail[256];
3349 struct CtdlMessage *msg;
3353 struct recptypes *valid = NULL;
3354 struct recptypes *valid_to = NULL;
3355 struct recptypes *valid_cc = NULL;
3356 struct recptypes *valid_bcc = NULL;
3358 int subject_required = 0;
3363 int newuseremail_ok = 0;
3367 post = extract_int(entargs, 0);
3368 extract_token(recp, entargs, 1, '|', sizeof recp);
3369 anon_flag = extract_int(entargs, 2);
3370 format_type = extract_int(entargs, 3);
3371 extract_token(subject, entargs, 4, '|', sizeof subject);
3372 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3373 do_confirm = extract_int(entargs, 6);
3374 extract_token(cc, entargs, 7, '|', sizeof cc);
3375 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3376 switch(CC->room.QRdefaultview) {
3379 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3382 supplied_euid[0] = 0;
3385 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3387 /* first check to make sure the request is valid. */
3389 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3392 cprintf("%d %s\n", err, errmsg);
3396 /* Check some other permission type things. */
3398 if (strlen(newusername) == 0) {
3399 strcpy(newusername, CC->user.fullname);
3401 if ( (CC->user.axlevel < 6)
3402 && (strcasecmp(newusername, CC->user.fullname))
3403 && (strcasecmp(newusername, CC->cs_inet_fn))
3405 cprintf("%d You don't have permission to author messages as '%s'.\n",
3406 ERROR + HIGHER_ACCESS_REQUIRED,
3413 if (strlen(newuseremail) == 0) {
3414 newuseremail_ok = 1;
3417 if (strlen(newuseremail) > 0) {
3418 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3419 newuseremail_ok = 1;
3421 else if (strlen(CC->cs_inet_other_emails) > 0) {
3422 j = num_tokens(CC->cs_inet_other_emails, '|');
3423 for (i=0; i<j; ++i) {
3424 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3425 if (!strcasecmp(newuseremail, buf)) {
3426 newuseremail_ok = 1;
3432 if (!newuseremail_ok) {
3433 cprintf("%d You don't have permission to author messages as '%s'.\n",
3434 ERROR + HIGHER_ACCESS_REQUIRED,
3440 CC->cs_flags |= CS_POSTING;
3442 /* In mailbox rooms we have to behave a little differently --
3443 * make sure the user has specified at least one recipient. Then
3444 * validate the recipient(s). We do this for the Mail> room, as
3445 * well as any room which has the "Mailbox" view set.
3448 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3449 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3451 if (CC->user.axlevel < 2) {
3452 strcpy(recp, "sysop");
3457 valid_to = validate_recipients(recp);
3458 if (valid_to->num_error > 0) {
3459 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3460 free_recipients(valid_to);
3464 valid_cc = validate_recipients(cc);
3465 if (valid_cc->num_error > 0) {
3466 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3467 free_recipients(valid_to);
3468 free_recipients(valid_cc);
3472 valid_bcc = validate_recipients(bcc);
3473 if (valid_bcc->num_error > 0) {
3474 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3475 free_recipients(valid_to);
3476 free_recipients(valid_cc);
3477 free_recipients(valid_bcc);
3481 /* Recipient required, but none were specified */
3482 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3483 free_recipients(valid_to);
3484 free_recipients(valid_cc);
3485 free_recipients(valid_bcc);
3486 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3490 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3491 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3492 cprintf("%d You do not have permission "
3493 "to send Internet mail.\n",
3494 ERROR + HIGHER_ACCESS_REQUIRED);
3495 free_recipients(valid_to);
3496 free_recipients(valid_cc);
3497 free_recipients(valid_bcc);
3502 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)
3503 && (CC->user.axlevel < 4) ) {
3504 cprintf("%d Higher access required for network mail.\n",
3505 ERROR + HIGHER_ACCESS_REQUIRED);
3506 free_recipients(valid_to);
3507 free_recipients(valid_cc);
3508 free_recipients(valid_bcc);
3512 if ((RESTRICT_INTERNET == 1)
3513 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3514 && ((CC->user.flags & US_INTERNET) == 0)
3515 && (!CC->internal_pgm)) {
3516 cprintf("%d You don't have access to Internet mail.\n",
3517 ERROR + HIGHER_ACCESS_REQUIRED);
3518 free_recipients(valid_to);
3519 free_recipients(valid_cc);
3520 free_recipients(valid_bcc);
3526 /* Is this a room which has anonymous-only or anonymous-option? */
3527 anonymous = MES_NORMAL;
3528 if (CC->room.QRflags & QR_ANONONLY) {
3529 anonymous = MES_ANONONLY;
3531 if (CC->room.QRflags & QR_ANONOPT) {
3532 if (anon_flag == 1) { /* only if the user requested it */
3533 anonymous = MES_ANONOPT;
3537 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3541 /* Recommend to the client that the use of a message subject is
3542 * strongly recommended in this room, if either the SUBJECTREQ flag
3543 * is set, or if there is one or more Internet email recipients.
3545 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3546 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3547 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3548 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3550 /* If we're only checking the validity of the request, return
3551 * success without creating the message.
3554 cprintf("%d %s|%d\n", CIT_OK,
3555 ((valid_to != NULL) ? valid_to->display_recp : ""),
3557 free_recipients(valid_to);
3558 free_recipients(valid_cc);
3559 free_recipients(valid_bcc);
3563 /* We don't need these anymore because we'll do it differently below */
3564 free_recipients(valid_to);
3565 free_recipients(valid_cc);
3566 free_recipients(valid_bcc);
3568 /* Read in the message from the client. */
3570 cprintf("%d send message\n", START_CHAT_MODE);
3572 cprintf("%d send message\n", SEND_LISTING);
3575 msg = CtdlMakeMessage(&CC->user, recp, cc,
3576 CC->room.QRname, anonymous, format_type,
3577 newusername, newuseremail, subject,
3578 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3581 /* Put together one big recipients struct containing to/cc/bcc all in
3582 * one. This is for the envelope.
3584 char *all_recps = malloc(SIZ * 3);
3585 strcpy(all_recps, recp);
3586 if (strlen(cc) > 0) {
3587 if (strlen(all_recps) > 0) {
3588 strcat(all_recps, ",");
3590 strcat(all_recps, cc);
3592 if (strlen(bcc) > 0) {
3593 if (strlen(all_recps) > 0) {
3594 strcat(all_recps, ",");
3596 strcat(all_recps, bcc);
3598 if (strlen(all_recps) > 0) {
3599 valid = validate_recipients(all_recps);
3607 msgnum = CtdlSubmitMsg(msg, valid, "");
3610 cprintf("%ld\n", msgnum);
3612 cprintf("Message accepted.\n");
3615 cprintf("Internal error.\n");
3617 if (msg->cm_fields['E'] != NULL) {
3618 cprintf("%s\n", msg->cm_fields['E']);
3625 CtdlFreeMessage(msg);
3627 if (valid != NULL) {
3628 free_recipients(valid);
3636 * API function to delete messages which match a set of criteria
3637 * (returns the actual number of messages deleted)
3639 int CtdlDeleteMessages(char *room_name, /* which room */
3640 long *dmsgnums, /* array of msg numbers to be deleted */
3641 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3642 char *content_type /* or "" for any. regular expressions expected. */
3645 struct ctdlroom qrbuf;
3646 struct cdbdata *cdbfr;
3647 long *msglist = NULL;
3648 long *dellist = NULL;
3651 int num_deleted = 0;
3653 struct MetaData smi;
3656 int need_to_free_re = 0;
3658 if (content_type) if (strlen(content_type) > 0) {
3659 regcomp(&re, content_type, 0);
3660 need_to_free_re = 1;
3662 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3663 room_name, num_dmsgnums, content_type);
3665 /* get room record, obtaining a lock... */
3666 if (lgetroom(&qrbuf, room_name) != 0) {
3667 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3669 if (need_to_free_re) regfree(&re);
3670 return (0); /* room not found */
3672 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3674 if (cdbfr != NULL) {
3675 dellist = malloc(cdbfr->len);
3676 msglist = (long *) cdbfr->ptr;
3677 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3678 num_msgs = cdbfr->len / sizeof(long);
3682 for (i = 0; i < num_msgs; ++i) {
3685 /* Set/clear a bit for each criterion */
3687 /* 0 messages in the list or a null list means that we are
3688 * interested in deleting any messages which meet the other criteria.
3690 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3691 delete_this |= 0x01;
3694 for (j=0; j<num_dmsgnums; ++j) {
3695 if (msglist[i] == dmsgnums[j]) {
3696 delete_this |= 0x01;
3701 if (strlen(content_type) == 0) {
3702 delete_this |= 0x02;
3704 GetMetaData(&smi, msglist[i]);
3705 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3706 delete_this |= 0x02;
3710 /* Delete message only if all bits are set */
3711 if (delete_this == 0x03) {
3712 dellist[num_deleted++] = msglist[i];
3717 num_msgs = sort_msglist(msglist, num_msgs);
3718 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3719 msglist, (int)(num_msgs * sizeof(long)));
3721 qrbuf.QRhighest = msglist[num_msgs - 1];
3725 /* Go through the messages we pulled out of the index, and decrement
3726 * their reference counts by 1. If this is the only room the message
3727 * was in, the reference count will reach zero and the message will
3728 * automatically be deleted from the database. We do this in a
3729 * separate pass because there might be plug-in hooks getting called,
3730 * and we don't want that happening during an S_ROOMS critical
3733 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3734 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3735 AdjRefCount(dellist[i], -1);
3738 /* Now free the memory we used, and go away. */
3739 if (msglist != NULL) free(msglist);
3740 if (dellist != NULL) free(dellist);
3741 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3742 if (need_to_free_re) regfree(&re);
3743 return (num_deleted);
3749 * Check whether the current user has permission to delete messages from
3750 * the current room (returns 1 for yes, 0 for no)
3752 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3754 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3755 if (ra & UA_DELETEALLOWED) return(1);
3763 * Delete message from current room
3765 void cmd_dele(char *args)
3774 extract_token(msgset, args, 0, '|', sizeof msgset);
3775 num_msgs = num_tokens(msgset, ',');
3777 cprintf("%d Nothing to do.\n", CIT_OK);
3781 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3782 cprintf("%d Higher access required.\n",
3783 ERROR + HIGHER_ACCESS_REQUIRED);
3788 * Build our message set to be moved/copied
3790 msgs = malloc(num_msgs * sizeof(long));
3791 for (i=0; i<num_msgs; ++i) {
3792 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3793 msgs[i] = atol(msgtok);
3796 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3800 cprintf("%d %d message%s deleted.\n", CIT_OK,
3801 num_deleted, ((num_deleted != 1) ? "s" : ""));
3803 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3809 * Back end API function for moves and deletes (multiple messages)
3811 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3814 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3815 if (err != 0) return(err);
3824 * move or copy a message to another room
3826 void cmd_move(char *args)
3833 char targ[ROOMNAMELEN];
3834 struct ctdlroom qtemp;
3841 extract_token(msgset, args, 0, '|', sizeof msgset);
3842 num_msgs = num_tokens(msgset, ',');
3844 cprintf("%d Nothing to do.\n", CIT_OK);
3848 extract_token(targ, args, 1, '|', sizeof targ);
3849 convert_room_name_macros(targ, sizeof targ);
3850 targ[ROOMNAMELEN - 1] = 0;
3851 is_copy = extract_int(args, 2);
3853 if (getroom(&qtemp, targ) != 0) {
3854 cprintf("%d '%s' does not exist.\n",
3855 ERROR + ROOM_NOT_FOUND, targ);
3859 getuser(&CC->user, CC->curr_user);
3860 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3862 /* Check for permission to perform this operation.
3863 * Remember: "CC->room" is source, "qtemp" is target.
3867 /* Aides can move/copy */
3868 if (CC->user.axlevel >= 6) permit = 1;
3870 /* Room aides can move/copy */
3871 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3873 /* Permit move/copy from personal rooms */
3874 if ((CC->room.QRflags & QR_MAILBOX)
3875 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3877 /* Permit only copy from public to personal room */
3879 && (!(CC->room.QRflags & QR_MAILBOX))
3880 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3882 /* Permit message removal from collaborative delete rooms */
3883 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3885 /* User must have access to target room */
3886 if (!(ra & UA_KNOWN)) permit = 0;
3889 cprintf("%d Higher access required.\n",
3890 ERROR + HIGHER_ACCESS_REQUIRED);
3895 * Build our message set to be moved/copied
3897 msgs = malloc(num_msgs * sizeof(long));
3898 for (i=0; i<num_msgs; ++i) {
3899 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3900 msgs[i] = atol(msgtok);
3906 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3908 cprintf("%d Cannot store message(s) in %s: error %d\n",
3914 /* Now delete the message from the source room,
3915 * if this is a 'move' rather than a 'copy' operation.
3918 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3922 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3928 * GetMetaData() - Get the supplementary record for a message
3930 void GetMetaData(struct MetaData *smibuf, long msgnum)
3933 struct cdbdata *cdbsmi;
3936 memset(smibuf, 0, sizeof(struct MetaData));
3937 smibuf->meta_msgnum = msgnum;
3938 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3940 /* Use the negative of the message number for its supp record index */
3941 TheIndex = (0L - msgnum);
3943 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3944 if (cdbsmi == NULL) {
3945 return; /* record not found; go with defaults */
3947 memcpy(smibuf, cdbsmi->ptr,
3948 ((cdbsmi->len > sizeof(struct MetaData)) ?
3949 sizeof(struct MetaData) : cdbsmi->len));
3956 * PutMetaData() - (re)write supplementary record for a message
3958 void PutMetaData(struct MetaData *smibuf)
3962 /* Use the negative of the message number for the metadata db index */
3963 TheIndex = (0L - smibuf->meta_msgnum);
3965 cdb_store(CDB_MSGMAIN,
3966 &TheIndex, (int)sizeof(long),
3967 smibuf, (int)sizeof(struct MetaData));
3972 * AdjRefCount - submit an adjustment to the reference count for a message.
3973 * (These are just queued -- we actually process them later.)
3975 void AdjRefCount(long msgnum, int incr)
3977 struct arcq new_arcq;
3979 begin_critical_section(S_SUPPMSGMAIN);
3980 if (arcfp == NULL) {
3981 arcfp = fopen(file_arcq, "ab+");
3983 end_critical_section(S_SUPPMSGMAIN);
3985 /* msgnum < 0 means that we're trying to close the file */
3987 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3988 begin_critical_section(S_SUPPMSGMAIN);
3989 if (arcfp != NULL) {
3993 end_critical_section(S_SUPPMSGMAIN);
3998 * If we can't open the queue, perform the operation synchronously.
4000 if (arcfp == NULL) {
4001 TDAP_AdjRefCount(msgnum, incr);
4005 new_arcq.arcq_msgnum = msgnum;
4006 new_arcq.arcq_delta = incr;
4007 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4015 * TDAP_ProcessAdjRefCountQueue()
4017 * Process the queue of message count adjustments that was created by calls
4018 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4019 * for each one. This should be an "off hours" operation.
4021 int TDAP_ProcessAdjRefCountQueue(void)
4023 char file_arcq_temp[PATH_MAX];
4026 struct arcq arcq_rec;
4027 int num_records_processed = 0;
4029 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4031 begin_critical_section(S_SUPPMSGMAIN);
4032 if (arcfp != NULL) {
4037 r = link(file_arcq, file_arcq_temp);
4039 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4040 end_critical_section(S_SUPPMSGMAIN);
4041 return(num_records_processed);
4045 end_critical_section(S_SUPPMSGMAIN);
4047 fp = fopen(file_arcq_temp, "rb");
4049 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4050 return(num_records_processed);
4053 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4054 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4055 ++num_records_processed;
4059 r = unlink(file_arcq_temp);
4061 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4064 return(num_records_processed);
4070 * TDAP_AdjRefCount - adjust the reference count for a message.
4071 * This one does it "for real" because it's called by
4072 * the autopurger function that processes the queue
4073 * created by AdjRefCount(). If a message's reference
4074 * count becomes zero, we also delete the message from
4075 * disk and de-index it.
4077 void TDAP_AdjRefCount(long msgnum, int incr)
4080 struct MetaData smi;
4083 /* This is a *tight* critical section; please keep it that way, as
4084 * it may get called while nested in other critical sections.
4085 * Complicating this any further will surely cause deadlock!
4087 begin_critical_section(S_SUPPMSGMAIN);
4088 GetMetaData(&smi, msgnum);
4089 smi.meta_refcount += incr;
4091 end_critical_section(S_SUPPMSGMAIN);
4092 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4093 msgnum, incr, smi.meta_refcount);
4095 /* If the reference count is now zero, delete the message
4096 * (and its supplementary record as well).
4098 if (smi.meta_refcount == 0) {
4099 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4101 /* Remove from fulltext index */
4102 if (config.c_enable_fulltext) {
4103 ft_index_message(msgnum, 0);
4106 /* Remove from message base */
4108 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4109 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4111 /* Remove metadata record */
4112 delnum = (0L - msgnum);
4113 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4119 * Write a generic object to this room
4121 * Note: this could be much more efficient. Right now we use two temporary
4122 * files, and still pull the message into memory as with all others.
4124 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4125 char *content_type, /* MIME type of this object */
4126 char *tempfilename, /* Where to fetch it from */
4127 struct ctdluser *is_mailbox, /* Mailbox room? */
4128 int is_binary, /* Is encoding necessary? */
4129 int is_unique, /* Del others of this type? */
4130 unsigned int flags /* Internal save flags */
4135 struct ctdlroom qrbuf;
4136 char roomname[ROOMNAMELEN];
4137 struct CtdlMessage *msg;
4139 char *raw_message = NULL;
4140 char *encoded_message = NULL;
4141 off_t raw_length = 0;
4143 if (is_mailbox != NULL) {
4144 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4147 safestrncpy(roomname, req_room, sizeof(roomname));
4150 fp = fopen(tempfilename, "rb");
4152 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4153 tempfilename, strerror(errno));
4156 fseek(fp, 0L, SEEK_END);
4157 raw_length = ftell(fp);
4159 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4161 raw_message = malloc((size_t)raw_length + 2);
4162 fread(raw_message, (size_t)raw_length, 1, fp);
4166 encoded_message = malloc((size_t)
4167 (((raw_length * 134) / 100) + 4096 ) );
4170 encoded_message = malloc((size_t)(raw_length + 4096));
4173 sprintf(encoded_message, "Content-type: %s\n", content_type);
4176 sprintf(&encoded_message[strlen(encoded_message)],
4177 "Content-transfer-encoding: base64\n\n"
4181 sprintf(&encoded_message[strlen(encoded_message)],
4182 "Content-transfer-encoding: 7bit\n\n"
4188 &encoded_message[strlen(encoded_message)],
4194 raw_message[raw_length] = 0;
4196 &encoded_message[strlen(encoded_message)],
4204 lprintf(CTDL_DEBUG, "Allocating\n");
4205 msg = malloc(sizeof(struct CtdlMessage));
4206 memset(msg, 0, sizeof(struct CtdlMessage));
4207 msg->cm_magic = CTDLMESSAGE_MAGIC;
4208 msg->cm_anon_type = MES_NORMAL;
4209 msg->cm_format_type = 4;
4210 msg->cm_fields['A'] = strdup(CC->user.fullname);
4211 msg->cm_fields['O'] = strdup(req_room);
4212 msg->cm_fields['N'] = strdup(config.c_nodename);
4213 msg->cm_fields['H'] = strdup(config.c_humannode);
4214 msg->cm_flags = flags;
4216 msg->cm_fields['M'] = encoded_message;
4218 /* Create the requested room if we have to. */
4219 if (getroom(&qrbuf, roomname) != 0) {
4220 create_room(roomname,
4221 ( (is_mailbox != NULL) ? 5 : 3 ),
4222 "", 0, 1, 0, VIEW_BBS);
4224 /* If the caller specified this object as unique, delete all
4225 * other objects of this type that are currently in the room.
4228 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4229 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4232 /* Now write the data */
4233 CtdlSubmitMsg(msg, NULL, roomname);
4234 CtdlFreeMessage(msg);
4242 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4243 config_msgnum = msgnum;
4247 char *CtdlGetSysConfig(char *sysconfname) {
4248 char hold_rm[ROOMNAMELEN];
4251 struct CtdlMessage *msg;
4254 strcpy(hold_rm, CC->room.QRname);
4255 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4256 getroom(&CC->room, hold_rm);
4261 /* We want the last (and probably only) config in this room */
4262 begin_critical_section(S_CONFIG);
4263 config_msgnum = (-1L);
4264 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4265 CtdlGetSysConfigBackend, NULL);
4266 msgnum = config_msgnum;
4267 end_critical_section(S_CONFIG);
4273 msg = CtdlFetchMessage(msgnum, 1);
4275 conf = strdup(msg->cm_fields['M']);
4276 CtdlFreeMessage(msg);
4283 getroom(&CC->room, hold_rm);
4285 if (conf != NULL) do {
4286 extract_token(buf, conf, 0, '\n', sizeof buf);
4287 strcpy(conf, &conf[strlen(buf)+1]);
4288 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
4293 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4294 char temp[PATH_MAX];
4297 CtdlMakeTempFileName(temp, sizeof temp);
4299 fp = fopen(temp, "w");
4300 if (fp == NULL) return;
4301 fprintf(fp, "%s", sysconfdata);
4304 /* this handy API function does all the work for us */
4305 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4311 * Determine whether a given Internet address belongs to the current user
4313 int CtdlIsMe(char *addr, int addr_buf_len)
4315 struct recptypes *recp;
4318 recp = validate_recipients(addr);
4319 if (recp == NULL) return(0);
4321 if (recp->num_local == 0) {
4322 free_recipients(recp);
4326 for (i=0; i<recp->num_local; ++i) {
4327 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4328 if (!strcasecmp(addr, CC->user.fullname)) {
4329 free_recipients(recp);
4334 free_recipients(recp);
4340 * Citadel protocol command to do the same
4342 void cmd_isme(char *argbuf) {
4345 if (CtdlAccessCheck(ac_logged_in)) return;
4346 extract_token(addr, argbuf, 0, '|', sizeof addr);
4348 if (CtdlIsMe(addr, sizeof addr)) {
4349 cprintf("%d %s\n", CIT_OK, addr);
4352 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);