#include "citserver.h"
#include "support.h"
#include "config.h"
+#include "control.h"
#include "dynloader.h"
#include "room_ops.h"
#include "user_ops.h"
char user[256];
char node[256];
char recp[256];
- int is_spam = 0; /* FIX implement anti-spamming */
+ int is_spam = 0; /* FIXME implement anti-spamming */
if (strlen(SMTP->from) == 0) {
cprintf("503 MAIL first, then RCPT. Duh.\r\n");
cvt = convert_internet_address(user, node, recp);
sprintf(recp, "%s@%s", user, node);
-
+ lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
switch(cvt) {
case rfc822_address_locally_validated:
cprintf("550 %s: no such user\r\n", recp);
return;
- case rfc822_address_invalid:
+ case rfc822_address_on_citadel_network:
+ cprintf("250 %s is on the local network\r\n", recp);
+ ++SMTP->number_of_recipients;
+ CtdlReallocUserData(SYM_SMTP_RECP,
+ strlen(SMTP_RECP) + 1024 );
+ strcat(SMTP_RECP, "ignet|");
+ strcat(SMTP_RECP, user);
+ strcat(SMTP_RECP, "|");
+ strcat(SMTP_RECP, node);
+ strcat(SMTP_RECP, "|0|\n");
+ return;
+
+ case rfc822_address_nonlocal:
if (is_spam) {
cprintf("551 Away with thee, spammer!\r\n");
}
+/*
+ * Send a message out through the local network
+ * (This is kind of ugly. IGnet should be done using clean server-to-server
+ * code instead of the old style spool.)
+ */
+void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
+ struct ser_ret smr;
+ char *hold_R, *hold_D;
+ FILE *fp;
+ char filename[256];
+ static int seq = 0;
+
+ lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
+
+ hold_R = msg->cm_fields['R'];
+ hold_D = msg->cm_fields['D'];
+ msg->cm_fields['R'] = user;
+ msg->cm_fields['D'] = dest;
+
+ serialize_message(&smr, msg);
+
+ msg->cm_fields['R'] = hold_R;
+ msg->cm_fields['D'] = hold_D;
+
+ if (smr.len != 0) {
+ sprintf(filename, "./network/spoolin/%s.%04x.%04x",
+ dest, getpid(), ++seq);
+ lprintf(9, "spool file name is <%s>\n", filename);
+ fp = fopen(filename, "wb");
+ if (fp != NULL) {
+ fwrite(smr.ser, smr.len, 1, fp);
+ fclose(fp);
+ }
+ phree(smr.ser);
+ }
+
+}
+
/*
++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');
++successful_saves;
}
+ /* Delivery over the local Citadel network (IGnet) */
+ if (!strcasecmp(dtype, "ignet")) {
+ extract(user, buf, 1);
+ extract(node, buf, 2);
+ smtp_deliver_ignet(msg, user, node);
+ }
+
/* Remote delivery */
if (!strcasecmp(dtype, "remote")) {
extract(user, buf, 1);
/* 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);
if (buf[0] != '2') {
if (buf[0] == '4') {
*status = 4;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
else {
*status = 5;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
}
if (buf[0] != '2') {
if (buf[0] == '4') {
*status = 4;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
else {
*status = 5;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
}
if (buf[0] != '2') {
if (buf[0] == '4') {
*status = 4;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
else {
*status = 5;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
}
if (buf[0] != '2') {
if (buf[0] == '4') {
*status = 4;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
else {
*status = 5;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
}
if (buf[0] != '3') {
if (buf[0] == '4') {
*status = 3;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
else {
*status = 5;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
}
if (buf[0] != '2') {
if (buf[0] == '4') {
*status = 4;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
else {
*status = 5;
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
goto bail;
}
}
/* We did it! */
- strcpy(dsn, &buf[4]);
+ safestrncpy(dsn, &buf[4], 1023);
*status = 2;
lprintf(9, ">QUIT\n");
/*
- * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to remove
- * all of the completed deliveries from a set of delivery instructions.
+ * 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);
+ time_t submitted = 0L;
+ struct CtdlMessage *bmsg = NULL;
+ int give_up = 0;
+ int mes_type = 0;
+
+ lprintf(9, "smtp_do_bounce() called\n");
+ strcpy(bounceto, "");
+
+ lines = num_tokens(instr, '\n');
+
+
+ /* See if it's time to give up on delivery of this message */
+ for (i=0; i<lines; ++i) {
+ extract_token(buf, instr, i, '\n');
+ extract(key, buf, 0);
+ extract(addr, buf, 1);
+ if (!strcasecmp(key, "submitted")) {
+ submitted = atol(addr);
+ }
+ }
+
+ if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
+ give_up = 1;
+ }
+
+
+
+ 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['O'] = strdoop(MAILROOM);
+ bmsg->cm_fields['N'] = strdoop(config.c_nodename);
+
+ if (give_up) bmsg->cm_fields['M'] = strdoop(
+"A message you sent could not be delivered to some or all of its recipients.\n"
+"The following addresses were undeliverable:\n\n"
+);
+
+ else bmsg->cm_fields['M'] = strdoop(
+"A message you sent could not be delivered to some or all of its recipients\n"
+"due to prolonged unavailability of its destination(s).\n"
+"Giving up on the following addresses:\n\n"
+);
+
+ /*
+ * Now go through the instructions checking for stuff.
+ */
+
+ 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 (give_up) bounce_this = 1;
+ }
+
+ if (bounce_this) {
+ ++num_bounces;
+
+ if (bmsg->cm_fields['M'] == NULL) {
+ lprintf(2, "ERROR ... M field is null "
+ "(%s:%d)\n", __FILE__, __LINE__);
+ }
+
+ 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 */
+ lprintf(9, "bounce to user? <%s>\n", bounceto);
+ if (strlen(bounceto) == 0) {
+ lprintf(7, "No bounce address specified\n");
+ bounce_msgid = (-1L);
+ }
+ else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
+ lprintf(7, "Invalid bounce address <%s>\n", bounceto);
+ bounce_msgid = (-1L);
+ }
+ else {
+ bounce_msgid = CtdlSaveMsg(bmsg,
+ bounceto,
+ "", mes_type, 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.
*/
char dsn[1024];
long text_msgid = (-1);
int incomplete_deliveries_remaining;
+ time_t attempted = 0L;
+ time_t last_attempted = 0L;
msg = CtdlFetchMessage(msgnum);
if (msg == NULL) {
}
}
- /* Learn the message ID */
+ /* Learn the message ID and find out about recent delivery attempts */
lines = num_tokens(instr, '\n');
for (i=0; i<lines; ++i) {
extract_token(buf, instr, i, '\n');
if (!strcasecmp(key, "msgid")) {
text_msgid = extract_long(buf, 1);
}
+ if (!strcasecmp(key, "attempted")) {
+ attempted = extract_long(buf, 1);
+ if (attempted > last_attempted)
+ last_attempted = attempted;
+ }
}
+
+ /*
+ * Postpone delivery if we've already tried recently.
+ */
+ if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
+ lprintf(7, "Retry time not yet reached.\n");
+ phree(instr);
+ return;
+ }
+
+
+ /*
+ * Bail out if there's no actual message associated with this
+ */
if (text_msgid < 0L) {
lprintf(3, "SMTP: no 'msgid' directive found!\n");
phree(instr);
}
+ /* Generate 'bounce' messages */
+ smtp_do_bounce(instr);
- /*
- * Go through the delivery list, deleting completed deliveries
- */
+ /* Go through the delivery list, deleting completed deliveries */
incomplete_deliveries_remaining =
smtp_purge_completed_deliveries(instr);
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);
+ "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);
* Run through the queue sending out messages.
*/
void smtp_do_queue(void) {
+ static int doing_queue = 0;
+
+ /*
+ * This is a simple concurrency check to make sure only one queue run
+ * is done at a time. We could do this with a mutex, but since we
+ * don't really require extremely fine granularity here, we'll do it
+ * with a static variable instead.
+ */
+ if (doing_queue) return;
+ doing_queue = 1;
+
+ /*
+ * Go ahead and run the queue
+ */
lprintf(5, "SMTP: processing outbound queue\n");
if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
lprintf(5, "SMTP: queue run completed\n");
+ doing_queue = 0;
}
CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
return "$Id$";
}
-