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).
1037 lprintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1041 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1042 memset(ret, 0, sizeof(struct CtdlMessage));
1044 ret->cm_magic = CTDLMESSAGE_MAGIC;
1045 ret->cm_anon_type = *mptr++; /* Anon type byte */
1046 ret->cm_format_type = *mptr++; /* Format type byte */
1049 * The rest is zero or more arbitrary fields. Load them in.
1050 * We're done when we encounter either a zero-length field or
1051 * have just processed the 'M' (message text) field.
1054 if (mptr >= upper_bound) {
1057 field_header = *mptr++;
1058 ret->cm_fields[field_header] = strdup(mptr);
1060 while (*mptr++ != 0); /* advance to next field */
1062 } while ((mptr < upper_bound) && (field_header != 'M'));
1066 /* Always make sure there's something in the msg text field. If
1067 * it's NULL, the message text is most likely stored separately,
1068 * so go ahead and fetch that. Failing that, just set a dummy
1069 * body so other code doesn't barf.
1071 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1072 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1073 if (dmsgtext != NULL) {
1074 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1078 if (ret->cm_fields['M'] == NULL) {
1079 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1082 /* Perform "before read" hooks (aborting if any return nonzero) */
1083 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1084 CtdlFreeMessage(ret);
1093 * Returns 1 if the supplied pointer points to a valid Citadel message.
1094 * If the pointer is NULL or the magic number check fails, returns 0.
1096 int is_valid_message(struct CtdlMessage *msg) {
1099 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1100 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1108 * 'Destructor' for struct CtdlMessage
1110 void CtdlFreeMessage(struct CtdlMessage *msg)
1114 if (is_valid_message(msg) == 0)
1116 if (msg != NULL) free (msg);
1120 for (i = 0; i < 256; ++i)
1121 if (msg->cm_fields[i] != NULL) {
1122 free(msg->cm_fields[i]);
1125 msg->cm_magic = 0; /* just in case */
1131 * Pre callback function for multipart/alternative
1133 * NOTE: this differs from the standard behavior for a reason. Normally when
1134 * displaying multipart/alternative you want to show the _last_ usable
1135 * format in the message. Here we show the _first_ one, because it's
1136 * usually text/plain. Since this set of functions is designed for text
1137 * output to non-MIME-aware clients, this is the desired behavior.
1140 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1141 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1146 ma = (struct ma_info *)cbuserdata;
1147 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1148 if (!strcasecmp(cbtype, "multipart/alternative")) {
1152 if (!strcasecmp(cbtype, "message/rfc822")) {
1158 * Post callback function for multipart/alternative
1160 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1161 void *content, char *cbtype, char *cbcharset, size_t length,
1162 char *encoding, void *cbuserdata)
1166 ma = (struct ma_info *)cbuserdata;
1167 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1168 if (!strcasecmp(cbtype, "multipart/alternative")) {
1172 if (!strcasecmp(cbtype, "message/rfc822")) {
1178 * Inline callback function for mime parser that wants to display text
1180 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1181 void *content, char *cbtype, char *cbcharset, size_t length,
1182 char *encoding, void *cbuserdata)
1189 ma = (struct ma_info *)cbuserdata;
1192 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1193 partnum, filename, cbtype, (long)length);
1196 * If we're in the middle of a multipart/alternative scope and
1197 * we've already printed another section, skip this one.
1199 if ( (ma->is_ma) && (ma->did_print) ) {
1200 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1205 if ( (!strcasecmp(cbtype, "text/plain"))
1206 || (IsEmptyStr(cbtype)) ) {
1209 client_write(wptr, length);
1210 if (wptr[length-1] != '\n') {
1217 if (!strcasecmp(cbtype, "text/html")) {
1218 ptr = html_to_ascii(content, length, 80, 0);
1220 client_write(ptr, wlen);
1221 if (ptr[wlen-1] != '\n') {
1228 if (ma->use_fo_hooks) {
1229 if (PerformFixedOutputHooks(cbtype, content, length)) {
1230 /* above function returns nonzero if it handled the part */
1235 if (strncasecmp(cbtype, "multipart/", 10)) {
1236 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1237 partnum, filename, cbtype, (long)length);
1243 * The client is elegant and sophisticated and wants to be choosy about
1244 * MIME content types, so figure out which multipart/alternative part
1245 * we're going to send.
1247 * We use a system of weights. When we find a part that matches one of the
1248 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1249 * and then set ma->chosen_pref to that MIME type's position in our preference
1250 * list. If we then hit another match, we only replace the first match if
1251 * the preference value is lower.
1253 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1254 void *content, char *cbtype, char *cbcharset, size_t length,
1255 char *encoding, void *cbuserdata)
1261 ma = (struct ma_info *)cbuserdata;
1263 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1264 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1265 // I don't know if there are any side effects! Please TEST TEST TEST
1266 //if (ma->is_ma > 0) {
1268 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1269 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1270 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1271 if (i < ma->chosen_pref) {
1272 lprintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1273 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1274 ma->chosen_pref = i;
1282 * Now that we've chosen our preferred part, output it.
1284 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1285 void *content, char *cbtype, char *cbcharset, size_t length,
1286 char *encoding, void *cbuserdata)
1290 int add_newline = 0;
1294 ma = (struct ma_info *)cbuserdata;
1296 /* This is not the MIME part you're looking for... */
1297 if (strcasecmp(partnum, ma->chosen_part)) return;
1299 /* If the content-type of this part is in our preferred formats
1300 * list, we can simply output it verbatim.
1302 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1303 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1304 if (!strcasecmp(buf, cbtype)) {
1305 /* Yeah! Go! W00t!! */
1307 text_content = (char *)content;
1308 if (text_content[length-1] != '\n') {
1311 cprintf("Content-type: %s", cbtype);
1312 if (!IsEmptyStr(cbcharset)) {
1313 cprintf("; charset=%s", cbcharset);
1315 cprintf("\nContent-length: %d\n",
1316 (int)(length + add_newline) );
1317 if (!IsEmptyStr(encoding)) {
1318 cprintf("Content-transfer-encoding: %s\n", encoding);
1321 cprintf("Content-transfer-encoding: 7bit\n");
1323 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1325 client_write(content, length);
1326 if (add_newline) cprintf("\n");
1331 /* No translations required or possible: output as text/plain */
1332 cprintf("Content-type: text/plain\n\n");
1333 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1334 length, encoding, cbuserdata);
1339 char desired_section[64];
1346 * Callback function for
1348 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1349 void *content, char *cbtype, char *cbcharset, size_t length,
1350 char *encoding, void *cbuserdata)
1352 struct encapmsg *encap;
1354 encap = (struct encapmsg *)cbuserdata;
1356 /* Only proceed if this is the desired section... */
1357 if (!strcasecmp(encap->desired_section, partnum)) {
1358 encap->msglen = length;
1359 encap->msg = malloc(length + 2);
1360 memcpy(encap->msg, content, length);
1370 * Get a message off disk. (returns om_* values found in msgbase.h)
1373 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1374 int mode, /* how would you like that message? */
1375 int headers_only, /* eschew the message body? */
1376 int do_proto, /* do Citadel protocol responses? */
1377 int crlf, /* Use CRLF newlines instead of LF? */
1378 char *section /* NULL or a message/rfc822 section */
1380 struct CtdlMessage *TheMessage = NULL;
1381 int retcode = om_no_such_msg;
1382 struct encapmsg encap;
1384 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1386 (section ? section : "<>")
1389 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1390 if (do_proto) cprintf("%d Not logged in.\n",
1391 ERROR + NOT_LOGGED_IN);
1392 return(om_not_logged_in);
1395 /* FIXME: check message id against msglist for this room */
1398 * Fetch the message from disk. If we're in any sort of headers
1399 * only mode, request that we don't even bother loading the body
1402 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1403 TheMessage = CtdlFetchMessage(msg_num, 0);
1406 TheMessage = CtdlFetchMessage(msg_num, 1);
1409 if (TheMessage == NULL) {
1410 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1411 ERROR + MESSAGE_NOT_FOUND, msg_num);
1412 return(om_no_such_msg);
1415 /* Here is the weird form of this command, to process only an
1416 * encapsulated message/rfc822 section.
1418 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1419 memset(&encap, 0, sizeof encap);
1420 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1421 mime_parser(TheMessage->cm_fields['M'],
1423 *extract_encapsulated_message,
1424 NULL, NULL, (void *)&encap, 0
1426 CtdlFreeMessage(TheMessage);
1430 encap.msg[encap.msglen] = 0;
1431 TheMessage = convert_internet_message(encap.msg);
1432 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1434 /* Now we let it fall through to the bottom of this
1435 * function, because TheMessage now contains the
1436 * encapsulated message instead of the top-level
1437 * message. Isn't that neat?
1442 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1443 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1444 retcode = om_no_such_msg;
1449 /* Ok, output the message now */
1450 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf);
1451 CtdlFreeMessage(TheMessage);
1458 * Get a message off disk. (returns om_* values found in msgbase.h)
1460 int CtdlOutputPreLoadedMsg(
1461 struct CtdlMessage *TheMessage,
1462 int mode, /* how would you like that message? */
1463 int headers_only, /* eschew the message body? */
1464 int do_proto, /* do Citadel protocol responses? */
1465 int crlf /* Use CRLF newlines instead of LF? */
1471 char display_name[256];
1473 char *nl; /* newline string */
1475 int subject_found = 0;
1478 /* Buffers needed for RFC822 translation. These are all filled
1479 * using functions that are bounds-checked, and therefore we can
1480 * make them substantially smaller than SIZ.
1488 char datestamp[100];
1490 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1491 ((TheMessage == NULL) ? "NULL" : "not null"),
1492 mode, headers_only, do_proto, crlf);
1494 strcpy(mid, "unknown");
1495 nl = (crlf ? "\r\n" : "\n");
1497 if (!is_valid_message(TheMessage)) {
1499 "ERROR: invalid preloaded message for output\n");
1500 return(om_no_such_msg);
1503 /* Are we downloading a MIME component? */
1504 if (mode == MT_DOWNLOAD) {
1505 if (TheMessage->cm_format_type != FMT_RFC822) {
1507 cprintf("%d This is not a MIME message.\n",
1508 ERROR + ILLEGAL_VALUE);
1509 } else if (CC->download_fp != NULL) {
1510 if (do_proto) cprintf(
1511 "%d You already have a download open.\n",
1512 ERROR + RESOURCE_BUSY);
1514 /* Parse the message text component */
1515 mptr = TheMessage->cm_fields['M'];
1516 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1517 /* If there's no file open by this time, the requested
1518 * section wasn't found, so print an error
1520 if (CC->download_fp == NULL) {
1521 if (do_proto) cprintf(
1522 "%d Section %s not found.\n",
1523 ERROR + FILE_NOT_FOUND,
1524 CC->download_desired_section);
1527 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1530 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1531 * in a single server operation instead of opening a download file.
1533 if (mode == MT_SPEW_SECTION) {
1534 if (TheMessage->cm_format_type != FMT_RFC822) {
1536 cprintf("%d This is not a MIME message.\n",
1537 ERROR + ILLEGAL_VALUE);
1539 /* Parse the message text component */
1542 mptr = TheMessage->cm_fields['M'];
1543 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1544 /* If section wasn't found, print an error
1547 if (do_proto) cprintf(
1548 "%d Section %s not found.\n",
1549 ERROR + FILE_NOT_FOUND,
1550 CC->download_desired_section);
1553 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1556 /* now for the user-mode message reading loops */
1557 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1559 /* Does the caller want to skip the headers? */
1560 if (headers_only == HEADERS_NONE) goto START_TEXT;
1562 /* Tell the client which format type we're using. */
1563 if ( (mode == MT_CITADEL) && (do_proto) ) {
1564 cprintf("type=%d\n", TheMessage->cm_format_type);
1567 /* nhdr=yes means that we're only displaying headers, no body */
1568 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1569 && (mode == MT_CITADEL)
1572 cprintf("nhdr=yes\n");
1575 /* begin header processing loop for Citadel message format */
1577 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1579 safestrncpy(display_name, "<unknown>", sizeof display_name);
1580 if (TheMessage->cm_fields['A']) {
1581 strcpy(buf, TheMessage->cm_fields['A']);
1582 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1583 safestrncpy(display_name, "****", sizeof display_name);
1585 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1586 safestrncpy(display_name, "anonymous", sizeof display_name);
1589 safestrncpy(display_name, buf, sizeof display_name);
1591 if ((is_room_aide())
1592 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1593 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1594 size_t tmp = strlen(display_name);
1595 snprintf(&display_name[tmp],
1596 sizeof display_name - tmp,
1601 /* Don't show Internet address for users on the
1602 * local Citadel network.
1605 if (TheMessage->cm_fields['N'] != NULL)
1606 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1607 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1611 /* Now spew the header fields in the order we like them. */
1612 safestrncpy(allkeys, FORDER, sizeof allkeys);
1613 for (i=0; i<strlen(allkeys); ++i) {
1614 k = (int) allkeys[i];
1616 if ( (TheMessage->cm_fields[k] != NULL)
1617 && (msgkeys[k] != NULL) ) {
1619 if (do_proto) cprintf("%s=%s\n",
1623 else if ((k == 'F') && (suppress_f)) {
1626 /* Masquerade display name if needed */
1628 if (do_proto) cprintf("%s=%s\n",
1630 TheMessage->cm_fields[k]
1639 /* begin header processing loop for RFC822 transfer format */
1644 strcpy(snode, NODENAME);
1645 strcpy(lnode, HUMANNODE);
1646 if (mode == MT_RFC822) {
1647 for (i = 0; i < 256; ++i) {
1648 if (TheMessage->cm_fields[i]) {
1649 mptr = TheMessage->cm_fields[i];
1652 safestrncpy(luser, mptr, sizeof luser);
1653 safestrncpy(suser, mptr, sizeof suser);
1655 else if (i == 'Y') {
1656 cprintf("CC: %s%s", mptr, nl);
1658 else if (i == 'P') {
1659 cprintf("Return-Path: %s%s", mptr, nl);
1661 else if (i == 'V') {
1662 cprintf("Envelope-To: %s%s", mptr, nl);
1664 else if (i == 'U') {
1665 cprintf("Subject: %s%s", mptr, nl);
1669 safestrncpy(mid, mptr, sizeof mid);
1671 safestrncpy(lnode, mptr, sizeof lnode);
1673 safestrncpy(fuser, mptr, sizeof fuser);
1674 /* else if (i == 'O')
1675 cprintf("X-Citadel-Room: %s%s",
1678 safestrncpy(snode, mptr, sizeof snode);
1681 if (haschar(mptr, '@') == 0)
1683 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1687 cprintf("To: %s%s", mptr, nl);
1690 else if (i == 'T') {
1691 datestring(datestamp, sizeof datestamp,
1692 atol(mptr), DATESTRING_RFC822);
1693 cprintf("Date: %s%s", datestamp, nl);
1697 if (subject_found == 0) {
1698 cprintf("Subject: (no subject)%s", nl);
1702 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1703 suser[i] = tolower(suser[i]);
1704 if (!isalnum(suser[i])) suser[i]='_';
1707 if (mode == MT_RFC822) {
1708 if (!strcasecmp(snode, NODENAME)) {
1709 safestrncpy(snode, FQDN, sizeof snode);
1712 /* Construct a fun message id */
1713 cprintf("Message-ID: <%s", mid);
1714 if (strchr(mid, '@')==NULL) {
1715 cprintf("@%s", snode);
1719 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1720 cprintf("From: \"----\" <x@x.org>%s", nl);
1722 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1723 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1725 else if (!IsEmptyStr(fuser)) {
1726 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1729 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1732 cprintf("Organization: %s%s", lnode, nl);
1734 /* Blank line signifying RFC822 end-of-headers */
1735 if (TheMessage->cm_format_type != FMT_RFC822) {
1740 /* end header processing loop ... at this point, we're in the text */
1742 if (headers_only == HEADERS_FAST) goto DONE;
1743 mptr = TheMessage->cm_fields['M'];
1745 /* Tell the client about the MIME parts in this message */
1746 if (TheMessage->cm_format_type == FMT_RFC822) {
1747 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1748 memset(&ma, 0, sizeof(struct ma_info));
1749 mime_parser(mptr, NULL,
1750 (do_proto ? *list_this_part : NULL),
1751 (do_proto ? *list_this_pref : NULL),
1752 (do_proto ? *list_this_suff : NULL),
1755 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1756 char *start_of_text = NULL;
1757 start_of_text = strstr(mptr, "\n\r\n");
1758 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1759 if (start_of_text == NULL) start_of_text = mptr;
1761 start_of_text = strstr(start_of_text, "\n");
1766 int nllen = strlen(nl);
1767 while (ch=*mptr, ch!=0) {
1773 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1774 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1775 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1778 sprintf(&outbuf[outlen], "%s", nl);
1782 outbuf[outlen++] = ch;
1787 if (outlen > 1000) {
1788 client_write(outbuf, outlen);
1793 client_write(outbuf, outlen);
1801 if (headers_only == HEADERS_ONLY) {
1805 /* signify start of msg text */
1806 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1807 if (do_proto) cprintf("text\n");
1810 /* If the format type on disk is 1 (fixed-format), then we want
1811 * everything to be output completely literally ... regardless of
1812 * what message transfer format is in use.
1814 if (TheMessage->cm_format_type == FMT_FIXED) {
1815 if (mode == MT_MIME) {
1816 cprintf("Content-type: text/plain\n\n");
1819 while (ch = *mptr++, ch > 0) {
1822 if ((ch == 10) || (strlen(buf) > 250)) {
1823 cprintf("%s%s", buf, nl);
1826 buf[strlen(buf) + 1] = 0;
1827 buf[strlen(buf)] = ch;
1830 if (!IsEmptyStr(buf))
1831 cprintf("%s%s", buf, nl);
1834 /* If the message on disk is format 0 (Citadel vari-format), we
1835 * output using the formatter at 80 columns. This is the final output
1836 * form if the transfer format is RFC822, but if the transfer format
1837 * is Citadel proprietary, it'll still work, because the indentation
1838 * for new paragraphs is correct and the client will reformat the
1839 * message to the reader's screen width.
1841 if (TheMessage->cm_format_type == FMT_CITADEL) {
1842 if (mode == MT_MIME) {
1843 cprintf("Content-type: text/x-citadel-variformat\n\n");
1845 memfmout(mptr, 0, nl);
1848 /* If the message on disk is format 4 (MIME), we've gotta hand it
1849 * off to the MIME parser. The client has already been told that
1850 * this message is format 1 (fixed format), so the callback function
1851 * we use will display those parts as-is.
1853 if (TheMessage->cm_format_type == FMT_RFC822) {
1854 memset(&ma, 0, sizeof(struct ma_info));
1856 if (mode == MT_MIME) {
1857 ma.use_fo_hooks = 0;
1858 strcpy(ma.chosen_part, "1");
1859 ma.chosen_pref = 9999;
1860 mime_parser(mptr, NULL,
1861 *choose_preferred, *fixed_output_pre,
1862 *fixed_output_post, (void *)&ma, 0);
1863 mime_parser(mptr, NULL,
1864 *output_preferred, NULL, NULL, (void *)&ma, 0);
1867 ma.use_fo_hooks = 1;
1868 mime_parser(mptr, NULL,
1869 *fixed_output, *fixed_output_pre,
1870 *fixed_output_post, (void *)&ma, 0);
1875 DONE: /* now we're done */
1876 if (do_proto) cprintf("000\n");
1883 * display a message (mode 0 - Citadel proprietary)
1885 void cmd_msg0(char *cmdbuf)
1888 int headers_only = HEADERS_ALL;
1890 msgid = extract_long(cmdbuf, 0);
1891 headers_only = extract_int(cmdbuf, 1);
1893 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1899 * display a message (mode 2 - RFC822)
1901 void cmd_msg2(char *cmdbuf)
1904 int headers_only = HEADERS_ALL;
1906 msgid = extract_long(cmdbuf, 0);
1907 headers_only = extract_int(cmdbuf, 1);
1909 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1915 * display a message (mode 3 - IGnet raw format - internal programs only)
1917 void cmd_msg3(char *cmdbuf)
1920 struct CtdlMessage *msg = NULL;
1923 if (CC->internal_pgm == 0) {
1924 cprintf("%d This command is for internal programs only.\n",
1925 ERROR + HIGHER_ACCESS_REQUIRED);
1929 msgnum = extract_long(cmdbuf, 0);
1930 msg = CtdlFetchMessage(msgnum, 1);
1932 cprintf("%d Message %ld not found.\n",
1933 ERROR + MESSAGE_NOT_FOUND, msgnum);
1937 serialize_message(&smr, msg);
1938 CtdlFreeMessage(msg);
1941 cprintf("%d Unable to serialize message\n",
1942 ERROR + INTERNAL_ERROR);
1946 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1947 client_write((char *)smr.ser, (int)smr.len);
1954 * Display a message using MIME content types
1956 void cmd_msg4(char *cmdbuf)
1961 msgid = extract_long(cmdbuf, 0);
1962 extract_token(section, cmdbuf, 1, '|', sizeof section);
1963 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1969 * Client tells us its preferred message format(s)
1971 void cmd_msgp(char *cmdbuf)
1973 safestrncpy(CC->preferred_formats, cmdbuf,
1974 sizeof(CC->preferred_formats));
1975 cprintf("%d ok\n", CIT_OK);
1980 * Open a component of a MIME message as a download file
1982 void cmd_opna(char *cmdbuf)
1985 char desired_section[128];
1987 msgid = extract_long(cmdbuf, 0);
1988 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1989 safestrncpy(CC->download_desired_section, desired_section,
1990 sizeof CC->download_desired_section);
1991 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1996 * Open a component of a MIME message and transmit it all at once
1998 void cmd_dlat(char *cmdbuf)
2001 char desired_section[128];
2003 msgid = extract_long(cmdbuf, 0);
2004 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2005 safestrncpy(CC->download_desired_section, desired_section,
2006 sizeof CC->download_desired_section);
2007 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2012 * Save one or more message pointers into a specified room
2013 * (Returns 0 for success, nonzero for failure)
2014 * roomname may be NULL to use the current room
2016 * Note that the 'supplied_msg' field may be set to NULL, in which case
2017 * the message will be fetched from disk, by number, if we need to perform
2018 * replication checks. This adds an additional database read, so if the
2019 * caller already has the message in memory then it should be supplied. (Obviously
2020 * this mode of operation only works if we're saving a single message.)
2022 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2023 int do_repl_check, struct CtdlMessage *supplied_msg)
2026 char hold_rm[ROOMNAMELEN];
2027 struct cdbdata *cdbfr;
2030 long highest_msg = 0L;
2033 struct CtdlMessage *msg = NULL;
2035 long *msgs_to_be_merged = NULL;
2036 int num_msgs_to_be_merged = 0;
2039 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2040 roomname, num_newmsgs, do_repl_check);
2042 strcpy(hold_rm, CC->room.QRname);
2045 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2046 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2047 if (num_newmsgs > 1) supplied_msg = NULL;
2049 /* Now the regular stuff */
2050 if (lgetroom(&CC->room,
2051 ((roomname != NULL) ? roomname : CC->room.QRname) )
2053 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2054 return(ERROR + ROOM_NOT_FOUND);
2058 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2059 num_msgs_to_be_merged = 0;
2062 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2063 if (cdbfr == NULL) {
2067 msglist = (long *) cdbfr->ptr;
2068 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2069 num_msgs = cdbfr->len / sizeof(long);
2074 /* Create a list of msgid's which were supplied by the caller, but do
2075 * not already exist in the target room. It is absolutely taboo to
2076 * have more than one reference to the same message in a room.
2078 for (i=0; i<num_newmsgs; ++i) {
2080 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2081 if (msglist[j] == newmsgidlist[i]) {
2086 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2090 lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2093 * Now merge the new messages
2095 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2096 if (msglist == NULL) {
2097 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2099 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2100 num_msgs += num_msgs_to_be_merged;
2102 /* Sort the message list, so all the msgid's are in order */
2103 num_msgs = sort_msglist(msglist, num_msgs);
2105 /* Determine the highest message number */
2106 highest_msg = msglist[num_msgs - 1];
2108 /* Write it back to disk. */
2109 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2110 msglist, (int)(num_msgs * sizeof(long)));
2112 /* Free up the memory we used. */
2115 /* Update the highest-message pointer and unlock the room. */
2116 CC->room.QRhighest = highest_msg;
2117 lputroom(&CC->room);
2119 /* Perform replication checks if necessary */
2120 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2121 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2123 for (i=0; i<num_msgs_to_be_merged; ++i) {
2124 msgid = msgs_to_be_merged[i];
2126 if (supplied_msg != NULL) {
2130 msg = CtdlFetchMessage(msgid, 0);
2134 ReplicationChecks(msg);
2136 /* If the message has an Exclusive ID, index that... */
2137 if (msg->cm_fields['E'] != NULL) {
2138 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2141 /* Free up the memory we may have allocated */
2142 if (msg != supplied_msg) {
2143 CtdlFreeMessage(msg);
2151 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2154 /* Submit this room for processing by hooks */
2155 PerformRoomHooks(&CC->room);
2157 /* Go back to the room we were in before we wandered here... */
2158 getroom(&CC->room, hold_rm);
2160 /* Bump the reference count for all messages which were merged */
2161 for (i=0; i<num_msgs_to_be_merged; ++i) {
2162 AdjRefCount(msgs_to_be_merged[i], +1);
2165 /* Free up memory... */
2166 if (msgs_to_be_merged != NULL) {
2167 free(msgs_to_be_merged);
2170 /* Return success. */
2176 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2179 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2180 int do_repl_check, struct CtdlMessage *supplied_msg)
2182 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2189 * Message base operation to save a new message to the message store
2190 * (returns new message number)
2192 * This is the back end for CtdlSubmitMsg() and should not be directly
2193 * called by server-side modules.
2196 long send_message(struct CtdlMessage *msg) {
2204 /* Get a new message number */
2205 newmsgid = get_new_message_number();
2206 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2208 /* Generate an ID if we don't have one already */
2209 if (msg->cm_fields['I']==NULL) {
2210 msg->cm_fields['I'] = strdup(msgidbuf);
2213 /* If the message is big, set its body aside for storage elsewhere */
2214 if (msg->cm_fields['M'] != NULL) {
2215 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2217 holdM = msg->cm_fields['M'];
2218 msg->cm_fields['M'] = NULL;
2222 /* Serialize our data structure for storage in the database */
2223 serialize_message(&smr, msg);
2226 msg->cm_fields['M'] = holdM;
2230 cprintf("%d Unable to serialize message\n",
2231 ERROR + INTERNAL_ERROR);
2235 /* Write our little bundle of joy into the message base */
2236 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2237 smr.ser, smr.len) < 0) {
2238 lprintf(CTDL_ERR, "Can't store message\n");
2242 cdb_store(CDB_BIGMSGS,
2252 /* Free the memory we used for the serialized message */
2255 /* Return the *local* message ID to the caller
2256 * (even if we're storing an incoming network message)
2264 * Serialize a struct CtdlMessage into the format used on disk and network.
2266 * This function loads up a "struct ser_ret" (defined in server.h) which
2267 * contains the length of the serialized message and a pointer to the
2268 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2270 void serialize_message(struct ser_ret *ret, /* return values */
2271 struct CtdlMessage *msg) /* unserialized msg */
2273 size_t wlen, fieldlen;
2275 static char *forder = FORDER;
2278 * Check for valid message format
2280 if (is_valid_message(msg) == 0) {
2281 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2288 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2289 ret->len = ret->len +
2290 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2292 ret->ser = malloc(ret->len);
2293 if (ret->ser == NULL) {
2294 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2295 (long)ret->len, strerror(errno));
2302 ret->ser[1] = msg->cm_anon_type;
2303 ret->ser[2] = msg->cm_format_type;
2306 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2307 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2308 ret->ser[wlen++] = (char)forder[i];
2309 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2310 wlen = wlen + fieldlen + 1;
2312 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2313 (long)ret->len, (long)wlen);
2321 * Check to see if any messages already exist in the current room which
2322 * carry the same Exclusive ID as this one. If any are found, delete them.
2324 void ReplicationChecks(struct CtdlMessage *msg) {
2325 long old_msgnum = (-1L);
2327 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2329 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2332 /* No exclusive id? Don't do anything. */
2333 if (msg == NULL) return;
2334 if (msg->cm_fields['E'] == NULL) return;
2335 if (IsEmptyStr(msg->cm_fields['E'])) return;
2336 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2337 msg->cm_fields['E'], CC->room.QRname);*/
2339 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2340 if (old_msgnum > 0L) {
2341 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2342 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2349 * Save a message to disk and submit it into the delivery system.
2351 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2352 struct recptypes *recps, /* recipients (if mail) */
2353 char *force /* force a particular room? */
2355 char submit_filename[128];
2356 char generated_timestamp[32];
2357 char hold_rm[ROOMNAMELEN];
2358 char actual_rm[ROOMNAMELEN];
2359 char force_room[ROOMNAMELEN];
2360 char content_type[SIZ]; /* We have to learn this */
2361 char recipient[SIZ];
2364 struct ctdluser userbuf;
2366 struct MetaData smi;
2367 FILE *network_fp = NULL;
2368 static int seqnum = 1;
2369 struct CtdlMessage *imsg = NULL;
2371 size_t instr_alloc = 0;
2373 char *hold_R, *hold_D;
2374 char *collected_addresses = NULL;
2375 struct addresses_to_be_filed *aptr = NULL;
2376 char *saved_rfc822_version = NULL;
2377 int qualified_for_journaling = 0;
2379 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2380 if (is_valid_message(msg) == 0) return(-1); /* self check */
2382 /* If this message has no timestamp, we take the liberty of
2383 * giving it one, right now.
2385 if (msg->cm_fields['T'] == NULL) {
2386 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2387 msg->cm_fields['T'] = strdup(generated_timestamp);
2390 /* If this message has no path, we generate one.
2392 if (msg->cm_fields['P'] == NULL) {
2393 if (msg->cm_fields['A'] != NULL) {
2394 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2395 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2396 if (isspace(msg->cm_fields['P'][a])) {
2397 msg->cm_fields['P'][a] = ' ';
2402 msg->cm_fields['P'] = strdup("unknown");
2406 if (force == NULL) {
2407 strcpy(force_room, "");
2410 strcpy(force_room, force);
2413 /* Learn about what's inside, because it's what's inside that counts */
2414 if (msg->cm_fields['M'] == NULL) {
2415 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2419 switch (msg->cm_format_type) {
2421 strcpy(content_type, "text/x-citadel-variformat");
2424 strcpy(content_type, "text/plain");
2427 strcpy(content_type, "text/plain");
2428 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2430 safestrncpy(content_type, &mptr[13], sizeof content_type);
2431 striplt(content_type);
2432 for (a = 0; a < strlen(content_type); ++a) {
2433 if ((content_type[a] == ';')
2434 || (content_type[a] == ' ')
2435 || (content_type[a] == 13)
2436 || (content_type[a] == 10)) {
2437 content_type[a] = 0;
2443 /* Goto the correct room */
2444 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2445 strcpy(hold_rm, CC->room.QRname);
2446 strcpy(actual_rm, CC->room.QRname);
2447 if (recps != NULL) {
2448 strcpy(actual_rm, SENTITEMS);
2451 /* If the user is a twit, move to the twit room for posting */
2453 if (CC->user.axlevel == 2) {
2454 strcpy(hold_rm, actual_rm);
2455 strcpy(actual_rm, config.c_twitroom);
2456 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2460 /* ...or if this message is destined for Aide> then go there. */
2461 if (!IsEmptyStr(force_room)) {
2462 strcpy(actual_rm, force_room);
2465 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2466 if (strcasecmp(actual_rm, CC->room.QRname)) {
2467 /* getroom(&CC->room, actual_rm); */
2468 usergoto(actual_rm, 0, 1, NULL, NULL);
2472 * If this message has no O (room) field, generate one.
2474 if (msg->cm_fields['O'] == NULL) {
2475 msg->cm_fields['O'] = strdup(CC->room.QRname);
2478 /* Perform "before save" hooks (aborting if any return nonzero) */
2479 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2480 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2483 * If this message has an Exclusive ID, and the room is replication
2484 * checking enabled, then do replication checks.
2486 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2487 ReplicationChecks(msg);
2490 /* Save it to disk */
2491 lprintf(CTDL_DEBUG, "Saving to disk\n");
2492 newmsgid = send_message(msg);
2493 if (newmsgid <= 0L) return(-5);
2495 /* Write a supplemental message info record. This doesn't have to
2496 * be a critical section because nobody else knows about this message
2499 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2500 memset(&smi, 0, sizeof(struct MetaData));
2501 smi.meta_msgnum = newmsgid;
2502 smi.meta_refcount = 0;
2503 safestrncpy(smi.meta_content_type, content_type,
2504 sizeof smi.meta_content_type);
2507 * Measure how big this message will be when rendered as RFC822.
2508 * We do this for two reasons:
2509 * 1. We need the RFC822 length for the new metadata record, so the
2510 * POP and IMAP services don't have to calculate message lengths
2511 * while the user is waiting (multiplied by potentially hundreds
2512 * or thousands of messages).
2513 * 2. If journaling is enabled, we will need an RFC822 version of the
2514 * message to attach to the journalized copy.
2516 if (CC->redirect_buffer != NULL) {
2517 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2520 CC->redirect_buffer = malloc(SIZ);
2521 CC->redirect_len = 0;
2522 CC->redirect_alloc = SIZ;
2523 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2524 smi.meta_rfc822_length = CC->redirect_len;
2525 saved_rfc822_version = CC->redirect_buffer;
2526 CC->redirect_buffer = NULL;
2527 CC->redirect_len = 0;
2528 CC->redirect_alloc = 0;
2532 /* Now figure out where to store the pointers */
2533 lprintf(CTDL_DEBUG, "Storing pointers\n");
2535 /* If this is being done by the networker delivering a private
2536 * message, we want to BYPASS saving the sender's copy (because there
2537 * is no local sender; it would otherwise go to the Trashcan).
2539 if ((!CC->internal_pgm) || (recps == NULL)) {
2540 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2541 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2542 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2546 /* For internet mail, drop a copy in the outbound queue room */
2548 if (recps->num_internet > 0) {
2549 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2552 /* If other rooms are specified, drop them there too. */
2554 if (recps->num_room > 0)
2555 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2556 extract_token(recipient, recps->recp_room, i,
2557 '|', sizeof recipient);
2558 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2559 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2562 /* Bump this user's messages posted counter. */
2563 lprintf(CTDL_DEBUG, "Updating user\n");
2564 lgetuser(&CC->user, CC->curr_user);
2565 CC->user.posted = CC->user.posted + 1;
2566 lputuser(&CC->user);
2568 /* If this is private, local mail, make a copy in the
2569 * recipient's mailbox and bump the reference count.
2572 if (recps->num_local > 0)
2573 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2574 extract_token(recipient, recps->recp_local, i,
2575 '|', sizeof recipient);
2576 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2578 if (getuser(&userbuf, recipient) == 0) {
2579 // Add a flag so the Funambol module knows its mail
2580 msg->cm_fields['W'] = strdup(recipient);
2581 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2582 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2583 BumpNewMailCounter(userbuf.usernum);
2584 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2585 /* Generate a instruction message for the Funambol notification
2586 * server, in the same style as the SMTP queue
2589 instr = malloc(instr_alloc);
2590 snprintf(instr, instr_alloc,
2591 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2593 SPOOLMIME, newmsgid, (long)time(NULL),
2594 msg->cm_fields['A'], msg->cm_fields['N']
2597 imsg = malloc(sizeof(struct CtdlMessage));
2598 memset(imsg, 0, sizeof(struct CtdlMessage));
2599 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2600 imsg->cm_anon_type = MES_NORMAL;
2601 imsg->cm_format_type = FMT_RFC822;
2602 imsg->cm_fields['A'] = strdup("Citadel");
2603 imsg->cm_fields['J'] = strdup("do not journal");
2604 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2605 imsg->cm_fields['W'] = strdup(recipient);
2606 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2607 CtdlFreeMessage(imsg);
2611 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2612 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2617 /* Perform "after save" hooks */
2618 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2619 PerformMessageHooks(msg, EVT_AFTERSAVE);
2621 /* For IGnet mail, we have to save a new copy into the spooler for
2622 * each recipient, with the R and D fields set to the recipient and
2623 * destination-node. This has two ugly side effects: all other
2624 * recipients end up being unlisted in this recipient's copy of the
2625 * message, and it has to deliver multiple messages to the same
2626 * node. We'll revisit this again in a year or so when everyone has
2627 * a network spool receiver that can handle the new style messages.
2630 if (recps->num_ignet > 0)
2631 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2632 extract_token(recipient, recps->recp_ignet, i,
2633 '|', sizeof recipient);
2635 hold_R = msg->cm_fields['R'];
2636 hold_D = msg->cm_fields['D'];
2637 msg->cm_fields['R'] = malloc(SIZ);
2638 msg->cm_fields['D'] = malloc(128);
2639 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2640 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2642 serialize_message(&smr, msg);
2644 snprintf(submit_filename, sizeof submit_filename,
2645 "%s/netmail.%04lx.%04x.%04x",
2647 (long) getpid(), CC->cs_pid, ++seqnum);
2648 network_fp = fopen(submit_filename, "wb+");
2649 if (network_fp != NULL) {
2650 fwrite(smr.ser, smr.len, 1, network_fp);
2656 free(msg->cm_fields['R']);
2657 free(msg->cm_fields['D']);
2658 msg->cm_fields['R'] = hold_R;
2659 msg->cm_fields['D'] = hold_D;
2662 /* Go back to the room we started from */
2663 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2664 if (strcasecmp(hold_rm, CC->room.QRname))
2665 usergoto(hold_rm, 0, 1, NULL, NULL);
2667 /* For internet mail, generate delivery instructions.
2668 * Yes, this is recursive. Deal with it. Infinite recursion does
2669 * not happen because the delivery instructions message does not
2670 * contain a recipient.
2673 if (recps->num_internet > 0) {
2674 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2676 instr = malloc(instr_alloc);
2677 snprintf(instr, instr_alloc,
2678 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2680 SPOOLMIME, newmsgid, (long)time(NULL),
2681 msg->cm_fields['A'], msg->cm_fields['N']
2684 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2685 size_t tmp = strlen(instr);
2686 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2687 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2688 instr_alloc = instr_alloc * 2;
2689 instr = realloc(instr, instr_alloc);
2691 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2694 imsg = malloc(sizeof(struct CtdlMessage));
2695 memset(imsg, 0, sizeof(struct CtdlMessage));
2696 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2697 imsg->cm_anon_type = MES_NORMAL;
2698 imsg->cm_format_type = FMT_RFC822;
2699 imsg->cm_fields['A'] = strdup("Citadel");
2700 imsg->cm_fields['J'] = strdup("do not journal");
2701 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2702 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2703 CtdlFreeMessage(imsg);
2707 * Any addresses to harvest for someone's address book?
2709 if ( (CC->logged_in) && (recps != NULL) ) {
2710 collected_addresses = harvest_collected_addresses(msg);
2713 if (collected_addresses != NULL) {
2714 begin_critical_section(S_ATBF);
2715 aptr = (struct addresses_to_be_filed *)
2716 malloc(sizeof(struct addresses_to_be_filed));
2718 MailboxName(actual_rm, sizeof actual_rm,
2719 &CC->user, USERCONTACTSROOM);
2720 aptr->roomname = strdup(actual_rm);
2721 aptr->collected_addresses = collected_addresses;
2723 end_critical_section(S_ATBF);
2727 * Determine whether this message qualifies for journaling.
2729 if (msg->cm_fields['J'] != NULL) {
2730 qualified_for_journaling = 0;
2733 if (recps == NULL) {
2734 qualified_for_journaling = config.c_journal_pubmsgs;
2736 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2737 qualified_for_journaling = config.c_journal_email;
2740 qualified_for_journaling = config.c_journal_pubmsgs;
2745 * Do we have to perform journaling? If so, hand off the saved
2746 * RFC822 version will be handed off to the journaler for background
2747 * submit. Otherwise, we have to free the memory ourselves.
2749 if (saved_rfc822_version != NULL) {
2750 if (qualified_for_journaling) {
2751 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2754 free(saved_rfc822_version);
2767 * Convenience function for generating small administrative messages.
2769 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text,
2770 int format_type, char *subject)
2772 struct CtdlMessage *msg;
2773 struct recptypes *recp = NULL;
2775 msg = malloc(sizeof(struct CtdlMessage));
2776 memset(msg, 0, sizeof(struct CtdlMessage));
2777 msg->cm_magic = CTDLMESSAGE_MAGIC;
2778 msg->cm_anon_type = MES_NORMAL;
2779 msg->cm_format_type = format_type;
2782 msg->cm_fields['A'] = strdup(from);
2784 else if (fromaddr != NULL) {
2785 msg->cm_fields['A'] = strdup(fromaddr);
2786 if (strchr(msg->cm_fields['A'], '@')) {
2787 *strchr(msg->cm_fields['A'], '@') = 0;
2791 msg->cm_fields['A'] = strdup("Citadel");
2794 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2795 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2796 msg->cm_fields['N'] = strdup(NODENAME);
2798 msg->cm_fields['R'] = strdup(to);
2799 recp = validate_recipients(to);
2801 if (subject != NULL) {
2802 msg->cm_fields['U'] = strdup(subject);
2804 msg->cm_fields['M'] = strdup(text);
2806 CtdlSubmitMsg(msg, recp, room);
2807 CtdlFreeMessage(msg);
2808 if (recp != NULL) free_recipients(recp);
2814 * Back end function used by CtdlMakeMessage() and similar functions
2816 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2817 size_t maxlen, /* maximum message length */
2818 char *exist, /* if non-null, append to it;
2819 exist is ALWAYS freed */
2820 int crlf, /* CRLF newlines instead of LF */
2821 int sock /* socket handle or 0 for this session's client socket */
2825 size_t message_len = 0;
2826 size_t buffer_len = 0;
2833 if (exist == NULL) {
2840 message_len = strlen(exist);
2841 buffer_len = message_len + 4096;
2842 m = realloc(exist, buffer_len);
2849 /* Do we need to change leading ".." to "." for SMTP escaping? */
2850 if (!strcmp(terminator, ".")) {
2854 /* flush the input if we have nowhere to store it */
2859 /* read in the lines of message text one by one */
2862 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
2865 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2867 if (!strcmp(buf, terminator)) finished = 1;
2869 strcat(buf, "\r\n");
2875 /* Unescape SMTP-style input of two dots at the beginning of the line */
2877 if (!strncmp(buf, "..", 2)) {
2878 strcpy(buf, &buf[1]);
2882 if ( (!flushing) && (!finished) ) {
2883 /* Measure the line */
2884 linelen = strlen(buf);
2886 /* augment the buffer if we have to */
2887 if ((message_len + linelen) >= buffer_len) {
2888 ptr = realloc(m, (buffer_len * 2) );
2889 if (ptr == NULL) { /* flush if can't allocate */
2892 buffer_len = (buffer_len * 2);
2894 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2898 /* Add the new line to the buffer. NOTE: this loop must avoid
2899 * using functions like strcat() and strlen() because they
2900 * traverse the entire buffer upon every call, and doing that
2901 * for a multi-megabyte message slows it down beyond usability.
2903 strcpy(&m[message_len], buf);
2904 message_len += linelen;
2907 /* if we've hit the max msg length, flush the rest */
2908 if (message_len >= maxlen) flushing = 1;
2910 } while (!finished);
2918 * Build a binary message to be saved on disk.
2919 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2920 * will become part of the message. This means you are no longer
2921 * responsible for managing that memory -- it will be freed along with
2922 * the rest of the fields when CtdlFreeMessage() is called.)
2925 struct CtdlMessage *CtdlMakeMessage(
2926 struct ctdluser *author, /* author's user structure */
2927 char *recipient, /* NULL if it's not mail */
2928 char *recp_cc, /* NULL if it's not mail */
2929 char *room, /* room where it's going */
2930 int type, /* see MES_ types in header file */
2931 int format_type, /* variformat, plain text, MIME... */
2932 char *fake_name, /* who we're masquerading as */
2933 char *my_email, /* which of my email addresses to use (empty is ok) */
2934 char *subject, /* Subject (optional) */
2935 char *supplied_euid, /* ...or NULL if this is irrelevant */
2936 char *preformatted_text /* ...or NULL to read text from client */
2938 char dest_node[256];
2940 struct CtdlMessage *msg;
2943 msg = malloc(sizeof(struct CtdlMessage));
2944 memset(msg, 0, sizeof(struct CtdlMessage));
2945 msg->cm_magic = CTDLMESSAGE_MAGIC;
2946 msg->cm_anon_type = type;
2947 msg->cm_format_type = format_type;
2949 /* Don't confuse the poor folks if it's not routed mail. */
2950 strcpy(dest_node, "");
2955 /* Path or Return-Path */
2956 if (my_email == NULL) my_email = "";
2958 if (!IsEmptyStr(my_email)) {
2959 msg->cm_fields['P'] = strdup(my_email);
2962 snprintf(buf, sizeof buf, "%s", author->fullname);
2963 msg->cm_fields['P'] = strdup(buf);
2965 for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
2966 if (isspace(msg->cm_fields['P'][i])) {
2967 msg->cm_fields['P'][i] = '_';
2971 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2972 msg->cm_fields['T'] = strdup(buf);
2974 if (fake_name[0]) /* author */
2975 msg->cm_fields['A'] = strdup(fake_name);
2977 msg->cm_fields['A'] = strdup(author->fullname);
2979 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2980 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2983 msg->cm_fields['O'] = strdup(CC->room.QRname);
2986 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2987 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2989 if (recipient[0] != 0) {
2990 msg->cm_fields['R'] = strdup(recipient);
2992 if (recp_cc[0] != 0) {
2993 msg->cm_fields['Y'] = strdup(recp_cc);
2995 if (dest_node[0] != 0) {
2996 msg->cm_fields['D'] = strdup(dest_node);
2999 if (!IsEmptyStr(my_email)) {
3000 msg->cm_fields['F'] = strdup(my_email);
3002 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3003 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3006 if (subject != NULL) {
3009 length = strlen(subject);
3015 while ((subject[i] != '\0') &&
3016 (IsAscii = isascii(subject[i]) != 0 ))
3019 msg->cm_fields['U'] = strdup(subject);
3020 else /* ok, we've got utf8 in the string. */
3022 msg->cm_fields['U'] = rfc2047encode(subject, length);
3028 if (supplied_euid != NULL) {
3029 msg->cm_fields['E'] = strdup(supplied_euid);
3032 if (preformatted_text != NULL) {
3033 msg->cm_fields['M'] = preformatted_text;
3036 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3044 * Check to see whether we have permission to post a message in the current
3045 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3046 * returns 0 on success.
3048 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3051 if (!(CC->logged_in)) {
3052 snprintf(errmsgbuf, n, "Not logged in.");
3053 return (ERROR + NOT_LOGGED_IN);
3056 if ((CC->user.axlevel < 2)
3057 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3058 snprintf(errmsgbuf, n, "Need to be validated to enter "
3059 "(except in %s> to sysop)", MAILROOM);
3060 return (ERROR + HIGHER_ACCESS_REQUIRED);
3063 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3064 if (!(ra & UA_POSTALLOWED)) {
3065 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3066 return (ERROR + HIGHER_ACCESS_REQUIRED);
3069 strcpy(errmsgbuf, "Ok");
3075 * Check to see if the specified user has Internet mail permission
3076 * (returns nonzero if permission is granted)
3078 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3080 /* Do not allow twits to send Internet mail */
3081 if (who->axlevel <= 2) return(0);
3083 /* Globally enabled? */
3084 if (config.c_restrict == 0) return(1);
3086 /* User flagged ok? */
3087 if (who->flags & US_INTERNET) return(2);
3089 /* Aide level access? */
3090 if (who->axlevel >= 6) return(3);
3092 /* No mail for you! */
3098 * Validate recipients, count delivery types and errors, and handle aliasing
3099 * FIXME check for dupes!!!!!
3101 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3102 * were specified, or the number of addresses found invalid.
3104 * Caller needs to free the result using free_recipients()
3106 struct recptypes *validate_recipients(char *supplied_recipients) {
3107 struct recptypes *ret;
3108 char *recipients = NULL;
3109 char this_recp[256];
3110 char this_recp_cooked[256];
3116 struct ctdluser tempUS;
3117 struct ctdlroom tempQR;
3121 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3122 if (ret == NULL) return(NULL);
3124 /* Set all strings to null and numeric values to zero */
3125 memset(ret, 0, sizeof(struct recptypes));
3127 if (supplied_recipients == NULL) {
3128 recipients = strdup("");
3131 recipients = strdup(supplied_recipients);
3134 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3135 * actually need, but it's healthier for the heap than doing lots of tiny
3136 * realloc() calls instead.
3139 ret->errormsg = malloc(strlen(recipients) + 1024);
3140 ret->recp_local = malloc(strlen(recipients) + 1024);
3141 ret->recp_internet = malloc(strlen(recipients) + 1024);
3142 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3143 ret->recp_room = malloc(strlen(recipients) + 1024);
3144 ret->display_recp = malloc(strlen(recipients) + 1024);
3146 ret->errormsg[0] = 0;
3147 ret->recp_local[0] = 0;
3148 ret->recp_internet[0] = 0;
3149 ret->recp_ignet[0] = 0;
3150 ret->recp_room[0] = 0;
3151 ret->display_recp[0] = 0;
3153 ret->recptypes_magic = RECPTYPES_MAGIC;
3155 /* Change all valid separator characters to commas */
3156 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3157 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3158 recipients[i] = ',';
3162 /* Now start extracting recipients... */
3164 while (!IsEmptyStr(recipients)) {
3166 for (i=0; i<=strlen(recipients); ++i) {
3167 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3168 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3169 safestrncpy(this_recp, recipients, i+1);
3171 if (recipients[i] == ',') {
3172 strcpy(recipients, &recipients[i+1]);
3175 strcpy(recipients, "");
3182 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3184 mailtype = alias(this_recp);
3185 mailtype = alias(this_recp);
3186 mailtype = alias(this_recp);
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];
3196 this_recp_cooked[j] = '\0';
3200 if (!strcasecmp(this_recp, "sysop")) {
3202 strcpy(this_recp, config.c_aideroom);
3203 if (!IsEmptyStr(ret->recp_room)) {
3204 strcat(ret->recp_room, "|");
3206 strcat(ret->recp_room, this_recp);
3208 else if (getuser(&tempUS, this_recp) == 0) {
3210 strcpy(this_recp, tempUS.fullname);
3211 if (!IsEmptyStr(ret->recp_local)) {
3212 strcat(ret->recp_local, "|");
3214 strcat(ret->recp_local, this_recp);
3216 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3218 strcpy(this_recp, tempUS.fullname);
3219 if (!IsEmptyStr(ret->recp_local)) {
3220 strcat(ret->recp_local, "|");
3222 strcat(ret->recp_local, this_recp);
3224 else if ( (!strncasecmp(this_recp, "room_", 5))
3225 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3227 if (!IsEmptyStr(ret->recp_room)) {
3228 strcat(ret->recp_room, "|");
3230 strcat(ret->recp_room, &this_recp_cooked[5]);
3238 /* Yes, you're reading this correctly: if the target
3239 * domain points back to the local system or an attached
3240 * Citadel directory, the address is invalid. That's
3241 * because if the address were valid, we would have
3242 * already translated it to a local address by now.
3244 if (IsDirectory(this_recp, 0)) {
3249 ++ret->num_internet;
3250 if (!IsEmptyStr(ret->recp_internet)) {
3251 strcat(ret->recp_internet, "|");
3253 strcat(ret->recp_internet, this_recp);
3258 if (!IsEmptyStr(ret->recp_ignet)) {
3259 strcat(ret->recp_ignet, "|");
3261 strcat(ret->recp_ignet, this_recp);
3269 if (IsEmptyStr(ret->errormsg)) {
3270 snprintf(append, sizeof append,
3271 "Invalid recipient: %s",
3275 snprintf(append, sizeof append, ", %s", this_recp);
3277 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3278 strcat(ret->errormsg, append);
3282 if (IsEmptyStr(ret->display_recp)) {
3283 strcpy(append, this_recp);
3286 snprintf(append, sizeof append, ", %s", this_recp);
3288 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3289 strcat(ret->display_recp, append);
3294 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3295 ret->num_room + ret->num_error) == 0) {
3296 ret->num_error = (-1);
3297 strcpy(ret->errormsg, "No recipients specified.");
3300 lprintf(CTDL_DEBUG, "validate_recipients()\n");
3301 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3302 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3303 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3304 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3305 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3313 * Destructor for struct recptypes
3315 void free_recipients(struct recptypes *valid) {
3317 if (valid == NULL) {
3321 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3322 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3326 if (valid->errormsg != NULL) free(valid->errormsg);
3327 if (valid->recp_local != NULL) free(valid->recp_local);
3328 if (valid->recp_internet != NULL) free(valid->recp_internet);
3329 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3330 if (valid->recp_room != NULL) free(valid->recp_room);
3331 if (valid->display_recp != NULL) free(valid->display_recp);
3338 * message entry - mode 0 (normal)
3340 void cmd_ent0(char *entargs)
3346 char supplied_euid[128];
3348 int format_type = 0;
3349 char newusername[256];
3350 char newuseremail[256];
3351 struct CtdlMessage *msg;
3355 struct recptypes *valid = NULL;
3356 struct recptypes *valid_to = NULL;
3357 struct recptypes *valid_cc = NULL;
3358 struct recptypes *valid_bcc = NULL;
3360 int subject_required = 0;
3365 int newuseremail_ok = 0;
3369 post = extract_int(entargs, 0);
3370 extract_token(recp, entargs, 1, '|', sizeof recp);
3371 anon_flag = extract_int(entargs, 2);
3372 format_type = extract_int(entargs, 3);
3373 extract_token(subject, entargs, 4, '|', sizeof subject);
3374 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3375 do_confirm = extract_int(entargs, 6);
3376 extract_token(cc, entargs, 7, '|', sizeof cc);
3377 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3378 switch(CC->room.QRdefaultview) {
3381 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3384 supplied_euid[0] = 0;
3387 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3389 /* first check to make sure the request is valid. */
3391 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3394 cprintf("%d %s\n", err, errmsg);
3398 /* Check some other permission type things. */
3400 if (IsEmptyStr(newusername)) {
3401 strcpy(newusername, CC->user.fullname);
3403 if ( (CC->user.axlevel < 6)
3404 && (strcasecmp(newusername, CC->user.fullname))
3405 && (strcasecmp(newusername, CC->cs_inet_fn))
3407 cprintf("%d You don't have permission to author messages as '%s'.\n",
3408 ERROR + HIGHER_ACCESS_REQUIRED,
3415 if (IsEmptyStr(newuseremail)) {
3416 newuseremail_ok = 1;
3419 if (!IsEmptyStr(newuseremail)) {
3420 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3421 newuseremail_ok = 1;
3423 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3424 j = num_tokens(CC->cs_inet_other_emails, '|');
3425 for (i=0; i<j; ++i) {
3426 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3427 if (!strcasecmp(newuseremail, buf)) {
3428 newuseremail_ok = 1;
3434 if (!newuseremail_ok) {
3435 cprintf("%d You don't have permission to author messages as '%s'.\n",
3436 ERROR + HIGHER_ACCESS_REQUIRED,
3442 CC->cs_flags |= CS_POSTING;
3444 /* In mailbox rooms we have to behave a little differently --
3445 * make sure the user has specified at least one recipient. Then
3446 * validate the recipient(s). We do this for the Mail> room, as
3447 * well as any room which has the "Mailbox" view set.
3450 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3451 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3453 if (CC->user.axlevel < 2) {
3454 strcpy(recp, "sysop");
3459 valid_to = validate_recipients(recp);
3460 if (valid_to->num_error > 0) {
3461 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3462 free_recipients(valid_to);
3466 valid_cc = validate_recipients(cc);
3467 if (valid_cc->num_error > 0) {
3468 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3469 free_recipients(valid_to);
3470 free_recipients(valid_cc);
3474 valid_bcc = validate_recipients(bcc);
3475 if (valid_bcc->num_error > 0) {
3476 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3477 free_recipients(valid_to);
3478 free_recipients(valid_cc);
3479 free_recipients(valid_bcc);
3483 /* Recipient required, but none were specified */
3484 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3485 free_recipients(valid_to);
3486 free_recipients(valid_cc);
3487 free_recipients(valid_bcc);
3488 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3492 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3493 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3494 cprintf("%d You do not have permission "
3495 "to send Internet mail.\n",
3496 ERROR + HIGHER_ACCESS_REQUIRED);
3497 free_recipients(valid_to);
3498 free_recipients(valid_cc);
3499 free_recipients(valid_bcc);
3504 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)
3505 && (CC->user.axlevel < 4) ) {
3506 cprintf("%d Higher access required for network mail.\n",
3507 ERROR + HIGHER_ACCESS_REQUIRED);
3508 free_recipients(valid_to);
3509 free_recipients(valid_cc);
3510 free_recipients(valid_bcc);
3514 if ((RESTRICT_INTERNET == 1)
3515 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3516 && ((CC->user.flags & US_INTERNET) == 0)
3517 && (!CC->internal_pgm)) {
3518 cprintf("%d You don't have access to Internet mail.\n",
3519 ERROR + HIGHER_ACCESS_REQUIRED);
3520 free_recipients(valid_to);
3521 free_recipients(valid_cc);
3522 free_recipients(valid_bcc);
3528 /* Is this a room which has anonymous-only or anonymous-option? */
3529 anonymous = MES_NORMAL;
3530 if (CC->room.QRflags & QR_ANONONLY) {
3531 anonymous = MES_ANONONLY;
3533 if (CC->room.QRflags & QR_ANONOPT) {
3534 if (anon_flag == 1) { /* only if the user requested it */
3535 anonymous = MES_ANONOPT;
3539 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3543 /* Recommend to the client that the use of a message subject is
3544 * strongly recommended in this room, if either the SUBJECTREQ flag
3545 * is set, or if there is one or more Internet email recipients.
3547 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3548 if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3549 if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3550 if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3552 /* If we're only checking the validity of the request, return
3553 * success without creating the message.
3556 cprintf("%d %s|%d\n", CIT_OK,
3557 ((valid_to != NULL) ? valid_to->display_recp : ""),
3559 free_recipients(valid_to);
3560 free_recipients(valid_cc);
3561 free_recipients(valid_bcc);
3565 /* We don't need these anymore because we'll do it differently below */
3566 free_recipients(valid_to);
3567 free_recipients(valid_cc);
3568 free_recipients(valid_bcc);
3570 /* Read in the message from the client. */
3572 cprintf("%d send message\n", START_CHAT_MODE);
3574 cprintf("%d send message\n", SEND_LISTING);
3577 msg = CtdlMakeMessage(&CC->user, recp, cc,
3578 CC->room.QRname, anonymous, format_type,
3579 newusername, newuseremail, subject,
3580 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3583 /* Put together one big recipients struct containing to/cc/bcc all in
3584 * one. This is for the envelope.
3586 char *all_recps = malloc(SIZ * 3);
3587 strcpy(all_recps, recp);
3588 if (!IsEmptyStr(cc)) {
3589 if (!IsEmptyStr(all_recps)) {
3590 strcat(all_recps, ",");
3592 strcat(all_recps, cc);
3594 if (!IsEmptyStr(bcc)) {
3595 if (!IsEmptyStr(all_recps)) {
3596 strcat(all_recps, ",");
3598 strcat(all_recps, bcc);
3600 if (!IsEmptyStr(all_recps)) {
3601 valid = validate_recipients(all_recps);
3609 msgnum = CtdlSubmitMsg(msg, valid, "");
3612 cprintf("%ld\n", msgnum);
3614 cprintf("Message accepted.\n");
3617 cprintf("Internal error.\n");
3619 if (msg->cm_fields['E'] != NULL) {
3620 cprintf("%s\n", msg->cm_fields['E']);
3627 CtdlFreeMessage(msg);
3629 if (valid != NULL) {
3630 free_recipients(valid);
3638 * API function to delete messages which match a set of criteria
3639 * (returns the actual number of messages deleted)
3641 int CtdlDeleteMessages(char *room_name, /* which room */
3642 long *dmsgnums, /* array of msg numbers to be deleted */
3643 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3644 char *content_type /* or "" for any. regular expressions expected. */
3647 struct ctdlroom qrbuf;
3648 struct cdbdata *cdbfr;
3649 long *msglist = NULL;
3650 long *dellist = NULL;
3653 int num_deleted = 0;
3655 struct MetaData smi;
3658 int need_to_free_re = 0;
3660 if (content_type) if (!IsEmptyStr(content_type)) {
3661 regcomp(&re, content_type, 0);
3662 need_to_free_re = 1;
3664 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3665 room_name, num_dmsgnums, content_type);
3667 /* get room record, obtaining a lock... */
3668 if (lgetroom(&qrbuf, room_name) != 0) {
3669 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3671 if (need_to_free_re) regfree(&re);
3672 return (0); /* room not found */
3674 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3676 if (cdbfr != NULL) {
3677 dellist = malloc(cdbfr->len);
3678 msglist = (long *) cdbfr->ptr;
3679 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3680 num_msgs = cdbfr->len / sizeof(long);
3684 for (i = 0; i < num_msgs; ++i) {
3687 /* Set/clear a bit for each criterion */
3689 /* 0 messages in the list or a null list means that we are
3690 * interested in deleting any messages which meet the other criteria.
3692 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3693 delete_this |= 0x01;
3696 for (j=0; j<num_dmsgnums; ++j) {
3697 if (msglist[i] == dmsgnums[j]) {
3698 delete_this |= 0x01;
3703 if (IsEmptyStr(content_type)) {
3704 delete_this |= 0x02;
3706 GetMetaData(&smi, msglist[i]);
3707 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3708 delete_this |= 0x02;
3712 /* Delete message only if all bits are set */
3713 if (delete_this == 0x03) {
3714 dellist[num_deleted++] = msglist[i];
3719 num_msgs = sort_msglist(msglist, num_msgs);
3720 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3721 msglist, (int)(num_msgs * sizeof(long)));
3723 qrbuf.QRhighest = msglist[num_msgs - 1];
3727 /* Go through the messages we pulled out of the index, and decrement
3728 * their reference counts by 1. If this is the only room the message
3729 * was in, the reference count will reach zero and the message will
3730 * automatically be deleted from the database. We do this in a
3731 * separate pass because there might be plug-in hooks getting called,
3732 * and we don't want that happening during an S_ROOMS critical
3735 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3736 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3737 AdjRefCount(dellist[i], -1);
3740 /* Now free the memory we used, and go away. */
3741 if (msglist != NULL) free(msglist);
3742 if (dellist != NULL) free(dellist);
3743 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3744 if (need_to_free_re) regfree(&re);
3745 return (num_deleted);
3751 * Check whether the current user has permission to delete messages from
3752 * the current room (returns 1 for yes, 0 for no)
3754 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3756 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3757 if (ra & UA_DELETEALLOWED) return(1);
3765 * Delete message from current room
3767 void cmd_dele(char *args)
3776 extract_token(msgset, args, 0, '|', sizeof msgset);
3777 num_msgs = num_tokens(msgset, ',');
3779 cprintf("%d Nothing to do.\n", CIT_OK);
3783 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3784 cprintf("%d Higher access required.\n",
3785 ERROR + HIGHER_ACCESS_REQUIRED);
3790 * Build our message set to be moved/copied
3792 msgs = malloc(num_msgs * sizeof(long));
3793 for (i=0; i<num_msgs; ++i) {
3794 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3795 msgs[i] = atol(msgtok);
3798 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3802 cprintf("%d %d message%s deleted.\n", CIT_OK,
3803 num_deleted, ((num_deleted != 1) ? "s" : ""));
3805 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3811 * Back end API function for moves and deletes (multiple messages)
3813 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3816 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3817 if (err != 0) return(err);
3826 * move or copy a message to another room
3828 void cmd_move(char *args)
3835 char targ[ROOMNAMELEN];
3836 struct ctdlroom qtemp;
3843 extract_token(msgset, args, 0, '|', sizeof msgset);
3844 num_msgs = num_tokens(msgset, ',');
3846 cprintf("%d Nothing to do.\n", CIT_OK);
3850 extract_token(targ, args, 1, '|', sizeof targ);
3851 convert_room_name_macros(targ, sizeof targ);
3852 targ[ROOMNAMELEN - 1] = 0;
3853 is_copy = extract_int(args, 2);
3855 if (getroom(&qtemp, targ) != 0) {
3856 cprintf("%d '%s' does not exist.\n",
3857 ERROR + ROOM_NOT_FOUND, targ);
3861 getuser(&CC->user, CC->curr_user);
3862 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3864 /* Check for permission to perform this operation.
3865 * Remember: "CC->room" is source, "qtemp" is target.
3869 /* Aides can move/copy */
3870 if (CC->user.axlevel >= 6) permit = 1;
3872 /* Room aides can move/copy */
3873 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3875 /* Permit move/copy from personal rooms */
3876 if ((CC->room.QRflags & QR_MAILBOX)
3877 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3879 /* Permit only copy from public to personal room */
3881 && (!(CC->room.QRflags & QR_MAILBOX))
3882 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3884 /* Permit message removal from collaborative delete rooms */
3885 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3887 /* User must have access to target room */
3888 if (!(ra & UA_KNOWN)) permit = 0;
3891 cprintf("%d Higher access required.\n",
3892 ERROR + HIGHER_ACCESS_REQUIRED);
3897 * Build our message set to be moved/copied
3899 msgs = malloc(num_msgs * sizeof(long));
3900 for (i=0; i<num_msgs; ++i) {
3901 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3902 msgs[i] = atol(msgtok);
3908 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3910 cprintf("%d Cannot store message(s) in %s: error %d\n",
3916 /* Now delete the message from the source room,
3917 * if this is a 'move' rather than a 'copy' operation.
3920 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3924 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3930 * GetMetaData() - Get the supplementary record for a message
3932 void GetMetaData(struct MetaData *smibuf, long msgnum)
3935 struct cdbdata *cdbsmi;
3938 memset(smibuf, 0, sizeof(struct MetaData));
3939 smibuf->meta_msgnum = msgnum;
3940 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3942 /* Use the negative of the message number for its supp record index */
3943 TheIndex = (0L - msgnum);
3945 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3946 if (cdbsmi == NULL) {
3947 return; /* record not found; go with defaults */
3949 memcpy(smibuf, cdbsmi->ptr,
3950 ((cdbsmi->len > sizeof(struct MetaData)) ?
3951 sizeof(struct MetaData) : cdbsmi->len));
3958 * PutMetaData() - (re)write supplementary record for a message
3960 void PutMetaData(struct MetaData *smibuf)
3964 /* Use the negative of the message number for the metadata db index */
3965 TheIndex = (0L - smibuf->meta_msgnum);
3967 cdb_store(CDB_MSGMAIN,
3968 &TheIndex, (int)sizeof(long),
3969 smibuf, (int)sizeof(struct MetaData));
3974 * AdjRefCount - submit an adjustment to the reference count for a message.
3975 * (These are just queued -- we actually process them later.)
3977 void AdjRefCount(long msgnum, int incr)
3979 struct arcq new_arcq;
3981 begin_critical_section(S_SUPPMSGMAIN);
3982 if (arcfp == NULL) {
3983 arcfp = fopen(file_arcq, "ab+");
3985 end_critical_section(S_SUPPMSGMAIN);
3987 /* msgnum < 0 means that we're trying to close the file */
3989 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3990 begin_critical_section(S_SUPPMSGMAIN);
3991 if (arcfp != NULL) {
3995 end_critical_section(S_SUPPMSGMAIN);
4000 * If we can't open the queue, perform the operation synchronously.
4002 if (arcfp == NULL) {
4003 TDAP_AdjRefCount(msgnum, incr);
4007 new_arcq.arcq_msgnum = msgnum;
4008 new_arcq.arcq_delta = incr;
4009 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4017 * TDAP_ProcessAdjRefCountQueue()
4019 * Process the queue of message count adjustments that was created by calls
4020 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4021 * for each one. This should be an "off hours" operation.
4023 int TDAP_ProcessAdjRefCountQueue(void)
4025 char file_arcq_temp[PATH_MAX];
4028 struct arcq arcq_rec;
4029 int num_records_processed = 0;
4031 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4033 begin_critical_section(S_SUPPMSGMAIN);
4034 if (arcfp != NULL) {
4039 r = link(file_arcq, file_arcq_temp);
4041 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4042 end_critical_section(S_SUPPMSGMAIN);
4043 return(num_records_processed);
4047 end_critical_section(S_SUPPMSGMAIN);
4049 fp = fopen(file_arcq_temp, "rb");
4051 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4052 return(num_records_processed);
4055 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4056 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4057 ++num_records_processed;
4061 r = unlink(file_arcq_temp);
4063 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4066 return(num_records_processed);
4072 * TDAP_AdjRefCount - adjust the reference count for a message.
4073 * This one does it "for real" because it's called by
4074 * the autopurger function that processes the queue
4075 * created by AdjRefCount(). If a message's reference
4076 * count becomes zero, we also delete the message from
4077 * disk and de-index it.
4079 void TDAP_AdjRefCount(long msgnum, int incr)
4082 struct MetaData smi;
4085 /* This is a *tight* critical section; please keep it that way, as
4086 * it may get called while nested in other critical sections.
4087 * Complicating this any further will surely cause deadlock!
4089 begin_critical_section(S_SUPPMSGMAIN);
4090 GetMetaData(&smi, msgnum);
4091 smi.meta_refcount += incr;
4093 end_critical_section(S_SUPPMSGMAIN);
4094 lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4095 msgnum, incr, smi.meta_refcount);
4097 /* If the reference count is now zero, delete the message
4098 * (and its supplementary record as well).
4100 if (smi.meta_refcount == 0) {
4101 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4103 /* Call delete hooks with NULL room to show it has gone altogether */
4104 PerformDeleteHooks(NULL, msgnum);
4106 /* Remove from message base */
4108 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4109 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4111 /* Remove metadata record */
4112 delnum = (0L - msgnum);
4113 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4119 * Write a generic object to this room
4121 * Note: this could be much more efficient. Right now we use two temporary
4122 * files, and still pull the message into memory as with all others.
4124 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4125 char *content_type, /* MIME type of this object */
4126 char *tempfilename, /* Where to fetch it from */
4127 struct ctdluser *is_mailbox, /* Mailbox room? */
4128 int is_binary, /* Is encoding necessary? */
4129 int is_unique, /* Del others of this type? */
4130 unsigned int flags /* Internal save flags */
4135 struct ctdlroom qrbuf;
4136 char roomname[ROOMNAMELEN];
4137 struct CtdlMessage *msg;
4139 char *raw_message = NULL;
4140 char *encoded_message = NULL;
4141 off_t raw_length = 0;
4143 if (is_mailbox != NULL) {
4144 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4147 safestrncpy(roomname, req_room, sizeof(roomname));
4150 fp = fopen(tempfilename, "rb");
4152 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4153 tempfilename, strerror(errno));
4156 fseek(fp, 0L, SEEK_END);
4157 raw_length = ftell(fp);
4159 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4161 raw_message = malloc((size_t)raw_length + 2);
4162 fread(raw_message, (size_t)raw_length, 1, fp);
4166 encoded_message = malloc((size_t)
4167 (((raw_length * 134) / 100) + 4096 ) );
4170 encoded_message = malloc((size_t)(raw_length + 4096));
4173 sprintf(encoded_message, "Content-type: %s\n", content_type);
4176 sprintf(&encoded_message[strlen(encoded_message)],
4177 "Content-transfer-encoding: base64\n\n"
4181 sprintf(&encoded_message[strlen(encoded_message)],
4182 "Content-transfer-encoding: 7bit\n\n"
4188 &encoded_message[strlen(encoded_message)],
4194 raw_message[raw_length] = 0;
4196 &encoded_message[strlen(encoded_message)],
4204 lprintf(CTDL_DEBUG, "Allocating\n");
4205 msg = malloc(sizeof(struct CtdlMessage));
4206 memset(msg, 0, sizeof(struct CtdlMessage));
4207 msg->cm_magic = CTDLMESSAGE_MAGIC;
4208 msg->cm_anon_type = MES_NORMAL;
4209 msg->cm_format_type = 4;
4210 msg->cm_fields['A'] = strdup(CC->user.fullname);
4211 msg->cm_fields['O'] = strdup(req_room);
4212 msg->cm_fields['N'] = strdup(config.c_nodename);
4213 msg->cm_fields['H'] = strdup(config.c_humannode);
4214 msg->cm_flags = flags;
4216 msg->cm_fields['M'] = encoded_message;
4218 /* Create the requested room if we have to. */
4219 if (getroom(&qrbuf, roomname) != 0) {
4220 create_room(roomname,
4221 ( (is_mailbox != NULL) ? 5 : 3 ),
4222 "", 0, 1, 0, VIEW_BBS);
4224 /* If the caller specified this object as unique, delete all
4225 * other objects of this type that are currently in the room.
4228 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4229 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4232 /* Now write the data */
4233 CtdlSubmitMsg(msg, NULL, roomname);
4234 CtdlFreeMessage(msg);
4242 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4243 config_msgnum = msgnum;
4247 char *CtdlGetSysConfig(char *sysconfname) {
4248 char hold_rm[ROOMNAMELEN];
4251 struct CtdlMessage *msg;
4254 strcpy(hold_rm, CC->room.QRname);
4255 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4256 getroom(&CC->room, hold_rm);
4261 /* We want the last (and probably only) config in this room */
4262 begin_critical_section(S_CONFIG);
4263 config_msgnum = (-1L);
4264 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4265 CtdlGetSysConfigBackend, NULL);
4266 msgnum = config_msgnum;
4267 end_critical_section(S_CONFIG);
4273 msg = CtdlFetchMessage(msgnum, 1);
4275 conf = strdup(msg->cm_fields['M']);
4276 CtdlFreeMessage(msg);
4283 getroom(&CC->room, hold_rm);
4285 if (conf != NULL) do {
4286 extract_token(buf, conf, 0, '\n', sizeof buf);
4287 strcpy(conf, &conf[strlen(buf)+1]);
4288 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4293 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4294 char temp[PATH_MAX];
4297 CtdlMakeTempFileName(temp, sizeof temp);
4299 fp = fopen(temp, "w");
4300 if (fp == NULL) return;
4301 fprintf(fp, "%s", sysconfdata);
4304 /* this handy API function does all the work for us */
4305 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4311 * Determine whether a given Internet address belongs to the current user
4313 int CtdlIsMe(char *addr, int addr_buf_len)
4315 struct recptypes *recp;
4318 recp = validate_recipients(addr);
4319 if (recp == NULL) return(0);
4321 if (recp->num_local == 0) {
4322 free_recipients(recp);
4326 for (i=0; i<recp->num_local; ++i) {
4327 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4328 if (!strcasecmp(addr, CC->user.fullname)) {
4329 free_recipients(recp);
4334 free_recipients(recp);
4340 * Citadel protocol command to do the same
4342 void cmd_isme(char *argbuf) {
4345 if (CtdlAccessCheck(ac_logged_in)) return;
4346 extract_token(addr, argbuf, 0, '|', sizeof addr);
4348 if (CtdlIsMe(addr, sizeof addr)) {
4349 cprintf("%d %s\n", CIT_OK, addr);
4352 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);