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 /* Update the highest-message pointer and unlock the room. */
1869 CC->room.QRhighest = highest_msg;
1870 lputroom(&CC->room);
1871 getroom(&CC->room, hold_rm);
1873 /* Bump the reference count for this message. */
1874 AdjRefCount(msgid, +1);
1876 /* If the message has an Exclusive ID, index that... */
1878 if (msg->cm_fields['E'] != NULL) {
1879 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
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>\n", msg->cm_fields['E']);
2024 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2025 if (old_msgnum > 0L) {
2026 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2027 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2034 * Save a message to disk and submit it into the delivery system.
2036 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2037 struct recptypes *recps, /* recipients (if mail) */
2038 char *force /* force a particular room? */
2040 char submit_filename[128];
2041 char generated_timestamp[32];
2042 char hold_rm[ROOMNAMELEN];
2043 char actual_rm[ROOMNAMELEN];
2044 char force_room[ROOMNAMELEN];
2045 char content_type[SIZ]; /* We have to learn this */
2046 char recipient[SIZ];
2049 struct ctdluser userbuf;
2051 struct MetaData smi;
2052 FILE *network_fp = NULL;
2053 static int seqnum = 1;
2054 struct CtdlMessage *imsg = NULL;
2057 char *hold_R, *hold_D;
2058 char *collected_addresses = NULL;
2059 struct addresses_to_be_filed *aptr = NULL;
2061 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2062 if (is_valid_message(msg) == 0) return(-1); /* self check */
2064 /* If this message has no timestamp, we take the liberty of
2065 * giving it one, right now.
2067 if (msg->cm_fields['T'] == NULL) {
2068 lprintf(CTDL_DEBUG, "Generating timestamp\n");
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 lprintf(CTDL_DEBUG, "Generating path\n");
2077 if (msg->cm_fields['A'] != NULL) {
2078 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2079 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2080 if (isspace(msg->cm_fields['P'][a])) {
2081 msg->cm_fields['P'][a] = ' ';
2086 msg->cm_fields['P'] = strdup("unknown");
2090 if (force == NULL) {
2091 strcpy(force_room, "");
2094 strcpy(force_room, force);
2097 /* Learn about what's inside, because it's what's inside that counts */
2098 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2099 if (msg->cm_fields['M'] == NULL) {
2100 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2104 switch (msg->cm_format_type) {
2106 strcpy(content_type, "text/x-citadel-variformat");
2109 strcpy(content_type, "text/plain");
2112 strcpy(content_type, "text/plain");
2113 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2115 safestrncpy(content_type, &mptr[14],
2116 sizeof content_type);
2117 for (a = 0; a < strlen(content_type); ++a) {
2118 if ((content_type[a] == ';')
2119 || (content_type[a] == ' ')
2120 || (content_type[a] == 13)
2121 || (content_type[a] == 10)) {
2122 content_type[a] = 0;
2128 /* Goto the correct room */
2129 lprintf(CTDL_DEBUG, "Selected room %s\n",
2130 (recps) ? CC->room.QRname : SENTITEMS);
2131 strcpy(hold_rm, CC->room.QRname);
2132 strcpy(actual_rm, CC->room.QRname);
2133 if (recps != NULL) {
2134 strcpy(actual_rm, SENTITEMS);
2137 /* If the user is a twit, move to the twit room for posting */
2138 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2139 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2141 if (CC->user.axlevel == 2) {
2142 strcpy(hold_rm, actual_rm);
2143 strcpy(actual_rm, config.c_twitroom);
2147 /* ...or if this message is destined for Aide> then go there. */
2148 if (strlen(force_room) > 0) {
2149 strcpy(actual_rm, force_room);
2152 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2153 if (strcasecmp(actual_rm, CC->room.QRname)) {
2154 /* getroom(&CC->room, actual_rm); */
2155 usergoto(actual_rm, 0, 1, NULL, NULL);
2159 * If this message has no O (room) field, generate one.
2161 if (msg->cm_fields['O'] == NULL) {
2162 msg->cm_fields['O'] = strdup(CC->room.QRname);
2165 /* Perform "before save" hooks (aborting if any return nonzero) */
2166 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2167 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2169 /* If this message has an Exclusive ID, perform replication checks */
2170 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2171 ReplicationChecks(msg);
2173 /* Save it to disk */
2174 lprintf(CTDL_DEBUG, "Saving to disk\n");
2175 newmsgid = send_message(msg);
2176 if (newmsgid <= 0L) return(-5);
2178 /* Write a supplemental message info record. This doesn't have to
2179 * be a critical section because nobody else knows about this message
2182 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2183 memset(&smi, 0, sizeof(struct MetaData));
2184 smi.meta_msgnum = newmsgid;
2185 smi.meta_refcount = 0;
2186 safestrncpy(smi.meta_content_type, content_type,
2187 sizeof smi.meta_content_type);
2189 /* As part of the new metadata record, measure how
2190 * big this message will be when displayed as RFC822.
2191 * Both POP and IMAP use this, and it's best to just take the hit now
2192 * instead of having to potentially measure thousands of messages when
2193 * a mailbox is opened later.
2196 if (CC->redirect_buffer != NULL) {
2197 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2200 CC->redirect_buffer = malloc(SIZ);
2201 CC->redirect_len = 0;
2202 CC->redirect_alloc = SIZ;
2203 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2204 smi.meta_rfc822_length = CC->redirect_len;
2205 free(CC->redirect_buffer);
2206 CC->redirect_buffer = NULL;
2207 CC->redirect_len = 0;
2208 CC->redirect_alloc = 0;
2212 /* Now figure out where to store the pointers */
2213 lprintf(CTDL_DEBUG, "Storing pointers\n");
2215 /* If this is being done by the networker delivering a private
2216 * message, we want to BYPASS saving the sender's copy (because there
2217 * is no local sender; it would otherwise go to the Trashcan).
2219 if ((!CC->internal_pgm) || (recps == NULL)) {
2220 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2221 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2222 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2226 /* For internet mail, drop a copy in the outbound queue room */
2228 if (recps->num_internet > 0) {
2229 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2232 /* If other rooms are specified, drop them there too. */
2234 if (recps->num_room > 0)
2235 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2236 extract_token(recipient, recps->recp_room, i,
2237 '|', sizeof recipient);
2238 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2239 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2242 /* Bump this user's messages posted counter. */
2243 lprintf(CTDL_DEBUG, "Updating user\n");
2244 lgetuser(&CC->user, CC->curr_user);
2245 CC->user.posted = CC->user.posted + 1;
2246 lputuser(&CC->user);
2248 /* If this is private, local mail, make a copy in the
2249 * recipient's mailbox and bump the reference count.
2252 if (recps->num_local > 0)
2253 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2254 extract_token(recipient, recps->recp_local, i,
2255 '|', sizeof recipient);
2256 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2258 if (getuser(&userbuf, recipient) == 0) {
2259 MailboxName(actual_rm, sizeof actual_rm,
2260 &userbuf, MAILROOM);
2261 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2262 BumpNewMailCounter(userbuf.usernum);
2265 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2266 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2270 /* Perform "after save" hooks */
2271 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2272 PerformMessageHooks(msg, EVT_AFTERSAVE);
2274 /* For IGnet mail, we have to save a new copy into the spooler for
2275 * each recipient, with the R and D fields set to the recipient and
2276 * destination-node. This has two ugly side effects: all other
2277 * recipients end up being unlisted in this recipient's copy of the
2278 * message, and it has to deliver multiple messages to the same
2279 * node. We'll revisit this again in a year or so when everyone has
2280 * a network spool receiver that can handle the new style messages.
2283 if (recps->num_ignet > 0)
2284 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2285 extract_token(recipient, recps->recp_ignet, i,
2286 '|', sizeof recipient);
2288 hold_R = msg->cm_fields['R'];
2289 hold_D = msg->cm_fields['D'];
2290 msg->cm_fields['R'] = malloc(SIZ);
2291 msg->cm_fields['D'] = malloc(128);
2292 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2293 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2295 serialize_message(&smr, msg);
2297 snprintf(submit_filename, sizeof submit_filename,
2298 #ifndef HAVE_SPOOL_DIR
2303 "/network/spoolin/netmail.%04lx.%04x.%04x",
2304 (long) getpid(), CC->cs_pid, ++seqnum);
2305 network_fp = fopen(submit_filename, "wb+");
2306 if (network_fp != NULL) {
2307 fwrite(smr.ser, smr.len, 1, network_fp);
2313 free(msg->cm_fields['R']);
2314 free(msg->cm_fields['D']);
2315 msg->cm_fields['R'] = hold_R;
2316 msg->cm_fields['D'] = hold_D;
2319 /* Go back to the room we started from */
2320 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2321 if (strcasecmp(hold_rm, CC->room.QRname))
2322 /* getroom(&CC->room, hold_rm); */
2323 usergoto(hold_rm, 0, 1, NULL, NULL);
2325 /* For internet mail, generate delivery instructions.
2326 * Yes, this is recursive. Deal with it. Infinite recursion does
2327 * not happen because the delivery instructions message does not
2328 * contain a recipient.
2331 if (recps->num_internet > 0) {
2332 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2333 instr = malloc(SIZ * 2);
2334 snprintf(instr, SIZ * 2,
2335 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2337 SPOOLMIME, newmsgid, (long)time(NULL),
2338 msg->cm_fields['A'], msg->cm_fields['N']
2341 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2342 size_t tmp = strlen(instr);
2343 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2344 snprintf(&instr[tmp], SIZ * 2 - tmp,
2345 "remote|%s|0||\n", recipient);
2348 imsg = malloc(sizeof(struct CtdlMessage));
2349 memset(imsg, 0, sizeof(struct CtdlMessage));
2350 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2351 imsg->cm_anon_type = MES_NORMAL;
2352 imsg->cm_format_type = FMT_RFC822;
2353 imsg->cm_fields['A'] = strdup("Citadel");
2354 imsg->cm_fields['M'] = instr;
2355 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2356 CtdlFreeMessage(imsg);
2360 * Any addresses to harvest for someone's address book?
2362 if ( (CC->logged_in) && (recps != NULL) ) {
2363 collected_addresses = harvest_collected_addresses(msg);
2366 if (collected_addresses != NULL) {
2367 begin_critical_section(S_ATBF);
2368 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2370 MailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2371 aptr->roomname = strdup(actual_rm);
2372 aptr->collected_addresses = collected_addresses;
2374 end_critical_section(S_ATBF);
2387 * Convenience function for generating small administrative messages.
2389 void quickie_message(char *from, char *to, char *room, char *text,
2390 int format_type, char *subject)
2392 struct CtdlMessage *msg;
2393 struct recptypes *recp = NULL;
2395 msg = malloc(sizeof(struct CtdlMessage));
2396 memset(msg, 0, sizeof(struct CtdlMessage));
2397 msg->cm_magic = CTDLMESSAGE_MAGIC;
2398 msg->cm_anon_type = MES_NORMAL;
2399 msg->cm_format_type = format_type;
2400 msg->cm_fields['A'] = strdup(from);
2401 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2402 msg->cm_fields['N'] = strdup(NODENAME);
2404 msg->cm_fields['R'] = strdup(to);
2405 recp = validate_recipients(to);
2407 if (subject != NULL) {
2408 msg->cm_fields['U'] = strdup(subject);
2410 msg->cm_fields['M'] = strdup(text);
2412 CtdlSubmitMsg(msg, recp, room);
2413 CtdlFreeMessage(msg);
2414 if (recp != NULL) free(recp);
2420 * Back end function used by CtdlMakeMessage() and similar functions
2422 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2423 size_t maxlen, /* maximum message length */
2424 char *exist, /* if non-null, append to it;
2425 exist is ALWAYS freed */
2426 int crlf /* CRLF newlines instead of LF */
2430 size_t message_len = 0;
2431 size_t buffer_len = 0;
2437 if (exist == NULL) {
2444 message_len = strlen(exist);
2445 buffer_len = message_len + 4096;
2446 m = realloc(exist, buffer_len);
2453 /* flush the input if we have nowhere to store it */
2458 /* read in the lines of message text one by one */
2460 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2461 if (!strcmp(buf, terminator)) finished = 1;
2463 strcat(buf, "\r\n");
2469 if ( (!flushing) && (!finished) ) {
2470 /* Measure the line */
2471 linelen = strlen(buf);
2473 /* augment the buffer if we have to */
2474 if ((message_len + linelen) >= buffer_len) {
2475 ptr = realloc(m, (buffer_len * 2) );
2476 if (ptr == NULL) { /* flush if can't allocate */
2479 buffer_len = (buffer_len * 2);
2481 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2485 /* Add the new line to the buffer. NOTE: this loop must avoid
2486 * using functions like strcat() and strlen() because they
2487 * traverse the entire buffer upon every call, and doing that
2488 * for a multi-megabyte message slows it down beyond usability.
2490 strcpy(&m[message_len], buf);
2491 message_len += linelen;
2494 /* if we've hit the max msg length, flush the rest */
2495 if (message_len >= maxlen) flushing = 1;
2497 } while (!finished);
2505 * Build a binary message to be saved on disk.
2506 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2507 * will become part of the message. This means you are no longer
2508 * responsible for managing that memory -- it will be freed along with
2509 * the rest of the fields when CtdlFreeMessage() is called.)
2512 struct CtdlMessage *CtdlMakeMessage(
2513 struct ctdluser *author, /* author's user structure */
2514 char *recipient, /* NULL if it's not mail */
2515 char *recp_cc, /* NULL if it's not mail */
2516 char *room, /* room where it's going */
2517 int type, /* see MES_ types in header file */
2518 int format_type, /* variformat, plain text, MIME... */
2519 char *fake_name, /* who we're masquerading as */
2520 char *subject, /* Subject (optional) */
2521 char *preformatted_text /* ...or NULL to read text from client */
2523 char dest_node[SIZ];
2525 struct CtdlMessage *msg;
2527 msg = malloc(sizeof(struct CtdlMessage));
2528 memset(msg, 0, sizeof(struct CtdlMessage));
2529 msg->cm_magic = CTDLMESSAGE_MAGIC;
2530 msg->cm_anon_type = type;
2531 msg->cm_format_type = format_type;
2533 /* Don't confuse the poor folks if it's not routed mail. */
2534 strcpy(dest_node, "");
2539 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2540 msg->cm_fields['P'] = strdup(buf);
2542 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2543 msg->cm_fields['T'] = strdup(buf);
2545 if (fake_name[0]) /* author */
2546 msg->cm_fields['A'] = strdup(fake_name);
2548 msg->cm_fields['A'] = strdup(author->fullname);
2550 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2551 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2554 msg->cm_fields['O'] = strdup(CC->room.QRname);
2557 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2558 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2560 if (recipient[0] != 0) {
2561 msg->cm_fields['R'] = strdup(recipient);
2563 if (recp_cc[0] != 0) {
2564 msg->cm_fields['Y'] = strdup(recp_cc);
2566 if (dest_node[0] != 0) {
2567 msg->cm_fields['D'] = strdup(dest_node);
2570 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2571 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2574 if (subject != NULL) {
2576 if (strlen(subject) > 0) {
2577 msg->cm_fields['U'] = strdup(subject);
2581 if (preformatted_text != NULL) {
2582 msg->cm_fields['M'] = preformatted_text;
2585 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2586 config.c_maxmsglen, NULL, 0);
2594 * Check to see whether we have permission to post a message in the current
2595 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2596 * returns 0 on success.
2598 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2600 if (!(CC->logged_in)) {
2601 snprintf(errmsgbuf, n, "Not logged in.");
2602 return (ERROR + NOT_LOGGED_IN);
2605 if ((CC->user.axlevel < 2)
2606 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2607 snprintf(errmsgbuf, n, "Need to be validated to enter "
2608 "(except in %s> to sysop)", MAILROOM);
2609 return (ERROR + HIGHER_ACCESS_REQUIRED);
2612 if ((CC->user.axlevel < 4)
2613 && (CC->room.QRflags & QR_NETWORK)) {
2614 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2615 return (ERROR + HIGHER_ACCESS_REQUIRED);
2618 if ((CC->user.axlevel < 6)
2619 && (CC->room.QRflags & QR_READONLY)) {
2620 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2621 return (ERROR + HIGHER_ACCESS_REQUIRED);
2624 strcpy(errmsgbuf, "Ok");
2630 * Check to see if the specified user has Internet mail permission
2631 * (returns nonzero if permission is granted)
2633 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2635 /* Do not allow twits to send Internet mail */
2636 if (who->axlevel <= 2) return(0);
2638 /* Globally enabled? */
2639 if (config.c_restrict == 0) return(1);
2641 /* User flagged ok? */
2642 if (who->flags & US_INTERNET) return(2);
2644 /* Aide level access? */
2645 if (who->axlevel >= 6) return(3);
2647 /* No mail for you! */
2653 * Validate recipients, count delivery types and errors, and handle aliasing
2654 * FIXME check for dupes!!!!!
2655 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2656 * or the number of addresses found invalid.
2658 struct recptypes *validate_recipients(char *recipients) {
2659 struct recptypes *ret;
2660 char this_recp[SIZ];
2661 char this_recp_cooked[SIZ];
2667 struct ctdluser tempUS;
2668 struct ctdlroom tempQR;
2671 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2672 if (ret == NULL) return(NULL);
2673 memset(ret, 0, sizeof(struct recptypes));
2676 ret->num_internet = 0;
2681 if (recipients == NULL) {
2684 else if (strlen(recipients) == 0) {
2688 /* Change all valid separator characters to commas */
2689 for (i=0; i<strlen(recipients); ++i) {
2690 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2691 recipients[i] = ',';
2696 num_recps = num_tokens(recipients, ',');
2699 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2700 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2702 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2703 mailtype = alias(this_recp);
2704 mailtype = alias(this_recp);
2705 mailtype = alias(this_recp);
2706 for (j=0; j<=strlen(this_recp); ++j) {
2707 if (this_recp[j]=='_') {
2708 this_recp_cooked[j] = ' ';
2711 this_recp_cooked[j] = this_recp[j];
2717 if (!strcasecmp(this_recp, "sysop")) {
2719 strcpy(this_recp, config.c_aideroom);
2720 if (strlen(ret->recp_room) > 0) {
2721 strcat(ret->recp_room, "|");
2723 strcat(ret->recp_room, this_recp);
2725 else if (getuser(&tempUS, this_recp) == 0) {
2727 strcpy(this_recp, tempUS.fullname);
2728 if (strlen(ret->recp_local) > 0) {
2729 strcat(ret->recp_local, "|");
2731 strcat(ret->recp_local, this_recp);
2733 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2735 strcpy(this_recp, tempUS.fullname);
2736 if (strlen(ret->recp_local) > 0) {
2737 strcat(ret->recp_local, "|");
2739 strcat(ret->recp_local, this_recp);
2741 else if ( (!strncasecmp(this_recp, "room_", 5))
2742 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2744 if (strlen(ret->recp_room) > 0) {
2745 strcat(ret->recp_room, "|");
2747 strcat(ret->recp_room, &this_recp_cooked[5]);
2755 /* Yes, you're reading this correctly: if the target
2756 * domain points back to the local system or an attached
2757 * Citadel directory, the address is invalid. That's
2758 * because if the address were valid, we would have
2759 * already translated it to a local address by now.
2761 if (IsDirectory(this_recp)) {
2766 ++ret->num_internet;
2767 if (strlen(ret->recp_internet) > 0) {
2768 strcat(ret->recp_internet, "|");
2770 strcat(ret->recp_internet, this_recp);
2775 if (strlen(ret->recp_ignet) > 0) {
2776 strcat(ret->recp_ignet, "|");
2778 strcat(ret->recp_ignet, this_recp);
2786 if (strlen(ret->errormsg) == 0) {
2787 snprintf(append, sizeof append,
2788 "Invalid recipient: %s",
2792 snprintf(append, sizeof append,
2795 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2796 strcat(ret->errormsg, append);
2800 if (strlen(ret->display_recp) == 0) {
2801 strcpy(append, this_recp);
2804 snprintf(append, sizeof append, ", %s",
2807 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2808 strcat(ret->display_recp, append);
2813 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2814 ret->num_room + ret->num_error) == 0) {
2815 ret->num_error = (-1);
2816 strcpy(ret->errormsg, "No recipients specified.");
2819 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2820 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2821 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2822 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2823 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2824 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2832 * message entry - mode 0 (normal)
2834 void cmd_ent0(char *entargs)
2840 char masquerade_as[SIZ];
2842 int format_type = 0;
2843 char newusername[SIZ];
2844 struct CtdlMessage *msg;
2848 struct recptypes *valid = NULL;
2849 struct recptypes *valid_to = NULL;
2850 struct recptypes *valid_cc = NULL;
2851 struct recptypes *valid_bcc = NULL;
2858 post = extract_int(entargs, 0);
2859 extract_token(recp, entargs, 1, '|', sizeof recp);
2860 anon_flag = extract_int(entargs, 2);
2861 format_type = extract_int(entargs, 3);
2862 extract_token(subject, entargs, 4, '|', sizeof subject);
2863 do_confirm = extract_int(entargs, 6);
2864 extract_token(cc, entargs, 7, '|', sizeof cc);
2865 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2867 /* first check to make sure the request is valid. */
2869 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2871 cprintf("%d %s\n", err, errmsg);
2875 /* Check some other permission type things. */
2878 if (CC->user.axlevel < 6) {
2879 cprintf("%d You don't have permission to masquerade.\n",
2880 ERROR + HIGHER_ACCESS_REQUIRED);
2883 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2884 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2885 safestrncpy(CC->fake_postname, newusername,
2886 sizeof(CC->fake_postname) );
2887 cprintf("%d ok\n", CIT_OK);
2890 CC->cs_flags |= CS_POSTING;
2892 /* In the Mail> room we have to behave a little differently --
2893 * make sure the user has specified at least one recipient. Then
2894 * validate the recipient(s).
2896 if ( (CC->room.QRflags & QR_MAILBOX)
2897 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2899 if (CC->user.axlevel < 2) {
2900 strcpy(recp, "sysop");
2905 valid_to = validate_recipients(recp);
2906 if (valid_to->num_error > 0) {
2907 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2912 valid_cc = validate_recipients(cc);
2913 if (valid_cc->num_error > 0) {
2914 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2920 valid_bcc = validate_recipients(bcc);
2921 if (valid_bcc->num_error > 0) {
2922 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2929 /* Recipient required, but none were specified */
2930 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2934 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2938 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2939 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2940 cprintf("%d You do not have permission "
2941 "to send Internet mail.\n",
2942 ERROR + HIGHER_ACCESS_REQUIRED);
2950 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)
2951 && (CC->user.axlevel < 4) ) {
2952 cprintf("%d Higher access required for network mail.\n",
2953 ERROR + HIGHER_ACCESS_REQUIRED);
2960 if ((RESTRICT_INTERNET == 1)
2961 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2962 && ((CC->user.flags & US_INTERNET) == 0)
2963 && (!CC->internal_pgm)) {
2964 cprintf("%d You don't have access to Internet mail.\n",
2965 ERROR + HIGHER_ACCESS_REQUIRED);
2974 /* Is this a room which has anonymous-only or anonymous-option? */
2975 anonymous = MES_NORMAL;
2976 if (CC->room.QRflags & QR_ANONONLY) {
2977 anonymous = MES_ANONONLY;
2979 if (CC->room.QRflags & QR_ANONOPT) {
2980 if (anon_flag == 1) { /* only if the user requested it */
2981 anonymous = MES_ANONOPT;
2985 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2989 /* If we're only checking the validity of the request, return
2990 * success without creating the message.
2993 cprintf("%d %s\n", CIT_OK,
2994 ((valid_to != NULL) ? valid_to->display_recp : "") );
3001 /* We don't need these anymore because we'll do it differently below */
3006 /* Handle author masquerading */
3007 if (CC->fake_postname[0]) {
3008 strcpy(masquerade_as, CC->fake_postname);
3010 else if (CC->fake_username[0]) {
3011 strcpy(masquerade_as, CC->fake_username);
3014 strcpy(masquerade_as, "");
3017 /* Read in the message from the client. */
3019 cprintf("%d send message\n", START_CHAT_MODE);
3021 cprintf("%d send message\n", SEND_LISTING);
3024 msg = CtdlMakeMessage(&CC->user, recp, cc,
3025 CC->room.QRname, anonymous, format_type,
3026 masquerade_as, subject, NULL);
3028 /* Put together one big recipients struct containing to/cc/bcc all in
3029 * one. This is for the envelope.
3031 char *all_recps = malloc(SIZ * 3);
3032 strcpy(all_recps, recp);
3033 if (strlen(cc) > 0) {
3034 if (strlen(all_recps) > 0) {
3035 strcat(all_recps, ",");
3037 strcat(all_recps, cc);
3039 if (strlen(bcc) > 0) {
3040 if (strlen(all_recps) > 0) {
3041 strcat(all_recps, ",");
3043 strcat(all_recps, bcc);
3045 if (strlen(all_recps) > 0) {
3046 valid = validate_recipients(all_recps);
3054 msgnum = CtdlSubmitMsg(msg, valid, "");
3057 cprintf("%ld\n", msgnum);
3059 cprintf("Message accepted.\n");
3062 cprintf("Internal error.\n");
3064 if (msg->cm_fields['E'] != NULL) {
3065 cprintf("%s\n", msg->cm_fields['E']);
3072 CtdlFreeMessage(msg);
3074 CC->fake_postname[0] = '\0';
3075 if (valid != NULL) {
3084 * API function to delete messages which match a set of criteria
3085 * (returns the actual number of messages deleted)
3087 int CtdlDeleteMessages(char *room_name, /* which room */
3088 long dmsgnum, /* or "0" for any */
3089 char *content_type, /* or "" for any */
3090 int deferred /* let TDAP sweep it later */
3094 struct ctdlroom qrbuf;
3095 struct cdbdata *cdbfr;
3096 long *msglist = NULL;
3097 long *dellist = NULL;
3100 int num_deleted = 0;
3102 struct MetaData smi;
3104 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3105 room_name, dmsgnum, content_type, deferred);
3107 /* get room record, obtaining a lock... */
3108 if (lgetroom(&qrbuf, room_name) != 0) {
3109 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3111 return (0); /* room not found */
3113 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3115 if (cdbfr != NULL) {
3116 msglist = malloc(cdbfr->len);
3117 dellist = malloc(cdbfr->len);
3118 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3119 num_msgs = cdbfr->len / sizeof(long);
3123 for (i = 0; i < num_msgs; ++i) {
3126 /* Set/clear a bit for each criterion */
3128 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3129 delete_this |= 0x01;
3131 if (strlen(content_type) == 0) {
3132 delete_this |= 0x02;
3134 GetMetaData(&smi, msglist[i]);
3135 if (!strcasecmp(smi.meta_content_type,
3137 delete_this |= 0x02;
3141 /* Delete message only if all bits are set */
3142 if (delete_this == 0x03) {
3143 dellist[num_deleted++] = msglist[i];
3148 num_msgs = sort_msglist(msglist, num_msgs);
3149 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3150 msglist, (int)(num_msgs * sizeof(long)));
3152 qrbuf.QRhighest = msglist[num_msgs - 1];
3157 * If the delete operation is "deferred" (and technically, any delete
3158 * operation not performed by THE DREADED AUTO-PURGER ought to be
3159 * a deferred delete) then we save a pointer to the message in the
3160 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3161 * at least 1, which will save the user from having to synchronously
3162 * wait for various disk-intensive operations to complete.
3164 if ( (deferred) && (num_deleted) ) {
3165 for (i=0; i<num_deleted; ++i) {
3166 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3170 /* Go through the messages we pulled out of the index, and decrement
3171 * their reference counts by 1. If this is the only room the message
3172 * was in, the reference count will reach zero and the message will
3173 * automatically be deleted from the database. We do this in a
3174 * separate pass because there might be plug-in hooks getting called,
3175 * and we don't want that happening during an S_ROOMS critical
3178 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3179 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3180 AdjRefCount(dellist[i], -1);
3183 /* Now free the memory we used, and go away. */
3184 if (msglist != NULL) free(msglist);
3185 if (dellist != NULL) free(dellist);
3186 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3187 return (num_deleted);
3193 * Check whether the current user has permission to delete messages from
3194 * the current room (returns 1 for yes, 0 for no)
3196 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3197 getuser(&CC->user, CC->curr_user);
3198 if ((CC->user.axlevel < 6)
3199 && (CC->user.usernum != CC->room.QRroomaide)
3200 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3201 && (!(CC->internal_pgm))) {
3210 * Delete message from current room
3212 void cmd_dele(char *delstr)
3217 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3218 cprintf("%d Higher access required.\n",
3219 ERROR + HIGHER_ACCESS_REQUIRED);
3222 delnum = extract_long(delstr, 0);
3224 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3227 cprintf("%d %d message%s deleted.\n", CIT_OK,
3228 num_deleted, ((num_deleted != 1) ? "s" : ""));
3230 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3236 * Back end API function for moves and deletes
3238 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3241 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3242 if (err != 0) return(err);
3250 * move or copy a message to another room
3252 void cmd_move(char *args)
3255 char targ[ROOMNAMELEN];
3256 struct ctdlroom qtemp;
3262 num = extract_long(args, 0);
3263 extract_token(targ, args, 1, '|', sizeof targ);
3264 targ[ROOMNAMELEN - 1] = 0;
3265 is_copy = extract_int(args, 2);
3267 if (getroom(&qtemp, targ) != 0) {
3268 cprintf("%d '%s' does not exist.\n",
3269 ERROR + ROOM_NOT_FOUND, targ);
3273 getuser(&CC->user, CC->curr_user);
3274 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3276 /* Check for permission to perform this operation.
3277 * Remember: "CC->room" is source, "qtemp" is target.
3281 /* Aides can move/copy */
3282 if (CC->user.axlevel >= 6) permit = 1;
3284 /* Room aides can move/copy */
3285 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3287 /* Permit move/copy from personal rooms */
3288 if ((CC->room.QRflags & QR_MAILBOX)
3289 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3291 /* Permit only copy from public to personal room */
3293 && (!(CC->room.QRflags & QR_MAILBOX))
3294 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3296 /* User must have access to target room */
3297 if (!(ra & UA_KNOWN)) permit = 0;
3300 cprintf("%d Higher access required.\n",
3301 ERROR + HIGHER_ACCESS_REQUIRED);
3305 err = CtdlCopyMsgToRoom(num, targ);
3307 cprintf("%d Cannot store message in %s: error %d\n",
3312 /* Now delete the message from the source room,
3313 * if this is a 'move' rather than a 'copy' operation.
3316 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3319 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3325 * GetMetaData() - Get the supplementary record for a message
3327 void GetMetaData(struct MetaData *smibuf, long msgnum)
3330 struct cdbdata *cdbsmi;
3333 memset(smibuf, 0, sizeof(struct MetaData));
3334 smibuf->meta_msgnum = msgnum;
3335 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3337 /* Use the negative of the message number for its supp record index */
3338 TheIndex = (0L - msgnum);
3340 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3341 if (cdbsmi == NULL) {
3342 return; /* record not found; go with defaults */
3344 memcpy(smibuf, cdbsmi->ptr,
3345 ((cdbsmi->len > sizeof(struct MetaData)) ?
3346 sizeof(struct MetaData) : cdbsmi->len));
3353 * PutMetaData() - (re)write supplementary record for a message
3355 void PutMetaData(struct MetaData *smibuf)
3359 /* Use the negative of the message number for the metadata db index */
3360 TheIndex = (0L - smibuf->meta_msgnum);
3362 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3363 smibuf->meta_msgnum, smibuf->meta_refcount);
3365 cdb_store(CDB_MSGMAIN,
3366 &TheIndex, (int)sizeof(long),
3367 smibuf, (int)sizeof(struct MetaData));
3372 * AdjRefCount - change the reference count for a message;
3373 * delete the message if it reaches zero
3375 void AdjRefCount(long msgnum, int incr)
3378 struct MetaData smi;
3381 /* This is a *tight* critical section; please keep it that way, as
3382 * it may get called while nested in other critical sections.
3383 * Complicating this any further will surely cause deadlock!
3385 begin_critical_section(S_SUPPMSGMAIN);
3386 GetMetaData(&smi, msgnum);
3387 smi.meta_refcount += incr;
3389 end_critical_section(S_SUPPMSGMAIN);
3390 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3391 msgnum, incr, smi.meta_refcount);
3393 /* If the reference count is now zero, delete the message
3394 * (and its supplementary record as well).
3396 if (smi.meta_refcount == 0) {
3397 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3399 /* Remove from fulltext index */
3400 if (config.c_enable_fulltext) {
3401 ft_index_message(msgnum, 0);
3404 /* Remove from message base */
3406 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3407 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3409 /* Remove metadata record */
3410 delnum = (0L - msgnum);
3411 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3416 * Write a generic object to this room
3418 * Note: this could be much more efficient. Right now we use two temporary
3419 * files, and still pull the message into memory as with all others.
3421 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3422 char *content_type, /* MIME type of this object */
3423 char *tempfilename, /* Where to fetch it from */
3424 struct ctdluser *is_mailbox, /* Mailbox room? */
3425 int is_binary, /* Is encoding necessary? */
3426 int is_unique, /* Del others of this type? */
3427 unsigned int flags /* Internal save flags */
3432 struct ctdlroom qrbuf;
3433 char roomname[ROOMNAMELEN];
3434 struct CtdlMessage *msg;
3436 char *raw_message = NULL;
3437 char *encoded_message = NULL;
3438 off_t raw_length = 0;
3440 if (is_mailbox != NULL)
3441 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3443 safestrncpy(roomname, req_room, sizeof(roomname));
3444 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3447 fp = fopen(tempfilename, "rb");
3449 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3450 tempfilename, strerror(errno));
3453 fseek(fp, 0L, SEEK_END);
3454 raw_length = ftell(fp);
3456 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3458 raw_message = malloc((size_t)raw_length + 2);
3459 fread(raw_message, (size_t)raw_length, 1, fp);
3463 encoded_message = malloc((size_t)
3464 (((raw_length * 134) / 100) + 4096 ) );
3467 encoded_message = malloc((size_t)(raw_length + 4096));
3470 sprintf(encoded_message, "Content-type: %s\n", content_type);
3473 sprintf(&encoded_message[strlen(encoded_message)],
3474 "Content-transfer-encoding: base64\n\n"
3478 sprintf(&encoded_message[strlen(encoded_message)],
3479 "Content-transfer-encoding: 7bit\n\n"
3485 &encoded_message[strlen(encoded_message)],
3491 raw_message[raw_length] = 0;
3493 &encoded_message[strlen(encoded_message)],
3501 lprintf(CTDL_DEBUG, "Allocating\n");
3502 msg = malloc(sizeof(struct CtdlMessage));
3503 memset(msg, 0, sizeof(struct CtdlMessage));
3504 msg->cm_magic = CTDLMESSAGE_MAGIC;
3505 msg->cm_anon_type = MES_NORMAL;
3506 msg->cm_format_type = 4;
3507 msg->cm_fields['A'] = strdup(CC->user.fullname);
3508 msg->cm_fields['O'] = strdup(req_room);
3509 msg->cm_fields['N'] = strdup(config.c_nodename);
3510 msg->cm_fields['H'] = strdup(config.c_humannode);
3511 msg->cm_flags = flags;
3513 msg->cm_fields['M'] = encoded_message;
3515 /* Create the requested room if we have to. */
3516 if (getroom(&qrbuf, roomname) != 0) {
3517 create_room(roomname,
3518 ( (is_mailbox != NULL) ? 5 : 3 ),
3519 "", 0, 1, 0, VIEW_BBS);
3521 /* If the caller specified this object as unique, delete all
3522 * other objects of this type that are currently in the room.
3525 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3526 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3529 /* Now write the data */
3530 CtdlSubmitMsg(msg, NULL, roomname);
3531 CtdlFreeMessage(msg);
3539 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3540 config_msgnum = msgnum;
3544 char *CtdlGetSysConfig(char *sysconfname) {
3545 char hold_rm[ROOMNAMELEN];
3548 struct CtdlMessage *msg;
3551 strcpy(hold_rm, CC->room.QRname);
3552 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3553 getroom(&CC->room, hold_rm);
3558 /* We want the last (and probably only) config in this room */
3559 begin_critical_section(S_CONFIG);
3560 config_msgnum = (-1L);
3561 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3562 CtdlGetSysConfigBackend, NULL);
3563 msgnum = config_msgnum;
3564 end_critical_section(S_CONFIG);
3570 msg = CtdlFetchMessage(msgnum, 1);
3572 conf = strdup(msg->cm_fields['M']);
3573 CtdlFreeMessage(msg);
3580 getroom(&CC->room, hold_rm);
3582 if (conf != NULL) do {
3583 extract_token(buf, conf, 0, '\n', sizeof buf);
3584 strcpy(conf, &conf[strlen(buf)+1]);
3585 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3590 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3591 char temp[PATH_MAX];
3594 strcpy(temp, tmpnam(NULL));
3596 fp = fopen(temp, "w");
3597 if (fp == NULL) return;
3598 fprintf(fp, "%s", sysconfdata);
3601 /* this handy API function does all the work for us */
3602 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3608 * Determine whether a given Internet address belongs to the current user
3610 int CtdlIsMe(char *addr, int addr_buf_len)
3612 struct recptypes *recp;
3615 recp = validate_recipients(addr);
3616 if (recp == NULL) return(0);
3618 if (recp->num_local == 0) {
3623 for (i=0; i<recp->num_local; ++i) {
3624 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3625 if (!strcasecmp(addr, CC->user.fullname)) {
3637 * Citadel protocol command to do the same
3639 void cmd_isme(char *argbuf) {
3642 if (CtdlAccessCheck(ac_logged_in)) return;
3643 extract_token(addr, argbuf, 0, '|', sizeof addr);
3645 if (CtdlIsMe(addr, sizeof addr)) {
3646 cprintf("%d %s\n", CIT_OK, addr);
3649 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);