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"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
57 struct addresses_to_be_filed *atbf = NULL;
60 * This really belongs in serv_network.c, but I don't know how to export
61 * symbols between modules.
63 struct FilterList *filterlist = NULL;
67 * These are the four-character field headers we use when outputting
68 * messages in Citadel format (as opposed to RFC822 format).
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,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 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);
145 stripallbut(name, '<', '>');
147 fp = fopen(file_mail_aliases, "r");
149 fp = fopen("/dev/null", "r");
156 while (fgets(aaa, sizeof aaa, fp) != NULL) {
157 while (isspace(name[0]))
158 strcpy(name, &name[1]);
159 aaa[strlen(aaa) - 1] = 0;
161 for (a = 0; a < strlen(aaa); ++a) {
163 strcpy(bbb, &aaa[a + 1]);
167 if (!strcasecmp(name, aaa))
172 /* Hit the Global Address Book */
173 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
177 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
179 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
180 for (a=0; a<strlen(name); ++a) {
181 if (name[a] == '@') {
182 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
184 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
189 /* determine local or remote type, see citadel.h */
190 at = haschar(name, '@');
191 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
192 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
193 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
195 /* figure out the delivery mode */
196 extract_token(node, name, 1, '@', sizeof node);
198 /* If there are one or more dots in the nodename, we assume that it
199 * is an FQDN and will attempt SMTP delivery to the Internet.
201 if (haschar(node, '.') > 0) {
202 return(MES_INTERNET);
205 /* Otherwise we look in the IGnet maps for a valid Citadel node.
206 * Try directly-connected nodes first...
208 ignetcfg = CtdlGetSysConfig(IGNETCFG);
209 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
210 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
211 extract_token(testnode, buf, 0, '|', sizeof testnode);
212 if (!strcasecmp(node, testnode)) {
220 * Then try nodes that are two or more hops away.
222 ignetmap = CtdlGetSysConfig(IGNETMAP);
223 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
224 extract_token(buf, ignetmap, i, '\n', sizeof buf);
225 extract_token(testnode, buf, 0, '|', sizeof testnode);
226 if (!strcasecmp(node, testnode)) {
233 /* If we get to this point it's an invalid node name */
242 fp = fopen(file_citadel_control, "r");
244 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
245 file_citadel_control,
249 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
255 * Back end for the MSGS command: output message number only.
257 void simple_listing(long msgnum, void *userdata)
259 cprintf("%ld\n", msgnum);
265 * Back end for the MSGS command: output header summary.
267 void headers_listing(long msgnum, void *userdata)
269 struct CtdlMessage *msg;
271 msg = CtdlFetchMessage(msgnum, 0);
273 cprintf("%ld|0|||||\n", msgnum);
277 cprintf("%ld|%s|%s|%s|%s|%s|\n",
279 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
280 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
281 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
282 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
283 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
285 CtdlFreeMessage(msg);
290 /* Determine if a given message matches the fields in a message template.
291 * Return 0 for a successful match.
293 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
296 /* If there aren't any fields in the template, all messages will
299 if (template == NULL) return(0);
301 /* Null messages are bogus. */
302 if (msg == NULL) return(1);
304 for (i='A'; i<='Z'; ++i) {
305 if (template->cm_fields[i] != NULL) {
306 if (msg->cm_fields[i] == NULL) {
309 if (strcasecmp(msg->cm_fields[i],
310 template->cm_fields[i])) return 1;
314 /* All compares succeeded: we have a match! */
321 * Retrieve the "seen" message list for the current room.
323 void CtdlGetSeen(char *buf, int which_set) {
326 /* Learn about the user and room in question */
327 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
329 if (which_set == ctdlsetseen_seen)
330 safestrncpy(buf, vbuf.v_seen, SIZ);
331 if (which_set == ctdlsetseen_answered)
332 safestrncpy(buf, vbuf.v_answered, SIZ);
338 * Manipulate the "seen msgs" string (or other message set strings)
340 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
341 int target_setting, int which_set,
342 struct ctdluser *which_user, struct ctdlroom *which_room) {
343 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
358 char setstr[SIZ], lostr[SIZ], histr[SIZ];
361 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
362 num_target_msgnums, target_msgnums[0],
363 target_setting, which_set);
365 /* Learn about the user and room in question */
366 CtdlGetRelationship(&vbuf,
367 ((which_user != NULL) ? which_user : &CC->user),
368 ((which_room != NULL) ? which_room : &CC->room)
371 /* Load the message list */
372 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
374 msglist = (long *) cdbfr->ptr;
375 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
376 num_msgs = cdbfr->len / sizeof(long);
379 return; /* No messages at all? No further action. */
382 is_set = malloc(num_msgs * sizeof(char));
383 memset(is_set, 0, (num_msgs * sizeof(char)) );
385 /* Decide which message set we're manipulating */
387 case ctdlsetseen_seen:
388 safestrncpy(vset, vbuf.v_seen, sizeof vset);
390 case ctdlsetseen_answered:
391 safestrncpy(vset, vbuf.v_answered, sizeof vset);
395 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
397 /* Translate the existing sequence set into an array of booleans */
398 num_sets = num_tokens(vset, ',');
399 for (s=0; s<num_sets; ++s) {
400 extract_token(setstr, vset, s, ',', sizeof setstr);
402 extract_token(lostr, setstr, 0, ':', sizeof lostr);
403 if (num_tokens(setstr, ':') >= 2) {
404 extract_token(histr, setstr, 1, ':', sizeof histr);
405 if (!strcmp(histr, "*")) {
406 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
410 strcpy(histr, lostr);
415 for (i = 0; i < num_msgs; ++i) {
416 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
422 /* Now translate the array of booleans back into a sequence set */
427 for (i=0; i<num_msgs; ++i) {
429 is_seen = is_set[i]; /* Default to existing setting */
431 for (k=0; k<num_target_msgnums; ++k) {
432 if (msglist[i] == target_msgnums[k]) {
433 is_seen = target_setting;
438 if (lo < 0L) lo = msglist[i];
442 if ( ((is_seen == 0) && (was_seen == 1))
443 || ((is_seen == 1) && (i == num_msgs-1)) ) {
445 /* begin trim-o-matic code */
448 while ( (strlen(vset) + 20) > sizeof vset) {
449 remove_token(vset, 0, ',');
451 if (j--) break; /* loop no more than 9 times */
453 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
457 snprintf(lostr, sizeof lostr,
458 "1:%ld,%s", t, vset);
459 safestrncpy(vset, lostr, sizeof vset);
461 /* end trim-o-matic code */
469 snprintf(&vset[tmp], (sizeof vset) - tmp,
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 /* Decide which message set we're manipulating */
484 case ctdlsetseen_seen:
485 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
487 case ctdlsetseen_answered:
488 safestrncpy(vbuf.v_answered, vset,
489 sizeof vbuf.v_answered);
494 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
496 CtdlSetRelationship(&vbuf,
497 ((which_user != NULL) ? which_user : &CC->user),
498 ((which_room != NULL) ? which_room : &CC->room)
504 * API function to perform an operation for each qualifying message in the
505 * current room. (Returns the number of messages processed.)
507 int CtdlForEachMessage(int mode, long ref,
509 struct CtdlMessage *compare,
510 void (*CallBack) (long, void *),
516 struct cdbdata *cdbfr;
517 long *msglist = NULL;
519 int num_processed = 0;
522 struct CtdlMessage *msg;
525 int printed_lastold = 0;
527 /* Learn about the user and room in question */
529 getuser(&CC->user, CC->curr_user);
530 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
532 /* Load the message list */
533 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
535 msglist = (long *) cdbfr->ptr;
536 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
537 num_msgs = cdbfr->len / sizeof(long);
540 return 0; /* No messages at all? No further action. */
545 * Now begin the traversal.
547 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
549 /* If the caller is looking for a specific MIME type, filter
550 * out all messages which are not of the type requested.
552 if (content_type != NULL) if (strlen(content_type) > 0) {
554 /* This call to GetMetaData() sits inside this loop
555 * so that we only do the extra database read per msg
556 * if we need to. Doing the extra read all the time
557 * really kills the server. If we ever need to use
558 * metadata for another search criterion, we need to
559 * move the read somewhere else -- but still be smart
560 * enough to only do the read if the caller has
561 * specified something that will need it.
563 GetMetaData(&smi, msglist[a]);
565 if (strcasecmp(smi.meta_content_type, content_type)) {
571 num_msgs = sort_msglist(msglist, num_msgs);
573 /* If a template was supplied, filter out the messages which
574 * don't match. (This could induce some delays!)
577 if (compare != NULL) {
578 for (a = 0; a < num_msgs; ++a) {
579 msg = CtdlFetchMessage(msglist[a], 1);
581 if (CtdlMsgCmp(msg, compare)) {
584 CtdlFreeMessage(msg);
592 * Now iterate through the message list, according to the
593 * criteria supplied by the caller.
596 for (a = 0; a < num_msgs; ++a) {
597 thismsg = msglist[a];
598 if (mode == MSGS_ALL) {
602 is_seen = is_msg_in_sequence_set(
603 vbuf.v_seen, thismsg);
604 if (is_seen) lastold = thismsg;
610 || ((mode == MSGS_OLD) && (is_seen))
611 || ((mode == MSGS_NEW) && (!is_seen))
612 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
613 || ((mode == MSGS_FIRST) && (a < ref))
614 || ((mode == MSGS_GT) && (thismsg > ref))
615 || ((mode == MSGS_EQ) && (thismsg == ref))
618 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
620 CallBack(lastold, userdata);
624 if (CallBack) CallBack(thismsg, userdata);
628 free(msglist); /* Clean up */
629 return num_processed;
635 * cmd_msgs() - get list of message #'s in this room
636 * implements the MSGS server command using CtdlForEachMessage()
638 void cmd_msgs(char *cmdbuf)
647 int with_template = 0;
648 struct CtdlMessage *template = NULL;
649 int with_headers = 0;
651 extract_token(which, cmdbuf, 0, '|', sizeof which);
652 cm_ref = extract_int(cmdbuf, 1);
653 with_template = extract_int(cmdbuf, 2);
654 with_headers = extract_int(cmdbuf, 3);
658 if (!strncasecmp(which, "OLD", 3))
660 else if (!strncasecmp(which, "NEW", 3))
662 else if (!strncasecmp(which, "FIRST", 5))
664 else if (!strncasecmp(which, "LAST", 4))
666 else if (!strncasecmp(which, "GT", 2))
669 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
670 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
676 cprintf("%d Send template then receive message list\n",
678 template = (struct CtdlMessage *)
679 malloc(sizeof(struct CtdlMessage));
680 memset(template, 0, sizeof(struct CtdlMessage));
681 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
682 extract_token(tfield, buf, 0, '|', sizeof tfield);
683 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
684 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
685 if (!strcasecmp(tfield, msgkeys[i])) {
686 template->cm_fields[i] =
694 cprintf("%d \n", LISTING_FOLLOWS);
697 CtdlForEachMessage(mode,
701 (with_headers ? headers_listing : simple_listing),
704 if (template != NULL) CtdlFreeMessage(template);
712 * help_subst() - support routine for help file viewer
714 void help_subst(char *strbuf, char *source, char *dest)
719 while (p = pattern2(strbuf, source), (p >= 0)) {
720 strcpy(workbuf, &strbuf[p + strlen(source)]);
721 strcpy(&strbuf[p], dest);
722 strcat(strbuf, workbuf);
727 void do_help_subst(char *buffer)
731 help_subst(buffer, "^nodename", config.c_nodename);
732 help_subst(buffer, "^humannode", config.c_humannode);
733 help_subst(buffer, "^fqdn", config.c_fqdn);
734 help_subst(buffer, "^username", CC->user.fullname);
735 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
736 help_subst(buffer, "^usernum", buf2);
737 help_subst(buffer, "^sysadm", config.c_sysadm);
738 help_subst(buffer, "^variantname", CITADEL);
739 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
740 help_subst(buffer, "^maxsessions", buf2);
741 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
747 * memfmout() - Citadel text formatter and paginator.
748 * Although the original purpose of this routine was to format
749 * text to the reader's screen width, all we're really using it
750 * for here is to format text out to 80 columns before sending it
751 * to the client. The client software may reformat it again.
754 int width, /* screen width to use */
755 char *mptr, /* where are we going to get our text from? */
756 char subst, /* nonzero if we should do substitutions */
757 char *nl) /* string to terminate lines with */
769 c = 1; /* c is the current pos */
773 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
775 buffer[strlen(buffer) + 1] = 0;
776 buffer[strlen(buffer)] = ch;
779 if (buffer[0] == '^')
780 do_help_subst(buffer);
782 buffer[strlen(buffer) + 1] = 0;
784 strcpy(buffer, &buffer[1]);
792 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
794 if (((old == 13) || (old == 10)) && (isspace(real))) {
802 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
803 cprintf("%s%s", nl, aaa);
812 if ((strlen(aaa) + c) > (width - 5)) {
821 if ((ch == 13) || (ch == 10)) {
822 cprintf("%s%s", aaa, nl);
829 cprintf("%s%s", aaa, nl);
835 * Callback function for mime parser that simply lists the part
837 void list_this_part(char *name, char *filename, char *partnum, char *disp,
838 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
843 ma = (struct ma_info *)cbuserdata;
844 if (ma->is_ma == 0) {
845 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
846 name, filename, partnum, disp, cbtype, (long)length);
851 * Callback function for multipart prefix
853 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
854 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
859 ma = (struct ma_info *)cbuserdata;
860 if (!strcasecmp(cbtype, "multipart/alternative")) {
864 if (ma->is_ma == 0) {
865 cprintf("pref=%s|%s\n", partnum, cbtype);
870 * Callback function for multipart sufffix
872 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
873 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
878 ma = (struct ma_info *)cbuserdata;
879 if (ma->is_ma == 0) {
880 cprintf("suff=%s|%s\n", partnum, cbtype);
882 if (!strcasecmp(cbtype, "multipart/alternative")) {
889 * Callback function for mime parser that opens a section for downloading
891 void mime_download(char *name, char *filename, char *partnum, char *disp,
892 void *content, char *cbtype, char *cbcharset, size_t length,
893 char *encoding, void *cbuserdata)
896 /* Silently go away if there's already a download open... */
897 if (CC->download_fp != NULL)
900 /* ...or if this is not the desired section */
901 if (strcasecmp(CC->download_desired_section, partnum))
904 CC->download_fp = tmpfile();
905 if (CC->download_fp == NULL)
908 fwrite(content, length, 1, CC->download_fp);
909 fflush(CC->download_fp);
910 rewind(CC->download_fp);
912 OpenCmdResult(filename, cbtype);
918 * Load a message from disk into memory.
919 * This is used by CtdlOutputMsg() and other fetch functions.
921 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
922 * using the CtdlMessageFree() function.
924 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
926 struct cdbdata *dmsgtext;
927 struct CtdlMessage *ret = NULL;
931 cit_uint8_t field_header;
933 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
935 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
936 if (dmsgtext == NULL) {
939 mptr = dmsgtext->ptr;
940 upper_bound = mptr + dmsgtext->len;
942 /* Parse the three bytes that begin EVERY message on disk.
943 * The first is always 0xFF, the on-disk magic number.
944 * The second is the anonymous/public type byte.
945 * The third is the format type byte (vari, fixed, or MIME).
950 "Message %ld appears to be corrupted.\n",
955 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
956 memset(ret, 0, sizeof(struct CtdlMessage));
958 ret->cm_magic = CTDLMESSAGE_MAGIC;
959 ret->cm_anon_type = *mptr++; /* Anon type byte */
960 ret->cm_format_type = *mptr++; /* Format type byte */
963 * The rest is zero or more arbitrary fields. Load them in.
964 * We're done when we encounter either a zero-length field or
965 * have just processed the 'M' (message text) field.
968 if (mptr >= upper_bound) {
971 field_header = *mptr++;
972 ret->cm_fields[field_header] = strdup(mptr);
974 while (*mptr++ != 0); /* advance to next field */
976 } while ((mptr < upper_bound) && (field_header != 'M'));
980 /* Always make sure there's something in the msg text field. If
981 * it's NULL, the message text is most likely stored separately,
982 * so go ahead and fetch that. Failing that, just set a dummy
983 * body so other code doesn't barf.
985 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
986 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
987 if (dmsgtext != NULL) {
988 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
992 if (ret->cm_fields['M'] == NULL) {
993 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
996 /* Perform "before read" hooks (aborting if any return nonzero) */
997 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
998 CtdlFreeMessage(ret);
1007 * Returns 1 if the supplied pointer points to a valid Citadel message.
1008 * If the pointer is NULL or the magic number check fails, returns 0.
1010 int is_valid_message(struct CtdlMessage *msg) {
1013 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1014 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1022 * 'Destructor' for struct CtdlMessage
1024 void CtdlFreeMessage(struct CtdlMessage *msg)
1028 if (is_valid_message(msg) == 0) return;
1030 for (i = 0; i < 256; ++i)
1031 if (msg->cm_fields[i] != NULL) {
1032 free(msg->cm_fields[i]);
1035 msg->cm_magic = 0; /* just in case */
1041 * Pre callback function for multipart/alternative
1043 * NOTE: this differs from the standard behavior for a reason. Normally when
1044 * displaying multipart/alternative you want to show the _last_ usable
1045 * format in the message. Here we show the _first_ one, because it's
1046 * usually text/plain. Since this set of functions is designed for text
1047 * output to non-MIME-aware clients, this is the desired behavior.
1050 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1051 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1056 ma = (struct ma_info *)cbuserdata;
1057 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1058 if (!strcasecmp(cbtype, "multipart/alternative")) {
1062 if (!strcasecmp(cbtype, "message/rfc822")) {
1068 * Post callback function for multipart/alternative
1070 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1071 void *content, char *cbtype, char *cbcharset, size_t length,
1072 char *encoding, void *cbuserdata)
1076 ma = (struct ma_info *)cbuserdata;
1077 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1078 if (!strcasecmp(cbtype, "multipart/alternative")) {
1082 if (!strcasecmp(cbtype, "message/rfc822")) {
1088 * Inline callback function for mime parser that wants to display text
1090 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1091 void *content, char *cbtype, char *cbcharset, size_t length,
1092 char *encoding, void *cbuserdata)
1099 ma = (struct ma_info *)cbuserdata;
1102 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1103 partnum, filename, cbtype, (long)length);
1106 * If we're in the middle of a multipart/alternative scope and
1107 * we've already printed another section, skip this one.
1109 if ( (ma->is_ma) && (ma->did_print) ) {
1110 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1116 if ( (!strcasecmp(cbtype, "text/plain"))
1117 || (strlen(cbtype)==0) ) {
1120 client_write(wptr, length);
1121 if (wptr[length-1] != '\n') {
1126 else if (!strcasecmp(cbtype, "text/html")) {
1127 ptr = html_to_ascii(content, length, 80, 0);
1129 client_write(ptr, wlen);
1130 if (ptr[wlen-1] != '\n') {
1135 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1136 /* above function returns nonzero if it handled the part */
1138 else if (strncasecmp(cbtype, "multipart/", 10)) {
1139 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1140 partnum, filename, cbtype, (long)length);
1145 * The client is elegant and sophisticated and wants to be choosy about
1146 * MIME content types, so figure out which multipart/alternative part
1147 * we're going to send.
1149 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1150 void *content, char *cbtype, char *cbcharset, size_t length,
1151 char *encoding, void *cbuserdata)
1157 ma = (struct ma_info *)cbuserdata;
1159 if (ma->is_ma > 0) {
1160 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1161 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1162 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1163 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1170 * Now that we've chosen our preferred part, output it.
1172 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1173 void *content, char *cbtype, char *cbcharset, size_t length,
1174 char *encoding, void *cbuserdata)
1178 int add_newline = 0;
1182 ma = (struct ma_info *)cbuserdata;
1184 /* This is not the MIME part you're looking for... */
1185 if (strcasecmp(partnum, ma->chosen_part)) return;
1187 /* If the content-type of this part is in our preferred formats
1188 * list, we can simply output it verbatim.
1190 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1191 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1192 if (!strcasecmp(buf, cbtype)) {
1193 /* Yeah! Go! W00t!! */
1195 text_content = (char *)content;
1196 if (text_content[length-1] != '\n') {
1200 cprintf("Content-type: %s", cbtype);
1201 if (strlen(cbcharset) > 0) {
1202 cprintf("; charset=%s", cbcharset);
1204 cprintf("\nContent-length: %d\n",
1205 (int)(length + add_newline) );
1206 if (strlen(encoding) > 0) {
1207 cprintf("Content-transfer-encoding: %s\n", encoding);
1210 cprintf("Content-transfer-encoding: 7bit\n");
1213 client_write(content, length);
1214 if (add_newline) cprintf("\n");
1219 /* No translations required or possible: output as text/plain */
1220 cprintf("Content-type: text/plain\n\n");
1221 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1222 length, encoding, cbuserdata);
1227 char desired_section[64];
1234 * Callback function for
1236 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1237 void *content, char *cbtype, char *cbcharset, size_t length,
1238 char *encoding, void *cbuserdata)
1240 struct encapmsg *encap;
1242 encap = (struct encapmsg *)cbuserdata;
1244 /* Only proceed if this is the desired section... */
1245 if (!strcasecmp(encap->desired_section, partnum)) {
1246 encap->msglen = length;
1247 encap->msg = malloc(length + 2);
1248 memcpy(encap->msg, content, length);
1258 * Get a message off disk. (returns om_* values found in msgbase.h)
1261 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
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? */
1266 char *section /* NULL or a message/rfc822 section */
1268 struct CtdlMessage *TheMessage = NULL;
1269 int retcode = om_no_such_msg;
1270 struct encapmsg encap;
1272 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1274 (section ? section : "<>")
1277 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1278 if (do_proto) cprintf("%d Not logged in.\n",
1279 ERROR + NOT_LOGGED_IN);
1280 return(om_not_logged_in);
1283 /* FIXME: check message id against msglist for this room */
1286 * Fetch the message from disk. If we're in any sort of headers
1287 * only mode, request that we don't even bother loading the body
1290 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1291 TheMessage = CtdlFetchMessage(msg_num, 0);
1294 TheMessage = CtdlFetchMessage(msg_num, 1);
1297 if (TheMessage == NULL) {
1298 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1299 ERROR + MESSAGE_NOT_FOUND, msg_num);
1300 return(om_no_such_msg);
1303 /* Here is the weird form of this command, to process only an
1304 * encapsulated message/rfc822 section.
1306 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1307 memset(&encap, 0, sizeof encap);
1308 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1309 mime_parser(TheMessage->cm_fields['M'],
1311 *extract_encapsulated_message,
1312 NULL, NULL, (void *)&encap, 0
1314 CtdlFreeMessage(TheMessage);
1318 encap.msg[encap.msglen] = 0;
1319 TheMessage = convert_internet_message(encap.msg);
1320 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1322 /* Now we let it fall through to the bottom of this
1323 * function, because TheMessage now contains the
1324 * encapsulated message instead of the top-level
1325 * message. Isn't that neat?
1330 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1331 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1332 retcode = om_no_such_msg;
1337 /* Ok, output the message now */
1338 retcode = CtdlOutputPreLoadedMsg(
1340 headers_only, do_proto, crlf);
1341 CtdlFreeMessage(TheMessage);
1348 * Get a message off disk. (returns om_* values found in msgbase.h)
1351 int CtdlOutputPreLoadedMsg(
1352 struct CtdlMessage *TheMessage,
1353 int mode, /* how would you like that message? */
1354 int headers_only, /* eschew the message body? */
1355 int do_proto, /* do Citadel protocol responses? */
1356 int crlf /* Use CRLF newlines instead of LF? */
1362 char display_name[256];
1364 char *nl; /* newline string */
1366 int subject_found = 0;
1369 /* Buffers needed for RFC822 translation. These are all filled
1370 * using functions that are bounds-checked, and therefore we can
1371 * make them substantially smaller than SIZ.
1379 char datestamp[100];
1381 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1382 ((TheMessage == NULL) ? "NULL" : "not null"),
1383 mode, headers_only, do_proto, crlf);
1385 strcpy(mid, "unknown");
1386 nl = (crlf ? "\r\n" : "\n");
1388 if (!is_valid_message(TheMessage)) {
1390 "ERROR: invalid preloaded message for output\n");
1391 return(om_no_such_msg);
1394 /* Are we downloading a MIME component? */
1395 if (mode == MT_DOWNLOAD) {
1396 if (TheMessage->cm_format_type != FMT_RFC822) {
1398 cprintf("%d This is not a MIME message.\n",
1399 ERROR + ILLEGAL_VALUE);
1400 } else if (CC->download_fp != NULL) {
1401 if (do_proto) cprintf(
1402 "%d You already have a download open.\n",
1403 ERROR + RESOURCE_BUSY);
1405 /* Parse the message text component */
1406 mptr = TheMessage->cm_fields['M'];
1407 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1408 /* If there's no file open by this time, the requested
1409 * section wasn't found, so print an error
1411 if (CC->download_fp == NULL) {
1412 if (do_proto) cprintf(
1413 "%d Section %s not found.\n",
1414 ERROR + FILE_NOT_FOUND,
1415 CC->download_desired_section);
1418 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1421 /* now for the user-mode message reading loops */
1422 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1424 /* Does the caller want to skip the headers? */
1425 if (headers_only == HEADERS_NONE) goto START_TEXT;
1427 /* Tell the client which format type we're using. */
1428 if ( (mode == MT_CITADEL) && (do_proto) ) {
1429 cprintf("type=%d\n", TheMessage->cm_format_type);
1432 /* nhdr=yes means that we're only displaying headers, no body */
1433 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1434 && (mode == MT_CITADEL)
1437 cprintf("nhdr=yes\n");
1440 /* begin header processing loop for Citadel message format */
1442 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1444 safestrncpy(display_name, "<unknown>", sizeof display_name);
1445 if (TheMessage->cm_fields['A']) {
1446 strcpy(buf, TheMessage->cm_fields['A']);
1447 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1448 safestrncpy(display_name, "****", sizeof display_name);
1450 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1451 safestrncpy(display_name, "anonymous", sizeof display_name);
1454 safestrncpy(display_name, buf, sizeof display_name);
1456 if ((is_room_aide())
1457 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1458 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1459 size_t tmp = strlen(display_name);
1460 snprintf(&display_name[tmp],
1461 sizeof display_name - tmp,
1466 /* Don't show Internet address for users on the
1467 * local Citadel network.
1470 if (TheMessage->cm_fields['N'] != NULL)
1471 if (strlen(TheMessage->cm_fields['N']) > 0)
1472 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1476 /* Now spew the header fields in the order we like them. */
1477 safestrncpy(allkeys, FORDER, sizeof allkeys);
1478 for (i=0; i<strlen(allkeys); ++i) {
1479 k = (int) allkeys[i];
1481 if ( (TheMessage->cm_fields[k] != NULL)
1482 && (msgkeys[k] != NULL) ) {
1484 if (do_proto) cprintf("%s=%s\n",
1488 else if ((k == 'F') && (suppress_f)) {
1491 /* Masquerade display name if needed */
1493 if (do_proto) cprintf("%s=%s\n",
1495 TheMessage->cm_fields[k]
1504 /* begin header processing loop for RFC822 transfer format */
1509 strcpy(snode, NODENAME);
1510 strcpy(lnode, HUMANNODE);
1511 if (mode == MT_RFC822) {
1512 for (i = 0; i < 256; ++i) {
1513 if (TheMessage->cm_fields[i]) {
1514 mptr = TheMessage->cm_fields[i];
1517 safestrncpy(luser, mptr, sizeof luser);
1518 safestrncpy(suser, mptr, sizeof suser);
1520 else if (i == 'Y') {
1521 cprintf("CC: %s%s", mptr, nl);
1523 else if (i == 'U') {
1524 cprintf("Subject: %s%s", mptr, nl);
1528 safestrncpy(mid, mptr, sizeof mid);
1530 safestrncpy(lnode, mptr, sizeof lnode);
1532 safestrncpy(fuser, mptr, sizeof fuser);
1533 /* else if (i == 'O')
1534 cprintf("X-Citadel-Room: %s%s",
1537 safestrncpy(snode, mptr, sizeof snode);
1539 cprintf("To: %s%s", mptr, nl);
1540 else if (i == 'T') {
1541 datestring(datestamp, sizeof datestamp,
1542 atol(mptr), DATESTRING_RFC822);
1543 cprintf("Date: %s%s", datestamp, nl);
1547 if (subject_found == 0) {
1548 cprintf("Subject: (no subject)%s", nl);
1552 for (i=0; i<strlen(suser); ++i) {
1553 suser[i] = tolower(suser[i]);
1554 if (!isalnum(suser[i])) suser[i]='_';
1557 if (mode == MT_RFC822) {
1558 if (!strcasecmp(snode, NODENAME)) {
1559 safestrncpy(snode, FQDN, sizeof snode);
1562 /* Construct a fun message id */
1563 cprintf("Message-ID: <%s", mid);
1564 if (strchr(mid, '@')==NULL) {
1565 cprintf("@%s", snode);
1569 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1570 cprintf("From: \"----\" <x@x.org>%s", nl);
1572 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1573 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1575 else if (strlen(fuser) > 0) {
1576 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1579 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1582 cprintf("Organization: %s%s", lnode, nl);
1584 /* Blank line signifying RFC822 end-of-headers */
1585 if (TheMessage->cm_format_type != FMT_RFC822) {
1590 /* end header processing loop ... at this point, we're in the text */
1592 if (headers_only == HEADERS_FAST) goto DONE;
1593 mptr = TheMessage->cm_fields['M'];
1595 /* Tell the client about the MIME parts in this message */
1596 if (TheMessage->cm_format_type == FMT_RFC822) {
1597 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1598 memset(&ma, 0, sizeof(struct ma_info));
1599 mime_parser(mptr, NULL,
1600 (do_proto ? *list_this_part : NULL),
1601 (do_proto ? *list_this_pref : NULL),
1602 (do_proto ? *list_this_suff : NULL),
1605 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1606 char *start_of_text = NULL;
1607 start_of_text = strstr(mptr, "\n\r\n");
1608 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1609 if (start_of_text == NULL) start_of_text = mptr;
1611 start_of_text = strstr(start_of_text, "\n");
1613 while (ch=*mptr, ch!=0) {
1617 else switch(headers_only) {
1619 if (mptr >= start_of_text) {
1620 if (ch == 10) cprintf("%s", nl);
1621 else cprintf("%c", ch);
1625 if (mptr < start_of_text) {
1626 if (ch == 10) cprintf("%s", nl);
1627 else cprintf("%c", ch);
1631 if (ch == 10) cprintf("%s", nl);
1632 else cprintf("%c", ch);
1641 if (headers_only == HEADERS_ONLY) {
1645 /* signify start of msg text */
1646 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1647 if (do_proto) cprintf("text\n");
1650 /* If the format type on disk is 1 (fixed-format), then we want
1651 * everything to be output completely literally ... regardless of
1652 * what message transfer format is in use.
1654 if (TheMessage->cm_format_type == FMT_FIXED) {
1655 if (mode == MT_MIME) {
1656 cprintf("Content-type: text/plain\n\n");
1659 while (ch = *mptr++, ch > 0) {
1662 if ((ch == 10) || (strlen(buf) > 250)) {
1663 cprintf("%s%s", buf, nl);
1666 buf[strlen(buf) + 1] = 0;
1667 buf[strlen(buf)] = ch;
1670 if (strlen(buf) > 0)
1671 cprintf("%s%s", buf, nl);
1674 /* If the message on disk is format 0 (Citadel vari-format), we
1675 * output using the formatter at 80 columns. This is the final output
1676 * form if the transfer format is RFC822, but if the transfer format
1677 * is Citadel proprietary, it'll still work, because the indentation
1678 * for new paragraphs is correct and the client will reformat the
1679 * message to the reader's screen width.
1681 if (TheMessage->cm_format_type == FMT_CITADEL) {
1682 if (mode == MT_MIME) {
1683 cprintf("Content-type: text/x-citadel-variformat\n\n");
1685 memfmout(80, mptr, 0, nl);
1688 /* If the message on disk is format 4 (MIME), we've gotta hand it
1689 * off to the MIME parser. The client has already been told that
1690 * this message is format 1 (fixed format), so the callback function
1691 * we use will display those parts as-is.
1693 if (TheMessage->cm_format_type == FMT_RFC822) {
1694 memset(&ma, 0, sizeof(struct ma_info));
1696 if (mode == MT_MIME) {
1697 strcpy(ma.chosen_part, "1");
1698 mime_parser(mptr, NULL,
1699 *choose_preferred, *fixed_output_pre,
1700 *fixed_output_post, (void *)&ma, 0);
1701 mime_parser(mptr, NULL,
1702 *output_preferred, NULL, NULL, (void *)&ma, 0);
1705 mime_parser(mptr, NULL,
1706 *fixed_output, *fixed_output_pre,
1707 *fixed_output_post, (void *)&ma, 0);
1712 DONE: /* now we're done */
1713 if (do_proto) cprintf("000\n");
1720 * display a message (mode 0 - Citadel proprietary)
1722 void cmd_msg0(char *cmdbuf)
1725 int headers_only = HEADERS_ALL;
1727 msgid = extract_long(cmdbuf, 0);
1728 headers_only = extract_int(cmdbuf, 1);
1730 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1736 * display a message (mode 2 - RFC822)
1738 void cmd_msg2(char *cmdbuf)
1741 int headers_only = HEADERS_ALL;
1743 msgid = extract_long(cmdbuf, 0);
1744 headers_only = extract_int(cmdbuf, 1);
1746 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1752 * display a message (mode 3 - IGnet raw format - internal programs only)
1754 void cmd_msg3(char *cmdbuf)
1757 struct CtdlMessage *msg;
1760 if (CC->internal_pgm == 0) {
1761 cprintf("%d This command is for internal programs only.\n",
1762 ERROR + HIGHER_ACCESS_REQUIRED);
1766 msgnum = extract_long(cmdbuf, 0);
1767 msg = CtdlFetchMessage(msgnum, 1);
1769 cprintf("%d Message %ld not found.\n",
1770 ERROR + MESSAGE_NOT_FOUND, msgnum);
1774 serialize_message(&smr, msg);
1775 CtdlFreeMessage(msg);
1778 cprintf("%d Unable to serialize message\n",
1779 ERROR + INTERNAL_ERROR);
1783 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1784 client_write((char *)smr.ser, (int)smr.len);
1791 * Display a message using MIME content types
1793 void cmd_msg4(char *cmdbuf)
1798 msgid = extract_long(cmdbuf, 0);
1799 extract_token(section, cmdbuf, 1, '|', sizeof section);
1800 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1806 * Client tells us its preferred message format(s)
1808 void cmd_msgp(char *cmdbuf)
1810 safestrncpy(CC->preferred_formats, cmdbuf,
1811 sizeof(CC->preferred_formats));
1812 cprintf("%d ok\n", CIT_OK);
1817 * Open a component of a MIME message as a download file
1819 void cmd_opna(char *cmdbuf)
1822 char desired_section[128];
1824 msgid = extract_long(cmdbuf, 0);
1825 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1826 safestrncpy(CC->download_desired_section, desired_section,
1827 sizeof CC->download_desired_section);
1828 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1833 * Save a message pointer into a specified room
1834 * (Returns 0 for success, nonzero for failure)
1835 * roomname may be NULL to use the current room
1837 * Note that the 'supplied_msg' field may be set to NULL, in which case
1838 * the message will be fetched from disk, by number, if we need to perform
1839 * replication checks. This adds an additional database read, so if the
1840 * caller already has the message in memory then it should be supplied.
1842 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1843 struct CtdlMessage *supplied_msg) {
1845 char hold_rm[ROOMNAMELEN];
1846 struct cdbdata *cdbfr;
1849 long highest_msg = 0L;
1850 struct CtdlMessage *msg = NULL;
1852 /*lprintf(CTDL_DEBUG,
1853 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1854 roomname, msgid, do_repl_check);*/
1856 strcpy(hold_rm, CC->room.QRname);
1858 /* Now the regular stuff */
1859 if (lgetroom(&CC->room,
1860 ((roomname != NULL) ? roomname : CC->room.QRname) )
1862 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1863 return(ERROR + ROOM_NOT_FOUND);
1866 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1867 if (cdbfr == NULL) {
1871 msglist = (long *) cdbfr->ptr;
1872 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1873 num_msgs = cdbfr->len / sizeof(long);
1877 /* Make sure the message doesn't already exist in this room. It
1878 * is absolutely taboo to have more than one reference to the same
1879 * message in a room.
1881 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1882 if (msglist[i] == msgid) {
1883 lputroom(&CC->room); /* unlock the room */
1884 getroom(&CC->room, hold_rm);
1886 return(ERROR + ALREADY_EXISTS);
1890 /* Now add the new message */
1892 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1894 if (msglist == NULL) {
1895 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1897 msglist[num_msgs - 1] = msgid;
1899 /* Sort the message list, so all the msgid's are in order */
1900 num_msgs = sort_msglist(msglist, num_msgs);
1902 /* Determine the highest message number */
1903 highest_msg = msglist[num_msgs - 1];
1905 /* Write it back to disk. */
1906 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1907 msglist, (int)(num_msgs * sizeof(long)));
1909 /* Free up the memory we used. */
1912 /* Update the highest-message pointer and unlock the room. */
1913 CC->room.QRhighest = highest_msg;
1914 lputroom(&CC->room);
1916 /* Perform replication checks if necessary */
1917 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1918 if (supplied_msg != NULL) {
1922 msg = CtdlFetchMessage(msgid, 0);
1926 ReplicationChecks(msg);
1931 /* If the message has an Exclusive ID, index that... */
1933 if (msg->cm_fields['E'] != NULL) {
1934 index_message_by_euid(msg->cm_fields['E'],
1939 /* Free up the memory we may have allocated */
1940 if ( (msg != NULL) && (msg != supplied_msg) ) {
1941 CtdlFreeMessage(msg);
1944 /* Go back to the room we were in before we wandered here... */
1945 getroom(&CC->room, hold_rm);
1947 /* Bump the reference count for this message. */
1948 AdjRefCount(msgid, +1);
1950 /* Return success. */
1957 * Message base operation to save a new message to the message store
1958 * (returns new message number)
1960 * This is the back end for CtdlSubmitMsg() and should not be directly
1961 * called by server-side modules.
1964 long send_message(struct CtdlMessage *msg) {
1972 /* Get a new message number */
1973 newmsgid = get_new_message_number();
1974 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1976 /* Generate an ID if we don't have one already */
1977 if (msg->cm_fields['I']==NULL) {
1978 msg->cm_fields['I'] = strdup(msgidbuf);
1981 /* If the message is big, set its body aside for storage elsewhere */
1982 if (msg->cm_fields['M'] != NULL) {
1983 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1985 holdM = msg->cm_fields['M'];
1986 msg->cm_fields['M'] = NULL;
1990 /* Serialize our data structure for storage in the database */
1991 serialize_message(&smr, msg);
1994 msg->cm_fields['M'] = holdM;
1998 cprintf("%d Unable to serialize message\n",
1999 ERROR + INTERNAL_ERROR);
2003 /* Write our little bundle of joy into the message base */
2004 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2005 smr.ser, smr.len) < 0) {
2006 lprintf(CTDL_ERR, "Can't store message\n");
2010 cdb_store(CDB_BIGMSGS,
2020 /* Free the memory we used for the serialized message */
2023 /* Return the *local* message ID to the caller
2024 * (even if we're storing an incoming network message)
2032 * Serialize a struct CtdlMessage into the format used on disk and network.
2034 * This function loads up a "struct ser_ret" (defined in server.h) which
2035 * contains the length of the serialized message and a pointer to the
2036 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2038 void serialize_message(struct ser_ret *ret, /* return values */
2039 struct CtdlMessage *msg) /* unserialized msg */
2043 static char *forder = FORDER;
2045 if (is_valid_message(msg) == 0) return; /* self check */
2048 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2049 ret->len = ret->len +
2050 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2052 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2053 ret->ser = malloc(ret->len);
2054 if (ret->ser == NULL) {
2060 ret->ser[1] = msg->cm_anon_type;
2061 ret->ser[2] = msg->cm_format_type;
2064 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2065 ret->ser[wlen++] = (char)forder[i];
2066 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2067 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2069 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2070 (long)ret->len, (long)wlen);
2078 * Check to see if any messages already exist in the current room which
2079 * carry the same Exclusive ID as this one. If any are found, delete them.
2081 void ReplicationChecks(struct CtdlMessage *msg) {
2082 long old_msgnum = (-1L);
2084 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2086 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2089 /* No exclusive id? Don't do anything. */
2090 if (msg == NULL) return;
2091 if (msg->cm_fields['E'] == NULL) return;
2092 if (strlen(msg->cm_fields['E']) == 0) return;
2093 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2094 msg->cm_fields['E'], CC->room.QRname);*/
2096 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2097 if (old_msgnum > 0L) {
2098 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2099 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2106 * Save a message to disk and submit it into the delivery system.
2108 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2109 struct recptypes *recps, /* recipients (if mail) */
2110 char *force /* force a particular room? */
2112 char submit_filename[128];
2113 char generated_timestamp[32];
2114 char hold_rm[ROOMNAMELEN];
2115 char actual_rm[ROOMNAMELEN];
2116 char force_room[ROOMNAMELEN];
2117 char content_type[SIZ]; /* We have to learn this */
2118 char recipient[SIZ];
2121 struct ctdluser userbuf;
2123 struct MetaData smi;
2124 FILE *network_fp = NULL;
2125 static int seqnum = 1;
2126 struct CtdlMessage *imsg = NULL;
2129 char *hold_R, *hold_D;
2130 char *collected_addresses = NULL;
2131 struct addresses_to_be_filed *aptr = NULL;
2132 char *saved_rfc822_version = NULL;
2133 int qualified_for_journaling = 0;
2135 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2136 if (is_valid_message(msg) == 0) return(-1); /* self check */
2138 /* If this message has no timestamp, we take the liberty of
2139 * giving it one, right now.
2141 if (msg->cm_fields['T'] == NULL) {
2142 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2143 msg->cm_fields['T'] = strdup(generated_timestamp);
2146 /* If this message has no path, we generate one.
2148 if (msg->cm_fields['P'] == NULL) {
2149 if (msg->cm_fields['A'] != NULL) {
2150 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2151 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2152 if (isspace(msg->cm_fields['P'][a])) {
2153 msg->cm_fields['P'][a] = ' ';
2158 msg->cm_fields['P'] = strdup("unknown");
2162 if (force == NULL) {
2163 strcpy(force_room, "");
2166 strcpy(force_room, force);
2169 /* Learn about what's inside, because it's what's inside that counts */
2170 if (msg->cm_fields['M'] == NULL) {
2171 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2175 switch (msg->cm_format_type) {
2177 strcpy(content_type, "text/x-citadel-variformat");
2180 strcpy(content_type, "text/plain");
2183 strcpy(content_type, "text/plain");
2184 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2186 safestrncpy(content_type, &mptr[14],
2187 sizeof content_type);
2188 for (a = 0; a < strlen(content_type); ++a) {
2189 if ((content_type[a] == ';')
2190 || (content_type[a] == ' ')
2191 || (content_type[a] == 13)
2192 || (content_type[a] == 10)) {
2193 content_type[a] = 0;
2199 /* Goto the correct room */
2200 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2201 strcpy(hold_rm, CC->room.QRname);
2202 strcpy(actual_rm, CC->room.QRname);
2203 if (recps != NULL) {
2204 strcpy(actual_rm, SENTITEMS);
2207 /* If the user is a twit, move to the twit room for posting */
2209 if (CC->user.axlevel == 2) {
2210 strcpy(hold_rm, actual_rm);
2211 strcpy(actual_rm, config.c_twitroom);
2212 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2216 /* ...or if this message is destined for Aide> then go there. */
2217 if (strlen(force_room) > 0) {
2218 strcpy(actual_rm, force_room);
2221 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2222 if (strcasecmp(actual_rm, CC->room.QRname)) {
2223 /* getroom(&CC->room, actual_rm); */
2224 usergoto(actual_rm, 0, 1, NULL, NULL);
2228 * If this message has no O (room) field, generate one.
2230 if (msg->cm_fields['O'] == NULL) {
2231 msg->cm_fields['O'] = strdup(CC->room.QRname);
2234 /* Perform "before save" hooks (aborting if any return nonzero) */
2235 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2236 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2239 * If this message has an Exclusive ID, and the room is replication
2240 * checking enabled, then do replication checks.
2242 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2243 ReplicationChecks(msg);
2246 /* Save it to disk */
2247 lprintf(CTDL_DEBUG, "Saving to disk\n");
2248 newmsgid = send_message(msg);
2249 if (newmsgid <= 0L) return(-5);
2251 /* Write a supplemental message info record. This doesn't have to
2252 * be a critical section because nobody else knows about this message
2255 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2256 memset(&smi, 0, sizeof(struct MetaData));
2257 smi.meta_msgnum = newmsgid;
2258 smi.meta_refcount = 0;
2259 safestrncpy(smi.meta_content_type, content_type,
2260 sizeof smi.meta_content_type);
2263 * Measure how big this message will be when rendered as RFC822.
2264 * We do this for two reasons:
2265 * 1. We need the RFC822 length for the new metadata record, so the
2266 * POP and IMAP services don't have to calculate message lengths
2267 * while the user is waiting (multiplied by potentially hundreds
2268 * or thousands of messages).
2269 * 2. If journaling is enabled, we will need an RFC822 version of the
2270 * message to attach to the journalized copy.
2272 if (CC->redirect_buffer != NULL) {
2273 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2276 CC->redirect_buffer = malloc(SIZ);
2277 CC->redirect_len = 0;
2278 CC->redirect_alloc = SIZ;
2279 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2280 smi.meta_rfc822_length = CC->redirect_len;
2281 saved_rfc822_version = CC->redirect_buffer;
2282 CC->redirect_buffer = NULL;
2283 CC->redirect_len = 0;
2284 CC->redirect_alloc = 0;
2288 /* Now figure out where to store the pointers */
2289 lprintf(CTDL_DEBUG, "Storing pointers\n");
2291 /* If this is being done by the networker delivering a private
2292 * message, we want to BYPASS saving the sender's copy (because there
2293 * is no local sender; it would otherwise go to the Trashcan).
2295 if ((!CC->internal_pgm) || (recps == NULL)) {
2296 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2297 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2298 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2302 /* For internet mail, drop a copy in the outbound queue room */
2304 if (recps->num_internet > 0) {
2305 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2308 /* If other rooms are specified, drop them there too. */
2310 if (recps->num_room > 0)
2311 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2312 extract_token(recipient, recps->recp_room, i,
2313 '|', sizeof recipient);
2314 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2315 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2318 /* Bump this user's messages posted counter. */
2319 lprintf(CTDL_DEBUG, "Updating user\n");
2320 lgetuser(&CC->user, CC->curr_user);
2321 CC->user.posted = CC->user.posted + 1;
2322 lputuser(&CC->user);
2324 /* If this is private, local mail, make a copy in the
2325 * recipient's mailbox and bump the reference count.
2328 if (recps->num_local > 0)
2329 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2330 extract_token(recipient, recps->recp_local, i,
2331 '|', sizeof recipient);
2332 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2334 if (getuser(&userbuf, recipient) == 0) {
2335 MailboxName(actual_rm, sizeof actual_rm,
2336 &userbuf, MAILROOM);
2337 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2338 BumpNewMailCounter(userbuf.usernum);
2341 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2342 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2347 /* Perform "after save" hooks */
2348 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2349 PerformMessageHooks(msg, EVT_AFTERSAVE);
2351 /* For IGnet mail, we have to save a new copy into the spooler for
2352 * each recipient, with the R and D fields set to the recipient and
2353 * destination-node. This has two ugly side effects: all other
2354 * recipients end up being unlisted in this recipient's copy of the
2355 * message, and it has to deliver multiple messages to the same
2356 * node. We'll revisit this again in a year or so when everyone has
2357 * a network spool receiver that can handle the new style messages.
2360 if (recps->num_ignet > 0)
2361 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2362 extract_token(recipient, recps->recp_ignet, i,
2363 '|', sizeof recipient);
2365 hold_R = msg->cm_fields['R'];
2366 hold_D = msg->cm_fields['D'];
2367 msg->cm_fields['R'] = malloc(SIZ);
2368 msg->cm_fields['D'] = malloc(128);
2369 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2370 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2372 serialize_message(&smr, msg);
2374 snprintf(submit_filename, sizeof submit_filename,
2375 "%s/netmail.%04lx.%04x.%04x",
2377 (long) getpid(), CC->cs_pid, ++seqnum);
2378 network_fp = fopen(submit_filename, "wb+");
2379 if (network_fp != NULL) {
2380 fwrite(smr.ser, smr.len, 1, network_fp);
2386 free(msg->cm_fields['R']);
2387 free(msg->cm_fields['D']);
2388 msg->cm_fields['R'] = hold_R;
2389 msg->cm_fields['D'] = hold_D;
2392 /* Go back to the room we started from */
2393 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2394 if (strcasecmp(hold_rm, CC->room.QRname))
2395 /* getroom(&CC->room, hold_rm); */
2396 usergoto(hold_rm, 0, 1, NULL, NULL);
2398 /* For internet mail, generate delivery instructions.
2399 * Yes, this is recursive. Deal with it. Infinite recursion does
2400 * not happen because the delivery instructions message does not
2401 * contain a recipient.
2404 if (recps->num_internet > 0) {
2405 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2406 instr = malloc(SIZ * 2);
2407 snprintf(instr, SIZ * 2,
2408 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2410 SPOOLMIME, newmsgid, (long)time(NULL),
2411 msg->cm_fields['A'], msg->cm_fields['N']
2414 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2415 size_t tmp = strlen(instr);
2416 extract_token(recipient, recps->recp_internet,
2417 i, '|', sizeof recipient);
2418 snprintf(&instr[tmp], SIZ * 2 - tmp,
2419 "remote|%s|0||\n", recipient);
2422 imsg = malloc(sizeof(struct CtdlMessage));
2423 memset(imsg, 0, sizeof(struct CtdlMessage));
2424 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2425 imsg->cm_anon_type = MES_NORMAL;
2426 imsg->cm_format_type = FMT_RFC822;
2427 imsg->cm_fields['A'] = strdup("Citadel");
2428 imsg->cm_fields['J'] = strdup("do not journal");
2429 imsg->cm_fields['M'] = instr;
2430 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2431 CtdlFreeMessage(imsg);
2435 * Any addresses to harvest for someone's address book?
2437 if ( (CC->logged_in) && (recps != NULL) ) {
2438 collected_addresses = harvest_collected_addresses(msg);
2441 if (collected_addresses != NULL) {
2442 begin_critical_section(S_ATBF);
2443 aptr = (struct addresses_to_be_filed *)
2444 malloc(sizeof(struct addresses_to_be_filed));
2446 MailboxName(actual_rm, sizeof actual_rm,
2447 &CC->user, USERCONTACTSROOM);
2448 aptr->roomname = strdup(actual_rm);
2449 aptr->collected_addresses = collected_addresses;
2451 end_critical_section(S_ATBF);
2455 * Determine whether this message qualifies for journaling.
2457 if (msg->cm_fields['J'] != NULL) {
2458 qualified_for_journaling = 0;
2461 if (recps == NULL) {
2462 qualified_for_journaling = config.c_journal_pubmsgs;
2464 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2465 qualified_for_journaling = config.c_journal_email;
2468 qualified_for_journaling = config.c_journal_pubmsgs;
2473 * Do we have to perform journaling? If so, hand off the saved
2474 * RFC822 version will be handed off to the journaler for background
2475 * submit. Otherwise, we have to free the memory ourselves.
2477 if (saved_rfc822_version != NULL) {
2478 if (qualified_for_journaling) {
2479 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2482 free(saved_rfc822_version);
2495 * Convenience function for generating small administrative messages.
2497 void quickie_message(char *from, char *to, char *room, char *text,
2498 int format_type, char *subject)
2500 struct CtdlMessage *msg;
2501 struct recptypes *recp = NULL;
2503 msg = malloc(sizeof(struct CtdlMessage));
2504 memset(msg, 0, sizeof(struct CtdlMessage));
2505 msg->cm_magic = CTDLMESSAGE_MAGIC;
2506 msg->cm_anon_type = MES_NORMAL;
2507 msg->cm_format_type = format_type;
2508 msg->cm_fields['A'] = strdup(from);
2509 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2510 msg->cm_fields['N'] = strdup(NODENAME);
2512 msg->cm_fields['R'] = strdup(to);
2513 recp = validate_recipients(to);
2515 if (subject != NULL) {
2516 msg->cm_fields['U'] = strdup(subject);
2518 msg->cm_fields['M'] = strdup(text);
2520 CtdlSubmitMsg(msg, recp, room);
2521 CtdlFreeMessage(msg);
2522 if (recp != NULL) free(recp);
2528 * Back end function used by CtdlMakeMessage() and similar functions
2530 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2531 size_t maxlen, /* maximum message length */
2532 char *exist, /* if non-null, append to it;
2533 exist is ALWAYS freed */
2534 int crlf /* CRLF newlines instead of LF */
2538 size_t message_len = 0;
2539 size_t buffer_len = 0;
2545 if (exist == NULL) {
2552 message_len = strlen(exist);
2553 buffer_len = message_len + 4096;
2554 m = realloc(exist, buffer_len);
2561 /* flush the input if we have nowhere to store it */
2566 /* read in the lines of message text one by one */
2568 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2569 if (!strcmp(buf, terminator)) finished = 1;
2571 strcat(buf, "\r\n");
2577 if ( (!flushing) && (!finished) ) {
2578 /* Measure the line */
2579 linelen = strlen(buf);
2581 /* augment the buffer if we have to */
2582 if ((message_len + linelen) >= buffer_len) {
2583 ptr = realloc(m, (buffer_len * 2) );
2584 if (ptr == NULL) { /* flush if can't allocate */
2587 buffer_len = (buffer_len * 2);
2589 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2593 /* Add the new line to the buffer. NOTE: this loop must avoid
2594 * using functions like strcat() and strlen() because they
2595 * traverse the entire buffer upon every call, and doing that
2596 * for a multi-megabyte message slows it down beyond usability.
2598 strcpy(&m[message_len], buf);
2599 message_len += linelen;
2602 /* if we've hit the max msg length, flush the rest */
2603 if (message_len >= maxlen) flushing = 1;
2605 } while (!finished);
2613 * Build a binary message to be saved on disk.
2614 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2615 * will become part of the message. This means you are no longer
2616 * responsible for managing that memory -- it will be freed along with
2617 * the rest of the fields when CtdlFreeMessage() is called.)
2620 struct CtdlMessage *CtdlMakeMessage(
2621 struct ctdluser *author, /* author's user structure */
2622 char *recipient, /* NULL if it's not mail */
2623 char *recp_cc, /* NULL if it's not mail */
2624 char *room, /* room where it's going */
2625 int type, /* see MES_ types in header file */
2626 int format_type, /* variformat, plain text, MIME... */
2627 char *fake_name, /* who we're masquerading as */
2628 char *subject, /* Subject (optional) */
2629 char *supplied_euid, /* ...or NULL if this is irrelevant */
2630 char *preformatted_text /* ...or NULL to read text from client */
2632 char dest_node[SIZ];
2634 struct CtdlMessage *msg;
2636 msg = malloc(sizeof(struct CtdlMessage));
2637 memset(msg, 0, sizeof(struct CtdlMessage));
2638 msg->cm_magic = CTDLMESSAGE_MAGIC;
2639 msg->cm_anon_type = type;
2640 msg->cm_format_type = format_type;
2642 /* Don't confuse the poor folks if it's not routed mail. */
2643 strcpy(dest_node, "");
2648 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2649 msg->cm_fields['P'] = strdup(buf);
2651 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2652 msg->cm_fields['T'] = strdup(buf);
2654 if (fake_name[0]) /* author */
2655 msg->cm_fields['A'] = strdup(fake_name);
2657 msg->cm_fields['A'] = strdup(author->fullname);
2659 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2660 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2663 msg->cm_fields['O'] = strdup(CC->room.QRname);
2666 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2667 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2669 if (recipient[0] != 0) {
2670 msg->cm_fields['R'] = strdup(recipient);
2672 if (recp_cc[0] != 0) {
2673 msg->cm_fields['Y'] = strdup(recp_cc);
2675 if (dest_node[0] != 0) {
2676 msg->cm_fields['D'] = strdup(dest_node);
2679 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2680 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2683 if (subject != NULL) {
2685 if (strlen(subject) > 0) {
2686 msg->cm_fields['U'] = strdup(subject);
2690 if (supplied_euid != NULL) {
2691 msg->cm_fields['E'] = strdup(supplied_euid);
2694 if (preformatted_text != NULL) {
2695 msg->cm_fields['M'] = preformatted_text;
2698 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2699 config.c_maxmsglen, NULL, 0);
2707 * Check to see whether we have permission to post a message in the current
2708 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2709 * returns 0 on success.
2711 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2713 if (!(CC->logged_in)) {
2714 snprintf(errmsgbuf, n, "Not logged in.");
2715 return (ERROR + NOT_LOGGED_IN);
2718 if ((CC->user.axlevel < 2)
2719 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2720 snprintf(errmsgbuf, n, "Need to be validated to enter "
2721 "(except in %s> to sysop)", MAILROOM);
2722 return (ERROR + HIGHER_ACCESS_REQUIRED);
2725 if ((CC->user.axlevel < 4)
2726 && (CC->room.QRflags & QR_NETWORK)) {
2727 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2728 return (ERROR + HIGHER_ACCESS_REQUIRED);
2731 if ((CC->user.axlevel < 6)
2732 && (CC->room.QRflags & QR_READONLY)) {
2733 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2734 return (ERROR + HIGHER_ACCESS_REQUIRED);
2737 strcpy(errmsgbuf, "Ok");
2743 * Check to see if the specified user has Internet mail permission
2744 * (returns nonzero if permission is granted)
2746 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2748 /* Do not allow twits to send Internet mail */
2749 if (who->axlevel <= 2) return(0);
2751 /* Globally enabled? */
2752 if (config.c_restrict == 0) return(1);
2754 /* User flagged ok? */
2755 if (who->flags & US_INTERNET) return(2);
2757 /* Aide level access? */
2758 if (who->axlevel >= 6) return(3);
2760 /* No mail for you! */
2766 * Validate recipients, count delivery types and errors, and handle aliasing
2767 * FIXME check for dupes!!!!!
2768 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2769 * or the number of addresses found invalid.
2771 struct recptypes *validate_recipients(char *supplied_recipients) {
2772 struct recptypes *ret;
2773 char recipients[SIZ];
2774 char this_recp[256];
2775 char this_recp_cooked[256];
2781 struct ctdluser tempUS;
2782 struct ctdlroom tempQR;
2786 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2787 if (ret == NULL) return(NULL);
2788 memset(ret, 0, sizeof(struct recptypes));
2791 ret->num_internet = 0;
2796 if (supplied_recipients == NULL) {
2797 strcpy(recipients, "");
2800 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2803 /* Change all valid separator characters to commas */
2804 for (i=0; i<strlen(recipients); ++i) {
2805 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2806 recipients[i] = ',';
2810 /* Now start extracting recipients... */
2812 while (strlen(recipients) > 0) {
2814 for (i=0; i<=strlen(recipients); ++i) {
2815 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2816 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2817 safestrncpy(this_recp, recipients, i+1);
2819 if (recipients[i] == ',') {
2820 strcpy(recipients, &recipients[i+1]);
2823 strcpy(recipients, "");
2830 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2832 mailtype = alias(this_recp);
2833 mailtype = alias(this_recp);
2834 mailtype = alias(this_recp);
2835 for (j=0; j<=strlen(this_recp); ++j) {
2836 if (this_recp[j]=='_') {
2837 this_recp_cooked[j] = ' ';
2840 this_recp_cooked[j] = this_recp[j];
2846 if (!strcasecmp(this_recp, "sysop")) {
2848 strcpy(this_recp, config.c_aideroom);
2849 if (strlen(ret->recp_room) > 0) {
2850 strcat(ret->recp_room, "|");
2852 strcat(ret->recp_room, this_recp);
2854 else if (getuser(&tempUS, this_recp) == 0) {
2856 strcpy(this_recp, tempUS.fullname);
2857 if (strlen(ret->recp_local) > 0) {
2858 strcat(ret->recp_local, "|");
2860 strcat(ret->recp_local, this_recp);
2862 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2864 strcpy(this_recp, tempUS.fullname);
2865 if (strlen(ret->recp_local) > 0) {
2866 strcat(ret->recp_local, "|");
2868 strcat(ret->recp_local, this_recp);
2870 else if ( (!strncasecmp(this_recp, "room_", 5))
2871 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2873 if (strlen(ret->recp_room) > 0) {
2874 strcat(ret->recp_room, "|");
2876 strcat(ret->recp_room, &this_recp_cooked[5]);
2884 /* Yes, you're reading this correctly: if the target
2885 * domain points back to the local system or an attached
2886 * Citadel directory, the address is invalid. That's
2887 * because if the address were valid, we would have
2888 * already translated it to a local address by now.
2890 if (IsDirectory(this_recp)) {
2895 ++ret->num_internet;
2896 if (strlen(ret->recp_internet) > 0) {
2897 strcat(ret->recp_internet, "|");
2899 strcat(ret->recp_internet, this_recp);
2904 if (strlen(ret->recp_ignet) > 0) {
2905 strcat(ret->recp_ignet, "|");
2907 strcat(ret->recp_ignet, this_recp);
2915 if (strlen(ret->errormsg) == 0) {
2916 snprintf(append, sizeof append,
2917 "Invalid recipient: %s",
2921 snprintf(append, sizeof append,
2924 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2925 strcat(ret->errormsg, append);
2929 if (strlen(ret->display_recp) == 0) {
2930 strcpy(append, this_recp);
2933 snprintf(append, sizeof append, ", %s",
2936 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2937 strcat(ret->display_recp, append);
2942 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2943 ret->num_room + ret->num_error) == 0) {
2944 ret->num_error = (-1);
2945 strcpy(ret->errormsg, "No recipients specified.");
2948 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2949 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2950 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2951 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2952 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2953 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2961 * message entry - mode 0 (normal)
2963 void cmd_ent0(char *entargs)
2969 char supplied_euid[128];
2970 char masquerade_as[SIZ];
2972 int format_type = 0;
2973 char newusername[SIZ];
2974 struct CtdlMessage *msg;
2978 struct recptypes *valid = NULL;
2979 struct recptypes *valid_to = NULL;
2980 struct recptypes *valid_cc = NULL;
2981 struct recptypes *valid_bcc = NULL;
2988 post = extract_int(entargs, 0);
2989 extract_token(recp, entargs, 1, '|', sizeof recp);
2990 anon_flag = extract_int(entargs, 2);
2991 format_type = extract_int(entargs, 3);
2992 extract_token(subject, entargs, 4, '|', sizeof subject);
2993 do_confirm = extract_int(entargs, 6);
2994 extract_token(cc, entargs, 7, '|', sizeof cc);
2995 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2996 switch(CC->room.QRdefaultview) {
2999 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3002 supplied_euid[0] = 0;
3006 /* first check to make sure the request is valid. */
3008 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3010 cprintf("%d %s\n", err, errmsg);
3014 /* Check some other permission type things. */
3017 if (CC->user.axlevel < 6) {
3018 cprintf("%d You don't have permission to masquerade.\n",
3019 ERROR + HIGHER_ACCESS_REQUIRED);
3022 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3023 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3024 safestrncpy(CC->fake_postname, newusername,
3025 sizeof(CC->fake_postname) );
3026 cprintf("%d ok\n", CIT_OK);
3029 CC->cs_flags |= CS_POSTING;
3031 /* In the Mail> room we have to behave a little differently --
3032 * make sure the user has specified at least one recipient. Then
3033 * validate the recipient(s).
3035 if ( (CC->room.QRflags & QR_MAILBOX)
3036 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3038 if (CC->user.axlevel < 2) {
3039 strcpy(recp, "sysop");
3044 valid_to = validate_recipients(recp);
3045 if (valid_to->num_error > 0) {
3046 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3051 valid_cc = validate_recipients(cc);
3052 if (valid_cc->num_error > 0) {
3053 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3059 valid_bcc = validate_recipients(bcc);
3060 if (valid_bcc->num_error > 0) {
3061 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3068 /* Recipient required, but none were specified */
3069 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3073 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3077 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3078 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3079 cprintf("%d You do not have permission "
3080 "to send Internet mail.\n",
3081 ERROR + HIGHER_ACCESS_REQUIRED);
3089 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)
3090 && (CC->user.axlevel < 4) ) {
3091 cprintf("%d Higher access required for network mail.\n",
3092 ERROR + HIGHER_ACCESS_REQUIRED);
3099 if ((RESTRICT_INTERNET == 1)
3100 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3101 && ((CC->user.flags & US_INTERNET) == 0)
3102 && (!CC->internal_pgm)) {
3103 cprintf("%d You don't have access to Internet mail.\n",
3104 ERROR + HIGHER_ACCESS_REQUIRED);
3113 /* Is this a room which has anonymous-only or anonymous-option? */
3114 anonymous = MES_NORMAL;
3115 if (CC->room.QRflags & QR_ANONONLY) {
3116 anonymous = MES_ANONONLY;
3118 if (CC->room.QRflags & QR_ANONOPT) {
3119 if (anon_flag == 1) { /* only if the user requested it */
3120 anonymous = MES_ANONOPT;
3124 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3128 /* If we're only checking the validity of the request, return
3129 * success without creating the message.
3132 cprintf("%d %s\n", CIT_OK,
3133 ((valid_to != NULL) ? valid_to->display_recp : "") );
3140 /* We don't need these anymore because we'll do it differently below */
3145 /* Handle author masquerading */
3146 if (CC->fake_postname[0]) {
3147 strcpy(masquerade_as, CC->fake_postname);
3149 else if (CC->fake_username[0]) {
3150 strcpy(masquerade_as, CC->fake_username);
3153 strcpy(masquerade_as, "");
3156 /* Read in the message from the client. */
3158 cprintf("%d send message\n", START_CHAT_MODE);
3160 cprintf("%d send message\n", SEND_LISTING);
3163 msg = CtdlMakeMessage(&CC->user, recp, cc,
3164 CC->room.QRname, anonymous, format_type,
3165 masquerade_as, subject,
3166 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3169 /* Put together one big recipients struct containing to/cc/bcc all in
3170 * one. This is for the envelope.
3172 char *all_recps = malloc(SIZ * 3);
3173 strcpy(all_recps, recp);
3174 if (strlen(cc) > 0) {
3175 if (strlen(all_recps) > 0) {
3176 strcat(all_recps, ",");
3178 strcat(all_recps, cc);
3180 if (strlen(bcc) > 0) {
3181 if (strlen(all_recps) > 0) {
3182 strcat(all_recps, ",");
3184 strcat(all_recps, bcc);
3186 if (strlen(all_recps) > 0) {
3187 valid = validate_recipients(all_recps);
3195 msgnum = CtdlSubmitMsg(msg, valid, "");
3198 cprintf("%ld\n", msgnum);
3200 cprintf("Message accepted.\n");
3203 cprintf("Internal error.\n");
3205 if (msg->cm_fields['E'] != NULL) {
3206 cprintf("%s\n", msg->cm_fields['E']);
3213 CtdlFreeMessage(msg);
3215 CC->fake_postname[0] = '\0';
3216 if (valid != NULL) {
3225 * API function to delete messages which match a set of criteria
3226 * (returns the actual number of messages deleted)
3228 int CtdlDeleteMessages(char *room_name, /* which room */
3229 long dmsgnum, /* or "0" for any */
3230 char *content_type, /* or "" for any */
3231 int deferred /* let TDAP sweep it later */
3235 struct ctdlroom qrbuf;
3236 struct cdbdata *cdbfr;
3237 long *msglist = NULL;
3238 long *dellist = NULL;
3241 int num_deleted = 0;
3243 struct MetaData smi;
3245 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3246 room_name, dmsgnum, content_type, deferred);
3248 /* get room record, obtaining a lock... */
3249 if (lgetroom(&qrbuf, room_name) != 0) {
3250 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3252 return (0); /* room not found */
3254 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3256 if (cdbfr != NULL) {
3257 dellist = malloc(cdbfr->len);
3258 msglist = (long *) cdbfr->ptr;
3259 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3260 num_msgs = cdbfr->len / sizeof(long);
3264 for (i = 0; i < num_msgs; ++i) {
3267 /* Set/clear a bit for each criterion */
3269 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3270 delete_this |= 0x01;
3272 if (strlen(content_type) == 0) {
3273 delete_this |= 0x02;
3275 GetMetaData(&smi, msglist[i]);
3276 if (!strcasecmp(smi.meta_content_type,
3278 delete_this |= 0x02;
3282 /* Delete message only if all bits are set */
3283 if (delete_this == 0x03) {
3284 dellist[num_deleted++] = msglist[i];
3289 num_msgs = sort_msglist(msglist, num_msgs);
3290 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3291 msglist, (int)(num_msgs * sizeof(long)));
3293 qrbuf.QRhighest = msglist[num_msgs - 1];
3298 * If the delete operation is "deferred" (and technically, any delete
3299 * operation not performed by THE DREADED AUTO-PURGER ought to be
3300 * a deferred delete) then we save a pointer to the message in the
3301 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3302 * at least 1, which will save the user from having to synchronously
3303 * wait for various disk-intensive operations to complete.
3305 if ( (deferred) && (num_deleted) ) {
3306 for (i=0; i<num_deleted; ++i) {
3307 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3311 /* Go through the messages we pulled out of the index, and decrement
3312 * their reference counts by 1. If this is the only room the message
3313 * was in, the reference count will reach zero and the message will
3314 * automatically be deleted from the database. We do this in a
3315 * separate pass because there might be plug-in hooks getting called,
3316 * and we don't want that happening during an S_ROOMS critical
3319 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3320 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3321 AdjRefCount(dellist[i], -1);
3324 /* Now free the memory we used, and go away. */
3325 if (msglist != NULL) free(msglist);
3326 if (dellist != NULL) free(dellist);
3327 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3328 return (num_deleted);
3334 * Check whether the current user has permission to delete messages from
3335 * the current room (returns 1 for yes, 0 for no)
3337 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3338 getuser(&CC->user, CC->curr_user);
3339 if ((CC->user.axlevel < 6)
3340 && (CC->user.usernum != CC->room.QRroomaide)
3341 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3342 && (!(CC->internal_pgm))) {
3351 * Delete message from current room
3353 void cmd_dele(char *delstr)
3358 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3359 cprintf("%d Higher access required.\n",
3360 ERROR + HIGHER_ACCESS_REQUIRED);
3363 delnum = extract_long(delstr, 0);
3365 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3368 cprintf("%d %d message%s deleted.\n", CIT_OK,
3369 num_deleted, ((num_deleted != 1) ? "s" : ""));
3371 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3377 * Back end API function for moves and deletes
3379 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3382 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3383 if (err != 0) return(err);
3391 * move or copy a message to another room
3393 void cmd_move(char *args)
3396 char targ[ROOMNAMELEN];
3397 struct ctdlroom qtemp;
3403 num = extract_long(args, 0);
3404 extract_token(targ, args, 1, '|', sizeof targ);
3405 convert_room_name_macros(targ, sizeof targ);
3406 targ[ROOMNAMELEN - 1] = 0;
3407 is_copy = extract_int(args, 2);
3409 if (getroom(&qtemp, targ) != 0) {
3410 cprintf("%d '%s' does not exist.\n",
3411 ERROR + ROOM_NOT_FOUND, targ);
3415 getuser(&CC->user, CC->curr_user);
3416 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3418 /* Check for permission to perform this operation.
3419 * Remember: "CC->room" is source, "qtemp" is target.
3423 /* Aides can move/copy */
3424 if (CC->user.axlevel >= 6) permit = 1;
3426 /* Room aides can move/copy */
3427 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3429 /* Permit move/copy from personal rooms */
3430 if ((CC->room.QRflags & QR_MAILBOX)
3431 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3433 /* Permit only copy from public to personal room */
3435 && (!(CC->room.QRflags & QR_MAILBOX))
3436 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3438 /* User must have access to target room */
3439 if (!(ra & UA_KNOWN)) permit = 0;
3442 cprintf("%d Higher access required.\n",
3443 ERROR + HIGHER_ACCESS_REQUIRED);
3447 err = CtdlCopyMsgToRoom(num, targ);
3449 cprintf("%d Cannot store message in %s: error %d\n",
3454 /* Now delete the message from the source room,
3455 * if this is a 'move' rather than a 'copy' operation.
3458 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3461 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3467 * GetMetaData() - Get the supplementary record for a message
3469 void GetMetaData(struct MetaData *smibuf, long msgnum)
3472 struct cdbdata *cdbsmi;
3475 memset(smibuf, 0, sizeof(struct MetaData));
3476 smibuf->meta_msgnum = msgnum;
3477 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3479 /* Use the negative of the message number for its supp record index */
3480 TheIndex = (0L - msgnum);
3482 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3483 if (cdbsmi == NULL) {
3484 return; /* record not found; go with defaults */
3486 memcpy(smibuf, cdbsmi->ptr,
3487 ((cdbsmi->len > sizeof(struct MetaData)) ?
3488 sizeof(struct MetaData) : cdbsmi->len));
3495 * PutMetaData() - (re)write supplementary record for a message
3497 void PutMetaData(struct MetaData *smibuf)
3501 /* Use the negative of the message number for the metadata db index */
3502 TheIndex = (0L - smibuf->meta_msgnum);
3504 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3505 smibuf->meta_msgnum, smibuf->meta_refcount);
3507 cdb_store(CDB_MSGMAIN,
3508 &TheIndex, (int)sizeof(long),
3509 smibuf, (int)sizeof(struct MetaData));
3514 * AdjRefCount - change the reference count for a message;
3515 * delete the message if it reaches zero
3517 void AdjRefCount(long msgnum, int incr)
3520 struct MetaData smi;
3523 /* This is a *tight* critical section; please keep it that way, as
3524 * it may get called while nested in other critical sections.
3525 * Complicating this any further will surely cause deadlock!
3527 begin_critical_section(S_SUPPMSGMAIN);
3528 GetMetaData(&smi, msgnum);
3529 smi.meta_refcount += incr;
3531 end_critical_section(S_SUPPMSGMAIN);
3532 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3533 msgnum, incr, smi.meta_refcount);
3535 /* If the reference count is now zero, delete the message
3536 * (and its supplementary record as well).
3538 if (smi.meta_refcount == 0) {
3539 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3541 /* Remove from fulltext index */
3542 if (config.c_enable_fulltext) {
3543 ft_index_message(msgnum, 0);
3546 /* Remove from message base */
3548 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3549 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3551 /* Remove metadata record */
3552 delnum = (0L - msgnum);
3553 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3558 * Write a generic object to this room
3560 * Note: this could be much more efficient. Right now we use two temporary
3561 * files, and still pull the message into memory as with all others.
3563 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3564 char *content_type, /* MIME type of this object */
3565 char *tempfilename, /* Where to fetch it from */
3566 struct ctdluser *is_mailbox, /* Mailbox room? */
3567 int is_binary, /* Is encoding necessary? */
3568 int is_unique, /* Del others of this type? */
3569 unsigned int flags /* Internal save flags */
3574 struct ctdlroom qrbuf;
3575 char roomname[ROOMNAMELEN];
3576 struct CtdlMessage *msg;
3578 char *raw_message = NULL;
3579 char *encoded_message = NULL;
3580 off_t raw_length = 0;
3582 if (is_mailbox != NULL) {
3583 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3586 safestrncpy(roomname, req_room, sizeof(roomname));
3589 fp = fopen(tempfilename, "rb");
3591 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3592 tempfilename, strerror(errno));
3595 fseek(fp, 0L, SEEK_END);
3596 raw_length = ftell(fp);
3598 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3600 raw_message = malloc((size_t)raw_length + 2);
3601 fread(raw_message, (size_t)raw_length, 1, fp);
3605 encoded_message = malloc((size_t)
3606 (((raw_length * 134) / 100) + 4096 ) );
3609 encoded_message = malloc((size_t)(raw_length + 4096));
3612 sprintf(encoded_message, "Content-type: %s\n", content_type);
3615 sprintf(&encoded_message[strlen(encoded_message)],
3616 "Content-transfer-encoding: base64\n\n"
3620 sprintf(&encoded_message[strlen(encoded_message)],
3621 "Content-transfer-encoding: 7bit\n\n"
3627 &encoded_message[strlen(encoded_message)],
3633 raw_message[raw_length] = 0;
3635 &encoded_message[strlen(encoded_message)],
3643 lprintf(CTDL_DEBUG, "Allocating\n");
3644 msg = malloc(sizeof(struct CtdlMessage));
3645 memset(msg, 0, sizeof(struct CtdlMessage));
3646 msg->cm_magic = CTDLMESSAGE_MAGIC;
3647 msg->cm_anon_type = MES_NORMAL;
3648 msg->cm_format_type = 4;
3649 msg->cm_fields['A'] = strdup(CC->user.fullname);
3650 msg->cm_fields['O'] = strdup(req_room);
3651 msg->cm_fields['N'] = strdup(config.c_nodename);
3652 msg->cm_fields['H'] = strdup(config.c_humannode);
3653 msg->cm_flags = flags;
3655 msg->cm_fields['M'] = encoded_message;
3657 /* Create the requested room if we have to. */
3658 if (getroom(&qrbuf, roomname) != 0) {
3659 create_room(roomname,
3660 ( (is_mailbox != NULL) ? 5 : 3 ),
3661 "", 0, 1, 0, VIEW_BBS);
3663 /* If the caller specified this object as unique, delete all
3664 * other objects of this type that are currently in the room.
3667 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3668 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3671 /* Now write the data */
3672 CtdlSubmitMsg(msg, NULL, roomname);
3673 CtdlFreeMessage(msg);
3681 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3682 config_msgnum = msgnum;
3686 char *CtdlGetSysConfig(char *sysconfname) {
3687 char hold_rm[ROOMNAMELEN];
3690 struct CtdlMessage *msg;
3693 strcpy(hold_rm, CC->room.QRname);
3694 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3695 getroom(&CC->room, hold_rm);
3700 /* We want the last (and probably only) config in this room */
3701 begin_critical_section(S_CONFIG);
3702 config_msgnum = (-1L);
3703 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3704 CtdlGetSysConfigBackend, NULL);
3705 msgnum = config_msgnum;
3706 end_critical_section(S_CONFIG);
3712 msg = CtdlFetchMessage(msgnum, 1);
3714 conf = strdup(msg->cm_fields['M']);
3715 CtdlFreeMessage(msg);
3722 getroom(&CC->room, hold_rm);
3724 if (conf != NULL) do {
3725 extract_token(buf, conf, 0, '\n', sizeof buf);
3726 strcpy(conf, &conf[strlen(buf)+1]);
3727 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3732 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3733 char temp[PATH_MAX];
3736 CtdlMakeTempFileName(temp, sizeof temp);
3738 fp = fopen(temp, "w");
3739 if (fp == NULL) return;
3740 fprintf(fp, "%s", sysconfdata);
3743 /* this handy API function does all the work for us */
3744 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3750 * Determine whether a given Internet address belongs to the current user
3752 int CtdlIsMe(char *addr, int addr_buf_len)
3754 struct recptypes *recp;
3757 recp = validate_recipients(addr);
3758 if (recp == NULL) return(0);
3760 if (recp->num_local == 0) {
3765 for (i=0; i<recp->num_local; ++i) {
3766 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3767 if (!strcasecmp(addr, CC->user.fullname)) {
3779 * Citadel protocol command to do the same
3781 void cmd_isme(char *argbuf) {
3784 if (CtdlAccessCheck(ac_logged_in)) return;
3785 extract_token(addr, argbuf, 0, '|', sizeof addr);
3787 if (CtdlIsMe(addr, sizeof addr)) {
3788 cprintf("%d %s\n", CIT_OK, addr);
3791 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);