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"
49 #include "mime_parser.h"
52 #include "internet_addressing.h"
54 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
55 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
56 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
58 extern struct config config;
63 * This really belongs in serv_network.c, but I don't know how to export
64 * symbols between modules.
66 struct FilterList *filterlist = NULL;
70 * These are the four-character field headers we use when outputting
71 * messages in Citadel format (as opposed to RFC822 format).
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,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
108 * This function is self explanatory.
109 * (What can I say, I'm in a weird mood today...)
111 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
115 for (i = 0; i < strlen(name); ++i) {
116 if (name[i] == '@') {
117 while (isspace(name[i - 1]) && i > 0) {
118 strcpy(&name[i - 1], &name[i]);
121 while (isspace(name[i + 1])) {
122 strcpy(&name[i + 1], &name[i + 2]);
130 * Aliasing for network mail.
131 * (Error messages have been commented out, because this is a server.)
133 int alias(char *name)
134 { /* process alias and routing info for mail */
137 char aaa[SIZ], bbb[SIZ];
138 char *ignetcfg = NULL;
139 char *ignetmap = NULL;
146 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
148 fp = fopen("network/mail.aliases", "r");
150 fp = fopen("/dev/null", "r");
157 while (fgets(aaa, sizeof aaa, fp) != NULL) {
158 while (isspace(name[0]))
159 strcpy(name, &name[1]);
160 aaa[strlen(aaa) - 1] = 0;
162 for (a = 0; a < strlen(aaa); ++a) {
164 strcpy(bbb, &aaa[a + 1]);
168 if (!strcasecmp(name, aaa))
173 /* Hit the Global Address Book */
174 if (CtdlDirectoryLookup(aaa, name) == 0) {
178 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
180 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
181 for (a=0; a<strlen(name); ++a) {
182 if (name[a] == '@') {
183 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
185 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
190 /* determine local or remote type, see citadel.h */
191 at = haschar(name, '@');
192 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
193 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
194 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
196 /* figure out the delivery mode */
197 extract_token(node, name, 1, '@');
199 /* If there are one or more dots in the nodename, we assume that it
200 * is an FQDN and will attempt SMTP delivery to the Internet.
202 if (haschar(node, '.') > 0) {
203 return(MES_INTERNET);
206 /* Otherwise we look in the IGnet maps for a valid Citadel node.
207 * Try directly-connected nodes first...
209 ignetcfg = CtdlGetSysConfig(IGNETCFG);
210 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
211 extract_token(buf, ignetcfg, i, '\n');
212 extract_token(testnode, buf, 0, '|');
213 if (!strcasecmp(node, testnode)) {
221 * Then try nodes that are two or more hops away.
223 ignetmap = CtdlGetSysConfig(IGNETMAP);
224 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
225 extract_token(buf, ignetmap, i, '\n');
226 extract_token(testnode, buf, 0, '|');
227 if (!strcasecmp(node, testnode)) {
234 /* If we get to this point it's an invalid node name */
243 fp = fopen("citadel.control", "r");
245 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
249 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
255 void simple_listing(long msgnum, void *userdata)
257 cprintf("%ld\n", msgnum);
262 /* Determine if a given message matches the fields in a message template.
263 * Return 0 for a successful match.
265 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
268 /* If there aren't any fields in the template, all messages will
271 if (template == NULL) return(0);
273 /* Null messages are bogus. */
274 if (msg == NULL) return(1);
276 for (i='A'; i<='Z'; ++i) {
277 if (template->cm_fields[i] != NULL) {
278 if (msg->cm_fields[i] == NULL) {
281 if (strcasecmp(msg->cm_fields[i],
282 template->cm_fields[i])) return 1;
286 /* All compares succeeded: we have a match! */
293 * Retrieve the "seen" message list for the current room.
295 void CtdlGetSeen(char *buf, int which_set) {
298 /* Learn about the user and room in question */
299 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
301 if (which_set == ctdlsetseen_seen)
302 safestrncpy(buf, vbuf.v_seen, SIZ);
303 if (which_set == ctdlsetseen_answered)
304 safestrncpy(buf, vbuf.v_answered, SIZ);
310 * Manipulate the "seen msgs" string (or other message set strings)
312 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
314 struct cdbdata *cdbfr;
325 /* Learn about the user and room in question */
326 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
328 /* Load the message list */
329 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
331 msglist = malloc(cdbfr->len);
332 memcpy(msglist, cdbfr->ptr, cdbfr->len);
333 num_msgs = cdbfr->len / sizeof(long);
336 return; /* No messages at all? No further action. */
339 /* Decide which message set we're manipulating */
340 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
341 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
343 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
346 for (i=0; i<num_msgs; ++i) {
349 if (msglist[i] == target_msgnum) {
350 is_seen = target_setting;
353 if (is_msg_in_mset(vset, msglist[i])) {
359 if (lo < 0L) lo = msglist[i];
362 if ( ((is_seen == 0) && (was_seen == 1))
363 || ((is_seen == 1) && (i == num_msgs-1)) ) {
366 if ( (strlen(newseen) + 20) > SIZ) {
367 strcpy(newseen, &newseen[20]);
370 tmp = strlen(newseen);
372 strcat(newseen, ",");
376 snprintf(&newseen[tmp], sizeof newseen - tmp,
380 snprintf(&newseen[tmp], sizeof newseen - tmp,
389 /* Decide which message set we're manipulating */
390 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
391 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
393 lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
395 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
400 * API function to perform an operation for each qualifying message in the
401 * current room. (Returns the number of messages processed.)
403 int CtdlForEachMessage(int mode, long ref,
405 struct CtdlMessage *compare,
406 void (*CallBack) (long, void *),
412 struct cdbdata *cdbfr;
413 long *msglist = NULL;
415 int num_processed = 0;
418 struct CtdlMessage *msg;
421 int printed_lastold = 0;
423 /* Learn about the user and room in question */
425 getuser(&CC->user, CC->curr_user);
426 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
428 /* Load the message list */
429 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
431 msglist = malloc(cdbfr->len);
432 memcpy(msglist, cdbfr->ptr, cdbfr->len);
433 num_msgs = cdbfr->len / sizeof(long);
436 return 0; /* No messages at all? No further action. */
441 * Now begin the traversal.
443 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
445 /* If the caller is looking for a specific MIME type, filter
446 * out all messages which are not of the type requested.
448 if (content_type != NULL) if (strlen(content_type) > 0) {
450 /* This call to GetMetaData() sits inside this loop
451 * so that we only do the extra database read per msg
452 * if we need to. Doing the extra read all the time
453 * really kills the server. If we ever need to use
454 * metadata for another search criterion, we need to
455 * move the read somewhere else -- but still be smart
456 * enough to only do the read if the caller has
457 * specified something that will need it.
459 GetMetaData(&smi, msglist[a]);
461 if (strcasecmp(smi.meta_content_type, content_type)) {
467 num_msgs = sort_msglist(msglist, num_msgs);
469 /* If a template was supplied, filter out the messages which
470 * don't match. (This could induce some delays!)
473 if (compare != NULL) {
474 for (a = 0; a < num_msgs; ++a) {
475 msg = CtdlFetchMessage(msglist[a]);
477 if (CtdlMsgCmp(msg, compare)) {
480 CtdlFreeMessage(msg);
488 * Now iterate through the message list, according to the
489 * criteria supplied by the caller.
492 for (a = 0; a < num_msgs; ++a) {
493 thismsg = msglist[a];
494 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
495 if (is_seen) lastold = thismsg;
500 || ((mode == MSGS_OLD) && (is_seen))
501 || ((mode == MSGS_NEW) && (!is_seen))
502 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
503 || ((mode == MSGS_FIRST) && (a < ref))
504 || ((mode == MSGS_GT) && (thismsg > ref))
505 || ((mode == MSGS_EQ) && (thismsg == ref))
508 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
510 CallBack(lastold, userdata);
514 if (CallBack) CallBack(thismsg, userdata);
518 free(msglist); /* Clean up */
519 return num_processed;
525 * cmd_msgs() - get list of message #'s in this room
526 * implements the MSGS server command using CtdlForEachMessage()
528 void cmd_msgs(char *cmdbuf)
537 int with_template = 0;
538 struct CtdlMessage *template = NULL;
540 extract(which, cmdbuf, 0);
541 cm_ref = extract_int(cmdbuf, 1);
542 with_template = extract_int(cmdbuf, 2);
546 if (!strncasecmp(which, "OLD", 3))
548 else if (!strncasecmp(which, "NEW", 3))
550 else if (!strncasecmp(which, "FIRST", 5))
552 else if (!strncasecmp(which, "LAST", 4))
554 else if (!strncasecmp(which, "GT", 2))
557 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
558 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
563 cprintf("%d Send template then receive message list\n",
565 template = (struct CtdlMessage *)
566 malloc(sizeof(struct CtdlMessage));
567 memset(template, 0, sizeof(struct CtdlMessage));
568 while(client_gets(buf), strcmp(buf,"000")) {
569 extract(tfield, buf, 0);
570 extract(tvalue, buf, 1);
571 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
572 if (!strcasecmp(tfield, msgkeys[i])) {
573 template->cm_fields[i] =
580 cprintf("%d Message list...\n", LISTING_FOLLOWS);
583 CtdlForEachMessage(mode, cm_ref,
584 NULL, template, simple_listing, NULL);
585 if (template != NULL) CtdlFreeMessage(template);
593 * help_subst() - support routine for help file viewer
595 void help_subst(char *strbuf, char *source, char *dest)
600 while (p = pattern2(strbuf, source), (p >= 0)) {
601 strcpy(workbuf, &strbuf[p + strlen(source)]);
602 strcpy(&strbuf[p], dest);
603 strcat(strbuf, workbuf);
608 void do_help_subst(char *buffer)
612 help_subst(buffer, "^nodename", config.c_nodename);
613 help_subst(buffer, "^humannode", config.c_humannode);
614 help_subst(buffer, "^fqdn", config.c_fqdn);
615 help_subst(buffer, "^username", CC->user.fullname);
616 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
617 help_subst(buffer, "^usernum", buf2);
618 help_subst(buffer, "^sysadm", config.c_sysadm);
619 help_subst(buffer, "^variantname", CITADEL);
620 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
621 help_subst(buffer, "^maxsessions", buf2);
622 help_subst(buffer, "^bbsdir", BBSDIR);
628 * memfmout() - Citadel text formatter and paginator.
629 * Although the original purpose of this routine was to format
630 * text to the reader's screen width, all we're really using it
631 * for here is to format text out to 80 columns before sending it
632 * to the client. The client software may reformat it again.
635 int width, /* screen width to use */
636 char *mptr, /* where are we going to get our text from? */
637 char subst, /* nonzero if we should do substitutions */
638 char *nl) /* string to terminate lines with */
650 c = 1; /* c is the current pos */
654 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
656 buffer[strlen(buffer) + 1] = 0;
657 buffer[strlen(buffer)] = ch;
660 if (buffer[0] == '^')
661 do_help_subst(buffer);
663 buffer[strlen(buffer) + 1] = 0;
665 strcpy(buffer, &buffer[1]);
673 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
675 if (((old == 13) || (old == 10)) && (isspace(real))) {
683 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
684 cprintf("%s%s", nl, aaa);
693 if ((strlen(aaa) + c) > (width - 5)) {
702 if ((ch == 13) || (ch == 10)) {
703 cprintf("%s%s", aaa, nl);
710 cprintf("%s%s", aaa, nl);
716 * Callback function for mime parser that simply lists the part
718 void list_this_part(char *name, char *filename, char *partnum, char *disp,
719 void *content, char *cbtype, size_t length, char *encoding,
723 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
724 name, filename, partnum, disp, cbtype, (long)length);
728 * Callback function for multipart prefix
730 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
731 void *content, char *cbtype, size_t length, char *encoding,
734 cprintf("pref=%s|%s\n", partnum, cbtype);
738 * Callback function for multipart sufffix
740 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
741 void *content, char *cbtype, size_t length, char *encoding,
744 cprintf("suff=%s|%s\n", partnum, cbtype);
749 * Callback function for mime parser that opens a section for downloading
751 void mime_download(char *name, char *filename, char *partnum, char *disp,
752 void *content, char *cbtype, size_t length, char *encoding,
756 /* Silently go away if there's already a download open... */
757 if (CC->download_fp != NULL)
760 /* ...or if this is not the desired section */
761 if (strcasecmp(desired_section, partnum))
764 CC->download_fp = tmpfile();
765 if (CC->download_fp == NULL)
768 fwrite(content, length, 1, CC->download_fp);
769 fflush(CC->download_fp);
770 rewind(CC->download_fp);
772 OpenCmdResult(filename, cbtype);
778 * Load a message from disk into memory.
779 * This is used by CtdlOutputMsg() and other fetch functions.
781 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
782 * using the CtdlMessageFree() function.
784 struct CtdlMessage *CtdlFetchMessage(long msgnum)
786 struct cdbdata *dmsgtext;
787 struct cdbdata *dbigmsg;
788 struct CtdlMessage *ret = NULL;
791 cit_uint8_t field_header;
794 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
795 if (dmsgtext == NULL) {
798 mptr = dmsgtext->ptr;
800 /* Parse the three bytes that begin EVERY message on disk.
801 * The first is always 0xFF, the on-disk magic number.
802 * The second is the anonymous/public type byte.
803 * The third is the format type byte (vari, fixed, or MIME).
807 lprintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
811 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
812 memset(ret, 0, sizeof(struct CtdlMessage));
814 ret->cm_magic = CTDLMESSAGE_MAGIC;
815 ret->cm_anon_type = *mptr++; /* Anon type byte */
816 ret->cm_format_type = *mptr++; /* Format type byte */
819 * The rest is zero or more arbitrary fields. Load them in.
820 * We're done when we encounter either a zero-length field or
821 * have just processed the 'M' (message text) field.
824 field_length = strlen(mptr);
825 if (field_length == 0)
827 field_header = *mptr++;
828 ret->cm_fields[field_header] = malloc(field_length);
829 strcpy(ret->cm_fields[field_header], mptr);
831 while (*mptr++ != 0); /* advance to next field */
833 } while ((field_length > 0) && (field_header != 'M'));
837 /* Always make sure there's something in the msg text field. If
838 * it's NULL, the message text is most likely stored separately,
839 * so go ahead and fetch that. Failing that, just set a dummy
840 * body so other code doesn't barf.
842 if (ret->cm_fields['M'] == NULL) {
844 dbigmsg = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
845 if (dmsgtext == NULL) {
846 ret->cm_fields['M'] = strdup("<no text>\n");
849 ret->cm_fields['M'] = strdup(dbigmsg->ptr);
854 /* Perform "before read" hooks (aborting if any return nonzero) */
855 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
856 CtdlFreeMessage(ret);
865 * Returns 1 if the supplied pointer points to a valid Citadel message.
866 * If the pointer is NULL or the magic number check fails, returns 0.
868 int is_valid_message(struct CtdlMessage *msg) {
871 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
872 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
880 * 'Destructor' for struct CtdlMessage
882 void CtdlFreeMessage(struct CtdlMessage *msg)
886 if (is_valid_message(msg) == 0) return;
888 for (i = 0; i < 256; ++i)
889 if (msg->cm_fields[i] != NULL) {
890 free(msg->cm_fields[i]);
893 msg->cm_magic = 0; /* just in case */
899 * Pre callback function for multipart/alternative
901 * NOTE: this differs from the standard behavior for a reason. Normally when
902 * displaying multipart/alternative you want to show the _last_ usable
903 * format in the message. Here we show the _first_ one, because it's
904 * usually text/plain. Since this set of functions is designed for text
905 * output to non-MIME-aware clients, this is the desired behavior.
908 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
909 void *content, char *cbtype, size_t length, char *encoding,
912 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
913 if (!strcasecmp(cbtype, "multipart/alternative")) {
921 * Post callback function for multipart/alternative
923 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
924 void *content, char *cbtype, size_t length, char *encoding,
927 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
928 if (!strcasecmp(cbtype, "multipart/alternative")) {
936 * Inline callback function for mime parser that wants to display text
938 void fixed_output(char *name, char *filename, char *partnum, char *disp,
939 void *content, char *cbtype, size_t length, char *encoding,
946 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
949 * If we're in the middle of a multipart/alternative scope and
950 * we've already printed another section, skip this one.
952 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
953 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
958 if ( (!strcasecmp(cbtype, "text/plain"))
959 || (strlen(cbtype)==0) ) {
962 client_write(wptr, length);
963 if (wptr[length-1] != '\n') {
968 else if (!strcasecmp(cbtype, "text/html")) {
969 ptr = html_to_ascii(content, 80, 0);
971 client_write(ptr, wlen);
972 if (ptr[wlen-1] != '\n') {
977 else if (strncasecmp(cbtype, "multipart/", 10)) {
978 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
979 partnum, filename, cbtype, (long)length);
984 * The client is elegant and sophisticated and wants to be choosy about
985 * MIME content types, so figure out which multipart/alternative part
986 * we're going to send.
988 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
989 void *content, char *cbtype, size_t length, char *encoding,
996 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
997 extract(buf, CC->preferred_formats, i);
998 if (!strcasecmp(buf, cbtype)) {
999 strcpy(ma->chosen_part, partnum);
1006 * Now that we've chosen our preferred part, output it.
1008 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1009 void *content, char *cbtype, size_t length, char *encoding,
1014 int add_newline = 0;
1017 /* This is not the MIME part you're looking for... */
1018 if (strcasecmp(partnum, ma->chosen_part)) return;
1020 /* If the content-type of this part is in our preferred formats
1021 * list, we can simply output it verbatim.
1023 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1024 extract(buf, CC->preferred_formats, i);
1025 if (!strcasecmp(buf, cbtype)) {
1026 /* Yeah! Go! W00t!! */
1028 text_content = (char *)content;
1029 if (text_content[length-1] != '\n') {
1033 cprintf("Content-type: %s\n", cbtype);
1034 cprintf("Content-length: %d\n",
1035 (int)(length + add_newline) );
1036 if (strlen(encoding) > 0) {
1037 cprintf("Content-transfer-encoding: %s\n", encoding);
1040 cprintf("Content-transfer-encoding: 7bit\n");
1043 client_write(content, length);
1044 if (add_newline) cprintf("\n");
1049 /* No translations required or possible: output as text/plain */
1050 cprintf("Content-type: text/plain\n\n");
1051 fixed_output(name, filename, partnum, disp, content, cbtype,
1052 length, encoding, cbuserdata);
1057 * Get a message off disk. (returns om_* values found in msgbase.h)
1060 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1061 int mode, /* how would you like that message? */
1062 int headers_only, /* eschew the message body? */
1063 int do_proto, /* do Citadel protocol responses? */
1064 int crlf /* Use CRLF newlines instead of LF? */
1066 struct CtdlMessage *TheMessage;
1069 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1074 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1075 if (do_proto) cprintf("%d Not logged in.\n",
1076 ERROR + NOT_LOGGED_IN);
1077 return(om_not_logged_in);
1080 /* FIXME ... small security issue
1081 * We need to check to make sure the requested message is actually
1082 * in the current room, and set msg_ok to 1 only if it is. This
1083 * functionality is currently missing because I'm in a hurry to replace
1084 * broken production code with nonbroken pre-beta code. :( -- ajc
1087 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
1088 ERROR + MESSAGE_NOT_FOUND, msg_num);
1089 return(om_no_such_msg);
1094 * Fetch the message from disk.
1096 TheMessage = CtdlFetchMessage(msg_num);
1098 if (TheMessage == NULL) {
1099 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1100 ERROR + MESSAGE_NOT_FOUND, msg_num);
1101 return(om_no_such_msg);
1104 retcode = CtdlOutputPreLoadedMsg(
1105 TheMessage, msg_num, mode,
1106 headers_only, do_proto, crlf);
1108 CtdlFreeMessage(TheMessage);
1115 * Get a message off disk. (returns om_* values found in msgbase.h)
1118 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1120 int mode, /* how would you like that message? */
1121 int headers_only, /* eschew the message body? */
1122 int do_proto, /* do Citadel protocol responses? */
1123 int crlf /* Use CRLF newlines instead of LF? */
1129 char display_name[SIZ];
1131 char *nl; /* newline string */
1133 int subject_found = 0;
1135 /* buffers needed for RFC822 translation */
1142 char datestamp[SIZ];
1145 snprintf(mid, sizeof mid, "%ld", msg_num);
1146 nl = (crlf ? "\r\n" : "\n");
1148 if (!is_valid_message(TheMessage)) {
1149 lprintf(CTDL_ERR, "ERROR: invalid preloaded message for output\n");
1150 return(om_no_such_msg);
1153 /* Are we downloading a MIME component? */
1154 if (mode == MT_DOWNLOAD) {
1155 if (TheMessage->cm_format_type != FMT_RFC822) {
1157 cprintf("%d This is not a MIME message.\n",
1158 ERROR + ILLEGAL_VALUE);
1159 } else if (CC->download_fp != NULL) {
1160 if (do_proto) cprintf(
1161 "%d You already have a download open.\n",
1162 ERROR + RESOURCE_BUSY);
1164 /* Parse the message text component */
1165 mptr = TheMessage->cm_fields['M'];
1166 mime_parser(mptr, NULL,
1167 *mime_download, NULL, NULL,
1169 /* If there's no file open by this time, the requested
1170 * section wasn't found, so print an error
1172 if (CC->download_fp == NULL) {
1173 if (do_proto) cprintf(
1174 "%d Section %s not found.\n",
1175 ERROR + FILE_NOT_FOUND,
1179 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1182 /* now for the user-mode message reading loops */
1183 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1185 /* Does the caller want to skip the headers? */
1186 if (headers_only == HEADERS_NONE) goto START_TEXT;
1188 /* Tell the client which format type we're using. */
1189 if ( (mode == MT_CITADEL) && (do_proto) ) {
1190 cprintf("type=%d\n", TheMessage->cm_format_type);
1193 /* nhdr=yes means that we're only displaying headers, no body */
1194 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1195 && (mode == MT_CITADEL)
1198 cprintf("nhdr=yes\n");
1201 /* begin header processing loop for Citadel message format */
1203 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1205 strcpy(display_name, "<unknown>");
1206 if (TheMessage->cm_fields['A']) {
1207 strcpy(buf, TheMessage->cm_fields['A']);
1208 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1209 strcpy(display_name, "****");
1211 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1212 strcpy(display_name, "anonymous");
1215 strcpy(display_name, buf);
1217 if ((is_room_aide())
1218 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1219 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1220 size_t tmp = strlen(display_name);
1221 snprintf(&display_name[tmp],
1222 sizeof display_name - tmp,
1227 /* Don't show Internet address for users on the
1228 * local Citadel network.
1231 if (TheMessage->cm_fields['N'] != NULL)
1232 if (strlen(TheMessage->cm_fields['N']) > 0)
1233 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1237 /* Now spew the header fields in the order we like them. */
1238 strcpy(allkeys, FORDER);
1239 for (i=0; i<strlen(allkeys); ++i) {
1240 k = (int) allkeys[i];
1242 if ( (TheMessage->cm_fields[k] != NULL)
1243 && (msgkeys[k] != NULL) ) {
1245 if (do_proto) cprintf("%s=%s\n",
1249 else if ((k == 'F') && (suppress_f)) {
1252 /* Masquerade display name if needed */
1254 if (do_proto) cprintf("%s=%s\n",
1256 TheMessage->cm_fields[k]
1265 /* begin header processing loop for RFC822 transfer format */
1270 strcpy(snode, NODENAME);
1271 strcpy(lnode, HUMANNODE);
1272 if (mode == MT_RFC822) {
1273 cprintf("X-UIDL: %ld%s", msg_num, nl);
1274 for (i = 0; i < 256; ++i) {
1275 if (TheMessage->cm_fields[i]) {
1276 mptr = TheMessage->cm_fields[i];
1279 strcpy(luser, mptr);
1280 strcpy(suser, mptr);
1283 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1284 into thinking that mail messages are newsgroup messages instead. When we
1285 add NNTP support back into Citadel we'll have to add code to only output
1286 this field when appropriate.
1287 else if (i == 'P') {
1288 cprintf("Path: %s%s", mptr, nl);
1291 else if (i == 'U') {
1292 cprintf("Subject: %s%s", mptr, nl);
1296 safestrncpy(mid, mptr, sizeof mid);
1298 safestrncpy(lnode, mptr, sizeof lnode);
1300 safestrncpy(fuser, mptr, sizeof fuser);
1302 cprintf("X-Citadel-Room: %s%s",
1305 safestrncpy(snode, mptr, sizeof snode);
1307 cprintf("To: %s%s", mptr, nl);
1308 else if (i == 'T') {
1309 datestring(datestamp, sizeof datestamp,
1310 atol(mptr), DATESTRING_RFC822);
1311 cprintf("Date: %s%s", datestamp, nl);
1315 if (subject_found == 0) {
1316 cprintf("Subject: (no subject)%s", nl);
1320 for (i=0; i<strlen(suser); ++i) {
1321 suser[i] = tolower(suser[i]);
1322 if (!isalnum(suser[i])) suser[i]='_';
1325 if (mode == MT_RFC822) {
1326 if (!strcasecmp(snode, NODENAME)) {
1327 strcpy(snode, FQDN);
1330 /* Construct a fun message id */
1331 cprintf("Message-ID: <%s", mid);
1332 if (strchr(mid, '@')==NULL) {
1333 cprintf("@%s", snode);
1337 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1338 cprintf("From: x@x.org (----)%s", nl);
1340 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1341 cprintf("From: x@x.org (anonymous)%s", nl);
1343 else if (strlen(fuser) > 0) {
1344 cprintf("From: %s (%s)%s", fuser, luser, nl);
1347 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1350 cprintf("Organization: %s%s", lnode, nl);
1352 /* Blank line signifying RFC822 end-of-headers */
1353 if (TheMessage->cm_format_type != FMT_RFC822) {
1358 /* end header processing loop ... at this point, we're in the text */
1360 if (headers_only == HEADERS_FAST) goto DONE;
1361 mptr = TheMessage->cm_fields['M'];
1363 /* Tell the client about the MIME parts in this message */
1364 if (TheMessage->cm_format_type == FMT_RFC822) {
1365 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1366 mime_parser(mptr, NULL,
1372 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1373 /* FIXME ... we have to put some code in here to avoid
1374 * printing duplicate header information when both
1375 * Citadel and RFC822 headers exist. Preference should
1376 * probably be given to the RFC822 headers.
1378 int done_rfc822_hdrs = 0;
1379 while (ch=*(mptr++), ch!=0) {
1384 if (!done_rfc822_hdrs) {
1385 if (headers_only != HEADERS_NONE) {
1390 if (headers_only != HEADERS_ONLY) {
1394 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1395 done_rfc822_hdrs = 1;
1399 if (done_rfc822_hdrs) {
1400 if (headers_only != HEADERS_NONE) {
1405 if (headers_only != HEADERS_ONLY) {
1409 if ((*mptr == 13) || (*mptr == 10)) {
1410 done_rfc822_hdrs = 1;
1418 if (headers_only == HEADERS_ONLY) {
1422 /* signify start of msg text */
1423 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1424 if (do_proto) cprintf("text\n");
1427 /* If the format type on disk is 1 (fixed-format), then we want
1428 * everything to be output completely literally ... regardless of
1429 * what message transfer format is in use.
1431 if (TheMessage->cm_format_type == FMT_FIXED) {
1432 if (mode == MT_MIME) {
1433 cprintf("Content-type: text/plain\n\n");
1436 while (ch = *mptr++, ch > 0) {
1439 if ((ch == 10) || (strlen(buf) > 250)) {
1440 cprintf("%s%s", buf, nl);
1443 buf[strlen(buf) + 1] = 0;
1444 buf[strlen(buf)] = ch;
1447 if (strlen(buf) > 0)
1448 cprintf("%s%s", buf, nl);
1451 /* If the message on disk is format 0 (Citadel vari-format), we
1452 * output using the formatter at 80 columns. This is the final output
1453 * form if the transfer format is RFC822, but if the transfer format
1454 * is Citadel proprietary, it'll still work, because the indentation
1455 * for new paragraphs is correct and the client will reformat the
1456 * message to the reader's screen width.
1458 if (TheMessage->cm_format_type == FMT_CITADEL) {
1459 if (mode == MT_MIME) {
1460 cprintf("Content-type: text/x-citadel-variformat\n\n");
1462 memfmout(80, mptr, 0, nl);
1465 /* If the message on disk is format 4 (MIME), we've gotta hand it
1466 * off to the MIME parser. The client has already been told that
1467 * this message is format 1 (fixed format), so the callback function
1468 * we use will display those parts as-is.
1470 if (TheMessage->cm_format_type == FMT_RFC822) {
1471 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1472 memset(ma, 0, sizeof(struct ma_info));
1474 if (mode == MT_MIME) {
1475 strcpy(ma->chosen_part, "1");
1476 mime_parser(mptr, NULL,
1477 *choose_preferred, *fixed_output_pre,
1478 *fixed_output_post, NULL, 0);
1479 mime_parser(mptr, NULL,
1480 *output_preferred, NULL, NULL, NULL, 0);
1483 mime_parser(mptr, NULL,
1484 *fixed_output, *fixed_output_pre,
1485 *fixed_output_post, NULL, 0);
1489 DONE: /* now we're done */
1490 if (do_proto) cprintf("000\n");
1497 * display a message (mode 0 - Citadel proprietary)
1499 void cmd_msg0(char *cmdbuf)
1502 int headers_only = HEADERS_ALL;
1504 msgid = extract_long(cmdbuf, 0);
1505 headers_only = extract_int(cmdbuf, 1);
1507 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1513 * display a message (mode 2 - RFC822)
1515 void cmd_msg2(char *cmdbuf)
1518 int headers_only = HEADERS_ALL;
1520 msgid = extract_long(cmdbuf, 0);
1521 headers_only = extract_int(cmdbuf, 1);
1523 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1529 * display a message (mode 3 - IGnet raw format - internal programs only)
1531 void cmd_msg3(char *cmdbuf)
1534 struct CtdlMessage *msg;
1537 if (CC->internal_pgm == 0) {
1538 cprintf("%d This command is for internal programs only.\n",
1539 ERROR + HIGHER_ACCESS_REQUIRED);
1543 msgnum = extract_long(cmdbuf, 0);
1544 msg = CtdlFetchMessage(msgnum);
1546 cprintf("%d Message %ld not found.\n",
1547 ERROR + MESSAGE_NOT_FOUND, msgnum);
1551 serialize_message(&smr, msg);
1552 CtdlFreeMessage(msg);
1555 cprintf("%d Unable to serialize message\n",
1556 ERROR + INTERNAL_ERROR);
1560 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1561 client_write(smr.ser, smr.len);
1568 * Display a message using MIME content types
1570 void cmd_msg4(char *cmdbuf)
1574 msgid = extract_long(cmdbuf, 0);
1575 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1581 * Client tells us its preferred message format(s)
1583 void cmd_msgp(char *cmdbuf)
1585 safestrncpy(CC->preferred_formats, cmdbuf,
1586 sizeof(CC->preferred_formats));
1587 cprintf("%d ok\n", CIT_OK);
1592 * Open a component of a MIME message as a download file
1594 void cmd_opna(char *cmdbuf)
1598 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1600 msgid = extract_long(cmdbuf, 0);
1601 extract(desired_section, cmdbuf, 1);
1603 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1608 * Save a message pointer into a specified room
1609 * (Returns 0 for success, nonzero for failure)
1610 * roomname may be NULL to use the current room
1612 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1614 char hold_rm[ROOMNAMELEN];
1615 struct cdbdata *cdbfr;
1618 long highest_msg = 0L;
1619 struct CtdlMessage *msg = NULL;
1621 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1622 roomname, msgid, flags);
1624 strcpy(hold_rm, CC->room.QRname);
1626 /* We may need to check to see if this message is real */
1627 if ( (flags & SM_VERIFY_GOODNESS)
1628 || (flags & SM_DO_REPL_CHECK)
1630 msg = CtdlFetchMessage(msgid);
1631 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1634 /* Perform replication checks if necessary */
1635 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1637 if (getroom(&CC->room,
1638 ((roomname != NULL) ? roomname : CC->room.QRname) )
1640 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1641 if (msg != NULL) CtdlFreeMessage(msg);
1642 return(ERROR + ROOM_NOT_FOUND);
1645 if (ReplicationChecks(msg) != 0) {
1646 getroom(&CC->room, hold_rm);
1647 if (msg != NULL) CtdlFreeMessage(msg);
1648 lprintf(CTDL_DEBUG, "Did replication, and newer exists\n");
1653 /* Now the regular stuff */
1654 if (lgetroom(&CC->room,
1655 ((roomname != NULL) ? roomname : CC->room.QRname) )
1657 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1658 if (msg != NULL) CtdlFreeMessage(msg);
1659 return(ERROR + ROOM_NOT_FOUND);
1662 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1663 if (cdbfr == NULL) {
1667 msglist = malloc(cdbfr->len);
1668 if (msglist == NULL)
1669 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1670 num_msgs = cdbfr->len / sizeof(long);
1671 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1676 /* Make sure the message doesn't already exist in this room. It
1677 * is absolutely taboo to have more than one reference to the same
1678 * message in a room.
1680 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1681 if (msglist[i] == msgid) {
1682 lputroom(&CC->room); /* unlock the room */
1683 getroom(&CC->room, hold_rm);
1684 if (msg != NULL) CtdlFreeMessage(msg);
1686 return(ERROR + ALREADY_EXISTS);
1690 /* Now add the new message */
1692 msglist = realloc(msglist,
1693 (num_msgs * sizeof(long)));
1695 if (msglist == NULL) {
1696 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1698 msglist[num_msgs - 1] = msgid;
1700 /* Sort the message list, so all the msgid's are in order */
1701 num_msgs = sort_msglist(msglist, num_msgs);
1703 /* Determine the highest message number */
1704 highest_msg = msglist[num_msgs - 1];
1706 /* Write it back to disk. */
1707 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long),
1708 msglist, num_msgs * sizeof(long));
1710 /* Free up the memory we used. */
1713 /* Update the highest-message pointer and unlock the room. */
1714 CC->room.QRhighest = highest_msg;
1715 lputroom(&CC->room);
1716 getroom(&CC->room, hold_rm);
1718 /* Bump the reference count for this message. */
1719 if ((flags & SM_DONT_BUMP_REF)==0) {
1720 AdjRefCount(msgid, +1);
1723 /* Return success. */
1724 if (msg != NULL) CtdlFreeMessage(msg);
1731 * Message base operation to send a message to the master file
1732 * (returns new message number)
1734 * This is the back end for CtdlSubmitMsg() and should not be directly
1735 * called by server-side modules.
1738 long send_message(struct CtdlMessage *msg) {
1746 /* Get a new message number */
1747 newmsgid = get_new_message_number();
1748 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1750 /* Generate an ID if we don't have one already */
1751 if (msg->cm_fields['I']==NULL) {
1752 msg->cm_fields['I'] = strdup(msgidbuf);
1755 /* If the message is big, set its body aside for storage elsewhere */
1756 if (msg->cm_fields['M'] != NULL) {
1757 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1759 holdM = msg->cm_fields['M'];
1760 msg->cm_fields['M'] = NULL;
1764 /* Serialize our data structure for storage in the database */
1765 serialize_message(&smr, msg);
1768 msg->cm_fields['M'] = holdM;
1772 cprintf("%d Unable to serialize message\n",
1773 ERROR + INTERNAL_ERROR);
1777 /* Write our little bundle of joy into the message base */
1778 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1779 smr.ser, smr.len) < 0) {
1780 lprintf(CTDL_ERR, "Can't store message\n");
1784 cdb_store(CDB_BIGMSGS, &newmsgid, sizeof(long),
1785 holdM, strlen(holdM) );
1790 /* Free the memory we used for the serialized message */
1793 /* Return the *local* message ID to the caller
1794 * (even if we're storing an incoming network message)
1802 * Serialize a struct CtdlMessage into the format used on disk and network.
1804 * This function loads up a "struct ser_ret" (defined in server.h) which
1805 * contains the length of the serialized message and a pointer to the
1806 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1808 void serialize_message(struct ser_ret *ret, /* return values */
1809 struct CtdlMessage *msg) /* unserialized msg */
1813 static char *forder = FORDER;
1815 if (is_valid_message(msg) == 0) return; /* self check */
1818 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1819 ret->len = ret->len +
1820 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1822 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1823 ret->ser = malloc(ret->len);
1824 if (ret->ser == NULL) {
1830 ret->ser[1] = msg->cm_anon_type;
1831 ret->ser[2] = msg->cm_format_type;
1834 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1835 ret->ser[wlen++] = (char)forder[i];
1836 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1837 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1839 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1840 (long)ret->len, (long)wlen);
1848 * Back end for the ReplicationChecks() function
1850 void check_repl(long msgnum, void *userdata) {
1851 struct CtdlMessage *msg;
1852 time_t timestamp = (-1L);
1854 lprintf(CTDL_DEBUG, "check_repl() found message %ld\n", msgnum);
1855 msg = CtdlFetchMessage(msgnum);
1856 if (msg == NULL) return;
1857 if (msg->cm_fields['T'] != NULL) {
1858 timestamp = atol(msg->cm_fields['T']);
1860 CtdlFreeMessage(msg);
1862 if (timestamp > msg_repl->highest) {
1863 msg_repl->highest = timestamp; /* newer! */
1864 lprintf(CTDL_DEBUG, "newer!\n");
1867 lprintf(CTDL_DEBUG, "older!\n");
1869 /* Existing isn't newer? Then delete the old one(s). */
1870 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1875 * Check to see if any messages already exist which carry the same Extended ID
1879 * -> With older timestamps: delete them and return 0. Message will be saved.
1880 * -> With newer timestamps: return 1. Message save will be aborted.
1882 int ReplicationChecks(struct CtdlMessage *msg) {
1883 struct CtdlMessage *template;
1886 lprintf(CTDL_DEBUG, "ReplicationChecks() started\n");
1887 /* No extended id? Don't do anything. */
1888 if (msg->cm_fields['E'] == NULL) return 0;
1889 if (strlen(msg->cm_fields['E']) == 0) return 0;
1890 lprintf(CTDL_DEBUG, "Extended ID: <%s>\n", msg->cm_fields['E']);
1892 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1893 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1894 msg_repl->highest = atol(msg->cm_fields['T']);
1896 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1897 memset(template, 0, sizeof(struct CtdlMessage));
1898 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1900 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1902 /* If a newer message exists with the same Extended ID, abort
1905 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1909 CtdlFreeMessage(template);
1910 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1918 * Save a message to disk and submit it into the delivery system.
1920 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1921 struct recptypes *recps, /* recipients (if mail) */
1922 char *force /* force a particular room? */
1925 char hold_rm[ROOMNAMELEN];
1926 char actual_rm[ROOMNAMELEN];
1927 char force_room[ROOMNAMELEN];
1928 char content_type[SIZ]; /* We have to learn this */
1929 char recipient[SIZ];
1932 struct ctdluser userbuf;
1934 struct MetaData smi;
1935 FILE *network_fp = NULL;
1936 static int seqnum = 1;
1937 struct CtdlMessage *imsg = NULL;
1940 char *hold_R, *hold_D;
1942 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1943 if (is_valid_message(msg) == 0) return(-1); /* self check */
1945 /* If this message has no timestamp, we take the liberty of
1946 * giving it one, right now.
1948 if (msg->cm_fields['T'] == NULL) {
1949 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1950 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1951 msg->cm_fields['T'] = strdup(aaa);
1954 /* If this message has no path, we generate one.
1956 if (msg->cm_fields['P'] == NULL) {
1957 lprintf(CTDL_DEBUG, "Generating path\n");
1958 if (msg->cm_fields['A'] != NULL) {
1959 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1960 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1961 if (isspace(msg->cm_fields['P'][a])) {
1962 msg->cm_fields['P'][a] = ' ';
1967 msg->cm_fields['P'] = strdup("unknown");
1971 if (force == NULL) {
1972 strcpy(force_room, "");
1975 strcpy(force_room, force);
1978 /* Learn about what's inside, because it's what's inside that counts */
1979 lprintf(CTDL_DEBUG, "Learning what's inside\n");
1980 if (msg->cm_fields['M'] == NULL) {
1981 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1985 switch (msg->cm_format_type) {
1987 strcpy(content_type, "text/x-citadel-variformat");
1990 strcpy(content_type, "text/plain");
1993 strcpy(content_type, "text/plain");
1994 /* advance past header fields */
1995 mptr = msg->cm_fields['M'];
1998 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1999 safestrncpy(content_type, mptr,
2000 sizeof(content_type));
2001 strcpy(content_type, &content_type[14]);
2002 for (a = 0; a < strlen(content_type); ++a)
2003 if ((content_type[a] == ';')
2004 || (content_type[a] == ' ')
2005 || (content_type[a] == 13)
2006 || (content_type[a] == 10))
2007 content_type[a] = 0;
2014 /* Goto the correct room */
2015 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2016 strcpy(hold_rm, CC->room.QRname);
2017 strcpy(actual_rm, CC->room.QRname);
2018 if (recps != NULL) {
2019 strcpy(actual_rm, SENTITEMS);
2022 /* If the user is a twit, move to the twit room for posting */
2023 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2024 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2026 if (CC->user.axlevel == 2) {
2027 strcpy(hold_rm, actual_rm);
2028 strcpy(actual_rm, config.c_twitroom);
2032 /* ...or if this message is destined for Aide> then go there. */
2033 if (strlen(force_room) > 0) {
2034 strcpy(actual_rm, force_room);
2037 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2038 if (strcasecmp(actual_rm, CC->room.QRname)) {
2039 getroom(&CC->room, actual_rm);
2043 * If this message has no O (room) field, generate one.
2045 if (msg->cm_fields['O'] == NULL) {
2046 msg->cm_fields['O'] = strdup(CC->room.QRname);
2049 /* Perform "before save" hooks (aborting if any return nonzero) */
2050 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2051 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
2053 /* If this message has an Extended ID, perform replication checks */
2054 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2055 if (ReplicationChecks(msg) > 0) return(-1);
2057 /* Save it to disk */
2058 lprintf(CTDL_DEBUG, "Saving to disk\n");
2059 newmsgid = send_message(msg);
2060 if (newmsgid <= 0L) return(-1);
2062 /* Write a supplemental message info record. This doesn't have to
2063 * be a critical section because nobody else knows about this message
2066 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2067 memset(&smi, 0, sizeof(struct MetaData));
2068 smi.meta_msgnum = newmsgid;
2069 smi.meta_refcount = 0;
2070 safestrncpy(smi.meta_content_type, content_type, 64);
2073 /* Now figure out where to store the pointers */
2074 lprintf(CTDL_DEBUG, "Storing pointers\n");
2076 /* If this is being done by the networker delivering a private
2077 * message, we want to BYPASS saving the sender's copy (because there
2078 * is no local sender; it would otherwise go to the Trashcan).
2080 if ((!CC->internal_pgm) || (recps == NULL)) {
2081 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2082 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2083 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2087 /* For internet mail, drop a copy in the outbound queue room */
2089 if (recps->num_internet > 0) {
2090 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2093 /* If other rooms are specified, drop them there too. */
2095 if (recps->num_room > 0)
2096 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2097 extract(recipient, recps->recp_room, i);
2098 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2099 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2102 /* Bump this user's messages posted counter. */
2103 lprintf(CTDL_DEBUG, "Updating user\n");
2104 lgetuser(&CC->user, CC->curr_user);
2105 CC->user.posted = CC->user.posted + 1;
2106 lputuser(&CC->user);
2108 /* If this is private, local mail, make a copy in the
2109 * recipient's mailbox and bump the reference count.
2112 if (recps->num_local > 0)
2113 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2114 extract(recipient, recps->recp_local, i);
2115 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2117 if (getuser(&userbuf, recipient) == 0) {
2118 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2119 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2120 BumpNewMailCounter(userbuf.usernum);
2123 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2124 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2128 /* Perform "after save" hooks */
2129 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2130 PerformMessageHooks(msg, EVT_AFTERSAVE);
2132 /* For IGnet mail, we have to save a new copy into the spooler for
2133 * each recipient, with the R and D fields set to the recipient and
2134 * destination-node. This has two ugly side effects: all other
2135 * recipients end up being unlisted in this recipient's copy of the
2136 * message, and it has to deliver multiple messages to the same
2137 * node. We'll revisit this again in a year or so when everyone has
2138 * a network spool receiver that can handle the new style messages.
2141 if (recps->num_ignet > 0)
2142 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2143 extract(recipient, recps->recp_ignet, i);
2145 hold_R = msg->cm_fields['R'];
2146 hold_D = msg->cm_fields['D'];
2147 msg->cm_fields['R'] = malloc(SIZ);
2148 msg->cm_fields['D'] = malloc(SIZ);
2149 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2150 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2152 serialize_message(&smr, msg);
2154 snprintf(aaa, sizeof aaa,
2155 "./network/spoolin/netmail.%04lx.%04x.%04x",
2156 (long) getpid(), CC->cs_pid, ++seqnum);
2157 network_fp = fopen(aaa, "wb+");
2158 if (network_fp != NULL) {
2159 fwrite(smr.ser, smr.len, 1, network_fp);
2165 free(msg->cm_fields['R']);
2166 free(msg->cm_fields['D']);
2167 msg->cm_fields['R'] = hold_R;
2168 msg->cm_fields['D'] = hold_D;
2171 /* Go back to the room we started from */
2172 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2173 if (strcasecmp(hold_rm, CC->room.QRname))
2174 getroom(&CC->room, hold_rm);
2176 /* For internet mail, generate delivery instructions.
2177 * Yes, this is recursive. Deal with it. Infinite recursion does
2178 * not happen because the delivery instructions message does not
2179 * contain a recipient.
2182 if (recps->num_internet > 0) {
2183 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2184 instr = malloc(SIZ * 2);
2185 snprintf(instr, SIZ * 2,
2186 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2188 SPOOLMIME, newmsgid, (long)time(NULL),
2189 msg->cm_fields['A'], msg->cm_fields['N']
2192 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2193 size_t tmp = strlen(instr);
2194 extract(recipient, recps->recp_internet, i);
2195 snprintf(&instr[tmp], SIZ * 2 - tmp,
2196 "remote|%s|0||\n", recipient);
2199 imsg = malloc(sizeof(struct CtdlMessage));
2200 memset(imsg, 0, sizeof(struct CtdlMessage));
2201 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2202 imsg->cm_anon_type = MES_NORMAL;
2203 imsg->cm_format_type = FMT_RFC822;
2204 imsg->cm_fields['A'] = strdup("Citadel");
2205 imsg->cm_fields['M'] = instr;
2206 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2207 CtdlFreeMessage(imsg);
2216 * Convenience function for generating small administrative messages.
2218 void quickie_message(char *from, char *to, char *room, char *text,
2219 int format_type, char *subject)
2221 struct CtdlMessage *msg;
2222 struct recptypes *recp = NULL;
2224 msg = malloc(sizeof(struct CtdlMessage));
2225 memset(msg, 0, sizeof(struct CtdlMessage));
2226 msg->cm_magic = CTDLMESSAGE_MAGIC;
2227 msg->cm_anon_type = MES_NORMAL;
2228 msg->cm_format_type = format_type;
2229 msg->cm_fields['A'] = strdup(from);
2230 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2231 msg->cm_fields['N'] = strdup(NODENAME);
2233 msg->cm_fields['R'] = strdup(to);
2234 recp = validate_recipients(to);
2236 if (subject != NULL) {
2237 msg->cm_fields['U'] = strdup(subject);
2239 msg->cm_fields['M'] = strdup(text);
2241 CtdlSubmitMsg(msg, recp, room);
2242 CtdlFreeMessage(msg);
2243 if (recp != NULL) free(recp);
2249 * Back end function used by CtdlMakeMessage() and similar functions
2251 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2252 size_t maxlen, /* maximum message length */
2253 char *exist, /* if non-null, append to it;
2254 exist is ALWAYS freed */
2255 int crlf /* CRLF newlines instead of LF */
2259 size_t message_len = 0;
2260 size_t buffer_len = 0;
2266 if (exist == NULL) {
2273 message_len = strlen(exist);
2274 buffer_len = message_len + 4096;
2275 m = realloc(exist, buffer_len);
2282 /* flush the input if we have nowhere to store it */
2287 /* read in the lines of message text one by one */
2289 if (client_gets(buf) < 1) finished = 1;
2290 if (!strcmp(buf, terminator)) finished = 1;
2292 strcat(buf, "\r\n");
2298 if ( (!flushing) && (!finished) ) {
2299 /* Measure the line */
2300 linelen = strlen(buf);
2302 /* augment the buffer if we have to */
2303 if ((message_len + linelen) >= buffer_len) {
2304 ptr = realloc(m, (buffer_len * 2) );
2305 if (ptr == NULL) { /* flush if can't allocate */
2308 buffer_len = (buffer_len * 2);
2310 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2314 /* Add the new line to the buffer. NOTE: this loop must avoid
2315 * using functions like strcat() and strlen() because they
2316 * traverse the entire buffer upon every call, and doing that
2317 * for a multi-megabyte message slows it down beyond usability.
2319 strcpy(&m[message_len], buf);
2320 message_len += linelen;
2323 /* if we've hit the max msg length, flush the rest */
2324 if (message_len >= maxlen) flushing = 1;
2326 } while (!finished);
2334 * Build a binary message to be saved on disk.
2335 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2336 * will become part of the message. This means you are no longer
2337 * responsible for managing that memory -- it will be freed along with
2338 * the rest of the fields when CtdlFreeMessage() is called.)
2341 struct CtdlMessage *CtdlMakeMessage(
2342 struct ctdluser *author, /* author's user structure */
2343 char *recipient, /* NULL if it's not mail */
2344 char *room, /* room where it's going */
2345 int type, /* see MES_ types in header file */
2346 int format_type, /* variformat, plain text, MIME... */
2347 char *fake_name, /* who we're masquerading as */
2348 char *subject, /* Subject (optional) */
2349 char *preformatted_text /* ...or NULL to read text from client */
2351 char dest_node[SIZ];
2353 struct CtdlMessage *msg;
2355 msg = malloc(sizeof(struct CtdlMessage));
2356 memset(msg, 0, sizeof(struct CtdlMessage));
2357 msg->cm_magic = CTDLMESSAGE_MAGIC;
2358 msg->cm_anon_type = type;
2359 msg->cm_format_type = format_type;
2361 /* Don't confuse the poor folks if it's not routed mail. */
2362 strcpy(dest_node, "");
2366 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2367 msg->cm_fields['P'] = strdup(buf);
2369 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2370 msg->cm_fields['T'] = strdup(buf);
2372 if (fake_name[0]) /* author */
2373 msg->cm_fields['A'] = strdup(fake_name);
2375 msg->cm_fields['A'] = strdup(author->fullname);
2377 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2378 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2381 msg->cm_fields['O'] = strdup(CC->room.QRname);
2384 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2385 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2387 if (recipient[0] != 0) {
2388 msg->cm_fields['R'] = strdup(recipient);
2390 if (dest_node[0] != 0) {
2391 msg->cm_fields['D'] = strdup(dest_node);
2394 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2395 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2398 if (subject != NULL) {
2400 if (strlen(subject) > 0) {
2401 msg->cm_fields['U'] = strdup(subject);
2405 if (preformatted_text != NULL) {
2406 msg->cm_fields['M'] = preformatted_text;
2409 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2410 config.c_maxmsglen, NULL, 0);
2418 * Check to see whether we have permission to post a message in the current
2419 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2420 * returns 0 on success.
2422 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2424 if (!(CC->logged_in)) {
2425 snprintf(errmsgbuf, n, "Not logged in.");
2426 return (ERROR + NOT_LOGGED_IN);
2429 if ((CC->user.axlevel < 2)
2430 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2431 snprintf(errmsgbuf, n, "Need to be validated to enter "
2432 "(except in %s> to sysop)", MAILROOM);
2433 return (ERROR + HIGHER_ACCESS_REQUIRED);
2436 if ((CC->user.axlevel < 4)
2437 && (CC->room.QRflags & QR_NETWORK)) {
2438 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2439 return (ERROR + HIGHER_ACCESS_REQUIRED);
2442 if ((CC->user.axlevel < 6)
2443 && (CC->room.QRflags & QR_READONLY)) {
2444 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2445 return (ERROR + HIGHER_ACCESS_REQUIRED);
2448 strcpy(errmsgbuf, "Ok");
2454 * Check to see if the specified user has Internet mail permission
2455 * (returns nonzero if permission is granted)
2457 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2459 /* Globally enabled? */
2460 if (config.c_restrict == 0) return(1);
2462 /* User flagged ok? */
2463 if (who->flags & US_INTERNET) return(2);
2465 /* Aide level access? */
2466 if (who->axlevel >= 6) return(3);
2468 /* No mail for you! */
2475 * Validate recipients, count delivery types and errors, and handle aliasing
2476 * FIXME check for dupes!!!!!
2478 struct recptypes *validate_recipients(char *recipients) {
2479 struct recptypes *ret;
2480 char this_recp[SIZ];
2481 char this_recp_cooked[SIZ];
2487 struct ctdluser tempUS;
2488 struct ctdlroom tempQR;
2491 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2492 if (ret == NULL) return(NULL);
2493 memset(ret, 0, sizeof(struct recptypes));
2496 ret->num_internet = 0;
2501 if (recipients == NULL) {
2504 else if (strlen(recipients) == 0) {
2508 /* Change all valid separator characters to commas */
2509 for (i=0; i<strlen(recipients); ++i) {
2510 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2511 recipients[i] = ',';
2516 num_recps = num_tokens(recipients, ',');
2519 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2520 extract_token(this_recp, recipients, i, ',');
2522 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2523 mailtype = alias(this_recp);
2524 mailtype = alias(this_recp);
2525 mailtype = alias(this_recp);
2526 for (j=0; j<=strlen(this_recp); ++j) {
2527 if (this_recp[j]=='_') {
2528 this_recp_cooked[j] = ' ';
2531 this_recp_cooked[j] = this_recp[j];
2537 if (!strcasecmp(this_recp, "sysop")) {
2539 strcpy(this_recp, config.c_aideroom);
2540 if (strlen(ret->recp_room) > 0) {
2541 strcat(ret->recp_room, "|");
2543 strcat(ret->recp_room, this_recp);
2545 else if (getuser(&tempUS, this_recp) == 0) {
2547 strcpy(this_recp, tempUS.fullname);
2548 if (strlen(ret->recp_local) > 0) {
2549 strcat(ret->recp_local, "|");
2551 strcat(ret->recp_local, this_recp);
2553 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2555 strcpy(this_recp, tempUS.fullname);
2556 if (strlen(ret->recp_local) > 0) {
2557 strcat(ret->recp_local, "|");
2559 strcat(ret->recp_local, this_recp);
2561 else if ( (!strncasecmp(this_recp, "room_", 5))
2562 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2564 if (strlen(ret->recp_room) > 0) {
2565 strcat(ret->recp_room, "|");
2567 strcat(ret->recp_room, &this_recp_cooked[5]);
2575 /* Yes, you're reading this correctly: if the target
2576 * domain points back to the local system or an attached
2577 * Citadel directory, the address is invalid. That's
2578 * because if the address were valid, we would have
2579 * already translated it to a local address by now.
2581 if (IsDirectory(this_recp)) {
2586 ++ret->num_internet;
2587 if (strlen(ret->recp_internet) > 0) {
2588 strcat(ret->recp_internet, "|");
2590 strcat(ret->recp_internet, this_recp);
2595 if (strlen(ret->recp_ignet) > 0) {
2596 strcat(ret->recp_ignet, "|");
2598 strcat(ret->recp_ignet, this_recp);
2606 if (strlen(ret->errormsg) == 0) {
2607 snprintf(append, sizeof append,
2608 "Invalid recipient: %s",
2612 snprintf(append, sizeof append,
2615 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2616 strcat(ret->errormsg, append);
2620 if (strlen(ret->display_recp) == 0) {
2621 strcpy(append, this_recp);
2624 snprintf(append, sizeof append, ", %s",
2627 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2628 strcat(ret->display_recp, append);
2633 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2634 ret->num_room + ret->num_error) == 0) {
2636 strcpy(ret->errormsg, "No recipients specified.");
2639 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2640 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2641 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2642 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2643 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2644 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2652 * message entry - mode 0 (normal)
2654 void cmd_ent0(char *entargs)
2658 char masquerade_as[SIZ];
2660 int format_type = 0;
2661 char newusername[SIZ];
2662 struct CtdlMessage *msg;
2666 struct recptypes *valid = NULL;
2669 post = extract_int(entargs, 0);
2670 extract(recp, entargs, 1);
2671 anon_flag = extract_int(entargs, 2);
2672 format_type = extract_int(entargs, 3);
2673 extract(subject, entargs, 4);
2675 /* first check to make sure the request is valid. */
2677 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2679 cprintf("%d %s\n", err, errmsg);
2683 /* Check some other permission type things. */
2686 if (CC->user.axlevel < 6) {
2687 cprintf("%d You don't have permission to masquerade.\n",
2688 ERROR + HIGHER_ACCESS_REQUIRED);
2691 extract(newusername, entargs, 5);
2692 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2693 safestrncpy(CC->fake_postname, newusername,
2694 sizeof(CC->fake_postname) );
2695 cprintf("%d ok\n", CIT_OK);
2698 CC->cs_flags |= CS_POSTING;
2700 /* In the Mail> room we have to behave a little differently --
2701 * make sure the user has specified at least one recipient. Then
2702 * validate the recipient(s).
2704 if ( (CC->room.QRflags & QR_MAILBOX)
2705 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2707 if (CC->user.axlevel < 2) {
2708 strcpy(recp, "sysop");
2711 valid = validate_recipients(recp);
2712 if (valid->num_error > 0) {
2714 ERROR + NO_SUCH_USER, valid->errormsg);
2718 if (valid->num_internet > 0) {
2719 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2720 cprintf("%d You do not have permission "
2721 "to send Internet mail.\n",
2722 ERROR + HIGHER_ACCESS_REQUIRED);
2728 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2729 && (CC->user.axlevel < 4) ) {
2730 cprintf("%d Higher access required for network mail.\n",
2731 ERROR + HIGHER_ACCESS_REQUIRED);
2736 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2737 && ((CC->user.flags & US_INTERNET) == 0)
2738 && (!CC->internal_pgm)) {
2739 cprintf("%d You don't have access to Internet mail.\n",
2740 ERROR + HIGHER_ACCESS_REQUIRED);
2747 /* Is this a room which has anonymous-only or anonymous-option? */
2748 anonymous = MES_NORMAL;
2749 if (CC->room.QRflags & QR_ANONONLY) {
2750 anonymous = MES_ANONONLY;
2752 if (CC->room.QRflags & QR_ANONOPT) {
2753 if (anon_flag == 1) { /* only if the user requested it */
2754 anonymous = MES_ANONOPT;
2758 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2762 /* If we're only checking the validity of the request, return
2763 * success without creating the message.
2766 cprintf("%d %s\n", CIT_OK,
2767 ((valid != NULL) ? valid->display_recp : "") );
2772 /* Handle author masquerading */
2773 if (CC->fake_postname[0]) {
2774 strcpy(masquerade_as, CC->fake_postname);
2776 else if (CC->fake_username[0]) {
2777 strcpy(masquerade_as, CC->fake_username);
2780 strcpy(masquerade_as, "");
2783 /* Read in the message from the client. */
2784 cprintf("%d send message\n", SEND_LISTING);
2785 msg = CtdlMakeMessage(&CC->user, recp,
2786 CC->room.QRname, anonymous, format_type,
2787 masquerade_as, subject, NULL);
2790 CtdlSubmitMsg(msg, valid, "");
2791 CtdlFreeMessage(msg);
2793 CC->fake_postname[0] = '\0';
2801 * API function to delete messages which match a set of criteria
2802 * (returns the actual number of messages deleted)
2804 int CtdlDeleteMessages(char *room_name, /* which room */
2805 long dmsgnum, /* or "0" for any */
2806 char *content_type /* or "" for any */
2810 struct ctdlroom qrbuf;
2811 struct cdbdata *cdbfr;
2812 long *msglist = NULL;
2813 long *dellist = NULL;
2816 int num_deleted = 0;
2818 struct MetaData smi;
2820 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2821 room_name, dmsgnum, content_type);
2823 /* get room record, obtaining a lock... */
2824 if (lgetroom(&qrbuf, room_name) != 0) {
2825 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2827 return (0); /* room not found */
2829 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2831 if (cdbfr != NULL) {
2832 msglist = malloc(cdbfr->len);
2833 dellist = malloc(cdbfr->len);
2834 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2835 num_msgs = cdbfr->len / sizeof(long);
2839 for (i = 0; i < num_msgs; ++i) {
2842 /* Set/clear a bit for each criterion */
2844 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2845 delete_this |= 0x01;
2847 if (strlen(content_type) == 0) {
2848 delete_this |= 0x02;
2850 GetMetaData(&smi, msglist[i]);
2851 if (!strcasecmp(smi.meta_content_type,
2853 delete_this |= 0x02;
2857 /* Delete message only if all bits are set */
2858 if (delete_this == 0x03) {
2859 dellist[num_deleted++] = msglist[i];
2864 num_msgs = sort_msglist(msglist, num_msgs);
2865 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2866 msglist, (num_msgs * sizeof(long)));
2868 qrbuf.QRhighest = msglist[num_msgs - 1];
2872 /* Go through the messages we pulled out of the index, and decrement
2873 * their reference counts by 1. If this is the only room the message
2874 * was in, the reference count will reach zero and the message will
2875 * automatically be deleted from the database. We do this in a
2876 * separate pass because there might be plug-in hooks getting called,
2877 * and we don't want that happening during an S_ROOMS critical
2880 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2881 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2882 AdjRefCount(dellist[i], -1);
2885 /* Now free the memory we used, and go away. */
2886 if (msglist != NULL) free(msglist);
2887 if (dellist != NULL) free(dellist);
2888 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2889 return (num_deleted);
2895 * Check whether the current user has permission to delete messages from
2896 * the current room (returns 1 for yes, 0 for no)
2898 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2899 getuser(&CC->user, CC->curr_user);
2900 if ((CC->user.axlevel < 6)
2901 && (CC->user.usernum != CC->room.QRroomaide)
2902 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2903 && (!(CC->internal_pgm))) {
2912 * Delete message from current room
2914 void cmd_dele(char *delstr)
2919 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2920 cprintf("%d Higher access required.\n",
2921 ERROR + HIGHER_ACCESS_REQUIRED);
2924 delnum = extract_long(delstr, 0);
2926 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2929 cprintf("%d %d message%s deleted.\n", CIT_OK,
2930 num_deleted, ((num_deleted != 1) ? "s" : ""));
2932 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2938 * Back end API function for moves and deletes
2940 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2943 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2944 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2945 if (err != 0) return(err);
2953 * move or copy a message to another room
2955 void cmd_move(char *args)
2959 struct ctdlroom qtemp;
2965 num = extract_long(args, 0);
2966 extract(targ, args, 1);
2967 targ[ROOMNAMELEN - 1] = 0;
2968 is_copy = extract_int(args, 2);
2970 if (getroom(&qtemp, targ) != 0) {
2971 cprintf("%d '%s' does not exist.\n",
2972 ERROR + ROOM_NOT_FOUND, targ);
2976 getuser(&CC->user, CC->curr_user);
2977 ra = CtdlRoomAccess(&qtemp, &CC->user);
2979 /* Check for permission to perform this operation.
2980 * Remember: "CC->room" is source, "qtemp" is target.
2984 /* Aides can move/copy */
2985 if (CC->user.axlevel >= 6) permit = 1;
2987 /* Room aides can move/copy */
2988 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
2990 /* Permit move/copy from personal rooms */
2991 if ((CC->room.QRflags & QR_MAILBOX)
2992 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2994 /* Permit only copy from public to personal room */
2996 && (!(CC->room.QRflags & QR_MAILBOX))
2997 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
2999 /* User must have access to target room */
3000 if (!(ra & UA_KNOWN)) permit = 0;
3003 cprintf("%d Higher access required.\n",
3004 ERROR + HIGHER_ACCESS_REQUIRED);
3008 err = CtdlCopyMsgToRoom(num, targ);
3010 cprintf("%d Cannot store message in %s: error %d\n",
3015 /* Now delete the message from the source room,
3016 * if this is a 'move' rather than a 'copy' operation.
3019 CtdlDeleteMessages(CC->room.QRname, num, "");
3022 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3028 * GetMetaData() - Get the supplementary record for a message
3030 void GetMetaData(struct MetaData *smibuf, long msgnum)
3033 struct cdbdata *cdbsmi;
3036 memset(smibuf, 0, sizeof(struct MetaData));
3037 smibuf->meta_msgnum = msgnum;
3038 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3040 /* Use the negative of the message number for its supp record index */
3041 TheIndex = (0L - msgnum);
3043 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3044 if (cdbsmi == NULL) {
3045 return; /* record not found; go with defaults */
3047 memcpy(smibuf, cdbsmi->ptr,
3048 ((cdbsmi->len > sizeof(struct MetaData)) ?
3049 sizeof(struct MetaData) : cdbsmi->len));
3056 * PutMetaData() - (re)write supplementary record for a message
3058 void PutMetaData(struct MetaData *smibuf)
3062 /* Use the negative of the message number for the metadata db index */
3063 TheIndex = (0L - smibuf->meta_msgnum);
3065 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3066 smibuf->meta_msgnum, smibuf->meta_refcount);
3068 cdb_store(CDB_MSGMAIN,
3069 &TheIndex, sizeof(long),
3070 smibuf, sizeof(struct MetaData));
3075 * AdjRefCount - change the reference count for a message;
3076 * delete the message if it reaches zero
3078 void AdjRefCount(long msgnum, int incr)
3081 struct MetaData smi;
3084 /* This is a *tight* critical section; please keep it that way, as
3085 * it may get called while nested in other critical sections.
3086 * Complicating this any further will surely cause deadlock!
3088 begin_critical_section(S_SUPPMSGMAIN);
3089 GetMetaData(&smi, msgnum);
3090 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3091 msgnum, smi.meta_refcount);
3092 smi.meta_refcount += incr;
3094 end_critical_section(S_SUPPMSGMAIN);
3095 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3096 msgnum, smi.meta_refcount);
3098 /* If the reference count is now zero, delete the message
3099 * (and its supplementary record as well).
3101 if (smi.meta_refcount == 0) {
3102 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3104 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3105 cdb_delete(CDB_BIGMSGS, &delnum, sizeof(long));
3107 /* We have to delete the metadata record too! */
3108 delnum = (0L - msgnum);
3109 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
3114 * Write a generic object to this room
3116 * Note: this could be much more efficient. Right now we use two temporary
3117 * files, and still pull the message into memory as with all others.
3119 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3120 char *content_type, /* MIME type of this object */
3121 char *tempfilename, /* Where to fetch it from */
3122 struct ctdluser *is_mailbox, /* Mailbox room? */
3123 int is_binary, /* Is encoding necessary? */
3124 int is_unique, /* Del others of this type? */
3125 unsigned int flags /* Internal save flags */
3130 struct ctdlroom qrbuf;
3131 char roomname[ROOMNAMELEN];
3132 struct CtdlMessage *msg;
3134 char *raw_message = NULL;
3135 char *encoded_message = NULL;
3136 off_t raw_length = 0;
3138 if (is_mailbox != NULL)
3139 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3141 safestrncpy(roomname, req_room, sizeof(roomname));
3142 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3145 fp = fopen(tempfilename, "rb");
3147 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3148 tempfilename, strerror(errno));
3151 fseek(fp, 0L, SEEK_END);
3152 raw_length = ftell(fp);
3154 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3156 raw_message = malloc((size_t)raw_length + 2);
3157 fread(raw_message, (size_t)raw_length, 1, fp);
3161 encoded_message = malloc((size_t)
3162 (((raw_length * 134) / 100) + 4096 ) );
3165 encoded_message = malloc((size_t)(raw_length + 4096));
3168 sprintf(encoded_message, "Content-type: %s\n", content_type);
3171 sprintf(&encoded_message[strlen(encoded_message)],
3172 "Content-transfer-encoding: base64\n\n"
3176 sprintf(&encoded_message[strlen(encoded_message)],
3177 "Content-transfer-encoding: 7bit\n\n"
3183 &encoded_message[strlen(encoded_message)],
3189 raw_message[raw_length] = 0;
3191 &encoded_message[strlen(encoded_message)],
3199 lprintf(CTDL_DEBUG, "Allocating\n");
3200 msg = malloc(sizeof(struct CtdlMessage));
3201 memset(msg, 0, sizeof(struct CtdlMessage));
3202 msg->cm_magic = CTDLMESSAGE_MAGIC;
3203 msg->cm_anon_type = MES_NORMAL;
3204 msg->cm_format_type = 4;
3205 msg->cm_fields['A'] = strdup(CC->user.fullname);
3206 msg->cm_fields['O'] = strdup(req_room);
3207 msg->cm_fields['N'] = strdup(config.c_nodename);
3208 msg->cm_fields['H'] = strdup(config.c_humannode);
3209 msg->cm_flags = flags;
3211 msg->cm_fields['M'] = encoded_message;
3213 /* Create the requested room if we have to. */
3214 if (getroom(&qrbuf, roomname) != 0) {
3215 create_room(roomname,
3216 ( (is_mailbox != NULL) ? 5 : 3 ),
3219 /* If the caller specified this object as unique, delete all
3220 * other objects of this type that are currently in the room.
3223 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3224 CtdlDeleteMessages(roomname, 0L, content_type));
3226 /* Now write the data */
3227 CtdlSubmitMsg(msg, NULL, roomname);
3228 CtdlFreeMessage(msg);
3236 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3237 config_msgnum = msgnum;
3241 char *CtdlGetSysConfig(char *sysconfname) {
3242 char hold_rm[ROOMNAMELEN];
3245 struct CtdlMessage *msg;
3248 strcpy(hold_rm, CC->room.QRname);
3249 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3250 getroom(&CC->room, hold_rm);
3255 /* We want the last (and probably only) config in this room */
3256 begin_critical_section(S_CONFIG);
3257 config_msgnum = (-1L);
3258 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3259 CtdlGetSysConfigBackend, NULL);
3260 msgnum = config_msgnum;
3261 end_critical_section(S_CONFIG);
3267 msg = CtdlFetchMessage(msgnum);
3269 conf = strdup(msg->cm_fields['M']);
3270 CtdlFreeMessage(msg);
3277 getroom(&CC->room, hold_rm);
3279 if (conf != NULL) do {
3280 extract_token(buf, conf, 0, '\n');
3281 strcpy(conf, &conf[strlen(buf)+1]);
3282 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3287 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3288 char temp[PATH_MAX];
3291 strcpy(temp, tmpnam(NULL));
3293 fp = fopen(temp, "w");
3294 if (fp == NULL) return;
3295 fprintf(fp, "%s", sysconfdata);
3298 /* this handy API function does all the work for us */
3299 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3305 * Determine whether a given Internet address belongs to the current user
3307 int CtdlIsMe(char *addr) {
3308 struct recptypes *recp;
3311 recp = validate_recipients(addr);
3312 if (recp == NULL) return(0);
3314 if (recp->num_local == 0) {
3319 for (i=0; i<recp->num_local; ++i) {
3320 extract(addr, recp->recp_local, i);
3321 if (!strcasecmp(addr, CC->user.fullname)) {
3333 * Citadel protocol command to do the same
3335 void cmd_isme(char *argbuf) {
3338 if (CtdlAccessCheck(ac_logged_in)) return;
3339 extract(addr, argbuf, 0);
3341 if (CtdlIsMe(addr)) {
3342 cprintf("%d %s\n", CIT_OK, addr);
3345 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);