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);
151 "mail.aliases", "r");
153 fp = fopen("/dev/null", "r");
160 while (fgets(aaa, sizeof aaa, fp) != NULL) {
161 while (isspace(name[0]))
162 strcpy(name, &name[1]);
163 aaa[strlen(aaa) - 1] = 0;
165 for (a = 0; a < strlen(aaa); ++a) {
167 strcpy(bbb, &aaa[a + 1]);
171 if (!strcasecmp(name, aaa))
176 /* Hit the Global Address Book */
177 if (CtdlDirectoryLookup(aaa, name) == 0) {
181 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
183 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
184 for (a=0; a<strlen(name); ++a) {
185 if (name[a] == '@') {
186 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
188 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
193 /* determine local or remote type, see citadel.h */
194 at = haschar(name, '@');
195 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
196 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
197 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
199 /* figure out the delivery mode */
200 extract_token(node, name, 1, '@', sizeof node);
202 /* If there are one or more dots in the nodename, we assume that it
203 * is an FQDN and will attempt SMTP delivery to the Internet.
205 if (haschar(node, '.') > 0) {
206 return(MES_INTERNET);
209 /* Otherwise we look in the IGnet maps for a valid Citadel node.
210 * Try directly-connected nodes first...
212 ignetcfg = CtdlGetSysConfig(IGNETCFG);
213 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
214 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
215 extract_token(testnode, buf, 0, '|', sizeof testnode);
216 if (!strcasecmp(node, testnode)) {
224 * Then try nodes that are two or more hops away.
226 ignetmap = CtdlGetSysConfig(IGNETMAP);
227 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
228 extract_token(buf, ignetmap, i, '\n', sizeof buf);
229 extract_token(testnode, buf, 0, '|', sizeof testnode);
230 if (!strcasecmp(node, testnode)) {
237 /* If we get to this point it's an invalid node name */
252 "/citadel.control", "r");
254 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
258 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
264 void simple_listing(long msgnum, void *userdata)
266 cprintf("%ld\n", msgnum);
271 /* Determine if a given message matches the fields in a message template.
272 * Return 0 for a successful match.
274 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
277 /* If there aren't any fields in the template, all messages will
280 if (template == NULL) return(0);
282 /* Null messages are bogus. */
283 if (msg == NULL) return(1);
285 for (i='A'; i<='Z'; ++i) {
286 if (template->cm_fields[i] != NULL) {
287 if (msg->cm_fields[i] == NULL) {
290 if (strcasecmp(msg->cm_fields[i],
291 template->cm_fields[i])) return 1;
295 /* All compares succeeded: we have a match! */
302 * Retrieve the "seen" message list for the current room.
304 void CtdlGetSeen(char *buf, int which_set) {
307 /* Learn about the user and room in question */
308 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
310 if (which_set == ctdlsetseen_seen)
311 safestrncpy(buf, vbuf.v_seen, SIZ);
312 if (which_set == ctdlsetseen_answered)
313 safestrncpy(buf, vbuf.v_answered, SIZ);
319 * Manipulate the "seen msgs" string (or other message set strings)
321 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set,
322 struct ctdluser *which_user, struct ctdlroom *which_room) {
323 struct cdbdata *cdbfr;
335 char *is_set; /* actually an array of booleans */
338 char setstr[SIZ], lostr[SIZ], histr[SIZ];
341 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
342 target_msgnum, target_setting, which_set);
344 /* Learn about the user and room in question */
345 CtdlGetRelationship(&vbuf,
346 ((which_user != NULL) ? which_user : &CC->user),
347 ((which_room != NULL) ? which_room : &CC->room)
350 /* Load the message list */
351 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
353 msglist = malloc(cdbfr->len);
354 memcpy(msglist, cdbfr->ptr, cdbfr->len);
355 num_msgs = cdbfr->len / sizeof(long);
358 return; /* No messages at all? No further action. */
361 is_set = malloc(num_msgs * sizeof(char));
362 memset(is_set, 0, (num_msgs * sizeof(char)) );
364 /* Decide which message set we're manipulating */
366 case ctdlsetseen_seen:
367 safestrncpy(vset, vbuf.v_seen, sizeof vset);
369 case ctdlsetseen_answered:
370 safestrncpy(vset, vbuf.v_answered, sizeof vset);
374 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
376 /* Translate the existing sequence set into an array of booleans */
377 num_sets = num_tokens(vset, ',');
378 for (s=0; s<num_sets; ++s) {
379 extract_token(setstr, vset, s, ',', sizeof setstr);
381 extract_token(lostr, setstr, 0, ':', sizeof lostr);
382 if (num_tokens(setstr, ':') >= 2) {
383 extract_token(histr, setstr, 1, ':', sizeof histr);
384 if (!strcmp(histr, "*")) {
385 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
389 strcpy(histr, lostr);
394 for (i = 0; i < num_msgs; ++i) {
395 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
401 /* Now translate the array of booleans back into a sequence set */
406 for (i=0; i<num_msgs; ++i) {
409 if (msglist[i] == target_msgnum) {
410 is_seen = target_setting;
417 if (lo < 0L) lo = msglist[i];
421 if ( ((is_seen == 0) && (was_seen == 1))
422 || ((is_seen == 1) && (i == num_msgs-1)) ) {
424 /* begin trim-o-matic code */
427 while ( (strlen(vset) + 20) > sizeof vset) {
428 remove_token(vset, 0, ',');
430 if (j--) break; /* loop no more than 9 times */
432 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
436 snprintf(lostr, sizeof lostr,
437 "1:%ld,%s", t, vset);
438 safestrncpy(vset, lostr, sizeof vset);
440 /* end trim-o-matic code */
448 snprintf(&vset[tmp], (sizeof vset) - tmp,
452 snprintf(&vset[tmp], (sizeof vset) - tmp,
461 /* Decide which message set we're manipulating */
463 case ctdlsetseen_seen:
464 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
466 case ctdlsetseen_answered:
467 safestrncpy(vbuf.v_answered, vset,
468 sizeof vbuf.v_answered);
473 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
475 CtdlSetRelationship(&vbuf,
476 ((which_user != NULL) ? which_user : &CC->user),
477 ((which_room != NULL) ? which_room : &CC->room)
483 * API function to perform an operation for each qualifying message in the
484 * current room. (Returns the number of messages processed.)
486 int CtdlForEachMessage(int mode, long ref,
488 struct CtdlMessage *compare,
489 void (*CallBack) (long, void *),
495 struct cdbdata *cdbfr;
496 long *msglist = NULL;
498 int num_processed = 0;
501 struct CtdlMessage *msg;
504 int printed_lastold = 0;
506 /* Learn about the user and room in question */
508 getuser(&CC->user, CC->curr_user);
509 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
511 /* Load the message list */
512 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
514 msglist = malloc(cdbfr->len);
515 memcpy(msglist, cdbfr->ptr, cdbfr->len);
516 num_msgs = cdbfr->len / sizeof(long);
519 return 0; /* No messages at all? No further action. */
524 * Now begin the traversal.
526 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
528 /* If the caller is looking for a specific MIME type, filter
529 * out all messages which are not of the type requested.
531 if (content_type != NULL) if (strlen(content_type) > 0) {
533 /* This call to GetMetaData() sits inside this loop
534 * so that we only do the extra database read per msg
535 * if we need to. Doing the extra read all the time
536 * really kills the server. If we ever need to use
537 * metadata for another search criterion, we need to
538 * move the read somewhere else -- but still be smart
539 * enough to only do the read if the caller has
540 * specified something that will need it.
542 GetMetaData(&smi, msglist[a]);
544 if (strcasecmp(smi.meta_content_type, content_type)) {
550 num_msgs = sort_msglist(msglist, num_msgs);
552 /* If a template was supplied, filter out the messages which
553 * don't match. (This could induce some delays!)
556 if (compare != NULL) {
557 for (a = 0; a < num_msgs; ++a) {
558 msg = CtdlFetchMessage(msglist[a], 1);
560 if (CtdlMsgCmp(msg, compare)) {
563 CtdlFreeMessage(msg);
571 * Now iterate through the message list, according to the
572 * criteria supplied by the caller.
575 for (a = 0; a < num_msgs; ++a) {
576 thismsg = msglist[a];
577 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
578 if (is_seen) lastold = thismsg;
583 || ((mode == MSGS_OLD) && (is_seen))
584 || ((mode == MSGS_NEW) && (!is_seen))
585 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
586 || ((mode == MSGS_FIRST) && (a < ref))
587 || ((mode == MSGS_GT) && (thismsg > ref))
588 || ((mode == MSGS_EQ) && (thismsg == ref))
591 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
593 CallBack(lastold, userdata);
597 if (CallBack) CallBack(thismsg, userdata);
601 free(msglist); /* Clean up */
602 return num_processed;
608 * cmd_msgs() - get list of message #'s in this room
609 * implements the MSGS server command using CtdlForEachMessage()
611 void cmd_msgs(char *cmdbuf)
620 int with_template = 0;
621 struct CtdlMessage *template = NULL;
623 extract_token(which, cmdbuf, 0, '|', sizeof which);
624 cm_ref = extract_int(cmdbuf, 1);
625 with_template = extract_int(cmdbuf, 2);
629 if (!strncasecmp(which, "OLD", 3))
631 else if (!strncasecmp(which, "NEW", 3))
633 else if (!strncasecmp(which, "FIRST", 5))
635 else if (!strncasecmp(which, "LAST", 4))
637 else if (!strncasecmp(which, "GT", 2))
640 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
641 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
647 cprintf("%d Send template then receive message list\n",
649 template = (struct CtdlMessage *)
650 malloc(sizeof(struct CtdlMessage));
651 memset(template, 0, sizeof(struct CtdlMessage));
652 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
653 extract_token(tfield, buf, 0, '|', sizeof tfield);
654 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
655 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
656 if (!strcasecmp(tfield, msgkeys[i])) {
657 template->cm_fields[i] =
665 cprintf("%d Message list...\n", LISTING_FOLLOWS);
668 CtdlForEachMessage(mode, cm_ref,
669 NULL, template, simple_listing, NULL);
670 if (template != NULL) CtdlFreeMessage(template);
678 * help_subst() - support routine for help file viewer
680 void help_subst(char *strbuf, char *source, char *dest)
685 while (p = pattern2(strbuf, source), (p >= 0)) {
686 strcpy(workbuf, &strbuf[p + strlen(source)]);
687 strcpy(&strbuf[p], dest);
688 strcat(strbuf, workbuf);
693 void do_help_subst(char *buffer)
697 help_subst(buffer, "^nodename", config.c_nodename);
698 help_subst(buffer, "^humannode", config.c_humannode);
699 help_subst(buffer, "^fqdn", config.c_fqdn);
700 help_subst(buffer, "^username", CC->user.fullname);
701 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
702 help_subst(buffer, "^usernum", buf2);
703 help_subst(buffer, "^sysadm", config.c_sysadm);
704 help_subst(buffer, "^variantname", CITADEL);
705 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
706 help_subst(buffer, "^maxsessions", buf2);
707 help_subst(buffer, "^bbsdir", CTDLDIR);
713 * memfmout() - Citadel text formatter and paginator.
714 * Although the original purpose of this routine was to format
715 * text to the reader's screen width, all we're really using it
716 * for here is to format text out to 80 columns before sending it
717 * to the client. The client software may reformat it again.
720 int width, /* screen width to use */
721 char *mptr, /* where are we going to get our text from? */
722 char subst, /* nonzero if we should do substitutions */
723 char *nl) /* string to terminate lines with */
735 c = 1; /* c is the current pos */
739 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
741 buffer[strlen(buffer) + 1] = 0;
742 buffer[strlen(buffer)] = ch;
745 if (buffer[0] == '^')
746 do_help_subst(buffer);
748 buffer[strlen(buffer) + 1] = 0;
750 strcpy(buffer, &buffer[1]);
758 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
760 if (((old == 13) || (old == 10)) && (isspace(real))) {
768 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
769 cprintf("%s%s", nl, aaa);
778 if ((strlen(aaa) + c) > (width - 5)) {
787 if ((ch == 13) || (ch == 10)) {
788 cprintf("%s%s", aaa, nl);
795 cprintf("%s%s", aaa, nl);
801 * Callback function for mime parser that simply lists the part
803 void list_this_part(char *name, char *filename, char *partnum, char *disp,
804 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
808 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
809 name, filename, partnum, disp, cbtype, (long)length);
813 * Callback function for multipart prefix
815 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
816 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
819 cprintf("pref=%s|%s\n", partnum, cbtype);
823 * Callback function for multipart sufffix
825 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
826 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
829 cprintf("suff=%s|%s\n", partnum, cbtype);
834 * Callback function for mime parser that opens a section for downloading
836 void mime_download(char *name, char *filename, char *partnum, char *disp,
837 void *content, char *cbtype, char *cbcharset, size_t length,
838 char *encoding, void *cbuserdata)
841 /* Silently go away if there's already a download open... */
842 if (CC->download_fp != NULL)
845 /* ...or if this is not the desired section */
846 if (strcasecmp(CC->download_desired_section, partnum))
849 CC->download_fp = tmpfile();
850 if (CC->download_fp == NULL)
853 fwrite(content, length, 1, CC->download_fp);
854 fflush(CC->download_fp);
855 rewind(CC->download_fp);
857 OpenCmdResult(filename, cbtype);
863 * Load a message from disk into memory.
864 * This is used by CtdlOutputMsg() and other fetch functions.
866 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
867 * using the CtdlMessageFree() function.
869 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
871 struct cdbdata *dmsgtext;
872 struct CtdlMessage *ret = NULL;
876 cit_uint8_t field_header;
878 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
880 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
881 if (dmsgtext == NULL) {
884 mptr = dmsgtext->ptr;
885 upper_bound = mptr + dmsgtext->len;
887 /* Parse the three bytes that begin EVERY message on disk.
888 * The first is always 0xFF, the on-disk magic number.
889 * The second is the anonymous/public type byte.
890 * The third is the format type byte (vari, fixed, or MIME).
895 "Message %ld appears to be corrupted.\n",
900 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
901 memset(ret, 0, sizeof(struct CtdlMessage));
903 ret->cm_magic = CTDLMESSAGE_MAGIC;
904 ret->cm_anon_type = *mptr++; /* Anon type byte */
905 ret->cm_format_type = *mptr++; /* Format type byte */
908 * The rest is zero or more arbitrary fields. Load them in.
909 * We're done when we encounter either a zero-length field or
910 * have just processed the 'M' (message text) field.
913 if (mptr >= upper_bound) {
916 field_header = *mptr++;
917 ret->cm_fields[field_header] = strdup(mptr);
919 while (*mptr++ != 0); /* advance to next field */
921 } while ((mptr < upper_bound) && (field_header != 'M'));
925 /* Always make sure there's something in the msg text field. If
926 * it's NULL, the message text is most likely stored separately,
927 * so go ahead and fetch that. Failing that, just set a dummy
928 * body so other code doesn't barf.
930 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
931 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
932 if (dmsgtext != NULL) {
933 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
937 if (ret->cm_fields['M'] == NULL) {
938 ret->cm_fields['M'] = strdup("<no text>\n");
941 /* Perform "before read" hooks (aborting if any return nonzero) */
942 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
943 CtdlFreeMessage(ret);
952 * Returns 1 if the supplied pointer points to a valid Citadel message.
953 * If the pointer is NULL or the magic number check fails, returns 0.
955 int is_valid_message(struct CtdlMessage *msg) {
958 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
959 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
967 * 'Destructor' for struct CtdlMessage
969 void CtdlFreeMessage(struct CtdlMessage *msg)
973 if (is_valid_message(msg) == 0) return;
975 for (i = 0; i < 256; ++i)
976 if (msg->cm_fields[i] != NULL) {
977 free(msg->cm_fields[i]);
980 msg->cm_magic = 0; /* just in case */
986 * Pre callback function for multipart/alternative
988 * NOTE: this differs from the standard behavior for a reason. Normally when
989 * displaying multipart/alternative you want to show the _last_ usable
990 * format in the message. Here we show the _first_ one, because it's
991 * usually text/plain. Since this set of functions is designed for text
992 * output to non-MIME-aware clients, this is the desired behavior.
995 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
996 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1001 ma = (struct ma_info *)cbuserdata;
1002 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1003 if (!strcasecmp(cbtype, "multipart/alternative")) {
1011 * Post callback function for multipart/alternative
1013 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1014 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1019 ma = (struct ma_info *)cbuserdata;
1020 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1021 if (!strcasecmp(cbtype, "multipart/alternative")) {
1029 * Inline callback function for mime parser that wants to display text
1031 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1032 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1040 ma = (struct ma_info *)cbuserdata;
1042 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1045 * If we're in the middle of a multipart/alternative scope and
1046 * we've already printed another section, skip this one.
1048 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1049 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1054 if ( (!strcasecmp(cbtype, "text/plain"))
1055 || (strlen(cbtype)==0) ) {
1058 client_write(wptr, length);
1059 if (wptr[length-1] != '\n') {
1064 else if (!strcasecmp(cbtype, "text/html")) {
1065 ptr = html_to_ascii(content, 80, 0);
1067 client_write(ptr, wlen);
1068 if (ptr[wlen-1] != '\n') {
1073 else if (strncasecmp(cbtype, "multipart/", 10)) {
1074 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1075 partnum, filename, cbtype, (long)length);
1080 * The client is elegant and sophisticated and wants to be choosy about
1081 * MIME content types, so figure out which multipart/alternative part
1082 * we're going to send.
1084 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1085 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1092 ma = (struct ma_info *)cbuserdata;
1094 if (ma->is_ma > 0) {
1095 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1096 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1097 if (!strcasecmp(buf, cbtype)) {
1098 strcpy(ma->chosen_part, partnum);
1105 * Now that we've chosen our preferred part, output it.
1107 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1108 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1113 int add_newline = 0;
1117 ma = (struct ma_info *)cbuserdata;
1119 /* This is not the MIME part you're looking for... */
1120 if (strcasecmp(partnum, ma->chosen_part)) return;
1122 /* If the content-type of this part is in our preferred formats
1123 * list, we can simply output it verbatim.
1125 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1126 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1127 if (!strcasecmp(buf, cbtype)) {
1128 /* Yeah! Go! W00t!! */
1130 text_content = (char *)content;
1131 if (text_content[length-1] != '\n') {
1135 cprintf("Content-type: %s", cbtype);
1136 if (strlen(cbcharset) > 0) {
1137 cprintf("; charset=%s", cbcharset);
1139 cprintf("\nContent-length: %d\n",
1140 (int)(length + add_newline) );
1141 if (strlen(encoding) > 0) {
1142 cprintf("Content-transfer-encoding: %s\n", encoding);
1145 cprintf("Content-transfer-encoding: 7bit\n");
1148 client_write(content, length);
1149 if (add_newline) cprintf("\n");
1154 /* No translations required or possible: output as text/plain */
1155 cprintf("Content-type: text/plain\n\n");
1156 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1157 length, encoding, cbuserdata);
1162 * Get a message off disk. (returns om_* values found in msgbase.h)
1165 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1166 int mode, /* how would you like that message? */
1167 int headers_only, /* eschew the message body? */
1168 int do_proto, /* do Citadel protocol responses? */
1169 int crlf /* Use CRLF newlines instead of LF? */
1171 struct CtdlMessage *TheMessage = NULL;
1172 int retcode = om_no_such_msg;
1174 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1177 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1178 if (do_proto) cprintf("%d Not logged in.\n",
1179 ERROR + NOT_LOGGED_IN);
1180 return(om_not_logged_in);
1183 /* FIXME: check message id against msglist for this room */
1186 * Fetch the message from disk. If we're in any sort of headers
1187 * only mode, request that we don't even bother loading the body
1190 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1191 TheMessage = CtdlFetchMessage(msg_num, 0);
1194 TheMessage = CtdlFetchMessage(msg_num, 1);
1197 if (TheMessage == NULL) {
1198 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1199 ERROR + MESSAGE_NOT_FOUND, msg_num);
1200 return(om_no_such_msg);
1203 retcode = CtdlOutputPreLoadedMsg(
1204 TheMessage, msg_num, mode,
1205 headers_only, do_proto, crlf);
1207 CtdlFreeMessage(TheMessage);
1214 * Get a message off disk. (returns om_* values found in msgbase.h)
1217 int CtdlOutputPreLoadedMsg(
1218 struct CtdlMessage *TheMessage,
1220 int mode, /* how would you like that message? */
1221 int headers_only, /* eschew the message body? */
1222 int do_proto, /* do Citadel protocol responses? */
1223 int crlf /* Use CRLF newlines instead of LF? */
1229 char display_name[256];
1231 char *nl; /* newline string */
1233 int subject_found = 0;
1236 /* Buffers needed for RFC822 translation. These are all filled
1237 * using functions that are bounds-checked, and therefore we can
1238 * make them substantially smaller than SIZ.
1246 char datestamp[100];
1248 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1249 ((TheMessage == NULL) ? "NULL" : "not null"),
1251 mode, headers_only, do_proto, crlf);
1253 snprintf(mid, sizeof mid, "%ld", msg_num);
1254 nl = (crlf ? "\r\n" : "\n");
1256 if (!is_valid_message(TheMessage)) {
1258 "ERROR: invalid preloaded message for output\n");
1259 return(om_no_such_msg);
1262 /* Are we downloading a MIME component? */
1263 if (mode == MT_DOWNLOAD) {
1264 if (TheMessage->cm_format_type != FMT_RFC822) {
1266 cprintf("%d This is not a MIME message.\n",
1267 ERROR + ILLEGAL_VALUE);
1268 } else if (CC->download_fp != NULL) {
1269 if (do_proto) cprintf(
1270 "%d You already have a download open.\n",
1271 ERROR + RESOURCE_BUSY);
1273 /* Parse the message text component */
1274 mptr = TheMessage->cm_fields['M'];
1275 ma = malloc(sizeof(struct ma_info));
1276 memset(ma, 0, sizeof(struct ma_info));
1277 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1279 /* If there's no file open by this time, the requested
1280 * section wasn't found, so print an error
1282 if (CC->download_fp == NULL) {
1283 if (do_proto) cprintf(
1284 "%d Section %s not found.\n",
1285 ERROR + FILE_NOT_FOUND,
1286 CC->download_desired_section);
1289 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1292 /* now for the user-mode message reading loops */
1293 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1295 /* Does the caller want to skip the headers? */
1296 if (headers_only == HEADERS_NONE) goto START_TEXT;
1298 /* Tell the client which format type we're using. */
1299 if ( (mode == MT_CITADEL) && (do_proto) ) {
1300 cprintf("type=%d\n", TheMessage->cm_format_type);
1303 /* nhdr=yes means that we're only displaying headers, no body */
1304 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1305 && (mode == MT_CITADEL)
1308 cprintf("nhdr=yes\n");
1311 /* begin header processing loop for Citadel message format */
1313 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1315 safestrncpy(display_name, "<unknown>", sizeof display_name);
1316 if (TheMessage->cm_fields['A']) {
1317 strcpy(buf, TheMessage->cm_fields['A']);
1318 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1319 safestrncpy(display_name, "****", sizeof display_name);
1321 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1322 safestrncpy(display_name, "anonymous", sizeof display_name);
1325 safestrncpy(display_name, buf, sizeof display_name);
1327 if ((is_room_aide())
1328 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1329 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1330 size_t tmp = strlen(display_name);
1331 snprintf(&display_name[tmp],
1332 sizeof display_name - tmp,
1337 /* Don't show Internet address for users on the
1338 * local Citadel network.
1341 if (TheMessage->cm_fields['N'] != NULL)
1342 if (strlen(TheMessage->cm_fields['N']) > 0)
1343 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1347 /* Now spew the header fields in the order we like them. */
1348 safestrncpy(allkeys, FORDER, sizeof allkeys);
1349 for (i=0; i<strlen(allkeys); ++i) {
1350 k = (int) allkeys[i];
1352 if ( (TheMessage->cm_fields[k] != NULL)
1353 && (msgkeys[k] != NULL) ) {
1355 if (do_proto) cprintf("%s=%s\n",
1359 else if ((k == 'F') && (suppress_f)) {
1362 /* Masquerade display name if needed */
1364 if (do_proto) cprintf("%s=%s\n",
1366 TheMessage->cm_fields[k]
1375 /* begin header processing loop for RFC822 transfer format */
1380 strcpy(snode, NODENAME);
1381 strcpy(lnode, HUMANNODE);
1382 if (mode == MT_RFC822) {
1383 for (i = 0; i < 256; ++i) {
1384 if (TheMessage->cm_fields[i]) {
1385 mptr = TheMessage->cm_fields[i];
1388 safestrncpy(luser, mptr, sizeof luser);
1389 safestrncpy(suser, mptr, sizeof suser);
1391 else if (i == 'U') {
1392 cprintf("Subject: %s%s", mptr, nl);
1396 safestrncpy(mid, mptr, sizeof mid);
1398 safestrncpy(lnode, mptr, sizeof lnode);
1400 safestrncpy(fuser, mptr, sizeof fuser);
1401 /* else if (i == 'O')
1402 cprintf("X-Citadel-Room: %s%s",
1405 safestrncpy(snode, mptr, sizeof snode);
1407 cprintf("To: %s%s", mptr, nl);
1408 else if (i == 'T') {
1409 datestring(datestamp, sizeof datestamp,
1410 atol(mptr), DATESTRING_RFC822);
1411 cprintf("Date: %s%s", datestamp, nl);
1415 if (subject_found == 0) {
1416 cprintf("Subject: (no subject)%s", nl);
1420 for (i=0; i<strlen(suser); ++i) {
1421 suser[i] = tolower(suser[i]);
1422 if (!isalnum(suser[i])) suser[i]='_';
1425 if (mode == MT_RFC822) {
1426 if (!strcasecmp(snode, NODENAME)) {
1427 safestrncpy(snode, FQDN, sizeof snode);
1430 /* Construct a fun message id */
1431 cprintf("Message-ID: <%s", mid);
1432 if (strchr(mid, '@')==NULL) {
1433 cprintf("@%s", snode);
1437 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1438 // cprintf("From: x@x.org (----)%s", nl);
1439 cprintf("From: \"----\" <x@x.org>%s", nl);
1441 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1442 // cprintf("From: x@x.org (anonymous)%s", nl);
1443 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1445 else if (strlen(fuser) > 0) {
1446 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1447 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1450 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1451 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1454 cprintf("Organization: %s%s", lnode, nl);
1456 /* Blank line signifying RFC822 end-of-headers */
1457 if (TheMessage->cm_format_type != FMT_RFC822) {
1462 /* end header processing loop ... at this point, we're in the text */
1464 if (headers_only == HEADERS_FAST) goto DONE;
1465 mptr = TheMessage->cm_fields['M'];
1467 /* Tell the client about the MIME parts in this message */
1468 if (TheMessage->cm_format_type == FMT_RFC822) {
1469 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1470 mime_parser(mptr, NULL,
1471 (do_proto ? *list_this_part : NULL),
1472 (do_proto ? *list_this_pref : NULL),
1473 (do_proto ? *list_this_suff : NULL),
1476 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1477 /* FIXME ... we have to put some code in here to avoid
1478 * printing duplicate header information when both
1479 * Citadel and RFC822 headers exist. Preference should
1480 * probably be given to the RFC822 headers.
1482 int done_rfc822_hdrs = 0;
1483 while (ch=*(mptr++), ch!=0) {
1488 if (!done_rfc822_hdrs) {
1489 if (headers_only != HEADERS_NONE) {
1494 if (headers_only != HEADERS_ONLY) {
1498 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1499 done_rfc822_hdrs = 1;
1503 if (done_rfc822_hdrs) {
1504 if (headers_only != HEADERS_NONE) {
1509 if (headers_only != HEADERS_ONLY) {
1513 if ((*mptr == 13) || (*mptr == 10)) {
1514 done_rfc822_hdrs = 1;
1522 if (headers_only == HEADERS_ONLY) {
1526 /* signify start of msg text */
1527 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1528 if (do_proto) cprintf("text\n");
1531 /* If the format type on disk is 1 (fixed-format), then we want
1532 * everything to be output completely literally ... regardless of
1533 * what message transfer format is in use.
1535 if (TheMessage->cm_format_type == FMT_FIXED) {
1536 if (mode == MT_MIME) {
1537 cprintf("Content-type: text/plain\n\n");
1540 while (ch = *mptr++, ch > 0) {
1543 if ((ch == 10) || (strlen(buf) > 250)) {
1544 cprintf("%s%s", buf, nl);
1547 buf[strlen(buf) + 1] = 0;
1548 buf[strlen(buf)] = ch;
1551 if (strlen(buf) > 0)
1552 cprintf("%s%s", buf, nl);
1555 /* If the message on disk is format 0 (Citadel vari-format), we
1556 * output using the formatter at 80 columns. This is the final output
1557 * form if the transfer format is RFC822, but if the transfer format
1558 * is Citadel proprietary, it'll still work, because the indentation
1559 * for new paragraphs is correct and the client will reformat the
1560 * message to the reader's screen width.
1562 if (TheMessage->cm_format_type == FMT_CITADEL) {
1563 if (mode == MT_MIME) {
1564 cprintf("Content-type: text/x-citadel-variformat\n\n");
1566 memfmout(80, mptr, 0, nl);
1569 /* If the message on disk is format 4 (MIME), we've gotta hand it
1570 * off to the MIME parser. The client has already been told that
1571 * this message is format 1 (fixed format), so the callback function
1572 * we use will display those parts as-is.
1574 if (TheMessage->cm_format_type == FMT_RFC822) {
1575 ma = malloc(sizeof(struct ma_info));
1576 memset(ma, 0, sizeof(struct ma_info));
1578 if (mode == MT_MIME) {
1579 strcpy(ma->chosen_part, "1");
1580 mime_parser(mptr, NULL,
1581 *choose_preferred, *fixed_output_pre,
1582 *fixed_output_post, (void *)ma, 0);
1583 mime_parser(mptr, NULL,
1584 *output_preferred, NULL, NULL, (void *)ma, 0);
1587 mime_parser(mptr, NULL,
1588 *fixed_output, *fixed_output_pre,
1589 *fixed_output_post, (void *)ma, 0);
1595 DONE: /* now we're done */
1596 if (do_proto) cprintf("000\n");
1603 * display a message (mode 0 - Citadel proprietary)
1605 void cmd_msg0(char *cmdbuf)
1608 int headers_only = HEADERS_ALL;
1610 msgid = extract_long(cmdbuf, 0);
1611 headers_only = extract_int(cmdbuf, 1);
1613 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1619 * display a message (mode 2 - RFC822)
1621 void cmd_msg2(char *cmdbuf)
1624 int headers_only = HEADERS_ALL;
1626 msgid = extract_long(cmdbuf, 0);
1627 headers_only = extract_int(cmdbuf, 1);
1629 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1635 * display a message (mode 3 - IGnet raw format - internal programs only)
1637 void cmd_msg3(char *cmdbuf)
1640 struct CtdlMessage *msg;
1643 if (CC->internal_pgm == 0) {
1644 cprintf("%d This command is for internal programs only.\n",
1645 ERROR + HIGHER_ACCESS_REQUIRED);
1649 msgnum = extract_long(cmdbuf, 0);
1650 msg = CtdlFetchMessage(msgnum, 1);
1652 cprintf("%d Message %ld not found.\n",
1653 ERROR + MESSAGE_NOT_FOUND, msgnum);
1657 serialize_message(&smr, msg);
1658 CtdlFreeMessage(msg);
1661 cprintf("%d Unable to serialize message\n",
1662 ERROR + INTERNAL_ERROR);
1666 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1667 client_write((char *)smr.ser, (int)smr.len);
1674 * Display a message using MIME content types
1676 void cmd_msg4(char *cmdbuf)
1680 msgid = extract_long(cmdbuf, 0);
1681 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1687 * Client tells us its preferred message format(s)
1689 void cmd_msgp(char *cmdbuf)
1691 safestrncpy(CC->preferred_formats, cmdbuf,
1692 sizeof(CC->preferred_formats));
1693 cprintf("%d ok\n", CIT_OK);
1698 * Open a component of a MIME message as a download file
1700 void cmd_opna(char *cmdbuf)
1703 char desired_section[128];
1705 msgid = extract_long(cmdbuf, 0);
1706 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1707 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1708 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1713 * Save a message pointer into a specified room
1714 * (Returns 0 for success, nonzero for failure)
1715 * roomname may be NULL to use the current room
1717 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1719 char hold_rm[ROOMNAMELEN];
1720 struct cdbdata *cdbfr;
1723 long highest_msg = 0L;
1724 struct CtdlMessage *msg = NULL;
1726 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1727 roomname, msgid, flags);
1729 strcpy(hold_rm, CC->room.QRname);
1731 /* We may need to check to see if this message is real */
1732 if ( (flags & SM_VERIFY_GOODNESS)
1733 || (flags & SM_DO_REPL_CHECK)
1735 msg = CtdlFetchMessage(msgid, 1);
1736 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1739 /* Perform replication checks if necessary */
1740 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1742 if (getroom(&CC->room,
1743 ((roomname != NULL) ? roomname : CC->room.QRname) )
1745 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1746 if (msg != NULL) CtdlFreeMessage(msg);
1747 return(ERROR + ROOM_NOT_FOUND);
1750 if (ReplicationChecks(msg) != 0) {
1751 getroom(&CC->room, hold_rm);
1752 if (msg != NULL) CtdlFreeMessage(msg);
1754 "Did replication, and newer exists\n");
1759 /* Now the regular stuff */
1760 if (lgetroom(&CC->room,
1761 ((roomname != NULL) ? roomname : CC->room.QRname) )
1763 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1764 if (msg != NULL) CtdlFreeMessage(msg);
1765 return(ERROR + ROOM_NOT_FOUND);
1768 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1769 if (cdbfr == NULL) {
1773 msglist = malloc(cdbfr->len);
1774 if (msglist == NULL)
1775 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1776 num_msgs = cdbfr->len / sizeof(long);
1777 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1782 /* Make sure the message doesn't already exist in this room. It
1783 * is absolutely taboo to have more than one reference to the same
1784 * message in a room.
1786 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1787 if (msglist[i] == msgid) {
1788 lputroom(&CC->room); /* unlock the room */
1789 getroom(&CC->room, hold_rm);
1790 if (msg != NULL) CtdlFreeMessage(msg);
1792 return(ERROR + ALREADY_EXISTS);
1796 /* Now add the new message */
1798 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1800 if (msglist == NULL) {
1801 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1803 msglist[num_msgs - 1] = msgid;
1805 /* Sort the message list, so all the msgid's are in order */
1806 num_msgs = sort_msglist(msglist, num_msgs);
1808 /* Determine the highest message number */
1809 highest_msg = msglist[num_msgs - 1];
1811 /* Write it back to disk. */
1812 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1813 msglist, (int)(num_msgs * sizeof(long)));
1815 /* Free up the memory we used. */
1818 /* Update the highest-message pointer and unlock the room. */
1819 CC->room.QRhighest = highest_msg;
1820 lputroom(&CC->room);
1821 getroom(&CC->room, hold_rm);
1823 /* Bump the reference count for this message. */
1824 if ((flags & SM_DONT_BUMP_REF)==0) {
1825 AdjRefCount(msgid, +1);
1828 /* Return success. */
1829 if (msg != NULL) CtdlFreeMessage(msg);
1836 * Message base operation to save a new message to the message store
1837 * (returns new message number)
1839 * This is the back end for CtdlSubmitMsg() and should not be directly
1840 * called by server-side modules.
1843 long send_message(struct CtdlMessage *msg) {
1851 /* Get a new message number */
1852 newmsgid = get_new_message_number();
1853 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1855 /* Generate an ID if we don't have one already */
1856 if (msg->cm_fields['I']==NULL) {
1857 msg->cm_fields['I'] = strdup(msgidbuf);
1860 /* If the message is big, set its body aside for storage elsewhere */
1861 if (msg->cm_fields['M'] != NULL) {
1862 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1864 holdM = msg->cm_fields['M'];
1865 msg->cm_fields['M'] = NULL;
1869 /* Serialize our data structure for storage in the database */
1870 serialize_message(&smr, msg);
1873 msg->cm_fields['M'] = holdM;
1877 cprintf("%d Unable to serialize message\n",
1878 ERROR + INTERNAL_ERROR);
1882 /* Write our little bundle of joy into the message base */
1883 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1884 smr.ser, smr.len) < 0) {
1885 lprintf(CTDL_ERR, "Can't store message\n");
1889 cdb_store(CDB_BIGMSGS,
1899 /* Free the memory we used for the serialized message */
1902 /* Return the *local* message ID to the caller
1903 * (even if we're storing an incoming network message)
1911 * Serialize a struct CtdlMessage into the format used on disk and network.
1913 * This function loads up a "struct ser_ret" (defined in server.h) which
1914 * contains the length of the serialized message and a pointer to the
1915 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1917 void serialize_message(struct ser_ret *ret, /* return values */
1918 struct CtdlMessage *msg) /* unserialized msg */
1922 static char *forder = FORDER;
1924 if (is_valid_message(msg) == 0) return; /* self check */
1927 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1928 ret->len = ret->len +
1929 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1931 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1932 ret->ser = malloc(ret->len);
1933 if (ret->ser == NULL) {
1939 ret->ser[1] = msg->cm_anon_type;
1940 ret->ser[2] = msg->cm_format_type;
1943 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1944 ret->ser[wlen++] = (char)forder[i];
1945 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1946 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1948 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1949 (long)ret->len, (long)wlen);
1957 * Back end for the ReplicationChecks() function
1959 void check_repl(long msgnum, void *userdata) {
1960 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1961 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1966 * Check to see if any messages already exist which carry the same Exclusive ID
1967 * as this one. If any are found, delete them.
1970 int ReplicationChecks(struct CtdlMessage *msg) {
1971 struct CtdlMessage *template;
1974 /* No exclusive id? Don't do anything. */
1975 if (msg->cm_fields['E'] == NULL) return 0;
1976 if (strlen(msg->cm_fields['E']) == 0) return 0;
1977 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1979 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1980 memset(template, 0, sizeof(struct CtdlMessage));
1981 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1983 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1985 CtdlFreeMessage(template);
1986 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1994 * Save a message to disk and submit it into the delivery system.
1996 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1997 struct recptypes *recps, /* recipients (if mail) */
1998 char *force /* force a particular room? */
2000 char submit_filename[128];
2001 char generated_timestamp[32];
2002 char hold_rm[ROOMNAMELEN];
2003 char actual_rm[ROOMNAMELEN];
2004 char force_room[ROOMNAMELEN];
2005 char content_type[SIZ]; /* We have to learn this */
2006 char recipient[SIZ];
2009 struct ctdluser userbuf;
2011 struct MetaData smi;
2012 FILE *network_fp = NULL;
2013 static int seqnum = 1;
2014 struct CtdlMessage *imsg = NULL;
2017 char *hold_R, *hold_D;
2019 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2020 if (is_valid_message(msg) == 0) return(-1); /* self check */
2022 /* If this message has no timestamp, we take the liberty of
2023 * giving it one, right now.
2025 if (msg->cm_fields['T'] == NULL) {
2026 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2027 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2028 msg->cm_fields['T'] = strdup(generated_timestamp);
2031 /* If this message has no path, we generate one.
2033 if (msg->cm_fields['P'] == NULL) {
2034 lprintf(CTDL_DEBUG, "Generating path\n");
2035 if (msg->cm_fields['A'] != NULL) {
2036 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2037 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2038 if (isspace(msg->cm_fields['P'][a])) {
2039 msg->cm_fields['P'][a] = ' ';
2044 msg->cm_fields['P'] = strdup("unknown");
2048 if (force == NULL) {
2049 strcpy(force_room, "");
2052 strcpy(force_room, force);
2055 /* Learn about what's inside, because it's what's inside that counts */
2056 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2057 if (msg->cm_fields['M'] == NULL) {
2058 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2062 switch (msg->cm_format_type) {
2064 strcpy(content_type, "text/x-citadel-variformat");
2067 strcpy(content_type, "text/plain");
2070 strcpy(content_type, "text/plain");
2071 mptr = bmstrstr(msg->cm_fields['M'],
2072 "Content-type: ", strncasecmp);
2074 safestrncpy(content_type, &mptr[14],
2075 sizeof content_type);
2076 for (a = 0; a < strlen(content_type); ++a) {
2077 if ((content_type[a] == ';')
2078 || (content_type[a] == ' ')
2079 || (content_type[a] == 13)
2080 || (content_type[a] == 10)) {
2081 content_type[a] = 0;
2087 /* Goto the correct room */
2088 lprintf(CTDL_DEBUG, "Selected room %s\n",
2089 (recps) ? CC->room.QRname : SENTITEMS);
2090 strcpy(hold_rm, CC->room.QRname);
2091 strcpy(actual_rm, CC->room.QRname);
2092 if (recps != NULL) {
2093 strcpy(actual_rm, SENTITEMS);
2096 /* If the user is a twit, move to the twit room for posting */
2097 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2098 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2100 if (CC->user.axlevel == 2) {
2101 strcpy(hold_rm, actual_rm);
2102 strcpy(actual_rm, config.c_twitroom);
2106 /* ...or if this message is destined for Aide> then go there. */
2107 if (strlen(force_room) > 0) {
2108 strcpy(actual_rm, force_room);
2111 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2112 if (strcasecmp(actual_rm, CC->room.QRname)) {
2113 /* getroom(&CC->room, actual_rm); */
2114 usergoto(actual_rm, 0, 1, NULL, NULL);
2118 * If this message has no O (room) field, generate one.
2120 if (msg->cm_fields['O'] == NULL) {
2121 msg->cm_fields['O'] = strdup(CC->room.QRname);
2124 /* Perform "before save" hooks (aborting if any return nonzero) */
2125 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2126 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2128 /* If this message has an Exclusive ID, perform replication checks */
2129 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2130 if (ReplicationChecks(msg) > 0) return(-4);
2132 /* Save it to disk */
2133 lprintf(CTDL_DEBUG, "Saving to disk\n");
2134 newmsgid = send_message(msg);
2135 if (newmsgid <= 0L) return(-5);
2137 /* Write a supplemental message info record. This doesn't have to
2138 * be a critical section because nobody else knows about this message
2141 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2142 memset(&smi, 0, sizeof(struct MetaData));
2143 smi.meta_msgnum = newmsgid;
2144 smi.meta_refcount = 0;
2145 safestrncpy(smi.meta_content_type, content_type,
2146 sizeof smi.meta_content_type);
2148 /* As part of the new metadata record, measure how
2149 * big this message will be when displayed as RFC822.
2150 * Both POP and IMAP use this, and it's best to just take the hit now
2151 * instead of having to potentially measure thousands of messages when
2152 * a mailbox is opened later.
2155 if (CC->redirect_buffer != NULL) {
2156 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2159 CC->redirect_buffer = malloc(SIZ);
2160 CC->redirect_len = 0;
2161 CC->redirect_alloc = SIZ;
2162 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2163 smi.meta_rfc822_length = CC->redirect_len;
2164 free(CC->redirect_buffer);
2165 CC->redirect_buffer = NULL;
2166 CC->redirect_len = 0;
2167 CC->redirect_alloc = 0;
2171 /* Now figure out where to store the pointers */
2172 lprintf(CTDL_DEBUG, "Storing pointers\n");
2174 /* If this is being done by the networker delivering a private
2175 * message, we want to BYPASS saving the sender's copy (because there
2176 * is no local sender; it would otherwise go to the Trashcan).
2178 if ((!CC->internal_pgm) || (recps == NULL)) {
2179 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2180 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2181 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2186 /* For internet mail, drop a copy in the outbound queue room */
2188 if (recps->num_internet > 0) {
2189 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2192 /* If other rooms are specified, drop them there too. */
2194 if (recps->num_room > 0)
2195 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2196 extract_token(recipient, recps->recp_room, i,
2197 '|', sizeof recipient);
2198 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2199 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2202 /* Bump this user's messages posted counter. */
2203 lprintf(CTDL_DEBUG, "Updating user\n");
2204 lgetuser(&CC->user, CC->curr_user);
2205 CC->user.posted = CC->user.posted + 1;
2206 lputuser(&CC->user);
2208 /* If this is private, local mail, make a copy in the
2209 * recipient's mailbox and bump the reference count.
2212 if (recps->num_local > 0)
2213 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2214 extract_token(recipient, recps->recp_local, i,
2215 '|', sizeof recipient);
2216 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2218 if (getuser(&userbuf, recipient) == 0) {
2219 MailboxName(actual_rm, sizeof actual_rm,
2220 &userbuf, MAILROOM);
2221 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2222 BumpNewMailCounter(userbuf.usernum);
2225 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2226 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2231 /* Perform "after save" hooks */
2232 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2233 PerformMessageHooks(msg, EVT_AFTERSAVE);
2235 /* For IGnet mail, we have to save a new copy into the spooler for
2236 * each recipient, with the R and D fields set to the recipient and
2237 * destination-node. This has two ugly side effects: all other
2238 * recipients end up being unlisted in this recipient's copy of the
2239 * message, and it has to deliver multiple messages to the same
2240 * node. We'll revisit this again in a year or so when everyone has
2241 * a network spool receiver that can handle the new style messages.
2244 if (recps->num_ignet > 0)
2245 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2246 extract_token(recipient, recps->recp_ignet, i,
2247 '|', sizeof recipient);
2249 hold_R = msg->cm_fields['R'];
2250 hold_D = msg->cm_fields['D'];
2251 msg->cm_fields['R'] = malloc(SIZ);
2252 msg->cm_fields['D'] = malloc(128);
2253 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2254 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2256 serialize_message(&smr, msg);
2258 snprintf(submit_filename, sizeof submit_filename,
2259 #ifndef HAVE_SPOOL_DIR
2264 "/network/spoolin/netmail.%04lx.%04x.%04x",
2265 (long) getpid(), CC->cs_pid, ++seqnum);
2266 network_fp = fopen(submit_filename, "wb+");
2267 if (network_fp != NULL) {
2268 fwrite(smr.ser, smr.len, 1, network_fp);
2274 free(msg->cm_fields['R']);
2275 free(msg->cm_fields['D']);
2276 msg->cm_fields['R'] = hold_R;
2277 msg->cm_fields['D'] = hold_D;
2280 /* Go back to the room we started from */
2281 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2282 if (strcasecmp(hold_rm, CC->room.QRname))
2283 /* getroom(&CC->room, hold_rm); */
2284 usergoto(hold_rm, 0, 1, NULL, NULL);
2286 /* For internet mail, generate delivery instructions.
2287 * Yes, this is recursive. Deal with it. Infinite recursion does
2288 * not happen because the delivery instructions message does not
2289 * contain a recipient.
2292 if (recps->num_internet > 0) {
2293 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2294 instr = malloc(SIZ * 2);
2295 snprintf(instr, SIZ * 2,
2296 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2298 SPOOLMIME, newmsgid, (long)time(NULL),
2299 msg->cm_fields['A'], msg->cm_fields['N']
2302 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2303 size_t tmp = strlen(instr);
2304 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2305 snprintf(&instr[tmp], SIZ * 2 - tmp,
2306 "remote|%s|0||\n", recipient);
2309 imsg = malloc(sizeof(struct CtdlMessage));
2310 memset(imsg, 0, sizeof(struct CtdlMessage));
2311 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2312 imsg->cm_anon_type = MES_NORMAL;
2313 imsg->cm_format_type = FMT_RFC822;
2314 imsg->cm_fields['A'] = strdup("Citadel");
2315 imsg->cm_fields['M'] = instr;
2316 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2317 CtdlFreeMessage(imsg);
2326 * Convenience function for generating small administrative messages.
2328 void quickie_message(char *from, char *to, char *room, char *text,
2329 int format_type, char *subject)
2331 struct CtdlMessage *msg;
2332 struct recptypes *recp = NULL;
2334 msg = malloc(sizeof(struct CtdlMessage));
2335 memset(msg, 0, sizeof(struct CtdlMessage));
2336 msg->cm_magic = CTDLMESSAGE_MAGIC;
2337 msg->cm_anon_type = MES_NORMAL;
2338 msg->cm_format_type = format_type;
2339 msg->cm_fields['A'] = strdup(from);
2340 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2341 msg->cm_fields['N'] = strdup(NODENAME);
2343 msg->cm_fields['R'] = strdup(to);
2344 recp = validate_recipients(to);
2346 if (subject != NULL) {
2347 msg->cm_fields['U'] = strdup(subject);
2349 msg->cm_fields['M'] = strdup(text);
2351 CtdlSubmitMsg(msg, recp, room);
2352 CtdlFreeMessage(msg);
2353 if (recp != NULL) free(recp);
2359 * Back end function used by CtdlMakeMessage() and similar functions
2361 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2362 size_t maxlen, /* maximum message length */
2363 char *exist, /* if non-null, append to it;
2364 exist is ALWAYS freed */
2365 int crlf /* CRLF newlines instead of LF */
2369 size_t message_len = 0;
2370 size_t buffer_len = 0;
2376 if (exist == NULL) {
2383 message_len = strlen(exist);
2384 buffer_len = message_len + 4096;
2385 m = realloc(exist, buffer_len);
2392 /* flush the input if we have nowhere to store it */
2397 /* read in the lines of message text one by one */
2399 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2400 if (!strcmp(buf, terminator)) finished = 1;
2402 strcat(buf, "\r\n");
2408 if ( (!flushing) && (!finished) ) {
2409 /* Measure the line */
2410 linelen = strlen(buf);
2412 /* augment the buffer if we have to */
2413 if ((message_len + linelen) >= buffer_len) {
2414 ptr = realloc(m, (buffer_len * 2) );
2415 if (ptr == NULL) { /* flush if can't allocate */
2418 buffer_len = (buffer_len * 2);
2420 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2424 /* Add the new line to the buffer. NOTE: this loop must avoid
2425 * using functions like strcat() and strlen() because they
2426 * traverse the entire buffer upon every call, and doing that
2427 * for a multi-megabyte message slows it down beyond usability.
2429 strcpy(&m[message_len], buf);
2430 message_len += linelen;
2433 /* if we've hit the max msg length, flush the rest */
2434 if (message_len >= maxlen) flushing = 1;
2436 } while (!finished);
2444 * Build a binary message to be saved on disk.
2445 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2446 * will become part of the message. This means you are no longer
2447 * responsible for managing that memory -- it will be freed along with
2448 * the rest of the fields when CtdlFreeMessage() is called.)
2451 struct CtdlMessage *CtdlMakeMessage(
2452 struct ctdluser *author, /* author's user structure */
2453 char *recipient, /* NULL if it's not mail */
2454 char *room, /* room where it's going */
2455 int type, /* see MES_ types in header file */
2456 int format_type, /* variformat, plain text, MIME... */
2457 char *fake_name, /* who we're masquerading as */
2458 char *subject, /* Subject (optional) */
2459 char *preformatted_text /* ...or NULL to read text from client */
2461 char dest_node[SIZ];
2463 struct CtdlMessage *msg;
2465 msg = malloc(sizeof(struct CtdlMessage));
2466 memset(msg, 0, sizeof(struct CtdlMessage));
2467 msg->cm_magic = CTDLMESSAGE_MAGIC;
2468 msg->cm_anon_type = type;
2469 msg->cm_format_type = format_type;
2471 /* Don't confuse the poor folks if it's not routed mail. */
2472 strcpy(dest_node, "");
2476 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2477 msg->cm_fields['P'] = strdup(buf);
2479 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2480 msg->cm_fields['T'] = strdup(buf);
2482 if (fake_name[0]) /* author */
2483 msg->cm_fields['A'] = strdup(fake_name);
2485 msg->cm_fields['A'] = strdup(author->fullname);
2487 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2488 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2491 msg->cm_fields['O'] = strdup(CC->room.QRname);
2494 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2495 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2497 if (recipient[0] != 0) {
2498 msg->cm_fields['R'] = strdup(recipient);
2500 if (dest_node[0] != 0) {
2501 msg->cm_fields['D'] = strdup(dest_node);
2504 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2505 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2508 if (subject != NULL) {
2510 if (strlen(subject) > 0) {
2511 msg->cm_fields['U'] = strdup(subject);
2515 if (preformatted_text != NULL) {
2516 msg->cm_fields['M'] = preformatted_text;
2519 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2520 config.c_maxmsglen, NULL, 0);
2528 * Check to see whether we have permission to post a message in the current
2529 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2530 * returns 0 on success.
2532 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2534 if (!(CC->logged_in)) {
2535 snprintf(errmsgbuf, n, "Not logged in.");
2536 return (ERROR + NOT_LOGGED_IN);
2539 if ((CC->user.axlevel < 2)
2540 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2541 snprintf(errmsgbuf, n, "Need to be validated to enter "
2542 "(except in %s> to sysop)", MAILROOM);
2543 return (ERROR + HIGHER_ACCESS_REQUIRED);
2546 if ((CC->user.axlevel < 4)
2547 && (CC->room.QRflags & QR_NETWORK)) {
2548 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2549 return (ERROR + HIGHER_ACCESS_REQUIRED);
2552 if ((CC->user.axlevel < 6)
2553 && (CC->room.QRflags & QR_READONLY)) {
2554 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2555 return (ERROR + HIGHER_ACCESS_REQUIRED);
2558 strcpy(errmsgbuf, "Ok");
2564 * Check to see if the specified user has Internet mail permission
2565 * (returns nonzero if permission is granted)
2567 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2569 /* Do not allow twits to send Internet mail */
2570 if (who->axlevel <= 2) return(0);
2572 /* Globally enabled? */
2573 if (config.c_restrict == 0) return(1);
2575 /* User flagged ok? */
2576 if (who->flags & US_INTERNET) return(2);
2578 /* Aide level access? */
2579 if (who->axlevel >= 6) return(3);
2581 /* No mail for you! */
2588 * Validate recipients, count delivery types and errors, and handle aliasing
2589 * FIXME check for dupes!!!!!
2591 struct recptypes *validate_recipients(char *recipients) {
2592 struct recptypes *ret;
2593 char this_recp[SIZ];
2594 char this_recp_cooked[SIZ];
2600 struct ctdluser tempUS;
2601 struct ctdlroom tempQR;
2604 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2605 if (ret == NULL) return(NULL);
2606 memset(ret, 0, sizeof(struct recptypes));
2609 ret->num_internet = 0;
2614 if (recipients == NULL) {
2617 else if (strlen(recipients) == 0) {
2621 /* Change all valid separator characters to commas */
2622 for (i=0; i<strlen(recipients); ++i) {
2623 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2624 recipients[i] = ',';
2629 num_recps = num_tokens(recipients, ',');
2632 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2633 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2635 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2636 mailtype = alias(this_recp);
2637 mailtype = alias(this_recp);
2638 mailtype = alias(this_recp);
2639 for (j=0; j<=strlen(this_recp); ++j) {
2640 if (this_recp[j]=='_') {
2641 this_recp_cooked[j] = ' ';
2644 this_recp_cooked[j] = this_recp[j];
2650 if (!strcasecmp(this_recp, "sysop")) {
2652 strcpy(this_recp, config.c_aideroom);
2653 if (strlen(ret->recp_room) > 0) {
2654 strcat(ret->recp_room, "|");
2656 strcat(ret->recp_room, this_recp);
2658 else if (getuser(&tempUS, this_recp) == 0) {
2660 strcpy(this_recp, tempUS.fullname);
2661 if (strlen(ret->recp_local) > 0) {
2662 strcat(ret->recp_local, "|");
2664 strcat(ret->recp_local, this_recp);
2666 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2668 strcpy(this_recp, tempUS.fullname);
2669 if (strlen(ret->recp_local) > 0) {
2670 strcat(ret->recp_local, "|");
2672 strcat(ret->recp_local, this_recp);
2674 else if ( (!strncasecmp(this_recp, "room_", 5))
2675 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2677 if (strlen(ret->recp_room) > 0) {
2678 strcat(ret->recp_room, "|");
2680 strcat(ret->recp_room, &this_recp_cooked[5]);
2688 /* Yes, you're reading this correctly: if the target
2689 * domain points back to the local system or an attached
2690 * Citadel directory, the address is invalid. That's
2691 * because if the address were valid, we would have
2692 * already translated it to a local address by now.
2694 if (IsDirectory(this_recp)) {
2699 ++ret->num_internet;
2700 if (strlen(ret->recp_internet) > 0) {
2701 strcat(ret->recp_internet, "|");
2703 strcat(ret->recp_internet, this_recp);
2708 if (strlen(ret->recp_ignet) > 0) {
2709 strcat(ret->recp_ignet, "|");
2711 strcat(ret->recp_ignet, this_recp);
2719 if (strlen(ret->errormsg) == 0) {
2720 snprintf(append, sizeof append,
2721 "Invalid recipient: %s",
2725 snprintf(append, sizeof append,
2728 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2729 strcat(ret->errormsg, append);
2733 if (strlen(ret->display_recp) == 0) {
2734 strcpy(append, this_recp);
2737 snprintf(append, sizeof append, ", %s",
2740 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2741 strcat(ret->display_recp, append);
2746 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2747 ret->num_room + ret->num_error) == 0) {
2749 strcpy(ret->errormsg, "No recipients specified.");
2752 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2753 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2754 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2755 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2756 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2757 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2765 * message entry - mode 0 (normal)
2767 void cmd_ent0(char *entargs)
2771 char masquerade_as[SIZ];
2773 int format_type = 0;
2774 char newusername[SIZ];
2775 struct CtdlMessage *msg;
2779 struct recptypes *valid = NULL;
2786 post = extract_int(entargs, 0);
2787 extract_token(recp, entargs, 1, '|', sizeof recp);
2788 anon_flag = extract_int(entargs, 2);
2789 format_type = extract_int(entargs, 3);
2790 extract_token(subject, entargs, 4, '|', sizeof subject);
2791 do_confirm = extract_int(entargs, 6);
2793 /* first check to make sure the request is valid. */
2795 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2797 cprintf("%d %s\n", err, errmsg);
2801 /* Check some other permission type things. */
2804 if (CC->user.axlevel < 6) {
2805 cprintf("%d You don't have permission to masquerade.\n",
2806 ERROR + HIGHER_ACCESS_REQUIRED);
2809 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2810 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2811 safestrncpy(CC->fake_postname, newusername,
2812 sizeof(CC->fake_postname) );
2813 cprintf("%d ok\n", CIT_OK);
2816 CC->cs_flags |= CS_POSTING;
2818 /* In the Mail> room we have to behave a little differently --
2819 * make sure the user has specified at least one recipient. Then
2820 * validate the recipient(s).
2822 if ( (CC->room.QRflags & QR_MAILBOX)
2823 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2825 if (CC->user.axlevel < 2) {
2826 strcpy(recp, "sysop");
2829 valid = validate_recipients(recp);
2830 if (valid->num_error > 0) {
2832 ERROR + NO_SUCH_USER, valid->errormsg);
2836 if (valid->num_internet > 0) {
2837 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2838 cprintf("%d You do not have permission "
2839 "to send Internet mail.\n",
2840 ERROR + HIGHER_ACCESS_REQUIRED);
2846 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2847 && (CC->user.axlevel < 4) ) {
2848 cprintf("%d Higher access required for network mail.\n",
2849 ERROR + HIGHER_ACCESS_REQUIRED);
2854 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2855 && ((CC->user.flags & US_INTERNET) == 0)
2856 && (!CC->internal_pgm)) {
2857 cprintf("%d You don't have access to Internet mail.\n",
2858 ERROR + HIGHER_ACCESS_REQUIRED);
2865 /* Is this a room which has anonymous-only or anonymous-option? */
2866 anonymous = MES_NORMAL;
2867 if (CC->room.QRflags & QR_ANONONLY) {
2868 anonymous = MES_ANONONLY;
2870 if (CC->room.QRflags & QR_ANONOPT) {
2871 if (anon_flag == 1) { /* only if the user requested it */
2872 anonymous = MES_ANONOPT;
2876 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2880 /* If we're only checking the validity of the request, return
2881 * success without creating the message.
2884 cprintf("%d %s\n", CIT_OK,
2885 ((valid != NULL) ? valid->display_recp : "") );
2890 /* Handle author masquerading */
2891 if (CC->fake_postname[0]) {
2892 strcpy(masquerade_as, CC->fake_postname);
2894 else if (CC->fake_username[0]) {
2895 strcpy(masquerade_as, CC->fake_username);
2898 strcpy(masquerade_as, "");
2901 /* Read in the message from the client. */
2903 cprintf("%d send message\n", START_CHAT_MODE);
2905 cprintf("%d send message\n", SEND_LISTING);
2907 msg = CtdlMakeMessage(&CC->user, recp,
2908 CC->room.QRname, anonymous, format_type,
2909 masquerade_as, subject, NULL);
2912 msgnum = CtdlSubmitMsg(msg, valid, "");
2915 cprintf("%ld\n", msgnum);
2917 cprintf("Message accepted.\n");
2920 cprintf("Internal error.\n");
2922 if (msg->cm_fields['E'] != NULL) {
2923 cprintf("%s\n", msg->cm_fields['E']);
2930 CtdlFreeMessage(msg);
2932 CC->fake_postname[0] = '\0';
2940 * API function to delete messages which match a set of criteria
2941 * (returns the actual number of messages deleted)
2943 int CtdlDeleteMessages(char *room_name, /* which room */
2944 long dmsgnum, /* or "0" for any */
2945 char *content_type /* or "" for any */
2949 struct ctdlroom qrbuf;
2950 struct cdbdata *cdbfr;
2951 long *msglist = NULL;
2952 long *dellist = NULL;
2955 int num_deleted = 0;
2957 struct MetaData smi;
2959 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2960 room_name, dmsgnum, content_type);
2962 /* get room record, obtaining a lock... */
2963 if (lgetroom(&qrbuf, room_name) != 0) {
2964 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2966 return (0); /* room not found */
2968 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2970 if (cdbfr != NULL) {
2971 msglist = malloc(cdbfr->len);
2972 dellist = malloc(cdbfr->len);
2973 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2974 num_msgs = cdbfr->len / sizeof(long);
2978 for (i = 0; i < num_msgs; ++i) {
2981 /* Set/clear a bit for each criterion */
2983 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2984 delete_this |= 0x01;
2986 if (strlen(content_type) == 0) {
2987 delete_this |= 0x02;
2989 GetMetaData(&smi, msglist[i]);
2990 if (!strcasecmp(smi.meta_content_type,
2992 delete_this |= 0x02;
2996 /* Delete message only if all bits are set */
2997 if (delete_this == 0x03) {
2998 dellist[num_deleted++] = msglist[i];
3003 num_msgs = sort_msglist(msglist, num_msgs);
3004 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3005 msglist, (int)(num_msgs * sizeof(long)));
3007 qrbuf.QRhighest = msglist[num_msgs - 1];
3011 /* Go through the messages we pulled out of the index, and decrement
3012 * their reference counts by 1. If this is the only room the message
3013 * was in, the reference count will reach zero and the message will
3014 * automatically be deleted from the database. We do this in a
3015 * separate pass because there might be plug-in hooks getting called,
3016 * and we don't want that happening during an S_ROOMS critical
3019 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3020 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3021 AdjRefCount(dellist[i], -1);
3024 /* Now free the memory we used, and go away. */
3025 if (msglist != NULL) free(msglist);
3026 if (dellist != NULL) free(dellist);
3027 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3028 return (num_deleted);
3034 * Check whether the current user has permission to delete messages from
3035 * the current room (returns 1 for yes, 0 for no)
3037 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3038 getuser(&CC->user, CC->curr_user);
3039 if ((CC->user.axlevel < 6)
3040 && (CC->user.usernum != CC->room.QRroomaide)
3041 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3042 && (!(CC->internal_pgm))) {
3051 * Delete message from current room
3053 void cmd_dele(char *delstr)
3058 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3059 cprintf("%d Higher access required.\n",
3060 ERROR + HIGHER_ACCESS_REQUIRED);
3063 delnum = extract_long(delstr, 0);
3065 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3068 cprintf("%d %d message%s deleted.\n", CIT_OK,
3069 num_deleted, ((num_deleted != 1) ? "s" : ""));
3071 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3077 * Back end API function for moves and deletes
3079 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3082 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3083 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3084 if (err != 0) return(err);
3092 * move or copy a message to another room
3094 void cmd_move(char *args)
3097 char targ[ROOMNAMELEN];
3098 struct ctdlroom qtemp;
3104 num = extract_long(args, 0);
3105 extract_token(targ, args, 1, '|', sizeof targ);
3106 targ[ROOMNAMELEN - 1] = 0;
3107 is_copy = extract_int(args, 2);
3109 if (getroom(&qtemp, targ) != 0) {
3110 cprintf("%d '%s' does not exist.\n",
3111 ERROR + ROOM_NOT_FOUND, targ);
3115 getuser(&CC->user, CC->curr_user);
3116 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3118 /* Check for permission to perform this operation.
3119 * Remember: "CC->room" is source, "qtemp" is target.
3123 /* Aides can move/copy */
3124 if (CC->user.axlevel >= 6) permit = 1;
3126 /* Room aides can move/copy */
3127 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3129 /* Permit move/copy from personal rooms */
3130 if ((CC->room.QRflags & QR_MAILBOX)
3131 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3133 /* Permit only copy from public to personal room */
3135 && (!(CC->room.QRflags & QR_MAILBOX))
3136 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3138 /* User must have access to target room */
3139 if (!(ra & UA_KNOWN)) permit = 0;
3142 cprintf("%d Higher access required.\n",
3143 ERROR + HIGHER_ACCESS_REQUIRED);
3147 err = CtdlCopyMsgToRoom(num, targ);
3149 cprintf("%d Cannot store message in %s: error %d\n",
3154 /* Now delete the message from the source room,
3155 * if this is a 'move' rather than a 'copy' operation.
3158 CtdlDeleteMessages(CC->room.QRname, num, "");
3161 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3167 * GetMetaData() - Get the supplementary record for a message
3169 void GetMetaData(struct MetaData *smibuf, long msgnum)
3172 struct cdbdata *cdbsmi;
3175 memset(smibuf, 0, sizeof(struct MetaData));
3176 smibuf->meta_msgnum = msgnum;
3177 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3179 /* Use the negative of the message number for its supp record index */
3180 TheIndex = (0L - msgnum);
3182 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3183 if (cdbsmi == NULL) {
3184 return; /* record not found; go with defaults */
3186 memcpy(smibuf, cdbsmi->ptr,
3187 ((cdbsmi->len > sizeof(struct MetaData)) ?
3188 sizeof(struct MetaData) : cdbsmi->len));
3195 * PutMetaData() - (re)write supplementary record for a message
3197 void PutMetaData(struct MetaData *smibuf)
3201 /* Use the negative of the message number for the metadata db index */
3202 TheIndex = (0L - smibuf->meta_msgnum);
3204 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3205 smibuf->meta_msgnum, smibuf->meta_refcount);
3207 cdb_store(CDB_MSGMAIN,
3208 &TheIndex, (int)sizeof(long),
3209 smibuf, (int)sizeof(struct MetaData));
3214 * AdjRefCount - change the reference count for a message;
3215 * delete the message if it reaches zero
3217 void AdjRefCount(long msgnum, int incr)
3220 struct MetaData smi;
3223 /* This is a *tight* critical section; please keep it that way, as
3224 * it may get called while nested in other critical sections.
3225 * Complicating this any further will surely cause deadlock!
3227 begin_critical_section(S_SUPPMSGMAIN);
3228 GetMetaData(&smi, msgnum);
3229 smi.meta_refcount += incr;
3231 end_critical_section(S_SUPPMSGMAIN);
3232 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3233 msgnum, incr, smi.meta_refcount);
3235 /* If the reference count is now zero, delete the message
3236 * (and its supplementary record as well).
3237 * FIXME ... defer this so it doesn't keep the user waiting.
3239 if (smi.meta_refcount == 0) {
3240 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3242 /* Remove from fulltext index */
3243 if (config.c_enable_fulltext) {
3244 ft_index_message(msgnum, 0);
3247 /* Remove from message base */
3249 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3250 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3252 /* Remove metadata record */
3253 delnum = (0L - msgnum);
3254 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3259 * Write a generic object to this room
3261 * Note: this could be much more efficient. Right now we use two temporary
3262 * files, and still pull the message into memory as with all others.
3264 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3265 char *content_type, /* MIME type of this object */
3266 char *tempfilename, /* Where to fetch it from */
3267 struct ctdluser *is_mailbox, /* Mailbox room? */
3268 int is_binary, /* Is encoding necessary? */
3269 int is_unique, /* Del others of this type? */
3270 unsigned int flags /* Internal save flags */
3275 struct ctdlroom qrbuf;
3276 char roomname[ROOMNAMELEN];
3277 struct CtdlMessage *msg;
3279 char *raw_message = NULL;
3280 char *encoded_message = NULL;
3281 off_t raw_length = 0;
3283 if (is_mailbox != NULL)
3284 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3286 safestrncpy(roomname, req_room, sizeof(roomname));
3287 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3290 fp = fopen(tempfilename, "rb");
3292 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3293 tempfilename, strerror(errno));
3296 fseek(fp, 0L, SEEK_END);
3297 raw_length = ftell(fp);
3299 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3301 raw_message = malloc((size_t)raw_length + 2);
3302 fread(raw_message, (size_t)raw_length, 1, fp);
3306 encoded_message = malloc((size_t)
3307 (((raw_length * 134) / 100) + 4096 ) );
3310 encoded_message = malloc((size_t)(raw_length + 4096));
3313 sprintf(encoded_message, "Content-type: %s\n", content_type);
3316 sprintf(&encoded_message[strlen(encoded_message)],
3317 "Content-transfer-encoding: base64\n\n"
3321 sprintf(&encoded_message[strlen(encoded_message)],
3322 "Content-transfer-encoding: 7bit\n\n"
3328 &encoded_message[strlen(encoded_message)],
3334 raw_message[raw_length] = 0;
3336 &encoded_message[strlen(encoded_message)],
3344 lprintf(CTDL_DEBUG, "Allocating\n");
3345 msg = malloc(sizeof(struct CtdlMessage));
3346 memset(msg, 0, sizeof(struct CtdlMessage));
3347 msg->cm_magic = CTDLMESSAGE_MAGIC;
3348 msg->cm_anon_type = MES_NORMAL;
3349 msg->cm_format_type = 4;
3350 msg->cm_fields['A'] = strdup(CC->user.fullname);
3351 msg->cm_fields['O'] = strdup(req_room);
3352 msg->cm_fields['N'] = strdup(config.c_nodename);
3353 msg->cm_fields['H'] = strdup(config.c_humannode);
3354 msg->cm_flags = flags;
3356 msg->cm_fields['M'] = encoded_message;
3358 /* Create the requested room if we have to. */
3359 if (getroom(&qrbuf, roomname) != 0) {
3360 create_room(roomname,
3361 ( (is_mailbox != NULL) ? 5 : 3 ),
3362 "", 0, 1, 0, VIEW_BBS);
3364 /* If the caller specified this object as unique, delete all
3365 * other objects of this type that are currently in the room.
3368 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3369 CtdlDeleteMessages(roomname, 0L, content_type));
3371 /* Now write the data */
3372 CtdlSubmitMsg(msg, NULL, roomname);
3373 CtdlFreeMessage(msg);
3381 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3382 config_msgnum = msgnum;
3386 char *CtdlGetSysConfig(char *sysconfname) {
3387 char hold_rm[ROOMNAMELEN];
3390 struct CtdlMessage *msg;
3393 strcpy(hold_rm, CC->room.QRname);
3394 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3395 getroom(&CC->room, hold_rm);
3400 /* We want the last (and probably only) config in this room */
3401 begin_critical_section(S_CONFIG);
3402 config_msgnum = (-1L);
3403 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3404 CtdlGetSysConfigBackend, NULL);
3405 msgnum = config_msgnum;
3406 end_critical_section(S_CONFIG);
3412 msg = CtdlFetchMessage(msgnum, 1);
3414 conf = strdup(msg->cm_fields['M']);
3415 CtdlFreeMessage(msg);
3422 getroom(&CC->room, hold_rm);
3424 if (conf != NULL) do {
3425 extract_token(buf, conf, 0, '\n', sizeof buf);
3426 strcpy(conf, &conf[strlen(buf)+1]);
3427 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3432 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3433 char temp[PATH_MAX];
3436 strcpy(temp, tmpnam(NULL));
3438 fp = fopen(temp, "w");
3439 if (fp == NULL) return;
3440 fprintf(fp, "%s", sysconfdata);
3443 /* this handy API function does all the work for us */
3444 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3450 * Determine whether a given Internet address belongs to the current user
3452 int CtdlIsMe(char *addr, int addr_buf_len)
3454 struct recptypes *recp;
3457 recp = validate_recipients(addr);
3458 if (recp == NULL) return(0);
3460 if (recp->num_local == 0) {
3465 for (i=0; i<recp->num_local; ++i) {
3466 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3467 if (!strcasecmp(addr, CC->user.fullname)) {
3479 * Citadel protocol command to do the same
3481 void cmd_isme(char *argbuf) {
3484 if (CtdlAccessCheck(ac_logged_in)) return;
3485 extract_token(addr, argbuf, 0, '|', sizeof addr);
3487 if (CtdlIsMe(addr, sizeof addr)) {
3488 cprintf("%d %s\n", CIT_OK, addr);
3491 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);