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"
61 * This really belongs in serv_network.c, but I don't know how to export
62 * symbols between modules.
64 struct FilterList *filterlist = NULL;
68 * These are the four-character field headers we use when outputting
69 * messages in Citadel format (as opposed to RFC822 format).
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
106 * This function is self explanatory.
107 * (What can I say, I'm in a weird mood today...)
109 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
113 for (i = 0; i < strlen(name); ++i) {
114 if (name[i] == '@') {
115 while (isspace(name[i - 1]) && i > 0) {
116 strcpy(&name[i - 1], &name[i]);
119 while (isspace(name[i + 1])) {
120 strcpy(&name[i + 1], &name[i + 2]);
128 * Aliasing for network mail.
129 * (Error messages have been commented out, because this is a server.)
131 int alias(char *name)
132 { /* process alias and routing info for mail */
135 char aaa[SIZ], bbb[SIZ];
136 char *ignetcfg = NULL;
137 char *ignetmap = NULL;
144 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
152 "mail.aliases", "r");
154 fp = fopen("/dev/null", "r");
161 while (fgets(aaa, sizeof aaa, fp) != NULL) {
162 while (isspace(name[0]))
163 strcpy(name, &name[1]);
164 aaa[strlen(aaa) - 1] = 0;
166 for (a = 0; a < strlen(aaa); ++a) {
168 strcpy(bbb, &aaa[a + 1]);
172 if (!strcasecmp(name, aaa))
177 /* Hit the Global Address Book */
178 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
182 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
184 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
185 for (a=0; a<strlen(name); ++a) {
186 if (name[a] == '@') {
187 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
189 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
194 /* determine local or remote type, see citadel.h */
195 at = haschar(name, '@');
196 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
197 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
198 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
200 /* figure out the delivery mode */
201 extract_token(node, name, 1, '@', sizeof node);
203 /* If there are one or more dots in the nodename, we assume that it
204 * is an FQDN and will attempt SMTP delivery to the Internet.
206 if (haschar(node, '.') > 0) {
207 return(MES_INTERNET);
210 /* Otherwise we look in the IGnet maps for a valid Citadel node.
211 * Try directly-connected nodes first...
213 ignetcfg = CtdlGetSysConfig(IGNETCFG);
214 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
215 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
216 extract_token(testnode, buf, 0, '|', sizeof testnode);
217 if (!strcasecmp(node, testnode)) {
225 * Then try nodes that are two or more hops away.
227 ignetmap = CtdlGetSysConfig(IGNETMAP);
228 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
229 extract_token(buf, ignetmap, i, '\n', sizeof buf);
230 extract_token(testnode, buf, 0, '|', sizeof testnode);
231 if (!strcasecmp(node, testnode)) {
238 /* If we get to this point it's an invalid node name */
253 "/citadel.control", "r");
255 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
259 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
265 * Back end for the MSGS command: output message number only.
267 void simple_listing(long msgnum, void *userdata)
269 cprintf("%ld\n", msgnum);
275 * Back end for the MSGS command: output header summary.
277 void headers_listing(long msgnum, void *userdata)
279 struct CtdlMessage *msg;
281 msg = CtdlFetchMessage(msgnum, 0);
283 cprintf("%ld|0|||||\n", msgnum);
287 cprintf("%ld|%s|%s|%s|%s|%s|\n",
289 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
290 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
291 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
292 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
293 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
295 CtdlFreeMessage(msg);
300 /* Determine if a given message matches the fields in a message template.
301 * Return 0 for a successful match.
303 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
306 /* If there aren't any fields in the template, all messages will
309 if (template == NULL) return(0);
311 /* Null messages are bogus. */
312 if (msg == NULL) return(1);
314 for (i='A'; i<='Z'; ++i) {
315 if (template->cm_fields[i] != NULL) {
316 if (msg->cm_fields[i] == NULL) {
319 if (strcasecmp(msg->cm_fields[i],
320 template->cm_fields[i])) return 1;
324 /* All compares succeeded: we have a match! */
331 * Retrieve the "seen" message list for the current room.
333 void CtdlGetSeen(char *buf, int which_set) {
336 /* Learn about the user and room in question */
337 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
339 if (which_set == ctdlsetseen_seen)
340 safestrncpy(buf, vbuf.v_seen, SIZ);
341 if (which_set == ctdlsetseen_answered)
342 safestrncpy(buf, vbuf.v_answered, SIZ);
348 * Manipulate the "seen msgs" string (or other message set strings)
350 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set,
351 struct ctdluser *which_user, struct ctdlroom *which_room) {
352 struct cdbdata *cdbfr;
364 char *is_set; /* actually an array of booleans */
367 char setstr[SIZ], lostr[SIZ], histr[SIZ];
370 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
371 target_msgnum, target_setting, which_set);
373 /* Learn about the user and room in question */
374 CtdlGetRelationship(&vbuf,
375 ((which_user != NULL) ? which_user : &CC->user),
376 ((which_room != NULL) ? which_room : &CC->room)
379 /* Load the message list */
380 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
382 msglist = malloc(cdbfr->len);
383 memcpy(msglist, cdbfr->ptr, cdbfr->len);
384 num_msgs = cdbfr->len / sizeof(long);
387 return; /* No messages at all? No further action. */
390 is_set = malloc(num_msgs * sizeof(char));
391 memset(is_set, 0, (num_msgs * sizeof(char)) );
393 /* Decide which message set we're manipulating */
395 case ctdlsetseen_seen:
396 safestrncpy(vset, vbuf.v_seen, sizeof vset);
398 case ctdlsetseen_answered:
399 safestrncpy(vset, vbuf.v_answered, sizeof vset);
403 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
405 /* Translate the existing sequence set into an array of booleans */
406 num_sets = num_tokens(vset, ',');
407 for (s=0; s<num_sets; ++s) {
408 extract_token(setstr, vset, s, ',', sizeof setstr);
410 extract_token(lostr, setstr, 0, ':', sizeof lostr);
411 if (num_tokens(setstr, ':') >= 2) {
412 extract_token(histr, setstr, 1, ':', sizeof histr);
413 if (!strcmp(histr, "*")) {
414 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
418 strcpy(histr, lostr);
423 for (i = 0; i < num_msgs; ++i) {
424 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
430 /* Now translate the array of booleans back into a sequence set */
435 for (i=0; i<num_msgs; ++i) {
438 if (msglist[i] == target_msgnum) {
439 is_seen = target_setting;
446 if (lo < 0L) lo = msglist[i];
450 if ( ((is_seen == 0) && (was_seen == 1))
451 || ((is_seen == 1) && (i == num_msgs-1)) ) {
453 /* begin trim-o-matic code */
456 while ( (strlen(vset) + 20) > sizeof vset) {
457 remove_token(vset, 0, ',');
459 if (j--) break; /* loop no more than 9 times */
461 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
465 snprintf(lostr, sizeof lostr,
466 "1:%ld,%s", t, vset);
467 safestrncpy(vset, lostr, sizeof vset);
469 /* end trim-o-matic code */
477 snprintf(&vset[tmp], (sizeof vset) - tmp,
481 snprintf(&vset[tmp], (sizeof vset) - tmp,
490 /* Decide which message set we're manipulating */
492 case ctdlsetseen_seen:
493 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
495 case ctdlsetseen_answered:
496 safestrncpy(vbuf.v_answered, vset,
497 sizeof vbuf.v_answered);
502 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
504 CtdlSetRelationship(&vbuf,
505 ((which_user != NULL) ? which_user : &CC->user),
506 ((which_room != NULL) ? which_room : &CC->room)
512 * API function to perform an operation for each qualifying message in the
513 * current room. (Returns the number of messages processed.)
515 int CtdlForEachMessage(int mode, long ref,
517 struct CtdlMessage *compare,
518 void (*CallBack) (long, void *),
524 struct cdbdata *cdbfr;
525 long *msglist = NULL;
527 int num_processed = 0;
530 struct CtdlMessage *msg;
533 int printed_lastold = 0;
535 /* Learn about the user and room in question */
537 getuser(&CC->user, CC->curr_user);
538 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
540 /* Load the message list */
541 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
543 msglist = malloc(cdbfr->len);
544 memcpy(msglist, cdbfr->ptr, cdbfr->len);
545 num_msgs = cdbfr->len / sizeof(long);
548 return 0; /* No messages at all? No further action. */
553 * Now begin the traversal.
555 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
557 /* If the caller is looking for a specific MIME type, filter
558 * out all messages which are not of the type requested.
560 if (content_type != NULL) if (strlen(content_type) > 0) {
562 /* This call to GetMetaData() sits inside this loop
563 * so that we only do the extra database read per msg
564 * if we need to. Doing the extra read all the time
565 * really kills the server. If we ever need to use
566 * metadata for another search criterion, we need to
567 * move the read somewhere else -- but still be smart
568 * enough to only do the read if the caller has
569 * specified something that will need it.
571 GetMetaData(&smi, msglist[a]);
573 if (strcasecmp(smi.meta_content_type, content_type)) {
579 num_msgs = sort_msglist(msglist, num_msgs);
581 /* If a template was supplied, filter out the messages which
582 * don't match. (This could induce some delays!)
585 if (compare != NULL) {
586 for (a = 0; a < num_msgs; ++a) {
587 msg = CtdlFetchMessage(msglist[a], 1);
589 if (CtdlMsgCmp(msg, compare)) {
592 CtdlFreeMessage(msg);
600 * Now iterate through the message list, according to the
601 * criteria supplied by the caller.
604 for (a = 0; a < num_msgs; ++a) {
605 thismsg = msglist[a];
606 if (mode == MSGS_ALL) {
610 is_seen = is_msg_in_sequence_set(
611 vbuf.v_seen, thismsg);
612 if (is_seen) lastold = thismsg;
618 || ((mode == MSGS_OLD) && (is_seen))
619 || ((mode == MSGS_NEW) && (!is_seen))
620 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
621 || ((mode == MSGS_FIRST) && (a < ref))
622 || ((mode == MSGS_GT) && (thismsg > ref))
623 || ((mode == MSGS_EQ) && (thismsg == ref))
626 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
628 CallBack(lastold, userdata);
632 if (CallBack) CallBack(thismsg, userdata);
636 free(msglist); /* Clean up */
637 return num_processed;
643 * cmd_msgs() - get list of message #'s in this room
644 * implements the MSGS server command using CtdlForEachMessage()
646 void cmd_msgs(char *cmdbuf)
655 int with_template = 0;
656 struct CtdlMessage *template = NULL;
657 int with_headers = 0;
659 extract_token(which, cmdbuf, 0, '|', sizeof which);
660 cm_ref = extract_int(cmdbuf, 1);
661 with_template = extract_int(cmdbuf, 2);
662 with_headers = extract_int(cmdbuf, 3);
666 if (!strncasecmp(which, "OLD", 3))
668 else if (!strncasecmp(which, "NEW", 3))
670 else if (!strncasecmp(which, "FIRST", 5))
672 else if (!strncasecmp(which, "LAST", 4))
674 else if (!strncasecmp(which, "GT", 2))
677 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
678 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
684 cprintf("%d Send template then receive message list\n",
686 template = (struct CtdlMessage *)
687 malloc(sizeof(struct CtdlMessage));
688 memset(template, 0, sizeof(struct CtdlMessage));
689 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
690 extract_token(tfield, buf, 0, '|', sizeof tfield);
691 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
692 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
693 if (!strcasecmp(tfield, msgkeys[i])) {
694 template->cm_fields[i] =
702 cprintf("%d \n", LISTING_FOLLOWS);
705 CtdlForEachMessage(mode,
709 (with_headers ? headers_listing : simple_listing),
712 if (template != NULL) CtdlFreeMessage(template);
720 * help_subst() - support routine for help file viewer
722 void help_subst(char *strbuf, char *source, char *dest)
727 while (p = pattern2(strbuf, source), (p >= 0)) {
728 strcpy(workbuf, &strbuf[p + strlen(source)]);
729 strcpy(&strbuf[p], dest);
730 strcat(strbuf, workbuf);
735 void do_help_subst(char *buffer)
739 help_subst(buffer, "^nodename", config.c_nodename);
740 help_subst(buffer, "^humannode", config.c_humannode);
741 help_subst(buffer, "^fqdn", config.c_fqdn);
742 help_subst(buffer, "^username", CC->user.fullname);
743 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
744 help_subst(buffer, "^usernum", buf2);
745 help_subst(buffer, "^sysadm", config.c_sysadm);
746 help_subst(buffer, "^variantname", CITADEL);
747 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
748 help_subst(buffer, "^maxsessions", buf2);
749 help_subst(buffer, "^bbsdir", CTDLDIR);
755 * memfmout() - Citadel text formatter and paginator.
756 * Although the original purpose of this routine was to format
757 * text to the reader's screen width, all we're really using it
758 * for here is to format text out to 80 columns before sending it
759 * to the client. The client software may reformat it again.
762 int width, /* screen width to use */
763 char *mptr, /* where are we going to get our text from? */
764 char subst, /* nonzero if we should do substitutions */
765 char *nl) /* string to terminate lines with */
777 c = 1; /* c is the current pos */
781 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
783 buffer[strlen(buffer) + 1] = 0;
784 buffer[strlen(buffer)] = ch;
787 if (buffer[0] == '^')
788 do_help_subst(buffer);
790 buffer[strlen(buffer) + 1] = 0;
792 strcpy(buffer, &buffer[1]);
800 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
802 if (((old == 13) || (old == 10)) && (isspace(real))) {
810 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
811 cprintf("%s%s", nl, aaa);
820 if ((strlen(aaa) + c) > (width - 5)) {
829 if ((ch == 13) || (ch == 10)) {
830 cprintf("%s%s", aaa, nl);
837 cprintf("%s%s", aaa, nl);
843 * Callback function for mime parser that simply lists the part
845 void list_this_part(char *name, char *filename, char *partnum, char *disp,
846 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
850 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
851 name, filename, partnum, disp, cbtype, (long)length);
855 * Callback function for multipart prefix
857 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
858 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
861 cprintf("pref=%s|%s\n", partnum, cbtype);
865 * Callback function for multipart sufffix
867 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
868 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
871 cprintf("suff=%s|%s\n", partnum, cbtype);
876 * Callback function for mime parser that opens a section for downloading
878 void mime_download(char *name, char *filename, char *partnum, char *disp,
879 void *content, char *cbtype, char *cbcharset, size_t length,
880 char *encoding, void *cbuserdata)
883 /* Silently go away if there's already a download open... */
884 if (CC->download_fp != NULL)
887 /* ...or if this is not the desired section */
888 if (strcasecmp(CC->download_desired_section, partnum))
891 CC->download_fp = tmpfile();
892 if (CC->download_fp == NULL)
895 fwrite(content, length, 1, CC->download_fp);
896 fflush(CC->download_fp);
897 rewind(CC->download_fp);
899 OpenCmdResult(filename, cbtype);
905 * Load a message from disk into memory.
906 * This is used by CtdlOutputMsg() and other fetch functions.
908 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
909 * using the CtdlMessageFree() function.
911 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
913 struct cdbdata *dmsgtext;
914 struct CtdlMessage *ret = NULL;
918 cit_uint8_t field_header;
920 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
922 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
923 if (dmsgtext == NULL) {
926 mptr = dmsgtext->ptr;
927 upper_bound = mptr + dmsgtext->len;
929 /* Parse the three bytes that begin EVERY message on disk.
930 * The first is always 0xFF, the on-disk magic number.
931 * The second is the anonymous/public type byte.
932 * The third is the format type byte (vari, fixed, or MIME).
937 "Message %ld appears to be corrupted.\n",
942 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
943 memset(ret, 0, sizeof(struct CtdlMessage));
945 ret->cm_magic = CTDLMESSAGE_MAGIC;
946 ret->cm_anon_type = *mptr++; /* Anon type byte */
947 ret->cm_format_type = *mptr++; /* Format type byte */
950 * The rest is zero or more arbitrary fields. Load them in.
951 * We're done when we encounter either a zero-length field or
952 * have just processed the 'M' (message text) field.
955 if (mptr >= upper_bound) {
958 field_header = *mptr++;
959 ret->cm_fields[field_header] = strdup(mptr);
961 while (*mptr++ != 0); /* advance to next field */
963 } while ((mptr < upper_bound) && (field_header != 'M'));
967 /* Always make sure there's something in the msg text field. If
968 * it's NULL, the message text is most likely stored separately,
969 * so go ahead and fetch that. Failing that, just set a dummy
970 * body so other code doesn't barf.
972 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
973 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
974 if (dmsgtext != NULL) {
975 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
979 if (ret->cm_fields['M'] == NULL) {
980 ret->cm_fields['M'] = strdup("<no text>\n");
983 /* Perform "before read" hooks (aborting if any return nonzero) */
984 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
985 CtdlFreeMessage(ret);
994 * Returns 1 if the supplied pointer points to a valid Citadel message.
995 * If the pointer is NULL or the magic number check fails, returns 0.
997 int is_valid_message(struct CtdlMessage *msg) {
1000 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1001 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1009 * 'Destructor' for struct CtdlMessage
1011 void CtdlFreeMessage(struct CtdlMessage *msg)
1015 if (is_valid_message(msg) == 0) return;
1017 for (i = 0; i < 256; ++i)
1018 if (msg->cm_fields[i] != NULL) {
1019 free(msg->cm_fields[i]);
1022 msg->cm_magic = 0; /* just in case */
1028 * Pre callback function for multipart/alternative
1030 * NOTE: this differs from the standard behavior for a reason. Normally when
1031 * displaying multipart/alternative you want to show the _last_ usable
1032 * format in the message. Here we show the _first_ one, because it's
1033 * usually text/plain. Since this set of functions is designed for text
1034 * output to non-MIME-aware clients, this is the desired behavior.
1037 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1038 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1043 ma = (struct ma_info *)cbuserdata;
1044 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1045 if (!strcasecmp(cbtype, "multipart/alternative")) {
1053 * Post callback function for multipart/alternative
1055 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1056 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1061 ma = (struct ma_info *)cbuserdata;
1062 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1063 if (!strcasecmp(cbtype, "multipart/alternative")) {
1071 * Inline callback function for mime parser that wants to display text
1073 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1074 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1082 ma = (struct ma_info *)cbuserdata;
1084 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1087 * If we're in the middle of a multipart/alternative scope and
1088 * we've already printed another section, skip this one.
1090 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1091 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1096 if ( (!strcasecmp(cbtype, "text/plain"))
1097 || (strlen(cbtype)==0) ) {
1100 client_write(wptr, length);
1101 if (wptr[length-1] != '\n') {
1106 else if (!strcasecmp(cbtype, "text/html")) {
1107 ptr = html_to_ascii(content, 80, 0);
1109 client_write(ptr, wlen);
1110 if (ptr[wlen-1] != '\n') {
1115 else if (strncasecmp(cbtype, "multipart/", 10)) {
1116 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1117 partnum, filename, cbtype, (long)length);
1122 * The client is elegant and sophisticated and wants to be choosy about
1123 * MIME content types, so figure out which multipart/alternative part
1124 * we're going to send.
1126 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1127 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1134 ma = (struct ma_info *)cbuserdata;
1136 if (ma->is_ma > 0) {
1137 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1138 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1139 if (!strcasecmp(buf, cbtype)) {
1140 strcpy(ma->chosen_part, partnum);
1147 * Now that we've chosen our preferred part, output it.
1149 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1150 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1155 int add_newline = 0;
1159 ma = (struct ma_info *)cbuserdata;
1161 /* This is not the MIME part you're looking for... */
1162 if (strcasecmp(partnum, ma->chosen_part)) return;
1164 /* If the content-type of this part is in our preferred formats
1165 * list, we can simply output it verbatim.
1167 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1168 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1169 if (!strcasecmp(buf, cbtype)) {
1170 /* Yeah! Go! W00t!! */
1172 text_content = (char *)content;
1173 if (text_content[length-1] != '\n') {
1177 cprintf("Content-type: %s", cbtype);
1178 if (strlen(cbcharset) > 0) {
1179 cprintf("; charset=%s", cbcharset);
1181 cprintf("\nContent-length: %d\n",
1182 (int)(length + add_newline) );
1183 if (strlen(encoding) > 0) {
1184 cprintf("Content-transfer-encoding: %s\n", encoding);
1187 cprintf("Content-transfer-encoding: 7bit\n");
1190 client_write(content, length);
1191 if (add_newline) cprintf("\n");
1196 /* No translations required or possible: output as text/plain */
1197 cprintf("Content-type: text/plain\n\n");
1198 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1199 length, encoding, cbuserdata);
1204 * Get a message off disk. (returns om_* values found in msgbase.h)
1207 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1208 int mode, /* how would you like that message? */
1209 int headers_only, /* eschew the message body? */
1210 int do_proto, /* do Citadel protocol responses? */
1211 int crlf /* Use CRLF newlines instead of LF? */
1213 struct CtdlMessage *TheMessage = NULL;
1214 int retcode = om_no_such_msg;
1216 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1219 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1220 if (do_proto) cprintf("%d Not logged in.\n",
1221 ERROR + NOT_LOGGED_IN);
1222 return(om_not_logged_in);
1225 /* FIXME: check message id against msglist for this room */
1228 * Fetch the message from disk. If we're in any sort of headers
1229 * only mode, request that we don't even bother loading the body
1232 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1233 TheMessage = CtdlFetchMessage(msg_num, 0);
1236 TheMessage = CtdlFetchMessage(msg_num, 1);
1239 if (TheMessage == NULL) {
1240 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1241 ERROR + MESSAGE_NOT_FOUND, msg_num);
1242 return(om_no_such_msg);
1245 retcode = CtdlOutputPreLoadedMsg(
1246 TheMessage, msg_num, mode,
1247 headers_only, do_proto, crlf);
1249 CtdlFreeMessage(TheMessage);
1256 * Get a message off disk. (returns om_* values found in msgbase.h)
1259 int CtdlOutputPreLoadedMsg(
1260 struct CtdlMessage *TheMessage,
1262 int mode, /* how would you like that message? */
1263 int headers_only, /* eschew the message body? */
1264 int do_proto, /* do Citadel protocol responses? */
1265 int crlf /* Use CRLF newlines instead of LF? */
1271 char display_name[256];
1273 char *nl; /* newline string */
1275 int subject_found = 0;
1278 /* Buffers needed for RFC822 translation. These are all filled
1279 * using functions that are bounds-checked, and therefore we can
1280 * make them substantially smaller than SIZ.
1288 char datestamp[100];
1290 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1291 ((TheMessage == NULL) ? "NULL" : "not null"),
1293 mode, headers_only, do_proto, crlf);
1295 snprintf(mid, sizeof mid, "%ld", msg_num);
1296 nl = (crlf ? "\r\n" : "\n");
1298 if (!is_valid_message(TheMessage)) {
1300 "ERROR: invalid preloaded message for output\n");
1301 return(om_no_such_msg);
1304 /* Are we downloading a MIME component? */
1305 if (mode == MT_DOWNLOAD) {
1306 if (TheMessage->cm_format_type != FMT_RFC822) {
1308 cprintf("%d This is not a MIME message.\n",
1309 ERROR + ILLEGAL_VALUE);
1310 } else if (CC->download_fp != NULL) {
1311 if (do_proto) cprintf(
1312 "%d You already have a download open.\n",
1313 ERROR + RESOURCE_BUSY);
1315 /* Parse the message text component */
1316 mptr = TheMessage->cm_fields['M'];
1317 ma = malloc(sizeof(struct ma_info));
1318 memset(ma, 0, sizeof(struct ma_info));
1319 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1321 /* If there's no file open by this time, the requested
1322 * section wasn't found, so print an error
1324 if (CC->download_fp == NULL) {
1325 if (do_proto) cprintf(
1326 "%d Section %s not found.\n",
1327 ERROR + FILE_NOT_FOUND,
1328 CC->download_desired_section);
1331 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1334 /* now for the user-mode message reading loops */
1335 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1337 /* Does the caller want to skip the headers? */
1338 if (headers_only == HEADERS_NONE) goto START_TEXT;
1340 /* Tell the client which format type we're using. */
1341 if ( (mode == MT_CITADEL) && (do_proto) ) {
1342 cprintf("type=%d\n", TheMessage->cm_format_type);
1345 /* nhdr=yes means that we're only displaying headers, no body */
1346 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1347 && (mode == MT_CITADEL)
1350 cprintf("nhdr=yes\n");
1353 /* begin header processing loop for Citadel message format */
1355 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1357 safestrncpy(display_name, "<unknown>", sizeof display_name);
1358 if (TheMessage->cm_fields['A']) {
1359 strcpy(buf, TheMessage->cm_fields['A']);
1360 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1361 safestrncpy(display_name, "****", sizeof display_name);
1363 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1364 safestrncpy(display_name, "anonymous", sizeof display_name);
1367 safestrncpy(display_name, buf, sizeof display_name);
1369 if ((is_room_aide())
1370 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1371 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1372 size_t tmp = strlen(display_name);
1373 snprintf(&display_name[tmp],
1374 sizeof display_name - tmp,
1379 /* Don't show Internet address for users on the
1380 * local Citadel network.
1383 if (TheMessage->cm_fields['N'] != NULL)
1384 if (strlen(TheMessage->cm_fields['N']) > 0)
1385 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1389 /* Now spew the header fields in the order we like them. */
1390 safestrncpy(allkeys, FORDER, sizeof allkeys);
1391 for (i=0; i<strlen(allkeys); ++i) {
1392 k = (int) allkeys[i];
1394 if ( (TheMessage->cm_fields[k] != NULL)
1395 && (msgkeys[k] != NULL) ) {
1397 if (do_proto) cprintf("%s=%s\n",
1401 else if ((k == 'F') && (suppress_f)) {
1404 /* Masquerade display name if needed */
1406 if (do_proto) cprintf("%s=%s\n",
1408 TheMessage->cm_fields[k]
1417 /* begin header processing loop for RFC822 transfer format */
1422 strcpy(snode, NODENAME);
1423 strcpy(lnode, HUMANNODE);
1424 if (mode == MT_RFC822) {
1425 for (i = 0; i < 256; ++i) {
1426 if (TheMessage->cm_fields[i]) {
1427 mptr = TheMessage->cm_fields[i];
1430 safestrncpy(luser, mptr, sizeof luser);
1431 safestrncpy(suser, mptr, sizeof suser);
1433 else if (i == 'Y') {
1434 cprintf("CC: %s%s", mptr, nl);
1436 else if (i == 'U') {
1437 cprintf("Subject: %s%s", mptr, nl);
1441 safestrncpy(mid, mptr, sizeof mid);
1443 safestrncpy(lnode, mptr, sizeof lnode);
1445 safestrncpy(fuser, mptr, sizeof fuser);
1446 /* else if (i == 'O')
1447 cprintf("X-Citadel-Room: %s%s",
1450 safestrncpy(snode, mptr, sizeof snode);
1452 cprintf("To: %s%s", mptr, nl);
1453 else if (i == 'T') {
1454 datestring(datestamp, sizeof datestamp,
1455 atol(mptr), DATESTRING_RFC822);
1456 cprintf("Date: %s%s", datestamp, nl);
1460 if (subject_found == 0) {
1461 cprintf("Subject: (no subject)%s", nl);
1465 for (i=0; i<strlen(suser); ++i) {
1466 suser[i] = tolower(suser[i]);
1467 if (!isalnum(suser[i])) suser[i]='_';
1470 if (mode == MT_RFC822) {
1471 if (!strcasecmp(snode, NODENAME)) {
1472 safestrncpy(snode, FQDN, sizeof snode);
1475 /* Construct a fun message id */
1476 cprintf("Message-ID: <%s", mid);
1477 if (strchr(mid, '@')==NULL) {
1478 cprintf("@%s", snode);
1482 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1483 cprintf("From: \"----\" <x@x.org>%s", nl);
1485 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1486 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1488 else if (strlen(fuser) > 0) {
1489 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1492 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1495 cprintf("Organization: %s%s", lnode, nl);
1497 /* Blank line signifying RFC822 end-of-headers */
1498 if (TheMessage->cm_format_type != FMT_RFC822) {
1503 /* end header processing loop ... at this point, we're in the text */
1505 if (headers_only == HEADERS_FAST) goto DONE;
1506 mptr = TheMessage->cm_fields['M'];
1508 /* Tell the client about the MIME parts in this message */
1509 if (TheMessage->cm_format_type == FMT_RFC822) {
1510 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1511 mime_parser(mptr, NULL,
1512 (do_proto ? *list_this_part : NULL),
1513 (do_proto ? *list_this_pref : NULL),
1514 (do_proto ? *list_this_suff : NULL),
1517 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1518 /* FIXME ... we have to put some code in here to avoid
1519 * printing duplicate header information when both
1520 * Citadel and RFC822 headers exist. Preference should
1521 * probably be given to the RFC822 headers.
1523 int done_rfc822_hdrs = 0;
1524 while (ch=*(mptr++), ch!=0) {
1529 if (!done_rfc822_hdrs) {
1530 if (headers_only != HEADERS_NONE) {
1535 if (headers_only != HEADERS_ONLY) {
1539 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1540 done_rfc822_hdrs = 1;
1544 if (done_rfc822_hdrs) {
1545 if (headers_only != HEADERS_NONE) {
1550 if (headers_only != HEADERS_ONLY) {
1554 if ((*mptr == 13) || (*mptr == 10)) {
1555 done_rfc822_hdrs = 1;
1563 if (headers_only == HEADERS_ONLY) {
1567 /* signify start of msg text */
1568 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1569 if (do_proto) cprintf("text\n");
1572 /* If the format type on disk is 1 (fixed-format), then we want
1573 * everything to be output completely literally ... regardless of
1574 * what message transfer format is in use.
1576 if (TheMessage->cm_format_type == FMT_FIXED) {
1577 if (mode == MT_MIME) {
1578 cprintf("Content-type: text/plain\n\n");
1581 while (ch = *mptr++, ch > 0) {
1584 if ((ch == 10) || (strlen(buf) > 250)) {
1585 cprintf("%s%s", buf, nl);
1588 buf[strlen(buf) + 1] = 0;
1589 buf[strlen(buf)] = ch;
1592 if (strlen(buf) > 0)
1593 cprintf("%s%s", buf, nl);
1596 /* If the message on disk is format 0 (Citadel vari-format), we
1597 * output using the formatter at 80 columns. This is the final output
1598 * form if the transfer format is RFC822, but if the transfer format
1599 * is Citadel proprietary, it'll still work, because the indentation
1600 * for new paragraphs is correct and the client will reformat the
1601 * message to the reader's screen width.
1603 if (TheMessage->cm_format_type == FMT_CITADEL) {
1604 if (mode == MT_MIME) {
1605 cprintf("Content-type: text/x-citadel-variformat\n\n");
1607 memfmout(80, mptr, 0, nl);
1610 /* If the message on disk is format 4 (MIME), we've gotta hand it
1611 * off to the MIME parser. The client has already been told that
1612 * this message is format 1 (fixed format), so the callback function
1613 * we use will display those parts as-is.
1615 if (TheMessage->cm_format_type == FMT_RFC822) {
1616 ma = malloc(sizeof(struct ma_info));
1617 memset(ma, 0, sizeof(struct ma_info));
1619 if (mode == MT_MIME) {
1620 strcpy(ma->chosen_part, "1");
1621 mime_parser(mptr, NULL,
1622 *choose_preferred, *fixed_output_pre,
1623 *fixed_output_post, (void *)ma, 0);
1624 mime_parser(mptr, NULL,
1625 *output_preferred, NULL, NULL, (void *)ma, 0);
1628 mime_parser(mptr, NULL,
1629 *fixed_output, *fixed_output_pre,
1630 *fixed_output_post, (void *)ma, 0);
1636 DONE: /* now we're done */
1637 if (do_proto) cprintf("000\n");
1644 * display a message (mode 0 - Citadel proprietary)
1646 void cmd_msg0(char *cmdbuf)
1649 int headers_only = HEADERS_ALL;
1651 msgid = extract_long(cmdbuf, 0);
1652 headers_only = extract_int(cmdbuf, 1);
1654 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1660 * display a message (mode 2 - RFC822)
1662 void cmd_msg2(char *cmdbuf)
1665 int headers_only = HEADERS_ALL;
1667 msgid = extract_long(cmdbuf, 0);
1668 headers_only = extract_int(cmdbuf, 1);
1670 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1676 * display a message (mode 3 - IGnet raw format - internal programs only)
1678 void cmd_msg3(char *cmdbuf)
1681 struct CtdlMessage *msg;
1684 if (CC->internal_pgm == 0) {
1685 cprintf("%d This command is for internal programs only.\n",
1686 ERROR + HIGHER_ACCESS_REQUIRED);
1690 msgnum = extract_long(cmdbuf, 0);
1691 msg = CtdlFetchMessage(msgnum, 1);
1693 cprintf("%d Message %ld not found.\n",
1694 ERROR + MESSAGE_NOT_FOUND, msgnum);
1698 serialize_message(&smr, msg);
1699 CtdlFreeMessage(msg);
1702 cprintf("%d Unable to serialize message\n",
1703 ERROR + INTERNAL_ERROR);
1707 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1708 client_write((char *)smr.ser, (int)smr.len);
1715 * Display a message using MIME content types
1717 void cmd_msg4(char *cmdbuf)
1721 msgid = extract_long(cmdbuf, 0);
1722 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1728 * Client tells us its preferred message format(s)
1730 void cmd_msgp(char *cmdbuf)
1732 safestrncpy(CC->preferred_formats, cmdbuf,
1733 sizeof(CC->preferred_formats));
1734 cprintf("%d ok\n", CIT_OK);
1739 * Open a component of a MIME message as a download file
1741 void cmd_opna(char *cmdbuf)
1744 char desired_section[128];
1746 msgid = extract_long(cmdbuf, 0);
1747 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1748 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1749 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1754 * Save a message pointer into a specified room
1755 * (Returns 0 for success, nonzero for failure)
1756 * roomname may be NULL to use the current room
1758 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1760 char hold_rm[ROOMNAMELEN];
1761 struct cdbdata *cdbfr;
1764 long highest_msg = 0L;
1765 struct CtdlMessage *msg = NULL;
1767 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1768 roomname, msgid, flags);
1770 strcpy(hold_rm, CC->room.QRname);
1772 /* We may need to check to see if this message is real */
1773 if ( (flags & SM_VERIFY_GOODNESS)
1774 || (flags & SM_DO_REPL_CHECK)
1776 msg = CtdlFetchMessage(msgid, 1);
1777 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1780 /* Perform replication checks if necessary */
1781 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1783 if (getroom(&CC->room,
1784 ((roomname != NULL) ? roomname : CC->room.QRname) )
1786 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1787 if (msg != NULL) CtdlFreeMessage(msg);
1788 return(ERROR + ROOM_NOT_FOUND);
1791 if (ReplicationChecks(msg) != 0) {
1792 getroom(&CC->room, hold_rm);
1793 if (msg != NULL) CtdlFreeMessage(msg);
1795 "Did replication, and newer exists\n");
1800 /* Now the regular stuff */
1801 if (lgetroom(&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 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1810 if (cdbfr == NULL) {
1814 msglist = malloc(cdbfr->len);
1815 if (msglist == NULL)
1816 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1817 num_msgs = cdbfr->len / sizeof(long);
1818 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1823 /* Make sure the message doesn't already exist in this room. It
1824 * is absolutely taboo to have more than one reference to the same
1825 * message in a room.
1827 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1828 if (msglist[i] == msgid) {
1829 lputroom(&CC->room); /* unlock the room */
1830 getroom(&CC->room, hold_rm);
1831 if (msg != NULL) CtdlFreeMessage(msg);
1833 return(ERROR + ALREADY_EXISTS);
1837 /* Now add the new message */
1839 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1841 if (msglist == NULL) {
1842 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1844 msglist[num_msgs - 1] = msgid;
1846 /* Sort the message list, so all the msgid's are in order */
1847 num_msgs = sort_msglist(msglist, num_msgs);
1849 /* Determine the highest message number */
1850 highest_msg = msglist[num_msgs - 1];
1852 /* Write it back to disk. */
1853 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1854 msglist, (int)(num_msgs * sizeof(long)));
1856 /* Free up the memory we used. */
1859 /* Update the highest-message pointer and unlock the room. */
1860 CC->room.QRhighest = highest_msg;
1861 lputroom(&CC->room);
1862 getroom(&CC->room, hold_rm);
1864 /* Bump the reference count for this message. */
1865 if ((flags & SM_DONT_BUMP_REF)==0) {
1866 AdjRefCount(msgid, +1);
1869 /* Return success. */
1870 if (msg != NULL) CtdlFreeMessage(msg);
1877 * Message base operation to save a new message to the message store
1878 * (returns new message number)
1880 * This is the back end for CtdlSubmitMsg() and should not be directly
1881 * called by server-side modules.
1884 long send_message(struct CtdlMessage *msg) {
1892 /* Get a new message number */
1893 newmsgid = get_new_message_number();
1894 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1896 /* Generate an ID if we don't have one already */
1897 if (msg->cm_fields['I']==NULL) {
1898 msg->cm_fields['I'] = strdup(msgidbuf);
1901 /* If the message is big, set its body aside for storage elsewhere */
1902 if (msg->cm_fields['M'] != NULL) {
1903 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1905 holdM = msg->cm_fields['M'];
1906 msg->cm_fields['M'] = NULL;
1910 /* Serialize our data structure for storage in the database */
1911 serialize_message(&smr, msg);
1914 msg->cm_fields['M'] = holdM;
1918 cprintf("%d Unable to serialize message\n",
1919 ERROR + INTERNAL_ERROR);
1923 /* Write our little bundle of joy into the message base */
1924 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1925 smr.ser, smr.len) < 0) {
1926 lprintf(CTDL_ERR, "Can't store message\n");
1930 cdb_store(CDB_BIGMSGS,
1940 /* Free the memory we used for the serialized message */
1943 /* Return the *local* message ID to the caller
1944 * (even if we're storing an incoming network message)
1952 * Serialize a struct CtdlMessage into the format used on disk and network.
1954 * This function loads up a "struct ser_ret" (defined in server.h) which
1955 * contains the length of the serialized message and a pointer to the
1956 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1958 void serialize_message(struct ser_ret *ret, /* return values */
1959 struct CtdlMessage *msg) /* unserialized msg */
1963 static char *forder = FORDER;
1965 if (is_valid_message(msg) == 0) return; /* self check */
1968 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1969 ret->len = ret->len +
1970 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1972 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1973 ret->ser = malloc(ret->len);
1974 if (ret->ser == NULL) {
1980 ret->ser[1] = msg->cm_anon_type;
1981 ret->ser[2] = msg->cm_format_type;
1984 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1985 ret->ser[wlen++] = (char)forder[i];
1986 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1987 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1989 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1990 (long)ret->len, (long)wlen);
1998 * Back end for the ReplicationChecks() function
2000 void check_repl(long msgnum, void *userdata) {
2001 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
2002 CtdlDeleteMessages(CC->room.QRname, msgnum, "", 0);
2007 * Check to see if any messages already exist which carry the same Exclusive ID
2008 * as this one. If any are found, delete them.
2011 int ReplicationChecks(struct CtdlMessage *msg) {
2012 struct CtdlMessage *template;
2015 /* No exclusive id? Don't do anything. */
2016 if (msg->cm_fields['E'] == NULL) return 0;
2017 if (strlen(msg->cm_fields['E']) == 0) return 0;
2018 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
2020 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
2021 memset(template, 0, sizeof(struct CtdlMessage));
2022 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
2024 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
2026 CtdlFreeMessage(template);
2027 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
2034 * Turn an arbitrary RFC822 address into a struct vCard for possible
2035 * inclusion into an address book.
2037 struct vCard *vcard_new_from_rfc822_addr(char *addr) {
2039 char user[256], node[256], name[256], email[256];
2042 if (v == NULL) return(NULL);
2044 process_rfc822_addr(addr, user, node, name);
2045 vcard_set_prop(v, "fn", name, 0);
2046 snprintf(email, sizeof email, "%s@%s", user, node);
2047 vcard_set_prop(v, "email;internet", email, 0);
2054 * Save a message to disk and submit it into the delivery system.
2056 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2057 struct recptypes *recps, /* recipients (if mail) */
2058 char *force /* force a particular room? */
2060 char submit_filename[128];
2061 char generated_timestamp[32];
2062 char hold_rm[ROOMNAMELEN];
2063 char actual_rm[ROOMNAMELEN];
2064 char force_room[ROOMNAMELEN];
2065 char content_type[SIZ]; /* We have to learn this */
2066 char recipient[SIZ];
2069 struct ctdluser userbuf;
2071 struct MetaData smi;
2072 FILE *network_fp = NULL;
2073 static int seqnum = 1;
2074 struct CtdlMessage *imsg = NULL;
2075 struct CtdlMessage *vmsg = NULL;
2077 struct vCard *v = NULL;
2080 char *hold_R, *hold_D;
2081 char *collected_addresses = NULL;
2083 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2084 if (is_valid_message(msg) == 0) return(-1); /* self check */
2086 /* If this message has no timestamp, we take the liberty of
2087 * giving it one, right now.
2089 if (msg->cm_fields['T'] == NULL) {
2090 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2091 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2092 msg->cm_fields['T'] = strdup(generated_timestamp);
2095 /* If this message has no path, we generate one.
2097 if (msg->cm_fields['P'] == NULL) {
2098 lprintf(CTDL_DEBUG, "Generating path\n");
2099 if (msg->cm_fields['A'] != NULL) {
2100 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2101 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2102 if (isspace(msg->cm_fields['P'][a])) {
2103 msg->cm_fields['P'][a] = ' ';
2108 msg->cm_fields['P'] = strdup("unknown");
2112 if (force == NULL) {
2113 strcpy(force_room, "");
2116 strcpy(force_room, force);
2119 /* Learn about what's inside, because it's what's inside that counts */
2120 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2121 if (msg->cm_fields['M'] == NULL) {
2122 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2126 switch (msg->cm_format_type) {
2128 strcpy(content_type, "text/x-citadel-variformat");
2131 strcpy(content_type, "text/plain");
2134 strcpy(content_type, "text/plain");
2135 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2137 safestrncpy(content_type, &mptr[14],
2138 sizeof content_type);
2139 for (a = 0; a < strlen(content_type); ++a) {
2140 if ((content_type[a] == ';')
2141 || (content_type[a] == ' ')
2142 || (content_type[a] == 13)
2143 || (content_type[a] == 10)) {
2144 content_type[a] = 0;
2150 /* Goto the correct room */
2151 lprintf(CTDL_DEBUG, "Selected room %s\n",
2152 (recps) ? CC->room.QRname : SENTITEMS);
2153 strcpy(hold_rm, CC->room.QRname);
2154 strcpy(actual_rm, CC->room.QRname);
2155 if (recps != NULL) {
2156 strcpy(actual_rm, SENTITEMS);
2159 /* If the user is a twit, move to the twit room for posting */
2160 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2161 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2163 if (CC->user.axlevel == 2) {
2164 strcpy(hold_rm, actual_rm);
2165 strcpy(actual_rm, config.c_twitroom);
2169 /* ...or if this message is destined for Aide> then go there. */
2170 if (strlen(force_room) > 0) {
2171 strcpy(actual_rm, force_room);
2174 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2175 if (strcasecmp(actual_rm, CC->room.QRname)) {
2176 /* getroom(&CC->room, actual_rm); */
2177 usergoto(actual_rm, 0, 1, NULL, NULL);
2181 * If this message has no O (room) field, generate one.
2183 if (msg->cm_fields['O'] == NULL) {
2184 msg->cm_fields['O'] = strdup(CC->room.QRname);
2187 /* Perform "before save" hooks (aborting if any return nonzero) */
2188 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2189 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2191 /* If this message has an Exclusive ID, perform replication checks */
2192 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2193 if (ReplicationChecks(msg) > 0) return(-4);
2195 /* Save it to disk */
2196 lprintf(CTDL_DEBUG, "Saving to disk\n");
2197 newmsgid = send_message(msg);
2198 if (newmsgid <= 0L) return(-5);
2200 /* Write a supplemental message info record. This doesn't have to
2201 * be a critical section because nobody else knows about this message
2204 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2205 memset(&smi, 0, sizeof(struct MetaData));
2206 smi.meta_msgnum = newmsgid;
2207 smi.meta_refcount = 0;
2208 safestrncpy(smi.meta_content_type, content_type,
2209 sizeof smi.meta_content_type);
2211 /* As part of the new metadata record, measure how
2212 * big this message will be when displayed as RFC822.
2213 * Both POP and IMAP use this, and it's best to just take the hit now
2214 * instead of having to potentially measure thousands of messages when
2215 * a mailbox is opened later.
2218 if (CC->redirect_buffer != NULL) {
2219 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2222 CC->redirect_buffer = malloc(SIZ);
2223 CC->redirect_len = 0;
2224 CC->redirect_alloc = SIZ;
2225 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2226 smi.meta_rfc822_length = CC->redirect_len;
2227 free(CC->redirect_buffer);
2228 CC->redirect_buffer = NULL;
2229 CC->redirect_len = 0;
2230 CC->redirect_alloc = 0;
2234 /* Now figure out where to store the pointers */
2235 lprintf(CTDL_DEBUG, "Storing pointers\n");
2237 /* If this is being done by the networker delivering a private
2238 * message, we want to BYPASS saving the sender's copy (because there
2239 * is no local sender; it would otherwise go to the Trashcan).
2241 if ((!CC->internal_pgm) || (recps == NULL)) {
2242 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2243 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2244 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2249 /* For internet mail, drop a copy in the outbound queue room */
2251 if (recps->num_internet > 0) {
2252 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2255 /* If other rooms are specified, drop them there too. */
2257 if (recps->num_room > 0)
2258 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2259 extract_token(recipient, recps->recp_room, i,
2260 '|', sizeof recipient);
2261 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2262 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2265 /* Bump this user's messages posted counter. */
2266 lprintf(CTDL_DEBUG, "Updating user\n");
2267 lgetuser(&CC->user, CC->curr_user);
2268 CC->user.posted = CC->user.posted + 1;
2269 lputuser(&CC->user);
2271 /* If this is private, local mail, make a copy in the
2272 * recipient's mailbox and bump the reference count.
2275 if (recps->num_local > 0)
2276 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2277 extract_token(recipient, recps->recp_local, i,
2278 '|', sizeof recipient);
2279 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2281 if (getuser(&userbuf, recipient) == 0) {
2282 MailboxName(actual_rm, sizeof actual_rm,
2283 &userbuf, MAILROOM);
2284 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2285 BumpNewMailCounter(userbuf.usernum);
2288 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2289 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2294 /* Perform "after save" hooks */
2295 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2296 PerformMessageHooks(msg, EVT_AFTERSAVE);
2298 /* For IGnet mail, we have to save a new copy into the spooler for
2299 * each recipient, with the R and D fields set to the recipient and
2300 * destination-node. This has two ugly side effects: all other
2301 * recipients end up being unlisted in this recipient's copy of the
2302 * message, and it has to deliver multiple messages to the same
2303 * node. We'll revisit this again in a year or so when everyone has
2304 * a network spool receiver that can handle the new style messages.
2307 if (recps->num_ignet > 0)
2308 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2309 extract_token(recipient, recps->recp_ignet, i,
2310 '|', sizeof recipient);
2312 hold_R = msg->cm_fields['R'];
2313 hold_D = msg->cm_fields['D'];
2314 msg->cm_fields['R'] = malloc(SIZ);
2315 msg->cm_fields['D'] = malloc(128);
2316 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2317 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2319 serialize_message(&smr, msg);
2321 snprintf(submit_filename, sizeof submit_filename,
2322 #ifndef HAVE_SPOOL_DIR
2327 "/network/spoolin/netmail.%04lx.%04x.%04x",
2328 (long) getpid(), CC->cs_pid, ++seqnum);
2329 network_fp = fopen(submit_filename, "wb+");
2330 if (network_fp != NULL) {
2331 fwrite(smr.ser, smr.len, 1, network_fp);
2337 free(msg->cm_fields['R']);
2338 free(msg->cm_fields['D']);
2339 msg->cm_fields['R'] = hold_R;
2340 msg->cm_fields['D'] = hold_D;
2343 /* Go back to the room we started from */
2344 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2345 if (strcasecmp(hold_rm, CC->room.QRname))
2346 /* getroom(&CC->room, hold_rm); */
2347 usergoto(hold_rm, 0, 1, NULL, NULL);
2349 /* For internet mail, generate delivery instructions.
2350 * Yes, this is recursive. Deal with it. Infinite recursion does
2351 * not happen because the delivery instructions message does not
2352 * contain a recipient.
2355 if (recps->num_internet > 0) {
2356 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2357 instr = malloc(SIZ * 2);
2358 snprintf(instr, SIZ * 2,
2359 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2361 SPOOLMIME, newmsgid, (long)time(NULL),
2362 msg->cm_fields['A'], msg->cm_fields['N']
2365 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2366 size_t tmp = strlen(instr);
2367 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2368 snprintf(&instr[tmp], SIZ * 2 - tmp,
2369 "remote|%s|0||\n", recipient);
2372 imsg = malloc(sizeof(struct CtdlMessage));
2373 memset(imsg, 0, sizeof(struct CtdlMessage));
2374 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2375 imsg->cm_anon_type = MES_NORMAL;
2376 imsg->cm_format_type = FMT_RFC822;
2377 imsg->cm_fields['A'] = strdup("Citadel");
2378 imsg->cm_fields['M'] = instr;
2379 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2380 CtdlFreeMessage(imsg);
2384 * Any addresses to harvest for someone's address book?
2385 * (Don't do this if this is not a message with recipients, otherwise we will
2386 * send this function into infinite recursion!!!!!)
2388 if (recps != NULL) {
2389 collected_addresses = harvest_collected_addresses(msg);
2392 if (collected_addresses != NULL) {
2393 for (i=0; i<num_tokens(collected_addresses, ','); ++i) {
2395 /* Make a vCard out of each address */
2396 extract_token(recipient, collected_addresses, i, ',', sizeof recipient);
2398 v = vcard_new_from_rfc822_addr(recipient);
2400 vmsg = malloc(sizeof(struct CtdlMessage));
2401 memset(vmsg, 0, sizeof(struct CtdlMessage));
2402 vmsg->cm_magic = CTDLMESSAGE_MAGIC;
2403 vmsg->cm_anon_type = MES_NORMAL;
2404 vmsg->cm_format_type = FMT_RFC822;
2405 vmsg->cm_fields['A'] = strdup("Citadel");
2406 vmsg->cm_fields['E'] = strdup(recipient); /* this handles dups */
2407 ser = vcard_serialize(v);
2409 vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
2410 sprintf(vmsg->cm_fields['M'],
2411 "Content-type: text/x-vcard"
2412 "\r\n\r\n%s\r\n", ser);
2416 CtdlSubmitMsg(vmsg, NULL, "Aide"); /* FIXME */
2417 CtdlFreeMessage(vmsg);
2420 free(collected_addresses);
2433 * Convenience function for generating small administrative messages.
2435 void quickie_message(char *from, char *to, char *room, char *text,
2436 int format_type, char *subject)
2438 struct CtdlMessage *msg;
2439 struct recptypes *recp = NULL;
2441 msg = malloc(sizeof(struct CtdlMessage));
2442 memset(msg, 0, sizeof(struct CtdlMessage));
2443 msg->cm_magic = CTDLMESSAGE_MAGIC;
2444 msg->cm_anon_type = MES_NORMAL;
2445 msg->cm_format_type = format_type;
2446 msg->cm_fields['A'] = strdup(from);
2447 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2448 msg->cm_fields['N'] = strdup(NODENAME);
2450 msg->cm_fields['R'] = strdup(to);
2451 recp = validate_recipients(to);
2453 if (subject != NULL) {
2454 msg->cm_fields['U'] = strdup(subject);
2456 msg->cm_fields['M'] = strdup(text);
2458 CtdlSubmitMsg(msg, recp, room);
2459 CtdlFreeMessage(msg);
2460 if (recp != NULL) free(recp);
2466 * Back end function used by CtdlMakeMessage() and similar functions
2468 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2469 size_t maxlen, /* maximum message length */
2470 char *exist, /* if non-null, append to it;
2471 exist is ALWAYS freed */
2472 int crlf /* CRLF newlines instead of LF */
2476 size_t message_len = 0;
2477 size_t buffer_len = 0;
2483 if (exist == NULL) {
2490 message_len = strlen(exist);
2491 buffer_len = message_len + 4096;
2492 m = realloc(exist, buffer_len);
2499 /* flush the input if we have nowhere to store it */
2504 /* read in the lines of message text one by one */
2506 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2507 if (!strcmp(buf, terminator)) finished = 1;
2509 strcat(buf, "\r\n");
2515 if ( (!flushing) && (!finished) ) {
2516 /* Measure the line */
2517 linelen = strlen(buf);
2519 /* augment the buffer if we have to */
2520 if ((message_len + linelen) >= buffer_len) {
2521 ptr = realloc(m, (buffer_len * 2) );
2522 if (ptr == NULL) { /* flush if can't allocate */
2525 buffer_len = (buffer_len * 2);
2527 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2531 /* Add the new line to the buffer. NOTE: this loop must avoid
2532 * using functions like strcat() and strlen() because they
2533 * traverse the entire buffer upon every call, and doing that
2534 * for a multi-megabyte message slows it down beyond usability.
2536 strcpy(&m[message_len], buf);
2537 message_len += linelen;
2540 /* if we've hit the max msg length, flush the rest */
2541 if (message_len >= maxlen) flushing = 1;
2543 } while (!finished);
2551 * Build a binary message to be saved on disk.
2552 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2553 * will become part of the message. This means you are no longer
2554 * responsible for managing that memory -- it will be freed along with
2555 * the rest of the fields when CtdlFreeMessage() is called.)
2558 struct CtdlMessage *CtdlMakeMessage(
2559 struct ctdluser *author, /* author's user structure */
2560 char *recipient, /* NULL if it's not mail */
2561 char *recp_cc, /* NULL if it's not mail */
2562 char *room, /* room where it's going */
2563 int type, /* see MES_ types in header file */
2564 int format_type, /* variformat, plain text, MIME... */
2565 char *fake_name, /* who we're masquerading as */
2566 char *subject, /* Subject (optional) */
2567 char *preformatted_text /* ...or NULL to read text from client */
2569 char dest_node[SIZ];
2571 struct CtdlMessage *msg;
2573 msg = malloc(sizeof(struct CtdlMessage));
2574 memset(msg, 0, sizeof(struct CtdlMessage));
2575 msg->cm_magic = CTDLMESSAGE_MAGIC;
2576 msg->cm_anon_type = type;
2577 msg->cm_format_type = format_type;
2579 /* Don't confuse the poor folks if it's not routed mail. */
2580 strcpy(dest_node, "");
2585 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2586 msg->cm_fields['P'] = strdup(buf);
2588 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2589 msg->cm_fields['T'] = strdup(buf);
2591 if (fake_name[0]) /* author */
2592 msg->cm_fields['A'] = strdup(fake_name);
2594 msg->cm_fields['A'] = strdup(author->fullname);
2596 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2597 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2600 msg->cm_fields['O'] = strdup(CC->room.QRname);
2603 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2604 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2606 if (recipient[0] != 0) {
2607 msg->cm_fields['R'] = strdup(recipient);
2609 if (recp_cc[0] != 0) {
2610 msg->cm_fields['Y'] = strdup(recp_cc);
2612 if (dest_node[0] != 0) {
2613 msg->cm_fields['D'] = strdup(dest_node);
2616 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2617 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2620 if (subject != NULL) {
2622 if (strlen(subject) > 0) {
2623 msg->cm_fields['U'] = strdup(subject);
2627 if (preformatted_text != NULL) {
2628 msg->cm_fields['M'] = preformatted_text;
2631 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2632 config.c_maxmsglen, NULL, 0);
2640 * Check to see whether we have permission to post a message in the current
2641 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2642 * returns 0 on success.
2644 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2646 if (!(CC->logged_in)) {
2647 snprintf(errmsgbuf, n, "Not logged in.");
2648 return (ERROR + NOT_LOGGED_IN);
2651 if ((CC->user.axlevel < 2)
2652 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2653 snprintf(errmsgbuf, n, "Need to be validated to enter "
2654 "(except in %s> to sysop)", MAILROOM);
2655 return (ERROR + HIGHER_ACCESS_REQUIRED);
2658 if ((CC->user.axlevel < 4)
2659 && (CC->room.QRflags & QR_NETWORK)) {
2660 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2661 return (ERROR + HIGHER_ACCESS_REQUIRED);
2664 if ((CC->user.axlevel < 6)
2665 && (CC->room.QRflags & QR_READONLY)) {
2666 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2667 return (ERROR + HIGHER_ACCESS_REQUIRED);
2670 strcpy(errmsgbuf, "Ok");
2676 * Check to see if the specified user has Internet mail permission
2677 * (returns nonzero if permission is granted)
2679 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2681 /* Do not allow twits to send Internet mail */
2682 if (who->axlevel <= 2) return(0);
2684 /* Globally enabled? */
2685 if (config.c_restrict == 0) return(1);
2687 /* User flagged ok? */
2688 if (who->flags & US_INTERNET) return(2);
2690 /* Aide level access? */
2691 if (who->axlevel >= 6) return(3);
2693 /* No mail for you! */
2699 * Validate recipients, count delivery types and errors, and handle aliasing
2700 * FIXME check for dupes!!!!!
2701 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2702 * or the number of addresses found invalid.
2704 struct recptypes *validate_recipients(char *recipients) {
2705 struct recptypes *ret;
2706 char this_recp[SIZ];
2707 char this_recp_cooked[SIZ];
2713 struct ctdluser tempUS;
2714 struct ctdlroom tempQR;
2717 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2718 if (ret == NULL) return(NULL);
2719 memset(ret, 0, sizeof(struct recptypes));
2722 ret->num_internet = 0;
2727 if (recipients == NULL) {
2730 else if (strlen(recipients) == 0) {
2734 /* Change all valid separator characters to commas */
2735 for (i=0; i<strlen(recipients); ++i) {
2736 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2737 recipients[i] = ',';
2742 num_recps = num_tokens(recipients, ',');
2745 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2746 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2748 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2749 mailtype = alias(this_recp);
2750 mailtype = alias(this_recp);
2751 mailtype = alias(this_recp);
2752 for (j=0; j<=strlen(this_recp); ++j) {
2753 if (this_recp[j]=='_') {
2754 this_recp_cooked[j] = ' ';
2757 this_recp_cooked[j] = this_recp[j];
2763 if (!strcasecmp(this_recp, "sysop")) {
2765 strcpy(this_recp, config.c_aideroom);
2766 if (strlen(ret->recp_room) > 0) {
2767 strcat(ret->recp_room, "|");
2769 strcat(ret->recp_room, this_recp);
2771 else if (getuser(&tempUS, this_recp) == 0) {
2773 strcpy(this_recp, tempUS.fullname);
2774 if (strlen(ret->recp_local) > 0) {
2775 strcat(ret->recp_local, "|");
2777 strcat(ret->recp_local, this_recp);
2779 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2781 strcpy(this_recp, tempUS.fullname);
2782 if (strlen(ret->recp_local) > 0) {
2783 strcat(ret->recp_local, "|");
2785 strcat(ret->recp_local, this_recp);
2787 else if ( (!strncasecmp(this_recp, "room_", 5))
2788 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2790 if (strlen(ret->recp_room) > 0) {
2791 strcat(ret->recp_room, "|");
2793 strcat(ret->recp_room, &this_recp_cooked[5]);
2801 /* Yes, you're reading this correctly: if the target
2802 * domain points back to the local system or an attached
2803 * Citadel directory, the address is invalid. That's
2804 * because if the address were valid, we would have
2805 * already translated it to a local address by now.
2807 if (IsDirectory(this_recp)) {
2812 ++ret->num_internet;
2813 if (strlen(ret->recp_internet) > 0) {
2814 strcat(ret->recp_internet, "|");
2816 strcat(ret->recp_internet, this_recp);
2821 if (strlen(ret->recp_ignet) > 0) {
2822 strcat(ret->recp_ignet, "|");
2824 strcat(ret->recp_ignet, this_recp);
2832 if (strlen(ret->errormsg) == 0) {
2833 snprintf(append, sizeof append,
2834 "Invalid recipient: %s",
2838 snprintf(append, sizeof append,
2841 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2842 strcat(ret->errormsg, append);
2846 if (strlen(ret->display_recp) == 0) {
2847 strcpy(append, this_recp);
2850 snprintf(append, sizeof append, ", %s",
2853 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2854 strcat(ret->display_recp, append);
2859 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2860 ret->num_room + ret->num_error) == 0) {
2861 ret->num_error = (-1);
2862 strcpy(ret->errormsg, "No recipients specified.");
2865 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2866 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2867 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2868 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2869 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2870 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2878 * message entry - mode 0 (normal)
2880 void cmd_ent0(char *entargs)
2886 char masquerade_as[SIZ];
2888 int format_type = 0;
2889 char newusername[SIZ];
2890 struct CtdlMessage *msg;
2894 struct recptypes *valid = NULL;
2895 struct recptypes *valid_to = NULL;
2896 struct recptypes *valid_cc = NULL;
2897 struct recptypes *valid_bcc = NULL;
2904 post = extract_int(entargs, 0);
2905 extract_token(recp, entargs, 1, '|', sizeof recp);
2906 anon_flag = extract_int(entargs, 2);
2907 format_type = extract_int(entargs, 3);
2908 extract_token(subject, entargs, 4, '|', sizeof subject);
2909 do_confirm = extract_int(entargs, 6);
2910 extract_token(cc, entargs, 7, '|', sizeof cc);
2911 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2913 /* first check to make sure the request is valid. */
2915 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2917 cprintf("%d %s\n", err, errmsg);
2921 /* Check some other permission type things. */
2924 if (CC->user.axlevel < 6) {
2925 cprintf("%d You don't have permission to masquerade.\n",
2926 ERROR + HIGHER_ACCESS_REQUIRED);
2929 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2930 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2931 safestrncpy(CC->fake_postname, newusername,
2932 sizeof(CC->fake_postname) );
2933 cprintf("%d ok\n", CIT_OK);
2936 CC->cs_flags |= CS_POSTING;
2938 /* In the Mail> room we have to behave a little differently --
2939 * make sure the user has specified at least one recipient. Then
2940 * validate the recipient(s).
2942 if ( (CC->room.QRflags & QR_MAILBOX)
2943 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2945 if (CC->user.axlevel < 2) {
2946 strcpy(recp, "sysop");
2951 valid_to = validate_recipients(recp);
2952 if (valid_to->num_error > 0) {
2953 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2958 valid_cc = validate_recipients(cc);
2959 if (valid_cc->num_error > 0) {
2960 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2966 valid_bcc = validate_recipients(bcc);
2967 if (valid_bcc->num_error > 0) {
2968 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2975 /* Recipient required, but none were specified */
2976 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2980 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2984 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2985 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2986 cprintf("%d You do not have permission "
2987 "to send Internet mail.\n",
2988 ERROR + HIGHER_ACCESS_REQUIRED);
2996 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)
2997 && (CC->user.axlevel < 4) ) {
2998 cprintf("%d Higher access required for network mail.\n",
2999 ERROR + HIGHER_ACCESS_REQUIRED);
3006 if ((RESTRICT_INTERNET == 1)
3007 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3008 && ((CC->user.flags & US_INTERNET) == 0)
3009 && (!CC->internal_pgm)) {
3010 cprintf("%d You don't have access to Internet mail.\n",
3011 ERROR + HIGHER_ACCESS_REQUIRED);
3020 /* Is this a room which has anonymous-only or anonymous-option? */
3021 anonymous = MES_NORMAL;
3022 if (CC->room.QRflags & QR_ANONONLY) {
3023 anonymous = MES_ANONONLY;
3025 if (CC->room.QRflags & QR_ANONOPT) {
3026 if (anon_flag == 1) { /* only if the user requested it */
3027 anonymous = MES_ANONOPT;
3031 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3035 /* If we're only checking the validity of the request, return
3036 * success without creating the message.
3039 cprintf("%d %s\n", CIT_OK,
3040 ((valid_to != NULL) ? valid_to->display_recp : "") );
3047 /* We don't need these anymore because we'll do it differently below */
3052 /* Handle author masquerading */
3053 if (CC->fake_postname[0]) {
3054 strcpy(masquerade_as, CC->fake_postname);
3056 else if (CC->fake_username[0]) {
3057 strcpy(masquerade_as, CC->fake_username);
3060 strcpy(masquerade_as, "");
3063 /* Read in the message from the client. */
3065 cprintf("%d send message\n", START_CHAT_MODE);
3067 cprintf("%d send message\n", SEND_LISTING);
3070 msg = CtdlMakeMessage(&CC->user, recp, cc,
3071 CC->room.QRname, anonymous, format_type,
3072 masquerade_as, subject, NULL);
3074 /* Put together one big recipients struct containing to/cc/bcc all in
3075 * one. This is for the envelope.
3077 char *all_recps = malloc(SIZ * 3);
3078 strcpy(all_recps, recp);
3079 if (strlen(cc) > 0) {
3080 if (strlen(all_recps) > 0) {
3081 strcat(all_recps, ",");
3083 strcat(all_recps, cc);
3085 if (strlen(bcc) > 0) {
3086 if (strlen(all_recps) > 0) {
3087 strcat(all_recps, ",");
3089 strcat(all_recps, bcc);
3091 if (strlen(all_recps) > 0) {
3092 valid = validate_recipients(all_recps);
3100 msgnum = CtdlSubmitMsg(msg, valid, "");
3103 cprintf("%ld\n", msgnum);
3105 cprintf("Message accepted.\n");
3108 cprintf("Internal error.\n");
3110 if (msg->cm_fields['E'] != NULL) {
3111 cprintf("%s\n", msg->cm_fields['E']);
3118 CtdlFreeMessage(msg);
3120 CC->fake_postname[0] = '\0';
3121 if (valid != NULL) {
3130 * API function to delete messages which match a set of criteria
3131 * (returns the actual number of messages deleted)
3133 int CtdlDeleteMessages(char *room_name, /* which room */
3134 long dmsgnum, /* or "0" for any */
3135 char *content_type, /* or "" for any */
3136 int deferred /* let TDAP sweep it later */
3140 struct ctdlroom qrbuf;
3141 struct cdbdata *cdbfr;
3142 long *msglist = NULL;
3143 long *dellist = NULL;
3146 int num_deleted = 0;
3148 struct MetaData smi;
3150 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3151 room_name, dmsgnum, content_type, deferred);
3153 /* get room record, obtaining a lock... */
3154 if (lgetroom(&qrbuf, room_name) != 0) {
3155 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3157 return (0); /* room not found */
3159 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3161 if (cdbfr != NULL) {
3162 msglist = malloc(cdbfr->len);
3163 dellist = malloc(cdbfr->len);
3164 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3165 num_msgs = cdbfr->len / sizeof(long);
3169 for (i = 0; i < num_msgs; ++i) {
3172 /* Set/clear a bit for each criterion */
3174 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3175 delete_this |= 0x01;
3177 if (strlen(content_type) == 0) {
3178 delete_this |= 0x02;
3180 GetMetaData(&smi, msglist[i]);
3181 if (!strcasecmp(smi.meta_content_type,
3183 delete_this |= 0x02;
3187 /* Delete message only if all bits are set */
3188 if (delete_this == 0x03) {
3189 dellist[num_deleted++] = msglist[i];
3194 num_msgs = sort_msglist(msglist, num_msgs);
3195 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3196 msglist, (int)(num_msgs * sizeof(long)));
3198 qrbuf.QRhighest = msglist[num_msgs - 1];
3203 * If the delete operation is "deferred" (and technically, any delete
3204 * operation not performed by THE DREADED AUTO-PURGER ought to be
3205 * a deferred delete) then we save a pointer to the message in the
3206 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3207 * at least 1, which will save the user from having to synchronously
3208 * wait for various disk-intensive operations to complete.
3210 if ( (deferred) && (num_deleted) ) {
3211 for (i=0; i<num_deleted; ++i) {
3212 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3216 /* Go through the messages we pulled out of the index, and decrement
3217 * their reference counts by 1. If this is the only room the message
3218 * was in, the reference count will reach zero and the message will
3219 * automatically be deleted from the database. We do this in a
3220 * separate pass because there might be plug-in hooks getting called,
3221 * and we don't want that happening during an S_ROOMS critical
3224 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3225 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3226 AdjRefCount(dellist[i], -1);
3229 /* Now free the memory we used, and go away. */
3230 if (msglist != NULL) free(msglist);
3231 if (dellist != NULL) free(dellist);
3232 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3233 return (num_deleted);
3239 * Check whether the current user has permission to delete messages from
3240 * the current room (returns 1 for yes, 0 for no)
3242 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3243 getuser(&CC->user, CC->curr_user);
3244 if ((CC->user.axlevel < 6)
3245 && (CC->user.usernum != CC->room.QRroomaide)
3246 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3247 && (!(CC->internal_pgm))) {
3256 * Delete message from current room
3258 void cmd_dele(char *delstr)
3263 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3264 cprintf("%d Higher access required.\n",
3265 ERROR + HIGHER_ACCESS_REQUIRED);
3268 delnum = extract_long(delstr, 0);
3270 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3273 cprintf("%d %d message%s deleted.\n", CIT_OK,
3274 num_deleted, ((num_deleted != 1) ? "s" : ""));
3276 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3282 * Back end API function for moves and deletes
3284 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3287 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3288 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3289 if (err != 0) return(err);
3297 * move or copy a message to another room
3299 void cmd_move(char *args)
3302 char targ[ROOMNAMELEN];
3303 struct ctdlroom qtemp;
3309 num = extract_long(args, 0);
3310 extract_token(targ, args, 1, '|', sizeof targ);
3311 targ[ROOMNAMELEN - 1] = 0;
3312 is_copy = extract_int(args, 2);
3314 if (getroom(&qtemp, targ) != 0) {
3315 cprintf("%d '%s' does not exist.\n",
3316 ERROR + ROOM_NOT_FOUND, targ);
3320 getuser(&CC->user, CC->curr_user);
3321 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3323 /* Check for permission to perform this operation.
3324 * Remember: "CC->room" is source, "qtemp" is target.
3328 /* Aides can move/copy */
3329 if (CC->user.axlevel >= 6) permit = 1;
3331 /* Room aides can move/copy */
3332 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3334 /* Permit move/copy from personal rooms */
3335 if ((CC->room.QRflags & QR_MAILBOX)
3336 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3338 /* Permit only copy from public to personal room */
3340 && (!(CC->room.QRflags & QR_MAILBOX))
3341 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3343 /* User must have access to target room */
3344 if (!(ra & UA_KNOWN)) permit = 0;
3347 cprintf("%d Higher access required.\n",
3348 ERROR + HIGHER_ACCESS_REQUIRED);
3352 err = CtdlCopyMsgToRoom(num, targ);
3354 cprintf("%d Cannot store message in %s: error %d\n",
3359 /* Now delete the message from the source room,
3360 * if this is a 'move' rather than a 'copy' operation.
3363 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3366 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3372 * GetMetaData() - Get the supplementary record for a message
3374 void GetMetaData(struct MetaData *smibuf, long msgnum)
3377 struct cdbdata *cdbsmi;
3380 memset(smibuf, 0, sizeof(struct MetaData));
3381 smibuf->meta_msgnum = msgnum;
3382 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3384 /* Use the negative of the message number for its supp record index */
3385 TheIndex = (0L - msgnum);
3387 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3388 if (cdbsmi == NULL) {
3389 return; /* record not found; go with defaults */
3391 memcpy(smibuf, cdbsmi->ptr,
3392 ((cdbsmi->len > sizeof(struct MetaData)) ?
3393 sizeof(struct MetaData) : cdbsmi->len));
3400 * PutMetaData() - (re)write supplementary record for a message
3402 void PutMetaData(struct MetaData *smibuf)
3406 /* Use the negative of the message number for the metadata db index */
3407 TheIndex = (0L - smibuf->meta_msgnum);
3409 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3410 smibuf->meta_msgnum, smibuf->meta_refcount);
3412 cdb_store(CDB_MSGMAIN,
3413 &TheIndex, (int)sizeof(long),
3414 smibuf, (int)sizeof(struct MetaData));
3419 * AdjRefCount - change the reference count for a message;
3420 * delete the message if it reaches zero
3422 void AdjRefCount(long msgnum, int incr)
3425 struct MetaData smi;
3428 /* This is a *tight* critical section; please keep it that way, as
3429 * it may get called while nested in other critical sections.
3430 * Complicating this any further will surely cause deadlock!
3432 begin_critical_section(S_SUPPMSGMAIN);
3433 GetMetaData(&smi, msgnum);
3434 smi.meta_refcount += incr;
3436 end_critical_section(S_SUPPMSGMAIN);
3437 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3438 msgnum, incr, smi.meta_refcount);
3440 /* If the reference count is now zero, delete the message
3441 * (and its supplementary record as well).
3443 if (smi.meta_refcount == 0) {
3444 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3446 /* Remove from fulltext index */
3447 if (config.c_enable_fulltext) {
3448 ft_index_message(msgnum, 0);
3451 /* Remove from message base */
3453 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3454 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3456 /* Remove metadata record */
3457 delnum = (0L - msgnum);
3458 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3463 * Write a generic object to this room
3465 * Note: this could be much more efficient. Right now we use two temporary
3466 * files, and still pull the message into memory as with all others.
3468 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3469 char *content_type, /* MIME type of this object */
3470 char *tempfilename, /* Where to fetch it from */
3471 struct ctdluser *is_mailbox, /* Mailbox room? */
3472 int is_binary, /* Is encoding necessary? */
3473 int is_unique, /* Del others of this type? */
3474 unsigned int flags /* Internal save flags */
3479 struct ctdlroom qrbuf;
3480 char roomname[ROOMNAMELEN];
3481 struct CtdlMessage *msg;
3483 char *raw_message = NULL;
3484 char *encoded_message = NULL;
3485 off_t raw_length = 0;
3487 if (is_mailbox != NULL)
3488 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3490 safestrncpy(roomname, req_room, sizeof(roomname));
3491 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3494 fp = fopen(tempfilename, "rb");
3496 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3497 tempfilename, strerror(errno));
3500 fseek(fp, 0L, SEEK_END);
3501 raw_length = ftell(fp);
3503 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3505 raw_message = malloc((size_t)raw_length + 2);
3506 fread(raw_message, (size_t)raw_length, 1, fp);
3510 encoded_message = malloc((size_t)
3511 (((raw_length * 134) / 100) + 4096 ) );
3514 encoded_message = malloc((size_t)(raw_length + 4096));
3517 sprintf(encoded_message, "Content-type: %s\n", content_type);
3520 sprintf(&encoded_message[strlen(encoded_message)],
3521 "Content-transfer-encoding: base64\n\n"
3525 sprintf(&encoded_message[strlen(encoded_message)],
3526 "Content-transfer-encoding: 7bit\n\n"
3532 &encoded_message[strlen(encoded_message)],
3538 raw_message[raw_length] = 0;
3540 &encoded_message[strlen(encoded_message)],
3548 lprintf(CTDL_DEBUG, "Allocating\n");
3549 msg = malloc(sizeof(struct CtdlMessage));
3550 memset(msg, 0, sizeof(struct CtdlMessage));
3551 msg->cm_magic = CTDLMESSAGE_MAGIC;
3552 msg->cm_anon_type = MES_NORMAL;
3553 msg->cm_format_type = 4;
3554 msg->cm_fields['A'] = strdup(CC->user.fullname);
3555 msg->cm_fields['O'] = strdup(req_room);
3556 msg->cm_fields['N'] = strdup(config.c_nodename);
3557 msg->cm_fields['H'] = strdup(config.c_humannode);
3558 msg->cm_flags = flags;
3560 msg->cm_fields['M'] = encoded_message;
3562 /* Create the requested room if we have to. */
3563 if (getroom(&qrbuf, roomname) != 0) {
3564 create_room(roomname,
3565 ( (is_mailbox != NULL) ? 5 : 3 ),
3566 "", 0, 1, 0, VIEW_BBS);
3568 /* If the caller specified this object as unique, delete all
3569 * other objects of this type that are currently in the room.
3572 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3573 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3576 /* Now write the data */
3577 CtdlSubmitMsg(msg, NULL, roomname);
3578 CtdlFreeMessage(msg);
3586 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3587 config_msgnum = msgnum;
3591 char *CtdlGetSysConfig(char *sysconfname) {
3592 char hold_rm[ROOMNAMELEN];
3595 struct CtdlMessage *msg;
3598 strcpy(hold_rm, CC->room.QRname);
3599 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3600 getroom(&CC->room, hold_rm);
3605 /* We want the last (and probably only) config in this room */
3606 begin_critical_section(S_CONFIG);
3607 config_msgnum = (-1L);
3608 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3609 CtdlGetSysConfigBackend, NULL);
3610 msgnum = config_msgnum;
3611 end_critical_section(S_CONFIG);
3617 msg = CtdlFetchMessage(msgnum, 1);
3619 conf = strdup(msg->cm_fields['M']);
3620 CtdlFreeMessage(msg);
3627 getroom(&CC->room, hold_rm);
3629 if (conf != NULL) do {
3630 extract_token(buf, conf, 0, '\n', sizeof buf);
3631 strcpy(conf, &conf[strlen(buf)+1]);
3632 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3637 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3638 char temp[PATH_MAX];
3641 strcpy(temp, tmpnam(NULL));
3643 fp = fopen(temp, "w");
3644 if (fp == NULL) return;
3645 fprintf(fp, "%s", sysconfdata);
3648 /* this handy API function does all the work for us */
3649 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3655 * Determine whether a given Internet address belongs to the current user
3657 int CtdlIsMe(char *addr, int addr_buf_len)
3659 struct recptypes *recp;
3662 recp = validate_recipients(addr);
3663 if (recp == NULL) return(0);
3665 if (recp->num_local == 0) {
3670 for (i=0; i<recp->num_local; ++i) {
3671 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3672 if (!strcasecmp(addr, CC->user.fullname)) {
3684 * Citadel protocol command to do the same
3686 void cmd_isme(char *argbuf) {
3689 if (CtdlAccessCheck(ac_logged_in)) return;
3690 extract_token(addr, argbuf, 0, '|', sizeof addr);
3692 if (CtdlIsMe(addr, sizeof addr)) {
3693 cprintf("%d %s\n", CIT_OK, addr);
3696 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);