4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
55 struct addresses_to_be_filed *atbf = NULL;
58 * This really belongs in serv_network.c, but I don't know how to export
59 * symbols between modules.
61 struct FilterList *filterlist = NULL;
65 * These are the four-character field headers we use when outputting
66 * messages in Citadel format (as opposed to RFC822 format).
69 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
103 * This function is self explanatory.
104 * (What can I say, I'm in a weird mood today...)
106 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
110 for (i = 0; i < strlen(name); ++i) {
111 if (name[i] == '@') {
112 while (isspace(name[i - 1]) && i > 0) {
113 strcpy(&name[i - 1], &name[i]);
116 while (isspace(name[i + 1])) {
117 strcpy(&name[i + 1], &name[i + 2]);
125 * Aliasing for network mail.
126 * (Error messages have been commented out, because this is a server.)
128 int alias(char *name)
129 { /* process alias and routing info for mail */
132 char aaa[SIZ], bbb[SIZ];
133 char *ignetcfg = NULL;
134 char *ignetmap = NULL;
141 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
142 stripallbut(name, '<', '>');
150 "mail.aliases", "r");
152 fp = fopen("/dev/null", "r");
159 while (fgets(aaa, sizeof aaa, fp) != NULL) {
160 while (isspace(name[0]))
161 strcpy(name, &name[1]);
162 aaa[strlen(aaa) - 1] = 0;
164 for (a = 0; a < strlen(aaa); ++a) {
166 strcpy(bbb, &aaa[a + 1]);
170 if (!strcasecmp(name, aaa))
175 /* Hit the Global Address Book */
176 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
180 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
182 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
183 for (a=0; a<strlen(name); ++a) {
184 if (name[a] == '@') {
185 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
187 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
192 /* determine local or remote type, see citadel.h */
193 at = haschar(name, '@');
194 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
195 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
196 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
198 /* figure out the delivery mode */
199 extract_token(node, name, 1, '@', sizeof node);
201 /* If there are one or more dots in the nodename, we assume that it
202 * is an FQDN and will attempt SMTP delivery to the Internet.
204 if (haschar(node, '.') > 0) {
205 return(MES_INTERNET);
208 /* Otherwise we look in the IGnet maps for a valid Citadel node.
209 * Try directly-connected nodes first...
211 ignetcfg = CtdlGetSysConfig(IGNETCFG);
212 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
213 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
214 extract_token(testnode, buf, 0, '|', sizeof testnode);
215 if (!strcasecmp(node, testnode)) {
223 * Then try nodes that are two or more hops away.
225 ignetmap = CtdlGetSysConfig(IGNETMAP);
226 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
227 extract_token(buf, ignetmap, i, '\n', sizeof buf);
228 extract_token(testnode, buf, 0, '|', sizeof testnode);
229 if (!strcasecmp(node, testnode)) {
236 /* If we get to this point it's an invalid node name */
246 #ifndef HAVE_DATA_DIR
251 "/citadel.control", "r");
253 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
257 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
263 * Back end for the MSGS command: output message number only.
265 void simple_listing(long msgnum, void *userdata)
267 cprintf("%ld\n", msgnum);
273 * Back end for the MSGS command: output header summary.
275 void headers_listing(long msgnum, void *userdata)
277 struct CtdlMessage *msg;
279 msg = CtdlFetchMessage(msgnum, 0);
281 cprintf("%ld|0|||||\n", msgnum);
285 cprintf("%ld|%s|%s|%s|%s|%s|\n",
287 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
288 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
289 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
290 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
291 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
293 CtdlFreeMessage(msg);
298 /* Determine if a given message matches the fields in a message template.
299 * Return 0 for a successful match.
301 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
304 /* If there aren't any fields in the template, all messages will
307 if (template == NULL) return(0);
309 /* Null messages are bogus. */
310 if (msg == NULL) return(1);
312 for (i='A'; i<='Z'; ++i) {
313 if (template->cm_fields[i] != NULL) {
314 if (msg->cm_fields[i] == NULL) {
317 if (strcasecmp(msg->cm_fields[i],
318 template->cm_fields[i])) return 1;
322 /* All compares succeeded: we have a match! */
329 * Retrieve the "seen" message list for the current room.
331 void CtdlGetSeen(char *buf, int which_set) {
334 /* Learn about the user and room in question */
335 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
337 if (which_set == ctdlsetseen_seen)
338 safestrncpy(buf, vbuf.v_seen, SIZ);
339 if (which_set == ctdlsetseen_answered)
340 safestrncpy(buf, vbuf.v_answered, SIZ);
346 * Manipulate the "seen msgs" string (or other message set strings)
348 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
349 int target_setting, int which_set,
350 struct ctdluser *which_user, struct ctdlroom *which_room) {
351 struct cdbdata *cdbfr;
363 char *is_set; /* actually an array of booleans */
366 char setstr[SIZ], lostr[SIZ], histr[SIZ];
369 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
370 num_target_msgnums, target_msgnums[0],
371 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 = (long *) cdbfr->ptr;
383 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
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) {
437 is_seen = is_set[i]; /* Default to existing setting */
439 for (k=0; k<num_target_msgnums; ++k) {
440 if (msglist[i] == target_msgnums[k]) {
441 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 = (long *) cdbfr->ptr;
544 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
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,
851 ma = (struct ma_info *)cbuserdata;
852 if (ma->is_ma == 0) {
853 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
854 name, filename, partnum, disp, cbtype, (long)length);
859 * Callback function for multipart prefix
861 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
862 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
867 ma = (struct ma_info *)cbuserdata;
868 if (!strcasecmp(cbtype, "multipart/alternative")) {
872 if (ma->is_ma == 0) {
873 cprintf("pref=%s|%s\n", partnum, cbtype);
878 * Callback function for multipart sufffix
880 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
881 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
886 ma = (struct ma_info *)cbuserdata;
887 if (ma->is_ma == 0) {
888 cprintf("suff=%s|%s\n", partnum, cbtype);
890 if (!strcasecmp(cbtype, "multipart/alternative")) {
897 * Callback function for mime parser that opens a section for downloading
899 void mime_download(char *name, char *filename, char *partnum, char *disp,
900 void *content, char *cbtype, char *cbcharset, size_t length,
901 char *encoding, void *cbuserdata)
904 /* Silently go away if there's already a download open... */
905 if (CC->download_fp != NULL)
908 /* ...or if this is not the desired section */
909 if (strcasecmp(CC->download_desired_section, partnum))
912 CC->download_fp = tmpfile();
913 if (CC->download_fp == NULL)
916 fwrite(content, length, 1, CC->download_fp);
917 fflush(CC->download_fp);
918 rewind(CC->download_fp);
920 OpenCmdResult(filename, cbtype);
926 * Load a message from disk into memory.
927 * This is used by CtdlOutputMsg() and other fetch functions.
929 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
930 * using the CtdlMessageFree() function.
932 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
934 struct cdbdata *dmsgtext;
935 struct CtdlMessage *ret = NULL;
939 cit_uint8_t field_header;
941 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
943 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
944 if (dmsgtext == NULL) {
947 mptr = dmsgtext->ptr;
948 upper_bound = mptr + dmsgtext->len;
950 /* Parse the three bytes that begin EVERY message on disk.
951 * The first is always 0xFF, the on-disk magic number.
952 * The second is the anonymous/public type byte.
953 * The third is the format type byte (vari, fixed, or MIME).
958 "Message %ld appears to be corrupted.\n",
963 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
964 memset(ret, 0, sizeof(struct CtdlMessage));
966 ret->cm_magic = CTDLMESSAGE_MAGIC;
967 ret->cm_anon_type = *mptr++; /* Anon type byte */
968 ret->cm_format_type = *mptr++; /* Format type byte */
971 * The rest is zero or more arbitrary fields. Load them in.
972 * We're done when we encounter either a zero-length field or
973 * have just processed the 'M' (message text) field.
976 if (mptr >= upper_bound) {
979 field_header = *mptr++;
980 ret->cm_fields[field_header] = strdup(mptr);
982 while (*mptr++ != 0); /* advance to next field */
984 } while ((mptr < upper_bound) && (field_header != 'M'));
988 /* Always make sure there's something in the msg text field. If
989 * it's NULL, the message text is most likely stored separately,
990 * so go ahead and fetch that. Failing that, just set a dummy
991 * body so other code doesn't barf.
993 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
994 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
995 if (dmsgtext != NULL) {
996 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1000 if (ret->cm_fields['M'] == NULL) {
1001 ret->cm_fields['M'] = strdup("<no text>\n");
1004 /* Perform "before read" hooks (aborting if any return nonzero) */
1005 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1006 CtdlFreeMessage(ret);
1015 * Returns 1 if the supplied pointer points to a valid Citadel message.
1016 * If the pointer is NULL or the magic number check fails, returns 0.
1018 int is_valid_message(struct CtdlMessage *msg) {
1021 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1022 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1030 * 'Destructor' for struct CtdlMessage
1032 void CtdlFreeMessage(struct CtdlMessage *msg)
1036 if (is_valid_message(msg) == 0) return;
1038 for (i = 0; i < 256; ++i)
1039 if (msg->cm_fields[i] != NULL) {
1040 free(msg->cm_fields[i]);
1043 msg->cm_magic = 0; /* just in case */
1049 * Pre callback function for multipart/alternative
1051 * NOTE: this differs from the standard behavior for a reason. Normally when
1052 * displaying multipart/alternative you want to show the _last_ usable
1053 * format in the message. Here we show the _first_ one, because it's
1054 * usually text/plain. Since this set of functions is designed for text
1055 * output to non-MIME-aware clients, this is the desired behavior.
1058 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1059 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1064 ma = (struct ma_info *)cbuserdata;
1065 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1066 if (!strcasecmp(cbtype, "multipart/alternative")) {
1070 if (!strcasecmp(cbtype, "message/rfc822")) {
1076 * Post callback function for multipart/alternative
1078 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1079 void *content, char *cbtype, char *cbcharset, size_t length,
1080 char *encoding, void *cbuserdata)
1084 ma = (struct ma_info *)cbuserdata;
1085 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1086 if (!strcasecmp(cbtype, "multipart/alternative")) {
1090 if (!strcasecmp(cbtype, "message/rfc822")) {
1096 * Inline callback function for mime parser that wants to display text
1098 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1099 void *content, char *cbtype, char *cbcharset, size_t length,
1100 char *encoding, void *cbuserdata)
1107 ma = (struct ma_info *)cbuserdata;
1110 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1111 partnum, filename, cbtype, (long)length);
1114 * If we're in the middle of a multipart/alternative scope and
1115 * we've already printed another section, skip this one.
1117 if ( (ma->is_ma) && (ma->did_print) ) {
1118 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1124 if ( (!strcasecmp(cbtype, "text/plain"))
1125 || (strlen(cbtype)==0) ) {
1128 client_write(wptr, length);
1129 if (wptr[length-1] != '\n') {
1134 else if (!strcasecmp(cbtype, "text/html")) {
1135 ptr = html_to_ascii(content, length, 80, 0);
1137 client_write(ptr, wlen);
1138 if (ptr[wlen-1] != '\n') {
1143 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1144 /* above function returns nonzero if it handled the part */
1146 else if (strncasecmp(cbtype, "multipart/", 10)) {
1147 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1148 partnum, filename, cbtype, (long)length);
1153 * The client is elegant and sophisticated and wants to be choosy about
1154 * MIME content types, so figure out which multipart/alternative part
1155 * we're going to send.
1157 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1158 void *content, char *cbtype, char *cbcharset, size_t length,
1159 char *encoding, void *cbuserdata)
1165 ma = (struct ma_info *)cbuserdata;
1167 if (ma->is_ma > 0) {
1168 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1169 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1170 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1171 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1178 * Now that we've chosen our preferred part, output it.
1180 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1181 void *content, char *cbtype, char *cbcharset, size_t length,
1182 char *encoding, void *cbuserdata)
1186 int add_newline = 0;
1190 ma = (struct ma_info *)cbuserdata;
1192 /* This is not the MIME part you're looking for... */
1193 if (strcasecmp(partnum, ma->chosen_part)) return;
1195 /* If the content-type of this part is in our preferred formats
1196 * list, we can simply output it verbatim.
1198 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1199 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1200 if (!strcasecmp(buf, cbtype)) {
1201 /* Yeah! Go! W00t!! */
1203 text_content = (char *)content;
1204 if (text_content[length-1] != '\n') {
1208 cprintf("Content-type: %s", cbtype);
1209 if (strlen(cbcharset) > 0) {
1210 cprintf("; charset=%s", cbcharset);
1212 cprintf("\nContent-length: %d\n",
1213 (int)(length + add_newline) );
1214 if (strlen(encoding) > 0) {
1215 cprintf("Content-transfer-encoding: %s\n", encoding);
1218 cprintf("Content-transfer-encoding: 7bit\n");
1221 client_write(content, length);
1222 if (add_newline) cprintf("\n");
1227 /* No translations required or possible: output as text/plain */
1228 cprintf("Content-type: text/plain\n\n");
1229 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1230 length, encoding, cbuserdata);
1235 char desired_section[64];
1242 * Callback function for
1244 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1245 void *content, char *cbtype, char *cbcharset, size_t length,
1246 char *encoding, void *cbuserdata)
1248 struct encapmsg *encap;
1250 encap = (struct encapmsg *)cbuserdata;
1252 /* Only proceed if this is the desired section... */
1253 if (!strcasecmp(encap->desired_section, partnum)) {
1254 encap->msglen = length;
1255 encap->msg = malloc(length + 2);
1256 memcpy(encap->msg, content, length);
1266 * Get a message off disk. (returns om_* values found in msgbase.h)
1269 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1270 int mode, /* how would you like that message? */
1271 int headers_only, /* eschew the message body? */
1272 int do_proto, /* do Citadel protocol responses? */
1273 int crlf, /* Use CRLF newlines instead of LF? */
1274 char *section /* NULL or a message/rfc822 section */
1276 struct CtdlMessage *TheMessage = NULL;
1277 int retcode = om_no_such_msg;
1278 struct encapmsg encap;
1280 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1282 (section ? section : "<>")
1285 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1286 if (do_proto) cprintf("%d Not logged in.\n",
1287 ERROR + NOT_LOGGED_IN);
1288 return(om_not_logged_in);
1291 /* FIXME: check message id against msglist for this room */
1294 * Fetch the message from disk. If we're in any sort of headers
1295 * only mode, request that we don't even bother loading the body
1298 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1299 TheMessage = CtdlFetchMessage(msg_num, 0);
1302 TheMessage = CtdlFetchMessage(msg_num, 1);
1305 if (TheMessage == NULL) {
1306 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1307 ERROR + MESSAGE_NOT_FOUND, msg_num);
1308 return(om_no_such_msg);
1311 /* Here is the weird form of this command, to process only an
1312 * encapsulated message/rfc822 section.
1314 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1315 memset(&encap, 0, sizeof encap);
1316 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1317 mime_parser(TheMessage->cm_fields['M'],
1319 *extract_encapsulated_message,
1320 NULL, NULL, (void *)&encap, 0
1322 CtdlFreeMessage(TheMessage);
1326 encap.msg[encap.msglen] = 0;
1327 TheMessage = convert_internet_message(encap.msg);
1328 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1330 /* Now we let it fall through to the bottom of this
1331 * function, because TheMessage now contains the
1332 * encapsulated message instead of the top-level
1333 * message. Isn't that neat?
1338 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1339 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1340 retcode = om_no_such_msg;
1345 /* Ok, output the message now */
1346 retcode = CtdlOutputPreLoadedMsg(
1348 headers_only, do_proto, crlf);
1349 CtdlFreeMessage(TheMessage);
1356 * Get a message off disk. (returns om_* values found in msgbase.h)
1359 int CtdlOutputPreLoadedMsg(
1360 struct CtdlMessage *TheMessage,
1361 int mode, /* how would you like that message? */
1362 int headers_only, /* eschew the message body? */
1363 int do_proto, /* do Citadel protocol responses? */
1364 int crlf /* Use CRLF newlines instead of LF? */
1370 char display_name[256];
1372 char *nl; /* newline string */
1374 int subject_found = 0;
1377 /* Buffers needed for RFC822 translation. These are all filled
1378 * using functions that are bounds-checked, and therefore we can
1379 * make them substantially smaller than SIZ.
1387 char datestamp[100];
1389 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1390 ((TheMessage == NULL) ? "NULL" : "not null"),
1391 mode, headers_only, do_proto, crlf);
1393 strcpy(mid, "unknown");
1394 nl = (crlf ? "\r\n" : "\n");
1396 if (!is_valid_message(TheMessage)) {
1398 "ERROR: invalid preloaded message for output\n");
1399 return(om_no_such_msg);
1402 /* Are we downloading a MIME component? */
1403 if (mode == MT_DOWNLOAD) {
1404 if (TheMessage->cm_format_type != FMT_RFC822) {
1406 cprintf("%d This is not a MIME message.\n",
1407 ERROR + ILLEGAL_VALUE);
1408 } else if (CC->download_fp != NULL) {
1409 if (do_proto) cprintf(
1410 "%d You already have a download open.\n",
1411 ERROR + RESOURCE_BUSY);
1413 /* Parse the message text component */
1414 mptr = TheMessage->cm_fields['M'];
1415 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1416 /* If there's no file open by this time, the requested
1417 * section wasn't found, so print an error
1419 if (CC->download_fp == NULL) {
1420 if (do_proto) cprintf(
1421 "%d Section %s not found.\n",
1422 ERROR + FILE_NOT_FOUND,
1423 CC->download_desired_section);
1426 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1429 /* now for the user-mode message reading loops */
1430 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1432 /* Does the caller want to skip the headers? */
1433 if (headers_only == HEADERS_NONE) goto START_TEXT;
1435 /* Tell the client which format type we're using. */
1436 if ( (mode == MT_CITADEL) && (do_proto) ) {
1437 cprintf("type=%d\n", TheMessage->cm_format_type);
1440 /* nhdr=yes means that we're only displaying headers, no body */
1441 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1442 && (mode == MT_CITADEL)
1445 cprintf("nhdr=yes\n");
1448 /* begin header processing loop for Citadel message format */
1450 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1452 safestrncpy(display_name, "<unknown>", sizeof display_name);
1453 if (TheMessage->cm_fields['A']) {
1454 strcpy(buf, TheMessage->cm_fields['A']);
1455 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1456 safestrncpy(display_name, "****", sizeof display_name);
1458 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1459 safestrncpy(display_name, "anonymous", sizeof display_name);
1462 safestrncpy(display_name, buf, sizeof display_name);
1464 if ((is_room_aide())
1465 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1466 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1467 size_t tmp = strlen(display_name);
1468 snprintf(&display_name[tmp],
1469 sizeof display_name - tmp,
1474 /* Don't show Internet address for users on the
1475 * local Citadel network.
1478 if (TheMessage->cm_fields['N'] != NULL)
1479 if (strlen(TheMessage->cm_fields['N']) > 0)
1480 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1484 /* Now spew the header fields in the order we like them. */
1485 safestrncpy(allkeys, FORDER, sizeof allkeys);
1486 for (i=0; i<strlen(allkeys); ++i) {
1487 k = (int) allkeys[i];
1489 if ( (TheMessage->cm_fields[k] != NULL)
1490 && (msgkeys[k] != NULL) ) {
1492 if (do_proto) cprintf("%s=%s\n",
1496 else if ((k == 'F') && (suppress_f)) {
1499 /* Masquerade display name if needed */
1501 if (do_proto) cprintf("%s=%s\n",
1503 TheMessage->cm_fields[k]
1512 /* begin header processing loop for RFC822 transfer format */
1517 strcpy(snode, NODENAME);
1518 strcpy(lnode, HUMANNODE);
1519 if (mode == MT_RFC822) {
1520 for (i = 0; i < 256; ++i) {
1521 if (TheMessage->cm_fields[i]) {
1522 mptr = TheMessage->cm_fields[i];
1525 safestrncpy(luser, mptr, sizeof luser);
1526 safestrncpy(suser, mptr, sizeof suser);
1528 else if (i == 'Y') {
1529 cprintf("CC: %s%s", mptr, nl);
1531 else if (i == 'U') {
1532 cprintf("Subject: %s%s", mptr, nl);
1536 safestrncpy(mid, mptr, sizeof mid);
1538 safestrncpy(lnode, mptr, sizeof lnode);
1540 safestrncpy(fuser, mptr, sizeof fuser);
1541 /* else if (i == 'O')
1542 cprintf("X-Citadel-Room: %s%s",
1545 safestrncpy(snode, mptr, sizeof snode);
1547 cprintf("To: %s%s", mptr, nl);
1548 else if (i == 'T') {
1549 datestring(datestamp, sizeof datestamp,
1550 atol(mptr), DATESTRING_RFC822);
1551 cprintf("Date: %s%s", datestamp, nl);
1555 if (subject_found == 0) {
1556 cprintf("Subject: (no subject)%s", nl);
1560 for (i=0; i<strlen(suser); ++i) {
1561 suser[i] = tolower(suser[i]);
1562 if (!isalnum(suser[i])) suser[i]='_';
1565 if (mode == MT_RFC822) {
1566 if (!strcasecmp(snode, NODENAME)) {
1567 safestrncpy(snode, FQDN, sizeof snode);
1570 /* Construct a fun message id */
1571 cprintf("Message-ID: <%s", mid);
1572 if (strchr(mid, '@')==NULL) {
1573 cprintf("@%s", snode);
1577 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1578 cprintf("From: \"----\" <x@x.org>%s", nl);
1580 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1581 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1583 else if (strlen(fuser) > 0) {
1584 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1587 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1590 cprintf("Organization: %s%s", lnode, nl);
1592 /* Blank line signifying RFC822 end-of-headers */
1593 if (TheMessage->cm_format_type != FMT_RFC822) {
1598 /* end header processing loop ... at this point, we're in the text */
1600 if (headers_only == HEADERS_FAST) goto DONE;
1601 mptr = TheMessage->cm_fields['M'];
1603 /* Tell the client about the MIME parts in this message */
1604 if (TheMessage->cm_format_type == FMT_RFC822) {
1605 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1606 memset(&ma, 0, sizeof(struct ma_info));
1607 mime_parser(mptr, NULL,
1608 (do_proto ? *list_this_part : NULL),
1609 (do_proto ? *list_this_pref : NULL),
1610 (do_proto ? *list_this_suff : NULL),
1613 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1614 char *start_of_text = NULL;
1615 start_of_text = strstr(mptr, "\n\r\n");
1616 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1617 if (start_of_text == NULL) start_of_text = mptr;
1619 start_of_text = strstr(start_of_text, "\n");
1621 while (ch=*mptr, ch!=0) {
1625 else switch(headers_only) {
1627 if (mptr >= start_of_text) {
1628 if (ch == 10) cprintf("%s", nl);
1629 else cprintf("%c", ch);
1633 if (mptr < start_of_text) {
1634 if (ch == 10) cprintf("%s", nl);
1635 else cprintf("%c", ch);
1639 if (ch == 10) cprintf("%s", nl);
1640 else cprintf("%c", ch);
1649 if (headers_only == HEADERS_ONLY) {
1653 /* signify start of msg text */
1654 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1655 if (do_proto) cprintf("text\n");
1658 /* If the format type on disk is 1 (fixed-format), then we want
1659 * everything to be output completely literally ... regardless of
1660 * what message transfer format is in use.
1662 if (TheMessage->cm_format_type == FMT_FIXED) {
1663 if (mode == MT_MIME) {
1664 cprintf("Content-type: text/plain\n\n");
1667 while (ch = *mptr++, ch > 0) {
1670 if ((ch == 10) || (strlen(buf) > 250)) {
1671 cprintf("%s%s", buf, nl);
1674 buf[strlen(buf) + 1] = 0;
1675 buf[strlen(buf)] = ch;
1678 if (strlen(buf) > 0)
1679 cprintf("%s%s", buf, nl);
1682 /* If the message on disk is format 0 (Citadel vari-format), we
1683 * output using the formatter at 80 columns. This is the final output
1684 * form if the transfer format is RFC822, but if the transfer format
1685 * is Citadel proprietary, it'll still work, because the indentation
1686 * for new paragraphs is correct and the client will reformat the
1687 * message to the reader's screen width.
1689 if (TheMessage->cm_format_type == FMT_CITADEL) {
1690 if (mode == MT_MIME) {
1691 cprintf("Content-type: text/x-citadel-variformat\n\n");
1693 memfmout(80, mptr, 0, nl);
1696 /* If the message on disk is format 4 (MIME), we've gotta hand it
1697 * off to the MIME parser. The client has already been told that
1698 * this message is format 1 (fixed format), so the callback function
1699 * we use will display those parts as-is.
1701 if (TheMessage->cm_format_type == FMT_RFC822) {
1702 memset(&ma, 0, sizeof(struct ma_info));
1704 if (mode == MT_MIME) {
1705 strcpy(ma.chosen_part, "1");
1706 mime_parser(mptr, NULL,
1707 *choose_preferred, *fixed_output_pre,
1708 *fixed_output_post, (void *)&ma, 0);
1709 mime_parser(mptr, NULL,
1710 *output_preferred, NULL, NULL, (void *)&ma, 0);
1713 mime_parser(mptr, NULL,
1714 *fixed_output, *fixed_output_pre,
1715 *fixed_output_post, (void *)&ma, 0);
1720 DONE: /* now we're done */
1721 if (do_proto) cprintf("000\n");
1728 * display a message (mode 0 - Citadel proprietary)
1730 void cmd_msg0(char *cmdbuf)
1733 int headers_only = HEADERS_ALL;
1735 msgid = extract_long(cmdbuf, 0);
1736 headers_only = extract_int(cmdbuf, 1);
1738 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1744 * display a message (mode 2 - RFC822)
1746 void cmd_msg2(char *cmdbuf)
1749 int headers_only = HEADERS_ALL;
1751 msgid = extract_long(cmdbuf, 0);
1752 headers_only = extract_int(cmdbuf, 1);
1754 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1760 * display a message (mode 3 - IGnet raw format - internal programs only)
1762 void cmd_msg3(char *cmdbuf)
1765 struct CtdlMessage *msg;
1768 if (CC->internal_pgm == 0) {
1769 cprintf("%d This command is for internal programs only.\n",
1770 ERROR + HIGHER_ACCESS_REQUIRED);
1774 msgnum = extract_long(cmdbuf, 0);
1775 msg = CtdlFetchMessage(msgnum, 1);
1777 cprintf("%d Message %ld not found.\n",
1778 ERROR + MESSAGE_NOT_FOUND, msgnum);
1782 serialize_message(&smr, msg);
1783 CtdlFreeMessage(msg);
1786 cprintf("%d Unable to serialize message\n",
1787 ERROR + INTERNAL_ERROR);
1791 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1792 client_write((char *)smr.ser, (int)smr.len);
1799 * Display a message using MIME content types
1801 void cmd_msg4(char *cmdbuf)
1806 msgid = extract_long(cmdbuf, 0);
1807 extract_token(section, cmdbuf, 1, '|', sizeof section);
1808 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1814 * Client tells us its preferred message format(s)
1816 void cmd_msgp(char *cmdbuf)
1818 safestrncpy(CC->preferred_formats, cmdbuf,
1819 sizeof(CC->preferred_formats));
1820 cprintf("%d ok\n", CIT_OK);
1825 * Open a component of a MIME message as a download file
1827 void cmd_opna(char *cmdbuf)
1830 char desired_section[128];
1832 msgid = extract_long(cmdbuf, 0);
1833 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1834 safestrncpy(CC->download_desired_section, desired_section,
1835 sizeof CC->download_desired_section);
1836 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1841 * Save a message pointer into a specified room
1842 * (Returns 0 for success, nonzero for failure)
1843 * roomname may be NULL to use the current room
1845 * Note that the 'supplied_msg' field may be set to NULL, in which case
1846 * the message will be fetched from disk, by number, if we need to perform
1847 * replication checks. This adds an additional database read, so if the
1848 * caller already has the message in memory then it should be supplied.
1850 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1851 struct CtdlMessage *supplied_msg) {
1853 char hold_rm[ROOMNAMELEN];
1854 struct cdbdata *cdbfr;
1857 long highest_msg = 0L;
1858 struct CtdlMessage *msg = NULL;
1860 /*lprintf(CTDL_DEBUG,
1861 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1862 roomname, msgid, do_repl_check);*/
1864 strcpy(hold_rm, CC->room.QRname);
1866 /* Now the regular stuff */
1867 if (lgetroom(&CC->room,
1868 ((roomname != NULL) ? roomname : CC->room.QRname) )
1870 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1871 return(ERROR + ROOM_NOT_FOUND);
1874 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1875 if (cdbfr == NULL) {
1879 msglist = (long *) cdbfr->ptr;
1880 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1881 num_msgs = cdbfr->len / sizeof(long);
1885 /* Make sure the message doesn't already exist in this room. It
1886 * is absolutely taboo to have more than one reference to the same
1887 * message in a room.
1889 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1890 if (msglist[i] == msgid) {
1891 lputroom(&CC->room); /* unlock the room */
1892 getroom(&CC->room, hold_rm);
1894 return(ERROR + ALREADY_EXISTS);
1898 /* Now add the new message */
1900 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1902 if (msglist == NULL) {
1903 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1905 msglist[num_msgs - 1] = msgid;
1907 /* Sort the message list, so all the msgid's are in order */
1908 num_msgs = sort_msglist(msglist, num_msgs);
1910 /* Determine the highest message number */
1911 highest_msg = msglist[num_msgs - 1];
1913 /* Write it back to disk. */
1914 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1915 msglist, (int)(num_msgs * sizeof(long)));
1917 /* Free up the memory we used. */
1920 /* Update the highest-message pointer and unlock the room. */
1921 CC->room.QRhighest = highest_msg;
1922 lputroom(&CC->room);
1924 /* Perform replication checks if necessary */
1925 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1926 if (supplied_msg != NULL) {
1930 msg = CtdlFetchMessage(msgid, 0);
1934 ReplicationChecks(msg);
1939 /* If the message has an Exclusive ID, index that... */
1941 if (msg->cm_fields['E'] != NULL) {
1942 index_message_by_euid(msg->cm_fields['E'],
1947 /* Free up the memory we may have allocated */
1948 if ( (msg != NULL) && (msg != supplied_msg) ) {
1949 CtdlFreeMessage(msg);
1952 /* Go back to the room we were in before we wandered here... */
1953 getroom(&CC->room, hold_rm);
1955 /* Bump the reference count for this message. */
1956 AdjRefCount(msgid, +1);
1958 /* Return success. */
1965 * Message base operation to save a new message to the message store
1966 * (returns new message number)
1968 * This is the back end for CtdlSubmitMsg() and should not be directly
1969 * called by server-side modules.
1972 long send_message(struct CtdlMessage *msg) {
1980 /* Get a new message number */
1981 newmsgid = get_new_message_number();
1982 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1984 /* Generate an ID if we don't have one already */
1985 if (msg->cm_fields['I']==NULL) {
1986 msg->cm_fields['I'] = strdup(msgidbuf);
1989 /* If the message is big, set its body aside for storage elsewhere */
1990 if (msg->cm_fields['M'] != NULL) {
1991 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1993 holdM = msg->cm_fields['M'];
1994 msg->cm_fields['M'] = NULL;
1998 /* Serialize our data structure for storage in the database */
1999 serialize_message(&smr, msg);
2002 msg->cm_fields['M'] = holdM;
2006 cprintf("%d Unable to serialize message\n",
2007 ERROR + INTERNAL_ERROR);
2011 /* Write our little bundle of joy into the message base */
2012 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2013 smr.ser, smr.len) < 0) {
2014 lprintf(CTDL_ERR, "Can't store message\n");
2018 cdb_store(CDB_BIGMSGS,
2028 /* Free the memory we used for the serialized message */
2031 /* Return the *local* message ID to the caller
2032 * (even if we're storing an incoming network message)
2040 * Serialize a struct CtdlMessage into the format used on disk and network.
2042 * This function loads up a "struct ser_ret" (defined in server.h) which
2043 * contains the length of the serialized message and a pointer to the
2044 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2046 void serialize_message(struct ser_ret *ret, /* return values */
2047 struct CtdlMessage *msg) /* unserialized msg */
2051 static char *forder = FORDER;
2053 if (is_valid_message(msg) == 0) return; /* self check */
2056 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2057 ret->len = ret->len +
2058 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2060 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2061 ret->ser = malloc(ret->len);
2062 if (ret->ser == NULL) {
2068 ret->ser[1] = msg->cm_anon_type;
2069 ret->ser[2] = msg->cm_format_type;
2072 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2073 ret->ser[wlen++] = (char)forder[i];
2074 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2075 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2077 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2078 (long)ret->len, (long)wlen);
2086 * Check to see if any messages already exist in the current room which
2087 * carry the same Exclusive ID as this one. If any are found, delete them.
2089 void ReplicationChecks(struct CtdlMessage *msg) {
2090 long old_msgnum = (-1L);
2092 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2094 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2097 /* No exclusive id? Don't do anything. */
2098 if (msg == NULL) return;
2099 if (msg->cm_fields['E'] == NULL) return;
2100 if (strlen(msg->cm_fields['E']) == 0) return;
2101 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2102 msg->cm_fields['E'], CC->room.QRname);*/
2104 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2105 if (old_msgnum > 0L) {
2106 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2107 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2114 * Save a message to disk and submit it into the delivery system.
2116 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2117 struct recptypes *recps, /* recipients (if mail) */
2118 char *force /* force a particular room? */
2120 char submit_filename[128];
2121 char generated_timestamp[32];
2122 char hold_rm[ROOMNAMELEN];
2123 char actual_rm[ROOMNAMELEN];
2124 char force_room[ROOMNAMELEN];
2125 char content_type[SIZ]; /* We have to learn this */
2126 char recipient[SIZ];
2129 struct ctdluser userbuf;
2131 struct MetaData smi;
2132 FILE *network_fp = NULL;
2133 static int seqnum = 1;
2134 struct CtdlMessage *imsg = NULL;
2137 char *hold_R, *hold_D;
2138 char *collected_addresses = NULL;
2139 struct addresses_to_be_filed *aptr = NULL;
2141 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2142 if (is_valid_message(msg) == 0) return(-1); /* self check */
2144 /* If this message has no timestamp, we take the liberty of
2145 * giving it one, right now.
2147 if (msg->cm_fields['T'] == NULL) {
2148 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2149 msg->cm_fields['T'] = strdup(generated_timestamp);
2152 /* If this message has no path, we generate one.
2154 if (msg->cm_fields['P'] == NULL) {
2155 if (msg->cm_fields['A'] != NULL) {
2156 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2157 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2158 if (isspace(msg->cm_fields['P'][a])) {
2159 msg->cm_fields['P'][a] = ' ';
2164 msg->cm_fields['P'] = strdup("unknown");
2168 if (force == NULL) {
2169 strcpy(force_room, "");
2172 strcpy(force_room, force);
2175 /* Learn about what's inside, because it's what's inside that counts */
2176 if (msg->cm_fields['M'] == NULL) {
2177 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2181 switch (msg->cm_format_type) {
2183 strcpy(content_type, "text/x-citadel-variformat");
2186 strcpy(content_type, "text/plain");
2189 strcpy(content_type, "text/plain");
2190 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2192 safestrncpy(content_type, &mptr[14],
2193 sizeof content_type);
2194 for (a = 0; a < strlen(content_type); ++a) {
2195 if ((content_type[a] == ';')
2196 || (content_type[a] == ' ')
2197 || (content_type[a] == 13)
2198 || (content_type[a] == 10)) {
2199 content_type[a] = 0;
2205 /* Goto the correct room */
2206 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2207 strcpy(hold_rm, CC->room.QRname);
2208 strcpy(actual_rm, CC->room.QRname);
2209 if (recps != NULL) {
2210 strcpy(actual_rm, SENTITEMS);
2213 /* If the user is a twit, move to the twit room for posting */
2215 if (CC->user.axlevel == 2) {
2216 strcpy(hold_rm, actual_rm);
2217 strcpy(actual_rm, config.c_twitroom);
2218 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2222 /* ...or if this message is destined for Aide> then go there. */
2223 if (strlen(force_room) > 0) {
2224 strcpy(actual_rm, force_room);
2227 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2228 if (strcasecmp(actual_rm, CC->room.QRname)) {
2229 /* getroom(&CC->room, actual_rm); */
2230 usergoto(actual_rm, 0, 1, NULL, NULL);
2234 * If this message has no O (room) field, generate one.
2236 if (msg->cm_fields['O'] == NULL) {
2237 msg->cm_fields['O'] = strdup(CC->room.QRname);
2240 /* Perform "before save" hooks (aborting if any return nonzero) */
2241 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2242 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2245 * If this message has an Exclusive ID, and the room is replication
2246 * checking enabled, then do replication checks.
2248 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2249 ReplicationChecks(msg);
2252 /* Save it to disk */
2253 lprintf(CTDL_DEBUG, "Saving to disk\n");
2254 newmsgid = send_message(msg);
2255 if (newmsgid <= 0L) return(-5);
2257 /* Write a supplemental message info record. This doesn't have to
2258 * be a critical section because nobody else knows about this message
2261 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2262 memset(&smi, 0, sizeof(struct MetaData));
2263 smi.meta_msgnum = newmsgid;
2264 smi.meta_refcount = 0;
2265 safestrncpy(smi.meta_content_type, content_type,
2266 sizeof smi.meta_content_type);
2268 /* As part of the new metadata record, measure how
2269 * big this message will be when displayed as RFC822.
2270 * Both POP and IMAP use this, and it's best to just take the hit now
2271 * instead of having to potentially measure thousands of messages when
2272 * a mailbox is opened later.
2275 if (CC->redirect_buffer != NULL) {
2276 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2279 CC->redirect_buffer = malloc(SIZ);
2280 CC->redirect_len = 0;
2281 CC->redirect_alloc = SIZ;
2282 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2283 smi.meta_rfc822_length = CC->redirect_len;
2284 free(CC->redirect_buffer);
2285 CC->redirect_buffer = NULL;
2286 CC->redirect_len = 0;
2287 CC->redirect_alloc = 0;
2291 /* Now figure out where to store the pointers */
2292 lprintf(CTDL_DEBUG, "Storing pointers\n");
2294 /* If this is being done by the networker delivering a private
2295 * message, we want to BYPASS saving the sender's copy (because there
2296 * is no local sender; it would otherwise go to the Trashcan).
2298 if ((!CC->internal_pgm) || (recps == NULL)) {
2299 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2300 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2301 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2305 /* For internet mail, drop a copy in the outbound queue room */
2307 if (recps->num_internet > 0) {
2308 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2311 /* If other rooms are specified, drop them there too. */
2313 if (recps->num_room > 0)
2314 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2315 extract_token(recipient, recps->recp_room, i,
2316 '|', sizeof recipient);
2317 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2318 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2321 /* Bump this user's messages posted counter. */
2322 lprintf(CTDL_DEBUG, "Updating user\n");
2323 lgetuser(&CC->user, CC->curr_user);
2324 CC->user.posted = CC->user.posted + 1;
2325 lputuser(&CC->user);
2327 /* If this is private, local mail, make a copy in the
2328 * recipient's mailbox and bump the reference count.
2331 if (recps->num_local > 0)
2332 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2333 extract_token(recipient, recps->recp_local, i,
2334 '|', sizeof recipient);
2335 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2337 if (getuser(&userbuf, recipient) == 0) {
2338 MailboxName(actual_rm, sizeof actual_rm,
2339 &userbuf, MAILROOM);
2340 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2341 BumpNewMailCounter(userbuf.usernum);
2344 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2345 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2350 /* Perform "after save" hooks */
2351 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2352 PerformMessageHooks(msg, EVT_AFTERSAVE);
2354 /* For IGnet mail, we have to save a new copy into the spooler for
2355 * each recipient, with the R and D fields set to the recipient and
2356 * destination-node. This has two ugly side effects: all other
2357 * recipients end up being unlisted in this recipient's copy of the
2358 * message, and it has to deliver multiple messages to the same
2359 * node. We'll revisit this again in a year or so when everyone has
2360 * a network spool receiver that can handle the new style messages.
2363 if (recps->num_ignet > 0)
2364 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2365 extract_token(recipient, recps->recp_ignet, i,
2366 '|', sizeof recipient);
2368 hold_R = msg->cm_fields['R'];
2369 hold_D = msg->cm_fields['D'];
2370 msg->cm_fields['R'] = malloc(SIZ);
2371 msg->cm_fields['D'] = malloc(128);
2372 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2373 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2375 serialize_message(&smr, msg);
2377 snprintf(submit_filename, sizeof submit_filename,
2378 #ifndef HAVE_SPOOL_DIR
2383 "/network/spoolin/netmail.%04lx.%04x.%04x",
2384 (long) getpid(), CC->cs_pid, ++seqnum);
2385 network_fp = fopen(submit_filename, "wb+");
2386 if (network_fp != NULL) {
2387 fwrite(smr.ser, smr.len, 1, network_fp);
2393 free(msg->cm_fields['R']);
2394 free(msg->cm_fields['D']);
2395 msg->cm_fields['R'] = hold_R;
2396 msg->cm_fields['D'] = hold_D;
2399 /* Go back to the room we started from */
2400 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2401 if (strcasecmp(hold_rm, CC->room.QRname))
2402 /* getroom(&CC->room, hold_rm); */
2403 usergoto(hold_rm, 0, 1, NULL, NULL);
2405 /* For internet mail, generate delivery instructions.
2406 * Yes, this is recursive. Deal with it. Infinite recursion does
2407 * not happen because the delivery instructions message does not
2408 * contain a recipient.
2411 if (recps->num_internet > 0) {
2412 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2413 instr = malloc(SIZ * 2);
2414 snprintf(instr, SIZ * 2,
2415 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2417 SPOOLMIME, newmsgid, (long)time(NULL),
2418 msg->cm_fields['A'], msg->cm_fields['N']
2421 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2422 size_t tmp = strlen(instr);
2423 extract_token(recipient, recps->recp_internet,
2424 i, '|', sizeof recipient);
2425 snprintf(&instr[tmp], SIZ * 2 - tmp,
2426 "remote|%s|0||\n", recipient);
2429 imsg = malloc(sizeof(struct CtdlMessage));
2430 memset(imsg, 0, sizeof(struct CtdlMessage));
2431 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2432 imsg->cm_anon_type = MES_NORMAL;
2433 imsg->cm_format_type = FMT_RFC822;
2434 imsg->cm_fields['A'] = strdup("Citadel");
2435 imsg->cm_fields['M'] = instr;
2436 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2437 CtdlFreeMessage(imsg);
2441 * Any addresses to harvest for someone's address book?
2443 if ( (CC->logged_in) && (recps != NULL) ) {
2444 collected_addresses = harvest_collected_addresses(msg);
2447 if (collected_addresses != NULL) {
2448 begin_critical_section(S_ATBF);
2449 aptr = (struct addresses_to_be_filed *)
2450 malloc(sizeof(struct addresses_to_be_filed));
2452 MailboxName(actual_rm, sizeof actual_rm,
2453 &CC->user, USERCONTACTSROOM);
2454 aptr->roomname = strdup(actual_rm);
2455 aptr->collected_addresses = collected_addresses;
2457 end_critical_section(S_ATBF);
2470 * Convenience function for generating small administrative messages.
2472 void quickie_message(char *from, char *to, char *room, char *text,
2473 int format_type, char *subject)
2475 struct CtdlMessage *msg;
2476 struct recptypes *recp = NULL;
2478 msg = malloc(sizeof(struct CtdlMessage));
2479 memset(msg, 0, sizeof(struct CtdlMessage));
2480 msg->cm_magic = CTDLMESSAGE_MAGIC;
2481 msg->cm_anon_type = MES_NORMAL;
2482 msg->cm_format_type = format_type;
2483 msg->cm_fields['A'] = strdup(from);
2484 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2485 msg->cm_fields['N'] = strdup(NODENAME);
2487 msg->cm_fields['R'] = strdup(to);
2488 recp = validate_recipients(to);
2490 if (subject != NULL) {
2491 msg->cm_fields['U'] = strdup(subject);
2493 msg->cm_fields['M'] = strdup(text);
2495 CtdlSubmitMsg(msg, recp, room);
2496 CtdlFreeMessage(msg);
2497 if (recp != NULL) free(recp);
2503 * Back end function used by CtdlMakeMessage() and similar functions
2505 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2506 size_t maxlen, /* maximum message length */
2507 char *exist, /* if non-null, append to it;
2508 exist is ALWAYS freed */
2509 int crlf /* CRLF newlines instead of LF */
2513 size_t message_len = 0;
2514 size_t buffer_len = 0;
2520 if (exist == NULL) {
2527 message_len = strlen(exist);
2528 buffer_len = message_len + 4096;
2529 m = realloc(exist, buffer_len);
2536 /* flush the input if we have nowhere to store it */
2541 /* read in the lines of message text one by one */
2543 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2544 if (!strcmp(buf, terminator)) finished = 1;
2546 strcat(buf, "\r\n");
2552 if ( (!flushing) && (!finished) ) {
2553 /* Measure the line */
2554 linelen = strlen(buf);
2556 /* augment the buffer if we have to */
2557 if ((message_len + linelen) >= buffer_len) {
2558 ptr = realloc(m, (buffer_len * 2) );
2559 if (ptr == NULL) { /* flush if can't allocate */
2562 buffer_len = (buffer_len * 2);
2564 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2568 /* Add the new line to the buffer. NOTE: this loop must avoid
2569 * using functions like strcat() and strlen() because they
2570 * traverse the entire buffer upon every call, and doing that
2571 * for a multi-megabyte message slows it down beyond usability.
2573 strcpy(&m[message_len], buf);
2574 message_len += linelen;
2577 /* if we've hit the max msg length, flush the rest */
2578 if (message_len >= maxlen) flushing = 1;
2580 } while (!finished);
2588 * Build a binary message to be saved on disk.
2589 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2590 * will become part of the message. This means you are no longer
2591 * responsible for managing that memory -- it will be freed along with
2592 * the rest of the fields when CtdlFreeMessage() is called.)
2595 struct CtdlMessage *CtdlMakeMessage(
2596 struct ctdluser *author, /* author's user structure */
2597 char *recipient, /* NULL if it's not mail */
2598 char *recp_cc, /* NULL if it's not mail */
2599 char *room, /* room where it's going */
2600 int type, /* see MES_ types in header file */
2601 int format_type, /* variformat, plain text, MIME... */
2602 char *fake_name, /* who we're masquerading as */
2603 char *subject, /* Subject (optional) */
2604 char *preformatted_text /* ...or NULL to read text from client */
2606 char dest_node[SIZ];
2608 struct CtdlMessage *msg;
2610 msg = malloc(sizeof(struct CtdlMessage));
2611 memset(msg, 0, sizeof(struct CtdlMessage));
2612 msg->cm_magic = CTDLMESSAGE_MAGIC;
2613 msg->cm_anon_type = type;
2614 msg->cm_format_type = format_type;
2616 /* Don't confuse the poor folks if it's not routed mail. */
2617 strcpy(dest_node, "");
2622 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2623 msg->cm_fields['P'] = strdup(buf);
2625 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2626 msg->cm_fields['T'] = strdup(buf);
2628 if (fake_name[0]) /* author */
2629 msg->cm_fields['A'] = strdup(fake_name);
2631 msg->cm_fields['A'] = strdup(author->fullname);
2633 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2634 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2637 msg->cm_fields['O'] = strdup(CC->room.QRname);
2640 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2641 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2643 if (recipient[0] != 0) {
2644 msg->cm_fields['R'] = strdup(recipient);
2646 if (recp_cc[0] != 0) {
2647 msg->cm_fields['Y'] = strdup(recp_cc);
2649 if (dest_node[0] != 0) {
2650 msg->cm_fields['D'] = strdup(dest_node);
2653 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2654 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2657 if (subject != NULL) {
2659 if (strlen(subject) > 0) {
2660 msg->cm_fields['U'] = strdup(subject);
2664 if (preformatted_text != NULL) {
2665 msg->cm_fields['M'] = preformatted_text;
2668 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2669 config.c_maxmsglen, NULL, 0);
2677 * Check to see whether we have permission to post a message in the current
2678 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2679 * returns 0 on success.
2681 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2683 if (!(CC->logged_in)) {
2684 snprintf(errmsgbuf, n, "Not logged in.");
2685 return (ERROR + NOT_LOGGED_IN);
2688 if ((CC->user.axlevel < 2)
2689 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2690 snprintf(errmsgbuf, n, "Need to be validated to enter "
2691 "(except in %s> to sysop)", MAILROOM);
2692 return (ERROR + HIGHER_ACCESS_REQUIRED);
2695 if ((CC->user.axlevel < 4)
2696 && (CC->room.QRflags & QR_NETWORK)) {
2697 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2698 return (ERROR + HIGHER_ACCESS_REQUIRED);
2701 if ((CC->user.axlevel < 6)
2702 && (CC->room.QRflags & QR_READONLY)) {
2703 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2704 return (ERROR + HIGHER_ACCESS_REQUIRED);
2707 strcpy(errmsgbuf, "Ok");
2713 * Check to see if the specified user has Internet mail permission
2714 * (returns nonzero if permission is granted)
2716 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2718 /* Do not allow twits to send Internet mail */
2719 if (who->axlevel <= 2) return(0);
2721 /* Globally enabled? */
2722 if (config.c_restrict == 0) return(1);
2724 /* User flagged ok? */
2725 if (who->flags & US_INTERNET) return(2);
2727 /* Aide level access? */
2728 if (who->axlevel >= 6) return(3);
2730 /* No mail for you! */
2736 * Validate recipients, count delivery types and errors, and handle aliasing
2737 * FIXME check for dupes!!!!!
2738 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2739 * or the number of addresses found invalid.
2741 struct recptypes *validate_recipients(char *supplied_recipients) {
2742 struct recptypes *ret;
2743 char recipients[SIZ];
2744 char this_recp[256];
2745 char this_recp_cooked[256];
2751 struct ctdluser tempUS;
2752 struct ctdlroom tempQR;
2756 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2757 if (ret == NULL) return(NULL);
2758 memset(ret, 0, sizeof(struct recptypes));
2761 ret->num_internet = 0;
2766 if (supplied_recipients == NULL) {
2767 strcpy(recipients, "");
2770 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2773 /* Change all valid separator characters to commas */
2774 for (i=0; i<strlen(recipients); ++i) {
2775 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2776 recipients[i] = ',';
2780 /* Now start extracting recipients... */
2782 while (strlen(recipients) > 0) {
2784 for (i=0; i<=strlen(recipients); ++i) {
2785 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2786 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2787 safestrncpy(this_recp, recipients, i+1);
2789 if (recipients[i] == ',') {
2790 strcpy(recipients, &recipients[i+1]);
2793 strcpy(recipients, "");
2800 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2802 mailtype = alias(this_recp);
2803 mailtype = alias(this_recp);
2804 mailtype = alias(this_recp);
2805 for (j=0; j<=strlen(this_recp); ++j) {
2806 if (this_recp[j]=='_') {
2807 this_recp_cooked[j] = ' ';
2810 this_recp_cooked[j] = this_recp[j];
2816 if (!strcasecmp(this_recp, "sysop")) {
2818 strcpy(this_recp, config.c_aideroom);
2819 if (strlen(ret->recp_room) > 0) {
2820 strcat(ret->recp_room, "|");
2822 strcat(ret->recp_room, this_recp);
2824 else if (getuser(&tempUS, this_recp) == 0) {
2826 strcpy(this_recp, tempUS.fullname);
2827 if (strlen(ret->recp_local) > 0) {
2828 strcat(ret->recp_local, "|");
2830 strcat(ret->recp_local, this_recp);
2832 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2834 strcpy(this_recp, tempUS.fullname);
2835 if (strlen(ret->recp_local) > 0) {
2836 strcat(ret->recp_local, "|");
2838 strcat(ret->recp_local, this_recp);
2840 else if ( (!strncasecmp(this_recp, "room_", 5))
2841 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2843 if (strlen(ret->recp_room) > 0) {
2844 strcat(ret->recp_room, "|");
2846 strcat(ret->recp_room, &this_recp_cooked[5]);
2854 /* Yes, you're reading this correctly: if the target
2855 * domain points back to the local system or an attached
2856 * Citadel directory, the address is invalid. That's
2857 * because if the address were valid, we would have
2858 * already translated it to a local address by now.
2860 if (IsDirectory(this_recp)) {
2865 ++ret->num_internet;
2866 if (strlen(ret->recp_internet) > 0) {
2867 strcat(ret->recp_internet, "|");
2869 strcat(ret->recp_internet, this_recp);
2874 if (strlen(ret->recp_ignet) > 0) {
2875 strcat(ret->recp_ignet, "|");
2877 strcat(ret->recp_ignet, this_recp);
2885 if (strlen(ret->errormsg) == 0) {
2886 snprintf(append, sizeof append,
2887 "Invalid recipient: %s",
2891 snprintf(append, sizeof append,
2894 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2895 strcat(ret->errormsg, append);
2899 if (strlen(ret->display_recp) == 0) {
2900 strcpy(append, this_recp);
2903 snprintf(append, sizeof append, ", %s",
2906 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2907 strcat(ret->display_recp, append);
2912 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2913 ret->num_room + ret->num_error) == 0) {
2914 ret->num_error = (-1);
2915 strcpy(ret->errormsg, "No recipients specified.");
2918 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2919 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2920 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2921 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2922 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2923 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2931 * message entry - mode 0 (normal)
2933 void cmd_ent0(char *entargs)
2939 char masquerade_as[SIZ];
2941 int format_type = 0;
2942 char newusername[SIZ];
2943 struct CtdlMessage *msg;
2947 struct recptypes *valid = NULL;
2948 struct recptypes *valid_to = NULL;
2949 struct recptypes *valid_cc = NULL;
2950 struct recptypes *valid_bcc = NULL;
2957 post = extract_int(entargs, 0);
2958 extract_token(recp, entargs, 1, '|', sizeof recp);
2959 anon_flag = extract_int(entargs, 2);
2960 format_type = extract_int(entargs, 3);
2961 extract_token(subject, entargs, 4, '|', sizeof subject);
2962 do_confirm = extract_int(entargs, 6);
2963 extract_token(cc, entargs, 7, '|', sizeof cc);
2964 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2966 /* first check to make sure the request is valid. */
2968 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2970 cprintf("%d %s\n", err, errmsg);
2974 /* Check some other permission type things. */
2977 if (CC->user.axlevel < 6) {
2978 cprintf("%d You don't have permission to masquerade.\n",
2979 ERROR + HIGHER_ACCESS_REQUIRED);
2982 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2983 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2984 safestrncpy(CC->fake_postname, newusername,
2985 sizeof(CC->fake_postname) );
2986 cprintf("%d ok\n", CIT_OK);
2989 CC->cs_flags |= CS_POSTING;
2991 /* In the Mail> room we have to behave a little differently --
2992 * make sure the user has specified at least one recipient. Then
2993 * validate the recipient(s).
2995 if ( (CC->room.QRflags & QR_MAILBOX)
2996 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2998 if (CC->user.axlevel < 2) {
2999 strcpy(recp, "sysop");
3004 valid_to = validate_recipients(recp);
3005 if (valid_to->num_error > 0) {
3006 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3011 valid_cc = validate_recipients(cc);
3012 if (valid_cc->num_error > 0) {
3013 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3019 valid_bcc = validate_recipients(bcc);
3020 if (valid_bcc->num_error > 0) {
3021 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3028 /* Recipient required, but none were specified */
3029 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3033 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3037 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3038 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3039 cprintf("%d You do not have permission "
3040 "to send Internet mail.\n",
3041 ERROR + HIGHER_ACCESS_REQUIRED);
3049 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)
3050 && (CC->user.axlevel < 4) ) {
3051 cprintf("%d Higher access required for network mail.\n",
3052 ERROR + HIGHER_ACCESS_REQUIRED);
3059 if ((RESTRICT_INTERNET == 1)
3060 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3061 && ((CC->user.flags & US_INTERNET) == 0)
3062 && (!CC->internal_pgm)) {
3063 cprintf("%d You don't have access to Internet mail.\n",
3064 ERROR + HIGHER_ACCESS_REQUIRED);
3073 /* Is this a room which has anonymous-only or anonymous-option? */
3074 anonymous = MES_NORMAL;
3075 if (CC->room.QRflags & QR_ANONONLY) {
3076 anonymous = MES_ANONONLY;
3078 if (CC->room.QRflags & QR_ANONOPT) {
3079 if (anon_flag == 1) { /* only if the user requested it */
3080 anonymous = MES_ANONOPT;
3084 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3088 /* If we're only checking the validity of the request, return
3089 * success without creating the message.
3092 cprintf("%d %s\n", CIT_OK,
3093 ((valid_to != NULL) ? valid_to->display_recp : "") );
3100 /* We don't need these anymore because we'll do it differently below */
3105 /* Handle author masquerading */
3106 if (CC->fake_postname[0]) {
3107 strcpy(masquerade_as, CC->fake_postname);
3109 else if (CC->fake_username[0]) {
3110 strcpy(masquerade_as, CC->fake_username);
3113 strcpy(masquerade_as, "");
3116 /* Read in the message from the client. */
3118 cprintf("%d send message\n", START_CHAT_MODE);
3120 cprintf("%d send message\n", SEND_LISTING);
3123 msg = CtdlMakeMessage(&CC->user, recp, cc,
3124 CC->room.QRname, anonymous, format_type,
3125 masquerade_as, subject, NULL);
3127 /* Put together one big recipients struct containing to/cc/bcc all in
3128 * one. This is for the envelope.
3130 char *all_recps = malloc(SIZ * 3);
3131 strcpy(all_recps, recp);
3132 if (strlen(cc) > 0) {
3133 if (strlen(all_recps) > 0) {
3134 strcat(all_recps, ",");
3136 strcat(all_recps, cc);
3138 if (strlen(bcc) > 0) {
3139 if (strlen(all_recps) > 0) {
3140 strcat(all_recps, ",");
3142 strcat(all_recps, bcc);
3144 if (strlen(all_recps) > 0) {
3145 valid = validate_recipients(all_recps);
3153 msgnum = CtdlSubmitMsg(msg, valid, "");
3156 cprintf("%ld\n", msgnum);
3158 cprintf("Message accepted.\n");
3161 cprintf("Internal error.\n");
3163 if (msg->cm_fields['E'] != NULL) {
3164 cprintf("%s\n", msg->cm_fields['E']);
3171 CtdlFreeMessage(msg);
3173 CC->fake_postname[0] = '\0';
3174 if (valid != NULL) {
3183 * API function to delete messages which match a set of criteria
3184 * (returns the actual number of messages deleted)
3186 int CtdlDeleteMessages(char *room_name, /* which room */
3187 long dmsgnum, /* or "0" for any */
3188 char *content_type, /* or "" for any */
3189 int deferred /* let TDAP sweep it later */
3193 struct ctdlroom qrbuf;
3194 struct cdbdata *cdbfr;
3195 long *msglist = NULL;
3196 long *dellist = NULL;
3199 int num_deleted = 0;
3201 struct MetaData smi;
3203 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3204 room_name, dmsgnum, content_type, deferred);
3206 /* get room record, obtaining a lock... */
3207 if (lgetroom(&qrbuf, room_name) != 0) {
3208 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3210 return (0); /* room not found */
3212 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3214 if (cdbfr != NULL) {
3215 dellist = malloc(cdbfr->len);
3216 msglist = (long *) cdbfr->ptr;
3217 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3218 num_msgs = cdbfr->len / sizeof(long);
3222 for (i = 0; i < num_msgs; ++i) {
3225 /* Set/clear a bit for each criterion */
3227 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3228 delete_this |= 0x01;
3230 if (strlen(content_type) == 0) {
3231 delete_this |= 0x02;
3233 GetMetaData(&smi, msglist[i]);
3234 if (!strcasecmp(smi.meta_content_type,
3236 delete_this |= 0x02;
3240 /* Delete message only if all bits are set */
3241 if (delete_this == 0x03) {
3242 dellist[num_deleted++] = msglist[i];
3247 num_msgs = sort_msglist(msglist, num_msgs);
3248 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3249 msglist, (int)(num_msgs * sizeof(long)));
3251 qrbuf.QRhighest = msglist[num_msgs - 1];
3256 * If the delete operation is "deferred" (and technically, any delete
3257 * operation not performed by THE DREADED AUTO-PURGER ought to be
3258 * a deferred delete) then we save a pointer to the message in the
3259 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3260 * at least 1, which will save the user from having to synchronously
3261 * wait for various disk-intensive operations to complete.
3263 if ( (deferred) && (num_deleted) ) {
3264 for (i=0; i<num_deleted; ++i) {
3265 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3269 /* Go through the messages we pulled out of the index, and decrement
3270 * their reference counts by 1. If this is the only room the message
3271 * was in, the reference count will reach zero and the message will
3272 * automatically be deleted from the database. We do this in a
3273 * separate pass because there might be plug-in hooks getting called,
3274 * and we don't want that happening during an S_ROOMS critical
3277 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3278 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3279 AdjRefCount(dellist[i], -1);
3282 /* Now free the memory we used, and go away. */
3283 if (msglist != NULL) free(msglist);
3284 if (dellist != NULL) free(dellist);
3285 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3286 return (num_deleted);
3292 * Check whether the current user has permission to delete messages from
3293 * the current room (returns 1 for yes, 0 for no)
3295 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3296 getuser(&CC->user, CC->curr_user);
3297 if ((CC->user.axlevel < 6)
3298 && (CC->user.usernum != CC->room.QRroomaide)
3299 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3300 && (!(CC->internal_pgm))) {
3309 * Delete message from current room
3311 void cmd_dele(char *delstr)
3316 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3317 cprintf("%d Higher access required.\n",
3318 ERROR + HIGHER_ACCESS_REQUIRED);
3321 delnum = extract_long(delstr, 0);
3323 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3326 cprintf("%d %d message%s deleted.\n", CIT_OK,
3327 num_deleted, ((num_deleted != 1) ? "s" : ""));
3329 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3335 * Back end API function for moves and deletes
3337 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3340 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3341 if (err != 0) return(err);
3349 * move or copy a message to another room
3351 void cmd_move(char *args)
3354 char targ[ROOMNAMELEN];
3355 struct ctdlroom qtemp;
3361 num = extract_long(args, 0);
3362 extract_token(targ, args, 1, '|', sizeof targ);
3363 convert_room_name_macros(targ, sizeof targ);
3364 targ[ROOMNAMELEN - 1] = 0;
3365 is_copy = extract_int(args, 2);
3367 if (getroom(&qtemp, targ) != 0) {
3368 cprintf("%d '%s' does not exist.\n",
3369 ERROR + ROOM_NOT_FOUND, targ);
3373 getuser(&CC->user, CC->curr_user);
3374 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3376 /* Check for permission to perform this operation.
3377 * Remember: "CC->room" is source, "qtemp" is target.
3381 /* Aides can move/copy */
3382 if (CC->user.axlevel >= 6) permit = 1;
3384 /* Room aides can move/copy */
3385 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3387 /* Permit move/copy from personal rooms */
3388 if ((CC->room.QRflags & QR_MAILBOX)
3389 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3391 /* Permit only copy from public to personal room */
3393 && (!(CC->room.QRflags & QR_MAILBOX))
3394 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3396 /* User must have access to target room */
3397 if (!(ra & UA_KNOWN)) permit = 0;
3400 cprintf("%d Higher access required.\n",
3401 ERROR + HIGHER_ACCESS_REQUIRED);
3405 err = CtdlCopyMsgToRoom(num, targ);
3407 cprintf("%d Cannot store message in %s: error %d\n",
3412 /* Now delete the message from the source room,
3413 * if this is a 'move' rather than a 'copy' operation.
3416 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3419 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3425 * GetMetaData() - Get the supplementary record for a message
3427 void GetMetaData(struct MetaData *smibuf, long msgnum)
3430 struct cdbdata *cdbsmi;
3433 memset(smibuf, 0, sizeof(struct MetaData));
3434 smibuf->meta_msgnum = msgnum;
3435 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3437 /* Use the negative of the message number for its supp record index */
3438 TheIndex = (0L - msgnum);
3440 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3441 if (cdbsmi == NULL) {
3442 return; /* record not found; go with defaults */
3444 memcpy(smibuf, cdbsmi->ptr,
3445 ((cdbsmi->len > sizeof(struct MetaData)) ?
3446 sizeof(struct MetaData) : cdbsmi->len));
3453 * PutMetaData() - (re)write supplementary record for a message
3455 void PutMetaData(struct MetaData *smibuf)
3459 /* Use the negative of the message number for the metadata db index */
3460 TheIndex = (0L - smibuf->meta_msgnum);
3462 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3463 smibuf->meta_msgnum, smibuf->meta_refcount);
3465 cdb_store(CDB_MSGMAIN,
3466 &TheIndex, (int)sizeof(long),
3467 smibuf, (int)sizeof(struct MetaData));
3472 * AdjRefCount - change the reference count for a message;
3473 * delete the message if it reaches zero
3475 void AdjRefCount(long msgnum, int incr)
3478 struct MetaData smi;
3481 /* This is a *tight* critical section; please keep it that way, as
3482 * it may get called while nested in other critical sections.
3483 * Complicating this any further will surely cause deadlock!
3485 begin_critical_section(S_SUPPMSGMAIN);
3486 GetMetaData(&smi, msgnum);
3487 smi.meta_refcount += incr;
3489 end_critical_section(S_SUPPMSGMAIN);
3490 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3491 msgnum, incr, smi.meta_refcount);
3493 /* If the reference count is now zero, delete the message
3494 * (and its supplementary record as well).
3496 if (smi.meta_refcount == 0) {
3497 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3499 /* Remove from fulltext index */
3500 if (config.c_enable_fulltext) {
3501 ft_index_message(msgnum, 0);
3504 /* Remove from message base */
3506 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3507 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3509 /* Remove metadata record */
3510 delnum = (0L - msgnum);
3511 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3516 * Write a generic object to this room
3518 * Note: this could be much more efficient. Right now we use two temporary
3519 * files, and still pull the message into memory as with all others.
3521 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3522 char *content_type, /* MIME type of this object */
3523 char *tempfilename, /* Where to fetch it from */
3524 struct ctdluser *is_mailbox, /* Mailbox room? */
3525 int is_binary, /* Is encoding necessary? */
3526 int is_unique, /* Del others of this type? */
3527 unsigned int flags /* Internal save flags */
3532 struct ctdlroom qrbuf;
3533 char roomname[ROOMNAMELEN];
3534 struct CtdlMessage *msg;
3536 char *raw_message = NULL;
3537 char *encoded_message = NULL;
3538 off_t raw_length = 0;
3540 if (is_mailbox != NULL) {
3541 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3544 safestrncpy(roomname, req_room, sizeof(roomname));
3547 fp = fopen(tempfilename, "rb");
3549 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3550 tempfilename, strerror(errno));
3553 fseek(fp, 0L, SEEK_END);
3554 raw_length = ftell(fp);
3556 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3558 raw_message = malloc((size_t)raw_length + 2);
3559 fread(raw_message, (size_t)raw_length, 1, fp);
3563 encoded_message = malloc((size_t)
3564 (((raw_length * 134) / 100) + 4096 ) );
3567 encoded_message = malloc((size_t)(raw_length + 4096));
3570 sprintf(encoded_message, "Content-type: %s\n", content_type);
3573 sprintf(&encoded_message[strlen(encoded_message)],
3574 "Content-transfer-encoding: base64\n\n"
3578 sprintf(&encoded_message[strlen(encoded_message)],
3579 "Content-transfer-encoding: 7bit\n\n"
3585 &encoded_message[strlen(encoded_message)],
3591 raw_message[raw_length] = 0;
3593 &encoded_message[strlen(encoded_message)],
3601 lprintf(CTDL_DEBUG, "Allocating\n");
3602 msg = malloc(sizeof(struct CtdlMessage));
3603 memset(msg, 0, sizeof(struct CtdlMessage));
3604 msg->cm_magic = CTDLMESSAGE_MAGIC;
3605 msg->cm_anon_type = MES_NORMAL;
3606 msg->cm_format_type = 4;
3607 msg->cm_fields['A'] = strdup(CC->user.fullname);
3608 msg->cm_fields['O'] = strdup(req_room);
3609 msg->cm_fields['N'] = strdup(config.c_nodename);
3610 msg->cm_fields['H'] = strdup(config.c_humannode);
3611 msg->cm_flags = flags;
3613 msg->cm_fields['M'] = encoded_message;
3615 /* Create the requested room if we have to. */
3616 if (getroom(&qrbuf, roomname) != 0) {
3617 create_room(roomname,
3618 ( (is_mailbox != NULL) ? 5 : 3 ),
3619 "", 0, 1, 0, VIEW_BBS);
3621 /* If the caller specified this object as unique, delete all
3622 * other objects of this type that are currently in the room.
3625 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3626 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3629 /* Now write the data */
3630 CtdlSubmitMsg(msg, NULL, roomname);
3631 CtdlFreeMessage(msg);
3639 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3640 config_msgnum = msgnum;
3644 char *CtdlGetSysConfig(char *sysconfname) {
3645 char hold_rm[ROOMNAMELEN];
3648 struct CtdlMessage *msg;
3651 strcpy(hold_rm, CC->room.QRname);
3652 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3653 getroom(&CC->room, hold_rm);
3658 /* We want the last (and probably only) config in this room */
3659 begin_critical_section(S_CONFIG);
3660 config_msgnum = (-1L);
3661 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3662 CtdlGetSysConfigBackend, NULL);
3663 msgnum = config_msgnum;
3664 end_critical_section(S_CONFIG);
3670 msg = CtdlFetchMessage(msgnum, 1);
3672 conf = strdup(msg->cm_fields['M']);
3673 CtdlFreeMessage(msg);
3680 getroom(&CC->room, hold_rm);
3682 if (conf != NULL) do {
3683 extract_token(buf, conf, 0, '\n', sizeof buf);
3684 strcpy(conf, &conf[strlen(buf)+1]);
3685 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3690 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3691 char temp[PATH_MAX];
3694 CtdlMakeTempFileName(temp, sizeof temp);
3696 fp = fopen(temp, "w");
3697 if (fp == NULL) return;
3698 fprintf(fp, "%s", sysconfdata);
3701 /* this handy API function does all the work for us */
3702 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3708 * Determine whether a given Internet address belongs to the current user
3710 int CtdlIsMe(char *addr, int addr_buf_len)
3712 struct recptypes *recp;
3715 recp = validate_recipients(addr);
3716 if (recp == NULL) return(0);
3718 if (recp->num_local == 0) {
3723 for (i=0; i<recp->num_local; ++i) {
3724 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3725 if (!strcasecmp(addr, CC->user.fullname)) {
3737 * Citadel protocol command to do the same
3739 void cmd_isme(char *argbuf) {
3742 if (CtdlAccessCheck(ac_logged_in)) return;
3743 extract_token(addr, argbuf, 0, '|', sizeof addr);
3745 if (CtdlIsMe(addr, sizeof addr)) {
3746 cprintf("%d %s\n", CIT_OK, addr);
3749 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);