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], n[256];
2042 if (v == NULL) return(NULL);
2044 process_rfc822_addr(addr, user, node, name);
2045 vcard_set_prop(v, "fn", name, 0);
2047 vcard_fn_to_n(n, name, sizeof n);
2048 vcard_set_prop(v, "n", n, 0);
2050 snprintf(email, sizeof email, "%s@%s", user, node);
2051 vcard_set_prop(v, "email;internet", email, 0);
2058 * Save a message to disk and submit it into the delivery system.
2060 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2061 struct recptypes *recps, /* recipients (if mail) */
2062 char *force /* force a particular room? */
2064 char submit_filename[128];
2065 char generated_timestamp[32];
2066 char hold_rm[ROOMNAMELEN];
2067 char actual_rm[ROOMNAMELEN];
2068 char force_room[ROOMNAMELEN];
2069 char content_type[SIZ]; /* We have to learn this */
2070 char recipient[SIZ];
2073 struct ctdluser userbuf;
2075 struct MetaData smi;
2076 FILE *network_fp = NULL;
2077 static int seqnum = 1;
2078 struct CtdlMessage *imsg = NULL;
2079 struct CtdlMessage *vmsg = NULL;
2080 long vmsgnum = (-1L);
2082 struct vCard *v = NULL;
2085 char *hold_R, *hold_D;
2086 char *collected_addresses = NULL;
2088 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2089 if (is_valid_message(msg) == 0) return(-1); /* self check */
2091 /* If this message has no timestamp, we take the liberty of
2092 * giving it one, right now.
2094 if (msg->cm_fields['T'] == NULL) {
2095 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2096 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2097 msg->cm_fields['T'] = strdup(generated_timestamp);
2100 /* If this message has no path, we generate one.
2102 if (msg->cm_fields['P'] == NULL) {
2103 lprintf(CTDL_DEBUG, "Generating path\n");
2104 if (msg->cm_fields['A'] != NULL) {
2105 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2106 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2107 if (isspace(msg->cm_fields['P'][a])) {
2108 msg->cm_fields['P'][a] = ' ';
2113 msg->cm_fields['P'] = strdup("unknown");
2117 if (force == NULL) {
2118 strcpy(force_room, "");
2121 strcpy(force_room, force);
2124 /* Learn about what's inside, because it's what's inside that counts */
2125 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2126 if (msg->cm_fields['M'] == NULL) {
2127 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2131 switch (msg->cm_format_type) {
2133 strcpy(content_type, "text/x-citadel-variformat");
2136 strcpy(content_type, "text/plain");
2139 strcpy(content_type, "text/plain");
2140 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2142 safestrncpy(content_type, &mptr[14],
2143 sizeof content_type);
2144 for (a = 0; a < strlen(content_type); ++a) {
2145 if ((content_type[a] == ';')
2146 || (content_type[a] == ' ')
2147 || (content_type[a] == 13)
2148 || (content_type[a] == 10)) {
2149 content_type[a] = 0;
2155 /* Goto the correct room */
2156 lprintf(CTDL_DEBUG, "Selected room %s\n",
2157 (recps) ? CC->room.QRname : SENTITEMS);
2158 strcpy(hold_rm, CC->room.QRname);
2159 strcpy(actual_rm, CC->room.QRname);
2160 if (recps != NULL) {
2161 strcpy(actual_rm, SENTITEMS);
2164 /* If the user is a twit, move to the twit room for posting */
2165 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2166 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2168 if (CC->user.axlevel == 2) {
2169 strcpy(hold_rm, actual_rm);
2170 strcpy(actual_rm, config.c_twitroom);
2174 /* ...or if this message is destined for Aide> then go there. */
2175 if (strlen(force_room) > 0) {
2176 strcpy(actual_rm, force_room);
2179 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2180 if (strcasecmp(actual_rm, CC->room.QRname)) {
2181 /* getroom(&CC->room, actual_rm); */
2182 usergoto(actual_rm, 0, 1, NULL, NULL);
2186 * If this message has no O (room) field, generate one.
2188 if (msg->cm_fields['O'] == NULL) {
2189 msg->cm_fields['O'] = strdup(CC->room.QRname);
2192 /* Perform "before save" hooks (aborting if any return nonzero) */
2193 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2194 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2196 /* If this message has an Exclusive ID, perform replication checks */
2197 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2198 if (ReplicationChecks(msg) > 0) return(-4);
2200 /* Save it to disk */
2201 lprintf(CTDL_DEBUG, "Saving to disk\n");
2202 newmsgid = send_message(msg);
2203 if (newmsgid <= 0L) return(-5);
2205 /* Write a supplemental message info record. This doesn't have to
2206 * be a critical section because nobody else knows about this message
2209 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2210 memset(&smi, 0, sizeof(struct MetaData));
2211 smi.meta_msgnum = newmsgid;
2212 smi.meta_refcount = 0;
2213 safestrncpy(smi.meta_content_type, content_type,
2214 sizeof smi.meta_content_type);
2216 /* As part of the new metadata record, measure how
2217 * big this message will be when displayed as RFC822.
2218 * Both POP and IMAP use this, and it's best to just take the hit now
2219 * instead of having to potentially measure thousands of messages when
2220 * a mailbox is opened later.
2223 if (CC->redirect_buffer != NULL) {
2224 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2227 CC->redirect_buffer = malloc(SIZ);
2228 CC->redirect_len = 0;
2229 CC->redirect_alloc = SIZ;
2230 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2231 smi.meta_rfc822_length = CC->redirect_len;
2232 free(CC->redirect_buffer);
2233 CC->redirect_buffer = NULL;
2234 CC->redirect_len = 0;
2235 CC->redirect_alloc = 0;
2239 /* Now figure out where to store the pointers */
2240 lprintf(CTDL_DEBUG, "Storing pointers\n");
2242 /* If this is being done by the networker delivering a private
2243 * message, we want to BYPASS saving the sender's copy (because there
2244 * is no local sender; it would otherwise go to the Trashcan).
2246 if ((!CC->internal_pgm) || (recps == NULL)) {
2247 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2248 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2249 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2254 /* For internet mail, drop a copy in the outbound queue room */
2256 if (recps->num_internet > 0) {
2257 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2260 /* If other rooms are specified, drop them there too. */
2262 if (recps->num_room > 0)
2263 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2264 extract_token(recipient, recps->recp_room, i,
2265 '|', sizeof recipient);
2266 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2267 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2270 /* Bump this user's messages posted counter. */
2271 lprintf(CTDL_DEBUG, "Updating user\n");
2272 lgetuser(&CC->user, CC->curr_user);
2273 CC->user.posted = CC->user.posted + 1;
2274 lputuser(&CC->user);
2276 /* If this is private, local mail, make a copy in the
2277 * recipient's mailbox and bump the reference count.
2280 if (recps->num_local > 0)
2281 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2282 extract_token(recipient, recps->recp_local, i,
2283 '|', sizeof recipient);
2284 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2286 if (getuser(&userbuf, recipient) == 0) {
2287 MailboxName(actual_rm, sizeof actual_rm,
2288 &userbuf, MAILROOM);
2289 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2290 BumpNewMailCounter(userbuf.usernum);
2293 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2294 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2299 /* Perform "after save" hooks */
2300 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2301 PerformMessageHooks(msg, EVT_AFTERSAVE);
2303 /* For IGnet mail, we have to save a new copy into the spooler for
2304 * each recipient, with the R and D fields set to the recipient and
2305 * destination-node. This has two ugly side effects: all other
2306 * recipients end up being unlisted in this recipient's copy of the
2307 * message, and it has to deliver multiple messages to the same
2308 * node. We'll revisit this again in a year or so when everyone has
2309 * a network spool receiver that can handle the new style messages.
2312 if (recps->num_ignet > 0)
2313 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2314 extract_token(recipient, recps->recp_ignet, i,
2315 '|', sizeof recipient);
2317 hold_R = msg->cm_fields['R'];
2318 hold_D = msg->cm_fields['D'];
2319 msg->cm_fields['R'] = malloc(SIZ);
2320 msg->cm_fields['D'] = malloc(128);
2321 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2322 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2324 serialize_message(&smr, msg);
2326 snprintf(submit_filename, sizeof submit_filename,
2327 #ifndef HAVE_SPOOL_DIR
2332 "/network/spoolin/netmail.%04lx.%04x.%04x",
2333 (long) getpid(), CC->cs_pid, ++seqnum);
2334 network_fp = fopen(submit_filename, "wb+");
2335 if (network_fp != NULL) {
2336 fwrite(smr.ser, smr.len, 1, network_fp);
2342 free(msg->cm_fields['R']);
2343 free(msg->cm_fields['D']);
2344 msg->cm_fields['R'] = hold_R;
2345 msg->cm_fields['D'] = hold_D;
2348 /* Go back to the room we started from */
2349 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2350 if (strcasecmp(hold_rm, CC->room.QRname))
2351 /* getroom(&CC->room, hold_rm); */
2352 usergoto(hold_rm, 0, 1, NULL, NULL);
2354 /* For internet mail, generate delivery instructions.
2355 * Yes, this is recursive. Deal with it. Infinite recursion does
2356 * not happen because the delivery instructions message does not
2357 * contain a recipient.
2360 if (recps->num_internet > 0) {
2361 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2362 instr = malloc(SIZ * 2);
2363 snprintf(instr, SIZ * 2,
2364 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2366 SPOOLMIME, newmsgid, (long)time(NULL),
2367 msg->cm_fields['A'], msg->cm_fields['N']
2370 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2371 size_t tmp = strlen(instr);
2372 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2373 snprintf(&instr[tmp], SIZ * 2 - tmp,
2374 "remote|%s|0||\n", recipient);
2377 imsg = malloc(sizeof(struct CtdlMessage));
2378 memset(imsg, 0, sizeof(struct CtdlMessage));
2379 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2380 imsg->cm_anon_type = MES_NORMAL;
2381 imsg->cm_format_type = FMT_RFC822;
2382 imsg->cm_fields['A'] = strdup("Citadel");
2383 imsg->cm_fields['M'] = instr;
2384 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2385 CtdlFreeMessage(imsg);
2389 * Any addresses to harvest for someone's address book?
2390 * (Don't do this if this is not a message with recipients, otherwise we will
2391 * send this function into infinite recursion!!!!!)
2393 if (recps != NULL) {
2394 collected_addresses = harvest_collected_addresses(msg);
2397 if (collected_addresses != NULL) {
2398 for (i=0; i<num_tokens(collected_addresses, ','); ++i) {
2400 /* Make a vCard out of each address */
2401 extract_token(recipient, collected_addresses, i, ',', sizeof recipient);
2403 v = vcard_new_from_rfc822_addr(recipient);
2405 vmsg = malloc(sizeof(struct CtdlMessage));
2406 memset(vmsg, 0, sizeof(struct CtdlMessage));
2407 vmsg->cm_magic = CTDLMESSAGE_MAGIC;
2408 vmsg->cm_anon_type = MES_NORMAL;
2409 vmsg->cm_format_type = FMT_RFC822;
2410 vmsg->cm_fields['A'] = strdup("Citadel");
2411 vmsg->cm_fields['E'] = strdup(recipient); /* this handles dups */
2412 ser = vcard_serialize(v);
2414 vmsg->cm_fields['M'] = malloc(strlen(ser) + 1024);
2415 sprintf(vmsg->cm_fields['M'],
2416 "Content-type: text/x-vcard"
2417 "\r\n\r\n%s\r\n", ser);
2422 if (recps->num_local > 0) {
2423 for (j=0; j<num_tokens(recps->recp_local, '|'); ++j) {
2424 extract_token(recipient, recps->recp_local, j,
2425 '|', sizeof recipient);
2426 lprintf(CTDL_DEBUG, "Adding contact for <%s>\n", recipient);
2427 if (getuser(&userbuf, recipient) == 0) {
2428 MailboxName(actual_rm, sizeof actual_rm,
2429 &userbuf, USERCONTACTSROOM);
2432 vmsgnum = CtdlSubmitMsg(vmsg,
2436 CtdlSaveMsgPointerInRoom(actual_rm,
2443 CtdlFreeMessage(vmsg);
2446 free(collected_addresses);
2459 * Convenience function for generating small administrative messages.
2461 void quickie_message(char *from, char *to, char *room, char *text,
2462 int format_type, char *subject)
2464 struct CtdlMessage *msg;
2465 struct recptypes *recp = NULL;
2467 msg = malloc(sizeof(struct CtdlMessage));
2468 memset(msg, 0, sizeof(struct CtdlMessage));
2469 msg->cm_magic = CTDLMESSAGE_MAGIC;
2470 msg->cm_anon_type = MES_NORMAL;
2471 msg->cm_format_type = format_type;
2472 msg->cm_fields['A'] = strdup(from);
2473 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2474 msg->cm_fields['N'] = strdup(NODENAME);
2476 msg->cm_fields['R'] = strdup(to);
2477 recp = validate_recipients(to);
2479 if (subject != NULL) {
2480 msg->cm_fields['U'] = strdup(subject);
2482 msg->cm_fields['M'] = strdup(text);
2484 CtdlSubmitMsg(msg, recp, room);
2485 CtdlFreeMessage(msg);
2486 if (recp != NULL) free(recp);
2492 * Back end function used by CtdlMakeMessage() and similar functions
2494 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2495 size_t maxlen, /* maximum message length */
2496 char *exist, /* if non-null, append to it;
2497 exist is ALWAYS freed */
2498 int crlf /* CRLF newlines instead of LF */
2502 size_t message_len = 0;
2503 size_t buffer_len = 0;
2509 if (exist == NULL) {
2516 message_len = strlen(exist);
2517 buffer_len = message_len + 4096;
2518 m = realloc(exist, buffer_len);
2525 /* flush the input if we have nowhere to store it */
2530 /* read in the lines of message text one by one */
2532 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2533 if (!strcmp(buf, terminator)) finished = 1;
2535 strcat(buf, "\r\n");
2541 if ( (!flushing) && (!finished) ) {
2542 /* Measure the line */
2543 linelen = strlen(buf);
2545 /* augment the buffer if we have to */
2546 if ((message_len + linelen) >= buffer_len) {
2547 ptr = realloc(m, (buffer_len * 2) );
2548 if (ptr == NULL) { /* flush if can't allocate */
2551 buffer_len = (buffer_len * 2);
2553 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2557 /* Add the new line to the buffer. NOTE: this loop must avoid
2558 * using functions like strcat() and strlen() because they
2559 * traverse the entire buffer upon every call, and doing that
2560 * for a multi-megabyte message slows it down beyond usability.
2562 strcpy(&m[message_len], buf);
2563 message_len += linelen;
2566 /* if we've hit the max msg length, flush the rest */
2567 if (message_len >= maxlen) flushing = 1;
2569 } while (!finished);
2577 * Build a binary message to be saved on disk.
2578 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2579 * will become part of the message. This means you are no longer
2580 * responsible for managing that memory -- it will be freed along with
2581 * the rest of the fields when CtdlFreeMessage() is called.)
2584 struct CtdlMessage *CtdlMakeMessage(
2585 struct ctdluser *author, /* author's user structure */
2586 char *recipient, /* NULL if it's not mail */
2587 char *recp_cc, /* NULL if it's not mail */
2588 char *room, /* room where it's going */
2589 int type, /* see MES_ types in header file */
2590 int format_type, /* variformat, plain text, MIME... */
2591 char *fake_name, /* who we're masquerading as */
2592 char *subject, /* Subject (optional) */
2593 char *preformatted_text /* ...or NULL to read text from client */
2595 char dest_node[SIZ];
2597 struct CtdlMessage *msg;
2599 msg = malloc(sizeof(struct CtdlMessage));
2600 memset(msg, 0, sizeof(struct CtdlMessage));
2601 msg->cm_magic = CTDLMESSAGE_MAGIC;
2602 msg->cm_anon_type = type;
2603 msg->cm_format_type = format_type;
2605 /* Don't confuse the poor folks if it's not routed mail. */
2606 strcpy(dest_node, "");
2611 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2612 msg->cm_fields['P'] = strdup(buf);
2614 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2615 msg->cm_fields['T'] = strdup(buf);
2617 if (fake_name[0]) /* author */
2618 msg->cm_fields['A'] = strdup(fake_name);
2620 msg->cm_fields['A'] = strdup(author->fullname);
2622 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2623 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2626 msg->cm_fields['O'] = strdup(CC->room.QRname);
2629 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2630 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2632 if (recipient[0] != 0) {
2633 msg->cm_fields['R'] = strdup(recipient);
2635 if (recp_cc[0] != 0) {
2636 msg->cm_fields['Y'] = strdup(recp_cc);
2638 if (dest_node[0] != 0) {
2639 msg->cm_fields['D'] = strdup(dest_node);
2642 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2643 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2646 if (subject != NULL) {
2648 if (strlen(subject) > 0) {
2649 msg->cm_fields['U'] = strdup(subject);
2653 if (preformatted_text != NULL) {
2654 msg->cm_fields['M'] = preformatted_text;
2657 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2658 config.c_maxmsglen, NULL, 0);
2666 * Check to see whether we have permission to post a message in the current
2667 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2668 * returns 0 on success.
2670 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2672 if (!(CC->logged_in)) {
2673 snprintf(errmsgbuf, n, "Not logged in.");
2674 return (ERROR + NOT_LOGGED_IN);
2677 if ((CC->user.axlevel < 2)
2678 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2679 snprintf(errmsgbuf, n, "Need to be validated to enter "
2680 "(except in %s> to sysop)", MAILROOM);
2681 return (ERROR + HIGHER_ACCESS_REQUIRED);
2684 if ((CC->user.axlevel < 4)
2685 && (CC->room.QRflags & QR_NETWORK)) {
2686 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2687 return (ERROR + HIGHER_ACCESS_REQUIRED);
2690 if ((CC->user.axlevel < 6)
2691 && (CC->room.QRflags & QR_READONLY)) {
2692 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2693 return (ERROR + HIGHER_ACCESS_REQUIRED);
2696 strcpy(errmsgbuf, "Ok");
2702 * Check to see if the specified user has Internet mail permission
2703 * (returns nonzero if permission is granted)
2705 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2707 /* Do not allow twits to send Internet mail */
2708 if (who->axlevel <= 2) return(0);
2710 /* Globally enabled? */
2711 if (config.c_restrict == 0) return(1);
2713 /* User flagged ok? */
2714 if (who->flags & US_INTERNET) return(2);
2716 /* Aide level access? */
2717 if (who->axlevel >= 6) return(3);
2719 /* No mail for you! */
2725 * Validate recipients, count delivery types and errors, and handle aliasing
2726 * FIXME check for dupes!!!!!
2727 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2728 * or the number of addresses found invalid.
2730 struct recptypes *validate_recipients(char *recipients) {
2731 struct recptypes *ret;
2732 char this_recp[SIZ];
2733 char this_recp_cooked[SIZ];
2739 struct ctdluser tempUS;
2740 struct ctdlroom tempQR;
2743 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2744 if (ret == NULL) return(NULL);
2745 memset(ret, 0, sizeof(struct recptypes));
2748 ret->num_internet = 0;
2753 if (recipients == NULL) {
2756 else if (strlen(recipients) == 0) {
2760 /* Change all valid separator characters to commas */
2761 for (i=0; i<strlen(recipients); ++i) {
2762 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2763 recipients[i] = ',';
2768 num_recps = num_tokens(recipients, ',');
2771 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2772 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2774 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2775 mailtype = alias(this_recp);
2776 mailtype = alias(this_recp);
2777 mailtype = alias(this_recp);
2778 for (j=0; j<=strlen(this_recp); ++j) {
2779 if (this_recp[j]=='_') {
2780 this_recp_cooked[j] = ' ';
2783 this_recp_cooked[j] = this_recp[j];
2789 if (!strcasecmp(this_recp, "sysop")) {
2791 strcpy(this_recp, config.c_aideroom);
2792 if (strlen(ret->recp_room) > 0) {
2793 strcat(ret->recp_room, "|");
2795 strcat(ret->recp_room, this_recp);
2797 else if (getuser(&tempUS, this_recp) == 0) {
2799 strcpy(this_recp, tempUS.fullname);
2800 if (strlen(ret->recp_local) > 0) {
2801 strcat(ret->recp_local, "|");
2803 strcat(ret->recp_local, this_recp);
2805 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2807 strcpy(this_recp, tempUS.fullname);
2808 if (strlen(ret->recp_local) > 0) {
2809 strcat(ret->recp_local, "|");
2811 strcat(ret->recp_local, this_recp);
2813 else if ( (!strncasecmp(this_recp, "room_", 5))
2814 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2816 if (strlen(ret->recp_room) > 0) {
2817 strcat(ret->recp_room, "|");
2819 strcat(ret->recp_room, &this_recp_cooked[5]);
2827 /* Yes, you're reading this correctly: if the target
2828 * domain points back to the local system or an attached
2829 * Citadel directory, the address is invalid. That's
2830 * because if the address were valid, we would have
2831 * already translated it to a local address by now.
2833 if (IsDirectory(this_recp)) {
2838 ++ret->num_internet;
2839 if (strlen(ret->recp_internet) > 0) {
2840 strcat(ret->recp_internet, "|");
2842 strcat(ret->recp_internet, this_recp);
2847 if (strlen(ret->recp_ignet) > 0) {
2848 strcat(ret->recp_ignet, "|");
2850 strcat(ret->recp_ignet, this_recp);
2858 if (strlen(ret->errormsg) == 0) {
2859 snprintf(append, sizeof append,
2860 "Invalid recipient: %s",
2864 snprintf(append, sizeof append,
2867 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2868 strcat(ret->errormsg, append);
2872 if (strlen(ret->display_recp) == 0) {
2873 strcpy(append, this_recp);
2876 snprintf(append, sizeof append, ", %s",
2879 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2880 strcat(ret->display_recp, append);
2885 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2886 ret->num_room + ret->num_error) == 0) {
2887 ret->num_error = (-1);
2888 strcpy(ret->errormsg, "No recipients specified.");
2891 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2892 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2893 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2894 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2895 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2896 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2904 * message entry - mode 0 (normal)
2906 void cmd_ent0(char *entargs)
2912 char masquerade_as[SIZ];
2914 int format_type = 0;
2915 char newusername[SIZ];
2916 struct CtdlMessage *msg;
2920 struct recptypes *valid = NULL;
2921 struct recptypes *valid_to = NULL;
2922 struct recptypes *valid_cc = NULL;
2923 struct recptypes *valid_bcc = NULL;
2930 post = extract_int(entargs, 0);
2931 extract_token(recp, entargs, 1, '|', sizeof recp);
2932 anon_flag = extract_int(entargs, 2);
2933 format_type = extract_int(entargs, 3);
2934 extract_token(subject, entargs, 4, '|', sizeof subject);
2935 do_confirm = extract_int(entargs, 6);
2936 extract_token(cc, entargs, 7, '|', sizeof cc);
2937 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2939 /* first check to make sure the request is valid. */
2941 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2943 cprintf("%d %s\n", err, errmsg);
2947 /* Check some other permission type things. */
2950 if (CC->user.axlevel < 6) {
2951 cprintf("%d You don't have permission to masquerade.\n",
2952 ERROR + HIGHER_ACCESS_REQUIRED);
2955 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2956 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2957 safestrncpy(CC->fake_postname, newusername,
2958 sizeof(CC->fake_postname) );
2959 cprintf("%d ok\n", CIT_OK);
2962 CC->cs_flags |= CS_POSTING;
2964 /* In the Mail> room we have to behave a little differently --
2965 * make sure the user has specified at least one recipient. Then
2966 * validate the recipient(s).
2968 if ( (CC->room.QRflags & QR_MAILBOX)
2969 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2971 if (CC->user.axlevel < 2) {
2972 strcpy(recp, "sysop");
2977 valid_to = validate_recipients(recp);
2978 if (valid_to->num_error > 0) {
2979 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2984 valid_cc = validate_recipients(cc);
2985 if (valid_cc->num_error > 0) {
2986 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2992 valid_bcc = validate_recipients(bcc);
2993 if (valid_bcc->num_error > 0) {
2994 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3001 /* Recipient required, but none were specified */
3002 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3006 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3010 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3011 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3012 cprintf("%d You do not have permission "
3013 "to send Internet mail.\n",
3014 ERROR + HIGHER_ACCESS_REQUIRED);
3022 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)
3023 && (CC->user.axlevel < 4) ) {
3024 cprintf("%d Higher access required for network mail.\n",
3025 ERROR + HIGHER_ACCESS_REQUIRED);
3032 if ((RESTRICT_INTERNET == 1)
3033 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3034 && ((CC->user.flags & US_INTERNET) == 0)
3035 && (!CC->internal_pgm)) {
3036 cprintf("%d You don't have access to Internet mail.\n",
3037 ERROR + HIGHER_ACCESS_REQUIRED);
3046 /* Is this a room which has anonymous-only or anonymous-option? */
3047 anonymous = MES_NORMAL;
3048 if (CC->room.QRflags & QR_ANONONLY) {
3049 anonymous = MES_ANONONLY;
3051 if (CC->room.QRflags & QR_ANONOPT) {
3052 if (anon_flag == 1) { /* only if the user requested it */
3053 anonymous = MES_ANONOPT;
3057 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3061 /* If we're only checking the validity of the request, return
3062 * success without creating the message.
3065 cprintf("%d %s\n", CIT_OK,
3066 ((valid_to != NULL) ? valid_to->display_recp : "") );
3073 /* We don't need these anymore because we'll do it differently below */
3078 /* Handle author masquerading */
3079 if (CC->fake_postname[0]) {
3080 strcpy(masquerade_as, CC->fake_postname);
3082 else if (CC->fake_username[0]) {
3083 strcpy(masquerade_as, CC->fake_username);
3086 strcpy(masquerade_as, "");
3089 /* Read in the message from the client. */
3091 cprintf("%d send message\n", START_CHAT_MODE);
3093 cprintf("%d send message\n", SEND_LISTING);
3096 msg = CtdlMakeMessage(&CC->user, recp, cc,
3097 CC->room.QRname, anonymous, format_type,
3098 masquerade_as, subject, NULL);
3100 /* Put together one big recipients struct containing to/cc/bcc all in
3101 * one. This is for the envelope.
3103 char *all_recps = malloc(SIZ * 3);
3104 strcpy(all_recps, recp);
3105 if (strlen(cc) > 0) {
3106 if (strlen(all_recps) > 0) {
3107 strcat(all_recps, ",");
3109 strcat(all_recps, cc);
3111 if (strlen(bcc) > 0) {
3112 if (strlen(all_recps) > 0) {
3113 strcat(all_recps, ",");
3115 strcat(all_recps, bcc);
3117 if (strlen(all_recps) > 0) {
3118 valid = validate_recipients(all_recps);
3126 msgnum = CtdlSubmitMsg(msg, valid, "");
3129 cprintf("%ld\n", msgnum);
3131 cprintf("Message accepted.\n");
3134 cprintf("Internal error.\n");
3136 if (msg->cm_fields['E'] != NULL) {
3137 cprintf("%s\n", msg->cm_fields['E']);
3144 CtdlFreeMessage(msg);
3146 CC->fake_postname[0] = '\0';
3147 if (valid != NULL) {
3156 * API function to delete messages which match a set of criteria
3157 * (returns the actual number of messages deleted)
3159 int CtdlDeleteMessages(char *room_name, /* which room */
3160 long dmsgnum, /* or "0" for any */
3161 char *content_type, /* or "" for any */
3162 int deferred /* let TDAP sweep it later */
3166 struct ctdlroom qrbuf;
3167 struct cdbdata *cdbfr;
3168 long *msglist = NULL;
3169 long *dellist = NULL;
3172 int num_deleted = 0;
3174 struct MetaData smi;
3176 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3177 room_name, dmsgnum, content_type, deferred);
3179 /* get room record, obtaining a lock... */
3180 if (lgetroom(&qrbuf, room_name) != 0) {
3181 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3183 return (0); /* room not found */
3185 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3187 if (cdbfr != NULL) {
3188 msglist = malloc(cdbfr->len);
3189 dellist = malloc(cdbfr->len);
3190 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3191 num_msgs = cdbfr->len / sizeof(long);
3195 for (i = 0; i < num_msgs; ++i) {
3198 /* Set/clear a bit for each criterion */
3200 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3201 delete_this |= 0x01;
3203 if (strlen(content_type) == 0) {
3204 delete_this |= 0x02;
3206 GetMetaData(&smi, msglist[i]);
3207 if (!strcasecmp(smi.meta_content_type,
3209 delete_this |= 0x02;
3213 /* Delete message only if all bits are set */
3214 if (delete_this == 0x03) {
3215 dellist[num_deleted++] = msglist[i];
3220 num_msgs = sort_msglist(msglist, num_msgs);
3221 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3222 msglist, (int)(num_msgs * sizeof(long)));
3224 qrbuf.QRhighest = msglist[num_msgs - 1];
3229 * If the delete operation is "deferred" (and technically, any delete
3230 * operation not performed by THE DREADED AUTO-PURGER ought to be
3231 * a deferred delete) then we save a pointer to the message in the
3232 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3233 * at least 1, which will save the user from having to synchronously
3234 * wait for various disk-intensive operations to complete.
3236 if ( (deferred) && (num_deleted) ) {
3237 for (i=0; i<num_deleted; ++i) {
3238 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3242 /* Go through the messages we pulled out of the index, and decrement
3243 * their reference counts by 1. If this is the only room the message
3244 * was in, the reference count will reach zero and the message will
3245 * automatically be deleted from the database. We do this in a
3246 * separate pass because there might be plug-in hooks getting called,
3247 * and we don't want that happening during an S_ROOMS critical
3250 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3251 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3252 AdjRefCount(dellist[i], -1);
3255 /* Now free the memory we used, and go away. */
3256 if (msglist != NULL) free(msglist);
3257 if (dellist != NULL) free(dellist);
3258 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3259 return (num_deleted);
3265 * Check whether the current user has permission to delete messages from
3266 * the current room (returns 1 for yes, 0 for no)
3268 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3269 getuser(&CC->user, CC->curr_user);
3270 if ((CC->user.axlevel < 6)
3271 && (CC->user.usernum != CC->room.QRroomaide)
3272 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3273 && (!(CC->internal_pgm))) {
3282 * Delete message from current room
3284 void cmd_dele(char *delstr)
3289 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3290 cprintf("%d Higher access required.\n",
3291 ERROR + HIGHER_ACCESS_REQUIRED);
3294 delnum = extract_long(delstr, 0);
3296 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3299 cprintf("%d %d message%s deleted.\n", CIT_OK,
3300 num_deleted, ((num_deleted != 1) ? "s" : ""));
3302 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3308 * Back end API function for moves and deletes
3310 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3313 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3314 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3315 if (err != 0) return(err);
3323 * move or copy a message to another room
3325 void cmd_move(char *args)
3328 char targ[ROOMNAMELEN];
3329 struct ctdlroom qtemp;
3335 num = extract_long(args, 0);
3336 extract_token(targ, args, 1, '|', sizeof targ);
3337 targ[ROOMNAMELEN - 1] = 0;
3338 is_copy = extract_int(args, 2);
3340 if (getroom(&qtemp, targ) != 0) {
3341 cprintf("%d '%s' does not exist.\n",
3342 ERROR + ROOM_NOT_FOUND, targ);
3346 getuser(&CC->user, CC->curr_user);
3347 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3349 /* Check for permission to perform this operation.
3350 * Remember: "CC->room" is source, "qtemp" is target.
3354 /* Aides can move/copy */
3355 if (CC->user.axlevel >= 6) permit = 1;
3357 /* Room aides can move/copy */
3358 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3360 /* Permit move/copy from personal rooms */
3361 if ((CC->room.QRflags & QR_MAILBOX)
3362 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3364 /* Permit only copy from public to personal room */
3366 && (!(CC->room.QRflags & QR_MAILBOX))
3367 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3369 /* User must have access to target room */
3370 if (!(ra & UA_KNOWN)) permit = 0;
3373 cprintf("%d Higher access required.\n",
3374 ERROR + HIGHER_ACCESS_REQUIRED);
3378 err = CtdlCopyMsgToRoom(num, targ);
3380 cprintf("%d Cannot store message in %s: error %d\n",
3385 /* Now delete the message from the source room,
3386 * if this is a 'move' rather than a 'copy' operation.
3389 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3392 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3398 * GetMetaData() - Get the supplementary record for a message
3400 void GetMetaData(struct MetaData *smibuf, long msgnum)
3403 struct cdbdata *cdbsmi;
3406 memset(smibuf, 0, sizeof(struct MetaData));
3407 smibuf->meta_msgnum = msgnum;
3408 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3410 /* Use the negative of the message number for its supp record index */
3411 TheIndex = (0L - msgnum);
3413 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3414 if (cdbsmi == NULL) {
3415 return; /* record not found; go with defaults */
3417 memcpy(smibuf, cdbsmi->ptr,
3418 ((cdbsmi->len > sizeof(struct MetaData)) ?
3419 sizeof(struct MetaData) : cdbsmi->len));
3426 * PutMetaData() - (re)write supplementary record for a message
3428 void PutMetaData(struct MetaData *smibuf)
3432 /* Use the negative of the message number for the metadata db index */
3433 TheIndex = (0L - smibuf->meta_msgnum);
3435 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3436 smibuf->meta_msgnum, smibuf->meta_refcount);
3438 cdb_store(CDB_MSGMAIN,
3439 &TheIndex, (int)sizeof(long),
3440 smibuf, (int)sizeof(struct MetaData));
3445 * AdjRefCount - change the reference count for a message;
3446 * delete the message if it reaches zero
3448 void AdjRefCount(long msgnum, int incr)
3451 struct MetaData smi;
3454 /* This is a *tight* critical section; please keep it that way, as
3455 * it may get called while nested in other critical sections.
3456 * Complicating this any further will surely cause deadlock!
3458 begin_critical_section(S_SUPPMSGMAIN);
3459 GetMetaData(&smi, msgnum);
3460 smi.meta_refcount += incr;
3462 end_critical_section(S_SUPPMSGMAIN);
3463 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3464 msgnum, incr, smi.meta_refcount);
3466 /* If the reference count is now zero, delete the message
3467 * (and its supplementary record as well).
3469 if (smi.meta_refcount == 0) {
3470 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3472 /* Remove from fulltext index */
3473 if (config.c_enable_fulltext) {
3474 ft_index_message(msgnum, 0);
3477 /* Remove from message base */
3479 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3480 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3482 /* Remove metadata record */
3483 delnum = (0L - msgnum);
3484 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3489 * Write a generic object to this room
3491 * Note: this could be much more efficient. Right now we use two temporary
3492 * files, and still pull the message into memory as with all others.
3494 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3495 char *content_type, /* MIME type of this object */
3496 char *tempfilename, /* Where to fetch it from */
3497 struct ctdluser *is_mailbox, /* Mailbox room? */
3498 int is_binary, /* Is encoding necessary? */
3499 int is_unique, /* Del others of this type? */
3500 unsigned int flags /* Internal save flags */
3505 struct ctdlroom qrbuf;
3506 char roomname[ROOMNAMELEN];
3507 struct CtdlMessage *msg;
3509 char *raw_message = NULL;
3510 char *encoded_message = NULL;
3511 off_t raw_length = 0;
3513 if (is_mailbox != NULL)
3514 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3516 safestrncpy(roomname, req_room, sizeof(roomname));
3517 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3520 fp = fopen(tempfilename, "rb");
3522 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3523 tempfilename, strerror(errno));
3526 fseek(fp, 0L, SEEK_END);
3527 raw_length = ftell(fp);
3529 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3531 raw_message = malloc((size_t)raw_length + 2);
3532 fread(raw_message, (size_t)raw_length, 1, fp);
3536 encoded_message = malloc((size_t)
3537 (((raw_length * 134) / 100) + 4096 ) );
3540 encoded_message = malloc((size_t)(raw_length + 4096));
3543 sprintf(encoded_message, "Content-type: %s\n", content_type);
3546 sprintf(&encoded_message[strlen(encoded_message)],
3547 "Content-transfer-encoding: base64\n\n"
3551 sprintf(&encoded_message[strlen(encoded_message)],
3552 "Content-transfer-encoding: 7bit\n\n"
3558 &encoded_message[strlen(encoded_message)],
3564 raw_message[raw_length] = 0;
3566 &encoded_message[strlen(encoded_message)],
3574 lprintf(CTDL_DEBUG, "Allocating\n");
3575 msg = malloc(sizeof(struct CtdlMessage));
3576 memset(msg, 0, sizeof(struct CtdlMessage));
3577 msg->cm_magic = CTDLMESSAGE_MAGIC;
3578 msg->cm_anon_type = MES_NORMAL;
3579 msg->cm_format_type = 4;
3580 msg->cm_fields['A'] = strdup(CC->user.fullname);
3581 msg->cm_fields['O'] = strdup(req_room);
3582 msg->cm_fields['N'] = strdup(config.c_nodename);
3583 msg->cm_fields['H'] = strdup(config.c_humannode);
3584 msg->cm_flags = flags;
3586 msg->cm_fields['M'] = encoded_message;
3588 /* Create the requested room if we have to. */
3589 if (getroom(&qrbuf, roomname) != 0) {
3590 create_room(roomname,
3591 ( (is_mailbox != NULL) ? 5 : 3 ),
3592 "", 0, 1, 0, VIEW_BBS);
3594 /* If the caller specified this object as unique, delete all
3595 * other objects of this type that are currently in the room.
3598 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3599 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3602 /* Now write the data */
3603 CtdlSubmitMsg(msg, NULL, roomname);
3604 CtdlFreeMessage(msg);
3612 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3613 config_msgnum = msgnum;
3617 char *CtdlGetSysConfig(char *sysconfname) {
3618 char hold_rm[ROOMNAMELEN];
3621 struct CtdlMessage *msg;
3624 strcpy(hold_rm, CC->room.QRname);
3625 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3626 getroom(&CC->room, hold_rm);
3631 /* We want the last (and probably only) config in this room */
3632 begin_critical_section(S_CONFIG);
3633 config_msgnum = (-1L);
3634 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3635 CtdlGetSysConfigBackend, NULL);
3636 msgnum = config_msgnum;
3637 end_critical_section(S_CONFIG);
3643 msg = CtdlFetchMessage(msgnum, 1);
3645 conf = strdup(msg->cm_fields['M']);
3646 CtdlFreeMessage(msg);
3653 getroom(&CC->room, hold_rm);
3655 if (conf != NULL) do {
3656 extract_token(buf, conf, 0, '\n', sizeof buf);
3657 strcpy(conf, &conf[strlen(buf)+1]);
3658 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3663 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3664 char temp[PATH_MAX];
3667 strcpy(temp, tmpnam(NULL));
3669 fp = fopen(temp, "w");
3670 if (fp == NULL) return;
3671 fprintf(fp, "%s", sysconfdata);
3674 /* this handy API function does all the work for us */
3675 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3681 * Determine whether a given Internet address belongs to the current user
3683 int CtdlIsMe(char *addr, int addr_buf_len)
3685 struct recptypes *recp;
3688 recp = validate_recipients(addr);
3689 if (recp == NULL) return(0);
3691 if (recp->num_local == 0) {
3696 for (i=0; i<recp->num_local; ++i) {
3697 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3698 if (!strcasecmp(addr, CC->user.fullname)) {
3710 * Citadel protocol command to do the same
3712 void cmd_isme(char *argbuf) {
3715 if (CtdlAccessCheck(ac_logged_in)) return;
3716 extract_token(addr, argbuf, 0, '|', sizeof addr);
3718 if (CtdlIsMe(addr, sizeof addr)) {
3719 cprintf("%d %s\n", CIT_OK, addr);
3722 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);