X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmsgbase.c;h=e832cad06691d0af92ec0be4f9ff73c7bc48b6bb;hb=27d35cf620da260472336afd096787cf5e5558d3;hp=12afea81e35edda7671b9ca2ab8e9caf8624833a;hpb=860ea57fbd4101b7532d84a0d641a3bbe538695f;p=citadel.git diff --git a/citadel/msgbase.c b/citadel/msgbase.c index 12afea81e..e832cad06 100644 --- a/citadel/msgbase.c +++ b/citadel/msgbase.c @@ -5,6 +5,10 @@ * */ +#ifdef DLL_EXPORT +#define IN_LIBCIT +#endif + #include "sysdep.h" #include #include @@ -22,6 +26,7 @@ # endif #endif + #include #include #include @@ -31,6 +36,7 @@ #include #include "citadel.h" #include "server.h" +#include "serv_extensions.h" #include "database.h" #include "msgbase.h" #include "support.h" @@ -40,7 +46,6 @@ #include "user_ops.h" #include "file_ops.h" #include "control.h" -#include "dynloader.h" #include "tools.h" #include "mime_parser.h" #include "html.h" @@ -54,38 +59,50 @@ extern struct config config; long config_msgnum; + +/* + * This really belongs in serv_network.c, but I don't know how to export + * symbols between modules. + */ +struct FilterList *filterlist = NULL; + + +/* + * These are the four-character field headers we use when outputting + * messages in Citadel format (as opposed to RFC822 format). + */ char *msgkeys[] = { - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, "from", - "", "", "", + NULL, NULL, NULL, "exti", "rfca", - "", + NULL, "hnod", "msgn", - "", "", "", + NULL, NULL, NULL, "text", "node", "room", "path", - "", + NULL, "rcpt", "spec", "time", "subj", - "", - "", - "", - "", - "" + NULL, + NULL, + NULL, + NULL, + NULL }; /* @@ -117,16 +134,25 @@ void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) int alias(char *name) { /* process alias and routing info for mail */ FILE *fp; - int a, b; - char aaa[300], bbb[300]; + int a, i; + char aaa[SIZ], bbb[SIZ]; + char *ignetcfg = NULL; + char *ignetmap = NULL; + int at = 0; + char node[SIZ]; + char testnode[SIZ]; + char buf[SIZ]; + striplt(name); remove_any_whitespace_to_the_left_or_right_of_at_symbol(name); fp = fopen("network/mail.aliases", "r"); - if (fp == NULL) + if (fp == NULL) { fp = fopen("/dev/null", "r"); - if (fp == NULL) + } + if (fp == NULL) { return (MES_ERROR); + } strcpy(aaa, ""); strcpy(bbb, ""); while (fgets(aaa, sizeof aaa, fp) != NULL) { @@ -144,6 +170,12 @@ int alias(char *name) strcpy(name, bbb); } fclose(fp); + + /* Hit the Global Address Book */ + if (CtdlDirectoryLookup(aaa, name) == 0) { + strcpy(name, aaa); + } + lprintf(7, "Mail is being forwarded to %s\n", name); /* Change "user @ xxx" to "user" if xxx is an alias for this host */ @@ -157,76 +189,51 @@ int alias(char *name) } /* determine local or remote type, see citadel.h */ - for (a = 0; a < strlen(name); ++a) - if (name[a] == '!') - return (MES_INTERNET); - for (a = 0; a < strlen(name); ++a) - if (name[a] == '@') - for (b = a; b < strlen(name); ++b) - if (name[b] == '.') - return (MES_INTERNET); - b = 0; - for (a = 0; a < strlen(name); ++a) - if (name[a] == '@') - ++b; - if (b > 1) { - lprintf(7, "Too many @'s in address\n"); - return (MES_ERROR); + at = haschar(name, '@'); + if (at == 0) return(MES_LOCAL); /* no @'s - local address */ + if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */ + remove_any_whitespace_to_the_left_or_right_of_at_symbol(name); + + /* figure out the delivery mode */ + extract_token(node, name, 1, '@'); + + /* If there are one or more dots in the nodename, we assume that it + * is an FQDN and will attempt SMTP delivery to the Internet. + */ + if (haschar(node, '.') > 0) { + return(MES_INTERNET); } - if (b == 1) { - for (a = 0; a < strlen(name); ++a) - if (name[a] == '@') - strcpy(bbb, &name[a + 1]); - while (bbb[0] == 32) - strcpy(bbb, &bbb[1]); - fp = fopen("network/mail.sysinfo", "r"); - if (fp == NULL) - return (MES_ERROR); -GETSN: do { - a = getstring(fp, aaa); - } while ((a >= 0) && (strcasecmp(aaa, bbb))); - a = getstring(fp, aaa); - if (!strncmp(aaa, "use ", 4)) { - strcpy(bbb, &aaa[4]); - fseek(fp, 0L, 0); - goto GETSN; - } - fclose(fp); - if (!strncmp(aaa, "uum", 3)) { - strcpy(bbb, name); - for (a = 0; a < strlen(bbb); ++a) { - if (bbb[a] == '@') - bbb[a] = 0; - if (bbb[a] == ' ') - bbb[a] = '_'; - } - while (bbb[strlen(bbb) - 1] == '_') - bbb[strlen(bbb) - 1] = 0; - sprintf(name, &aaa[4], bbb); - lprintf(9, "returning MES_INTERNET\n"); - return (MES_INTERNET); - } - if (!strncmp(aaa, "bin", 3)) { - strcpy(aaa, name); - strcpy(bbb, name); - while (aaa[strlen(aaa) - 1] != '@') - aaa[strlen(aaa) - 1] = 0; - aaa[strlen(aaa) - 1] = 0; - while (aaa[strlen(aaa) - 1] == ' ') - aaa[strlen(aaa) - 1] = 0; - while (bbb[0] != '@') - strcpy(bbb, &bbb[1]); - strcpy(bbb, &bbb[1]); - while (bbb[0] == ' ') - strcpy(bbb, &bbb[1]); - sprintf(name, "%s @%s", aaa, bbb); - lprintf(9, "returning MES_BINARY\n"); - return (MES_BINARY); + + /* Otherwise we look in the IGnet maps for a valid Citadel node. + * Try directly-connected nodes first... + */ + ignetcfg = CtdlGetSysConfig(IGNETCFG); + for (i=0; iuser, &CC->room); + + safestrncpy(buf, vbuf.v_seen, SIZ); +} + + + /* * Manipulate the "seen msgs" string. */ @@ -293,12 +315,10 @@ void CtdlSetSeen(long target_msgnum, int target_setting) { int num_msgs = 0; /* Learn about the user and room in question */ - get_mm(); - getuser(&CC->usersupp, CC->curr_user); - CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + CtdlGetRelationship(&vbuf, &CC->user, &CC->room); /* Load the message list */ - cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long)); + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = mallok(cdbfr->len); memcpy(msglist, cdbfr->ptr, cdbfr->len); @@ -329,17 +349,24 @@ void CtdlSetSeen(long target_msgnum, int target_setting) { } if ( ((is_seen == 0) && (was_seen == 1)) || ((is_seen == 1) && (i == num_msgs-1)) ) { + size_t tmp; + if ( (strlen(newseen) + 20) > SIZ) { strcpy(newseen, &newseen[20]); newseen[0] = '*'; } - if (strlen(newseen) > 0) strcat(newseen, ","); + tmp = strlen(newseen); + if (tmp > 0) { + strcat(newseen, ","); + tmp++; + } if (lo == hi) { - sprintf(&newseen[strlen(newseen)], "%ld", lo); + snprintf(&newseen[tmp], sizeof newseen - tmp, + "%ld", lo); } else { - sprintf(&newseen[strlen(newseen)], "%ld:%ld", - lo, hi); + snprintf(&newseen[tmp], sizeof newseen - tmp, + "%ld:%ld", lo, hi); } lo = (-1L); hi = (-1L); @@ -350,7 +377,7 @@ void CtdlSetSeen(long target_msgnum, int target_setting) { safestrncpy(vbuf.v_seen, newseen, SIZ); lprintf(9, " after optimize: %s\n", vbuf.v_seen); phree(msglist); - CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + CtdlSetRelationship(&vbuf, &CC->user, &CC->room); } @@ -359,7 +386,6 @@ void CtdlSetSeen(long target_msgnum, int target_setting) { * current room. (Returns the number of messages processed.) */ int CtdlForEachMessage(int mode, long ref, - int moderation_level, char *content_type, struct CtdlMessage *compare, void (*CallBack) (long, void *), @@ -373,7 +399,7 @@ int CtdlForEachMessage(int mode, long ref, int num_msgs = 0; int num_processed = 0; long thismsg; - struct SuppMsgInfo smi; + struct MetaData smi; struct CtdlMessage *msg; int is_seen; long lastold = 0L; @@ -381,11 +407,11 @@ int CtdlForEachMessage(int mode, long ref, /* Learn about the user and room in question */ get_mm(); - getuser(&CC->usersupp, CC->curr_user); - CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + getuser(&CC->user, CC->curr_user); + CtdlGetRelationship(&vbuf, &CC->user, &CC->room); /* Load the message list */ - cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long)); + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = mallok(cdbfr->len); memcpy(msglist, cdbfr->ptr, cdbfr->len); @@ -400,20 +426,24 @@ int CtdlForEachMessage(int mode, long ref, * Now begin the traversal. */ if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) { - GetSuppMsgInfo(&smi, msglist[a]); - - /* Filter out messages that are moderated below the level - * currently being viewed at. - */ - if (smi.smi_mod < moderation_level) { - msglist[a] = 0L; - } /* If the caller is looking for a specific MIME type, filter * out all messages which are not of the type requested. */ if (content_type != NULL) if (strlen(content_type) > 0) { - if (strcasecmp(smi.smi_content_type, content_type)) { + + /* This call to GetMetaData() sits inside this loop + * so that we only do the extra database read per msg + * if we need to. Doing the extra read all the time + * really kills the server. If we ever need to use + * metadata for another search criterion, we need to + * move the read somewhere else -- but still be smart + * enough to only do the read if the caller has + * specified something that will need it. + */ + GetMetaData(&smi, msglist[a]); + + if (strcasecmp(smi.meta_content_type, content_type)) { msglist[a] = 0L; } } @@ -460,7 +490,7 @@ int CtdlForEachMessage(int mode, long ref, || ((mode == MSGS_EQ) && (thismsg == ref)) ) ) { - if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) { + if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) { if (CallBack) CallBack(lastold, userdata); printed_lastold = 1; @@ -536,7 +566,6 @@ void cmd_msgs(char *cmdbuf) } CtdlForEachMessage(mode, cm_ref, - CC->usersupp.moderation_filter, NULL, template, simple_listing, NULL); if (template != NULL) CtdlFreeMessage(template); cprintf("000\n"); @@ -568,13 +597,14 @@ void do_help_subst(char *buffer) help_subst(buffer, "^nodename", config.c_nodename); help_subst(buffer, "^humannode", config.c_humannode); help_subst(buffer, "^fqdn", config.c_fqdn); - help_subst(buffer, "^username", CC->usersupp.fullname); - sprintf(buf2, "%ld", CC->usersupp.usernum); + help_subst(buffer, "^username", CC->user.fullname); + snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum); help_subst(buffer, "^usernum", buf2); help_subst(buffer, "^sysadm", config.c_sysadm); help_subst(buffer, "^variantname", CITADEL); - sprintf(buf2, "%d", config.c_maxsessions); + snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions); help_subst(buffer, "^maxsessions", buf2); + help_subst(buffer, "^bbsdir", BBSDIR); } @@ -595,7 +625,7 @@ void memfmout( int a, b, c; int real = 0; int old = 0; - CIT_UBYTE ch; + cit_uint8_t ch; char aaa[140]; char buffer[SIZ]; @@ -675,8 +705,28 @@ void list_this_part(char *name, char *filename, char *partnum, char *disp, void *cbuserdata) { - cprintf("part=%s|%s|%s|%s|%s|%d\n", - name, filename, partnum, disp, cbtype, length); + cprintf("part=%s|%s|%s|%s|%s|%ld\n", + name, filename, partnum, disp, cbtype, (long)length); +} + +/* + * Callback function for multipart prefix + */ +void list_this_pref(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, size_t length, char *encoding, + void *cbuserdata) +{ + cprintf("pref=%s|%s\n", partnum, cbtype); +} + +/* + * Callback function for multipart sufffix + */ +void list_this_suff(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, size_t length, char *encoding, + void *cbuserdata) +{ + cprintf("suff=%s|%s\n", partnum, cbtype); } @@ -721,8 +771,8 @@ struct CtdlMessage *CtdlFetchMessage(long msgnum) struct cdbdata *dmsgtext; struct CtdlMessage *ret = NULL; char *mptr; - CIT_UBYTE ch; - CIT_UBYTE field_header; + cit_uint8_t ch; + cit_uint8_t field_header; size_t field_length; dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long)); @@ -817,7 +867,44 @@ void CtdlFreeMessage(struct CtdlMessage *msg) /* - * Callback function for mime parser that wants to display text + * Pre callback function for multipart/alternative + * + * NOTE: this differs from the standard behavior for a reason. Normally when + * displaying multipart/alternative you want to show the _last_ usable + * format in the message. Here we show the _first_ one, because it's + * usually text/plain. Since this set of functions is designed for text + * output to non-MIME-aware clients, this is the desired behavior. + * + */ +void fixed_output_pre(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, size_t length, char *encoding, + void *cbuserdata) +{ + lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype); + if (!strcasecmp(cbtype, "multipart/alternative")) { + ++ma->is_ma; + ma->did_print = 0; + return; + } +} + +/* + * Post callback function for multipart/alternative + */ +void fixed_output_post(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, size_t length, char *encoding, + void *cbuserdata) +{ + lprintf(9, "fixed_output_post() type=<%s>\n", cbtype); + if (!strcasecmp(cbtype, "multipart/alternative")) { + --ma->is_ma; + ma->did_print = 0; + return; + } +} + +/* + * Inline callback function for mime parser that wants to display text */ void fixed_output(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, size_t length, char *encoding, @@ -826,56 +913,116 @@ void fixed_output(char *name, char *filename, char *partnum, char *disp, char *ptr; char *wptr; size_t wlen; - CIT_UBYTE ch = 0; - - if (!strcasecmp(cbtype, "multipart/alternative")) { - strcpy(ma->prefix, partnum); - strcat(ma->prefix, "."); - ma->is_ma = 1; - ma->did_print = 0; - return; - } - - if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix))) - && (ma->is_ma == 1) - && (ma->did_print == 1) ) { + + lprintf(9, "fixed_output() type=<%s>\n", cbtype); + + /* + * If we're in the middle of a multipart/alternative scope and + * we've already printed another section, skip this one. + */ + if ( (ma->is_ma == 1) && (ma->did_print == 1) ) { lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype); return; } - ma->did_print = 1; if ( (!strcasecmp(cbtype, "text/plain")) || (strlen(cbtype)==0) ) { - wlen = length; wptr = content; - while (wlen--) { - ch = *wptr++; - /********** - if (ch==10) cprintf("\r\n"); - else cprintf("%c", ch); - **********/ - cprintf("%c", ch); + if (length > 0) { + client_write(wptr, length); + if (wptr[length-1] != '\n') { + cprintf("\n"); + } } - if (ch != '\n') cprintf("\n"); } else if (!strcasecmp(cbtype, "text/html")) { ptr = html_to_ascii(content, 80, 0); wlen = strlen(ptr); - wptr = ptr; - while (wlen--) { - ch = *wptr++; - if (ch==10) cprintf("\r\n"); - else cprintf("%c", ch); + client_write(ptr, wlen); + if (ptr[wlen-1] != '\n') { + cprintf("\n"); } phree(ptr); } else if (strncasecmp(cbtype, "multipart/", 10)) { - cprintf("Part %s: %s (%s) (%d bytes)\r\n", - partnum, filename, cbtype, length); + cprintf("Part %s: %s (%s) (%ld bytes)\r\n", + partnum, filename, cbtype, (long)length); + } + } + +/* + * The client is elegant and sophisticated and wants to be choosy about + * MIME content types, so figure out which multipart/alternative part + * we're going to send. + */ +void choose_preferred(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, size_t length, char *encoding, + void *cbuserdata) +{ + char buf[SIZ]; + int i; + + if (ma->is_ma > 0) { + for (i=0; ipreferred_formats, '|'); ++i) { + extract(buf, CC->preferred_formats, i); + if (!strcasecmp(buf, cbtype)) { + strcpy(ma->chosen_part, partnum); + } + } + } +} + +/* + * Now that we've chosen our preferred part, output it. + */ +void output_preferred(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, size_t length, char *encoding, + void *cbuserdata) +{ + int i; + char buf[SIZ]; + int add_newline = 0; + char *text_content; + + /* This is not the MIME part you're looking for... */ + if (strcasecmp(partnum, ma->chosen_part)) return; + + /* If the content-type of this part is in our preferred formats + * list, we can simply output it verbatim. + */ + for (i=0; ipreferred_formats, '|'); ++i) { + extract(buf, CC->preferred_formats, i); + if (!strcasecmp(buf, cbtype)) { + /* Yeah! Go! W00t!! */ + + text_content = (char *)content; + if (text_content[length-1] != '\n') { + ++add_newline; + } + + cprintf("Content-type: %s\n", cbtype); + cprintf("Content-length: %d\n", + (int)(length + add_newline) ); + if (strlen(encoding) > 0) { + cprintf("Content-transfer-encoding: %s\n", encoding); + } + else { + cprintf("Content-transfer-encoding: 7bit\n"); + } + cprintf("\n"); + client_write(content, length); + if (add_newline) cprintf("\n"); + return; } } + /* No translations required or possible: output as text/plain */ + cprintf("Content-type: text/plain\n\n"); + fixed_output(name, filename, partnum, disp, content, cbtype, + length, encoding, cbuserdata); +} + /* * Get a message off disk. (returns om_* values found in msgbase.h) @@ -915,9 +1062,10 @@ int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */ */ /* - * Fetch the message from disk + * Fetch the message from disk. */ TheMessage = CtdlFetchMessage(msg_num); + if (TheMessage == NULL) { if (do_proto) cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num); @@ -929,6 +1077,7 @@ int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */ headers_only, do_proto, crlf); CtdlFreeMessage(TheMessage); + return(retcode); } @@ -946,11 +1095,13 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, ) { int i, k; char buf[1024]; - CIT_UBYTE ch; + cit_uint8_t ch; char allkeys[SIZ]; char display_name[SIZ]; char *mptr; char *nl; /* newline string */ + int suppress_f = 0; + int subject_found = 0; /* buffers needed for RFC822 translation */ char suser[SIZ]; @@ -962,7 +1113,7 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, char datestamp[SIZ]; /* */ - sprintf(mid, "%ld", msg_num); + snprintf(mid, sizeof mid, "%ld", msg_num); nl = (crlf ? "\r\n" : "\n"); if (!is_valid_message(TheMessage)) { @@ -1002,22 +1153,20 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, /* now for the user-mode message reading loops */ if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num); - /* Tell the client which format type we're using. If this is a - * MIME message, *lie* about it and tell the user it's fixed-format. - */ - if (mode == MT_CITADEL) { - if (TheMessage->cm_format_type == FMT_RFC822) { - if (do_proto) cprintf("type=1\n"); - } - else { - if (do_proto) cprintf("type=%d\n", - TheMessage->cm_format_type); - } + /* Does the caller want to skip the headers? */ + if (headers_only == HEADERS_NONE) goto START_TEXT; + + /* Tell the client which format type we're using. */ + if ( (mode == MT_CITADEL) && (do_proto) ) { + cprintf("type=%d\n", TheMessage->cm_format_type); } /* nhdr=yes means that we're only displaying headers, no body */ - if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) { - if (do_proto) cprintf("nhdr=yes\n"); + if ( (TheMessage->cm_anon_type == MES_ANONONLY) + && (mode == MT_CITADEL) + && (do_proto) + ) { + cprintf("nhdr=yes\n"); } /* begin header processing loop for Citadel message format */ @@ -1028,30 +1177,51 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, if (TheMessage->cm_fields['A']) { strcpy(buf, TheMessage->cm_fields['A']); PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG); - if (TheMessage->cm_anon_type == MES_ANON) + if (TheMessage->cm_anon_type == MES_ANONONLY) { strcpy(display_name, "****"); - else if (TheMessage->cm_anon_type == MES_AN2) + } + else if (TheMessage->cm_anon_type == MES_ANONOPT) { strcpy(display_name, "anonymous"); - else + } + else { strcpy(display_name, buf); + } if ((is_room_aide()) - && ((TheMessage->cm_anon_type == MES_ANON) - || (TheMessage->cm_anon_type == MES_AN2))) { - sprintf(&display_name[strlen(display_name)], - " [%s]", buf); + && ((TheMessage->cm_anon_type == MES_ANONONLY) + || (TheMessage->cm_anon_type == MES_ANONOPT))) { + size_t tmp = strlen(display_name); + snprintf(&display_name[tmp], + sizeof display_name - tmp, + " [%s]", buf); } } + /* Don't show Internet address for users on the + * local Citadel network. + */ + suppress_f = 0; + if (TheMessage->cm_fields['N'] != NULL) + if (strlen(TheMessage->cm_fields['N']) > 0) + if (haschar(TheMessage->cm_fields['N'], '.') == 0) { + suppress_f = 1; + } + + /* Now spew the header fields in the order we like them. */ strcpy(allkeys, FORDER); for (i=0; icm_fields[k] != NULL) { + if ( (TheMessage->cm_fields[k] != NULL) + && (msgkeys[k] != NULL) ) { if (k == 'A') { if (do_proto) cprintf("%s=%s\n", msgkeys[k], display_name); } + else if ((k == 'F') && (suppress_f)) { + /* do nothing */ + } + /* Masquerade display name if needed */ else { if (do_proto) cprintf("%s=%s\n", msgkeys[k], @@ -1090,26 +1260,33 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, cprintf("Path: %s%s", mptr, nl); } ****/ - else if (i == 'U') + else if (i == 'U') { cprintf("Subject: %s%s", mptr, nl); + subject_found = 1; + } else if (i == 'I') - strcpy(mid, mptr); + safestrncpy(mid, mptr, sizeof mid); else if (i == 'H') - strcpy(lnode, mptr); + safestrncpy(lnode, mptr, sizeof lnode); + else if (i == 'F') + safestrncpy(fuser, mptr, sizeof fuser); else if (i == 'O') cprintf("X-Citadel-Room: %s%s", mptr, nl); else if (i == 'N') - strcpy(snode, mptr); + safestrncpy(snode, mptr, sizeof snode); else if (i == 'R') cprintf("To: %s%s", mptr, nl); else if (i == 'T') { - datestring(datestamp, atol(mptr), - DATESTRING_RFC822 ); + datestring(datestamp, sizeof datestamp, + atol(mptr), DATESTRING_RFC822); cprintf("Date: %s%s", datestamp, nl); } } } + if (subject_found == 0) { + cprintf("Subject: (no subject)%s", nl); + } } for (i=0; icm_format_type != FMT_RFC822) { + cprintf("%s", nl); + } } /* end header processing loop ... at this point, we're in the text */ - +START_TEXT: mptr = TheMessage->cm_fields['M']; /* Tell the client about the MIME parts in this message */ if (TheMessage->cm_format_type == FMT_RFC822) { - if (mode == MT_CITADEL) { + if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) { mime_parser(mptr, NULL, - *list_this_part, NULL, NULL, + *list_this_part, + *list_this_pref, + *list_this_suff, NULL, 0); } - else if (mode == MT_MIME) { /* list parts only */ - mime_parser(mptr, NULL, - *list_this_part, NULL, NULL, - NULL, 0); - if (do_proto) cprintf("000\n"); - return(om_ok); - } else if (mode == MT_RFC822) { /* unparsed RFC822 dump */ /* FIXME ... we have to put some code in here to avoid * printing duplicate header information when both * Citadel and RFC822 headers exist. Preference should * probably be given to the RFC822 headers. */ + int done_rfc822_hdrs = 0; while (ch=*(mptr++), ch!=0) { - if (ch==13) ; - else if (ch==10) cprintf("%s", nl); - else cprintf("%c", ch); + if (ch==13) { + /* do nothing */ + } + else if (ch==10) { + if (!done_rfc822_hdrs) { + if (headers_only != HEADERS_NONE) { + cprintf("%s", nl); + } + } + else { + if (headers_only != HEADERS_ONLY) { + cprintf("%s", nl); + } + } + if ((*(mptr) == 13) || (*(mptr) == 10)) { + done_rfc822_hdrs = 1; + } + } + else { + if (done_rfc822_hdrs) { + if (headers_only != HEADERS_NONE) { + cprintf("%c", ch); + } + } + else { + if (headers_only != HEADERS_ONLY) { + cprintf("%c", ch); + } + } + if ((*mptr == 13) || (*mptr == 10)) { + done_rfc822_hdrs = 1; + } + } } - if (do_proto) cprintf("000\n"); - return(om_ok); + goto DONE; } } - if (headers_only) { - if (do_proto) cprintf("000\n"); - return(om_ok); + if (headers_only == HEADERS_ONLY) { + goto DONE; } /* signify start of msg text */ - if (mode == MT_CITADEL) + if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) { if (do_proto) cprintf("text\n"); - if (mode == MT_RFC822) { - if (TheMessage->cm_fields['U'] == NULL) { - cprintf("Subject: (no subject)%s", nl); - } - cprintf("%s", nl); } /* If the format type on disk is 1 (fixed-format), then we want @@ -1195,6 +1396,9 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, * what message transfer format is in use. */ if (TheMessage->cm_format_type == FMT_FIXED) { + if (mode == MT_MIME) { + cprintf("Content-type: text/plain\n\n"); + } strcpy(buf, ""); while (ch = *mptr++, ch > 0) { if (ch == 13) @@ -1219,6 +1423,9 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, * message to the reader's screen width. */ if (TheMessage->cm_format_type == FMT_CITADEL) { + if (mode == MT_MIME) { + cprintf("Content-type: text/x-citadel-variformat\n\n"); + } memfmout(80, mptr, 0, nl); } @@ -1230,12 +1437,23 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, if (TheMessage->cm_format_type == FMT_RFC822) { CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info)); memset(ma, 0, sizeof(struct ma_info)); - mime_parser(mptr, NULL, - *fixed_output, NULL, NULL, - NULL, 0); + + if (mode == MT_MIME) { + strcpy(ma->chosen_part, "1"); + mime_parser(mptr, NULL, + *choose_preferred, *fixed_output_pre, + *fixed_output_post, NULL, 0); + mime_parser(mptr, NULL, + *output_preferred, NULL, NULL, NULL, 0); + } + else { + mime_parser(mptr, NULL, + *fixed_output, *fixed_output_pre, + *fixed_output_post, NULL, 0); + } } - /* now we're done */ +DONE: /* now we're done */ if (do_proto) cprintf("000\n"); return(om_ok); } @@ -1248,7 +1466,7 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage, void cmd_msg0(char *cmdbuf) { long msgid; - int headers_only = 0; + int headers_only = HEADERS_ALL; msgid = extract_long(cmdbuf, 0); headers_only = extract_int(cmdbuf, 1); @@ -1264,7 +1482,7 @@ void cmd_msg0(char *cmdbuf) void cmd_msg2(char *cmdbuf) { long msgid; - int headers_only = 0; + int headers_only = HEADERS_ALL; msgid = extract_long(cmdbuf, 0); headers_only = extract_int(cmdbuf, 1); @@ -1306,7 +1524,7 @@ void cmd_msg3(char *cmdbuf) return; } - cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len); + cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len); client_write(smr.ser, smr.len); phree(smr.ser); } @@ -1314,7 +1532,7 @@ void cmd_msg3(char *cmdbuf) /* - * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete) + * Display a message using MIME content types */ void cmd_msg4(char *cmdbuf) { @@ -1324,6 +1542,19 @@ void cmd_msg4(char *cmdbuf) CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0); } + + +/* + * Client tells us its preferred message format(s) + */ +void cmd_msgp(char *cmdbuf) +{ + safestrncpy(CC->preferred_formats, cmdbuf, + sizeof(CC->preferred_formats)); + cprintf("%d ok\n", CIT_OK); +} + + /* * Open a component of a MIME message as a download file */ @@ -1357,7 +1588,7 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n", roomname, msgid, flags); - strcpy(hold_rm, CC->quickroom.QRname); + strcpy(hold_rm, CC->room.QRname); /* We may need to check to see if this message is real */ if ( (flags & SM_VERIFY_GOODNESS) @@ -1370,8 +1601,8 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { /* Perform replication checks if necessary */ if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) { - if (getroom(&CC->quickroom, - ((roomname != NULL) ? roomname : CC->quickroom.QRname) ) + if (getroom(&CC->room, + ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) { lprintf(9, "No such room <%s>\n", roomname); if (msg != NULL) CtdlFreeMessage(msg); @@ -1379,7 +1610,7 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { } if (ReplicationChecks(msg) != 0) { - getroom(&CC->quickroom, hold_rm); + getroom(&CC->room, hold_rm); if (msg != NULL) CtdlFreeMessage(msg); lprintf(9, "Did replication, and newer exists\n"); return(0); @@ -1387,15 +1618,15 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { } /* Now the regular stuff */ - if (lgetroom(&CC->quickroom, - ((roomname != NULL) ? roomname : CC->quickroom.QRname) ) + if (lgetroom(&CC->room, + ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) { lprintf(9, "No such room <%s>\n", roomname); if (msg != NULL) CtdlFreeMessage(msg); return(ERROR + ROOM_NOT_FOUND); } - cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long)); + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr == NULL) { msglist = NULL; num_msgs = 0; @@ -1415,8 +1646,8 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { */ if (num_msgs > 0) for (i=0; iquickroom); /* unlock the room */ - getroom(&CC->quickroom, hold_rm); + lputroom(&CC->room); /* unlock the room */ + getroom(&CC->room, hold_rm); if (msg != NULL) CtdlFreeMessage(msg); return(ERROR + ALREADY_EXISTS); } @@ -1439,16 +1670,16 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { highest_msg = msglist[num_msgs - 1]; /* Write it back to disk. */ - cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long), + cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long), msglist, num_msgs * sizeof(long)); /* Free up the memory we used. */ phree(msglist); /* Update the highest-message pointer and unlock the room. */ - CC->quickroom.QRhighest = highest_msg; - lputroom(&CC->quickroom); - getroom(&CC->quickroom, hold_rm); + CC->room.QRhighest = highest_msg; + lputroom(&CC->room); + getroom(&CC->room, hold_rm); /* Bump the reference count for this message. */ if ((flags & SM_DONT_BUMP_REF)==0) { @@ -1466,7 +1697,7 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { * Message base operation to send a message to the master file * (returns new message number) * - * This is the back end for CtdlSaveMsg() and should not be directly + * This is the back end for CtdlSubmitMsg() and should not be directly * called by server-side modules. * */ @@ -1480,7 +1711,7 @@ long send_message(struct CtdlMessage *msg, /* pointer to buffer */ /* Get a new message number */ newmsgid = get_new_message_number(); - sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn); + snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn); /* Generate an ID if we don't have one already */ if (msg->cm_fields['I']==NULL) { @@ -1543,7 +1774,7 @@ void serialize_message(struct ser_ret *ret, /* return values */ ret->len = ret->len + strlen(msg->cm_fields[(int)forder[i]]) + 2; - lprintf(9, "calling malloc(%d)\n", ret->len); + lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len); ret->ser = mallok(ret->len); if (ret->ser == NULL) { ret->len = 0; @@ -1560,8 +1791,8 @@ void serialize_message(struct ser_ret *ret, /* return values */ strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]); wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1; } - if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n", - ret->len, wlen); + if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n", + (long)ret->len, (long)wlen); return; } @@ -1591,7 +1822,7 @@ void check_repl(long msgnum, void *userdata) { lprintf(9, "older!\n"); /* Existing isn't newer? Then delete the old one(s). */ - CtdlDeleteMessages(CC->quickroom.QRname, msgnum, ""); + CtdlDeleteMessages(CC->room.QRname, msgnum, ""); } @@ -1621,8 +1852,7 @@ int ReplicationChecks(struct CtdlMessage *msg) { memset(template, 0, sizeof(struct CtdlMessage)); template->cm_fields['E'] = strdoop(msg->cm_fields['E']); - CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template, - check_repl, NULL); + CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL); /* If a newer message exists with the same Extended ID, abort * this save. @@ -1640,14 +1870,13 @@ int ReplicationChecks(struct CtdlMessage *msg) { /* - * Save a message to disk + * Save a message to disk and submit it into the delivery system. */ -long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ - char *rec, /* Recipient (mail) */ - char *force, /* force a particular room? */ - int supplied_mailtype) /* local or remote type */ -{ - char aaa[100]; +long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ + struct recptypes *recps, /* recipients (if mail) */ + char *force /* force a particular room? */ +) { + char aaa[SIZ]; char hold_rm[ROOMNAMELEN]; char actual_rm[ROOMNAMELEN]; char force_room[ROOMNAMELEN]; @@ -1655,25 +1884,25 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ char recipient[SIZ]; long newmsgid; char *mptr = NULL; - struct usersupp userbuf; - int a; - struct SuppMsgInfo smi; + struct ctdluser userbuf; + int a, i; + struct MetaData smi; FILE *network_fp = NULL; static int seqnum = 1; - struct CtdlMessage *imsg; + struct CtdlMessage *imsg = NULL; char *instr; - int mailtype; + struct ser_ret smr; + char *hold_R, *hold_D; - lprintf(9, "CtdlSaveMsg() called\n"); + lprintf(9, "CtdlSubmitMsg() called\n"); if (is_valid_message(msg) == 0) return(-1); /* self check */ - mailtype = supplied_mailtype; /* If this message has no timestamp, we take the liberty of * giving it one, right now. */ if (msg->cm_fields['T'] == NULL) { lprintf(9, "Generating timestamp\n"); - sprintf(aaa, "%ld", time(NULL)); + snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL)); msg->cm_fields['T'] = strdoop(aaa); } @@ -1694,28 +1923,12 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ } } - strcpy(force_room, force); - - /* Strip non-printable characters out of the recipient name */ - lprintf(9, "Checking recipient (if present)\n"); - strcpy(recipient, rec); - for (a = 0; a < strlen(recipient); ++a) - if (!isprint(recipient[a])) - strcpy(&recipient[a], &recipient[a + 1]); - - /* Change "user @ xxx" to "user" if xxx is an alias for this host */ - for (a=0; a\n", recipient); - mailtype = MES_LOCAL; - } - } + if (force == NULL) { + strcpy(force_room, ""); + } + else { + strcpy(force_room, force); } - - lprintf(9, "Recipient is <%s>\n", recipient); /* Learn about what's inside, because it's what's inside that counts */ lprintf(9, "Learning what's inside\n"); @@ -1754,13 +1967,16 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ /* Goto the correct room */ lprintf(9, "Switching rooms\n"); - strcpy(hold_rm, CC->quickroom.QRname); - strcpy(actual_rm, CC->quickroom.QRname); + strcpy(hold_rm, CC->room.QRname); + strcpy(actual_rm, CC->room.QRname); + if (recps != NULL) { + strcpy(actual_rm, SENTITEMS); + } /* If the user is a twit, move to the twit room for posting */ lprintf(9, "Handling twit stuff\n"); if (TWITDETECT) { - if (CC->usersupp.axlevel == 2) { + if (CC->user.axlevel == 2) { strcpy(hold_rm, actual_rm); strcpy(actual_rm, config.c_twitroom); } @@ -1772,15 +1988,15 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ } lprintf(9, "Possibly relocating\n"); - if (strcasecmp(actual_rm, CC->quickroom.QRname)) { - getroom(&CC->quickroom, actual_rm); + if (strcasecmp(actual_rm, CC->room.QRname)) { + getroom(&CC->room, actual_rm); } /* * If this message has no O (room) field, generate one. */ if (msg->cm_fields['O'] == NULL) { - msg->cm_fields['O'] = strdoop(CC->quickroom.QRname); + msg->cm_fields['O'] = strdoop(CC->room.QRname); } /* Perform "before save" hooks (aborting if any return nonzero) */ @@ -1791,37 +2007,21 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ lprintf(9, "Performing replication checks\n"); if (ReplicationChecks(msg) > 0) return(-1); - /* Network mail - send a copy to the network program. */ - if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) { - lprintf(9, "Sending network spool\n"); - sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x", - (long) getpid(), CC->cs_pid, ++seqnum); - lprintf(9, "Saving a copy to %s\n", aaa); - network_fp = fopen(aaa, "ab+"); - if (network_fp == NULL) - lprintf(2, "ERROR: %s\n", strerror(errno)); - } - /* Save it to disk */ lprintf(9, "Saving to disk\n"); - newmsgid = send_message(msg, network_fp); - if (network_fp != NULL) { - fclose(network_fp); - system("exec nohup ./netproc -i >/dev/null 2>&1 &"); - } - + newmsgid = send_message(msg, NULL); if (newmsgid <= 0L) return(-1); /* Write a supplemental message info record. This doesn't have to * be a critical section because nobody else knows about this message * yet. */ - lprintf(9, "Creating SuppMsgInfo record\n"); - memset(&smi, 0, sizeof(struct SuppMsgInfo)); - smi.smi_msgnum = newmsgid; - smi.smi_refcount = 0; - safestrncpy(smi.smi_content_type, content_type, 64); - PutSuppMsgInfo(&smi); + lprintf(9, "Creating MetaData record\n"); + memset(&smi, 0, sizeof(struct MetaData)); + smi.meta_msgnum = newmsgid; + smi.meta_refcount = 0; + safestrncpy(smi.meta_content_type, content_type, 64); + PutMetaData(&smi); /* Now figure out where to store the pointers */ lprintf(9, "Storing pointers\n"); @@ -1830,37 +2030,51 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ * message, we want to BYPASS saving the sender's copy (because there * is no local sender; it would otherwise go to the Trashcan). */ - if ((!CC->internal_pgm) || (strlen(recipient) == 0)) { + if ((!CC->internal_pgm) || (recps == NULL)) { if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) { lprintf(3, "ERROR saving message pointer!\n"); - CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0); + CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0); } } /* For internet mail, drop a copy in the outbound queue room */ - if (mailtype == MES_INTERNET) { + if (recps != NULL) + if (recps->num_internet > 0) { CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0); } + /* If other rooms are specified, drop them there too. */ + if (recps != NULL) + if (recps->num_room > 0) + for (i=0; irecp_room, '|'); ++i) { + extract(recipient, recps->recp_room, i); + lprintf(9, "Delivering to local room <%s>\n", recipient); + CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0); + } + /* Bump this user's messages posted counter. */ lprintf(9, "Updating user\n"); - lgetuser(&CC->usersupp, CC->curr_user); - CC->usersupp.posted = CC->usersupp.posted + 1; - lputuser(&CC->usersupp); + lgetuser(&CC->user, CC->curr_user); + CC->user.posted = CC->user.posted + 1; + lputuser(&CC->user); /* If this is private, local mail, make a copy in the * recipient's mailbox and bump the reference count. */ - if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) { + if (recps != NULL) + if (recps->num_local > 0) + for (i=0; irecp_local, '|'); ++i) { + extract(recipient, recps->recp_local, i); + lprintf(9, "Delivering private local mail to <%s>\n", + recipient); if (getuser(&userbuf, recipient) == 0) { - lprintf(9, "Delivering private mail\n"); - MailboxName(actual_rm, &userbuf, MAILROOM); + MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM); CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0); + BumpNewMailCounter(userbuf.usernum); } else { - lprintf(9, "No user <%s>, saving in %s> instead\n", - recipient, AIDEROOM); - CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0); + lprintf(9, "No user <%s>\n", recipient); + CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0); } } @@ -1868,24 +2082,72 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ lprintf(9, "Performing after-save hooks\n"); PerformMessageHooks(msg, EVT_AFTERSAVE); - /* */ + /* For IGnet mail, we have to save a new copy into the spooler for + * each recipient, with the R and D fields set to the recipient and + * destination-node. This has two ugly side effects: all other + * recipients end up being unlisted in this recipient's copy of the + * message, and it has to deliver multiple messages to the same + * node. We'll revisit this again in a year or so when everyone has + * a network spool receiver that can handle the new style messages. + */ + if (recps != NULL) + if (recps->num_ignet > 0) + for (i=0; irecp_ignet, '|'); ++i) { + extract(recipient, recps->recp_ignet, i); + + hold_R = msg->cm_fields['R']; + hold_D = msg->cm_fields['D']; + msg->cm_fields['R'] = mallok(SIZ); + msg->cm_fields['D'] = mallok(SIZ); + extract_token(msg->cm_fields['R'], recipient, 0, '@'); + extract_token(msg->cm_fields['D'], recipient, 1, '@'); + + serialize_message(&smr, msg); + if (smr.len > 0) { + snprintf(aaa, sizeof aaa, + "./network/spoolin/netmail.%04lx.%04x.%04x", + (long) getpid(), CC->cs_pid, ++seqnum); + network_fp = fopen(aaa, "wb+"); + if (network_fp != NULL) { + fwrite(smr.ser, smr.len, 1, network_fp); + fclose(network_fp); + } + phree(smr.ser); + } + + phree(msg->cm_fields['R']); + phree(msg->cm_fields['D']); + msg->cm_fields['R'] = hold_R; + msg->cm_fields['D'] = hold_D; + } + + /* Go back to the room we started from */ lprintf(9, "Returning to original room\n"); - if (strcasecmp(hold_rm, CC->quickroom.QRname)) - getroom(&CC->quickroom, hold_rm); + if (strcasecmp(hold_rm, CC->room.QRname)) + getroom(&CC->room, hold_rm); - /* For internet mail, generate delivery instructions - * (Yes, this is recursive! Deal with it!) + /* For internet mail, generate delivery instructions. + * Yes, this is recursive. Deal with it. Infinite recursion does + * not happen because the delivery instructions message does not + * contain a recipient. */ - if (mailtype == MES_INTERNET) { + if (recps != NULL) + if (recps->num_internet > 0) { lprintf(9, "Generating delivery instructions\n"); - instr = mallok(2048); - sprintf(instr, + instr = mallok(SIZ * 2); + snprintf(instr, SIZ * 2, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n" - "bounceto|%s@%s\n" - "remote|%s|0||\n", - SPOOLMIME, newmsgid, time(NULL), - msg->cm_fields['A'], msg->cm_fields['N'], - recipient ); + "bounceto|%s@%s\n", + SPOOLMIME, newmsgid, (long)time(NULL), + msg->cm_fields['A'], msg->cm_fields['N'] + ); + + for (i=0; irecp_internet, '|'); ++i) { + size_t tmp = strlen(instr); + extract(recipient, recps->recp_internet, i); + snprintf(&instr[tmp], SIZ * 2 - tmp, + "remote|%s|0||\n", recipient); + } imsg = mallok(sizeof(struct CtdlMessage)); memset(imsg, 0, sizeof(struct CtdlMessage)); @@ -1894,7 +2156,7 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ imsg->cm_format_type = FMT_RFC822; imsg->cm_fields['A'] = strdoop("Citadel"); imsg->cm_fields['M'] = instr; - CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL); + CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM); CtdlFreeMessage(imsg); } @@ -1906,36 +2168,44 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ /* * Convenience function for generating small administrative messages. */ -void quickie_message(char *from, char *to, char *room, char *text) +void quickie_message(char *from, char *to, char *room, char *text, + int format_type, char *subject) { struct CtdlMessage *msg; + struct recptypes *recp = NULL; msg = mallok(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; - msg->cm_format_type = 0; + msg->cm_format_type = format_type; msg->cm_fields['A'] = strdoop(from); - msg->cm_fields['O'] = strdoop(room); + if (room != NULL) msg->cm_fields['O'] = strdoop(room); msg->cm_fields['N'] = strdoop(NODENAME); - if (to != NULL) + if (to != NULL) { msg->cm_fields['R'] = strdoop(to); + recp = validate_recipients(to); + } + if (subject != NULL) { + msg->cm_fields['U'] = strdoop(subject); + } msg->cm_fields['M'] = strdoop(text); - CtdlSaveMsg(msg, "", room, MES_LOCAL); + CtdlSubmitMsg(msg, recp, room); CtdlFreeMessage(msg); - syslog(LOG_NOTICE, text); + if (recp != NULL) phree(recp); } /* - * Back end function used by make_message() and similar functions + * Back end function used by CtdlMakeMessage() and similar functions */ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ size_t maxlen, /* maximum message length */ - char *exist /* if non-null, append to it; + char *exist, /* if non-null, append to it; exist is ALWAYS freed */ + int crlf /* CRLF newlines instead of LF */ ) { char buf[SIZ]; int linelen; @@ -1943,62 +2213,70 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ size_t buffer_len = 0; char *ptr; char *m; + int flushing = 0; + int finished = 0; if (exist == NULL) { m = mallok(4096); + m[0] = 0; + buffer_len = 4096; + message_len = 0; } else { - m = reallok(exist, strlen(exist) + 4096); - if (m == NULL) phree(exist); + message_len = strlen(exist); + buffer_len = message_len + 4096; + m = reallok(exist, buffer_len); + if (m == NULL) { + phree(exist); + return m; + } } + + /* flush the input if we have nowhere to store it */ if (m == NULL) { - while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;; - return(NULL); - } else { - buffer_len = 4096; - m[0] = 0; - message_len = 0; + flushing = 1; } + /* read in the lines of message text one by one */ - while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) { - - /* strip trailing newline type stuff */ - if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0; - if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0; - - linelen = strlen(buf); - - /* augment the buffer if we have to */ - if ((message_len + linelen + 2) > buffer_len) { - lprintf(9, "realloking\n"); - ptr = reallok(m, (buffer_len * 2) ); - if (ptr == NULL) { /* flush if can't allocate */ - while ( (client_gets(buf)>0) && - strcmp(buf, terminator)) ;; - return(m); - } else { - buffer_len = (buffer_len * 2); - m = ptr; - lprintf(9, "buffer_len is %d\n", buffer_len); - } + do { + if (client_gets(buf) < 1) finished = 1; + if (!strcmp(buf, terminator)) finished = 1; + if (crlf) { + strcat(buf, "\r\n"); + } + else { + strcat(buf, "\n"); } - /* Add the new line to the buffer. We avoid using strcat() - * because that would involve traversing the entire message - * after each line, and this function needs to run fast. - */ - strcpy(&m[message_len], buf); - m[message_len + linelen] = '\n'; - m[message_len + linelen + 1] = 0; - message_len = message_len + linelen + 1; + if ( (!flushing) && (!finished) ) { + /* Measure the line */ + linelen = strlen(buf); + + /* augment the buffer if we have to */ + if ((message_len + linelen) >= buffer_len) { + ptr = reallok(m, (buffer_len * 2) ); + if (ptr == NULL) { /* flush if can't allocate */ + flushing = 1; + } else { + buffer_len = (buffer_len * 2); + m = ptr; + lprintf(9, "buffer_len is now %ld\n", (long)buffer_len); + } + } + + /* Add the new line to the buffer. NOTE: this loop must avoid + * using functions like strcat() and strlen() because they + * traverse the entire buffer upon every call, and doing that + * for a multi-megabyte message slows it down beyond usability. + */ + strcpy(&m[message_len], buf); + message_len += linelen; + } /* if we've hit the max msg length, flush the rest */ - if (message_len >= maxlen) { - while ( (client_gets(buf)>0) - && strcmp(buf, terminator)) ;; - return(m); - } - } + if (message_len >= maxlen) flushing = 1; + + } while (!finished); return(m); } @@ -2007,20 +2285,23 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ /* * Build a binary message to be saved on disk. + * (NOTE: if you supply 'preformatted_text', the buffer you give it + * will become part of the message. This means you are no longer + * responsible for managing that memory -- it will be freed along with + * the rest of the fields when CtdlFreeMessage() is called.) */ -struct CtdlMessage *make_message( - struct usersupp *author, /* author's usersupp structure */ +struct CtdlMessage *CtdlMakeMessage( + struct ctdluser *author, /* author's user structure */ char *recipient, /* NULL if it's not mail */ char *room, /* room where it's going */ int type, /* see MES_ types in header file */ - int net_type, /* see MES_ types in header file */ - int format_type, /* local or remote (see citadel.h) */ - char *fake_name) /* who we're masquerading as */ -{ - - int a; - char dest_node[32]; + int format_type, /* variformat, plain text, MIME... */ + char *fake_name, /* who we're masquerading as */ + char *subject, /* Subject (optional) */ + char *preformatted_text /* ...or NULL to read text from client */ +) { + char dest_node[SIZ]; char buf[SIZ]; struct CtdlMessage *msg; @@ -2033,29 +2314,12 @@ struct CtdlMessage *make_message( /* Don't confuse the poor folks if it's not routed mail. */ strcpy(dest_node, ""); - /* If net_type is MES_BINARY, split out the destination node. */ - if (net_type == MES_BINARY) { - strcpy(dest_node, NODENAME); - for (a = 0; a < strlen(recipient); ++a) { - if (recipient[a] == '@') { - recipient[a] = 0; - strcpy(dest_node, &recipient[a + 1]); - } - } - } - - /* if net_type is MES_INTERNET, set the dest node to 'internet' */ - if (net_type == MES_INTERNET) { - strcpy(dest_node, "internet"); - } - - while (isspace(recipient[strlen(recipient) - 1])) - recipient[strlen(recipient) - 1] = 0; + striplt(recipient); - sprintf(buf, "cit%ld", author->usernum); /* Path */ + snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */ msg->cm_fields['P'] = strdoop(buf); - sprintf(buf, "%ld", time(NULL)); /* timestamp */ + snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */ msg->cm_fields['T'] = strdoop(buf); if (fake_name[0]) /* author */ @@ -2063,23 +2327,41 @@ struct CtdlMessage *make_message( else msg->cm_fields['A'] = strdoop(author->fullname); - if (CC->quickroom.QRflags & QR_MAILBOX) /* room */ - msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]); - else - msg->cm_fields['O'] = strdoop(CC->quickroom.QRname); + if (CC->room.QRflags & QR_MAILBOX) { /* room */ + msg->cm_fields['O'] = strdoop(&CC->room.QRname[11]); + } + else { + msg->cm_fields['O'] = strdoop(CC->room.QRname); + } msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */ msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */ - if (recipient[0] != 0) + if (recipient[0] != 0) { msg->cm_fields['R'] = strdoop(recipient); - if (dest_node[0] != 0) + } + if (dest_node[0] != 0) { msg->cm_fields['D'] = strdoop(dest_node); + } + if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) { + msg->cm_fields['F'] = strdoop(CC->cs_inet_email); + } - msg->cm_fields['M'] = CtdlReadMessageBody("000", - config.c_maxmsglen, NULL); + if (subject != NULL) { + striplt(subject); + if (strlen(subject) > 0) { + msg->cm_fields['U'] = strdoop(subject); + } + } + if (preformatted_text != NULL) { + msg->cm_fields['M'] = preformatted_text; + } + else { + msg->cm_fields['M'] = CtdlReadMessageBody("000", + config.c_maxmsglen, NULL, 0); + } return(msg); } @@ -2090,29 +2372,29 @@ struct CtdlMessage *make_message( * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or * returns 0 on success. */ -int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) { +int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) { if (!(CC->logged_in)) { - sprintf(errmsgbuf, "Not logged in."); + snprintf(errmsgbuf, n, "Not logged in."); return (ERROR + NOT_LOGGED_IN); } - if ((CC->usersupp.axlevel < 2) - && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) { - sprintf(errmsgbuf, "Need to be validated to enter " + if ((CC->user.axlevel < 2) + && ((CC->room.QRflags & QR_MAILBOX) == 0)) { + snprintf(errmsgbuf, n, "Need to be validated to enter " "(except in %s> to sysop)", MAILROOM); return (ERROR + HIGHER_ACCESS_REQUIRED); } - if ((CC->usersupp.axlevel < 4) - && (CC->quickroom.QRflags & QR_NETWORK)) { - sprintf(errmsgbuf, "Need net privileges to enter here."); + if ((CC->user.axlevel < 4) + && (CC->room.QRflags & QR_NETWORK)) { + snprintf(errmsgbuf, n, "Need net privileges to enter here."); return (ERROR + HIGHER_ACCESS_REQUIRED); } - if ((CC->usersupp.axlevel < 6) - && (CC->quickroom.QRflags & QR_READONLY)) { - sprintf(errmsgbuf, "Sorry, this is a read-only room."); + if ((CC->user.axlevel < 6) + && (CC->room.QRflags & QR_READONLY)) { + snprintf(errmsgbuf, n, "Sorry, this is a read-only room."); return (ERROR + HIGHER_ACCESS_REQUIRED); } @@ -2121,6 +2403,202 @@ int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) { } +/* + * Check to see if the specified user has Internet mail permission + * (returns nonzero if permission is granted) + */ +int CtdlCheckInternetMailPermission(struct ctdluser *who) { + + /* Globally enabled? */ + if (config.c_restrict == 0) return(1); + + /* User flagged ok? */ + if (who->flags & US_INTERNET) return(2); + + /* Aide level access? */ + if (who->axlevel >= 6) return(3); + + /* No mail for you! */ + return(0); +} + + + +/* + * Validate recipients, count delivery types and errors, and handle aliasing + * FIXME check for dupes!!!!! + */ +struct recptypes *validate_recipients(char *recipients) { + struct recptypes *ret; + char this_recp[SIZ]; + char this_recp_cooked[SIZ]; + char append[SIZ]; + int num_recps; + int i, j; + int mailtype; + int invalid; + struct ctdluser tempUS; + struct ctdlroom tempQR; + + /* Initialize */ + ret = (struct recptypes *) malloc(sizeof(struct recptypes)); + if (ret == NULL) return(NULL); + memset(ret, 0, sizeof(struct recptypes)); + + ret->num_local = 0; + ret->num_internet = 0; + ret->num_ignet = 0; + ret->num_error = 0; + ret->num_room = 0; + + if (recipients == NULL) { + num_recps = 0; + } + else if (strlen(recipients) == 0) { + num_recps = 0; + } + else { + /* Change all valid separator characters to commas */ + for (i=0; i 0) for (i=0; i\n", i, this_recp); + mailtype = alias(this_recp); + mailtype = alias(this_recp); + mailtype = alias(this_recp); + for (j=0; j<=strlen(this_recp); ++j) { + if (this_recp[j]=='_') { + this_recp_cooked[j] = ' '; + } + else { + this_recp_cooked[j] = this_recp[j]; + } + } + invalid = 0; + switch(mailtype) { + case MES_LOCAL: + if (!strcasecmp(this_recp, "sysop")) { + ++ret->num_room; + strcpy(this_recp, config.c_aideroom); + if (strlen(ret->recp_room) > 0) { + strcat(ret->recp_room, "|"); + } + strcat(ret->recp_room, this_recp); + } + else if (getuser(&tempUS, this_recp) == 0) { + ++ret->num_local; + strcpy(this_recp, tempUS.fullname); + if (strlen(ret->recp_local) > 0) { + strcat(ret->recp_local, "|"); + } + strcat(ret->recp_local, this_recp); + } + else if (getuser(&tempUS, this_recp_cooked) == 0) { + ++ret->num_local; + strcpy(this_recp, tempUS.fullname); + if (strlen(ret->recp_local) > 0) { + strcat(ret->recp_local, "|"); + } + strcat(ret->recp_local, this_recp); + } + else if ( (!strncasecmp(this_recp, "room_", 5)) + && (!getroom(&tempQR, &this_recp_cooked[5])) ) { + ++ret->num_room; + if (strlen(ret->recp_room) > 0) { + strcat(ret->recp_room, "|"); + } + strcat(ret->recp_room, &this_recp_cooked[5]); + } + else { + ++ret->num_error; + invalid = 1; + } + break; + case MES_INTERNET: + /* Yes, you're reading this correctly: if the target + * domain points back to the local system or an attached + * Citadel directory, the address is invalid. That's + * because if the address were valid, we would have + * already translated it to a local address by now. + */ + if (IsDirectory(this_recp)) { + ++ret->num_error; + invalid = 1; + } + else { + ++ret->num_internet; + if (strlen(ret->recp_internet) > 0) { + strcat(ret->recp_internet, "|"); + } + strcat(ret->recp_internet, this_recp); + } + break; + case MES_IGNET: + ++ret->num_ignet; + if (strlen(ret->recp_ignet) > 0) { + strcat(ret->recp_ignet, "|"); + } + strcat(ret->recp_ignet, this_recp); + break; + case MES_ERROR: + ++ret->num_error; + invalid = 1; + break; + } + if (invalid) { + if (strlen(ret->errormsg) == 0) { + snprintf(append, sizeof append, + "Invalid recipient: %s", + this_recp); + } + else { + snprintf(append, sizeof append, + ", %s", this_recp); + } + if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) { + strcat(ret->errormsg, append); + } + } + else { + if (strlen(ret->display_recp) == 0) { + strcpy(append, this_recp); + } + else { + snprintf(append, sizeof append, ", %s", + this_recp); + } + if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) { + strcat(ret->display_recp, append); + } + } + } + + if ((ret->num_local + ret->num_internet + ret->num_ignet + + ret->num_room + ret->num_error) == 0) { + ++ret->num_error; + strcpy(ret->errormsg, "No recipients specified."); + } + + lprintf(9, "validate_recipients()\n"); + lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local); + lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room); + lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet); + lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet); + lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg); + + return(ret); +} + /* @@ -2129,237 +2607,149 @@ int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) { void cmd_ent0(char *entargs) { int post = 0; - char recipient[SIZ]; + char recp[SIZ]; + char masquerade_as[SIZ]; int anon_flag = 0; int format_type = 0; char newusername[SIZ]; struct CtdlMessage *msg; - int a, b; - int e = 0; - int mtsflag = 0; - struct usersupp tempUS; - char buf[SIZ]; + int anonymous = 0; + char errmsg[SIZ]; int err = 0; + struct recptypes *valid = NULL; + char subject[SIZ]; post = extract_int(entargs, 0); - extract(recipient, entargs, 1); + extract(recp, entargs, 1); anon_flag = extract_int(entargs, 2); format_type = extract_int(entargs, 3); + extract(subject, entargs, 4); /* first check to make sure the request is valid. */ - err = CtdlDoIHavePermissionToPostInThisRoom(buf); + err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg); if (err) { - cprintf("%d %s\n", err, buf); + cprintf("%d %s\n", err, errmsg); return; } /* Check some other permission type things. */ if (post == 2) { - if (CC->usersupp.axlevel < 6) { + if (CC->user.axlevel < 6) { cprintf("%d You don't have permission to masquerade.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } - extract(newusername, entargs, 4); - memset(CC->fake_postname, 0, 32); - strcpy(CC->fake_postname, newusername); - cprintf("%d Ok\n", OK); + extract(newusername, entargs, 5); + memset(CC->fake_postname, 0, sizeof(CC->fake_postname) ); + safestrncpy(CC->fake_postname, newusername, + sizeof(CC->fake_postname) ); + cprintf("%d ok\n", CIT_OK); return; } CC->cs_flags |= CS_POSTING; - buf[0] = 0; - if (CC->quickroom.QRflags & QR_MAILBOX) { - if (CC->usersupp.axlevel >= 2) { - strcpy(buf, recipient); - } else - strcpy(buf, "sysop"); - e = alias(buf); /* alias and mail type */ - if ((buf[0] == 0) || (e == MES_ERROR)) { - cprintf("%d Unknown address - cannot send message.\n", - ERROR + NO_SUCH_USER); + /* In the Mail> room we have to behave a little differently -- + * make sure the user has specified at least one recipient. Then + * validate the recipient(s). + */ + if ( (CC->room.QRflags & QR_MAILBOX) + && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) { + + if (CC->user.axlevel < 2) { + strcpy(recp, "sysop"); + } + + valid = validate_recipients(recp); + if (valid->num_error > 0) { + cprintf("%d %s\n", + ERROR + NO_SUCH_USER, valid->errormsg); + phree(valid); return; } - if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) { - cprintf("%d Net privileges required for network mail.\n", + if (valid->num_internet > 0) { + if (CtdlCheckInternetMailPermission(&CC->user)==0) { + cprintf("%d You do not have permission " + "to send Internet mail.\n", + ERROR + HIGHER_ACCESS_REQUIRED); + phree(valid); + return; + } + } + + if ( ( (valid->num_internet + valid->num_ignet) > 0) + && (CC->user.axlevel < 4) ) { + cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); + phree(valid); return; } - if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET) - && ((CC->usersupp.flags & US_INTERNET) == 0) + + if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0) + && ((CC->user.flags & US_INTERNET) == 0) && (!CC->internal_pgm)) { cprintf("%d You don't have access to Internet mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); + phree(valid); return; } - if (!strcasecmp(buf, "sysop")) { - mtsflag = 1; - } - else if (e == MES_LOCAL) { /* don't search local file */ - if (!strcasecmp(buf, CC->usersupp.fullname)) { - cprintf("%d Can't send mail to yourself!\n", - ERROR + NO_SUCH_USER); - return; - } - /* Check to make sure the user exists; also get the correct - * upper/lower casing of the name. - */ - a = getuser(&tempUS, buf); - if (a != 0) { - cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); - return; - } - strcpy(buf, tempUS.fullname); + + } + + /* Is this a room which has anonymous-only or anonymous-option? */ + anonymous = MES_NORMAL; + if (CC->room.QRflags & QR_ANONONLY) { + anonymous = MES_ANONONLY; + } + if (CC->room.QRflags & QR_ANONOPT) { + if (anon_flag == 1) { /* only if the user requested it */ + anonymous = MES_ANONOPT; } } - b = MES_NORMAL; - if (CC->quickroom.QRflags & QR_ANONONLY) - b = MES_ANON; - if (CC->quickroom.QRflags & QR_ANONOPT) { - if (anon_flag == 1) - b = MES_AN2; + if ((CC->room.QRflags & QR_MAILBOX) == 0) { + recp[0] = 0; } - if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) - buf[0] = 0; /* If we're only checking the validity of the request, return * success without creating the message. */ if (post == 0) { - cprintf("%d %s\n", OK, buf); + cprintf("%d %s\n", CIT_OK, + ((valid != NULL) ? valid->display_recp : "") ); + phree(valid); return; } - cprintf("%d send message\n", SEND_LISTING); + /* Handle author masquerading */ + if (CC->fake_postname[0]) { + strcpy(masquerade_as, CC->fake_postname); + } + else if (CC->fake_username[0]) { + strcpy(masquerade_as, CC->fake_username); + } + else { + strcpy(masquerade_as, ""); + } /* Read in the message from the client. */ - if (CC->fake_postname[0]) - msg = make_message(&CC->usersupp, buf, - CC->quickroom.QRname, b, e, format_type, - CC->fake_postname); - else if (CC->fake_username[0]) - msg = make_message(&CC->usersupp, buf, - CC->quickroom.QRname, b, e, format_type, - CC->fake_username); - else - msg = make_message(&CC->usersupp, buf, - CC->quickroom.QRname, b, e, format_type, ""); + cprintf("%d send message\n", SEND_LISTING); + msg = CtdlMakeMessage(&CC->user, recp, + CC->room.QRname, anonymous, format_type, + masquerade_as, subject, NULL); - if (msg != NULL) - CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e); + if (msg != NULL) { + CtdlSubmitMsg(msg, valid, ""); CtdlFreeMessage(msg); + } CC->fake_postname[0] = '\0'; + phree(valid); return; } -/* - * message entry - mode 3 (raw) - */ -void cmd_ent3(char *entargs) -{ - char recp[SIZ]; - int a; - int e = 0; - int valid_msg = 1; - unsigned char ch, which_field; - struct usersupp tempUS; - long msglen; - struct CtdlMessage *msg; - char *tempbuf; - - if (CC->internal_pgm == 0) { - cprintf("%d This command is for internal programs only.\n", - ERROR); - return; - } - - /* See if there's a recipient, but make sure it's a real one */ - extract(recp, entargs, 1); - for (a = 0; a < strlen(recp); ++a) - if (!isprint(recp[a])) - strcpy(&recp[a], &recp[a + 1]); - while (isspace(recp[0])) - strcpy(recp, &recp[1]); - while (isspace(recp[strlen(recp) - 1])) - recp[strlen(recp) - 1] = 0; - - /* If we're in Mail, check the recipient */ - if (strlen(recp) > 0) { - e = alias(recp); /* alias and mail type */ - if ((recp[0] == 0) || (e == MES_ERROR)) { - cprintf("%d Unknown address - cannot send message.\n", - ERROR + NO_SUCH_USER); - return; - } - if (e == MES_LOCAL) { - a = getuser(&tempUS, recp); - if (a != 0) { - cprintf("%d No such user.\n", - ERROR + NO_SUCH_USER); - return; - } - } - } - - /* At this point, message has been approved. */ - if (extract_int(entargs, 0) == 0) { - cprintf("%d OK to send\n", OK); - return; - } - - msglen = extract_long(entargs, 2); - msg = mallok(sizeof(struct CtdlMessage)); - if (msg == NULL) { - cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR); - return; - } - - memset(msg, 0, sizeof(struct CtdlMessage)); - tempbuf = mallok(msglen); - if (tempbuf == NULL) { - cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR); - phree(msg); - return; - } - - cprintf("%d %ld\n", SEND_BINARY, msglen); - - client_read(&ch, 1); /* 0xFF magic number */ - msg->cm_magic = CTDLMESSAGE_MAGIC; - client_read(&ch, 1); /* anon type */ - msg->cm_anon_type = ch; - client_read(&ch, 1); /* format type */ - msg->cm_format_type = ch; - msglen = msglen - 3; - - while (msglen > 0) { - client_read(&which_field, 1); - if (!isalpha(which_field)) valid_msg = 0; - --msglen; - tempbuf[0] = 0; - do { - client_read(&ch, 1); - --msglen; - a = strlen(tempbuf); - tempbuf[a+1] = 0; - tempbuf[a] = ch; - } while ( (ch != 0) && (msglen > 0) ); - if (valid_msg) - msg->cm_fields[which_field] = strdoop(tempbuf); - } - - msg->cm_flags = CM_SKIP_HOOKS; - if (valid_msg) CtdlSaveMsg(msg, recp, "", e); - CtdlFreeMessage(msg); - phree(tempbuf); -} - - /* * API function to delete messages which match a set of criteria * (returns the actual number of messages deleted) @@ -2370,14 +2760,15 @@ int CtdlDeleteMessages(char *room_name, /* which room */ ) { - struct quickroom qrbuf; + struct ctdlroom qrbuf; struct cdbdata *cdbfr; long *msglist = NULL; + long *dellist = NULL; int num_msgs = 0; int i; int num_deleted = 0; int delete_this; - struct SuppMsgInfo smi; + struct MetaData smi; lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n", room_name, dmsgnum, content_type); @@ -2392,6 +2783,7 @@ int CtdlDeleteMessages(char *room_name, /* which room */ if (cdbfr != NULL) { msglist = mallok(cdbfr->len); + dellist = mallok(cdbfr->len); memcpy(msglist, cdbfr->ptr, cdbfr->len); num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); @@ -2408,8 +2800,8 @@ int CtdlDeleteMessages(char *room_name, /* which room */ if (strlen(content_type) == 0) { delete_this |= 0x02; } else { - GetSuppMsgInfo(&smi, msglist[i]); - if (!strcasecmp(smi.smi_content_type, + GetMetaData(&smi, msglist[i]); + if (!strcasecmp(smi.meta_content_type, content_type)) { delete_this |= 0x02; } @@ -2417,9 +2809,8 @@ int CtdlDeleteMessages(char *room_name, /* which room */ /* Delete message only if all bits are set */ if (delete_this == 0x03) { - AdjRefCount(msglist[i], -1); + dellist[num_deleted++] = msglist[i]; msglist[i] = 0L; - ++num_deleted; } } @@ -2428,9 +2819,25 @@ int CtdlDeleteMessages(char *room_name, /* which room */ msglist, (num_msgs * sizeof(long))); qrbuf.QRhighest = msglist[num_msgs - 1]; - phree(msglist); } lputroom(&qrbuf); + + /* Go through the messages we pulled out of the index, and decrement + * their reference counts by 1. If this is the only room the message + * was in, the reference count will reach zero and the message will + * automatically be deleted from the database. We do this in a + * separate pass because there might be plug-in hooks getting called, + * and we don't want that happening during an S_ROOMS critical + * section. + */ + if (num_deleted) for (i=0; iusersupp, CC->curr_user); - if ((CC->usersupp.axlevel < 6) - && (CC->usersupp.usernum != CC->quickroom.QRroomaide) - && ((CC->quickroom.QRflags & QR_MAILBOX) == 0) + getuser(&CC->user, CC->curr_user); + if ((CC->user.axlevel < 6) + && (CC->user.usernum != CC->room.QRroomaide) + && ((CC->room.QRflags & QR_MAILBOX) == 0) && (!(CC->internal_pgm))) { return(0); } @@ -2469,10 +2876,10 @@ void cmd_dele(char *delstr) } delnum = extract_long(delstr, 0); - num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, ""); + num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, ""); if (num_deleted) { - cprintf("%d %d message%s deleted.\n", OK, + cprintf("%d %d message%s deleted.\n", CIT_OK, num_deleted, ((num_deleted != 1) ? "s" : "")); } else { cprintf("%d Message %ld not found.\n", ERROR, delnum); @@ -2502,7 +2909,7 @@ void cmd_move(char *args) { long num; char targ[SIZ]; - struct quickroom qtemp; + struct ctdlroom qtemp; int err; int is_copy = 0; @@ -2511,16 +2918,24 @@ void cmd_move(char *args) targ[ROOMNAMELEN - 1] = 0; is_copy = extract_int(args, 2); - getuser(&CC->usersupp, CC->curr_user); - if ((CC->usersupp.axlevel < 6) - && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) { - cprintf("%d Higher access required.\n", - ERROR + HIGHER_ACCESS_REQUIRED); + if (getroom(&qtemp, targ) != 0) { + cprintf("%d '%s' does not exist.\n", ERROR, targ); return; } - if (getroom(&qtemp, targ) != 0) { - cprintf("%d '%s' does not exist.\n", ERROR, targ); + getuser(&CC->user, CC->curr_user); + /* Aides can move/copy */ + if ((CC->user.axlevel < 6) + /* Roomaides can move/copy */ + && (CC->user.usernum != CC->room.QRroomaide) + /* Permit move/copy to/from personal rooms */ + && (!((CC->room.QRflags & QR_MAILBOX) + && (qtemp.QRflags & QR_MAILBOX))) + /* Permit only copy from public to personal room */ + && (!(is_copy && !(CC->room.QRflags & QR_MAILBOX) + && (qtemp.QRflags & QR_MAILBOX)))) { + cprintf("%d Higher access required.\n", + ERROR + HIGHER_ACCESS_REQUIRED); return; } @@ -2534,25 +2949,27 @@ void cmd_move(char *args) /* Now delete the message from the source room, * if this is a 'move' rather than a 'copy' operation. */ - if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, ""); + if (is_copy == 0) { + CtdlDeleteMessages(CC->room.QRname, num, ""); + } - cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") ); + cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") ); } /* - * GetSuppMsgInfo() - Get the supplementary record for a message + * GetMetaData() - Get the supplementary record for a message */ -void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum) +void GetMetaData(struct MetaData *smibuf, long msgnum) { struct cdbdata *cdbsmi; long TheIndex; - memset(smibuf, 0, sizeof(struct SuppMsgInfo)); - smibuf->smi_msgnum = msgnum; - smibuf->smi_refcount = 1; /* Default reference count is 1 */ + memset(smibuf, 0, sizeof(struct MetaData)); + smibuf->meta_msgnum = msgnum; + smibuf->meta_refcount = 1; /* Default reference count is 1 */ /* Use the negative of the message number for its supp record index */ TheIndex = (0L - msgnum); @@ -2562,29 +2979,29 @@ void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum) return; /* record not found; go with defaults */ } memcpy(smibuf, cdbsmi->ptr, - ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ? - sizeof(struct SuppMsgInfo) : cdbsmi->len)); + ((cdbsmi->len > sizeof(struct MetaData)) ? + sizeof(struct MetaData) : cdbsmi->len)); cdb_free(cdbsmi); return; } /* - * PutSuppMsgInfo() - (re)write supplementary record for a message + * PutMetaData() - (re)write supplementary record for a message */ -void PutSuppMsgInfo(struct SuppMsgInfo *smibuf) +void PutMetaData(struct MetaData *smibuf) { long TheIndex; - /* Use the negative of the message number for its supp record index */ - TheIndex = (0L - smibuf->smi_msgnum); + /* Use the negative of the message number for the metadata db index */ + TheIndex = (0L - smibuf->meta_msgnum); - lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n", - smibuf->smi_msgnum, smibuf->smi_refcount); + lprintf(9, "PutMetaData(%ld) - ref count is %d\n", + smibuf->meta_msgnum, smibuf->meta_refcount); cdb_store(CDB_MSGMAIN, &TheIndex, sizeof(long), - smibuf, sizeof(struct SuppMsgInfo)); + smibuf, sizeof(struct MetaData)); } @@ -2595,7 +3012,7 @@ void PutSuppMsgInfo(struct SuppMsgInfo *smibuf) void AdjRefCount(long msgnum, int incr) { - struct SuppMsgInfo smi; + struct MetaData smi; long delnum; /* This is a *tight* critical section; please keep it that way, as @@ -2603,22 +3020,24 @@ void AdjRefCount(long msgnum, int incr) * Complicating this any further will surely cause deadlock! */ begin_critical_section(S_SUPPMSGMAIN); - GetSuppMsgInfo(&smi, msgnum); + GetMetaData(&smi, msgnum); lprintf(9, "Ref count for message <%ld> before write is <%d>\n", - msgnum, smi.smi_refcount); - smi.smi_refcount += incr; - PutSuppMsgInfo(&smi); + msgnum, smi.meta_refcount); + smi.meta_refcount += incr; + PutMetaData(&smi); end_critical_section(S_SUPPMSGMAIN); lprintf(9, "Ref count for message <%ld> after write is <%d>\n", - msgnum, smi.smi_refcount); + msgnum, smi.meta_refcount); /* If the reference count is now zero, delete the message * (and its supplementary record as well). */ - if (smi.smi_refcount == 0) { + if (smi.meta_refcount == 0) { lprintf(9, "Deleting message <%ld>\n", msgnum); delnum = msgnum; cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long)); + + /* We have to delete the metadata record too! */ delnum = (0L - msgnum); cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long)); } @@ -2633,58 +3052,82 @@ void AdjRefCount(long msgnum, int incr) void CtdlWriteObject(char *req_room, /* Room to stuff it in */ char *content_type, /* MIME type of this object */ char *tempfilename, /* Where to fetch it from */ - struct usersupp *is_mailbox, /* Mailbox room? */ + struct ctdluser *is_mailbox, /* Mailbox room? */ int is_binary, /* Is encoding necessary? */ int is_unique, /* Del others of this type? */ unsigned int flags /* Internal save flags */ ) { - FILE *fp, *tempfp; - char filename[PATH_MAX]; - char cmdbuf[SIZ]; - char ch; - struct quickroom qrbuf; + FILE *fp; + struct ctdlroom qrbuf; char roomname[ROOMNAMELEN]; struct CtdlMessage *msg; - size_t len; + + char *raw_message = NULL; + char *encoded_message = NULL; + off_t raw_length = 0; if (is_mailbox != NULL) - MailboxName(roomname, is_mailbox, req_room); + MailboxName(roomname, sizeof roomname, is_mailbox, req_room); else safestrncpy(roomname, req_room, sizeof(roomname)); lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags); - strcpy(filename, tmpnam(NULL)); - fp = fopen(filename, "w"); - if (fp == NULL) - return; - tempfp = fopen(tempfilename, "r"); - if (tempfp == NULL) { - fclose(fp); - unlink(filename); + fp = fopen(tempfilename, "rb"); + if (fp == NULL) { + lprintf(5, "Cannot open %s: %s\n", + tempfilename, strerror(errno)); return; } + fseek(fp, 0L, SEEK_END); + raw_length = ftell(fp); + rewind(fp); + lprintf(9, "Raw length is %ld\n", (long)raw_length); - fprintf(fp, "Content-type: %s\n", content_type); - lprintf(9, "Content-type: %s\n", content_type); + raw_message = mallok((size_t)raw_length + 2); + fread(raw_message, (size_t)raw_length, 1, fp); + fclose(fp); - if (is_binary == 0) { - fprintf(fp, "Content-transfer-encoding: 7bit\n\n"); - while (ch = getc(tempfp), ch > 0) - putc(ch, fp); - fclose(tempfp); - putc(0, fp); - fclose(fp); - } else { - fprintf(fp, "Content-transfer-encoding: base64\n\n"); - fclose(tempfp); - fclose(fp); - sprintf(cmdbuf, "./base64 -e <%s >>%s", - tempfilename, filename); - system(cmdbuf); + if (is_binary) { + encoded_message = mallok((size_t) + (((raw_length * 134) / 100) + 4096 ) ); + } + else { + encoded_message = mallok((size_t)(raw_length + 4096)); + } + + sprintf(encoded_message, "Content-type: %s\n", content_type); + + if (is_binary) { + sprintf(&encoded_message[strlen(encoded_message)], + "Content-transfer-encoding: base64\n\n" + ); } + else { + sprintf(&encoded_message[strlen(encoded_message)], + "Content-transfer-encoding: 7bit\n\n" + ); + } + + if (is_binary) { + CtdlEncodeBase64( + &encoded_message[strlen(encoded_message)], + raw_message, + (int)raw_length + ); + } + else { + raw_message[raw_length] = 0; + memcpy( + &encoded_message[strlen(encoded_message)], + raw_message, + (int)(raw_length+1) + ); + } + + phree(raw_message); lprintf(9, "Allocating\n"); msg = mallok(sizeof(struct CtdlMessage)); @@ -2692,27 +3135,19 @@ void CtdlWriteObject(char *req_room, /* Room to stuff it in */ msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = 4; - msg->cm_fields['A'] = strdoop(CC->usersupp.fullname); + msg->cm_fields['A'] = strdoop(CC->user.fullname); msg->cm_fields['O'] = strdoop(req_room); msg->cm_fields['N'] = strdoop(config.c_nodename); msg->cm_fields['H'] = strdoop(config.c_humannode); msg->cm_flags = flags; - lprintf(9, "Loading\n"); - fp = fopen(filename, "rb"); - fseek(fp, 0L, SEEK_END); - len = ftell(fp); - rewind(fp); - msg->cm_fields['M'] = mallok(len); - fread(msg->cm_fields['M'], len, 1, fp); - fclose(fp); - unlink(filename); + msg->cm_fields['M'] = encoded_message; /* Create the requested room if we have to. */ if (getroom(&qrbuf, roomname) != 0) { create_room(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), - "", 0, 1); + "", 0, 1, 0); } /* If the caller specified this object as unique, delete all * other objects of this type that are currently in the room. @@ -2722,7 +3157,7 @@ void CtdlWriteObject(char *req_room, /* Room to stuff it in */ CtdlDeleteMessages(roomname, 0L, content_type)); } /* Now write the data */ - CtdlSaveMsg(msg, "", roomname, MES_LOCAL); + CtdlSubmitMsg(msg, NULL, roomname); CtdlFreeMessage(msg); } @@ -2743,9 +3178,9 @@ char *CtdlGetSysConfig(char *sysconfname) { struct CtdlMessage *msg; char buf[SIZ]; - strcpy(hold_rm, CC->quickroom.QRname); - if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) { - getroom(&CC->quickroom, hold_rm); + strcpy(hold_rm, CC->room.QRname); + if (getroom(&CC->room, SYSCONFIGROOM) != 0) { + getroom(&CC->room, hold_rm); return NULL; } @@ -2753,7 +3188,7 @@ char *CtdlGetSysConfig(char *sysconfname) { /* We want the last (and probably only) config in this room */ begin_critical_section(S_CONFIG); config_msgnum = (-1L); - CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL, + CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL, CtdlGetSysConfigBackend, NULL); msgnum = config_msgnum; end_critical_section(S_CONFIG); @@ -2772,12 +3207,10 @@ char *CtdlGetSysConfig(char *sysconfname) { } } - getroom(&CC->quickroom, hold_rm); + getroom(&CC->room, hold_rm); - lprintf(9, "eggstracting...\n"); if (conf != NULL) do { extract_token(buf, conf, 0, '\n'); - lprintf(9, "eggstracted <%s>\n", buf); strcpy(conf, &conf[strlen(buf)+1]); } while ( (strlen(conf)>0) && (strlen(buf)>0) ); @@ -2799,3 +3232,50 @@ void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) { CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0); unlink(temp); } + + +/* + * Determine whether a given Internet address belongs to the current user + */ +int CtdlIsMe(char *addr) { + struct recptypes *recp; + int i; + + recp = validate_recipients(addr); + if (recp == NULL) return(0); + + if (recp->num_local == 0) { + phree(recp); + return(0); + } + + for (i=0; inum_local; ++i) { + extract(addr, recp->recp_local, i); + if (!strcasecmp(addr, CC->user.fullname)) { + phree(recp); + return(1); + } + } + + phree(recp); + return(0); +} + + +/* + * Citadel protocol command to do the same + */ +void cmd_isme(char *argbuf) { + char addr[SIZ]; + + if (CtdlAccessCheck(ac_logged_in)) return; + extract(addr, argbuf, 0); + + if (CtdlIsMe(addr)) { + cprintf("%d %s\n", CIT_OK, addr); + } + else { + cprintf("%d Not you.\n", ERROR); + } + +}