4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
38 #include "serv_extensions.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
54 #include "serv_fulltext.h"
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,
105 * This function is self explanatory.
106 * (What can I say, I'm in a weird mood today...)
108 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 for (i = 0; i < strlen(name); ++i) {
113 if (name[i] == '@') {
114 while (isspace(name[i - 1]) && i > 0) {
115 strcpy(&name[i - 1], &name[i]);
118 while (isspace(name[i + 1])) {
119 strcpy(&name[i + 1], &name[i + 2]);
127 * Aliasing for network mail.
128 * (Error messages have been commented out, because this is a server.)
130 int alias(char *name)
131 { /* process alias and routing info for mail */
134 char aaa[SIZ], bbb[SIZ];
135 char *ignetcfg = NULL;
136 char *ignetmap = NULL;
143 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
145 fp = fopen("network/mail.aliases", "r");
147 fp = fopen("/dev/null", "r");
154 while (fgets(aaa, sizeof aaa, fp) != NULL) {
155 while (isspace(name[0]))
156 strcpy(name, &name[1]);
157 aaa[strlen(aaa) - 1] = 0;
159 for (a = 0; a < strlen(aaa); ++a) {
161 strcpy(bbb, &aaa[a + 1]);
165 if (!strcasecmp(name, aaa))
170 /* Hit the Global Address Book */
171 if (CtdlDirectoryLookup(aaa, name) == 0) {
175 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
177 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
178 for (a=0; a<strlen(name); ++a) {
179 if (name[a] == '@') {
180 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
182 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
187 /* determine local or remote type, see citadel.h */
188 at = haschar(name, '@');
189 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
190 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
191 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
193 /* figure out the delivery mode */
194 extract_token(node, name, 1, '@', sizeof node);
196 /* If there are one or more dots in the nodename, we assume that it
197 * is an FQDN and will attempt SMTP delivery to the Internet.
199 if (haschar(node, '.') > 0) {
200 return(MES_INTERNET);
203 /* Otherwise we look in the IGnet maps for a valid Citadel node.
204 * Try directly-connected nodes first...
206 ignetcfg = CtdlGetSysConfig(IGNETCFG);
207 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
208 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
209 extract_token(testnode, buf, 0, '|', sizeof testnode);
210 if (!strcasecmp(node, testnode)) {
218 * Then try nodes that are two or more hops away.
220 ignetmap = CtdlGetSysConfig(IGNETMAP);
221 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
222 extract_token(buf, ignetmap, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
231 /* If we get to this point it's an invalid node name */
240 fp = fopen("citadel.control", "r");
242 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
246 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
252 void simple_listing(long msgnum, void *userdata)
254 cprintf("%ld\n", msgnum);
259 /* Determine if a given message matches the fields in a message template.
260 * Return 0 for a successful match.
262 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
265 /* If there aren't any fields in the template, all messages will
268 if (template == NULL) return(0);
270 /* Null messages are bogus. */
271 if (msg == NULL) return(1);
273 for (i='A'; i<='Z'; ++i) {
274 if (template->cm_fields[i] != NULL) {
275 if (msg->cm_fields[i] == NULL) {
278 if (strcasecmp(msg->cm_fields[i],
279 template->cm_fields[i])) return 1;
283 /* All compares succeeded: we have a match! */
290 * Retrieve the "seen" message list for the current room.
292 void CtdlGetSeen(char *buf, int which_set) {
295 /* Learn about the user and room in question */
296 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
298 if (which_set == ctdlsetseen_seen)
299 safestrncpy(buf, vbuf.v_seen, SIZ);
300 if (which_set == ctdlsetseen_answered)
301 safestrncpy(buf, vbuf.v_answered, SIZ);
307 * Manipulate the "seen msgs" string (or other message set strings)
309 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set,
310 struct ctdluser *which_user, struct ctdlroom *which_room) {
311 struct cdbdata *cdbfr;
323 char *is_set; /* actually an array of booleans */
326 char setstr[SIZ], lostr[SIZ], histr[SIZ];
328 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
329 target_msgnum, target_setting, which_set);
331 /* Learn about the user and room in question */
332 CtdlGetRelationship(&vbuf,
333 ((which_user != NULL) ? which_user : &CC->user),
334 ((which_room != NULL) ? which_room : &CC->room)
337 /* Load the message list */
338 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
340 msglist = malloc(cdbfr->len);
341 memcpy(msglist, cdbfr->ptr, cdbfr->len);
342 num_msgs = cdbfr->len / sizeof(long);
345 return; /* No messages at all? No further action. */
348 is_set = malloc(num_msgs * sizeof(char));
349 memset(is_set, 0, (num_msgs * sizeof(char)) );
351 /* Decide which message set we're manipulating */
353 case ctdlsetseen_seen:
354 safestrncpy(vset, vbuf.v_seen, sizeof vset);
356 case ctdlsetseen_answered:
357 safestrncpy(vset, vbuf.v_answered, sizeof vset);
361 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
363 /* Translate the existing sequence set into an array of booleans */
364 num_sets = num_tokens(vset, ',');
365 for (s=0; s<num_sets; ++s) {
366 extract_token(setstr, vset, s, ',', sizeof setstr);
368 extract_token(lostr, setstr, 0, ':', sizeof lostr);
369 if (num_tokens(setstr, ':') >= 2) {
370 extract_token(histr, setstr, 1, ':', sizeof histr);
371 if (!strcmp(histr, "*")) {
372 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
376 strcpy(histr, lostr);
381 for (i = 0; i < num_msgs; ++i) {
382 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
388 /* Now translate the array of booleans back into a sequence set */
391 for (i=0; i<num_msgs; ++i) {
394 if (msglist[i] == target_msgnum) {
395 is_seen = target_setting;
404 if (lo < 0L) lo = msglist[i];
407 if ( ((is_seen == 0) && (was_seen == 1))
408 || ((is_seen == 1) && (i == num_msgs-1)) ) {
413 while ( (strlen(vset) + 20) > sizeof vset) {
414 remove_token(vset, 0, ',');
416 if (j--) break; /* loop no more than 9 times */
418 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
422 snprintf(lostr, sizeof lostr,
423 "1:%ld,%s", t, vset);
424 safestrncpy(vset, lostr, sizeof vset);
432 snprintf(&vset[tmp], sizeof vset - tmp,
436 snprintf(&vset[tmp], sizeof vset - tmp,
445 /* Decide which message set we're manipulating */
447 case ctdlsetseen_seen:
448 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
450 case ctdlsetseen_answered:
451 safestrncpy(vbuf.v_answered, vset,
452 sizeof vbuf.v_answered);
457 lprintf(CTDL_DEBUG, " after optimize: %s\n", vset);
459 CtdlSetRelationship(&vbuf,
460 ((which_user != NULL) ? which_user : &CC->user),
461 ((which_room != NULL) ? which_room : &CC->room)
467 * API function to perform an operation for each qualifying message in the
468 * current room. (Returns the number of messages processed.)
470 int CtdlForEachMessage(int mode, long ref,
472 struct CtdlMessage *compare,
473 void (*CallBack) (long, void *),
479 struct cdbdata *cdbfr;
480 long *msglist = NULL;
482 int num_processed = 0;
485 struct CtdlMessage *msg;
488 int printed_lastold = 0;
490 /* Learn about the user and room in question */
492 getuser(&CC->user, CC->curr_user);
493 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
495 /* Load the message list */
496 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
498 msglist = malloc(cdbfr->len);
499 memcpy(msglist, cdbfr->ptr, cdbfr->len);
500 num_msgs = cdbfr->len / sizeof(long);
503 return 0; /* No messages at all? No further action. */
508 * Now begin the traversal.
510 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
512 /* If the caller is looking for a specific MIME type, filter
513 * out all messages which are not of the type requested.
515 if (content_type != NULL) if (strlen(content_type) > 0) {
517 /* This call to GetMetaData() sits inside this loop
518 * so that we only do the extra database read per msg
519 * if we need to. Doing the extra read all the time
520 * really kills the server. If we ever need to use
521 * metadata for another search criterion, we need to
522 * move the read somewhere else -- but still be smart
523 * enough to only do the read if the caller has
524 * specified something that will need it.
526 GetMetaData(&smi, msglist[a]);
528 if (strcasecmp(smi.meta_content_type, content_type)) {
534 num_msgs = sort_msglist(msglist, num_msgs);
536 /* If a template was supplied, filter out the messages which
537 * don't match. (This could induce some delays!)
540 if (compare != NULL) {
541 for (a = 0; a < num_msgs; ++a) {
542 msg = CtdlFetchMessage(msglist[a], 1);
544 if (CtdlMsgCmp(msg, compare)) {
547 CtdlFreeMessage(msg);
555 * Now iterate through the message list, according to the
556 * criteria supplied by the caller.
559 for (a = 0; a < num_msgs; ++a) {
560 thismsg = msglist[a];
561 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
562 if (is_seen) lastold = thismsg;
567 || ((mode == MSGS_OLD) && (is_seen))
568 || ((mode == MSGS_NEW) && (!is_seen))
569 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
570 || ((mode == MSGS_FIRST) && (a < ref))
571 || ((mode == MSGS_GT) && (thismsg > ref))
572 || ((mode == MSGS_EQ) && (thismsg == ref))
575 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
577 CallBack(lastold, userdata);
581 if (CallBack) CallBack(thismsg, userdata);
585 free(msglist); /* Clean up */
586 return num_processed;
592 * cmd_msgs() - get list of message #'s in this room
593 * implements the MSGS server command using CtdlForEachMessage()
595 void cmd_msgs(char *cmdbuf)
604 int with_template = 0;
605 struct CtdlMessage *template = NULL;
607 extract_token(which, cmdbuf, 0, '|', sizeof which);
608 cm_ref = extract_int(cmdbuf, 1);
609 with_template = extract_int(cmdbuf, 2);
613 if (!strncasecmp(which, "OLD", 3))
615 else if (!strncasecmp(which, "NEW", 3))
617 else if (!strncasecmp(which, "FIRST", 5))
619 else if (!strncasecmp(which, "LAST", 4))
621 else if (!strncasecmp(which, "GT", 2))
624 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
625 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
631 cprintf("%d Send template then receive message list\n",
633 template = (struct CtdlMessage *)
634 malloc(sizeof(struct CtdlMessage));
635 memset(template, 0, sizeof(struct CtdlMessage));
636 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
637 extract_token(tfield, buf, 0, '|', sizeof tfield);
638 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
639 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
640 if (!strcasecmp(tfield, msgkeys[i])) {
641 template->cm_fields[i] =
649 cprintf("%d Message list...\n", LISTING_FOLLOWS);
652 CtdlForEachMessage(mode, cm_ref,
653 NULL, template, simple_listing, NULL);
654 if (template != NULL) CtdlFreeMessage(template);
662 * help_subst() - support routine for help file viewer
664 void help_subst(char *strbuf, char *source, char *dest)
669 while (p = pattern2(strbuf, source), (p >= 0)) {
670 strcpy(workbuf, &strbuf[p + strlen(source)]);
671 strcpy(&strbuf[p], dest);
672 strcat(strbuf, workbuf);
677 void do_help_subst(char *buffer)
681 help_subst(buffer, "^nodename", config.c_nodename);
682 help_subst(buffer, "^humannode", config.c_humannode);
683 help_subst(buffer, "^fqdn", config.c_fqdn);
684 help_subst(buffer, "^username", CC->user.fullname);
685 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
686 help_subst(buffer, "^usernum", buf2);
687 help_subst(buffer, "^sysadm", config.c_sysadm);
688 help_subst(buffer, "^variantname", CITADEL);
689 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
690 help_subst(buffer, "^maxsessions", buf2);
691 help_subst(buffer, "^bbsdir", CTDLDIR);
697 * memfmout() - Citadel text formatter and paginator.
698 * Although the original purpose of this routine was to format
699 * text to the reader's screen width, all we're really using it
700 * for here is to format text out to 80 columns before sending it
701 * to the client. The client software may reformat it again.
704 int width, /* screen width to use */
705 char *mptr, /* where are we going to get our text from? */
706 char subst, /* nonzero if we should do substitutions */
707 char *nl) /* string to terminate lines with */
719 c = 1; /* c is the current pos */
723 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
725 buffer[strlen(buffer) + 1] = 0;
726 buffer[strlen(buffer)] = ch;
729 if (buffer[0] == '^')
730 do_help_subst(buffer);
732 buffer[strlen(buffer) + 1] = 0;
734 strcpy(buffer, &buffer[1]);
742 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
744 if (((old == 13) || (old == 10)) && (isspace(real))) {
752 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
753 cprintf("%s%s", nl, aaa);
762 if ((strlen(aaa) + c) > (width - 5)) {
771 if ((ch == 13) || (ch == 10)) {
772 cprintf("%s%s", aaa, nl);
779 cprintf("%s%s", aaa, nl);
785 * Callback function for mime parser that simply lists the part
787 void list_this_part(char *name, char *filename, char *partnum, char *disp,
788 void *content, char *cbtype, size_t length, char *encoding,
792 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
793 name, filename, partnum, disp, cbtype, (long)length);
797 * Callback function for multipart prefix
799 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
800 void *content, char *cbtype, size_t length, char *encoding,
803 cprintf("pref=%s|%s\n", partnum, cbtype);
807 * Callback function for multipart sufffix
809 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
810 void *content, char *cbtype, size_t length, char *encoding,
813 cprintf("suff=%s|%s\n", partnum, cbtype);
818 * Callback function for mime parser that opens a section for downloading
820 void mime_download(char *name, char *filename, char *partnum, char *disp,
821 void *content, char *cbtype, size_t length, char *encoding,
825 /* Silently go away if there's already a download open... */
826 if (CC->download_fp != NULL)
829 /* ...or if this is not the desired section */
830 if (strcasecmp(CC->download_desired_section, partnum))
833 CC->download_fp = tmpfile();
834 if (CC->download_fp == NULL)
837 fwrite(content, length, 1, CC->download_fp);
838 fflush(CC->download_fp);
839 rewind(CC->download_fp);
841 OpenCmdResult(filename, cbtype);
847 * Load a message from disk into memory.
848 * This is used by CtdlOutputMsg() and other fetch functions.
850 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
851 * using the CtdlMessageFree() function.
853 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
855 struct cdbdata *dmsgtext;
856 struct CtdlMessage *ret = NULL;
860 cit_uint8_t field_header;
862 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
864 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
865 if (dmsgtext == NULL) {
868 mptr = dmsgtext->ptr;
869 upper_bound = mptr + dmsgtext->len;
871 /* Parse the three bytes that begin EVERY message on disk.
872 * The first is always 0xFF, the on-disk magic number.
873 * The second is the anonymous/public type byte.
874 * The third is the format type byte (vari, fixed, or MIME).
879 "Message %ld appears to be corrupted.\n",
884 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
885 memset(ret, 0, sizeof(struct CtdlMessage));
887 ret->cm_magic = CTDLMESSAGE_MAGIC;
888 ret->cm_anon_type = *mptr++; /* Anon type byte */
889 ret->cm_format_type = *mptr++; /* Format type byte */
892 * The rest is zero or more arbitrary fields. Load them in.
893 * We're done when we encounter either a zero-length field or
894 * have just processed the 'M' (message text) field.
897 if (mptr >= upper_bound) {
900 field_header = *mptr++;
901 ret->cm_fields[field_header] = strdup(mptr);
903 while (*mptr++ != 0); /* advance to next field */
905 } while ((mptr < upper_bound) && (field_header != 'M'));
909 /* Always make sure there's something in the msg text field. If
910 * it's NULL, the message text is most likely stored separately,
911 * so go ahead and fetch that. Failing that, just set a dummy
912 * body so other code doesn't barf.
914 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
915 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
916 if (dmsgtext != NULL) {
917 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
921 if (ret->cm_fields['M'] == NULL) {
922 ret->cm_fields['M'] = strdup("<no text>\n");
925 /* Perform "before read" hooks (aborting if any return nonzero) */
926 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
927 CtdlFreeMessage(ret);
936 * Returns 1 if the supplied pointer points to a valid Citadel message.
937 * If the pointer is NULL or the magic number check fails, returns 0.
939 int is_valid_message(struct CtdlMessage *msg) {
942 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
943 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
951 * 'Destructor' for struct CtdlMessage
953 void CtdlFreeMessage(struct CtdlMessage *msg)
957 if (is_valid_message(msg) == 0) return;
959 for (i = 0; i < 256; ++i)
960 if (msg->cm_fields[i] != NULL) {
961 free(msg->cm_fields[i]);
964 msg->cm_magic = 0; /* just in case */
970 * Pre callback function for multipart/alternative
972 * NOTE: this differs from the standard behavior for a reason. Normally when
973 * displaying multipart/alternative you want to show the _last_ usable
974 * format in the message. Here we show the _first_ one, because it's
975 * usually text/plain. Since this set of functions is designed for text
976 * output to non-MIME-aware clients, this is the desired behavior.
979 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
980 void *content, char *cbtype, size_t length, char *encoding,
985 ma = (struct ma_info *)cbuserdata;
986 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
987 if (!strcasecmp(cbtype, "multipart/alternative")) {
995 * Post callback function for multipart/alternative
997 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
998 void *content, char *cbtype, size_t length, char *encoding,
1003 ma = (struct ma_info *)cbuserdata;
1004 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1005 if (!strcasecmp(cbtype, "multipart/alternative")) {
1013 * Inline callback function for mime parser that wants to display text
1015 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1016 void *content, char *cbtype, size_t length, char *encoding,
1024 ma = (struct ma_info *)cbuserdata;
1026 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1029 * If we're in the middle of a multipart/alternative scope and
1030 * we've already printed another section, skip this one.
1032 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1033 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1038 if ( (!strcasecmp(cbtype, "text/plain"))
1039 || (strlen(cbtype)==0) ) {
1042 client_write(wptr, length);
1043 if (wptr[length-1] != '\n') {
1048 else if (!strcasecmp(cbtype, "text/html")) {
1049 ptr = html_to_ascii(content, 80, 0);
1051 client_write(ptr, wlen);
1052 if (ptr[wlen-1] != '\n') {
1057 else if (strncasecmp(cbtype, "multipart/", 10)) {
1058 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1059 partnum, filename, cbtype, (long)length);
1064 * The client is elegant and sophisticated and wants to be choosy about
1065 * MIME content types, so figure out which multipart/alternative part
1066 * we're going to send.
1068 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1069 void *content, char *cbtype, size_t length, char *encoding,
1076 ma = (struct ma_info *)cbuserdata;
1078 if (ma->is_ma > 0) {
1079 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1080 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1081 if (!strcasecmp(buf, cbtype)) {
1082 strcpy(ma->chosen_part, partnum);
1089 * Now that we've chosen our preferred part, output it.
1091 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1092 void *content, char *cbtype, size_t length, char *encoding,
1097 int add_newline = 0;
1101 ma = (struct ma_info *)cbuserdata;
1103 /* This is not the MIME part you're looking for... */
1104 if (strcasecmp(partnum, ma->chosen_part)) return;
1106 /* If the content-type of this part is in our preferred formats
1107 * list, we can simply output it verbatim.
1109 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1110 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1111 if (!strcasecmp(buf, cbtype)) {
1112 /* Yeah! Go! W00t!! */
1114 text_content = (char *)content;
1115 if (text_content[length-1] != '\n') {
1119 cprintf("Content-type: %s\n", cbtype);
1120 cprintf("Content-length: %d\n",
1121 (int)(length + add_newline) );
1122 if (strlen(encoding) > 0) {
1123 cprintf("Content-transfer-encoding: %s\n", encoding);
1126 cprintf("Content-transfer-encoding: 7bit\n");
1129 client_write(content, length);
1130 if (add_newline) cprintf("\n");
1135 /* No translations required or possible: output as text/plain */
1136 cprintf("Content-type: text/plain\n\n");
1137 fixed_output(name, filename, partnum, disp, content, cbtype,
1138 length, encoding, cbuserdata);
1143 * Get a message off disk. (returns om_* values found in msgbase.h)
1146 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1147 int mode, /* how would you like that message? */
1148 int headers_only, /* eschew the message body? */
1149 int do_proto, /* do Citadel protocol responses? */
1150 int crlf /* Use CRLF newlines instead of LF? */
1152 struct CtdlMessage *TheMessage = NULL;
1153 int retcode = om_no_such_msg;
1155 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1158 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1159 if (do_proto) cprintf("%d Not logged in.\n",
1160 ERROR + NOT_LOGGED_IN);
1161 return(om_not_logged_in);
1164 /* FIXME: check message id against msglist for this room */
1167 * Fetch the message from disk. If we're in any sort of headers
1168 * only mode, request that we don't even bother loading the body
1171 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1172 TheMessage = CtdlFetchMessage(msg_num, 0);
1175 TheMessage = CtdlFetchMessage(msg_num, 1);
1178 if (TheMessage == NULL) {
1179 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1180 ERROR + MESSAGE_NOT_FOUND, msg_num);
1181 return(om_no_such_msg);
1184 retcode = CtdlOutputPreLoadedMsg(
1185 TheMessage, msg_num, mode,
1186 headers_only, do_proto, crlf);
1188 CtdlFreeMessage(TheMessage);
1195 * Get a message off disk. (returns om_* values found in msgbase.h)
1198 int CtdlOutputPreLoadedMsg(
1199 struct CtdlMessage *TheMessage,
1201 int mode, /* how would you like that message? */
1202 int headers_only, /* eschew the message body? */
1203 int do_proto, /* do Citadel protocol responses? */
1204 int crlf /* Use CRLF newlines instead of LF? */
1210 char display_name[256];
1212 char *nl; /* newline string */
1214 int subject_found = 0;
1217 /* Buffers needed for RFC822 translation. These are all filled
1218 * using functions that are bounds-checked, and therefore we can
1219 * make them substantially smaller than SIZ.
1227 char datestamp[100];
1229 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1230 ((TheMessage == NULL) ? "NULL" : "not null"),
1232 mode, headers_only, do_proto, crlf);
1234 snprintf(mid, sizeof mid, "%ld", msg_num);
1235 nl = (crlf ? "\r\n" : "\n");
1237 if (!is_valid_message(TheMessage)) {
1239 "ERROR: invalid preloaded message for output\n");
1240 return(om_no_such_msg);
1243 /* Are we downloading a MIME component? */
1244 if (mode == MT_DOWNLOAD) {
1245 if (TheMessage->cm_format_type != FMT_RFC822) {
1247 cprintf("%d This is not a MIME message.\n",
1248 ERROR + ILLEGAL_VALUE);
1249 } else if (CC->download_fp != NULL) {
1250 if (do_proto) cprintf(
1251 "%d You already have a download open.\n",
1252 ERROR + RESOURCE_BUSY);
1254 /* Parse the message text component */
1255 mptr = TheMessage->cm_fields['M'];
1256 ma = malloc(sizeof(struct ma_info));
1257 memset(ma, 0, sizeof(struct ma_info));
1258 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1260 /* If there's no file open by this time, the requested
1261 * section wasn't found, so print an error
1263 if (CC->download_fp == NULL) {
1264 if (do_proto) cprintf(
1265 "%d Section %s not found.\n",
1266 ERROR + FILE_NOT_FOUND,
1267 CC->download_desired_section);
1270 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1273 /* now for the user-mode message reading loops */
1274 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1276 /* Does the caller want to skip the headers? */
1277 if (headers_only == HEADERS_NONE) goto START_TEXT;
1279 /* Tell the client which format type we're using. */
1280 if ( (mode == MT_CITADEL) && (do_proto) ) {
1281 cprintf("type=%d\n", TheMessage->cm_format_type);
1284 /* nhdr=yes means that we're only displaying headers, no body */
1285 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1286 && (mode == MT_CITADEL)
1289 cprintf("nhdr=yes\n");
1292 /* begin header processing loop for Citadel message format */
1294 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1296 safestrncpy(display_name, "<unknown>", sizeof display_name);
1297 if (TheMessage->cm_fields['A']) {
1298 strcpy(buf, TheMessage->cm_fields['A']);
1299 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1300 safestrncpy(display_name, "****", sizeof display_name);
1302 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1303 safestrncpy(display_name, "anonymous", sizeof display_name);
1306 safestrncpy(display_name, buf, sizeof display_name);
1308 if ((is_room_aide())
1309 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1310 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1311 size_t tmp = strlen(display_name);
1312 snprintf(&display_name[tmp],
1313 sizeof display_name - tmp,
1318 /* Don't show Internet address for users on the
1319 * local Citadel network.
1322 if (TheMessage->cm_fields['N'] != NULL)
1323 if (strlen(TheMessage->cm_fields['N']) > 0)
1324 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1328 /* Now spew the header fields in the order we like them. */
1329 safestrncpy(allkeys, FORDER, sizeof allkeys);
1330 for (i=0; i<strlen(allkeys); ++i) {
1331 k = (int) allkeys[i];
1333 if ( (TheMessage->cm_fields[k] != NULL)
1334 && (msgkeys[k] != NULL) ) {
1336 if (do_proto) cprintf("%s=%s\n",
1340 else if ((k == 'F') && (suppress_f)) {
1343 /* Masquerade display name if needed */
1345 if (do_proto) cprintf("%s=%s\n",
1347 TheMessage->cm_fields[k]
1356 /* begin header processing loop for RFC822 transfer format */
1361 strcpy(snode, NODENAME);
1362 strcpy(lnode, HUMANNODE);
1363 if (mode == MT_RFC822) {
1364 for (i = 0; i < 256; ++i) {
1365 if (TheMessage->cm_fields[i]) {
1366 mptr = TheMessage->cm_fields[i];
1369 safestrncpy(luser, mptr, sizeof luser);
1370 safestrncpy(suser, mptr, sizeof suser);
1372 else if (i == 'U') {
1373 cprintf("Subject: %s%s", mptr, nl);
1377 safestrncpy(mid, mptr, sizeof mid);
1379 safestrncpy(lnode, mptr, sizeof lnode);
1381 safestrncpy(fuser, mptr, sizeof fuser);
1382 /* else if (i == 'O')
1383 cprintf("X-Citadel-Room: %s%s",
1386 safestrncpy(snode, mptr, sizeof snode);
1388 cprintf("To: %s%s", mptr, nl);
1389 else if (i == 'T') {
1390 datestring(datestamp, sizeof datestamp,
1391 atol(mptr), DATESTRING_RFC822);
1392 cprintf("Date: %s%s", datestamp, nl);
1396 if (subject_found == 0) {
1397 cprintf("Subject: (no subject)%s", nl);
1401 for (i=0; i<strlen(suser); ++i) {
1402 suser[i] = tolower(suser[i]);
1403 if (!isalnum(suser[i])) suser[i]='_';
1406 if (mode == MT_RFC822) {
1407 if (!strcasecmp(snode, NODENAME)) {
1408 safestrncpy(snode, FQDN, sizeof snode);
1411 /* Construct a fun message id */
1412 cprintf("Message-ID: <%s", mid);
1413 if (strchr(mid, '@')==NULL) {
1414 cprintf("@%s", snode);
1418 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1419 // cprintf("From: x@x.org (----)%s", nl);
1420 cprintf("From: \"----\" <x@x.org>%s", nl);
1422 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1423 // cprintf("From: x@x.org (anonymous)%s", nl);
1424 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1426 else if (strlen(fuser) > 0) {
1427 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1428 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1431 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1432 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1435 cprintf("Organization: %s%s", lnode, nl);
1437 /* Blank line signifying RFC822 end-of-headers */
1438 if (TheMessage->cm_format_type != FMT_RFC822) {
1443 /* end header processing loop ... at this point, we're in the text */
1445 if (headers_only == HEADERS_FAST) goto DONE;
1446 mptr = TheMessage->cm_fields['M'];
1448 /* Tell the client about the MIME parts in this message */
1449 if (TheMessage->cm_format_type == FMT_RFC822) {
1450 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1451 mime_parser(mptr, NULL,
1452 (do_proto ? *list_this_part : NULL),
1453 (do_proto ? *list_this_pref : NULL),
1454 (do_proto ? *list_this_suff : NULL),
1457 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1458 /* FIXME ... we have to put some code in here to avoid
1459 * printing duplicate header information when both
1460 * Citadel and RFC822 headers exist. Preference should
1461 * probably be given to the RFC822 headers.
1463 int done_rfc822_hdrs = 0;
1464 while (ch=*(mptr++), ch!=0) {
1469 if (!done_rfc822_hdrs) {
1470 if (headers_only != HEADERS_NONE) {
1475 if (headers_only != HEADERS_ONLY) {
1479 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1480 done_rfc822_hdrs = 1;
1484 if (done_rfc822_hdrs) {
1485 if (headers_only != HEADERS_NONE) {
1490 if (headers_only != HEADERS_ONLY) {
1494 if ((*mptr == 13) || (*mptr == 10)) {
1495 done_rfc822_hdrs = 1;
1503 if (headers_only == HEADERS_ONLY) {
1507 /* signify start of msg text */
1508 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1509 if (do_proto) cprintf("text\n");
1512 /* If the format type on disk is 1 (fixed-format), then we want
1513 * everything to be output completely literally ... regardless of
1514 * what message transfer format is in use.
1516 if (TheMessage->cm_format_type == FMT_FIXED) {
1517 if (mode == MT_MIME) {
1518 cprintf("Content-type: text/plain\n\n");
1521 while (ch = *mptr++, ch > 0) {
1524 if ((ch == 10) || (strlen(buf) > 250)) {
1525 cprintf("%s%s", buf, nl);
1528 buf[strlen(buf) + 1] = 0;
1529 buf[strlen(buf)] = ch;
1532 if (strlen(buf) > 0)
1533 cprintf("%s%s", buf, nl);
1536 /* If the message on disk is format 0 (Citadel vari-format), we
1537 * output using the formatter at 80 columns. This is the final output
1538 * form if the transfer format is RFC822, but if the transfer format
1539 * is Citadel proprietary, it'll still work, because the indentation
1540 * for new paragraphs is correct and the client will reformat the
1541 * message to the reader's screen width.
1543 if (TheMessage->cm_format_type == FMT_CITADEL) {
1544 if (mode == MT_MIME) {
1545 cprintf("Content-type: text/x-citadel-variformat\n\n");
1547 memfmout(80, mptr, 0, nl);
1550 /* If the message on disk is format 4 (MIME), we've gotta hand it
1551 * off to the MIME parser. The client has already been told that
1552 * this message is format 1 (fixed format), so the callback function
1553 * we use will display those parts as-is.
1555 if (TheMessage->cm_format_type == FMT_RFC822) {
1556 ma = malloc(sizeof(struct ma_info));
1557 memset(ma, 0, sizeof(struct ma_info));
1559 if (mode == MT_MIME) {
1560 strcpy(ma->chosen_part, "1");
1561 mime_parser(mptr, NULL,
1562 *choose_preferred, *fixed_output_pre,
1563 *fixed_output_post, (void *)ma, 0);
1564 mime_parser(mptr, NULL,
1565 *output_preferred, NULL, NULL, (void *)ma, 0);
1568 mime_parser(mptr, NULL,
1569 *fixed_output, *fixed_output_pre,
1570 *fixed_output_post, (void *)ma, 0);
1576 DONE: /* now we're done */
1577 if (do_proto) cprintf("000\n");
1584 * display a message (mode 0 - Citadel proprietary)
1586 void cmd_msg0(char *cmdbuf)
1589 int headers_only = HEADERS_ALL;
1591 msgid = extract_long(cmdbuf, 0);
1592 headers_only = extract_int(cmdbuf, 1);
1594 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1600 * display a message (mode 2 - RFC822)
1602 void cmd_msg2(char *cmdbuf)
1605 int headers_only = HEADERS_ALL;
1607 msgid = extract_long(cmdbuf, 0);
1608 headers_only = extract_int(cmdbuf, 1);
1610 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1616 * display a message (mode 3 - IGnet raw format - internal programs only)
1618 void cmd_msg3(char *cmdbuf)
1621 struct CtdlMessage *msg;
1624 if (CC->internal_pgm == 0) {
1625 cprintf("%d This command is for internal programs only.\n",
1626 ERROR + HIGHER_ACCESS_REQUIRED);
1630 msgnum = extract_long(cmdbuf, 0);
1631 msg = CtdlFetchMessage(msgnum, 1);
1633 cprintf("%d Message %ld not found.\n",
1634 ERROR + MESSAGE_NOT_FOUND, msgnum);
1638 serialize_message(&smr, msg);
1639 CtdlFreeMessage(msg);
1642 cprintf("%d Unable to serialize message\n",
1643 ERROR + INTERNAL_ERROR);
1647 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1648 client_write((char *)smr.ser, (int)smr.len);
1655 * Display a message using MIME content types
1657 void cmd_msg4(char *cmdbuf)
1661 msgid = extract_long(cmdbuf, 0);
1662 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1668 * Client tells us its preferred message format(s)
1670 void cmd_msgp(char *cmdbuf)
1672 safestrncpy(CC->preferred_formats, cmdbuf,
1673 sizeof(CC->preferred_formats));
1674 cprintf("%d ok\n", CIT_OK);
1679 * Open a component of a MIME message as a download file
1681 void cmd_opna(char *cmdbuf)
1684 char desired_section[128];
1686 msgid = extract_long(cmdbuf, 0);
1687 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1688 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1689 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1694 * Save a message pointer into a specified room
1695 * (Returns 0 for success, nonzero for failure)
1696 * roomname may be NULL to use the current room
1698 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1700 char hold_rm[ROOMNAMELEN];
1701 struct cdbdata *cdbfr;
1704 long highest_msg = 0L;
1705 struct CtdlMessage *msg = NULL;
1707 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1708 roomname, msgid, flags);
1710 strcpy(hold_rm, CC->room.QRname);
1712 /* We may need to check to see if this message is real */
1713 if ( (flags & SM_VERIFY_GOODNESS)
1714 || (flags & SM_DO_REPL_CHECK)
1716 msg = CtdlFetchMessage(msgid, 1);
1717 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1720 /* Perform replication checks if necessary */
1721 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1723 if (getroom(&CC->room,
1724 ((roomname != NULL) ? roomname : CC->room.QRname) )
1726 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1727 if (msg != NULL) CtdlFreeMessage(msg);
1728 return(ERROR + ROOM_NOT_FOUND);
1731 if (ReplicationChecks(msg) != 0) {
1732 getroom(&CC->room, hold_rm);
1733 if (msg != NULL) CtdlFreeMessage(msg);
1735 "Did replication, and newer exists\n");
1740 /* Now the regular stuff */
1741 if (lgetroom(&CC->room,
1742 ((roomname != NULL) ? roomname : CC->room.QRname) )
1744 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1745 if (msg != NULL) CtdlFreeMessage(msg);
1746 return(ERROR + ROOM_NOT_FOUND);
1749 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1750 if (cdbfr == NULL) {
1754 msglist = malloc(cdbfr->len);
1755 if (msglist == NULL)
1756 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1757 num_msgs = cdbfr->len / sizeof(long);
1758 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1763 /* Make sure the message doesn't already exist in this room. It
1764 * is absolutely taboo to have more than one reference to the same
1765 * message in a room.
1767 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1768 if (msglist[i] == msgid) {
1769 lputroom(&CC->room); /* unlock the room */
1770 getroom(&CC->room, hold_rm);
1771 if (msg != NULL) CtdlFreeMessage(msg);
1773 return(ERROR + ALREADY_EXISTS);
1777 /* Now add the new message */
1779 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1781 if (msglist == NULL) {
1782 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1784 msglist[num_msgs - 1] = msgid;
1786 /* Sort the message list, so all the msgid's are in order */
1787 num_msgs = sort_msglist(msglist, num_msgs);
1789 /* Determine the highest message number */
1790 highest_msg = msglist[num_msgs - 1];
1792 /* Write it back to disk. */
1793 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1794 msglist, (int)(num_msgs * sizeof(long)));
1796 /* Free up the memory we used. */
1799 /* Update the highest-message pointer and unlock the room. */
1800 CC->room.QRhighest = highest_msg;
1801 lputroom(&CC->room);
1802 getroom(&CC->room, hold_rm);
1804 /* Bump the reference count for this message. */
1805 if ((flags & SM_DONT_BUMP_REF)==0) {
1806 AdjRefCount(msgid, +1);
1809 /* Return success. */
1810 if (msg != NULL) CtdlFreeMessage(msg);
1817 * Message base operation to save a new message to the message store
1818 * (returns new message number)
1820 * This is the back end for CtdlSubmitMsg() and should not be directly
1821 * called by server-side modules.
1824 long send_message(struct CtdlMessage *msg) {
1832 /* Get a new message number */
1833 newmsgid = get_new_message_number();
1834 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1836 /* Generate an ID if we don't have one already */
1837 if (msg->cm_fields['I']==NULL) {
1838 msg->cm_fields['I'] = strdup(msgidbuf);
1841 /* If the message is big, set its body aside for storage elsewhere */
1842 if (msg->cm_fields['M'] != NULL) {
1843 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1845 holdM = msg->cm_fields['M'];
1846 msg->cm_fields['M'] = NULL;
1850 /* Serialize our data structure for storage in the database */
1851 serialize_message(&smr, msg);
1854 msg->cm_fields['M'] = holdM;
1858 cprintf("%d Unable to serialize message\n",
1859 ERROR + INTERNAL_ERROR);
1863 /* Write our little bundle of joy into the message base */
1864 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1865 smr.ser, smr.len) < 0) {
1866 lprintf(CTDL_ERR, "Can't store message\n");
1870 cdb_store(CDB_BIGMSGS,
1880 /* Free the memory we used for the serialized message */
1883 /* Return the *local* message ID to the caller
1884 * (even if we're storing an incoming network message)
1892 * Serialize a struct CtdlMessage into the format used on disk and network.
1894 * This function loads up a "struct ser_ret" (defined in server.h) which
1895 * contains the length of the serialized message and a pointer to the
1896 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1898 void serialize_message(struct ser_ret *ret, /* return values */
1899 struct CtdlMessage *msg) /* unserialized msg */
1903 static char *forder = FORDER;
1905 if (is_valid_message(msg) == 0) return; /* self check */
1908 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1909 ret->len = ret->len +
1910 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1912 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1913 ret->ser = malloc(ret->len);
1914 if (ret->ser == NULL) {
1920 ret->ser[1] = msg->cm_anon_type;
1921 ret->ser[2] = msg->cm_format_type;
1924 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1925 ret->ser[wlen++] = (char)forder[i];
1926 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1927 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1929 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1930 (long)ret->len, (long)wlen);
1938 * Back end for the ReplicationChecks() function
1940 void check_repl(long msgnum, void *userdata) {
1941 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1942 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1947 * Check to see if any messages already exist which carry the same Exclusive ID
1948 * as this one. If any are found, delete them.
1951 int ReplicationChecks(struct CtdlMessage *msg) {
1952 struct CtdlMessage *template;
1955 /* No exclusive id? Don't do anything. */
1956 if (msg->cm_fields['E'] == NULL) return 0;
1957 if (strlen(msg->cm_fields['E']) == 0) return 0;
1958 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1960 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1961 memset(template, 0, sizeof(struct CtdlMessage));
1962 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1964 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1966 CtdlFreeMessage(template);
1967 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1975 * Save a message to disk and submit it into the delivery system.
1977 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1978 struct recptypes *recps, /* recipients (if mail) */
1979 char *force /* force a particular room? */
1981 char submit_filename[128];
1982 char generated_timestamp[32];
1983 char hold_rm[ROOMNAMELEN];
1984 char actual_rm[ROOMNAMELEN];
1985 char force_room[ROOMNAMELEN];
1986 char content_type[SIZ]; /* We have to learn this */
1987 char recipient[SIZ];
1990 struct ctdluser userbuf;
1992 struct MetaData smi;
1993 FILE *network_fp = NULL;
1994 static int seqnum = 1;
1995 struct CtdlMessage *imsg = NULL;
1998 char *hold_R, *hold_D;
2000 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2001 if (is_valid_message(msg) == 0) return(-1); /* self check */
2003 /* If this message has no timestamp, we take the liberty of
2004 * giving it one, right now.
2006 if (msg->cm_fields['T'] == NULL) {
2007 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2008 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2009 msg->cm_fields['T'] = strdup(generated_timestamp);
2012 /* If this message has no path, we generate one.
2014 if (msg->cm_fields['P'] == NULL) {
2015 lprintf(CTDL_DEBUG, "Generating path\n");
2016 if (msg->cm_fields['A'] != NULL) {
2017 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2018 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2019 if (isspace(msg->cm_fields['P'][a])) {
2020 msg->cm_fields['P'][a] = ' ';
2025 msg->cm_fields['P'] = strdup("unknown");
2029 if (force == NULL) {
2030 strcpy(force_room, "");
2033 strcpy(force_room, force);
2036 /* Learn about what's inside, because it's what's inside that counts */
2037 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2038 if (msg->cm_fields['M'] == NULL) {
2039 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2043 switch (msg->cm_format_type) {
2045 strcpy(content_type, "text/x-citadel-variformat");
2048 strcpy(content_type, "text/plain");
2051 strcpy(content_type, "text/plain");
2052 mptr = bmstrstr(msg->cm_fields['M'],
2053 "Content-type: ", strncasecmp);
2055 safestrncpy(content_type, &mptr[14],
2056 sizeof content_type);
2057 for (a = 0; a < strlen(content_type); ++a) {
2058 if ((content_type[a] == ';')
2059 || (content_type[a] == ' ')
2060 || (content_type[a] == 13)
2061 || (content_type[a] == 10)) {
2062 content_type[a] = 0;
2068 /* Goto the correct room */
2069 lprintf(CTDL_DEBUG, "Selected room %s\n",
2070 (recps) ? CC->room.QRname : SENTITEMS);
2071 strcpy(hold_rm, CC->room.QRname);
2072 strcpy(actual_rm, CC->room.QRname);
2073 if (recps != NULL) {
2074 strcpy(actual_rm, SENTITEMS);
2077 /* If the user is a twit, move to the twit room for posting */
2078 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2079 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2081 if (CC->user.axlevel == 2) {
2082 strcpy(hold_rm, actual_rm);
2083 strcpy(actual_rm, config.c_twitroom);
2087 /* ...or if this message is destined for Aide> then go there. */
2088 if (strlen(force_room) > 0) {
2089 strcpy(actual_rm, force_room);
2092 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2093 if (strcasecmp(actual_rm, CC->room.QRname)) {
2094 /* getroom(&CC->room, actual_rm); */
2095 usergoto(actual_rm, 0, 1, NULL, NULL);
2099 * If this message has no O (room) field, generate one.
2101 if (msg->cm_fields['O'] == NULL) {
2102 msg->cm_fields['O'] = strdup(CC->room.QRname);
2105 /* Perform "before save" hooks (aborting if any return nonzero) */
2106 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2107 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2109 /* If this message has an Exclusive ID, perform replication checks */
2110 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2111 if (ReplicationChecks(msg) > 0) return(-4);
2113 /* Save it to disk */
2114 lprintf(CTDL_DEBUG, "Saving to disk\n");
2115 newmsgid = send_message(msg);
2116 if (newmsgid <= 0L) return(-5);
2118 /* Write a supplemental message info record. This doesn't have to
2119 * be a critical section because nobody else knows about this message
2122 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2123 memset(&smi, 0, sizeof(struct MetaData));
2124 smi.meta_msgnum = newmsgid;
2125 smi.meta_refcount = 0;
2126 safestrncpy(smi.meta_content_type, content_type,
2127 sizeof smi.meta_content_type);
2129 /* As part of the new metadata record, measure how
2130 * big this message will be when displayed as RFC822.
2131 * Both POP and IMAP use this, and it's best to just take the hit now
2132 * instead of having to potentially measure thousands of messages when
2133 * a mailbox is opened later.
2136 if (CC->redirect_buffer != NULL) {
2137 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2140 CC->redirect_buffer = malloc(SIZ);
2141 CC->redirect_len = 0;
2142 CC->redirect_alloc = SIZ;
2143 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2144 smi.meta_rfc822_length = CC->redirect_len;
2145 free(CC->redirect_buffer);
2146 CC->redirect_buffer = NULL;
2147 CC->redirect_len = 0;
2148 CC->redirect_alloc = 0;
2152 /* Now figure out where to store the pointers */
2153 lprintf(CTDL_DEBUG, "Storing pointers\n");
2155 /* If this is being done by the networker delivering a private
2156 * message, we want to BYPASS saving the sender's copy (because there
2157 * is no local sender; it would otherwise go to the Trashcan).
2159 if ((!CC->internal_pgm) || (recps == NULL)) {
2160 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2161 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2162 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2167 /* For internet mail, drop a copy in the outbound queue room */
2169 if (recps->num_internet > 0) {
2170 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2173 /* If other rooms are specified, drop them there too. */
2175 if (recps->num_room > 0)
2176 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2177 extract_token(recipient, recps->recp_room, i,
2178 '|', sizeof recipient);
2179 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2180 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2183 /* Bump this user's messages posted counter. */
2184 lprintf(CTDL_DEBUG, "Updating user\n");
2185 lgetuser(&CC->user, CC->curr_user);
2186 CC->user.posted = CC->user.posted + 1;
2187 lputuser(&CC->user);
2189 /* If this is private, local mail, make a copy in the
2190 * recipient's mailbox and bump the reference count.
2193 if (recps->num_local > 0)
2194 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2195 extract_token(recipient, recps->recp_local, i,
2196 '|', sizeof recipient);
2197 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2199 if (getuser(&userbuf, recipient) == 0) {
2200 MailboxName(actual_rm, sizeof actual_rm,
2201 &userbuf, MAILROOM);
2202 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2203 BumpNewMailCounter(userbuf.usernum);
2206 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2207 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2212 /* Perform "after save" hooks */
2213 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2214 PerformMessageHooks(msg, EVT_AFTERSAVE);
2216 /* For IGnet mail, we have to save a new copy into the spooler for
2217 * each recipient, with the R and D fields set to the recipient and
2218 * destination-node. This has two ugly side effects: all other
2219 * recipients end up being unlisted in this recipient's copy of the
2220 * message, and it has to deliver multiple messages to the same
2221 * node. We'll revisit this again in a year or so when everyone has
2222 * a network spool receiver that can handle the new style messages.
2225 if (recps->num_ignet > 0)
2226 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2227 extract_token(recipient, recps->recp_ignet, i,
2228 '|', sizeof recipient);
2230 hold_R = msg->cm_fields['R'];
2231 hold_D = msg->cm_fields['D'];
2232 msg->cm_fields['R'] = malloc(SIZ);
2233 msg->cm_fields['D'] = malloc(128);
2234 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2235 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2237 serialize_message(&smr, msg);
2239 snprintf(submit_filename, sizeof submit_filename,
2240 "./network/spoolin/netmail.%04lx.%04x.%04x",
2241 (long) getpid(), CC->cs_pid, ++seqnum);
2242 network_fp = fopen(submit_filename, "wb+");
2243 if (network_fp != NULL) {
2244 fwrite(smr.ser, smr.len, 1, network_fp);
2250 free(msg->cm_fields['R']);
2251 free(msg->cm_fields['D']);
2252 msg->cm_fields['R'] = hold_R;
2253 msg->cm_fields['D'] = hold_D;
2256 /* Go back to the room we started from */
2257 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2258 if (strcasecmp(hold_rm, CC->room.QRname))
2259 /* getroom(&CC->room, hold_rm); */
2260 usergoto(hold_rm, 0, 1, NULL, NULL);
2262 /* For internet mail, generate delivery instructions.
2263 * Yes, this is recursive. Deal with it. Infinite recursion does
2264 * not happen because the delivery instructions message does not
2265 * contain a recipient.
2268 if (recps->num_internet > 0) {
2269 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2270 instr = malloc(SIZ * 2);
2271 snprintf(instr, SIZ * 2,
2272 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2274 SPOOLMIME, newmsgid, (long)time(NULL),
2275 msg->cm_fields['A'], msg->cm_fields['N']
2278 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2279 size_t tmp = strlen(instr);
2280 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2281 snprintf(&instr[tmp], SIZ * 2 - tmp,
2282 "remote|%s|0||\n", recipient);
2285 imsg = malloc(sizeof(struct CtdlMessage));
2286 memset(imsg, 0, sizeof(struct CtdlMessage));
2287 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2288 imsg->cm_anon_type = MES_NORMAL;
2289 imsg->cm_format_type = FMT_RFC822;
2290 imsg->cm_fields['A'] = strdup("Citadel");
2291 imsg->cm_fields['M'] = instr;
2292 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2293 CtdlFreeMessage(imsg);
2302 * Convenience function for generating small administrative messages.
2304 void quickie_message(char *from, char *to, char *room, char *text,
2305 int format_type, char *subject)
2307 struct CtdlMessage *msg;
2308 struct recptypes *recp = NULL;
2310 msg = malloc(sizeof(struct CtdlMessage));
2311 memset(msg, 0, sizeof(struct CtdlMessage));
2312 msg->cm_magic = CTDLMESSAGE_MAGIC;
2313 msg->cm_anon_type = MES_NORMAL;
2314 msg->cm_format_type = format_type;
2315 msg->cm_fields['A'] = strdup(from);
2316 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2317 msg->cm_fields['N'] = strdup(NODENAME);
2319 msg->cm_fields['R'] = strdup(to);
2320 recp = validate_recipients(to);
2322 if (subject != NULL) {
2323 msg->cm_fields['U'] = strdup(subject);
2325 msg->cm_fields['M'] = strdup(text);
2327 CtdlSubmitMsg(msg, recp, room);
2328 CtdlFreeMessage(msg);
2329 if (recp != NULL) free(recp);
2335 * Back end function used by CtdlMakeMessage() and similar functions
2337 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2338 size_t maxlen, /* maximum message length */
2339 char *exist, /* if non-null, append to it;
2340 exist is ALWAYS freed */
2341 int crlf /* CRLF newlines instead of LF */
2345 size_t message_len = 0;
2346 size_t buffer_len = 0;
2352 if (exist == NULL) {
2359 message_len = strlen(exist);
2360 buffer_len = message_len + 4096;
2361 m = realloc(exist, buffer_len);
2368 /* flush the input if we have nowhere to store it */
2373 /* read in the lines of message text one by one */
2375 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2376 if (!strcmp(buf, terminator)) finished = 1;
2378 strcat(buf, "\r\n");
2384 if ( (!flushing) && (!finished) ) {
2385 /* Measure the line */
2386 linelen = strlen(buf);
2388 /* augment the buffer if we have to */
2389 if ((message_len + linelen) >= buffer_len) {
2390 ptr = realloc(m, (buffer_len * 2) );
2391 if (ptr == NULL) { /* flush if can't allocate */
2394 buffer_len = (buffer_len * 2);
2396 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2400 /* Add the new line to the buffer. NOTE: this loop must avoid
2401 * using functions like strcat() and strlen() because they
2402 * traverse the entire buffer upon every call, and doing that
2403 * for a multi-megabyte message slows it down beyond usability.
2405 strcpy(&m[message_len], buf);
2406 message_len += linelen;
2409 /* if we've hit the max msg length, flush the rest */
2410 if (message_len >= maxlen) flushing = 1;
2412 } while (!finished);
2420 * Build a binary message to be saved on disk.
2421 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2422 * will become part of the message. This means you are no longer
2423 * responsible for managing that memory -- it will be freed along with
2424 * the rest of the fields when CtdlFreeMessage() is called.)
2427 struct CtdlMessage *CtdlMakeMessage(
2428 struct ctdluser *author, /* author's user structure */
2429 char *recipient, /* NULL if it's not mail */
2430 char *room, /* room where it's going */
2431 int type, /* see MES_ types in header file */
2432 int format_type, /* variformat, plain text, MIME... */
2433 char *fake_name, /* who we're masquerading as */
2434 char *subject, /* Subject (optional) */
2435 char *preformatted_text /* ...or NULL to read text from client */
2437 char dest_node[SIZ];
2439 struct CtdlMessage *msg;
2441 msg = malloc(sizeof(struct CtdlMessage));
2442 memset(msg, 0, sizeof(struct CtdlMessage));
2443 msg->cm_magic = CTDLMESSAGE_MAGIC;
2444 msg->cm_anon_type = type;
2445 msg->cm_format_type = format_type;
2447 /* Don't confuse the poor folks if it's not routed mail. */
2448 strcpy(dest_node, "");
2452 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2453 msg->cm_fields['P'] = strdup(buf);
2455 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2456 msg->cm_fields['T'] = strdup(buf);
2458 if (fake_name[0]) /* author */
2459 msg->cm_fields['A'] = strdup(fake_name);
2461 msg->cm_fields['A'] = strdup(author->fullname);
2463 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2464 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2467 msg->cm_fields['O'] = strdup(CC->room.QRname);
2470 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2471 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2473 if (recipient[0] != 0) {
2474 msg->cm_fields['R'] = strdup(recipient);
2476 if (dest_node[0] != 0) {
2477 msg->cm_fields['D'] = strdup(dest_node);
2480 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2481 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2484 if (subject != NULL) {
2486 if (strlen(subject) > 0) {
2487 msg->cm_fields['U'] = strdup(subject);
2491 if (preformatted_text != NULL) {
2492 msg->cm_fields['M'] = preformatted_text;
2495 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2496 config.c_maxmsglen, NULL, 0);
2504 * Check to see whether we have permission to post a message in the current
2505 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2506 * returns 0 on success.
2508 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2510 if (!(CC->logged_in)) {
2511 snprintf(errmsgbuf, n, "Not logged in.");
2512 return (ERROR + NOT_LOGGED_IN);
2515 if ((CC->user.axlevel < 2)
2516 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2517 snprintf(errmsgbuf, n, "Need to be validated to enter "
2518 "(except in %s> to sysop)", MAILROOM);
2519 return (ERROR + HIGHER_ACCESS_REQUIRED);
2522 if ((CC->user.axlevel < 4)
2523 && (CC->room.QRflags & QR_NETWORK)) {
2524 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2525 return (ERROR + HIGHER_ACCESS_REQUIRED);
2528 if ((CC->user.axlevel < 6)
2529 && (CC->room.QRflags & QR_READONLY)) {
2530 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2531 return (ERROR + HIGHER_ACCESS_REQUIRED);
2534 strcpy(errmsgbuf, "Ok");
2540 * Check to see if the specified user has Internet mail permission
2541 * (returns nonzero if permission is granted)
2543 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2545 /* Do not allow twits to send Internet mail */
2546 if (who->axlevel <= 2) return(0);
2548 /* Globally enabled? */
2549 if (config.c_restrict == 0) return(1);
2551 /* User flagged ok? */
2552 if (who->flags & US_INTERNET) return(2);
2554 /* Aide level access? */
2555 if (who->axlevel >= 6) return(3);
2557 /* No mail for you! */
2564 * Validate recipients, count delivery types and errors, and handle aliasing
2565 * FIXME check for dupes!!!!!
2567 struct recptypes *validate_recipients(char *recipients) {
2568 struct recptypes *ret;
2569 char this_recp[SIZ];
2570 char this_recp_cooked[SIZ];
2576 struct ctdluser tempUS;
2577 struct ctdlroom tempQR;
2580 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2581 if (ret == NULL) return(NULL);
2582 memset(ret, 0, sizeof(struct recptypes));
2585 ret->num_internet = 0;
2590 if (recipients == NULL) {
2593 else if (strlen(recipients) == 0) {
2597 /* Change all valid separator characters to commas */
2598 for (i=0; i<strlen(recipients); ++i) {
2599 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2600 recipients[i] = ',';
2605 num_recps = num_tokens(recipients, ',');
2608 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2609 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2611 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2612 mailtype = alias(this_recp);
2613 mailtype = alias(this_recp);
2614 mailtype = alias(this_recp);
2615 for (j=0; j<=strlen(this_recp); ++j) {
2616 if (this_recp[j]=='_') {
2617 this_recp_cooked[j] = ' ';
2620 this_recp_cooked[j] = this_recp[j];
2626 if (!strcasecmp(this_recp, "sysop")) {
2628 strcpy(this_recp, config.c_aideroom);
2629 if (strlen(ret->recp_room) > 0) {
2630 strcat(ret->recp_room, "|");
2632 strcat(ret->recp_room, this_recp);
2634 else if (getuser(&tempUS, this_recp) == 0) {
2636 strcpy(this_recp, tempUS.fullname);
2637 if (strlen(ret->recp_local) > 0) {
2638 strcat(ret->recp_local, "|");
2640 strcat(ret->recp_local, this_recp);
2642 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2644 strcpy(this_recp, tempUS.fullname);
2645 if (strlen(ret->recp_local) > 0) {
2646 strcat(ret->recp_local, "|");
2648 strcat(ret->recp_local, this_recp);
2650 else if ( (!strncasecmp(this_recp, "room_", 5))
2651 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2653 if (strlen(ret->recp_room) > 0) {
2654 strcat(ret->recp_room, "|");
2656 strcat(ret->recp_room, &this_recp_cooked[5]);
2664 /* Yes, you're reading this correctly: if the target
2665 * domain points back to the local system or an attached
2666 * Citadel directory, the address is invalid. That's
2667 * because if the address were valid, we would have
2668 * already translated it to a local address by now.
2670 if (IsDirectory(this_recp)) {
2675 ++ret->num_internet;
2676 if (strlen(ret->recp_internet) > 0) {
2677 strcat(ret->recp_internet, "|");
2679 strcat(ret->recp_internet, this_recp);
2684 if (strlen(ret->recp_ignet) > 0) {
2685 strcat(ret->recp_ignet, "|");
2687 strcat(ret->recp_ignet, this_recp);
2695 if (strlen(ret->errormsg) == 0) {
2696 snprintf(append, sizeof append,
2697 "Invalid recipient: %s",
2701 snprintf(append, sizeof append,
2704 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2705 strcat(ret->errormsg, append);
2709 if (strlen(ret->display_recp) == 0) {
2710 strcpy(append, this_recp);
2713 snprintf(append, sizeof append, ", %s",
2716 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2717 strcat(ret->display_recp, append);
2722 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2723 ret->num_room + ret->num_error) == 0) {
2725 strcpy(ret->errormsg, "No recipients specified.");
2728 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2729 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2730 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2731 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2732 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2733 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2741 * message entry - mode 0 (normal)
2743 void cmd_ent0(char *entargs)
2747 char masquerade_as[SIZ];
2749 int format_type = 0;
2750 char newusername[SIZ];
2751 struct CtdlMessage *msg;
2755 struct recptypes *valid = NULL;
2762 post = extract_int(entargs, 0);
2763 extract_token(recp, entargs, 1, '|', sizeof recp);
2764 anon_flag = extract_int(entargs, 2);
2765 format_type = extract_int(entargs, 3);
2766 extract_token(subject, entargs, 4, '|', sizeof subject);
2767 do_confirm = extract_int(entargs, 6);
2769 /* first check to make sure the request is valid. */
2771 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2773 cprintf("%d %s\n", err, errmsg);
2777 /* Check some other permission type things. */
2780 if (CC->user.axlevel < 6) {
2781 cprintf("%d You don't have permission to masquerade.\n",
2782 ERROR + HIGHER_ACCESS_REQUIRED);
2785 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2786 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2787 safestrncpy(CC->fake_postname, newusername,
2788 sizeof(CC->fake_postname) );
2789 cprintf("%d ok\n", CIT_OK);
2792 CC->cs_flags |= CS_POSTING;
2794 /* In the Mail> room we have to behave a little differently --
2795 * make sure the user has specified at least one recipient. Then
2796 * validate the recipient(s).
2798 if ( (CC->room.QRflags & QR_MAILBOX)
2799 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2801 if (CC->user.axlevel < 2) {
2802 strcpy(recp, "sysop");
2805 valid = validate_recipients(recp);
2806 if (valid->num_error > 0) {
2808 ERROR + NO_SUCH_USER, valid->errormsg);
2812 if (valid->num_internet > 0) {
2813 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2814 cprintf("%d You do not have permission "
2815 "to send Internet mail.\n",
2816 ERROR + HIGHER_ACCESS_REQUIRED);
2822 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2823 && (CC->user.axlevel < 4) ) {
2824 cprintf("%d Higher access required for network mail.\n",
2825 ERROR + HIGHER_ACCESS_REQUIRED);
2830 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2831 && ((CC->user.flags & US_INTERNET) == 0)
2832 && (!CC->internal_pgm)) {
2833 cprintf("%d You don't have access to Internet mail.\n",
2834 ERROR + HIGHER_ACCESS_REQUIRED);
2841 /* Is this a room which has anonymous-only or anonymous-option? */
2842 anonymous = MES_NORMAL;
2843 if (CC->room.QRflags & QR_ANONONLY) {
2844 anonymous = MES_ANONONLY;
2846 if (CC->room.QRflags & QR_ANONOPT) {
2847 if (anon_flag == 1) { /* only if the user requested it */
2848 anonymous = MES_ANONOPT;
2852 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2856 /* If we're only checking the validity of the request, return
2857 * success without creating the message.
2860 cprintf("%d %s\n", CIT_OK,
2861 ((valid != NULL) ? valid->display_recp : "") );
2866 /* Handle author masquerading */
2867 if (CC->fake_postname[0]) {
2868 strcpy(masquerade_as, CC->fake_postname);
2870 else if (CC->fake_username[0]) {
2871 strcpy(masquerade_as, CC->fake_username);
2874 strcpy(masquerade_as, "");
2877 /* Read in the message from the client. */
2879 cprintf("%d send message\n", START_CHAT_MODE);
2881 cprintf("%d send message\n", SEND_LISTING);
2883 msg = CtdlMakeMessage(&CC->user, recp,
2884 CC->room.QRname, anonymous, format_type,
2885 masquerade_as, subject, NULL);
2888 msgnum = CtdlSubmitMsg(msg, valid, "");
2891 cprintf("%ld\n", msgnum);
2893 cprintf("Message accepted.\n");
2896 cprintf("Internal error.\n");
2898 if (msg->cm_fields['E'] != NULL) {
2899 cprintf("%s\n", msg->cm_fields['E']);
2906 CtdlFreeMessage(msg);
2908 CC->fake_postname[0] = '\0';
2916 * API function to delete messages which match a set of criteria
2917 * (returns the actual number of messages deleted)
2919 int CtdlDeleteMessages(char *room_name, /* which room */
2920 long dmsgnum, /* or "0" for any */
2921 char *content_type /* or "" for any */
2925 struct ctdlroom qrbuf;
2926 struct cdbdata *cdbfr;
2927 long *msglist = NULL;
2928 long *dellist = NULL;
2931 int num_deleted = 0;
2933 struct MetaData smi;
2935 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2936 room_name, dmsgnum, content_type);
2938 /* get room record, obtaining a lock... */
2939 if (lgetroom(&qrbuf, room_name) != 0) {
2940 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2942 return (0); /* room not found */
2944 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2946 if (cdbfr != NULL) {
2947 msglist = malloc(cdbfr->len);
2948 dellist = malloc(cdbfr->len);
2949 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2950 num_msgs = cdbfr->len / sizeof(long);
2954 for (i = 0; i < num_msgs; ++i) {
2957 /* Set/clear a bit for each criterion */
2959 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2960 delete_this |= 0x01;
2962 if (strlen(content_type) == 0) {
2963 delete_this |= 0x02;
2965 GetMetaData(&smi, msglist[i]);
2966 if (!strcasecmp(smi.meta_content_type,
2968 delete_this |= 0x02;
2972 /* Delete message only if all bits are set */
2973 if (delete_this == 0x03) {
2974 dellist[num_deleted++] = msglist[i];
2979 num_msgs = sort_msglist(msglist, num_msgs);
2980 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2981 msglist, (int)(num_msgs * sizeof(long)));
2983 qrbuf.QRhighest = msglist[num_msgs - 1];
2987 /* Go through the messages we pulled out of the index, and decrement
2988 * their reference counts by 1. If this is the only room the message
2989 * was in, the reference count will reach zero and the message will
2990 * automatically be deleted from the database. We do this in a
2991 * separate pass because there might be plug-in hooks getting called,
2992 * and we don't want that happening during an S_ROOMS critical
2995 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2996 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2997 AdjRefCount(dellist[i], -1);
3000 /* Now free the memory we used, and go away. */
3001 if (msglist != NULL) free(msglist);
3002 if (dellist != NULL) free(dellist);
3003 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3004 return (num_deleted);
3010 * Check whether the current user has permission to delete messages from
3011 * the current room (returns 1 for yes, 0 for no)
3013 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3014 getuser(&CC->user, CC->curr_user);
3015 if ((CC->user.axlevel < 6)
3016 && (CC->user.usernum != CC->room.QRroomaide)
3017 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3018 && (!(CC->internal_pgm))) {
3027 * Delete message from current room
3029 void cmd_dele(char *delstr)
3034 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3035 cprintf("%d Higher access required.\n",
3036 ERROR + HIGHER_ACCESS_REQUIRED);
3039 delnum = extract_long(delstr, 0);
3041 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3044 cprintf("%d %d message%s deleted.\n", CIT_OK,
3045 num_deleted, ((num_deleted != 1) ? "s" : ""));
3047 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3053 * Back end API function for moves and deletes
3055 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3058 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3059 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3060 if (err != 0) return(err);
3068 * move or copy a message to another room
3070 void cmd_move(char *args)
3073 char targ[ROOMNAMELEN];
3074 struct ctdlroom qtemp;
3080 num = extract_long(args, 0);
3081 extract_token(targ, args, 1, '|', sizeof targ);
3082 targ[ROOMNAMELEN - 1] = 0;
3083 is_copy = extract_int(args, 2);
3085 if (getroom(&qtemp, targ) != 0) {
3086 cprintf("%d '%s' does not exist.\n",
3087 ERROR + ROOM_NOT_FOUND, targ);
3091 getuser(&CC->user, CC->curr_user);
3092 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3094 /* Check for permission to perform this operation.
3095 * Remember: "CC->room" is source, "qtemp" is target.
3099 /* Aides can move/copy */
3100 if (CC->user.axlevel >= 6) permit = 1;
3102 /* Room aides can move/copy */
3103 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3105 /* Permit move/copy from personal rooms */
3106 if ((CC->room.QRflags & QR_MAILBOX)
3107 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3109 /* Permit only copy from public to personal room */
3111 && (!(CC->room.QRflags & QR_MAILBOX))
3112 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3114 /* User must have access to target room */
3115 if (!(ra & UA_KNOWN)) permit = 0;
3118 cprintf("%d Higher access required.\n",
3119 ERROR + HIGHER_ACCESS_REQUIRED);
3123 err = CtdlCopyMsgToRoom(num, targ);
3125 cprintf("%d Cannot store message in %s: error %d\n",
3130 /* Now delete the message from the source room,
3131 * if this is a 'move' rather than a 'copy' operation.
3134 CtdlDeleteMessages(CC->room.QRname, num, "");
3137 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3143 * GetMetaData() - Get the supplementary record for a message
3145 void GetMetaData(struct MetaData *smibuf, long msgnum)
3148 struct cdbdata *cdbsmi;
3151 memset(smibuf, 0, sizeof(struct MetaData));
3152 smibuf->meta_msgnum = msgnum;
3153 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3155 /* Use the negative of the message number for its supp record index */
3156 TheIndex = (0L - msgnum);
3158 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3159 if (cdbsmi == NULL) {
3160 return; /* record not found; go with defaults */
3162 memcpy(smibuf, cdbsmi->ptr,
3163 ((cdbsmi->len > sizeof(struct MetaData)) ?
3164 sizeof(struct MetaData) : cdbsmi->len));
3171 * PutMetaData() - (re)write supplementary record for a message
3173 void PutMetaData(struct MetaData *smibuf)
3177 /* Use the negative of the message number for the metadata db index */
3178 TheIndex = (0L - smibuf->meta_msgnum);
3180 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3181 smibuf->meta_msgnum, smibuf->meta_refcount);
3183 cdb_store(CDB_MSGMAIN,
3184 &TheIndex, (int)sizeof(long),
3185 smibuf, (int)sizeof(struct MetaData));
3190 * AdjRefCount - change the reference count for a message;
3191 * delete the message if it reaches zero
3193 void AdjRefCount(long msgnum, int incr)
3196 struct MetaData smi;
3199 /* This is a *tight* critical section; please keep it that way, as
3200 * it may get called while nested in other critical sections.
3201 * Complicating this any further will surely cause deadlock!
3203 begin_critical_section(S_SUPPMSGMAIN);
3204 GetMetaData(&smi, msgnum);
3205 smi.meta_refcount += incr;
3207 end_critical_section(S_SUPPMSGMAIN);
3208 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3209 msgnum, incr, smi.meta_refcount);
3211 /* If the reference count is now zero, delete the message
3212 * (and its supplementary record as well).
3213 * FIXME ... defer this so it doesn't keep the user waiting.
3215 if (smi.meta_refcount == 0) {
3216 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3218 /* Remove from fulltext index */
3219 if (config.c_enable_fulltext) {
3220 ft_index_message(msgnum, 0);
3223 /* Remove from message base */
3225 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3226 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3228 /* Remove metadata record */
3229 delnum = (0L - msgnum);
3230 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3235 * Write a generic object to this room
3237 * Note: this could be much more efficient. Right now we use two temporary
3238 * files, and still pull the message into memory as with all others.
3240 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3241 char *content_type, /* MIME type of this object */
3242 char *tempfilename, /* Where to fetch it from */
3243 struct ctdluser *is_mailbox, /* Mailbox room? */
3244 int is_binary, /* Is encoding necessary? */
3245 int is_unique, /* Del others of this type? */
3246 unsigned int flags /* Internal save flags */
3251 struct ctdlroom qrbuf;
3252 char roomname[ROOMNAMELEN];
3253 struct CtdlMessage *msg;
3255 char *raw_message = NULL;
3256 char *encoded_message = NULL;
3257 off_t raw_length = 0;
3259 if (is_mailbox != NULL)
3260 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3262 safestrncpy(roomname, req_room, sizeof(roomname));
3263 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3266 fp = fopen(tempfilename, "rb");
3268 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3269 tempfilename, strerror(errno));
3272 fseek(fp, 0L, SEEK_END);
3273 raw_length = ftell(fp);
3275 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3277 raw_message = malloc((size_t)raw_length + 2);
3278 fread(raw_message, (size_t)raw_length, 1, fp);
3282 encoded_message = malloc((size_t)
3283 (((raw_length * 134) / 100) + 4096 ) );
3286 encoded_message = malloc((size_t)(raw_length + 4096));
3289 sprintf(encoded_message, "Content-type: %s\n", content_type);
3292 sprintf(&encoded_message[strlen(encoded_message)],
3293 "Content-transfer-encoding: base64\n\n"
3297 sprintf(&encoded_message[strlen(encoded_message)],
3298 "Content-transfer-encoding: 7bit\n\n"
3304 &encoded_message[strlen(encoded_message)],
3310 raw_message[raw_length] = 0;
3312 &encoded_message[strlen(encoded_message)],
3320 lprintf(CTDL_DEBUG, "Allocating\n");
3321 msg = malloc(sizeof(struct CtdlMessage));
3322 memset(msg, 0, sizeof(struct CtdlMessage));
3323 msg->cm_magic = CTDLMESSAGE_MAGIC;
3324 msg->cm_anon_type = MES_NORMAL;
3325 msg->cm_format_type = 4;
3326 msg->cm_fields['A'] = strdup(CC->user.fullname);
3327 msg->cm_fields['O'] = strdup(req_room);
3328 msg->cm_fields['N'] = strdup(config.c_nodename);
3329 msg->cm_fields['H'] = strdup(config.c_humannode);
3330 msg->cm_flags = flags;
3332 msg->cm_fields['M'] = encoded_message;
3334 /* Create the requested room if we have to. */
3335 if (getroom(&qrbuf, roomname) != 0) {
3336 create_room(roomname,
3337 ( (is_mailbox != NULL) ? 5 : 3 ),
3338 "", 0, 1, 0, VIEW_BBS);
3340 /* If the caller specified this object as unique, delete all
3341 * other objects of this type that are currently in the room.
3344 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3345 CtdlDeleteMessages(roomname, 0L, content_type));
3347 /* Now write the data */
3348 CtdlSubmitMsg(msg, NULL, roomname);
3349 CtdlFreeMessage(msg);
3357 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3358 config_msgnum = msgnum;
3362 char *CtdlGetSysConfig(char *sysconfname) {
3363 char hold_rm[ROOMNAMELEN];
3366 struct CtdlMessage *msg;
3369 strcpy(hold_rm, CC->room.QRname);
3370 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3371 getroom(&CC->room, hold_rm);
3376 /* We want the last (and probably only) config in this room */
3377 begin_critical_section(S_CONFIG);
3378 config_msgnum = (-1L);
3379 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3380 CtdlGetSysConfigBackend, NULL);
3381 msgnum = config_msgnum;
3382 end_critical_section(S_CONFIG);
3388 msg = CtdlFetchMessage(msgnum, 1);
3390 conf = strdup(msg->cm_fields['M']);
3391 CtdlFreeMessage(msg);
3398 getroom(&CC->room, hold_rm);
3400 if (conf != NULL) do {
3401 extract_token(buf, conf, 0, '\n', sizeof buf);
3402 strcpy(conf, &conf[strlen(buf)+1]);
3403 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3408 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3409 char temp[PATH_MAX];
3412 strcpy(temp, tmpnam(NULL));
3414 fp = fopen(temp, "w");
3415 if (fp == NULL) return;
3416 fprintf(fp, "%s", sysconfdata);
3419 /* this handy API function does all the work for us */
3420 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3426 * Determine whether a given Internet address belongs to the current user
3428 int CtdlIsMe(char *addr, int addr_buf_len)
3430 struct recptypes *recp;
3433 recp = validate_recipients(addr);
3434 if (recp == NULL) return(0);
3436 if (recp->num_local == 0) {
3441 for (i=0; i<recp->num_local; ++i) {
3442 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3443 if (!strcasecmp(addr, CC->user.fullname)) {
3455 * Citadel protocol command to do the same
3457 void cmd_isme(char *argbuf) {
3460 if (CtdlAccessCheck(ac_logged_in)) return;
3461 extract_token(addr, argbuf, 0, '|', sizeof addr);
3463 if (CtdlIsMe(addr, sizeof addr)) {
3464 cprintf("%d %s\n", CIT_OK, addr);
3467 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);