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("<no text>\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) {
3012 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3015 supplied_euid[0] = 0;
3019 /* first check to make sure the request is valid. */
3021 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3023 cprintf("%d %s\n", err, errmsg);
3027 /* Check some other permission type things. */
3030 if (CC->user.axlevel < 6) {
3031 cprintf("%d You don't have permission to masquerade.\n",
3032 ERROR + HIGHER_ACCESS_REQUIRED);
3035 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3036 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3037 safestrncpy(CC->fake_postname, newusername,
3038 sizeof(CC->fake_postname) );
3039 cprintf("%d ok\n", CIT_OK);
3042 CC->cs_flags |= CS_POSTING;
3044 /* In the Mail> room we have to behave a little differently --
3045 * make sure the user has specified at least one recipient. Then
3046 * validate the recipient(s).
3048 if ( (CC->room.QRflags & QR_MAILBOX)
3049 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3051 if (CC->user.axlevel < 2) {
3052 strcpy(recp, "sysop");
3057 valid_to = validate_recipients(recp);
3058 if (valid_to->num_error > 0) {
3059 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3064 valid_cc = validate_recipients(cc);
3065 if (valid_cc->num_error > 0) {
3066 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3072 valid_bcc = validate_recipients(bcc);
3073 if (valid_bcc->num_error > 0) {
3074 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3081 /* Recipient required, but none were specified */
3082 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3086 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3090 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3091 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3092 cprintf("%d You do not have permission "
3093 "to send Internet mail.\n",
3094 ERROR + HIGHER_ACCESS_REQUIRED);
3102 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)
3103 && (CC->user.axlevel < 4) ) {
3104 cprintf("%d Higher access required for network mail.\n",
3105 ERROR + HIGHER_ACCESS_REQUIRED);
3112 if ((RESTRICT_INTERNET == 1)
3113 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3114 && ((CC->user.flags & US_INTERNET) == 0)
3115 && (!CC->internal_pgm)) {
3116 cprintf("%d You don't have access to Internet mail.\n",
3117 ERROR + HIGHER_ACCESS_REQUIRED);
3126 /* Is this a room which has anonymous-only or anonymous-option? */
3127 anonymous = MES_NORMAL;
3128 if (CC->room.QRflags & QR_ANONONLY) {
3129 anonymous = MES_ANONONLY;
3131 if (CC->room.QRflags & QR_ANONOPT) {
3132 if (anon_flag == 1) { /* only if the user requested it */
3133 anonymous = MES_ANONOPT;
3137 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3141 /* If we're only checking the validity of the request, return
3142 * success without creating the message.
3145 cprintf("%d %s\n", CIT_OK,
3146 ((valid_to != NULL) ? valid_to->display_recp : "") );
3153 /* We don't need these anymore because we'll do it differently below */
3158 /* Handle author masquerading */
3159 if (CC->fake_postname[0]) {
3160 strcpy(masquerade_as, CC->fake_postname);
3162 else if (CC->fake_username[0]) {
3163 strcpy(masquerade_as, CC->fake_username);
3166 strcpy(masquerade_as, "");
3169 /* Read in the message from the client. */
3171 cprintf("%d send message\n", START_CHAT_MODE);
3173 cprintf("%d send message\n", SEND_LISTING);
3176 msg = CtdlMakeMessage(&CC->user, recp, cc,
3177 CC->room.QRname, anonymous, format_type,
3178 masquerade_as, subject,
3179 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3182 /* Put together one big recipients struct containing to/cc/bcc all in
3183 * one. This is for the envelope.
3185 char *all_recps = malloc(SIZ * 3);
3186 strcpy(all_recps, recp);
3187 if (strlen(cc) > 0) {
3188 if (strlen(all_recps) > 0) {
3189 strcat(all_recps, ",");
3191 strcat(all_recps, cc);
3193 if (strlen(bcc) > 0) {
3194 if (strlen(all_recps) > 0) {
3195 strcat(all_recps, ",");
3197 strcat(all_recps, bcc);
3199 if (strlen(all_recps) > 0) {
3200 valid = validate_recipients(all_recps);
3208 msgnum = CtdlSubmitMsg(msg, valid, "");
3211 cprintf("%ld\n", msgnum);
3213 cprintf("Message accepted.\n");
3216 cprintf("Internal error.\n");
3218 if (msg->cm_fields['E'] != NULL) {
3219 cprintf("%s\n", msg->cm_fields['E']);
3226 CtdlFreeMessage(msg);
3228 CC->fake_postname[0] = '\0';
3229 if (valid != NULL) {
3238 * API function to delete messages which match a set of criteria
3239 * (returns the actual number of messages deleted)
3241 int CtdlDeleteMessages(char *room_name, /* which room */
3242 long dmsgnum, /* or "0" for any */
3243 char *content_type, /* or "" for any */
3244 int deferred /* let TDAP sweep it later */
3248 struct ctdlroom qrbuf;
3249 struct cdbdata *cdbfr;
3250 long *msglist = NULL;
3251 long *dellist = NULL;
3254 int num_deleted = 0;
3256 struct MetaData smi;
3258 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3259 room_name, dmsgnum, content_type, deferred);
3261 /* get room record, obtaining a lock... */
3262 if (lgetroom(&qrbuf, room_name) != 0) {
3263 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3265 return (0); /* room not found */
3267 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3269 if (cdbfr != NULL) {
3270 dellist = malloc(cdbfr->len);
3271 msglist = (long *) cdbfr->ptr;
3272 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3273 num_msgs = cdbfr->len / sizeof(long);
3277 for (i = 0; i < num_msgs; ++i) {
3280 /* Set/clear a bit for each criterion */
3282 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3283 delete_this |= 0x01;
3285 if (strlen(content_type) == 0) {
3286 delete_this |= 0x02;
3288 GetMetaData(&smi, msglist[i]);
3289 if (!strcasecmp(smi.meta_content_type,
3291 delete_this |= 0x02;
3295 /* Delete message only if all bits are set */
3296 if (delete_this == 0x03) {
3297 dellist[num_deleted++] = msglist[i];
3302 num_msgs = sort_msglist(msglist, num_msgs);
3303 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3304 msglist, (int)(num_msgs * sizeof(long)));
3306 qrbuf.QRhighest = msglist[num_msgs - 1];
3311 * If the delete operation is "deferred" (and technically, any delete
3312 * operation not performed by THE DREADED AUTO-PURGER ought to be
3313 * a deferred delete) then we save a pointer to the message in the
3314 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3315 * at least 1, which will save the user from having to synchronously
3316 * wait for various disk-intensive operations to complete.
3318 if ( (deferred) && (num_deleted) ) {
3319 for (i=0; i<num_deleted; ++i) {
3320 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3324 /* Go through the messages we pulled out of the index, and decrement
3325 * their reference counts by 1. If this is the only room the message
3326 * was in, the reference count will reach zero and the message will
3327 * automatically be deleted from the database. We do this in a
3328 * separate pass because there might be plug-in hooks getting called,
3329 * and we don't want that happening during an S_ROOMS critical
3332 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3333 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3334 AdjRefCount(dellist[i], -1);
3337 /* Now free the memory we used, and go away. */
3338 if (msglist != NULL) free(msglist);
3339 if (dellist != NULL) free(dellist);
3340 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3341 return (num_deleted);
3347 * Check whether the current user has permission to delete messages from
3348 * the current room (returns 1 for yes, 0 for no)
3350 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3351 getuser(&CC->user, CC->curr_user);
3352 if ((CC->user.axlevel < 6)
3353 && (CC->user.usernum != CC->room.QRroomaide)
3354 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3355 && (!(CC->internal_pgm))) {
3364 * Delete message from current room
3366 void cmd_dele(char *delstr)
3371 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3372 cprintf("%d Higher access required.\n",
3373 ERROR + HIGHER_ACCESS_REQUIRED);
3376 delnum = extract_long(delstr, 0);
3378 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3381 cprintf("%d %d message%s deleted.\n", CIT_OK,
3382 num_deleted, ((num_deleted != 1) ? "s" : ""));
3384 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3390 * Back end API function for moves and deletes
3392 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3395 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3396 if (err != 0) return(err);
3404 * move or copy a message to another room
3406 void cmd_move(char *args)
3409 char targ[ROOMNAMELEN];
3410 struct ctdlroom qtemp;
3416 num = extract_long(args, 0);
3417 extract_token(targ, args, 1, '|', sizeof targ);
3418 convert_room_name_macros(targ, sizeof targ);
3419 targ[ROOMNAMELEN - 1] = 0;
3420 is_copy = extract_int(args, 2);
3422 if (getroom(&qtemp, targ) != 0) {
3423 cprintf("%d '%s' does not exist.\n",
3424 ERROR + ROOM_NOT_FOUND, targ);
3428 getuser(&CC->user, CC->curr_user);
3429 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3431 /* Check for permission to perform this operation.
3432 * Remember: "CC->room" is source, "qtemp" is target.
3436 /* Aides can move/copy */
3437 if (CC->user.axlevel >= 6) permit = 1;
3439 /* Room aides can move/copy */
3440 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3442 /* Permit move/copy from personal rooms */
3443 if ((CC->room.QRflags & QR_MAILBOX)
3444 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3446 /* Permit only copy from public to personal room */
3448 && (!(CC->room.QRflags & QR_MAILBOX))
3449 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3451 /* User must have access to target room */
3452 if (!(ra & UA_KNOWN)) permit = 0;
3455 cprintf("%d Higher access required.\n",
3456 ERROR + HIGHER_ACCESS_REQUIRED);
3460 err = CtdlCopyMsgToRoom(num, targ);
3462 cprintf("%d Cannot store message in %s: error %d\n",
3467 /* Now delete the message from the source room,
3468 * if this is a 'move' rather than a 'copy' operation.
3471 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3474 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3480 * GetMetaData() - Get the supplementary record for a message
3482 void GetMetaData(struct MetaData *smibuf, long msgnum)
3485 struct cdbdata *cdbsmi;
3488 memset(smibuf, 0, sizeof(struct MetaData));
3489 smibuf->meta_msgnum = msgnum;
3490 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3492 /* Use the negative of the message number for its supp record index */
3493 TheIndex = (0L - msgnum);
3495 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3496 if (cdbsmi == NULL) {
3497 return; /* record not found; go with defaults */
3499 memcpy(smibuf, cdbsmi->ptr,
3500 ((cdbsmi->len > sizeof(struct MetaData)) ?
3501 sizeof(struct MetaData) : cdbsmi->len));
3508 * PutMetaData() - (re)write supplementary record for a message
3510 void PutMetaData(struct MetaData *smibuf)
3514 /* Use the negative of the message number for the metadata db index */
3515 TheIndex = (0L - smibuf->meta_msgnum);
3517 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3518 smibuf->meta_msgnum, smibuf->meta_refcount);
3520 cdb_store(CDB_MSGMAIN,
3521 &TheIndex, (int)sizeof(long),
3522 smibuf, (int)sizeof(struct MetaData));
3527 * AdjRefCount - change the reference count for a message;
3528 * delete the message if it reaches zero
3530 void AdjRefCount(long msgnum, int incr)
3533 struct MetaData smi;
3536 /* This is a *tight* critical section; please keep it that way, as
3537 * it may get called while nested in other critical sections.
3538 * Complicating this any further will surely cause deadlock!
3540 begin_critical_section(S_SUPPMSGMAIN);
3541 GetMetaData(&smi, msgnum);
3542 smi.meta_refcount += incr;
3544 end_critical_section(S_SUPPMSGMAIN);
3545 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3546 msgnum, incr, smi.meta_refcount);
3548 /* If the reference count is now zero, delete the message
3549 * (and its supplementary record as well).
3551 if (smi.meta_refcount == 0) {
3552 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3554 /* Remove from fulltext index */
3555 if (config.c_enable_fulltext) {
3556 ft_index_message(msgnum, 0);
3559 /* Remove from message base */
3561 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3562 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3564 /* Remove metadata record */
3565 delnum = (0L - msgnum);
3566 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3571 * Write a generic object to this room
3573 * Note: this could be much more efficient. Right now we use two temporary
3574 * files, and still pull the message into memory as with all others.
3576 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3577 char *content_type, /* MIME type of this object */
3578 char *tempfilename, /* Where to fetch it from */
3579 struct ctdluser *is_mailbox, /* Mailbox room? */
3580 int is_binary, /* Is encoding necessary? */
3581 int is_unique, /* Del others of this type? */
3582 unsigned int flags /* Internal save flags */
3587 struct ctdlroom qrbuf;
3588 char roomname[ROOMNAMELEN];
3589 struct CtdlMessage *msg;
3591 char *raw_message = NULL;
3592 char *encoded_message = NULL;
3593 off_t raw_length = 0;
3595 if (is_mailbox != NULL) {
3596 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3599 safestrncpy(roomname, req_room, sizeof(roomname));
3602 fp = fopen(tempfilename, "rb");
3604 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3605 tempfilename, strerror(errno));
3608 fseek(fp, 0L, SEEK_END);
3609 raw_length = ftell(fp);
3611 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3613 raw_message = malloc((size_t)raw_length + 2);
3614 fread(raw_message, (size_t)raw_length, 1, fp);
3618 encoded_message = malloc((size_t)
3619 (((raw_length * 134) / 100) + 4096 ) );
3622 encoded_message = malloc((size_t)(raw_length + 4096));
3625 sprintf(encoded_message, "Content-type: %s\n", content_type);
3628 sprintf(&encoded_message[strlen(encoded_message)],
3629 "Content-transfer-encoding: base64\n\n"
3633 sprintf(&encoded_message[strlen(encoded_message)],
3634 "Content-transfer-encoding: 7bit\n\n"
3640 &encoded_message[strlen(encoded_message)],
3646 raw_message[raw_length] = 0;
3648 &encoded_message[strlen(encoded_message)],
3656 lprintf(CTDL_DEBUG, "Allocating\n");
3657 msg = malloc(sizeof(struct CtdlMessage));
3658 memset(msg, 0, sizeof(struct CtdlMessage));
3659 msg->cm_magic = CTDLMESSAGE_MAGIC;
3660 msg->cm_anon_type = MES_NORMAL;
3661 msg->cm_format_type = 4;
3662 msg->cm_fields['A'] = strdup(CC->user.fullname);
3663 msg->cm_fields['O'] = strdup(req_room);
3664 msg->cm_fields['N'] = strdup(config.c_nodename);
3665 msg->cm_fields['H'] = strdup(config.c_humannode);
3666 msg->cm_flags = flags;
3668 msg->cm_fields['M'] = encoded_message;
3670 /* Create the requested room if we have to. */
3671 if (getroom(&qrbuf, roomname) != 0) {
3672 create_room(roomname,
3673 ( (is_mailbox != NULL) ? 5 : 3 ),
3674 "", 0, 1, 0, VIEW_BBS);
3676 /* If the caller specified this object as unique, delete all
3677 * other objects of this type that are currently in the room.
3680 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3681 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3684 /* Now write the data */
3685 CtdlSubmitMsg(msg, NULL, roomname);
3686 CtdlFreeMessage(msg);
3694 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3695 config_msgnum = msgnum;
3699 char *CtdlGetSysConfig(char *sysconfname) {
3700 char hold_rm[ROOMNAMELEN];
3703 struct CtdlMessage *msg;
3706 strcpy(hold_rm, CC->room.QRname);
3707 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3708 getroom(&CC->room, hold_rm);
3713 /* We want the last (and probably only) config in this room */
3714 begin_critical_section(S_CONFIG);
3715 config_msgnum = (-1L);
3716 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3717 CtdlGetSysConfigBackend, NULL);
3718 msgnum = config_msgnum;
3719 end_critical_section(S_CONFIG);
3725 msg = CtdlFetchMessage(msgnum, 1);
3727 conf = strdup(msg->cm_fields['M']);
3728 CtdlFreeMessage(msg);
3735 getroom(&CC->room, hold_rm);
3737 if (conf != NULL) do {
3738 extract_token(buf, conf, 0, '\n', sizeof buf);
3739 strcpy(conf, &conf[strlen(buf)+1]);
3740 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3745 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3746 char temp[PATH_MAX];
3749 CtdlMakeTempFileName(temp, sizeof temp);
3751 fp = fopen(temp, "w");
3752 if (fp == NULL) return;
3753 fprintf(fp, "%s", sysconfdata);
3756 /* this handy API function does all the work for us */
3757 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3763 * Determine whether a given Internet address belongs to the current user
3765 int CtdlIsMe(char *addr, int addr_buf_len)
3767 struct recptypes *recp;
3770 recp = validate_recipients(addr);
3771 if (recp == NULL) return(0);
3773 if (recp->num_local == 0) {
3778 for (i=0; i<recp->num_local; ++i) {
3779 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3780 if (!strcasecmp(addr, CC->user.fullname)) {
3792 * Citadel protocol command to do the same
3794 void cmd_isme(char *argbuf) {
3797 if (CtdlAccessCheck(ac_logged_in)) return;
3798 extract_token(addr, argbuf, 0, '|', sizeof addr);
3800 if (CtdlIsMe(addr, sizeof addr)) {
3801 cprintf("%d %s\n", CIT_OK, addr);
3804 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);