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;
145 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
146 stripallbut(name, '<', '>');
149 * DIRTY HACK FOLLOWS! due to configs in the network dir in the
150 * legacy installations, we need to calculate ifdeffed here.
161 fp = fopen(filename, "r");
163 fp = fopen("/dev/null", "r");
170 while (fgets(aaa, sizeof aaa, fp) != NULL) {
171 while (isspace(name[0]))
172 strcpy(name, &name[1]);
173 aaa[strlen(aaa) - 1] = 0;
175 for (a = 0; a < strlen(aaa); ++a) {
177 strcpy(bbb, &aaa[a + 1]);
181 if (!strcasecmp(name, aaa))
186 /* Hit the Global Address Book */
187 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
191 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
193 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
194 for (a=0; a<strlen(name); ++a) {
195 if (name[a] == '@') {
196 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
198 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
203 /* determine local or remote type, see citadel.h */
204 at = haschar(name, '@');
205 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
206 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
207 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
209 /* figure out the delivery mode */
210 extract_token(node, name, 1, '@', sizeof node);
212 /* If there are one or more dots in the nodename, we assume that it
213 * is an FQDN and will attempt SMTP delivery to the Internet.
215 if (haschar(node, '.') > 0) {
216 return(MES_INTERNET);
219 /* Otherwise we look in the IGnet maps for a valid Citadel node.
220 * Try directly-connected nodes first...
222 ignetcfg = CtdlGetSysConfig(IGNETCFG);
223 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
224 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
225 extract_token(testnode, buf, 0, '|', sizeof testnode);
226 if (!strcasecmp(node, testnode)) {
234 * Then try nodes that are two or more hops away.
236 ignetmap = CtdlGetSysConfig(IGNETMAP);
237 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
238 extract_token(buf, ignetmap, i, '\n', sizeof buf);
239 extract_token(testnode, buf, 0, '|', sizeof testnode);
240 if (!strcasecmp(node, testnode)) {
247 /* If we get to this point it's an invalid node name */
256 fp = fopen(file_citadel_control, "r");
258 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
259 file_citadel_control,
263 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
269 * Back end for the MSGS command: output message number only.
271 void simple_listing(long msgnum, void *userdata)
273 cprintf("%ld\n", msgnum);
279 * Back end for the MSGS command: output header summary.
281 void headers_listing(long msgnum, void *userdata)
283 struct CtdlMessage *msg;
285 msg = CtdlFetchMessage(msgnum, 0);
287 cprintf("%ld|0|||||\n", msgnum);
291 cprintf("%ld|%s|%s|%s|%s|%s|\n",
293 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
294 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
295 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
296 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
297 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
299 CtdlFreeMessage(msg);
304 /* Determine if a given message matches the fields in a message template.
305 * Return 0 for a successful match.
307 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
310 /* If there aren't any fields in the template, all messages will
313 if (template == NULL) return(0);
315 /* Null messages are bogus. */
316 if (msg == NULL) return(1);
318 for (i='A'; i<='Z'; ++i) {
319 if (template->cm_fields[i] != NULL) {
320 if (msg->cm_fields[i] == NULL) {
323 if (strcasecmp(msg->cm_fields[i],
324 template->cm_fields[i])) return 1;
328 /* All compares succeeded: we have a match! */
335 * Retrieve the "seen" message list for the current room.
337 void CtdlGetSeen(char *buf, int which_set) {
340 /* Learn about the user and room in question */
341 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
343 if (which_set == ctdlsetseen_seen)
344 safestrncpy(buf, vbuf.v_seen, SIZ);
345 if (which_set == ctdlsetseen_answered)
346 safestrncpy(buf, vbuf.v_answered, SIZ);
352 * Manipulate the "seen msgs" string (or other message set strings)
354 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
355 int target_setting, int which_set,
356 struct ctdluser *which_user, struct ctdlroom *which_room) {
357 struct cdbdata *cdbfr;
369 char *is_set; /* actually an array of booleans */
372 char setstr[SIZ], lostr[SIZ], histr[SIZ];
375 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
376 num_target_msgnums, target_msgnums[0],
377 target_setting, which_set);
379 /* Learn about the user and room in question */
380 CtdlGetRelationship(&vbuf,
381 ((which_user != NULL) ? which_user : &CC->user),
382 ((which_room != NULL) ? which_room : &CC->room)
385 /* Load the message list */
386 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
388 msglist = (long *) cdbfr->ptr;
389 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
390 num_msgs = cdbfr->len / sizeof(long);
393 return; /* No messages at all? No further action. */
396 is_set = malloc(num_msgs * sizeof(char));
397 memset(is_set, 0, (num_msgs * sizeof(char)) );
399 /* Decide which message set we're manipulating */
401 case ctdlsetseen_seen:
402 safestrncpy(vset, vbuf.v_seen, sizeof vset);
404 case ctdlsetseen_answered:
405 safestrncpy(vset, vbuf.v_answered, sizeof vset);
409 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
411 /* Translate the existing sequence set into an array of booleans */
412 num_sets = num_tokens(vset, ',');
413 for (s=0; s<num_sets; ++s) {
414 extract_token(setstr, vset, s, ',', sizeof setstr);
416 extract_token(lostr, setstr, 0, ':', sizeof lostr);
417 if (num_tokens(setstr, ':') >= 2) {
418 extract_token(histr, setstr, 1, ':', sizeof histr);
419 if (!strcmp(histr, "*")) {
420 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
424 strcpy(histr, lostr);
429 for (i = 0; i < num_msgs; ++i) {
430 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
436 /* Now translate the array of booleans back into a sequence set */
441 for (i=0; i<num_msgs; ++i) {
443 is_seen = is_set[i]; /* Default to existing setting */
445 for (k=0; k<num_target_msgnums; ++k) {
446 if (msglist[i] == target_msgnums[k]) {
447 is_seen = target_setting;
452 if (lo < 0L) lo = msglist[i];
456 if ( ((is_seen == 0) && (was_seen == 1))
457 || ((is_seen == 1) && (i == num_msgs-1)) ) {
459 /* begin trim-o-matic code */
462 while ( (strlen(vset) + 20) > sizeof vset) {
463 remove_token(vset, 0, ',');
465 if (j--) break; /* loop no more than 9 times */
467 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
471 snprintf(lostr, sizeof lostr,
472 "1:%ld,%s", t, vset);
473 safestrncpy(vset, lostr, sizeof vset);
475 /* end trim-o-matic code */
483 snprintf(&vset[tmp], (sizeof vset) - tmp,
487 snprintf(&vset[tmp], (sizeof vset) - tmp,
496 /* Decide which message set we're manipulating */
498 case ctdlsetseen_seen:
499 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
501 case ctdlsetseen_answered:
502 safestrncpy(vbuf.v_answered, vset,
503 sizeof vbuf.v_answered);
508 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
510 CtdlSetRelationship(&vbuf,
511 ((which_user != NULL) ? which_user : &CC->user),
512 ((which_room != NULL) ? which_room : &CC->room)
518 * API function to perform an operation for each qualifying message in the
519 * current room. (Returns the number of messages processed.)
521 int CtdlForEachMessage(int mode, long ref,
523 struct CtdlMessage *compare,
524 void (*CallBack) (long, void *),
530 struct cdbdata *cdbfr;
531 long *msglist = NULL;
533 int num_processed = 0;
536 struct CtdlMessage *msg;
539 int printed_lastold = 0;
541 /* Learn about the user and room in question */
543 getuser(&CC->user, CC->curr_user);
544 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
546 /* Load the message list */
547 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
549 msglist = (long *) cdbfr->ptr;
550 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
551 num_msgs = cdbfr->len / sizeof(long);
554 return 0; /* No messages at all? No further action. */
559 * Now begin the traversal.
561 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
563 /* If the caller is looking for a specific MIME type, filter
564 * out all messages which are not of the type requested.
566 if (content_type != NULL) if (strlen(content_type) > 0) {
568 /* This call to GetMetaData() sits inside this loop
569 * so that we only do the extra database read per msg
570 * if we need to. Doing the extra read all the time
571 * really kills the server. If we ever need to use
572 * metadata for another search criterion, we need to
573 * move the read somewhere else -- but still be smart
574 * enough to only do the read if the caller has
575 * specified something that will need it.
577 GetMetaData(&smi, msglist[a]);
579 if (strcasecmp(smi.meta_content_type, content_type)) {
585 num_msgs = sort_msglist(msglist, num_msgs);
587 /* If a template was supplied, filter out the messages which
588 * don't match. (This could induce some delays!)
591 if (compare != NULL) {
592 for (a = 0; a < num_msgs; ++a) {
593 msg = CtdlFetchMessage(msglist[a], 1);
595 if (CtdlMsgCmp(msg, compare)) {
598 CtdlFreeMessage(msg);
606 * Now iterate through the message list, according to the
607 * criteria supplied by the caller.
610 for (a = 0; a < num_msgs; ++a) {
611 thismsg = msglist[a];
612 if (mode == MSGS_ALL) {
616 is_seen = is_msg_in_sequence_set(
617 vbuf.v_seen, thismsg);
618 if (is_seen) lastold = thismsg;
624 || ((mode == MSGS_OLD) && (is_seen))
625 || ((mode == MSGS_NEW) && (!is_seen))
626 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
627 || ((mode == MSGS_FIRST) && (a < ref))
628 || ((mode == MSGS_GT) && (thismsg > ref))
629 || ((mode == MSGS_EQ) && (thismsg == ref))
632 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
634 CallBack(lastold, userdata);
638 if (CallBack) CallBack(thismsg, userdata);
642 free(msglist); /* Clean up */
643 return num_processed;
649 * cmd_msgs() - get list of message #'s in this room
650 * implements the MSGS server command using CtdlForEachMessage()
652 void cmd_msgs(char *cmdbuf)
661 int with_template = 0;
662 struct CtdlMessage *template = NULL;
663 int with_headers = 0;
665 extract_token(which, cmdbuf, 0, '|', sizeof which);
666 cm_ref = extract_int(cmdbuf, 1);
667 with_template = extract_int(cmdbuf, 2);
668 with_headers = extract_int(cmdbuf, 3);
672 if (!strncasecmp(which, "OLD", 3))
674 else if (!strncasecmp(which, "NEW", 3))
676 else if (!strncasecmp(which, "FIRST", 5))
678 else if (!strncasecmp(which, "LAST", 4))
680 else if (!strncasecmp(which, "GT", 2))
683 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
684 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
690 cprintf("%d Send template then receive message list\n",
692 template = (struct CtdlMessage *)
693 malloc(sizeof(struct CtdlMessage));
694 memset(template, 0, sizeof(struct CtdlMessage));
695 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
696 extract_token(tfield, buf, 0, '|', sizeof tfield);
697 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
698 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
699 if (!strcasecmp(tfield, msgkeys[i])) {
700 template->cm_fields[i] =
708 cprintf("%d \n", LISTING_FOLLOWS);
711 CtdlForEachMessage(mode,
715 (with_headers ? headers_listing : simple_listing),
718 if (template != NULL) CtdlFreeMessage(template);
726 * help_subst() - support routine for help file viewer
728 void help_subst(char *strbuf, char *source, char *dest)
733 while (p = pattern2(strbuf, source), (p >= 0)) {
734 strcpy(workbuf, &strbuf[p + strlen(source)]);
735 strcpy(&strbuf[p], dest);
736 strcat(strbuf, workbuf);
741 void do_help_subst(char *buffer)
745 help_subst(buffer, "^nodename", config.c_nodename);
746 help_subst(buffer, "^humannode", config.c_humannode);
747 help_subst(buffer, "^fqdn", config.c_fqdn);
748 help_subst(buffer, "^username", CC->user.fullname);
749 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
750 help_subst(buffer, "^usernum", buf2);
751 help_subst(buffer, "^sysadm", config.c_sysadm);
752 help_subst(buffer, "^variantname", CITADEL);
753 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
754 help_subst(buffer, "^maxsessions", buf2);
755 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
761 * memfmout() - Citadel text formatter and paginator.
762 * Although the original purpose of this routine was to format
763 * text to the reader's screen width, all we're really using it
764 * for here is to format text out to 80 columns before sending it
765 * to the client. The client software may reformat it again.
768 int width, /* screen width to use */
769 char *mptr, /* where are we going to get our text from? */
770 char subst, /* nonzero if we should do substitutions */
771 char *nl) /* string to terminate lines with */
783 c = 1; /* c is the current pos */
787 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
789 buffer[strlen(buffer) + 1] = 0;
790 buffer[strlen(buffer)] = ch;
793 if (buffer[0] == '^')
794 do_help_subst(buffer);
796 buffer[strlen(buffer) + 1] = 0;
798 strcpy(buffer, &buffer[1]);
806 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
808 if (((old == 13) || (old == 10)) && (isspace(real))) {
816 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
817 cprintf("%s%s", nl, aaa);
826 if ((strlen(aaa) + c) > (width - 5)) {
835 if ((ch == 13) || (ch == 10)) {
836 cprintf("%s%s", aaa, nl);
843 cprintf("%s%s", aaa, nl);
849 * Callback function for mime parser that simply lists the part
851 void list_this_part(char *name, char *filename, char *partnum, char *disp,
852 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
857 ma = (struct ma_info *)cbuserdata;
858 if (ma->is_ma == 0) {
859 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
860 name, filename, partnum, disp, cbtype, (long)length);
865 * Callback function for multipart prefix
867 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
868 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
873 ma = (struct ma_info *)cbuserdata;
874 if (!strcasecmp(cbtype, "multipart/alternative")) {
878 if (ma->is_ma == 0) {
879 cprintf("pref=%s|%s\n", partnum, cbtype);
884 * Callback function for multipart sufffix
886 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
887 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
892 ma = (struct ma_info *)cbuserdata;
893 if (ma->is_ma == 0) {
894 cprintf("suff=%s|%s\n", partnum, cbtype);
896 if (!strcasecmp(cbtype, "multipart/alternative")) {
903 * Callback function for mime parser that opens a section for downloading
905 void mime_download(char *name, char *filename, char *partnum, char *disp,
906 void *content, char *cbtype, char *cbcharset, size_t length,
907 char *encoding, void *cbuserdata)
910 /* Silently go away if there's already a download open... */
911 if (CC->download_fp != NULL)
914 /* ...or if this is not the desired section */
915 if (strcasecmp(CC->download_desired_section, partnum))
918 CC->download_fp = tmpfile();
919 if (CC->download_fp == NULL)
922 fwrite(content, length, 1, CC->download_fp);
923 fflush(CC->download_fp);
924 rewind(CC->download_fp);
926 OpenCmdResult(filename, cbtype);
932 * Load a message from disk into memory.
933 * This is used by CtdlOutputMsg() and other fetch functions.
935 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
936 * using the CtdlMessageFree() function.
938 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
940 struct cdbdata *dmsgtext;
941 struct CtdlMessage *ret = NULL;
945 cit_uint8_t field_header;
947 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
949 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
950 if (dmsgtext == NULL) {
953 mptr = dmsgtext->ptr;
954 upper_bound = mptr + dmsgtext->len;
956 /* Parse the three bytes that begin EVERY message on disk.
957 * The first is always 0xFF, the on-disk magic number.
958 * The second is the anonymous/public type byte.
959 * The third is the format type byte (vari, fixed, or MIME).
964 "Message %ld appears to be corrupted.\n",
969 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
970 memset(ret, 0, sizeof(struct CtdlMessage));
972 ret->cm_magic = CTDLMESSAGE_MAGIC;
973 ret->cm_anon_type = *mptr++; /* Anon type byte */
974 ret->cm_format_type = *mptr++; /* Format type byte */
977 * The rest is zero or more arbitrary fields. Load them in.
978 * We're done when we encounter either a zero-length field or
979 * have just processed the 'M' (message text) field.
982 if (mptr >= upper_bound) {
985 field_header = *mptr++;
986 ret->cm_fields[field_header] = strdup(mptr);
988 while (*mptr++ != 0); /* advance to next field */
990 } while ((mptr < upper_bound) && (field_header != 'M'));
994 /* Always make sure there's something in the msg text field. If
995 * it's NULL, the message text is most likely stored separately,
996 * so go ahead and fetch that. Failing that, just set a dummy
997 * body so other code doesn't barf.
999 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1000 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1001 if (dmsgtext != NULL) {
1002 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1006 if (ret->cm_fields['M'] == NULL) {
1007 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1010 /* Perform "before read" hooks (aborting if any return nonzero) */
1011 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1012 CtdlFreeMessage(ret);
1021 * Returns 1 if the supplied pointer points to a valid Citadel message.
1022 * If the pointer is NULL or the magic number check fails, returns 0.
1024 int is_valid_message(struct CtdlMessage *msg) {
1027 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1028 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1036 * 'Destructor' for struct CtdlMessage
1038 void CtdlFreeMessage(struct CtdlMessage *msg)
1042 if (is_valid_message(msg) == 0) return;
1044 for (i = 0; i < 256; ++i)
1045 if (msg->cm_fields[i] != NULL) {
1046 free(msg->cm_fields[i]);
1049 msg->cm_magic = 0; /* just in case */
1055 * Pre callback function for multipart/alternative
1057 * NOTE: this differs from the standard behavior for a reason. Normally when
1058 * displaying multipart/alternative you want to show the _last_ usable
1059 * format in the message. Here we show the _first_ one, because it's
1060 * usually text/plain. Since this set of functions is designed for text
1061 * output to non-MIME-aware clients, this is the desired behavior.
1064 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1065 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1070 ma = (struct ma_info *)cbuserdata;
1071 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1072 if (!strcasecmp(cbtype, "multipart/alternative")) {
1076 if (!strcasecmp(cbtype, "message/rfc822")) {
1082 * Post callback function for multipart/alternative
1084 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1085 void *content, char *cbtype, char *cbcharset, size_t length,
1086 char *encoding, void *cbuserdata)
1090 ma = (struct ma_info *)cbuserdata;
1091 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1092 if (!strcasecmp(cbtype, "multipart/alternative")) {
1096 if (!strcasecmp(cbtype, "message/rfc822")) {
1102 * Inline callback function for mime parser that wants to display text
1104 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1105 void *content, char *cbtype, char *cbcharset, size_t length,
1106 char *encoding, void *cbuserdata)
1113 ma = (struct ma_info *)cbuserdata;
1116 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1117 partnum, filename, cbtype, (long)length);
1120 * If we're in the middle of a multipart/alternative scope and
1121 * we've already printed another section, skip this one.
1123 if ( (ma->is_ma) && (ma->did_print) ) {
1124 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1130 if ( (!strcasecmp(cbtype, "text/plain"))
1131 || (strlen(cbtype)==0) ) {
1134 client_write(wptr, length);
1135 if (wptr[length-1] != '\n') {
1140 else if (!strcasecmp(cbtype, "text/html")) {
1141 ptr = html_to_ascii(content, length, 80, 0);
1143 client_write(ptr, wlen);
1144 if (ptr[wlen-1] != '\n') {
1149 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1150 /* above function returns nonzero if it handled the part */
1152 else if (strncasecmp(cbtype, "multipart/", 10)) {
1153 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1154 partnum, filename, cbtype, (long)length);
1159 * The client is elegant and sophisticated and wants to be choosy about
1160 * MIME content types, so figure out which multipart/alternative part
1161 * we're going to send.
1163 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1164 void *content, char *cbtype, char *cbcharset, size_t length,
1165 char *encoding, void *cbuserdata)
1171 ma = (struct ma_info *)cbuserdata;
1173 if (ma->is_ma > 0) {
1174 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1175 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1176 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1177 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1184 * Now that we've chosen our preferred part, output it.
1186 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1187 void *content, char *cbtype, char *cbcharset, size_t length,
1188 char *encoding, void *cbuserdata)
1192 int add_newline = 0;
1196 ma = (struct ma_info *)cbuserdata;
1198 /* This is not the MIME part you're looking for... */
1199 if (strcasecmp(partnum, ma->chosen_part)) return;
1201 /* If the content-type of this part is in our preferred formats
1202 * list, we can simply output it verbatim.
1204 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1205 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1206 if (!strcasecmp(buf, cbtype)) {
1207 /* Yeah! Go! W00t!! */
1209 text_content = (char *)content;
1210 if (text_content[length-1] != '\n') {
1214 cprintf("Content-type: %s", cbtype);
1215 if (strlen(cbcharset) > 0) {
1216 cprintf("; charset=%s", cbcharset);
1218 cprintf("\nContent-length: %d\n",
1219 (int)(length + add_newline) );
1220 if (strlen(encoding) > 0) {
1221 cprintf("Content-transfer-encoding: %s\n", encoding);
1224 cprintf("Content-transfer-encoding: 7bit\n");
1227 client_write(content, length);
1228 if (add_newline) cprintf("\n");
1233 /* No translations required or possible: output as text/plain */
1234 cprintf("Content-type: text/plain\n\n");
1235 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1236 length, encoding, cbuserdata);
1241 char desired_section[64];
1248 * Callback function for
1250 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1251 void *content, char *cbtype, char *cbcharset, size_t length,
1252 char *encoding, void *cbuserdata)
1254 struct encapmsg *encap;
1256 encap = (struct encapmsg *)cbuserdata;
1258 /* Only proceed if this is the desired section... */
1259 if (!strcasecmp(encap->desired_section, partnum)) {
1260 encap->msglen = length;
1261 encap->msg = malloc(length + 2);
1262 memcpy(encap->msg, content, length);
1272 * Get a message off disk. (returns om_* values found in msgbase.h)
1275 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1276 int mode, /* how would you like that message? */
1277 int headers_only, /* eschew the message body? */
1278 int do_proto, /* do Citadel protocol responses? */
1279 int crlf, /* Use CRLF newlines instead of LF? */
1280 char *section /* NULL or a message/rfc822 section */
1282 struct CtdlMessage *TheMessage = NULL;
1283 int retcode = om_no_such_msg;
1284 struct encapmsg encap;
1286 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1288 (section ? section : "<>")
1291 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1292 if (do_proto) cprintf("%d Not logged in.\n",
1293 ERROR + NOT_LOGGED_IN);
1294 return(om_not_logged_in);
1297 /* FIXME: check message id against msglist for this room */
1300 * Fetch the message from disk. If we're in any sort of headers
1301 * only mode, request that we don't even bother loading the body
1304 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1305 TheMessage = CtdlFetchMessage(msg_num, 0);
1308 TheMessage = CtdlFetchMessage(msg_num, 1);
1311 if (TheMessage == NULL) {
1312 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1313 ERROR + MESSAGE_NOT_FOUND, msg_num);
1314 return(om_no_such_msg);
1317 /* Here is the weird form of this command, to process only an
1318 * encapsulated message/rfc822 section.
1320 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1321 memset(&encap, 0, sizeof encap);
1322 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1323 mime_parser(TheMessage->cm_fields['M'],
1325 *extract_encapsulated_message,
1326 NULL, NULL, (void *)&encap, 0
1328 CtdlFreeMessage(TheMessage);
1332 encap.msg[encap.msglen] = 0;
1333 TheMessage = convert_internet_message(encap.msg);
1334 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1336 /* Now we let it fall through to the bottom of this
1337 * function, because TheMessage now contains the
1338 * encapsulated message instead of the top-level
1339 * message. Isn't that neat?
1344 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1345 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1346 retcode = om_no_such_msg;
1351 /* Ok, output the message now */
1352 retcode = CtdlOutputPreLoadedMsg(
1354 headers_only, do_proto, crlf);
1355 CtdlFreeMessage(TheMessage);
1362 * Get a message off disk. (returns om_* values found in msgbase.h)
1365 int CtdlOutputPreLoadedMsg(
1366 struct CtdlMessage *TheMessage,
1367 int mode, /* how would you like that message? */
1368 int headers_only, /* eschew the message body? */
1369 int do_proto, /* do Citadel protocol responses? */
1370 int crlf /* Use CRLF newlines instead of LF? */
1376 char display_name[256];
1378 char *nl; /* newline string */
1380 int subject_found = 0;
1383 /* Buffers needed for RFC822 translation. These are all filled
1384 * using functions that are bounds-checked, and therefore we can
1385 * make them substantially smaller than SIZ.
1393 char datestamp[100];
1395 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1396 ((TheMessage == NULL) ? "NULL" : "not null"),
1397 mode, headers_only, do_proto, crlf);
1399 strcpy(mid, "unknown");
1400 nl = (crlf ? "\r\n" : "\n");
1402 if (!is_valid_message(TheMessage)) {
1404 "ERROR: invalid preloaded message for output\n");
1405 return(om_no_such_msg);
1408 /* Are we downloading a MIME component? */
1409 if (mode == MT_DOWNLOAD) {
1410 if (TheMessage->cm_format_type != FMT_RFC822) {
1412 cprintf("%d This is not a MIME message.\n",
1413 ERROR + ILLEGAL_VALUE);
1414 } else if (CC->download_fp != NULL) {
1415 if (do_proto) cprintf(
1416 "%d You already have a download open.\n",
1417 ERROR + RESOURCE_BUSY);
1419 /* Parse the message text component */
1420 mptr = TheMessage->cm_fields['M'];
1421 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1422 /* If there's no file open by this time, the requested
1423 * section wasn't found, so print an error
1425 if (CC->download_fp == NULL) {
1426 if (do_proto) cprintf(
1427 "%d Section %s not found.\n",
1428 ERROR + FILE_NOT_FOUND,
1429 CC->download_desired_section);
1432 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1435 /* now for the user-mode message reading loops */
1436 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1438 /* Does the caller want to skip the headers? */
1439 if (headers_only == HEADERS_NONE) goto START_TEXT;
1441 /* Tell the client which format type we're using. */
1442 if ( (mode == MT_CITADEL) && (do_proto) ) {
1443 cprintf("type=%d\n", TheMessage->cm_format_type);
1446 /* nhdr=yes means that we're only displaying headers, no body */
1447 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1448 && (mode == MT_CITADEL)
1451 cprintf("nhdr=yes\n");
1454 /* begin header processing loop for Citadel message format */
1456 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1458 safestrncpy(display_name, "<unknown>", sizeof display_name);
1459 if (TheMessage->cm_fields['A']) {
1460 strcpy(buf, TheMessage->cm_fields['A']);
1461 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1462 safestrncpy(display_name, "****", sizeof display_name);
1464 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1465 safestrncpy(display_name, "anonymous", sizeof display_name);
1468 safestrncpy(display_name, buf, sizeof display_name);
1470 if ((is_room_aide())
1471 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1472 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1473 size_t tmp = strlen(display_name);
1474 snprintf(&display_name[tmp],
1475 sizeof display_name - tmp,
1480 /* Don't show Internet address for users on the
1481 * local Citadel network.
1484 if (TheMessage->cm_fields['N'] != NULL)
1485 if (strlen(TheMessage->cm_fields['N']) > 0)
1486 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1490 /* Now spew the header fields in the order we like them. */
1491 safestrncpy(allkeys, FORDER, sizeof allkeys);
1492 for (i=0; i<strlen(allkeys); ++i) {
1493 k = (int) allkeys[i];
1495 if ( (TheMessage->cm_fields[k] != NULL)
1496 && (msgkeys[k] != NULL) ) {
1498 if (do_proto) cprintf("%s=%s\n",
1502 else if ((k == 'F') && (suppress_f)) {
1505 /* Masquerade display name if needed */
1507 if (do_proto) cprintf("%s=%s\n",
1509 TheMessage->cm_fields[k]
1518 /* begin header processing loop for RFC822 transfer format */
1523 strcpy(snode, NODENAME);
1524 strcpy(lnode, HUMANNODE);
1525 if (mode == MT_RFC822) {
1526 for (i = 0; i < 256; ++i) {
1527 if (TheMessage->cm_fields[i]) {
1528 mptr = TheMessage->cm_fields[i];
1531 safestrncpy(luser, mptr, sizeof luser);
1532 safestrncpy(suser, mptr, sizeof suser);
1534 else if (i == 'Y') {
1535 cprintf("CC: %s%s", mptr, nl);
1537 else if (i == 'U') {
1538 cprintf("Subject: %s%s", mptr, nl);
1542 safestrncpy(mid, mptr, sizeof mid);
1544 safestrncpy(lnode, mptr, sizeof lnode);
1546 safestrncpy(fuser, mptr, sizeof fuser);
1547 /* else if (i == 'O')
1548 cprintf("X-Citadel-Room: %s%s",
1551 safestrncpy(snode, mptr, sizeof snode);
1553 cprintf("To: %s%s", mptr, nl);
1554 else if (i == 'T') {
1555 datestring(datestamp, sizeof datestamp,
1556 atol(mptr), DATESTRING_RFC822);
1557 cprintf("Date: %s%s", datestamp, nl);
1561 if (subject_found == 0) {
1562 cprintf("Subject: (no subject)%s", nl);
1566 for (i=0; i<strlen(suser); ++i) {
1567 suser[i] = tolower(suser[i]);
1568 if (!isalnum(suser[i])) suser[i]='_';
1571 if (mode == MT_RFC822) {
1572 if (!strcasecmp(snode, NODENAME)) {
1573 safestrncpy(snode, FQDN, sizeof snode);
1576 /* Construct a fun message id */
1577 cprintf("Message-ID: <%s", mid);
1578 if (strchr(mid, '@')==NULL) {
1579 cprintf("@%s", snode);
1583 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1584 cprintf("From: \"----\" <x@x.org>%s", nl);
1586 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1587 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1589 else if (strlen(fuser) > 0) {
1590 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1593 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1596 cprintf("Organization: %s%s", lnode, nl);
1598 /* Blank line signifying RFC822 end-of-headers */
1599 if (TheMessage->cm_format_type != FMT_RFC822) {
1604 /* end header processing loop ... at this point, we're in the text */
1606 if (headers_only == HEADERS_FAST) goto DONE;
1607 mptr = TheMessage->cm_fields['M'];
1609 /* Tell the client about the MIME parts in this message */
1610 if (TheMessage->cm_format_type == FMT_RFC822) {
1611 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1612 memset(&ma, 0, sizeof(struct ma_info));
1613 mime_parser(mptr, NULL,
1614 (do_proto ? *list_this_part : NULL),
1615 (do_proto ? *list_this_pref : NULL),
1616 (do_proto ? *list_this_suff : NULL),
1619 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1620 char *start_of_text = NULL;
1621 start_of_text = strstr(mptr, "\n\r\n");
1622 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1623 if (start_of_text == NULL) start_of_text = mptr;
1625 start_of_text = strstr(start_of_text, "\n");
1627 while (ch=*mptr, ch!=0) {
1631 else switch(headers_only) {
1633 if (mptr >= start_of_text) {
1634 if (ch == 10) cprintf("%s", nl);
1635 else cprintf("%c", ch);
1639 if (mptr < start_of_text) {
1640 if (ch == 10) cprintf("%s", nl);
1641 else cprintf("%c", ch);
1645 if (ch == 10) cprintf("%s", nl);
1646 else cprintf("%c", ch);
1655 if (headers_only == HEADERS_ONLY) {
1659 /* signify start of msg text */
1660 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1661 if (do_proto) cprintf("text\n");
1664 /* If the format type on disk is 1 (fixed-format), then we want
1665 * everything to be output completely literally ... regardless of
1666 * what message transfer format is in use.
1668 if (TheMessage->cm_format_type == FMT_FIXED) {
1669 if (mode == MT_MIME) {
1670 cprintf("Content-type: text/plain\n\n");
1673 while (ch = *mptr++, ch > 0) {
1676 if ((ch == 10) || (strlen(buf) > 250)) {
1677 cprintf("%s%s", buf, nl);
1680 buf[strlen(buf) + 1] = 0;
1681 buf[strlen(buf)] = ch;
1684 if (strlen(buf) > 0)
1685 cprintf("%s%s", buf, nl);
1688 /* If the message on disk is format 0 (Citadel vari-format), we
1689 * output using the formatter at 80 columns. This is the final output
1690 * form if the transfer format is RFC822, but if the transfer format
1691 * is Citadel proprietary, it'll still work, because the indentation
1692 * for new paragraphs is correct and the client will reformat the
1693 * message to the reader's screen width.
1695 if (TheMessage->cm_format_type == FMT_CITADEL) {
1696 if (mode == MT_MIME) {
1697 cprintf("Content-type: text/x-citadel-variformat\n\n");
1699 memfmout(80, mptr, 0, nl);
1702 /* If the message on disk is format 4 (MIME), we've gotta hand it
1703 * off to the MIME parser. The client has already been told that
1704 * this message is format 1 (fixed format), so the callback function
1705 * we use will display those parts as-is.
1707 if (TheMessage->cm_format_type == FMT_RFC822) {
1708 memset(&ma, 0, sizeof(struct ma_info));
1710 if (mode == MT_MIME) {
1711 strcpy(ma.chosen_part, "1");
1712 mime_parser(mptr, NULL,
1713 *choose_preferred, *fixed_output_pre,
1714 *fixed_output_post, (void *)&ma, 0);
1715 mime_parser(mptr, NULL,
1716 *output_preferred, NULL, NULL, (void *)&ma, 0);
1719 mime_parser(mptr, NULL,
1720 *fixed_output, *fixed_output_pre,
1721 *fixed_output_post, (void *)&ma, 0);
1726 DONE: /* now we're done */
1727 if (do_proto) cprintf("000\n");
1734 * display a message (mode 0 - Citadel proprietary)
1736 void cmd_msg0(char *cmdbuf)
1739 int headers_only = HEADERS_ALL;
1741 msgid = extract_long(cmdbuf, 0);
1742 headers_only = extract_int(cmdbuf, 1);
1744 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1750 * display a message (mode 2 - RFC822)
1752 void cmd_msg2(char *cmdbuf)
1755 int headers_only = HEADERS_ALL;
1757 msgid = extract_long(cmdbuf, 0);
1758 headers_only = extract_int(cmdbuf, 1);
1760 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1766 * display a message (mode 3 - IGnet raw format - internal programs only)
1768 void cmd_msg3(char *cmdbuf)
1771 struct CtdlMessage *msg;
1774 if (CC->internal_pgm == 0) {
1775 cprintf("%d This command is for internal programs only.\n",
1776 ERROR + HIGHER_ACCESS_REQUIRED);
1780 msgnum = extract_long(cmdbuf, 0);
1781 msg = CtdlFetchMessage(msgnum, 1);
1783 cprintf("%d Message %ld not found.\n",
1784 ERROR + MESSAGE_NOT_FOUND, msgnum);
1788 serialize_message(&smr, msg);
1789 CtdlFreeMessage(msg);
1792 cprintf("%d Unable to serialize message\n",
1793 ERROR + INTERNAL_ERROR);
1797 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1798 client_write((char *)smr.ser, (int)smr.len);
1805 * Display a message using MIME content types
1807 void cmd_msg4(char *cmdbuf)
1812 msgid = extract_long(cmdbuf, 0);
1813 extract_token(section, cmdbuf, 1, '|', sizeof section);
1814 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1820 * Client tells us its preferred message format(s)
1822 void cmd_msgp(char *cmdbuf)
1824 safestrncpy(CC->preferred_formats, cmdbuf,
1825 sizeof(CC->preferred_formats));
1826 cprintf("%d ok\n", CIT_OK);
1831 * Open a component of a MIME message as a download file
1833 void cmd_opna(char *cmdbuf)
1836 char desired_section[128];
1838 msgid = extract_long(cmdbuf, 0);
1839 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1840 safestrncpy(CC->download_desired_section, desired_section,
1841 sizeof CC->download_desired_section);
1842 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1847 * Save a message pointer into a specified room
1848 * (Returns 0 for success, nonzero for failure)
1849 * roomname may be NULL to use the current room
1851 * Note that the 'supplied_msg' field may be set to NULL, in which case
1852 * the message will be fetched from disk, by number, if we need to perform
1853 * replication checks. This adds an additional database read, so if the
1854 * caller already has the message in memory then it should be supplied.
1856 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1857 struct CtdlMessage *supplied_msg) {
1859 char hold_rm[ROOMNAMELEN];
1860 struct cdbdata *cdbfr;
1863 long highest_msg = 0L;
1864 struct CtdlMessage *msg = NULL;
1866 /*lprintf(CTDL_DEBUG,
1867 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1868 roomname, msgid, do_repl_check);*/
1870 strcpy(hold_rm, CC->room.QRname);
1872 /* Now the regular stuff */
1873 if (lgetroom(&CC->room,
1874 ((roomname != NULL) ? roomname : CC->room.QRname) )
1876 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1877 return(ERROR + ROOM_NOT_FOUND);
1880 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1881 if (cdbfr == NULL) {
1885 msglist = (long *) cdbfr->ptr;
1886 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1887 num_msgs = cdbfr->len / sizeof(long);
1891 /* Make sure the message doesn't already exist in this room. It
1892 * is absolutely taboo to have more than one reference to the same
1893 * message in a room.
1895 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1896 if (msglist[i] == msgid) {
1897 lputroom(&CC->room); /* unlock the room */
1898 getroom(&CC->room, hold_rm);
1900 return(ERROR + ALREADY_EXISTS);
1904 /* Now add the new message */
1906 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1908 if (msglist == NULL) {
1909 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1911 msglist[num_msgs - 1] = msgid;
1913 /* Sort the message list, so all the msgid's are in order */
1914 num_msgs = sort_msglist(msglist, num_msgs);
1916 /* Determine the highest message number */
1917 highest_msg = msglist[num_msgs - 1];
1919 /* Write it back to disk. */
1920 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1921 msglist, (int)(num_msgs * sizeof(long)));
1923 /* Free up the memory we used. */
1926 /* Update the highest-message pointer and unlock the room. */
1927 CC->room.QRhighest = highest_msg;
1928 lputroom(&CC->room);
1930 /* Perform replication checks if necessary */
1931 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1932 if (supplied_msg != NULL) {
1936 msg = CtdlFetchMessage(msgid, 0);
1940 ReplicationChecks(msg);
1945 /* If the message has an Exclusive ID, index that... */
1947 if (msg->cm_fields['E'] != NULL) {
1948 index_message_by_euid(msg->cm_fields['E'],
1953 /* Free up the memory we may have allocated */
1954 if ( (msg != NULL) && (msg != supplied_msg) ) {
1955 CtdlFreeMessage(msg);
1958 /* Go back to the room we were in before we wandered here... */
1959 getroom(&CC->room, hold_rm);
1961 /* Bump the reference count for this message. */
1962 AdjRefCount(msgid, +1);
1964 /* Return success. */
1971 * Message base operation to save a new message to the message store
1972 * (returns new message number)
1974 * This is the back end for CtdlSubmitMsg() and should not be directly
1975 * called by server-side modules.
1978 long send_message(struct CtdlMessage *msg) {
1986 /* Get a new message number */
1987 newmsgid = get_new_message_number();
1988 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1990 /* Generate an ID if we don't have one already */
1991 if (msg->cm_fields['I']==NULL) {
1992 msg->cm_fields['I'] = strdup(msgidbuf);
1995 /* If the message is big, set its body aside for storage elsewhere */
1996 if (msg->cm_fields['M'] != NULL) {
1997 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1999 holdM = msg->cm_fields['M'];
2000 msg->cm_fields['M'] = NULL;
2004 /* Serialize our data structure for storage in the database */
2005 serialize_message(&smr, msg);
2008 msg->cm_fields['M'] = holdM;
2012 cprintf("%d Unable to serialize message\n",
2013 ERROR + INTERNAL_ERROR);
2017 /* Write our little bundle of joy into the message base */
2018 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2019 smr.ser, smr.len) < 0) {
2020 lprintf(CTDL_ERR, "Can't store message\n");
2024 cdb_store(CDB_BIGMSGS,
2034 /* Free the memory we used for the serialized message */
2037 /* Return the *local* message ID to the caller
2038 * (even if we're storing an incoming network message)
2046 * Serialize a struct CtdlMessage into the format used on disk and network.
2048 * This function loads up a "struct ser_ret" (defined in server.h) which
2049 * contains the length of the serialized message and a pointer to the
2050 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2052 void serialize_message(struct ser_ret *ret, /* return values */
2053 struct CtdlMessage *msg) /* unserialized msg */
2057 static char *forder = FORDER;
2059 if (is_valid_message(msg) == 0) return; /* self check */
2062 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2063 ret->len = ret->len +
2064 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2066 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2067 ret->ser = malloc(ret->len);
2068 if (ret->ser == NULL) {
2074 ret->ser[1] = msg->cm_anon_type;
2075 ret->ser[2] = msg->cm_format_type;
2078 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2079 ret->ser[wlen++] = (char)forder[i];
2080 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2081 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2083 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2084 (long)ret->len, (long)wlen);
2092 * Check to see if any messages already exist in the current room which
2093 * carry the same Exclusive ID as this one. If any are found, delete them.
2095 void ReplicationChecks(struct CtdlMessage *msg) {
2096 long old_msgnum = (-1L);
2098 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2100 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2103 /* No exclusive id? Don't do anything. */
2104 if (msg == NULL) return;
2105 if (msg->cm_fields['E'] == NULL) return;
2106 if (strlen(msg->cm_fields['E']) == 0) return;
2107 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2108 msg->cm_fields['E'], CC->room.QRname);*/
2110 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2111 if (old_msgnum > 0L) {
2112 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2113 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2120 * Save a message to disk and submit it into the delivery system.
2122 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2123 struct recptypes *recps, /* recipients (if mail) */
2124 char *force /* force a particular room? */
2126 char submit_filename[128];
2127 char generated_timestamp[32];
2128 char hold_rm[ROOMNAMELEN];
2129 char actual_rm[ROOMNAMELEN];
2130 char force_room[ROOMNAMELEN];
2131 char content_type[SIZ]; /* We have to learn this */
2132 char recipient[SIZ];
2135 struct ctdluser userbuf;
2137 struct MetaData smi;
2138 FILE *network_fp = NULL;
2139 static int seqnum = 1;
2140 struct CtdlMessage *imsg = NULL;
2143 char *hold_R, *hold_D;
2144 char *collected_addresses = NULL;
2145 struct addresses_to_be_filed *aptr = NULL;
2146 char *saved_rfc822_version = NULL;
2147 int qualified_for_journaling = 0;
2149 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2150 if (is_valid_message(msg) == 0) return(-1); /* self check */
2152 /* If this message has no timestamp, we take the liberty of
2153 * giving it one, right now.
2155 if (msg->cm_fields['T'] == NULL) {
2156 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2157 msg->cm_fields['T'] = strdup(generated_timestamp);
2160 /* If this message has no path, we generate one.
2162 if (msg->cm_fields['P'] == NULL) {
2163 if (msg->cm_fields['A'] != NULL) {
2164 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2165 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2166 if (isspace(msg->cm_fields['P'][a])) {
2167 msg->cm_fields['P'][a] = ' ';
2172 msg->cm_fields['P'] = strdup("unknown");
2176 if (force == NULL) {
2177 strcpy(force_room, "");
2180 strcpy(force_room, force);
2183 /* Learn about what's inside, because it's what's inside that counts */
2184 if (msg->cm_fields['M'] == NULL) {
2185 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2189 switch (msg->cm_format_type) {
2191 strcpy(content_type, "text/x-citadel-variformat");
2194 strcpy(content_type, "text/plain");
2197 strcpy(content_type, "text/plain");
2198 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2200 safestrncpy(content_type, &mptr[14],
2201 sizeof content_type);
2202 for (a = 0; a < strlen(content_type); ++a) {
2203 if ((content_type[a] == ';')
2204 || (content_type[a] == ' ')
2205 || (content_type[a] == 13)
2206 || (content_type[a] == 10)) {
2207 content_type[a] = 0;
2213 /* Goto the correct room */
2214 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2215 strcpy(hold_rm, CC->room.QRname);
2216 strcpy(actual_rm, CC->room.QRname);
2217 if (recps != NULL) {
2218 strcpy(actual_rm, SENTITEMS);
2221 /* If the user is a twit, move to the twit room for posting */
2223 if (CC->user.axlevel == 2) {
2224 strcpy(hold_rm, actual_rm);
2225 strcpy(actual_rm, config.c_twitroom);
2226 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2230 /* ...or if this message is destined for Aide> then go there. */
2231 if (strlen(force_room) > 0) {
2232 strcpy(actual_rm, force_room);
2235 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2236 if (strcasecmp(actual_rm, CC->room.QRname)) {
2237 /* getroom(&CC->room, actual_rm); */
2238 usergoto(actual_rm, 0, 1, NULL, NULL);
2242 * If this message has no O (room) field, generate one.
2244 if (msg->cm_fields['O'] == NULL) {
2245 msg->cm_fields['O'] = strdup(CC->room.QRname);
2248 /* Perform "before save" hooks (aborting if any return nonzero) */
2249 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2250 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2253 * If this message has an Exclusive ID, and the room is replication
2254 * checking enabled, then do replication checks.
2256 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2257 ReplicationChecks(msg);
2260 /* Save it to disk */
2261 lprintf(CTDL_DEBUG, "Saving to disk\n");
2262 newmsgid = send_message(msg);
2263 if (newmsgid <= 0L) return(-5);
2265 /* Write a supplemental message info record. This doesn't have to
2266 * be a critical section because nobody else knows about this message
2269 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2270 memset(&smi, 0, sizeof(struct MetaData));
2271 smi.meta_msgnum = newmsgid;
2272 smi.meta_refcount = 0;
2273 safestrncpy(smi.meta_content_type, content_type,
2274 sizeof smi.meta_content_type);
2277 * Measure how big this message will be when rendered as RFC822.
2278 * We do this for two reasons:
2279 * 1. We need the RFC822 length for the new metadata record, so the
2280 * POP and IMAP services don't have to calculate message lengths
2281 * while the user is waiting (multiplied by potentially hundreds
2282 * or thousands of messages).
2283 * 2. If journaling is enabled, we will need an RFC822 version of the
2284 * message to attach to the journalized copy.
2286 if (CC->redirect_buffer != NULL) {
2287 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2290 CC->redirect_buffer = malloc(SIZ);
2291 CC->redirect_len = 0;
2292 CC->redirect_alloc = SIZ;
2293 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2294 smi.meta_rfc822_length = CC->redirect_len;
2295 saved_rfc822_version = CC->redirect_buffer;
2296 CC->redirect_buffer = NULL;
2297 CC->redirect_len = 0;
2298 CC->redirect_alloc = 0;
2302 /* Now figure out where to store the pointers */
2303 lprintf(CTDL_DEBUG, "Storing pointers\n");
2305 /* If this is being done by the networker delivering a private
2306 * message, we want to BYPASS saving the sender's copy (because there
2307 * is no local sender; it would otherwise go to the Trashcan).
2309 if ((!CC->internal_pgm) || (recps == NULL)) {
2310 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2311 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2312 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2316 /* For internet mail, drop a copy in the outbound queue room */
2318 if (recps->num_internet > 0) {
2319 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2322 /* If other rooms are specified, drop them there too. */
2324 if (recps->num_room > 0)
2325 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2326 extract_token(recipient, recps->recp_room, i,
2327 '|', sizeof recipient);
2328 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2329 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2332 /* Bump this user's messages posted counter. */
2333 lprintf(CTDL_DEBUG, "Updating user\n");
2334 lgetuser(&CC->user, CC->curr_user);
2335 CC->user.posted = CC->user.posted + 1;
2336 lputuser(&CC->user);
2338 /* If this is private, local mail, make a copy in the
2339 * recipient's mailbox and bump the reference count.
2342 if (recps->num_local > 0)
2343 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2344 extract_token(recipient, recps->recp_local, i,
2345 '|', sizeof recipient);
2346 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2348 if (getuser(&userbuf, recipient) == 0) {
2349 MailboxName(actual_rm, sizeof actual_rm,
2350 &userbuf, MAILROOM);
2351 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2352 BumpNewMailCounter(userbuf.usernum);
2355 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2356 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2361 /* Perform "after save" hooks */
2362 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2363 PerformMessageHooks(msg, EVT_AFTERSAVE);
2365 /* For IGnet mail, we have to save a new copy into the spooler for
2366 * each recipient, with the R and D fields set to the recipient and
2367 * destination-node. This has two ugly side effects: all other
2368 * recipients end up being unlisted in this recipient's copy of the
2369 * message, and it has to deliver multiple messages to the same
2370 * node. We'll revisit this again in a year or so when everyone has
2371 * a network spool receiver that can handle the new style messages.
2374 if (recps->num_ignet > 0)
2375 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2376 extract_token(recipient, recps->recp_ignet, i,
2377 '|', sizeof recipient);
2379 hold_R = msg->cm_fields['R'];
2380 hold_D = msg->cm_fields['D'];
2381 msg->cm_fields['R'] = malloc(SIZ);
2382 msg->cm_fields['D'] = malloc(128);
2383 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2384 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2386 serialize_message(&smr, msg);
2388 snprintf(submit_filename, sizeof submit_filename,
2389 "%s/netmail.%04lx.%04x.%04x",
2391 (long) getpid(), CC->cs_pid, ++seqnum);
2392 network_fp = fopen(submit_filename, "wb+");
2393 if (network_fp != NULL) {
2394 fwrite(smr.ser, smr.len, 1, network_fp);
2400 free(msg->cm_fields['R']);
2401 free(msg->cm_fields['D']);
2402 msg->cm_fields['R'] = hold_R;
2403 msg->cm_fields['D'] = hold_D;
2406 /* Go back to the room we started from */
2407 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2408 if (strcasecmp(hold_rm, CC->room.QRname))
2409 /* getroom(&CC->room, hold_rm); */
2410 usergoto(hold_rm, 0, 1, NULL, NULL);
2412 /* For internet mail, generate delivery instructions.
2413 * Yes, this is recursive. Deal with it. Infinite recursion does
2414 * not happen because the delivery instructions message does not
2415 * contain a recipient.
2418 if (recps->num_internet > 0) {
2419 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2420 instr = malloc(SIZ * 2);
2421 snprintf(instr, SIZ * 2,
2422 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2424 SPOOLMIME, newmsgid, (long)time(NULL),
2425 msg->cm_fields['A'], msg->cm_fields['N']
2428 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2429 size_t tmp = strlen(instr);
2430 extract_token(recipient, recps->recp_internet,
2431 i, '|', sizeof recipient);
2432 snprintf(&instr[tmp], SIZ * 2 - tmp,
2433 "remote|%s|0||\n", recipient);
2436 imsg = malloc(sizeof(struct CtdlMessage));
2437 memset(imsg, 0, sizeof(struct CtdlMessage));
2438 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2439 imsg->cm_anon_type = MES_NORMAL;
2440 imsg->cm_format_type = FMT_RFC822;
2441 imsg->cm_fields['A'] = strdup("Citadel");
2442 imsg->cm_fields['J'] = strdup("do not journal");
2443 imsg->cm_fields['M'] = instr;
2444 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2445 CtdlFreeMessage(imsg);
2449 * Any addresses to harvest for someone's address book?
2451 if ( (CC->logged_in) && (recps != NULL) ) {
2452 collected_addresses = harvest_collected_addresses(msg);
2455 if (collected_addresses != NULL) {
2456 begin_critical_section(S_ATBF);
2457 aptr = (struct addresses_to_be_filed *)
2458 malloc(sizeof(struct addresses_to_be_filed));
2460 MailboxName(actual_rm, sizeof actual_rm,
2461 &CC->user, USERCONTACTSROOM);
2462 aptr->roomname = strdup(actual_rm);
2463 aptr->collected_addresses = collected_addresses;
2465 end_critical_section(S_ATBF);
2469 * Determine whether this message qualifies for journaling.
2471 if (msg->cm_fields['J'] != NULL) {
2472 qualified_for_journaling = 0;
2475 if (recps == NULL) {
2476 qualified_for_journaling = config.c_journal_pubmsgs;
2478 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2479 qualified_for_journaling = config.c_journal_email;
2482 qualified_for_journaling = config.c_journal_pubmsgs;
2487 * Do we have to perform journaling? If so, hand off the saved
2488 * RFC822 version will be handed off to the journaler for background
2489 * submit. Otherwise, we have to free the memory ourselves.
2491 if (saved_rfc822_version != NULL) {
2492 if (qualified_for_journaling) {
2493 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2496 free(saved_rfc822_version);
2509 * Convenience function for generating small administrative messages.
2511 void quickie_message(char *from, char *to, char *room, char *text,
2512 int format_type, char *subject)
2514 struct CtdlMessage *msg;
2515 struct recptypes *recp = NULL;
2517 msg = malloc(sizeof(struct CtdlMessage));
2518 memset(msg, 0, sizeof(struct CtdlMessage));
2519 msg->cm_magic = CTDLMESSAGE_MAGIC;
2520 msg->cm_anon_type = MES_NORMAL;
2521 msg->cm_format_type = format_type;
2522 msg->cm_fields['A'] = strdup(from);
2523 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2524 msg->cm_fields['N'] = strdup(NODENAME);
2526 msg->cm_fields['R'] = strdup(to);
2527 recp = validate_recipients(to);
2529 if (subject != NULL) {
2530 msg->cm_fields['U'] = strdup(subject);
2532 msg->cm_fields['M'] = strdup(text);
2534 CtdlSubmitMsg(msg, recp, room);
2535 CtdlFreeMessage(msg);
2536 if (recp != NULL) free(recp);
2542 * Back end function used by CtdlMakeMessage() and similar functions
2544 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2545 size_t maxlen, /* maximum message length */
2546 char *exist, /* if non-null, append to it;
2547 exist is ALWAYS freed */
2548 int crlf /* CRLF newlines instead of LF */
2552 size_t message_len = 0;
2553 size_t buffer_len = 0;
2559 if (exist == NULL) {
2566 message_len = strlen(exist);
2567 buffer_len = message_len + 4096;
2568 m = realloc(exist, buffer_len);
2575 /* flush the input if we have nowhere to store it */
2580 /* read in the lines of message text one by one */
2582 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2583 if (!strcmp(buf, terminator)) finished = 1;
2585 strcat(buf, "\r\n");
2591 if ( (!flushing) && (!finished) ) {
2592 /* Measure the line */
2593 linelen = strlen(buf);
2595 /* augment the buffer if we have to */
2596 if ((message_len + linelen) >= buffer_len) {
2597 ptr = realloc(m, (buffer_len * 2) );
2598 if (ptr == NULL) { /* flush if can't allocate */
2601 buffer_len = (buffer_len * 2);
2603 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2607 /* Add the new line to the buffer. NOTE: this loop must avoid
2608 * using functions like strcat() and strlen() because they
2609 * traverse the entire buffer upon every call, and doing that
2610 * for a multi-megabyte message slows it down beyond usability.
2612 strcpy(&m[message_len], buf);
2613 message_len += linelen;
2616 /* if we've hit the max msg length, flush the rest */
2617 if (message_len >= maxlen) flushing = 1;
2619 } while (!finished);
2627 * Build a binary message to be saved on disk.
2628 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2629 * will become part of the message. This means you are no longer
2630 * responsible for managing that memory -- it will be freed along with
2631 * the rest of the fields when CtdlFreeMessage() is called.)
2634 struct CtdlMessage *CtdlMakeMessage(
2635 struct ctdluser *author, /* author's user structure */
2636 char *recipient, /* NULL if it's not mail */
2637 char *recp_cc, /* NULL if it's not mail */
2638 char *room, /* room where it's going */
2639 int type, /* see MES_ types in header file */
2640 int format_type, /* variformat, plain text, MIME... */
2641 char *fake_name, /* who we're masquerading as */
2642 char *subject, /* Subject (optional) */
2643 char *supplied_euid, /* ...or NULL if this is irrelevant */
2644 char *preformatted_text /* ...or NULL to read text from client */
2646 char dest_node[SIZ];
2648 struct CtdlMessage *msg;
2650 msg = malloc(sizeof(struct CtdlMessage));
2651 memset(msg, 0, sizeof(struct CtdlMessage));
2652 msg->cm_magic = CTDLMESSAGE_MAGIC;
2653 msg->cm_anon_type = type;
2654 msg->cm_format_type = format_type;
2656 /* Don't confuse the poor folks if it's not routed mail. */
2657 strcpy(dest_node, "");
2662 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2663 msg->cm_fields['P'] = strdup(buf);
2665 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2666 msg->cm_fields['T'] = strdup(buf);
2668 if (fake_name[0]) /* author */
2669 msg->cm_fields['A'] = strdup(fake_name);
2671 msg->cm_fields['A'] = strdup(author->fullname);
2673 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2674 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2677 msg->cm_fields['O'] = strdup(CC->room.QRname);
2680 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2681 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2683 if (recipient[0] != 0) {
2684 msg->cm_fields['R'] = strdup(recipient);
2686 if (recp_cc[0] != 0) {
2687 msg->cm_fields['Y'] = strdup(recp_cc);
2689 if (dest_node[0] != 0) {
2690 msg->cm_fields['D'] = strdup(dest_node);
2693 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2694 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2697 if (subject != NULL) {
2699 if (strlen(subject) > 0) {
2700 msg->cm_fields['U'] = strdup(subject);
2704 if (supplied_euid != NULL) {
2705 msg->cm_fields['E'] = strdup(supplied_euid);
2708 if (preformatted_text != NULL) {
2709 msg->cm_fields['M'] = preformatted_text;
2712 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2713 config.c_maxmsglen, NULL, 0);
2721 * Check to see whether we have permission to post a message in the current
2722 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2723 * returns 0 on success.
2725 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2727 if (!(CC->logged_in)) {
2728 snprintf(errmsgbuf, n, "Not logged in.");
2729 return (ERROR + NOT_LOGGED_IN);
2732 if ((CC->user.axlevel < 2)
2733 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2734 snprintf(errmsgbuf, n, "Need to be validated to enter "
2735 "(except in %s> to sysop)", MAILROOM);
2736 return (ERROR + HIGHER_ACCESS_REQUIRED);
2739 if ((CC->user.axlevel < 4)
2740 && (CC->room.QRflags & QR_NETWORK)) {
2741 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2742 return (ERROR + HIGHER_ACCESS_REQUIRED);
2745 if ((CC->user.axlevel < 6)
2746 && (CC->room.QRflags & QR_READONLY)) {
2747 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2748 return (ERROR + HIGHER_ACCESS_REQUIRED);
2751 strcpy(errmsgbuf, "Ok");
2757 * Check to see if the specified user has Internet mail permission
2758 * (returns nonzero if permission is granted)
2760 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2762 /* Do not allow twits to send Internet mail */
2763 if (who->axlevel <= 2) return(0);
2765 /* Globally enabled? */
2766 if (config.c_restrict == 0) return(1);
2768 /* User flagged ok? */
2769 if (who->flags & US_INTERNET) return(2);
2771 /* Aide level access? */
2772 if (who->axlevel >= 6) return(3);
2774 /* No mail for you! */
2780 * Validate recipients, count delivery types and errors, and handle aliasing
2781 * FIXME check for dupes!!!!!
2782 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2783 * or the number of addresses found invalid.
2785 struct recptypes *validate_recipients(char *supplied_recipients) {
2786 struct recptypes *ret;
2787 char recipients[SIZ];
2788 char this_recp[256];
2789 char this_recp_cooked[256];
2795 struct ctdluser tempUS;
2796 struct ctdlroom tempQR;
2800 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2801 if (ret == NULL) return(NULL);
2802 memset(ret, 0, sizeof(struct recptypes));
2805 ret->num_internet = 0;
2810 if (supplied_recipients == NULL) {
2811 strcpy(recipients, "");
2814 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2817 /* Change all valid separator characters to commas */
2818 for (i=0; i<strlen(recipients); ++i) {
2819 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2820 recipients[i] = ',';
2824 /* Now start extracting recipients... */
2826 while (strlen(recipients) > 0) {
2828 for (i=0; i<=strlen(recipients); ++i) {
2829 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2830 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2831 safestrncpy(this_recp, recipients, i+1);
2833 if (recipients[i] == ',') {
2834 strcpy(recipients, &recipients[i+1]);
2837 strcpy(recipients, "");
2844 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2846 mailtype = alias(this_recp);
2847 mailtype = alias(this_recp);
2848 mailtype = alias(this_recp);
2849 for (j=0; j<=strlen(this_recp); ++j) {
2850 if (this_recp[j]=='_') {
2851 this_recp_cooked[j] = ' ';
2854 this_recp_cooked[j] = this_recp[j];
2860 if (!strcasecmp(this_recp, "sysop")) {
2862 strcpy(this_recp, config.c_aideroom);
2863 if (strlen(ret->recp_room) > 0) {
2864 strcat(ret->recp_room, "|");
2866 strcat(ret->recp_room, this_recp);
2868 else if (getuser(&tempUS, this_recp) == 0) {
2870 strcpy(this_recp, tempUS.fullname);
2871 if (strlen(ret->recp_local) > 0) {
2872 strcat(ret->recp_local, "|");
2874 strcat(ret->recp_local, this_recp);
2876 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2878 strcpy(this_recp, tempUS.fullname);
2879 if (strlen(ret->recp_local) > 0) {
2880 strcat(ret->recp_local, "|");
2882 strcat(ret->recp_local, this_recp);
2884 else if ( (!strncasecmp(this_recp, "room_", 5))
2885 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2887 if (strlen(ret->recp_room) > 0) {
2888 strcat(ret->recp_room, "|");
2890 strcat(ret->recp_room, &this_recp_cooked[5]);
2898 /* Yes, you're reading this correctly: if the target
2899 * domain points back to the local system or an attached
2900 * Citadel directory, the address is invalid. That's
2901 * because if the address were valid, we would have
2902 * already translated it to a local address by now.
2904 if (IsDirectory(this_recp)) {
2909 ++ret->num_internet;
2910 if (strlen(ret->recp_internet) > 0) {
2911 strcat(ret->recp_internet, "|");
2913 strcat(ret->recp_internet, this_recp);
2918 if (strlen(ret->recp_ignet) > 0) {
2919 strcat(ret->recp_ignet, "|");
2921 strcat(ret->recp_ignet, this_recp);
2929 if (strlen(ret->errormsg) == 0) {
2930 snprintf(append, sizeof append,
2931 "Invalid recipient: %s",
2935 snprintf(append, sizeof append,
2938 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2939 strcat(ret->errormsg, append);
2943 if (strlen(ret->display_recp) == 0) {
2944 strcpy(append, this_recp);
2947 snprintf(append, sizeof append, ", %s",
2950 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2951 strcat(ret->display_recp, append);
2956 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2957 ret->num_room + ret->num_error) == 0) {
2958 ret->num_error = (-1);
2959 strcpy(ret->errormsg, "No recipients specified.");
2962 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2963 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2964 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2965 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2966 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2967 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2975 * message entry - mode 0 (normal)
2977 void cmd_ent0(char *entargs)
2983 char supplied_euid[128];
2984 char masquerade_as[SIZ];
2986 int format_type = 0;
2987 char newusername[SIZ];
2988 struct CtdlMessage *msg;
2992 struct recptypes *valid = NULL;
2993 struct recptypes *valid_to = NULL;
2994 struct recptypes *valid_cc = NULL;
2995 struct recptypes *valid_bcc = NULL;
3002 post = extract_int(entargs, 0);
3003 extract_token(recp, entargs, 1, '|', sizeof recp);
3004 anon_flag = extract_int(entargs, 2);
3005 format_type = extract_int(entargs, 3);
3006 extract_token(subject, entargs, 4, '|', sizeof subject);
3007 do_confirm = extract_int(entargs, 6);
3008 extract_token(cc, entargs, 7, '|', sizeof cc);
3009 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3010 switch(CC->room.QRdefaultview) {
3013 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3016 supplied_euid[0] = 0;
3020 /* first check to make sure the request is valid. */
3022 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3024 cprintf("%d %s\n", err, errmsg);
3028 /* Check some other permission type things. */
3031 if (CC->user.axlevel < 6) {
3032 cprintf("%d You don't have permission to masquerade.\n",
3033 ERROR + HIGHER_ACCESS_REQUIRED);
3036 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3037 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3038 safestrncpy(CC->fake_postname, newusername,
3039 sizeof(CC->fake_postname) );
3040 cprintf("%d ok\n", CIT_OK);
3043 CC->cs_flags |= CS_POSTING;
3045 /* In the Mail> room we have to behave a little differently --
3046 * make sure the user has specified at least one recipient. Then
3047 * validate the recipient(s).
3049 if ( (CC->room.QRflags & QR_MAILBOX)
3050 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3052 if (CC->user.axlevel < 2) {
3053 strcpy(recp, "sysop");
3058 valid_to = validate_recipients(recp);
3059 if (valid_to->num_error > 0) {
3060 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3065 valid_cc = validate_recipients(cc);
3066 if (valid_cc->num_error > 0) {
3067 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3073 valid_bcc = validate_recipients(bcc);
3074 if (valid_bcc->num_error > 0) {
3075 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3082 /* Recipient required, but none were specified */
3083 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3087 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3091 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3092 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3093 cprintf("%d You do not have permission "
3094 "to send Internet mail.\n",
3095 ERROR + HIGHER_ACCESS_REQUIRED);
3103 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)
3104 && (CC->user.axlevel < 4) ) {
3105 cprintf("%d Higher access required for network mail.\n",
3106 ERROR + HIGHER_ACCESS_REQUIRED);
3113 if ((RESTRICT_INTERNET == 1)
3114 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3115 && ((CC->user.flags & US_INTERNET) == 0)
3116 && (!CC->internal_pgm)) {
3117 cprintf("%d You don't have access to Internet mail.\n",
3118 ERROR + HIGHER_ACCESS_REQUIRED);
3127 /* Is this a room which has anonymous-only or anonymous-option? */
3128 anonymous = MES_NORMAL;
3129 if (CC->room.QRflags & QR_ANONONLY) {
3130 anonymous = MES_ANONONLY;
3132 if (CC->room.QRflags & QR_ANONOPT) {
3133 if (anon_flag == 1) { /* only if the user requested it */
3134 anonymous = MES_ANONOPT;
3138 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3142 /* If we're only checking the validity of the request, return
3143 * success without creating the message.
3146 cprintf("%d %s\n", CIT_OK,
3147 ((valid_to != NULL) ? valid_to->display_recp : "") );
3154 /* We don't need these anymore because we'll do it differently below */
3159 /* Handle author masquerading */
3160 if (CC->fake_postname[0]) {
3161 strcpy(masquerade_as, CC->fake_postname);
3163 else if (CC->fake_username[0]) {
3164 strcpy(masquerade_as, CC->fake_username);
3167 strcpy(masquerade_as, "");
3170 /* Read in the message from the client. */
3172 cprintf("%d send message\n", START_CHAT_MODE);
3174 cprintf("%d send message\n", SEND_LISTING);
3177 msg = CtdlMakeMessage(&CC->user, recp, cc,
3178 CC->room.QRname, anonymous, format_type,
3179 masquerade_as, subject,
3180 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3183 /* Put together one big recipients struct containing to/cc/bcc all in
3184 * one. This is for the envelope.
3186 char *all_recps = malloc(SIZ * 3);
3187 strcpy(all_recps, recp);
3188 if (strlen(cc) > 0) {
3189 if (strlen(all_recps) > 0) {
3190 strcat(all_recps, ",");
3192 strcat(all_recps, cc);
3194 if (strlen(bcc) > 0) {
3195 if (strlen(all_recps) > 0) {
3196 strcat(all_recps, ",");
3198 strcat(all_recps, bcc);
3200 if (strlen(all_recps) > 0) {
3201 valid = validate_recipients(all_recps);
3209 msgnum = CtdlSubmitMsg(msg, valid, "");
3212 cprintf("%ld\n", msgnum);
3214 cprintf("Message accepted.\n");
3217 cprintf("Internal error.\n");
3219 if (msg->cm_fields['E'] != NULL) {
3220 cprintf("%s\n", msg->cm_fields['E']);
3227 CtdlFreeMessage(msg);
3229 CC->fake_postname[0] = '\0';
3230 if (valid != NULL) {
3239 * API function to delete messages which match a set of criteria
3240 * (returns the actual number of messages deleted)
3242 int CtdlDeleteMessages(char *room_name, /* which room */
3243 long dmsgnum, /* or "0" for any */
3244 char *content_type, /* or "" for any */
3245 int deferred /* let TDAP sweep it later */
3249 struct ctdlroom qrbuf;
3250 struct cdbdata *cdbfr;
3251 long *msglist = NULL;
3252 long *dellist = NULL;
3255 int num_deleted = 0;
3257 struct MetaData smi;
3259 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3260 room_name, dmsgnum, content_type, deferred);
3262 /* get room record, obtaining a lock... */
3263 if (lgetroom(&qrbuf, room_name) != 0) {
3264 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3266 return (0); /* room not found */
3268 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3270 if (cdbfr != NULL) {
3271 dellist = malloc(cdbfr->len);
3272 msglist = (long *) cdbfr->ptr;
3273 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3274 num_msgs = cdbfr->len / sizeof(long);
3278 for (i = 0; i < num_msgs; ++i) {
3281 /* Set/clear a bit for each criterion */
3283 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3284 delete_this |= 0x01;
3286 if (strlen(content_type) == 0) {
3287 delete_this |= 0x02;
3289 GetMetaData(&smi, msglist[i]);
3290 if (!strcasecmp(smi.meta_content_type,
3292 delete_this |= 0x02;
3296 /* Delete message only if all bits are set */
3297 if (delete_this == 0x03) {
3298 dellist[num_deleted++] = msglist[i];
3303 num_msgs = sort_msglist(msglist, num_msgs);
3304 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3305 msglist, (int)(num_msgs * sizeof(long)));
3307 qrbuf.QRhighest = msglist[num_msgs - 1];
3312 * If the delete operation is "deferred" (and technically, any delete
3313 * operation not performed by THE DREADED AUTO-PURGER ought to be
3314 * a deferred delete) then we save a pointer to the message in the
3315 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3316 * at least 1, which will save the user from having to synchronously
3317 * wait for various disk-intensive operations to complete.
3319 if ( (deferred) && (num_deleted) ) {
3320 for (i=0; i<num_deleted; ++i) {
3321 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3325 /* Go through the messages we pulled out of the index, and decrement
3326 * their reference counts by 1. If this is the only room the message
3327 * was in, the reference count will reach zero and the message will
3328 * automatically be deleted from the database. We do this in a
3329 * separate pass because there might be plug-in hooks getting called,
3330 * and we don't want that happening during an S_ROOMS critical
3333 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3334 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3335 AdjRefCount(dellist[i], -1);
3338 /* Now free the memory we used, and go away. */
3339 if (msglist != NULL) free(msglist);
3340 if (dellist != NULL) free(dellist);
3341 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3342 return (num_deleted);
3348 * Check whether the current user has permission to delete messages from
3349 * the current room (returns 1 for yes, 0 for no)
3351 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3352 getuser(&CC->user, CC->curr_user);
3353 if ((CC->user.axlevel < 6)
3354 && (CC->user.usernum != CC->room.QRroomaide)
3355 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3356 && (!(CC->internal_pgm))) {
3365 * Delete message from current room
3367 void cmd_dele(char *delstr)
3372 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3373 cprintf("%d Higher access required.\n",
3374 ERROR + HIGHER_ACCESS_REQUIRED);
3377 delnum = extract_long(delstr, 0);
3379 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3382 cprintf("%d %d message%s deleted.\n", CIT_OK,
3383 num_deleted, ((num_deleted != 1) ? "s" : ""));
3385 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3391 * Back end API function for moves and deletes
3393 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3396 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3397 if (err != 0) return(err);
3405 * move or copy a message to another room
3407 void cmd_move(char *args)
3410 char targ[ROOMNAMELEN];
3411 struct ctdlroom qtemp;
3417 num = extract_long(args, 0);
3418 extract_token(targ, args, 1, '|', sizeof targ);
3419 convert_room_name_macros(targ, sizeof targ);
3420 targ[ROOMNAMELEN - 1] = 0;
3421 is_copy = extract_int(args, 2);
3423 if (getroom(&qtemp, targ) != 0) {
3424 cprintf("%d '%s' does not exist.\n",
3425 ERROR + ROOM_NOT_FOUND, targ);
3429 getuser(&CC->user, CC->curr_user);
3430 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3432 /* Check for permission to perform this operation.
3433 * Remember: "CC->room" is source, "qtemp" is target.
3437 /* Aides can move/copy */
3438 if (CC->user.axlevel >= 6) permit = 1;
3440 /* Room aides can move/copy */
3441 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3443 /* Permit move/copy from personal rooms */
3444 if ((CC->room.QRflags & QR_MAILBOX)
3445 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3447 /* Permit only copy from public to personal room */
3449 && (!(CC->room.QRflags & QR_MAILBOX))
3450 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3452 /* User must have access to target room */
3453 if (!(ra & UA_KNOWN)) permit = 0;
3456 cprintf("%d Higher access required.\n",
3457 ERROR + HIGHER_ACCESS_REQUIRED);
3461 err = CtdlCopyMsgToRoom(num, targ);
3463 cprintf("%d Cannot store message in %s: error %d\n",
3468 /* Now delete the message from the source room,
3469 * if this is a 'move' rather than a 'copy' operation.
3472 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3475 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3481 * GetMetaData() - Get the supplementary record for a message
3483 void GetMetaData(struct MetaData *smibuf, long msgnum)
3486 struct cdbdata *cdbsmi;
3489 memset(smibuf, 0, sizeof(struct MetaData));
3490 smibuf->meta_msgnum = msgnum;
3491 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3493 /* Use the negative of the message number for its supp record index */
3494 TheIndex = (0L - msgnum);
3496 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3497 if (cdbsmi == NULL) {
3498 return; /* record not found; go with defaults */
3500 memcpy(smibuf, cdbsmi->ptr,
3501 ((cdbsmi->len > sizeof(struct MetaData)) ?
3502 sizeof(struct MetaData) : cdbsmi->len));
3509 * PutMetaData() - (re)write supplementary record for a message
3511 void PutMetaData(struct MetaData *smibuf)
3515 /* Use the negative of the message number for the metadata db index */
3516 TheIndex = (0L - smibuf->meta_msgnum);
3518 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3519 smibuf->meta_msgnum, smibuf->meta_refcount);
3521 cdb_store(CDB_MSGMAIN,
3522 &TheIndex, (int)sizeof(long),
3523 smibuf, (int)sizeof(struct MetaData));
3528 * AdjRefCount - change the reference count for a message;
3529 * delete the message if it reaches zero
3531 void AdjRefCount(long msgnum, int incr)
3534 struct MetaData smi;
3537 /* This is a *tight* critical section; please keep it that way, as
3538 * it may get called while nested in other critical sections.
3539 * Complicating this any further will surely cause deadlock!
3541 begin_critical_section(S_SUPPMSGMAIN);
3542 GetMetaData(&smi, msgnum);
3543 smi.meta_refcount += incr;
3545 end_critical_section(S_SUPPMSGMAIN);
3546 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3547 msgnum, incr, smi.meta_refcount);
3549 /* If the reference count is now zero, delete the message
3550 * (and its supplementary record as well).
3552 if (smi.meta_refcount == 0) {
3553 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3555 /* Remove from fulltext index */
3556 if (config.c_enable_fulltext) {
3557 ft_index_message(msgnum, 0);
3560 /* Remove from message base */
3562 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3563 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3565 /* Remove metadata record */
3566 delnum = (0L - msgnum);
3567 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3572 * Write a generic object to this room
3574 * Note: this could be much more efficient. Right now we use two temporary
3575 * files, and still pull the message into memory as with all others.
3577 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3578 char *content_type, /* MIME type of this object */
3579 char *tempfilename, /* Where to fetch it from */
3580 struct ctdluser *is_mailbox, /* Mailbox room? */
3581 int is_binary, /* Is encoding necessary? */
3582 int is_unique, /* Del others of this type? */
3583 unsigned int flags /* Internal save flags */
3588 struct ctdlroom qrbuf;
3589 char roomname[ROOMNAMELEN];
3590 struct CtdlMessage *msg;
3592 char *raw_message = NULL;
3593 char *encoded_message = NULL;
3594 off_t raw_length = 0;
3596 if (is_mailbox != NULL) {
3597 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3600 safestrncpy(roomname, req_room, sizeof(roomname));
3603 fp = fopen(tempfilename, "rb");
3605 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3606 tempfilename, strerror(errno));
3609 fseek(fp, 0L, SEEK_END);
3610 raw_length = ftell(fp);
3612 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3614 raw_message = malloc((size_t)raw_length + 2);
3615 fread(raw_message, (size_t)raw_length, 1, fp);
3619 encoded_message = malloc((size_t)
3620 (((raw_length * 134) / 100) + 4096 ) );
3623 encoded_message = malloc((size_t)(raw_length + 4096));
3626 sprintf(encoded_message, "Content-type: %s\n", content_type);
3629 sprintf(&encoded_message[strlen(encoded_message)],
3630 "Content-transfer-encoding: base64\n\n"
3634 sprintf(&encoded_message[strlen(encoded_message)],
3635 "Content-transfer-encoding: 7bit\n\n"
3641 &encoded_message[strlen(encoded_message)],
3647 raw_message[raw_length] = 0;
3649 &encoded_message[strlen(encoded_message)],
3657 lprintf(CTDL_DEBUG, "Allocating\n");
3658 msg = malloc(sizeof(struct CtdlMessage));
3659 memset(msg, 0, sizeof(struct CtdlMessage));
3660 msg->cm_magic = CTDLMESSAGE_MAGIC;
3661 msg->cm_anon_type = MES_NORMAL;
3662 msg->cm_format_type = 4;
3663 msg->cm_fields['A'] = strdup(CC->user.fullname);
3664 msg->cm_fields['O'] = strdup(req_room);
3665 msg->cm_fields['N'] = strdup(config.c_nodename);
3666 msg->cm_fields['H'] = strdup(config.c_humannode);
3667 msg->cm_flags = flags;
3669 msg->cm_fields['M'] = encoded_message;
3671 /* Create the requested room if we have to. */
3672 if (getroom(&qrbuf, roomname) != 0) {
3673 create_room(roomname,
3674 ( (is_mailbox != NULL) ? 5 : 3 ),
3675 "", 0, 1, 0, VIEW_BBS);
3677 /* If the caller specified this object as unique, delete all
3678 * other objects of this type that are currently in the room.
3681 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3682 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3685 /* Now write the data */
3686 CtdlSubmitMsg(msg, NULL, roomname);
3687 CtdlFreeMessage(msg);
3695 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3696 config_msgnum = msgnum;
3700 char *CtdlGetSysConfig(char *sysconfname) {
3701 char hold_rm[ROOMNAMELEN];
3704 struct CtdlMessage *msg;
3707 strcpy(hold_rm, CC->room.QRname);
3708 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3709 getroom(&CC->room, hold_rm);
3714 /* We want the last (and probably only) config in this room */
3715 begin_critical_section(S_CONFIG);
3716 config_msgnum = (-1L);
3717 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3718 CtdlGetSysConfigBackend, NULL);
3719 msgnum = config_msgnum;
3720 end_critical_section(S_CONFIG);
3726 msg = CtdlFetchMessage(msgnum, 1);
3728 conf = strdup(msg->cm_fields['M']);
3729 CtdlFreeMessage(msg);
3736 getroom(&CC->room, hold_rm);
3738 if (conf != NULL) do {
3739 extract_token(buf, conf, 0, '\n', sizeof buf);
3740 strcpy(conf, &conf[strlen(buf)+1]);
3741 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3746 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3747 char temp[PATH_MAX];
3750 CtdlMakeTempFileName(temp, sizeof temp);
3752 fp = fopen(temp, "w");
3753 if (fp == NULL) return;
3754 fprintf(fp, "%s", sysconfdata);
3757 /* this handy API function does all the work for us */
3758 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3764 * Determine whether a given Internet address belongs to the current user
3766 int CtdlIsMe(char *addr, int addr_buf_len)
3768 struct recptypes *recp;
3771 recp = validate_recipients(addr);
3772 if (recp == NULL) return(0);
3774 if (recp->num_local == 0) {
3779 for (i=0; i<recp->num_local; ++i) {
3780 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3781 if (!strcasecmp(addr, CC->user.fullname)) {
3793 * Citadel protocol command to do the same
3795 void cmd_isme(char *argbuf) {
3798 if (CtdlAccessCheck(ac_logged_in)) return;
3799 extract_token(addr, argbuf, 0, '|', sizeof addr);
3801 if (CtdlIsMe(addr, sizeof addr)) {
3802 cprintf("%d %s\n", CIT_OK, addr);
3805 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);