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(
1268 TheMessage, msg_num, mode,
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,
1284 int mode, /* how would you like that message? */
1285 int headers_only, /* eschew the message body? */
1286 int do_proto, /* do Citadel protocol responses? */
1287 int crlf /* Use CRLF newlines instead of LF? */
1293 char display_name[256];
1295 char *nl; /* newline string */
1297 int subject_found = 0;
1300 /* Buffers needed for RFC822 translation. These are all filled
1301 * using functions that are bounds-checked, and therefore we can
1302 * make them substantially smaller than SIZ.
1310 char datestamp[100];
1312 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1313 ((TheMessage == NULL) ? "NULL" : "not null"),
1315 mode, headers_only, do_proto, crlf);
1317 snprintf(mid, sizeof mid, "%ld", msg_num);
1318 nl = (crlf ? "\r\n" : "\n");
1320 if (!is_valid_message(TheMessage)) {
1322 "ERROR: invalid preloaded message for output\n");
1323 return(om_no_such_msg);
1326 /* Are we downloading a MIME component? */
1327 if (mode == MT_DOWNLOAD) {
1328 if (TheMessage->cm_format_type != FMT_RFC822) {
1330 cprintf("%d This is not a MIME message.\n",
1331 ERROR + ILLEGAL_VALUE);
1332 } else if (CC->download_fp != NULL) {
1333 if (do_proto) cprintf(
1334 "%d You already have a download open.\n",
1335 ERROR + RESOURCE_BUSY);
1337 /* Parse the message text component */
1338 mptr = TheMessage->cm_fields['M'];
1339 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1340 /* If there's no file open by this time, the requested
1341 * section wasn't found, so print an error
1343 if (CC->download_fp == NULL) {
1344 if (do_proto) cprintf(
1345 "%d Section %s not found.\n",
1346 ERROR + FILE_NOT_FOUND,
1347 CC->download_desired_section);
1350 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1353 /* now for the user-mode message reading loops */
1354 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1356 /* Does the caller want to skip the headers? */
1357 if (headers_only == HEADERS_NONE) goto START_TEXT;
1359 /* Tell the client which format type we're using. */
1360 if ( (mode == MT_CITADEL) && (do_proto) ) {
1361 cprintf("type=%d\n", TheMessage->cm_format_type);
1364 /* nhdr=yes means that we're only displaying headers, no body */
1365 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1366 && (mode == MT_CITADEL)
1369 cprintf("nhdr=yes\n");
1372 /* begin header processing loop for Citadel message format */
1374 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1376 safestrncpy(display_name, "<unknown>", sizeof display_name);
1377 if (TheMessage->cm_fields['A']) {
1378 strcpy(buf, TheMessage->cm_fields['A']);
1379 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1380 safestrncpy(display_name, "****", sizeof display_name);
1382 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1383 safestrncpy(display_name, "anonymous", sizeof display_name);
1386 safestrncpy(display_name, buf, sizeof display_name);
1388 if ((is_room_aide())
1389 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1390 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1391 size_t tmp = strlen(display_name);
1392 snprintf(&display_name[tmp],
1393 sizeof display_name - tmp,
1398 /* Don't show Internet address for users on the
1399 * local Citadel network.
1402 if (TheMessage->cm_fields['N'] != NULL)
1403 if (strlen(TheMessage->cm_fields['N']) > 0)
1404 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1408 /* Now spew the header fields in the order we like them. */
1409 safestrncpy(allkeys, FORDER, sizeof allkeys);
1410 for (i=0; i<strlen(allkeys); ++i) {
1411 k = (int) allkeys[i];
1413 if ( (TheMessage->cm_fields[k] != NULL)
1414 && (msgkeys[k] != NULL) ) {
1416 if (do_proto) cprintf("%s=%s\n",
1420 else if ((k == 'F') && (suppress_f)) {
1423 /* Masquerade display name if needed */
1425 if (do_proto) cprintf("%s=%s\n",
1427 TheMessage->cm_fields[k]
1436 /* begin header processing loop for RFC822 transfer format */
1441 strcpy(snode, NODENAME);
1442 strcpy(lnode, HUMANNODE);
1443 if (mode == MT_RFC822) {
1444 for (i = 0; i < 256; ++i) {
1445 if (TheMessage->cm_fields[i]) {
1446 mptr = TheMessage->cm_fields[i];
1449 safestrncpy(luser, mptr, sizeof luser);
1450 safestrncpy(suser, mptr, sizeof suser);
1452 else if (i == 'Y') {
1453 cprintf("CC: %s%s", mptr, nl);
1455 else if (i == 'U') {
1456 cprintf("Subject: %s%s", mptr, nl);
1460 safestrncpy(mid, mptr, sizeof mid);
1462 safestrncpy(lnode, mptr, sizeof lnode);
1464 safestrncpy(fuser, mptr, sizeof fuser);
1465 /* else if (i == 'O')
1466 cprintf("X-Citadel-Room: %s%s",
1469 safestrncpy(snode, mptr, sizeof snode);
1471 cprintf("To: %s%s", mptr, nl);
1472 else if (i == 'T') {
1473 datestring(datestamp, sizeof datestamp,
1474 atol(mptr), DATESTRING_RFC822);
1475 cprintf("Date: %s%s", datestamp, nl);
1479 if (subject_found == 0) {
1480 cprintf("Subject: (no subject)%s", nl);
1484 for (i=0; i<strlen(suser); ++i) {
1485 suser[i] = tolower(suser[i]);
1486 if (!isalnum(suser[i])) suser[i]='_';
1489 if (mode == MT_RFC822) {
1490 if (!strcasecmp(snode, NODENAME)) {
1491 safestrncpy(snode, FQDN, sizeof snode);
1494 /* Construct a fun message id */
1495 cprintf("Message-ID: <%s", mid);
1496 if (strchr(mid, '@')==NULL) {
1497 cprintf("@%s", snode);
1501 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1502 cprintf("From: \"----\" <x@x.org>%s", nl);
1504 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1505 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1507 else if (strlen(fuser) > 0) {
1508 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1511 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1514 cprintf("Organization: %s%s", lnode, nl);
1516 /* Blank line signifying RFC822 end-of-headers */
1517 if (TheMessage->cm_format_type != FMT_RFC822) {
1522 /* end header processing loop ... at this point, we're in the text */
1524 if (headers_only == HEADERS_FAST) goto DONE;
1525 mptr = TheMessage->cm_fields['M'];
1527 /* Tell the client about the MIME parts in this message */
1528 if (TheMessage->cm_format_type == FMT_RFC822) {
1529 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1530 memset(&ma, 0, sizeof(struct ma_info));
1531 mime_parser(mptr, NULL,
1532 (do_proto ? *list_this_part : NULL),
1533 (do_proto ? *list_this_pref : NULL),
1534 (do_proto ? *list_this_suff : NULL),
1537 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1538 /* FIXME ... we have to put some code in here to avoid
1539 * printing duplicate header information when both
1540 * Citadel and RFC822 headers exist. Preference should
1541 * probably be given to the RFC822 headers.
1543 int done_rfc822_hdrs = 0;
1544 while (ch=*(mptr++), ch!=0) {
1549 if (!done_rfc822_hdrs) {
1550 if (headers_only != HEADERS_NONE) {
1555 if (headers_only != HEADERS_ONLY) {
1559 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1560 done_rfc822_hdrs = 1;
1564 if (done_rfc822_hdrs) {
1565 if (headers_only != HEADERS_NONE) {
1570 if (headers_only != HEADERS_ONLY) {
1574 if ((*mptr == 13) || (*mptr == 10)) {
1575 done_rfc822_hdrs = 1;
1583 if (headers_only == HEADERS_ONLY) {
1587 /* signify start of msg text */
1588 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1589 if (do_proto) cprintf("text\n");
1592 /* If the format type on disk is 1 (fixed-format), then we want
1593 * everything to be output completely literally ... regardless of
1594 * what message transfer format is in use.
1596 if (TheMessage->cm_format_type == FMT_FIXED) {
1597 if (mode == MT_MIME) {
1598 cprintf("Content-type: text/plain\n\n");
1601 while (ch = *mptr++, ch > 0) {
1604 if ((ch == 10) || (strlen(buf) > 250)) {
1605 cprintf("%s%s", buf, nl);
1608 buf[strlen(buf) + 1] = 0;
1609 buf[strlen(buf)] = ch;
1612 if (strlen(buf) > 0)
1613 cprintf("%s%s", buf, nl);
1616 /* If the message on disk is format 0 (Citadel vari-format), we
1617 * output using the formatter at 80 columns. This is the final output
1618 * form if the transfer format is RFC822, but if the transfer format
1619 * is Citadel proprietary, it'll still work, because the indentation
1620 * for new paragraphs is correct and the client will reformat the
1621 * message to the reader's screen width.
1623 if (TheMessage->cm_format_type == FMT_CITADEL) {
1624 if (mode == MT_MIME) {
1625 cprintf("Content-type: text/x-citadel-variformat\n\n");
1627 memfmout(80, mptr, 0, nl);
1630 /* If the message on disk is format 4 (MIME), we've gotta hand it
1631 * off to the MIME parser. The client has already been told that
1632 * this message is format 1 (fixed format), so the callback function
1633 * we use will display those parts as-is.
1635 if (TheMessage->cm_format_type == FMT_RFC822) {
1636 memset(&ma, 0, sizeof(struct ma_info));
1638 if (mode == MT_MIME) {
1639 strcpy(ma.chosen_part, "1");
1640 mime_parser(mptr, NULL,
1641 *choose_preferred, *fixed_output_pre,
1642 *fixed_output_post, (void *)&ma, 0);
1643 mime_parser(mptr, NULL,
1644 *output_preferred, NULL, NULL, (void *)&ma, 0);
1647 mime_parser(mptr, NULL,
1648 *fixed_output, *fixed_output_pre,
1649 *fixed_output_post, (void *)&ma, 0);
1654 DONE: /* now we're done */
1655 if (do_proto) cprintf("000\n");
1662 * display a message (mode 0 - Citadel proprietary)
1664 void cmd_msg0(char *cmdbuf)
1667 int headers_only = HEADERS_ALL;
1669 msgid = extract_long(cmdbuf, 0);
1670 headers_only = extract_int(cmdbuf, 1);
1672 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1678 * display a message (mode 2 - RFC822)
1680 void cmd_msg2(char *cmdbuf)
1683 int headers_only = HEADERS_ALL;
1685 msgid = extract_long(cmdbuf, 0);
1686 headers_only = extract_int(cmdbuf, 1);
1688 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1694 * display a message (mode 3 - IGnet raw format - internal programs only)
1696 void cmd_msg3(char *cmdbuf)
1699 struct CtdlMessage *msg;
1702 if (CC->internal_pgm == 0) {
1703 cprintf("%d This command is for internal programs only.\n",
1704 ERROR + HIGHER_ACCESS_REQUIRED);
1708 msgnum = extract_long(cmdbuf, 0);
1709 msg = CtdlFetchMessage(msgnum, 1);
1711 cprintf("%d Message %ld not found.\n",
1712 ERROR + MESSAGE_NOT_FOUND, msgnum);
1716 serialize_message(&smr, msg);
1717 CtdlFreeMessage(msg);
1720 cprintf("%d Unable to serialize message\n",
1721 ERROR + INTERNAL_ERROR);
1725 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1726 client_write((char *)smr.ser, (int)smr.len);
1733 * Display a message using MIME content types
1735 void cmd_msg4(char *cmdbuf)
1739 msgid = extract_long(cmdbuf, 0);
1740 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1746 * Client tells us its preferred message format(s)
1748 void cmd_msgp(char *cmdbuf)
1750 safestrncpy(CC->preferred_formats, cmdbuf,
1751 sizeof(CC->preferred_formats));
1752 cprintf("%d ok\n", CIT_OK);
1757 * Open a component of a MIME message as a download file
1759 void cmd_opna(char *cmdbuf)
1762 char desired_section[128];
1764 msgid = extract_long(cmdbuf, 0);
1765 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1766 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1767 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1772 * Save a message pointer into a specified room
1773 * (Returns 0 for success, nonzero for failure)
1774 * roomname may be NULL to use the current room
1776 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1778 char hold_rm[ROOMNAMELEN];
1779 struct cdbdata *cdbfr;
1782 long highest_msg = 0L;
1783 struct CtdlMessage *msg = NULL;
1785 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1786 roomname, msgid, flags);
1788 strcpy(hold_rm, CC->room.QRname);
1790 /* We may need to check to see if this message is real */
1791 if ( (flags & SM_VERIFY_GOODNESS)
1792 || (flags & SM_DO_REPL_CHECK)
1794 msg = CtdlFetchMessage(msgid, 1);
1795 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1798 /* Perform replication checks if necessary */
1799 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1801 if (getroom(&CC->room,
1802 ((roomname != NULL) ? roomname : CC->room.QRname) )
1804 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1805 if (msg != NULL) CtdlFreeMessage(msg);
1806 return(ERROR + ROOM_NOT_FOUND);
1809 if (ReplicationChecks(msg) != 0) {
1810 getroom(&CC->room, hold_rm);
1811 if (msg != NULL) CtdlFreeMessage(msg);
1813 "Did replication, and newer exists\n");
1818 /* Now the regular stuff */
1819 if (lgetroom(&CC->room,
1820 ((roomname != NULL) ? roomname : CC->room.QRname) )
1822 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1823 if (msg != NULL) CtdlFreeMessage(msg);
1824 return(ERROR + ROOM_NOT_FOUND);
1827 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1828 if (cdbfr == NULL) {
1832 msglist = malloc(cdbfr->len);
1833 if (msglist == NULL)
1834 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1835 num_msgs = cdbfr->len / sizeof(long);
1836 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1841 /* Make sure the message doesn't already exist in this room. It
1842 * is absolutely taboo to have more than one reference to the same
1843 * message in a room.
1845 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1846 if (msglist[i] == msgid) {
1847 lputroom(&CC->room); /* unlock the room */
1848 getroom(&CC->room, hold_rm);
1849 if (msg != NULL) CtdlFreeMessage(msg);
1851 return(ERROR + ALREADY_EXISTS);
1855 /* Now add the new message */
1857 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1859 if (msglist == NULL) {
1860 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1862 msglist[num_msgs - 1] = msgid;
1864 /* Sort the message list, so all the msgid's are in order */
1865 num_msgs = sort_msglist(msglist, num_msgs);
1867 /* Determine the highest message number */
1868 highest_msg = msglist[num_msgs - 1];
1870 /* Write it back to disk. */
1871 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1872 msglist, (int)(num_msgs * sizeof(long)));
1874 /* Free up the memory we used. */
1877 /* Update the highest-message pointer and unlock the room. */
1878 CC->room.QRhighest = highest_msg;
1879 lputroom(&CC->room);
1880 getroom(&CC->room, hold_rm);
1882 /* Bump the reference count for this message. */
1883 if ((flags & SM_DONT_BUMP_REF)==0) {
1884 AdjRefCount(msgid, +1);
1887 /* Return success. */
1888 if (msg != NULL) CtdlFreeMessage(msg);
1895 * Message base operation to save a new message to the message store
1896 * (returns new message number)
1898 * This is the back end for CtdlSubmitMsg() and should not be directly
1899 * called by server-side modules.
1902 long send_message(struct CtdlMessage *msg) {
1910 /* Get a new message number */
1911 newmsgid = get_new_message_number();
1912 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1914 /* Generate an ID if we don't have one already */
1915 if (msg->cm_fields['I']==NULL) {
1916 msg->cm_fields['I'] = strdup(msgidbuf);
1919 /* If the message is big, set its body aside for storage elsewhere */
1920 if (msg->cm_fields['M'] != NULL) {
1921 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1923 holdM = msg->cm_fields['M'];
1924 msg->cm_fields['M'] = NULL;
1928 /* Serialize our data structure for storage in the database */
1929 serialize_message(&smr, msg);
1932 msg->cm_fields['M'] = holdM;
1936 cprintf("%d Unable to serialize message\n",
1937 ERROR + INTERNAL_ERROR);
1941 /* Write our little bundle of joy into the message base */
1942 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1943 smr.ser, smr.len) < 0) {
1944 lprintf(CTDL_ERR, "Can't store message\n");
1948 cdb_store(CDB_BIGMSGS,
1958 /* Free the memory we used for the serialized message */
1961 /* Return the *local* message ID to the caller
1962 * (even if we're storing an incoming network message)
1970 * Serialize a struct CtdlMessage into the format used on disk and network.
1972 * This function loads up a "struct ser_ret" (defined in server.h) which
1973 * contains the length of the serialized message and a pointer to the
1974 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1976 void serialize_message(struct ser_ret *ret, /* return values */
1977 struct CtdlMessage *msg) /* unserialized msg */
1981 static char *forder = FORDER;
1983 if (is_valid_message(msg) == 0) return; /* self check */
1986 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1987 ret->len = ret->len +
1988 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1990 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1991 ret->ser = malloc(ret->len);
1992 if (ret->ser == NULL) {
1998 ret->ser[1] = msg->cm_anon_type;
1999 ret->ser[2] = msg->cm_format_type;
2002 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2003 ret->ser[wlen++] = (char)forder[i];
2004 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2005 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2007 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2008 (long)ret->len, (long)wlen);
2016 * Back end for the ReplicationChecks() function
2018 void check_repl(long msgnum, void *userdata) {
2019 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
2020 CtdlDeleteMessages(CC->room.QRname, msgnum, "", 0);
2025 * Check to see if any messages already exist which carry the same Exclusive ID
2026 * as this one. If any are found, delete them.
2029 int ReplicationChecks(struct CtdlMessage *msg) {
2030 struct CtdlMessage *template;
2033 /* No exclusive id? Don't do anything. */
2034 if (msg->cm_fields['E'] == NULL) return 0;
2035 if (strlen(msg->cm_fields['E']) == 0) return 0;
2036 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
2038 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
2039 memset(template, 0, sizeof(struct CtdlMessage));
2040 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
2042 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
2044 CtdlFreeMessage(template);
2045 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
2052 * Save a message to disk and submit it into the delivery system.
2054 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2055 struct recptypes *recps, /* recipients (if mail) */
2056 char *force /* force a particular room? */
2058 char submit_filename[128];
2059 char generated_timestamp[32];
2060 char hold_rm[ROOMNAMELEN];
2061 char actual_rm[ROOMNAMELEN];
2062 char force_room[ROOMNAMELEN];
2063 char content_type[SIZ]; /* We have to learn this */
2064 char recipient[SIZ];
2067 struct ctdluser userbuf;
2069 struct MetaData smi;
2070 FILE *network_fp = NULL;
2071 static int seqnum = 1;
2072 struct CtdlMessage *imsg = NULL;
2075 char *hold_R, *hold_D;
2076 char *collected_addresses = NULL;
2077 struct addresses_to_be_filed *aptr = NULL;
2079 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2080 if (is_valid_message(msg) == 0) return(-1); /* self check */
2082 /* If this message has no timestamp, we take the liberty of
2083 * giving it one, right now.
2085 if (msg->cm_fields['T'] == NULL) {
2086 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2087 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2088 msg->cm_fields['T'] = strdup(generated_timestamp);
2091 /* If this message has no path, we generate one.
2093 if (msg->cm_fields['P'] == NULL) {
2094 lprintf(CTDL_DEBUG, "Generating path\n");
2095 if (msg->cm_fields['A'] != NULL) {
2096 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2097 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2098 if (isspace(msg->cm_fields['P'][a])) {
2099 msg->cm_fields['P'][a] = ' ';
2104 msg->cm_fields['P'] = strdup("unknown");
2108 if (force == NULL) {
2109 strcpy(force_room, "");
2112 strcpy(force_room, force);
2115 /* Learn about what's inside, because it's what's inside that counts */
2116 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2117 if (msg->cm_fields['M'] == NULL) {
2118 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2122 switch (msg->cm_format_type) {
2124 strcpy(content_type, "text/x-citadel-variformat");
2127 strcpy(content_type, "text/plain");
2130 strcpy(content_type, "text/plain");
2131 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2133 safestrncpy(content_type, &mptr[14],
2134 sizeof content_type);
2135 for (a = 0; a < strlen(content_type); ++a) {
2136 if ((content_type[a] == ';')
2137 || (content_type[a] == ' ')
2138 || (content_type[a] == 13)
2139 || (content_type[a] == 10)) {
2140 content_type[a] = 0;
2146 /* Goto the correct room */
2147 lprintf(CTDL_DEBUG, "Selected room %s\n",
2148 (recps) ? CC->room.QRname : SENTITEMS);
2149 strcpy(hold_rm, CC->room.QRname);
2150 strcpy(actual_rm, CC->room.QRname);
2151 if (recps != NULL) {
2152 strcpy(actual_rm, SENTITEMS);
2155 /* If the user is a twit, move to the twit room for posting */
2156 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2157 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2159 if (CC->user.axlevel == 2) {
2160 strcpy(hold_rm, actual_rm);
2161 strcpy(actual_rm, config.c_twitroom);
2165 /* ...or if this message is destined for Aide> then go there. */
2166 if (strlen(force_room) > 0) {
2167 strcpy(actual_rm, force_room);
2170 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2171 if (strcasecmp(actual_rm, CC->room.QRname)) {
2172 /* getroom(&CC->room, actual_rm); */
2173 usergoto(actual_rm, 0, 1, NULL, NULL);
2177 * If this message has no O (room) field, generate one.
2179 if (msg->cm_fields['O'] == NULL) {
2180 msg->cm_fields['O'] = strdup(CC->room.QRname);
2183 /* Perform "before save" hooks (aborting if any return nonzero) */
2184 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2185 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2187 /* If this message has an Exclusive ID, perform replication checks */
2188 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2189 if (ReplicationChecks(msg) > 0) return(-4);
2191 /* Save it to disk */
2192 lprintf(CTDL_DEBUG, "Saving to disk\n");
2193 newmsgid = send_message(msg);
2194 if (newmsgid <= 0L) return(-5);
2196 /* Write a supplemental message info record. This doesn't have to
2197 * be a critical section because nobody else knows about this message
2200 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2201 memset(&smi, 0, sizeof(struct MetaData));
2202 smi.meta_msgnum = newmsgid;
2203 smi.meta_refcount = 0;
2204 safestrncpy(smi.meta_content_type, content_type,
2205 sizeof smi.meta_content_type);
2207 /* As part of the new metadata record, measure how
2208 * big this message will be when displayed as RFC822.
2209 * Both POP and IMAP use this, and it's best to just take the hit now
2210 * instead of having to potentially measure thousands of messages when
2211 * a mailbox is opened later.
2214 if (CC->redirect_buffer != NULL) {
2215 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2218 CC->redirect_buffer = malloc(SIZ);
2219 CC->redirect_len = 0;
2220 CC->redirect_alloc = SIZ;
2221 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2222 smi.meta_rfc822_length = CC->redirect_len;
2223 free(CC->redirect_buffer);
2224 CC->redirect_buffer = NULL;
2225 CC->redirect_len = 0;
2226 CC->redirect_alloc = 0;
2230 /* Now figure out where to store the pointers */
2231 lprintf(CTDL_DEBUG, "Storing pointers\n");
2233 /* If this is being done by the networker delivering a private
2234 * message, we want to BYPASS saving the sender's copy (because there
2235 * is no local sender; it would otherwise go to the Trashcan).
2237 if ((!CC->internal_pgm) || (recps == NULL)) {
2238 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2239 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2240 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2245 /* For internet mail, drop a copy in the outbound queue room */
2247 if (recps->num_internet > 0) {
2248 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2251 /* If other rooms are specified, drop them there too. */
2253 if (recps->num_room > 0)
2254 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2255 extract_token(recipient, recps->recp_room, i,
2256 '|', sizeof recipient);
2257 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2258 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2261 /* Bump this user's messages posted counter. */
2262 lprintf(CTDL_DEBUG, "Updating user\n");
2263 lgetuser(&CC->user, CC->curr_user);
2264 CC->user.posted = CC->user.posted + 1;
2265 lputuser(&CC->user);
2267 /* If this is private, local mail, make a copy in the
2268 * recipient's mailbox and bump the reference count.
2271 if (recps->num_local > 0)
2272 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2273 extract_token(recipient, recps->recp_local, i,
2274 '|', sizeof recipient);
2275 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2277 if (getuser(&userbuf, recipient) == 0) {
2278 MailboxName(actual_rm, sizeof actual_rm,
2279 &userbuf, MAILROOM);
2280 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2281 BumpNewMailCounter(userbuf.usernum);
2284 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2285 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2290 /* Perform "after save" hooks */
2291 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2292 PerformMessageHooks(msg, EVT_AFTERSAVE);
2294 /* For IGnet mail, we have to save a new copy into the spooler for
2295 * each recipient, with the R and D fields set to the recipient and
2296 * destination-node. This has two ugly side effects: all other
2297 * recipients end up being unlisted in this recipient's copy of the
2298 * message, and it has to deliver multiple messages to the same
2299 * node. We'll revisit this again in a year or so when everyone has
2300 * a network spool receiver that can handle the new style messages.
2303 if (recps->num_ignet > 0)
2304 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2305 extract_token(recipient, recps->recp_ignet, i,
2306 '|', sizeof recipient);
2308 hold_R = msg->cm_fields['R'];
2309 hold_D = msg->cm_fields['D'];
2310 msg->cm_fields['R'] = malloc(SIZ);
2311 msg->cm_fields['D'] = malloc(128);
2312 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2313 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2315 serialize_message(&smr, msg);
2317 snprintf(submit_filename, sizeof submit_filename,
2318 #ifndef HAVE_SPOOL_DIR
2323 "/network/spoolin/netmail.%04lx.%04x.%04x",
2324 (long) getpid(), CC->cs_pid, ++seqnum);
2325 network_fp = fopen(submit_filename, "wb+");
2326 if (network_fp != NULL) {
2327 fwrite(smr.ser, smr.len, 1, network_fp);
2333 free(msg->cm_fields['R']);
2334 free(msg->cm_fields['D']);
2335 msg->cm_fields['R'] = hold_R;
2336 msg->cm_fields['D'] = hold_D;
2339 /* Go back to the room we started from */
2340 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2341 if (strcasecmp(hold_rm, CC->room.QRname))
2342 /* getroom(&CC->room, hold_rm); */
2343 usergoto(hold_rm, 0, 1, NULL, NULL);
2345 /* For internet mail, generate delivery instructions.
2346 * Yes, this is recursive. Deal with it. Infinite recursion does
2347 * not happen because the delivery instructions message does not
2348 * contain a recipient.
2351 if (recps->num_internet > 0) {
2352 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2353 instr = malloc(SIZ * 2);
2354 snprintf(instr, SIZ * 2,
2355 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2357 SPOOLMIME, newmsgid, (long)time(NULL),
2358 msg->cm_fields['A'], msg->cm_fields['N']
2361 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2362 size_t tmp = strlen(instr);
2363 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2364 snprintf(&instr[tmp], SIZ * 2 - tmp,
2365 "remote|%s|0||\n", recipient);
2368 imsg = malloc(sizeof(struct CtdlMessage));
2369 memset(imsg, 0, sizeof(struct CtdlMessage));
2370 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2371 imsg->cm_anon_type = MES_NORMAL;
2372 imsg->cm_format_type = FMT_RFC822;
2373 imsg->cm_fields['A'] = strdup("Citadel");
2374 imsg->cm_fields['M'] = instr;
2375 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2376 CtdlFreeMessage(imsg);
2380 * Any addresses to harvest for someone's address book?
2382 if ( (CC->logged_in) && (recps != NULL) ) {
2383 collected_addresses = harvest_collected_addresses(msg);
2386 if (collected_addresses != NULL) {
2387 begin_critical_section(S_ATBF);
2388 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2390 MailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2391 aptr->roomname = strdup(actual_rm);
2392 aptr->collected_addresses = collected_addresses;
2394 end_critical_section(S_ATBF);
2407 * Convenience function for generating small administrative messages.
2409 void quickie_message(char *from, char *to, char *room, char *text,
2410 int format_type, char *subject)
2412 struct CtdlMessage *msg;
2413 struct recptypes *recp = NULL;
2415 msg = malloc(sizeof(struct CtdlMessage));
2416 memset(msg, 0, sizeof(struct CtdlMessage));
2417 msg->cm_magic = CTDLMESSAGE_MAGIC;
2418 msg->cm_anon_type = MES_NORMAL;
2419 msg->cm_format_type = format_type;
2420 msg->cm_fields['A'] = strdup(from);
2421 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2422 msg->cm_fields['N'] = strdup(NODENAME);
2424 msg->cm_fields['R'] = strdup(to);
2425 recp = validate_recipients(to);
2427 if (subject != NULL) {
2428 msg->cm_fields['U'] = strdup(subject);
2430 msg->cm_fields['M'] = strdup(text);
2432 CtdlSubmitMsg(msg, recp, room);
2433 CtdlFreeMessage(msg);
2434 if (recp != NULL) free(recp);
2440 * Back end function used by CtdlMakeMessage() and similar functions
2442 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2443 size_t maxlen, /* maximum message length */
2444 char *exist, /* if non-null, append to it;
2445 exist is ALWAYS freed */
2446 int crlf /* CRLF newlines instead of LF */
2450 size_t message_len = 0;
2451 size_t buffer_len = 0;
2457 if (exist == NULL) {
2464 message_len = strlen(exist);
2465 buffer_len = message_len + 4096;
2466 m = realloc(exist, buffer_len);
2473 /* flush the input if we have nowhere to store it */
2478 /* read in the lines of message text one by one */
2480 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2481 if (!strcmp(buf, terminator)) finished = 1;
2483 strcat(buf, "\r\n");
2489 if ( (!flushing) && (!finished) ) {
2490 /* Measure the line */
2491 linelen = strlen(buf);
2493 /* augment the buffer if we have to */
2494 if ((message_len + linelen) >= buffer_len) {
2495 ptr = realloc(m, (buffer_len * 2) );
2496 if (ptr == NULL) { /* flush if can't allocate */
2499 buffer_len = (buffer_len * 2);
2501 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2505 /* Add the new line to the buffer. NOTE: this loop must avoid
2506 * using functions like strcat() and strlen() because they
2507 * traverse the entire buffer upon every call, and doing that
2508 * for a multi-megabyte message slows it down beyond usability.
2510 strcpy(&m[message_len], buf);
2511 message_len += linelen;
2514 /* if we've hit the max msg length, flush the rest */
2515 if (message_len >= maxlen) flushing = 1;
2517 } while (!finished);
2525 * Build a binary message to be saved on disk.
2526 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2527 * will become part of the message. This means you are no longer
2528 * responsible for managing that memory -- it will be freed along with
2529 * the rest of the fields when CtdlFreeMessage() is called.)
2532 struct CtdlMessage *CtdlMakeMessage(
2533 struct ctdluser *author, /* author's user structure */
2534 char *recipient, /* NULL if it's not mail */
2535 char *recp_cc, /* NULL if it's not mail */
2536 char *room, /* room where it's going */
2537 int type, /* see MES_ types in header file */
2538 int format_type, /* variformat, plain text, MIME... */
2539 char *fake_name, /* who we're masquerading as */
2540 char *subject, /* Subject (optional) */
2541 char *preformatted_text /* ...or NULL to read text from client */
2543 char dest_node[SIZ];
2545 struct CtdlMessage *msg;
2547 msg = malloc(sizeof(struct CtdlMessage));
2548 memset(msg, 0, sizeof(struct CtdlMessage));
2549 msg->cm_magic = CTDLMESSAGE_MAGIC;
2550 msg->cm_anon_type = type;
2551 msg->cm_format_type = format_type;
2553 /* Don't confuse the poor folks if it's not routed mail. */
2554 strcpy(dest_node, "");
2559 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2560 msg->cm_fields['P'] = strdup(buf);
2562 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2563 msg->cm_fields['T'] = strdup(buf);
2565 if (fake_name[0]) /* author */
2566 msg->cm_fields['A'] = strdup(fake_name);
2568 msg->cm_fields['A'] = strdup(author->fullname);
2570 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2571 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2574 msg->cm_fields['O'] = strdup(CC->room.QRname);
2577 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2578 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2580 if (recipient[0] != 0) {
2581 msg->cm_fields['R'] = strdup(recipient);
2583 if (recp_cc[0] != 0) {
2584 msg->cm_fields['Y'] = strdup(recp_cc);
2586 if (dest_node[0] != 0) {
2587 msg->cm_fields['D'] = strdup(dest_node);
2590 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2591 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2594 if (subject != NULL) {
2596 if (strlen(subject) > 0) {
2597 msg->cm_fields['U'] = strdup(subject);
2601 if (preformatted_text != NULL) {
2602 msg->cm_fields['M'] = preformatted_text;
2605 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2606 config.c_maxmsglen, NULL, 0);
2614 * Check to see whether we have permission to post a message in the current
2615 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2616 * returns 0 on success.
2618 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2620 if (!(CC->logged_in)) {
2621 snprintf(errmsgbuf, n, "Not logged in.");
2622 return (ERROR + NOT_LOGGED_IN);
2625 if ((CC->user.axlevel < 2)
2626 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2627 snprintf(errmsgbuf, n, "Need to be validated to enter "
2628 "(except in %s> to sysop)", MAILROOM);
2629 return (ERROR + HIGHER_ACCESS_REQUIRED);
2632 if ((CC->user.axlevel < 4)
2633 && (CC->room.QRflags & QR_NETWORK)) {
2634 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2635 return (ERROR + HIGHER_ACCESS_REQUIRED);
2638 if ((CC->user.axlevel < 6)
2639 && (CC->room.QRflags & QR_READONLY)) {
2640 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2641 return (ERROR + HIGHER_ACCESS_REQUIRED);
2644 strcpy(errmsgbuf, "Ok");
2650 * Check to see if the specified user has Internet mail permission
2651 * (returns nonzero if permission is granted)
2653 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2655 /* Do not allow twits to send Internet mail */
2656 if (who->axlevel <= 2) return(0);
2658 /* Globally enabled? */
2659 if (config.c_restrict == 0) return(1);
2661 /* User flagged ok? */
2662 if (who->flags & US_INTERNET) return(2);
2664 /* Aide level access? */
2665 if (who->axlevel >= 6) return(3);
2667 /* No mail for you! */
2673 * Validate recipients, count delivery types and errors, and handle aliasing
2674 * FIXME check for dupes!!!!!
2675 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2676 * or the number of addresses found invalid.
2678 struct recptypes *validate_recipients(char *recipients) {
2679 struct recptypes *ret;
2680 char this_recp[SIZ];
2681 char this_recp_cooked[SIZ];
2687 struct ctdluser tempUS;
2688 struct ctdlroom tempQR;
2691 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2692 if (ret == NULL) return(NULL);
2693 memset(ret, 0, sizeof(struct recptypes));
2696 ret->num_internet = 0;
2701 if (recipients == NULL) {
2704 else if (strlen(recipients) == 0) {
2708 /* Change all valid separator characters to commas */
2709 for (i=0; i<strlen(recipients); ++i) {
2710 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2711 recipients[i] = ',';
2716 num_recps = num_tokens(recipients, ',');
2719 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2720 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2722 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2723 mailtype = alias(this_recp);
2724 mailtype = alias(this_recp);
2725 mailtype = alias(this_recp);
2726 for (j=0; j<=strlen(this_recp); ++j) {
2727 if (this_recp[j]=='_') {
2728 this_recp_cooked[j] = ' ';
2731 this_recp_cooked[j] = this_recp[j];
2737 if (!strcasecmp(this_recp, "sysop")) {
2739 strcpy(this_recp, config.c_aideroom);
2740 if (strlen(ret->recp_room) > 0) {
2741 strcat(ret->recp_room, "|");
2743 strcat(ret->recp_room, this_recp);
2745 else if (getuser(&tempUS, this_recp) == 0) {
2747 strcpy(this_recp, tempUS.fullname);
2748 if (strlen(ret->recp_local) > 0) {
2749 strcat(ret->recp_local, "|");
2751 strcat(ret->recp_local, this_recp);
2753 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2755 strcpy(this_recp, tempUS.fullname);
2756 if (strlen(ret->recp_local) > 0) {
2757 strcat(ret->recp_local, "|");
2759 strcat(ret->recp_local, this_recp);
2761 else if ( (!strncasecmp(this_recp, "room_", 5))
2762 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2764 if (strlen(ret->recp_room) > 0) {
2765 strcat(ret->recp_room, "|");
2767 strcat(ret->recp_room, &this_recp_cooked[5]);
2775 /* Yes, you're reading this correctly: if the target
2776 * domain points back to the local system or an attached
2777 * Citadel directory, the address is invalid. That's
2778 * because if the address were valid, we would have
2779 * already translated it to a local address by now.
2781 if (IsDirectory(this_recp)) {
2786 ++ret->num_internet;
2787 if (strlen(ret->recp_internet) > 0) {
2788 strcat(ret->recp_internet, "|");
2790 strcat(ret->recp_internet, this_recp);
2795 if (strlen(ret->recp_ignet) > 0) {
2796 strcat(ret->recp_ignet, "|");
2798 strcat(ret->recp_ignet, this_recp);
2806 if (strlen(ret->errormsg) == 0) {
2807 snprintf(append, sizeof append,
2808 "Invalid recipient: %s",
2812 snprintf(append, sizeof append,
2815 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2816 strcat(ret->errormsg, append);
2820 if (strlen(ret->display_recp) == 0) {
2821 strcpy(append, this_recp);
2824 snprintf(append, sizeof append, ", %s",
2827 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2828 strcat(ret->display_recp, append);
2833 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2834 ret->num_room + ret->num_error) == 0) {
2835 ret->num_error = (-1);
2836 strcpy(ret->errormsg, "No recipients specified.");
2839 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2840 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2841 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2842 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2843 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2844 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2852 * message entry - mode 0 (normal)
2854 void cmd_ent0(char *entargs)
2860 char masquerade_as[SIZ];
2862 int format_type = 0;
2863 char newusername[SIZ];
2864 struct CtdlMessage *msg;
2868 struct recptypes *valid = NULL;
2869 struct recptypes *valid_to = NULL;
2870 struct recptypes *valid_cc = NULL;
2871 struct recptypes *valid_bcc = NULL;
2878 post = extract_int(entargs, 0);
2879 extract_token(recp, entargs, 1, '|', sizeof recp);
2880 anon_flag = extract_int(entargs, 2);
2881 format_type = extract_int(entargs, 3);
2882 extract_token(subject, entargs, 4, '|', sizeof subject);
2883 do_confirm = extract_int(entargs, 6);
2884 extract_token(cc, entargs, 7, '|', sizeof cc);
2885 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2887 /* first check to make sure the request is valid. */
2889 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2891 cprintf("%d %s\n", err, errmsg);
2895 /* Check some other permission type things. */
2898 if (CC->user.axlevel < 6) {
2899 cprintf("%d You don't have permission to masquerade.\n",
2900 ERROR + HIGHER_ACCESS_REQUIRED);
2903 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2904 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2905 safestrncpy(CC->fake_postname, newusername,
2906 sizeof(CC->fake_postname) );
2907 cprintf("%d ok\n", CIT_OK);
2910 CC->cs_flags |= CS_POSTING;
2912 /* In the Mail> room we have to behave a little differently --
2913 * make sure the user has specified at least one recipient. Then
2914 * validate the recipient(s).
2916 if ( (CC->room.QRflags & QR_MAILBOX)
2917 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2919 if (CC->user.axlevel < 2) {
2920 strcpy(recp, "sysop");
2925 valid_to = validate_recipients(recp);
2926 if (valid_to->num_error > 0) {
2927 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2932 valid_cc = validate_recipients(cc);
2933 if (valid_cc->num_error > 0) {
2934 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2940 valid_bcc = validate_recipients(bcc);
2941 if (valid_bcc->num_error > 0) {
2942 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2949 /* Recipient required, but none were specified */
2950 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2954 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2958 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2959 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2960 cprintf("%d You do not have permission "
2961 "to send Internet mail.\n",
2962 ERROR + HIGHER_ACCESS_REQUIRED);
2970 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)
2971 && (CC->user.axlevel < 4) ) {
2972 cprintf("%d Higher access required for network mail.\n",
2973 ERROR + HIGHER_ACCESS_REQUIRED);
2980 if ((RESTRICT_INTERNET == 1)
2981 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2982 && ((CC->user.flags & US_INTERNET) == 0)
2983 && (!CC->internal_pgm)) {
2984 cprintf("%d You don't have access to Internet mail.\n",
2985 ERROR + HIGHER_ACCESS_REQUIRED);
2994 /* Is this a room which has anonymous-only or anonymous-option? */
2995 anonymous = MES_NORMAL;
2996 if (CC->room.QRflags & QR_ANONONLY) {
2997 anonymous = MES_ANONONLY;
2999 if (CC->room.QRflags & QR_ANONOPT) {
3000 if (anon_flag == 1) { /* only if the user requested it */
3001 anonymous = MES_ANONOPT;
3005 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3009 /* If we're only checking the validity of the request, return
3010 * success without creating the message.
3013 cprintf("%d %s\n", CIT_OK,
3014 ((valid_to != NULL) ? valid_to->display_recp : "") );
3021 /* We don't need these anymore because we'll do it differently below */
3026 /* Handle author masquerading */
3027 if (CC->fake_postname[0]) {
3028 strcpy(masquerade_as, CC->fake_postname);
3030 else if (CC->fake_username[0]) {
3031 strcpy(masquerade_as, CC->fake_username);
3034 strcpy(masquerade_as, "");
3037 /* Read in the message from the client. */
3039 cprintf("%d send message\n", START_CHAT_MODE);
3041 cprintf("%d send message\n", SEND_LISTING);
3044 msg = CtdlMakeMessage(&CC->user, recp, cc,
3045 CC->room.QRname, anonymous, format_type,
3046 masquerade_as, subject, NULL);
3048 /* Put together one big recipients struct containing to/cc/bcc all in
3049 * one. This is for the envelope.
3051 char *all_recps = malloc(SIZ * 3);
3052 strcpy(all_recps, recp);
3053 if (strlen(cc) > 0) {
3054 if (strlen(all_recps) > 0) {
3055 strcat(all_recps, ",");
3057 strcat(all_recps, cc);
3059 if (strlen(bcc) > 0) {
3060 if (strlen(all_recps) > 0) {
3061 strcat(all_recps, ",");
3063 strcat(all_recps, bcc);
3065 if (strlen(all_recps) > 0) {
3066 valid = validate_recipients(all_recps);
3074 msgnum = CtdlSubmitMsg(msg, valid, "");
3077 cprintf("%ld\n", msgnum);
3079 cprintf("Message accepted.\n");
3082 cprintf("Internal error.\n");
3084 if (msg->cm_fields['E'] != NULL) {
3085 cprintf("%s\n", msg->cm_fields['E']);
3092 CtdlFreeMessage(msg);
3094 CC->fake_postname[0] = '\0';
3095 if (valid != NULL) {
3104 * API function to delete messages which match a set of criteria
3105 * (returns the actual number of messages deleted)
3107 int CtdlDeleteMessages(char *room_name, /* which room */
3108 long dmsgnum, /* or "0" for any */
3109 char *content_type, /* or "" for any */
3110 int deferred /* let TDAP sweep it later */
3114 struct ctdlroom qrbuf;
3115 struct cdbdata *cdbfr;
3116 long *msglist = NULL;
3117 long *dellist = NULL;
3120 int num_deleted = 0;
3122 struct MetaData smi;
3124 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3125 room_name, dmsgnum, content_type, deferred);
3127 /* get room record, obtaining a lock... */
3128 if (lgetroom(&qrbuf, room_name) != 0) {
3129 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3131 return (0); /* room not found */
3133 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3135 if (cdbfr != NULL) {
3136 msglist = malloc(cdbfr->len);
3137 dellist = malloc(cdbfr->len);
3138 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3139 num_msgs = cdbfr->len / sizeof(long);
3143 for (i = 0; i < num_msgs; ++i) {
3146 /* Set/clear a bit for each criterion */
3148 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3149 delete_this |= 0x01;
3151 if (strlen(content_type) == 0) {
3152 delete_this |= 0x02;
3154 GetMetaData(&smi, msglist[i]);
3155 if (!strcasecmp(smi.meta_content_type,
3157 delete_this |= 0x02;
3161 /* Delete message only if all bits are set */
3162 if (delete_this == 0x03) {
3163 dellist[num_deleted++] = msglist[i];
3168 num_msgs = sort_msglist(msglist, num_msgs);
3169 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3170 msglist, (int)(num_msgs * sizeof(long)));
3172 qrbuf.QRhighest = msglist[num_msgs - 1];
3177 * If the delete operation is "deferred" (and technically, any delete
3178 * operation not performed by THE DREADED AUTO-PURGER ought to be
3179 * a deferred delete) then we save a pointer to the message in the
3180 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3181 * at least 1, which will save the user from having to synchronously
3182 * wait for various disk-intensive operations to complete.
3184 if ( (deferred) && (num_deleted) ) {
3185 for (i=0; i<num_deleted; ++i) {
3186 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3190 /* Go through the messages we pulled out of the index, and decrement
3191 * their reference counts by 1. If this is the only room the message
3192 * was in, the reference count will reach zero and the message will
3193 * automatically be deleted from the database. We do this in a
3194 * separate pass because there might be plug-in hooks getting called,
3195 * and we don't want that happening during an S_ROOMS critical
3198 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3199 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3200 AdjRefCount(dellist[i], -1);
3203 /* Now free the memory we used, and go away. */
3204 if (msglist != NULL) free(msglist);
3205 if (dellist != NULL) free(dellist);
3206 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3207 return (num_deleted);
3213 * Check whether the current user has permission to delete messages from
3214 * the current room (returns 1 for yes, 0 for no)
3216 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3217 getuser(&CC->user, CC->curr_user);
3218 if ((CC->user.axlevel < 6)
3219 && (CC->user.usernum != CC->room.QRroomaide)
3220 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3221 && (!(CC->internal_pgm))) {
3230 * Delete message from current room
3232 void cmd_dele(char *delstr)
3237 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3238 cprintf("%d Higher access required.\n",
3239 ERROR + HIGHER_ACCESS_REQUIRED);
3242 delnum = extract_long(delstr, 0);
3244 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3247 cprintf("%d %d message%s deleted.\n", CIT_OK,
3248 num_deleted, ((num_deleted != 1) ? "s" : ""));
3250 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3256 * Back end API function for moves and deletes
3258 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3261 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3262 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3263 if (err != 0) return(err);
3271 * move or copy a message to another room
3273 void cmd_move(char *args)
3276 char targ[ROOMNAMELEN];
3277 struct ctdlroom qtemp;
3283 num = extract_long(args, 0);
3284 extract_token(targ, args, 1, '|', sizeof targ);
3285 targ[ROOMNAMELEN - 1] = 0;
3286 is_copy = extract_int(args, 2);
3288 if (getroom(&qtemp, targ) != 0) {
3289 cprintf("%d '%s' does not exist.\n",
3290 ERROR + ROOM_NOT_FOUND, targ);
3294 getuser(&CC->user, CC->curr_user);
3295 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3297 /* Check for permission to perform this operation.
3298 * Remember: "CC->room" is source, "qtemp" is target.
3302 /* Aides can move/copy */
3303 if (CC->user.axlevel >= 6) permit = 1;
3305 /* Room aides can move/copy */
3306 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3308 /* Permit move/copy from personal rooms */
3309 if ((CC->room.QRflags & QR_MAILBOX)
3310 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3312 /* Permit only copy from public to personal room */
3314 && (!(CC->room.QRflags & QR_MAILBOX))
3315 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3317 /* User must have access to target room */
3318 if (!(ra & UA_KNOWN)) permit = 0;
3321 cprintf("%d Higher access required.\n",
3322 ERROR + HIGHER_ACCESS_REQUIRED);
3326 err = CtdlCopyMsgToRoom(num, targ);
3328 cprintf("%d Cannot store message in %s: error %d\n",
3333 /* Now delete the message from the source room,
3334 * if this is a 'move' rather than a 'copy' operation.
3337 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3340 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3346 * GetMetaData() - Get the supplementary record for a message
3348 void GetMetaData(struct MetaData *smibuf, long msgnum)
3351 struct cdbdata *cdbsmi;
3354 memset(smibuf, 0, sizeof(struct MetaData));
3355 smibuf->meta_msgnum = msgnum;
3356 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3358 /* Use the negative of the message number for its supp record index */
3359 TheIndex = (0L - msgnum);
3361 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3362 if (cdbsmi == NULL) {
3363 return; /* record not found; go with defaults */
3365 memcpy(smibuf, cdbsmi->ptr,
3366 ((cdbsmi->len > sizeof(struct MetaData)) ?
3367 sizeof(struct MetaData) : cdbsmi->len));
3374 * PutMetaData() - (re)write supplementary record for a message
3376 void PutMetaData(struct MetaData *smibuf)
3380 /* Use the negative of the message number for the metadata db index */
3381 TheIndex = (0L - smibuf->meta_msgnum);
3383 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3384 smibuf->meta_msgnum, smibuf->meta_refcount);
3386 cdb_store(CDB_MSGMAIN,
3387 &TheIndex, (int)sizeof(long),
3388 smibuf, (int)sizeof(struct MetaData));
3393 * AdjRefCount - change the reference count for a message;
3394 * delete the message if it reaches zero
3396 void AdjRefCount(long msgnum, int incr)
3399 struct MetaData smi;
3402 /* This is a *tight* critical section; please keep it that way, as
3403 * it may get called while nested in other critical sections.
3404 * Complicating this any further will surely cause deadlock!
3406 begin_critical_section(S_SUPPMSGMAIN);
3407 GetMetaData(&smi, msgnum);
3408 smi.meta_refcount += incr;
3410 end_critical_section(S_SUPPMSGMAIN);
3411 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3412 msgnum, incr, smi.meta_refcount);
3414 /* If the reference count is now zero, delete the message
3415 * (and its supplementary record as well).
3417 if (smi.meta_refcount == 0) {
3418 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3420 /* Remove from fulltext index */
3421 if (config.c_enable_fulltext) {
3422 ft_index_message(msgnum, 0);
3425 /* Remove from message base */
3427 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3428 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3430 /* Remove metadata record */
3431 delnum = (0L - msgnum);
3432 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3437 * Write a generic object to this room
3439 * Note: this could be much more efficient. Right now we use two temporary
3440 * files, and still pull the message into memory as with all others.
3442 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3443 char *content_type, /* MIME type of this object */
3444 char *tempfilename, /* Where to fetch it from */
3445 struct ctdluser *is_mailbox, /* Mailbox room? */
3446 int is_binary, /* Is encoding necessary? */
3447 int is_unique, /* Del others of this type? */
3448 unsigned int flags /* Internal save flags */
3453 struct ctdlroom qrbuf;
3454 char roomname[ROOMNAMELEN];
3455 struct CtdlMessage *msg;
3457 char *raw_message = NULL;
3458 char *encoded_message = NULL;
3459 off_t raw_length = 0;
3461 if (is_mailbox != NULL)
3462 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3464 safestrncpy(roomname, req_room, sizeof(roomname));
3465 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3468 fp = fopen(tempfilename, "rb");
3470 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3471 tempfilename, strerror(errno));
3474 fseek(fp, 0L, SEEK_END);
3475 raw_length = ftell(fp);
3477 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3479 raw_message = malloc((size_t)raw_length + 2);
3480 fread(raw_message, (size_t)raw_length, 1, fp);
3484 encoded_message = malloc((size_t)
3485 (((raw_length * 134) / 100) + 4096 ) );
3488 encoded_message = malloc((size_t)(raw_length + 4096));
3491 sprintf(encoded_message, "Content-type: %s\n", content_type);
3494 sprintf(&encoded_message[strlen(encoded_message)],
3495 "Content-transfer-encoding: base64\n\n"
3499 sprintf(&encoded_message[strlen(encoded_message)],
3500 "Content-transfer-encoding: 7bit\n\n"
3506 &encoded_message[strlen(encoded_message)],
3512 raw_message[raw_length] = 0;
3514 &encoded_message[strlen(encoded_message)],
3522 lprintf(CTDL_DEBUG, "Allocating\n");
3523 msg = malloc(sizeof(struct CtdlMessage));
3524 memset(msg, 0, sizeof(struct CtdlMessage));
3525 msg->cm_magic = CTDLMESSAGE_MAGIC;
3526 msg->cm_anon_type = MES_NORMAL;
3527 msg->cm_format_type = 4;
3528 msg->cm_fields['A'] = strdup(CC->user.fullname);
3529 msg->cm_fields['O'] = strdup(req_room);
3530 msg->cm_fields['N'] = strdup(config.c_nodename);
3531 msg->cm_fields['H'] = strdup(config.c_humannode);
3532 msg->cm_flags = flags;
3534 msg->cm_fields['M'] = encoded_message;
3536 /* Create the requested room if we have to. */
3537 if (getroom(&qrbuf, roomname) != 0) {
3538 create_room(roomname,
3539 ( (is_mailbox != NULL) ? 5 : 3 ),
3540 "", 0, 1, 0, VIEW_BBS);
3542 /* If the caller specified this object as unique, delete all
3543 * other objects of this type that are currently in the room.
3546 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3547 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3550 /* Now write the data */
3551 CtdlSubmitMsg(msg, NULL, roomname);
3552 CtdlFreeMessage(msg);
3560 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3561 config_msgnum = msgnum;
3565 char *CtdlGetSysConfig(char *sysconfname) {
3566 char hold_rm[ROOMNAMELEN];
3569 struct CtdlMessage *msg;
3572 strcpy(hold_rm, CC->room.QRname);
3573 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3574 getroom(&CC->room, hold_rm);
3579 /* We want the last (and probably only) config in this room */
3580 begin_critical_section(S_CONFIG);
3581 config_msgnum = (-1L);
3582 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3583 CtdlGetSysConfigBackend, NULL);
3584 msgnum = config_msgnum;
3585 end_critical_section(S_CONFIG);
3591 msg = CtdlFetchMessage(msgnum, 1);
3593 conf = strdup(msg->cm_fields['M']);
3594 CtdlFreeMessage(msg);
3601 getroom(&CC->room, hold_rm);
3603 if (conf != NULL) do {
3604 extract_token(buf, conf, 0, '\n', sizeof buf);
3605 strcpy(conf, &conf[strlen(buf)+1]);
3606 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3611 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3612 char temp[PATH_MAX];
3615 strcpy(temp, tmpnam(NULL));
3617 fp = fopen(temp, "w");
3618 if (fp == NULL) return;
3619 fprintf(fp, "%s", sysconfdata);
3622 /* this handy API function does all the work for us */
3623 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3629 * Determine whether a given Internet address belongs to the current user
3631 int CtdlIsMe(char *addr, int addr_buf_len)
3633 struct recptypes *recp;
3636 recp = validate_recipients(addr);
3637 if (recp == NULL) return(0);
3639 if (recp->num_local == 0) {
3644 for (i=0; i<recp->num_local; ++i) {
3645 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3646 if (!strcasecmp(addr, CC->user.fullname)) {
3658 * Citadel protocol command to do the same
3660 void cmd_isme(char *argbuf) {
3663 if (CtdlAccessCheck(ac_logged_in)) return;
3664 extract_token(addr, argbuf, 0, '|', sizeof addr);
3666 if (CtdlIsMe(addr, sizeof addr)) {
3667 cprintf("%d %s\n", CIT_OK, addr);
3670 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);