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];
329 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
330 target_msgnum, target_setting, which_set);
332 /* Learn about the user and room in question */
333 CtdlGetRelationship(&vbuf,
334 ((which_user != NULL) ? which_user : &CC->user),
335 ((which_room != NULL) ? which_room : &CC->room)
338 /* Load the message list */
339 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
341 msglist = malloc(cdbfr->len);
342 memcpy(msglist, cdbfr->ptr, cdbfr->len);
343 num_msgs = cdbfr->len / sizeof(long);
346 return; /* No messages at all? No further action. */
349 is_set = malloc(num_msgs * sizeof(char));
350 memset(is_set, 0, (num_msgs * sizeof(char)) );
352 /* Decide which message set we're manipulating */
354 case ctdlsetseen_seen:
355 safestrncpy(vset, vbuf.v_seen, sizeof vset);
357 case ctdlsetseen_answered:
358 safestrncpy(vset, vbuf.v_answered, sizeof vset);
362 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
364 /* Translate the existing sequence set into an array of booleans */
365 num_sets = num_tokens(vset, ',');
366 for (s=0; s<num_sets; ++s) {
367 extract_token(setstr, vset, s, ',', sizeof setstr);
369 extract_token(lostr, setstr, 0, ':', sizeof lostr);
370 if (num_tokens(setstr, ':') >= 2) {
371 extract_token(histr, setstr, 1, ':', sizeof histr);
372 if (!strcmp(histr, "*")) {
373 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
377 strcpy(histr, lostr);
382 for (i = 0; i < num_msgs; ++i) {
383 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
389 /* Now translate the array of booleans back into a sequence set */
394 for (i=0; i<num_msgs; ++i) {
397 if (msglist[i] == target_msgnum) {
398 is_seen = target_setting;
405 if (lo < 0L) lo = msglist[i];
409 if ( ((is_seen == 0) && (was_seen == 1))
410 || ((is_seen == 1) && (i == num_msgs-1)) ) {
412 /* begin trim-o-matic code */
415 while ( (strlen(vset) + 20) > sizeof vset) {
416 remove_token(vset, 0, ',');
418 if (j--) break; /* loop no more than 9 times */
420 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
424 snprintf(lostr, sizeof lostr,
425 "1:%ld,%s", t, vset);
426 safestrncpy(vset, lostr, sizeof vset);
428 /* end trim-o-matic code */
436 snprintf(&vset[tmp], (sizeof vset) - tmp,
440 snprintf(&vset[tmp], (sizeof vset) - tmp,
449 /* Decide which message set we're manipulating */
451 case ctdlsetseen_seen:
452 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
454 case ctdlsetseen_answered:
455 safestrncpy(vbuf.v_answered, vset,
456 sizeof vbuf.v_answered);
461 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
463 CtdlSetRelationship(&vbuf,
464 ((which_user != NULL) ? which_user : &CC->user),
465 ((which_room != NULL) ? which_room : &CC->room)
471 * API function to perform an operation for each qualifying message in the
472 * current room. (Returns the number of messages processed.)
474 int CtdlForEachMessage(int mode, long ref,
476 struct CtdlMessage *compare,
477 void (*CallBack) (long, void *),
483 struct cdbdata *cdbfr;
484 long *msglist = NULL;
486 int num_processed = 0;
489 struct CtdlMessage *msg;
492 int printed_lastold = 0;
494 /* Learn about the user and room in question */
496 getuser(&CC->user, CC->curr_user);
497 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
499 /* Load the message list */
500 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
502 msglist = malloc(cdbfr->len);
503 memcpy(msglist, cdbfr->ptr, cdbfr->len);
504 num_msgs = cdbfr->len / sizeof(long);
507 return 0; /* No messages at all? No further action. */
512 * Now begin the traversal.
514 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
516 /* If the caller is looking for a specific MIME type, filter
517 * out all messages which are not of the type requested.
519 if (content_type != NULL) if (strlen(content_type) > 0) {
521 /* This call to GetMetaData() sits inside this loop
522 * so that we only do the extra database read per msg
523 * if we need to. Doing the extra read all the time
524 * really kills the server. If we ever need to use
525 * metadata for another search criterion, we need to
526 * move the read somewhere else -- but still be smart
527 * enough to only do the read if the caller has
528 * specified something that will need it.
530 GetMetaData(&smi, msglist[a]);
532 if (strcasecmp(smi.meta_content_type, content_type)) {
538 num_msgs = sort_msglist(msglist, num_msgs);
540 /* If a template was supplied, filter out the messages which
541 * don't match. (This could induce some delays!)
544 if (compare != NULL) {
545 for (a = 0; a < num_msgs; ++a) {
546 msg = CtdlFetchMessage(msglist[a], 1);
548 if (CtdlMsgCmp(msg, compare)) {
551 CtdlFreeMessage(msg);
559 * Now iterate through the message list, according to the
560 * criteria supplied by the caller.
563 for (a = 0; a < num_msgs; ++a) {
564 thismsg = msglist[a];
565 is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
566 if (is_seen) lastold = thismsg;
571 || ((mode == MSGS_OLD) && (is_seen))
572 || ((mode == MSGS_NEW) && (!is_seen))
573 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
574 || ((mode == MSGS_FIRST) && (a < ref))
575 || ((mode == MSGS_GT) && (thismsg > ref))
576 || ((mode == MSGS_EQ) && (thismsg == ref))
579 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
581 CallBack(lastold, userdata);
585 if (CallBack) CallBack(thismsg, userdata);
589 free(msglist); /* Clean up */
590 return num_processed;
596 * cmd_msgs() - get list of message #'s in this room
597 * implements the MSGS server command using CtdlForEachMessage()
599 void cmd_msgs(char *cmdbuf)
608 int with_template = 0;
609 struct CtdlMessage *template = NULL;
611 extract_token(which, cmdbuf, 0, '|', sizeof which);
612 cm_ref = extract_int(cmdbuf, 1);
613 with_template = extract_int(cmdbuf, 2);
617 if (!strncasecmp(which, "OLD", 3))
619 else if (!strncasecmp(which, "NEW", 3))
621 else if (!strncasecmp(which, "FIRST", 5))
623 else if (!strncasecmp(which, "LAST", 4))
625 else if (!strncasecmp(which, "GT", 2))
628 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
629 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
635 cprintf("%d Send template then receive message list\n",
637 template = (struct CtdlMessage *)
638 malloc(sizeof(struct CtdlMessage));
639 memset(template, 0, sizeof(struct CtdlMessage));
640 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
641 extract_token(tfield, buf, 0, '|', sizeof tfield);
642 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
643 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
644 if (!strcasecmp(tfield, msgkeys[i])) {
645 template->cm_fields[i] =
653 cprintf("%d Message list...\n", LISTING_FOLLOWS);
656 CtdlForEachMessage(mode, cm_ref,
657 NULL, template, simple_listing, NULL);
658 if (template != NULL) CtdlFreeMessage(template);
666 * help_subst() - support routine for help file viewer
668 void help_subst(char *strbuf, char *source, char *dest)
673 while (p = pattern2(strbuf, source), (p >= 0)) {
674 strcpy(workbuf, &strbuf[p + strlen(source)]);
675 strcpy(&strbuf[p], dest);
676 strcat(strbuf, workbuf);
681 void do_help_subst(char *buffer)
685 help_subst(buffer, "^nodename", config.c_nodename);
686 help_subst(buffer, "^humannode", config.c_humannode);
687 help_subst(buffer, "^fqdn", config.c_fqdn);
688 help_subst(buffer, "^username", CC->user.fullname);
689 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
690 help_subst(buffer, "^usernum", buf2);
691 help_subst(buffer, "^sysadm", config.c_sysadm);
692 help_subst(buffer, "^variantname", CITADEL);
693 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
694 help_subst(buffer, "^maxsessions", buf2);
695 help_subst(buffer, "^bbsdir", CTDLDIR);
701 * memfmout() - Citadel text formatter and paginator.
702 * Although the original purpose of this routine was to format
703 * text to the reader's screen width, all we're really using it
704 * for here is to format text out to 80 columns before sending it
705 * to the client. The client software may reformat it again.
708 int width, /* screen width to use */
709 char *mptr, /* where are we going to get our text from? */
710 char subst, /* nonzero if we should do substitutions */
711 char *nl) /* string to terminate lines with */
723 c = 1; /* c is the current pos */
727 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
729 buffer[strlen(buffer) + 1] = 0;
730 buffer[strlen(buffer)] = ch;
733 if (buffer[0] == '^')
734 do_help_subst(buffer);
736 buffer[strlen(buffer) + 1] = 0;
738 strcpy(buffer, &buffer[1]);
746 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
748 if (((old == 13) || (old == 10)) && (isspace(real))) {
756 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
757 cprintf("%s%s", nl, aaa);
766 if ((strlen(aaa) + c) > (width - 5)) {
775 if ((ch == 13) || (ch == 10)) {
776 cprintf("%s%s", aaa, nl);
783 cprintf("%s%s", aaa, nl);
789 * Callback function for mime parser that simply lists the part
791 void list_this_part(char *name, char *filename, char *partnum, char *disp,
792 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
796 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
797 name, filename, partnum, disp, cbtype, (long)length);
801 * Callback function for multipart prefix
803 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
804 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
807 cprintf("pref=%s|%s\n", partnum, cbtype);
811 * Callback function for multipart sufffix
813 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
814 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
817 cprintf("suff=%s|%s\n", partnum, cbtype);
822 * Callback function for mime parser that opens a section for downloading
824 void mime_download(char *name, char *filename, char *partnum, char *disp,
825 void *content, char *cbtype, char *cbcharset, size_t length,
826 char *encoding, void *cbuserdata)
829 /* Silently go away if there's already a download open... */
830 if (CC->download_fp != NULL)
833 /* ...or if this is not the desired section */
834 if (strcasecmp(CC->download_desired_section, partnum))
837 CC->download_fp = tmpfile();
838 if (CC->download_fp == NULL)
841 fwrite(content, length, 1, CC->download_fp);
842 fflush(CC->download_fp);
843 rewind(CC->download_fp);
845 OpenCmdResult(filename, cbtype);
851 * Load a message from disk into memory.
852 * This is used by CtdlOutputMsg() and other fetch functions.
854 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
855 * using the CtdlMessageFree() function.
857 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
859 struct cdbdata *dmsgtext;
860 struct CtdlMessage *ret = NULL;
864 cit_uint8_t field_header;
866 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
868 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
869 if (dmsgtext == NULL) {
872 mptr = dmsgtext->ptr;
873 upper_bound = mptr + dmsgtext->len;
875 /* Parse the three bytes that begin EVERY message on disk.
876 * The first is always 0xFF, the on-disk magic number.
877 * The second is the anonymous/public type byte.
878 * The third is the format type byte (vari, fixed, or MIME).
883 "Message %ld appears to be corrupted.\n",
888 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
889 memset(ret, 0, sizeof(struct CtdlMessage));
891 ret->cm_magic = CTDLMESSAGE_MAGIC;
892 ret->cm_anon_type = *mptr++; /* Anon type byte */
893 ret->cm_format_type = *mptr++; /* Format type byte */
896 * The rest is zero or more arbitrary fields. Load them in.
897 * We're done when we encounter either a zero-length field or
898 * have just processed the 'M' (message text) field.
901 if (mptr >= upper_bound) {
904 field_header = *mptr++;
905 ret->cm_fields[field_header] = strdup(mptr);
907 while (*mptr++ != 0); /* advance to next field */
909 } while ((mptr < upper_bound) && (field_header != 'M'));
913 /* Always make sure there's something in the msg text field. If
914 * it's NULL, the message text is most likely stored separately,
915 * so go ahead and fetch that. Failing that, just set a dummy
916 * body so other code doesn't barf.
918 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
919 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
920 if (dmsgtext != NULL) {
921 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
925 if (ret->cm_fields['M'] == NULL) {
926 ret->cm_fields['M'] = strdup("<no text>\n");
929 /* Perform "before read" hooks (aborting if any return nonzero) */
930 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
931 CtdlFreeMessage(ret);
940 * Returns 1 if the supplied pointer points to a valid Citadel message.
941 * If the pointer is NULL or the magic number check fails, returns 0.
943 int is_valid_message(struct CtdlMessage *msg) {
946 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
947 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
955 * 'Destructor' for struct CtdlMessage
957 void CtdlFreeMessage(struct CtdlMessage *msg)
961 if (is_valid_message(msg) == 0) return;
963 for (i = 0; i < 256; ++i)
964 if (msg->cm_fields[i] != NULL) {
965 free(msg->cm_fields[i]);
968 msg->cm_magic = 0; /* just in case */
974 * Pre callback function for multipart/alternative
976 * NOTE: this differs from the standard behavior for a reason. Normally when
977 * displaying multipart/alternative you want to show the _last_ usable
978 * format in the message. Here we show the _first_ one, because it's
979 * usually text/plain. Since this set of functions is designed for text
980 * output to non-MIME-aware clients, this is the desired behavior.
983 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
984 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
989 ma = (struct ma_info *)cbuserdata;
990 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
991 if (!strcasecmp(cbtype, "multipart/alternative")) {
999 * Post callback function for multipart/alternative
1001 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1002 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1007 ma = (struct ma_info *)cbuserdata;
1008 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1009 if (!strcasecmp(cbtype, "multipart/alternative")) {
1017 * Inline callback function for mime parser that wants to display text
1019 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1020 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1028 ma = (struct ma_info *)cbuserdata;
1030 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1033 * If we're in the middle of a multipart/alternative scope and
1034 * we've already printed another section, skip this one.
1036 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1037 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1042 if ( (!strcasecmp(cbtype, "text/plain"))
1043 || (strlen(cbtype)==0) ) {
1046 client_write(wptr, length);
1047 if (wptr[length-1] != '\n') {
1052 else if (!strcasecmp(cbtype, "text/html")) {
1053 ptr = html_to_ascii(content, 80, 0);
1055 client_write(ptr, wlen);
1056 if (ptr[wlen-1] != '\n') {
1061 else if (strncasecmp(cbtype, "multipart/", 10)) {
1062 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1063 partnum, filename, cbtype, (long)length);
1068 * The client is elegant and sophisticated and wants to be choosy about
1069 * MIME content types, so figure out which multipart/alternative part
1070 * we're going to send.
1072 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1073 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1080 ma = (struct ma_info *)cbuserdata;
1082 if (ma->is_ma > 0) {
1083 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1084 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1085 if (!strcasecmp(buf, cbtype)) {
1086 strcpy(ma->chosen_part, partnum);
1093 * Now that we've chosen our preferred part, output it.
1095 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1096 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1101 int add_newline = 0;
1105 ma = (struct ma_info *)cbuserdata;
1107 /* This is not the MIME part you're looking for... */
1108 if (strcasecmp(partnum, ma->chosen_part)) return;
1110 /* If the content-type of this part is in our preferred formats
1111 * list, we can simply output it verbatim.
1113 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1114 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1115 if (!strcasecmp(buf, cbtype)) {
1116 /* Yeah! Go! W00t!! */
1118 text_content = (char *)content;
1119 if (text_content[length-1] != '\n') {
1123 cprintf("Content-type: %s\n", cbtype);
1124 cprintf("Content-length: %d\n",
1125 (int)(length + add_newline) );
1126 if (strlen(encoding) > 0) {
1127 cprintf("Content-transfer-encoding: %s\n", encoding);
1130 cprintf("Content-transfer-encoding: 7bit\n");
1133 client_write(content, length);
1134 if (add_newline) cprintf("\n");
1139 /* No translations required or possible: output as text/plain */
1140 cprintf("Content-type: text/plain\n\n");
1141 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1142 length, encoding, cbuserdata);
1147 * Get a message off disk. (returns om_* values found in msgbase.h)
1150 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1151 int mode, /* how would you like that message? */
1152 int headers_only, /* eschew the message body? */
1153 int do_proto, /* do Citadel protocol responses? */
1154 int crlf /* Use CRLF newlines instead of LF? */
1156 struct CtdlMessage *TheMessage = NULL;
1157 int retcode = om_no_such_msg;
1159 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1162 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1163 if (do_proto) cprintf("%d Not logged in.\n",
1164 ERROR + NOT_LOGGED_IN);
1165 return(om_not_logged_in);
1168 /* FIXME: check message id against msglist for this room */
1171 * Fetch the message from disk. If we're in any sort of headers
1172 * only mode, request that we don't even bother loading the body
1175 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1176 TheMessage = CtdlFetchMessage(msg_num, 0);
1179 TheMessage = CtdlFetchMessage(msg_num, 1);
1182 if (TheMessage == NULL) {
1183 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1184 ERROR + MESSAGE_NOT_FOUND, msg_num);
1185 return(om_no_such_msg);
1188 retcode = CtdlOutputPreLoadedMsg(
1189 TheMessage, msg_num, mode,
1190 headers_only, do_proto, crlf);
1192 CtdlFreeMessage(TheMessage);
1199 * Get a message off disk. (returns om_* values found in msgbase.h)
1202 int CtdlOutputPreLoadedMsg(
1203 struct CtdlMessage *TheMessage,
1205 int mode, /* how would you like that message? */
1206 int headers_only, /* eschew the message body? */
1207 int do_proto, /* do Citadel protocol responses? */
1208 int crlf /* Use CRLF newlines instead of LF? */
1214 char display_name[256];
1216 char *nl; /* newline string */
1218 int subject_found = 0;
1221 /* Buffers needed for RFC822 translation. These are all filled
1222 * using functions that are bounds-checked, and therefore we can
1223 * make them substantially smaller than SIZ.
1231 char datestamp[100];
1233 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1234 ((TheMessage == NULL) ? "NULL" : "not null"),
1236 mode, headers_only, do_proto, crlf);
1238 snprintf(mid, sizeof mid, "%ld", msg_num);
1239 nl = (crlf ? "\r\n" : "\n");
1241 if (!is_valid_message(TheMessage)) {
1243 "ERROR: invalid preloaded message for output\n");
1244 return(om_no_such_msg);
1247 /* Are we downloading a MIME component? */
1248 if (mode == MT_DOWNLOAD) {
1249 if (TheMessage->cm_format_type != FMT_RFC822) {
1251 cprintf("%d This is not a MIME message.\n",
1252 ERROR + ILLEGAL_VALUE);
1253 } else if (CC->download_fp != NULL) {
1254 if (do_proto) cprintf(
1255 "%d You already have a download open.\n",
1256 ERROR + RESOURCE_BUSY);
1258 /* Parse the message text component */
1259 mptr = TheMessage->cm_fields['M'];
1260 ma = malloc(sizeof(struct ma_info));
1261 memset(ma, 0, sizeof(struct ma_info));
1262 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1264 /* If there's no file open by this time, the requested
1265 * section wasn't found, so print an error
1267 if (CC->download_fp == NULL) {
1268 if (do_proto) cprintf(
1269 "%d Section %s not found.\n",
1270 ERROR + FILE_NOT_FOUND,
1271 CC->download_desired_section);
1274 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1277 /* now for the user-mode message reading loops */
1278 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1280 /* Does the caller want to skip the headers? */
1281 if (headers_only == HEADERS_NONE) goto START_TEXT;
1283 /* Tell the client which format type we're using. */
1284 if ( (mode == MT_CITADEL) && (do_proto) ) {
1285 cprintf("type=%d\n", TheMessage->cm_format_type);
1288 /* nhdr=yes means that we're only displaying headers, no body */
1289 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1290 && (mode == MT_CITADEL)
1293 cprintf("nhdr=yes\n");
1296 /* begin header processing loop for Citadel message format */
1298 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1300 safestrncpy(display_name, "<unknown>", sizeof display_name);
1301 if (TheMessage->cm_fields['A']) {
1302 strcpy(buf, TheMessage->cm_fields['A']);
1303 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1304 safestrncpy(display_name, "****", sizeof display_name);
1306 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1307 safestrncpy(display_name, "anonymous", sizeof display_name);
1310 safestrncpy(display_name, buf, sizeof display_name);
1312 if ((is_room_aide())
1313 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1314 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1315 size_t tmp = strlen(display_name);
1316 snprintf(&display_name[tmp],
1317 sizeof display_name - tmp,
1322 /* Don't show Internet address for users on the
1323 * local Citadel network.
1326 if (TheMessage->cm_fields['N'] != NULL)
1327 if (strlen(TheMessage->cm_fields['N']) > 0)
1328 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1332 /* Now spew the header fields in the order we like them. */
1333 safestrncpy(allkeys, FORDER, sizeof allkeys);
1334 for (i=0; i<strlen(allkeys); ++i) {
1335 k = (int) allkeys[i];
1337 if ( (TheMessage->cm_fields[k] != NULL)
1338 && (msgkeys[k] != NULL) ) {
1340 if (do_proto) cprintf("%s=%s\n",
1344 else if ((k == 'F') && (suppress_f)) {
1347 /* Masquerade display name if needed */
1349 if (do_proto) cprintf("%s=%s\n",
1351 TheMessage->cm_fields[k]
1360 /* begin header processing loop for RFC822 transfer format */
1365 strcpy(snode, NODENAME);
1366 strcpy(lnode, HUMANNODE);
1367 if (mode == MT_RFC822) {
1368 for (i = 0; i < 256; ++i) {
1369 if (TheMessage->cm_fields[i]) {
1370 mptr = TheMessage->cm_fields[i];
1373 safestrncpy(luser, mptr, sizeof luser);
1374 safestrncpy(suser, mptr, sizeof suser);
1376 else if (i == 'U') {
1377 cprintf("Subject: %s%s", mptr, nl);
1381 safestrncpy(mid, mptr, sizeof mid);
1383 safestrncpy(lnode, mptr, sizeof lnode);
1385 safestrncpy(fuser, mptr, sizeof fuser);
1386 /* else if (i == 'O')
1387 cprintf("X-Citadel-Room: %s%s",
1390 safestrncpy(snode, mptr, sizeof snode);
1392 cprintf("To: %s%s", mptr, nl);
1393 else if (i == 'T') {
1394 datestring(datestamp, sizeof datestamp,
1395 atol(mptr), DATESTRING_RFC822);
1396 cprintf("Date: %s%s", datestamp, nl);
1400 if (subject_found == 0) {
1401 cprintf("Subject: (no subject)%s", nl);
1405 for (i=0; i<strlen(suser); ++i) {
1406 suser[i] = tolower(suser[i]);
1407 if (!isalnum(suser[i])) suser[i]='_';
1410 if (mode == MT_RFC822) {
1411 if (!strcasecmp(snode, NODENAME)) {
1412 safestrncpy(snode, FQDN, sizeof snode);
1415 /* Construct a fun message id */
1416 cprintf("Message-ID: <%s", mid);
1417 if (strchr(mid, '@')==NULL) {
1418 cprintf("@%s", snode);
1422 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1423 // cprintf("From: x@x.org (----)%s", nl);
1424 cprintf("From: \"----\" <x@x.org>%s", nl);
1426 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1427 // cprintf("From: x@x.org (anonymous)%s", nl);
1428 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1430 else if (strlen(fuser) > 0) {
1431 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1432 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1435 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1436 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1439 cprintf("Organization: %s%s", lnode, nl);
1441 /* Blank line signifying RFC822 end-of-headers */
1442 if (TheMessage->cm_format_type != FMT_RFC822) {
1447 /* end header processing loop ... at this point, we're in the text */
1449 if (headers_only == HEADERS_FAST) goto DONE;
1450 mptr = TheMessage->cm_fields['M'];
1452 /* Tell the client about the MIME parts in this message */
1453 if (TheMessage->cm_format_type == FMT_RFC822) {
1454 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1455 mime_parser(mptr, NULL,
1456 (do_proto ? *list_this_part : NULL),
1457 (do_proto ? *list_this_pref : NULL),
1458 (do_proto ? *list_this_suff : NULL),
1461 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1462 /* FIXME ... we have to put some code in here to avoid
1463 * printing duplicate header information when both
1464 * Citadel and RFC822 headers exist. Preference should
1465 * probably be given to the RFC822 headers.
1467 int done_rfc822_hdrs = 0;
1468 while (ch=*(mptr++), ch!=0) {
1473 if (!done_rfc822_hdrs) {
1474 if (headers_only != HEADERS_NONE) {
1479 if (headers_only != HEADERS_ONLY) {
1483 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1484 done_rfc822_hdrs = 1;
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;
1507 if (headers_only == HEADERS_ONLY) {
1511 /* signify start of msg text */
1512 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1513 if (do_proto) cprintf("text\n");
1516 /* If the format type on disk is 1 (fixed-format), then we want
1517 * everything to be output completely literally ... regardless of
1518 * what message transfer format is in use.
1520 if (TheMessage->cm_format_type == FMT_FIXED) {
1521 if (mode == MT_MIME) {
1522 cprintf("Content-type: text/plain\n\n");
1525 while (ch = *mptr++, ch > 0) {
1528 if ((ch == 10) || (strlen(buf) > 250)) {
1529 cprintf("%s%s", buf, nl);
1532 buf[strlen(buf) + 1] = 0;
1533 buf[strlen(buf)] = ch;
1536 if (strlen(buf) > 0)
1537 cprintf("%s%s", buf, nl);
1540 /* If the message on disk is format 0 (Citadel vari-format), we
1541 * output using the formatter at 80 columns. This is the final output
1542 * form if the transfer format is RFC822, but if the transfer format
1543 * is Citadel proprietary, it'll still work, because the indentation
1544 * for new paragraphs is correct and the client will reformat the
1545 * message to the reader's screen width.
1547 if (TheMessage->cm_format_type == FMT_CITADEL) {
1548 if (mode == MT_MIME) {
1549 cprintf("Content-type: text/x-citadel-variformat\n\n");
1551 memfmout(80, mptr, 0, nl);
1554 /* If the message on disk is format 4 (MIME), we've gotta hand it
1555 * off to the MIME parser. The client has already been told that
1556 * this message is format 1 (fixed format), so the callback function
1557 * we use will display those parts as-is.
1559 if (TheMessage->cm_format_type == FMT_RFC822) {
1560 ma = malloc(sizeof(struct ma_info));
1561 memset(ma, 0, sizeof(struct ma_info));
1563 if (mode == MT_MIME) {
1564 strcpy(ma->chosen_part, "1");
1565 mime_parser(mptr, NULL,
1566 *choose_preferred, *fixed_output_pre,
1567 *fixed_output_post, (void *)ma, 0);
1568 mime_parser(mptr, NULL,
1569 *output_preferred, NULL, NULL, (void *)ma, 0);
1572 mime_parser(mptr, NULL,
1573 *fixed_output, *fixed_output_pre,
1574 *fixed_output_post, (void *)ma, 0);
1580 DONE: /* now we're done */
1581 if (do_proto) cprintf("000\n");
1588 * display a message (mode 0 - Citadel proprietary)
1590 void cmd_msg0(char *cmdbuf)
1593 int headers_only = HEADERS_ALL;
1595 msgid = extract_long(cmdbuf, 0);
1596 headers_only = extract_int(cmdbuf, 1);
1598 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1604 * display a message (mode 2 - RFC822)
1606 void cmd_msg2(char *cmdbuf)
1609 int headers_only = HEADERS_ALL;
1611 msgid = extract_long(cmdbuf, 0);
1612 headers_only = extract_int(cmdbuf, 1);
1614 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1620 * display a message (mode 3 - IGnet raw format - internal programs only)
1622 void cmd_msg3(char *cmdbuf)
1625 struct CtdlMessage *msg;
1628 if (CC->internal_pgm == 0) {
1629 cprintf("%d This command is for internal programs only.\n",
1630 ERROR + HIGHER_ACCESS_REQUIRED);
1634 msgnum = extract_long(cmdbuf, 0);
1635 msg = CtdlFetchMessage(msgnum, 1);
1637 cprintf("%d Message %ld not found.\n",
1638 ERROR + MESSAGE_NOT_FOUND, msgnum);
1642 serialize_message(&smr, msg);
1643 CtdlFreeMessage(msg);
1646 cprintf("%d Unable to serialize message\n",
1647 ERROR + INTERNAL_ERROR);
1651 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1652 client_write((char *)smr.ser, (int)smr.len);
1659 * Display a message using MIME content types
1661 void cmd_msg4(char *cmdbuf)
1665 msgid = extract_long(cmdbuf, 0);
1666 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1672 * Client tells us its preferred message format(s)
1674 void cmd_msgp(char *cmdbuf)
1676 safestrncpy(CC->preferred_formats, cmdbuf,
1677 sizeof(CC->preferred_formats));
1678 cprintf("%d ok\n", CIT_OK);
1683 * Open a component of a MIME message as a download file
1685 void cmd_opna(char *cmdbuf)
1688 char desired_section[128];
1690 msgid = extract_long(cmdbuf, 0);
1691 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1692 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1693 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1698 * Save a message pointer into a specified room
1699 * (Returns 0 for success, nonzero for failure)
1700 * roomname may be NULL to use the current room
1702 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1704 char hold_rm[ROOMNAMELEN];
1705 struct cdbdata *cdbfr;
1708 long highest_msg = 0L;
1709 struct CtdlMessage *msg = NULL;
1711 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1712 roomname, msgid, flags);
1714 strcpy(hold_rm, CC->room.QRname);
1716 /* We may need to check to see if this message is real */
1717 if ( (flags & SM_VERIFY_GOODNESS)
1718 || (flags & SM_DO_REPL_CHECK)
1720 msg = CtdlFetchMessage(msgid, 1);
1721 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1724 /* Perform replication checks if necessary */
1725 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1727 if (getroom(&CC->room,
1728 ((roomname != NULL) ? roomname : CC->room.QRname) )
1730 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1731 if (msg != NULL) CtdlFreeMessage(msg);
1732 return(ERROR + ROOM_NOT_FOUND);
1735 if (ReplicationChecks(msg) != 0) {
1736 getroom(&CC->room, hold_rm);
1737 if (msg != NULL) CtdlFreeMessage(msg);
1739 "Did replication, and newer exists\n");
1744 /* Now the regular stuff */
1745 if (lgetroom(&CC->room,
1746 ((roomname != NULL) ? roomname : CC->room.QRname) )
1748 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1749 if (msg != NULL) CtdlFreeMessage(msg);
1750 return(ERROR + ROOM_NOT_FOUND);
1753 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1754 if (cdbfr == NULL) {
1758 msglist = malloc(cdbfr->len);
1759 if (msglist == NULL)
1760 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1761 num_msgs = cdbfr->len / sizeof(long);
1762 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1767 /* Make sure the message doesn't already exist in this room. It
1768 * is absolutely taboo to have more than one reference to the same
1769 * message in a room.
1771 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1772 if (msglist[i] == msgid) {
1773 lputroom(&CC->room); /* unlock the room */
1774 getroom(&CC->room, hold_rm);
1775 if (msg != NULL) CtdlFreeMessage(msg);
1777 return(ERROR + ALREADY_EXISTS);
1781 /* Now add the new message */
1783 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1785 if (msglist == NULL) {
1786 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1788 msglist[num_msgs - 1] = msgid;
1790 /* Sort the message list, so all the msgid's are in order */
1791 num_msgs = sort_msglist(msglist, num_msgs);
1793 /* Determine the highest message number */
1794 highest_msg = msglist[num_msgs - 1];
1796 /* Write it back to disk. */
1797 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1798 msglist, (int)(num_msgs * sizeof(long)));
1800 /* Free up the memory we used. */
1803 /* Update the highest-message pointer and unlock the room. */
1804 CC->room.QRhighest = highest_msg;
1805 lputroom(&CC->room);
1806 getroom(&CC->room, hold_rm);
1808 /* Bump the reference count for this message. */
1809 if ((flags & SM_DONT_BUMP_REF)==0) {
1810 AdjRefCount(msgid, +1);
1813 /* Return success. */
1814 if (msg != NULL) CtdlFreeMessage(msg);
1821 * Message base operation to save a new message to the message store
1822 * (returns new message number)
1824 * This is the back end for CtdlSubmitMsg() and should not be directly
1825 * called by server-side modules.
1828 long send_message(struct CtdlMessage *msg) {
1836 /* Get a new message number */
1837 newmsgid = get_new_message_number();
1838 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1840 /* Generate an ID if we don't have one already */
1841 if (msg->cm_fields['I']==NULL) {
1842 msg->cm_fields['I'] = strdup(msgidbuf);
1845 /* If the message is big, set its body aside for storage elsewhere */
1846 if (msg->cm_fields['M'] != NULL) {
1847 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1849 holdM = msg->cm_fields['M'];
1850 msg->cm_fields['M'] = NULL;
1854 /* Serialize our data structure for storage in the database */
1855 serialize_message(&smr, msg);
1858 msg->cm_fields['M'] = holdM;
1862 cprintf("%d Unable to serialize message\n",
1863 ERROR + INTERNAL_ERROR);
1867 /* Write our little bundle of joy into the message base */
1868 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1869 smr.ser, smr.len) < 0) {
1870 lprintf(CTDL_ERR, "Can't store message\n");
1874 cdb_store(CDB_BIGMSGS,
1884 /* Free the memory we used for the serialized message */
1887 /* Return the *local* message ID to the caller
1888 * (even if we're storing an incoming network message)
1896 * Serialize a struct CtdlMessage into the format used on disk and network.
1898 * This function loads up a "struct ser_ret" (defined in server.h) which
1899 * contains the length of the serialized message and a pointer to the
1900 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1902 void serialize_message(struct ser_ret *ret, /* return values */
1903 struct CtdlMessage *msg) /* unserialized msg */
1907 static char *forder = FORDER;
1909 if (is_valid_message(msg) == 0) return; /* self check */
1912 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1913 ret->len = ret->len +
1914 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1916 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1917 ret->ser = malloc(ret->len);
1918 if (ret->ser == NULL) {
1924 ret->ser[1] = msg->cm_anon_type;
1925 ret->ser[2] = msg->cm_format_type;
1928 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1929 ret->ser[wlen++] = (char)forder[i];
1930 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1931 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1933 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1934 (long)ret->len, (long)wlen);
1942 * Back end for the ReplicationChecks() function
1944 void check_repl(long msgnum, void *userdata) {
1945 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1946 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1951 * Check to see if any messages already exist which carry the same Exclusive ID
1952 * as this one. If any are found, delete them.
1955 int ReplicationChecks(struct CtdlMessage *msg) {
1956 struct CtdlMessage *template;
1959 /* No exclusive id? Don't do anything. */
1960 if (msg->cm_fields['E'] == NULL) return 0;
1961 if (strlen(msg->cm_fields['E']) == 0) return 0;
1962 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1964 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1965 memset(template, 0, sizeof(struct CtdlMessage));
1966 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1968 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1970 CtdlFreeMessage(template);
1971 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1979 * Save a message to disk and submit it into the delivery system.
1981 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1982 struct recptypes *recps, /* recipients (if mail) */
1983 char *force /* force a particular room? */
1985 char submit_filename[128];
1986 char generated_timestamp[32];
1987 char hold_rm[ROOMNAMELEN];
1988 char actual_rm[ROOMNAMELEN];
1989 char force_room[ROOMNAMELEN];
1990 char content_type[SIZ]; /* We have to learn this */
1991 char recipient[SIZ];
1994 struct ctdluser userbuf;
1996 struct MetaData smi;
1997 FILE *network_fp = NULL;
1998 static int seqnum = 1;
1999 struct CtdlMessage *imsg = NULL;
2002 char *hold_R, *hold_D;
2004 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2005 if (is_valid_message(msg) == 0) return(-1); /* self check */
2007 /* If this message has no timestamp, we take the liberty of
2008 * giving it one, right now.
2010 if (msg->cm_fields['T'] == NULL) {
2011 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2012 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2013 msg->cm_fields['T'] = strdup(generated_timestamp);
2016 /* If this message has no path, we generate one.
2018 if (msg->cm_fields['P'] == NULL) {
2019 lprintf(CTDL_DEBUG, "Generating path\n");
2020 if (msg->cm_fields['A'] != NULL) {
2021 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2022 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2023 if (isspace(msg->cm_fields['P'][a])) {
2024 msg->cm_fields['P'][a] = ' ';
2029 msg->cm_fields['P'] = strdup("unknown");
2033 if (force == NULL) {
2034 strcpy(force_room, "");
2037 strcpy(force_room, force);
2040 /* Learn about what's inside, because it's what's inside that counts */
2041 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2042 if (msg->cm_fields['M'] == NULL) {
2043 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2047 switch (msg->cm_format_type) {
2049 strcpy(content_type, "text/x-citadel-variformat");
2052 strcpy(content_type, "text/plain");
2055 strcpy(content_type, "text/plain");
2056 mptr = bmstrstr(msg->cm_fields['M'],
2057 "Content-type: ", strncasecmp);
2059 safestrncpy(content_type, &mptr[14],
2060 sizeof content_type);
2061 for (a = 0; a < strlen(content_type); ++a) {
2062 if ((content_type[a] == ';')
2063 || (content_type[a] == ' ')
2064 || (content_type[a] == 13)
2065 || (content_type[a] == 10)) {
2066 content_type[a] = 0;
2072 /* Goto the correct room */
2073 lprintf(CTDL_DEBUG, "Selected room %s\n",
2074 (recps) ? CC->room.QRname : SENTITEMS);
2075 strcpy(hold_rm, CC->room.QRname);
2076 strcpy(actual_rm, CC->room.QRname);
2077 if (recps != NULL) {
2078 strcpy(actual_rm, SENTITEMS);
2081 /* If the user is a twit, move to the twit room for posting */
2082 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2083 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2085 if (CC->user.axlevel == 2) {
2086 strcpy(hold_rm, actual_rm);
2087 strcpy(actual_rm, config.c_twitroom);
2091 /* ...or if this message is destined for Aide> then go there. */
2092 if (strlen(force_room) > 0) {
2093 strcpy(actual_rm, force_room);
2096 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2097 if (strcasecmp(actual_rm, CC->room.QRname)) {
2098 /* getroom(&CC->room, actual_rm); */
2099 usergoto(actual_rm, 0, 1, NULL, NULL);
2103 * If this message has no O (room) field, generate one.
2105 if (msg->cm_fields['O'] == NULL) {
2106 msg->cm_fields['O'] = strdup(CC->room.QRname);
2109 /* Perform "before save" hooks (aborting if any return nonzero) */
2110 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2111 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2113 /* If this message has an Exclusive ID, perform replication checks */
2114 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2115 if (ReplicationChecks(msg) > 0) return(-4);
2117 /* Save it to disk */
2118 lprintf(CTDL_DEBUG, "Saving to disk\n");
2119 newmsgid = send_message(msg);
2120 if (newmsgid <= 0L) return(-5);
2122 /* Write a supplemental message info record. This doesn't have to
2123 * be a critical section because nobody else knows about this message
2126 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2127 memset(&smi, 0, sizeof(struct MetaData));
2128 smi.meta_msgnum = newmsgid;
2129 smi.meta_refcount = 0;
2130 safestrncpy(smi.meta_content_type, content_type,
2131 sizeof smi.meta_content_type);
2133 /* As part of the new metadata record, measure how
2134 * big this message will be when displayed as RFC822.
2135 * Both POP and IMAP use this, and it's best to just take the hit now
2136 * instead of having to potentially measure thousands of messages when
2137 * a mailbox is opened later.
2140 if (CC->redirect_buffer != NULL) {
2141 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2144 CC->redirect_buffer = malloc(SIZ);
2145 CC->redirect_len = 0;
2146 CC->redirect_alloc = SIZ;
2147 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2148 smi.meta_rfc822_length = CC->redirect_len;
2149 free(CC->redirect_buffer);
2150 CC->redirect_buffer = NULL;
2151 CC->redirect_len = 0;
2152 CC->redirect_alloc = 0;
2156 /* Now figure out where to store the pointers */
2157 lprintf(CTDL_DEBUG, "Storing pointers\n");
2159 /* If this is being done by the networker delivering a private
2160 * message, we want to BYPASS saving the sender's copy (because there
2161 * is no local sender; it would otherwise go to the Trashcan).
2163 if ((!CC->internal_pgm) || (recps == NULL)) {
2164 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2165 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2166 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2171 /* For internet mail, drop a copy in the outbound queue room */
2173 if (recps->num_internet > 0) {
2174 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2177 /* If other rooms are specified, drop them there too. */
2179 if (recps->num_room > 0)
2180 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2181 extract_token(recipient, recps->recp_room, i,
2182 '|', sizeof recipient);
2183 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2184 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2187 /* Bump this user's messages posted counter. */
2188 lprintf(CTDL_DEBUG, "Updating user\n");
2189 lgetuser(&CC->user, CC->curr_user);
2190 CC->user.posted = CC->user.posted + 1;
2191 lputuser(&CC->user);
2193 /* If this is private, local mail, make a copy in the
2194 * recipient's mailbox and bump the reference count.
2197 if (recps->num_local > 0)
2198 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2199 extract_token(recipient, recps->recp_local, i,
2200 '|', sizeof recipient);
2201 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2203 if (getuser(&userbuf, recipient) == 0) {
2204 MailboxName(actual_rm, sizeof actual_rm,
2205 &userbuf, MAILROOM);
2206 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2207 BumpNewMailCounter(userbuf.usernum);
2210 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2211 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2216 /* Perform "after save" hooks */
2217 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2218 PerformMessageHooks(msg, EVT_AFTERSAVE);
2220 /* For IGnet mail, we have to save a new copy into the spooler for
2221 * each recipient, with the R and D fields set to the recipient and
2222 * destination-node. This has two ugly side effects: all other
2223 * recipients end up being unlisted in this recipient's copy of the
2224 * message, and it has to deliver multiple messages to the same
2225 * node. We'll revisit this again in a year or so when everyone has
2226 * a network spool receiver that can handle the new style messages.
2229 if (recps->num_ignet > 0)
2230 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2231 extract_token(recipient, recps->recp_ignet, i,
2232 '|', sizeof recipient);
2234 hold_R = msg->cm_fields['R'];
2235 hold_D = msg->cm_fields['D'];
2236 msg->cm_fields['R'] = malloc(SIZ);
2237 msg->cm_fields['D'] = malloc(128);
2238 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2239 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2241 serialize_message(&smr, msg);
2243 snprintf(submit_filename, sizeof submit_filename,
2244 "./network/spoolin/netmail.%04lx.%04x.%04x",
2245 (long) getpid(), CC->cs_pid, ++seqnum);
2246 network_fp = fopen(submit_filename, "wb+");
2247 if (network_fp != NULL) {
2248 fwrite(smr.ser, smr.len, 1, network_fp);
2254 free(msg->cm_fields['R']);
2255 free(msg->cm_fields['D']);
2256 msg->cm_fields['R'] = hold_R;
2257 msg->cm_fields['D'] = hold_D;
2260 /* Go back to the room we started from */
2261 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2262 if (strcasecmp(hold_rm, CC->room.QRname))
2263 /* getroom(&CC->room, hold_rm); */
2264 usergoto(hold_rm, 0, 1, NULL, NULL);
2266 /* For internet mail, generate delivery instructions.
2267 * Yes, this is recursive. Deal with it. Infinite recursion does
2268 * not happen because the delivery instructions message does not
2269 * contain a recipient.
2272 if (recps->num_internet > 0) {
2273 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2274 instr = malloc(SIZ * 2);
2275 snprintf(instr, SIZ * 2,
2276 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2278 SPOOLMIME, newmsgid, (long)time(NULL),
2279 msg->cm_fields['A'], msg->cm_fields['N']
2282 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2283 size_t tmp = strlen(instr);
2284 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2285 snprintf(&instr[tmp], SIZ * 2 - tmp,
2286 "remote|%s|0||\n", recipient);
2289 imsg = malloc(sizeof(struct CtdlMessage));
2290 memset(imsg, 0, sizeof(struct CtdlMessage));
2291 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2292 imsg->cm_anon_type = MES_NORMAL;
2293 imsg->cm_format_type = FMT_RFC822;
2294 imsg->cm_fields['A'] = strdup("Citadel");
2295 imsg->cm_fields['M'] = instr;
2296 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2297 CtdlFreeMessage(imsg);
2306 * Convenience function for generating small administrative messages.
2308 void quickie_message(char *from, char *to, char *room, char *text,
2309 int format_type, char *subject)
2311 struct CtdlMessage *msg;
2312 struct recptypes *recp = NULL;
2314 msg = malloc(sizeof(struct CtdlMessage));
2315 memset(msg, 0, sizeof(struct CtdlMessage));
2316 msg->cm_magic = CTDLMESSAGE_MAGIC;
2317 msg->cm_anon_type = MES_NORMAL;
2318 msg->cm_format_type = format_type;
2319 msg->cm_fields['A'] = strdup(from);
2320 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2321 msg->cm_fields['N'] = strdup(NODENAME);
2323 msg->cm_fields['R'] = strdup(to);
2324 recp = validate_recipients(to);
2326 if (subject != NULL) {
2327 msg->cm_fields['U'] = strdup(subject);
2329 msg->cm_fields['M'] = strdup(text);
2331 CtdlSubmitMsg(msg, recp, room);
2332 CtdlFreeMessage(msg);
2333 if (recp != NULL) free(recp);
2339 * Back end function used by CtdlMakeMessage() and similar functions
2341 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2342 size_t maxlen, /* maximum message length */
2343 char *exist, /* if non-null, append to it;
2344 exist is ALWAYS freed */
2345 int crlf /* CRLF newlines instead of LF */
2349 size_t message_len = 0;
2350 size_t buffer_len = 0;
2356 if (exist == NULL) {
2363 message_len = strlen(exist);
2364 buffer_len = message_len + 4096;
2365 m = realloc(exist, buffer_len);
2372 /* flush the input if we have nowhere to store it */
2377 /* read in the lines of message text one by one */
2379 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2380 if (!strcmp(buf, terminator)) finished = 1;
2382 strcat(buf, "\r\n");
2388 if ( (!flushing) && (!finished) ) {
2389 /* Measure the line */
2390 linelen = strlen(buf);
2392 /* augment the buffer if we have to */
2393 if ((message_len + linelen) >= buffer_len) {
2394 ptr = realloc(m, (buffer_len * 2) );
2395 if (ptr == NULL) { /* flush if can't allocate */
2398 buffer_len = (buffer_len * 2);
2400 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2404 /* Add the new line to the buffer. NOTE: this loop must avoid
2405 * using functions like strcat() and strlen() because they
2406 * traverse the entire buffer upon every call, and doing that
2407 * for a multi-megabyte message slows it down beyond usability.
2409 strcpy(&m[message_len], buf);
2410 message_len += linelen;
2413 /* if we've hit the max msg length, flush the rest */
2414 if (message_len >= maxlen) flushing = 1;
2416 } while (!finished);
2424 * Build a binary message to be saved on disk.
2425 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2426 * will become part of the message. This means you are no longer
2427 * responsible for managing that memory -- it will be freed along with
2428 * the rest of the fields when CtdlFreeMessage() is called.)
2431 struct CtdlMessage *CtdlMakeMessage(
2432 struct ctdluser *author, /* author's user structure */
2433 char *recipient, /* NULL if it's not mail */
2434 char *room, /* room where it's going */
2435 int type, /* see MES_ types in header file */
2436 int format_type, /* variformat, plain text, MIME... */
2437 char *fake_name, /* who we're masquerading as */
2438 char *subject, /* Subject (optional) */
2439 char *preformatted_text /* ...or NULL to read text from client */
2441 char dest_node[SIZ];
2443 struct CtdlMessage *msg;
2445 msg = malloc(sizeof(struct CtdlMessage));
2446 memset(msg, 0, sizeof(struct CtdlMessage));
2447 msg->cm_magic = CTDLMESSAGE_MAGIC;
2448 msg->cm_anon_type = type;
2449 msg->cm_format_type = format_type;
2451 /* Don't confuse the poor folks if it's not routed mail. */
2452 strcpy(dest_node, "");
2456 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2457 msg->cm_fields['P'] = strdup(buf);
2459 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2460 msg->cm_fields['T'] = strdup(buf);
2462 if (fake_name[0]) /* author */
2463 msg->cm_fields['A'] = strdup(fake_name);
2465 msg->cm_fields['A'] = strdup(author->fullname);
2467 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2468 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2471 msg->cm_fields['O'] = strdup(CC->room.QRname);
2474 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2475 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2477 if (recipient[0] != 0) {
2478 msg->cm_fields['R'] = strdup(recipient);
2480 if (dest_node[0] != 0) {
2481 msg->cm_fields['D'] = strdup(dest_node);
2484 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2485 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2488 if (subject != NULL) {
2490 if (strlen(subject) > 0) {
2491 msg->cm_fields['U'] = strdup(subject);
2495 if (preformatted_text != NULL) {
2496 msg->cm_fields['M'] = preformatted_text;
2499 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2500 config.c_maxmsglen, NULL, 0);
2508 * Check to see whether we have permission to post a message in the current
2509 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2510 * returns 0 on success.
2512 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2514 if (!(CC->logged_in)) {
2515 snprintf(errmsgbuf, n, "Not logged in.");
2516 return (ERROR + NOT_LOGGED_IN);
2519 if ((CC->user.axlevel < 2)
2520 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2521 snprintf(errmsgbuf, n, "Need to be validated to enter "
2522 "(except in %s> to sysop)", MAILROOM);
2523 return (ERROR + HIGHER_ACCESS_REQUIRED);
2526 if ((CC->user.axlevel < 4)
2527 && (CC->room.QRflags & QR_NETWORK)) {
2528 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2529 return (ERROR + HIGHER_ACCESS_REQUIRED);
2532 if ((CC->user.axlevel < 6)
2533 && (CC->room.QRflags & QR_READONLY)) {
2534 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2535 return (ERROR + HIGHER_ACCESS_REQUIRED);
2538 strcpy(errmsgbuf, "Ok");
2544 * Check to see if the specified user has Internet mail permission
2545 * (returns nonzero if permission is granted)
2547 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2549 /* Do not allow twits to send Internet mail */
2550 if (who->axlevel <= 2) return(0);
2552 /* Globally enabled? */
2553 if (config.c_restrict == 0) return(1);
2555 /* User flagged ok? */
2556 if (who->flags & US_INTERNET) return(2);
2558 /* Aide level access? */
2559 if (who->axlevel >= 6) return(3);
2561 /* No mail for you! */
2568 * Validate recipients, count delivery types and errors, and handle aliasing
2569 * FIXME check for dupes!!!!!
2571 struct recptypes *validate_recipients(char *recipients) {
2572 struct recptypes *ret;
2573 char this_recp[SIZ];
2574 char this_recp_cooked[SIZ];
2580 struct ctdluser tempUS;
2581 struct ctdlroom tempQR;
2584 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2585 if (ret == NULL) return(NULL);
2586 memset(ret, 0, sizeof(struct recptypes));
2589 ret->num_internet = 0;
2594 if (recipients == NULL) {
2597 else if (strlen(recipients) == 0) {
2601 /* Change all valid separator characters to commas */
2602 for (i=0; i<strlen(recipients); ++i) {
2603 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2604 recipients[i] = ',';
2609 num_recps = num_tokens(recipients, ',');
2612 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2613 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2615 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2616 mailtype = alias(this_recp);
2617 mailtype = alias(this_recp);
2618 mailtype = alias(this_recp);
2619 for (j=0; j<=strlen(this_recp); ++j) {
2620 if (this_recp[j]=='_') {
2621 this_recp_cooked[j] = ' ';
2624 this_recp_cooked[j] = this_recp[j];
2630 if (!strcasecmp(this_recp, "sysop")) {
2632 strcpy(this_recp, config.c_aideroom);
2633 if (strlen(ret->recp_room) > 0) {
2634 strcat(ret->recp_room, "|");
2636 strcat(ret->recp_room, this_recp);
2638 else if (getuser(&tempUS, this_recp) == 0) {
2640 strcpy(this_recp, tempUS.fullname);
2641 if (strlen(ret->recp_local) > 0) {
2642 strcat(ret->recp_local, "|");
2644 strcat(ret->recp_local, this_recp);
2646 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2648 strcpy(this_recp, tempUS.fullname);
2649 if (strlen(ret->recp_local) > 0) {
2650 strcat(ret->recp_local, "|");
2652 strcat(ret->recp_local, this_recp);
2654 else if ( (!strncasecmp(this_recp, "room_", 5))
2655 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2657 if (strlen(ret->recp_room) > 0) {
2658 strcat(ret->recp_room, "|");
2660 strcat(ret->recp_room, &this_recp_cooked[5]);
2668 /* Yes, you're reading this correctly: if the target
2669 * domain points back to the local system or an attached
2670 * Citadel directory, the address is invalid. That's
2671 * because if the address were valid, we would have
2672 * already translated it to a local address by now.
2674 if (IsDirectory(this_recp)) {
2679 ++ret->num_internet;
2680 if (strlen(ret->recp_internet) > 0) {
2681 strcat(ret->recp_internet, "|");
2683 strcat(ret->recp_internet, this_recp);
2688 if (strlen(ret->recp_ignet) > 0) {
2689 strcat(ret->recp_ignet, "|");
2691 strcat(ret->recp_ignet, this_recp);
2699 if (strlen(ret->errormsg) == 0) {
2700 snprintf(append, sizeof append,
2701 "Invalid recipient: %s",
2705 snprintf(append, sizeof append,
2708 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2709 strcat(ret->errormsg, append);
2713 if (strlen(ret->display_recp) == 0) {
2714 strcpy(append, this_recp);
2717 snprintf(append, sizeof append, ", %s",
2720 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2721 strcat(ret->display_recp, append);
2726 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2727 ret->num_room + ret->num_error) == 0) {
2729 strcpy(ret->errormsg, "No recipients specified.");
2732 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2733 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2734 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2735 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2736 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2737 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2745 * message entry - mode 0 (normal)
2747 void cmd_ent0(char *entargs)
2751 char masquerade_as[SIZ];
2753 int format_type = 0;
2754 char newusername[SIZ];
2755 struct CtdlMessage *msg;
2759 struct recptypes *valid = NULL;
2766 post = extract_int(entargs, 0);
2767 extract_token(recp, entargs, 1, '|', sizeof recp);
2768 anon_flag = extract_int(entargs, 2);
2769 format_type = extract_int(entargs, 3);
2770 extract_token(subject, entargs, 4, '|', sizeof subject);
2771 do_confirm = extract_int(entargs, 6);
2773 /* first check to make sure the request is valid. */
2775 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2777 cprintf("%d %s\n", err, errmsg);
2781 /* Check some other permission type things. */
2784 if (CC->user.axlevel < 6) {
2785 cprintf("%d You don't have permission to masquerade.\n",
2786 ERROR + HIGHER_ACCESS_REQUIRED);
2789 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2790 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2791 safestrncpy(CC->fake_postname, newusername,
2792 sizeof(CC->fake_postname) );
2793 cprintf("%d ok\n", CIT_OK);
2796 CC->cs_flags |= CS_POSTING;
2798 /* In the Mail> room we have to behave a little differently --
2799 * make sure the user has specified at least one recipient. Then
2800 * validate the recipient(s).
2802 if ( (CC->room.QRflags & QR_MAILBOX)
2803 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2805 if (CC->user.axlevel < 2) {
2806 strcpy(recp, "sysop");
2809 valid = validate_recipients(recp);
2810 if (valid->num_error > 0) {
2812 ERROR + NO_SUCH_USER, valid->errormsg);
2816 if (valid->num_internet > 0) {
2817 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2818 cprintf("%d You do not have permission "
2819 "to send Internet mail.\n",
2820 ERROR + HIGHER_ACCESS_REQUIRED);
2826 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2827 && (CC->user.axlevel < 4) ) {
2828 cprintf("%d Higher access required for network mail.\n",
2829 ERROR + HIGHER_ACCESS_REQUIRED);
2834 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2835 && ((CC->user.flags & US_INTERNET) == 0)
2836 && (!CC->internal_pgm)) {
2837 cprintf("%d You don't have access to Internet mail.\n",
2838 ERROR + HIGHER_ACCESS_REQUIRED);
2845 /* Is this a room which has anonymous-only or anonymous-option? */
2846 anonymous = MES_NORMAL;
2847 if (CC->room.QRflags & QR_ANONONLY) {
2848 anonymous = MES_ANONONLY;
2850 if (CC->room.QRflags & QR_ANONOPT) {
2851 if (anon_flag == 1) { /* only if the user requested it */
2852 anonymous = MES_ANONOPT;
2856 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2860 /* If we're only checking the validity of the request, return
2861 * success without creating the message.
2864 cprintf("%d %s\n", CIT_OK,
2865 ((valid != NULL) ? valid->display_recp : "") );
2870 /* Handle author masquerading */
2871 if (CC->fake_postname[0]) {
2872 strcpy(masquerade_as, CC->fake_postname);
2874 else if (CC->fake_username[0]) {
2875 strcpy(masquerade_as, CC->fake_username);
2878 strcpy(masquerade_as, "");
2881 /* Read in the message from the client. */
2883 cprintf("%d send message\n", START_CHAT_MODE);
2885 cprintf("%d send message\n", SEND_LISTING);
2887 msg = CtdlMakeMessage(&CC->user, recp,
2888 CC->room.QRname, anonymous, format_type,
2889 masquerade_as, subject, NULL);
2892 msgnum = CtdlSubmitMsg(msg, valid, "");
2895 cprintf("%ld\n", msgnum);
2897 cprintf("Message accepted.\n");
2900 cprintf("Internal error.\n");
2902 if (msg->cm_fields['E'] != NULL) {
2903 cprintf("%s\n", msg->cm_fields['E']);
2910 CtdlFreeMessage(msg);
2912 CC->fake_postname[0] = '\0';
2920 * API function to delete messages which match a set of criteria
2921 * (returns the actual number of messages deleted)
2923 int CtdlDeleteMessages(char *room_name, /* which room */
2924 long dmsgnum, /* or "0" for any */
2925 char *content_type /* or "" for any */
2929 struct ctdlroom qrbuf;
2930 struct cdbdata *cdbfr;
2931 long *msglist = NULL;
2932 long *dellist = NULL;
2935 int num_deleted = 0;
2937 struct MetaData smi;
2939 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2940 room_name, dmsgnum, content_type);
2942 /* get room record, obtaining a lock... */
2943 if (lgetroom(&qrbuf, room_name) != 0) {
2944 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2946 return (0); /* room not found */
2948 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2950 if (cdbfr != NULL) {
2951 msglist = malloc(cdbfr->len);
2952 dellist = malloc(cdbfr->len);
2953 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2954 num_msgs = cdbfr->len / sizeof(long);
2958 for (i = 0; i < num_msgs; ++i) {
2961 /* Set/clear a bit for each criterion */
2963 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2964 delete_this |= 0x01;
2966 if (strlen(content_type) == 0) {
2967 delete_this |= 0x02;
2969 GetMetaData(&smi, msglist[i]);
2970 if (!strcasecmp(smi.meta_content_type,
2972 delete_this |= 0x02;
2976 /* Delete message only if all bits are set */
2977 if (delete_this == 0x03) {
2978 dellist[num_deleted++] = msglist[i];
2983 num_msgs = sort_msglist(msglist, num_msgs);
2984 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2985 msglist, (int)(num_msgs * sizeof(long)));
2987 qrbuf.QRhighest = msglist[num_msgs - 1];
2991 /* Go through the messages we pulled out of the index, and decrement
2992 * their reference counts by 1. If this is the only room the message
2993 * was in, the reference count will reach zero and the message will
2994 * automatically be deleted from the database. We do this in a
2995 * separate pass because there might be plug-in hooks getting called,
2996 * and we don't want that happening during an S_ROOMS critical
2999 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3000 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3001 AdjRefCount(dellist[i], -1);
3004 /* Now free the memory we used, and go away. */
3005 if (msglist != NULL) free(msglist);
3006 if (dellist != NULL) free(dellist);
3007 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3008 return (num_deleted);
3014 * Check whether the current user has permission to delete messages from
3015 * the current room (returns 1 for yes, 0 for no)
3017 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3018 getuser(&CC->user, CC->curr_user);
3019 if ((CC->user.axlevel < 6)
3020 && (CC->user.usernum != CC->room.QRroomaide)
3021 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3022 && (!(CC->internal_pgm))) {
3031 * Delete message from current room
3033 void cmd_dele(char *delstr)
3038 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3039 cprintf("%d Higher access required.\n",
3040 ERROR + HIGHER_ACCESS_REQUIRED);
3043 delnum = extract_long(delstr, 0);
3045 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3048 cprintf("%d %d message%s deleted.\n", CIT_OK,
3049 num_deleted, ((num_deleted != 1) ? "s" : ""));
3051 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3057 * Back end API function for moves and deletes
3059 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3062 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3063 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3064 if (err != 0) return(err);
3072 * move or copy a message to another room
3074 void cmd_move(char *args)
3077 char targ[ROOMNAMELEN];
3078 struct ctdlroom qtemp;
3084 num = extract_long(args, 0);
3085 extract_token(targ, args, 1, '|', sizeof targ);
3086 targ[ROOMNAMELEN - 1] = 0;
3087 is_copy = extract_int(args, 2);
3089 if (getroom(&qtemp, targ) != 0) {
3090 cprintf("%d '%s' does not exist.\n",
3091 ERROR + ROOM_NOT_FOUND, targ);
3095 getuser(&CC->user, CC->curr_user);
3096 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3098 /* Check for permission to perform this operation.
3099 * Remember: "CC->room" is source, "qtemp" is target.
3103 /* Aides can move/copy */
3104 if (CC->user.axlevel >= 6) permit = 1;
3106 /* Room aides can move/copy */
3107 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3109 /* Permit move/copy from personal rooms */
3110 if ((CC->room.QRflags & QR_MAILBOX)
3111 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3113 /* Permit only copy from public to personal room */
3115 && (!(CC->room.QRflags & QR_MAILBOX))
3116 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3118 /* User must have access to target room */
3119 if (!(ra & UA_KNOWN)) permit = 0;
3122 cprintf("%d Higher access required.\n",
3123 ERROR + HIGHER_ACCESS_REQUIRED);
3127 err = CtdlCopyMsgToRoom(num, targ);
3129 cprintf("%d Cannot store message in %s: error %d\n",
3134 /* Now delete the message from the source room,
3135 * if this is a 'move' rather than a 'copy' operation.
3138 CtdlDeleteMessages(CC->room.QRname, num, "");
3141 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3147 * GetMetaData() - Get the supplementary record for a message
3149 void GetMetaData(struct MetaData *smibuf, long msgnum)
3152 struct cdbdata *cdbsmi;
3155 memset(smibuf, 0, sizeof(struct MetaData));
3156 smibuf->meta_msgnum = msgnum;
3157 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3159 /* Use the negative of the message number for its supp record index */
3160 TheIndex = (0L - msgnum);
3162 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3163 if (cdbsmi == NULL) {
3164 return; /* record not found; go with defaults */
3166 memcpy(smibuf, cdbsmi->ptr,
3167 ((cdbsmi->len > sizeof(struct MetaData)) ?
3168 sizeof(struct MetaData) : cdbsmi->len));
3175 * PutMetaData() - (re)write supplementary record for a message
3177 void PutMetaData(struct MetaData *smibuf)
3181 /* Use the negative of the message number for the metadata db index */
3182 TheIndex = (0L - smibuf->meta_msgnum);
3184 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3185 smibuf->meta_msgnum, smibuf->meta_refcount);
3187 cdb_store(CDB_MSGMAIN,
3188 &TheIndex, (int)sizeof(long),
3189 smibuf, (int)sizeof(struct MetaData));
3194 * AdjRefCount - change the reference count for a message;
3195 * delete the message if it reaches zero
3197 void AdjRefCount(long msgnum, int incr)
3200 struct MetaData smi;
3203 /* This is a *tight* critical section; please keep it that way, as
3204 * it may get called while nested in other critical sections.
3205 * Complicating this any further will surely cause deadlock!
3207 begin_critical_section(S_SUPPMSGMAIN);
3208 GetMetaData(&smi, msgnum);
3209 smi.meta_refcount += incr;
3211 end_critical_section(S_SUPPMSGMAIN);
3212 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3213 msgnum, incr, smi.meta_refcount);
3215 /* If the reference count is now zero, delete the message
3216 * (and its supplementary record as well).
3217 * FIXME ... defer this so it doesn't keep the user waiting.
3219 if (smi.meta_refcount == 0) {
3220 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3222 /* Remove from fulltext index */
3223 if (config.c_enable_fulltext) {
3224 ft_index_message(msgnum, 0);
3227 /* Remove from message base */
3229 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3230 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3232 /* Remove metadata record */
3233 delnum = (0L - msgnum);
3234 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3239 * Write a generic object to this room
3241 * Note: this could be much more efficient. Right now we use two temporary
3242 * files, and still pull the message into memory as with all others.
3244 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3245 char *content_type, /* MIME type of this object */
3246 char *tempfilename, /* Where to fetch it from */
3247 struct ctdluser *is_mailbox, /* Mailbox room? */
3248 int is_binary, /* Is encoding necessary? */
3249 int is_unique, /* Del others of this type? */
3250 unsigned int flags /* Internal save flags */
3255 struct ctdlroom qrbuf;
3256 char roomname[ROOMNAMELEN];
3257 struct CtdlMessage *msg;
3259 char *raw_message = NULL;
3260 char *encoded_message = NULL;
3261 off_t raw_length = 0;
3263 if (is_mailbox != NULL)
3264 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3266 safestrncpy(roomname, req_room, sizeof(roomname));
3267 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3270 fp = fopen(tempfilename, "rb");
3272 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3273 tempfilename, strerror(errno));
3276 fseek(fp, 0L, SEEK_END);
3277 raw_length = ftell(fp);
3279 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3281 raw_message = malloc((size_t)raw_length + 2);
3282 fread(raw_message, (size_t)raw_length, 1, fp);
3286 encoded_message = malloc((size_t)
3287 (((raw_length * 134) / 100) + 4096 ) );
3290 encoded_message = malloc((size_t)(raw_length + 4096));
3293 sprintf(encoded_message, "Content-type: %s\n", content_type);
3296 sprintf(&encoded_message[strlen(encoded_message)],
3297 "Content-transfer-encoding: base64\n\n"
3301 sprintf(&encoded_message[strlen(encoded_message)],
3302 "Content-transfer-encoding: 7bit\n\n"
3308 &encoded_message[strlen(encoded_message)],
3314 raw_message[raw_length] = 0;
3316 &encoded_message[strlen(encoded_message)],
3324 lprintf(CTDL_DEBUG, "Allocating\n");
3325 msg = malloc(sizeof(struct CtdlMessage));
3326 memset(msg, 0, sizeof(struct CtdlMessage));
3327 msg->cm_magic = CTDLMESSAGE_MAGIC;
3328 msg->cm_anon_type = MES_NORMAL;
3329 msg->cm_format_type = 4;
3330 msg->cm_fields['A'] = strdup(CC->user.fullname);
3331 msg->cm_fields['O'] = strdup(req_room);
3332 msg->cm_fields['N'] = strdup(config.c_nodename);
3333 msg->cm_fields['H'] = strdup(config.c_humannode);
3334 msg->cm_flags = flags;
3336 msg->cm_fields['M'] = encoded_message;
3338 /* Create the requested room if we have to. */
3339 if (getroom(&qrbuf, roomname) != 0) {
3340 create_room(roomname,
3341 ( (is_mailbox != NULL) ? 5 : 3 ),
3342 "", 0, 1, 0, VIEW_BBS);
3344 /* If the caller specified this object as unique, delete all
3345 * other objects of this type that are currently in the room.
3348 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3349 CtdlDeleteMessages(roomname, 0L, content_type));
3351 /* Now write the data */
3352 CtdlSubmitMsg(msg, NULL, roomname);
3353 CtdlFreeMessage(msg);
3361 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3362 config_msgnum = msgnum;
3366 char *CtdlGetSysConfig(char *sysconfname) {
3367 char hold_rm[ROOMNAMELEN];
3370 struct CtdlMessage *msg;
3373 strcpy(hold_rm, CC->room.QRname);
3374 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3375 getroom(&CC->room, hold_rm);
3380 /* We want the last (and probably only) config in this room */
3381 begin_critical_section(S_CONFIG);
3382 config_msgnum = (-1L);
3383 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3384 CtdlGetSysConfigBackend, NULL);
3385 msgnum = config_msgnum;
3386 end_critical_section(S_CONFIG);
3392 msg = CtdlFetchMessage(msgnum, 1);
3394 conf = strdup(msg->cm_fields['M']);
3395 CtdlFreeMessage(msg);
3402 getroom(&CC->room, hold_rm);
3404 if (conf != NULL) do {
3405 extract_token(buf, conf, 0, '\n', sizeof buf);
3406 strcpy(conf, &conf[strlen(buf)+1]);
3407 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3412 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3413 char temp[PATH_MAX];
3416 strcpy(temp, tmpnam(NULL));
3418 fp = fopen(temp, "w");
3419 if (fp == NULL) return;
3420 fprintf(fp, "%s", sysconfdata);
3423 /* this handy API function does all the work for us */
3424 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3430 * Determine whether a given Internet address belongs to the current user
3432 int CtdlIsMe(char *addr, int addr_buf_len)
3434 struct recptypes *recp;
3437 recp = validate_recipients(addr);
3438 if (recp == NULL) return(0);
3440 if (recp->num_local == 0) {
3445 for (i=0; i<recp->num_local; ++i) {
3446 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3447 if (!strcasecmp(addr, CC->user.fullname)) {
3459 * Citadel protocol command to do the same
3461 void cmd_isme(char *argbuf) {
3464 if (CtdlAccessCheck(ac_logged_in)) return;
3465 extract_token(addr, argbuf, 0, '|', sizeof addr);
3467 if (CtdlIsMe(addr, sizeof addr)) {
3468 cprintf("%d %s\n", CIT_OK, addr);
3471 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);