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, char *encoding,
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, char *encoding,
1107 ma = (struct ma_info *)cbuserdata;
1109 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1112 * If we're in the middle of a multipart/alternative scope and
1113 * we've already printed another section, skip this one.
1115 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1116 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1121 if ( (!strcasecmp(cbtype, "text/plain"))
1122 || (strlen(cbtype)==0) ) {
1125 client_write(wptr, length);
1126 if (wptr[length-1] != '\n') {
1131 else if (!strcasecmp(cbtype, "text/html")) {
1132 ptr = html_to_ascii(content, 80, 0);
1134 client_write(ptr, wlen);
1135 if (ptr[wlen-1] != '\n') {
1140 else if (strncasecmp(cbtype, "multipart/", 10)) {
1141 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1142 partnum, filename, cbtype, (long)length);
1147 * The client is elegant and sophisticated and wants to be choosy about
1148 * MIME content types, so figure out which multipart/alternative part
1149 * we're going to send.
1151 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1152 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1159 ma = (struct ma_info *)cbuserdata;
1161 if (ma->is_ma > 0) {
1162 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1163 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1164 if (!strcasecmp(buf, cbtype)) {
1165 strcpy(ma->chosen_part, partnum);
1172 * Now that we've chosen our preferred part, output it.
1174 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1175 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1180 int add_newline = 0;
1184 ma = (struct ma_info *)cbuserdata;
1186 /* This is not the MIME part you're looking for... */
1187 if (strcasecmp(partnum, ma->chosen_part)) return;
1189 /* If the content-type of this part is in our preferred formats
1190 * list, we can simply output it verbatim.
1192 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1193 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1194 if (!strcasecmp(buf, cbtype)) {
1195 /* Yeah! Go! W00t!! */
1197 text_content = (char *)content;
1198 if (text_content[length-1] != '\n') {
1202 cprintf("Content-type: %s", cbtype);
1203 if (strlen(cbcharset) > 0) {
1204 cprintf("; charset=%s", cbcharset);
1206 cprintf("\nContent-length: %d\n",
1207 (int)(length + add_newline) );
1208 if (strlen(encoding) > 0) {
1209 cprintf("Content-transfer-encoding: %s\n", encoding);
1212 cprintf("Content-transfer-encoding: 7bit\n");
1215 client_write(content, length);
1216 if (add_newline) cprintf("\n");
1221 /* No translations required or possible: output as text/plain */
1222 cprintf("Content-type: text/plain\n\n");
1223 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1224 length, encoding, cbuserdata);
1229 * Get a message off disk. (returns om_* values found in msgbase.h)
1232 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1233 int mode, /* how would you like that message? */
1234 int headers_only, /* eschew the message body? */
1235 int do_proto, /* do Citadel protocol responses? */
1236 int crlf /* Use CRLF newlines instead of LF? */
1238 struct CtdlMessage *TheMessage = NULL;
1239 int retcode = om_no_such_msg;
1241 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1244 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1245 if (do_proto) cprintf("%d Not logged in.\n",
1246 ERROR + NOT_LOGGED_IN);
1247 return(om_not_logged_in);
1250 /* FIXME: check message id against msglist for this room */
1253 * Fetch the message from disk. If we're in any sort of headers
1254 * only mode, request that we don't even bother loading the body
1257 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1258 TheMessage = CtdlFetchMessage(msg_num, 0);
1261 TheMessage = CtdlFetchMessage(msg_num, 1);
1264 if (TheMessage == NULL) {
1265 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1266 ERROR + MESSAGE_NOT_FOUND, msg_num);
1267 return(om_no_such_msg);
1270 retcode = CtdlOutputPreLoadedMsg(
1272 headers_only, do_proto, crlf);
1274 CtdlFreeMessage(TheMessage);
1281 * Get a message off disk. (returns om_* values found in msgbase.h)
1284 int CtdlOutputPreLoadedMsg(
1285 struct CtdlMessage *TheMessage,
1286 int mode, /* how would you like that message? */
1287 int headers_only, /* eschew the message body? */
1288 int do_proto, /* do Citadel protocol responses? */
1289 int crlf /* Use CRLF newlines instead of LF? */
1295 char display_name[256];
1297 char *nl; /* newline string */
1299 int subject_found = 0;
1302 /* Buffers needed for RFC822 translation. These are all filled
1303 * using functions that are bounds-checked, and therefore we can
1304 * make them substantially smaller than SIZ.
1312 char datestamp[100];
1314 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1315 ((TheMessage == NULL) ? "NULL" : "not null"),
1316 mode, headers_only, do_proto, crlf);
1318 strcpy(mid, "unknown");
1319 nl = (crlf ? "\r\n" : "\n");
1321 if (!is_valid_message(TheMessage)) {
1323 "ERROR: invalid preloaded message for output\n");
1324 return(om_no_such_msg);
1327 /* Are we downloading a MIME component? */
1328 if (mode == MT_DOWNLOAD) {
1329 if (TheMessage->cm_format_type != FMT_RFC822) {
1331 cprintf("%d This is not a MIME message.\n",
1332 ERROR + ILLEGAL_VALUE);
1333 } else if (CC->download_fp != NULL) {
1334 if (do_proto) cprintf(
1335 "%d You already have a download open.\n",
1336 ERROR + RESOURCE_BUSY);
1338 /* Parse the message text component */
1339 mptr = TheMessage->cm_fields['M'];
1340 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1341 /* If there's no file open by this time, the requested
1342 * section wasn't found, so print an error
1344 if (CC->download_fp == NULL) {
1345 if (do_proto) cprintf(
1346 "%d Section %s not found.\n",
1347 ERROR + FILE_NOT_FOUND,
1348 CC->download_desired_section);
1351 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1354 /* now for the user-mode message reading loops */
1355 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1357 /* Does the caller want to skip the headers? */
1358 if (headers_only == HEADERS_NONE) goto START_TEXT;
1360 /* Tell the client which format type we're using. */
1361 if ( (mode == MT_CITADEL) && (do_proto) ) {
1362 cprintf("type=%d\n", TheMessage->cm_format_type);
1365 /* nhdr=yes means that we're only displaying headers, no body */
1366 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1367 && (mode == MT_CITADEL)
1370 cprintf("nhdr=yes\n");
1373 /* begin header processing loop for Citadel message format */
1375 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1377 safestrncpy(display_name, "<unknown>", sizeof display_name);
1378 if (TheMessage->cm_fields['A']) {
1379 strcpy(buf, TheMessage->cm_fields['A']);
1380 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1381 safestrncpy(display_name, "****", sizeof display_name);
1383 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1384 safestrncpy(display_name, "anonymous", sizeof display_name);
1387 safestrncpy(display_name, buf, sizeof display_name);
1389 if ((is_room_aide())
1390 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1391 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1392 size_t tmp = strlen(display_name);
1393 snprintf(&display_name[tmp],
1394 sizeof display_name - tmp,
1399 /* Don't show Internet address for users on the
1400 * local Citadel network.
1403 if (TheMessage->cm_fields['N'] != NULL)
1404 if (strlen(TheMessage->cm_fields['N']) > 0)
1405 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1409 /* Now spew the header fields in the order we like them. */
1410 safestrncpy(allkeys, FORDER, sizeof allkeys);
1411 for (i=0; i<strlen(allkeys); ++i) {
1412 k = (int) allkeys[i];
1414 if ( (TheMessage->cm_fields[k] != NULL)
1415 && (msgkeys[k] != NULL) ) {
1417 if (do_proto) cprintf("%s=%s\n",
1421 else if ((k == 'F') && (suppress_f)) {
1424 /* Masquerade display name if needed */
1426 if (do_proto) cprintf("%s=%s\n",
1428 TheMessage->cm_fields[k]
1437 /* begin header processing loop for RFC822 transfer format */
1442 strcpy(snode, NODENAME);
1443 strcpy(lnode, HUMANNODE);
1444 if (mode == MT_RFC822) {
1445 for (i = 0; i < 256; ++i) {
1446 if (TheMessage->cm_fields[i]) {
1447 mptr = TheMessage->cm_fields[i];
1450 safestrncpy(luser, mptr, sizeof luser);
1451 safestrncpy(suser, mptr, sizeof suser);
1453 else if (i == 'Y') {
1454 cprintf("CC: %s%s", mptr, nl);
1456 else if (i == 'U') {
1457 cprintf("Subject: %s%s", mptr, nl);
1461 safestrncpy(mid, mptr, sizeof mid);
1463 safestrncpy(lnode, mptr, sizeof lnode);
1465 safestrncpy(fuser, mptr, sizeof fuser);
1466 /* else if (i == 'O')
1467 cprintf("X-Citadel-Room: %s%s",
1470 safestrncpy(snode, mptr, sizeof snode);
1472 cprintf("To: %s%s", mptr, nl);
1473 else if (i == 'T') {
1474 datestring(datestamp, sizeof datestamp,
1475 atol(mptr), DATESTRING_RFC822);
1476 cprintf("Date: %s%s", datestamp, nl);
1480 if (subject_found == 0) {
1481 cprintf("Subject: (no subject)%s", nl);
1485 for (i=0; i<strlen(suser); ++i) {
1486 suser[i] = tolower(suser[i]);
1487 if (!isalnum(suser[i])) suser[i]='_';
1490 if (mode == MT_RFC822) {
1491 if (!strcasecmp(snode, NODENAME)) {
1492 safestrncpy(snode, FQDN, sizeof snode);
1495 /* Construct a fun message id */
1496 cprintf("Message-ID: <%s", mid);
1497 if (strchr(mid, '@')==NULL) {
1498 cprintf("@%s", snode);
1502 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1503 cprintf("From: \"----\" <x@x.org>%s", nl);
1505 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1506 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1508 else if (strlen(fuser) > 0) {
1509 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1512 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1515 cprintf("Organization: %s%s", lnode, nl);
1517 /* Blank line signifying RFC822 end-of-headers */
1518 if (TheMessage->cm_format_type != FMT_RFC822) {
1523 /* end header processing loop ... at this point, we're in the text */
1525 if (headers_only == HEADERS_FAST) goto DONE;
1526 mptr = TheMessage->cm_fields['M'];
1528 /* Tell the client about the MIME parts in this message */
1529 if (TheMessage->cm_format_type == FMT_RFC822) {
1530 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1531 memset(&ma, 0, sizeof(struct ma_info));
1532 mime_parser(mptr, NULL,
1533 (do_proto ? *list_this_part : NULL),
1534 (do_proto ? *list_this_pref : NULL),
1535 (do_proto ? *list_this_suff : NULL),
1538 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1539 char *start_of_text = NULL;
1540 start_of_text = strstr(mptr, "\n\r\n");
1541 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1542 if (start_of_text == NULL) start_of_text = mptr;
1544 start_of_text = strstr(start_of_text, "\n");
1546 while (ch=*mptr, ch!=0) {
1550 else switch(headers_only) {
1552 if (mptr >= start_of_text) {
1553 if (ch == 10) cprintf("%s", nl);
1554 else cprintf("%c", ch);
1558 if (mptr < start_of_text) {
1559 if (ch == 10) cprintf("%s", nl);
1560 else cprintf("%c", ch);
1564 if (ch == 10) cprintf("%s", nl);
1565 else cprintf("%c", ch);
1574 if (headers_only == HEADERS_ONLY) {
1578 /* signify start of msg text */
1579 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1580 if (do_proto) cprintf("text\n");
1583 /* If the format type on disk is 1 (fixed-format), then we want
1584 * everything to be output completely literally ... regardless of
1585 * what message transfer format is in use.
1587 if (TheMessage->cm_format_type == FMT_FIXED) {
1588 if (mode == MT_MIME) {
1589 cprintf("Content-type: text/plain\n\n");
1592 while (ch = *mptr++, ch > 0) {
1595 if ((ch == 10) || (strlen(buf) > 250)) {
1596 cprintf("%s%s", buf, nl);
1599 buf[strlen(buf) + 1] = 0;
1600 buf[strlen(buf)] = ch;
1603 if (strlen(buf) > 0)
1604 cprintf("%s%s", buf, nl);
1607 /* If the message on disk is format 0 (Citadel vari-format), we
1608 * output using the formatter at 80 columns. This is the final output
1609 * form if the transfer format is RFC822, but if the transfer format
1610 * is Citadel proprietary, it'll still work, because the indentation
1611 * for new paragraphs is correct and the client will reformat the
1612 * message to the reader's screen width.
1614 if (TheMessage->cm_format_type == FMT_CITADEL) {
1615 if (mode == MT_MIME) {
1616 cprintf("Content-type: text/x-citadel-variformat\n\n");
1618 memfmout(80, mptr, 0, nl);
1621 /* If the message on disk is format 4 (MIME), we've gotta hand it
1622 * off to the MIME parser. The client has already been told that
1623 * this message is format 1 (fixed format), so the callback function
1624 * we use will display those parts as-is.
1626 if (TheMessage->cm_format_type == FMT_RFC822) {
1627 memset(&ma, 0, sizeof(struct ma_info));
1629 if (mode == MT_MIME) {
1630 strcpy(ma.chosen_part, "1");
1631 mime_parser(mptr, NULL,
1632 *choose_preferred, *fixed_output_pre,
1633 *fixed_output_post, (void *)&ma, 0);
1634 mime_parser(mptr, NULL,
1635 *output_preferred, NULL, NULL, (void *)&ma, 0);
1638 mime_parser(mptr, NULL,
1639 *fixed_output, *fixed_output_pre,
1640 *fixed_output_post, (void *)&ma, 0);
1645 DONE: /* now we're done */
1646 if (do_proto) cprintf("000\n");
1653 * display a message (mode 0 - Citadel proprietary)
1655 void cmd_msg0(char *cmdbuf)
1658 int headers_only = HEADERS_ALL;
1660 msgid = extract_long(cmdbuf, 0);
1661 headers_only = extract_int(cmdbuf, 1);
1663 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1669 * display a message (mode 2 - RFC822)
1671 void cmd_msg2(char *cmdbuf)
1674 int headers_only = HEADERS_ALL;
1676 msgid = extract_long(cmdbuf, 0);
1677 headers_only = extract_int(cmdbuf, 1);
1679 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1685 * display a message (mode 3 - IGnet raw format - internal programs only)
1687 void cmd_msg3(char *cmdbuf)
1690 struct CtdlMessage *msg;
1693 if (CC->internal_pgm == 0) {
1694 cprintf("%d This command is for internal programs only.\n",
1695 ERROR + HIGHER_ACCESS_REQUIRED);
1699 msgnum = extract_long(cmdbuf, 0);
1700 msg = CtdlFetchMessage(msgnum, 1);
1702 cprintf("%d Message %ld not found.\n",
1703 ERROR + MESSAGE_NOT_FOUND, msgnum);
1707 serialize_message(&smr, msg);
1708 CtdlFreeMessage(msg);
1711 cprintf("%d Unable to serialize message\n",
1712 ERROR + INTERNAL_ERROR);
1716 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1717 client_write((char *)smr.ser, (int)smr.len);
1724 * Display a message using MIME content types
1726 void cmd_msg4(char *cmdbuf)
1730 msgid = extract_long(cmdbuf, 0);
1731 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1737 * Client tells us its preferred message format(s)
1739 void cmd_msgp(char *cmdbuf)
1741 safestrncpy(CC->preferred_formats, cmdbuf,
1742 sizeof(CC->preferred_formats));
1743 cprintf("%d ok\n", CIT_OK);
1748 * Open a component of a MIME message as a download file
1750 void cmd_opna(char *cmdbuf)
1753 char desired_section[128];
1755 msgid = extract_long(cmdbuf, 0);
1756 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1757 safestrncpy(CC->download_desired_section, desired_section,
1758 sizeof CC->download_desired_section);
1759 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1764 * Save a message pointer into a specified room
1765 * (Returns 0 for success, nonzero for failure)
1766 * roomname may be NULL to use the current room
1768 * Note that the 'supplied_msg' field may be set to NULL, in which case
1769 * the message will be fetched from disk, by number, if we need to perform
1770 * replication checks. This adds an additional database read, so if the
1771 * caller already has the message in memory then it should be supplied.
1773 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1774 struct CtdlMessage *supplied_msg) {
1776 char hold_rm[ROOMNAMELEN];
1777 struct cdbdata *cdbfr;
1780 long highest_msg = 0L;
1781 struct CtdlMessage *msg = NULL;
1783 /*lprintf(CTDL_DEBUG,
1784 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1785 roomname, msgid, do_repl_check);*/
1787 strcpy(hold_rm, CC->room.QRname);
1789 /* Now the regular stuff */
1790 if (lgetroom(&CC->room,
1791 ((roomname != NULL) ? roomname : CC->room.QRname) )
1793 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1794 return(ERROR + ROOM_NOT_FOUND);
1797 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1798 if (cdbfr == NULL) {
1802 msglist = (long *) cdbfr->ptr;
1803 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1804 num_msgs = cdbfr->len / sizeof(long);
1808 /* Make sure the message doesn't already exist in this room. It
1809 * is absolutely taboo to have more than one reference to the same
1810 * message in a room.
1812 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1813 if (msglist[i] == msgid) {
1814 lputroom(&CC->room); /* unlock the room */
1815 getroom(&CC->room, hold_rm);
1817 return(ERROR + ALREADY_EXISTS);
1821 /* Now add the new message */
1823 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1825 if (msglist == NULL) {
1826 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1828 msglist[num_msgs - 1] = msgid;
1830 /* Sort the message list, so all the msgid's are in order */
1831 num_msgs = sort_msglist(msglist, num_msgs);
1833 /* Determine the highest message number */
1834 highest_msg = msglist[num_msgs - 1];
1836 /* Write it back to disk. */
1837 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1838 msglist, (int)(num_msgs * sizeof(long)));
1840 /* Free up the memory we used. */
1843 /* Update the highest-message pointer and unlock the room. */
1844 CC->room.QRhighest = highest_msg;
1845 lputroom(&CC->room);
1847 /* Perform replication checks if necessary */
1848 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1849 if (supplied_msg != NULL) {
1853 msg = CtdlFetchMessage(msgid, 0);
1857 ReplicationChecks(msg);
1862 /* If the message has an Exclusive ID, index that... */
1864 if (msg->cm_fields['E'] != NULL) {
1865 index_message_by_euid(msg->cm_fields['E'],
1870 /* Free up the memory we may have allocated */
1871 if ( (msg != NULL) && (msg != supplied_msg) ) {
1872 CtdlFreeMessage(msg);
1875 /* Go back to the room we were in before we wandered here... */
1876 getroom(&CC->room, hold_rm);
1878 /* Bump the reference count for this message. */
1879 AdjRefCount(msgid, +1);
1881 /* Return success. */
1888 * Message base operation to save a new message to the message store
1889 * (returns new message number)
1891 * This is the back end for CtdlSubmitMsg() and should not be directly
1892 * called by server-side modules.
1895 long send_message(struct CtdlMessage *msg) {
1903 /* Get a new message number */
1904 newmsgid = get_new_message_number();
1905 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1907 /* Generate an ID if we don't have one already */
1908 if (msg->cm_fields['I']==NULL) {
1909 msg->cm_fields['I'] = strdup(msgidbuf);
1912 /* If the message is big, set its body aside for storage elsewhere */
1913 if (msg->cm_fields['M'] != NULL) {
1914 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1916 holdM = msg->cm_fields['M'];
1917 msg->cm_fields['M'] = NULL;
1921 /* Serialize our data structure for storage in the database */
1922 serialize_message(&smr, msg);
1925 msg->cm_fields['M'] = holdM;
1929 cprintf("%d Unable to serialize message\n",
1930 ERROR + INTERNAL_ERROR);
1934 /* Write our little bundle of joy into the message base */
1935 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1936 smr.ser, smr.len) < 0) {
1937 lprintf(CTDL_ERR, "Can't store message\n");
1941 cdb_store(CDB_BIGMSGS,
1951 /* Free the memory we used for the serialized message */
1954 /* Return the *local* message ID to the caller
1955 * (even if we're storing an incoming network message)
1963 * Serialize a struct CtdlMessage into the format used on disk and network.
1965 * This function loads up a "struct ser_ret" (defined in server.h) which
1966 * contains the length of the serialized message and a pointer to the
1967 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1969 void serialize_message(struct ser_ret *ret, /* return values */
1970 struct CtdlMessage *msg) /* unserialized msg */
1974 static char *forder = FORDER;
1976 if (is_valid_message(msg) == 0) return; /* self check */
1979 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1980 ret->len = ret->len +
1981 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1983 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1984 ret->ser = malloc(ret->len);
1985 if (ret->ser == NULL) {
1991 ret->ser[1] = msg->cm_anon_type;
1992 ret->ser[2] = msg->cm_format_type;
1995 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1996 ret->ser[wlen++] = (char)forder[i];
1997 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1998 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2000 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2001 (long)ret->len, (long)wlen);
2009 * Check to see if any messages already exist in the current room which
2010 * carry the same Exclusive ID as this one. If any are found, delete them.
2012 void ReplicationChecks(struct CtdlMessage *msg) {
2013 long old_msgnum = (-1L);
2015 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2017 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2020 /* No exclusive id? Don't do anything. */
2021 if (msg == NULL) return;
2022 if (msg->cm_fields['E'] == NULL) return;
2023 if (strlen(msg->cm_fields['E']) == 0) return;
2024 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2025 msg->cm_fields['E'], CC->room.QRname);*/
2027 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2028 if (old_msgnum > 0L) {
2029 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2030 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2037 * Save a message to disk and submit it into the delivery system.
2039 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2040 struct recptypes *recps, /* recipients (if mail) */
2041 char *force /* force a particular room? */
2043 char submit_filename[128];
2044 char generated_timestamp[32];
2045 char hold_rm[ROOMNAMELEN];
2046 char actual_rm[ROOMNAMELEN];
2047 char force_room[ROOMNAMELEN];
2048 char content_type[SIZ]; /* We have to learn this */
2049 char recipient[SIZ];
2052 struct ctdluser userbuf;
2054 struct MetaData smi;
2055 FILE *network_fp = NULL;
2056 static int seqnum = 1;
2057 struct CtdlMessage *imsg = NULL;
2060 char *hold_R, *hold_D;
2061 char *collected_addresses = NULL;
2062 struct addresses_to_be_filed *aptr = NULL;
2064 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2065 if (is_valid_message(msg) == 0) return(-1); /* self check */
2067 /* If this message has no timestamp, we take the liberty of
2068 * giving it one, right now.
2070 if (msg->cm_fields['T'] == NULL) {
2071 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2072 msg->cm_fields['T'] = strdup(generated_timestamp);
2075 /* If this message has no path, we generate one.
2077 if (msg->cm_fields['P'] == NULL) {
2078 if (msg->cm_fields['A'] != NULL) {
2079 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2080 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2081 if (isspace(msg->cm_fields['P'][a])) {
2082 msg->cm_fields['P'][a] = ' ';
2087 msg->cm_fields['P'] = strdup("unknown");
2091 if (force == NULL) {
2092 strcpy(force_room, "");
2095 strcpy(force_room, force);
2098 /* Learn about what's inside, because it's what's inside that counts */
2099 if (msg->cm_fields['M'] == NULL) {
2100 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2104 switch (msg->cm_format_type) {
2106 strcpy(content_type, "text/x-citadel-variformat");
2109 strcpy(content_type, "text/plain");
2112 strcpy(content_type, "text/plain");
2113 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2115 safestrncpy(content_type, &mptr[14],
2116 sizeof content_type);
2117 for (a = 0; a < strlen(content_type); ++a) {
2118 if ((content_type[a] == ';')
2119 || (content_type[a] == ' ')
2120 || (content_type[a] == 13)
2121 || (content_type[a] == 10)) {
2122 content_type[a] = 0;
2128 /* Goto the correct room */
2129 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2130 strcpy(hold_rm, CC->room.QRname);
2131 strcpy(actual_rm, CC->room.QRname);
2132 if (recps != NULL) {
2133 strcpy(actual_rm, SENTITEMS);
2136 /* If the user is a twit, move to the twit room for posting */
2138 if (CC->user.axlevel == 2) {
2139 strcpy(hold_rm, actual_rm);
2140 strcpy(actual_rm, config.c_twitroom);
2141 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2145 /* ...or if this message is destined for Aide> then go there. */
2146 if (strlen(force_room) > 0) {
2147 strcpy(actual_rm, force_room);
2150 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2151 if (strcasecmp(actual_rm, CC->room.QRname)) {
2152 /* getroom(&CC->room, actual_rm); */
2153 usergoto(actual_rm, 0, 1, NULL, NULL);
2157 * If this message has no O (room) field, generate one.
2159 if (msg->cm_fields['O'] == NULL) {
2160 msg->cm_fields['O'] = strdup(CC->room.QRname);
2163 /* Perform "before save" hooks (aborting if any return nonzero) */
2164 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2165 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2168 * If this message has an Exclusive ID, and the room is replication
2169 * checking enabled, then do replication checks.
2171 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2172 ReplicationChecks(msg);
2175 /* Save it to disk */
2176 lprintf(CTDL_DEBUG, "Saving to disk\n");
2177 newmsgid = send_message(msg);
2178 if (newmsgid <= 0L) return(-5);
2180 /* Write a supplemental message info record. This doesn't have to
2181 * be a critical section because nobody else knows about this message
2184 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2185 memset(&smi, 0, sizeof(struct MetaData));
2186 smi.meta_msgnum = newmsgid;
2187 smi.meta_refcount = 0;
2188 safestrncpy(smi.meta_content_type, content_type,
2189 sizeof smi.meta_content_type);
2191 /* As part of the new metadata record, measure how
2192 * big this message will be when displayed as RFC822.
2193 * Both POP and IMAP use this, and it's best to just take the hit now
2194 * instead of having to potentially measure thousands of messages when
2195 * a mailbox is opened later.
2198 if (CC->redirect_buffer != NULL) {
2199 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2202 CC->redirect_buffer = malloc(SIZ);
2203 CC->redirect_len = 0;
2204 CC->redirect_alloc = SIZ;
2205 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2206 smi.meta_rfc822_length = CC->redirect_len;
2207 free(CC->redirect_buffer);
2208 CC->redirect_buffer = NULL;
2209 CC->redirect_len = 0;
2210 CC->redirect_alloc = 0;
2214 /* Now figure out where to store the pointers */
2215 lprintf(CTDL_DEBUG, "Storing pointers\n");
2217 /* If this is being done by the networker delivering a private
2218 * message, we want to BYPASS saving the sender's copy (because there
2219 * is no local sender; it would otherwise go to the Trashcan).
2221 if ((!CC->internal_pgm) || (recps == NULL)) {
2222 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2223 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2224 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2228 /* For internet mail, drop a copy in the outbound queue room */
2230 if (recps->num_internet > 0) {
2231 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2234 /* If other rooms are specified, drop them there too. */
2236 if (recps->num_room > 0)
2237 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2238 extract_token(recipient, recps->recp_room, i,
2239 '|', sizeof recipient);
2240 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2241 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2244 /* Bump this user's messages posted counter. */
2245 lprintf(CTDL_DEBUG, "Updating user\n");
2246 lgetuser(&CC->user, CC->curr_user);
2247 CC->user.posted = CC->user.posted + 1;
2248 lputuser(&CC->user);
2250 /* If this is private, local mail, make a copy in the
2251 * recipient's mailbox and bump the reference count.
2254 if (recps->num_local > 0)
2255 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2256 extract_token(recipient, recps->recp_local, i,
2257 '|', sizeof recipient);
2258 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2260 if (getuser(&userbuf, recipient) == 0) {
2261 MailboxName(actual_rm, sizeof actual_rm,
2262 &userbuf, MAILROOM);
2263 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2264 BumpNewMailCounter(userbuf.usernum);
2267 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2268 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2273 /* Perform "after save" hooks */
2274 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2275 PerformMessageHooks(msg, EVT_AFTERSAVE);
2277 /* For IGnet mail, we have to save a new copy into the spooler for
2278 * each recipient, with the R and D fields set to the recipient and
2279 * destination-node. This has two ugly side effects: all other
2280 * recipients end up being unlisted in this recipient's copy of the
2281 * message, and it has to deliver multiple messages to the same
2282 * node. We'll revisit this again in a year or so when everyone has
2283 * a network spool receiver that can handle the new style messages.
2286 if (recps->num_ignet > 0)
2287 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2288 extract_token(recipient, recps->recp_ignet, i,
2289 '|', sizeof recipient);
2291 hold_R = msg->cm_fields['R'];
2292 hold_D = msg->cm_fields['D'];
2293 msg->cm_fields['R'] = malloc(SIZ);
2294 msg->cm_fields['D'] = malloc(128);
2295 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2296 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2298 serialize_message(&smr, msg);
2300 snprintf(submit_filename, sizeof submit_filename,
2301 #ifndef HAVE_SPOOL_DIR
2306 "/network/spoolin/netmail.%04lx.%04x.%04x",
2307 (long) getpid(), CC->cs_pid, ++seqnum);
2308 network_fp = fopen(submit_filename, "wb+");
2309 if (network_fp != NULL) {
2310 fwrite(smr.ser, smr.len, 1, network_fp);
2316 free(msg->cm_fields['R']);
2317 free(msg->cm_fields['D']);
2318 msg->cm_fields['R'] = hold_R;
2319 msg->cm_fields['D'] = hold_D;
2322 /* Go back to the room we started from */
2323 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2324 if (strcasecmp(hold_rm, CC->room.QRname))
2325 /* getroom(&CC->room, hold_rm); */
2326 usergoto(hold_rm, 0, 1, NULL, NULL);
2328 /* For internet mail, generate delivery instructions.
2329 * Yes, this is recursive. Deal with it. Infinite recursion does
2330 * not happen because the delivery instructions message does not
2331 * contain a recipient.
2334 if (recps->num_internet > 0) {
2335 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2336 instr = malloc(SIZ * 2);
2337 snprintf(instr, SIZ * 2,
2338 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2340 SPOOLMIME, newmsgid, (long)time(NULL),
2341 msg->cm_fields['A'], msg->cm_fields['N']
2344 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2345 size_t tmp = strlen(instr);
2346 extract_token(recipient, recps->recp_internet,
2347 i, '|', sizeof recipient);
2348 snprintf(&instr[tmp], SIZ * 2 - tmp,
2349 "remote|%s|0||\n", recipient);
2352 imsg = malloc(sizeof(struct CtdlMessage));
2353 memset(imsg, 0, sizeof(struct CtdlMessage));
2354 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2355 imsg->cm_anon_type = MES_NORMAL;
2356 imsg->cm_format_type = FMT_RFC822;
2357 imsg->cm_fields['A'] = strdup("Citadel");
2358 imsg->cm_fields['M'] = instr;
2359 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2360 CtdlFreeMessage(imsg);
2364 * Any addresses to harvest for someone's address book?
2366 if ( (CC->logged_in) && (recps != NULL) ) {
2367 collected_addresses = harvest_collected_addresses(msg);
2370 if (collected_addresses != NULL) {
2371 begin_critical_section(S_ATBF);
2372 aptr = (struct addresses_to_be_filed *)
2373 malloc(sizeof(struct addresses_to_be_filed));
2375 MailboxName(actual_rm, sizeof actual_rm,
2376 &CC->user, USERCONTACTSROOM);
2377 aptr->roomname = strdup(actual_rm);
2378 aptr->collected_addresses = collected_addresses;
2380 end_critical_section(S_ATBF);
2393 * Convenience function for generating small administrative messages.
2395 void quickie_message(char *from, char *to, char *room, char *text,
2396 int format_type, char *subject)
2398 struct CtdlMessage *msg;
2399 struct recptypes *recp = NULL;
2401 msg = malloc(sizeof(struct CtdlMessage));
2402 memset(msg, 0, sizeof(struct CtdlMessage));
2403 msg->cm_magic = CTDLMESSAGE_MAGIC;
2404 msg->cm_anon_type = MES_NORMAL;
2405 msg->cm_format_type = format_type;
2406 msg->cm_fields['A'] = strdup(from);
2407 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2408 msg->cm_fields['N'] = strdup(NODENAME);
2410 msg->cm_fields['R'] = strdup(to);
2411 recp = validate_recipients(to);
2413 if (subject != NULL) {
2414 msg->cm_fields['U'] = strdup(subject);
2416 msg->cm_fields['M'] = strdup(text);
2418 CtdlSubmitMsg(msg, recp, room);
2419 CtdlFreeMessage(msg);
2420 if (recp != NULL) free(recp);
2426 * Back end function used by CtdlMakeMessage() and similar functions
2428 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2429 size_t maxlen, /* maximum message length */
2430 char *exist, /* if non-null, append to it;
2431 exist is ALWAYS freed */
2432 int crlf /* CRLF newlines instead of LF */
2436 size_t message_len = 0;
2437 size_t buffer_len = 0;
2443 if (exist == NULL) {
2450 message_len = strlen(exist);
2451 buffer_len = message_len + 4096;
2452 m = realloc(exist, buffer_len);
2459 /* flush the input if we have nowhere to store it */
2464 /* read in the lines of message text one by one */
2466 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2467 if (!strcmp(buf, terminator)) finished = 1;
2469 strcat(buf, "\r\n");
2475 if ( (!flushing) && (!finished) ) {
2476 /* Measure the line */
2477 linelen = strlen(buf);
2479 /* augment the buffer if we have to */
2480 if ((message_len + linelen) >= buffer_len) {
2481 ptr = realloc(m, (buffer_len * 2) );
2482 if (ptr == NULL) { /* flush if can't allocate */
2485 buffer_len = (buffer_len * 2);
2487 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2491 /* Add the new line to the buffer. NOTE: this loop must avoid
2492 * using functions like strcat() and strlen() because they
2493 * traverse the entire buffer upon every call, and doing that
2494 * for a multi-megabyte message slows it down beyond usability.
2496 strcpy(&m[message_len], buf);
2497 message_len += linelen;
2500 /* if we've hit the max msg length, flush the rest */
2501 if (message_len >= maxlen) flushing = 1;
2503 } while (!finished);
2511 * Build a binary message to be saved on disk.
2512 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2513 * will become part of the message. This means you are no longer
2514 * responsible for managing that memory -- it will be freed along with
2515 * the rest of the fields when CtdlFreeMessage() is called.)
2518 struct CtdlMessage *CtdlMakeMessage(
2519 struct ctdluser *author, /* author's user structure */
2520 char *recipient, /* NULL if it's not mail */
2521 char *recp_cc, /* NULL if it's not mail */
2522 char *room, /* room where it's going */
2523 int type, /* see MES_ types in header file */
2524 int format_type, /* variformat, plain text, MIME... */
2525 char *fake_name, /* who we're masquerading as */
2526 char *subject, /* Subject (optional) */
2527 char *preformatted_text /* ...or NULL to read text from client */
2529 char dest_node[SIZ];
2531 struct CtdlMessage *msg;
2533 msg = malloc(sizeof(struct CtdlMessage));
2534 memset(msg, 0, sizeof(struct CtdlMessage));
2535 msg->cm_magic = CTDLMESSAGE_MAGIC;
2536 msg->cm_anon_type = type;
2537 msg->cm_format_type = format_type;
2539 /* Don't confuse the poor folks if it's not routed mail. */
2540 strcpy(dest_node, "");
2545 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2546 msg->cm_fields['P'] = strdup(buf);
2548 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2549 msg->cm_fields['T'] = strdup(buf);
2551 if (fake_name[0]) /* author */
2552 msg->cm_fields['A'] = strdup(fake_name);
2554 msg->cm_fields['A'] = strdup(author->fullname);
2556 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2557 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2560 msg->cm_fields['O'] = strdup(CC->room.QRname);
2563 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2564 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2566 if (recipient[0] != 0) {
2567 msg->cm_fields['R'] = strdup(recipient);
2569 if (recp_cc[0] != 0) {
2570 msg->cm_fields['Y'] = strdup(recp_cc);
2572 if (dest_node[0] != 0) {
2573 msg->cm_fields['D'] = strdup(dest_node);
2576 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2577 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2580 if (subject != NULL) {
2582 if (strlen(subject) > 0) {
2583 msg->cm_fields['U'] = strdup(subject);
2587 if (preformatted_text != NULL) {
2588 msg->cm_fields['M'] = preformatted_text;
2591 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2592 config.c_maxmsglen, NULL, 0);
2600 * Check to see whether we have permission to post a message in the current
2601 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2602 * returns 0 on success.
2604 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2606 if (!(CC->logged_in)) {
2607 snprintf(errmsgbuf, n, "Not logged in.");
2608 return (ERROR + NOT_LOGGED_IN);
2611 if ((CC->user.axlevel < 2)
2612 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2613 snprintf(errmsgbuf, n, "Need to be validated to enter "
2614 "(except in %s> to sysop)", MAILROOM);
2615 return (ERROR + HIGHER_ACCESS_REQUIRED);
2618 if ((CC->user.axlevel < 4)
2619 && (CC->room.QRflags & QR_NETWORK)) {
2620 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2621 return (ERROR + HIGHER_ACCESS_REQUIRED);
2624 if ((CC->user.axlevel < 6)
2625 && (CC->room.QRflags & QR_READONLY)) {
2626 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2627 return (ERROR + HIGHER_ACCESS_REQUIRED);
2630 strcpy(errmsgbuf, "Ok");
2636 * Check to see if the specified user has Internet mail permission
2637 * (returns nonzero if permission is granted)
2639 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2641 /* Do not allow twits to send Internet mail */
2642 if (who->axlevel <= 2) return(0);
2644 /* Globally enabled? */
2645 if (config.c_restrict == 0) return(1);
2647 /* User flagged ok? */
2648 if (who->flags & US_INTERNET) return(2);
2650 /* Aide level access? */
2651 if (who->axlevel >= 6) return(3);
2653 /* No mail for you! */
2659 * Validate recipients, count delivery types and errors, and handle aliasing
2660 * FIXME check for dupes!!!!!
2661 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2662 * or the number of addresses found invalid.
2664 struct recptypes *validate_recipients(char *supplied_recipients) {
2665 struct recptypes *ret;
2666 char recipients[SIZ];
2667 char this_recp[256];
2668 char this_recp_cooked[256];
2674 struct ctdluser tempUS;
2675 struct ctdlroom tempQR;
2679 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2680 if (ret == NULL) return(NULL);
2681 memset(ret, 0, sizeof(struct recptypes));
2684 ret->num_internet = 0;
2689 if (supplied_recipients == NULL) {
2690 strcpy(recipients, "");
2693 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2696 /* Change all valid separator characters to commas */
2697 for (i=0; i<strlen(recipients); ++i) {
2698 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2699 recipients[i] = ',';
2703 /* Now start extracting recipients... */
2705 while (strlen(recipients) > 0) {
2707 for (i=0; i<=strlen(recipients); ++i) {
2708 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2709 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2710 safestrncpy(this_recp, recipients, i+1);
2712 if (recipients[i] == ',') {
2713 strcpy(recipients, &recipients[i+1]);
2716 strcpy(recipients, "");
2723 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2725 mailtype = alias(this_recp);
2726 mailtype = alias(this_recp);
2727 mailtype = alias(this_recp);
2728 for (j=0; j<=strlen(this_recp); ++j) {
2729 if (this_recp[j]=='_') {
2730 this_recp_cooked[j] = ' ';
2733 this_recp_cooked[j] = this_recp[j];
2739 if (!strcasecmp(this_recp, "sysop")) {
2741 strcpy(this_recp, config.c_aideroom);
2742 if (strlen(ret->recp_room) > 0) {
2743 strcat(ret->recp_room, "|");
2745 strcat(ret->recp_room, this_recp);
2747 else if (getuser(&tempUS, this_recp) == 0) {
2749 strcpy(this_recp, tempUS.fullname);
2750 if (strlen(ret->recp_local) > 0) {
2751 strcat(ret->recp_local, "|");
2753 strcat(ret->recp_local, this_recp);
2755 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2757 strcpy(this_recp, tempUS.fullname);
2758 if (strlen(ret->recp_local) > 0) {
2759 strcat(ret->recp_local, "|");
2761 strcat(ret->recp_local, this_recp);
2763 else if ( (!strncasecmp(this_recp, "room_", 5))
2764 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2766 if (strlen(ret->recp_room) > 0) {
2767 strcat(ret->recp_room, "|");
2769 strcat(ret->recp_room, &this_recp_cooked[5]);
2777 /* Yes, you're reading this correctly: if the target
2778 * domain points back to the local system or an attached
2779 * Citadel directory, the address is invalid. That's
2780 * because if the address were valid, we would have
2781 * already translated it to a local address by now.
2783 if (IsDirectory(this_recp)) {
2788 ++ret->num_internet;
2789 if (strlen(ret->recp_internet) > 0) {
2790 strcat(ret->recp_internet, "|");
2792 strcat(ret->recp_internet, this_recp);
2797 if (strlen(ret->recp_ignet) > 0) {
2798 strcat(ret->recp_ignet, "|");
2800 strcat(ret->recp_ignet, this_recp);
2808 if (strlen(ret->errormsg) == 0) {
2809 snprintf(append, sizeof append,
2810 "Invalid recipient: %s",
2814 snprintf(append, sizeof append,
2817 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2818 strcat(ret->errormsg, append);
2822 if (strlen(ret->display_recp) == 0) {
2823 strcpy(append, this_recp);
2826 snprintf(append, sizeof append, ", %s",
2829 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2830 strcat(ret->display_recp, append);
2835 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2836 ret->num_room + ret->num_error) == 0) {
2837 ret->num_error = (-1);
2838 strcpy(ret->errormsg, "No recipients specified.");
2841 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2842 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2843 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2844 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2845 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2846 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2854 * message entry - mode 0 (normal)
2856 void cmd_ent0(char *entargs)
2862 char masquerade_as[SIZ];
2864 int format_type = 0;
2865 char newusername[SIZ];
2866 struct CtdlMessage *msg;
2870 struct recptypes *valid = NULL;
2871 struct recptypes *valid_to = NULL;
2872 struct recptypes *valid_cc = NULL;
2873 struct recptypes *valid_bcc = NULL;
2880 post = extract_int(entargs, 0);
2881 extract_token(recp, entargs, 1, '|', sizeof recp);
2882 anon_flag = extract_int(entargs, 2);
2883 format_type = extract_int(entargs, 3);
2884 extract_token(subject, entargs, 4, '|', sizeof subject);
2885 do_confirm = extract_int(entargs, 6);
2886 extract_token(cc, entargs, 7, '|', sizeof cc);
2887 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2889 /* first check to make sure the request is valid. */
2891 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2893 cprintf("%d %s\n", err, errmsg);
2897 /* Check some other permission type things. */
2900 if (CC->user.axlevel < 6) {
2901 cprintf("%d You don't have permission to masquerade.\n",
2902 ERROR + HIGHER_ACCESS_REQUIRED);
2905 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2906 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2907 safestrncpy(CC->fake_postname, newusername,
2908 sizeof(CC->fake_postname) );
2909 cprintf("%d ok\n", CIT_OK);
2912 CC->cs_flags |= CS_POSTING;
2914 /* In the Mail> room we have to behave a little differently --
2915 * make sure the user has specified at least one recipient. Then
2916 * validate the recipient(s).
2918 if ( (CC->room.QRflags & QR_MAILBOX)
2919 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2921 if (CC->user.axlevel < 2) {
2922 strcpy(recp, "sysop");
2927 valid_to = validate_recipients(recp);
2928 if (valid_to->num_error > 0) {
2929 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2934 valid_cc = validate_recipients(cc);
2935 if (valid_cc->num_error > 0) {
2936 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2942 valid_bcc = validate_recipients(bcc);
2943 if (valid_bcc->num_error > 0) {
2944 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2951 /* Recipient required, but none were specified */
2952 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2956 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2960 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2961 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2962 cprintf("%d You do not have permission "
2963 "to send Internet mail.\n",
2964 ERROR + HIGHER_ACCESS_REQUIRED);
2972 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)
2973 && (CC->user.axlevel < 4) ) {
2974 cprintf("%d Higher access required for network mail.\n",
2975 ERROR + HIGHER_ACCESS_REQUIRED);
2982 if ((RESTRICT_INTERNET == 1)
2983 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2984 && ((CC->user.flags & US_INTERNET) == 0)
2985 && (!CC->internal_pgm)) {
2986 cprintf("%d You don't have access to Internet mail.\n",
2987 ERROR + HIGHER_ACCESS_REQUIRED);
2996 /* Is this a room which has anonymous-only or anonymous-option? */
2997 anonymous = MES_NORMAL;
2998 if (CC->room.QRflags & QR_ANONONLY) {
2999 anonymous = MES_ANONONLY;
3001 if (CC->room.QRflags & QR_ANONOPT) {
3002 if (anon_flag == 1) { /* only if the user requested it */
3003 anonymous = MES_ANONOPT;
3007 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3011 /* If we're only checking the validity of the request, return
3012 * success without creating the message.
3015 cprintf("%d %s\n", CIT_OK,
3016 ((valid_to != NULL) ? valid_to->display_recp : "") );
3023 /* We don't need these anymore because we'll do it differently below */
3028 /* Handle author masquerading */
3029 if (CC->fake_postname[0]) {
3030 strcpy(masquerade_as, CC->fake_postname);
3032 else if (CC->fake_username[0]) {
3033 strcpy(masquerade_as, CC->fake_username);
3036 strcpy(masquerade_as, "");
3039 /* Read in the message from the client. */
3041 cprintf("%d send message\n", START_CHAT_MODE);
3043 cprintf("%d send message\n", SEND_LISTING);
3046 msg = CtdlMakeMessage(&CC->user, recp, cc,
3047 CC->room.QRname, anonymous, format_type,
3048 masquerade_as, subject, NULL);
3050 /* Put together one big recipients struct containing to/cc/bcc all in
3051 * one. This is for the envelope.
3053 char *all_recps = malloc(SIZ * 3);
3054 strcpy(all_recps, recp);
3055 if (strlen(cc) > 0) {
3056 if (strlen(all_recps) > 0) {
3057 strcat(all_recps, ",");
3059 strcat(all_recps, cc);
3061 if (strlen(bcc) > 0) {
3062 if (strlen(all_recps) > 0) {
3063 strcat(all_recps, ",");
3065 strcat(all_recps, bcc);
3067 if (strlen(all_recps) > 0) {
3068 valid = validate_recipients(all_recps);
3076 msgnum = CtdlSubmitMsg(msg, valid, "");
3079 cprintf("%ld\n", msgnum);
3081 cprintf("Message accepted.\n");
3084 cprintf("Internal error.\n");
3086 if (msg->cm_fields['E'] != NULL) {
3087 cprintf("%s\n", msg->cm_fields['E']);
3094 CtdlFreeMessage(msg);
3096 CC->fake_postname[0] = '\0';
3097 if (valid != NULL) {
3106 * API function to delete messages which match a set of criteria
3107 * (returns the actual number of messages deleted)
3109 int CtdlDeleteMessages(char *room_name, /* which room */
3110 long dmsgnum, /* or "0" for any */
3111 char *content_type, /* or "" for any */
3112 int deferred /* let TDAP sweep it later */
3116 struct ctdlroom qrbuf;
3117 struct cdbdata *cdbfr;
3118 long *msglist = NULL;
3119 long *dellist = NULL;
3122 int num_deleted = 0;
3124 struct MetaData smi;
3126 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3127 room_name, dmsgnum, content_type, deferred);
3129 /* get room record, obtaining a lock... */
3130 if (lgetroom(&qrbuf, room_name) != 0) {
3131 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3133 return (0); /* room not found */
3135 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3137 if (cdbfr != NULL) {
3138 dellist = malloc(cdbfr->len);
3139 msglist = (long *) cdbfr->ptr;
3140 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3141 num_msgs = cdbfr->len / sizeof(long);
3145 for (i = 0; i < num_msgs; ++i) {
3148 /* Set/clear a bit for each criterion */
3150 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3151 delete_this |= 0x01;
3153 if (strlen(content_type) == 0) {
3154 delete_this |= 0x02;
3156 GetMetaData(&smi, msglist[i]);
3157 if (!strcasecmp(smi.meta_content_type,
3159 delete_this |= 0x02;
3163 /* Delete message only if all bits are set */
3164 if (delete_this == 0x03) {
3165 dellist[num_deleted++] = msglist[i];
3170 num_msgs = sort_msglist(msglist, num_msgs);
3171 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3172 msglist, (int)(num_msgs * sizeof(long)));
3174 qrbuf.QRhighest = msglist[num_msgs - 1];
3179 * If the delete operation is "deferred" (and technically, any delete
3180 * operation not performed by THE DREADED AUTO-PURGER ought to be
3181 * a deferred delete) then we save a pointer to the message in the
3182 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3183 * at least 1, which will save the user from having to synchronously
3184 * wait for various disk-intensive operations to complete.
3186 if ( (deferred) && (num_deleted) ) {
3187 for (i=0; i<num_deleted; ++i) {
3188 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3192 /* Go through the messages we pulled out of the index, and decrement
3193 * their reference counts by 1. If this is the only room the message
3194 * was in, the reference count will reach zero and the message will
3195 * automatically be deleted from the database. We do this in a
3196 * separate pass because there might be plug-in hooks getting called,
3197 * and we don't want that happening during an S_ROOMS critical
3200 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3201 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3202 AdjRefCount(dellist[i], -1);
3205 /* Now free the memory we used, and go away. */
3206 if (msglist != NULL) free(msglist);
3207 if (dellist != NULL) free(dellist);
3208 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3209 return (num_deleted);
3215 * Check whether the current user has permission to delete messages from
3216 * the current room (returns 1 for yes, 0 for no)
3218 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3219 getuser(&CC->user, CC->curr_user);
3220 if ((CC->user.axlevel < 6)
3221 && (CC->user.usernum != CC->room.QRroomaide)
3222 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3223 && (!(CC->internal_pgm))) {
3232 * Delete message from current room
3234 void cmd_dele(char *delstr)
3239 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3240 cprintf("%d Higher access required.\n",
3241 ERROR + HIGHER_ACCESS_REQUIRED);
3244 delnum = extract_long(delstr, 0);
3246 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3249 cprintf("%d %d message%s deleted.\n", CIT_OK,
3250 num_deleted, ((num_deleted != 1) ? "s" : ""));
3252 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3258 * Back end API function for moves and deletes
3260 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3263 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3264 if (err != 0) return(err);
3272 * move or copy a message to another room
3274 void cmd_move(char *args)
3277 char targ[ROOMNAMELEN];
3278 struct ctdlroom qtemp;
3284 num = extract_long(args, 0);
3285 extract_token(targ, args, 1, '|', sizeof targ);
3286 targ[ROOMNAMELEN - 1] = 0;
3287 is_copy = extract_int(args, 2);
3289 if (getroom(&qtemp, targ) != 0) {
3290 cprintf("%d '%s' does not exist.\n",
3291 ERROR + ROOM_NOT_FOUND, targ);
3295 getuser(&CC->user, CC->curr_user);
3296 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3298 /* Check for permission to perform this operation.
3299 * Remember: "CC->room" is source, "qtemp" is target.
3303 /* Aides can move/copy */
3304 if (CC->user.axlevel >= 6) permit = 1;
3306 /* Room aides can move/copy */
3307 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3309 /* Permit move/copy from personal rooms */
3310 if ((CC->room.QRflags & QR_MAILBOX)
3311 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3313 /* Permit only copy from public to personal room */
3315 && (!(CC->room.QRflags & QR_MAILBOX))
3316 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3318 /* User must have access to target room */
3319 if (!(ra & UA_KNOWN)) permit = 0;
3322 cprintf("%d Higher access required.\n",
3323 ERROR + HIGHER_ACCESS_REQUIRED);
3327 err = CtdlCopyMsgToRoom(num, targ);
3329 cprintf("%d Cannot store message in %s: error %d\n",
3334 /* Now delete the message from the source room,
3335 * if this is a 'move' rather than a 'copy' operation.
3338 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3341 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3347 * GetMetaData() - Get the supplementary record for a message
3349 void GetMetaData(struct MetaData *smibuf, long msgnum)
3352 struct cdbdata *cdbsmi;
3355 memset(smibuf, 0, sizeof(struct MetaData));
3356 smibuf->meta_msgnum = msgnum;
3357 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3359 /* Use the negative of the message number for its supp record index */
3360 TheIndex = (0L - msgnum);
3362 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3363 if (cdbsmi == NULL) {
3364 return; /* record not found; go with defaults */
3366 memcpy(smibuf, cdbsmi->ptr,
3367 ((cdbsmi->len > sizeof(struct MetaData)) ?
3368 sizeof(struct MetaData) : cdbsmi->len));
3375 * PutMetaData() - (re)write supplementary record for a message
3377 void PutMetaData(struct MetaData *smibuf)
3381 /* Use the negative of the message number for the metadata db index */
3382 TheIndex = (0L - smibuf->meta_msgnum);
3384 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3385 smibuf->meta_msgnum, smibuf->meta_refcount);
3387 cdb_store(CDB_MSGMAIN,
3388 &TheIndex, (int)sizeof(long),
3389 smibuf, (int)sizeof(struct MetaData));
3394 * AdjRefCount - change the reference count for a message;
3395 * delete the message if it reaches zero
3397 void AdjRefCount(long msgnum, int incr)
3400 struct MetaData smi;
3403 /* This is a *tight* critical section; please keep it that way, as
3404 * it may get called while nested in other critical sections.
3405 * Complicating this any further will surely cause deadlock!
3407 begin_critical_section(S_SUPPMSGMAIN);
3408 GetMetaData(&smi, msgnum);
3409 smi.meta_refcount += incr;
3411 end_critical_section(S_SUPPMSGMAIN);
3412 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3413 msgnum, incr, smi.meta_refcount);
3415 /* If the reference count is now zero, delete the message
3416 * (and its supplementary record as well).
3418 if (smi.meta_refcount == 0) {
3419 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3421 /* Remove from fulltext index */
3422 if (config.c_enable_fulltext) {
3423 ft_index_message(msgnum, 0);
3426 /* Remove from message base */
3428 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3429 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3431 /* Remove metadata record */
3432 delnum = (0L - msgnum);
3433 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3438 * Write a generic object to this room
3440 * Note: this could be much more efficient. Right now we use two temporary
3441 * files, and still pull the message into memory as with all others.
3443 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3444 char *content_type, /* MIME type of this object */
3445 char *tempfilename, /* Where to fetch it from */
3446 struct ctdluser *is_mailbox, /* Mailbox room? */
3447 int is_binary, /* Is encoding necessary? */
3448 int is_unique, /* Del others of this type? */
3449 unsigned int flags /* Internal save flags */
3454 struct ctdlroom qrbuf;
3455 char roomname[ROOMNAMELEN];
3456 struct CtdlMessage *msg;
3458 char *raw_message = NULL;
3459 char *encoded_message = NULL;
3460 off_t raw_length = 0;
3462 if (is_mailbox != NULL)
3463 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3465 safestrncpy(roomname, req_room, sizeof(roomname));
3466 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3469 fp = fopen(tempfilename, "rb");
3471 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3472 tempfilename, strerror(errno));
3475 fseek(fp, 0L, SEEK_END);
3476 raw_length = ftell(fp);
3478 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3480 raw_message = malloc((size_t)raw_length + 2);
3481 fread(raw_message, (size_t)raw_length, 1, fp);
3485 encoded_message = malloc((size_t)
3486 (((raw_length * 134) / 100) + 4096 ) );
3489 encoded_message = malloc((size_t)(raw_length + 4096));
3492 sprintf(encoded_message, "Content-type: %s\n", content_type);
3495 sprintf(&encoded_message[strlen(encoded_message)],
3496 "Content-transfer-encoding: base64\n\n"
3500 sprintf(&encoded_message[strlen(encoded_message)],
3501 "Content-transfer-encoding: 7bit\n\n"
3507 &encoded_message[strlen(encoded_message)],
3513 raw_message[raw_length] = 0;
3515 &encoded_message[strlen(encoded_message)],
3523 lprintf(CTDL_DEBUG, "Allocating\n");
3524 msg = malloc(sizeof(struct CtdlMessage));
3525 memset(msg, 0, sizeof(struct CtdlMessage));
3526 msg->cm_magic = CTDLMESSAGE_MAGIC;
3527 msg->cm_anon_type = MES_NORMAL;
3528 msg->cm_format_type = 4;
3529 msg->cm_fields['A'] = strdup(CC->user.fullname);
3530 msg->cm_fields['O'] = strdup(req_room);
3531 msg->cm_fields['N'] = strdup(config.c_nodename);
3532 msg->cm_fields['H'] = strdup(config.c_humannode);
3533 msg->cm_flags = flags;
3535 msg->cm_fields['M'] = encoded_message;
3537 /* Create the requested room if we have to. */
3538 if (getroom(&qrbuf, roomname) != 0) {
3539 create_room(roomname,
3540 ( (is_mailbox != NULL) ? 5 : 3 ),
3541 "", 0, 1, 0, VIEW_BBS);
3543 /* If the caller specified this object as unique, delete all
3544 * other objects of this type that are currently in the room.
3547 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3548 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3551 /* Now write the data */
3552 CtdlSubmitMsg(msg, NULL, roomname);
3553 CtdlFreeMessage(msg);
3561 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3562 config_msgnum = msgnum;
3566 char *CtdlGetSysConfig(char *sysconfname) {
3567 char hold_rm[ROOMNAMELEN];
3570 struct CtdlMessage *msg;
3573 strcpy(hold_rm, CC->room.QRname);
3574 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3575 getroom(&CC->room, hold_rm);
3580 /* We want the last (and probably only) config in this room */
3581 begin_critical_section(S_CONFIG);
3582 config_msgnum = (-1L);
3583 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3584 CtdlGetSysConfigBackend, NULL);
3585 msgnum = config_msgnum;
3586 end_critical_section(S_CONFIG);
3592 msg = CtdlFetchMessage(msgnum, 1);
3594 conf = strdup(msg->cm_fields['M']);
3595 CtdlFreeMessage(msg);
3602 getroom(&CC->room, hold_rm);
3604 if (conf != NULL) do {
3605 extract_token(buf, conf, 0, '\n', sizeof buf);
3606 strcpy(conf, &conf[strlen(buf)+1]);
3607 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3612 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3613 char temp[PATH_MAX];
3616 strcpy(temp, tmpnam(NULL));
3618 fp = fopen(temp, "w");
3619 if (fp == NULL) return;
3620 fprintf(fp, "%s", sysconfdata);
3623 /* this handy API function does all the work for us */
3624 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3630 * Determine whether a given Internet address belongs to the current user
3632 int CtdlIsMe(char *addr, int addr_buf_len)
3634 struct recptypes *recp;
3637 recp = validate_recipients(addr);
3638 if (recp == NULL) return(0);
3640 if (recp->num_local == 0) {
3645 for (i=0; i<recp->num_local; ++i) {
3646 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3647 if (!strcasecmp(addr, CC->user.fullname)) {
3659 * Citadel protocol command to do the same
3661 void cmd_isme(char *argbuf) {
3664 if (CtdlAccessCheck(ac_logged_in)) return;
3665 extract_token(addr, argbuf, 0, '|', sizeof addr);
3667 if (CtdlIsMe(addr, sizeof addr)) {
3668 cprintf("%d %s\n", CIT_OK, addr);
3671 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);