#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
+#include <ctype.h>
#include <string.h>
#include <limits.h>
+#include <time.h>
#include "citadel.h"
#include "server.h"
-#include <time.h>
#include "sysdep_decls.h"
#include "citserver.h"
#include "support.h"
struct citsmtp { /* Information about the current session */
int command_state;
+ char helo_node[256];
struct usersupp vrfy_buffer;
int vrfy_count;
char vrfy_match[256];
/*****************************************************************************/
+
+
/*
* Here's where our SMTP session begins its happy day.
*/
*/
void smtp_hello(char *argbuf, int is_esmtp) {
+ safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
+
if (!is_esmtp) {
cprintf("250 Greetings and joyous salutations.\r\n");
}
else {
cvt = convert_internet_address(user, node, SMTP->from);
lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
- if (!strcasecmp(node, config.c_nodename)) { /* FIX use fcn */
+ if (CtdlHostAlias(node) == hostalias_localhost) {
cprintf("550 You must log in to send mail from %s\r\n",
- config.c_fqdn);
+ node);
strcpy(SMTP->from, "");
return;
}
++successful_saves;
instr = mallok(1024);
- sprintf(instr, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n",
- SPOOLMIME, msgid, time(NULL) );
+ sprintf(instr, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
+ "bounceto|%s\n",
+ SPOOLMIME, msgid, time(NULL),
+ SMTP->from );
for (i=0; i<SMTP->number_of_recipients; ++i) {
extract_token(buf, SMTP_RECP, i, '\n');
generate_rfc822_datestamp(nowstamp, time(NULL));
body = mallok(4096);
+
if (body != NULL) sprintf(body,
"Received: from %s\n"
" by %s;\n"
" %s\n",
- "FIX.FIX.com",
+ SMTP->helo_node,
config.c_fqdn,
nowstamp);
*/
void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
{
- char buf[256];
int sock = (-1);
char mxhosts[1024];
int num_mxhosts;
int mx;
+ int i;
char user[256], node[256], name[256];
+ char buf[1024];
+ char mailfrom[1024];
+ int lp, rp;
+ FILE *msg_fp = NULL;
+ size_t msg_size;
+ size_t blocksize = 0;
+ int scan_done;
/* Parse out the host portion of the recipient address */
process_rfc822_addr(addr, user, node, name);
lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
user, node, name);
+ /* Load the message out of the database into a temp file */
+ msg_fp = tmpfile();
+ if (msg_fp == NULL) {
+ *status = 4;
+ sprintf(dsn, "Error creating temporary file");
+ return;
+ }
+ else {
+ CtdlRedirectOutput(msg_fp, -1);
+ CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
+ CtdlRedirectOutput(NULL, -1);
+ fseek(msg_fp, 0L, SEEK_END);
+ msg_size = ftell(msg_fp);
+ }
+
+
+ /* Extract something to send later in the 'MAIL From:' command */
+ strcpy(mailfrom, "");
+ rewind(msg_fp);
+ scan_done = 0;
+ do {
+ if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
+ if (!strncasecmp(buf, "From:", 5)) {
+ safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
+ striplt(mailfrom);
+ for (i=0; i<strlen(mailfrom); ++i) {
+ if (!isprint(mailfrom[i])) {
+ strcpy(&mailfrom[i], &mailfrom[i+1]);
+ i=0;
+ }
+ }
+
+ /* Strip out parenthesized names */
+ lp = (-1);
+ rp = (-1);
+ for (i=0; i<strlen(mailfrom); ++i) {
+ if (mailfrom[i] == '(') lp = i;
+ if (mailfrom[i] == ')') rp = i;
+ }
+ if ((lp>0)&&(rp>lp)) {
+ strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
+ }
+
+ /* Prefer brokketized names */
+ lp = (-1);
+ rp = (-1);
+ for (i=0; i<strlen(mailfrom); ++i) {
+ if (mailfrom[i] == '<') lp = i;
+ if (mailfrom[i] == '>') rp = i;
+ }
+ if ((lp>=0)&&(rp>lp)) {
+ mailfrom[rp] = 0;
+ strcpy(mailfrom, &mailfrom[lp]);
+ }
+
+ scan_done = 1;
+ }
+ } while (scan_done == 0);
+ if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
+
+
+ /* Figure out what mail exchanger host we have to connect to */
num_mxhosts = getmx(mxhosts, node);
lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
if (num_mxhosts < 1) {
}
if (sock < 0) {
- *status = 3; /* dsn is already filled in */
+ *status = 4; /* dsn is already filled in */
return;
}
/* Process the SMTP greeting from the server */
if (sock_gets(sock, buf) < 0) {
- *status = 3;
+ *status = 4;
strcpy(dsn, "Connection broken during SMTP conversation");
- sock_close(sock);
- return;
+ goto bail;
}
- lprintf(9, "%s\n", buf);
+ lprintf(9, "<%s\n", buf);
if (buf[0] != '2') {
if (buf[0] == '4') {
- *status = 3;
+ *status = 4;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
else {
*status = 5;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
}
/* Do a HELO command */
sprintf(buf, "HELO %s", config.c_fqdn);
+ lprintf(9, ">%s\n", buf);
sock_puts(sock, buf);
if (sock_gets(sock, buf) < 0) {
- *status = 3;
+ *status = 4;
strcpy(dsn, "Connection broken during SMTP conversation");
- sock_close(sock);
- return;
+ goto bail;
}
- lprintf(9, "%s\n", buf);
+ lprintf(9, "<%s\n", buf);
if (buf[0] != '2') {
if (buf[0] == '4') {
- *status = 3;
+ *status = 4;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
else {
*status = 5;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
}
/* HELO succeeded, now try the MAIL From: command */
- sprintf(buf, "MAIL From: ajc@uncnsrd.mt-kisco.ny.us"); /* FIX */
+ sprintf(buf, "MAIL From: %s", mailfrom);
+ lprintf(9, ">%s\n", buf);
sock_puts(sock, buf);
if (sock_gets(sock, buf) < 0) {
- *status = 3;
+ *status = 4;
strcpy(dsn, "Connection broken during SMTP conversation");
- sock_close(sock);
- return;
+ goto bail;
}
- lprintf(9, "%s\n", buf);
+ lprintf(9, "<%s\n", buf);
if (buf[0] != '2') {
if (buf[0] == '4') {
- *status = 3;
+ *status = 4;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
else {
*status = 5;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
}
/* MAIL succeeded, now try the RCPT To: command */
sprintf(buf, "RCPT To: %s", addr);
+ lprintf(9, ">%s\n", buf);
sock_puts(sock, buf);
if (sock_gets(sock, buf) < 0) {
- *status = 3;
+ *status = 4;
strcpy(dsn, "Connection broken during SMTP conversation");
- sock_close(sock);
- return;
+ goto bail;
}
- lprintf(9, "%s\n", buf);
+ lprintf(9, "<%s\n", buf);
if (buf[0] != '2') {
if (buf[0] == '4') {
- *status = 3;
+ *status = 4;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
else {
*status = 5;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
}
/* RCPT succeeded, now try the DATA command */
+ lprintf(9, ">DATA\n");
sock_puts(sock, "DATA");
if (sock_gets(sock, buf) < 0) {
- *status = 3;
+ *status = 4;
strcpy(dsn, "Connection broken during SMTP conversation");
- sock_close(sock);
- return;
+ goto bail;
}
- lprintf(9, "%s\n", buf);
+ lprintf(9, "<%s\n", buf);
if (buf[0] != '3') {
if (buf[0] == '4') {
*status = 3;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
else {
*status = 5;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
}
/* If we reach this point, the server is expecting data */
- CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, NULL, sock, 1);
- sock_puts(sock, ".");
+ rewind(msg_fp);
+ while (msg_size > 0) {
+ blocksize = sizeof(buf);
+ if (blocksize > msg_size) blocksize = msg_size;
+ fread(buf, blocksize, 1, msg_fp);
+ sock_write(sock, buf, blocksize);
+ msg_size -= blocksize;
+ }
+ if (buf[blocksize-1] != 10) {
+ lprintf(5, "Possible problem: message did not correctly "
+ "terminate. (expecting 0x10, got 0x%02x)\n",
+ buf[blocksize-1]);
+ }
+
+ sock_write(sock, ".\r\n", 3);
if (sock_gets(sock, buf) < 0) {
- *status = 3;
+ *status = 4;
strcpy(dsn, "Connection broken during SMTP conversation");
- sock_close(sock);
- return;
+ goto bail;
}
lprintf(9, "%s\n", buf);
if (buf[0] != '2') {
if (buf[0] == '4') {
- *status = 3;
+ *status = 4;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
else {
*status = 5;
strcpy(dsn, &buf[4]);
- sock_close(sock);
- return;
+ goto bail;
}
}
strcpy(dsn, &buf[4]);
*status = 2;
+ lprintf(9, ">QUIT\n");
sock_puts(sock, "QUIT");
sock_gets(sock, buf);
- lprintf(9, "%s\n", buf);
+ lprintf(9, "<%s\n", buf);
+
+bail: if (msg_fp != NULL) fclose(msg_fp);
sock_close(sock);
+ return;
+}
+
+
+
+/*
+ * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
+ * instructions for "5" codes (permanent fatal errors) and produce/deliver
+ * a "bounce" message (delivery status notification).
+ */
+void smtp_do_bounce(char *instr) {
+ int i;
+ int lines;
+ int status;
+ char buf[1024];
+ char key[1024];
+ char addr[1024];
+ char dsn[1024];
+ char bounceto[1024];
+ int num_bounces = 0;
+ int bounce_this = 0;
+ long bounce_msgid = (-1);
+ struct CtdlMessage *bmsg = NULL;
+
+ lprintf(9, "smtp_do_bounce() called\n");
+ strcpy(bounceto, "");
+
+ bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
+ if (bmsg == NULL) return;
+ memset(bmsg, 0, sizeof(struct CtdlMessage));
+
+ bmsg->cm_magic = CTDLMESSAGE_MAGIC;
+ bmsg->cm_anon_type = MES_NORMAL;
+ bmsg->cm_format_type = 1;
+ bmsg->cm_fields['A'] = strdoop("Citadel");
+ bmsg->cm_fields['N'] = strdoop(config.c_nodename);
+ bmsg->cm_fields['M'] = strdoop(
+ "BOUNCE! BOUNCE!! BOUNCE!!!\n\n"
+ "FIX ... this message should be made to look nice and stuff.\n"
+ "In the meantime, you should be aware that the following\n"
+ "recipient addresses had permanent fatal errors:\n\n");
+
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n');
+ extract(key, buf, 0);
+ extract(addr, buf, 1);
+ status = extract_int(buf, 2);
+ extract(dsn, buf, 3);
+ bounce_this = 0;
+
+ lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
+ key, addr, status, dsn);
+
+ if (!strcasecmp(key, "bounceto")) {
+ strcpy(bounceto, addr);
+ }
+
+ if (
+ (!strcasecmp(key, "local"))
+ || (!strcasecmp(key, "remote"))
+ || (!strcasecmp(key, "ignet"))
+ || (!strcasecmp(key, "room"))
+ ) {
+ if (status == 5) bounce_this = 1;
+ }
+
+ if (bounce_this) {
+ ++num_bounces;
+
+ /* FIX put this back in!
+ bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
+ strlen(bmsg->cm_fields['M'] + 1024) );
+ strcat(bmsg->cm_fields['M'], addr);
+ strcat(bmsg->cm_fields['M'], ": ");
+ strcat(bmsg->cm_fields['M'], dsn);
+ strcat(bmsg->cm_fields['M'], "\n");
+ */
+
+ remove_token(instr, i, '\n');
+ --i;
+ --lines;
+ }
+ }
+
+ /* Deliver the bounce if there's anything worth mentioning */
+ lprintf(9, "num_bounces = %d\n", num_bounces);
+ if (num_bounces > 0) {
+
+ /* First try the user who sent the message FIX
+ lprintf(9, "bounce to user? <%s>\n", bounceto);
+ if (strlen(bounceto) == 0) bounce_msgid = (-1L);
+ else bounce_msgid = CtdlSaveMsg(bmsg,
+ bounceto,
+ "", MES_LOCAL, 1);
+ */
+
+ /* Otherwise, go to the Aide> room */
+ lprintf(9, "bounce to room?\n");
+ if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
+ "", AIDEROOM,
+ MES_LOCAL, 1);
+ }
+
+ CtdlFreeMessage(bmsg);
+ lprintf(9, "Done processing bounces\n");
}
+/*
+ * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
+ * set of delivery instructions for completed deliveries and remove them.
+ *
+ * It returns the number of incomplete deliveries remaining.
+ */
+int smtp_purge_completed_deliveries(char *instr) {
+ int i;
+ int lines;
+ int status;
+ char buf[1024];
+ char key[1024];
+ char addr[1024];
+ char dsn[1024];
+ int completed;
+ int incomplete = 0;
+
+ lines = num_tokens(instr, '\n');
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n');
+ extract(key, buf, 0);
+ extract(addr, buf, 1);
+ status = extract_int(buf, 2);
+ extract(dsn, buf, 3);
+
+ completed = 0;
+
+ if (
+ (!strcasecmp(key, "local"))
+ || (!strcasecmp(key, "remote"))
+ || (!strcasecmp(key, "ignet"))
+ || (!strcasecmp(key, "room"))
+ ) {
+ if (status == 2) completed = 1;
+ else ++incomplete;
+ }
+
+ if (completed) {
+ remove_token(instr, i, '\n');
+ --i;
+ --lines;
+ }
+ }
+
+ return(incomplete);
+}
/*
char addr[1024];
char dsn[1024];
long text_msgid = (-1);
+ int incomplete_deliveries_remaining;
msg = CtdlFetchMessage(msgnum);
if (msg == NULL) {
phree(results);
}
- /* Delete the instructions and replace with the updated ones */
- CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, 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 = FMT_RFC822;
- msg->cm_fields['M'] = malloc(strlen(instr)+256);
- sprintf(msg->cm_fields['M'],
- "Content-type: %s\n\n%s\n", SPOOLMIME, instr);
- phree(instr);
- CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
- CtdlFreeMessage(msg);
+
+ /* Generate 'bounce' messages */
+ smtp_do_bounce(instr);
+
+ /* Go through the delivery list, deleting completed deliveries */
+ incomplete_deliveries_remaining =
+ smtp_purge_completed_deliveries(instr);
+
+
+ /*
+ * No delivery instructions remain, so delete both the instructions
+ * message and the message message.
+ */
+ if (incomplete_deliveries_remaining <= 0) {
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);
+ }
+
+
+ /*
+ * Uncompleted delivery instructions remain, so delete the old
+ * instructions and replace with the updated ones.
+ */
+ if (incomplete_deliveries_remaining > 0) {
+ CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, 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 = FMT_RFC822;
+ msg->cm_fields['M'] = malloc(strlen(instr)+256);
+ sprintf(msg->cm_fields['M'],
+ "Content-type: %s\n\n%s\nattempted|%ld\n",
+ SPOOLMIME, instr, time(NULL) );
+ phree(instr);
+ CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
+ CtdlFreeMessage(msg);
+ }
+
}
}
-/**** FIX temporary hack to run the queue *****/
-void cmd_qqqq(char *argbuf) {
- smtp_do_queue();
- cprintf("%d ok\n", OK);
-}
-
-
/*****************************************************************************/
/* MODULE INITIALIZATION STUFF */
CtdlRegisterServiceHook(SMTP_PORT,
smtp_greeting,
smtp_command_loop);
-
- /**** FIX ... temporary hack to run the queue ******/
- CtdlRegisterProtoHook(cmd_qqqq, "QQQQ", "run the queue");
-
create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
+ CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
return "$Id$";
}