4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
38 #include "serv_extensions.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
54 #include "serv_fulltext.h"
56 #include "euidindex.h"
59 struct addresses_to_be_filed *atbf = NULL;
62 * This really belongs in serv_network.c, but I don't know how to export
63 * symbols between modules.
65 struct FilterList *filterlist = NULL;
69 * These are the four-character field headers we use when outputting
70 * messages in Citadel format (as opposed to RFC822 format).
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
107 * This function is self explanatory.
108 * (What can I say, I'm in a weird mood today...)
110 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
114 for (i = 0; i < strlen(name); ++i) {
115 if (name[i] == '@') {
116 while (isspace(name[i - 1]) && i > 0) {
117 strcpy(&name[i - 1], &name[i]);
120 while (isspace(name[i + 1])) {
121 strcpy(&name[i + 1], &name[i + 2]);
129 * Aliasing for network mail.
130 * (Error messages have been commented out, because this is a server.)
132 int alias(char *name)
133 { /* process alias and routing info for mail */
136 char aaa[SIZ], bbb[SIZ];
137 char *ignetcfg = NULL;
138 char *ignetmap = NULL;
145 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
146 stripallbut(name, '<', '>');
154 "mail.aliases", "r");
156 fp = fopen("/dev/null", "r");
163 while (fgets(aaa, sizeof aaa, fp) != NULL) {
164 while (isspace(name[0]))
165 strcpy(name, &name[1]);
166 aaa[strlen(aaa) - 1] = 0;
168 for (a = 0; a < strlen(aaa); ++a) {
170 strcpy(bbb, &aaa[a + 1]);
174 if (!strcasecmp(name, aaa))
179 /* Hit the Global Address Book */
180 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
184 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
186 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
187 for (a=0; a<strlen(name); ++a) {
188 if (name[a] == '@') {
189 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
191 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
196 /* determine local or remote type, see citadel.h */
197 at = haschar(name, '@');
198 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
199 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
200 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
202 /* figure out the delivery mode */
203 extract_token(node, name, 1, '@', sizeof node);
205 /* If there are one or more dots in the nodename, we assume that it
206 * is an FQDN and will attempt SMTP delivery to the Internet.
208 if (haschar(node, '.') > 0) {
209 return(MES_INTERNET);
212 /* Otherwise we look in the IGnet maps for a valid Citadel node.
213 * Try directly-connected nodes first...
215 ignetcfg = CtdlGetSysConfig(IGNETCFG);
216 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
217 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
218 extract_token(testnode, buf, 0, '|', sizeof testnode);
219 if (!strcasecmp(node, testnode)) {
227 * Then try nodes that are two or more hops away.
229 ignetmap = CtdlGetSysConfig(IGNETMAP);
230 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
231 extract_token(buf, ignetmap, i, '\n', sizeof buf);
232 extract_token(testnode, buf, 0, '|', sizeof testnode);
233 if (!strcasecmp(node, testnode)) {
240 /* If we get to this point it's an invalid node name */
255 "/citadel.control", "r");
257 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
261 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
267 * Back end for the MSGS command: output message number only.
269 void simple_listing(long msgnum, void *userdata)
271 cprintf("%ld\n", msgnum);
277 * Back end for the MSGS command: output header summary.
279 void headers_listing(long msgnum, void *userdata)
281 struct CtdlMessage *msg;
283 msg = CtdlFetchMessage(msgnum, 0);
285 cprintf("%ld|0|||||\n", msgnum);
289 cprintf("%ld|%s|%s|%s|%s|%s|\n",
291 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
292 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
293 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
294 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
295 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
297 CtdlFreeMessage(msg);
302 /* Determine if a given message matches the fields in a message template.
303 * Return 0 for a successful match.
305 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
308 /* If there aren't any fields in the template, all messages will
311 if (template == NULL) return(0);
313 /* Null messages are bogus. */
314 if (msg == NULL) return(1);
316 for (i='A'; i<='Z'; ++i) {
317 if (template->cm_fields[i] != NULL) {
318 if (msg->cm_fields[i] == NULL) {
321 if (strcasecmp(msg->cm_fields[i],
322 template->cm_fields[i])) return 1;
326 /* All compares succeeded: we have a match! */
333 * Retrieve the "seen" message list for the current room.
335 void CtdlGetSeen(char *buf, int which_set) {
338 /* Learn about the user and room in question */
339 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
341 if (which_set == ctdlsetseen_seen)
342 safestrncpy(buf, vbuf.v_seen, SIZ);
343 if (which_set == ctdlsetseen_answered)
344 safestrncpy(buf, vbuf.v_answered, SIZ);
350 * Manipulate the "seen msgs" string (or other message set strings)
352 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
353 int target_setting, int which_set,
354 struct ctdluser *which_user, struct ctdlroom *which_room) {
355 struct cdbdata *cdbfr;
367 char *is_set; /* actually an array of booleans */
370 char setstr[SIZ], lostr[SIZ], histr[SIZ];
373 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
374 num_target_msgnums, target_msgnums[0],
375 target_setting, which_set);
377 /* Learn about the user and room in question */
378 CtdlGetRelationship(&vbuf,
379 ((which_user != NULL) ? which_user : &CC->user),
380 ((which_room != NULL) ? which_room : &CC->room)
383 /* Load the message list */
384 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
386 msglist = (long *) cdbfr->ptr;
387 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
388 num_msgs = cdbfr->len / sizeof(long);
391 return; /* No messages at all? No further action. */
394 is_set = malloc(num_msgs * sizeof(char));
395 memset(is_set, 0, (num_msgs * sizeof(char)) );
397 /* Decide which message set we're manipulating */
399 case ctdlsetseen_seen:
400 safestrncpy(vset, vbuf.v_seen, sizeof vset);
402 case ctdlsetseen_answered:
403 safestrncpy(vset, vbuf.v_answered, sizeof vset);
407 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
409 /* Translate the existing sequence set into an array of booleans */
410 num_sets = num_tokens(vset, ',');
411 for (s=0; s<num_sets; ++s) {
412 extract_token(setstr, vset, s, ',', sizeof setstr);
414 extract_token(lostr, setstr, 0, ':', sizeof lostr);
415 if (num_tokens(setstr, ':') >= 2) {
416 extract_token(histr, setstr, 1, ':', sizeof histr);
417 if (!strcmp(histr, "*")) {
418 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
422 strcpy(histr, lostr);
427 for (i = 0; i < num_msgs; ++i) {
428 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
434 /* Now translate the array of booleans back into a sequence set */
439 for (i=0; i<num_msgs; ++i) {
441 is_seen = is_set[i]; /* Default to existing setting */
443 for (k=0; k<num_target_msgnums; ++k) {
444 if (msglist[i] == target_msgnums[k]) {
445 is_seen = target_setting;
450 if (lo < 0L) lo = msglist[i];
454 if ( ((is_seen == 0) && (was_seen == 1))
455 || ((is_seen == 1) && (i == num_msgs-1)) ) {
457 /* begin trim-o-matic code */
460 while ( (strlen(vset) + 20) > sizeof vset) {
461 remove_token(vset, 0, ',');
463 if (j--) break; /* loop no more than 9 times */
465 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
469 snprintf(lostr, sizeof lostr,
470 "1:%ld,%s", t, vset);
471 safestrncpy(vset, lostr, sizeof vset);
473 /* end trim-o-matic code */
481 snprintf(&vset[tmp], (sizeof vset) - tmp,
485 snprintf(&vset[tmp], (sizeof vset) - tmp,
494 /* Decide which message set we're manipulating */
496 case ctdlsetseen_seen:
497 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
499 case ctdlsetseen_answered:
500 safestrncpy(vbuf.v_answered, vset,
501 sizeof vbuf.v_answered);
506 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
508 CtdlSetRelationship(&vbuf,
509 ((which_user != NULL) ? which_user : &CC->user),
510 ((which_room != NULL) ? which_room : &CC->room)
516 * API function to perform an operation for each qualifying message in the
517 * current room. (Returns the number of messages processed.)
519 int CtdlForEachMessage(int mode, long ref,
521 struct CtdlMessage *compare,
522 void (*CallBack) (long, void *),
528 struct cdbdata *cdbfr;
529 long *msglist = NULL;
531 int num_processed = 0;
534 struct CtdlMessage *msg;
537 int printed_lastold = 0;
539 /* Learn about the user and room in question */
541 getuser(&CC->user, CC->curr_user);
542 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
544 /* Load the message list */
545 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
547 msglist = (long *) cdbfr->ptr;
548 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
549 num_msgs = cdbfr->len / sizeof(long);
552 return 0; /* No messages at all? No further action. */
557 * Now begin the traversal.
559 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
561 /* If the caller is looking for a specific MIME type, filter
562 * out all messages which are not of the type requested.
564 if (content_type != NULL) if (strlen(content_type) > 0) {
566 /* This call to GetMetaData() sits inside this loop
567 * so that we only do the extra database read per msg
568 * if we need to. Doing the extra read all the time
569 * really kills the server. If we ever need to use
570 * metadata for another search criterion, we need to
571 * move the read somewhere else -- but still be smart
572 * enough to only do the read if the caller has
573 * specified something that will need it.
575 GetMetaData(&smi, msglist[a]);
577 if (strcasecmp(smi.meta_content_type, content_type)) {
583 num_msgs = sort_msglist(msglist, num_msgs);
585 /* If a template was supplied, filter out the messages which
586 * don't match. (This could induce some delays!)
589 if (compare != NULL) {
590 for (a = 0; a < num_msgs; ++a) {
591 msg = CtdlFetchMessage(msglist[a], 1);
593 if (CtdlMsgCmp(msg, compare)) {
596 CtdlFreeMessage(msg);
604 * Now iterate through the message list, according to the
605 * criteria supplied by the caller.
608 for (a = 0; a < num_msgs; ++a) {
609 thismsg = msglist[a];
610 if (mode == MSGS_ALL) {
614 is_seen = is_msg_in_sequence_set(
615 vbuf.v_seen, thismsg);
616 if (is_seen) lastold = thismsg;
622 || ((mode == MSGS_OLD) && (is_seen))
623 || ((mode == MSGS_NEW) && (!is_seen))
624 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
625 || ((mode == MSGS_FIRST) && (a < ref))
626 || ((mode == MSGS_GT) && (thismsg > ref))
627 || ((mode == MSGS_EQ) && (thismsg == ref))
630 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
632 CallBack(lastold, userdata);
636 if (CallBack) CallBack(thismsg, userdata);
640 free(msglist); /* Clean up */
641 return num_processed;
647 * cmd_msgs() - get list of message #'s in this room
648 * implements the MSGS server command using CtdlForEachMessage()
650 void cmd_msgs(char *cmdbuf)
659 int with_template = 0;
660 struct CtdlMessage *template = NULL;
661 int with_headers = 0;
663 extract_token(which, cmdbuf, 0, '|', sizeof which);
664 cm_ref = extract_int(cmdbuf, 1);
665 with_template = extract_int(cmdbuf, 2);
666 with_headers = extract_int(cmdbuf, 3);
670 if (!strncasecmp(which, "OLD", 3))
672 else if (!strncasecmp(which, "NEW", 3))
674 else if (!strncasecmp(which, "FIRST", 5))
676 else if (!strncasecmp(which, "LAST", 4))
678 else if (!strncasecmp(which, "GT", 2))
681 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
682 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
688 cprintf("%d Send template then receive message list\n",
690 template = (struct CtdlMessage *)
691 malloc(sizeof(struct CtdlMessage));
692 memset(template, 0, sizeof(struct CtdlMessage));
693 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
694 extract_token(tfield, buf, 0, '|', sizeof tfield);
695 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
696 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
697 if (!strcasecmp(tfield, msgkeys[i])) {
698 template->cm_fields[i] =
706 cprintf("%d \n", LISTING_FOLLOWS);
709 CtdlForEachMessage(mode,
713 (with_headers ? headers_listing : simple_listing),
716 if (template != NULL) CtdlFreeMessage(template);
724 * help_subst() - support routine for help file viewer
726 void help_subst(char *strbuf, char *source, char *dest)
731 while (p = pattern2(strbuf, source), (p >= 0)) {
732 strcpy(workbuf, &strbuf[p + strlen(source)]);
733 strcpy(&strbuf[p], dest);
734 strcat(strbuf, workbuf);
739 void do_help_subst(char *buffer)
743 help_subst(buffer, "^nodename", config.c_nodename);
744 help_subst(buffer, "^humannode", config.c_humannode);
745 help_subst(buffer, "^fqdn", config.c_fqdn);
746 help_subst(buffer, "^username", CC->user.fullname);
747 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
748 help_subst(buffer, "^usernum", buf2);
749 help_subst(buffer, "^sysadm", config.c_sysadm);
750 help_subst(buffer, "^variantname", CITADEL);
751 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
752 help_subst(buffer, "^maxsessions", buf2);
753 help_subst(buffer, "^bbsdir", CTDLDIR);
759 * memfmout() - Citadel text formatter and paginator.
760 * Although the original purpose of this routine was to format
761 * text to the reader's screen width, all we're really using it
762 * for here is to format text out to 80 columns before sending it
763 * to the client. The client software may reformat it again.
766 int width, /* screen width to use */
767 char *mptr, /* where are we going to get our text from? */
768 char subst, /* nonzero if we should do substitutions */
769 char *nl) /* string to terminate lines with */
781 c = 1; /* c is the current pos */
785 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
787 buffer[strlen(buffer) + 1] = 0;
788 buffer[strlen(buffer)] = ch;
791 if (buffer[0] == '^')
792 do_help_subst(buffer);
794 buffer[strlen(buffer) + 1] = 0;
796 strcpy(buffer, &buffer[1]);
804 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
806 if (((old == 13) || (old == 10)) && (isspace(real))) {
814 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
815 cprintf("%s%s", nl, aaa);
824 if ((strlen(aaa) + c) > (width - 5)) {
833 if ((ch == 13) || (ch == 10)) {
834 cprintf("%s%s", aaa, nl);
841 cprintf("%s%s", aaa, nl);
847 * Callback function for mime parser that simply lists the part
849 void list_this_part(char *name, char *filename, char *partnum, char *disp,
850 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
855 ma = (struct ma_info *)cbuserdata;
856 if (ma->is_ma == 0) {
857 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
858 name, filename, partnum, disp, cbtype, (long)length);
863 * Callback function for multipart prefix
865 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
866 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
871 ma = (struct ma_info *)cbuserdata;
872 if (!strcasecmp(cbtype, "multipart/alternative")) {
876 if (ma->is_ma == 0) {
877 cprintf("pref=%s|%s\n", partnum, cbtype);
882 * Callback function for multipart sufffix
884 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
885 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
890 ma = (struct ma_info *)cbuserdata;
891 if (ma->is_ma == 0) {
892 cprintf("suff=%s|%s\n", partnum, cbtype);
894 if (!strcasecmp(cbtype, "multipart/alternative")) {
901 * Callback function for mime parser that opens a section for downloading
903 void mime_download(char *name, char *filename, char *partnum, char *disp,
904 void *content, char *cbtype, char *cbcharset, size_t length,
905 char *encoding, void *cbuserdata)
908 /* Silently go away if there's already a download open... */
909 if (CC->download_fp != NULL)
912 /* ...or if this is not the desired section */
913 if (strcasecmp(CC->download_desired_section, partnum))
916 CC->download_fp = tmpfile();
917 if (CC->download_fp == NULL)
920 fwrite(content, length, 1, CC->download_fp);
921 fflush(CC->download_fp);
922 rewind(CC->download_fp);
924 OpenCmdResult(filename, cbtype);
930 * Load a message from disk into memory.
931 * This is used by CtdlOutputMsg() and other fetch functions.
933 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
934 * using the CtdlMessageFree() function.
936 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
938 struct cdbdata *dmsgtext;
939 struct CtdlMessage *ret = NULL;
943 cit_uint8_t field_header;
945 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
947 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
948 if (dmsgtext == NULL) {
951 mptr = dmsgtext->ptr;
952 upper_bound = mptr + dmsgtext->len;
954 /* Parse the three bytes that begin EVERY message on disk.
955 * The first is always 0xFF, the on-disk magic number.
956 * The second is the anonymous/public type byte.
957 * The third is the format type byte (vari, fixed, or MIME).
962 "Message %ld appears to be corrupted.\n",
967 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
968 memset(ret, 0, sizeof(struct CtdlMessage));
970 ret->cm_magic = CTDLMESSAGE_MAGIC;
971 ret->cm_anon_type = *mptr++; /* Anon type byte */
972 ret->cm_format_type = *mptr++; /* Format type byte */
975 * The rest is zero or more arbitrary fields. Load them in.
976 * We're done when we encounter either a zero-length field or
977 * have just processed the 'M' (message text) field.
980 if (mptr >= upper_bound) {
983 field_header = *mptr++;
984 ret->cm_fields[field_header] = strdup(mptr);
986 while (*mptr++ != 0); /* advance to next field */
988 } while ((mptr < upper_bound) && (field_header != 'M'));
992 /* Always make sure there's something in the msg text field. If
993 * it's NULL, the message text is most likely stored separately,
994 * so go ahead and fetch that. Failing that, just set a dummy
995 * body so other code doesn't barf.
997 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
998 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
999 if (dmsgtext != NULL) {
1000 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1004 if (ret->cm_fields['M'] == NULL) {
1005 ret->cm_fields['M'] = strdup("<no text>\n");
1008 /* Perform "before read" hooks (aborting if any return nonzero) */
1009 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1010 CtdlFreeMessage(ret);
1019 * Returns 1 if the supplied pointer points to a valid Citadel message.
1020 * If the pointer is NULL or the magic number check fails, returns 0.
1022 int is_valid_message(struct CtdlMessage *msg) {
1025 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1026 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1034 * 'Destructor' for struct CtdlMessage
1036 void CtdlFreeMessage(struct CtdlMessage *msg)
1040 if (is_valid_message(msg) == 0) return;
1042 for (i = 0; i < 256; ++i)
1043 if (msg->cm_fields[i] != NULL) {
1044 free(msg->cm_fields[i]);
1047 msg->cm_magic = 0; /* just in case */
1053 * Pre callback function for multipart/alternative
1055 * NOTE: this differs from the standard behavior for a reason. Normally when
1056 * displaying multipart/alternative you want to show the _last_ usable
1057 * format in the message. Here we show the _first_ one, because it's
1058 * usually text/plain. Since this set of functions is designed for text
1059 * output to non-MIME-aware clients, this is the desired behavior.
1062 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1063 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1068 ma = (struct ma_info *)cbuserdata;
1069 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1070 if (!strcasecmp(cbtype, "multipart/alternative")) {
1078 * Post callback function for multipart/alternative
1080 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1081 void *content, char *cbtype, char *cbcharset, size_t length,
1082 char *encoding, void *cbuserdata)
1086 ma = (struct ma_info *)cbuserdata;
1087 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1088 if (!strcasecmp(cbtype, "multipart/alternative")) {
1096 * Inline callback function for mime parser that wants to display text
1098 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1099 void *content, char *cbtype, char *cbcharset, size_t length,
1100 char *encoding, void *cbuserdata)
1107 ma = (struct ma_info *)cbuserdata;
1110 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1111 partnum, filename, cbtype, (long)length);
1114 * If we're in the middle of a multipart/alternative scope and
1115 * we've already printed another section, skip this one.
1117 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1118 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1124 if ( (!strcasecmp(cbtype, "text/plain"))
1125 || (strlen(cbtype)==0) ) {
1128 client_write(wptr, length);
1129 if (wptr[length-1] != '\n') {
1134 else if (!strcasecmp(cbtype, "text/html")) {
1135 ptr = html_to_ascii(content, length, 80, 0);
1137 client_write(ptr, wlen);
1138 if (ptr[wlen-1] != '\n') {
1143 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1144 /* above function returns nonzero if it handled the part */
1146 else if (strncasecmp(cbtype, "multipart/", 10)) {
1147 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1148 partnum, filename, cbtype, (long)length);
1153 * The client is elegant and sophisticated and wants to be choosy about
1154 * MIME content types, so figure out which multipart/alternative part
1155 * we're going to send.
1157 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1158 void *content, char *cbtype, char *cbcharset, size_t length,
1159 char *encoding, void *cbuserdata)
1165 ma = (struct ma_info *)cbuserdata;
1167 if (ma->is_ma > 0) {
1168 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1169 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1170 if (!strcasecmp(buf, cbtype)) {
1171 strcpy(ma->chosen_part, partnum);
1178 * Now that we've chosen our preferred part, output it.
1180 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1181 void *content, char *cbtype, char *cbcharset, size_t length,
1182 char *encoding, void *cbuserdata)
1186 int add_newline = 0;
1190 ma = (struct ma_info *)cbuserdata;
1192 /* This is not the MIME part you're looking for... */
1193 if (strcasecmp(partnum, ma->chosen_part)) return;
1195 /* If the content-type of this part is in our preferred formats
1196 * list, we can simply output it verbatim.
1198 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1199 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1200 if (!strcasecmp(buf, cbtype)) {
1201 /* Yeah! Go! W00t!! */
1203 text_content = (char *)content;
1204 if (text_content[length-1] != '\n') {
1208 cprintf("Content-type: %s", cbtype);
1209 if (strlen(cbcharset) > 0) {
1210 cprintf("; charset=%s", cbcharset);
1212 cprintf("\nContent-length: %d\n",
1213 (int)(length + add_newline) );
1214 if (strlen(encoding) > 0) {
1215 cprintf("Content-transfer-encoding: %s\n", encoding);
1218 cprintf("Content-transfer-encoding: 7bit\n");
1221 client_write(content, length);
1222 if (add_newline) cprintf("\n");
1227 /* No translations required or possible: output as text/plain */
1228 cprintf("Content-type: text/plain\n\n");
1229 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1230 length, encoding, cbuserdata);
1235 * Get a message off disk. (returns om_* values found in msgbase.h)
1238 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1239 int mode, /* how would you like that message? */
1240 int headers_only, /* eschew the message body? */
1241 int do_proto, /* do Citadel protocol responses? */
1242 int crlf /* Use CRLF newlines instead of LF? */
1244 struct CtdlMessage *TheMessage = NULL;
1245 int retcode = om_no_such_msg;
1247 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1250 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1251 if (do_proto) cprintf("%d Not logged in.\n",
1252 ERROR + NOT_LOGGED_IN);
1253 return(om_not_logged_in);
1256 /* FIXME: check message id against msglist for this room */
1259 * Fetch the message from disk. If we're in any sort of headers
1260 * only mode, request that we don't even bother loading the body
1263 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1264 TheMessage = CtdlFetchMessage(msg_num, 0);
1267 TheMessage = CtdlFetchMessage(msg_num, 1);
1270 if (TheMessage == NULL) {
1271 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1272 ERROR + MESSAGE_NOT_FOUND, msg_num);
1273 return(om_no_such_msg);
1276 retcode = CtdlOutputPreLoadedMsg(
1278 headers_only, do_proto, crlf);
1280 CtdlFreeMessage(TheMessage);
1287 * Get a message off disk. (returns om_* values found in msgbase.h)
1290 int CtdlOutputPreLoadedMsg(
1291 struct CtdlMessage *TheMessage,
1292 int mode, /* how would you like that message? */
1293 int headers_only, /* eschew the message body? */
1294 int do_proto, /* do Citadel protocol responses? */
1295 int crlf /* Use CRLF newlines instead of LF? */
1301 char display_name[256];
1303 char *nl; /* newline string */
1305 int subject_found = 0;
1308 /* Buffers needed for RFC822 translation. These are all filled
1309 * using functions that are bounds-checked, and therefore we can
1310 * make them substantially smaller than SIZ.
1318 char datestamp[100];
1320 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1321 ((TheMessage == NULL) ? "NULL" : "not null"),
1322 mode, headers_only, do_proto, crlf);
1324 strcpy(mid, "unknown");
1325 nl = (crlf ? "\r\n" : "\n");
1327 if (!is_valid_message(TheMessage)) {
1329 "ERROR: invalid preloaded message for output\n");
1330 return(om_no_such_msg);
1333 /* Are we downloading a MIME component? */
1334 if (mode == MT_DOWNLOAD) {
1335 if (TheMessage->cm_format_type != FMT_RFC822) {
1337 cprintf("%d This is not a MIME message.\n",
1338 ERROR + ILLEGAL_VALUE);
1339 } else if (CC->download_fp != NULL) {
1340 if (do_proto) cprintf(
1341 "%d You already have a download open.\n",
1342 ERROR + RESOURCE_BUSY);
1344 /* Parse the message text component */
1345 mptr = TheMessage->cm_fields['M'];
1346 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1347 /* If there's no file open by this time, the requested
1348 * section wasn't found, so print an error
1350 if (CC->download_fp == NULL) {
1351 if (do_proto) cprintf(
1352 "%d Section %s not found.\n",
1353 ERROR + FILE_NOT_FOUND,
1354 CC->download_desired_section);
1357 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1360 /* now for the user-mode message reading loops */
1361 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1363 /* Does the caller want to skip the headers? */
1364 if (headers_only == HEADERS_NONE) goto START_TEXT;
1366 /* Tell the client which format type we're using. */
1367 if ( (mode == MT_CITADEL) && (do_proto) ) {
1368 cprintf("type=%d\n", TheMessage->cm_format_type);
1371 /* nhdr=yes means that we're only displaying headers, no body */
1372 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1373 && (mode == MT_CITADEL)
1376 cprintf("nhdr=yes\n");
1379 /* begin header processing loop for Citadel message format */
1381 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1383 safestrncpy(display_name, "<unknown>", sizeof display_name);
1384 if (TheMessage->cm_fields['A']) {
1385 strcpy(buf, TheMessage->cm_fields['A']);
1386 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1387 safestrncpy(display_name, "****", sizeof display_name);
1389 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1390 safestrncpy(display_name, "anonymous", sizeof display_name);
1393 safestrncpy(display_name, buf, sizeof display_name);
1395 if ((is_room_aide())
1396 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1397 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1398 size_t tmp = strlen(display_name);
1399 snprintf(&display_name[tmp],
1400 sizeof display_name - tmp,
1405 /* Don't show Internet address for users on the
1406 * local Citadel network.
1409 if (TheMessage->cm_fields['N'] != NULL)
1410 if (strlen(TheMessage->cm_fields['N']) > 0)
1411 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1415 /* Now spew the header fields in the order we like them. */
1416 safestrncpy(allkeys, FORDER, sizeof allkeys);
1417 for (i=0; i<strlen(allkeys); ++i) {
1418 k = (int) allkeys[i];
1420 if ( (TheMessage->cm_fields[k] != NULL)
1421 && (msgkeys[k] != NULL) ) {
1423 if (do_proto) cprintf("%s=%s\n",
1427 else if ((k == 'F') && (suppress_f)) {
1430 /* Masquerade display name if needed */
1432 if (do_proto) cprintf("%s=%s\n",
1434 TheMessage->cm_fields[k]
1443 /* begin header processing loop for RFC822 transfer format */
1448 strcpy(snode, NODENAME);
1449 strcpy(lnode, HUMANNODE);
1450 if (mode == MT_RFC822) {
1451 for (i = 0; i < 256; ++i) {
1452 if (TheMessage->cm_fields[i]) {
1453 mptr = TheMessage->cm_fields[i];
1456 safestrncpy(luser, mptr, sizeof luser);
1457 safestrncpy(suser, mptr, sizeof suser);
1459 else if (i == 'Y') {
1460 cprintf("CC: %s%s", mptr, nl);
1462 else if (i == 'U') {
1463 cprintf("Subject: %s%s", mptr, nl);
1467 safestrncpy(mid, mptr, sizeof mid);
1469 safestrncpy(lnode, mptr, sizeof lnode);
1471 safestrncpy(fuser, mptr, sizeof fuser);
1472 /* else if (i == 'O')
1473 cprintf("X-Citadel-Room: %s%s",
1476 safestrncpy(snode, mptr, sizeof snode);
1478 cprintf("To: %s%s", mptr, nl);
1479 else if (i == 'T') {
1480 datestring(datestamp, sizeof datestamp,
1481 atol(mptr), DATESTRING_RFC822);
1482 cprintf("Date: %s%s", datestamp, nl);
1486 if (subject_found == 0) {
1487 cprintf("Subject: (no subject)%s", nl);
1491 for (i=0; i<strlen(suser); ++i) {
1492 suser[i] = tolower(suser[i]);
1493 if (!isalnum(suser[i])) suser[i]='_';
1496 if (mode == MT_RFC822) {
1497 if (!strcasecmp(snode, NODENAME)) {
1498 safestrncpy(snode, FQDN, sizeof snode);
1501 /* Construct a fun message id */
1502 cprintf("Message-ID: <%s", mid);
1503 if (strchr(mid, '@')==NULL) {
1504 cprintf("@%s", snode);
1508 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1509 cprintf("From: \"----\" <x@x.org>%s", nl);
1511 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1512 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1514 else if (strlen(fuser) > 0) {
1515 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1518 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1521 cprintf("Organization: %s%s", lnode, nl);
1523 /* Blank line signifying RFC822 end-of-headers */
1524 if (TheMessage->cm_format_type != FMT_RFC822) {
1529 /* end header processing loop ... at this point, we're in the text */
1531 if (headers_only == HEADERS_FAST) goto DONE;
1532 mptr = TheMessage->cm_fields['M'];
1534 /* Tell the client about the MIME parts in this message */
1535 if (TheMessage->cm_format_type == FMT_RFC822) {
1536 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1537 memset(&ma, 0, sizeof(struct ma_info));
1538 mime_parser(mptr, NULL,
1539 (do_proto ? *list_this_part : NULL),
1540 (do_proto ? *list_this_pref : NULL),
1541 (do_proto ? *list_this_suff : NULL),
1544 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1545 char *start_of_text = NULL;
1546 start_of_text = strstr(mptr, "\n\r\n");
1547 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1548 if (start_of_text == NULL) start_of_text = mptr;
1550 start_of_text = strstr(start_of_text, "\n");
1552 while (ch=*mptr, ch!=0) {
1556 else switch(headers_only) {
1558 if (mptr >= start_of_text) {
1559 if (ch == 10) cprintf("%s", nl);
1560 else cprintf("%c", ch);
1564 if (mptr < start_of_text) {
1565 if (ch == 10) cprintf("%s", nl);
1566 else cprintf("%c", ch);
1570 if (ch == 10) cprintf("%s", nl);
1571 else cprintf("%c", ch);
1580 if (headers_only == HEADERS_ONLY) {
1584 /* signify start of msg text */
1585 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1586 if (do_proto) cprintf("text\n");
1589 /* If the format type on disk is 1 (fixed-format), then we want
1590 * everything to be output completely literally ... regardless of
1591 * what message transfer format is in use.
1593 if (TheMessage->cm_format_type == FMT_FIXED) {
1594 if (mode == MT_MIME) {
1595 cprintf("Content-type: text/plain\n\n");
1598 while (ch = *mptr++, ch > 0) {
1601 if ((ch == 10) || (strlen(buf) > 250)) {
1602 cprintf("%s%s", buf, nl);
1605 buf[strlen(buf) + 1] = 0;
1606 buf[strlen(buf)] = ch;
1609 if (strlen(buf) > 0)
1610 cprintf("%s%s", buf, nl);
1613 /* If the message on disk is format 0 (Citadel vari-format), we
1614 * output using the formatter at 80 columns. This is the final output
1615 * form if the transfer format is RFC822, but if the transfer format
1616 * is Citadel proprietary, it'll still work, because the indentation
1617 * for new paragraphs is correct and the client will reformat the
1618 * message to the reader's screen width.
1620 if (TheMessage->cm_format_type == FMT_CITADEL) {
1621 if (mode == MT_MIME) {
1622 cprintf("Content-type: text/x-citadel-variformat\n\n");
1624 memfmout(80, mptr, 0, nl);
1627 /* If the message on disk is format 4 (MIME), we've gotta hand it
1628 * off to the MIME parser. The client has already been told that
1629 * this message is format 1 (fixed format), so the callback function
1630 * we use will display those parts as-is.
1632 if (TheMessage->cm_format_type == FMT_RFC822) {
1633 memset(&ma, 0, sizeof(struct ma_info));
1635 if (mode == MT_MIME) {
1636 strcpy(ma.chosen_part, "1");
1637 mime_parser(mptr, NULL,
1638 *choose_preferred, *fixed_output_pre,
1639 *fixed_output_post, (void *)&ma, 0);
1640 mime_parser(mptr, NULL,
1641 *output_preferred, NULL, NULL, (void *)&ma, 0);
1644 mime_parser(mptr, NULL,
1645 *fixed_output, *fixed_output_pre,
1646 *fixed_output_post, (void *)&ma, 0);
1651 DONE: /* now we're done */
1652 if (do_proto) cprintf("000\n");
1659 * display a message (mode 0 - Citadel proprietary)
1661 void cmd_msg0(char *cmdbuf)
1664 int headers_only = HEADERS_ALL;
1666 msgid = extract_long(cmdbuf, 0);
1667 headers_only = extract_int(cmdbuf, 1);
1669 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1675 * display a message (mode 2 - RFC822)
1677 void cmd_msg2(char *cmdbuf)
1680 int headers_only = HEADERS_ALL;
1682 msgid = extract_long(cmdbuf, 0);
1683 headers_only = extract_int(cmdbuf, 1);
1685 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1691 * display a message (mode 3 - IGnet raw format - internal programs only)
1693 void cmd_msg3(char *cmdbuf)
1696 struct CtdlMessage *msg;
1699 if (CC->internal_pgm == 0) {
1700 cprintf("%d This command is for internal programs only.\n",
1701 ERROR + HIGHER_ACCESS_REQUIRED);
1705 msgnum = extract_long(cmdbuf, 0);
1706 msg = CtdlFetchMessage(msgnum, 1);
1708 cprintf("%d Message %ld not found.\n",
1709 ERROR + MESSAGE_NOT_FOUND, msgnum);
1713 serialize_message(&smr, msg);
1714 CtdlFreeMessage(msg);
1717 cprintf("%d Unable to serialize message\n",
1718 ERROR + INTERNAL_ERROR);
1722 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1723 client_write((char *)smr.ser, (int)smr.len);
1730 * Display a message using MIME content types
1732 void cmd_msg4(char *cmdbuf)
1736 msgid = extract_long(cmdbuf, 0);
1737 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1743 * Client tells us its preferred message format(s)
1745 void cmd_msgp(char *cmdbuf)
1747 safestrncpy(CC->preferred_formats, cmdbuf,
1748 sizeof(CC->preferred_formats));
1749 cprintf("%d ok\n", CIT_OK);
1754 * Open a component of a MIME message as a download file
1756 void cmd_opna(char *cmdbuf)
1759 char desired_section[128];
1761 msgid = extract_long(cmdbuf, 0);
1762 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1763 safestrncpy(CC->download_desired_section, desired_section,
1764 sizeof CC->download_desired_section);
1765 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1770 * Save a message pointer into a specified room
1771 * (Returns 0 for success, nonzero for failure)
1772 * roomname may be NULL to use the current room
1774 * Note that the 'supplied_msg' field may be set to NULL, in which case
1775 * the message will be fetched from disk, by number, if we need to perform
1776 * replication checks. This adds an additional database read, so if the
1777 * caller already has the message in memory then it should be supplied.
1779 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1780 struct CtdlMessage *supplied_msg) {
1782 char hold_rm[ROOMNAMELEN];
1783 struct cdbdata *cdbfr;
1786 long highest_msg = 0L;
1787 struct CtdlMessage *msg = NULL;
1789 /*lprintf(CTDL_DEBUG,
1790 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1791 roomname, msgid, do_repl_check);*/
1793 strcpy(hold_rm, CC->room.QRname);
1795 /* Now the regular stuff */
1796 if (lgetroom(&CC->room,
1797 ((roomname != NULL) ? roomname : CC->room.QRname) )
1799 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1800 return(ERROR + ROOM_NOT_FOUND);
1803 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1804 if (cdbfr == NULL) {
1808 msglist = (long *) cdbfr->ptr;
1809 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1810 num_msgs = cdbfr->len / sizeof(long);
1814 /* Make sure the message doesn't already exist in this room. It
1815 * is absolutely taboo to have more than one reference to the same
1816 * message in a room.
1818 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1819 if (msglist[i] == msgid) {
1820 lputroom(&CC->room); /* unlock the room */
1821 getroom(&CC->room, hold_rm);
1823 return(ERROR + ALREADY_EXISTS);
1827 /* Now add the new message */
1829 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1831 if (msglist == NULL) {
1832 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1834 msglist[num_msgs - 1] = msgid;
1836 /* Sort the message list, so all the msgid's are in order */
1837 num_msgs = sort_msglist(msglist, num_msgs);
1839 /* Determine the highest message number */
1840 highest_msg = msglist[num_msgs - 1];
1842 /* Write it back to disk. */
1843 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1844 msglist, (int)(num_msgs * sizeof(long)));
1846 /* Free up the memory we used. */
1849 /* Update the highest-message pointer and unlock the room. */
1850 CC->room.QRhighest = highest_msg;
1851 lputroom(&CC->room);
1853 /* Perform replication checks if necessary */
1854 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1855 if (supplied_msg != NULL) {
1859 msg = CtdlFetchMessage(msgid, 0);
1863 ReplicationChecks(msg);
1868 /* If the message has an Exclusive ID, index that... */
1870 if (msg->cm_fields['E'] != NULL) {
1871 index_message_by_euid(msg->cm_fields['E'],
1876 /* Free up the memory we may have allocated */
1877 if ( (msg != NULL) && (msg != supplied_msg) ) {
1878 CtdlFreeMessage(msg);
1881 /* Go back to the room we were in before we wandered here... */
1882 getroom(&CC->room, hold_rm);
1884 /* Bump the reference count for this message. */
1885 AdjRefCount(msgid, +1);
1887 /* Return success. */
1894 * Message base operation to save a new message to the message store
1895 * (returns new message number)
1897 * This is the back end for CtdlSubmitMsg() and should not be directly
1898 * called by server-side modules.
1901 long send_message(struct CtdlMessage *msg) {
1909 /* Get a new message number */
1910 newmsgid = get_new_message_number();
1911 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1913 /* Generate an ID if we don't have one already */
1914 if (msg->cm_fields['I']==NULL) {
1915 msg->cm_fields['I'] = strdup(msgidbuf);
1918 /* If the message is big, set its body aside for storage elsewhere */
1919 if (msg->cm_fields['M'] != NULL) {
1920 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1922 holdM = msg->cm_fields['M'];
1923 msg->cm_fields['M'] = NULL;
1927 /* Serialize our data structure for storage in the database */
1928 serialize_message(&smr, msg);
1931 msg->cm_fields['M'] = holdM;
1935 cprintf("%d Unable to serialize message\n",
1936 ERROR + INTERNAL_ERROR);
1940 /* Write our little bundle of joy into the message base */
1941 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1942 smr.ser, smr.len) < 0) {
1943 lprintf(CTDL_ERR, "Can't store message\n");
1947 cdb_store(CDB_BIGMSGS,
1957 /* Free the memory we used for the serialized message */
1960 /* Return the *local* message ID to the caller
1961 * (even if we're storing an incoming network message)
1969 * Serialize a struct CtdlMessage into the format used on disk and network.
1971 * This function loads up a "struct ser_ret" (defined in server.h) which
1972 * contains the length of the serialized message and a pointer to the
1973 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1975 void serialize_message(struct ser_ret *ret, /* return values */
1976 struct CtdlMessage *msg) /* unserialized msg */
1980 static char *forder = FORDER;
1982 if (is_valid_message(msg) == 0) return; /* self check */
1985 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1986 ret->len = ret->len +
1987 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1989 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1990 ret->ser = malloc(ret->len);
1991 if (ret->ser == NULL) {
1997 ret->ser[1] = msg->cm_anon_type;
1998 ret->ser[2] = msg->cm_format_type;
2001 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2002 ret->ser[wlen++] = (char)forder[i];
2003 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2004 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2006 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2007 (long)ret->len, (long)wlen);
2015 * Check to see if any messages already exist in the current room which
2016 * carry the same Exclusive ID as this one. If any are found, delete them.
2018 void ReplicationChecks(struct CtdlMessage *msg) {
2019 long old_msgnum = (-1L);
2021 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2023 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2026 /* No exclusive id? Don't do anything. */
2027 if (msg == NULL) return;
2028 if (msg->cm_fields['E'] == NULL) return;
2029 if (strlen(msg->cm_fields['E']) == 0) return;
2030 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2031 msg->cm_fields['E'], CC->room.QRname);*/
2033 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2034 if (old_msgnum > 0L) {
2035 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2036 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2043 * Save a message to disk and submit it into the delivery system.
2045 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2046 struct recptypes *recps, /* recipients (if mail) */
2047 char *force /* force a particular room? */
2049 char submit_filename[128];
2050 char generated_timestamp[32];
2051 char hold_rm[ROOMNAMELEN];
2052 char actual_rm[ROOMNAMELEN];
2053 char force_room[ROOMNAMELEN];
2054 char content_type[SIZ]; /* We have to learn this */
2055 char recipient[SIZ];
2058 struct ctdluser userbuf;
2060 struct MetaData smi;
2061 FILE *network_fp = NULL;
2062 static int seqnum = 1;
2063 struct CtdlMessage *imsg = NULL;
2066 char *hold_R, *hold_D;
2067 char *collected_addresses = NULL;
2068 struct addresses_to_be_filed *aptr = NULL;
2070 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2071 if (is_valid_message(msg) == 0) return(-1); /* self check */
2073 /* If this message has no timestamp, we take the liberty of
2074 * giving it one, right now.
2076 if (msg->cm_fields['T'] == NULL) {
2077 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2078 msg->cm_fields['T'] = strdup(generated_timestamp);
2081 /* If this message has no path, we generate one.
2083 if (msg->cm_fields['P'] == NULL) {
2084 if (msg->cm_fields['A'] != NULL) {
2085 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2086 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2087 if (isspace(msg->cm_fields['P'][a])) {
2088 msg->cm_fields['P'][a] = ' ';
2093 msg->cm_fields['P'] = strdup("unknown");
2097 if (force == NULL) {
2098 strcpy(force_room, "");
2101 strcpy(force_room, force);
2104 /* Learn about what's inside, because it's what's inside that counts */
2105 if (msg->cm_fields['M'] == NULL) {
2106 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2110 switch (msg->cm_format_type) {
2112 strcpy(content_type, "text/x-citadel-variformat");
2115 strcpy(content_type, "text/plain");
2118 strcpy(content_type, "text/plain");
2119 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2121 safestrncpy(content_type, &mptr[14],
2122 sizeof content_type);
2123 for (a = 0; a < strlen(content_type); ++a) {
2124 if ((content_type[a] == ';')
2125 || (content_type[a] == ' ')
2126 || (content_type[a] == 13)
2127 || (content_type[a] == 10)) {
2128 content_type[a] = 0;
2134 /* Goto the correct room */
2135 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2136 strcpy(hold_rm, CC->room.QRname);
2137 strcpy(actual_rm, CC->room.QRname);
2138 if (recps != NULL) {
2139 strcpy(actual_rm, SENTITEMS);
2142 /* If the user is a twit, move to the twit room for posting */
2144 if (CC->user.axlevel == 2) {
2145 strcpy(hold_rm, actual_rm);
2146 strcpy(actual_rm, config.c_twitroom);
2147 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2151 /* ...or if this message is destined for Aide> then go there. */
2152 if (strlen(force_room) > 0) {
2153 strcpy(actual_rm, force_room);
2156 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2157 if (strcasecmp(actual_rm, CC->room.QRname)) {
2158 /* getroom(&CC->room, actual_rm); */
2159 usergoto(actual_rm, 0, 1, NULL, NULL);
2163 * If this message has no O (room) field, generate one.
2165 if (msg->cm_fields['O'] == NULL) {
2166 msg->cm_fields['O'] = strdup(CC->room.QRname);
2169 /* Perform "before save" hooks (aborting if any return nonzero) */
2170 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2171 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2174 * If this message has an Exclusive ID, and the room is replication
2175 * checking enabled, then do replication checks.
2177 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2178 ReplicationChecks(msg);
2181 /* Save it to disk */
2182 lprintf(CTDL_DEBUG, "Saving to disk\n");
2183 newmsgid = send_message(msg);
2184 if (newmsgid <= 0L) return(-5);
2186 /* Write a supplemental message info record. This doesn't have to
2187 * be a critical section because nobody else knows about this message
2190 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2191 memset(&smi, 0, sizeof(struct MetaData));
2192 smi.meta_msgnum = newmsgid;
2193 smi.meta_refcount = 0;
2194 safestrncpy(smi.meta_content_type, content_type,
2195 sizeof smi.meta_content_type);
2197 /* As part of the new metadata record, measure how
2198 * big this message will be when displayed as RFC822.
2199 * Both POP and IMAP use this, and it's best to just take the hit now
2200 * instead of having to potentially measure thousands of messages when
2201 * a mailbox is opened later.
2204 if (CC->redirect_buffer != NULL) {
2205 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2208 CC->redirect_buffer = malloc(SIZ);
2209 CC->redirect_len = 0;
2210 CC->redirect_alloc = SIZ;
2211 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2212 smi.meta_rfc822_length = CC->redirect_len;
2213 free(CC->redirect_buffer);
2214 CC->redirect_buffer = NULL;
2215 CC->redirect_len = 0;
2216 CC->redirect_alloc = 0;
2220 /* Now figure out where to store the pointers */
2221 lprintf(CTDL_DEBUG, "Storing pointers\n");
2223 /* If this is being done by the networker delivering a private
2224 * message, we want to BYPASS saving the sender's copy (because there
2225 * is no local sender; it would otherwise go to the Trashcan).
2227 if ((!CC->internal_pgm) || (recps == NULL)) {
2228 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2229 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2230 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2234 /* For internet mail, drop a copy in the outbound queue room */
2236 if (recps->num_internet > 0) {
2237 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2240 /* If other rooms are specified, drop them there too. */
2242 if (recps->num_room > 0)
2243 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2244 extract_token(recipient, recps->recp_room, i,
2245 '|', sizeof recipient);
2246 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2247 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2250 /* Bump this user's messages posted counter. */
2251 lprintf(CTDL_DEBUG, "Updating user\n");
2252 lgetuser(&CC->user, CC->curr_user);
2253 CC->user.posted = CC->user.posted + 1;
2254 lputuser(&CC->user);
2256 /* If this is private, local mail, make a copy in the
2257 * recipient's mailbox and bump the reference count.
2260 if (recps->num_local > 0)
2261 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2262 extract_token(recipient, recps->recp_local, i,
2263 '|', sizeof recipient);
2264 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2266 if (getuser(&userbuf, recipient) == 0) {
2267 MailboxName(actual_rm, sizeof actual_rm,
2268 &userbuf, MAILROOM);
2269 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2270 BumpNewMailCounter(userbuf.usernum);
2273 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2274 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2279 /* Perform "after save" hooks */
2280 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2281 PerformMessageHooks(msg, EVT_AFTERSAVE);
2283 /* For IGnet mail, we have to save a new copy into the spooler for
2284 * each recipient, with the R and D fields set to the recipient and
2285 * destination-node. This has two ugly side effects: all other
2286 * recipients end up being unlisted in this recipient's copy of the
2287 * message, and it has to deliver multiple messages to the same
2288 * node. We'll revisit this again in a year or so when everyone has
2289 * a network spool receiver that can handle the new style messages.
2292 if (recps->num_ignet > 0)
2293 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2294 extract_token(recipient, recps->recp_ignet, i,
2295 '|', sizeof recipient);
2297 hold_R = msg->cm_fields['R'];
2298 hold_D = msg->cm_fields['D'];
2299 msg->cm_fields['R'] = malloc(SIZ);
2300 msg->cm_fields['D'] = malloc(128);
2301 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2302 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2304 serialize_message(&smr, msg);
2306 snprintf(submit_filename, sizeof submit_filename,
2307 #ifndef HAVE_SPOOL_DIR
2312 "/network/spoolin/netmail.%04lx.%04x.%04x",
2313 (long) getpid(), CC->cs_pid, ++seqnum);
2314 network_fp = fopen(submit_filename, "wb+");
2315 if (network_fp != NULL) {
2316 fwrite(smr.ser, smr.len, 1, network_fp);
2322 free(msg->cm_fields['R']);
2323 free(msg->cm_fields['D']);
2324 msg->cm_fields['R'] = hold_R;
2325 msg->cm_fields['D'] = hold_D;
2328 /* Go back to the room we started from */
2329 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2330 if (strcasecmp(hold_rm, CC->room.QRname))
2331 /* getroom(&CC->room, hold_rm); */
2332 usergoto(hold_rm, 0, 1, NULL, NULL);
2334 /* For internet mail, generate delivery instructions.
2335 * Yes, this is recursive. Deal with it. Infinite recursion does
2336 * not happen because the delivery instructions message does not
2337 * contain a recipient.
2340 if (recps->num_internet > 0) {
2341 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2342 instr = malloc(SIZ * 2);
2343 snprintf(instr, SIZ * 2,
2344 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2346 SPOOLMIME, newmsgid, (long)time(NULL),
2347 msg->cm_fields['A'], msg->cm_fields['N']
2350 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2351 size_t tmp = strlen(instr);
2352 extract_token(recipient, recps->recp_internet,
2353 i, '|', sizeof recipient);
2354 snprintf(&instr[tmp], SIZ * 2 - tmp,
2355 "remote|%s|0||\n", recipient);
2358 imsg = malloc(sizeof(struct CtdlMessage));
2359 memset(imsg, 0, sizeof(struct CtdlMessage));
2360 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2361 imsg->cm_anon_type = MES_NORMAL;
2362 imsg->cm_format_type = FMT_RFC822;
2363 imsg->cm_fields['A'] = strdup("Citadel");
2364 imsg->cm_fields['M'] = instr;
2365 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2366 CtdlFreeMessage(imsg);
2370 * Any addresses to harvest for someone's address book?
2372 if ( (CC->logged_in) && (recps != NULL) ) {
2373 collected_addresses = harvest_collected_addresses(msg);
2376 if (collected_addresses != NULL) {
2377 begin_critical_section(S_ATBF);
2378 aptr = (struct addresses_to_be_filed *)
2379 malloc(sizeof(struct addresses_to_be_filed));
2381 MailboxName(actual_rm, sizeof actual_rm,
2382 &CC->user, USERCONTACTSROOM);
2383 aptr->roomname = strdup(actual_rm);
2384 aptr->collected_addresses = collected_addresses;
2386 end_critical_section(S_ATBF);
2399 * Convenience function for generating small administrative messages.
2401 void quickie_message(char *from, char *to, char *room, char *text,
2402 int format_type, char *subject)
2404 struct CtdlMessage *msg;
2405 struct recptypes *recp = NULL;
2407 msg = malloc(sizeof(struct CtdlMessage));
2408 memset(msg, 0, sizeof(struct CtdlMessage));
2409 msg->cm_magic = CTDLMESSAGE_MAGIC;
2410 msg->cm_anon_type = MES_NORMAL;
2411 msg->cm_format_type = format_type;
2412 msg->cm_fields['A'] = strdup(from);
2413 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2414 msg->cm_fields['N'] = strdup(NODENAME);
2416 msg->cm_fields['R'] = strdup(to);
2417 recp = validate_recipients(to);
2419 if (subject != NULL) {
2420 msg->cm_fields['U'] = strdup(subject);
2422 msg->cm_fields['M'] = strdup(text);
2424 CtdlSubmitMsg(msg, recp, room);
2425 CtdlFreeMessage(msg);
2426 if (recp != NULL) free(recp);
2432 * Back end function used by CtdlMakeMessage() and similar functions
2434 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2435 size_t maxlen, /* maximum message length */
2436 char *exist, /* if non-null, append to it;
2437 exist is ALWAYS freed */
2438 int crlf /* CRLF newlines instead of LF */
2442 size_t message_len = 0;
2443 size_t buffer_len = 0;
2449 if (exist == NULL) {
2456 message_len = strlen(exist);
2457 buffer_len = message_len + 4096;
2458 m = realloc(exist, buffer_len);
2465 /* flush the input if we have nowhere to store it */
2470 /* read in the lines of message text one by one */
2472 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2473 if (!strcmp(buf, terminator)) finished = 1;
2475 strcat(buf, "\r\n");
2481 if ( (!flushing) && (!finished) ) {
2482 /* Measure the line */
2483 linelen = strlen(buf);
2485 /* augment the buffer if we have to */
2486 if ((message_len + linelen) >= buffer_len) {
2487 ptr = realloc(m, (buffer_len * 2) );
2488 if (ptr == NULL) { /* flush if can't allocate */
2491 buffer_len = (buffer_len * 2);
2493 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2497 /* Add the new line to the buffer. NOTE: this loop must avoid
2498 * using functions like strcat() and strlen() because they
2499 * traverse the entire buffer upon every call, and doing that
2500 * for a multi-megabyte message slows it down beyond usability.
2502 strcpy(&m[message_len], buf);
2503 message_len += linelen;
2506 /* if we've hit the max msg length, flush the rest */
2507 if (message_len >= maxlen) flushing = 1;
2509 } while (!finished);
2517 * Build a binary message to be saved on disk.
2518 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2519 * will become part of the message. This means you are no longer
2520 * responsible for managing that memory -- it will be freed along with
2521 * the rest of the fields when CtdlFreeMessage() is called.)
2524 struct CtdlMessage *CtdlMakeMessage(
2525 struct ctdluser *author, /* author's user structure */
2526 char *recipient, /* NULL if it's not mail */
2527 char *recp_cc, /* NULL if it's not mail */
2528 char *room, /* room where it's going */
2529 int type, /* see MES_ types in header file */
2530 int format_type, /* variformat, plain text, MIME... */
2531 char *fake_name, /* who we're masquerading as */
2532 char *subject, /* Subject (optional) */
2533 char *preformatted_text /* ...or NULL to read text from client */
2535 char dest_node[SIZ];
2537 struct CtdlMessage *msg;
2539 msg = malloc(sizeof(struct CtdlMessage));
2540 memset(msg, 0, sizeof(struct CtdlMessage));
2541 msg->cm_magic = CTDLMESSAGE_MAGIC;
2542 msg->cm_anon_type = type;
2543 msg->cm_format_type = format_type;
2545 /* Don't confuse the poor folks if it's not routed mail. */
2546 strcpy(dest_node, "");
2551 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2552 msg->cm_fields['P'] = strdup(buf);
2554 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2555 msg->cm_fields['T'] = strdup(buf);
2557 if (fake_name[0]) /* author */
2558 msg->cm_fields['A'] = strdup(fake_name);
2560 msg->cm_fields['A'] = strdup(author->fullname);
2562 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2563 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2566 msg->cm_fields['O'] = strdup(CC->room.QRname);
2569 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2570 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2572 if (recipient[0] != 0) {
2573 msg->cm_fields['R'] = strdup(recipient);
2575 if (recp_cc[0] != 0) {
2576 msg->cm_fields['Y'] = strdup(recp_cc);
2578 if (dest_node[0] != 0) {
2579 msg->cm_fields['D'] = strdup(dest_node);
2582 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2583 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2586 if (subject != NULL) {
2588 if (strlen(subject) > 0) {
2589 msg->cm_fields['U'] = strdup(subject);
2593 if (preformatted_text != NULL) {
2594 msg->cm_fields['M'] = preformatted_text;
2597 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2598 config.c_maxmsglen, NULL, 0);
2606 * Check to see whether we have permission to post a message in the current
2607 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2608 * returns 0 on success.
2610 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2612 if (!(CC->logged_in)) {
2613 snprintf(errmsgbuf, n, "Not logged in.");
2614 return (ERROR + NOT_LOGGED_IN);
2617 if ((CC->user.axlevel < 2)
2618 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2619 snprintf(errmsgbuf, n, "Need to be validated to enter "
2620 "(except in %s> to sysop)", MAILROOM);
2621 return (ERROR + HIGHER_ACCESS_REQUIRED);
2624 if ((CC->user.axlevel < 4)
2625 && (CC->room.QRflags & QR_NETWORK)) {
2626 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2627 return (ERROR + HIGHER_ACCESS_REQUIRED);
2630 if ((CC->user.axlevel < 6)
2631 && (CC->room.QRflags & QR_READONLY)) {
2632 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2633 return (ERROR + HIGHER_ACCESS_REQUIRED);
2636 strcpy(errmsgbuf, "Ok");
2642 * Check to see if the specified user has Internet mail permission
2643 * (returns nonzero if permission is granted)
2645 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2647 /* Do not allow twits to send Internet mail */
2648 if (who->axlevel <= 2) return(0);
2650 /* Globally enabled? */
2651 if (config.c_restrict == 0) return(1);
2653 /* User flagged ok? */
2654 if (who->flags & US_INTERNET) return(2);
2656 /* Aide level access? */
2657 if (who->axlevel >= 6) return(3);
2659 /* No mail for you! */
2665 * Validate recipients, count delivery types and errors, and handle aliasing
2666 * FIXME check for dupes!!!!!
2667 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2668 * or the number of addresses found invalid.
2670 struct recptypes *validate_recipients(char *supplied_recipients) {
2671 struct recptypes *ret;
2672 char recipients[SIZ];
2673 char this_recp[256];
2674 char this_recp_cooked[256];
2680 struct ctdluser tempUS;
2681 struct ctdlroom tempQR;
2685 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2686 if (ret == NULL) return(NULL);
2687 memset(ret, 0, sizeof(struct recptypes));
2690 ret->num_internet = 0;
2695 if (supplied_recipients == NULL) {
2696 strcpy(recipients, "");
2699 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2702 /* Change all valid separator characters to commas */
2703 for (i=0; i<strlen(recipients); ++i) {
2704 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2705 recipients[i] = ',';
2709 /* Now start extracting recipients... */
2711 while (strlen(recipients) > 0) {
2713 for (i=0; i<=strlen(recipients); ++i) {
2714 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2715 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2716 safestrncpy(this_recp, recipients, i+1);
2718 if (recipients[i] == ',') {
2719 strcpy(recipients, &recipients[i+1]);
2722 strcpy(recipients, "");
2729 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2731 mailtype = alias(this_recp);
2732 mailtype = alias(this_recp);
2733 mailtype = alias(this_recp);
2734 for (j=0; j<=strlen(this_recp); ++j) {
2735 if (this_recp[j]=='_') {
2736 this_recp_cooked[j] = ' ';
2739 this_recp_cooked[j] = this_recp[j];
2745 if (!strcasecmp(this_recp, "sysop")) {
2747 strcpy(this_recp, config.c_aideroom);
2748 if (strlen(ret->recp_room) > 0) {
2749 strcat(ret->recp_room, "|");
2751 strcat(ret->recp_room, this_recp);
2753 else if (getuser(&tempUS, this_recp) == 0) {
2755 strcpy(this_recp, tempUS.fullname);
2756 if (strlen(ret->recp_local) > 0) {
2757 strcat(ret->recp_local, "|");
2759 strcat(ret->recp_local, this_recp);
2761 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2763 strcpy(this_recp, tempUS.fullname);
2764 if (strlen(ret->recp_local) > 0) {
2765 strcat(ret->recp_local, "|");
2767 strcat(ret->recp_local, this_recp);
2769 else if ( (!strncasecmp(this_recp, "room_", 5))
2770 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2772 if (strlen(ret->recp_room) > 0) {
2773 strcat(ret->recp_room, "|");
2775 strcat(ret->recp_room, &this_recp_cooked[5]);
2783 /* Yes, you're reading this correctly: if the target
2784 * domain points back to the local system or an attached
2785 * Citadel directory, the address is invalid. That's
2786 * because if the address were valid, we would have
2787 * already translated it to a local address by now.
2789 if (IsDirectory(this_recp)) {
2794 ++ret->num_internet;
2795 if (strlen(ret->recp_internet) > 0) {
2796 strcat(ret->recp_internet, "|");
2798 strcat(ret->recp_internet, this_recp);
2803 if (strlen(ret->recp_ignet) > 0) {
2804 strcat(ret->recp_ignet, "|");
2806 strcat(ret->recp_ignet, this_recp);
2814 if (strlen(ret->errormsg) == 0) {
2815 snprintf(append, sizeof append,
2816 "Invalid recipient: %s",
2820 snprintf(append, sizeof append,
2823 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2824 strcat(ret->errormsg, append);
2828 if (strlen(ret->display_recp) == 0) {
2829 strcpy(append, this_recp);
2832 snprintf(append, sizeof append, ", %s",
2835 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2836 strcat(ret->display_recp, append);
2841 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2842 ret->num_room + ret->num_error) == 0) {
2843 ret->num_error = (-1);
2844 strcpy(ret->errormsg, "No recipients specified.");
2847 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2848 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2849 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2850 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2851 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2852 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2860 * message entry - mode 0 (normal)
2862 void cmd_ent0(char *entargs)
2868 char masquerade_as[SIZ];
2870 int format_type = 0;
2871 char newusername[SIZ];
2872 struct CtdlMessage *msg;
2876 struct recptypes *valid = NULL;
2877 struct recptypes *valid_to = NULL;
2878 struct recptypes *valid_cc = NULL;
2879 struct recptypes *valid_bcc = NULL;
2886 post = extract_int(entargs, 0);
2887 extract_token(recp, entargs, 1, '|', sizeof recp);
2888 anon_flag = extract_int(entargs, 2);
2889 format_type = extract_int(entargs, 3);
2890 extract_token(subject, entargs, 4, '|', sizeof subject);
2891 do_confirm = extract_int(entargs, 6);
2892 extract_token(cc, entargs, 7, '|', sizeof cc);
2893 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2895 /* first check to make sure the request is valid. */
2897 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2899 cprintf("%d %s\n", err, errmsg);
2903 /* Check some other permission type things. */
2906 if (CC->user.axlevel < 6) {
2907 cprintf("%d You don't have permission to masquerade.\n",
2908 ERROR + HIGHER_ACCESS_REQUIRED);
2911 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2912 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2913 safestrncpy(CC->fake_postname, newusername,
2914 sizeof(CC->fake_postname) );
2915 cprintf("%d ok\n", CIT_OK);
2918 CC->cs_flags |= CS_POSTING;
2920 /* In the Mail> room we have to behave a little differently --
2921 * make sure the user has specified at least one recipient. Then
2922 * validate the recipient(s).
2924 if ( (CC->room.QRflags & QR_MAILBOX)
2925 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2927 if (CC->user.axlevel < 2) {
2928 strcpy(recp, "sysop");
2933 valid_to = validate_recipients(recp);
2934 if (valid_to->num_error > 0) {
2935 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2940 valid_cc = validate_recipients(cc);
2941 if (valid_cc->num_error > 0) {
2942 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2948 valid_bcc = validate_recipients(bcc);
2949 if (valid_bcc->num_error > 0) {
2950 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2957 /* Recipient required, but none were specified */
2958 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2962 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2966 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2967 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2968 cprintf("%d You do not have permission "
2969 "to send Internet mail.\n",
2970 ERROR + HIGHER_ACCESS_REQUIRED);
2978 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)
2979 && (CC->user.axlevel < 4) ) {
2980 cprintf("%d Higher access required for network mail.\n",
2981 ERROR + HIGHER_ACCESS_REQUIRED);
2988 if ((RESTRICT_INTERNET == 1)
2989 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2990 && ((CC->user.flags & US_INTERNET) == 0)
2991 && (!CC->internal_pgm)) {
2992 cprintf("%d You don't have access to Internet mail.\n",
2993 ERROR + HIGHER_ACCESS_REQUIRED);
3002 /* Is this a room which has anonymous-only or anonymous-option? */
3003 anonymous = MES_NORMAL;
3004 if (CC->room.QRflags & QR_ANONONLY) {
3005 anonymous = MES_ANONONLY;
3007 if (CC->room.QRflags & QR_ANONOPT) {
3008 if (anon_flag == 1) { /* only if the user requested it */
3009 anonymous = MES_ANONOPT;
3013 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3017 /* If we're only checking the validity of the request, return
3018 * success without creating the message.
3021 cprintf("%d %s\n", CIT_OK,
3022 ((valid_to != NULL) ? valid_to->display_recp : "") );
3029 /* We don't need these anymore because we'll do it differently below */
3034 /* Handle author masquerading */
3035 if (CC->fake_postname[0]) {
3036 strcpy(masquerade_as, CC->fake_postname);
3038 else if (CC->fake_username[0]) {
3039 strcpy(masquerade_as, CC->fake_username);
3042 strcpy(masquerade_as, "");
3045 /* Read in the message from the client. */
3047 cprintf("%d send message\n", START_CHAT_MODE);
3049 cprintf("%d send message\n", SEND_LISTING);
3052 msg = CtdlMakeMessage(&CC->user, recp, cc,
3053 CC->room.QRname, anonymous, format_type,
3054 masquerade_as, subject, NULL);
3056 /* Put together one big recipients struct containing to/cc/bcc all in
3057 * one. This is for the envelope.
3059 char *all_recps = malloc(SIZ * 3);
3060 strcpy(all_recps, recp);
3061 if (strlen(cc) > 0) {
3062 if (strlen(all_recps) > 0) {
3063 strcat(all_recps, ",");
3065 strcat(all_recps, cc);
3067 if (strlen(bcc) > 0) {
3068 if (strlen(all_recps) > 0) {
3069 strcat(all_recps, ",");
3071 strcat(all_recps, bcc);
3073 if (strlen(all_recps) > 0) {
3074 valid = validate_recipients(all_recps);
3082 msgnum = CtdlSubmitMsg(msg, valid, "");
3085 cprintf("%ld\n", msgnum);
3087 cprintf("Message accepted.\n");
3090 cprintf("Internal error.\n");
3092 if (msg->cm_fields['E'] != NULL) {
3093 cprintf("%s\n", msg->cm_fields['E']);
3100 CtdlFreeMessage(msg);
3102 CC->fake_postname[0] = '\0';
3103 if (valid != NULL) {
3112 * API function to delete messages which match a set of criteria
3113 * (returns the actual number of messages deleted)
3115 int CtdlDeleteMessages(char *room_name, /* which room */
3116 long dmsgnum, /* or "0" for any */
3117 char *content_type, /* or "" for any */
3118 int deferred /* let TDAP sweep it later */
3122 struct ctdlroom qrbuf;
3123 struct cdbdata *cdbfr;
3124 long *msglist = NULL;
3125 long *dellist = NULL;
3128 int num_deleted = 0;
3130 struct MetaData smi;
3132 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3133 room_name, dmsgnum, content_type, deferred);
3135 /* get room record, obtaining a lock... */
3136 if (lgetroom(&qrbuf, room_name) != 0) {
3137 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3139 return (0); /* room not found */
3141 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3143 if (cdbfr != NULL) {
3144 dellist = malloc(cdbfr->len);
3145 msglist = (long *) cdbfr->ptr;
3146 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3147 num_msgs = cdbfr->len / sizeof(long);
3151 for (i = 0; i < num_msgs; ++i) {
3154 /* Set/clear a bit for each criterion */
3156 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3157 delete_this |= 0x01;
3159 if (strlen(content_type) == 0) {
3160 delete_this |= 0x02;
3162 GetMetaData(&smi, msglist[i]);
3163 if (!strcasecmp(smi.meta_content_type,
3165 delete_this |= 0x02;
3169 /* Delete message only if all bits are set */
3170 if (delete_this == 0x03) {
3171 dellist[num_deleted++] = msglist[i];
3176 num_msgs = sort_msglist(msglist, num_msgs);
3177 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3178 msglist, (int)(num_msgs * sizeof(long)));
3180 qrbuf.QRhighest = msglist[num_msgs - 1];
3185 * If the delete operation is "deferred" (and technically, any delete
3186 * operation not performed by THE DREADED AUTO-PURGER ought to be
3187 * a deferred delete) then we save a pointer to the message in the
3188 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3189 * at least 1, which will save the user from having to synchronously
3190 * wait for various disk-intensive operations to complete.
3192 if ( (deferred) && (num_deleted) ) {
3193 for (i=0; i<num_deleted; ++i) {
3194 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3198 /* Go through the messages we pulled out of the index, and decrement
3199 * their reference counts by 1. If this is the only room the message
3200 * was in, the reference count will reach zero and the message will
3201 * automatically be deleted from the database. We do this in a
3202 * separate pass because there might be plug-in hooks getting called,
3203 * and we don't want that happening during an S_ROOMS critical
3206 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3207 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3208 AdjRefCount(dellist[i], -1);
3211 /* Now free the memory we used, and go away. */
3212 if (msglist != NULL) free(msglist);
3213 if (dellist != NULL) free(dellist);
3214 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3215 return (num_deleted);
3221 * Check whether the current user has permission to delete messages from
3222 * the current room (returns 1 for yes, 0 for no)
3224 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3225 getuser(&CC->user, CC->curr_user);
3226 if ((CC->user.axlevel < 6)
3227 && (CC->user.usernum != CC->room.QRroomaide)
3228 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3229 && (!(CC->internal_pgm))) {
3238 * Delete message from current room
3240 void cmd_dele(char *delstr)
3245 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3246 cprintf("%d Higher access required.\n",
3247 ERROR + HIGHER_ACCESS_REQUIRED);
3250 delnum = extract_long(delstr, 0);
3252 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3255 cprintf("%d %d message%s deleted.\n", CIT_OK,
3256 num_deleted, ((num_deleted != 1) ? "s" : ""));
3258 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3264 * Back end API function for moves and deletes
3266 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3269 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3270 if (err != 0) return(err);
3278 * move or copy a message to another room
3280 void cmd_move(char *args)
3283 char targ[ROOMNAMELEN];
3284 struct ctdlroom qtemp;
3290 num = extract_long(args, 0);
3291 extract_token(targ, args, 1, '|', sizeof targ);
3292 targ[ROOMNAMELEN - 1] = 0;
3293 is_copy = extract_int(args, 2);
3295 if (getroom(&qtemp, targ) != 0) {
3296 cprintf("%d '%s' does not exist.\n",
3297 ERROR + ROOM_NOT_FOUND, targ);
3301 getuser(&CC->user, CC->curr_user);
3302 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3304 /* Check for permission to perform this operation.
3305 * Remember: "CC->room" is source, "qtemp" is target.
3309 /* Aides can move/copy */
3310 if (CC->user.axlevel >= 6) permit = 1;
3312 /* Room aides can move/copy */
3313 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3315 /* Permit move/copy from personal rooms */
3316 if ((CC->room.QRflags & QR_MAILBOX)
3317 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3319 /* Permit only copy from public to personal room */
3321 && (!(CC->room.QRflags & QR_MAILBOX))
3322 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3324 /* User must have access to target room */
3325 if (!(ra & UA_KNOWN)) permit = 0;
3328 cprintf("%d Higher access required.\n",
3329 ERROR + HIGHER_ACCESS_REQUIRED);
3333 err = CtdlCopyMsgToRoom(num, targ);
3335 cprintf("%d Cannot store message in %s: error %d\n",
3340 /* Now delete the message from the source room,
3341 * if this is a 'move' rather than a 'copy' operation.
3344 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3347 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3353 * GetMetaData() - Get the supplementary record for a message
3355 void GetMetaData(struct MetaData *smibuf, long msgnum)
3358 struct cdbdata *cdbsmi;
3361 memset(smibuf, 0, sizeof(struct MetaData));
3362 smibuf->meta_msgnum = msgnum;
3363 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3365 /* Use the negative of the message number for its supp record index */
3366 TheIndex = (0L - msgnum);
3368 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3369 if (cdbsmi == NULL) {
3370 return; /* record not found; go with defaults */
3372 memcpy(smibuf, cdbsmi->ptr,
3373 ((cdbsmi->len > sizeof(struct MetaData)) ?
3374 sizeof(struct MetaData) : cdbsmi->len));
3381 * PutMetaData() - (re)write supplementary record for a message
3383 void PutMetaData(struct MetaData *smibuf)
3387 /* Use the negative of the message number for the metadata db index */
3388 TheIndex = (0L - smibuf->meta_msgnum);
3390 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3391 smibuf->meta_msgnum, smibuf->meta_refcount);
3393 cdb_store(CDB_MSGMAIN,
3394 &TheIndex, (int)sizeof(long),
3395 smibuf, (int)sizeof(struct MetaData));
3400 * AdjRefCount - change the reference count for a message;
3401 * delete the message if it reaches zero
3403 void AdjRefCount(long msgnum, int incr)
3406 struct MetaData smi;
3409 /* This is a *tight* critical section; please keep it that way, as
3410 * it may get called while nested in other critical sections.
3411 * Complicating this any further will surely cause deadlock!
3413 begin_critical_section(S_SUPPMSGMAIN);
3414 GetMetaData(&smi, msgnum);
3415 smi.meta_refcount += incr;
3417 end_critical_section(S_SUPPMSGMAIN);
3418 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3419 msgnum, incr, smi.meta_refcount);
3421 /* If the reference count is now zero, delete the message
3422 * (and its supplementary record as well).
3424 if (smi.meta_refcount == 0) {
3425 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3427 /* Remove from fulltext index */
3428 if (config.c_enable_fulltext) {
3429 ft_index_message(msgnum, 0);
3432 /* Remove from message base */
3434 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3435 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3437 /* Remove metadata record */
3438 delnum = (0L - msgnum);
3439 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3444 * Write a generic object to this room
3446 * Note: this could be much more efficient. Right now we use two temporary
3447 * files, and still pull the message into memory as with all others.
3449 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3450 char *content_type, /* MIME type of this object */
3451 char *tempfilename, /* Where to fetch it from */
3452 struct ctdluser *is_mailbox, /* Mailbox room? */
3453 int is_binary, /* Is encoding necessary? */
3454 int is_unique, /* Del others of this type? */
3455 unsigned int flags /* Internal save flags */
3460 struct ctdlroom qrbuf;
3461 char roomname[ROOMNAMELEN];
3462 struct CtdlMessage *msg;
3464 char *raw_message = NULL;
3465 char *encoded_message = NULL;
3466 off_t raw_length = 0;
3468 if (is_mailbox != NULL)
3469 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3471 safestrncpy(roomname, req_room, sizeof(roomname));
3472 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3475 fp = fopen(tempfilename, "rb");
3477 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3478 tempfilename, strerror(errno));
3481 fseek(fp, 0L, SEEK_END);
3482 raw_length = ftell(fp);
3484 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3486 raw_message = malloc((size_t)raw_length + 2);
3487 fread(raw_message, (size_t)raw_length, 1, fp);
3491 encoded_message = malloc((size_t)
3492 (((raw_length * 134) / 100) + 4096 ) );
3495 encoded_message = malloc((size_t)(raw_length + 4096));
3498 sprintf(encoded_message, "Content-type: %s\n", content_type);
3501 sprintf(&encoded_message[strlen(encoded_message)],
3502 "Content-transfer-encoding: base64\n\n"
3506 sprintf(&encoded_message[strlen(encoded_message)],
3507 "Content-transfer-encoding: 7bit\n\n"
3513 &encoded_message[strlen(encoded_message)],
3519 raw_message[raw_length] = 0;
3521 &encoded_message[strlen(encoded_message)],
3529 lprintf(CTDL_DEBUG, "Allocating\n");
3530 msg = malloc(sizeof(struct CtdlMessage));
3531 memset(msg, 0, sizeof(struct CtdlMessage));
3532 msg->cm_magic = CTDLMESSAGE_MAGIC;
3533 msg->cm_anon_type = MES_NORMAL;
3534 msg->cm_format_type = 4;
3535 msg->cm_fields['A'] = strdup(CC->user.fullname);
3536 msg->cm_fields['O'] = strdup(req_room);
3537 msg->cm_fields['N'] = strdup(config.c_nodename);
3538 msg->cm_fields['H'] = strdup(config.c_humannode);
3539 msg->cm_flags = flags;
3541 msg->cm_fields['M'] = encoded_message;
3543 /* Create the requested room if we have to. */
3544 if (getroom(&qrbuf, roomname) != 0) {
3545 create_room(roomname,
3546 ( (is_mailbox != NULL) ? 5 : 3 ),
3547 "", 0, 1, 0, VIEW_BBS);
3549 /* If the caller specified this object as unique, delete all
3550 * other objects of this type that are currently in the room.
3553 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3554 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3557 /* Now write the data */
3558 CtdlSubmitMsg(msg, NULL, roomname);
3559 CtdlFreeMessage(msg);
3567 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3568 config_msgnum = msgnum;
3572 char *CtdlGetSysConfig(char *sysconfname) {
3573 char hold_rm[ROOMNAMELEN];
3576 struct CtdlMessage *msg;
3579 strcpy(hold_rm, CC->room.QRname);
3580 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3581 getroom(&CC->room, hold_rm);
3586 /* We want the last (and probably only) config in this room */
3587 begin_critical_section(S_CONFIG);
3588 config_msgnum = (-1L);
3589 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3590 CtdlGetSysConfigBackend, NULL);
3591 msgnum = config_msgnum;
3592 end_critical_section(S_CONFIG);
3598 msg = CtdlFetchMessage(msgnum, 1);
3600 conf = strdup(msg->cm_fields['M']);
3601 CtdlFreeMessage(msg);
3608 getroom(&CC->room, hold_rm);
3610 if (conf != NULL) do {
3611 extract_token(buf, conf, 0, '\n', sizeof buf);
3612 strcpy(conf, &conf[strlen(buf)+1]);
3613 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3618 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3619 char temp[PATH_MAX];
3622 strcpy(temp, tmpnam(NULL));
3624 fp = fopen(temp, "w");
3625 if (fp == NULL) return;
3626 fprintf(fp, "%s", sysconfdata);
3629 /* this handy API function does all the work for us */
3630 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3636 * Determine whether a given Internet address belongs to the current user
3638 int CtdlIsMe(char *addr, int addr_buf_len)
3640 struct recptypes *recp;
3643 recp = validate_recipients(addr);
3644 if (recp == NULL) return(0);
3646 if (recp->num_local == 0) {
3651 for (i=0; i<recp->num_local; ++i) {
3652 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3653 if (!strcasecmp(addr, CC->user.fullname)) {
3665 * Citadel protocol command to do the same
3667 void cmd_isme(char *argbuf) {
3670 if (CtdlAccessCheck(ac_logged_in)) return;
3671 extract_token(addr, argbuf, 0, '|', sizeof addr);
3673 if (CtdlIsMe(addr, sizeof addr)) {
3674 cprintf("%d %s\n", CIT_OK, addr);
3677 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);