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);
153 "mail.aliases", "r");
155 fp = fopen("/dev/null", "r");
162 while (fgets(aaa, sizeof aaa, fp) != NULL) {
163 while (isspace(name[0]))
164 strcpy(name, &name[1]);
165 aaa[strlen(aaa) - 1] = 0;
167 for (a = 0; a < strlen(aaa); ++a) {
169 strcpy(bbb, &aaa[a + 1]);
173 if (!strcasecmp(name, aaa))
178 /* Hit the Global Address Book */
179 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
183 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
185 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
186 for (a=0; a<strlen(name); ++a) {
187 if (name[a] == '@') {
188 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
190 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
195 /* determine local or remote type, see citadel.h */
196 at = haschar(name, '@');
197 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
198 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
199 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
201 /* figure out the delivery mode */
202 extract_token(node, name, 1, '@', sizeof node);
204 /* If there are one or more dots in the nodename, we assume that it
205 * is an FQDN and will attempt SMTP delivery to the Internet.
207 if (haschar(node, '.') > 0) {
208 return(MES_INTERNET);
211 /* Otherwise we look in the IGnet maps for a valid Citadel node.
212 * Try directly-connected nodes first...
214 ignetcfg = CtdlGetSysConfig(IGNETCFG);
215 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
216 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
217 extract_token(testnode, buf, 0, '|', sizeof testnode);
218 if (!strcasecmp(node, testnode)) {
226 * Then try nodes that are two or more hops away.
228 ignetmap = CtdlGetSysConfig(IGNETMAP);
229 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
230 extract_token(buf, ignetmap, i, '\n', sizeof buf);
231 extract_token(testnode, buf, 0, '|', sizeof testnode);
232 if (!strcasecmp(node, testnode)) {
239 /* If we get to this point it's an invalid node name */
254 "/citadel.control", "r");
256 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
260 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
301 /* Determine if a given message matches the fields in a message template.
302 * Return 0 for a successful match.
304 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
307 /* If there aren't any fields in the template, all messages will
310 if (template == NULL) return(0);
312 /* Null messages are bogus. */
313 if (msg == NULL) return(1);
315 for (i='A'; i<='Z'; ++i) {
316 if (template->cm_fields[i] != NULL) {
317 if (msg->cm_fields[i] == NULL) {
320 if (strcasecmp(msg->cm_fields[i],
321 template->cm_fields[i])) return 1;
325 /* All compares succeeded: we have a match! */
332 * Retrieve the "seen" message list for the current room.
334 void CtdlGetSeen(char *buf, int which_set) {
337 /* Learn about the user and room in question */
338 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
340 if (which_set == ctdlsetseen_seen)
341 safestrncpy(buf, vbuf.v_seen, SIZ);
342 if (which_set == ctdlsetseen_answered)
343 safestrncpy(buf, vbuf.v_answered, SIZ);
349 * Manipulate the "seen msgs" string (or other message set strings)
351 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set,
352 struct ctdluser *which_user, struct ctdlroom *which_room) {
353 struct cdbdata *cdbfr;
365 char *is_set; /* actually an array of booleans */
368 char setstr[SIZ], lostr[SIZ], histr[SIZ];
371 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
372 target_msgnum, target_setting, which_set);
374 /* Learn about the user and room in question */
375 CtdlGetRelationship(&vbuf,
376 ((which_user != NULL) ? which_user : &CC->user),
377 ((which_room != NULL) ? which_room : &CC->room)
380 /* Load the message list */
381 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
383 msglist = malloc(cdbfr->len);
384 memcpy(msglist, cdbfr->ptr, cdbfr->len);
385 num_msgs = cdbfr->len / sizeof(long);
388 return; /* No messages at all? No further action. */
391 is_set = malloc(num_msgs * sizeof(char));
392 memset(is_set, 0, (num_msgs * sizeof(char)) );
394 /* Decide which message set we're manipulating */
396 case ctdlsetseen_seen:
397 safestrncpy(vset, vbuf.v_seen, sizeof vset);
399 case ctdlsetseen_answered:
400 safestrncpy(vset, vbuf.v_answered, sizeof vset);
404 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
406 /* Translate the existing sequence set into an array of booleans */
407 num_sets = num_tokens(vset, ',');
408 for (s=0; s<num_sets; ++s) {
409 extract_token(setstr, vset, s, ',', sizeof setstr);
411 extract_token(lostr, setstr, 0, ':', sizeof lostr);
412 if (num_tokens(setstr, ':') >= 2) {
413 extract_token(histr, setstr, 1, ':', sizeof histr);
414 if (!strcmp(histr, "*")) {
415 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
419 strcpy(histr, lostr);
424 for (i = 0; i < num_msgs; ++i) {
425 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
431 /* Now translate the array of booleans back into a sequence set */
436 for (i=0; i<num_msgs; ++i) {
439 if (msglist[i] == target_msgnum) {
440 is_seen = target_setting;
447 if (lo < 0L) lo = msglist[i];
451 if ( ((is_seen == 0) && (was_seen == 1))
452 || ((is_seen == 1) && (i == num_msgs-1)) ) {
454 /* begin trim-o-matic code */
457 while ( (strlen(vset) + 20) > sizeof vset) {
458 remove_token(vset, 0, ',');
460 if (j--) break; /* loop no more than 9 times */
462 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
466 snprintf(lostr, sizeof lostr,
467 "1:%ld,%s", t, vset);
468 safestrncpy(vset, lostr, sizeof vset);
470 /* end trim-o-matic code */
478 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 snprintf(&vset[tmp], (sizeof vset) - tmp,
491 /* Decide which message set we're manipulating */
493 case ctdlsetseen_seen:
494 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
496 case ctdlsetseen_answered:
497 safestrncpy(vbuf.v_answered, vset,
498 sizeof vbuf.v_answered);
503 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
505 CtdlSetRelationship(&vbuf,
506 ((which_user != NULL) ? which_user : &CC->user),
507 ((which_room != NULL) ? which_room : &CC->room)
513 * API function to perform an operation for each qualifying message in the
514 * current room. (Returns the number of messages processed.)
516 int CtdlForEachMessage(int mode, long ref,
518 struct CtdlMessage *compare,
519 void (*CallBack) (long, void *),
525 struct cdbdata *cdbfr;
526 long *msglist = NULL;
528 int num_processed = 0;
531 struct CtdlMessage *msg;
534 int printed_lastold = 0;
536 /* Learn about the user and room in question */
538 getuser(&CC->user, CC->curr_user);
539 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
541 /* Load the message list */
542 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
544 msglist = malloc(cdbfr->len);
545 memcpy(msglist, cdbfr->ptr, cdbfr->len);
546 num_msgs = cdbfr->len / sizeof(long);
549 return 0; /* No messages at all? No further action. */
554 * Now begin the traversal.
556 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
558 /* If the caller is looking for a specific MIME type, filter
559 * out all messages which are not of the type requested.
561 if (content_type != NULL) if (strlen(content_type) > 0) {
563 /* This call to GetMetaData() sits inside this loop
564 * so that we only do the extra database read per msg
565 * if we need to. Doing the extra read all the time
566 * really kills the server. If we ever need to use
567 * metadata for another search criterion, we need to
568 * move the read somewhere else -- but still be smart
569 * enough to only do the read if the caller has
570 * specified something that will need it.
572 GetMetaData(&smi, msglist[a]);
574 if (strcasecmp(smi.meta_content_type, content_type)) {
580 num_msgs = sort_msglist(msglist, num_msgs);
582 /* If a template was supplied, filter out the messages which
583 * don't match. (This could induce some delays!)
586 if (compare != NULL) {
587 for (a = 0; a < num_msgs; ++a) {
588 msg = CtdlFetchMessage(msglist[a], 1);
590 if (CtdlMsgCmp(msg, compare)) {
593 CtdlFreeMessage(msg);
601 * Now iterate through the message list, according to the
602 * criteria supplied by the caller.
605 for (a = 0; a < num_msgs; ++a) {
606 thismsg = msglist[a];
607 if (mode == MSGS_ALL) {
611 is_seen = is_msg_in_sequence_set(
612 vbuf.v_seen, thismsg);
613 if (is_seen) lastold = thismsg;
619 || ((mode == MSGS_OLD) && (is_seen))
620 || ((mode == MSGS_NEW) && (!is_seen))
621 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
622 || ((mode == MSGS_FIRST) && (a < ref))
623 || ((mode == MSGS_GT) && (thismsg > ref))
624 || ((mode == MSGS_EQ) && (thismsg == ref))
627 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
629 CallBack(lastold, userdata);
633 if (CallBack) CallBack(thismsg, userdata);
637 free(msglist); /* Clean up */
638 return num_processed;
644 * cmd_msgs() - get list of message #'s in this room
645 * implements the MSGS server command using CtdlForEachMessage()
647 void cmd_msgs(char *cmdbuf)
656 int with_template = 0;
657 struct CtdlMessage *template = NULL;
658 int with_headers = 0;
660 extract_token(which, cmdbuf, 0, '|', sizeof which);
661 cm_ref = extract_int(cmdbuf, 1);
662 with_template = extract_int(cmdbuf, 2);
663 with_headers = extract_int(cmdbuf, 3);
667 if (!strncasecmp(which, "OLD", 3))
669 else if (!strncasecmp(which, "NEW", 3))
671 else if (!strncasecmp(which, "FIRST", 5))
673 else if (!strncasecmp(which, "LAST", 4))
675 else if (!strncasecmp(which, "GT", 2))
678 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
679 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
685 cprintf("%d Send template then receive message list\n",
687 template = (struct CtdlMessage *)
688 malloc(sizeof(struct CtdlMessage));
689 memset(template, 0, sizeof(struct CtdlMessage));
690 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
691 extract_token(tfield, buf, 0, '|', sizeof tfield);
692 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
693 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
694 if (!strcasecmp(tfield, msgkeys[i])) {
695 template->cm_fields[i] =
703 cprintf("%d \n", LISTING_FOLLOWS);
706 CtdlForEachMessage(mode,
710 (with_headers ? headers_listing : simple_listing),
713 if (template != NULL) CtdlFreeMessage(template);
721 * help_subst() - support routine for help file viewer
723 void help_subst(char *strbuf, char *source, char *dest)
728 while (p = pattern2(strbuf, source), (p >= 0)) {
729 strcpy(workbuf, &strbuf[p + strlen(source)]);
730 strcpy(&strbuf[p], dest);
731 strcat(strbuf, workbuf);
736 void do_help_subst(char *buffer)
740 help_subst(buffer, "^nodename", config.c_nodename);
741 help_subst(buffer, "^humannode", config.c_humannode);
742 help_subst(buffer, "^fqdn", config.c_fqdn);
743 help_subst(buffer, "^username", CC->user.fullname);
744 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
745 help_subst(buffer, "^usernum", buf2);
746 help_subst(buffer, "^sysadm", config.c_sysadm);
747 help_subst(buffer, "^variantname", CITADEL);
748 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
749 help_subst(buffer, "^maxsessions", buf2);
750 help_subst(buffer, "^bbsdir", CTDLDIR);
756 * memfmout() - Citadel text formatter and paginator.
757 * Although the original purpose of this routine was to format
758 * text to the reader's screen width, all we're really using it
759 * for here is to format text out to 80 columns before sending it
760 * to the client. The client software may reformat it again.
763 int width, /* screen width to use */
764 char *mptr, /* where are we going to get our text from? */
765 char subst, /* nonzero if we should do substitutions */
766 char *nl) /* string to terminate lines with */
778 c = 1; /* c is the current pos */
782 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
784 buffer[strlen(buffer) + 1] = 0;
785 buffer[strlen(buffer)] = ch;
788 if (buffer[0] == '^')
789 do_help_subst(buffer);
791 buffer[strlen(buffer) + 1] = 0;
793 strcpy(buffer, &buffer[1]);
801 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
803 if (((old == 13) || (old == 10)) && (isspace(real))) {
811 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
812 cprintf("%s%s", nl, aaa);
821 if ((strlen(aaa) + c) > (width - 5)) {
830 if ((ch == 13) || (ch == 10)) {
831 cprintf("%s%s", aaa, nl);
838 cprintf("%s%s", aaa, nl);
844 * Callback function for mime parser that simply lists the part
846 void list_this_part(char *name, char *filename, char *partnum, char *disp,
847 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
852 ma = (struct ma_info *)cbuserdata;
853 if (ma->is_ma == 0) {
854 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
855 name, filename, partnum, disp, cbtype, (long)length);
860 * Callback function for multipart prefix
862 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
863 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
868 ma = (struct ma_info *)cbuserdata;
869 if (!strcasecmp(cbtype, "multipart/alternative")) {
873 if (ma->is_ma == 0) {
874 cprintf("pref=%s|%s\n", partnum, cbtype);
879 * Callback function for multipart sufffix
881 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
882 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
887 ma = (struct ma_info *)cbuserdata;
888 if (ma->is_ma == 0) {
889 cprintf("suff=%s|%s\n", partnum, cbtype);
891 if (!strcasecmp(cbtype, "multipart/alternative")) {
898 * Callback function for mime parser that opens a section for downloading
900 void mime_download(char *name, char *filename, char *partnum, char *disp,
901 void *content, char *cbtype, char *cbcharset, size_t length,
902 char *encoding, void *cbuserdata)
905 /* Silently go away if there's already a download open... */
906 if (CC->download_fp != NULL)
909 /* ...or if this is not the desired section */
910 if (strcasecmp(CC->download_desired_section, partnum))
913 CC->download_fp = tmpfile();
914 if (CC->download_fp == NULL)
917 fwrite(content, length, 1, CC->download_fp);
918 fflush(CC->download_fp);
919 rewind(CC->download_fp);
921 OpenCmdResult(filename, cbtype);
927 * Load a message from disk into memory.
928 * This is used by CtdlOutputMsg() and other fetch functions.
930 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
931 * using the CtdlMessageFree() function.
933 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
935 struct cdbdata *dmsgtext;
936 struct CtdlMessage *ret = NULL;
940 cit_uint8_t field_header;
942 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
944 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
945 if (dmsgtext == NULL) {
948 mptr = dmsgtext->ptr;
949 upper_bound = mptr + dmsgtext->len;
951 /* Parse the three bytes that begin EVERY message on disk.
952 * The first is always 0xFF, the on-disk magic number.
953 * The second is the anonymous/public type byte.
954 * The third is the format type byte (vari, fixed, or MIME).
959 "Message %ld appears to be corrupted.\n",
964 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
965 memset(ret, 0, sizeof(struct CtdlMessage));
967 ret->cm_magic = CTDLMESSAGE_MAGIC;
968 ret->cm_anon_type = *mptr++; /* Anon type byte */
969 ret->cm_format_type = *mptr++; /* Format type byte */
972 * The rest is zero or more arbitrary fields. Load them in.
973 * We're done when we encounter either a zero-length field or
974 * have just processed the 'M' (message text) field.
977 if (mptr >= upper_bound) {
980 field_header = *mptr++;
981 ret->cm_fields[field_header] = strdup(mptr);
983 while (*mptr++ != 0); /* advance to next field */
985 } while ((mptr < upper_bound) && (field_header != 'M'));
989 /* Always make sure there's something in the msg text field. If
990 * it's NULL, the message text is most likely stored separately,
991 * so go ahead and fetch that. Failing that, just set a dummy
992 * body so other code doesn't barf.
994 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
995 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
996 if (dmsgtext != NULL) {
997 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1001 if (ret->cm_fields['M'] == NULL) {
1002 ret->cm_fields['M'] = strdup("<no text>\n");
1005 /* Perform "before read" hooks (aborting if any return nonzero) */
1006 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1007 CtdlFreeMessage(ret);
1016 * Returns 1 if the supplied pointer points to a valid Citadel message.
1017 * If the pointer is NULL or the magic number check fails, returns 0.
1019 int is_valid_message(struct CtdlMessage *msg) {
1022 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1023 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1031 * 'Destructor' for struct CtdlMessage
1033 void CtdlFreeMessage(struct CtdlMessage *msg)
1037 if (is_valid_message(msg) == 0) return;
1039 for (i = 0; i < 256; ++i)
1040 if (msg->cm_fields[i] != NULL) {
1041 free(msg->cm_fields[i]);
1044 msg->cm_magic = 0; /* just in case */
1050 * Pre callback function for multipart/alternative
1052 * NOTE: this differs from the standard behavior for a reason. Normally when
1053 * displaying multipart/alternative you want to show the _last_ usable
1054 * format in the message. Here we show the _first_ one, because it's
1055 * usually text/plain. Since this set of functions is designed for text
1056 * output to non-MIME-aware clients, this is the desired behavior.
1059 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1060 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1065 ma = (struct ma_info *)cbuserdata;
1066 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1067 if (!strcasecmp(cbtype, "multipart/alternative")) {
1075 * Post callback function for multipart/alternative
1077 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1078 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1083 ma = (struct ma_info *)cbuserdata;
1084 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1085 if (!strcasecmp(cbtype, "multipart/alternative")) {
1093 * Inline callback function for mime parser that wants to display text
1095 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1096 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1104 ma = (struct ma_info *)cbuserdata;
1106 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1109 * If we're in the middle of a multipart/alternative scope and
1110 * we've already printed another section, skip this one.
1112 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1113 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1118 if ( (!strcasecmp(cbtype, "text/plain"))
1119 || (strlen(cbtype)==0) ) {
1122 client_write(wptr, length);
1123 if (wptr[length-1] != '\n') {
1128 else if (!strcasecmp(cbtype, "text/html")) {
1129 ptr = html_to_ascii(content, 80, 0);
1131 client_write(ptr, wlen);
1132 if (ptr[wlen-1] != '\n') {
1137 else if (strncasecmp(cbtype, "multipart/", 10)) {
1138 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1139 partnum, filename, cbtype, (long)length);
1144 * The client is elegant and sophisticated and wants to be choosy about
1145 * MIME content types, so figure out which multipart/alternative part
1146 * we're going to send.
1148 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1149 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1156 ma = (struct ma_info *)cbuserdata;
1158 if (ma->is_ma > 0) {
1159 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1160 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1161 if (!strcasecmp(buf, cbtype)) {
1162 strcpy(ma->chosen_part, partnum);
1169 * Now that we've chosen our preferred part, output it.
1171 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1172 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1177 int add_newline = 0;
1181 ma = (struct ma_info *)cbuserdata;
1183 /* This is not the MIME part you're looking for... */
1184 if (strcasecmp(partnum, ma->chosen_part)) return;
1186 /* If the content-type of this part is in our preferred formats
1187 * list, we can simply output it verbatim.
1189 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1190 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1191 if (!strcasecmp(buf, cbtype)) {
1192 /* Yeah! Go! W00t!! */
1194 text_content = (char *)content;
1195 if (text_content[length-1] != '\n') {
1199 cprintf("Content-type: %s", cbtype);
1200 if (strlen(cbcharset) > 0) {
1201 cprintf("; charset=%s", cbcharset);
1203 cprintf("\nContent-length: %d\n",
1204 (int)(length + add_newline) );
1205 if (strlen(encoding) > 0) {
1206 cprintf("Content-transfer-encoding: %s\n", encoding);
1209 cprintf("Content-transfer-encoding: 7bit\n");
1212 client_write(content, length);
1213 if (add_newline) cprintf("\n");
1218 /* No translations required or possible: output as text/plain */
1219 cprintf("Content-type: text/plain\n\n");
1220 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1221 length, encoding, cbuserdata);
1226 * Get a message off disk. (returns om_* values found in msgbase.h)
1229 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1230 int mode, /* how would you like that message? */
1231 int headers_only, /* eschew the message body? */
1232 int do_proto, /* do Citadel protocol responses? */
1233 int crlf /* Use CRLF newlines instead of LF? */
1235 struct CtdlMessage *TheMessage = NULL;
1236 int retcode = om_no_such_msg;
1238 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1241 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1242 if (do_proto) cprintf("%d Not logged in.\n",
1243 ERROR + NOT_LOGGED_IN);
1244 return(om_not_logged_in);
1247 /* FIXME: check message id against msglist for this room */
1250 * Fetch the message from disk. If we're in any sort of headers
1251 * only mode, request that we don't even bother loading the body
1254 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1255 TheMessage = CtdlFetchMessage(msg_num, 0);
1258 TheMessage = CtdlFetchMessage(msg_num, 1);
1261 if (TheMessage == NULL) {
1262 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1263 ERROR + MESSAGE_NOT_FOUND, msg_num);
1264 return(om_no_such_msg);
1267 retcode = CtdlOutputPreLoadedMsg(
1269 headers_only, do_proto, crlf);
1271 CtdlFreeMessage(TheMessage);
1278 * Get a message off disk. (returns om_* values found in msgbase.h)
1281 int CtdlOutputPreLoadedMsg(
1282 struct CtdlMessage *TheMessage,
1283 int mode, /* how would you like that message? */
1284 int headers_only, /* eschew the message body? */
1285 int do_proto, /* do Citadel protocol responses? */
1286 int crlf /* Use CRLF newlines instead of LF? */
1292 char display_name[256];
1294 char *nl; /* newline string */
1296 int subject_found = 0;
1299 /* Buffers needed for RFC822 translation. These are all filled
1300 * using functions that are bounds-checked, and therefore we can
1301 * make them substantially smaller than SIZ.
1309 char datestamp[100];
1311 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1312 ((TheMessage == NULL) ? "NULL" : "not null"),
1313 mode, headers_only, do_proto, crlf);
1315 strcpy(mid, "unknown");
1316 nl = (crlf ? "\r\n" : "\n");
1318 if (!is_valid_message(TheMessage)) {
1320 "ERROR: invalid preloaded message for output\n");
1321 return(om_no_such_msg);
1324 /* Are we downloading a MIME component? */
1325 if (mode == MT_DOWNLOAD) {
1326 if (TheMessage->cm_format_type != FMT_RFC822) {
1328 cprintf("%d This is not a MIME message.\n",
1329 ERROR + ILLEGAL_VALUE);
1330 } else if (CC->download_fp != NULL) {
1331 if (do_proto) cprintf(
1332 "%d You already have a download open.\n",
1333 ERROR + RESOURCE_BUSY);
1335 /* Parse the message text component */
1336 mptr = TheMessage->cm_fields['M'];
1337 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1338 /* If there's no file open by this time, the requested
1339 * section wasn't found, so print an error
1341 if (CC->download_fp == NULL) {
1342 if (do_proto) cprintf(
1343 "%d Section %s not found.\n",
1344 ERROR + FILE_NOT_FOUND,
1345 CC->download_desired_section);
1348 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1351 /* now for the user-mode message reading loops */
1352 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1354 /* Does the caller want to skip the headers? */
1355 if (headers_only == HEADERS_NONE) goto START_TEXT;
1357 /* Tell the client which format type we're using. */
1358 if ( (mode == MT_CITADEL) && (do_proto) ) {
1359 cprintf("type=%d\n", TheMessage->cm_format_type);
1362 /* nhdr=yes means that we're only displaying headers, no body */
1363 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1364 && (mode == MT_CITADEL)
1367 cprintf("nhdr=yes\n");
1370 /* begin header processing loop for Citadel message format */
1372 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1374 safestrncpy(display_name, "<unknown>", sizeof display_name);
1375 if (TheMessage->cm_fields['A']) {
1376 strcpy(buf, TheMessage->cm_fields['A']);
1377 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1378 safestrncpy(display_name, "****", sizeof display_name);
1380 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1381 safestrncpy(display_name, "anonymous", sizeof display_name);
1384 safestrncpy(display_name, buf, sizeof display_name);
1386 if ((is_room_aide())
1387 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1388 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1389 size_t tmp = strlen(display_name);
1390 snprintf(&display_name[tmp],
1391 sizeof display_name - tmp,
1396 /* Don't show Internet address for users on the
1397 * local Citadel network.
1400 if (TheMessage->cm_fields['N'] != NULL)
1401 if (strlen(TheMessage->cm_fields['N']) > 0)
1402 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1406 /* Now spew the header fields in the order we like them. */
1407 safestrncpy(allkeys, FORDER, sizeof allkeys);
1408 for (i=0; i<strlen(allkeys); ++i) {
1409 k = (int) allkeys[i];
1411 if ( (TheMessage->cm_fields[k] != NULL)
1412 && (msgkeys[k] != NULL) ) {
1414 if (do_proto) cprintf("%s=%s\n",
1418 else if ((k == 'F') && (suppress_f)) {
1421 /* Masquerade display name if needed */
1423 if (do_proto) cprintf("%s=%s\n",
1425 TheMessage->cm_fields[k]
1434 /* begin header processing loop for RFC822 transfer format */
1439 strcpy(snode, NODENAME);
1440 strcpy(lnode, HUMANNODE);
1441 if (mode == MT_RFC822) {
1442 for (i = 0; i < 256; ++i) {
1443 if (TheMessage->cm_fields[i]) {
1444 mptr = TheMessage->cm_fields[i];
1447 safestrncpy(luser, mptr, sizeof luser);
1448 safestrncpy(suser, mptr, sizeof suser);
1450 else if (i == 'Y') {
1451 cprintf("CC: %s%s", mptr, nl);
1453 else if (i == 'U') {
1454 cprintf("Subject: %s%s", mptr, nl);
1458 safestrncpy(mid, mptr, sizeof mid);
1460 safestrncpy(lnode, mptr, sizeof lnode);
1462 safestrncpy(fuser, mptr, sizeof fuser);
1463 /* else if (i == 'O')
1464 cprintf("X-Citadel-Room: %s%s",
1467 safestrncpy(snode, mptr, sizeof snode);
1469 cprintf("To: %s%s", mptr, nl);
1470 else if (i == 'T') {
1471 datestring(datestamp, sizeof datestamp,
1472 atol(mptr), DATESTRING_RFC822);
1473 cprintf("Date: %s%s", datestamp, nl);
1477 if (subject_found == 0) {
1478 cprintf("Subject: (no subject)%s", nl);
1482 for (i=0; i<strlen(suser); ++i) {
1483 suser[i] = tolower(suser[i]);
1484 if (!isalnum(suser[i])) suser[i]='_';
1487 if (mode == MT_RFC822) {
1488 if (!strcasecmp(snode, NODENAME)) {
1489 safestrncpy(snode, FQDN, sizeof snode);
1492 /* Construct a fun message id */
1493 cprintf("Message-ID: <%s", mid);
1494 if (strchr(mid, '@')==NULL) {
1495 cprintf("@%s", snode);
1499 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1500 cprintf("From: \"----\" <x@x.org>%s", nl);
1502 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1503 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1505 else if (strlen(fuser) > 0) {
1506 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1509 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1512 cprintf("Organization: %s%s", lnode, nl);
1514 /* Blank line signifying RFC822 end-of-headers */
1515 if (TheMessage->cm_format_type != FMT_RFC822) {
1520 /* end header processing loop ... at this point, we're in the text */
1522 if (headers_only == HEADERS_FAST) goto DONE;
1523 mptr = TheMessage->cm_fields['M'];
1525 /* Tell the client about the MIME parts in this message */
1526 if (TheMessage->cm_format_type == FMT_RFC822) {
1527 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1528 memset(&ma, 0, sizeof(struct ma_info));
1529 mime_parser(mptr, NULL,
1530 (do_proto ? *list_this_part : NULL),
1531 (do_proto ? *list_this_pref : NULL),
1532 (do_proto ? *list_this_suff : NULL),
1535 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1536 char *start_of_text = NULL;
1537 start_of_text = strstr(mptr, "\n\r\n");
1538 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1539 if (start_of_text == NULL) start_of_text = mptr;
1541 start_of_text = strstr(start_of_text, "\n");
1543 while (ch=*mptr, ch!=0) {
1547 else switch(headers_only) {
1549 if (mptr >= start_of_text) {
1550 if (ch == 10) cprintf("%s", nl);
1551 else cprintf("%c", ch);
1555 if (mptr < start_of_text) {
1556 if (ch == 10) cprintf("%s", nl);
1557 else cprintf("%c", ch);
1561 if (ch == 10) cprintf("%s", nl);
1562 else cprintf("%c", ch);
1571 if (headers_only == HEADERS_ONLY) {
1575 /* signify start of msg text */
1576 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1577 if (do_proto) cprintf("text\n");
1580 /* If the format type on disk is 1 (fixed-format), then we want
1581 * everything to be output completely literally ... regardless of
1582 * what message transfer format is in use.
1584 if (TheMessage->cm_format_type == FMT_FIXED) {
1585 if (mode == MT_MIME) {
1586 cprintf("Content-type: text/plain\n\n");
1589 while (ch = *mptr++, ch > 0) {
1592 if ((ch == 10) || (strlen(buf) > 250)) {
1593 cprintf("%s%s", buf, nl);
1596 buf[strlen(buf) + 1] = 0;
1597 buf[strlen(buf)] = ch;
1600 if (strlen(buf) > 0)
1601 cprintf("%s%s", buf, nl);
1604 /* If the message on disk is format 0 (Citadel vari-format), we
1605 * output using the formatter at 80 columns. This is the final output
1606 * form if the transfer format is RFC822, but if the transfer format
1607 * is Citadel proprietary, it'll still work, because the indentation
1608 * for new paragraphs is correct and the client will reformat the
1609 * message to the reader's screen width.
1611 if (TheMessage->cm_format_type == FMT_CITADEL) {
1612 if (mode == MT_MIME) {
1613 cprintf("Content-type: text/x-citadel-variformat\n\n");
1615 memfmout(80, mptr, 0, nl);
1618 /* If the message on disk is format 4 (MIME), we've gotta hand it
1619 * off to the MIME parser. The client has already been told that
1620 * this message is format 1 (fixed format), so the callback function
1621 * we use will display those parts as-is.
1623 if (TheMessage->cm_format_type == FMT_RFC822) {
1624 memset(&ma, 0, sizeof(struct ma_info));
1626 if (mode == MT_MIME) {
1627 strcpy(ma.chosen_part, "1");
1628 mime_parser(mptr, NULL,
1629 *choose_preferred, *fixed_output_pre,
1630 *fixed_output_post, (void *)&ma, 0);
1631 mime_parser(mptr, NULL,
1632 *output_preferred, NULL, NULL, (void *)&ma, 0);
1635 mime_parser(mptr, NULL,
1636 *fixed_output, *fixed_output_pre,
1637 *fixed_output_post, (void *)&ma, 0);
1642 DONE: /* now we're done */
1643 if (do_proto) cprintf("000\n");
1650 * display a message (mode 0 - Citadel proprietary)
1652 void cmd_msg0(char *cmdbuf)
1655 int headers_only = HEADERS_ALL;
1657 msgid = extract_long(cmdbuf, 0);
1658 headers_only = extract_int(cmdbuf, 1);
1660 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1666 * display a message (mode 2 - RFC822)
1668 void cmd_msg2(char *cmdbuf)
1671 int headers_only = HEADERS_ALL;
1673 msgid = extract_long(cmdbuf, 0);
1674 headers_only = extract_int(cmdbuf, 1);
1676 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1682 * display a message (mode 3 - IGnet raw format - internal programs only)
1684 void cmd_msg3(char *cmdbuf)
1687 struct CtdlMessage *msg;
1690 if (CC->internal_pgm == 0) {
1691 cprintf("%d This command is for internal programs only.\n",
1692 ERROR + HIGHER_ACCESS_REQUIRED);
1696 msgnum = extract_long(cmdbuf, 0);
1697 msg = CtdlFetchMessage(msgnum, 1);
1699 cprintf("%d Message %ld not found.\n",
1700 ERROR + MESSAGE_NOT_FOUND, msgnum);
1704 serialize_message(&smr, msg);
1705 CtdlFreeMessage(msg);
1708 cprintf("%d Unable to serialize message\n",
1709 ERROR + INTERNAL_ERROR);
1713 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1714 client_write((char *)smr.ser, (int)smr.len);
1721 * Display a message using MIME content types
1723 void cmd_msg4(char *cmdbuf)
1727 msgid = extract_long(cmdbuf, 0);
1728 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1734 * Client tells us its preferred message format(s)
1736 void cmd_msgp(char *cmdbuf)
1738 safestrncpy(CC->preferred_formats, cmdbuf,
1739 sizeof(CC->preferred_formats));
1740 cprintf("%d ok\n", CIT_OK);
1745 * Open a component of a MIME message as a download file
1747 void cmd_opna(char *cmdbuf)
1750 char desired_section[128];
1752 msgid = extract_long(cmdbuf, 0);
1753 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1754 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1755 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1760 * Save a message pointer into a specified room
1761 * (Returns 0 for success, nonzero for failure)
1762 * roomname may be NULL to use the current room
1764 * Note that the 'supplied_msg' field may be set to NULL, in which case
1765 * the message will be fetched from disk, by number, if we need to perform
1766 * replication checks. This adds an additional database read, so if the
1767 * caller already has the message in memory then it should be supplied.
1769 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1770 struct CtdlMessage *supplied_msg) {
1772 char hold_rm[ROOMNAMELEN];
1773 struct cdbdata *cdbfr;
1776 long highest_msg = 0L;
1777 struct CtdlMessage *msg = NULL;
1779 /*lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(roomname=%s, msgid=%ld, do_repl_check=%d)\n",
1780 roomname, msgid, do_repl_check);*/
1782 strcpy(hold_rm, CC->room.QRname);
1784 /* We may need to check to see if this message is real */
1785 if (do_repl_check) {
1786 if (supplied_msg != NULL) {
1790 msg = CtdlFetchMessage(msgid, 0);
1792 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1795 /* Perform replication checks if necessary */
1796 if ( (do_repl_check) && (msg != NULL) ) {
1798 if (getroom(&CC->room,
1799 ((roomname != NULL) ? roomname : CC->room.QRname) )
1801 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1802 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1803 return(ERROR + ROOM_NOT_FOUND);
1806 ReplicationChecks(msg);
1809 /* Now the regular stuff */
1810 if (lgetroom(&CC->room,
1811 ((roomname != NULL) ? roomname : CC->room.QRname) )
1813 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1814 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1815 return(ERROR + ROOM_NOT_FOUND);
1818 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1819 if (cdbfr == NULL) {
1823 msglist = malloc(cdbfr->len);
1824 if (msglist == NULL)
1825 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1826 num_msgs = cdbfr->len / sizeof(long);
1827 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1832 /* Make sure the message doesn't already exist in this room. It
1833 * is absolutely taboo to have more than one reference to the same
1834 * message in a room.
1836 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1837 if (msglist[i] == msgid) {
1838 lputroom(&CC->room); /* unlock the room */
1839 getroom(&CC->room, hold_rm);
1840 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1842 return(ERROR + ALREADY_EXISTS);
1846 /* Now add the new message */
1848 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1850 if (msglist == NULL) {
1851 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1853 msglist[num_msgs - 1] = msgid;
1855 /* Sort the message list, so all the msgid's are in order */
1856 num_msgs = sort_msglist(msglist, num_msgs);
1858 /* Determine the highest message number */
1859 highest_msg = msglist[num_msgs - 1];
1861 /* Write it back to disk. */
1862 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1863 msglist, (int)(num_msgs * sizeof(long)));
1865 /* Free up the memory we used. */
1868 /* If the message has an Exclusive ID, index that... */
1870 if (msg->cm_fields['E'] != NULL) {
1871 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
1875 /* Update the highest-message pointer and unlock the room. */
1876 CC->room.QRhighest = highest_msg;
1877 lputroom(&CC->room);
1878 getroom(&CC->room, hold_rm);
1880 /* Bump the reference count for this message. */
1881 AdjRefCount(msgid, +1);
1883 /* Return success. */
1884 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
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 /* No exclusive id? Don't do anything. */
2019 if (msg == NULL) return;
2020 if (msg->cm_fields['E'] == NULL) return;
2021 if (strlen(msg->cm_fields['E']) == 0) return;
2022 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2023 msg->cm_fields['E'], CC->room.QRname);*/
2025 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2026 if (old_msgnum > 0L) {
2027 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2028 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2035 * Save a message to disk and submit it into the delivery system.
2037 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2038 struct recptypes *recps, /* recipients (if mail) */
2039 char *force /* force a particular room? */
2041 char submit_filename[128];
2042 char generated_timestamp[32];
2043 char hold_rm[ROOMNAMELEN];
2044 char actual_rm[ROOMNAMELEN];
2045 char force_room[ROOMNAMELEN];
2046 char content_type[SIZ]; /* We have to learn this */
2047 char recipient[SIZ];
2050 struct ctdluser userbuf;
2052 struct MetaData smi;
2053 FILE *network_fp = NULL;
2054 static int seqnum = 1;
2055 struct CtdlMessage *imsg = NULL;
2058 char *hold_R, *hold_D;
2059 char *collected_addresses = NULL;
2060 struct addresses_to_be_filed *aptr = NULL;
2062 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2063 if (is_valid_message(msg) == 0) return(-1); /* self check */
2065 /* If this message has no timestamp, we take the liberty of
2066 * giving it one, right now.
2068 if (msg->cm_fields['T'] == NULL) {
2069 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2070 msg->cm_fields['T'] = strdup(generated_timestamp);
2073 /* If this message has no path, we generate one.
2075 if (msg->cm_fields['P'] == NULL) {
2076 if (msg->cm_fields['A'] != NULL) {
2077 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2078 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2079 if (isspace(msg->cm_fields['P'][a])) {
2080 msg->cm_fields['P'][a] = ' ';
2085 msg->cm_fields['P'] = strdup("unknown");
2089 if (force == NULL) {
2090 strcpy(force_room, "");
2093 strcpy(force_room, force);
2096 /* Learn about what's inside, because it's what's inside that counts */
2097 if (msg->cm_fields['M'] == NULL) {
2098 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2102 switch (msg->cm_format_type) {
2104 strcpy(content_type, "text/x-citadel-variformat");
2107 strcpy(content_type, "text/plain");
2110 strcpy(content_type, "text/plain");
2111 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2113 safestrncpy(content_type, &mptr[14],
2114 sizeof content_type);
2115 for (a = 0; a < strlen(content_type); ++a) {
2116 if ((content_type[a] == ';')
2117 || (content_type[a] == ' ')
2118 || (content_type[a] == 13)
2119 || (content_type[a] == 10)) {
2120 content_type[a] = 0;
2126 /* Goto the correct room */
2127 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2128 strcpy(hold_rm, CC->room.QRname);
2129 strcpy(actual_rm, CC->room.QRname);
2130 if (recps != NULL) {
2131 strcpy(actual_rm, SENTITEMS);
2134 /* If the user is a twit, move to the twit room for posting */
2136 if (CC->user.axlevel == 2) {
2137 strcpy(hold_rm, actual_rm);
2138 strcpy(actual_rm, config.c_twitroom);
2139 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2143 /* ...or if this message is destined for Aide> then go there. */
2144 if (strlen(force_room) > 0) {
2145 strcpy(actual_rm, force_room);
2148 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2149 if (strcasecmp(actual_rm, CC->room.QRname)) {
2150 /* getroom(&CC->room, actual_rm); */
2151 usergoto(actual_rm, 0, 1, NULL, NULL);
2155 * If this message has no O (room) field, generate one.
2157 if (msg->cm_fields['O'] == NULL) {
2158 msg->cm_fields['O'] = strdup(CC->room.QRname);
2161 /* Perform "before save" hooks (aborting if any return nonzero) */
2162 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2163 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2165 /* If this message has an Exclusive ID, perform replication checks */
2166 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2167 ReplicationChecks(msg);
2169 /* Save it to disk */
2170 lprintf(CTDL_DEBUG, "Saving to disk\n");
2171 newmsgid = send_message(msg);
2172 if (newmsgid <= 0L) return(-5);
2174 /* Write a supplemental message info record. This doesn't have to
2175 * be a critical section because nobody else knows about this message
2178 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2179 memset(&smi, 0, sizeof(struct MetaData));
2180 smi.meta_msgnum = newmsgid;
2181 smi.meta_refcount = 0;
2182 safestrncpy(smi.meta_content_type, content_type,
2183 sizeof smi.meta_content_type);
2185 /* As part of the new metadata record, measure how
2186 * big this message will be when displayed as RFC822.
2187 * Both POP and IMAP use this, and it's best to just take the hit now
2188 * instead of having to potentially measure thousands of messages when
2189 * a mailbox is opened later.
2192 if (CC->redirect_buffer != NULL) {
2193 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2196 CC->redirect_buffer = malloc(SIZ);
2197 CC->redirect_len = 0;
2198 CC->redirect_alloc = SIZ;
2199 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2200 smi.meta_rfc822_length = CC->redirect_len;
2201 free(CC->redirect_buffer);
2202 CC->redirect_buffer = NULL;
2203 CC->redirect_len = 0;
2204 CC->redirect_alloc = 0;
2208 /* Now figure out where to store the pointers */
2209 lprintf(CTDL_DEBUG, "Storing pointers\n");
2211 /* If this is being done by the networker delivering a private
2212 * message, we want to BYPASS saving the sender's copy (because there
2213 * is no local sender; it would otherwise go to the Trashcan).
2215 if ((!CC->internal_pgm) || (recps == NULL)) {
2216 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2217 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2218 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2222 /* For internet mail, drop a copy in the outbound queue room */
2224 if (recps->num_internet > 0) {
2225 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2228 /* If other rooms are specified, drop them there too. */
2230 if (recps->num_room > 0)
2231 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2232 extract_token(recipient, recps->recp_room, i,
2233 '|', sizeof recipient);
2234 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2235 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2238 /* Bump this user's messages posted counter. */
2239 lprintf(CTDL_DEBUG, "Updating user\n");
2240 lgetuser(&CC->user, CC->curr_user);
2241 CC->user.posted = CC->user.posted + 1;
2242 lputuser(&CC->user);
2244 /* If this is private, local mail, make a copy in the
2245 * recipient's mailbox and bump the reference count.
2248 if (recps->num_local > 0)
2249 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2250 extract_token(recipient, recps->recp_local, i,
2251 '|', sizeof recipient);
2252 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2254 if (getuser(&userbuf, recipient) == 0) {
2255 MailboxName(actual_rm, sizeof actual_rm,
2256 &userbuf, MAILROOM);
2257 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2258 BumpNewMailCounter(userbuf.usernum);
2261 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2262 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2266 /* Perform "after save" hooks */
2267 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2268 PerformMessageHooks(msg, EVT_AFTERSAVE);
2270 /* For IGnet mail, we have to save a new copy into the spooler for
2271 * each recipient, with the R and D fields set to the recipient and
2272 * destination-node. This has two ugly side effects: all other
2273 * recipients end up being unlisted in this recipient's copy of the
2274 * message, and it has to deliver multiple messages to the same
2275 * node. We'll revisit this again in a year or so when everyone has
2276 * a network spool receiver that can handle the new style messages.
2279 if (recps->num_ignet > 0)
2280 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2281 extract_token(recipient, recps->recp_ignet, i,
2282 '|', sizeof recipient);
2284 hold_R = msg->cm_fields['R'];
2285 hold_D = msg->cm_fields['D'];
2286 msg->cm_fields['R'] = malloc(SIZ);
2287 msg->cm_fields['D'] = malloc(128);
2288 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2289 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2291 serialize_message(&smr, msg);
2293 snprintf(submit_filename, sizeof submit_filename,
2294 #ifndef HAVE_SPOOL_DIR
2299 "/network/spoolin/netmail.%04lx.%04x.%04x",
2300 (long) getpid(), CC->cs_pid, ++seqnum);
2301 network_fp = fopen(submit_filename, "wb+");
2302 if (network_fp != NULL) {
2303 fwrite(smr.ser, smr.len, 1, network_fp);
2309 free(msg->cm_fields['R']);
2310 free(msg->cm_fields['D']);
2311 msg->cm_fields['R'] = hold_R;
2312 msg->cm_fields['D'] = hold_D;
2315 /* Go back to the room we started from */
2316 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2317 if (strcasecmp(hold_rm, CC->room.QRname))
2318 /* getroom(&CC->room, hold_rm); */
2319 usergoto(hold_rm, 0, 1, NULL, NULL);
2321 /* For internet mail, generate delivery instructions.
2322 * Yes, this is recursive. Deal with it. Infinite recursion does
2323 * not happen because the delivery instructions message does not
2324 * contain a recipient.
2327 if (recps->num_internet > 0) {
2328 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2329 instr = malloc(SIZ * 2);
2330 snprintf(instr, SIZ * 2,
2331 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2333 SPOOLMIME, newmsgid, (long)time(NULL),
2334 msg->cm_fields['A'], msg->cm_fields['N']
2337 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2338 size_t tmp = strlen(instr);
2339 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2340 snprintf(&instr[tmp], SIZ * 2 - tmp,
2341 "remote|%s|0||\n", recipient);
2344 imsg = malloc(sizeof(struct CtdlMessage));
2345 memset(imsg, 0, sizeof(struct CtdlMessage));
2346 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2347 imsg->cm_anon_type = MES_NORMAL;
2348 imsg->cm_format_type = FMT_RFC822;
2349 imsg->cm_fields['A'] = strdup("Citadel");
2350 imsg->cm_fields['M'] = instr;
2351 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2352 CtdlFreeMessage(imsg);
2356 * Any addresses to harvest for someone's address book?
2358 if ( (CC->logged_in) && (recps != NULL) ) {
2359 collected_addresses = harvest_collected_addresses(msg);
2362 if (collected_addresses != NULL) {
2363 begin_critical_section(S_ATBF);
2364 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2366 MailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2367 aptr->roomname = strdup(actual_rm);
2368 aptr->collected_addresses = collected_addresses;
2370 end_critical_section(S_ATBF);
2383 * Convenience function for generating small administrative messages.
2385 void quickie_message(char *from, char *to, char *room, char *text,
2386 int format_type, char *subject)
2388 struct CtdlMessage *msg;
2389 struct recptypes *recp = NULL;
2391 msg = malloc(sizeof(struct CtdlMessage));
2392 memset(msg, 0, sizeof(struct CtdlMessage));
2393 msg->cm_magic = CTDLMESSAGE_MAGIC;
2394 msg->cm_anon_type = MES_NORMAL;
2395 msg->cm_format_type = format_type;
2396 msg->cm_fields['A'] = strdup(from);
2397 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2398 msg->cm_fields['N'] = strdup(NODENAME);
2400 msg->cm_fields['R'] = strdup(to);
2401 recp = validate_recipients(to);
2403 if (subject != NULL) {
2404 msg->cm_fields['U'] = strdup(subject);
2406 msg->cm_fields['M'] = strdup(text);
2408 CtdlSubmitMsg(msg, recp, room);
2409 CtdlFreeMessage(msg);
2410 if (recp != NULL) free(recp);
2416 * Back end function used by CtdlMakeMessage() and similar functions
2418 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2419 size_t maxlen, /* maximum message length */
2420 char *exist, /* if non-null, append to it;
2421 exist is ALWAYS freed */
2422 int crlf /* CRLF newlines instead of LF */
2426 size_t message_len = 0;
2427 size_t buffer_len = 0;
2433 if (exist == NULL) {
2440 message_len = strlen(exist);
2441 buffer_len = message_len + 4096;
2442 m = realloc(exist, buffer_len);
2449 /* flush the input if we have nowhere to store it */
2454 /* read in the lines of message text one by one */
2456 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2457 if (!strcmp(buf, terminator)) finished = 1;
2459 strcat(buf, "\r\n");
2465 if ( (!flushing) && (!finished) ) {
2466 /* Measure the line */
2467 linelen = strlen(buf);
2469 /* augment the buffer if we have to */
2470 if ((message_len + linelen) >= buffer_len) {
2471 ptr = realloc(m, (buffer_len * 2) );
2472 if (ptr == NULL) { /* flush if can't allocate */
2475 buffer_len = (buffer_len * 2);
2477 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2481 /* Add the new line to the buffer. NOTE: this loop must avoid
2482 * using functions like strcat() and strlen() because they
2483 * traverse the entire buffer upon every call, and doing that
2484 * for a multi-megabyte message slows it down beyond usability.
2486 strcpy(&m[message_len], buf);
2487 message_len += linelen;
2490 /* if we've hit the max msg length, flush the rest */
2491 if (message_len >= maxlen) flushing = 1;
2493 } while (!finished);
2501 * Build a binary message to be saved on disk.
2502 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2503 * will become part of the message. This means you are no longer
2504 * responsible for managing that memory -- it will be freed along with
2505 * the rest of the fields when CtdlFreeMessage() is called.)
2508 struct CtdlMessage *CtdlMakeMessage(
2509 struct ctdluser *author, /* author's user structure */
2510 char *recipient, /* NULL if it's not mail */
2511 char *recp_cc, /* NULL if it's not mail */
2512 char *room, /* room where it's going */
2513 int type, /* see MES_ types in header file */
2514 int format_type, /* variformat, plain text, MIME... */
2515 char *fake_name, /* who we're masquerading as */
2516 char *subject, /* Subject (optional) */
2517 char *preformatted_text /* ...or NULL to read text from client */
2519 char dest_node[SIZ];
2521 struct CtdlMessage *msg;
2523 msg = malloc(sizeof(struct CtdlMessage));
2524 memset(msg, 0, sizeof(struct CtdlMessage));
2525 msg->cm_magic = CTDLMESSAGE_MAGIC;
2526 msg->cm_anon_type = type;
2527 msg->cm_format_type = format_type;
2529 /* Don't confuse the poor folks if it's not routed mail. */
2530 strcpy(dest_node, "");
2535 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2536 msg->cm_fields['P'] = strdup(buf);
2538 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2539 msg->cm_fields['T'] = strdup(buf);
2541 if (fake_name[0]) /* author */
2542 msg->cm_fields['A'] = strdup(fake_name);
2544 msg->cm_fields['A'] = strdup(author->fullname);
2546 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2547 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2550 msg->cm_fields['O'] = strdup(CC->room.QRname);
2553 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2554 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2556 if (recipient[0] != 0) {
2557 msg->cm_fields['R'] = strdup(recipient);
2559 if (recp_cc[0] != 0) {
2560 msg->cm_fields['Y'] = strdup(recp_cc);
2562 if (dest_node[0] != 0) {
2563 msg->cm_fields['D'] = strdup(dest_node);
2566 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2567 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2570 if (subject != NULL) {
2572 if (strlen(subject) > 0) {
2573 msg->cm_fields['U'] = strdup(subject);
2577 if (preformatted_text != NULL) {
2578 msg->cm_fields['M'] = preformatted_text;
2581 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2582 config.c_maxmsglen, NULL, 0);
2590 * Check to see whether we have permission to post a message in the current
2591 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2592 * returns 0 on success.
2594 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2596 if (!(CC->logged_in)) {
2597 snprintf(errmsgbuf, n, "Not logged in.");
2598 return (ERROR + NOT_LOGGED_IN);
2601 if ((CC->user.axlevel < 2)
2602 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2603 snprintf(errmsgbuf, n, "Need to be validated to enter "
2604 "(except in %s> to sysop)", MAILROOM);
2605 return (ERROR + HIGHER_ACCESS_REQUIRED);
2608 if ((CC->user.axlevel < 4)
2609 && (CC->room.QRflags & QR_NETWORK)) {
2610 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2611 return (ERROR + HIGHER_ACCESS_REQUIRED);
2614 if ((CC->user.axlevel < 6)
2615 && (CC->room.QRflags & QR_READONLY)) {
2616 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2617 return (ERROR + HIGHER_ACCESS_REQUIRED);
2620 strcpy(errmsgbuf, "Ok");
2626 * Check to see if the specified user has Internet mail permission
2627 * (returns nonzero if permission is granted)
2629 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2631 /* Do not allow twits to send Internet mail */
2632 if (who->axlevel <= 2) return(0);
2634 /* Globally enabled? */
2635 if (config.c_restrict == 0) return(1);
2637 /* User flagged ok? */
2638 if (who->flags & US_INTERNET) return(2);
2640 /* Aide level access? */
2641 if (who->axlevel >= 6) return(3);
2643 /* No mail for you! */
2649 * Validate recipients, count delivery types and errors, and handle aliasing
2650 * FIXME check for dupes!!!!!
2651 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2652 * or the number of addresses found invalid.
2654 struct recptypes *validate_recipients(char *recipients) {
2655 struct recptypes *ret;
2656 char this_recp[SIZ];
2657 char this_recp_cooked[SIZ];
2663 struct ctdluser tempUS;
2664 struct ctdlroom tempQR;
2667 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2668 if (ret == NULL) return(NULL);
2669 memset(ret, 0, sizeof(struct recptypes));
2672 ret->num_internet = 0;
2677 if (recipients == NULL) {
2680 else if (strlen(recipients) == 0) {
2684 /* Change all valid separator characters to commas */
2685 for (i=0; i<strlen(recipients); ++i) {
2686 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2687 recipients[i] = ',';
2692 num_recps = num_tokens(recipients, ',');
2695 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2696 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2698 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2699 mailtype = alias(this_recp);
2700 mailtype = alias(this_recp);
2701 mailtype = alias(this_recp);
2702 for (j=0; j<=strlen(this_recp); ++j) {
2703 if (this_recp[j]=='_') {
2704 this_recp_cooked[j] = ' ';
2707 this_recp_cooked[j] = this_recp[j];
2713 if (!strcasecmp(this_recp, "sysop")) {
2715 strcpy(this_recp, config.c_aideroom);
2716 if (strlen(ret->recp_room) > 0) {
2717 strcat(ret->recp_room, "|");
2719 strcat(ret->recp_room, this_recp);
2721 else if (getuser(&tempUS, this_recp) == 0) {
2723 strcpy(this_recp, tempUS.fullname);
2724 if (strlen(ret->recp_local) > 0) {
2725 strcat(ret->recp_local, "|");
2727 strcat(ret->recp_local, this_recp);
2729 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2731 strcpy(this_recp, tempUS.fullname);
2732 if (strlen(ret->recp_local) > 0) {
2733 strcat(ret->recp_local, "|");
2735 strcat(ret->recp_local, this_recp);
2737 else if ( (!strncasecmp(this_recp, "room_", 5))
2738 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2740 if (strlen(ret->recp_room) > 0) {
2741 strcat(ret->recp_room, "|");
2743 strcat(ret->recp_room, &this_recp_cooked[5]);
2751 /* Yes, you're reading this correctly: if the target
2752 * domain points back to the local system or an attached
2753 * Citadel directory, the address is invalid. That's
2754 * because if the address were valid, we would have
2755 * already translated it to a local address by now.
2757 if (IsDirectory(this_recp)) {
2762 ++ret->num_internet;
2763 if (strlen(ret->recp_internet) > 0) {
2764 strcat(ret->recp_internet, "|");
2766 strcat(ret->recp_internet, this_recp);
2771 if (strlen(ret->recp_ignet) > 0) {
2772 strcat(ret->recp_ignet, "|");
2774 strcat(ret->recp_ignet, this_recp);
2782 if (strlen(ret->errormsg) == 0) {
2783 snprintf(append, sizeof append,
2784 "Invalid recipient: %s",
2788 snprintf(append, sizeof append,
2791 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2792 strcat(ret->errormsg, append);
2796 if (strlen(ret->display_recp) == 0) {
2797 strcpy(append, this_recp);
2800 snprintf(append, sizeof append, ", %s",
2803 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2804 strcat(ret->display_recp, append);
2809 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2810 ret->num_room + ret->num_error) == 0) {
2811 ret->num_error = (-1);
2812 strcpy(ret->errormsg, "No recipients specified.");
2815 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2816 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2817 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2818 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2819 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2820 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2828 * message entry - mode 0 (normal)
2830 void cmd_ent0(char *entargs)
2836 char masquerade_as[SIZ];
2838 int format_type = 0;
2839 char newusername[SIZ];
2840 struct CtdlMessage *msg;
2844 struct recptypes *valid = NULL;
2845 struct recptypes *valid_to = NULL;
2846 struct recptypes *valid_cc = NULL;
2847 struct recptypes *valid_bcc = NULL;
2854 post = extract_int(entargs, 0);
2855 extract_token(recp, entargs, 1, '|', sizeof recp);
2856 anon_flag = extract_int(entargs, 2);
2857 format_type = extract_int(entargs, 3);
2858 extract_token(subject, entargs, 4, '|', sizeof subject);
2859 do_confirm = extract_int(entargs, 6);
2860 extract_token(cc, entargs, 7, '|', sizeof cc);
2861 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2863 /* first check to make sure the request is valid. */
2865 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2867 cprintf("%d %s\n", err, errmsg);
2871 /* Check some other permission type things. */
2874 if (CC->user.axlevel < 6) {
2875 cprintf("%d You don't have permission to masquerade.\n",
2876 ERROR + HIGHER_ACCESS_REQUIRED);
2879 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2880 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2881 safestrncpy(CC->fake_postname, newusername,
2882 sizeof(CC->fake_postname) );
2883 cprintf("%d ok\n", CIT_OK);
2886 CC->cs_flags |= CS_POSTING;
2888 /* In the Mail> room we have to behave a little differently --
2889 * make sure the user has specified at least one recipient. Then
2890 * validate the recipient(s).
2892 if ( (CC->room.QRflags & QR_MAILBOX)
2893 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2895 if (CC->user.axlevel < 2) {
2896 strcpy(recp, "sysop");
2901 valid_to = validate_recipients(recp);
2902 if (valid_to->num_error > 0) {
2903 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2908 valid_cc = validate_recipients(cc);
2909 if (valid_cc->num_error > 0) {
2910 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2916 valid_bcc = validate_recipients(bcc);
2917 if (valid_bcc->num_error > 0) {
2918 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2925 /* Recipient required, but none were specified */
2926 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2930 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2934 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2935 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2936 cprintf("%d You do not have permission "
2937 "to send Internet mail.\n",
2938 ERROR + HIGHER_ACCESS_REQUIRED);
2946 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)
2947 && (CC->user.axlevel < 4) ) {
2948 cprintf("%d Higher access required for network mail.\n",
2949 ERROR + HIGHER_ACCESS_REQUIRED);
2956 if ((RESTRICT_INTERNET == 1)
2957 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2958 && ((CC->user.flags & US_INTERNET) == 0)
2959 && (!CC->internal_pgm)) {
2960 cprintf("%d You don't have access to Internet mail.\n",
2961 ERROR + HIGHER_ACCESS_REQUIRED);
2970 /* Is this a room which has anonymous-only or anonymous-option? */
2971 anonymous = MES_NORMAL;
2972 if (CC->room.QRflags & QR_ANONONLY) {
2973 anonymous = MES_ANONONLY;
2975 if (CC->room.QRflags & QR_ANONOPT) {
2976 if (anon_flag == 1) { /* only if the user requested it */
2977 anonymous = MES_ANONOPT;
2981 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2985 /* If we're only checking the validity of the request, return
2986 * success without creating the message.
2989 cprintf("%d %s\n", CIT_OK,
2990 ((valid_to != NULL) ? valid_to->display_recp : "") );
2997 /* We don't need these anymore because we'll do it differently below */
3002 /* Handle author masquerading */
3003 if (CC->fake_postname[0]) {
3004 strcpy(masquerade_as, CC->fake_postname);
3006 else if (CC->fake_username[0]) {
3007 strcpy(masquerade_as, CC->fake_username);
3010 strcpy(masquerade_as, "");
3013 /* Read in the message from the client. */
3015 cprintf("%d send message\n", START_CHAT_MODE);
3017 cprintf("%d send message\n", SEND_LISTING);
3020 msg = CtdlMakeMessage(&CC->user, recp, cc,
3021 CC->room.QRname, anonymous, format_type,
3022 masquerade_as, subject, NULL);
3024 /* Put together one big recipients struct containing to/cc/bcc all in
3025 * one. This is for the envelope.
3027 char *all_recps = malloc(SIZ * 3);
3028 strcpy(all_recps, recp);
3029 if (strlen(cc) > 0) {
3030 if (strlen(all_recps) > 0) {
3031 strcat(all_recps, ",");
3033 strcat(all_recps, cc);
3035 if (strlen(bcc) > 0) {
3036 if (strlen(all_recps) > 0) {
3037 strcat(all_recps, ",");
3039 strcat(all_recps, bcc);
3041 if (strlen(all_recps) > 0) {
3042 valid = validate_recipients(all_recps);
3050 msgnum = CtdlSubmitMsg(msg, valid, "");
3053 cprintf("%ld\n", msgnum);
3055 cprintf("Message accepted.\n");
3058 cprintf("Internal error.\n");
3060 if (msg->cm_fields['E'] != NULL) {
3061 cprintf("%s\n", msg->cm_fields['E']);
3068 CtdlFreeMessage(msg);
3070 CC->fake_postname[0] = '\0';
3071 if (valid != NULL) {
3080 * API function to delete messages which match a set of criteria
3081 * (returns the actual number of messages deleted)
3083 int CtdlDeleteMessages(char *room_name, /* which room */
3084 long dmsgnum, /* or "0" for any */
3085 char *content_type, /* or "" for any */
3086 int deferred /* let TDAP sweep it later */
3090 struct ctdlroom qrbuf;
3091 struct cdbdata *cdbfr;
3092 long *msglist = NULL;
3093 long *dellist = NULL;
3096 int num_deleted = 0;
3098 struct MetaData smi;
3100 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3101 room_name, dmsgnum, content_type, deferred);
3103 /* get room record, obtaining a lock... */
3104 if (lgetroom(&qrbuf, room_name) != 0) {
3105 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3107 return (0); /* room not found */
3109 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3111 if (cdbfr != NULL) {
3112 msglist = malloc(cdbfr->len);
3113 dellist = malloc(cdbfr->len);
3114 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3115 num_msgs = cdbfr->len / sizeof(long);
3119 for (i = 0; i < num_msgs; ++i) {
3122 /* Set/clear a bit for each criterion */
3124 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3125 delete_this |= 0x01;
3127 if (strlen(content_type) == 0) {
3128 delete_this |= 0x02;
3130 GetMetaData(&smi, msglist[i]);
3131 if (!strcasecmp(smi.meta_content_type,
3133 delete_this |= 0x02;
3137 /* Delete message only if all bits are set */
3138 if (delete_this == 0x03) {
3139 dellist[num_deleted++] = msglist[i];
3144 num_msgs = sort_msglist(msglist, num_msgs);
3145 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3146 msglist, (int)(num_msgs * sizeof(long)));
3148 qrbuf.QRhighest = msglist[num_msgs - 1];
3153 * If the delete operation is "deferred" (and technically, any delete
3154 * operation not performed by THE DREADED AUTO-PURGER ought to be
3155 * a deferred delete) then we save a pointer to the message in the
3156 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3157 * at least 1, which will save the user from having to synchronously
3158 * wait for various disk-intensive operations to complete.
3160 if ( (deferred) && (num_deleted) ) {
3161 for (i=0; i<num_deleted; ++i) {
3162 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3166 /* Go through the messages we pulled out of the index, and decrement
3167 * their reference counts by 1. If this is the only room the message
3168 * was in, the reference count will reach zero and the message will
3169 * automatically be deleted from the database. We do this in a
3170 * separate pass because there might be plug-in hooks getting called,
3171 * and we don't want that happening during an S_ROOMS critical
3174 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3175 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3176 AdjRefCount(dellist[i], -1);
3179 /* Now free the memory we used, and go away. */
3180 if (msglist != NULL) free(msglist);
3181 if (dellist != NULL) free(dellist);
3182 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3183 return (num_deleted);
3189 * Check whether the current user has permission to delete messages from
3190 * the current room (returns 1 for yes, 0 for no)
3192 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3193 getuser(&CC->user, CC->curr_user);
3194 if ((CC->user.axlevel < 6)
3195 && (CC->user.usernum != CC->room.QRroomaide)
3196 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3197 && (!(CC->internal_pgm))) {
3206 * Delete message from current room
3208 void cmd_dele(char *delstr)
3213 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3214 cprintf("%d Higher access required.\n",
3215 ERROR + HIGHER_ACCESS_REQUIRED);
3218 delnum = extract_long(delstr, 0);
3220 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3223 cprintf("%d %d message%s deleted.\n", CIT_OK,
3224 num_deleted, ((num_deleted != 1) ? "s" : ""));
3226 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3232 * Back end API function for moves and deletes
3234 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3237 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3238 if (err != 0) return(err);
3246 * move or copy a message to another room
3248 void cmd_move(char *args)
3251 char targ[ROOMNAMELEN];
3252 struct ctdlroom qtemp;
3258 num = extract_long(args, 0);
3259 extract_token(targ, args, 1, '|', sizeof targ);
3260 targ[ROOMNAMELEN - 1] = 0;
3261 is_copy = extract_int(args, 2);
3263 if (getroom(&qtemp, targ) != 0) {
3264 cprintf("%d '%s' does not exist.\n",
3265 ERROR + ROOM_NOT_FOUND, targ);
3269 getuser(&CC->user, CC->curr_user);
3270 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3272 /* Check for permission to perform this operation.
3273 * Remember: "CC->room" is source, "qtemp" is target.
3277 /* Aides can move/copy */
3278 if (CC->user.axlevel >= 6) permit = 1;
3280 /* Room aides can move/copy */
3281 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3283 /* Permit move/copy from personal rooms */
3284 if ((CC->room.QRflags & QR_MAILBOX)
3285 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3287 /* Permit only copy from public to personal room */
3289 && (!(CC->room.QRflags & QR_MAILBOX))
3290 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3292 /* User must have access to target room */
3293 if (!(ra & UA_KNOWN)) permit = 0;
3296 cprintf("%d Higher access required.\n",
3297 ERROR + HIGHER_ACCESS_REQUIRED);
3301 err = CtdlCopyMsgToRoom(num, targ);
3303 cprintf("%d Cannot store message in %s: error %d\n",
3308 /* Now delete the message from the source room,
3309 * if this is a 'move' rather than a 'copy' operation.
3312 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3315 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3321 * GetMetaData() - Get the supplementary record for a message
3323 void GetMetaData(struct MetaData *smibuf, long msgnum)
3326 struct cdbdata *cdbsmi;
3329 memset(smibuf, 0, sizeof(struct MetaData));
3330 smibuf->meta_msgnum = msgnum;
3331 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3333 /* Use the negative of the message number for its supp record index */
3334 TheIndex = (0L - msgnum);
3336 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3337 if (cdbsmi == NULL) {
3338 return; /* record not found; go with defaults */
3340 memcpy(smibuf, cdbsmi->ptr,
3341 ((cdbsmi->len > sizeof(struct MetaData)) ?
3342 sizeof(struct MetaData) : cdbsmi->len));
3349 * PutMetaData() - (re)write supplementary record for a message
3351 void PutMetaData(struct MetaData *smibuf)
3355 /* Use the negative of the message number for the metadata db index */
3356 TheIndex = (0L - smibuf->meta_msgnum);
3358 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3359 smibuf->meta_msgnum, smibuf->meta_refcount);
3361 cdb_store(CDB_MSGMAIN,
3362 &TheIndex, (int)sizeof(long),
3363 smibuf, (int)sizeof(struct MetaData));
3368 * AdjRefCount - change the reference count for a message;
3369 * delete the message if it reaches zero
3371 void AdjRefCount(long msgnum, int incr)
3374 struct MetaData smi;
3377 /* This is a *tight* critical section; please keep it that way, as
3378 * it may get called while nested in other critical sections.
3379 * Complicating this any further will surely cause deadlock!
3381 begin_critical_section(S_SUPPMSGMAIN);
3382 GetMetaData(&smi, msgnum);
3383 smi.meta_refcount += incr;
3385 end_critical_section(S_SUPPMSGMAIN);
3386 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3387 msgnum, incr, smi.meta_refcount);
3389 /* If the reference count is now zero, delete the message
3390 * (and its supplementary record as well).
3392 if (smi.meta_refcount == 0) {
3393 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3395 /* Remove from fulltext index */
3396 if (config.c_enable_fulltext) {
3397 ft_index_message(msgnum, 0);
3400 /* Remove from message base */
3402 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3403 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3405 /* Remove metadata record */
3406 delnum = (0L - msgnum);
3407 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3412 * Write a generic object to this room
3414 * Note: this could be much more efficient. Right now we use two temporary
3415 * files, and still pull the message into memory as with all others.
3417 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3418 char *content_type, /* MIME type of this object */
3419 char *tempfilename, /* Where to fetch it from */
3420 struct ctdluser *is_mailbox, /* Mailbox room? */
3421 int is_binary, /* Is encoding necessary? */
3422 int is_unique, /* Del others of this type? */
3423 unsigned int flags /* Internal save flags */
3428 struct ctdlroom qrbuf;
3429 char roomname[ROOMNAMELEN];
3430 struct CtdlMessage *msg;
3432 char *raw_message = NULL;
3433 char *encoded_message = NULL;
3434 off_t raw_length = 0;
3436 if (is_mailbox != NULL)
3437 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3439 safestrncpy(roomname, req_room, sizeof(roomname));
3440 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3443 fp = fopen(tempfilename, "rb");
3445 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3446 tempfilename, strerror(errno));
3449 fseek(fp, 0L, SEEK_END);
3450 raw_length = ftell(fp);
3452 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3454 raw_message = malloc((size_t)raw_length + 2);
3455 fread(raw_message, (size_t)raw_length, 1, fp);
3459 encoded_message = malloc((size_t)
3460 (((raw_length * 134) / 100) + 4096 ) );
3463 encoded_message = malloc((size_t)(raw_length + 4096));
3466 sprintf(encoded_message, "Content-type: %s\n", content_type);
3469 sprintf(&encoded_message[strlen(encoded_message)],
3470 "Content-transfer-encoding: base64\n\n"
3474 sprintf(&encoded_message[strlen(encoded_message)],
3475 "Content-transfer-encoding: 7bit\n\n"
3481 &encoded_message[strlen(encoded_message)],
3487 raw_message[raw_length] = 0;
3489 &encoded_message[strlen(encoded_message)],
3497 lprintf(CTDL_DEBUG, "Allocating\n");
3498 msg = malloc(sizeof(struct CtdlMessage));
3499 memset(msg, 0, sizeof(struct CtdlMessage));
3500 msg->cm_magic = CTDLMESSAGE_MAGIC;
3501 msg->cm_anon_type = MES_NORMAL;
3502 msg->cm_format_type = 4;
3503 msg->cm_fields['A'] = strdup(CC->user.fullname);
3504 msg->cm_fields['O'] = strdup(req_room);
3505 msg->cm_fields['N'] = strdup(config.c_nodename);
3506 msg->cm_fields['H'] = strdup(config.c_humannode);
3507 msg->cm_flags = flags;
3509 msg->cm_fields['M'] = encoded_message;
3511 /* Create the requested room if we have to. */
3512 if (getroom(&qrbuf, roomname) != 0) {
3513 create_room(roomname,
3514 ( (is_mailbox != NULL) ? 5 : 3 ),
3515 "", 0, 1, 0, VIEW_BBS);
3517 /* If the caller specified this object as unique, delete all
3518 * other objects of this type that are currently in the room.
3521 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3522 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3525 /* Now write the data */
3526 CtdlSubmitMsg(msg, NULL, roomname);
3527 CtdlFreeMessage(msg);
3535 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3536 config_msgnum = msgnum;
3540 char *CtdlGetSysConfig(char *sysconfname) {
3541 char hold_rm[ROOMNAMELEN];
3544 struct CtdlMessage *msg;
3547 strcpy(hold_rm, CC->room.QRname);
3548 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3549 getroom(&CC->room, hold_rm);
3554 /* We want the last (and probably only) config in this room */
3555 begin_critical_section(S_CONFIG);
3556 config_msgnum = (-1L);
3557 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3558 CtdlGetSysConfigBackend, NULL);
3559 msgnum = config_msgnum;
3560 end_critical_section(S_CONFIG);
3566 msg = CtdlFetchMessage(msgnum, 1);
3568 conf = strdup(msg->cm_fields['M']);
3569 CtdlFreeMessage(msg);
3576 getroom(&CC->room, hold_rm);
3578 if (conf != NULL) do {
3579 extract_token(buf, conf, 0, '\n', sizeof buf);
3580 strcpy(conf, &conf[strlen(buf)+1]);
3581 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3586 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3587 char temp[PATH_MAX];
3590 strcpy(temp, tmpnam(NULL));
3592 fp = fopen(temp, "w");
3593 if (fp == NULL) return;
3594 fprintf(fp, "%s", sysconfdata);
3597 /* this handy API function does all the work for us */
3598 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3604 * Determine whether a given Internet address belongs to the current user
3606 int CtdlIsMe(char *addr, int addr_buf_len)
3608 struct recptypes *recp;
3611 recp = validate_recipients(addr);
3612 if (recp == NULL) return(0);
3614 if (recp->num_local == 0) {
3619 for (i=0; i<recp->num_local; ++i) {
3620 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3621 if (!strcasecmp(addr, CC->user.fullname)) {
3633 * Citadel protocol command to do the same
3635 void cmd_isme(char *argbuf) {
3638 if (CtdlAccessCheck(ac_logged_in)) return;
3639 extract_token(addr, argbuf, 0, '|', sizeof addr);
3641 if (CtdlIsMe(addr, sizeof addr)) {
3642 cprintf("%d %s\n", CIT_OK, addr);
3645 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);