X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmsgbase.c;h=61488f1faf6a7fd178874e0f29a88cf302d0d2e4;hb=e0e892370401431821a22abbf60ac79ed59273d7;hp=dadf85861401442f368ca88274d412667abd3ae9;hpb=e58fe5e7e00ebadd65cd6fd66a8101da57380467;p=citadel.git diff --git a/citadel/msgbase.c b/citadel/msgbase.c index dadf85861..61488f1fa 100644 --- a/citadel/msgbase.c +++ b/citadel/msgbase.c @@ -8,9 +8,6 @@ #include #include #include -#ifdef HAVE_PTHREAD_H -#include -#endif #include #include "citadel.h" #include "server.h" @@ -28,18 +25,46 @@ #include "dynloader.h" #include "tools.h" #include "mime_parser.h" - -#define MSGS_ALL 0 -#define MSGS_OLD 1 -#define MSGS_NEW 2 -#define MSGS_FIRST 3 -#define MSGS_LAST 4 -#define MSGS_GT 5 +#include "html.h" #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION)) +#define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO)) +#define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL)) extern struct config config; +char *msgkeys[] = { + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", + "from", + "", "", "", + "exti", + "", "", + "hnod", + "msgn", + "", "", "", + "text", + "node", + "room", + "path", + "", + "rcpt", + "spec", + "time", + "subj", + "", + "", + "", + "", + "" +}; /* * This function is self explanatory. @@ -73,8 +98,6 @@ int alias(char *name) int a, b; char aaa[300], bbb[300]; - lprintf(9, "alias() called for <%s>\n", name); - remove_any_whitespace_to_the_left_or_right_of_at_symbol(name); fp = fopen("network/mail.aliases", "r"); @@ -181,71 +204,210 @@ void get_mm(void) fclose(fp); } + + +void simple_listing(long msgnum) +{ + cprintf("%ld\n", msgnum); +} + + + +/* Determine if a given message matches the fields in a message template. + * Return 0 for a successful match. + */ +int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) { + int i; + + /* If there aren't any fields in the template, all messages will + * match. + */ + if (template == NULL) return(0); + + /* Null messages are bogus. */ + if (msg == NULL) return(1); + + for (i='A'; i<='Z'; ++i) { + if (template->cm_fields[i] != NULL) { + if (msg->cm_fields[i] == NULL) { + return 1; + } + if (strcasecmp(msg->cm_fields[i], + template->cm_fields[i])) return 1; + } + } + + /* All compares succeeded: we have a match! */ + return 0; +} + + + + +/* + * API function to perform an operation for each qualifying message in the + * current room. + */ +void CtdlForEachMessage(int mode, long ref, + char *content_type, + struct CtdlMessage *compare, + void (*CallBack) (long msgnum)) +{ + + int a; + struct visit vbuf; + struct cdbdata *cdbfr; + long *msglist = NULL; + int num_msgs = 0; + long thismsg; + struct SuppMsgInfo smi; + struct CtdlMessage *msg; + + /* Learn about the user and room in question */ + get_mm(); + getuser(&CC->usersupp, CC->curr_user); + CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + + /* Load the message list */ + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long)); + if (cdbfr != NULL) { + msglist = mallok(cdbfr->len); + memcpy(msglist, cdbfr->ptr, cdbfr->len); + num_msgs = cdbfr->len / sizeof(long); + cdb_free(cdbfr); + } else { + return; /* No messages at all? No further action. */ + } + + + /* If the caller is looking for a specific MIME type, then filter + * out all messages which are not of the type requested. + */ + if (num_msgs > 0) + if (content_type != NULL) + if (strlen(content_type) > 0) + for (a = 0; a < num_msgs; ++a) { + GetSuppMsgInfo(&smi, msglist[a]); + if (strcasecmp(smi.smi_content_type, content_type)) { + msglist[a] = 0L; + } + } + + num_msgs = sort_msglist(msglist, num_msgs); + + /* If a template was supplied, filter out the messages which + * don't match. (This could induce some delays!) + */ + if (num_msgs > 0) { + if (compare != NULL) { + for (a = 0; a < num_msgs; ++a) { + msg = CtdlFetchMessage(msglist[a]); + if (msg != NULL) { + if (CtdlMsgCmp(msg, compare)) { + msglist[a] = 0L; + } + CtdlFreeMessage(msg); + } + } + } + } + + + /* + * Now iterate through the message list, according to the + * criteria supplied by the caller. + */ + if (num_msgs > 0) + for (a = 0; a < num_msgs; ++a) { + thismsg = msglist[a]; + if ((thismsg > 0) + && ( + + (mode == MSGS_ALL) + || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen)) + || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen)) + || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen) + && (CC->usersupp.flags & US_LASTOLD)) + || ((mode == MSGS_LAST) && (a >= (num_msgs - ref))) + || ((mode == MSGS_FIRST) && (a < ref)) + || ((mode == MSGS_GT) && (thismsg > ref)) + ) + ) { + CallBack(thismsg); + } + } + phree(msglist); /* Clean up */ +} + + + /* * cmd_msgs() - get list of message #'s in this room + * implements the MSGS server command using CtdlForEachMessage() */ void cmd_msgs(char *cmdbuf) { - int a = 0; int mode = 0; char which[256]; - int cm_howmany = 0; - long cm_gt = 0L; - struct visit vbuf; + char buf[256]; + char tfield[256]; + char tvalue[256]; + int cm_ref = 0; + int i; + int with_template = 0; + struct CtdlMessage *template = NULL; extract(which, cmdbuf, 0); + cm_ref = extract_int(cmdbuf, 1); + with_template = extract_int(cmdbuf, 2); mode = MSGS_ALL; strcat(which, " "); if (!strncasecmp(which, "OLD", 3)) mode = MSGS_OLD; - if (!strncasecmp(which, "NEW", 3)) + else if (!strncasecmp(which, "NEW", 3)) mode = MSGS_NEW; - if (!strncasecmp(which, "FIRST", 5)) { + else if (!strncasecmp(which, "FIRST", 5)) mode = MSGS_FIRST; - cm_howmany = extract_int(cmdbuf, 1); - } - if (!strncasecmp(which, "LAST", 4)) { + else if (!strncasecmp(which, "LAST", 4)) mode = MSGS_LAST; - cm_howmany = extract_int(cmdbuf, 1); - } - if (!strncasecmp(which, "GT", 2)) { + else if (!strncasecmp(which, "GT", 2)) mode = MSGS_GT; - cm_gt = extract_long(cmdbuf, 1); - } + if ((!(CC->logged_in)) && (!(CC->internal_pgm))) { cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN); return; } - get_mm(); - get_msglist(&CC->quickroom); - getuser(&CC->usersupp, CC->curr_user); - CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); - - cprintf("%d Message list...\n", LISTING_FOLLOWS); - if (CC->num_msgs != 0) { - for (a = 0; a < (CC->num_msgs); ++a) - if ((MessageFromList(a) >= 0) - && ( - (mode == MSGS_ALL) - || ((mode == MSGS_OLD) && (MessageFromList(a) <= vbuf.v_lastseen)) - || ((mode == MSGS_NEW) && (MessageFromList(a) > vbuf.v_lastseen)) - || ((mode == MSGS_NEW) && (MessageFromList(a) >= vbuf.v_lastseen) - && (CC->usersupp.flags & US_LASTOLD)) - || ((mode == MSGS_LAST) && (a >= (CC->num_msgs - cm_howmany))) - || ((mode == MSGS_FIRST) && (a < cm_howmany)) - || ((mode == MSGS_GT) && (MessageFromList(a) > cm_gt)) - ) - ) { - cprintf("%ld\n", MessageFromList(a)); + if (with_template) { + cprintf("%d Send template then receive message list\n", + START_CHAT_MODE); + template = (struct CtdlMessage *) + mallok(sizeof(struct CtdlMessage)); + memset(template, 0, sizeof(struct CtdlMessage)); + while(client_gets(buf), strcmp(buf,"000")) { + extract(tfield, buf, 0); + extract(tvalue, buf, 1); + for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) { + if (!strcasecmp(tfield, msgkeys[i])) { + template->cm_fields[i] = + strdoop(tvalue); + } } + } + } + else { + cprintf("%d Message list...\n", LISTING_FOLLOWS); } + + CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing); + if (template != NULL) CtdlFreeMessage(template); cprintf("000\n"); } + /* * help_subst() - support routine for help file viewer */ @@ -317,8 +479,9 @@ FMTA: if (subst) { buffer[strlen(buffer) + 1] = 0; a = buffer[0]; strcpy(buffer, &buffer[1]); - } else + } else { ch = *mptr++; + } old = real; real = ch; @@ -386,10 +549,34 @@ void list_this_part(char *name, char *filename, char *partnum, char *disp, void fixed_output(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, size_t length) { + char *ptr; + + 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, "Skipping part %s (%s)\n", partnum, cbtype); + return; + } + + ma->did_print = 1; if (!strcasecmp(cbtype, "text/plain")) { client_write(content, length); - } else { + } + else if (!strcasecmp(cbtype, "text/html")) { + ptr = html_to_ascii(content, 80, 0); + client_write(ptr, strlen(ptr)); + phree(ptr); + } + else if (strncasecmp(cbtype, "multipart/", 10)) { cprintf("Part %s: %s (%s) (%d bytes)\n", partnum, filename, cbtype, length); } @@ -424,21 +611,129 @@ void mime_download(char *name, char *filename, char *partnum, char *disp, +/* + * Load a message from disk into memory. + * This is used by output_message() and other fetch functions. + * + * NOTE: Caller is responsible for freeing the returned CtdlMessage struct + * using the CtdlMessageFree() function. + */ +struct CtdlMessage *CtdlFetchMessage(long msgnum) +{ + struct cdbdata *dmsgtext; + struct CtdlMessage *ret = NULL; + char *mptr; + CIT_UBYTE ch; + CIT_UBYTE field_header; + size_t field_length; + + dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long)); + if (dmsgtext == NULL) { + return NULL; + } + mptr = dmsgtext->ptr; + + /* Parse the three bytes that begin EVERY message on disk. + * The first is always 0xFF, the on-disk magic number. + * The second is the anonymous/public type byte. + * The third is the format type byte (vari, fixed, or MIME). + */ + ch = *mptr++; + if (ch != 255) { + lprintf(5, "Message %ld appears to be corrupted.\n", msgnum); + cdb_free(dmsgtext); + return NULL; + } + ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage)); + memset(ret, 0, sizeof(struct CtdlMessage)); + + ret->cm_magic = CTDLMESSAGE_MAGIC; + ret->cm_anon_type = *mptr++; /* Anon type byte */ + ret->cm_format_type = *mptr++; /* Format type byte */ + + /* + * The rest is zero or more arbitrary fields. Load them in. + * We're done when we encounter either a zero-length field or + * have just processed the 'M' (message text) field. + */ + do { + field_length = strlen(mptr); + if (field_length == 0) + break; + field_header = *mptr++; + ret->cm_fields[field_header] = mallok(field_length); + strcpy(ret->cm_fields[field_header], mptr); + + while (*mptr++ != 0); /* advance to next field */ + + } while ((field_length > 0) && (field_header != 'M')); + + cdb_free(dmsgtext); + + /* Always make sure there's something in the msg text field */ + if (ret->cm_fields['M'] == NULL) + ret->cm_fields['M'] = strdoop("\n"); + + /* Perform "before read" hooks (aborting if any return nonzero) */ + if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) { + CtdlFreeMessage(ret); + return NULL; + } + + return (ret); +} + + +/* + * Returns 1 if the supplied pointer points to a valid Citadel message. + * If the pointer is NULL or the magic number check fails, returns 0. + */ +int is_valid_message(struct CtdlMessage *msg) { + if (msg == NULL) + return 0; + if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) { + lprintf(3, "is_valid_message() -- self-check failed\n"); + return 0; + } + return 1; +} + + +/* + * 'Destructor' for struct CtdlMessage + */ +void CtdlFreeMessage(struct CtdlMessage *msg) +{ + int i; + + if (is_valid_message(msg) == 0) return; + + for (i = 0; i < 256; ++i) + if (msg->cm_fields[i] != NULL) + phree(msg->cm_fields[i]); + + msg->cm_magic = 0; /* just in case */ + phree(msg); +} + + + /* * Get a message off disk. (return value is the message's timestamp) * */ -time_t output_message(char *msgid, int mode, int headers_only) +void output_message(char *msgid, int mode, int headers_only) { long msg_num; - int a; - CIT_UBYTE ch, rch; - CIT_UBYTE format_type, anon_flag; + int a, i, k; char buf[1024]; - long msg_len; - int msg_ok = 0; + time_t xtime; + CIT_UBYTE ch; + char allkeys[256]; + char display_name[256]; + + struct CtdlMessage *TheMessage = NULL; - struct cdbdata *dmsgtext; char *mptr; /* buffers needed for RFC822 translation */ @@ -447,84 +742,49 @@ time_t output_message(char *msgid, int mode, int headers_only) char snode[256]; char lnode[256]; char mid[256]; - time_t xtime = 0L; /* */ msg_num = atol(msgid); + safestrncpy(mid, msgid, sizeof mid); - - if ((!(CC->logged_in)) && (!(CC->internal_pgm)) && (mode != MT_DATE)) { + if ((!(CC->logged_in)) && (!(CC->internal_pgm))) { cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); - return (xtime); - } - /* We used to need to check in the current room's message list - * to determine where the message's disk position. We no longer need - * to do this, but we do it anyway as a security measure, in order to - * prevent rogue clients from reading messages not in the current room. - */ - - msg_ok = 0; - if (CC->num_msgs > 0) { - for (a = 0; a < CC->num_msgs; ++a) { - if (MessageFromList(a) == msg_num) { - msg_ok = 1; - } - } - } - if (!msg_ok) { - if (mode != MT_DATE) - cprintf("%d Message %ld is not in this room.\n", - ERROR, msg_num); - return (xtime); + return; } - dmsgtext = cdb_fetch(CDB_MSGMAIN, &msg_num, sizeof(long)); - if (dmsgtext == NULL) { - if (mode != MT_DATE) - cprintf("%d Can't find message %ld\n", - ERROR + INTERNAL_ERROR); - return (xtime); - } - msg_len = (long) dmsgtext->len; - mptr = dmsgtext->ptr; + /* FIX ... small security issue + * We need to check to make sure the requested message is actually + * in the current room, and set msg_ok to 1 only if it is. This + * functionality is currently missing because I'm in a hurry to replace + * broken production code with nonbroken pre-beta code. :( -- ajc + * + if (!msg_ok) { + cprintf("%d Message %ld is not in this room.\n", + ERROR, msg_num); + return; + } + */ - /* this loop spews out the whole message if we're doing raw format */ - if (mode == MT_RAW) { - cprintf("%d %ld\n", BINARY_FOLLOWS, msg_len); - client_write(dmsgtext->ptr, (int) msg_len); - cdb_free(dmsgtext); - return (xtime); - } - /* Otherwise, we'll start parsing it field by field... */ - ch = *mptr++; - if (ch != 255) { - cprintf("%d Illegal message format on disk\n", - ERROR + INTERNAL_ERROR); - cdb_free(dmsgtext); - return (xtime); + /* + * Fetch the message from disk + */ + TheMessage = CtdlFetchMessage(msg_num); + if (TheMessage == NULL) { + cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num); + return; } - anon_flag = *mptr++; - format_type = *mptr++; /* Are we downloading a MIME component? */ if (mode == MT_DOWNLOAD) { - if (format_type != 4) { + if (TheMessage->cm_format_type != 4) { cprintf("%d This is not a MIME message.\n", ERROR); } else if (CC->download_fp != NULL) { cprintf("%d You already have a download open.\n", ERROR); } else { - /* Skip to the message body */ - while (ch = *mptr++, (ch != 'M' && ch != 0)) { - buf[0] = 0; - do { - buf[strlen(buf) + 1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - } - /* Now parse it */ + /* Parse the message text component */ + mptr = TheMessage->cm_fields['M']; mime_parser(mptr, NULL, *mime_download); /* If there's no file open by this time, the requested * section wasn't found, so print an error @@ -535,168 +795,158 @@ time_t output_message(char *msgid, int mode, int headers_only) desired_section); } } - cdb_free(dmsgtext); - return (xtime); + CtdlFreeMessage(TheMessage); + return; } - /* Are we just looking for the message date? */ - if (mode == MT_DATE) - while (ch = *mptr++, (ch != 'M' && ch != 0)) { - buf[0] = 0; - do { - buf[strlen(buf) + 1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - - if (ch == 'T') { - xtime = atol(buf); - cdb_free(dmsgtext); - return (xtime); - } - } + /* now for the user-mode message reading loops */ cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num); - if (mode == MT_CITADEL) - cprintf("type=%d\n", format_type); + /* 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 == 4) + cprintf("type=1\n"); + else + cprintf("type=%d\n", TheMessage->cm_format_type); + } - if ((anon_flag == MES_ANON) && (mode == MT_CITADEL)) { + /* nhdr=yes means that we're only displaying headers, no body */ + if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) { cprintf("nhdr=yes\n"); } + /* begin header processing loop for Citadel message format */ - if ((mode == MT_CITADEL) || (mode == MT_MIME)) - while (ch = *mptr++, (ch != 'M' && ch != 0)) { - buf[0] = 0; - do { - buf[strlen(buf) + 1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - - if (ch == 'A') { - PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG); - if (anon_flag == MES_ANON) - cprintf("from=****"); - else if (anon_flag == MES_AN2) - cprintf("from=anonymous"); - else - cprintf("from=%s", buf); - if ((is_room_aide()) && ((anon_flag == MES_ANON) - || (anon_flag == MES_AN2))) - cprintf(" [%s]", buf); - cprintf("\n"); - } else if (ch == 'P') - cprintf("path=%s\n", buf); - else if (ch == 'U') - cprintf("subj=%s\n", buf); - else if (ch == 'I') - cprintf("msgn=%s\n", buf); - else if (ch == 'H') - cprintf("hnod=%s\n", buf); - else if (ch == 'O') - cprintf("room=%s\n", buf); - else if (ch == 'N') - cprintf("node=%s\n", buf); - else if (ch == 'R') - cprintf("rcpt=%s\n", buf); - else if (ch == 'T') - cprintf("time=%s\n", buf); - /* else cprintf("fld%c=%s\n",ch,buf); */ + if ((mode == MT_CITADEL) || (mode == MT_MIME)) { + + strcpy(display_name, ""); + if (TheMessage->cm_fields['A']) { + strcpy(buf, TheMessage->cm_fields['A']); + PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG); + if (TheMessage->cm_anon_type == MES_ANON) + strcpy(display_name, "****"); + else if (TheMessage->cm_anon_type == MES_AN2) + strcpy(display_name, "anonymous"); + 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); + } } + + strcpy(allkeys, FORDER); + for (i=0; icm_fields[k] != NULL) { + if (k == 'A') { + cprintf("%s=%s\n", + msgkeys[k], + display_name); + } + else { + cprintf("%s=%s\n", + msgkeys[k], + TheMessage->cm_fields[k] + ); + } + } + } + } + + } + /* begin header processing loop for RFC822 transfer format */ strcpy(suser, ""); strcpy(luser, ""); strcpy(snode, NODENAME); strcpy(lnode, HUMANNODE); - if (mode == MT_RFC822) - while (ch = *mptr++, (ch != 'M' && ch != 0)) { - buf[0] = 0; - do { - buf[strlen(buf) + 1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - - if (ch == 'A') - strcpy(luser, buf); - else if (ch == 'P') { - cprintf("Path: %s\n", buf); - for (a = 0; a < strlen(buf); ++a) { - if (buf[a] == '!') { - strcpy(buf, &buf[a + 1]); - a = 0; + if (mode == MT_RFC822) { + for (i = 0; i < 256; ++i) { + if (TheMessage->cm_fields[i]) { + mptr = TheMessage->cm_fields[i]; + + if (i == 'A') { + strcpy(luser, mptr); + } else if (i == 'P') { + cprintf("Path: %s\n", mptr); + for (a = 0; a < strlen(mptr); ++a) { + if (mptr[a] == '!') { + strcpy(mptr, &mptr[a + 1]); + a = 0; + } } + strcpy(suser, mptr); + } else if (i == 'U') + cprintf("Subject: %s\n", mptr); + else if (i == 'I') + strcpy(mid, mptr); + else if (i == 'H') + strcpy(lnode, mptr); + else if (i == 'O') + cprintf("X-Citadel-Room: %s\n", mptr); + else if (i == 'N') + strcpy(snode, mptr); + else if (i == 'R') + cprintf("To: %s\n", mptr); + else if (i == 'T') { + xtime = atol(mptr); + cprintf("Date: %s", asctime(localtime(&xtime))); } - strcpy(suser, buf); - } else if (ch == 'U') - cprintf("Subject: %s\n", buf); - else if (ch == 'I') - strcpy(mid, buf); - else if (ch == 'H') - strcpy(lnode, buf); - else if (ch == 'O') - cprintf("X-Citadel-Room: %s\n", buf); - else if (ch == 'N') - strcpy(snode, buf); - else if (ch == 'R') - cprintf("To: %s\n", buf); - else if (ch == 'T') { - xtime = atol(buf); - cprintf("Date: %s", asctime(localtime(&xtime))); } } + } + if (mode == MT_RFC822) { if (!strcasecmp(snode, NODENAME)) { strcpy(snode, FQDN); } cprintf("Message-ID: <%s@%s>\n", mid, snode); PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG); - cprintf("From: %s@%s (%s)\n", - suser, snode, luser); + cprintf("From: %s@%s (%s)\n", suser, snode, luser); cprintf("Organization: %s\n", lnode); } + /* end header processing loop ... at this point, we're in the text */ - if (ch == 0) { - cprintf("text\n*** ?Message truncated\n000\n"); - cdb_free(dmsgtext); - return (xtime); - } - /* do some sort of MIME output */ - if (format_type == 4) { - if ((mode == MT_CITADEL) || (mode == MT_MIME)) { + mptr = TheMessage->cm_fields['M']; + + /* Tell the client about the MIME parts in this message */ + if (TheMessage->cm_format_type == 4) { /* legacy textual dump */ + if (mode == MT_CITADEL) { mime_parser(mptr, NULL, *list_this_part); } - if (mode == MT_MIME) { /* If MT_MIME then it's parts only */ + else if (mode == MT_MIME) { /* list parts only */ + mime_parser(mptr, NULL, *list_this_part); cprintf("000\n"); - cdb_free(dmsgtext); - return (xtime); + CtdlFreeMessage(TheMessage); + return; } } + if (headers_only) { - /* give 'em a length */ - msg_len = 0L; - while (ch = *mptr++, ch > 0) { - ++msg_len; - } - cprintf("mlen=%ld\n", msg_len); cprintf("000\n"); - cdb_free(dmsgtext); - return (xtime); + CtdlFreeMessage(TheMessage); + return; } + /* signify start of msg text */ if (mode == MT_CITADEL) cprintf("text\n"); - if ((mode == MT_RFC822) && (format_type != 4)) + if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4)) cprintf("\n"); /* If the format type on disk is 1 (fixed-format), then we want * everything to be output completely literally ... regardless of * what message transfer format is in use. */ - if (format_type == 1) { + if (TheMessage->cm_format_type == 1) { strcpy(buf, ""); while (ch = *mptr++, ch > 0) { if (ch == 13) @@ -712,6 +962,7 @@ time_t output_message(char *msgid, int mode, int headers_only) if (strlen(buf) > 0) cprintf("%s\n", buf); } + /* If the message on disk is format 0 (Citadel vari-format), we * output using the formatter at 80 columns. This is the final output * form if the transfer format is RFC822, but if the transfer format @@ -719,24 +970,29 @@ time_t output_message(char *msgid, int mode, int headers_only) * for new paragraphs is correct and the client will reformat the * message to the reader's screen width. */ - if (format_type == 0) { + if (TheMessage->cm_format_type == 0) { memfmout(80, mptr, 0); } + /* If the message on disk is format 4 (MIME), we've gotta hand it * off to the MIME parser. The client has already been told that * this message is format 1 (fixed format), so the callback function * we use will display those parts as-is. */ - if (format_type == 4) { + if (TheMessage->cm_format_type == 4) { + CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info)); + memset(ma, 0, sizeof(struct ma_info)); mime_parser(mptr, NULL, *fixed_output); } + /* now we're done */ cprintf("000\n"); - cdb_free(dmsgtext); - return (xtime); + CtdlFreeMessage(TheMessage); + return; } + /* * display a message (mode 0 - Citadel proprietary) */ @@ -767,25 +1023,47 @@ void cmd_msg2(char *cmdbuf) output_message(msgid, MT_RFC822, headers_only); } + + /* * display a message (mode 3 - IGnet raw format - internal programs only) */ void cmd_msg3(char *cmdbuf) { - char msgid[256]; - int headers_only = 0; + long msgnum; + struct CtdlMessage *msg; + struct ser_ret smr; if (CC->internal_pgm == 0) { cprintf("%d This command is for internal programs only.\n", ERROR); return; } - extract(msgid, cmdbuf, 0); - headers_only = extract_int(cmdbuf, 1); - output_message(msgid, MT_RAW, headers_only); + msgnum = extract_long(cmdbuf, 0); + msg = CtdlFetchMessage(msgnum); + if (msg == NULL) { + cprintf("%d Message %ld not found.\n", + ERROR, msgnum); + return; + } + + serialize_message(&smr, msg); + CtdlFreeMessage(msg); + + if (smr.len == 0) { + cprintf("%d Unable to serialize message\n", + ERROR+INTERNAL_ERROR); + return; + } + + cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len); + client_write(smr.ser, smr.len); + phree(smr.ser); } + + /* * display a message (mode 4 - MIME) (FIX ... still evolving, not complete) */ @@ -798,8 +1076,6 @@ void cmd_msg4(char *cmdbuf) output_message(msgid, MT_MIME, 0); } - - /* * Open a component of a MIME message as a download file */ @@ -813,6 +1089,125 @@ void cmd_opna(char *cmdbuf) extract(desired_section, cmdbuf, 1); output_message(msgid, MT_DOWNLOAD, 0); +} + + +/* + * Save a message pointer into a specified room + * (Returns 0 for success, nonzero for failure) + * roomname may be NULL to use the current room + */ +int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { + int i; + char hold_rm[ROOMNAMELEN]; + struct cdbdata *cdbfr; + int num_msgs; + long *msglist; + long highest_msg = 0L; + struct CtdlMessage *msg = NULL; + + lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n", + roomname, msgid, flags); + + strcpy(hold_rm, CC->quickroom.QRname); + + /* We may need to check to see if this message is real */ + if ( (flags & SM_VERIFY_GOODNESS) + || (flags & SM_DO_REPL_CHECK) + ) { + msg = CtdlFetchMessage(msgid); + if (msg == NULL) return(ERROR + ILLEGAL_VALUE); + } + + /* Perform replication checks if necessary */ + if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) { + + if (getroom(&CC->quickroom, + ((roomname != NULL) ? roomname : CC->quickroom.QRname) ) + != 0) { + lprintf(9, "No such room <%s>\n", roomname); + if (msg != NULL) CtdlFreeMessage(msg); + return(ERROR + ROOM_NOT_FOUND); + } + + if (ReplicationChecks(msg) != 0) { + getroom(&CC->quickroom, hold_rm); + if (msg != NULL) CtdlFreeMessage(msg); + lprintf(9, "Did replication, and newer exists\n"); + return(0); + } + } + + /* Now the regular stuff */ + if (lgetroom(&CC->quickroom, + ((roomname != NULL) ? roomname : CC->quickroom.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)); + if (cdbfr == NULL) { + msglist = NULL; + num_msgs = 0; + } else { + msglist = mallok(cdbfr->len); + if (msglist == NULL) + lprintf(3, "ERROR malloc msglist!\n"); + num_msgs = cdbfr->len / sizeof(long); + memcpy(msglist, cdbfr->ptr, cdbfr->len); + cdb_free(cdbfr); + } + + + /* Make sure the message doesn't already exist in this room. It + * is absolutely taboo to have more than one reference to the same + * message in a room. + */ + if (num_msgs > 0) for (i=0; iquickroom); /* unlock the room */ + getroom(&CC->quickroom, hold_rm); + if (msg != NULL) CtdlFreeMessage(msg); + return(ERROR + ALREADY_EXISTS); + } + } + + /* Now add the new message */ + ++num_msgs; + msglist = reallok(msglist, + (num_msgs * sizeof(long))); + + if (msglist == NULL) { + lprintf(3, "ERROR: can't realloc message list!\n"); + } + msglist[num_msgs - 1] = msgid; + + /* Sort the message list, so all the msgid's are in order */ + num_msgs = sort_msglist(msglist, num_msgs); + + /* Determine the highest message number */ + highest_msg = msglist[num_msgs - 1]; + + /* Write it back to disk. */ + cdb_store(CDB_MSGLISTS, &CC->quickroom.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); + + /* Bump the reference count for this message. */ + AdjRefCount(msgid, +1); + + /* Return success. */ + if (msg != NULL) CtdlFreeMessage(msg); + return (0); } @@ -820,38 +1215,40 @@ void cmd_opna(char *cmdbuf) /* * 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 + * called by server-side modules. + * */ -long send_message(char *message_in_memory, /* pointer to buffer */ - size_t message_length, /* length of buffer */ - int generate_id) -{ /* 1 to generate an I field */ - +long send_message(struct CtdlMessage *msg, /* pointer to buffer */ + int generate_id, /* generate 'I' field? */ + FILE *save_a_copy) /* save a copy to disk? */ +{ long newmsgid; - char *actual_message; - size_t actual_length; long retval; char msgidbuf[32]; + struct ser_ret smr; /* Get a new message number */ newmsgid = get_new_message_number(); + sprintf(msgidbuf, "%ld", newmsgid); if (generate_id) { - sprintf(msgidbuf, "I%ld", newmsgid); - actual_length = message_length + strlen(msgidbuf) + 1; - actual_message = mallok(actual_length); - memcpy(actual_message, message_in_memory, 3); - memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf) + 1)); - memcpy(&actual_message[strlen(msgidbuf) + 4], - &message_in_memory[3], message_length - 3); - } else { - actual_message = message_in_memory; - actual_length = message_length; + msg->cm_fields['I'] = strdoop(msgidbuf); } + + serialize_message(&smr, msg); + + if (smr.len == 0) { + cprintf("%d Unable to serialize message\n", + ERROR+INTERNAL_ERROR); + return (-1L); + } /* Write our little bundle of joy into the message base */ begin_critical_section(S_MSGMAIN); if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long), - actual_message, actual_length) < 0) { + smr.ser, smr.len) < 0) { lprintf(2, "Can't store message\n"); retval = 0L; } else { @@ -859,220 +1256,448 @@ long send_message(char *message_in_memory, /* pointer to buffer */ } end_critical_section(S_MSGMAIN); - if (generate_id) { - phree(actual_message); + /* If the caller specified that a copy should be saved to a particular + * file handle, do that now too. + */ + if (save_a_copy != NULL) { + fwrite(smr.ser, smr.len, 1, save_a_copy); } - /* Finally, return the pointers */ - return (retval); + + /* Free the memory we used for the serialized message */ + phree(smr.ser); + + /* Return the *local* message ID to the caller + * (even if we're storing an incoming network message) + */ + return(retval); } /* - * this is a simple file copy routine. + * Serialize a struct CtdlMessage into the format used on disk and network. + * + * This function loads up a "struct ser_ret" (defined in server.h) which + * contains the length of the serialized message and a pointer to the + * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER. */ -void copy_file(char *from, char *to) +void serialize_message(struct ser_ret *ret, /* return values */ + struct CtdlMessage *msg) /* unserialized msg */ { - FILE *ffp, *tfp; - int a; + size_t wlen; + int i; + static char *forder = FORDER; - ffp = fopen(from, "r"); - if (ffp == NULL) - return; - tfp = fopen(to, "w"); - if (tfp == NULL) { - fclose(ffp); + if (is_valid_message(msg) == 0) return; /* self check */ + + ret->len = 3; + for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) + ret->len = ret->len + + strlen(msg->cm_fields[(int)forder[i]]) + 2; + + lprintf(9, "calling malloc\n"); + ret->ser = mallok(ret->len); + if (ret->ser == NULL) { + ret->len = 0; return; } - while (a = getc(ffp), a >= 0) { - putc(a, tfp); + + ret->ser[0] = 0xFF; + ret->ser[1] = msg->cm_anon_type; + ret->ser[2] = msg->cm_format_type; + wlen = 3; + + for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) { + ret->ser[wlen++] = (char)forder[i]; + strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]); + wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1; } - fclose(ffp); - fclose(tfp); + if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n", + ret->len, wlen); + return; } /* - * message base operation to save a message and install its pointers + * Back end for the ReplicationChecks() function + */ +void check_repl(long msgnum) { + struct CtdlMessage *msg; + time_t timestamp = (-1L); + + lprintf(9, "check_repl() found message %ld\n", msgnum); + msg = CtdlFetchMessage(msgnum); + if (msg == NULL) return; + if (msg->cm_fields['T'] != NULL) { + timestamp = atol(msg->cm_fields['T']); + } + CtdlFreeMessage(msg); + + if (timestamp > msg_repl->highest) { + msg_repl->highest = timestamp; /* newer! */ + lprintf(9, "newer!\n"); + return; + } + lprintf(9, "older!\n"); + + /* Existing isn't newer? Then delete the old one(s). */ + CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL); +} + + +/* + * Check to see if any messages already exist which carry the same Extended ID + * as this one. + * + * If any are found: + * -> With older timestamps: delete them and return 0. Message will be saved. + * -> With newer timestamps: return 1. Message save will be aborted. + */ +int ReplicationChecks(struct CtdlMessage *msg) { + struct CtdlMessage *template; + int abort_this = 0; + + lprintf(9, "ReplicationChecks() started\n"); + /* No extended id? Don't do anything. */ + if (msg->cm_fields['E'] == NULL) return 0; + if (strlen(msg->cm_fields['E']) == 0) return 0; + lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']); + + CtdlAllocUserData(SYM_REPL, sizeof(struct repl)); + strcpy(msg_repl->extended_id, msg->cm_fields['E']); + msg_repl->highest = atol(msg->cm_fields['T']); + + template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); + memset(template, 0, sizeof(struct CtdlMessage)); + template->cm_fields['E'] = strdoop(msg->cm_fields['E']); + + CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl); + + /* If a newer message exists with the same Extended ID, abort + * this save. + */ + if (msg_repl->highest > atol(msg->cm_fields['T']) ) { + abort_this = 1; + } + + CtdlFreeMessage(template); + lprintf(9, "ReplicationChecks() returning %d\n", abort_this); + return(abort_this); +} + + + + +/* + * Save a message to disk */ -void save_message(char *mtmp, /* file containing proper message */ - char *rec, /* Recipient (if mail) */ - char *force, /* if non-zero length, force a room */ - int mailtype, /* local or remote type, see citadel.h */ - int generate_id) -{ /* set to 1 to generate an 'I' field */ +void CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ + char *rec, /* Recipient (mail) */ + char *force, /* force a particular room? */ + int mailtype, /* local or remote type */ + int generate_id) /* 1 = generate 'I' field */ +{ char aaa[100]; char hold_rm[ROOMNAMELEN]; char actual_rm[ROOMNAMELEN]; char force_room[ROOMNAMELEN]; + char content_type[256]; /* We have to learn this */ char recipient[256]; long newmsgid; - char *message_in_memory; - struct stat statbuf; - size_t templen; - FILE *fp; + char *mptr; struct usersupp userbuf; int a; - static int seqnum = 0; - int successful_local_recipients; - struct quickroom qtemp; + struct SuppMsgInfo smi; + FILE *network_fp = NULL; + static int seqnum = 1; - lprintf(9, "save_message(%s,%s,%s,%d,%d)\n", - mtmp, rec, force, mailtype, generate_id); + lprintf(9, "CtdlSaveMsg() called\n"); + if (is_valid_message(msg) == 0) return; /* self check */ + + /* If this message has no timestamp, we take the liberty of + * giving it one, right now. + */ + if (msg->cm_fields['T'] == NULL) { + sprintf(aaa, "%ld", time(NULL)); + msg->cm_fields['T'] = strdoop(aaa); + } + + lprintf(9, "checkpoint 1 \n"); + /* If this message has no path, we generate one. + */ + if (msg->cm_fields['A'] == NULL) { + msg->cm_fields['A'] = strdoop("unknown user"); + } + if (msg->cm_fields['P'] == NULL) { + msg->cm_fields['P'] = strdoop(msg->cm_fields['A']); + for (a=0; acm_fields['P']); ++a) { + if (isspace(msg->cm_fields['P'][a])) { + msg->cm_fields['P'][a] = ' '; + } + } + } strcpy(force_room, force); + lprintf(9, "checkpoint 2 \n"); /* Strip non-printable characters out of the recipient name */ strcpy(recipient, rec); for (a = 0; a < strlen(recipient); ++a) if (!isprint(recipient[a])) strcpy(&recipient[a], &recipient[a + 1]); - /* Measure the message */ - stat(mtmp, &statbuf); - templen = statbuf.st_size; - - /* Now read it into memory */ - message_in_memory = (char *) mallok(templen); - if (message_in_memory == NULL) { - lprintf(2, "Can't allocate memory to save message!\n"); - return; + /* Learn about what's inside, because it's what's inside that counts */ + + lprintf(9, "checkpoint 3 \n"); + switch (msg->cm_format_type) { + case 0: + strcpy(content_type, "text/x-citadel-variformat"); + break; + case 1: + strcpy(content_type, "text/plain"); + break; + case 4: + strcpy(content_type, "text/plain"); + /* advance past header fields */ + mptr = msg->cm_fields['M']; + a = strlen(mptr); + while (--a) { + if (!strncasecmp(mptr, "Content-type: ", 14)) { + safestrncpy(content_type, mptr, + sizeof(content_type)); + strcpy(content_type, &content_type[14]); + for (a = 0; a < strlen(content_type); ++a) + if ((content_type[a] == ';') + || (content_type[a] == ' ') + || (content_type[a] == 13) + || (content_type[a] == 10)) + content_type[a] = 0; + break; + } + ++mptr; + } } - fp = fopen(mtmp, "rb"); - fread(message_in_memory, templen, 1, fp); - fclose(fp); - - newmsgid = send_message(message_in_memory, templen, generate_id); - phree(message_in_memory); - if (newmsgid <= 0L) - return; + lprintf(9, "checkpoint 4 \n"); + /* Goto the correct room */ + strcpy(hold_rm, CC->quickroom.QRname); strcpy(actual_rm, CC->quickroom.QRname); - strcpy(hold_rm, ""); - /* If the user is a twit, move to the twit room for posting... */ - if (TWITDETECT) + lprintf(9, "checkpoint 5 \n"); + /* If the user is a twit, move to the twit room for posting */ + if ( (CC->logged_in) && (TWITDETECT) ) { if (CC->usersupp.axlevel == 2) { strcpy(hold_rm, actual_rm); strcpy(actual_rm, config.c_twitroom); } + } + + lprintf(9, "checkpoint 6 \n"); /* ...or if this message is destined for Aide> then go there. */ - lprintf(9, "actual room forcing loop\n"); if (strlen(force_room) > 0) { - strcpy(hold_rm, actual_rm); strcpy(actual_rm, force_room); } - /* This call to usergoto() changes rooms if necessary. It also - * causes the latest message list to be read into memory. - */ - usergoto(actual_rm, 0); - /* read in the quickroom record, obtaining a lock... */ - lgetroom(&CC->quickroom, actual_rm); + lprintf(9, "checkpoint 7 \n"); + if (strcasecmp(actual_rm, CC->quickroom.QRname)) + getroom(&CC->quickroom, actual_rm); - /* Fix an obscure bug */ - if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) { - CC->quickroom.QRflags = CC->quickroom.QRflags & ~QR_MAILBOX; - } - /* Add the message pointer to the room */ - CC->quickroom.QRhighest = AddMessageToRoom(&CC->quickroom, newmsgid); + lprintf(9, "checkpoint 8 \n"); + /* Perform "before save" hooks (aborting if any return nonzero) */ + if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return; - /* update quickroom */ - lputroom(&CC->quickroom); + lprintf(9, "checkpoint 9 \n"); + /* If this message has an Extended ID, perform replication checks */ + if (ReplicationChecks(msg) > 0) return; + lprintf(9, "checkpoint 10 \n"); /* Network mail - send a copy to the network program. */ if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) { sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x", (long) getpid(), CC->cs_pid, ++seqnum); - copy_file(mtmp, aaa); + 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)); + } + + lprintf(9, "checkpoint 11 \n"); + /* Save it to disk */ + newmsgid = send_message(msg, generate_id, network_fp); + if (network_fp != NULL) { + fclose(network_fp); system("exec nohup ./netproc -i >/dev/null 2>&1 &"); } + + if (newmsgid <= 0L) return; + + lprintf(9, "checkpoint 12 \n"); + /* Write a supplemental message info record. This doesn't have to + * be a critical section because nobody else knows about this message + * yet. + */ + memset(&smi, 0, sizeof(struct SuppMsgInfo)); + smi.smi_msgnum = newmsgid; + smi.smi_refcount = 0; + safestrncpy(smi.smi_content_type, content_type, 64); + PutSuppMsgInfo(&smi); + + /* Now figure out where to store the pointers */ + + + lprintf(9, "checkpoint 13 \n"); + /* If this is being done by the networker delivering a private + * 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)) { + CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0); + } + + lprintf(9, "checkpoint 14 \n"); /* Bump this user's messages posted counter. */ - lgetuser(&CC->usersupp, CC->curr_user); - CC->usersupp.posted = CC->usersupp.posted + 1; - lputuser(&CC->usersupp, CC->curr_user); + if (CC->logged_in) { + lgetuser(&CC->usersupp, CC->curr_user); + CC->usersupp.posted = CC->usersupp.posted + 1; + lputuser(&CC->usersupp); + } + lprintf(9, "checkpoint 15 \n"); /* If this is private, local mail, make a copy in the * recipient's mailbox and bump the reference count. */ - successful_local_recipients = 0; if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) { if (getuser(&userbuf, recipient) == 0) { MailboxName(actual_rm, &userbuf, MAILROOM); - lprintf(9, "Targeting mailbox: <%s>\n", actual_rm); - if (lgetroom(&qtemp, actual_rm) == 0) { - qtemp.QRhighest = - AddMessageToRoom(&qtemp, newmsgid); - lputroom(&qtemp); - ++successful_local_recipients; - } + CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0); } } - /* If we've posted in a room other than the current room, then we - * have to now go back to the current room... - */ - if (strlen(hold_rm) > 0) { - usergoto(hold_rm, 0); - } - unlink(mtmp); /* delete the temporary file */ - /* If the message was delivered to more than one location locally, - * we have to bump the reference count accordingly. - */ - if (successful_local_recipients != 0) { - AdjRefCount(newmsgid, successful_local_recipients); - } + lprintf(9, "checkpoint 16 \n"); + /* Perform "after save" hooks */ + PerformMessageHooks(msg, EVT_AFTERSAVE); + + lprintf(9, "checkpoint 17 \n"); + /* */ + if (strcasecmp(hold_rm, CC->quickroom.QRname)) + getroom(&CC->quickroom, hold_rm); } + /* - * Generate an administrative message and post it in the Aide> room. + * Convenience function for generating small administrative messages. */ -void aide_message(char *text) +void quickie_message(char *from, char *to, char *room, char *text) { - FILE *fp; - - fp = fopen(CC->temp, "wb"); - fprintf(fp, "%c%c%c", 255, MES_NORMAL, 0); - fprintf(fp, "Psysop%c", 0); - fprintf(fp, "T%ld%c", (long) time(NULL), 0); - fprintf(fp, "ACitadel%c", 0); - fprintf(fp, "OAide%c", 0); - fprintf(fp, "N%s%c", NODENAME, 0); - fprintf(fp, "M%s\n%c", text, 0); - fclose(fp); - save_message(CC->temp, "", AIDEROOM, MES_LOCAL, 1); + struct CtdlMessage *msg; + + 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_fields['A'] = strdoop(from); + msg->cm_fields['O'] = strdoop(room); + msg->cm_fields['N'] = strdoop(NODENAME); + if (to != NULL) + msg->cm_fields['R'] = strdoop(to); + msg->cm_fields['M'] = strdoop(text); + + CtdlSaveMsg(msg, "", room, MES_LOCAL, 1); + CtdlFreeMessage(msg); syslog(LOG_NOTICE, text); } +/* + * Back end function used by make_message() and similar functions + */ +char *CtdlReadMessageBody(char *terminator, size_t maxlen) { + char buf[256]; + size_t message_len = 0; + size_t buffer_len = 0; + char *ptr, *append; + char *m; + + m = mallok(4096); + if (m == NULL) { + while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;; + return(NULL); + } else { + buffer_len = 4096; + m[0] = 0; + message_len = 0; + } + /* read in the lines of message text one by one */ + append = NULL; + while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) { + + /* augment the buffer if we have to */ + if ((message_len + strlen(buf) + 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; + append = NULL; + lprintf(9, "buffer_len is %d\n", buffer_len); + } + } + + if (append == NULL) append = m; + while (strlen(append) > 0) ++append; + strcpy(append, buf); + strcat(append, "\n"); + message_len = message_len + strlen(buf) + 1; + + /* 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); + } + } + return(m); +} + + + + /* * Build a binary message to be saved on disk. */ -void make_message( - char *filename, /* temporary file name */ - struct usersupp *author, /* author's usersupp 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 */ - FILE *fp; +struct CtdlMessage *make_message( + struct usersupp *author, /* author's usersupp 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; - time_t now; char dest_node[32]; char buf[256]; + struct CtdlMessage *msg; + + msg = mallok(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_anon_type = type; + msg->cm_format_type = format_type; /* 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); @@ -1083,47 +1708,44 @@ void make_message( } } } + /* 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; - time(&now); - fp = fopen(filename, "w"); - putc(255, fp); - putc(type, fp); /* Normal or anonymous, see MES_ flags */ - putc(format_type, fp); /* Formatted or unformatted */ - fprintf(fp, "Pcit%ld%c", author->usernum, 0); /* path */ - fprintf(fp, "T%ld%c", (long) now, 0); /* date/time */ - if (fake_name[0]) - fprintf(fp, "A%s%c", fake_name, 0); + sprintf(buf, "cit%ld", author->usernum); /* Path */ + msg->cm_fields['P'] = strdoop(buf); + + sprintf(buf, "%ld", time(NULL)); /* timestamp */ + msg->cm_fields['T'] = strdoop(buf); + + if (fake_name[0]) /* author */ + msg->cm_fields['A'] = strdoop(fake_name); else - fprintf(fp, "A%s%c", author->fullname, 0); /* author */ + msg->cm_fields['A'] = strdoop(author->fullname); - if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */ - fprintf(fp, "O%s%c", &CC->quickroom.QRname[11], 0); - } else { - fprintf(fp, "O%s%c", CC->quickroom.QRname, 0); - } + 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); - fprintf(fp, "N%s%c", NODENAME, 0); /* nodename */ - fprintf(fp, "H%s%c", HUMANNODE, 0); /* human nodename */ + msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */ + msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */ if (recipient[0] != 0) - fprintf(fp, "R%s%c", recipient, 0); + msg->cm_fields['R'] = strdoop(recipient); if (dest_node[0] != 0) - fprintf(fp, "D%s%c", dest_node, 0); + msg->cm_fields['D'] = strdoop(dest_node); - putc('M', fp); - while (client_gets(buf), strcmp(buf, "000")) { - fprintf(fp, "%s\n", buf); - } - syslog(LOG_INFO, "Closing message"); - putc(0, fp); - fclose(fp); + msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen); + + + return(msg); } @@ -1131,7 +1753,7 @@ void make_message( /* - * message entry - mode 0 (normal) + * message entry - mode 0 (normal) */ void cmd_ent0(char *entargs) { @@ -1139,8 +1761,8 @@ void cmd_ent0(char *entargs) char recipient[256]; int anon_flag = 0; int format_type = 0; - char newusername[256]; /* */ - + char newusername[256]; + struct CtdlMessage *msg; int a, b; int e = 0; int mtsflag = 0; @@ -1177,9 +1799,9 @@ void cmd_ent0(char *entargs) mtsflag = 0; - if (post == 2) { /* */ + if (post == 2) { if (CC->usersupp.axlevel < 6) { - cprintf("%d You don't have permission to do an aide post.\n", + cprintf("%d You don't have permission to masquerade.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } @@ -1197,9 +1819,7 @@ void cmd_ent0(char *entargs) strcpy(buf, recipient); } else strcpy(buf, "sysop"); - lprintf(9, "calling alias()\n"); e = alias(buf); /* alias and mail type */ - lprintf(9, "alias() returned %d\n", e); if ((buf[0] == 0) || (e == MES_ERROR)) { cprintf("%d Unknown address - cannot send message.\n", ERROR + NO_SUCH_USER); @@ -1229,7 +1849,7 @@ void cmd_ent0(char *entargs) return; } /* Check to make sure the user exists; also get the correct - * upper/lower casing of the name. + * upper/lower casing of the name. */ a = getuser(&tempUS, buf); if (a != 0) { @@ -1238,7 +1858,8 @@ void cmd_ent0(char *entargs) } strcpy(buf, tempUS.fullname); } - SKFALL:b = MES_NORMAL; + +SKFALL: b = MES_NORMAL; if (CC->quickroom.QRflags & QR_ANONONLY) b = MES_ANON; if (CC->quickroom.QRflags & QR_ANONOPT) { @@ -1255,14 +1876,25 @@ void cmd_ent0(char *entargs) cprintf("%d %s\n", OK, buf); return; } + cprintf("%d send message\n", SEND_LISTING); + + /* Read in the message from the client. */ if (CC->fake_postname[0]) - make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_postname); + msg = make_message(&CC->usersupp, buf, + CC->quickroom.QRname, b, e, format_type, + CC->fake_postname); else if (CC->fake_username[0]) - make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, CC->fake_username); + msg = make_message(&CC->usersupp, buf, + CC->quickroom.QRname, b, e, format_type, + CC->fake_username); else - make_message(CC->temp, &CC->usersupp, buf, CC->quickroom.QRname, b, e, format_type, ""); - save_message(CC->temp, buf, (mtsflag ? AIDEROOM : ""), e, 1); + msg = make_message(&CC->usersupp, buf, + CC->quickroom.QRname, b, e, format_type, ""); + + if (msg != NULL) + CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1); + CtdlFreeMessage(msg); CC->fake_postname[0] = '\0'; return; } @@ -1275,19 +1907,21 @@ void cmd_ent0(char *entargs) void cmd_ent3(char *entargs) { char recp[256]; - char buf[256]; int a; int e = 0; + int valid_msg = 1; + unsigned char ch, which_field; struct usersupp tempUS; long msglen; - long bloklen; - FILE *fp; + 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) @@ -1315,89 +1949,183 @@ void cmd_ent3(char *entargs) } } } + /* At this point, message has been approved. */ if (extract_int(entargs, 0) == 0) { cprintf("%d OK to send\n", OK); return; } - /* open a temp file to hold the message */ - fp = fopen(CC->temp, "wb"); - if (fp == NULL) { - cprintf("%d Cannot open %s: %s\n", - ERROR + INTERNAL_ERROR, - CC->temp, strerror(errno)); + + msglen = extract_long(entargs, 2); + msg = mallok(sizeof(struct CtdlMessage)); + if (msg == NULL) { + cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR); return; } - msglen = extract_long(entargs, 2); + + 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); - while (msglen > 0L) { - bloklen = ((msglen >= 255L) ? 255 : msglen); - client_read(buf, (int) bloklen); - fwrite(buf, (int) bloklen, 1, fp); - msglen = msglen - bloklen; + + 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, 0); + CtdlFreeMessage(msg); + phree(tempbuf); +} + + +/* + * API function to delete messages which match a set of criteria + * (returns the actual number of messages deleted) + */ +int CtdlDeleteMessages(char *room_name, /* which room */ + long dmsgnum, /* or "0" for any */ + char *content_type /* or NULL for any */ +) +{ + + struct quickroom qrbuf; + struct cdbdata *cdbfr; + long *msglist = NULL; + int num_msgs = 0; + int i; + int num_deleted = 0; + int delete_this; + struct SuppMsgInfo smi; + + lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n", + room_name, dmsgnum, content_type); + + /* get room record, obtaining a lock... */ + if (lgetroom(&qrbuf, room_name) != 0) { + lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n", + room_name); + return (0); /* room not found */ } - fclose(fp); + cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long)); + + if (cdbfr != NULL) { + msglist = mallok(cdbfr->len); + memcpy(msglist, cdbfr->ptr, cdbfr->len); + num_msgs = cdbfr->len / sizeof(long); + cdb_free(cdbfr); + } + if (num_msgs > 0) { + for (i = 0; i < num_msgs; ++i) { + delete_this = 0x00; + + /* Set/clear a bit for each criterion */ - save_message(CC->temp, recp, "", e, 0); + if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) { + delete_this |= 0x01; + } + if (content_type == NULL) { + delete_this |= 0x02; + } else { + GetSuppMsgInfo(&smi, msglist[i]); + if (!strcasecmp(smi.smi_content_type, + content_type)) { + delete_this |= 0x02; + } + } + + /* Delete message only if all bits are set */ + if (delete_this == 0x03) { + AdjRefCount(msglist[i], -1); + msglist[i] = 0L; + ++num_deleted; + } + } + + num_msgs = sort_msglist(msglist, num_msgs); + cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long), + msglist, (num_msgs * sizeof(long))); + + qrbuf.QRhighest = msglist[num_msgs - 1]; + phree(msglist); + } + lputroom(&qrbuf); + lprintf(9, "%d message(s) deleted.\n", num_deleted); + return (num_deleted); } + /* * Delete message from current room */ void cmd_dele(char *delstr) { long delnum; - int a, ok; + int num_deleted; getuser(&CC->usersupp, CC->curr_user); if ((CC->usersupp.axlevel < 6) && (CC->usersupp.usernum != CC->quickroom.QRroomaide) - && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) { + && ((CC->quickroom.QRflags & QR_MAILBOX) == 0) + && (!(CC->internal_pgm))) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } delnum = extract_long(delstr, 0); - /* get room records, obtaining a lock... */ - lgetroom(&CC->quickroom, CC->quickroom.QRname); - get_msglist(&CC->quickroom); + num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL); - ok = 0; - if (CC->num_msgs > 0) - for (a = 0; a < (CC->num_msgs); ++a) { - if (MessageFromList(a) == delnum) { - SetMessageInList(a, 0L); - ok = 1; - } - } - CC->num_msgs = sort_msglist(CC->msglist, CC->num_msgs); - CC->quickroom.QRhighest = MessageFromList(CC->num_msgs - 1); - - put_msglist(&CC->quickroom); - lputroom(&CC->quickroom); - if (ok == 1) { - AdjRefCount(delnum, -1); - cprintf("%d Message deleted.\n", OK); - } else - cprintf("%d No message %ld.\n", ERROR, delnum); + if (num_deleted) { + cprintf("%d %d message%s deleted.\n", OK, + num_deleted, ((num_deleted != 1) ? "s" : "")); + } else { + cprintf("%d Message %ld not found.\n", ERROR, delnum); + } } /* - * move a message to another room + * move or copy a message to another room */ void cmd_move(char *args) { long num; - char targ[32]; - int a; + char targ[256]; struct quickroom qtemp; - int foundit; + int err; + int is_copy = 0; num = extract_long(args, 0); extract(targ, args, 1); + targ[ROOMNAMELEN - 1] = 0; + is_copy = extract_int(args, 2); getuser(&CC->usersupp, CC->curr_user); if ((CC->usersupp.axlevel < 6) @@ -1406,37 +2134,26 @@ void cmd_move(char *args) ERROR + HIGHER_ACCESS_REQUIRED); return; } + if (getroom(&qtemp, targ) != 0) { cprintf("%d '%s' does not exist.\n", ERROR, targ); return; } - /* yank the message out of the current room... */ - lgetroom(&CC->quickroom, CC->quickroom.QRname); - get_msglist(&CC->quickroom); - foundit = 0; - for (a = 0; a < (CC->num_msgs); ++a) { - if (MessageFromList(a) == num) { - foundit = 1; - SetMessageInList(a, 0L); - } - } - if (foundit) { - CC->num_msgs = sort_msglist(CC->msglist, CC->num_msgs); - put_msglist(&CC->quickroom); - CC->quickroom.QRhighest = MessageFromList((CC->num_msgs) - 1); - } - lputroom(&CC->quickroom); - if (!foundit) { - cprintf("%d msg %ld does not exist.\n", ERROR, num); + err = CtdlSaveMsgPointerInRoom(targ, num, + (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) ); + if (err != 0) { + cprintf("%d Cannot store message in %s: error %d\n", + err, targ, err); return; } - /* put the message into the target room */ - lgetroom(&qtemp, targ); - qtemp.QRhighest = AddMessageToRoom(&qtemp, num); - lputroom(&qtemp); - cprintf("%d Message moved.\n", OK); + /* 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, NULL); + + cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") ); } @@ -1479,14 +2196,15 @@ void PutSuppMsgInfo(struct SuppMsgInfo *smibuf) /* Use the negative of the message number for its supp record index */ TheIndex = (0L - smibuf->smi_msgnum); + lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n", + smibuf->smi_msgnum, smibuf->smi_refcount); + cdb_store(CDB_MSGMAIN, &TheIndex, sizeof(long), smibuf, sizeof(struct SuppMsgInfo)); } - - /* * AdjRefCount - change the reference count for a message; * delete the message if it reaches zero @@ -1497,9 +2215,6 @@ void AdjRefCount(long msgnum, int incr) struct SuppMsgInfo smi; long delnum; - lprintf(9, "Adjust msg <%ld> ref count by <%d>\n", - msgnum, incr); - /* This is a *tight* critical section; please keep it that way, as * it may get called while nested in other critical sections. * Complicating this any further will surely cause deadlock! @@ -1524,3 +2239,103 @@ void AdjRefCount(long msgnum, int incr) cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long)); } } + +/* + * Write a generic object to this room + * + * Note: this could be much more efficient. Right now we use two temporary + * files, and still pull the message into memory as with all others. + */ +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? */ + 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[256]; + int ch; + struct quickroom qrbuf; + char roomname[ROOMNAMELEN]; + struct CtdlMessage *msg; + size_t len; + + if (is_mailbox != NULL) + MailboxName(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); + return; + } + + fprintf(fp, "Content-type: %s\n", content_type); + lprintf(9, "Content-type: %s\n", content_type); + + 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); + } + + lprintf(9, "Allocating\n"); + 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 = 4; + msg->cm_fields['A'] = strdoop(CC->usersupp.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); + + /* Create the requested room if we have to. */ + if (getroom(&qrbuf, roomname) != 0) { + create_room(roomname, 4, "", 0); + } + /* If the caller specified this object as unique, delete all + * other objects of this type that are currently in the room. + */ + if (is_unique) { + lprintf(9, "Deleted %d other msgs of this type\n", + CtdlDeleteMessages(roomname, 0L, content_type)); + } + /* Now write the data */ + CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1); + CtdlFreeMessage(msg); +}