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", cbtype);
1124 if (strlen(cbcharset) > 0) {
1125 cprintf("; charset=%s", cbcharset);
1127 cprintf("\nContent-length: %d\n",
1128 (int)(length + add_newline) );
1129 if (strlen(encoding) > 0) {
1130 cprintf("Content-transfer-encoding: %s\n", encoding);
1133 cprintf("Content-transfer-encoding: 7bit\n");
1136 client_write(content, length);
1137 if (add_newline) cprintf("\n");
1142 /* No translations required or possible: output as text/plain */
1143 cprintf("Content-type: text/plain\n\n");
1144 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1145 length, encoding, cbuserdata);
1150 * Get a message off disk. (returns om_* values found in msgbase.h)
1153 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1154 int mode, /* how would you like that message? */
1155 int headers_only, /* eschew the message body? */
1156 int do_proto, /* do Citadel protocol responses? */
1157 int crlf /* Use CRLF newlines instead of LF? */
1159 struct CtdlMessage *TheMessage = NULL;
1160 int retcode = om_no_such_msg;
1162 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1165 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1166 if (do_proto) cprintf("%d Not logged in.\n",
1167 ERROR + NOT_LOGGED_IN);
1168 return(om_not_logged_in);
1171 /* FIXME: check message id against msglist for this room */
1174 * Fetch the message from disk. If we're in any sort of headers
1175 * only mode, request that we don't even bother loading the body
1178 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1179 TheMessage = CtdlFetchMessage(msg_num, 0);
1182 TheMessage = CtdlFetchMessage(msg_num, 1);
1185 if (TheMessage == NULL) {
1186 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1187 ERROR + MESSAGE_NOT_FOUND, msg_num);
1188 return(om_no_such_msg);
1191 retcode = CtdlOutputPreLoadedMsg(
1192 TheMessage, msg_num, mode,
1193 headers_only, do_proto, crlf);
1195 CtdlFreeMessage(TheMessage);
1202 * Get a message off disk. (returns om_* values found in msgbase.h)
1205 int CtdlOutputPreLoadedMsg(
1206 struct CtdlMessage *TheMessage,
1208 int mode, /* how would you like that message? */
1209 int headers_only, /* eschew the message body? */
1210 int do_proto, /* do Citadel protocol responses? */
1211 int crlf /* Use CRLF newlines instead of LF? */
1217 char display_name[256];
1219 char *nl; /* newline string */
1221 int subject_found = 0;
1224 /* Buffers needed for RFC822 translation. These are all filled
1225 * using functions that are bounds-checked, and therefore we can
1226 * make them substantially smaller than SIZ.
1234 char datestamp[100];
1236 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1237 ((TheMessage == NULL) ? "NULL" : "not null"),
1239 mode, headers_only, do_proto, crlf);
1241 snprintf(mid, sizeof mid, "%ld", msg_num);
1242 nl = (crlf ? "\r\n" : "\n");
1244 if (!is_valid_message(TheMessage)) {
1246 "ERROR: invalid preloaded message for output\n");
1247 return(om_no_such_msg);
1250 /* Are we downloading a MIME component? */
1251 if (mode == MT_DOWNLOAD) {
1252 if (TheMessage->cm_format_type != FMT_RFC822) {
1254 cprintf("%d This is not a MIME message.\n",
1255 ERROR + ILLEGAL_VALUE);
1256 } else if (CC->download_fp != NULL) {
1257 if (do_proto) cprintf(
1258 "%d You already have a download open.\n",
1259 ERROR + RESOURCE_BUSY);
1261 /* Parse the message text component */
1262 mptr = TheMessage->cm_fields['M'];
1263 ma = malloc(sizeof(struct ma_info));
1264 memset(ma, 0, sizeof(struct ma_info));
1265 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1267 /* If there's no file open by this time, the requested
1268 * section wasn't found, so print an error
1270 if (CC->download_fp == NULL) {
1271 if (do_proto) cprintf(
1272 "%d Section %s not found.\n",
1273 ERROR + FILE_NOT_FOUND,
1274 CC->download_desired_section);
1277 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1280 /* now for the user-mode message reading loops */
1281 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1283 /* Does the caller want to skip the headers? */
1284 if (headers_only == HEADERS_NONE) goto START_TEXT;
1286 /* Tell the client which format type we're using. */
1287 if ( (mode == MT_CITADEL) && (do_proto) ) {
1288 cprintf("type=%d\n", TheMessage->cm_format_type);
1291 /* nhdr=yes means that we're only displaying headers, no body */
1292 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1293 && (mode == MT_CITADEL)
1296 cprintf("nhdr=yes\n");
1299 /* begin header processing loop for Citadel message format */
1301 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1303 safestrncpy(display_name, "<unknown>", sizeof display_name);
1304 if (TheMessage->cm_fields['A']) {
1305 strcpy(buf, TheMessage->cm_fields['A']);
1306 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1307 safestrncpy(display_name, "****", sizeof display_name);
1309 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1310 safestrncpy(display_name, "anonymous", sizeof display_name);
1313 safestrncpy(display_name, buf, sizeof display_name);
1315 if ((is_room_aide())
1316 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1317 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1318 size_t tmp = strlen(display_name);
1319 snprintf(&display_name[tmp],
1320 sizeof display_name - tmp,
1325 /* Don't show Internet address for users on the
1326 * local Citadel network.
1329 if (TheMessage->cm_fields['N'] != NULL)
1330 if (strlen(TheMessage->cm_fields['N']) > 0)
1331 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1335 /* Now spew the header fields in the order we like them. */
1336 safestrncpy(allkeys, FORDER, sizeof allkeys);
1337 for (i=0; i<strlen(allkeys); ++i) {
1338 k = (int) allkeys[i];
1340 if ( (TheMessage->cm_fields[k] != NULL)
1341 && (msgkeys[k] != NULL) ) {
1343 if (do_proto) cprintf("%s=%s\n",
1347 else if ((k == 'F') && (suppress_f)) {
1350 /* Masquerade display name if needed */
1352 if (do_proto) cprintf("%s=%s\n",
1354 TheMessage->cm_fields[k]
1363 /* begin header processing loop for RFC822 transfer format */
1368 strcpy(snode, NODENAME);
1369 strcpy(lnode, HUMANNODE);
1370 if (mode == MT_RFC822) {
1371 for (i = 0; i < 256; ++i) {
1372 if (TheMessage->cm_fields[i]) {
1373 mptr = TheMessage->cm_fields[i];
1376 safestrncpy(luser, mptr, sizeof luser);
1377 safestrncpy(suser, mptr, sizeof suser);
1379 else if (i == 'U') {
1380 cprintf("Subject: %s%s", mptr, nl);
1384 safestrncpy(mid, mptr, sizeof mid);
1386 safestrncpy(lnode, mptr, sizeof lnode);
1388 safestrncpy(fuser, mptr, sizeof fuser);
1389 /* else if (i == 'O')
1390 cprintf("X-Citadel-Room: %s%s",
1393 safestrncpy(snode, mptr, sizeof snode);
1395 cprintf("To: %s%s", mptr, nl);
1396 else if (i == 'T') {
1397 datestring(datestamp, sizeof datestamp,
1398 atol(mptr), DATESTRING_RFC822);
1399 cprintf("Date: %s%s", datestamp, nl);
1403 if (subject_found == 0) {
1404 cprintf("Subject: (no subject)%s", nl);
1408 for (i=0; i<strlen(suser); ++i) {
1409 suser[i] = tolower(suser[i]);
1410 if (!isalnum(suser[i])) suser[i]='_';
1413 if (mode == MT_RFC822) {
1414 if (!strcasecmp(snode, NODENAME)) {
1415 safestrncpy(snode, FQDN, sizeof snode);
1418 /* Construct a fun message id */
1419 cprintf("Message-ID: <%s", mid);
1420 if (strchr(mid, '@')==NULL) {
1421 cprintf("@%s", snode);
1425 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1426 // cprintf("From: x@x.org (----)%s", nl);
1427 cprintf("From: \"----\" <x@x.org>%s", nl);
1429 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1430 // cprintf("From: x@x.org (anonymous)%s", nl);
1431 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1433 else if (strlen(fuser) > 0) {
1434 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1435 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1438 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1439 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1442 cprintf("Organization: %s%s", lnode, nl);
1444 /* Blank line signifying RFC822 end-of-headers */
1445 if (TheMessage->cm_format_type != FMT_RFC822) {
1450 /* end header processing loop ... at this point, we're in the text */
1452 if (headers_only == HEADERS_FAST) goto DONE;
1453 mptr = TheMessage->cm_fields['M'];
1455 /* Tell the client about the MIME parts in this message */
1456 if (TheMessage->cm_format_type == FMT_RFC822) {
1457 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1458 mime_parser(mptr, NULL,
1459 (do_proto ? *list_this_part : NULL),
1460 (do_proto ? *list_this_pref : NULL),
1461 (do_proto ? *list_this_suff : NULL),
1464 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1465 /* FIXME ... we have to put some code in here to avoid
1466 * printing duplicate header information when both
1467 * Citadel and RFC822 headers exist. Preference should
1468 * probably be given to the RFC822 headers.
1470 int done_rfc822_hdrs = 0;
1471 while (ch=*(mptr++), ch!=0) {
1476 if (!done_rfc822_hdrs) {
1477 if (headers_only != HEADERS_NONE) {
1482 if (headers_only != HEADERS_ONLY) {
1486 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1487 done_rfc822_hdrs = 1;
1491 if (done_rfc822_hdrs) {
1492 if (headers_only != HEADERS_NONE) {
1497 if (headers_only != HEADERS_ONLY) {
1501 if ((*mptr == 13) || (*mptr == 10)) {
1502 done_rfc822_hdrs = 1;
1510 if (headers_only == HEADERS_ONLY) {
1514 /* signify start of msg text */
1515 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1516 if (do_proto) cprintf("text\n");
1519 /* If the format type on disk is 1 (fixed-format), then we want
1520 * everything to be output completely literally ... regardless of
1521 * what message transfer format is in use.
1523 if (TheMessage->cm_format_type == FMT_FIXED) {
1524 if (mode == MT_MIME) {
1525 cprintf("Content-type: text/plain\n\n");
1528 while (ch = *mptr++, ch > 0) {
1531 if ((ch == 10) || (strlen(buf) > 250)) {
1532 cprintf("%s%s", buf, nl);
1535 buf[strlen(buf) + 1] = 0;
1536 buf[strlen(buf)] = ch;
1539 if (strlen(buf) > 0)
1540 cprintf("%s%s", buf, nl);
1543 /* If the message on disk is format 0 (Citadel vari-format), we
1544 * output using the formatter at 80 columns. This is the final output
1545 * form if the transfer format is RFC822, but if the transfer format
1546 * is Citadel proprietary, it'll still work, because the indentation
1547 * for new paragraphs is correct and the client will reformat the
1548 * message to the reader's screen width.
1550 if (TheMessage->cm_format_type == FMT_CITADEL) {
1551 if (mode == MT_MIME) {
1552 cprintf("Content-type: text/x-citadel-variformat\n\n");
1554 memfmout(80, mptr, 0, nl);
1557 /* If the message on disk is format 4 (MIME), we've gotta hand it
1558 * off to the MIME parser. The client has already been told that
1559 * this message is format 1 (fixed format), so the callback function
1560 * we use will display those parts as-is.
1562 if (TheMessage->cm_format_type == FMT_RFC822) {
1563 ma = malloc(sizeof(struct ma_info));
1564 memset(ma, 0, sizeof(struct ma_info));
1566 if (mode == MT_MIME) {
1567 strcpy(ma->chosen_part, "1");
1568 mime_parser(mptr, NULL,
1569 *choose_preferred, *fixed_output_pre,
1570 *fixed_output_post, (void *)ma, 0);
1571 mime_parser(mptr, NULL,
1572 *output_preferred, NULL, NULL, (void *)ma, 0);
1575 mime_parser(mptr, NULL,
1576 *fixed_output, *fixed_output_pre,
1577 *fixed_output_post, (void *)ma, 0);
1583 DONE: /* now we're done */
1584 if (do_proto) cprintf("000\n");
1591 * display a message (mode 0 - Citadel proprietary)
1593 void cmd_msg0(char *cmdbuf)
1596 int headers_only = HEADERS_ALL;
1598 msgid = extract_long(cmdbuf, 0);
1599 headers_only = extract_int(cmdbuf, 1);
1601 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1607 * display a message (mode 2 - RFC822)
1609 void cmd_msg2(char *cmdbuf)
1612 int headers_only = HEADERS_ALL;
1614 msgid = extract_long(cmdbuf, 0);
1615 headers_only = extract_int(cmdbuf, 1);
1617 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1623 * display a message (mode 3 - IGnet raw format - internal programs only)
1625 void cmd_msg3(char *cmdbuf)
1628 struct CtdlMessage *msg;
1631 if (CC->internal_pgm == 0) {
1632 cprintf("%d This command is for internal programs only.\n",
1633 ERROR + HIGHER_ACCESS_REQUIRED);
1637 msgnum = extract_long(cmdbuf, 0);
1638 msg = CtdlFetchMessage(msgnum, 1);
1640 cprintf("%d Message %ld not found.\n",
1641 ERROR + MESSAGE_NOT_FOUND, msgnum);
1645 serialize_message(&smr, msg);
1646 CtdlFreeMessage(msg);
1649 cprintf("%d Unable to serialize message\n",
1650 ERROR + INTERNAL_ERROR);
1654 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1655 client_write((char *)smr.ser, (int)smr.len);
1662 * Display a message using MIME content types
1664 void cmd_msg4(char *cmdbuf)
1668 msgid = extract_long(cmdbuf, 0);
1669 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1675 * Client tells us its preferred message format(s)
1677 void cmd_msgp(char *cmdbuf)
1679 safestrncpy(CC->preferred_formats, cmdbuf,
1680 sizeof(CC->preferred_formats));
1681 cprintf("%d ok\n", CIT_OK);
1686 * Open a component of a MIME message as a download file
1688 void cmd_opna(char *cmdbuf)
1691 char desired_section[128];
1693 msgid = extract_long(cmdbuf, 0);
1694 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1695 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1696 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1701 * Save a message pointer into a specified room
1702 * (Returns 0 for success, nonzero for failure)
1703 * roomname may be NULL to use the current room
1705 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1707 char hold_rm[ROOMNAMELEN];
1708 struct cdbdata *cdbfr;
1711 long highest_msg = 0L;
1712 struct CtdlMessage *msg = NULL;
1714 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1715 roomname, msgid, flags);
1717 strcpy(hold_rm, CC->room.QRname);
1719 /* We may need to check to see if this message is real */
1720 if ( (flags & SM_VERIFY_GOODNESS)
1721 || (flags & SM_DO_REPL_CHECK)
1723 msg = CtdlFetchMessage(msgid, 1);
1724 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1727 /* Perform replication checks if necessary */
1728 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1730 if (getroom(&CC->room,
1731 ((roomname != NULL) ? roomname : CC->room.QRname) )
1733 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1734 if (msg != NULL) CtdlFreeMessage(msg);
1735 return(ERROR + ROOM_NOT_FOUND);
1738 if (ReplicationChecks(msg) != 0) {
1739 getroom(&CC->room, hold_rm);
1740 if (msg != NULL) CtdlFreeMessage(msg);
1742 "Did replication, and newer exists\n");
1747 /* Now the regular stuff */
1748 if (lgetroom(&CC->room,
1749 ((roomname != NULL) ? roomname : CC->room.QRname) )
1751 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1752 if (msg != NULL) CtdlFreeMessage(msg);
1753 return(ERROR + ROOM_NOT_FOUND);
1756 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1757 if (cdbfr == NULL) {
1761 msglist = malloc(cdbfr->len);
1762 if (msglist == NULL)
1763 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1764 num_msgs = cdbfr->len / sizeof(long);
1765 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1770 /* Make sure the message doesn't already exist in this room. It
1771 * is absolutely taboo to have more than one reference to the same
1772 * message in a room.
1774 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1775 if (msglist[i] == msgid) {
1776 lputroom(&CC->room); /* unlock the room */
1777 getroom(&CC->room, hold_rm);
1778 if (msg != NULL) CtdlFreeMessage(msg);
1780 return(ERROR + ALREADY_EXISTS);
1784 /* Now add the new message */
1786 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1788 if (msglist == NULL) {
1789 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1791 msglist[num_msgs - 1] = msgid;
1793 /* Sort the message list, so all the msgid's are in order */
1794 num_msgs = sort_msglist(msglist, num_msgs);
1796 /* Determine the highest message number */
1797 highest_msg = msglist[num_msgs - 1];
1799 /* Write it back to disk. */
1800 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1801 msglist, (int)(num_msgs * sizeof(long)));
1803 /* Free up the memory we used. */
1806 /* Update the highest-message pointer and unlock the room. */
1807 CC->room.QRhighest = highest_msg;
1808 lputroom(&CC->room);
1809 getroom(&CC->room, hold_rm);
1811 /* Bump the reference count for this message. */
1812 if ((flags & SM_DONT_BUMP_REF)==0) {
1813 AdjRefCount(msgid, +1);
1816 /* Return success. */
1817 if (msg != NULL) CtdlFreeMessage(msg);
1824 * Message base operation to save a new message to the message store
1825 * (returns new message number)
1827 * This is the back end for CtdlSubmitMsg() and should not be directly
1828 * called by server-side modules.
1831 long send_message(struct CtdlMessage *msg) {
1839 /* Get a new message number */
1840 newmsgid = get_new_message_number();
1841 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1843 /* Generate an ID if we don't have one already */
1844 if (msg->cm_fields['I']==NULL) {
1845 msg->cm_fields['I'] = strdup(msgidbuf);
1848 /* If the message is big, set its body aside for storage elsewhere */
1849 if (msg->cm_fields['M'] != NULL) {
1850 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1852 holdM = msg->cm_fields['M'];
1853 msg->cm_fields['M'] = NULL;
1857 /* Serialize our data structure for storage in the database */
1858 serialize_message(&smr, msg);
1861 msg->cm_fields['M'] = holdM;
1865 cprintf("%d Unable to serialize message\n",
1866 ERROR + INTERNAL_ERROR);
1870 /* Write our little bundle of joy into the message base */
1871 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1872 smr.ser, smr.len) < 0) {
1873 lprintf(CTDL_ERR, "Can't store message\n");
1877 cdb_store(CDB_BIGMSGS,
1887 /* Free the memory we used for the serialized message */
1890 /* Return the *local* message ID to the caller
1891 * (even if we're storing an incoming network message)
1899 * Serialize a struct CtdlMessage into the format used on disk and network.
1901 * This function loads up a "struct ser_ret" (defined in server.h) which
1902 * contains the length of the serialized message and a pointer to the
1903 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1905 void serialize_message(struct ser_ret *ret, /* return values */
1906 struct CtdlMessage *msg) /* unserialized msg */
1910 static char *forder = FORDER;
1912 if (is_valid_message(msg) == 0) return; /* self check */
1915 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1916 ret->len = ret->len +
1917 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1919 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1920 ret->ser = malloc(ret->len);
1921 if (ret->ser == NULL) {
1927 ret->ser[1] = msg->cm_anon_type;
1928 ret->ser[2] = msg->cm_format_type;
1931 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1932 ret->ser[wlen++] = (char)forder[i];
1933 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1934 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1936 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1937 (long)ret->len, (long)wlen);
1945 * Back end for the ReplicationChecks() function
1947 void check_repl(long msgnum, void *userdata) {
1948 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1949 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1954 * Check to see if any messages already exist which carry the same Exclusive ID
1955 * as this one. If any are found, delete them.
1958 int ReplicationChecks(struct CtdlMessage *msg) {
1959 struct CtdlMessage *template;
1962 /* No exclusive id? Don't do anything. */
1963 if (msg->cm_fields['E'] == NULL) return 0;
1964 if (strlen(msg->cm_fields['E']) == 0) return 0;
1965 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1967 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1968 memset(template, 0, sizeof(struct CtdlMessage));
1969 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1971 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1973 CtdlFreeMessage(template);
1974 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1982 * Save a message to disk and submit it into the delivery system.
1984 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1985 struct recptypes *recps, /* recipients (if mail) */
1986 char *force /* force a particular room? */
1988 char submit_filename[128];
1989 char generated_timestamp[32];
1990 char hold_rm[ROOMNAMELEN];
1991 char actual_rm[ROOMNAMELEN];
1992 char force_room[ROOMNAMELEN];
1993 char content_type[SIZ]; /* We have to learn this */
1994 char recipient[SIZ];
1997 struct ctdluser userbuf;
1999 struct MetaData smi;
2000 FILE *network_fp = NULL;
2001 static int seqnum = 1;
2002 struct CtdlMessage *imsg = NULL;
2005 char *hold_R, *hold_D;
2007 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2008 if (is_valid_message(msg) == 0) return(-1); /* self check */
2010 /* If this message has no timestamp, we take the liberty of
2011 * giving it one, right now.
2013 if (msg->cm_fields['T'] == NULL) {
2014 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2015 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2016 msg->cm_fields['T'] = strdup(generated_timestamp);
2019 /* If this message has no path, we generate one.
2021 if (msg->cm_fields['P'] == NULL) {
2022 lprintf(CTDL_DEBUG, "Generating path\n");
2023 if (msg->cm_fields['A'] != NULL) {
2024 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2025 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2026 if (isspace(msg->cm_fields['P'][a])) {
2027 msg->cm_fields['P'][a] = ' ';
2032 msg->cm_fields['P'] = strdup("unknown");
2036 if (force == NULL) {
2037 strcpy(force_room, "");
2040 strcpy(force_room, force);
2043 /* Learn about what's inside, because it's what's inside that counts */
2044 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2045 if (msg->cm_fields['M'] == NULL) {
2046 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2050 switch (msg->cm_format_type) {
2052 strcpy(content_type, "text/x-citadel-variformat");
2055 strcpy(content_type, "text/plain");
2058 strcpy(content_type, "text/plain");
2059 mptr = bmstrstr(msg->cm_fields['M'],
2060 "Content-type: ", strncasecmp);
2062 safestrncpy(content_type, &mptr[14],
2063 sizeof content_type);
2064 for (a = 0; a < strlen(content_type); ++a) {
2065 if ((content_type[a] == ';')
2066 || (content_type[a] == ' ')
2067 || (content_type[a] == 13)
2068 || (content_type[a] == 10)) {
2069 content_type[a] = 0;
2075 /* Goto the correct room */
2076 lprintf(CTDL_DEBUG, "Selected room %s\n",
2077 (recps) ? CC->room.QRname : SENTITEMS);
2078 strcpy(hold_rm, CC->room.QRname);
2079 strcpy(actual_rm, CC->room.QRname);
2080 if (recps != NULL) {
2081 strcpy(actual_rm, SENTITEMS);
2084 /* If the user is a twit, move to the twit room for posting */
2085 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2086 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2088 if (CC->user.axlevel == 2) {
2089 strcpy(hold_rm, actual_rm);
2090 strcpy(actual_rm, config.c_twitroom);
2094 /* ...or if this message is destined for Aide> then go there. */
2095 if (strlen(force_room) > 0) {
2096 strcpy(actual_rm, force_room);
2099 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2100 if (strcasecmp(actual_rm, CC->room.QRname)) {
2101 /* getroom(&CC->room, actual_rm); */
2102 usergoto(actual_rm, 0, 1, NULL, NULL);
2106 * If this message has no O (room) field, generate one.
2108 if (msg->cm_fields['O'] == NULL) {
2109 msg->cm_fields['O'] = strdup(CC->room.QRname);
2112 /* Perform "before save" hooks (aborting if any return nonzero) */
2113 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2114 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2116 /* If this message has an Exclusive ID, perform replication checks */
2117 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2118 if (ReplicationChecks(msg) > 0) return(-4);
2120 /* Save it to disk */
2121 lprintf(CTDL_DEBUG, "Saving to disk\n");
2122 newmsgid = send_message(msg);
2123 if (newmsgid <= 0L) return(-5);
2125 /* Write a supplemental message info record. This doesn't have to
2126 * be a critical section because nobody else knows about this message
2129 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2130 memset(&smi, 0, sizeof(struct MetaData));
2131 smi.meta_msgnum = newmsgid;
2132 smi.meta_refcount = 0;
2133 safestrncpy(smi.meta_content_type, content_type,
2134 sizeof smi.meta_content_type);
2136 /* As part of the new metadata record, measure how
2137 * big this message will be when displayed as RFC822.
2138 * Both POP and IMAP use this, and it's best to just take the hit now
2139 * instead of having to potentially measure thousands of messages when
2140 * a mailbox is opened later.
2143 if (CC->redirect_buffer != NULL) {
2144 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2147 CC->redirect_buffer = malloc(SIZ);
2148 CC->redirect_len = 0;
2149 CC->redirect_alloc = SIZ;
2150 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2151 smi.meta_rfc822_length = CC->redirect_len;
2152 free(CC->redirect_buffer);
2153 CC->redirect_buffer = NULL;
2154 CC->redirect_len = 0;
2155 CC->redirect_alloc = 0;
2159 /* Now figure out where to store the pointers */
2160 lprintf(CTDL_DEBUG, "Storing pointers\n");
2162 /* If this is being done by the networker delivering a private
2163 * message, we want to BYPASS saving the sender's copy (because there
2164 * is no local sender; it would otherwise go to the Trashcan).
2166 if ((!CC->internal_pgm) || (recps == NULL)) {
2167 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2168 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2169 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2174 /* For internet mail, drop a copy in the outbound queue room */
2176 if (recps->num_internet > 0) {
2177 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2180 /* If other rooms are specified, drop them there too. */
2182 if (recps->num_room > 0)
2183 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2184 extract_token(recipient, recps->recp_room, i,
2185 '|', sizeof recipient);
2186 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2187 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2190 /* Bump this user's messages posted counter. */
2191 lprintf(CTDL_DEBUG, "Updating user\n");
2192 lgetuser(&CC->user, CC->curr_user);
2193 CC->user.posted = CC->user.posted + 1;
2194 lputuser(&CC->user);
2196 /* If this is private, local mail, make a copy in the
2197 * recipient's mailbox and bump the reference count.
2200 if (recps->num_local > 0)
2201 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2202 extract_token(recipient, recps->recp_local, i,
2203 '|', sizeof recipient);
2204 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2206 if (getuser(&userbuf, recipient) == 0) {
2207 MailboxName(actual_rm, sizeof actual_rm,
2208 &userbuf, MAILROOM);
2209 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2210 BumpNewMailCounter(userbuf.usernum);
2213 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2214 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2219 /* Perform "after save" hooks */
2220 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2221 PerformMessageHooks(msg, EVT_AFTERSAVE);
2223 /* For IGnet mail, we have to save a new copy into the spooler for
2224 * each recipient, with the R and D fields set to the recipient and
2225 * destination-node. This has two ugly side effects: all other
2226 * recipients end up being unlisted in this recipient's copy of the
2227 * message, and it has to deliver multiple messages to the same
2228 * node. We'll revisit this again in a year or so when everyone has
2229 * a network spool receiver that can handle the new style messages.
2232 if (recps->num_ignet > 0)
2233 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2234 extract_token(recipient, recps->recp_ignet, i,
2235 '|', sizeof recipient);
2237 hold_R = msg->cm_fields['R'];
2238 hold_D = msg->cm_fields['D'];
2239 msg->cm_fields['R'] = malloc(SIZ);
2240 msg->cm_fields['D'] = malloc(128);
2241 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2242 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2244 serialize_message(&smr, msg);
2246 snprintf(submit_filename, sizeof submit_filename,
2247 "./network/spoolin/netmail.%04lx.%04x.%04x",
2248 (long) getpid(), CC->cs_pid, ++seqnum);
2249 network_fp = fopen(submit_filename, "wb+");
2250 if (network_fp != NULL) {
2251 fwrite(smr.ser, smr.len, 1, network_fp);
2257 free(msg->cm_fields['R']);
2258 free(msg->cm_fields['D']);
2259 msg->cm_fields['R'] = hold_R;
2260 msg->cm_fields['D'] = hold_D;
2263 /* Go back to the room we started from */
2264 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2265 if (strcasecmp(hold_rm, CC->room.QRname))
2266 /* getroom(&CC->room, hold_rm); */
2267 usergoto(hold_rm, 0, 1, NULL, NULL);
2269 /* For internet mail, generate delivery instructions.
2270 * Yes, this is recursive. Deal with it. Infinite recursion does
2271 * not happen because the delivery instructions message does not
2272 * contain a recipient.
2275 if (recps->num_internet > 0) {
2276 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2277 instr = malloc(SIZ * 2);
2278 snprintf(instr, SIZ * 2,
2279 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2281 SPOOLMIME, newmsgid, (long)time(NULL),
2282 msg->cm_fields['A'], msg->cm_fields['N']
2285 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2286 size_t tmp = strlen(instr);
2287 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2288 snprintf(&instr[tmp], SIZ * 2 - tmp,
2289 "remote|%s|0||\n", recipient);
2292 imsg = malloc(sizeof(struct CtdlMessage));
2293 memset(imsg, 0, sizeof(struct CtdlMessage));
2294 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2295 imsg->cm_anon_type = MES_NORMAL;
2296 imsg->cm_format_type = FMT_RFC822;
2297 imsg->cm_fields['A'] = strdup("Citadel");
2298 imsg->cm_fields['M'] = instr;
2299 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2300 CtdlFreeMessage(imsg);
2309 * Convenience function for generating small administrative messages.
2311 void quickie_message(char *from, char *to, char *room, char *text,
2312 int format_type, char *subject)
2314 struct CtdlMessage *msg;
2315 struct recptypes *recp = NULL;
2317 msg = malloc(sizeof(struct CtdlMessage));
2318 memset(msg, 0, sizeof(struct CtdlMessage));
2319 msg->cm_magic = CTDLMESSAGE_MAGIC;
2320 msg->cm_anon_type = MES_NORMAL;
2321 msg->cm_format_type = format_type;
2322 msg->cm_fields['A'] = strdup(from);
2323 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2324 msg->cm_fields['N'] = strdup(NODENAME);
2326 msg->cm_fields['R'] = strdup(to);
2327 recp = validate_recipients(to);
2329 if (subject != NULL) {
2330 msg->cm_fields['U'] = strdup(subject);
2332 msg->cm_fields['M'] = strdup(text);
2334 CtdlSubmitMsg(msg, recp, room);
2335 CtdlFreeMessage(msg);
2336 if (recp != NULL) free(recp);
2342 * Back end function used by CtdlMakeMessage() and similar functions
2344 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2345 size_t maxlen, /* maximum message length */
2346 char *exist, /* if non-null, append to it;
2347 exist is ALWAYS freed */
2348 int crlf /* CRLF newlines instead of LF */
2352 size_t message_len = 0;
2353 size_t buffer_len = 0;
2359 if (exist == NULL) {
2366 message_len = strlen(exist);
2367 buffer_len = message_len + 4096;
2368 m = realloc(exist, buffer_len);
2375 /* flush the input if we have nowhere to store it */
2380 /* read in the lines of message text one by one */
2382 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2383 if (!strcmp(buf, terminator)) finished = 1;
2385 strcat(buf, "\r\n");
2391 if ( (!flushing) && (!finished) ) {
2392 /* Measure the line */
2393 linelen = strlen(buf);
2395 /* augment the buffer if we have to */
2396 if ((message_len + linelen) >= buffer_len) {
2397 ptr = realloc(m, (buffer_len * 2) );
2398 if (ptr == NULL) { /* flush if can't allocate */
2401 buffer_len = (buffer_len * 2);
2403 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2407 /* Add the new line to the buffer. NOTE: this loop must avoid
2408 * using functions like strcat() and strlen() because they
2409 * traverse the entire buffer upon every call, and doing that
2410 * for a multi-megabyte message slows it down beyond usability.
2412 strcpy(&m[message_len], buf);
2413 message_len += linelen;
2416 /* if we've hit the max msg length, flush the rest */
2417 if (message_len >= maxlen) flushing = 1;
2419 } while (!finished);
2427 * Build a binary message to be saved on disk.
2428 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2429 * will become part of the message. This means you are no longer
2430 * responsible for managing that memory -- it will be freed along with
2431 * the rest of the fields when CtdlFreeMessage() is called.)
2434 struct CtdlMessage *CtdlMakeMessage(
2435 struct ctdluser *author, /* author's user structure */
2436 char *recipient, /* NULL if it's not mail */
2437 char *room, /* room where it's going */
2438 int type, /* see MES_ types in header file */
2439 int format_type, /* variformat, plain text, MIME... */
2440 char *fake_name, /* who we're masquerading as */
2441 char *subject, /* Subject (optional) */
2442 char *preformatted_text /* ...or NULL to read text from client */
2444 char dest_node[SIZ];
2446 struct CtdlMessage *msg;
2448 msg = malloc(sizeof(struct CtdlMessage));
2449 memset(msg, 0, sizeof(struct CtdlMessage));
2450 msg->cm_magic = CTDLMESSAGE_MAGIC;
2451 msg->cm_anon_type = type;
2452 msg->cm_format_type = format_type;
2454 /* Don't confuse the poor folks if it's not routed mail. */
2455 strcpy(dest_node, "");
2459 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2460 msg->cm_fields['P'] = strdup(buf);
2462 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2463 msg->cm_fields['T'] = strdup(buf);
2465 if (fake_name[0]) /* author */
2466 msg->cm_fields['A'] = strdup(fake_name);
2468 msg->cm_fields['A'] = strdup(author->fullname);
2470 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2471 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2474 msg->cm_fields['O'] = strdup(CC->room.QRname);
2477 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2478 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2480 if (recipient[0] != 0) {
2481 msg->cm_fields['R'] = strdup(recipient);
2483 if (dest_node[0] != 0) {
2484 msg->cm_fields['D'] = strdup(dest_node);
2487 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2488 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2491 if (subject != NULL) {
2493 if (strlen(subject) > 0) {
2494 msg->cm_fields['U'] = strdup(subject);
2498 if (preformatted_text != NULL) {
2499 msg->cm_fields['M'] = preformatted_text;
2502 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2503 config.c_maxmsglen, NULL, 0);
2511 * Check to see whether we have permission to post a message in the current
2512 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2513 * returns 0 on success.
2515 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2517 if (!(CC->logged_in)) {
2518 snprintf(errmsgbuf, n, "Not logged in.");
2519 return (ERROR + NOT_LOGGED_IN);
2522 if ((CC->user.axlevel < 2)
2523 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2524 snprintf(errmsgbuf, n, "Need to be validated to enter "
2525 "(except in %s> to sysop)", MAILROOM);
2526 return (ERROR + HIGHER_ACCESS_REQUIRED);
2529 if ((CC->user.axlevel < 4)
2530 && (CC->room.QRflags & QR_NETWORK)) {
2531 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2532 return (ERROR + HIGHER_ACCESS_REQUIRED);
2535 if ((CC->user.axlevel < 6)
2536 && (CC->room.QRflags & QR_READONLY)) {
2537 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2538 return (ERROR + HIGHER_ACCESS_REQUIRED);
2541 strcpy(errmsgbuf, "Ok");
2547 * Check to see if the specified user has Internet mail permission
2548 * (returns nonzero if permission is granted)
2550 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2552 /* Do not allow twits to send Internet mail */
2553 if (who->axlevel <= 2) return(0);
2555 /* Globally enabled? */
2556 if (config.c_restrict == 0) return(1);
2558 /* User flagged ok? */
2559 if (who->flags & US_INTERNET) return(2);
2561 /* Aide level access? */
2562 if (who->axlevel >= 6) return(3);
2564 /* No mail for you! */
2571 * Validate recipients, count delivery types and errors, and handle aliasing
2572 * FIXME check for dupes!!!!!
2574 struct recptypes *validate_recipients(char *recipients) {
2575 struct recptypes *ret;
2576 char this_recp[SIZ];
2577 char this_recp_cooked[SIZ];
2583 struct ctdluser tempUS;
2584 struct ctdlroom tempQR;
2587 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2588 if (ret == NULL) return(NULL);
2589 memset(ret, 0, sizeof(struct recptypes));
2592 ret->num_internet = 0;
2597 if (recipients == NULL) {
2600 else if (strlen(recipients) == 0) {
2604 /* Change all valid separator characters to commas */
2605 for (i=0; i<strlen(recipients); ++i) {
2606 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2607 recipients[i] = ',';
2612 num_recps = num_tokens(recipients, ',');
2615 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2616 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2618 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2619 mailtype = alias(this_recp);
2620 mailtype = alias(this_recp);
2621 mailtype = alias(this_recp);
2622 for (j=0; j<=strlen(this_recp); ++j) {
2623 if (this_recp[j]=='_') {
2624 this_recp_cooked[j] = ' ';
2627 this_recp_cooked[j] = this_recp[j];
2633 if (!strcasecmp(this_recp, "sysop")) {
2635 strcpy(this_recp, config.c_aideroom);
2636 if (strlen(ret->recp_room) > 0) {
2637 strcat(ret->recp_room, "|");
2639 strcat(ret->recp_room, this_recp);
2641 else if (getuser(&tempUS, this_recp) == 0) {
2643 strcpy(this_recp, tempUS.fullname);
2644 if (strlen(ret->recp_local) > 0) {
2645 strcat(ret->recp_local, "|");
2647 strcat(ret->recp_local, this_recp);
2649 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2651 strcpy(this_recp, tempUS.fullname);
2652 if (strlen(ret->recp_local) > 0) {
2653 strcat(ret->recp_local, "|");
2655 strcat(ret->recp_local, this_recp);
2657 else if ( (!strncasecmp(this_recp, "room_", 5))
2658 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2660 if (strlen(ret->recp_room) > 0) {
2661 strcat(ret->recp_room, "|");
2663 strcat(ret->recp_room, &this_recp_cooked[5]);
2671 /* Yes, you're reading this correctly: if the target
2672 * domain points back to the local system or an attached
2673 * Citadel directory, the address is invalid. That's
2674 * because if the address were valid, we would have
2675 * already translated it to a local address by now.
2677 if (IsDirectory(this_recp)) {
2682 ++ret->num_internet;
2683 if (strlen(ret->recp_internet) > 0) {
2684 strcat(ret->recp_internet, "|");
2686 strcat(ret->recp_internet, this_recp);
2691 if (strlen(ret->recp_ignet) > 0) {
2692 strcat(ret->recp_ignet, "|");
2694 strcat(ret->recp_ignet, this_recp);
2702 if (strlen(ret->errormsg) == 0) {
2703 snprintf(append, sizeof append,
2704 "Invalid recipient: %s",
2708 snprintf(append, sizeof append,
2711 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2712 strcat(ret->errormsg, append);
2716 if (strlen(ret->display_recp) == 0) {
2717 strcpy(append, this_recp);
2720 snprintf(append, sizeof append, ", %s",
2723 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2724 strcat(ret->display_recp, append);
2729 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2730 ret->num_room + ret->num_error) == 0) {
2732 strcpy(ret->errormsg, "No recipients specified.");
2735 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2736 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2737 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2738 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2739 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2740 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2748 * message entry - mode 0 (normal)
2750 void cmd_ent0(char *entargs)
2754 char masquerade_as[SIZ];
2756 int format_type = 0;
2757 char newusername[SIZ];
2758 struct CtdlMessage *msg;
2762 struct recptypes *valid = NULL;
2769 post = extract_int(entargs, 0);
2770 extract_token(recp, entargs, 1, '|', sizeof recp);
2771 anon_flag = extract_int(entargs, 2);
2772 format_type = extract_int(entargs, 3);
2773 extract_token(subject, entargs, 4, '|', sizeof subject);
2774 do_confirm = extract_int(entargs, 6);
2776 /* first check to make sure the request is valid. */
2778 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2780 cprintf("%d %s\n", err, errmsg);
2784 /* Check some other permission type things. */
2787 if (CC->user.axlevel < 6) {
2788 cprintf("%d You don't have permission to masquerade.\n",
2789 ERROR + HIGHER_ACCESS_REQUIRED);
2792 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2793 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2794 safestrncpy(CC->fake_postname, newusername,
2795 sizeof(CC->fake_postname) );
2796 cprintf("%d ok\n", CIT_OK);
2799 CC->cs_flags |= CS_POSTING;
2801 /* In the Mail> room we have to behave a little differently --
2802 * make sure the user has specified at least one recipient. Then
2803 * validate the recipient(s).
2805 if ( (CC->room.QRflags & QR_MAILBOX)
2806 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2808 if (CC->user.axlevel < 2) {
2809 strcpy(recp, "sysop");
2812 valid = validate_recipients(recp);
2813 if (valid->num_error > 0) {
2815 ERROR + NO_SUCH_USER, valid->errormsg);
2819 if (valid->num_internet > 0) {
2820 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2821 cprintf("%d You do not have permission "
2822 "to send Internet mail.\n",
2823 ERROR + HIGHER_ACCESS_REQUIRED);
2829 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2830 && (CC->user.axlevel < 4) ) {
2831 cprintf("%d Higher access required for network mail.\n",
2832 ERROR + HIGHER_ACCESS_REQUIRED);
2837 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2838 && ((CC->user.flags & US_INTERNET) == 0)
2839 && (!CC->internal_pgm)) {
2840 cprintf("%d You don't have access to Internet mail.\n",
2841 ERROR + HIGHER_ACCESS_REQUIRED);
2848 /* Is this a room which has anonymous-only or anonymous-option? */
2849 anonymous = MES_NORMAL;
2850 if (CC->room.QRflags & QR_ANONONLY) {
2851 anonymous = MES_ANONONLY;
2853 if (CC->room.QRflags & QR_ANONOPT) {
2854 if (anon_flag == 1) { /* only if the user requested it */
2855 anonymous = MES_ANONOPT;
2859 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2863 /* If we're only checking the validity of the request, return
2864 * success without creating the message.
2867 cprintf("%d %s\n", CIT_OK,
2868 ((valid != NULL) ? valid->display_recp : "") );
2873 /* Handle author masquerading */
2874 if (CC->fake_postname[0]) {
2875 strcpy(masquerade_as, CC->fake_postname);
2877 else if (CC->fake_username[0]) {
2878 strcpy(masquerade_as, CC->fake_username);
2881 strcpy(masquerade_as, "");
2884 /* Read in the message from the client. */
2886 cprintf("%d send message\n", START_CHAT_MODE);
2888 cprintf("%d send message\n", SEND_LISTING);
2890 msg = CtdlMakeMessage(&CC->user, recp,
2891 CC->room.QRname, anonymous, format_type,
2892 masquerade_as, subject, NULL);
2895 msgnum = CtdlSubmitMsg(msg, valid, "");
2898 cprintf("%ld\n", msgnum);
2900 cprintf("Message accepted.\n");
2903 cprintf("Internal error.\n");
2905 if (msg->cm_fields['E'] != NULL) {
2906 cprintf("%s\n", msg->cm_fields['E']);
2913 CtdlFreeMessage(msg);
2915 CC->fake_postname[0] = '\0';
2923 * API function to delete messages which match a set of criteria
2924 * (returns the actual number of messages deleted)
2926 int CtdlDeleteMessages(char *room_name, /* which room */
2927 long dmsgnum, /* or "0" for any */
2928 char *content_type /* or "" for any */
2932 struct ctdlroom qrbuf;
2933 struct cdbdata *cdbfr;
2934 long *msglist = NULL;
2935 long *dellist = NULL;
2938 int num_deleted = 0;
2940 struct MetaData smi;
2942 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2943 room_name, dmsgnum, content_type);
2945 /* get room record, obtaining a lock... */
2946 if (lgetroom(&qrbuf, room_name) != 0) {
2947 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2949 return (0); /* room not found */
2951 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2953 if (cdbfr != NULL) {
2954 msglist = malloc(cdbfr->len);
2955 dellist = malloc(cdbfr->len);
2956 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2957 num_msgs = cdbfr->len / sizeof(long);
2961 for (i = 0; i < num_msgs; ++i) {
2964 /* Set/clear a bit for each criterion */
2966 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2967 delete_this |= 0x01;
2969 if (strlen(content_type) == 0) {
2970 delete_this |= 0x02;
2972 GetMetaData(&smi, msglist[i]);
2973 if (!strcasecmp(smi.meta_content_type,
2975 delete_this |= 0x02;
2979 /* Delete message only if all bits are set */
2980 if (delete_this == 0x03) {
2981 dellist[num_deleted++] = msglist[i];
2986 num_msgs = sort_msglist(msglist, num_msgs);
2987 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2988 msglist, (int)(num_msgs * sizeof(long)));
2990 qrbuf.QRhighest = msglist[num_msgs - 1];
2994 /* Go through the messages we pulled out of the index, and decrement
2995 * their reference counts by 1. If this is the only room the message
2996 * was in, the reference count will reach zero and the message will
2997 * automatically be deleted from the database. We do this in a
2998 * separate pass because there might be plug-in hooks getting called,
2999 * and we don't want that happening during an S_ROOMS critical
3002 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3003 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3004 AdjRefCount(dellist[i], -1);
3007 /* Now free the memory we used, and go away. */
3008 if (msglist != NULL) free(msglist);
3009 if (dellist != NULL) free(dellist);
3010 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3011 return (num_deleted);
3017 * Check whether the current user has permission to delete messages from
3018 * the current room (returns 1 for yes, 0 for no)
3020 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3021 getuser(&CC->user, CC->curr_user);
3022 if ((CC->user.axlevel < 6)
3023 && (CC->user.usernum != CC->room.QRroomaide)
3024 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3025 && (!(CC->internal_pgm))) {
3034 * Delete message from current room
3036 void cmd_dele(char *delstr)
3041 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3042 cprintf("%d Higher access required.\n",
3043 ERROR + HIGHER_ACCESS_REQUIRED);
3046 delnum = extract_long(delstr, 0);
3048 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3051 cprintf("%d %d message%s deleted.\n", CIT_OK,
3052 num_deleted, ((num_deleted != 1) ? "s" : ""));
3054 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3060 * Back end API function for moves and deletes
3062 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3065 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3066 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3067 if (err != 0) return(err);
3075 * move or copy a message to another room
3077 void cmd_move(char *args)
3080 char targ[ROOMNAMELEN];
3081 struct ctdlroom qtemp;
3087 num = extract_long(args, 0);
3088 extract_token(targ, args, 1, '|', sizeof targ);
3089 targ[ROOMNAMELEN - 1] = 0;
3090 is_copy = extract_int(args, 2);
3092 if (getroom(&qtemp, targ) != 0) {
3093 cprintf("%d '%s' does not exist.\n",
3094 ERROR + ROOM_NOT_FOUND, targ);
3098 getuser(&CC->user, CC->curr_user);
3099 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3101 /* Check for permission to perform this operation.
3102 * Remember: "CC->room" is source, "qtemp" is target.
3106 /* Aides can move/copy */
3107 if (CC->user.axlevel >= 6) permit = 1;
3109 /* Room aides can move/copy */
3110 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3112 /* Permit move/copy from personal rooms */
3113 if ((CC->room.QRflags & QR_MAILBOX)
3114 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3116 /* Permit only copy from public to personal room */
3118 && (!(CC->room.QRflags & QR_MAILBOX))
3119 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3121 /* User must have access to target room */
3122 if (!(ra & UA_KNOWN)) permit = 0;
3125 cprintf("%d Higher access required.\n",
3126 ERROR + HIGHER_ACCESS_REQUIRED);
3130 err = CtdlCopyMsgToRoom(num, targ);
3132 cprintf("%d Cannot store message in %s: error %d\n",
3137 /* Now delete the message from the source room,
3138 * if this is a 'move' rather than a 'copy' operation.
3141 CtdlDeleteMessages(CC->room.QRname, num, "");
3144 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3150 * GetMetaData() - Get the supplementary record for a message
3152 void GetMetaData(struct MetaData *smibuf, long msgnum)
3155 struct cdbdata *cdbsmi;
3158 memset(smibuf, 0, sizeof(struct MetaData));
3159 smibuf->meta_msgnum = msgnum;
3160 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3162 /* Use the negative of the message number for its supp record index */
3163 TheIndex = (0L - msgnum);
3165 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3166 if (cdbsmi == NULL) {
3167 return; /* record not found; go with defaults */
3169 memcpy(smibuf, cdbsmi->ptr,
3170 ((cdbsmi->len > sizeof(struct MetaData)) ?
3171 sizeof(struct MetaData) : cdbsmi->len));
3178 * PutMetaData() - (re)write supplementary record for a message
3180 void PutMetaData(struct MetaData *smibuf)
3184 /* Use the negative of the message number for the metadata db index */
3185 TheIndex = (0L - smibuf->meta_msgnum);
3187 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3188 smibuf->meta_msgnum, smibuf->meta_refcount);
3190 cdb_store(CDB_MSGMAIN,
3191 &TheIndex, (int)sizeof(long),
3192 smibuf, (int)sizeof(struct MetaData));
3197 * AdjRefCount - change the reference count for a message;
3198 * delete the message if it reaches zero
3200 void AdjRefCount(long msgnum, int incr)
3203 struct MetaData smi;
3206 /* This is a *tight* critical section; please keep it that way, as
3207 * it may get called while nested in other critical sections.
3208 * Complicating this any further will surely cause deadlock!
3210 begin_critical_section(S_SUPPMSGMAIN);
3211 GetMetaData(&smi, msgnum);
3212 smi.meta_refcount += incr;
3214 end_critical_section(S_SUPPMSGMAIN);
3215 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3216 msgnum, incr, smi.meta_refcount);
3218 /* If the reference count is now zero, delete the message
3219 * (and its supplementary record as well).
3220 * FIXME ... defer this so it doesn't keep the user waiting.
3222 if (smi.meta_refcount == 0) {
3223 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3225 /* Remove from fulltext index */
3226 if (config.c_enable_fulltext) {
3227 ft_index_message(msgnum, 0);
3230 /* Remove from message base */
3232 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3233 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3235 /* Remove metadata record */
3236 delnum = (0L - msgnum);
3237 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3242 * Write a generic object to this room
3244 * Note: this could be much more efficient. Right now we use two temporary
3245 * files, and still pull the message into memory as with all others.
3247 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3248 char *content_type, /* MIME type of this object */
3249 char *tempfilename, /* Where to fetch it from */
3250 struct ctdluser *is_mailbox, /* Mailbox room? */
3251 int is_binary, /* Is encoding necessary? */
3252 int is_unique, /* Del others of this type? */
3253 unsigned int flags /* Internal save flags */
3258 struct ctdlroom qrbuf;
3259 char roomname[ROOMNAMELEN];
3260 struct CtdlMessage *msg;
3262 char *raw_message = NULL;
3263 char *encoded_message = NULL;
3264 off_t raw_length = 0;
3266 if (is_mailbox != NULL)
3267 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3269 safestrncpy(roomname, req_room, sizeof(roomname));
3270 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3273 fp = fopen(tempfilename, "rb");
3275 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3276 tempfilename, strerror(errno));
3279 fseek(fp, 0L, SEEK_END);
3280 raw_length = ftell(fp);
3282 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3284 raw_message = malloc((size_t)raw_length + 2);
3285 fread(raw_message, (size_t)raw_length, 1, fp);
3289 encoded_message = malloc((size_t)
3290 (((raw_length * 134) / 100) + 4096 ) );
3293 encoded_message = malloc((size_t)(raw_length + 4096));
3296 sprintf(encoded_message, "Content-type: %s\n", content_type);
3299 sprintf(&encoded_message[strlen(encoded_message)],
3300 "Content-transfer-encoding: base64\n\n"
3304 sprintf(&encoded_message[strlen(encoded_message)],
3305 "Content-transfer-encoding: 7bit\n\n"
3311 &encoded_message[strlen(encoded_message)],
3317 raw_message[raw_length] = 0;
3319 &encoded_message[strlen(encoded_message)],
3327 lprintf(CTDL_DEBUG, "Allocating\n");
3328 msg = malloc(sizeof(struct CtdlMessage));
3329 memset(msg, 0, sizeof(struct CtdlMessage));
3330 msg->cm_magic = CTDLMESSAGE_MAGIC;
3331 msg->cm_anon_type = MES_NORMAL;
3332 msg->cm_format_type = 4;
3333 msg->cm_fields['A'] = strdup(CC->user.fullname);
3334 msg->cm_fields['O'] = strdup(req_room);
3335 msg->cm_fields['N'] = strdup(config.c_nodename);
3336 msg->cm_fields['H'] = strdup(config.c_humannode);
3337 msg->cm_flags = flags;
3339 msg->cm_fields['M'] = encoded_message;
3341 /* Create the requested room if we have to. */
3342 if (getroom(&qrbuf, roomname) != 0) {
3343 create_room(roomname,
3344 ( (is_mailbox != NULL) ? 5 : 3 ),
3345 "", 0, 1, 0, VIEW_BBS);
3347 /* If the caller specified this object as unique, delete all
3348 * other objects of this type that are currently in the room.
3351 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3352 CtdlDeleteMessages(roomname, 0L, content_type));
3354 /* Now write the data */
3355 CtdlSubmitMsg(msg, NULL, roomname);
3356 CtdlFreeMessage(msg);
3364 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3365 config_msgnum = msgnum;
3369 char *CtdlGetSysConfig(char *sysconfname) {
3370 char hold_rm[ROOMNAMELEN];
3373 struct CtdlMessage *msg;
3376 strcpy(hold_rm, CC->room.QRname);
3377 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3378 getroom(&CC->room, hold_rm);
3383 /* We want the last (and probably only) config in this room */
3384 begin_critical_section(S_CONFIG);
3385 config_msgnum = (-1L);
3386 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3387 CtdlGetSysConfigBackend, NULL);
3388 msgnum = config_msgnum;
3389 end_critical_section(S_CONFIG);
3395 msg = CtdlFetchMessage(msgnum, 1);
3397 conf = strdup(msg->cm_fields['M']);
3398 CtdlFreeMessage(msg);
3405 getroom(&CC->room, hold_rm);
3407 if (conf != NULL) do {
3408 extract_token(buf, conf, 0, '\n', sizeof buf);
3409 strcpy(conf, &conf[strlen(buf)+1]);
3410 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3415 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3416 char temp[PATH_MAX];
3419 strcpy(temp, tmpnam(NULL));
3421 fp = fopen(temp, "w");
3422 if (fp == NULL) return;
3423 fprintf(fp, "%s", sysconfdata);
3426 /* this handy API function does all the work for us */
3427 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3433 * Determine whether a given Internet address belongs to the current user
3435 int CtdlIsMe(char *addr, int addr_buf_len)
3437 struct recptypes *recp;
3440 recp = validate_recipients(addr);
3441 if (recp == NULL) return(0);
3443 if (recp->num_local == 0) {
3448 for (i=0; i<recp->num_local; ++i) {
3449 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3450 if (!strcasecmp(addr, CC->user.fullname)) {
3462 * Citadel protocol command to do the same
3464 void cmd_isme(char *argbuf) {
3467 if (CtdlAccessCheck(ac_logged_in)) return;
3468 extract_token(addr, argbuf, 0, '|', sizeof addr);
3470 if (CtdlIsMe(addr, sizeof addr)) {
3471 cprintf("%d %s\n", CIT_OK, addr);
3474 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);