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, newmsgid, 0, msg);
2272 /* Perform "after save" hooks */
2273 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2274 PerformMessageHooks(msg, EVT_AFTERSAVE);
2276 /* For IGnet mail, we have to save a new copy into the spooler for
2277 * each recipient, with the R and D fields set to the recipient and
2278 * destination-node. This has two ugly side effects: all other
2279 * recipients end up being unlisted in this recipient's copy of the
2280 * message, and it has to deliver multiple messages to the same
2281 * node. We'll revisit this again in a year or so when everyone has
2282 * a network spool receiver that can handle the new style messages.
2285 if (recps->num_ignet > 0)
2286 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2287 extract_token(recipient, recps->recp_ignet, i,
2288 '|', sizeof recipient);
2290 hold_R = msg->cm_fields['R'];
2291 hold_D = msg->cm_fields['D'];
2292 msg->cm_fields['R'] = malloc(SIZ);
2293 msg->cm_fields['D'] = malloc(128);
2294 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2295 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2297 serialize_message(&smr, msg);
2299 snprintf(submit_filename, sizeof submit_filename,
2300 #ifndef HAVE_SPOOL_DIR
2305 "/network/spoolin/netmail.%04lx.%04x.%04x",
2306 (long) getpid(), CC->cs_pid, ++seqnum);
2307 network_fp = fopen(submit_filename, "wb+");
2308 if (network_fp != NULL) {
2309 fwrite(smr.ser, smr.len, 1, network_fp);
2315 free(msg->cm_fields['R']);
2316 free(msg->cm_fields['D']);
2317 msg->cm_fields['R'] = hold_R;
2318 msg->cm_fields['D'] = hold_D;
2321 /* Go back to the room we started from */
2322 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2323 if (strcasecmp(hold_rm, CC->room.QRname))
2324 /* getroom(&CC->room, hold_rm); */
2325 usergoto(hold_rm, 0, 1, NULL, NULL);
2327 /* For internet mail, generate delivery instructions.
2328 * Yes, this is recursive. Deal with it. Infinite recursion does
2329 * not happen because the delivery instructions message does not
2330 * contain a recipient.
2333 if (recps->num_internet > 0) {
2334 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2335 instr = malloc(SIZ * 2);
2336 snprintf(instr, SIZ * 2,
2337 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2339 SPOOLMIME, newmsgid, (long)time(NULL),
2340 msg->cm_fields['A'], msg->cm_fields['N']
2343 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2344 size_t tmp = strlen(instr);
2345 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2346 snprintf(&instr[tmp], SIZ * 2 - tmp,
2347 "remote|%s|0||\n", recipient);
2350 imsg = malloc(sizeof(struct CtdlMessage));
2351 memset(imsg, 0, sizeof(struct CtdlMessage));
2352 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2353 imsg->cm_anon_type = MES_NORMAL;
2354 imsg->cm_format_type = FMT_RFC822;
2355 imsg->cm_fields['A'] = strdup("Citadel");
2356 imsg->cm_fields['M'] = instr;
2357 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2358 CtdlFreeMessage(imsg);
2362 * Any addresses to harvest for someone's address book?
2364 if ( (CC->logged_in) && (recps != NULL) ) {
2365 collected_addresses = harvest_collected_addresses(msg);
2368 if (collected_addresses != NULL) {
2369 begin_critical_section(S_ATBF);
2370 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2372 MailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2373 aptr->roomname = strdup(actual_rm);
2374 aptr->collected_addresses = collected_addresses;
2376 end_critical_section(S_ATBF);
2389 * Convenience function for generating small administrative messages.
2391 void quickie_message(char *from, char *to, char *room, char *text,
2392 int format_type, char *subject)
2394 struct CtdlMessage *msg;
2395 struct recptypes *recp = NULL;
2397 msg = malloc(sizeof(struct CtdlMessage));
2398 memset(msg, 0, sizeof(struct CtdlMessage));
2399 msg->cm_magic = CTDLMESSAGE_MAGIC;
2400 msg->cm_anon_type = MES_NORMAL;
2401 msg->cm_format_type = format_type;
2402 msg->cm_fields['A'] = strdup(from);
2403 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2404 msg->cm_fields['N'] = strdup(NODENAME);
2406 msg->cm_fields['R'] = strdup(to);
2407 recp = validate_recipients(to);
2409 if (subject != NULL) {
2410 msg->cm_fields['U'] = strdup(subject);
2412 msg->cm_fields['M'] = strdup(text);
2414 CtdlSubmitMsg(msg, recp, room);
2415 CtdlFreeMessage(msg);
2416 if (recp != NULL) free(recp);
2422 * Back end function used by CtdlMakeMessage() and similar functions
2424 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2425 size_t maxlen, /* maximum message length */
2426 char *exist, /* if non-null, append to it;
2427 exist is ALWAYS freed */
2428 int crlf /* CRLF newlines instead of LF */
2432 size_t message_len = 0;
2433 size_t buffer_len = 0;
2439 if (exist == NULL) {
2446 message_len = strlen(exist);
2447 buffer_len = message_len + 4096;
2448 m = realloc(exist, buffer_len);
2455 /* flush the input if we have nowhere to store it */
2460 /* read in the lines of message text one by one */
2462 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2463 if (!strcmp(buf, terminator)) finished = 1;
2465 strcat(buf, "\r\n");
2471 if ( (!flushing) && (!finished) ) {
2472 /* Measure the line */
2473 linelen = strlen(buf);
2475 /* augment the buffer if we have to */
2476 if ((message_len + linelen) >= buffer_len) {
2477 ptr = realloc(m, (buffer_len * 2) );
2478 if (ptr == NULL) { /* flush if can't allocate */
2481 buffer_len = (buffer_len * 2);
2483 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2487 /* Add the new line to the buffer. NOTE: this loop must avoid
2488 * using functions like strcat() and strlen() because they
2489 * traverse the entire buffer upon every call, and doing that
2490 * for a multi-megabyte message slows it down beyond usability.
2492 strcpy(&m[message_len], buf);
2493 message_len += linelen;
2496 /* if we've hit the max msg length, flush the rest */
2497 if (message_len >= maxlen) flushing = 1;
2499 } while (!finished);
2507 * Build a binary message to be saved on disk.
2508 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2509 * will become part of the message. This means you are no longer
2510 * responsible for managing that memory -- it will be freed along with
2511 * the rest of the fields when CtdlFreeMessage() is called.)
2514 struct CtdlMessage *CtdlMakeMessage(
2515 struct ctdluser *author, /* author's user structure */
2516 char *recipient, /* NULL if it's not mail */
2517 char *recp_cc, /* NULL if it's not mail */
2518 char *room, /* room where it's going */
2519 int type, /* see MES_ types in header file */
2520 int format_type, /* variformat, plain text, MIME... */
2521 char *fake_name, /* who we're masquerading as */
2522 char *subject, /* Subject (optional) */
2523 char *preformatted_text /* ...or NULL to read text from client */
2525 char dest_node[SIZ];
2527 struct CtdlMessage *msg;
2529 msg = malloc(sizeof(struct CtdlMessage));
2530 memset(msg, 0, sizeof(struct CtdlMessage));
2531 msg->cm_magic = CTDLMESSAGE_MAGIC;
2532 msg->cm_anon_type = type;
2533 msg->cm_format_type = format_type;
2535 /* Don't confuse the poor folks if it's not routed mail. */
2536 strcpy(dest_node, "");
2541 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2542 msg->cm_fields['P'] = strdup(buf);
2544 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2545 msg->cm_fields['T'] = strdup(buf);
2547 if (fake_name[0]) /* author */
2548 msg->cm_fields['A'] = strdup(fake_name);
2550 msg->cm_fields['A'] = strdup(author->fullname);
2552 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2553 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2556 msg->cm_fields['O'] = strdup(CC->room.QRname);
2559 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2560 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2562 if (recipient[0] != 0) {
2563 msg->cm_fields['R'] = strdup(recipient);
2565 if (recp_cc[0] != 0) {
2566 msg->cm_fields['Y'] = strdup(recp_cc);
2568 if (dest_node[0] != 0) {
2569 msg->cm_fields['D'] = strdup(dest_node);
2572 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2573 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2576 if (subject != NULL) {
2578 if (strlen(subject) > 0) {
2579 msg->cm_fields['U'] = strdup(subject);
2583 if (preformatted_text != NULL) {
2584 msg->cm_fields['M'] = preformatted_text;
2587 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2588 config.c_maxmsglen, NULL, 0);
2596 * Check to see whether we have permission to post a message in the current
2597 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2598 * returns 0 on success.
2600 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2602 if (!(CC->logged_in)) {
2603 snprintf(errmsgbuf, n, "Not logged in.");
2604 return (ERROR + NOT_LOGGED_IN);
2607 if ((CC->user.axlevel < 2)
2608 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2609 snprintf(errmsgbuf, n, "Need to be validated to enter "
2610 "(except in %s> to sysop)", MAILROOM);
2611 return (ERROR + HIGHER_ACCESS_REQUIRED);
2614 if ((CC->user.axlevel < 4)
2615 && (CC->room.QRflags & QR_NETWORK)) {
2616 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2617 return (ERROR + HIGHER_ACCESS_REQUIRED);
2620 if ((CC->user.axlevel < 6)
2621 && (CC->room.QRflags & QR_READONLY)) {
2622 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2623 return (ERROR + HIGHER_ACCESS_REQUIRED);
2626 strcpy(errmsgbuf, "Ok");
2632 * Check to see if the specified user has Internet mail permission
2633 * (returns nonzero if permission is granted)
2635 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2637 /* Do not allow twits to send Internet mail */
2638 if (who->axlevel <= 2) return(0);
2640 /* Globally enabled? */
2641 if (config.c_restrict == 0) return(1);
2643 /* User flagged ok? */
2644 if (who->flags & US_INTERNET) return(2);
2646 /* Aide level access? */
2647 if (who->axlevel >= 6) return(3);
2649 /* No mail for you! */
2655 * Validate recipients, count delivery types and errors, and handle aliasing
2656 * FIXME check for dupes!!!!!
2657 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2658 * or the number of addresses found invalid.
2660 struct recptypes *validate_recipients(char *supplied_recipients) {
2661 struct recptypes *ret;
2662 char recipients[SIZ];
2663 char this_recp[256];
2664 char this_recp_cooked[256];
2670 struct ctdluser tempUS;
2671 struct ctdlroom tempQR;
2675 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2676 if (ret == NULL) return(NULL);
2677 memset(ret, 0, sizeof(struct recptypes));
2680 ret->num_internet = 0;
2685 if (supplied_recipients == NULL) {
2686 strcpy(recipients, "");
2689 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2692 /* Change all valid separator characters to commas */
2693 for (i=0; i<strlen(recipients); ++i) {
2694 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2695 recipients[i] = ',';
2699 /* Now start extracting recipients... */
2701 while (strlen(recipients) > 0) {
2703 for (i=0; i<=strlen(recipients); ++i) {
2704 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2705 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2706 safestrncpy(this_recp, recipients, i+1);
2708 if (recipients[i] == ',') {
2709 strcpy(recipients, &recipients[i+1]);
2712 strcpy(recipients, "");
2719 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2721 mailtype = alias(this_recp);
2722 mailtype = alias(this_recp);
2723 mailtype = alias(this_recp);
2724 for (j=0; j<=strlen(this_recp); ++j) {
2725 if (this_recp[j]=='_') {
2726 this_recp_cooked[j] = ' ';
2729 this_recp_cooked[j] = this_recp[j];
2735 if (!strcasecmp(this_recp, "sysop")) {
2737 strcpy(this_recp, config.c_aideroom);
2738 if (strlen(ret->recp_room) > 0) {
2739 strcat(ret->recp_room, "|");
2741 strcat(ret->recp_room, this_recp);
2743 else if (getuser(&tempUS, this_recp) == 0) {
2745 strcpy(this_recp, tempUS.fullname);
2746 if (strlen(ret->recp_local) > 0) {
2747 strcat(ret->recp_local, "|");
2749 strcat(ret->recp_local, this_recp);
2751 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2753 strcpy(this_recp, tempUS.fullname);
2754 if (strlen(ret->recp_local) > 0) {
2755 strcat(ret->recp_local, "|");
2757 strcat(ret->recp_local, this_recp);
2759 else if ( (!strncasecmp(this_recp, "room_", 5))
2760 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2762 if (strlen(ret->recp_room) > 0) {
2763 strcat(ret->recp_room, "|");
2765 strcat(ret->recp_room, &this_recp_cooked[5]);
2773 /* Yes, you're reading this correctly: if the target
2774 * domain points back to the local system or an attached
2775 * Citadel directory, the address is invalid. That's
2776 * because if the address were valid, we would have
2777 * already translated it to a local address by now.
2779 if (IsDirectory(this_recp)) {
2784 ++ret->num_internet;
2785 if (strlen(ret->recp_internet) > 0) {
2786 strcat(ret->recp_internet, "|");
2788 strcat(ret->recp_internet, this_recp);
2793 if (strlen(ret->recp_ignet) > 0) {
2794 strcat(ret->recp_ignet, "|");
2796 strcat(ret->recp_ignet, this_recp);
2804 if (strlen(ret->errormsg) == 0) {
2805 snprintf(append, sizeof append,
2806 "Invalid recipient: %s",
2810 snprintf(append, sizeof append,
2813 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2814 strcat(ret->errormsg, append);
2818 if (strlen(ret->display_recp) == 0) {
2819 strcpy(append, this_recp);
2822 snprintf(append, sizeof append, ", %s",
2825 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2826 strcat(ret->display_recp, append);
2831 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2832 ret->num_room + ret->num_error) == 0) {
2833 ret->num_error = (-1);
2834 strcpy(ret->errormsg, "No recipients specified.");
2837 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2838 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2839 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2840 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2841 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2842 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2850 * message entry - mode 0 (normal)
2852 void cmd_ent0(char *entargs)
2858 char masquerade_as[SIZ];
2860 int format_type = 0;
2861 char newusername[SIZ];
2862 struct CtdlMessage *msg;
2866 struct recptypes *valid = NULL;
2867 struct recptypes *valid_to = NULL;
2868 struct recptypes *valid_cc = NULL;
2869 struct recptypes *valid_bcc = NULL;
2876 post = extract_int(entargs, 0);
2877 extract_token(recp, entargs, 1, '|', sizeof recp);
2878 anon_flag = extract_int(entargs, 2);
2879 format_type = extract_int(entargs, 3);
2880 extract_token(subject, entargs, 4, '|', sizeof subject);
2881 do_confirm = extract_int(entargs, 6);
2882 extract_token(cc, entargs, 7, '|', sizeof cc);
2883 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2885 /* first check to make sure the request is valid. */
2887 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2889 cprintf("%d %s\n", err, errmsg);
2893 /* Check some other permission type things. */
2896 if (CC->user.axlevel < 6) {
2897 cprintf("%d You don't have permission to masquerade.\n",
2898 ERROR + HIGHER_ACCESS_REQUIRED);
2901 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2902 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2903 safestrncpy(CC->fake_postname, newusername,
2904 sizeof(CC->fake_postname) );
2905 cprintf("%d ok\n", CIT_OK);
2908 CC->cs_flags |= CS_POSTING;
2910 /* In the Mail> room we have to behave a little differently --
2911 * make sure the user has specified at least one recipient. Then
2912 * validate the recipient(s).
2914 if ( (CC->room.QRflags & QR_MAILBOX)
2915 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2917 if (CC->user.axlevel < 2) {
2918 strcpy(recp, "sysop");
2923 valid_to = validate_recipients(recp);
2924 if (valid_to->num_error > 0) {
2925 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2930 valid_cc = validate_recipients(cc);
2931 if (valid_cc->num_error > 0) {
2932 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2938 valid_bcc = validate_recipients(bcc);
2939 if (valid_bcc->num_error > 0) {
2940 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2947 /* Recipient required, but none were specified */
2948 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2952 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2956 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2957 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2958 cprintf("%d You do not have permission "
2959 "to send Internet mail.\n",
2960 ERROR + HIGHER_ACCESS_REQUIRED);
2968 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)
2969 && (CC->user.axlevel < 4) ) {
2970 cprintf("%d Higher access required for network mail.\n",
2971 ERROR + HIGHER_ACCESS_REQUIRED);
2978 if ((RESTRICT_INTERNET == 1)
2979 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2980 && ((CC->user.flags & US_INTERNET) == 0)
2981 && (!CC->internal_pgm)) {
2982 cprintf("%d You don't have access to Internet mail.\n",
2983 ERROR + HIGHER_ACCESS_REQUIRED);
2992 /* Is this a room which has anonymous-only or anonymous-option? */
2993 anonymous = MES_NORMAL;
2994 if (CC->room.QRflags & QR_ANONONLY) {
2995 anonymous = MES_ANONONLY;
2997 if (CC->room.QRflags & QR_ANONOPT) {
2998 if (anon_flag == 1) { /* only if the user requested it */
2999 anonymous = MES_ANONOPT;
3003 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3007 /* If we're only checking the validity of the request, return
3008 * success without creating the message.
3011 cprintf("%d %s\n", CIT_OK,
3012 ((valid_to != NULL) ? valid_to->display_recp : "") );
3019 /* We don't need these anymore because we'll do it differently below */
3024 /* Handle author masquerading */
3025 if (CC->fake_postname[0]) {
3026 strcpy(masquerade_as, CC->fake_postname);
3028 else if (CC->fake_username[0]) {
3029 strcpy(masquerade_as, CC->fake_username);
3032 strcpy(masquerade_as, "");
3035 /* Read in the message from the client. */
3037 cprintf("%d send message\n", START_CHAT_MODE);
3039 cprintf("%d send message\n", SEND_LISTING);
3042 msg = CtdlMakeMessage(&CC->user, recp, cc,
3043 CC->room.QRname, anonymous, format_type,
3044 masquerade_as, subject, NULL);
3046 /* Put together one big recipients struct containing to/cc/bcc all in
3047 * one. This is for the envelope.
3049 char *all_recps = malloc(SIZ * 3);
3050 strcpy(all_recps, recp);
3051 if (strlen(cc) > 0) {
3052 if (strlen(all_recps) > 0) {
3053 strcat(all_recps, ",");
3055 strcat(all_recps, cc);
3057 if (strlen(bcc) > 0) {
3058 if (strlen(all_recps) > 0) {
3059 strcat(all_recps, ",");
3061 strcat(all_recps, bcc);
3063 if (strlen(all_recps) > 0) {
3064 valid = validate_recipients(all_recps);
3072 msgnum = CtdlSubmitMsg(msg, valid, "");
3075 cprintf("%ld\n", msgnum);
3077 cprintf("Message accepted.\n");
3080 cprintf("Internal error.\n");
3082 if (msg->cm_fields['E'] != NULL) {
3083 cprintf("%s\n", msg->cm_fields['E']);
3090 CtdlFreeMessage(msg);
3092 CC->fake_postname[0] = '\0';
3093 if (valid != NULL) {
3102 * API function to delete messages which match a set of criteria
3103 * (returns the actual number of messages deleted)
3105 int CtdlDeleteMessages(char *room_name, /* which room */
3106 long dmsgnum, /* or "0" for any */
3107 char *content_type, /* or "" for any */
3108 int deferred /* let TDAP sweep it later */
3112 struct ctdlroom qrbuf;
3113 struct cdbdata *cdbfr;
3114 long *msglist = NULL;
3115 long *dellist = NULL;
3118 int num_deleted = 0;
3120 struct MetaData smi;
3122 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3123 room_name, dmsgnum, content_type, deferred);
3125 /* get room record, obtaining a lock... */
3126 if (lgetroom(&qrbuf, room_name) != 0) {
3127 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3129 return (0); /* room not found */
3131 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3133 if (cdbfr != NULL) {
3134 dellist = malloc(cdbfr->len);
3135 msglist = (long *) cdbfr->ptr;
3136 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3137 num_msgs = cdbfr->len / sizeof(long);
3141 for (i = 0; i < num_msgs; ++i) {
3144 /* Set/clear a bit for each criterion */
3146 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3147 delete_this |= 0x01;
3149 if (strlen(content_type) == 0) {
3150 delete_this |= 0x02;
3152 GetMetaData(&smi, msglist[i]);
3153 if (!strcasecmp(smi.meta_content_type,
3155 delete_this |= 0x02;
3159 /* Delete message only if all bits are set */
3160 if (delete_this == 0x03) {
3161 dellist[num_deleted++] = msglist[i];
3166 num_msgs = sort_msglist(msglist, num_msgs);
3167 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3168 msglist, (int)(num_msgs * sizeof(long)));
3170 qrbuf.QRhighest = msglist[num_msgs - 1];
3175 * If the delete operation is "deferred" (and technically, any delete
3176 * operation not performed by THE DREADED AUTO-PURGER ought to be
3177 * a deferred delete) then we save a pointer to the message in the
3178 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3179 * at least 1, which will save the user from having to synchronously
3180 * wait for various disk-intensive operations to complete.
3182 if ( (deferred) && (num_deleted) ) {
3183 for (i=0; i<num_deleted; ++i) {
3184 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3188 /* Go through the messages we pulled out of the index, and decrement
3189 * their reference counts by 1. If this is the only room the message
3190 * was in, the reference count will reach zero and the message will
3191 * automatically be deleted from the database. We do this in a
3192 * separate pass because there might be plug-in hooks getting called,
3193 * and we don't want that happening during an S_ROOMS critical
3196 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3197 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3198 AdjRefCount(dellist[i], -1);
3201 /* Now free the memory we used, and go away. */
3202 if (msglist != NULL) free(msglist);
3203 if (dellist != NULL) free(dellist);
3204 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3205 return (num_deleted);
3211 * Check whether the current user has permission to delete messages from
3212 * the current room (returns 1 for yes, 0 for no)
3214 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3215 getuser(&CC->user, CC->curr_user);
3216 if ((CC->user.axlevel < 6)
3217 && (CC->user.usernum != CC->room.QRroomaide)
3218 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3219 && (!(CC->internal_pgm))) {
3228 * Delete message from current room
3230 void cmd_dele(char *delstr)
3235 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3236 cprintf("%d Higher access required.\n",
3237 ERROR + HIGHER_ACCESS_REQUIRED);
3240 delnum = extract_long(delstr, 0);
3242 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3245 cprintf("%d %d message%s deleted.\n", CIT_OK,
3246 num_deleted, ((num_deleted != 1) ? "s" : ""));
3248 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3254 * Back end API function for moves and deletes
3256 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3259 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3260 if (err != 0) return(err);
3268 * move or copy a message to another room
3270 void cmd_move(char *args)
3273 char targ[ROOMNAMELEN];
3274 struct ctdlroom qtemp;
3280 num = extract_long(args, 0);
3281 extract_token(targ, args, 1, '|', sizeof targ);
3282 targ[ROOMNAMELEN - 1] = 0;
3283 is_copy = extract_int(args, 2);
3285 if (getroom(&qtemp, targ) != 0) {
3286 cprintf("%d '%s' does not exist.\n",
3287 ERROR + ROOM_NOT_FOUND, targ);
3291 getuser(&CC->user, CC->curr_user);
3292 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3294 /* Check for permission to perform this operation.
3295 * Remember: "CC->room" is source, "qtemp" is target.
3299 /* Aides can move/copy */
3300 if (CC->user.axlevel >= 6) permit = 1;
3302 /* Room aides can move/copy */
3303 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3305 /* Permit move/copy from personal rooms */
3306 if ((CC->room.QRflags & QR_MAILBOX)
3307 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3309 /* Permit only copy from public to personal room */
3311 && (!(CC->room.QRflags & QR_MAILBOX))
3312 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3314 /* User must have access to target room */
3315 if (!(ra & UA_KNOWN)) permit = 0;
3318 cprintf("%d Higher access required.\n",
3319 ERROR + HIGHER_ACCESS_REQUIRED);
3323 err = CtdlCopyMsgToRoom(num, targ);
3325 cprintf("%d Cannot store message in %s: error %d\n",
3330 /* Now delete the message from the source room,
3331 * if this is a 'move' rather than a 'copy' operation.
3334 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3337 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3343 * GetMetaData() - Get the supplementary record for a message
3345 void GetMetaData(struct MetaData *smibuf, long msgnum)
3348 struct cdbdata *cdbsmi;
3351 memset(smibuf, 0, sizeof(struct MetaData));
3352 smibuf->meta_msgnum = msgnum;
3353 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3355 /* Use the negative of the message number for its supp record index */
3356 TheIndex = (0L - msgnum);
3358 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3359 if (cdbsmi == NULL) {
3360 return; /* record not found; go with defaults */
3362 memcpy(smibuf, cdbsmi->ptr,
3363 ((cdbsmi->len > sizeof(struct MetaData)) ?
3364 sizeof(struct MetaData) : cdbsmi->len));
3371 * PutMetaData() - (re)write supplementary record for a message
3373 void PutMetaData(struct MetaData *smibuf)
3377 /* Use the negative of the message number for the metadata db index */
3378 TheIndex = (0L - smibuf->meta_msgnum);
3380 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3381 smibuf->meta_msgnum, smibuf->meta_refcount);
3383 cdb_store(CDB_MSGMAIN,
3384 &TheIndex, (int)sizeof(long),
3385 smibuf, (int)sizeof(struct MetaData));
3390 * AdjRefCount - change the reference count for a message;
3391 * delete the message if it reaches zero
3393 void AdjRefCount(long msgnum, int incr)
3396 struct MetaData smi;
3399 /* This is a *tight* critical section; please keep it that way, as
3400 * it may get called while nested in other critical sections.
3401 * Complicating this any further will surely cause deadlock!
3403 begin_critical_section(S_SUPPMSGMAIN);
3404 GetMetaData(&smi, msgnum);
3405 smi.meta_refcount += incr;
3407 end_critical_section(S_SUPPMSGMAIN);
3408 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3409 msgnum, incr, smi.meta_refcount);
3411 /* If the reference count is now zero, delete the message
3412 * (and its supplementary record as well).
3414 if (smi.meta_refcount == 0) {
3415 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3417 /* Remove from fulltext index */
3418 if (config.c_enable_fulltext) {
3419 ft_index_message(msgnum, 0);
3422 /* Remove from message base */
3424 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3425 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3427 /* Remove metadata record */
3428 delnum = (0L - msgnum);
3429 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3434 * Write a generic object to this room
3436 * Note: this could be much more efficient. Right now we use two temporary
3437 * files, and still pull the message into memory as with all others.
3439 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3440 char *content_type, /* MIME type of this object */
3441 char *tempfilename, /* Where to fetch it from */
3442 struct ctdluser *is_mailbox, /* Mailbox room? */
3443 int is_binary, /* Is encoding necessary? */
3444 int is_unique, /* Del others of this type? */
3445 unsigned int flags /* Internal save flags */
3450 struct ctdlroom qrbuf;
3451 char roomname[ROOMNAMELEN];
3452 struct CtdlMessage *msg;
3454 char *raw_message = NULL;
3455 char *encoded_message = NULL;
3456 off_t raw_length = 0;
3458 if (is_mailbox != NULL)
3459 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3461 safestrncpy(roomname, req_room, sizeof(roomname));
3462 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3465 fp = fopen(tempfilename, "rb");
3467 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3468 tempfilename, strerror(errno));
3471 fseek(fp, 0L, SEEK_END);
3472 raw_length = ftell(fp);
3474 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3476 raw_message = malloc((size_t)raw_length + 2);
3477 fread(raw_message, (size_t)raw_length, 1, fp);
3481 encoded_message = malloc((size_t)
3482 (((raw_length * 134) / 100) + 4096 ) );
3485 encoded_message = malloc((size_t)(raw_length + 4096));
3488 sprintf(encoded_message, "Content-type: %s\n", content_type);
3491 sprintf(&encoded_message[strlen(encoded_message)],
3492 "Content-transfer-encoding: base64\n\n"
3496 sprintf(&encoded_message[strlen(encoded_message)],
3497 "Content-transfer-encoding: 7bit\n\n"
3503 &encoded_message[strlen(encoded_message)],
3509 raw_message[raw_length] = 0;
3511 &encoded_message[strlen(encoded_message)],
3519 lprintf(CTDL_DEBUG, "Allocating\n");
3520 msg = malloc(sizeof(struct CtdlMessage));
3521 memset(msg, 0, sizeof(struct CtdlMessage));
3522 msg->cm_magic = CTDLMESSAGE_MAGIC;
3523 msg->cm_anon_type = MES_NORMAL;
3524 msg->cm_format_type = 4;
3525 msg->cm_fields['A'] = strdup(CC->user.fullname);
3526 msg->cm_fields['O'] = strdup(req_room);
3527 msg->cm_fields['N'] = strdup(config.c_nodename);
3528 msg->cm_fields['H'] = strdup(config.c_humannode);
3529 msg->cm_flags = flags;
3531 msg->cm_fields['M'] = encoded_message;
3533 /* Create the requested room if we have to. */
3534 if (getroom(&qrbuf, roomname) != 0) {
3535 create_room(roomname,
3536 ( (is_mailbox != NULL) ? 5 : 3 ),
3537 "", 0, 1, 0, VIEW_BBS);
3539 /* If the caller specified this object as unique, delete all
3540 * other objects of this type that are currently in the room.
3543 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3544 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3547 /* Now write the data */
3548 CtdlSubmitMsg(msg, NULL, roomname);
3549 CtdlFreeMessage(msg);
3557 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3558 config_msgnum = msgnum;
3562 char *CtdlGetSysConfig(char *sysconfname) {
3563 char hold_rm[ROOMNAMELEN];
3566 struct CtdlMessage *msg;
3569 strcpy(hold_rm, CC->room.QRname);
3570 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3571 getroom(&CC->room, hold_rm);
3576 /* We want the last (and probably only) config in this room */
3577 begin_critical_section(S_CONFIG);
3578 config_msgnum = (-1L);
3579 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3580 CtdlGetSysConfigBackend, NULL);
3581 msgnum = config_msgnum;
3582 end_critical_section(S_CONFIG);
3588 msg = CtdlFetchMessage(msgnum, 1);
3590 conf = strdup(msg->cm_fields['M']);
3591 CtdlFreeMessage(msg);
3598 getroom(&CC->room, hold_rm);
3600 if (conf != NULL) do {
3601 extract_token(buf, conf, 0, '\n', sizeof buf);
3602 strcpy(conf, &conf[strlen(buf)+1]);
3603 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3608 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3609 char temp[PATH_MAX];
3612 strcpy(temp, tmpnam(NULL));
3614 fp = fopen(temp, "w");
3615 if (fp == NULL) return;
3616 fprintf(fp, "%s", sysconfdata);
3619 /* this handy API function does all the work for us */
3620 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3626 * Determine whether a given Internet address belongs to the current user
3628 int CtdlIsMe(char *addr, int addr_buf_len)
3630 struct recptypes *recp;
3633 recp = validate_recipients(addr);
3634 if (recp == NULL) return(0);
3636 if (recp->num_local == 0) {
3641 for (i=0; i<recp->num_local; ++i) {
3642 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3643 if (!strcasecmp(addr, CC->user.fullname)) {
3655 * Citadel protocol command to do the same
3657 void cmd_isme(char *argbuf) {
3660 if (CtdlAccessCheck(ac_logged_in)) return;
3661 extract_token(addr, argbuf, 0, '|', sizeof addr);
3663 if (CtdlIsMe(addr, sizeof addr)) {
3664 cprintf("%d %s\n", CIT_OK, addr);
3667 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);