4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
38 #include "serv_extensions.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
54 #include "serv_fulltext.h"
56 #include "euidindex.h"
59 struct addresses_to_be_filed *atbf = NULL;
62 * This really belongs in serv_network.c, but I don't know how to export
63 * symbols between modules.
65 struct FilterList *filterlist = NULL;
69 * These are the four-character field headers we use when outputting
70 * messages in Citadel format (as opposed to RFC822 format).
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
107 * This function is self explanatory.
108 * (What can I say, I'm in a weird mood today...)
110 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
114 for (i = 0; i < strlen(name); ++i) {
115 if (name[i] == '@') {
116 while (isspace(name[i - 1]) && i > 0) {
117 strcpy(&name[i - 1], &name[i]);
120 while (isspace(name[i + 1])) {
121 strcpy(&name[i + 1], &name[i + 2]);
129 * Aliasing for network mail.
130 * (Error messages have been commented out, because this is a server.)
132 int alias(char *name)
133 { /* process alias and routing info for mail */
136 char aaa[SIZ], bbb[SIZ];
137 char *ignetcfg = NULL;
138 char *ignetmap = NULL;
145 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
146 stripallbut(name, '<', '>');
154 "mail.aliases", "r");
156 fp = fopen("/dev/null", "r");
163 while (fgets(aaa, sizeof aaa, fp) != NULL) {
164 while (isspace(name[0]))
165 strcpy(name, &name[1]);
166 aaa[strlen(aaa) - 1] = 0;
168 for (a = 0; a < strlen(aaa); ++a) {
170 strcpy(bbb, &aaa[a + 1]);
174 if (!strcasecmp(name, aaa))
179 /* Hit the Global Address Book */
180 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
184 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
186 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
187 for (a=0; a<strlen(name); ++a) {
188 if (name[a] == '@') {
189 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
191 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
196 /* determine local or remote type, see citadel.h */
197 at = haschar(name, '@');
198 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
199 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
200 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
202 /* figure out the delivery mode */
203 extract_token(node, name, 1, '@', sizeof node);
205 /* If there are one or more dots in the nodename, we assume that it
206 * is an FQDN and will attempt SMTP delivery to the Internet.
208 if (haschar(node, '.') > 0) {
209 return(MES_INTERNET);
212 /* Otherwise we look in the IGnet maps for a valid Citadel node.
213 * Try directly-connected nodes first...
215 ignetcfg = CtdlGetSysConfig(IGNETCFG);
216 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
217 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
218 extract_token(testnode, buf, 0, '|', sizeof testnode);
219 if (!strcasecmp(node, testnode)) {
227 * Then try nodes that are two or more hops away.
229 ignetmap = CtdlGetSysConfig(IGNETMAP);
230 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
231 extract_token(buf, ignetmap, i, '\n', sizeof buf);
232 extract_token(testnode, buf, 0, '|', sizeof testnode);
233 if (!strcasecmp(node, testnode)) {
240 /* If we get to this point it's an invalid node name */
255 "/citadel.control", "r");
257 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
261 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
267 * Back end for the MSGS command: output message number only.
269 void simple_listing(long msgnum, void *userdata)
271 cprintf("%ld\n", msgnum);
277 * Back end for the MSGS command: output header summary.
279 void headers_listing(long msgnum, void *userdata)
281 struct CtdlMessage *msg;
283 msg = CtdlFetchMessage(msgnum, 0);
285 cprintf("%ld|0|||||\n", msgnum);
289 cprintf("%ld|%s|%s|%s|%s|%s|\n",
291 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
292 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
293 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
294 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
295 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
297 CtdlFreeMessage(msg);
302 /* Determine if a given message matches the fields in a message template.
303 * Return 0 for a successful match.
305 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
308 /* If there aren't any fields in the template, all messages will
311 if (template == NULL) return(0);
313 /* Null messages are bogus. */
314 if (msg == NULL) return(1);
316 for (i='A'; i<='Z'; ++i) {
317 if (template->cm_fields[i] != NULL) {
318 if (msg->cm_fields[i] == NULL) {
321 if (strcasecmp(msg->cm_fields[i],
322 template->cm_fields[i])) return 1;
326 /* All compares succeeded: we have a match! */
333 * Retrieve the "seen" message list for the current room.
335 void CtdlGetSeen(char *buf, int which_set) {
338 /* Learn about the user and room in question */
339 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
341 if (which_set == ctdlsetseen_seen)
342 safestrncpy(buf, vbuf.v_seen, SIZ);
343 if (which_set == ctdlsetseen_answered)
344 safestrncpy(buf, vbuf.v_answered, SIZ);
350 * Manipulate the "seen msgs" string (or other message set strings)
352 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
353 int target_setting, int which_set,
354 struct ctdluser *which_user, struct ctdlroom *which_room) {
355 struct cdbdata *cdbfr;
367 char *is_set; /* actually an array of booleans */
370 char setstr[SIZ], lostr[SIZ], histr[SIZ];
373 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
374 num_target_msgnums, target_msgnums[0],
375 target_setting, which_set);
377 /* Learn about the user and room in question */
378 CtdlGetRelationship(&vbuf,
379 ((which_user != NULL) ? which_user : &CC->user),
380 ((which_room != NULL) ? which_room : &CC->room)
383 /* Load the message list */
384 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
386 msglist = (long *) cdbfr->ptr;
387 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
388 num_msgs = cdbfr->len / sizeof(long);
391 return; /* No messages at all? No further action. */
394 is_set = malloc(num_msgs * sizeof(char));
395 memset(is_set, 0, (num_msgs * sizeof(char)) );
397 /* Decide which message set we're manipulating */
399 case ctdlsetseen_seen:
400 safestrncpy(vset, vbuf.v_seen, sizeof vset);
402 case ctdlsetseen_answered:
403 safestrncpy(vset, vbuf.v_answered, sizeof vset);
407 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
409 /* Translate the existing sequence set into an array of booleans */
410 num_sets = num_tokens(vset, ',');
411 for (s=0; s<num_sets; ++s) {
412 extract_token(setstr, vset, s, ',', sizeof setstr);
414 extract_token(lostr, setstr, 0, ':', sizeof lostr);
415 if (num_tokens(setstr, ':') >= 2) {
416 extract_token(histr, setstr, 1, ':', sizeof histr);
417 if (!strcmp(histr, "*")) {
418 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
422 strcpy(histr, lostr);
427 for (i = 0; i < num_msgs; ++i) {
428 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
434 /* Now translate the array of booleans back into a sequence set */
439 for (i=0; i<num_msgs; ++i) {
441 is_seen = is_set[i]; /* Default to existing setting */
443 for (k=0; k<num_target_msgnums; ++k) {
444 if (msglist[i] == target_msgnums[k]) {
445 is_seen = target_setting;
450 if (lo < 0L) lo = msglist[i];
454 if ( ((is_seen == 0) && (was_seen == 1))
455 || ((is_seen == 1) && (i == num_msgs-1)) ) {
457 /* begin trim-o-matic code */
460 while ( (strlen(vset) + 20) > sizeof vset) {
461 remove_token(vset, 0, ',');
463 if (j--) break; /* loop no more than 9 times */
465 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
469 snprintf(lostr, sizeof lostr,
470 "1:%ld,%s", t, vset);
471 safestrncpy(vset, lostr, sizeof vset);
473 /* end trim-o-matic code */
481 snprintf(&vset[tmp], (sizeof vset) - tmp,
485 snprintf(&vset[tmp], (sizeof vset) - tmp,
494 /* Decide which message set we're manipulating */
496 case ctdlsetseen_seen:
497 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
499 case ctdlsetseen_answered:
500 safestrncpy(vbuf.v_answered, vset,
501 sizeof vbuf.v_answered);
506 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
508 CtdlSetRelationship(&vbuf,
509 ((which_user != NULL) ? which_user : &CC->user),
510 ((which_room != NULL) ? which_room : &CC->room)
516 * API function to perform an operation for each qualifying message in the
517 * current room. (Returns the number of messages processed.)
519 int CtdlForEachMessage(int mode, long ref,
521 struct CtdlMessage *compare,
522 void (*CallBack) (long, void *),
528 struct cdbdata *cdbfr;
529 long *msglist = NULL;
531 int num_processed = 0;
534 struct CtdlMessage *msg;
537 int printed_lastold = 0;
539 /* Learn about the user and room in question */
541 getuser(&CC->user, CC->curr_user);
542 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
544 /* Load the message list */
545 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
547 msglist = (long *) cdbfr->ptr;
548 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
549 num_msgs = cdbfr->len / sizeof(long);
552 return 0; /* No messages at all? No further action. */
557 * Now begin the traversal.
559 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
561 /* If the caller is looking for a specific MIME type, filter
562 * out all messages which are not of the type requested.
564 if (content_type != NULL) if (strlen(content_type) > 0) {
566 /* This call to GetMetaData() sits inside this loop
567 * so that we only do the extra database read per msg
568 * if we need to. Doing the extra read all the time
569 * really kills the server. If we ever need to use
570 * metadata for another search criterion, we need to
571 * move the read somewhere else -- but still be smart
572 * enough to only do the read if the caller has
573 * specified something that will need it.
575 GetMetaData(&smi, msglist[a]);
577 if (strcasecmp(smi.meta_content_type, content_type)) {
583 num_msgs = sort_msglist(msglist, num_msgs);
585 /* If a template was supplied, filter out the messages which
586 * don't match. (This could induce some delays!)
589 if (compare != NULL) {
590 for (a = 0; a < num_msgs; ++a) {
591 msg = CtdlFetchMessage(msglist[a], 1);
593 if (CtdlMsgCmp(msg, compare)) {
596 CtdlFreeMessage(msg);
604 * Now iterate through the message list, according to the
605 * criteria supplied by the caller.
608 for (a = 0; a < num_msgs; ++a) {
609 thismsg = msglist[a];
610 if (mode == MSGS_ALL) {
614 is_seen = is_msg_in_sequence_set(
615 vbuf.v_seen, thismsg);
616 if (is_seen) lastold = thismsg;
622 || ((mode == MSGS_OLD) && (is_seen))
623 || ((mode == MSGS_NEW) && (!is_seen))
624 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
625 || ((mode == MSGS_FIRST) && (a < ref))
626 || ((mode == MSGS_GT) && (thismsg > ref))
627 || ((mode == MSGS_EQ) && (thismsg == ref))
630 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
632 CallBack(lastold, userdata);
636 if (CallBack) CallBack(thismsg, userdata);
640 free(msglist); /* Clean up */
641 return num_processed;
647 * cmd_msgs() - get list of message #'s in this room
648 * implements the MSGS server command using CtdlForEachMessage()
650 void cmd_msgs(char *cmdbuf)
659 int with_template = 0;
660 struct CtdlMessage *template = NULL;
661 int with_headers = 0;
663 extract_token(which, cmdbuf, 0, '|', sizeof which);
664 cm_ref = extract_int(cmdbuf, 1);
665 with_template = extract_int(cmdbuf, 2);
666 with_headers = extract_int(cmdbuf, 3);
670 if (!strncasecmp(which, "OLD", 3))
672 else if (!strncasecmp(which, "NEW", 3))
674 else if (!strncasecmp(which, "FIRST", 5))
676 else if (!strncasecmp(which, "LAST", 4))
678 else if (!strncasecmp(which, "GT", 2))
681 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
682 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
688 cprintf("%d Send template then receive message list\n",
690 template = (struct CtdlMessage *)
691 malloc(sizeof(struct CtdlMessage));
692 memset(template, 0, sizeof(struct CtdlMessage));
693 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
694 extract_token(tfield, buf, 0, '|', sizeof tfield);
695 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
696 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
697 if (!strcasecmp(tfield, msgkeys[i])) {
698 template->cm_fields[i] =
706 cprintf("%d \n", LISTING_FOLLOWS);
709 CtdlForEachMessage(mode,
713 (with_headers ? headers_listing : simple_listing),
716 if (template != NULL) CtdlFreeMessage(template);
724 * help_subst() - support routine for help file viewer
726 void help_subst(char *strbuf, char *source, char *dest)
731 while (p = pattern2(strbuf, source), (p >= 0)) {
732 strcpy(workbuf, &strbuf[p + strlen(source)]);
733 strcpy(&strbuf[p], dest);
734 strcat(strbuf, workbuf);
739 void do_help_subst(char *buffer)
743 help_subst(buffer, "^nodename", config.c_nodename);
744 help_subst(buffer, "^humannode", config.c_humannode);
745 help_subst(buffer, "^fqdn", config.c_fqdn);
746 help_subst(buffer, "^username", CC->user.fullname);
747 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
748 help_subst(buffer, "^usernum", buf2);
749 help_subst(buffer, "^sysadm", config.c_sysadm);
750 help_subst(buffer, "^variantname", CITADEL);
751 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
752 help_subst(buffer, "^maxsessions", buf2);
753 help_subst(buffer, "^bbsdir", CTDLDIR);
759 * memfmout() - Citadel text formatter and paginator.
760 * Although the original purpose of this routine was to format
761 * text to the reader's screen width, all we're really using it
762 * for here is to format text out to 80 columns before sending it
763 * to the client. The client software may reformat it again.
766 int width, /* screen width to use */
767 char *mptr, /* where are we going to get our text from? */
768 char subst, /* nonzero if we should do substitutions */
769 char *nl) /* string to terminate lines with */
781 c = 1; /* c is the current pos */
785 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
787 buffer[strlen(buffer) + 1] = 0;
788 buffer[strlen(buffer)] = ch;
791 if (buffer[0] == '^')
792 do_help_subst(buffer);
794 buffer[strlen(buffer) + 1] = 0;
796 strcpy(buffer, &buffer[1]);
804 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
806 if (((old == 13) || (old == 10)) && (isspace(real))) {
814 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
815 cprintf("%s%s", nl, aaa);
824 if ((strlen(aaa) + c) > (width - 5)) {
833 if ((ch == 13) || (ch == 10)) {
834 cprintf("%s%s", aaa, nl);
841 cprintf("%s%s", aaa, nl);
847 * Callback function for mime parser that simply lists the part
849 void list_this_part(char *name, char *filename, char *partnum, char *disp,
850 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
855 ma = (struct ma_info *)cbuserdata;
856 if (ma->is_ma == 0) {
857 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
858 name, filename, partnum, disp, cbtype, (long)length);
863 * Callback function for multipart prefix
865 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
866 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
871 ma = (struct ma_info *)cbuserdata;
872 if (!strcasecmp(cbtype, "multipart/alternative")) {
876 if (ma->is_ma == 0) {
877 cprintf("pref=%s|%s\n", partnum, cbtype);
882 * Callback function for multipart sufffix
884 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
885 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
890 ma = (struct ma_info *)cbuserdata;
891 if (ma->is_ma == 0) {
892 cprintf("suff=%s|%s\n", partnum, cbtype);
894 if (!strcasecmp(cbtype, "multipart/alternative")) {
901 * Callback function for mime parser that opens a section for downloading
903 void mime_download(char *name, char *filename, char *partnum, char *disp,
904 void *content, char *cbtype, char *cbcharset, size_t length,
905 char *encoding, void *cbuserdata)
908 /* Silently go away if there's already a download open... */
909 if (CC->download_fp != NULL)
912 /* ...or if this is not the desired section */
913 if (strcasecmp(CC->download_desired_section, partnum))
916 CC->download_fp = tmpfile();
917 if (CC->download_fp == NULL)
920 fwrite(content, length, 1, CC->download_fp);
921 fflush(CC->download_fp);
922 rewind(CC->download_fp);
924 OpenCmdResult(filename, cbtype);
930 * Load a message from disk into memory.
931 * This is used by CtdlOutputMsg() and other fetch functions.
933 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
934 * using the CtdlMessageFree() function.
936 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
938 struct cdbdata *dmsgtext;
939 struct CtdlMessage *ret = NULL;
943 cit_uint8_t field_header;
945 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
947 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
948 if (dmsgtext == NULL) {
951 mptr = dmsgtext->ptr;
952 upper_bound = mptr + dmsgtext->len;
954 /* Parse the three bytes that begin EVERY message on disk.
955 * The first is always 0xFF, the on-disk magic number.
956 * The second is the anonymous/public type byte.
957 * The third is the format type byte (vari, fixed, or MIME).
962 "Message %ld appears to be corrupted.\n",
967 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
968 memset(ret, 0, sizeof(struct CtdlMessage));
970 ret->cm_magic = CTDLMESSAGE_MAGIC;
971 ret->cm_anon_type = *mptr++; /* Anon type byte */
972 ret->cm_format_type = *mptr++; /* Format type byte */
975 * The rest is zero or more arbitrary fields. Load them in.
976 * We're done when we encounter either a zero-length field or
977 * have just processed the 'M' (message text) field.
980 if (mptr >= upper_bound) {
983 field_header = *mptr++;
984 ret->cm_fields[field_header] = strdup(mptr);
986 while (*mptr++ != 0); /* advance to next field */
988 } while ((mptr < upper_bound) && (field_header != 'M'));
992 /* Always make sure there's something in the msg text field. If
993 * it's NULL, the message text is most likely stored separately,
994 * so go ahead and fetch that. Failing that, just set a dummy
995 * body so other code doesn't barf.
997 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
998 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
999 if (dmsgtext != NULL) {
1000 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1004 if (ret->cm_fields['M'] == NULL) {
1005 ret->cm_fields['M'] = strdup("<no text>\n");
1008 /* Perform "before read" hooks (aborting if any return nonzero) */
1009 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1010 CtdlFreeMessage(ret);
1019 * Returns 1 if the supplied pointer points to a valid Citadel message.
1020 * If the pointer is NULL or the magic number check fails, returns 0.
1022 int is_valid_message(struct CtdlMessage *msg) {
1025 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1026 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1034 * 'Destructor' for struct CtdlMessage
1036 void CtdlFreeMessage(struct CtdlMessage *msg)
1040 if (is_valid_message(msg) == 0) return;
1042 for (i = 0; i < 256; ++i)
1043 if (msg->cm_fields[i] != NULL) {
1044 free(msg->cm_fields[i]);
1047 msg->cm_magic = 0; /* just in case */
1053 * Pre callback function for multipart/alternative
1055 * NOTE: this differs from the standard behavior for a reason. Normally when
1056 * displaying multipart/alternative you want to show the _last_ usable
1057 * format in the message. Here we show the _first_ one, because it's
1058 * usually text/plain. Since this set of functions is designed for text
1059 * output to non-MIME-aware clients, this is the desired behavior.
1062 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1063 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1068 ma = (struct ma_info *)cbuserdata;
1069 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1070 if (!strcasecmp(cbtype, "multipart/alternative")) {
1078 * Post callback function for multipart/alternative
1080 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1081 void *content, char *cbtype, char *cbcharset, size_t length,
1082 char *encoding, void *cbuserdata)
1086 ma = (struct ma_info *)cbuserdata;
1087 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1088 if (!strcasecmp(cbtype, "multipart/alternative")) {
1096 * Inline callback function for mime parser that wants to display text
1098 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1099 void *content, char *cbtype, char *cbcharset, size_t length,
1100 char *encoding, void *cbuserdata)
1107 ma = (struct ma_info *)cbuserdata;
1110 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1111 partnum, filename, cbtype, (long)length);
1114 * If we're in the middle of a multipart/alternative scope and
1115 * we've already printed another section, skip this one.
1117 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1118 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1124 if ( (!strcasecmp(cbtype, "text/plain"))
1125 || (strlen(cbtype)==0) ) {
1128 client_write(wptr, length);
1129 if (wptr[length-1] != '\n') {
1134 else if (!strcasecmp(cbtype, "text/html")) {
1135 ptr = html_to_ascii(content, length, 80, 0);
1137 client_write(ptr, wlen);
1138 if (ptr[wlen-1] != '\n') {
1143 else if (strncasecmp(cbtype, "multipart/", 10)) {
1144 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1145 partnum, filename, cbtype, (long)length);
1150 * The client is elegant and sophisticated and wants to be choosy about
1151 * MIME content types, so figure out which multipart/alternative part
1152 * we're going to send.
1154 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1155 void *content, char *cbtype, char *cbcharset, size_t length,
1156 char *encoding, void *cbuserdata)
1162 ma = (struct ma_info *)cbuserdata;
1164 if (ma->is_ma > 0) {
1165 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1166 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1167 if (!strcasecmp(buf, cbtype)) {
1168 strcpy(ma->chosen_part, partnum);
1175 * Now that we've chosen our preferred part, output it.
1177 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1178 void *content, char *cbtype, char *cbcharset, size_t length,
1179 char *encoding, void *cbuserdata)
1183 int add_newline = 0;
1187 ma = (struct ma_info *)cbuserdata;
1189 /* This is not the MIME part you're looking for... */
1190 if (strcasecmp(partnum, ma->chosen_part)) return;
1192 /* If the content-type of this part is in our preferred formats
1193 * list, we can simply output it verbatim.
1195 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1196 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1197 if (!strcasecmp(buf, cbtype)) {
1198 /* Yeah! Go! W00t!! */
1200 text_content = (char *)content;
1201 if (text_content[length-1] != '\n') {
1205 cprintf("Content-type: %s", cbtype);
1206 if (strlen(cbcharset) > 0) {
1207 cprintf("; charset=%s", cbcharset);
1209 cprintf("\nContent-length: %d\n",
1210 (int)(length + add_newline) );
1211 if (strlen(encoding) > 0) {
1212 cprintf("Content-transfer-encoding: %s\n", encoding);
1215 cprintf("Content-transfer-encoding: 7bit\n");
1218 client_write(content, length);
1219 if (add_newline) cprintf("\n");
1224 /* No translations required or possible: output as text/plain */
1225 cprintf("Content-type: text/plain\n\n");
1226 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1227 length, encoding, cbuserdata);
1232 * Get a message off disk. (returns om_* values found in msgbase.h)
1235 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1236 int mode, /* how would you like that message? */
1237 int headers_only, /* eschew the message body? */
1238 int do_proto, /* do Citadel protocol responses? */
1239 int crlf /* Use CRLF newlines instead of LF? */
1241 struct CtdlMessage *TheMessage = NULL;
1242 int retcode = om_no_such_msg;
1244 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1247 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1248 if (do_proto) cprintf("%d Not logged in.\n",
1249 ERROR + NOT_LOGGED_IN);
1250 return(om_not_logged_in);
1253 /* FIXME: check message id against msglist for this room */
1256 * Fetch the message from disk. If we're in any sort of headers
1257 * only mode, request that we don't even bother loading the body
1260 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1261 TheMessage = CtdlFetchMessage(msg_num, 0);
1264 TheMessage = CtdlFetchMessage(msg_num, 1);
1267 if (TheMessage == NULL) {
1268 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1269 ERROR + MESSAGE_NOT_FOUND, msg_num);
1270 return(om_no_such_msg);
1273 retcode = CtdlOutputPreLoadedMsg(
1275 headers_only, do_proto, crlf);
1277 CtdlFreeMessage(TheMessage);
1284 * Get a message off disk. (returns om_* values found in msgbase.h)
1287 int CtdlOutputPreLoadedMsg(
1288 struct CtdlMessage *TheMessage,
1289 int mode, /* how would you like that message? */
1290 int headers_only, /* eschew the message body? */
1291 int do_proto, /* do Citadel protocol responses? */
1292 int crlf /* Use CRLF newlines instead of LF? */
1298 char display_name[256];
1300 char *nl; /* newline string */
1302 int subject_found = 0;
1305 /* Buffers needed for RFC822 translation. These are all filled
1306 * using functions that are bounds-checked, and therefore we can
1307 * make them substantially smaller than SIZ.
1315 char datestamp[100];
1317 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1318 ((TheMessage == NULL) ? "NULL" : "not null"),
1319 mode, headers_only, do_proto, crlf);
1321 strcpy(mid, "unknown");
1322 nl = (crlf ? "\r\n" : "\n");
1324 if (!is_valid_message(TheMessage)) {
1326 "ERROR: invalid preloaded message for output\n");
1327 return(om_no_such_msg);
1330 /* Are we downloading a MIME component? */
1331 if (mode == MT_DOWNLOAD) {
1332 if (TheMessage->cm_format_type != FMT_RFC822) {
1334 cprintf("%d This is not a MIME message.\n",
1335 ERROR + ILLEGAL_VALUE);
1336 } else if (CC->download_fp != NULL) {
1337 if (do_proto) cprintf(
1338 "%d You already have a download open.\n",
1339 ERROR + RESOURCE_BUSY);
1341 /* Parse the message text component */
1342 mptr = TheMessage->cm_fields['M'];
1343 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1344 /* If there's no file open by this time, the requested
1345 * section wasn't found, so print an error
1347 if (CC->download_fp == NULL) {
1348 if (do_proto) cprintf(
1349 "%d Section %s not found.\n",
1350 ERROR + FILE_NOT_FOUND,
1351 CC->download_desired_section);
1354 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1357 /* now for the user-mode message reading loops */
1358 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1360 /* Does the caller want to skip the headers? */
1361 if (headers_only == HEADERS_NONE) goto START_TEXT;
1363 /* Tell the client which format type we're using. */
1364 if ( (mode == MT_CITADEL) && (do_proto) ) {
1365 cprintf("type=%d\n", TheMessage->cm_format_type);
1368 /* nhdr=yes means that we're only displaying headers, no body */
1369 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1370 && (mode == MT_CITADEL)
1373 cprintf("nhdr=yes\n");
1376 /* begin header processing loop for Citadel message format */
1378 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1380 safestrncpy(display_name, "<unknown>", sizeof display_name);
1381 if (TheMessage->cm_fields['A']) {
1382 strcpy(buf, TheMessage->cm_fields['A']);
1383 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1384 safestrncpy(display_name, "****", sizeof display_name);
1386 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1387 safestrncpy(display_name, "anonymous", sizeof display_name);
1390 safestrncpy(display_name, buf, sizeof display_name);
1392 if ((is_room_aide())
1393 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1394 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1395 size_t tmp = strlen(display_name);
1396 snprintf(&display_name[tmp],
1397 sizeof display_name - tmp,
1402 /* Don't show Internet address for users on the
1403 * local Citadel network.
1406 if (TheMessage->cm_fields['N'] != NULL)
1407 if (strlen(TheMessage->cm_fields['N']) > 0)
1408 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1412 /* Now spew the header fields in the order we like them. */
1413 safestrncpy(allkeys, FORDER, sizeof allkeys);
1414 for (i=0; i<strlen(allkeys); ++i) {
1415 k = (int) allkeys[i];
1417 if ( (TheMessage->cm_fields[k] != NULL)
1418 && (msgkeys[k] != NULL) ) {
1420 if (do_proto) cprintf("%s=%s\n",
1424 else if ((k == 'F') && (suppress_f)) {
1427 /* Masquerade display name if needed */
1429 if (do_proto) cprintf("%s=%s\n",
1431 TheMessage->cm_fields[k]
1440 /* begin header processing loop for RFC822 transfer format */
1445 strcpy(snode, NODENAME);
1446 strcpy(lnode, HUMANNODE);
1447 if (mode == MT_RFC822) {
1448 for (i = 0; i < 256; ++i) {
1449 if (TheMessage->cm_fields[i]) {
1450 mptr = TheMessage->cm_fields[i];
1453 safestrncpy(luser, mptr, sizeof luser);
1454 safestrncpy(suser, mptr, sizeof suser);
1456 else if (i == 'Y') {
1457 cprintf("CC: %s%s", mptr, nl);
1459 else if (i == 'U') {
1460 cprintf("Subject: %s%s", mptr, nl);
1464 safestrncpy(mid, mptr, sizeof mid);
1466 safestrncpy(lnode, mptr, sizeof lnode);
1468 safestrncpy(fuser, mptr, sizeof fuser);
1469 /* else if (i == 'O')
1470 cprintf("X-Citadel-Room: %s%s",
1473 safestrncpy(snode, mptr, sizeof snode);
1475 cprintf("To: %s%s", mptr, nl);
1476 else if (i == 'T') {
1477 datestring(datestamp, sizeof datestamp,
1478 atol(mptr), DATESTRING_RFC822);
1479 cprintf("Date: %s%s", datestamp, nl);
1483 if (subject_found == 0) {
1484 cprintf("Subject: (no subject)%s", nl);
1488 for (i=0; i<strlen(suser); ++i) {
1489 suser[i] = tolower(suser[i]);
1490 if (!isalnum(suser[i])) suser[i]='_';
1493 if (mode == MT_RFC822) {
1494 if (!strcasecmp(snode, NODENAME)) {
1495 safestrncpy(snode, FQDN, sizeof snode);
1498 /* Construct a fun message id */
1499 cprintf("Message-ID: <%s", mid);
1500 if (strchr(mid, '@')==NULL) {
1501 cprintf("@%s", snode);
1505 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1506 cprintf("From: \"----\" <x@x.org>%s", nl);
1508 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1509 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1511 else if (strlen(fuser) > 0) {
1512 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1515 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1518 cprintf("Organization: %s%s", lnode, nl);
1520 /* Blank line signifying RFC822 end-of-headers */
1521 if (TheMessage->cm_format_type != FMT_RFC822) {
1526 /* end header processing loop ... at this point, we're in the text */
1528 if (headers_only == HEADERS_FAST) goto DONE;
1529 mptr = TheMessage->cm_fields['M'];
1531 /* Tell the client about the MIME parts in this message */
1532 if (TheMessage->cm_format_type == FMT_RFC822) {
1533 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1534 memset(&ma, 0, sizeof(struct ma_info));
1535 mime_parser(mptr, NULL,
1536 (do_proto ? *list_this_part : NULL),
1537 (do_proto ? *list_this_pref : NULL),
1538 (do_proto ? *list_this_suff : NULL),
1541 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1542 char *start_of_text = NULL;
1543 start_of_text = strstr(mptr, "\n\r\n");
1544 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1545 if (start_of_text == NULL) start_of_text = mptr;
1547 start_of_text = strstr(start_of_text, "\n");
1549 while (ch=*mptr, ch!=0) {
1553 else switch(headers_only) {
1555 if (mptr >= start_of_text) {
1556 if (ch == 10) cprintf("%s", nl);
1557 else cprintf("%c", ch);
1561 if (mptr < start_of_text) {
1562 if (ch == 10) cprintf("%s", nl);
1563 else cprintf("%c", ch);
1567 if (ch == 10) cprintf("%s", nl);
1568 else cprintf("%c", ch);
1577 if (headers_only == HEADERS_ONLY) {
1581 /* signify start of msg text */
1582 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1583 if (do_proto) cprintf("text\n");
1586 /* If the format type on disk is 1 (fixed-format), then we want
1587 * everything to be output completely literally ... regardless of
1588 * what message transfer format is in use.
1590 if (TheMessage->cm_format_type == FMT_FIXED) {
1591 if (mode == MT_MIME) {
1592 cprintf("Content-type: text/plain\n\n");
1595 while (ch = *mptr++, ch > 0) {
1598 if ((ch == 10) || (strlen(buf) > 250)) {
1599 cprintf("%s%s", buf, nl);
1602 buf[strlen(buf) + 1] = 0;
1603 buf[strlen(buf)] = ch;
1606 if (strlen(buf) > 0)
1607 cprintf("%s%s", buf, nl);
1610 /* If the message on disk is format 0 (Citadel vari-format), we
1611 * output using the formatter at 80 columns. This is the final output
1612 * form if the transfer format is RFC822, but if the transfer format
1613 * is Citadel proprietary, it'll still work, because the indentation
1614 * for new paragraphs is correct and the client will reformat the
1615 * message to the reader's screen width.
1617 if (TheMessage->cm_format_type == FMT_CITADEL) {
1618 if (mode == MT_MIME) {
1619 cprintf("Content-type: text/x-citadel-variformat\n\n");
1621 memfmout(80, mptr, 0, nl);
1624 /* If the message on disk is format 4 (MIME), we've gotta hand it
1625 * off to the MIME parser. The client has already been told that
1626 * this message is format 1 (fixed format), so the callback function
1627 * we use will display those parts as-is.
1629 if (TheMessage->cm_format_type == FMT_RFC822) {
1630 memset(&ma, 0, sizeof(struct ma_info));
1632 if (mode == MT_MIME) {
1633 strcpy(ma.chosen_part, "1");
1634 mime_parser(mptr, NULL,
1635 *choose_preferred, *fixed_output_pre,
1636 *fixed_output_post, (void *)&ma, 0);
1637 mime_parser(mptr, NULL,
1638 *output_preferred, NULL, NULL, (void *)&ma, 0);
1641 mime_parser(mptr, NULL,
1642 *fixed_output, *fixed_output_pre,
1643 *fixed_output_post, (void *)&ma, 0);
1648 DONE: /* now we're done */
1649 if (do_proto) cprintf("000\n");
1656 * display a message (mode 0 - Citadel proprietary)
1658 void cmd_msg0(char *cmdbuf)
1661 int headers_only = HEADERS_ALL;
1663 msgid = extract_long(cmdbuf, 0);
1664 headers_only = extract_int(cmdbuf, 1);
1666 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1672 * display a message (mode 2 - RFC822)
1674 void cmd_msg2(char *cmdbuf)
1677 int headers_only = HEADERS_ALL;
1679 msgid = extract_long(cmdbuf, 0);
1680 headers_only = extract_int(cmdbuf, 1);
1682 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1688 * display a message (mode 3 - IGnet raw format - internal programs only)
1690 void cmd_msg3(char *cmdbuf)
1693 struct CtdlMessage *msg;
1696 if (CC->internal_pgm == 0) {
1697 cprintf("%d This command is for internal programs only.\n",
1698 ERROR + HIGHER_ACCESS_REQUIRED);
1702 msgnum = extract_long(cmdbuf, 0);
1703 msg = CtdlFetchMessage(msgnum, 1);
1705 cprintf("%d Message %ld not found.\n",
1706 ERROR + MESSAGE_NOT_FOUND, msgnum);
1710 serialize_message(&smr, msg);
1711 CtdlFreeMessage(msg);
1714 cprintf("%d Unable to serialize message\n",
1715 ERROR + INTERNAL_ERROR);
1719 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1720 client_write((char *)smr.ser, (int)smr.len);
1727 * Display a message using MIME content types
1729 void cmd_msg4(char *cmdbuf)
1733 msgid = extract_long(cmdbuf, 0);
1734 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1740 * Client tells us its preferred message format(s)
1742 void cmd_msgp(char *cmdbuf)
1744 safestrncpy(CC->preferred_formats, cmdbuf,
1745 sizeof(CC->preferred_formats));
1746 cprintf("%d ok\n", CIT_OK);
1751 * Open a component of a MIME message as a download file
1753 void cmd_opna(char *cmdbuf)
1756 char desired_section[128];
1758 msgid = extract_long(cmdbuf, 0);
1759 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1760 safestrncpy(CC->download_desired_section, desired_section,
1761 sizeof CC->download_desired_section);
1762 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1767 * Save a message pointer into a specified room
1768 * (Returns 0 for success, nonzero for failure)
1769 * roomname may be NULL to use the current room
1771 * Note that the 'supplied_msg' field may be set to NULL, in which case
1772 * the message will be fetched from disk, by number, if we need to perform
1773 * replication checks. This adds an additional database read, so if the
1774 * caller already has the message in memory then it should be supplied.
1776 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1777 struct CtdlMessage *supplied_msg) {
1779 char hold_rm[ROOMNAMELEN];
1780 struct cdbdata *cdbfr;
1783 long highest_msg = 0L;
1784 struct CtdlMessage *msg = NULL;
1786 /*lprintf(CTDL_DEBUG,
1787 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1788 roomname, msgid, do_repl_check);*/
1790 strcpy(hold_rm, CC->room.QRname);
1792 /* Now the regular stuff */
1793 if (lgetroom(&CC->room,
1794 ((roomname != NULL) ? roomname : CC->room.QRname) )
1796 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1797 return(ERROR + ROOM_NOT_FOUND);
1800 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1801 if (cdbfr == NULL) {
1805 msglist = (long *) cdbfr->ptr;
1806 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1807 num_msgs = cdbfr->len / sizeof(long);
1811 /* Make sure the message doesn't already exist in this room. It
1812 * is absolutely taboo to have more than one reference to the same
1813 * message in a room.
1815 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1816 if (msglist[i] == msgid) {
1817 lputroom(&CC->room); /* unlock the room */
1818 getroom(&CC->room, hold_rm);
1820 return(ERROR + ALREADY_EXISTS);
1824 /* Now add the new message */
1826 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1828 if (msglist == NULL) {
1829 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1831 msglist[num_msgs - 1] = msgid;
1833 /* Sort the message list, so all the msgid's are in order */
1834 num_msgs = sort_msglist(msglist, num_msgs);
1836 /* Determine the highest message number */
1837 highest_msg = msglist[num_msgs - 1];
1839 /* Write it back to disk. */
1840 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1841 msglist, (int)(num_msgs * sizeof(long)));
1843 /* Free up the memory we used. */
1846 /* Update the highest-message pointer and unlock the room. */
1847 CC->room.QRhighest = highest_msg;
1848 lputroom(&CC->room);
1850 /* Perform replication checks if necessary */
1851 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1852 if (supplied_msg != NULL) {
1856 msg = CtdlFetchMessage(msgid, 0);
1860 ReplicationChecks(msg);
1865 /* If the message has an Exclusive ID, index that... */
1867 if (msg->cm_fields['E'] != NULL) {
1868 index_message_by_euid(msg->cm_fields['E'],
1873 /* Free up the memory we may have allocated */
1874 if ( (msg != NULL) && (msg != supplied_msg) ) {
1875 CtdlFreeMessage(msg);
1878 /* Go back to the room we were in before we wandered here... */
1879 getroom(&CC->room, hold_rm);
1881 /* Bump the reference count for this message. */
1882 AdjRefCount(msgid, +1);
1884 /* Return success. */
1891 * Message base operation to save a new message to the message store
1892 * (returns new message number)
1894 * This is the back end for CtdlSubmitMsg() and should not be directly
1895 * called by server-side modules.
1898 long send_message(struct CtdlMessage *msg) {
1906 /* Get a new message number */
1907 newmsgid = get_new_message_number();
1908 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1910 /* Generate an ID if we don't have one already */
1911 if (msg->cm_fields['I']==NULL) {
1912 msg->cm_fields['I'] = strdup(msgidbuf);
1915 /* If the message is big, set its body aside for storage elsewhere */
1916 if (msg->cm_fields['M'] != NULL) {
1917 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1919 holdM = msg->cm_fields['M'];
1920 msg->cm_fields['M'] = NULL;
1924 /* Serialize our data structure for storage in the database */
1925 serialize_message(&smr, msg);
1928 msg->cm_fields['M'] = holdM;
1932 cprintf("%d Unable to serialize message\n",
1933 ERROR + INTERNAL_ERROR);
1937 /* Write our little bundle of joy into the message base */
1938 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1939 smr.ser, smr.len) < 0) {
1940 lprintf(CTDL_ERR, "Can't store message\n");
1944 cdb_store(CDB_BIGMSGS,
1954 /* Free the memory we used for the serialized message */
1957 /* Return the *local* message ID to the caller
1958 * (even if we're storing an incoming network message)
1966 * Serialize a struct CtdlMessage into the format used on disk and network.
1968 * This function loads up a "struct ser_ret" (defined in server.h) which
1969 * contains the length of the serialized message and a pointer to the
1970 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1972 void serialize_message(struct ser_ret *ret, /* return values */
1973 struct CtdlMessage *msg) /* unserialized msg */
1977 static char *forder = FORDER;
1979 if (is_valid_message(msg) == 0) return; /* self check */
1982 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1983 ret->len = ret->len +
1984 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1986 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1987 ret->ser = malloc(ret->len);
1988 if (ret->ser == NULL) {
1994 ret->ser[1] = msg->cm_anon_type;
1995 ret->ser[2] = msg->cm_format_type;
1998 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1999 ret->ser[wlen++] = (char)forder[i];
2000 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2001 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2003 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2004 (long)ret->len, (long)wlen);
2012 * Check to see if any messages already exist in the current room which
2013 * carry the same Exclusive ID as this one. If any are found, delete them.
2015 void ReplicationChecks(struct CtdlMessage *msg) {
2016 long old_msgnum = (-1L);
2018 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2020 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2023 /* No exclusive id? Don't do anything. */
2024 if (msg == NULL) return;
2025 if (msg->cm_fields['E'] == NULL) return;
2026 if (strlen(msg->cm_fields['E']) == 0) return;
2027 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2028 msg->cm_fields['E'], CC->room.QRname);*/
2030 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2031 if (old_msgnum > 0L) {
2032 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2033 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2040 * Save a message to disk and submit it into the delivery system.
2042 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2043 struct recptypes *recps, /* recipients (if mail) */
2044 char *force /* force a particular room? */
2046 char submit_filename[128];
2047 char generated_timestamp[32];
2048 char hold_rm[ROOMNAMELEN];
2049 char actual_rm[ROOMNAMELEN];
2050 char force_room[ROOMNAMELEN];
2051 char content_type[SIZ]; /* We have to learn this */
2052 char recipient[SIZ];
2055 struct ctdluser userbuf;
2057 struct MetaData smi;
2058 FILE *network_fp = NULL;
2059 static int seqnum = 1;
2060 struct CtdlMessage *imsg = NULL;
2063 char *hold_R, *hold_D;
2064 char *collected_addresses = NULL;
2065 struct addresses_to_be_filed *aptr = NULL;
2067 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2068 if (is_valid_message(msg) == 0) return(-1); /* self check */
2070 /* If this message has no timestamp, we take the liberty of
2071 * giving it one, right now.
2073 if (msg->cm_fields['T'] == NULL) {
2074 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2075 msg->cm_fields['T'] = strdup(generated_timestamp);
2078 /* If this message has no path, we generate one.
2080 if (msg->cm_fields['P'] == NULL) {
2081 if (msg->cm_fields['A'] != NULL) {
2082 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2083 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2084 if (isspace(msg->cm_fields['P'][a])) {
2085 msg->cm_fields['P'][a] = ' ';
2090 msg->cm_fields['P'] = strdup("unknown");
2094 if (force == NULL) {
2095 strcpy(force_room, "");
2098 strcpy(force_room, force);
2101 /* Learn about what's inside, because it's what's inside that counts */
2102 if (msg->cm_fields['M'] == NULL) {
2103 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2107 switch (msg->cm_format_type) {
2109 strcpy(content_type, "text/x-citadel-variformat");
2112 strcpy(content_type, "text/plain");
2115 strcpy(content_type, "text/plain");
2116 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2118 safestrncpy(content_type, &mptr[14],
2119 sizeof content_type);
2120 for (a = 0; a < strlen(content_type); ++a) {
2121 if ((content_type[a] == ';')
2122 || (content_type[a] == ' ')
2123 || (content_type[a] == 13)
2124 || (content_type[a] == 10)) {
2125 content_type[a] = 0;
2131 /* Goto the correct room */
2132 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2133 strcpy(hold_rm, CC->room.QRname);
2134 strcpy(actual_rm, CC->room.QRname);
2135 if (recps != NULL) {
2136 strcpy(actual_rm, SENTITEMS);
2139 /* If the user is a twit, move to the twit room for posting */
2141 if (CC->user.axlevel == 2) {
2142 strcpy(hold_rm, actual_rm);
2143 strcpy(actual_rm, config.c_twitroom);
2144 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2148 /* ...or if this message is destined for Aide> then go there. */
2149 if (strlen(force_room) > 0) {
2150 strcpy(actual_rm, force_room);
2153 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2154 if (strcasecmp(actual_rm, CC->room.QRname)) {
2155 /* getroom(&CC->room, actual_rm); */
2156 usergoto(actual_rm, 0, 1, NULL, NULL);
2160 * If this message has no O (room) field, generate one.
2162 if (msg->cm_fields['O'] == NULL) {
2163 msg->cm_fields['O'] = strdup(CC->room.QRname);
2166 /* Perform "before save" hooks (aborting if any return nonzero) */
2167 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2168 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2171 * If this message has an Exclusive ID, and the room is replication
2172 * checking enabled, then do replication checks.
2174 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2175 ReplicationChecks(msg);
2178 /* Save it to disk */
2179 lprintf(CTDL_DEBUG, "Saving to disk\n");
2180 newmsgid = send_message(msg);
2181 if (newmsgid <= 0L) return(-5);
2183 /* Write a supplemental message info record. This doesn't have to
2184 * be a critical section because nobody else knows about this message
2187 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2188 memset(&smi, 0, sizeof(struct MetaData));
2189 smi.meta_msgnum = newmsgid;
2190 smi.meta_refcount = 0;
2191 safestrncpy(smi.meta_content_type, content_type,
2192 sizeof smi.meta_content_type);
2194 /* As part of the new metadata record, measure how
2195 * big this message will be when displayed as RFC822.
2196 * Both POP and IMAP use this, and it's best to just take the hit now
2197 * instead of having to potentially measure thousands of messages when
2198 * a mailbox is opened later.
2201 if (CC->redirect_buffer != NULL) {
2202 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2205 CC->redirect_buffer = malloc(SIZ);
2206 CC->redirect_len = 0;
2207 CC->redirect_alloc = SIZ;
2208 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2209 smi.meta_rfc822_length = CC->redirect_len;
2210 free(CC->redirect_buffer);
2211 CC->redirect_buffer = NULL;
2212 CC->redirect_len = 0;
2213 CC->redirect_alloc = 0;
2217 /* Now figure out where to store the pointers */
2218 lprintf(CTDL_DEBUG, "Storing pointers\n");
2220 /* If this is being done by the networker delivering a private
2221 * message, we want to BYPASS saving the sender's copy (because there
2222 * is no local sender; it would otherwise go to the Trashcan).
2224 if ((!CC->internal_pgm) || (recps == NULL)) {
2225 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2226 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2227 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2231 /* For internet mail, drop a copy in the outbound queue room */
2233 if (recps->num_internet > 0) {
2234 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2237 /* If other rooms are specified, drop them there too. */
2239 if (recps->num_room > 0)
2240 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2241 extract_token(recipient, recps->recp_room, i,
2242 '|', sizeof recipient);
2243 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2244 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2247 /* Bump this user's messages posted counter. */
2248 lprintf(CTDL_DEBUG, "Updating user\n");
2249 lgetuser(&CC->user, CC->curr_user);
2250 CC->user.posted = CC->user.posted + 1;
2251 lputuser(&CC->user);
2253 /* If this is private, local mail, make a copy in the
2254 * recipient's mailbox and bump the reference count.
2257 if (recps->num_local > 0)
2258 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2259 extract_token(recipient, recps->recp_local, i,
2260 '|', sizeof recipient);
2261 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2263 if (getuser(&userbuf, recipient) == 0) {
2264 MailboxName(actual_rm, sizeof actual_rm,
2265 &userbuf, MAILROOM);
2266 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2267 BumpNewMailCounter(userbuf.usernum);
2270 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2271 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2276 /* Perform "after save" hooks */
2277 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2278 PerformMessageHooks(msg, EVT_AFTERSAVE);
2280 /* For IGnet mail, we have to save a new copy into the spooler for
2281 * each recipient, with the R and D fields set to the recipient and
2282 * destination-node. This has two ugly side effects: all other
2283 * recipients end up being unlisted in this recipient's copy of the
2284 * message, and it has to deliver multiple messages to the same
2285 * node. We'll revisit this again in a year or so when everyone has
2286 * a network spool receiver that can handle the new style messages.
2289 if (recps->num_ignet > 0)
2290 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2291 extract_token(recipient, recps->recp_ignet, i,
2292 '|', sizeof recipient);
2294 hold_R = msg->cm_fields['R'];
2295 hold_D = msg->cm_fields['D'];
2296 msg->cm_fields['R'] = malloc(SIZ);
2297 msg->cm_fields['D'] = malloc(128);
2298 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2299 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2301 serialize_message(&smr, msg);
2303 snprintf(submit_filename, sizeof submit_filename,
2304 #ifndef HAVE_SPOOL_DIR
2309 "/network/spoolin/netmail.%04lx.%04x.%04x",
2310 (long) getpid(), CC->cs_pid, ++seqnum);
2311 network_fp = fopen(submit_filename, "wb+");
2312 if (network_fp != NULL) {
2313 fwrite(smr.ser, smr.len, 1, network_fp);
2319 free(msg->cm_fields['R']);
2320 free(msg->cm_fields['D']);
2321 msg->cm_fields['R'] = hold_R;
2322 msg->cm_fields['D'] = hold_D;
2325 /* Go back to the room we started from */
2326 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2327 if (strcasecmp(hold_rm, CC->room.QRname))
2328 /* getroom(&CC->room, hold_rm); */
2329 usergoto(hold_rm, 0, 1, NULL, NULL);
2331 /* For internet mail, generate delivery instructions.
2332 * Yes, this is recursive. Deal with it. Infinite recursion does
2333 * not happen because the delivery instructions message does not
2334 * contain a recipient.
2337 if (recps->num_internet > 0) {
2338 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2339 instr = malloc(SIZ * 2);
2340 snprintf(instr, SIZ * 2,
2341 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2343 SPOOLMIME, newmsgid, (long)time(NULL),
2344 msg->cm_fields['A'], msg->cm_fields['N']
2347 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2348 size_t tmp = strlen(instr);
2349 extract_token(recipient, recps->recp_internet,
2350 i, '|', sizeof recipient);
2351 snprintf(&instr[tmp], SIZ * 2 - tmp,
2352 "remote|%s|0||\n", recipient);
2355 imsg = malloc(sizeof(struct CtdlMessage));
2356 memset(imsg, 0, sizeof(struct CtdlMessage));
2357 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2358 imsg->cm_anon_type = MES_NORMAL;
2359 imsg->cm_format_type = FMT_RFC822;
2360 imsg->cm_fields['A'] = strdup("Citadel");
2361 imsg->cm_fields['M'] = instr;
2362 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2363 CtdlFreeMessage(imsg);
2367 * Any addresses to harvest for someone's address book?
2369 if ( (CC->logged_in) && (recps != NULL) ) {
2370 collected_addresses = harvest_collected_addresses(msg);
2373 if (collected_addresses != NULL) {
2374 begin_critical_section(S_ATBF);
2375 aptr = (struct addresses_to_be_filed *)
2376 malloc(sizeof(struct addresses_to_be_filed));
2378 MailboxName(actual_rm, sizeof actual_rm,
2379 &CC->user, USERCONTACTSROOM);
2380 aptr->roomname = strdup(actual_rm);
2381 aptr->collected_addresses = collected_addresses;
2383 end_critical_section(S_ATBF);
2396 * Convenience function for generating small administrative messages.
2398 void quickie_message(char *from, char *to, char *room, char *text,
2399 int format_type, char *subject)
2401 struct CtdlMessage *msg;
2402 struct recptypes *recp = NULL;
2404 msg = malloc(sizeof(struct CtdlMessage));
2405 memset(msg, 0, sizeof(struct CtdlMessage));
2406 msg->cm_magic = CTDLMESSAGE_MAGIC;
2407 msg->cm_anon_type = MES_NORMAL;
2408 msg->cm_format_type = format_type;
2409 msg->cm_fields['A'] = strdup(from);
2410 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2411 msg->cm_fields['N'] = strdup(NODENAME);
2413 msg->cm_fields['R'] = strdup(to);
2414 recp = validate_recipients(to);
2416 if (subject != NULL) {
2417 msg->cm_fields['U'] = strdup(subject);
2419 msg->cm_fields['M'] = strdup(text);
2421 CtdlSubmitMsg(msg, recp, room);
2422 CtdlFreeMessage(msg);
2423 if (recp != NULL) free(recp);
2429 * Back end function used by CtdlMakeMessage() and similar functions
2431 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2432 size_t maxlen, /* maximum message length */
2433 char *exist, /* if non-null, append to it;
2434 exist is ALWAYS freed */
2435 int crlf /* CRLF newlines instead of LF */
2439 size_t message_len = 0;
2440 size_t buffer_len = 0;
2446 if (exist == NULL) {
2453 message_len = strlen(exist);
2454 buffer_len = message_len + 4096;
2455 m = realloc(exist, buffer_len);
2462 /* flush the input if we have nowhere to store it */
2467 /* read in the lines of message text one by one */
2469 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2470 if (!strcmp(buf, terminator)) finished = 1;
2472 strcat(buf, "\r\n");
2478 if ( (!flushing) && (!finished) ) {
2479 /* Measure the line */
2480 linelen = strlen(buf);
2482 /* augment the buffer if we have to */
2483 if ((message_len + linelen) >= buffer_len) {
2484 ptr = realloc(m, (buffer_len * 2) );
2485 if (ptr == NULL) { /* flush if can't allocate */
2488 buffer_len = (buffer_len * 2);
2490 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2494 /* Add the new line to the buffer. NOTE: this loop must avoid
2495 * using functions like strcat() and strlen() because they
2496 * traverse the entire buffer upon every call, and doing that
2497 * for a multi-megabyte message slows it down beyond usability.
2499 strcpy(&m[message_len], buf);
2500 message_len += linelen;
2503 /* if we've hit the max msg length, flush the rest */
2504 if (message_len >= maxlen) flushing = 1;
2506 } while (!finished);
2514 * Build a binary message to be saved on disk.
2515 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2516 * will become part of the message. This means you are no longer
2517 * responsible for managing that memory -- it will be freed along with
2518 * the rest of the fields when CtdlFreeMessage() is called.)
2521 struct CtdlMessage *CtdlMakeMessage(
2522 struct ctdluser *author, /* author's user structure */
2523 char *recipient, /* NULL if it's not mail */
2524 char *recp_cc, /* NULL if it's not mail */
2525 char *room, /* room where it's going */
2526 int type, /* see MES_ types in header file */
2527 int format_type, /* variformat, plain text, MIME... */
2528 char *fake_name, /* who we're masquerading as */
2529 char *subject, /* Subject (optional) */
2530 char *preformatted_text /* ...or NULL to read text from client */
2532 char dest_node[SIZ];
2534 struct CtdlMessage *msg;
2536 msg = malloc(sizeof(struct CtdlMessage));
2537 memset(msg, 0, sizeof(struct CtdlMessage));
2538 msg->cm_magic = CTDLMESSAGE_MAGIC;
2539 msg->cm_anon_type = type;
2540 msg->cm_format_type = format_type;
2542 /* Don't confuse the poor folks if it's not routed mail. */
2543 strcpy(dest_node, "");
2548 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2549 msg->cm_fields['P'] = strdup(buf);
2551 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2552 msg->cm_fields['T'] = strdup(buf);
2554 if (fake_name[0]) /* author */
2555 msg->cm_fields['A'] = strdup(fake_name);
2557 msg->cm_fields['A'] = strdup(author->fullname);
2559 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2560 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2563 msg->cm_fields['O'] = strdup(CC->room.QRname);
2566 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2567 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2569 if (recipient[0] != 0) {
2570 msg->cm_fields['R'] = strdup(recipient);
2572 if (recp_cc[0] != 0) {
2573 msg->cm_fields['Y'] = strdup(recp_cc);
2575 if (dest_node[0] != 0) {
2576 msg->cm_fields['D'] = strdup(dest_node);
2579 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2580 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2583 if (subject != NULL) {
2585 if (strlen(subject) > 0) {
2586 msg->cm_fields['U'] = strdup(subject);
2590 if (preformatted_text != NULL) {
2591 msg->cm_fields['M'] = preformatted_text;
2594 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2595 config.c_maxmsglen, NULL, 0);
2603 * Check to see whether we have permission to post a message in the current
2604 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2605 * returns 0 on success.
2607 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2609 if (!(CC->logged_in)) {
2610 snprintf(errmsgbuf, n, "Not logged in.");
2611 return (ERROR + NOT_LOGGED_IN);
2614 if ((CC->user.axlevel < 2)
2615 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2616 snprintf(errmsgbuf, n, "Need to be validated to enter "
2617 "(except in %s> to sysop)", MAILROOM);
2618 return (ERROR + HIGHER_ACCESS_REQUIRED);
2621 if ((CC->user.axlevel < 4)
2622 && (CC->room.QRflags & QR_NETWORK)) {
2623 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2624 return (ERROR + HIGHER_ACCESS_REQUIRED);
2627 if ((CC->user.axlevel < 6)
2628 && (CC->room.QRflags & QR_READONLY)) {
2629 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2630 return (ERROR + HIGHER_ACCESS_REQUIRED);
2633 strcpy(errmsgbuf, "Ok");
2639 * Check to see if the specified user has Internet mail permission
2640 * (returns nonzero if permission is granted)
2642 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2644 /* Do not allow twits to send Internet mail */
2645 if (who->axlevel <= 2) return(0);
2647 /* Globally enabled? */
2648 if (config.c_restrict == 0) return(1);
2650 /* User flagged ok? */
2651 if (who->flags & US_INTERNET) return(2);
2653 /* Aide level access? */
2654 if (who->axlevel >= 6) return(3);
2656 /* No mail for you! */
2662 * Validate recipients, count delivery types and errors, and handle aliasing
2663 * FIXME check for dupes!!!!!
2664 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2665 * or the number of addresses found invalid.
2667 struct recptypes *validate_recipients(char *supplied_recipients) {
2668 struct recptypes *ret;
2669 char recipients[SIZ];
2670 char this_recp[256];
2671 char this_recp_cooked[256];
2677 struct ctdluser tempUS;
2678 struct ctdlroom tempQR;
2682 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2683 if (ret == NULL) return(NULL);
2684 memset(ret, 0, sizeof(struct recptypes));
2687 ret->num_internet = 0;
2692 if (supplied_recipients == NULL) {
2693 strcpy(recipients, "");
2696 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2699 /* Change all valid separator characters to commas */
2700 for (i=0; i<strlen(recipients); ++i) {
2701 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2702 recipients[i] = ',';
2706 /* Now start extracting recipients... */
2708 while (strlen(recipients) > 0) {
2710 for (i=0; i<=strlen(recipients); ++i) {
2711 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2712 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2713 safestrncpy(this_recp, recipients, i+1);
2715 if (recipients[i] == ',') {
2716 strcpy(recipients, &recipients[i+1]);
2719 strcpy(recipients, "");
2726 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2728 mailtype = alias(this_recp);
2729 mailtype = alias(this_recp);
2730 mailtype = alias(this_recp);
2731 for (j=0; j<=strlen(this_recp); ++j) {
2732 if (this_recp[j]=='_') {
2733 this_recp_cooked[j] = ' ';
2736 this_recp_cooked[j] = this_recp[j];
2742 if (!strcasecmp(this_recp, "sysop")) {
2744 strcpy(this_recp, config.c_aideroom);
2745 if (strlen(ret->recp_room) > 0) {
2746 strcat(ret->recp_room, "|");
2748 strcat(ret->recp_room, this_recp);
2750 else if (getuser(&tempUS, this_recp) == 0) {
2752 strcpy(this_recp, tempUS.fullname);
2753 if (strlen(ret->recp_local) > 0) {
2754 strcat(ret->recp_local, "|");
2756 strcat(ret->recp_local, this_recp);
2758 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2760 strcpy(this_recp, tempUS.fullname);
2761 if (strlen(ret->recp_local) > 0) {
2762 strcat(ret->recp_local, "|");
2764 strcat(ret->recp_local, this_recp);
2766 else if ( (!strncasecmp(this_recp, "room_", 5))
2767 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2769 if (strlen(ret->recp_room) > 0) {
2770 strcat(ret->recp_room, "|");
2772 strcat(ret->recp_room, &this_recp_cooked[5]);
2780 /* Yes, you're reading this correctly: if the target
2781 * domain points back to the local system or an attached
2782 * Citadel directory, the address is invalid. That's
2783 * because if the address were valid, we would have
2784 * already translated it to a local address by now.
2786 if (IsDirectory(this_recp)) {
2791 ++ret->num_internet;
2792 if (strlen(ret->recp_internet) > 0) {
2793 strcat(ret->recp_internet, "|");
2795 strcat(ret->recp_internet, this_recp);
2800 if (strlen(ret->recp_ignet) > 0) {
2801 strcat(ret->recp_ignet, "|");
2803 strcat(ret->recp_ignet, this_recp);
2811 if (strlen(ret->errormsg) == 0) {
2812 snprintf(append, sizeof append,
2813 "Invalid recipient: %s",
2817 snprintf(append, sizeof append,
2820 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2821 strcat(ret->errormsg, append);
2825 if (strlen(ret->display_recp) == 0) {
2826 strcpy(append, this_recp);
2829 snprintf(append, sizeof append, ", %s",
2832 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2833 strcat(ret->display_recp, append);
2838 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2839 ret->num_room + ret->num_error) == 0) {
2840 ret->num_error = (-1);
2841 strcpy(ret->errormsg, "No recipients specified.");
2844 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2845 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2846 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2847 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2848 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2849 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2857 * message entry - mode 0 (normal)
2859 void cmd_ent0(char *entargs)
2865 char masquerade_as[SIZ];
2867 int format_type = 0;
2868 char newusername[SIZ];
2869 struct CtdlMessage *msg;
2873 struct recptypes *valid = NULL;
2874 struct recptypes *valid_to = NULL;
2875 struct recptypes *valid_cc = NULL;
2876 struct recptypes *valid_bcc = NULL;
2883 post = extract_int(entargs, 0);
2884 extract_token(recp, entargs, 1, '|', sizeof recp);
2885 anon_flag = extract_int(entargs, 2);
2886 format_type = extract_int(entargs, 3);
2887 extract_token(subject, entargs, 4, '|', sizeof subject);
2888 do_confirm = extract_int(entargs, 6);
2889 extract_token(cc, entargs, 7, '|', sizeof cc);
2890 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2892 /* first check to make sure the request is valid. */
2894 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2896 cprintf("%d %s\n", err, errmsg);
2900 /* Check some other permission type things. */
2903 if (CC->user.axlevel < 6) {
2904 cprintf("%d You don't have permission to masquerade.\n",
2905 ERROR + HIGHER_ACCESS_REQUIRED);
2908 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2909 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2910 safestrncpy(CC->fake_postname, newusername,
2911 sizeof(CC->fake_postname) );
2912 cprintf("%d ok\n", CIT_OK);
2915 CC->cs_flags |= CS_POSTING;
2917 /* In the Mail> room we have to behave a little differently --
2918 * make sure the user has specified at least one recipient. Then
2919 * validate the recipient(s).
2921 if ( (CC->room.QRflags & QR_MAILBOX)
2922 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2924 if (CC->user.axlevel < 2) {
2925 strcpy(recp, "sysop");
2930 valid_to = validate_recipients(recp);
2931 if (valid_to->num_error > 0) {
2932 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2937 valid_cc = validate_recipients(cc);
2938 if (valid_cc->num_error > 0) {
2939 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2945 valid_bcc = validate_recipients(bcc);
2946 if (valid_bcc->num_error > 0) {
2947 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2954 /* Recipient required, but none were specified */
2955 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2959 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2963 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2964 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2965 cprintf("%d You do not have permission "
2966 "to send Internet mail.\n",
2967 ERROR + HIGHER_ACCESS_REQUIRED);
2975 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)
2976 && (CC->user.axlevel < 4) ) {
2977 cprintf("%d Higher access required for network mail.\n",
2978 ERROR + HIGHER_ACCESS_REQUIRED);
2985 if ((RESTRICT_INTERNET == 1)
2986 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2987 && ((CC->user.flags & US_INTERNET) == 0)
2988 && (!CC->internal_pgm)) {
2989 cprintf("%d You don't have access to Internet mail.\n",
2990 ERROR + HIGHER_ACCESS_REQUIRED);
2999 /* Is this a room which has anonymous-only or anonymous-option? */
3000 anonymous = MES_NORMAL;
3001 if (CC->room.QRflags & QR_ANONONLY) {
3002 anonymous = MES_ANONONLY;
3004 if (CC->room.QRflags & QR_ANONOPT) {
3005 if (anon_flag == 1) { /* only if the user requested it */
3006 anonymous = MES_ANONOPT;
3010 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3014 /* If we're only checking the validity of the request, return
3015 * success without creating the message.
3018 cprintf("%d %s\n", CIT_OK,
3019 ((valid_to != NULL) ? valid_to->display_recp : "") );
3026 /* We don't need these anymore because we'll do it differently below */
3031 /* Handle author masquerading */
3032 if (CC->fake_postname[0]) {
3033 strcpy(masquerade_as, CC->fake_postname);
3035 else if (CC->fake_username[0]) {
3036 strcpy(masquerade_as, CC->fake_username);
3039 strcpy(masquerade_as, "");
3042 /* Read in the message from the client. */
3044 cprintf("%d send message\n", START_CHAT_MODE);
3046 cprintf("%d send message\n", SEND_LISTING);
3049 msg = CtdlMakeMessage(&CC->user, recp, cc,
3050 CC->room.QRname, anonymous, format_type,
3051 masquerade_as, subject, NULL);
3053 /* Put together one big recipients struct containing to/cc/bcc all in
3054 * one. This is for the envelope.
3056 char *all_recps = malloc(SIZ * 3);
3057 strcpy(all_recps, recp);
3058 if (strlen(cc) > 0) {
3059 if (strlen(all_recps) > 0) {
3060 strcat(all_recps, ",");
3062 strcat(all_recps, cc);
3064 if (strlen(bcc) > 0) {
3065 if (strlen(all_recps) > 0) {
3066 strcat(all_recps, ",");
3068 strcat(all_recps, bcc);
3070 if (strlen(all_recps) > 0) {
3071 valid = validate_recipients(all_recps);
3079 msgnum = CtdlSubmitMsg(msg, valid, "");
3082 cprintf("%ld\n", msgnum);
3084 cprintf("Message accepted.\n");
3087 cprintf("Internal error.\n");
3089 if (msg->cm_fields['E'] != NULL) {
3090 cprintf("%s\n", msg->cm_fields['E']);
3097 CtdlFreeMessage(msg);
3099 CC->fake_postname[0] = '\0';
3100 if (valid != NULL) {
3109 * API function to delete messages which match a set of criteria
3110 * (returns the actual number of messages deleted)
3112 int CtdlDeleteMessages(char *room_name, /* which room */
3113 long dmsgnum, /* or "0" for any */
3114 char *content_type, /* or "" for any */
3115 int deferred /* let TDAP sweep it later */
3119 struct ctdlroom qrbuf;
3120 struct cdbdata *cdbfr;
3121 long *msglist = NULL;
3122 long *dellist = NULL;
3125 int num_deleted = 0;
3127 struct MetaData smi;
3129 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3130 room_name, dmsgnum, content_type, deferred);
3132 /* get room record, obtaining a lock... */
3133 if (lgetroom(&qrbuf, room_name) != 0) {
3134 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3136 return (0); /* room not found */
3138 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3140 if (cdbfr != NULL) {
3141 dellist = malloc(cdbfr->len);
3142 msglist = (long *) cdbfr->ptr;
3143 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3144 num_msgs = cdbfr->len / sizeof(long);
3148 for (i = 0; i < num_msgs; ++i) {
3151 /* Set/clear a bit for each criterion */
3153 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3154 delete_this |= 0x01;
3156 if (strlen(content_type) == 0) {
3157 delete_this |= 0x02;
3159 GetMetaData(&smi, msglist[i]);
3160 if (!strcasecmp(smi.meta_content_type,
3162 delete_this |= 0x02;
3166 /* Delete message only if all bits are set */
3167 if (delete_this == 0x03) {
3168 dellist[num_deleted++] = msglist[i];
3173 num_msgs = sort_msglist(msglist, num_msgs);
3174 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3175 msglist, (int)(num_msgs * sizeof(long)));
3177 qrbuf.QRhighest = msglist[num_msgs - 1];
3182 * If the delete operation is "deferred" (and technically, any delete
3183 * operation not performed by THE DREADED AUTO-PURGER ought to be
3184 * a deferred delete) then we save a pointer to the message in the
3185 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3186 * at least 1, which will save the user from having to synchronously
3187 * wait for various disk-intensive operations to complete.
3189 if ( (deferred) && (num_deleted) ) {
3190 for (i=0; i<num_deleted; ++i) {
3191 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3195 /* Go through the messages we pulled out of the index, and decrement
3196 * their reference counts by 1. If this is the only room the message
3197 * was in, the reference count will reach zero and the message will
3198 * automatically be deleted from the database. We do this in a
3199 * separate pass because there might be plug-in hooks getting called,
3200 * and we don't want that happening during an S_ROOMS critical
3203 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3204 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3205 AdjRefCount(dellist[i], -1);
3208 /* Now free the memory we used, and go away. */
3209 if (msglist != NULL) free(msglist);
3210 if (dellist != NULL) free(dellist);
3211 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3212 return (num_deleted);
3218 * Check whether the current user has permission to delete messages from
3219 * the current room (returns 1 for yes, 0 for no)
3221 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3222 getuser(&CC->user, CC->curr_user);
3223 if ((CC->user.axlevel < 6)
3224 && (CC->user.usernum != CC->room.QRroomaide)
3225 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3226 && (!(CC->internal_pgm))) {
3235 * Delete message from current room
3237 void cmd_dele(char *delstr)
3242 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3243 cprintf("%d Higher access required.\n",
3244 ERROR + HIGHER_ACCESS_REQUIRED);
3247 delnum = extract_long(delstr, 0);
3249 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3252 cprintf("%d %d message%s deleted.\n", CIT_OK,
3253 num_deleted, ((num_deleted != 1) ? "s" : ""));
3255 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3261 * Back end API function for moves and deletes
3263 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3266 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3267 if (err != 0) return(err);
3275 * move or copy a message to another room
3277 void cmd_move(char *args)
3280 char targ[ROOMNAMELEN];
3281 struct ctdlroom qtemp;
3287 num = extract_long(args, 0);
3288 extract_token(targ, args, 1, '|', sizeof targ);
3289 targ[ROOMNAMELEN - 1] = 0;
3290 is_copy = extract_int(args, 2);
3292 if (getroom(&qtemp, targ) != 0) {
3293 cprintf("%d '%s' does not exist.\n",
3294 ERROR + ROOM_NOT_FOUND, targ);
3298 getuser(&CC->user, CC->curr_user);
3299 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3301 /* Check for permission to perform this operation.
3302 * Remember: "CC->room" is source, "qtemp" is target.
3306 /* Aides can move/copy */
3307 if (CC->user.axlevel >= 6) permit = 1;
3309 /* Room aides can move/copy */
3310 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3312 /* Permit move/copy from personal rooms */
3313 if ((CC->room.QRflags & QR_MAILBOX)
3314 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3316 /* Permit only copy from public to personal room */
3318 && (!(CC->room.QRflags & QR_MAILBOX))
3319 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3321 /* User must have access to target room */
3322 if (!(ra & UA_KNOWN)) permit = 0;
3325 cprintf("%d Higher access required.\n",
3326 ERROR + HIGHER_ACCESS_REQUIRED);
3330 err = CtdlCopyMsgToRoom(num, targ);
3332 cprintf("%d Cannot store message in %s: error %d\n",
3337 /* Now delete the message from the source room,
3338 * if this is a 'move' rather than a 'copy' operation.
3341 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3344 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3350 * GetMetaData() - Get the supplementary record for a message
3352 void GetMetaData(struct MetaData *smibuf, long msgnum)
3355 struct cdbdata *cdbsmi;
3358 memset(smibuf, 0, sizeof(struct MetaData));
3359 smibuf->meta_msgnum = msgnum;
3360 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3362 /* Use the negative of the message number for its supp record index */
3363 TheIndex = (0L - msgnum);
3365 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3366 if (cdbsmi == NULL) {
3367 return; /* record not found; go with defaults */
3369 memcpy(smibuf, cdbsmi->ptr,
3370 ((cdbsmi->len > sizeof(struct MetaData)) ?
3371 sizeof(struct MetaData) : cdbsmi->len));
3378 * PutMetaData() - (re)write supplementary record for a message
3380 void PutMetaData(struct MetaData *smibuf)
3384 /* Use the negative of the message number for the metadata db index */
3385 TheIndex = (0L - smibuf->meta_msgnum);
3387 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3388 smibuf->meta_msgnum, smibuf->meta_refcount);
3390 cdb_store(CDB_MSGMAIN,
3391 &TheIndex, (int)sizeof(long),
3392 smibuf, (int)sizeof(struct MetaData));
3397 * AdjRefCount - change the reference count for a message;
3398 * delete the message if it reaches zero
3400 void AdjRefCount(long msgnum, int incr)
3403 struct MetaData smi;
3406 /* This is a *tight* critical section; please keep it that way, as
3407 * it may get called while nested in other critical sections.
3408 * Complicating this any further will surely cause deadlock!
3410 begin_critical_section(S_SUPPMSGMAIN);
3411 GetMetaData(&smi, msgnum);
3412 smi.meta_refcount += incr;
3414 end_critical_section(S_SUPPMSGMAIN);
3415 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3416 msgnum, incr, smi.meta_refcount);
3418 /* If the reference count is now zero, delete the message
3419 * (and its supplementary record as well).
3421 if (smi.meta_refcount == 0) {
3422 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3424 /* Remove from fulltext index */
3425 if (config.c_enable_fulltext) {
3426 ft_index_message(msgnum, 0);
3429 /* Remove from message base */
3431 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3432 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3434 /* Remove metadata record */
3435 delnum = (0L - msgnum);
3436 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3441 * Write a generic object to this room
3443 * Note: this could be much more efficient. Right now we use two temporary
3444 * files, and still pull the message into memory as with all others.
3446 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3447 char *content_type, /* MIME type of this object */
3448 char *tempfilename, /* Where to fetch it from */
3449 struct ctdluser *is_mailbox, /* Mailbox room? */
3450 int is_binary, /* Is encoding necessary? */
3451 int is_unique, /* Del others of this type? */
3452 unsigned int flags /* Internal save flags */
3457 struct ctdlroom qrbuf;
3458 char roomname[ROOMNAMELEN];
3459 struct CtdlMessage *msg;
3461 char *raw_message = NULL;
3462 char *encoded_message = NULL;
3463 off_t raw_length = 0;
3465 if (is_mailbox != NULL)
3466 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3468 safestrncpy(roomname, req_room, sizeof(roomname));
3469 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3472 fp = fopen(tempfilename, "rb");
3474 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3475 tempfilename, strerror(errno));
3478 fseek(fp, 0L, SEEK_END);
3479 raw_length = ftell(fp);
3481 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3483 raw_message = malloc((size_t)raw_length + 2);
3484 fread(raw_message, (size_t)raw_length, 1, fp);
3488 encoded_message = malloc((size_t)
3489 (((raw_length * 134) / 100) + 4096 ) );
3492 encoded_message = malloc((size_t)(raw_length + 4096));
3495 sprintf(encoded_message, "Content-type: %s\n", content_type);
3498 sprintf(&encoded_message[strlen(encoded_message)],
3499 "Content-transfer-encoding: base64\n\n"
3503 sprintf(&encoded_message[strlen(encoded_message)],
3504 "Content-transfer-encoding: 7bit\n\n"
3510 &encoded_message[strlen(encoded_message)],
3516 raw_message[raw_length] = 0;
3518 &encoded_message[strlen(encoded_message)],
3526 lprintf(CTDL_DEBUG, "Allocating\n");
3527 msg = malloc(sizeof(struct CtdlMessage));
3528 memset(msg, 0, sizeof(struct CtdlMessage));
3529 msg->cm_magic = CTDLMESSAGE_MAGIC;
3530 msg->cm_anon_type = MES_NORMAL;
3531 msg->cm_format_type = 4;
3532 msg->cm_fields['A'] = strdup(CC->user.fullname);
3533 msg->cm_fields['O'] = strdup(req_room);
3534 msg->cm_fields['N'] = strdup(config.c_nodename);
3535 msg->cm_fields['H'] = strdup(config.c_humannode);
3536 msg->cm_flags = flags;
3538 msg->cm_fields['M'] = encoded_message;
3540 /* Create the requested room if we have to. */
3541 if (getroom(&qrbuf, roomname) != 0) {
3542 create_room(roomname,
3543 ( (is_mailbox != NULL) ? 5 : 3 ),
3544 "", 0, 1, 0, VIEW_BBS);
3546 /* If the caller specified this object as unique, delete all
3547 * other objects of this type that are currently in the room.
3550 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3551 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3554 /* Now write the data */
3555 CtdlSubmitMsg(msg, NULL, roomname);
3556 CtdlFreeMessage(msg);
3564 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3565 config_msgnum = msgnum;
3569 char *CtdlGetSysConfig(char *sysconfname) {
3570 char hold_rm[ROOMNAMELEN];
3573 struct CtdlMessage *msg;
3576 strcpy(hold_rm, CC->room.QRname);
3577 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3578 getroom(&CC->room, hold_rm);
3583 /* We want the last (and probably only) config in this room */
3584 begin_critical_section(S_CONFIG);
3585 config_msgnum = (-1L);
3586 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3587 CtdlGetSysConfigBackend, NULL);
3588 msgnum = config_msgnum;
3589 end_critical_section(S_CONFIG);
3595 msg = CtdlFetchMessage(msgnum, 1);
3597 conf = strdup(msg->cm_fields['M']);
3598 CtdlFreeMessage(msg);
3605 getroom(&CC->room, hold_rm);
3607 if (conf != NULL) do {
3608 extract_token(buf, conf, 0, '\n', sizeof buf);
3609 strcpy(conf, &conf[strlen(buf)+1]);
3610 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3615 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3616 char temp[PATH_MAX];
3619 strcpy(temp, tmpnam(NULL));
3621 fp = fopen(temp, "w");
3622 if (fp == NULL) return;
3623 fprintf(fp, "%s", sysconfdata);
3626 /* this handy API function does all the work for us */
3627 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3633 * Determine whether a given Internet address belongs to the current user
3635 int CtdlIsMe(char *addr, int addr_buf_len)
3637 struct recptypes *recp;
3640 recp = validate_recipients(addr);
3641 if (recp == NULL) return(0);
3643 if (recp->num_local == 0) {
3648 for (i=0; i<recp->num_local; ++i) {
3649 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3650 if (!strcasecmp(addr, CC->user.fullname)) {
3662 * Citadel protocol command to do the same
3664 void cmd_isme(char *argbuf) {
3667 if (CtdlAccessCheck(ac_logged_in)) return;
3668 extract_token(addr, argbuf, 0, '|', sizeof addr);
3670 if (CtdlIsMe(addr, sizeof addr)) {
3671 cprintf("%d %s\n", CIT_OK, addr);
3674 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);