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 extern struct config config;
59 * This really belongs in serv_network.c, but I don't know how to export
60 * symbols between modules.
62 struct FilterList *filterlist = NULL;
66 * These are the four-character field headers we use when outputting
67 * messages in Citadel format (as opposed to RFC822 format).
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
104 * This function is self explanatory.
105 * (What can I say, I'm in a weird mood today...)
107 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
111 for (i = 0; i < strlen(name); ++i) {
112 if (name[i] == '@') {
113 while (isspace(name[i - 1]) && i > 0) {
114 strcpy(&name[i - 1], &name[i]);
117 while (isspace(name[i + 1])) {
118 strcpy(&name[i + 1], &name[i + 2]);
126 * Aliasing for network mail.
127 * (Error messages have been commented out, because this is a server.)
129 int alias(char *name)
130 { /* process alias and routing info for mail */
133 char aaa[SIZ], bbb[SIZ];
134 char *ignetcfg = NULL;
135 char *ignetmap = NULL;
142 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
144 fp = fopen("network/mail.aliases", "r");
146 fp = fopen("/dev/null", "r");
153 while (fgets(aaa, sizeof aaa, fp) != NULL) {
154 while (isspace(name[0]))
155 strcpy(name, &name[1]);
156 aaa[strlen(aaa) - 1] = 0;
158 for (a = 0; a < strlen(aaa); ++a) {
160 strcpy(bbb, &aaa[a + 1]);
164 if (!strcasecmp(name, aaa))
169 /* Hit the Global Address Book */
170 if (CtdlDirectoryLookup(aaa, name) == 0) {
174 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
176 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
177 for (a=0; a<strlen(name); ++a) {
178 if (name[a] == '@') {
179 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
181 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
186 /* determine local or remote type, see citadel.h */
187 at = haschar(name, '@');
188 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
189 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
190 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
192 /* figure out the delivery mode */
193 extract_token(node, name, 1, '@', sizeof node);
195 /* If there are one or more dots in the nodename, we assume that it
196 * is an FQDN and will attempt SMTP delivery to the Internet.
198 if (haschar(node, '.') > 0) {
199 return(MES_INTERNET);
202 /* Otherwise we look in the IGnet maps for a valid Citadel node.
203 * Try directly-connected nodes first...
205 ignetcfg = CtdlGetSysConfig(IGNETCFG);
206 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
207 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
208 extract_token(testnode, buf, 0, '|', sizeof testnode);
209 if (!strcasecmp(node, testnode)) {
217 * Then try nodes that are two or more hops away.
219 ignetmap = CtdlGetSysConfig(IGNETMAP);
220 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
221 extract_token(buf, ignetmap, i, '\n', sizeof buf);
222 extract_token(testnode, buf, 0, '|', sizeof testnode);
223 if (!strcasecmp(node, testnode)) {
230 /* If we get to this point it's an invalid node name */
239 fp = fopen("citadel.control", "r");
241 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
245 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
258 /* Determine if a given message matches the fields in a message template.
259 * Return 0 for a successful match.
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
264 /* If there aren't any fields in the template, all messages will
267 if (template == NULL) return(0);
269 /* Null messages are bogus. */
270 if (msg == NULL) return(1);
272 for (i='A'; i<='Z'; ++i) {
273 if (template->cm_fields[i] != NULL) {
274 if (msg->cm_fields[i] == NULL) {
277 if (strcasecmp(msg->cm_fields[i],
278 template->cm_fields[i])) return 1;
282 /* All compares succeeded: we have a match! */
289 * Retrieve the "seen" message list for the current room.
291 void CtdlGetSeen(char *buf, int which_set) {
294 /* Learn about the user and room in question */
295 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
297 if (which_set == ctdlsetseen_seen)
298 safestrncpy(buf, vbuf.v_seen, SIZ);
299 if (which_set == ctdlsetseen_answered)
300 safestrncpy(buf, vbuf.v_answered, SIZ);
306 * Manipulate the "seen msgs" string (or other message set strings)
308 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
310 struct cdbdata *cdbfr;
321 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
322 target_msgnum, target_setting, which_set);
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 /* Load the message list */
328 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
330 msglist = malloc(cdbfr->len);
331 memcpy(msglist, cdbfr->ptr, cdbfr->len);
332 num_msgs = cdbfr->len / sizeof(long);
335 return; /* No messages at all? No further action. */
338 /* Decide which message set we're manipulating */
339 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
340 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
342 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
345 for (i=0; i<num_msgs; ++i) {
348 if (msglist[i] == target_msgnum) {
349 is_seen = target_setting;
352 if (is_msg_in_mset(vset, msglist[i])) {
358 if (lo < 0L) lo = msglist[i];
361 if ( ((is_seen == 0) && (was_seen == 1))
362 || ((is_seen == 1) && (i == num_msgs-1)) ) {
365 if ( (strlen(newseen) + 20) > SIZ) {
366 strcpy(newseen, &newseen[20]);
369 tmp = strlen(newseen);
371 strcat(newseen, ",");
375 snprintf(&newseen[tmp], sizeof newseen - tmp,
379 snprintf(&newseen[tmp], sizeof newseen - tmp,
388 /* Decide which message set we're manipulating */
389 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
390 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
392 lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
394 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
399 * API function to perform an operation for each qualifying message in the
400 * current room. (Returns the number of messages processed.)
402 int CtdlForEachMessage(int mode, long ref,
404 struct CtdlMessage *compare,
405 void (*CallBack) (long, void *),
411 struct cdbdata *cdbfr;
412 long *msglist = NULL;
414 int num_processed = 0;
417 struct CtdlMessage *msg;
420 int printed_lastold = 0;
422 /* Learn about the user and room in question */
424 getuser(&CC->user, CC->curr_user);
425 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
427 /* Load the message list */
428 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
430 msglist = malloc(cdbfr->len);
431 memcpy(msglist, cdbfr->ptr, cdbfr->len);
432 num_msgs = cdbfr->len / sizeof(long);
435 return 0; /* No messages at all? No further action. */
440 * Now begin the traversal.
442 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
444 /* If the caller is looking for a specific MIME type, filter
445 * out all messages which are not of the type requested.
447 if (content_type != NULL) if (strlen(content_type) > 0) {
449 /* This call to GetMetaData() sits inside this loop
450 * so that we only do the extra database read per msg
451 * if we need to. Doing the extra read all the time
452 * really kills the server. If we ever need to use
453 * metadata for another search criterion, we need to
454 * move the read somewhere else -- but still be smart
455 * enough to only do the read if the caller has
456 * specified something that will need it.
458 GetMetaData(&smi, msglist[a]);
460 if (strcasecmp(smi.meta_content_type, content_type)) {
466 num_msgs = sort_msglist(msglist, num_msgs);
468 /* If a template was supplied, filter out the messages which
469 * don't match. (This could induce some delays!)
472 if (compare != NULL) {
473 for (a = 0; a < num_msgs; ++a) {
474 msg = CtdlFetchMessage(msglist[a], 1);
476 if (CtdlMsgCmp(msg, compare)) {
479 CtdlFreeMessage(msg);
487 * Now iterate through the message list, according to the
488 * criteria supplied by the caller.
491 for (a = 0; a < num_msgs; ++a) {
492 thismsg = msglist[a];
493 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
494 if (is_seen) lastold = thismsg;
499 || ((mode == MSGS_OLD) && (is_seen))
500 || ((mode == MSGS_NEW) && (!is_seen))
501 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
502 || ((mode == MSGS_FIRST) && (a < ref))
503 || ((mode == MSGS_GT) && (thismsg > ref))
504 || ((mode == MSGS_EQ) && (thismsg == ref))
507 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
509 CallBack(lastold, userdata);
513 if (CallBack) CallBack(thismsg, userdata);
517 free(msglist); /* Clean up */
518 return num_processed;
524 * cmd_msgs() - get list of message #'s in this room
525 * implements the MSGS server command using CtdlForEachMessage()
527 void cmd_msgs(char *cmdbuf)
536 int with_template = 0;
537 struct CtdlMessage *template = NULL;
539 extract_token(which, cmdbuf, 0, '|', sizeof which);
540 cm_ref = extract_int(cmdbuf, 1);
541 with_template = extract_int(cmdbuf, 2);
545 if (!strncasecmp(which, "OLD", 3))
547 else if (!strncasecmp(which, "NEW", 3))
549 else if (!strncasecmp(which, "FIRST", 5))
551 else if (!strncasecmp(which, "LAST", 4))
553 else if (!strncasecmp(which, "GT", 2))
556 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
557 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_getln(buf, sizeof buf), strcmp(buf,"000")) {
569 extract_token(tfield, buf, 0, '|', sizeof tfield);
570 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
571 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
572 if (!strcasecmp(tfield, msgkeys[i])) {
573 template->cm_fields[i] =
581 cprintf("%d Message list...\n", LISTING_FOLLOWS);
584 CtdlForEachMessage(mode, cm_ref,
585 NULL, template, simple_listing, NULL);
586 if (template != NULL) CtdlFreeMessage(template);
594 * help_subst() - support routine for help file viewer
596 void help_subst(char *strbuf, char *source, char *dest)
601 while (p = pattern2(strbuf, source), (p >= 0)) {
602 strcpy(workbuf, &strbuf[p + strlen(source)]);
603 strcpy(&strbuf[p], dest);
604 strcat(strbuf, workbuf);
609 void do_help_subst(char *buffer)
613 help_subst(buffer, "^nodename", config.c_nodename);
614 help_subst(buffer, "^humannode", config.c_humannode);
615 help_subst(buffer, "^fqdn", config.c_fqdn);
616 help_subst(buffer, "^username", CC->user.fullname);
617 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
618 help_subst(buffer, "^usernum", buf2);
619 help_subst(buffer, "^sysadm", config.c_sysadm);
620 help_subst(buffer, "^variantname", CITADEL);
621 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
622 help_subst(buffer, "^maxsessions", buf2);
623 help_subst(buffer, "^bbsdir", CTDLDIR);
629 * memfmout() - Citadel text formatter and paginator.
630 * Although the original purpose of this routine was to format
631 * text to the reader's screen width, all we're really using it
632 * for here is to format text out to 80 columns before sending it
633 * to the client. The client software may reformat it again.
636 int width, /* screen width to use */
637 char *mptr, /* where are we going to get our text from? */
638 char subst, /* nonzero if we should do substitutions */
639 char *nl) /* string to terminate lines with */
651 c = 1; /* c is the current pos */
655 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
657 buffer[strlen(buffer) + 1] = 0;
658 buffer[strlen(buffer)] = ch;
661 if (buffer[0] == '^')
662 do_help_subst(buffer);
664 buffer[strlen(buffer) + 1] = 0;
666 strcpy(buffer, &buffer[1]);
674 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
676 if (((old == 13) || (old == 10)) && (isspace(real))) {
684 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
685 cprintf("%s%s", nl, aaa);
694 if ((strlen(aaa) + c) > (width - 5)) {
703 if ((ch == 13) || (ch == 10)) {
704 cprintf("%s%s", aaa, nl);
711 cprintf("%s%s", aaa, nl);
717 * Callback function for mime parser that simply lists the part
719 void list_this_part(char *name, char *filename, char *partnum, char *disp,
720 void *content, char *cbtype, size_t length, char *encoding,
724 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
725 name, filename, partnum, disp, cbtype, (long)length);
729 * Callback function for multipart prefix
731 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
732 void *content, char *cbtype, size_t length, char *encoding,
735 cprintf("pref=%s|%s\n", partnum, cbtype);
739 * Callback function for multipart sufffix
741 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
742 void *content, char *cbtype, size_t length, char *encoding,
745 cprintf("suff=%s|%s\n", partnum, cbtype);
750 * Callback function for mime parser that opens a section for downloading
752 void mime_download(char *name, char *filename, char *partnum, char *disp,
753 void *content, char *cbtype, size_t length, char *encoding,
757 /* Silently go away if there's already a download open... */
758 if (CC->download_fp != NULL)
761 /* ...or if this is not the desired section */
762 if (strcasecmp(CC->download_desired_section, partnum))
765 CC->download_fp = tmpfile();
766 if (CC->download_fp == NULL)
769 fwrite(content, length, 1, CC->download_fp);
770 fflush(CC->download_fp);
771 rewind(CC->download_fp);
773 OpenCmdResult(filename, cbtype);
779 * Load a message from disk into memory.
780 * This is used by CtdlOutputMsg() and other fetch functions.
782 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
783 * using the CtdlMessageFree() function.
785 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
787 struct cdbdata *dmsgtext;
788 struct CtdlMessage *ret = NULL;
792 cit_uint8_t field_header;
794 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
796 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
797 if (dmsgtext == NULL) {
800 mptr = dmsgtext->ptr;
801 upper_bound = mptr + dmsgtext->len;
803 /* Parse the three bytes that begin EVERY message on disk.
804 * The first is always 0xFF, the on-disk magic number.
805 * The second is the anonymous/public type byte.
806 * The third is the format type byte (vari, fixed, or MIME).
811 "Message %ld appears to be corrupted.\n",
816 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
817 memset(ret, 0, sizeof(struct CtdlMessage));
819 ret->cm_magic = CTDLMESSAGE_MAGIC;
820 ret->cm_anon_type = *mptr++; /* Anon type byte */
821 ret->cm_format_type = *mptr++; /* Format type byte */
824 * The rest is zero or more arbitrary fields. Load them in.
825 * We're done when we encounter either a zero-length field or
826 * have just processed the 'M' (message text) field.
829 if (mptr >= upper_bound) {
832 field_header = *mptr++;
833 ret->cm_fields[field_header] = strdup(mptr);
835 while (*mptr++ != 0); /* advance to next field */
837 } while ((mptr < upper_bound) && (field_header != 'M'));
841 /* Always make sure there's something in the msg text field. If
842 * it's NULL, the message text is most likely stored separately,
843 * so go ahead and fetch that. Failing that, just set a dummy
844 * body so other code doesn't barf.
846 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
847 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
848 if (dmsgtext != NULL) {
849 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
853 if (ret->cm_fields['M'] == NULL) {
854 ret->cm_fields['M'] = strdup("<no text>\n");
857 /* Perform "before read" hooks (aborting if any return nonzero) */
858 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
859 CtdlFreeMessage(ret);
868 * Returns 1 if the supplied pointer points to a valid Citadel message.
869 * If the pointer is NULL or the magic number check fails, returns 0.
871 int is_valid_message(struct CtdlMessage *msg) {
874 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
875 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
883 * 'Destructor' for struct CtdlMessage
885 void CtdlFreeMessage(struct CtdlMessage *msg)
889 if (is_valid_message(msg) == 0) return;
891 for (i = 0; i < 256; ++i)
892 if (msg->cm_fields[i] != NULL) {
893 free(msg->cm_fields[i]);
896 msg->cm_magic = 0; /* just in case */
902 * Pre callback function for multipart/alternative
904 * NOTE: this differs from the standard behavior for a reason. Normally when
905 * displaying multipart/alternative you want to show the _last_ usable
906 * format in the message. Here we show the _first_ one, because it's
907 * usually text/plain. Since this set of functions is designed for text
908 * output to non-MIME-aware clients, this is the desired behavior.
911 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
912 void *content, char *cbtype, size_t length, char *encoding,
917 ma = (struct ma_info *)cbuserdata;
918 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
919 if (!strcasecmp(cbtype, "multipart/alternative")) {
927 * Post callback function for multipart/alternative
929 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
930 void *content, char *cbtype, size_t length, char *encoding,
935 ma = (struct ma_info *)cbuserdata;
936 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
937 if (!strcasecmp(cbtype, "multipart/alternative")) {
945 * Inline callback function for mime parser that wants to display text
947 void fixed_output(char *name, char *filename, char *partnum, char *disp,
948 void *content, char *cbtype, size_t length, char *encoding,
956 ma = (struct ma_info *)cbuserdata;
958 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
961 * If we're in the middle of a multipart/alternative scope and
962 * we've already printed another section, skip this one.
964 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
965 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
970 if ( (!strcasecmp(cbtype, "text/plain"))
971 || (strlen(cbtype)==0) ) {
974 client_write(wptr, length);
975 if (wptr[length-1] != '\n') {
980 else if (!strcasecmp(cbtype, "text/html")) {
981 ptr = html_to_ascii(content, 80, 0);
983 client_write(ptr, wlen);
984 if (ptr[wlen-1] != '\n') {
989 else if (strncasecmp(cbtype, "multipart/", 10)) {
990 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
991 partnum, filename, cbtype, (long)length);
996 * The client is elegant and sophisticated and wants to be choosy about
997 * MIME content types, so figure out which multipart/alternative part
998 * we're going to send.
1000 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1001 void *content, char *cbtype, size_t length, char *encoding,
1008 ma = (struct ma_info *)cbuserdata;
1010 if (ma->is_ma > 0) {
1011 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1012 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1013 if (!strcasecmp(buf, cbtype)) {
1014 strcpy(ma->chosen_part, partnum);
1021 * Now that we've chosen our preferred part, output it.
1023 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1024 void *content, char *cbtype, size_t length, char *encoding,
1029 int add_newline = 0;
1033 ma = (struct ma_info *)cbuserdata;
1035 /* This is not the MIME part you're looking for... */
1036 if (strcasecmp(partnum, ma->chosen_part)) return;
1038 /* If the content-type of this part is in our preferred formats
1039 * list, we can simply output it verbatim.
1041 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1042 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1043 if (!strcasecmp(buf, cbtype)) {
1044 /* Yeah! Go! W00t!! */
1046 text_content = (char *)content;
1047 if (text_content[length-1] != '\n') {
1051 cprintf("Content-type: %s\n", cbtype);
1052 cprintf("Content-length: %d\n",
1053 (int)(length + add_newline) );
1054 if (strlen(encoding) > 0) {
1055 cprintf("Content-transfer-encoding: %s\n", encoding);
1058 cprintf("Content-transfer-encoding: 7bit\n");
1061 client_write(content, length);
1062 if (add_newline) cprintf("\n");
1067 /* No translations required or possible: output as text/plain */
1068 cprintf("Content-type: text/plain\n\n");
1069 fixed_output(name, filename, partnum, disp, content, cbtype,
1070 length, encoding, cbuserdata);
1075 * Get a message off disk. (returns om_* values found in msgbase.h)
1078 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1079 int mode, /* how would you like that message? */
1080 int headers_only, /* eschew the message body? */
1081 int do_proto, /* do Citadel protocol responses? */
1082 int crlf /* Use CRLF newlines instead of LF? */
1084 struct CtdlMessage *TheMessage = NULL;
1085 int retcode = om_no_such_msg;
1087 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1090 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1091 if (do_proto) cprintf("%d Not logged in.\n",
1092 ERROR + NOT_LOGGED_IN);
1093 return(om_not_logged_in);
1096 /* FIXME: check message id against msglist for this room */
1099 * Fetch the message from disk. If we're in sooper-fast headers
1100 * only mode, request that we don't even bother loading the body
1103 if (headers_only == HEADERS_FAST) {
1104 TheMessage = CtdlFetchMessage(msg_num, 0);
1107 TheMessage = CtdlFetchMessage(msg_num, 1);
1110 if (TheMessage == NULL) {
1111 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1112 ERROR + MESSAGE_NOT_FOUND, msg_num);
1113 return(om_no_such_msg);
1116 retcode = CtdlOutputPreLoadedMsg(
1117 TheMessage, msg_num, mode,
1118 headers_only, do_proto, crlf);
1120 CtdlFreeMessage(TheMessage);
1127 * Get a message off disk. (returns om_* values found in msgbase.h)
1130 int CtdlOutputPreLoadedMsg(
1131 struct CtdlMessage *TheMessage,
1133 int mode, /* how would you like that message? */
1134 int headers_only, /* eschew the message body? */
1135 int do_proto, /* do Citadel protocol responses? */
1136 int crlf /* Use CRLF newlines instead of LF? */
1142 char display_name[256];
1144 char *nl; /* newline string */
1146 int subject_found = 0;
1149 /* Buffers needed for RFC822 translation. These are all filled
1150 * using functions that are bounds-checked, and therefore we can
1151 * make them substantially smaller than SIZ.
1159 char datestamp[100];
1161 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1162 ((TheMessage == NULL) ? "NULL" : "not null"),
1164 mode, headers_only, do_proto, crlf);
1166 snprintf(mid, sizeof mid, "%ld", msg_num);
1167 nl = (crlf ? "\r\n" : "\n");
1169 if (!is_valid_message(TheMessage)) {
1171 "ERROR: invalid preloaded message for output\n");
1172 return(om_no_such_msg);
1175 /* Are we downloading a MIME component? */
1176 if (mode == MT_DOWNLOAD) {
1177 if (TheMessage->cm_format_type != FMT_RFC822) {
1179 cprintf("%d This is not a MIME message.\n",
1180 ERROR + ILLEGAL_VALUE);
1181 } else if (CC->download_fp != NULL) {
1182 if (do_proto) cprintf(
1183 "%d You already have a download open.\n",
1184 ERROR + RESOURCE_BUSY);
1186 /* Parse the message text component */
1187 mptr = TheMessage->cm_fields['M'];
1188 ma = malloc(sizeof(struct ma_info));
1189 memset(ma, 0, sizeof(struct ma_info));
1190 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1192 /* If there's no file open by this time, the requested
1193 * section wasn't found, so print an error
1195 if (CC->download_fp == NULL) {
1196 if (do_proto) cprintf(
1197 "%d Section %s not found.\n",
1198 ERROR + FILE_NOT_FOUND,
1199 CC->download_desired_section);
1202 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1205 /* now for the user-mode message reading loops */
1206 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1208 /* Does the caller want to skip the headers? */
1209 if (headers_only == HEADERS_NONE) goto START_TEXT;
1211 /* Tell the client which format type we're using. */
1212 if ( (mode == MT_CITADEL) && (do_proto) ) {
1213 cprintf("type=%d\n", TheMessage->cm_format_type);
1216 /* nhdr=yes means that we're only displaying headers, no body */
1217 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1218 && (mode == MT_CITADEL)
1221 cprintf("nhdr=yes\n");
1224 /* begin header processing loop for Citadel message format */
1226 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1228 safestrncpy(display_name, "<unknown>", sizeof display_name);
1229 if (TheMessage->cm_fields['A']) {
1230 strcpy(buf, TheMessage->cm_fields['A']);
1231 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1232 safestrncpy(display_name, "****", sizeof display_name);
1234 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1235 safestrncpy(display_name, "anonymous", sizeof display_name);
1238 safestrncpy(display_name, buf, sizeof display_name);
1240 if ((is_room_aide())
1241 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1242 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1243 size_t tmp = strlen(display_name);
1244 snprintf(&display_name[tmp],
1245 sizeof display_name - tmp,
1250 /* Don't show Internet address for users on the
1251 * local Citadel network.
1254 if (TheMessage->cm_fields['N'] != NULL)
1255 if (strlen(TheMessage->cm_fields['N']) > 0)
1256 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1260 /* Now spew the header fields in the order we like them. */
1261 safestrncpy(allkeys, FORDER, sizeof allkeys);
1262 for (i=0; i<strlen(allkeys); ++i) {
1263 k = (int) allkeys[i];
1265 if ( (TheMessage->cm_fields[k] != NULL)
1266 && (msgkeys[k] != NULL) ) {
1268 if (do_proto) cprintf("%s=%s\n",
1272 else if ((k == 'F') && (suppress_f)) {
1275 /* Masquerade display name if needed */
1277 if (do_proto) cprintf("%s=%s\n",
1279 TheMessage->cm_fields[k]
1288 /* begin header processing loop for RFC822 transfer format */
1293 strcpy(snode, NODENAME);
1294 strcpy(lnode, HUMANNODE);
1295 if (mode == MT_RFC822) {
1296 for (i = 0; i < 256; ++i) {
1297 if (TheMessage->cm_fields[i]) {
1298 mptr = TheMessage->cm_fields[i];
1301 safestrncpy(luser, mptr, sizeof luser);
1302 safestrncpy(suser, mptr, sizeof suser);
1304 else if (i == 'U') {
1305 cprintf("Subject: %s%s", mptr, nl);
1309 safestrncpy(mid, mptr, sizeof mid);
1311 safestrncpy(lnode, mptr, sizeof lnode);
1313 safestrncpy(fuser, mptr, sizeof fuser);
1315 cprintf("X-Citadel-Room: %s%s",
1318 safestrncpy(snode, mptr, sizeof snode);
1320 cprintf("To: %s%s", mptr, nl);
1321 else if (i == 'T') {
1322 datestring(datestamp, sizeof datestamp,
1323 atol(mptr), DATESTRING_RFC822);
1324 cprintf("Date: %s%s", datestamp, nl);
1328 if (subject_found == 0) {
1329 cprintf("Subject: (no subject)%s", nl);
1333 for (i=0; i<strlen(suser); ++i) {
1334 suser[i] = tolower(suser[i]);
1335 if (!isalnum(suser[i])) suser[i]='_';
1338 if (mode == MT_RFC822) {
1339 if (!strcasecmp(snode, NODENAME)) {
1340 safestrncpy(snode, FQDN, sizeof snode);
1343 /* Construct a fun message id */
1344 cprintf("Message-ID: <%s", mid);
1345 if (strchr(mid, '@')==NULL) {
1346 cprintf("@%s", snode);
1350 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1351 cprintf("From: x@x.org (----)%s", nl);
1353 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1354 cprintf("From: x@x.org (anonymous)%s", nl);
1356 else if (strlen(fuser) > 0) {
1357 cprintf("From: %s (%s)%s", fuser, luser, nl);
1360 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1363 cprintf("Organization: %s%s", lnode, nl);
1365 /* Blank line signifying RFC822 end-of-headers */
1366 if (TheMessage->cm_format_type != FMT_RFC822) {
1371 /* end header processing loop ... at this point, we're in the text */
1373 if (headers_only == HEADERS_FAST) goto DONE;
1374 mptr = TheMessage->cm_fields['M'];
1376 /* Tell the client about the MIME parts in this message */
1377 if (TheMessage->cm_format_type == FMT_RFC822) {
1378 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1379 mime_parser(mptr, NULL,
1385 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1386 /* FIXME ... we have to put some code in here to avoid
1387 * printing duplicate header information when both
1388 * Citadel and RFC822 headers exist. Preference should
1389 * probably be given to the RFC822 headers.
1391 int done_rfc822_hdrs = 0;
1392 while (ch=*(mptr++), ch!=0) {
1397 if (!done_rfc822_hdrs) {
1398 if (headers_only != HEADERS_NONE) {
1403 if (headers_only != HEADERS_ONLY) {
1407 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1408 done_rfc822_hdrs = 1;
1412 if (done_rfc822_hdrs) {
1413 if (headers_only != HEADERS_NONE) {
1418 if (headers_only != HEADERS_ONLY) {
1422 if ((*mptr == 13) || (*mptr == 10)) {
1423 done_rfc822_hdrs = 1;
1431 if (headers_only == HEADERS_ONLY) {
1435 /* signify start of msg text */
1436 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1437 if (do_proto) cprintf("text\n");
1440 /* If the format type on disk is 1 (fixed-format), then we want
1441 * everything to be output completely literally ... regardless of
1442 * what message transfer format is in use.
1444 if (TheMessage->cm_format_type == FMT_FIXED) {
1445 if (mode == MT_MIME) {
1446 cprintf("Content-type: text/plain\n\n");
1449 while (ch = *mptr++, ch > 0) {
1452 if ((ch == 10) || (strlen(buf) > 250)) {
1453 cprintf("%s%s", buf, nl);
1456 buf[strlen(buf) + 1] = 0;
1457 buf[strlen(buf)] = ch;
1460 if (strlen(buf) > 0)
1461 cprintf("%s%s", buf, nl);
1464 /* If the message on disk is format 0 (Citadel vari-format), we
1465 * output using the formatter at 80 columns. This is the final output
1466 * form if the transfer format is RFC822, but if the transfer format
1467 * is Citadel proprietary, it'll still work, because the indentation
1468 * for new paragraphs is correct and the client will reformat the
1469 * message to the reader's screen width.
1471 if (TheMessage->cm_format_type == FMT_CITADEL) {
1472 if (mode == MT_MIME) {
1473 cprintf("Content-type: text/x-citadel-variformat\n\n");
1475 memfmout(80, mptr, 0, nl);
1478 /* If the message on disk is format 4 (MIME), we've gotta hand it
1479 * off to the MIME parser. The client has already been told that
1480 * this message is format 1 (fixed format), so the callback function
1481 * we use will display those parts as-is.
1483 if (TheMessage->cm_format_type == FMT_RFC822) {
1484 ma = malloc(sizeof(struct ma_info));
1485 memset(ma, 0, sizeof(struct ma_info));
1487 if (mode == MT_MIME) {
1488 strcpy(ma->chosen_part, "1");
1489 mime_parser(mptr, NULL,
1490 *choose_preferred, *fixed_output_pre,
1491 *fixed_output_post, (void *)ma, 0);
1492 mime_parser(mptr, NULL,
1493 *output_preferred, NULL, NULL, (void *)ma, 0);
1496 mime_parser(mptr, NULL,
1497 *fixed_output, *fixed_output_pre,
1498 *fixed_output_post, (void *)ma, 0);
1504 DONE: /* now we're done */
1505 if (do_proto) cprintf("000\n");
1512 * display a message (mode 0 - Citadel proprietary)
1514 void cmd_msg0(char *cmdbuf)
1517 int headers_only = HEADERS_ALL;
1519 msgid = extract_long(cmdbuf, 0);
1520 headers_only = extract_int(cmdbuf, 1);
1522 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1528 * display a message (mode 2 - RFC822)
1530 void cmd_msg2(char *cmdbuf)
1533 int headers_only = HEADERS_ALL;
1535 msgid = extract_long(cmdbuf, 0);
1536 headers_only = extract_int(cmdbuf, 1);
1538 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1544 * display a message (mode 3 - IGnet raw format - internal programs only)
1546 void cmd_msg3(char *cmdbuf)
1549 struct CtdlMessage *msg;
1552 if (CC->internal_pgm == 0) {
1553 cprintf("%d This command is for internal programs only.\n",
1554 ERROR + HIGHER_ACCESS_REQUIRED);
1558 msgnum = extract_long(cmdbuf, 0);
1559 msg = CtdlFetchMessage(msgnum, 1);
1561 cprintf("%d Message %ld not found.\n",
1562 ERROR + MESSAGE_NOT_FOUND, msgnum);
1566 serialize_message(&smr, msg);
1567 CtdlFreeMessage(msg);
1570 cprintf("%d Unable to serialize message\n",
1571 ERROR + INTERNAL_ERROR);
1575 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1576 client_write((char *)smr.ser, (int)smr.len);
1583 * Display a message using MIME content types
1585 void cmd_msg4(char *cmdbuf)
1589 msgid = extract_long(cmdbuf, 0);
1590 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1596 * Client tells us its preferred message format(s)
1598 void cmd_msgp(char *cmdbuf)
1600 safestrncpy(CC->preferred_formats, cmdbuf,
1601 sizeof(CC->preferred_formats));
1602 cprintf("%d ok\n", CIT_OK);
1607 * Open a component of a MIME message as a download file
1609 void cmd_opna(char *cmdbuf)
1612 char desired_section[128];
1614 msgid = extract_long(cmdbuf, 0);
1615 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1616 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1617 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1622 * Save a message pointer into a specified room
1623 * (Returns 0 for success, nonzero for failure)
1624 * roomname may be NULL to use the current room
1626 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1628 char hold_rm[ROOMNAMELEN];
1629 struct cdbdata *cdbfr;
1632 long highest_msg = 0L;
1633 struct CtdlMessage *msg = NULL;
1635 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1636 roomname, msgid, flags);
1638 strcpy(hold_rm, CC->room.QRname);
1640 /* We may need to check to see if this message is real */
1641 if ( (flags & SM_VERIFY_GOODNESS)
1642 || (flags & SM_DO_REPL_CHECK)
1644 msg = CtdlFetchMessage(msgid, 1);
1645 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1648 /* Perform replication checks if necessary */
1649 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1651 if (getroom(&CC->room,
1652 ((roomname != NULL) ? roomname : CC->room.QRname) )
1654 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1655 if (msg != NULL) CtdlFreeMessage(msg);
1656 return(ERROR + ROOM_NOT_FOUND);
1659 if (ReplicationChecks(msg) != 0) {
1660 getroom(&CC->room, hold_rm);
1661 if (msg != NULL) CtdlFreeMessage(msg);
1663 "Did replication, and newer exists\n");
1668 /* Now the regular stuff */
1669 if (lgetroom(&CC->room,
1670 ((roomname != NULL) ? roomname : CC->room.QRname) )
1672 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1673 if (msg != NULL) CtdlFreeMessage(msg);
1674 return(ERROR + ROOM_NOT_FOUND);
1677 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1678 if (cdbfr == NULL) {
1682 msglist = malloc(cdbfr->len);
1683 if (msglist == NULL)
1684 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1685 num_msgs = cdbfr->len / sizeof(long);
1686 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1691 /* Make sure the message doesn't already exist in this room. It
1692 * is absolutely taboo to have more than one reference to the same
1693 * message in a room.
1695 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1696 if (msglist[i] == msgid) {
1697 lputroom(&CC->room); /* unlock the room */
1698 getroom(&CC->room, hold_rm);
1699 if (msg != NULL) CtdlFreeMessage(msg);
1701 return(ERROR + ALREADY_EXISTS);
1705 /* Now add the new message */
1707 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1709 if (msglist == NULL) {
1710 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1712 msglist[num_msgs - 1] = msgid;
1714 /* Sort the message list, so all the msgid's are in order */
1715 num_msgs = sort_msglist(msglist, num_msgs);
1717 /* Determine the highest message number */
1718 highest_msg = msglist[num_msgs - 1];
1720 /* Write it back to disk. */
1721 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1722 msglist, (int)(num_msgs * sizeof(long)));
1724 /* Free up the memory we used. */
1727 /* Update the highest-message pointer and unlock the room. */
1728 CC->room.QRhighest = highest_msg;
1729 lputroom(&CC->room);
1730 getroom(&CC->room, hold_rm);
1732 /* Bump the reference count for this message. */
1733 if ((flags & SM_DONT_BUMP_REF)==0) {
1734 AdjRefCount(msgid, +1);
1737 /* Return success. */
1738 if (msg != NULL) CtdlFreeMessage(msg);
1745 * Message base operation to save a new message to the message store
1746 * (returns new message number)
1748 * This is the back end for CtdlSubmitMsg() and should not be directly
1749 * called by server-side modules.
1752 long send_message(struct CtdlMessage *msg) {
1760 /* Get a new message number */
1761 newmsgid = get_new_message_number();
1762 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1764 /* Generate an ID if we don't have one already */
1765 if (msg->cm_fields['I']==NULL) {
1766 msg->cm_fields['I'] = strdup(msgidbuf);
1769 /* If the message is big, set its body aside for storage elsewhere */
1770 if (msg->cm_fields['M'] != NULL) {
1771 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1773 holdM = msg->cm_fields['M'];
1774 msg->cm_fields['M'] = NULL;
1778 /* Serialize our data structure for storage in the database */
1779 serialize_message(&smr, msg);
1782 msg->cm_fields['M'] = holdM;
1786 cprintf("%d Unable to serialize message\n",
1787 ERROR + INTERNAL_ERROR);
1791 /* Write our little bundle of joy into the message base */
1792 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1793 smr.ser, smr.len) < 0) {
1794 lprintf(CTDL_ERR, "Can't store message\n");
1798 cdb_store(CDB_BIGMSGS,
1808 /* Free the memory we used for the serialized message */
1811 /* Return the *local* message ID to the caller
1812 * (even if we're storing an incoming network message)
1820 * Serialize a struct CtdlMessage into the format used on disk and network.
1822 * This function loads up a "struct ser_ret" (defined in server.h) which
1823 * contains the length of the serialized message and a pointer to the
1824 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1826 void serialize_message(struct ser_ret *ret, /* return values */
1827 struct CtdlMessage *msg) /* unserialized msg */
1831 static char *forder = FORDER;
1833 if (is_valid_message(msg) == 0) return; /* self check */
1836 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1837 ret->len = ret->len +
1838 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1840 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1841 ret->ser = malloc(ret->len);
1842 if (ret->ser == NULL) {
1848 ret->ser[1] = msg->cm_anon_type;
1849 ret->ser[2] = msg->cm_format_type;
1852 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1853 ret->ser[wlen++] = (char)forder[i];
1854 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1855 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1857 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1858 (long)ret->len, (long)wlen);
1866 * Back end for the ReplicationChecks() function
1868 void check_repl(long msgnum, void *userdata) {
1869 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1870 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1875 * Check to see if any messages already exist which carry the same Exclusive ID
1876 * as this one. If any are found, delete them.
1879 int ReplicationChecks(struct CtdlMessage *msg) {
1880 struct CtdlMessage *template;
1883 /* No exclusive id? Don't do anything. */
1884 if (msg->cm_fields['E'] == NULL) return 0;
1885 if (strlen(msg->cm_fields['E']) == 0) return 0;
1886 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1888 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1889 memset(template, 0, sizeof(struct CtdlMessage));
1890 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1892 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1894 CtdlFreeMessage(template);
1895 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1903 * Save a message to disk and submit it into the delivery system.
1905 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1906 struct recptypes *recps, /* recipients (if mail) */
1907 char *force /* force a particular room? */
1909 char submit_filename[128];
1910 char generated_timestamp[32];
1911 char hold_rm[ROOMNAMELEN];
1912 char actual_rm[ROOMNAMELEN];
1913 char force_room[ROOMNAMELEN];
1914 char content_type[SIZ]; /* We have to learn this */
1915 char recipient[SIZ];
1918 struct ctdluser userbuf;
1920 struct MetaData smi;
1921 FILE *network_fp = NULL;
1922 static int seqnum = 1;
1923 struct CtdlMessage *imsg = NULL;
1926 char *hold_R, *hold_D;
1928 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1929 if (is_valid_message(msg) == 0) return(-1); /* self check */
1931 /* If this message has no timestamp, we take the liberty of
1932 * giving it one, right now.
1934 if (msg->cm_fields['T'] == NULL) {
1935 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1936 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1937 msg->cm_fields['T'] = strdup(generated_timestamp);
1940 /* If this message has no path, we generate one.
1942 if (msg->cm_fields['P'] == NULL) {
1943 lprintf(CTDL_DEBUG, "Generating path\n");
1944 if (msg->cm_fields['A'] != NULL) {
1945 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1946 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1947 if (isspace(msg->cm_fields['P'][a])) {
1948 msg->cm_fields['P'][a] = ' ';
1953 msg->cm_fields['P'] = strdup("unknown");
1957 if (force == NULL) {
1958 strcpy(force_room, "");
1961 strcpy(force_room, force);
1964 /* Learn about what's inside, because it's what's inside that counts */
1965 lprintf(CTDL_DEBUG, "Learning what's inside\n");
1966 if (msg->cm_fields['M'] == NULL) {
1967 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1971 switch (msg->cm_format_type) {
1973 strcpy(content_type, "text/x-citadel-variformat");
1976 strcpy(content_type, "text/plain");
1979 strcpy(content_type, "text/plain");
1980 mptr = bmstrstr(msg->cm_fields['M'],
1981 "Content-type: ", strncasecmp);
1983 safestrncpy(content_type, &mptr[14],
1984 sizeof content_type);
1985 for (a = 0; a < strlen(content_type); ++a) {
1986 if ((content_type[a] == ';')
1987 || (content_type[a] == ' ')
1988 || (content_type[a] == 13)
1989 || (content_type[a] == 10)) {
1990 content_type[a] = 0;
1996 /* Goto the correct room */
1997 lprintf(CTDL_DEBUG, "Selected room %s\n",
1998 (recps) ? CC->room.QRname : SENTITEMS);
1999 strcpy(hold_rm, CC->room.QRname);
2000 strcpy(actual_rm, CC->room.QRname);
2001 if (recps != NULL) {
2002 strcpy(actual_rm, SENTITEMS);
2005 /* If the user is a twit, move to the twit room for posting */
2006 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2007 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2009 if (CC->user.axlevel == 2) {
2010 strcpy(hold_rm, actual_rm);
2011 strcpy(actual_rm, config.c_twitroom);
2015 /* ...or if this message is destined for Aide> then go there. */
2016 if (strlen(force_room) > 0) {
2017 strcpy(actual_rm, force_room);
2020 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2021 if (strcasecmp(actual_rm, CC->room.QRname)) {
2022 getroom(&CC->room, actual_rm);
2026 * If this message has no O (room) field, generate one.
2028 if (msg->cm_fields['O'] == NULL) {
2029 msg->cm_fields['O'] = strdup(CC->room.QRname);
2032 /* Perform "before save" hooks (aborting if any return nonzero) */
2033 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2034 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2036 /* If this message has an Exclusive ID, perform replication checks */
2037 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2038 if (ReplicationChecks(msg) > 0) return(-4);
2040 /* Save it to disk */
2041 lprintf(CTDL_DEBUG, "Saving to disk\n");
2042 newmsgid = send_message(msg);
2043 if (newmsgid <= 0L) return(-5);
2045 /* Write a supplemental message info record. This doesn't have to
2046 * be a critical section because nobody else knows about this message
2049 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2050 memset(&smi, 0, sizeof(struct MetaData));
2051 smi.meta_msgnum = newmsgid;
2052 smi.meta_refcount = 0;
2053 safestrncpy(smi.meta_content_type, content_type,
2054 sizeof smi.meta_content_type);
2056 /* As part of the new metadata record, measure how
2057 * big this message will be when displayed as RFC822.
2058 * Both POP and IMAP use this, and it's best to just take the hit now
2059 * instead of having to potentially measure thousands of messages when
2060 * a mailbox is opened later.
2063 if (CC->redirect_buffer != NULL) {
2064 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2067 CC->redirect_buffer = malloc(SIZ);
2068 CC->redirect_len = 0;
2069 CC->redirect_alloc = SIZ;
2070 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2071 smi.meta_rfc822_length = CC->redirect_len;
2072 free(CC->redirect_buffer);
2073 CC->redirect_buffer = NULL;
2074 CC->redirect_len = 0;
2075 CC->redirect_alloc = 0;
2079 /* Now figure out where to store the pointers */
2080 lprintf(CTDL_DEBUG, "Storing pointers\n");
2082 /* If this is being done by the networker delivering a private
2083 * message, we want to BYPASS saving the sender's copy (because there
2084 * is no local sender; it would otherwise go to the Trashcan).
2086 if ((!CC->internal_pgm) || (recps == NULL)) {
2087 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2088 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2089 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2094 /* For internet mail, drop a copy in the outbound queue room */
2096 if (recps->num_internet > 0) {
2097 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2100 /* If other rooms are specified, drop them there too. */
2102 if (recps->num_room > 0)
2103 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2104 extract_token(recipient, recps->recp_room, i,
2105 '|', sizeof recipient);
2106 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2107 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2110 /* Bump this user's messages posted counter. */
2111 lprintf(CTDL_DEBUG, "Updating user\n");
2112 lgetuser(&CC->user, CC->curr_user);
2113 CC->user.posted = CC->user.posted + 1;
2114 lputuser(&CC->user);
2116 /* If this is private, local mail, make a copy in the
2117 * recipient's mailbox and bump the reference count.
2120 if (recps->num_local > 0)
2121 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2122 extract_token(recipient, recps->recp_local, i,
2123 '|', sizeof recipient);
2124 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2126 if (getuser(&userbuf, recipient) == 0) {
2127 MailboxName(actual_rm, sizeof actual_rm,
2128 &userbuf, MAILROOM);
2129 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2130 BumpNewMailCounter(userbuf.usernum);
2133 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2134 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2139 /* Perform "after save" hooks */
2140 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2141 PerformMessageHooks(msg, EVT_AFTERSAVE);
2143 /* For IGnet mail, we have to save a new copy into the spooler for
2144 * each recipient, with the R and D fields set to the recipient and
2145 * destination-node. This has two ugly side effects: all other
2146 * recipients end up being unlisted in this recipient's copy of the
2147 * message, and it has to deliver multiple messages to the same
2148 * node. We'll revisit this again in a year or so when everyone has
2149 * a network spool receiver that can handle the new style messages.
2152 if (recps->num_ignet > 0)
2153 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2154 extract_token(recipient, recps->recp_ignet, i,
2155 '|', sizeof recipient);
2157 hold_R = msg->cm_fields['R'];
2158 hold_D = msg->cm_fields['D'];
2159 msg->cm_fields['R'] = malloc(SIZ);
2160 msg->cm_fields['D'] = malloc(128);
2161 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2162 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2164 serialize_message(&smr, msg);
2166 snprintf(submit_filename, sizeof submit_filename,
2167 "./network/spoolin/netmail.%04lx.%04x.%04x",
2168 (long) getpid(), CC->cs_pid, ++seqnum);
2169 network_fp = fopen(submit_filename, "wb+");
2170 if (network_fp != NULL) {
2171 fwrite(smr.ser, smr.len, 1, network_fp);
2177 free(msg->cm_fields['R']);
2178 free(msg->cm_fields['D']);
2179 msg->cm_fields['R'] = hold_R;
2180 msg->cm_fields['D'] = hold_D;
2183 /* Go back to the room we started from */
2184 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2185 if (strcasecmp(hold_rm, CC->room.QRname))
2186 getroom(&CC->room, hold_rm);
2188 /* For internet mail, generate delivery instructions.
2189 * Yes, this is recursive. Deal with it. Infinite recursion does
2190 * not happen because the delivery instructions message does not
2191 * contain a recipient.
2194 if (recps->num_internet > 0) {
2195 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2196 instr = malloc(SIZ * 2);
2197 snprintf(instr, SIZ * 2,
2198 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2200 SPOOLMIME, newmsgid, (long)time(NULL),
2201 msg->cm_fields['A'], msg->cm_fields['N']
2204 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2205 size_t tmp = strlen(instr);
2206 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2207 snprintf(&instr[tmp], SIZ * 2 - tmp,
2208 "remote|%s|0||\n", recipient);
2211 imsg = malloc(sizeof(struct CtdlMessage));
2212 memset(imsg, 0, sizeof(struct CtdlMessage));
2213 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2214 imsg->cm_anon_type = MES_NORMAL;
2215 imsg->cm_format_type = FMT_RFC822;
2216 imsg->cm_fields['A'] = strdup("Citadel");
2217 imsg->cm_fields['M'] = instr;
2218 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2219 CtdlFreeMessage(imsg);
2228 * Convenience function for generating small administrative messages.
2230 void quickie_message(char *from, char *to, char *room, char *text,
2231 int format_type, char *subject)
2233 struct CtdlMessage *msg;
2234 struct recptypes *recp = NULL;
2236 msg = malloc(sizeof(struct CtdlMessage));
2237 memset(msg, 0, sizeof(struct CtdlMessage));
2238 msg->cm_magic = CTDLMESSAGE_MAGIC;
2239 msg->cm_anon_type = MES_NORMAL;
2240 msg->cm_format_type = format_type;
2241 msg->cm_fields['A'] = strdup(from);
2242 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2243 msg->cm_fields['N'] = strdup(NODENAME);
2245 msg->cm_fields['R'] = strdup(to);
2246 recp = validate_recipients(to);
2248 if (subject != NULL) {
2249 msg->cm_fields['U'] = strdup(subject);
2251 msg->cm_fields['M'] = strdup(text);
2253 CtdlSubmitMsg(msg, recp, room);
2254 CtdlFreeMessage(msg);
2255 if (recp != NULL) free(recp);
2261 * Back end function used by CtdlMakeMessage() and similar functions
2263 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2264 size_t maxlen, /* maximum message length */
2265 char *exist, /* if non-null, append to it;
2266 exist is ALWAYS freed */
2267 int crlf /* CRLF newlines instead of LF */
2271 size_t message_len = 0;
2272 size_t buffer_len = 0;
2278 if (exist == NULL) {
2285 message_len = strlen(exist);
2286 buffer_len = message_len + 4096;
2287 m = realloc(exist, buffer_len);
2294 /* flush the input if we have nowhere to store it */
2299 /* read in the lines of message text one by one */
2301 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2302 if (!strcmp(buf, terminator)) finished = 1;
2304 strcat(buf, "\r\n");
2310 if ( (!flushing) && (!finished) ) {
2311 /* Measure the line */
2312 linelen = strlen(buf);
2314 /* augment the buffer if we have to */
2315 if ((message_len + linelen) >= buffer_len) {
2316 ptr = realloc(m, (buffer_len * 2) );
2317 if (ptr == NULL) { /* flush if can't allocate */
2320 buffer_len = (buffer_len * 2);
2322 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2326 /* Add the new line to the buffer. NOTE: this loop must avoid
2327 * using functions like strcat() and strlen() because they
2328 * traverse the entire buffer upon every call, and doing that
2329 * for a multi-megabyte message slows it down beyond usability.
2331 strcpy(&m[message_len], buf);
2332 message_len += linelen;
2335 /* if we've hit the max msg length, flush the rest */
2336 if (message_len >= maxlen) flushing = 1;
2338 } while (!finished);
2346 * Build a binary message to be saved on disk.
2347 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2348 * will become part of the message. This means you are no longer
2349 * responsible for managing that memory -- it will be freed along with
2350 * the rest of the fields when CtdlFreeMessage() is called.)
2353 struct CtdlMessage *CtdlMakeMessage(
2354 struct ctdluser *author, /* author's user structure */
2355 char *recipient, /* NULL if it's not mail */
2356 char *room, /* room where it's going */
2357 int type, /* see MES_ types in header file */
2358 int format_type, /* variformat, plain text, MIME... */
2359 char *fake_name, /* who we're masquerading as */
2360 char *subject, /* Subject (optional) */
2361 char *preformatted_text /* ...or NULL to read text from client */
2363 char dest_node[SIZ];
2365 struct CtdlMessage *msg;
2367 msg = malloc(sizeof(struct CtdlMessage));
2368 memset(msg, 0, sizeof(struct CtdlMessage));
2369 msg->cm_magic = CTDLMESSAGE_MAGIC;
2370 msg->cm_anon_type = type;
2371 msg->cm_format_type = format_type;
2373 /* Don't confuse the poor folks if it's not routed mail. */
2374 strcpy(dest_node, "");
2378 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2379 msg->cm_fields['P'] = strdup(buf);
2381 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2382 msg->cm_fields['T'] = strdup(buf);
2384 if (fake_name[0]) /* author */
2385 msg->cm_fields['A'] = strdup(fake_name);
2387 msg->cm_fields['A'] = strdup(author->fullname);
2389 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2390 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2393 msg->cm_fields['O'] = strdup(CC->room.QRname);
2396 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2397 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2399 if (recipient[0] != 0) {
2400 msg->cm_fields['R'] = strdup(recipient);
2402 if (dest_node[0] != 0) {
2403 msg->cm_fields['D'] = strdup(dest_node);
2406 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2407 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2410 if (subject != NULL) {
2412 if (strlen(subject) > 0) {
2413 msg->cm_fields['U'] = strdup(subject);
2417 if (preformatted_text != NULL) {
2418 msg->cm_fields['M'] = preformatted_text;
2421 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2422 config.c_maxmsglen, NULL, 0);
2430 * Check to see whether we have permission to post a message in the current
2431 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2432 * returns 0 on success.
2434 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2436 if (!(CC->logged_in)) {
2437 snprintf(errmsgbuf, n, "Not logged in.");
2438 return (ERROR + NOT_LOGGED_IN);
2441 if ((CC->user.axlevel < 2)
2442 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2443 snprintf(errmsgbuf, n, "Need to be validated to enter "
2444 "(except in %s> to sysop)", MAILROOM);
2445 return (ERROR + HIGHER_ACCESS_REQUIRED);
2448 if ((CC->user.axlevel < 4)
2449 && (CC->room.QRflags & QR_NETWORK)) {
2450 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2451 return (ERROR + HIGHER_ACCESS_REQUIRED);
2454 if ((CC->user.axlevel < 6)
2455 && (CC->room.QRflags & QR_READONLY)) {
2456 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2457 return (ERROR + HIGHER_ACCESS_REQUIRED);
2460 strcpy(errmsgbuf, "Ok");
2466 * Check to see if the specified user has Internet mail permission
2467 * (returns nonzero if permission is granted)
2469 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2471 /* Do not allow twits to send Internet mail */
2472 if (who->axlevel <= 2) return(0);
2474 /* Globally enabled? */
2475 if (config.c_restrict == 0) return(1);
2477 /* User flagged ok? */
2478 if (who->flags & US_INTERNET) return(2);
2480 /* Aide level access? */
2481 if (who->axlevel >= 6) return(3);
2483 /* No mail for you! */
2490 * Validate recipients, count delivery types and errors, and handle aliasing
2491 * FIXME check for dupes!!!!!
2493 struct recptypes *validate_recipients(char *recipients) {
2494 struct recptypes *ret;
2495 char this_recp[SIZ];
2496 char this_recp_cooked[SIZ];
2502 struct ctdluser tempUS;
2503 struct ctdlroom tempQR;
2506 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2507 if (ret == NULL) return(NULL);
2508 memset(ret, 0, sizeof(struct recptypes));
2511 ret->num_internet = 0;
2516 if (recipients == NULL) {
2519 else if (strlen(recipients) == 0) {
2523 /* Change all valid separator characters to commas */
2524 for (i=0; i<strlen(recipients); ++i) {
2525 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2526 recipients[i] = ',';
2531 num_recps = num_tokens(recipients, ',');
2534 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2535 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2537 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2538 mailtype = alias(this_recp);
2539 mailtype = alias(this_recp);
2540 mailtype = alias(this_recp);
2541 for (j=0; j<=strlen(this_recp); ++j) {
2542 if (this_recp[j]=='_') {
2543 this_recp_cooked[j] = ' ';
2546 this_recp_cooked[j] = this_recp[j];
2552 if (!strcasecmp(this_recp, "sysop")) {
2554 strcpy(this_recp, config.c_aideroom);
2555 if (strlen(ret->recp_room) > 0) {
2556 strcat(ret->recp_room, "|");
2558 strcat(ret->recp_room, this_recp);
2560 else if (getuser(&tempUS, this_recp) == 0) {
2562 strcpy(this_recp, tempUS.fullname);
2563 if (strlen(ret->recp_local) > 0) {
2564 strcat(ret->recp_local, "|");
2566 strcat(ret->recp_local, this_recp);
2568 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2570 strcpy(this_recp, tempUS.fullname);
2571 if (strlen(ret->recp_local) > 0) {
2572 strcat(ret->recp_local, "|");
2574 strcat(ret->recp_local, this_recp);
2576 else if ( (!strncasecmp(this_recp, "room_", 5))
2577 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2579 if (strlen(ret->recp_room) > 0) {
2580 strcat(ret->recp_room, "|");
2582 strcat(ret->recp_room, &this_recp_cooked[5]);
2590 /* Yes, you're reading this correctly: if the target
2591 * domain points back to the local system or an attached
2592 * Citadel directory, the address is invalid. That's
2593 * because if the address were valid, we would have
2594 * already translated it to a local address by now.
2596 if (IsDirectory(this_recp)) {
2601 ++ret->num_internet;
2602 if (strlen(ret->recp_internet) > 0) {
2603 strcat(ret->recp_internet, "|");
2605 strcat(ret->recp_internet, this_recp);
2610 if (strlen(ret->recp_ignet) > 0) {
2611 strcat(ret->recp_ignet, "|");
2613 strcat(ret->recp_ignet, this_recp);
2621 if (strlen(ret->errormsg) == 0) {
2622 snprintf(append, sizeof append,
2623 "Invalid recipient: %s",
2627 snprintf(append, sizeof append,
2630 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2631 strcat(ret->errormsg, append);
2635 if (strlen(ret->display_recp) == 0) {
2636 strcpy(append, this_recp);
2639 snprintf(append, sizeof append, ", %s",
2642 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2643 strcat(ret->display_recp, append);
2648 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2649 ret->num_room + ret->num_error) == 0) {
2651 strcpy(ret->errormsg, "No recipients specified.");
2654 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2655 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2656 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2657 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2658 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2659 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2667 * message entry - mode 0 (normal)
2669 void cmd_ent0(char *entargs)
2673 char masquerade_as[SIZ];
2675 int format_type = 0;
2676 char newusername[SIZ];
2677 struct CtdlMessage *msg;
2681 struct recptypes *valid = NULL;
2688 post = extract_int(entargs, 0);
2689 extract_token(recp, entargs, 1, '|', sizeof recp);
2690 anon_flag = extract_int(entargs, 2);
2691 format_type = extract_int(entargs, 3);
2692 extract_token(subject, entargs, 4, '|', sizeof subject);
2693 do_confirm = extract_int(entargs, 6);
2695 /* first check to make sure the request is valid. */
2697 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2699 cprintf("%d %s\n", err, errmsg);
2703 /* Check some other permission type things. */
2706 if (CC->user.axlevel < 6) {
2707 cprintf("%d You don't have permission to masquerade.\n",
2708 ERROR + HIGHER_ACCESS_REQUIRED);
2711 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2712 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2713 safestrncpy(CC->fake_postname, newusername,
2714 sizeof(CC->fake_postname) );
2715 cprintf("%d ok\n", CIT_OK);
2718 CC->cs_flags |= CS_POSTING;
2720 /* In the Mail> room we have to behave a little differently --
2721 * make sure the user has specified at least one recipient. Then
2722 * validate the recipient(s).
2724 if ( (CC->room.QRflags & QR_MAILBOX)
2725 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2727 if (CC->user.axlevel < 2) {
2728 strcpy(recp, "sysop");
2731 valid = validate_recipients(recp);
2732 if (valid->num_error > 0) {
2734 ERROR + NO_SUCH_USER, valid->errormsg);
2738 if (valid->num_internet > 0) {
2739 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2740 cprintf("%d You do not have permission "
2741 "to send Internet mail.\n",
2742 ERROR + HIGHER_ACCESS_REQUIRED);
2748 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2749 && (CC->user.axlevel < 4) ) {
2750 cprintf("%d Higher access required for network mail.\n",
2751 ERROR + HIGHER_ACCESS_REQUIRED);
2756 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2757 && ((CC->user.flags & US_INTERNET) == 0)
2758 && (!CC->internal_pgm)) {
2759 cprintf("%d You don't have access to Internet mail.\n",
2760 ERROR + HIGHER_ACCESS_REQUIRED);
2767 /* Is this a room which has anonymous-only or anonymous-option? */
2768 anonymous = MES_NORMAL;
2769 if (CC->room.QRflags & QR_ANONONLY) {
2770 anonymous = MES_ANONONLY;
2772 if (CC->room.QRflags & QR_ANONOPT) {
2773 if (anon_flag == 1) { /* only if the user requested it */
2774 anonymous = MES_ANONOPT;
2778 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2782 /* If we're only checking the validity of the request, return
2783 * success without creating the message.
2786 cprintf("%d %s\n", CIT_OK,
2787 ((valid != NULL) ? valid->display_recp : "") );
2792 /* Handle author masquerading */
2793 if (CC->fake_postname[0]) {
2794 strcpy(masquerade_as, CC->fake_postname);
2796 else if (CC->fake_username[0]) {
2797 strcpy(masquerade_as, CC->fake_username);
2800 strcpy(masquerade_as, "");
2803 /* Read in the message from the client. */
2805 cprintf("%d send message\n", START_CHAT_MODE);
2807 cprintf("%d send message\n", SEND_LISTING);
2809 msg = CtdlMakeMessage(&CC->user, recp,
2810 CC->room.QRname, anonymous, format_type,
2811 masquerade_as, subject, NULL);
2814 msgnum = CtdlSubmitMsg(msg, valid, "");
2817 cprintf("%ld\n", msgnum);
2819 cprintf("Message accepted.\n");
2822 cprintf("Internal error.\n");
2824 if (msg->cm_fields['E'] != NULL) {
2825 cprintf("%s\n", msg->cm_fields['E']);
2832 CtdlFreeMessage(msg);
2834 CC->fake_postname[0] = '\0';
2842 * API function to delete messages which match a set of criteria
2843 * (returns the actual number of messages deleted)
2845 int CtdlDeleteMessages(char *room_name, /* which room */
2846 long dmsgnum, /* or "0" for any */
2847 char *content_type /* or "" for any */
2851 struct ctdlroom qrbuf;
2852 struct cdbdata *cdbfr;
2853 long *msglist = NULL;
2854 long *dellist = NULL;
2857 int num_deleted = 0;
2859 struct MetaData smi;
2861 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2862 room_name, dmsgnum, content_type);
2864 /* get room record, obtaining a lock... */
2865 if (lgetroom(&qrbuf, room_name) != 0) {
2866 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2868 return (0); /* room not found */
2870 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2872 if (cdbfr != NULL) {
2873 msglist = malloc(cdbfr->len);
2874 dellist = malloc(cdbfr->len);
2875 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2876 num_msgs = cdbfr->len / sizeof(long);
2880 for (i = 0; i < num_msgs; ++i) {
2883 /* Set/clear a bit for each criterion */
2885 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2886 delete_this |= 0x01;
2888 if (strlen(content_type) == 0) {
2889 delete_this |= 0x02;
2891 GetMetaData(&smi, msglist[i]);
2892 if (!strcasecmp(smi.meta_content_type,
2894 delete_this |= 0x02;
2898 /* Delete message only if all bits are set */
2899 if (delete_this == 0x03) {
2900 dellist[num_deleted++] = msglist[i];
2905 num_msgs = sort_msglist(msglist, num_msgs);
2906 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2907 msglist, (int)(num_msgs * sizeof(long)));
2909 qrbuf.QRhighest = msglist[num_msgs - 1];
2913 /* Go through the messages we pulled out of the index, and decrement
2914 * their reference counts by 1. If this is the only room the message
2915 * was in, the reference count will reach zero and the message will
2916 * automatically be deleted from the database. We do this in a
2917 * separate pass because there might be plug-in hooks getting called,
2918 * and we don't want that happening during an S_ROOMS critical
2921 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2922 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2923 AdjRefCount(dellist[i], -1);
2926 /* Now free the memory we used, and go away. */
2927 if (msglist != NULL) free(msglist);
2928 if (dellist != NULL) free(dellist);
2929 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2930 return (num_deleted);
2936 * Check whether the current user has permission to delete messages from
2937 * the current room (returns 1 for yes, 0 for no)
2939 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2940 getuser(&CC->user, CC->curr_user);
2941 if ((CC->user.axlevel < 6)
2942 && (CC->user.usernum != CC->room.QRroomaide)
2943 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2944 && (!(CC->internal_pgm))) {
2953 * Delete message from current room
2955 void cmd_dele(char *delstr)
2960 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2961 cprintf("%d Higher access required.\n",
2962 ERROR + HIGHER_ACCESS_REQUIRED);
2965 delnum = extract_long(delstr, 0);
2967 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2970 cprintf("%d %d message%s deleted.\n", CIT_OK,
2971 num_deleted, ((num_deleted != 1) ? "s" : ""));
2973 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2979 * Back end API function for moves and deletes
2981 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2984 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2985 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2986 if (err != 0) return(err);
2994 * move or copy a message to another room
2996 void cmd_move(char *args)
2999 char targ[ROOMNAMELEN];
3000 struct ctdlroom qtemp;
3006 num = extract_long(args, 0);
3007 extract_token(targ, args, 1, '|', sizeof targ);
3008 targ[ROOMNAMELEN - 1] = 0;
3009 is_copy = extract_int(args, 2);
3011 if (getroom(&qtemp, targ) != 0) {
3012 cprintf("%d '%s' does not exist.\n",
3013 ERROR + ROOM_NOT_FOUND, targ);
3017 getuser(&CC->user, CC->curr_user);
3018 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3020 /* Check for permission to perform this operation.
3021 * Remember: "CC->room" is source, "qtemp" is target.
3025 /* Aides can move/copy */
3026 if (CC->user.axlevel >= 6) permit = 1;
3028 /* Room aides can move/copy */
3029 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3031 /* Permit move/copy from personal rooms */
3032 if ((CC->room.QRflags & QR_MAILBOX)
3033 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3035 /* Permit only copy from public to personal room */
3037 && (!(CC->room.QRflags & QR_MAILBOX))
3038 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3040 /* User must have access to target room */
3041 if (!(ra & UA_KNOWN)) permit = 0;
3044 cprintf("%d Higher access required.\n",
3045 ERROR + HIGHER_ACCESS_REQUIRED);
3049 err = CtdlCopyMsgToRoom(num, targ);
3051 cprintf("%d Cannot store message in %s: error %d\n",
3056 /* Now delete the message from the source room,
3057 * if this is a 'move' rather than a 'copy' operation.
3060 CtdlDeleteMessages(CC->room.QRname, num, "");
3063 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3069 * GetMetaData() - Get the supplementary record for a message
3071 void GetMetaData(struct MetaData *smibuf, long msgnum)
3074 struct cdbdata *cdbsmi;
3077 memset(smibuf, 0, sizeof(struct MetaData));
3078 smibuf->meta_msgnum = msgnum;
3079 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3081 /* Use the negative of the message number for its supp record index */
3082 TheIndex = (0L - msgnum);
3084 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3085 if (cdbsmi == NULL) {
3086 return; /* record not found; go with defaults */
3088 memcpy(smibuf, cdbsmi->ptr,
3089 ((cdbsmi->len > sizeof(struct MetaData)) ?
3090 sizeof(struct MetaData) : cdbsmi->len));
3097 * PutMetaData() - (re)write supplementary record for a message
3099 void PutMetaData(struct MetaData *smibuf)
3103 /* Use the negative of the message number for the metadata db index */
3104 TheIndex = (0L - smibuf->meta_msgnum);
3106 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3107 smibuf->meta_msgnum, smibuf->meta_refcount);
3109 cdb_store(CDB_MSGMAIN,
3110 &TheIndex, (int)sizeof(long),
3111 smibuf, (int)sizeof(struct MetaData));
3116 * AdjRefCount - change the reference count for a message;
3117 * delete the message if it reaches zero
3119 void AdjRefCount(long msgnum, int incr)
3122 struct MetaData smi;
3125 /* This is a *tight* critical section; please keep it that way, as
3126 * it may get called while nested in other critical sections.
3127 * Complicating this any further will surely cause deadlock!
3129 begin_critical_section(S_SUPPMSGMAIN);
3130 GetMetaData(&smi, msgnum);
3131 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3132 msgnum, smi.meta_refcount);
3133 smi.meta_refcount += incr;
3135 end_critical_section(S_SUPPMSGMAIN);
3136 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3137 msgnum, smi.meta_refcount);
3139 /* If the reference count is now zero, delete the message
3140 * (and its supplementary record as well).
3142 if (smi.meta_refcount == 0) {
3143 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3145 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3146 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3148 /* We have to delete the metadata record too! */
3149 delnum = (0L - msgnum);
3150 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3155 * Write a generic object to this room
3157 * Note: this could be much more efficient. Right now we use two temporary
3158 * files, and still pull the message into memory as with all others.
3160 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3161 char *content_type, /* MIME type of this object */
3162 char *tempfilename, /* Where to fetch it from */
3163 struct ctdluser *is_mailbox, /* Mailbox room? */
3164 int is_binary, /* Is encoding necessary? */
3165 int is_unique, /* Del others of this type? */
3166 unsigned int flags /* Internal save flags */
3171 struct ctdlroom qrbuf;
3172 char roomname[ROOMNAMELEN];
3173 struct CtdlMessage *msg;
3175 char *raw_message = NULL;
3176 char *encoded_message = NULL;
3177 off_t raw_length = 0;
3179 if (is_mailbox != NULL)
3180 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3182 safestrncpy(roomname, req_room, sizeof(roomname));
3183 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3186 fp = fopen(tempfilename, "rb");
3188 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3189 tempfilename, strerror(errno));
3192 fseek(fp, 0L, SEEK_END);
3193 raw_length = ftell(fp);
3195 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3197 raw_message = malloc((size_t)raw_length + 2);
3198 fread(raw_message, (size_t)raw_length, 1, fp);
3202 encoded_message = malloc((size_t)
3203 (((raw_length * 134) / 100) + 4096 ) );
3206 encoded_message = malloc((size_t)(raw_length + 4096));
3209 sprintf(encoded_message, "Content-type: %s\n", content_type);
3212 sprintf(&encoded_message[strlen(encoded_message)],
3213 "Content-transfer-encoding: base64\n\n"
3217 sprintf(&encoded_message[strlen(encoded_message)],
3218 "Content-transfer-encoding: 7bit\n\n"
3224 &encoded_message[strlen(encoded_message)],
3230 raw_message[raw_length] = 0;
3232 &encoded_message[strlen(encoded_message)],
3240 lprintf(CTDL_DEBUG, "Allocating\n");
3241 msg = malloc(sizeof(struct CtdlMessage));
3242 memset(msg, 0, sizeof(struct CtdlMessage));
3243 msg->cm_magic = CTDLMESSAGE_MAGIC;
3244 msg->cm_anon_type = MES_NORMAL;
3245 msg->cm_format_type = 4;
3246 msg->cm_fields['A'] = strdup(CC->user.fullname);
3247 msg->cm_fields['O'] = strdup(req_room);
3248 msg->cm_fields['N'] = strdup(config.c_nodename);
3249 msg->cm_fields['H'] = strdup(config.c_humannode);
3250 msg->cm_flags = flags;
3252 msg->cm_fields['M'] = encoded_message;
3254 /* Create the requested room if we have to. */
3255 if (getroom(&qrbuf, roomname) != 0) {
3256 create_room(roomname,
3257 ( (is_mailbox != NULL) ? 5 : 3 ),
3258 "", 0, 1, 0, VIEW_BBS);
3260 /* If the caller specified this object as unique, delete all
3261 * other objects of this type that are currently in the room.
3264 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3265 CtdlDeleteMessages(roomname, 0L, content_type));
3267 /* Now write the data */
3268 CtdlSubmitMsg(msg, NULL, roomname);
3269 CtdlFreeMessage(msg);
3277 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3278 config_msgnum = msgnum;
3282 char *CtdlGetSysConfig(char *sysconfname) {
3283 char hold_rm[ROOMNAMELEN];
3286 struct CtdlMessage *msg;
3289 strcpy(hold_rm, CC->room.QRname);
3290 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3291 getroom(&CC->room, hold_rm);
3296 /* We want the last (and probably only) config in this room */
3297 begin_critical_section(S_CONFIG);
3298 config_msgnum = (-1L);
3299 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3300 CtdlGetSysConfigBackend, NULL);
3301 msgnum = config_msgnum;
3302 end_critical_section(S_CONFIG);
3308 msg = CtdlFetchMessage(msgnum, 1);
3310 conf = strdup(msg->cm_fields['M']);
3311 CtdlFreeMessage(msg);
3318 getroom(&CC->room, hold_rm);
3320 if (conf != NULL) do {
3321 extract_token(buf, conf, 0, '\n', sizeof buf);
3322 strcpy(conf, &conf[strlen(buf)+1]);
3323 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3328 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3329 char temp[PATH_MAX];
3332 strcpy(temp, tmpnam(NULL));
3334 fp = fopen(temp, "w");
3335 if (fp == NULL) return;
3336 fprintf(fp, "%s", sysconfdata);
3339 /* this handy API function does all the work for us */
3340 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3346 * Determine whether a given Internet address belongs to the current user
3348 int CtdlIsMe(char *addr, int addr_buf_len)
3350 struct recptypes *recp;
3353 recp = validate_recipients(addr);
3354 if (recp == NULL) return(0);
3356 if (recp->num_local == 0) {
3361 for (i=0; i<recp->num_local; ++i) {
3362 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3363 if (!strcasecmp(addr, CC->user.fullname)) {
3375 * Citadel protocol command to do the same
3377 void cmd_isme(char *argbuf) {
3380 if (CtdlAccessCheck(ac_logged_in)) return;
3381 extract_token(addr, argbuf, 0, '|', sizeof addr);
3383 if (CtdlIsMe(addr, sizeof addr)) {
3384 cprintf("%d %s\n", CIT_OK, addr);
3387 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);