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"
53 #include "euidindex.h"
54 #include "journaling.h"
55 #include "citadel_dirs.h"
56 #include "clientsocket.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
112 * This function is self explanatory.
113 * (What can I say, I'm in a weird mood today...)
115 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
119 for (i = 0; i < strlen(name); ++i) {
120 if (name[i] == '@') {
121 while (isspace(name[i - 1]) && i > 0) {
122 strcpy(&name[i - 1], &name[i]);
125 while (isspace(name[i + 1])) {
126 strcpy(&name[i + 1], &name[i + 2]);
134 * Aliasing for network mail.
135 * (Error messages have been commented out, because this is a server.)
137 int alias(char *name)
138 { /* process alias and routing info for mail */
141 char aaa[SIZ], bbb[SIZ];
142 char *ignetcfg = NULL;
143 char *ignetmap = NULL;
149 char original_name[256];
150 safestrncpy(original_name, name, sizeof original_name);
153 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
154 stripallbut(name, '<', '>');
156 fp = fopen(file_mail_aliases, "r");
158 fp = fopen("/dev/null", "r");
165 while (fgets(aaa, sizeof aaa, fp) != NULL) {
166 while (isspace(name[0]))
167 strcpy(name, &name[1]);
168 aaa[strlen(aaa) - 1] = 0;
170 for (a = 0; a < strlen(aaa); ++a) {
172 strcpy(bbb, &aaa[a + 1]);
176 if (!strcasecmp(name, aaa))
181 /* Hit the Global Address Book */
182 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
186 if (strcasecmp(original_name, name)) {
187 lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
190 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
191 for (a=0; a<strlen(name); ++a) {
192 if (name[a] == '@') {
193 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
195 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
200 /* determine local or remote type, see citadel.h */
201 at = haschar(name, '@');
202 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
203 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
204 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
206 /* figure out the delivery mode */
207 extract_token(node, name, 1, '@', sizeof node);
209 /* If there are one or more dots in the nodename, we assume that it
210 * is an FQDN and will attempt SMTP delivery to the Internet.
212 if (haschar(node, '.') > 0) {
213 return(MES_INTERNET);
216 /* Otherwise we look in the IGnet maps for a valid Citadel node.
217 * Try directly-connected nodes first...
219 ignetcfg = CtdlGetSysConfig(IGNETCFG);
220 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
221 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
222 extract_token(testnode, buf, 0, '|', sizeof testnode);
223 if (!strcasecmp(node, testnode)) {
231 * Then try nodes that are two or more hops away.
233 ignetmap = CtdlGetSysConfig(IGNETMAP);
234 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
235 extract_token(buf, ignetmap, i, '\n', sizeof buf);
236 extract_token(testnode, buf, 0, '|', sizeof testnode);
237 if (!strcasecmp(node, testnode)) {
244 /* If we get to this point it's an invalid node name */
250 * Back end for the MSGS command: output message number only.
252 void simple_listing(long msgnum, void *userdata)
254 cprintf("%ld\n", msgnum);
260 * Back end for the MSGS command: output header summary.
262 void headers_listing(long msgnum, void *userdata)
264 struct CtdlMessage *msg;
266 msg = CtdlFetchMessage(msgnum, 0);
268 cprintf("%ld|0|||||\n", msgnum);
272 cprintf("%ld|%s|%s|%s|%s|%s|\n",
274 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
275 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
276 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
277 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
278 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
280 CtdlFreeMessage(msg);
285 /* Determine if a given message matches the fields in a message template.
286 * Return 0 for a successful match.
288 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
291 /* If there aren't any fields in the template, all messages will
294 if (template == NULL) return(0);
296 /* Null messages are bogus. */
297 if (msg == NULL) return(1);
299 for (i='A'; i<='Z'; ++i) {
300 if (template->cm_fields[i] != NULL) {
301 if (msg->cm_fields[i] == NULL) {
304 if (strcasecmp(msg->cm_fields[i],
305 template->cm_fields[i])) return 1;
309 /* All compares succeeded: we have a match! */
316 * Retrieve the "seen" message list for the current room.
318 void CtdlGetSeen(char *buf, int which_set) {
321 /* Learn about the user and room in question */
322 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
324 if (which_set == ctdlsetseen_seen)
325 safestrncpy(buf, vbuf.v_seen, SIZ);
326 if (which_set == ctdlsetseen_answered)
327 safestrncpy(buf, vbuf.v_answered, SIZ);
333 * Manipulate the "seen msgs" string (or other message set strings)
335 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
336 int target_setting, int which_set,
337 struct ctdluser *which_user, struct ctdlroom *which_room) {
338 struct cdbdata *cdbfr;
350 char *is_set; /* actually an array of booleans */
353 char setstr[SIZ], lostr[SIZ], histr[SIZ];
356 /* Don't bother doing *anything* if we were passed a list of zero messages */
357 if (num_target_msgnums < 1) {
361 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
362 num_target_msgnums, target_msgnums[0],
363 target_setting, which_set);
365 /* Learn about the user and room in question */
366 CtdlGetRelationship(&vbuf,
367 ((which_user != NULL) ? which_user : &CC->user),
368 ((which_room != NULL) ? which_room : &CC->room)
371 /* Load the message list */
372 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
374 msglist = (long *) cdbfr->ptr;
375 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
376 num_msgs = cdbfr->len / sizeof(long);
379 return; /* No messages at all? No further action. */
382 is_set = malloc(num_msgs * sizeof(char));
383 memset(is_set, 0, (num_msgs * sizeof(char)) );
385 /* Decide which message set we're manipulating */
387 case ctdlsetseen_seen:
388 safestrncpy(vset, vbuf.v_seen, sizeof vset);
390 case ctdlsetseen_answered:
391 safestrncpy(vset, vbuf.v_answered, sizeof vset);
395 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
397 /* Translate the existing sequence set into an array of booleans */
398 num_sets = num_tokens(vset, ',');
399 for (s=0; s<num_sets; ++s) {
400 extract_token(setstr, vset, s, ',', sizeof setstr);
402 extract_token(lostr, setstr, 0, ':', sizeof lostr);
403 if (num_tokens(setstr, ':') >= 2) {
404 extract_token(histr, setstr, 1, ':', sizeof histr);
405 if (!strcmp(histr, "*")) {
406 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
410 strcpy(histr, lostr);
415 for (i = 0; i < num_msgs; ++i) {
416 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
422 /* Now translate the array of booleans back into a sequence set */
427 for (i=0; i<num_msgs; ++i) {
429 is_seen = is_set[i]; /* Default to existing setting */
431 for (k=0; k<num_target_msgnums; ++k) {
432 if (msglist[i] == target_msgnums[k]) {
433 is_seen = target_setting;
438 if (lo < 0L) lo = msglist[i];
442 if ( ((is_seen == 0) && (was_seen == 1))
443 || ((is_seen == 1) && (i == num_msgs-1)) ) {
445 /* begin trim-o-matic code */
448 while ( (strlen(vset) + 20) > sizeof vset) {
449 remove_token(vset, 0, ',');
451 if (j--) break; /* loop no more than 9 times */
453 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
457 snprintf(lostr, sizeof lostr,
458 "1:%ld,%s", t, vset);
459 safestrncpy(vset, lostr, sizeof vset);
461 /* end trim-o-matic code */
469 snprintf(&vset[tmp], (sizeof vset) - tmp,
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 /* Decide which message set we're manipulating */
484 case ctdlsetseen_seen:
485 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
487 case ctdlsetseen_answered:
488 safestrncpy(vbuf.v_answered, vset,
489 sizeof vbuf.v_answered);
494 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
496 CtdlSetRelationship(&vbuf,
497 ((which_user != NULL) ? which_user : &CC->user),
498 ((which_room != NULL) ? which_room : &CC->room)
504 * API function to perform an operation for each qualifying message in the
505 * current room. (Returns the number of messages processed.)
507 int CtdlForEachMessage(int mode, long ref, char *search_string,
509 struct CtdlMessage *compare,
510 void (*CallBack) (long, void *),
516 struct cdbdata *cdbfr;
517 long *msglist = NULL;
519 int num_processed = 0;
522 struct CtdlMessage *msg = NULL;
525 int printed_lastold = 0;
526 int num_search_msgs = 0;
527 long *search_msgs = NULL;
529 int need_to_free_re = 0;
532 if (content_type) if (!IsEmptyStr(content_type)) {
533 regcomp(&re, content_type, 0);
537 /* Learn about the user and room in question */
538 getuser(&CC->user, CC->curr_user);
539 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
541 /* Load the message list */
542 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
544 msglist = (long *) cdbfr->ptr;
545 num_msgs = cdbfr->len / sizeof(long);
547 if (need_to_free_re) regfree(&re);
548 return 0; /* No messages at all? No further action. */
553 * Now begin the traversal.
555 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
557 /* If the caller is looking for a specific MIME type, filter
558 * out all messages which are not of the type requested.
560 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
562 /* This call to GetMetaData() sits inside this loop
563 * so that we only do the extra database read per msg
564 * if we need to. Doing the extra read all the time
565 * really kills the server. If we ever need to use
566 * metadata for another search criterion, we need to
567 * move the read somewhere else -- but still be smart
568 * enough to only do the read if the caller has
569 * specified something that will need it.
571 GetMetaData(&smi, msglist[a]);
573 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
574 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
580 num_msgs = sort_msglist(msglist, num_msgs);
582 /* If a template was supplied, filter out the messages which
583 * don't match. (This could induce some delays!)
586 if (compare != NULL) {
587 for (a = 0; a < num_msgs; ++a) {
588 msg = CtdlFetchMessage(msglist[a], 1);
590 if (CtdlMsgCmp(msg, compare)) {
593 CtdlFreeMessage(msg);
599 /* If a search string was specified, get a message list from
600 * the full text index and remove messages which aren't on both
604 * Since the lists are sorted and strictly ascending, and the
605 * output list is guaranteed to be shorter than or equal to the
606 * input list, we overwrite the bottom of the input list. This
607 * eliminates the need to memmove big chunks of the list over and
610 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
612 /* Call search module via hook mechanism.
613 * NULL means use any search function available.
614 * otherwise replace with a char * to name of search routine
616 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
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 cdb_free(cdbfr); /* 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 || (IsEmptyStr(cbtype)) ) {
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 (!IsEmptyStr(cbcharset)) {
1311 cprintf("; charset=%s", cbcharset);
1313 cprintf("\nContent-length: %d\n",
1314 (int)(length + add_newline) );
1315 if (!IsEmptyStr(encoding)) {
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 (!IsEmptyStr(section)) 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 (!IsEmptyStr(TheMessage->cm_fields['N']))
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);
1682 if (haschar(mptr, '@') == 0)
1684 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1688 cprintf("To: %s%s", mptr, nl);
1691 else if (i == 'T') {
1692 datestring(datestamp, sizeof datestamp,
1693 atol(mptr), DATESTRING_RFC822);
1694 cprintf("Date: %s%s", datestamp, nl);
1698 if (subject_found == 0) {
1699 cprintf("Subject: (no subject)%s", nl);
1703 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1704 suser[i] = tolower(suser[i]);
1705 if (!isalnum(suser[i])) suser[i]='_';
1708 if (mode == MT_RFC822) {
1709 if (!strcasecmp(snode, NODENAME)) {
1710 safestrncpy(snode, FQDN, sizeof snode);
1713 /* Construct a fun message id */
1714 cprintf("Message-ID: <%s", mid);
1715 if (strchr(mid, '@')==NULL) {
1716 cprintf("@%s", snode);
1720 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1721 cprintf("From: \"----\" <x@x.org>%s", nl);
1723 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1724 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1726 else if (!IsEmptyStr(fuser)) {
1727 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1730 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1733 cprintf("Organization: %s%s", lnode, nl);
1735 /* Blank line signifying RFC822 end-of-headers */
1736 if (TheMessage->cm_format_type != FMT_RFC822) {
1741 /* end header processing loop ... at this point, we're in the text */
1743 if (headers_only == HEADERS_FAST) goto DONE;
1744 mptr = TheMessage->cm_fields['M'];
1746 /* Tell the client about the MIME parts in this message */
1747 if (TheMessage->cm_format_type == FMT_RFC822) {
1748 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1749 memset(&ma, 0, sizeof(struct ma_info));
1750 mime_parser(mptr, NULL,
1751 (do_proto ? *list_this_part : NULL),
1752 (do_proto ? *list_this_pref : NULL),
1753 (do_proto ? *list_this_suff : NULL),
1756 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1757 char *start_of_text = NULL;
1758 start_of_text = strstr(mptr, "\n\r\n");
1759 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1760 if (start_of_text == NULL) start_of_text = mptr;
1762 start_of_text = strstr(start_of_text, "\n");
1767 int nllen = strlen(nl);
1768 while (ch=*mptr, ch!=0) {
1774 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1775 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1776 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1779 sprintf(&outbuf[outlen], "%s", nl);
1783 outbuf[outlen++] = ch;
1788 if (outlen > 1000) {
1789 client_write(outbuf, outlen);
1794 client_write(outbuf, outlen);
1802 if (headers_only == HEADERS_ONLY) {
1806 /* signify start of msg text */
1807 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1808 if (do_proto) cprintf("text\n");
1811 /* If the format type on disk is 1 (fixed-format), then we want
1812 * everything to be output completely literally ... regardless of
1813 * what message transfer format is in use.
1815 if (TheMessage->cm_format_type == FMT_FIXED) {
1816 if (mode == MT_MIME) {
1817 cprintf("Content-type: text/plain\n\n");
1820 while (ch = *mptr++, ch > 0) {
1823 if ((ch == 10) || (strlen(buf) > 250)) {
1824 cprintf("%s%s", buf, nl);
1827 buf[strlen(buf) + 1] = 0;
1828 buf[strlen(buf)] = ch;
1831 if (!IsEmptyStr(buf))
1832 cprintf("%s%s", buf, nl);
1835 /* If the message on disk is format 0 (Citadel vari-format), we
1836 * output using the formatter at 80 columns. This is the final output
1837 * form if the transfer format is RFC822, but if the transfer format
1838 * is Citadel proprietary, it'll still work, because the indentation
1839 * for new paragraphs is correct and the client will reformat the
1840 * message to the reader's screen width.
1842 if (TheMessage->cm_format_type == FMT_CITADEL) {
1843 if (mode == MT_MIME) {
1844 cprintf("Content-type: text/x-citadel-variformat\n\n");
1846 memfmout(mptr, 0, nl);
1849 /* If the message on disk is format 4 (MIME), we've gotta hand it
1850 * off to the MIME parser. The client has already been told that
1851 * this message is format 1 (fixed format), so the callback function
1852 * we use will display those parts as-is.
1854 if (TheMessage->cm_format_type == FMT_RFC822) {
1855 memset(&ma, 0, sizeof(struct ma_info));
1857 if (mode == MT_MIME) {
1858 ma.use_fo_hooks = 0;
1859 strcpy(ma.chosen_part, "1");
1860 ma.chosen_pref = 9999;
1861 mime_parser(mptr, NULL,
1862 *choose_preferred, *fixed_output_pre,
1863 *fixed_output_post, (void *)&ma, 0);
1864 mime_parser(mptr, NULL,
1865 *output_preferred, NULL, NULL, (void *)&ma, 0);
1868 ma.use_fo_hooks = 1;
1869 mime_parser(mptr, NULL,
1870 *fixed_output, *fixed_output_pre,
1871 *fixed_output_post, (void *)&ma, 0);
1876 DONE: /* now we're done */
1877 if (do_proto) cprintf("000\n");
1884 * display a message (mode 0 - Citadel proprietary)
1886 void cmd_msg0(char *cmdbuf)
1889 int headers_only = HEADERS_ALL;
1891 msgid = extract_long(cmdbuf, 0);
1892 headers_only = extract_int(cmdbuf, 1);
1894 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1900 * display a message (mode 2 - RFC822)
1902 void cmd_msg2(char *cmdbuf)
1905 int headers_only = HEADERS_ALL;
1907 msgid = extract_long(cmdbuf, 0);
1908 headers_only = extract_int(cmdbuf, 1);
1910 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1916 * display a message (mode 3 - IGnet raw format - internal programs only)
1918 void cmd_msg3(char *cmdbuf)
1921 struct CtdlMessage *msg = NULL;
1924 if (CC->internal_pgm == 0) {
1925 cprintf("%d This command is for internal programs only.\n",
1926 ERROR + HIGHER_ACCESS_REQUIRED);
1930 msgnum = extract_long(cmdbuf, 0);
1931 msg = CtdlFetchMessage(msgnum, 1);
1933 cprintf("%d Message %ld not found.\n",
1934 ERROR + MESSAGE_NOT_FOUND, msgnum);
1938 serialize_message(&smr, msg);
1939 CtdlFreeMessage(msg);
1942 cprintf("%d Unable to serialize message\n",
1943 ERROR + INTERNAL_ERROR);
1947 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1948 client_write((char *)smr.ser, (int)smr.len);
1955 * Display a message using MIME content types
1957 void cmd_msg4(char *cmdbuf)
1962 msgid = extract_long(cmdbuf, 0);
1963 extract_token(section, cmdbuf, 1, '|', sizeof section);
1964 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1970 * Client tells us its preferred message format(s)
1972 void cmd_msgp(char *cmdbuf)
1974 safestrncpy(CC->preferred_formats, cmdbuf,
1975 sizeof(CC->preferred_formats));
1976 cprintf("%d ok\n", CIT_OK);
1981 * Open a component of a MIME message as a download file
1983 void cmd_opna(char *cmdbuf)
1986 char desired_section[128];
1988 msgid = extract_long(cmdbuf, 0);
1989 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1990 safestrncpy(CC->download_desired_section, desired_section,
1991 sizeof CC->download_desired_section);
1992 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1997 * Open a component of a MIME message and transmit it all at once
1999 void cmd_dlat(char *cmdbuf)
2002 char desired_section[128];
2004 msgid = extract_long(cmdbuf, 0);
2005 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2006 safestrncpy(CC->download_desired_section, desired_section,
2007 sizeof CC->download_desired_section);
2008 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2013 * Save one or more message pointers into a specified room
2014 * (Returns 0 for success, nonzero for failure)
2015 * roomname may be NULL to use the current room
2017 * Note that the 'supplied_msg' field may be set to NULL, in which case
2018 * the message will be fetched from disk, by number, if we need to perform
2019 * replication checks. This adds an additional database read, so if the
2020 * caller already has the message in memory then it should be supplied. (Obviously
2021 * this mode of operation only works if we're saving a single message.)
2023 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2024 int do_repl_check, struct CtdlMessage *supplied_msg)
2027 char hold_rm[ROOMNAMELEN];
2028 struct cdbdata *cdbfr;
2031 long highest_msg = 0L;
2034 struct CtdlMessage *msg = NULL;
2036 long *msgs_to_be_merged = NULL;
2037 int num_msgs_to_be_merged = 0;
2040 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2041 roomname, num_newmsgs, do_repl_check);
2043 strcpy(hold_rm, CC->room.QRname);
2046 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2047 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2048 if (num_newmsgs > 1) supplied_msg = NULL;
2050 /* Now the regular stuff */
2051 if (lgetroom(&CC->room,
2052 ((roomname != NULL) ? roomname : CC->room.QRname) )
2054 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2055 return(ERROR + ROOM_NOT_FOUND);
2059 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2060 num_msgs_to_be_merged = 0;
2063 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2064 if (cdbfr == NULL) {
2068 msglist = (long *) cdbfr->ptr;
2069 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2070 num_msgs = cdbfr->len / sizeof(long);
2075 /* Create a list of msgid's which were supplied by the caller, but do
2076 * not already exist in the target room. It is absolutely taboo to
2077 * have more than one reference to the same message in a room.
2079 for (i=0; i<num_newmsgs; ++i) {
2081 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2082 if (msglist[j] == newmsgidlist[i]) {
2087 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2091 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2094 * Now merge the new messages
2096 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2097 if (msglist == NULL) {
2098 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2100 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2101 num_msgs += num_msgs_to_be_merged;
2103 /* Sort the message list, so all the msgid's are in order */
2104 num_msgs = sort_msglist(msglist, num_msgs);
2106 /* Determine the highest message number */
2107 highest_msg = msglist[num_msgs - 1];
2109 /* Write it back to disk. */
2110 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2111 msglist, (int)(num_msgs * sizeof(long)));
2113 /* Free up the memory we used. */
2116 /* Update the highest-message pointer and unlock the room. */
2117 CC->room.QRhighest = highest_msg;
2118 lputroom(&CC->room);
2120 /* Perform replication checks if necessary */
2121 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2122 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2124 for (i=0; i<num_msgs_to_be_merged; ++i) {
2125 msgid = msgs_to_be_merged[i];
2127 if (supplied_msg != NULL) {
2131 msg = CtdlFetchMessage(msgid, 0);
2135 ReplicationChecks(msg);
2137 /* If the message has an Exclusive ID, index that... */
2138 if (msg->cm_fields['E'] != NULL) {
2139 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2142 /* Free up the memory we may have allocated */
2143 if (msg != supplied_msg) {
2144 CtdlFreeMessage(msg);
2152 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2155 /* Submit this room for processing by hooks */
2156 PerformRoomHooks(&CC->room);
2158 /* Go back to the room we were in before we wandered here... */
2159 getroom(&CC->room, hold_rm);
2161 /* Bump the reference count for all messages which were merged */
2162 for (i=0; i<num_msgs_to_be_merged; ++i) {
2163 AdjRefCount(msgs_to_be_merged[i], +1);
2166 /* Free up memory... */
2167 if (msgs_to_be_merged != NULL) {
2168 free(msgs_to_be_merged);
2171 /* Return success. */
2177 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2180 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2181 int do_repl_check, struct CtdlMessage *supplied_msg)
2183 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2190 * Message base operation to save a new message to the message store
2191 * (returns new message number)
2193 * This is the back end for CtdlSubmitMsg() and should not be directly
2194 * called by server-side modules.
2197 long send_message(struct CtdlMessage *msg) {
2205 /* Get a new message number */
2206 newmsgid = get_new_message_number();
2207 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2209 /* Generate an ID if we don't have one already */
2210 if (msg->cm_fields['I']==NULL) {
2211 msg->cm_fields['I'] = strdup(msgidbuf);
2214 /* If the message is big, set its body aside for storage elsewhere */
2215 if (msg->cm_fields['M'] != NULL) {
2216 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2218 holdM = msg->cm_fields['M'];
2219 msg->cm_fields['M'] = NULL;
2223 /* Serialize our data structure for storage in the database */
2224 serialize_message(&smr, msg);
2227 msg->cm_fields['M'] = holdM;
2231 cprintf("%d Unable to serialize message\n",
2232 ERROR + INTERNAL_ERROR);
2236 /* Write our little bundle of joy into the message base */
2237 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2238 smr.ser, smr.len) < 0) {
2239 lprintf(CTDL_ERR, "Can't store message\n");
2243 cdb_store(CDB_BIGMSGS,
2253 /* Free the memory we used for the serialized message */
2256 /* Return the *local* message ID to the caller
2257 * (even if we're storing an incoming network message)
2265 * Serialize a struct CtdlMessage into the format used on disk and network.
2267 * This function loads up a "struct ser_ret" (defined in server.h) which
2268 * contains the length of the serialized message and a pointer to the
2269 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2271 void serialize_message(struct ser_ret *ret, /* return values */
2272 struct CtdlMessage *msg) /* unserialized msg */
2274 size_t wlen, fieldlen;
2276 static char *forder = FORDER;
2279 * Check for valid message format
2281 if (is_valid_message(msg) == 0) {
2282 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2289 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2290 ret->len = ret->len +
2291 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2293 ret->ser = malloc(ret->len);
2294 if (ret->ser == NULL) {
2295 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2296 (long)ret->len, strerror(errno));
2303 ret->ser[1] = msg->cm_anon_type;
2304 ret->ser[2] = msg->cm_format_type;
2307 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2308 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2309 ret->ser[wlen++] = (char)forder[i];
2310 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2311 wlen = wlen + fieldlen + 1;
2313 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2314 (long)ret->len, (long)wlen);
2322 * Check to see if any messages already exist in the current room which
2323 * carry the same Exclusive ID as this one. If any are found, delete them.
2325 void ReplicationChecks(struct CtdlMessage *msg) {
2326 long old_msgnum = (-1L);
2328 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2330 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2333 /* No exclusive id? Don't do anything. */
2334 if (msg == NULL) return;
2335 if (msg->cm_fields['E'] == NULL) return;
2336 if (IsEmptyStr(msg->cm_fields['E'])) return;
2337 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2338 msg->cm_fields['E'], CC->room.QRname);*/
2340 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2341 if (old_msgnum > 0L) {
2342 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2343 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2350 * Save a message to disk and submit it into the delivery system.
2352 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2353 struct recptypes *recps, /* recipients (if mail) */
2354 char *force /* force a particular room? */
2356 char submit_filename[128];
2357 char generated_timestamp[32];
2358 char hold_rm[ROOMNAMELEN];
2359 char actual_rm[ROOMNAMELEN];
2360 char force_room[ROOMNAMELEN];
2361 char content_type[SIZ]; /* We have to learn this */
2362 char recipient[SIZ];
2365 struct ctdluser userbuf;
2367 struct MetaData smi;
2368 FILE *network_fp = NULL;
2369 static int seqnum = 1;
2370 struct CtdlMessage *imsg = NULL;
2372 size_t instr_alloc = 0;
2374 char *hold_R, *hold_D;
2375 char *collected_addresses = NULL;
2376 struct addresses_to_be_filed *aptr = NULL;
2377 char *saved_rfc822_version = NULL;
2378 int qualified_for_journaling = 0;
2380 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2381 if (is_valid_message(msg) == 0) return(-1); /* self check */
2383 /* If this message has no timestamp, we take the liberty of
2384 * giving it one, right now.
2386 if (msg->cm_fields['T'] == NULL) {
2387 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2388 msg->cm_fields['T'] = strdup(generated_timestamp);
2391 /* If this message has no path, we generate one.
2393 if (msg->cm_fields['P'] == NULL) {
2394 if (msg->cm_fields['A'] != NULL) {
2395 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2396 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2397 if (isspace(msg->cm_fields['P'][a])) {
2398 msg->cm_fields['P'][a] = ' ';
2403 msg->cm_fields['P'] = strdup("unknown");
2407 if (force == NULL) {
2408 strcpy(force_room, "");
2411 strcpy(force_room, force);
2414 /* Learn about what's inside, because it's what's inside that counts */
2415 if (msg->cm_fields['M'] == NULL) {
2416 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2420 switch (msg->cm_format_type) {
2422 strcpy(content_type, "text/x-citadel-variformat");
2425 strcpy(content_type, "text/plain");
2428 strcpy(content_type, "text/plain");
2429 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2431 safestrncpy(content_type, &mptr[13], sizeof content_type);
2432 striplt(content_type);
2433 for (a = 0; a < strlen(content_type); ++a) {
2434 if ((content_type[a] == ';')
2435 || (content_type[a] == ' ')
2436 || (content_type[a] == 13)
2437 || (content_type[a] == 10)) {
2438 content_type[a] = 0;
2444 /* Goto the correct room */
2445 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2446 strcpy(hold_rm, CC->room.QRname);
2447 strcpy(actual_rm, CC->room.QRname);
2448 if (recps != NULL) {
2449 strcpy(actual_rm, SENTITEMS);
2452 /* If the user is a twit, move to the twit room for posting */
2454 if (CC->user.axlevel == 2) {
2455 strcpy(hold_rm, actual_rm);
2456 strcpy(actual_rm, config.c_twitroom);
2457 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2461 /* ...or if this message is destined for Aide> then go there. */
2462 if (!IsEmptyStr(force_room)) {
2463 strcpy(actual_rm, force_room);
2466 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2467 if (strcasecmp(actual_rm, CC->room.QRname)) {
2468 /* getroom(&CC->room, actual_rm); */
2469 usergoto(actual_rm, 0, 1, NULL, NULL);
2473 * If this message has no O (room) field, generate one.
2475 if (msg->cm_fields['O'] == NULL) {
2476 msg->cm_fields['O'] = strdup(CC->room.QRname);
2479 /* Perform "before save" hooks (aborting if any return nonzero) */
2480 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2481 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2484 * If this message has an Exclusive ID, and the room is replication
2485 * checking enabled, then do replication checks.
2487 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2488 ReplicationChecks(msg);
2491 /* Save it to disk */
2492 lprintf(CTDL_DEBUG, "Saving to disk\n");
2493 newmsgid = send_message(msg);
2494 if (newmsgid <= 0L) return(-5);
2496 /* Write a supplemental message info record. This doesn't have to
2497 * be a critical section because nobody else knows about this message
2500 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2501 memset(&smi, 0, sizeof(struct MetaData));
2502 smi.meta_msgnum = newmsgid;
2503 smi.meta_refcount = 0;
2504 safestrncpy(smi.meta_content_type, content_type,
2505 sizeof smi.meta_content_type);
2508 * Measure how big this message will be when rendered as RFC822.
2509 * We do this for two reasons:
2510 * 1. We need the RFC822 length for the new metadata record, so the
2511 * POP and IMAP services don't have to calculate message lengths
2512 * while the user is waiting (multiplied by potentially hundreds
2513 * or thousands of messages).
2514 * 2. If journaling is enabled, we will need an RFC822 version of the
2515 * message to attach to the journalized copy.
2517 if (CC->redirect_buffer != NULL) {
2518 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2521 CC->redirect_buffer = malloc(SIZ);
2522 CC->redirect_len = 0;
2523 CC->redirect_alloc = SIZ;
2524 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2525 smi.meta_rfc822_length = CC->redirect_len;
2526 saved_rfc822_version = CC->redirect_buffer;
2527 CC->redirect_buffer = NULL;
2528 CC->redirect_len = 0;
2529 CC->redirect_alloc = 0;
2533 /* Now figure out where to store the pointers */
2534 lprintf(CTDL_DEBUG, "Storing pointers\n");
2536 /* If this is being done by the networker delivering a private
2537 * message, we want to BYPASS saving the sender's copy (because there
2538 * is no local sender; it would otherwise go to the Trashcan).
2540 if ((!CC->internal_pgm) || (recps == NULL)) {
2541 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2542 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2543 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2547 /* For internet mail, drop a copy in the outbound queue room */
2549 if (recps->num_internet > 0) {
2550 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2553 /* If other rooms are specified, drop them there too. */
2555 if (recps->num_room > 0)
2556 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2557 extract_token(recipient, recps->recp_room, i,
2558 '|', sizeof recipient);
2559 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2560 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2563 /* Bump this user's messages posted counter. */
2564 lprintf(CTDL_DEBUG, "Updating user\n");
2565 lgetuser(&CC->user, CC->curr_user);
2566 CC->user.posted = CC->user.posted + 1;
2567 lputuser(&CC->user);
2569 /* If this is private, local mail, make a copy in the
2570 * recipient's mailbox and bump the reference count.
2573 if (recps->num_local > 0)
2574 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2575 extract_token(recipient, recps->recp_local, i,
2576 '|', sizeof recipient);
2577 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2579 if (getuser(&userbuf, recipient) == 0) {
2580 // Add a flag so the Funambol module knows its mail
2581 msg->cm_fields['W'] = strdup(recipient);
2582 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2583 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2584 BumpNewMailCounter(userbuf.usernum);
2585 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2586 /* Generate a instruction message for the Funambol notification
2587 * server, in the same style as the SMTP queue
2590 instr = malloc(instr_alloc);
2591 snprintf(instr, instr_alloc,
2592 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2594 SPOOLMIME, newmsgid, (long)time(NULL),
2595 msg->cm_fields['A'], msg->cm_fields['N']
2598 imsg = malloc(sizeof(struct CtdlMessage));
2599 memset(imsg, 0, sizeof(struct CtdlMessage));
2600 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2601 imsg->cm_anon_type = MES_NORMAL;
2602 imsg->cm_format_type = FMT_RFC822;
2603 imsg->cm_fields['A'] = strdup("Citadel");
2604 imsg->cm_fields['J'] = strdup("do not journal");
2605 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2606 imsg->cm_fields['W'] = strdup(recipient);
2607 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2608 CtdlFreeMessage(imsg);
2612 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2613 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2618 /* Perform "after save" hooks */
2619 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2620 PerformMessageHooks(msg, EVT_AFTERSAVE);
2622 /* For IGnet mail, we have to save a new copy into the spooler for
2623 * each recipient, with the R and D fields set to the recipient and
2624 * destination-node. This has two ugly side effects: all other
2625 * recipients end up being unlisted in this recipient's copy of the
2626 * message, and it has to deliver multiple messages to the same
2627 * node. We'll revisit this again in a year or so when everyone has
2628 * a network spool receiver that can handle the new style messages.
2631 if (recps->num_ignet > 0)
2632 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2633 extract_token(recipient, recps->recp_ignet, i,
2634 '|', sizeof recipient);
2636 hold_R = msg->cm_fields['R'];
2637 hold_D = msg->cm_fields['D'];
2638 msg->cm_fields['R'] = malloc(SIZ);
2639 msg->cm_fields['D'] = malloc(128);
2640 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2641 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2643 serialize_message(&smr, msg);
2645 snprintf(submit_filename, sizeof submit_filename,
2646 "%s/netmail.%04lx.%04x.%04x",
2648 (long) getpid(), CC->cs_pid, ++seqnum);
2649 network_fp = fopen(submit_filename, "wb+");
2650 if (network_fp != NULL) {
2651 fwrite(smr.ser, smr.len, 1, network_fp);
2657 free(msg->cm_fields['R']);
2658 free(msg->cm_fields['D']);
2659 msg->cm_fields['R'] = hold_R;
2660 msg->cm_fields['D'] = hold_D;
2663 /* Go back to the room we started from */
2664 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2665 if (strcasecmp(hold_rm, CC->room.QRname))
2666 usergoto(hold_rm, 0, 1, NULL, NULL);
2668 /* For internet mail, generate delivery instructions.
2669 * Yes, this is recursive. Deal with it. Infinite recursion does
2670 * not happen because the delivery instructions message does not
2671 * contain a recipient.
2674 if (recps->num_internet > 0) {
2675 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2677 instr = malloc(instr_alloc);
2678 snprintf(instr, instr_alloc,
2679 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2681 SPOOLMIME, newmsgid, (long)time(NULL),
2682 msg->cm_fields['A'], msg->cm_fields['N']
2685 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2686 size_t tmp = strlen(instr);
2687 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2688 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2689 instr_alloc = instr_alloc * 2;
2690 instr = realloc(instr, instr_alloc);
2692 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2695 imsg = malloc(sizeof(struct CtdlMessage));
2696 memset(imsg, 0, sizeof(struct CtdlMessage));
2697 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2698 imsg->cm_anon_type = MES_NORMAL;
2699 imsg->cm_format_type = FMT_RFC822;
2700 imsg->cm_fields['A'] = strdup("Citadel");
2701 imsg->cm_fields['J'] = strdup("do not journal");
2702 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2703 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2704 CtdlFreeMessage(imsg);
2708 * Any addresses to harvest for someone's address book?
2710 if ( (CC->logged_in) && (recps != NULL) ) {
2711 collected_addresses = harvest_collected_addresses(msg);
2714 if (collected_addresses != NULL) {
2715 begin_critical_section(S_ATBF);
2716 aptr = (struct addresses_to_be_filed *)
2717 malloc(sizeof(struct addresses_to_be_filed));
2719 MailboxName(actual_rm, sizeof actual_rm,
2720 &CC->user, USERCONTACTSROOM);
2721 aptr->roomname = strdup(actual_rm);
2722 aptr->collected_addresses = collected_addresses;
2724 end_critical_section(S_ATBF);
2728 * Determine whether this message qualifies for journaling.
2730 if (msg->cm_fields['J'] != NULL) {
2731 qualified_for_journaling = 0;
2734 if (recps == NULL) {
2735 qualified_for_journaling = config.c_journal_pubmsgs;
2737 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2738 qualified_for_journaling = config.c_journal_email;
2741 qualified_for_journaling = config.c_journal_pubmsgs;
2746 * Do we have to perform journaling? If so, hand off the saved
2747 * RFC822 version will be handed off to the journaler for background
2748 * submit. Otherwise, we have to free the memory ourselves.
2750 if (saved_rfc822_version != NULL) {
2751 if (qualified_for_journaling) {
2752 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2755 free(saved_rfc822_version);
2768 * Convenience function for generating small administrative messages.
2770 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2771 int format_type, char *subject)
2773 struct CtdlMessage *msg;
2774 struct recptypes *recp = NULL;
2776 msg = malloc(sizeof(struct CtdlMessage));
2777 memset(msg, 0, sizeof(struct CtdlMessage));
2778 msg->cm_magic = CTDLMESSAGE_MAGIC;
2779 msg->cm_anon_type = MES_NORMAL;
2780 msg->cm_format_type = format_type;
2783 msg->cm_fields['A'] = strdup(from);
2785 else if (fromaddr != NULL) {
2786 msg->cm_fields['A'] = strdup(fromaddr);
2787 if (strchr(msg->cm_fields['A'], '@')) {
2788 *strchr(msg->cm_fields['A'], '@') = 0;
2792 msg->cm_fields['A'] = strdup("Citadel");
2795 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2796 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2797 msg->cm_fields['N'] = strdup(NODENAME);
2799 msg->cm_fields['R'] = strdup(to);
2800 recp = validate_recipients(to);
2802 if (subject != NULL) {
2803 msg->cm_fields['U'] = strdup(subject);
2805 msg->cm_fields['M'] = strdup(text);
2807 CtdlSubmitMsg(msg, recp, room);
2808 CtdlFreeMessage(msg);
2809 if (recp != NULL) free_recipients(recp);
2815 * Back end function used by CtdlMakeMessage() and similar functions
2817 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2818 size_t maxlen, /* maximum message length */
2819 char *exist, /* if non-null, append to it;
2820 exist is ALWAYS freed */
2821 int crlf, /* CRLF newlines instead of LF */
2822 int sock /* socket handle or 0 for this session's client socket */
2826 size_t message_len = 0;
2827 size_t buffer_len = 0;
2834 if (exist == NULL) {
2841 message_len = strlen(exist);
2842 buffer_len = message_len + 4096;
2843 m = realloc(exist, buffer_len);
2850 /* Do we need to change leading ".." to "." for SMTP escaping? */
2851 if (!strcmp(terminator, ".")) {
2855 /* flush the input if we have nowhere to store it */
2860 /* read in the lines of message text one by one */
2863 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
2866 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2868 if (!strcmp(buf, terminator)) finished = 1;
2870 strcat(buf, "\r\n");
2876 /* Unescape SMTP-style input of two dots at the beginning of the line */
2878 if (!strncmp(buf, "..", 2)) {
2879 strcpy(buf, &buf[1]);
2883 if ( (!flushing) && (!finished) ) {
2884 /* Measure the line */
2885 linelen = strlen(buf);
2887 /* augment the buffer if we have to */
2888 if ((message_len + linelen) >= buffer_len) {
2889 ptr = realloc(m, (buffer_len * 2) );
2890 if (ptr == NULL) { /* flush if can't allocate */
2893 buffer_len = (buffer_len * 2);
2895 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2899 /* Add the new line to the buffer. NOTE: this loop must avoid
2900 * using functions like strcat() and strlen() because they
2901 * traverse the entire buffer upon every call, and doing that
2902 * for a multi-megabyte message slows it down beyond usability.
2904 strcpy(&m[message_len], buf);
2905 message_len += linelen;
2908 /* if we've hit the max msg length, flush the rest */
2909 if (message_len >= maxlen) flushing = 1;
2911 } while (!finished);
2919 * Build a binary message to be saved on disk.
2920 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2921 * will become part of the message. This means you are no longer
2922 * responsible for managing that memory -- it will be freed along with
2923 * the rest of the fields when CtdlFreeMessage() is called.)
2926 struct CtdlMessage *CtdlMakeMessage(
2927 struct ctdluser *author, /* author's user structure */
2928 char *recipient, /* NULL if it's not mail */
2929 char *recp_cc, /* NULL if it's not mail */
2930 char *room, /* room where it's going */
2931 int type, /* see MES_ types in header file */
2932 int format_type, /* variformat, plain text, MIME... */
2933 char *fake_name, /* who we're masquerading as */
2934 char *my_email, /* which of my email addresses to use (empty is ok) */
2935 char *subject, /* Subject (optional) */
2936 char *supplied_euid, /* ...or NULL if this is irrelevant */
2937 char *preformatted_text /* ...or NULL to read text from client */
2939 char dest_node[256];
2941 struct CtdlMessage *msg;
2944 msg = malloc(sizeof(struct CtdlMessage));
2945 memset(msg, 0, sizeof(struct CtdlMessage));
2946 msg->cm_magic = CTDLMESSAGE_MAGIC;
2947 msg->cm_anon_type = type;
2948 msg->cm_format_type = format_type;
2950 /* Don't confuse the poor folks if it's not routed mail. */
2951 strcpy(dest_node, "");
2956 /* Path or Return-Path */
2957 if (my_email == NULL) my_email = "";
2959 if (!IsEmptyStr(my_email)) {
2960 msg->cm_fields['P'] = strdup(my_email);
2963 snprintf(buf, sizeof buf, "%s", author->fullname);
2964 msg->cm_fields['P'] = strdup(buf);
2966 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
2967 if (isspace(msg->cm_fields['P'][i])) {
2968 msg->cm_fields['P'][i] = '_';
2972 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2973 msg->cm_fields['T'] = strdup(buf);
2975 if (fake_name[0]) /* author */
2976 msg->cm_fields['A'] = strdup(fake_name);
2978 msg->cm_fields['A'] = strdup(author->fullname);
2980 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2981 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2984 msg->cm_fields['O'] = strdup(CC->room.QRname);
2987 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2988 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2990 if (recipient[0] != 0) {
2991 msg->cm_fields['R'] = strdup(recipient);
2993 if (recp_cc[0] != 0) {
2994 msg->cm_fields['Y'] = strdup(recp_cc);
2996 if (dest_node[0] != 0) {
2997 msg->cm_fields['D'] = strdup(dest_node);
3000 if (!IsEmptyStr(my_email)) {
3001 msg->cm_fields['F'] = strdup(my_email);
3003 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3004 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3007 if (subject != NULL) {
3010 length = strlen(subject);
3016 while ((subject[i] != '\0') &&
3017 (IsAscii = isascii(subject[i]) != 0 ))
3020 msg->cm_fields['U'] = strdup(subject);
3021 else /* ok, we've got utf8 in the string. */
3023 msg->cm_fields['U'] = rfc2047encode(subject, length);
3029 if (supplied_euid != NULL) {
3030 msg->cm_fields['E'] = strdup(supplied_euid);
3033 if (preformatted_text != NULL) {
3034 msg->cm_fields['M'] = preformatted_text;
3037 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3045 * Check to see whether we have permission to post a message in the current
3046 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3047 * returns 0 on success.
3049 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3052 if (!(CC->logged_in)) {
3053 snprintf(errmsgbuf, n, "Not logged in.");
3054 return (ERROR + NOT_LOGGED_IN);
3057 if ((CC->user.axlevel < 2)
3058 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3059 snprintf(errmsgbuf, n, "Need to be validated to enter "
3060 "(except in %s> to sysop)", MAILROOM);
3061 return (ERROR + HIGHER_ACCESS_REQUIRED);
3064 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3065 if (!(ra & UA_POSTALLOWED)) {
3066 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3067 return (ERROR + HIGHER_ACCESS_REQUIRED);
3070 strcpy(errmsgbuf, "Ok");
3076 * Check to see if the specified user has Internet mail permission
3077 * (returns nonzero if permission is granted)
3079 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3081 /* Do not allow twits to send Internet mail */
3082 if (who->axlevel <= 2) return(0);
3084 /* Globally enabled? */
3085 if (config.c_restrict == 0) return(1);
3087 /* User flagged ok? */
3088 if (who->flags & US_INTERNET) return(2);
3090 /* Aide level access? */
3091 if (who->axlevel >= 6) return(3);
3093 /* No mail for you! */
3099 * Validate recipients, count delivery types and errors, and handle aliasing
3100 * FIXME check for dupes!!!!!
3102 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3103 * were specified, or the number of addresses found invalid.
3105 * Caller needs to free the result using free_recipients()
3107 struct recptypes *validate_recipients(char *supplied_recipients) {
3108 struct recptypes *ret;
3109 char *recipients = NULL;
3110 char this_recp[256];
3111 char this_recp_cooked[256];
3117 struct ctdluser tempUS;
3118 struct ctdlroom tempQR;
3122 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3123 if (ret == NULL) return(NULL);
3125 /* Set all strings to null and numeric values to zero */
3126 memset(ret, 0, sizeof(struct recptypes));
3128 if (supplied_recipients == NULL) {
3129 recipients = strdup("");
3132 recipients = strdup(supplied_recipients);
3135 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3136 * actually need, but it's healthier for the heap than doing lots of tiny
3137 * realloc() calls instead.
3140 ret->errormsg = malloc(strlen(recipients) + 1024);
3141 ret->recp_local = malloc(strlen(recipients) + 1024);
3142 ret->recp_internet = malloc(strlen(recipients) + 1024);
3143 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3144 ret->recp_room = malloc(strlen(recipients) + 1024);
3145 ret->display_recp = malloc(strlen(recipients) + 1024);
3147 ret->errormsg[0] = 0;
3148 ret->recp_local[0] = 0;
3149 ret->recp_internet[0] = 0;
3150 ret->recp_ignet[0] = 0;
3151 ret->recp_room[0] = 0;
3152 ret->display_recp[0] = 0;
3154 ret->recptypes_magic = RECPTYPES_MAGIC;
3156 /* Change all valid separator characters to commas */
3157 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3158 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3159 recipients[i] = ',';
3163 /* Now start extracting recipients... */
3165 while (!IsEmptyStr(recipients)) {
3167 for (i=0; i<=strlen(recipients); ++i) {
3168 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3169 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3170 safestrncpy(this_recp, recipients, i+1);
3172 if (recipients[i] == ',') {
3173 strcpy(recipients, &recipients[i+1]);
3176 strcpy(recipients, "");
3183 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3185 mailtype = alias(this_recp);
3186 mailtype = alias(this_recp);
3187 mailtype = alias(this_recp);
3188 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3189 if (this_recp[j]=='_') {
3190 this_recp_cooked[j] = ' ';
3193 this_recp_cooked[j] = this_recp[j];
3199 if (!strcasecmp(this_recp, "sysop")) {
3201 strcpy(this_recp, config.c_aideroom);
3202 if (!IsEmptyStr(ret->recp_room)) {
3203 strcat(ret->recp_room, "|");
3205 strcat(ret->recp_room, this_recp);
3207 else if (getuser(&tempUS, this_recp) == 0) {
3209 strcpy(this_recp, tempUS.fullname);
3210 if (!IsEmptyStr(ret->recp_local)) {
3211 strcat(ret->recp_local, "|");
3213 strcat(ret->recp_local, this_recp);
3215 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3217 strcpy(this_recp, tempUS.fullname);
3218 if (!IsEmptyStr(ret->recp_local)) {
3219 strcat(ret->recp_local, "|");
3221 strcat(ret->recp_local, this_recp);
3223 else if ( (!strncasecmp(this_recp, "room_", 5))
3224 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3226 if (!IsEmptyStr(ret->recp_room)) {
3227 strcat(ret->recp_room, "|");
3229 strcat(ret->recp_room, &this_recp_cooked[5]);
3237 /* Yes, you're reading this correctly: if the target
3238 * domain points back to the local system or an attached
3239 * Citadel directory, the address is invalid. That's
3240 * because if the address were valid, we would have
3241 * already translated it to a local address by now.
3243 if (IsDirectory(this_recp, 0)) {
3248 ++ret->num_internet;
3249 if (!IsEmptyStr(ret->recp_internet)) {
3250 strcat(ret->recp_internet, "|");
3252 strcat(ret->recp_internet, this_recp);
3257 if (!IsEmptyStr(ret->recp_ignet)) {
3258 strcat(ret->recp_ignet, "|");
3260 strcat(ret->recp_ignet, this_recp);
3268 if (IsEmptyStr(ret->errormsg)) {
3269 snprintf(append, sizeof append,
3270 "Invalid recipient: %s",
3274 snprintf(append, sizeof append, ", %s", this_recp);
3276 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3277 strcat(ret->errormsg, append);
3281 if (IsEmptyStr(ret->display_recp)) {
3282 strcpy(append, this_recp);
3285 snprintf(append, sizeof append, ", %s", this_recp);
3287 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3288 strcat(ret->display_recp, append);
3293 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3294 ret->num_room + ret->num_error) == 0) {
3295 ret->num_error = (-1);
3296 strcpy(ret->errormsg, "No recipients specified.");
3299 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3300 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3301 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3302 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3303 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3304 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3312 * Destructor for struct recptypes
3314 void free_recipients(struct recptypes *valid) {
3316 if (valid == NULL) {
3320 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3321 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3325 if (valid->errormsg != NULL) free(valid->errormsg);
3326 if (valid->recp_local != NULL) free(valid->recp_local);
3327 if (valid->recp_internet != NULL) free(valid->recp_internet);
3328 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3329 if (valid->recp_room != NULL) free(valid->recp_room);
3330 if (valid->display_recp != NULL) free(valid->display_recp);
3337 * message entry - mode 0 (normal)
3339 void cmd_ent0(char *entargs)
3345 char supplied_euid[128];
3347 int format_type = 0;
3348 char newusername[256];
3349 char newuseremail[256];
3350 struct CtdlMessage *msg;
3354 struct recptypes *valid = NULL;
3355 struct recptypes *valid_to = NULL;
3356 struct recptypes *valid_cc = NULL;
3357 struct recptypes *valid_bcc = NULL;
3359 int subject_required = 0;
3364 int newuseremail_ok = 0;
3368 post = extract_int(entargs, 0);
3369 extract_token(recp, entargs, 1, '|', sizeof recp);
3370 anon_flag = extract_int(entargs, 2);
3371 format_type = extract_int(entargs, 3);
3372 extract_token(subject, entargs, 4, '|', sizeof subject);
3373 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3374 do_confirm = extract_int(entargs, 6);
3375 extract_token(cc, entargs, 7, '|', sizeof cc);
3376 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3377 switch(CC->room.QRdefaultview) {
3380 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3383 supplied_euid[0] = 0;
3386 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3388 /* first check to make sure the request is valid. */
3390 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3393 cprintf("%d %s\n", err, errmsg);
3397 /* Check some other permission type things. */
3399 if (IsEmptyStr(newusername)) {
3400 strcpy(newusername, CC->user.fullname);
3402 if ( (CC->user.axlevel < 6)
3403 && (strcasecmp(newusername, CC->user.fullname))
3404 && (strcasecmp(newusername, CC->cs_inet_fn))
3406 cprintf("%d You don't have permission to author messages as '%s'.\n",
3407 ERROR + HIGHER_ACCESS_REQUIRED,
3414 if (IsEmptyStr(newuseremail)) {
3415 newuseremail_ok = 1;
3418 if (!IsEmptyStr(newuseremail)) {
3419 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3420 newuseremail_ok = 1;
3422 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3423 j = num_tokens(CC->cs_inet_other_emails, '|');
3424 for (i=0; i<j; ++i) {
3425 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3426 if (!strcasecmp(newuseremail, buf)) {
3427 newuseremail_ok = 1;
3433 if (!newuseremail_ok) {
3434 cprintf("%d You don't have permission to author messages as '%s'.\n",
3435 ERROR + HIGHER_ACCESS_REQUIRED,
3441 CC->cs_flags |= CS_POSTING;
3443 /* In mailbox rooms we have to behave a little differently --
3444 * make sure the user has specified at least one recipient. Then
3445 * validate the recipient(s). We do this for the Mail> room, as
3446 * well as any room which has the "Mailbox" view set.
3449 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3450 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3452 if (CC->user.axlevel < 2) {
3453 strcpy(recp, "sysop");
3458 valid_to = validate_recipients(recp);
3459 if (valid_to->num_error > 0) {
3460 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3461 free_recipients(valid_to);
3465 valid_cc = validate_recipients(cc);
3466 if (valid_cc->num_error > 0) {
3467 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3468 free_recipients(valid_to);
3469 free_recipients(valid_cc);
3473 valid_bcc = validate_recipients(bcc);
3474 if (valid_bcc->num_error > 0) {
3475 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3476 free_recipients(valid_to);
3477 free_recipients(valid_cc);
3478 free_recipients(valid_bcc);
3482 /* Recipient required, but none were specified */
3483 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3484 free_recipients(valid_to);
3485 free_recipients(valid_cc);
3486 free_recipients(valid_bcc);
3487 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3491 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3492 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3493 cprintf("%d You do not have permission "
3494 "to send Internet mail.\n",
3495 ERROR + HIGHER_ACCESS_REQUIRED);
3496 free_recipients(valid_to);
3497 free_recipients(valid_cc);
3498 free_recipients(valid_bcc);
3503 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)
3504 && (CC->user.axlevel < 4) ) {
3505 cprintf("%d Higher access required for network mail.\n",
3506 ERROR + HIGHER_ACCESS_REQUIRED);
3507 free_recipients(valid_to);
3508 free_recipients(valid_cc);
3509 free_recipients(valid_bcc);
3513 if ((RESTRICT_INTERNET == 1)
3514 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3515 && ((CC->user.flags & US_INTERNET) == 0)
3516 && (!CC->internal_pgm)) {
3517 cprintf("%d You don't have access to Internet mail.\n",
3518 ERROR + HIGHER_ACCESS_REQUIRED);
3519 free_recipients(valid_to);
3520 free_recipients(valid_cc);
3521 free_recipients(valid_bcc);
3527 /* Is this a room which has anonymous-only or anonymous-option? */
3528 anonymous = MES_NORMAL;
3529 if (CC->room.QRflags & QR_ANONONLY) {
3530 anonymous = MES_ANONONLY;
3532 if (CC->room.QRflags & QR_ANONOPT) {
3533 if (anon_flag == 1) { /* only if the user requested it */
3534 anonymous = MES_ANONOPT;
3538 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3542 /* Recommend to the client that the use of a message subject is
3543 * strongly recommended in this room, if either the SUBJECTREQ flag
3544 * is set, or if there is one or more Internet email recipients.
3546 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3547 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3548 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3549 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3551 /* If we're only checking the validity of the request, return
3552 * success without creating the message.
3555 cprintf("%d %s|%d\n", CIT_OK,
3556 ((valid_to != NULL) ? valid_to->display_recp : ""),
3558 free_recipients(valid_to);
3559 free_recipients(valid_cc);
3560 free_recipients(valid_bcc);
3564 /* We don't need these anymore because we'll do it differently below */
3565 free_recipients(valid_to);
3566 free_recipients(valid_cc);
3567 free_recipients(valid_bcc);
3569 /* Read in the message from the client. */
3571 cprintf("%d send message\n", START_CHAT_MODE);
3573 cprintf("%d send message\n", SEND_LISTING);
3576 msg = CtdlMakeMessage(&CC->user, recp, cc,
3577 CC->room.QRname, anonymous, format_type,
3578 newusername, newuseremail, subject,
3579 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3582 /* Put together one big recipients struct containing to/cc/bcc all in
3583 * one. This is for the envelope.
3585 char *all_recps = malloc(SIZ * 3);
3586 strcpy(all_recps, recp);
3587 if (!IsEmptyStr(cc)) {
3588 if (!IsEmptyStr(all_recps)) {
3589 strcat(all_recps, ",");
3591 strcat(all_recps, cc);
3593 if (!IsEmptyStr(bcc)) {
3594 if (!IsEmptyStr(all_recps)) {
3595 strcat(all_recps, ",");
3597 strcat(all_recps, bcc);
3599 if (!IsEmptyStr(all_recps)) {
3600 valid = validate_recipients(all_recps);
3608 msgnum = CtdlSubmitMsg(msg, valid, "");
3611 cprintf("%ld\n", msgnum);
3613 cprintf("Message accepted.\n");
3616 cprintf("Internal error.\n");
3618 if (msg->cm_fields['E'] != NULL) {
3619 cprintf("%s\n", msg->cm_fields['E']);
3626 CtdlFreeMessage(msg);
3628 if (valid != NULL) {
3629 free_recipients(valid);
3637 * API function to delete messages which match a set of criteria
3638 * (returns the actual number of messages deleted)
3640 int CtdlDeleteMessages(char *room_name, /* which room */
3641 long *dmsgnums, /* array of msg numbers to be deleted */
3642 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3643 char *content_type /* or "" for any. regular expressions expected. */
3646 struct ctdlroom qrbuf;
3647 struct cdbdata *cdbfr;
3648 long *msglist = NULL;
3649 long *dellist = NULL;
3652 int num_deleted = 0;
3654 struct MetaData smi;
3657 int need_to_free_re = 0;
3659 if (content_type) if (!IsEmptyStr(content_type)) {
3660 regcomp(&re, content_type, 0);
3661 need_to_free_re = 1;
3663 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3664 room_name, num_dmsgnums, content_type);
3666 /* get room record, obtaining a lock... */
3667 if (lgetroom(&qrbuf, room_name) != 0) {
3668 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3670 if (need_to_free_re) regfree(&re);
3671 return (0); /* room not found */
3673 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3675 if (cdbfr != NULL) {
3676 dellist = malloc(cdbfr->len);
3677 msglist = (long *) cdbfr->ptr;
3678 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3679 num_msgs = cdbfr->len / sizeof(long);
3683 for (i = 0; i < num_msgs; ++i) {
3686 /* Set/clear a bit for each criterion */
3688 /* 0 messages in the list or a null list means that we are
3689 * interested in deleting any messages which meet the other criteria.
3691 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3692 delete_this |= 0x01;
3695 for (j=0; j<num_dmsgnums; ++j) {
3696 if (msglist[i] == dmsgnums[j]) {
3697 delete_this |= 0x01;
3702 if (IsEmptyStr(content_type)) {
3703 delete_this |= 0x02;
3705 GetMetaData(&smi, msglist[i]);
3706 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3707 delete_this |= 0x02;
3711 /* Delete message only if all bits are set */
3712 if (delete_this == 0x03) {
3713 dellist[num_deleted++] = msglist[i];
3718 num_msgs = sort_msglist(msglist, num_msgs);
3719 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3720 msglist, (int)(num_msgs * sizeof(long)));
3722 qrbuf.QRhighest = msglist[num_msgs - 1];
3726 /* Go through the messages we pulled out of the index, and decrement
3727 * their reference counts by 1. If this is the only room the message
3728 * was in, the reference count will reach zero and the message will
3729 * automatically be deleted from the database. We do this in a
3730 * separate pass because there might be plug-in hooks getting called,
3731 * and we don't want that happening during an S_ROOMS critical
3734 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3735 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3736 AdjRefCount(dellist[i], -1);
3739 /* Now free the memory we used, and go away. */
3740 if (msglist != NULL) free(msglist);
3741 if (dellist != NULL) free(dellist);
3742 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3743 if (need_to_free_re) regfree(&re);
3744 return (num_deleted);
3750 * Check whether the current user has permission to delete messages from
3751 * the current room (returns 1 for yes, 0 for no)
3753 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3755 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3756 if (ra & UA_DELETEALLOWED) return(1);
3764 * Delete message from current room
3766 void cmd_dele(char *args)
3775 extract_token(msgset, args, 0, '|', sizeof msgset);
3776 num_msgs = num_tokens(msgset, ',');
3778 cprintf("%d Nothing to do.\n", CIT_OK);
3782 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3783 cprintf("%d Higher access required.\n",
3784 ERROR + HIGHER_ACCESS_REQUIRED);
3789 * Build our message set to be moved/copied
3791 msgs = malloc(num_msgs * sizeof(long));
3792 for (i=0; i<num_msgs; ++i) {
3793 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3794 msgs[i] = atol(msgtok);
3797 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3801 cprintf("%d %d message%s deleted.\n", CIT_OK,
3802 num_deleted, ((num_deleted != 1) ? "s" : ""));
3804 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3810 * Back end API function for moves and deletes (multiple messages)
3812 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3815 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3816 if (err != 0) return(err);
3825 * move or copy a message to another room
3827 void cmd_move(char *args)
3834 char targ[ROOMNAMELEN];
3835 struct ctdlroom qtemp;
3842 extract_token(msgset, args, 0, '|', sizeof msgset);
3843 num_msgs = num_tokens(msgset, ',');
3845 cprintf("%d Nothing to do.\n", CIT_OK);
3849 extract_token(targ, args, 1, '|', sizeof targ);
3850 convert_room_name_macros(targ, sizeof targ);
3851 targ[ROOMNAMELEN - 1] = 0;
3852 is_copy = extract_int(args, 2);
3854 if (getroom(&qtemp, targ) != 0) {
3855 cprintf("%d '%s' does not exist.\n",
3856 ERROR + ROOM_NOT_FOUND, targ);
3860 getuser(&CC->user, CC->curr_user);
3861 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3863 /* Check for permission to perform this operation.
3864 * Remember: "CC->room" is source, "qtemp" is target.
3868 /* Aides can move/copy */
3869 if (CC->user.axlevel >= 6) permit = 1;
3871 /* Room aides can move/copy */
3872 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3874 /* Permit move/copy from personal rooms */
3875 if ((CC->room.QRflags & QR_MAILBOX)
3876 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3878 /* Permit only copy from public to personal room */
3880 && (!(CC->room.QRflags & QR_MAILBOX))
3881 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3883 /* Permit message removal from collaborative delete rooms */
3884 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3886 /* User must have access to target room */
3887 if (!(ra & UA_KNOWN)) permit = 0;
3890 cprintf("%d Higher access required.\n",
3891 ERROR + HIGHER_ACCESS_REQUIRED);
3896 * Build our message set to be moved/copied
3898 msgs = malloc(num_msgs * sizeof(long));
3899 for (i=0; i<num_msgs; ++i) {
3900 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3901 msgs[i] = atol(msgtok);
3907 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3909 cprintf("%d Cannot store message(s) in %s: error %d\n",
3915 /* Now delete the message from the source room,
3916 * if this is a 'move' rather than a 'copy' operation.
3919 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3923 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3929 * GetMetaData() - Get the supplementary record for a message
3931 void GetMetaData(struct MetaData *smibuf, long msgnum)
3934 struct cdbdata *cdbsmi;
3937 memset(smibuf, 0, sizeof(struct MetaData));
3938 smibuf->meta_msgnum = msgnum;
3939 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3941 /* Use the negative of the message number for its supp record index */
3942 TheIndex = (0L - msgnum);
3944 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3945 if (cdbsmi == NULL) {
3946 return; /* record not found; go with defaults */
3948 memcpy(smibuf, cdbsmi->ptr,
3949 ((cdbsmi->len > sizeof(struct MetaData)) ?
3950 sizeof(struct MetaData) : cdbsmi->len));
3957 * PutMetaData() - (re)write supplementary record for a message
3959 void PutMetaData(struct MetaData *smibuf)
3963 /* Use the negative of the message number for the metadata db index */
3964 TheIndex = (0L - smibuf->meta_msgnum);
3966 cdb_store(CDB_MSGMAIN,
3967 &TheIndex, (int)sizeof(long),
3968 smibuf, (int)sizeof(struct MetaData));
3973 * AdjRefCount - submit an adjustment to the reference count for a message.
3974 * (These are just queued -- we actually process them later.)
3976 void AdjRefCount(long msgnum, int incr)
3978 struct arcq new_arcq;
3980 begin_critical_section(S_SUPPMSGMAIN);
3981 if (arcfp == NULL) {
3982 arcfp = fopen(file_arcq, "ab+");
3984 end_critical_section(S_SUPPMSGMAIN);
3986 /* msgnum < 0 means that we're trying to close the file */
3988 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3989 begin_critical_section(S_SUPPMSGMAIN);
3990 if (arcfp != NULL) {
3994 end_critical_section(S_SUPPMSGMAIN);
3999 * If we can't open the queue, perform the operation synchronously.
4001 if (arcfp == NULL) {
4002 TDAP_AdjRefCount(msgnum, incr);
4006 new_arcq.arcq_msgnum = msgnum;
4007 new_arcq.arcq_delta = incr;
4008 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4016 * TDAP_ProcessAdjRefCountQueue()
4018 * Process the queue of message count adjustments that was created by calls
4019 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4020 * for each one. This should be an "off hours" operation.
4022 int TDAP_ProcessAdjRefCountQueue(void)
4024 char file_arcq_temp[PATH_MAX];
4027 struct arcq arcq_rec;
4028 int num_records_processed = 0;
4030 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4032 begin_critical_section(S_SUPPMSGMAIN);
4033 if (arcfp != NULL) {
4038 r = link(file_arcq, file_arcq_temp);
4040 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4041 end_critical_section(S_SUPPMSGMAIN);
4042 return(num_records_processed);
4046 end_critical_section(S_SUPPMSGMAIN);
4048 fp = fopen(file_arcq_temp, "rb");
4050 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4051 return(num_records_processed);
4054 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4055 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4056 ++num_records_processed;
4060 r = unlink(file_arcq_temp);
4062 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4065 return(num_records_processed);
4071 * TDAP_AdjRefCount - adjust the reference count for a message.
4072 * This one does it "for real" because it's called by
4073 * the autopurger function that processes the queue
4074 * created by AdjRefCount(). If a message's reference
4075 * count becomes zero, we also delete the message from
4076 * disk and de-index it.
4078 void TDAP_AdjRefCount(long msgnum, int incr)
4081 struct MetaData smi;
4084 /* This is a *tight* critical section; please keep it that way, as
4085 * it may get called while nested in other critical sections.
4086 * Complicating this any further will surely cause deadlock!
4088 begin_critical_section(S_SUPPMSGMAIN);
4089 GetMetaData(&smi, msgnum);
4090 smi.meta_refcount += incr;
4092 end_critical_section(S_SUPPMSGMAIN);
4093 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4094 msgnum, incr, smi.meta_refcount);
4096 /* If the reference count is now zero, delete the message
4097 * (and its supplementary record as well).
4099 if (smi.meta_refcount == 0) {
4100 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4102 /* Call delete hooks with NULL room to show it has gone altogether */
4103 PerformDeleteHooks(NULL, msgnum);
4105 /* Remove from message base */
4107 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4108 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4110 /* Remove metadata record */
4111 delnum = (0L - msgnum);
4112 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4118 * Write a generic object to this room
4120 * Note: this could be much more efficient. Right now we use two temporary
4121 * files, and still pull the message into memory as with all others.
4123 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4124 char *content_type, /* MIME type of this object */
4125 char *tempfilename, /* Where to fetch it from */
4126 struct ctdluser *is_mailbox, /* Mailbox room? */
4127 int is_binary, /* Is encoding necessary? */
4128 int is_unique, /* Del others of this type? */
4129 unsigned int flags /* Internal save flags */
4134 struct ctdlroom qrbuf;
4135 char roomname[ROOMNAMELEN];
4136 struct CtdlMessage *msg;
4138 char *raw_message = NULL;
4139 char *encoded_message = NULL;
4140 off_t raw_length = 0;
4142 if (is_mailbox != NULL) {
4143 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4146 safestrncpy(roomname, req_room, sizeof(roomname));
4149 fp = fopen(tempfilename, "rb");
4151 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4152 tempfilename, strerror(errno));
4155 fseek(fp, 0L, SEEK_END);
4156 raw_length = ftell(fp);
4158 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4160 raw_message = malloc((size_t)raw_length + 2);
4161 fread(raw_message, (size_t)raw_length, 1, fp);
4165 encoded_message = malloc((size_t)
4166 (((raw_length * 134) / 100) + 4096 ) );
4169 encoded_message = malloc((size_t)(raw_length + 4096));
4172 sprintf(encoded_message, "Content-type: %s\n", content_type);
4175 sprintf(&encoded_message[strlen(encoded_message)],
4176 "Content-transfer-encoding: base64\n\n"
4180 sprintf(&encoded_message[strlen(encoded_message)],
4181 "Content-transfer-encoding: 7bit\n\n"
4187 &encoded_message[strlen(encoded_message)],
4193 raw_message[raw_length] = 0;
4195 &encoded_message[strlen(encoded_message)],
4203 lprintf(CTDL_DEBUG, "Allocating\n");
4204 msg = malloc(sizeof(struct CtdlMessage));
4205 memset(msg, 0, sizeof(struct CtdlMessage));
4206 msg->cm_magic = CTDLMESSAGE_MAGIC;
4207 msg->cm_anon_type = MES_NORMAL;
4208 msg->cm_format_type = 4;
4209 msg->cm_fields['A'] = strdup(CC->user.fullname);
4210 msg->cm_fields['O'] = strdup(req_room);
4211 msg->cm_fields['N'] = strdup(config.c_nodename);
4212 msg->cm_fields['H'] = strdup(config.c_humannode);
4213 msg->cm_flags = flags;
4215 msg->cm_fields['M'] = encoded_message;
4217 /* Create the requested room if we have to. */
4218 if (getroom(&qrbuf, roomname) != 0) {
4219 create_room(roomname,
4220 ( (is_mailbox != NULL) ? 5 : 3 ),
4221 "", 0, 1, 0, VIEW_BBS);
4223 /* If the caller specified this object as unique, delete all
4224 * other objects of this type that are currently in the room.
4227 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4228 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4231 /* Now write the data */
4232 CtdlSubmitMsg(msg, NULL, roomname);
4233 CtdlFreeMessage(msg);
4241 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4242 config_msgnum = msgnum;
4246 char *CtdlGetSysConfig(char *sysconfname) {
4247 char hold_rm[ROOMNAMELEN];
4250 struct CtdlMessage *msg;
4253 strcpy(hold_rm, CC->room.QRname);
4254 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4255 getroom(&CC->room, hold_rm);
4260 /* We want the last (and probably only) config in this room */
4261 begin_critical_section(S_CONFIG);
4262 config_msgnum = (-1L);
4263 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4264 CtdlGetSysConfigBackend, NULL);
4265 msgnum = config_msgnum;
4266 end_critical_section(S_CONFIG);
4272 msg = CtdlFetchMessage(msgnum, 1);
4274 conf = strdup(msg->cm_fields['M']);
4275 CtdlFreeMessage(msg);
4282 getroom(&CC->room, hold_rm);
4284 if (conf != NULL) do {
4285 extract_token(buf, conf, 0, '\n', sizeof buf);
4286 strcpy(conf, &conf[strlen(buf)+1]);
4287 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4292 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4293 char temp[PATH_MAX];
4296 CtdlMakeTempFileName(temp, sizeof temp);
4298 fp = fopen(temp, "w");
4299 if (fp == NULL) return;
4300 fprintf(fp, "%s", sysconfdata);
4303 /* this handy API function does all the work for us */
4304 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4310 * Determine whether a given Internet address belongs to the current user
4312 int CtdlIsMe(char *addr, int addr_buf_len)
4314 struct recptypes *recp;
4317 recp = validate_recipients(addr);
4318 if (recp == NULL) return(0);
4320 if (recp->num_local == 0) {
4321 free_recipients(recp);
4325 for (i=0; i<recp->num_local; ++i) {
4326 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4327 if (!strcasecmp(addr, CC->user.fullname)) {
4328 free_recipients(recp);
4333 free_recipients(recp);
4339 * Citadel protocol command to do the same
4341 void cmd_isme(char *argbuf) {
4344 if (CtdlAccessCheck(ac_logged_in)) return;
4345 extract_token(addr, argbuf, 0, '|', sizeof addr);
4347 if (CtdlIsMe(addr, sizeof addr)) {
4348 cprintf("%d %s\n", CIT_OK, addr);
4351 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);