4 * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
17 #include <sys/types.h>
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
24 # include <sys/time.h>
36 #include "sysdep_decls.h"
37 #include "citserver.h"
41 #include "dynloader.h"
48 #include "internet_addressing.h"
51 #include "clientsocket.h"
54 struct citsmtp { /* Information about the current session */
57 struct usersupp vrfy_buffer;
61 int number_of_recipients;
63 int message_originated_locally;
66 enum { /* Command states for login authentication */
72 enum { /* Delivery modes */
77 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
78 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
85 /*****************************************************************************/
86 /* SMTP SERVER (INBOUND) STUFF */
87 /*****************************************************************************/
93 * Here's where our SMTP session begins its happy day.
95 void smtp_greeting(void) {
97 strcpy(CC->cs_clientname, "SMTP session");
99 CC->cs_flags |= CS_STEALTH;
100 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
101 CtdlAllocUserData(SYM_SMTP_RECP, SIZ);
102 sprintf(SMTP_RECP, "%s", "");
104 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
109 * Implement HELO and EHLO commands.
111 void smtp_hello(char *argbuf, int is_esmtp) {
113 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
116 cprintf("250 Greetings and joyous salutations.\r\n");
119 cprintf("250-Extended greetings and joyous salutations.\r\n");
120 cprintf("250-HELP\r\n");
121 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
122 cprintf("250 AUTH=LOGIN\r\n");
128 * Implement HELP command.
130 void smtp_help(void) {
131 cprintf("214-Commands accepted:\r\n");
132 cprintf("214- DATA\r\n");
133 cprintf("214- EHLO\r\n");
134 cprintf("214- EXPN\r\n");
135 cprintf("214- HELO\r\n");
136 cprintf("214- HELP\r\n");
137 cprintf("214- MAIL\r\n");
138 cprintf("214- NOOP\r\n");
139 cprintf("214- QUIT\r\n");
140 cprintf("214- RCPT\r\n");
141 cprintf("214- RSET\r\n");
142 cprintf("214- VRFY\r\n");
150 void smtp_get_user(char *argbuf) {
154 decode_base64(username, argbuf);
155 lprintf(9, "Trying <%s>\n", username);
156 if (CtdlLoginExistingUser(username) == login_ok) {
157 encode_base64(buf, "Password:");
158 cprintf("334 %s\r\n", buf);
159 SMTP->command_state = smtp_password;
162 cprintf("500 No such user.\r\n");
163 SMTP->command_state = smtp_command;
171 void smtp_get_pass(char *argbuf) {
174 decode_base64(password, argbuf);
175 lprintf(9, "Trying <%s>\n", password);
176 if (CtdlTryPassword(password) == pass_ok) {
177 cprintf("235 Authentication successful.\r\n");
178 lprintf(9, "SMTP authenticated login successful\n");
179 CC->internal_pgm = 0;
180 CC->cs_flags &= ~CS_STEALTH;
183 cprintf("500 Authentication failed.\r\n");
185 SMTP->command_state = smtp_command;
192 void smtp_auth(char *argbuf) {
195 if (strncasecmp(argbuf, "login", 5) ) {
196 cprintf("550 We only support LOGIN authentication.\r\n");
200 if (strlen(argbuf) >= 7) {
201 smtp_get_user(&argbuf[6]);
205 encode_base64(buf, "Username:");
206 cprintf("334 %s\r\n", buf);
207 SMTP->command_state = smtp_user;
213 * Back end for smtp_vrfy() command
215 void smtp_vrfy_backend(struct usersupp *us, void *data) {
217 if (!fuzzy_match(us, SMTP->vrfy_match)) {
219 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
225 * Implements the VRFY (verify user name) command.
226 * Performs fuzzy match on full user names.
228 void smtp_vrfy(char *argbuf) {
229 SMTP->vrfy_count = 0;
230 strcpy(SMTP->vrfy_match, argbuf);
231 ForEachUser(smtp_vrfy_backend, NULL);
233 if (SMTP->vrfy_count < 1) {
234 cprintf("550 String does not match anything.\r\n");
236 else if (SMTP->vrfy_count == 1) {
237 cprintf("250 %s <cit%ld@%s>\r\n",
238 SMTP->vrfy_buffer.fullname,
239 SMTP->vrfy_buffer.usernum,
242 else if (SMTP->vrfy_count > 1) {
243 cprintf("553 Request ambiguous: %d users matched.\r\n",
252 * Back end for smtp_expn() command
254 void smtp_expn_backend(struct usersupp *us, void *data) {
256 if (!fuzzy_match(us, SMTP->vrfy_match)) {
258 if (SMTP->vrfy_count >= 1) {
259 cprintf("250-%s <cit%ld@%s>\r\n",
260 SMTP->vrfy_buffer.fullname,
261 SMTP->vrfy_buffer.usernum,
266 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
272 * Implements the EXPN (expand user name) command.
273 * Performs fuzzy match on full user names.
275 void smtp_expn(char *argbuf) {
276 SMTP->vrfy_count = 0;
277 strcpy(SMTP->vrfy_match, argbuf);
278 ForEachUser(smtp_expn_backend, NULL);
280 if (SMTP->vrfy_count < 1) {
281 cprintf("550 String does not match anything.\r\n");
283 else if (SMTP->vrfy_count >= 1) {
284 cprintf("250 %s <cit%ld@%s>\r\n",
285 SMTP->vrfy_buffer.fullname,
286 SMTP->vrfy_buffer.usernum,
293 * Implements the RSET (reset state) command.
294 * Currently this just zeroes out the state buffer. If pointers to data
295 * allocated with mallok() are ever placed in the state buffer, we have to
296 * be sure to phree() them first!
298 void smtp_rset(void) {
299 memset(SMTP, 0, sizeof(struct citsmtp));
300 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
301 if (CC->logged_in) logout(CC);
302 cprintf("250 Zap!\r\n");
306 * Clear out the portions of the state buffer that need to be cleared out
307 * after the DATA command finishes.
309 void smtp_data_clear(void) {
310 strcpy(SMTP->from, "");
311 SMTP->number_of_recipients = 0;
312 SMTP->delivery_mode = 0;
313 SMTP->message_originated_locally = 0;
314 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
320 * Implements the "MAIL From:" command
322 void smtp_mail(char *argbuf) {
327 if (strlen(SMTP->from) != 0) {
328 cprintf("503 Only one sender permitted\r\n");
332 if (strncasecmp(argbuf, "From:", 5)) {
333 cprintf("501 Syntax error\r\n");
337 strcpy(SMTP->from, &argbuf[5]);
340 if (strlen(SMTP->from) == 0) {
341 cprintf("501 Empty sender name is not permitted\r\n");
346 /* If this SMTP connection is from a logged-in user, make sure that
347 * the user only sends email from his/her own address.
350 cvt = convert_internet_address(user, node, SMTP->from);
351 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
352 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
353 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
354 strcpy(SMTP->from, "");
358 SMTP->message_originated_locally = 1;
362 /* Otherwise, make sure outsiders aren't trying to forge mail from
367 cvt = convert_internet_address(user, node, SMTP->from);
369 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
370 if (CtdlHostAlias(node) == hostalias_localhost) {
372 cprintf("550 You must log in to send mail from %s\r\n",
374 strcpy(SMTP->from, "");
379 cprintf("250 Sender ok\r\n");
385 * Implements the "RCPT To:" command
387 void smtp_rcpt(char *argbuf) {
393 if (strlen(SMTP->from) == 0) {
394 cprintf("503 Need MAIL before RCPT\r\n");
398 if (strncasecmp(argbuf, "To:", 3)) {
399 cprintf("501 Syntax error\r\n");
403 strcpy(recp, &argbuf[3]);
409 cvt = convert_internet_address(user, node, recp);
410 snprintf(recp, sizeof recp, "%s@%s", user, node);
411 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
414 case rfc822_address_locally_validated:
415 cprintf("250 %s is a valid recipient.\r\n", user);
416 ++SMTP->number_of_recipients;
417 CtdlReallocUserData(SYM_SMTP_RECP,
418 strlen(SMTP_RECP) + 1024 );
419 strcat(SMTP_RECP, "local|");
420 strcat(SMTP_RECP, user);
421 strcat(SMTP_RECP, "|0\n");
424 case rfc822_room_delivery:
425 cprintf("250 Delivering to room '%s'\r\n", user);
426 ++SMTP->number_of_recipients;
427 CtdlReallocUserData(SYM_SMTP_RECP,
428 strlen(SMTP_RECP) + 1024 );
429 strcat(SMTP_RECP, "room|");
430 strcat(SMTP_RECP, user);
431 strcat(SMTP_RECP, "|0|\n");
434 case rfc822_no_such_user:
435 cprintf("550 %s: no such user\r\n", recp);
438 case rfc822_address_on_citadel_network:
439 cprintf("250 %s is on the local network\r\n", recp);
440 ++SMTP->number_of_recipients;
441 CtdlReallocUserData(SYM_SMTP_RECP,
442 strlen(SMTP_RECP) + 1024 );
443 strcat(SMTP_RECP, "ignet|");
444 strcat(SMTP_RECP, user);
445 strcat(SMTP_RECP, "|");
446 strcat(SMTP_RECP, node);
447 strcat(SMTP_RECP, "|0|\n");
450 case rfc822_address_nonlocal:
451 if (SMTP->message_originated_locally == 0) {
452 cprintf("551 Third-party relaying denied.\r\n");
455 cprintf("250 Remote recipient %s ok\r\n", recp);
456 ++SMTP->number_of_recipients;
457 CtdlReallocUserData(SYM_SMTP_RECP,
458 strlen(SMTP_RECP) + 1024 );
459 strcat(SMTP_RECP, "remote|");
460 strcat(SMTP_RECP, recp);
461 strcat(SMTP_RECP, "|0|\n");
467 cprintf("599 Unknown error\r\n");
473 * Send a message out through the local network
474 * (This is kind of ugly. IGnet should be done using clean server-to-server
475 * code instead of the old style spool.)
477 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
479 char *hold_R, *hold_D, *hold_O;
484 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
486 hold_R = msg->cm_fields['R'];
487 hold_D = msg->cm_fields['D'];
488 hold_O = msg->cm_fields['O'];
489 msg->cm_fields['R'] = user;
490 msg->cm_fields['D'] = dest;
491 msg->cm_fields['O'] = MAILROOM;
493 serialize_message(&smr, msg);
495 msg->cm_fields['R'] = hold_R;
496 msg->cm_fields['D'] = hold_D;
497 msg->cm_fields['O'] = hold_O;
500 snprintf(filename, sizeof filename,
501 "./network/spoolin/%s.%04x.%04x",
502 dest, getpid(), ++seq);
503 lprintf(9, "spool file name is <%s>\n", filename);
504 fp = fopen(filename, "wb");
506 fwrite(smr.ser, smr.len, 1, fp);
517 * Back end for smtp_data() ... this does the actual delivery of the message
518 * Returns 0 on success, nonzero on failure
520 int smtp_message_delivery(struct CtdlMessage *msg) {
527 int successful_saves = 0; /* number of successful local saves */
528 int failed_saves = 0; /* number of failed deliveries */
529 int remote_spools = 0; /* number of copies to send out */
532 struct usersupp userbuf;
533 char *instr; /* Remote delivery instructions */
534 struct CtdlMessage *imsg;
536 lprintf(9, "smtp_message_delivery() called\n");
538 /* Fill in 'from' fields with envelope information if missing */
539 process_rfc822_addr(SMTP->from, user, node, name);
540 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
541 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
542 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
543 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
545 /* Save the message in the queue */
546 msgid = CtdlSaveMsg(msg,
552 instr = mallok(1024);
553 snprintf(instr, 1024,
554 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
556 SPOOLMIME, msgid, time(NULL),
559 for (i=0; i<SMTP->number_of_recipients; ++i) {
560 extract_token(buf, SMTP_RECP, i, '\n');
561 extract(dtype, buf, 0);
563 /* Stuff local mailboxes */
564 if (!strcasecmp(dtype, "local")) {
565 extract(user, buf, 1);
566 if (getuser(&userbuf, user) == 0) {
567 MailboxName(room, &userbuf, MAILROOM);
568 CtdlSaveMsgPointerInRoom(room, msgid, 0);
576 /* Delivery to local non-mailbox rooms */
577 if (!strcasecmp(dtype, "room")) {
578 extract(room, buf, 1);
579 CtdlSaveMsgPointerInRoom(room, msgid, 0);
583 /* Delivery over the local Citadel network (IGnet) */
584 if (!strcasecmp(dtype, "ignet")) {
585 extract(user, buf, 1);
586 extract(node, buf, 2);
587 smtp_deliver_ignet(msg, user, node);
590 /* Remote delivery */
591 if (!strcasecmp(dtype, "remote")) {
592 extract(user, buf, 1);
593 instr = reallok(instr, strlen(instr) + 1024);
594 snprintf(&instr[strlen(instr)],
595 strlen(instr) + 1024,
603 /* If there are remote spools to be done, save the instructions */
604 if (remote_spools > 0) {
605 imsg = mallok(sizeof(struct CtdlMessage));
606 memset(imsg, 0, sizeof(struct CtdlMessage));
607 imsg->cm_magic = CTDLMESSAGE_MAGIC;
608 imsg->cm_anon_type = MES_NORMAL;
609 imsg->cm_format_type = FMT_RFC822;
610 imsg->cm_fields['M'] = instr;
611 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
612 CtdlFreeMessage(imsg);
615 /* If there are no remote spools, delete the message */
617 phree(instr); /* only needed here, because CtdlSaveMsg()
618 * would free this buffer otherwise */
619 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
622 return(failed_saves);
628 * Implements the DATA command
630 void smtp_data(void) {
632 struct CtdlMessage *msg;
636 if (strlen(SMTP->from) == 0) {
637 cprintf("503 Need MAIL command first.\r\n");
641 if (SMTP->number_of_recipients < 1) {
642 cprintf("503 Need RCPT command first.\r\n");
646 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
648 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
651 if (body != NULL) snprintf(body, 4096,
652 "Received: from %s\n"
659 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
661 cprintf("550 Unable to save message text: internal error.\r\n");
665 lprintf(9, "Converting message...\n");
666 msg = convert_internet_message(body);
668 /* If the user is locally authenticated, FORCE the From: header to
669 * show up as the real sender
672 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
673 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
674 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
675 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
676 msg->cm_fields['N'] = strdoop(config.c_nodename);
677 msg->cm_fields['H'] = strdoop(config.c_humannode);
680 retval = smtp_message_delivery(msg);
681 CtdlFreeMessage(msg);
684 cprintf("250 ok terrific\r\n");
687 cprintf("550 Internal delivery errors: %d\r\n", retval);
690 smtp_data_clear(); /* clear out the buffers now */
697 * Main command loop for SMTP sessions.
699 void smtp_command_loop(void) {
703 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
704 if (client_gets(cmdbuf) < 1) {
705 lprintf(3, "SMTP socket is broken. Ending session.\n");
709 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
710 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
712 if (SMTP->command_state == smtp_user) {
713 smtp_get_user(cmdbuf);
716 else if (SMTP->command_state == smtp_password) {
717 smtp_get_pass(cmdbuf);
720 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
721 smtp_auth(&cmdbuf[5]);
724 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
728 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
729 smtp_hello(&cmdbuf[5], 1);
732 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
733 smtp_expn(&cmdbuf[5]);
736 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
737 smtp_hello(&cmdbuf[5], 0);
740 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
744 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
745 smtp_mail(&cmdbuf[5]);
748 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
749 cprintf("250 This command successfully did nothing.\r\n");
752 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
753 cprintf("221 Goodbye...\r\n");
758 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
759 smtp_rcpt(&cmdbuf[5]);
762 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
766 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
767 smtp_vrfy(&cmdbuf[5]);
771 cprintf("502 I'm afraid I can't do that.\r\n");
779 /*****************************************************************************/
780 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
781 /*****************************************************************************/
788 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
791 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
798 char user[SIZ], node[SIZ], name[SIZ];
804 size_t blocksize = 0;
807 /* Parse out the host portion of the recipient address */
808 process_rfc822_addr(addr, user, node, name);
810 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
813 /* Load the message out of the database into a temp file */
815 if (msg_fp == NULL) {
817 sprintf(dsn, "Error creating temporary file");
821 CtdlRedirectOutput(msg_fp, -1);
822 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
823 CtdlRedirectOutput(NULL, -1);
824 fseek(msg_fp, 0L, SEEK_END);
825 msg_size = ftell(msg_fp);
829 /* Extract something to send later in the 'MAIL From:' command */
830 strcpy(mailfrom, "");
834 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
835 if (!strncasecmp(buf, "From:", 5)) {
836 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
838 for (i=0; i<strlen(mailfrom); ++i) {
839 if (!isprint(mailfrom[i])) {
840 strcpy(&mailfrom[i], &mailfrom[i+1]);
845 /* Strip out parenthesized names */
848 for (i=0; i<strlen(mailfrom); ++i) {
849 if (mailfrom[i] == '(') lp = i;
850 if (mailfrom[i] == ')') rp = i;
852 if ((lp>0)&&(rp>lp)) {
853 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
856 /* Prefer brokketized names */
859 for (i=0; i<strlen(mailfrom); ++i) {
860 if (mailfrom[i] == '<') lp = i;
861 if (mailfrom[i] == '>') rp = i;
863 if ((lp>=0)&&(rp>lp)) {
865 strcpy(mailfrom, &mailfrom[lp]);
870 } while (scan_done == 0);
871 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
874 /* Figure out what mail exchanger host we have to connect to */
875 num_mxhosts = getmx(mxhosts, node);
876 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
877 if (num_mxhosts < 1) {
879 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
883 for (mx=0; mx<num_mxhosts; ++mx) {
884 extract(buf, mxhosts, mx);
885 lprintf(9, "Trying <%s>\n", buf);
886 sock = sock_connect(buf, "25", "tcp");
887 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
888 if (sock >= 0) lprintf(9, "Connected!\n");
889 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
890 if (sock >= 0) break;
894 *status = 4; /* dsn is already filled in */
898 /* Process the SMTP greeting from the server */
899 if (ml_sock_gets(sock, buf) < 0) {
901 strcpy(dsn, "Connection broken during SMTP conversation");
904 lprintf(9, "<%s\n", buf);
908 safestrncpy(dsn, &buf[4], 1023);
913 safestrncpy(dsn, &buf[4], 1023);
918 /* At this point we know we are talking to a real SMTP server */
920 /* Do a HELO command */
921 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
922 lprintf(9, ">%s\n", buf);
923 sock_puts_crlf(sock, buf);
924 if (ml_sock_gets(sock, buf) < 0) {
926 strcpy(dsn, "Connection broken during SMTP conversation");
929 lprintf(9, "<%s\n", buf);
933 safestrncpy(dsn, &buf[4], 1023);
938 safestrncpy(dsn, &buf[4], 1023);
944 /* HELO succeeded, now try the MAIL From: command */
945 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
946 lprintf(9, ">%s\n", buf);
947 sock_puts_crlf(sock, buf);
948 if (ml_sock_gets(sock, buf) < 0) {
950 strcpy(dsn, "Connection broken during SMTP conversation");
953 lprintf(9, "<%s\n", buf);
957 safestrncpy(dsn, &buf[4], 1023);
962 safestrncpy(dsn, &buf[4], 1023);
968 /* MAIL succeeded, now try the RCPT To: command */
969 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
970 lprintf(9, ">%s\n", buf);
971 sock_puts_crlf(sock, buf);
972 if (ml_sock_gets(sock, buf) < 0) {
974 strcpy(dsn, "Connection broken during SMTP conversation");
977 lprintf(9, "<%s\n", buf);
981 safestrncpy(dsn, &buf[4], 1023);
986 safestrncpy(dsn, &buf[4], 1023);
992 /* RCPT succeeded, now try the DATA command */
993 lprintf(9, ">DATA\n");
994 sock_puts_crlf(sock, "DATA");
995 if (ml_sock_gets(sock, buf) < 0) {
997 strcpy(dsn, "Connection broken during SMTP conversation");
1000 lprintf(9, "<%s\n", buf);
1001 if (buf[0] != '3') {
1002 if (buf[0] == '4') {
1004 safestrncpy(dsn, &buf[4], 1023);
1009 safestrncpy(dsn, &buf[4], 1023);
1014 /* If we reach this point, the server is expecting data */
1016 while (msg_size > 0) {
1017 blocksize = sizeof(buf);
1018 if (blocksize > msg_size) blocksize = msg_size;
1019 fread(buf, blocksize, 1, msg_fp);
1020 sock_write(sock, buf, blocksize);
1021 msg_size -= blocksize;
1023 if (buf[blocksize-1] != 10) {
1024 lprintf(5, "Possible problem: message did not correctly "
1025 "terminate. (expecting 0x10, got 0x%02x)\n",
1029 sock_write(sock, ".\r\n", 3);
1030 if (ml_sock_gets(sock, buf) < 0) {
1032 strcpy(dsn, "Connection broken during SMTP conversation");
1035 lprintf(9, "%s\n", buf);
1036 if (buf[0] != '2') {
1037 if (buf[0] == '4') {
1039 safestrncpy(dsn, &buf[4], 1023);
1044 safestrncpy(dsn, &buf[4], 1023);
1050 safestrncpy(dsn, &buf[4], 1023);
1053 lprintf(9, ">QUIT\n");
1054 sock_puts_crlf(sock, "QUIT");
1055 ml_sock_gets(sock, buf);
1056 lprintf(9, "<%s\n", buf);
1058 bail: if (msg_fp != NULL) fclose(msg_fp);
1066 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1067 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1068 * a "bounce" message (delivery status notification).
1070 void smtp_do_bounce(char *instr) {
1078 char bounceto[1024];
1079 int num_bounces = 0;
1080 int bounce_this = 0;
1081 long bounce_msgid = (-1);
1082 time_t submitted = 0L;
1083 struct CtdlMessage *bmsg = NULL;
1087 lprintf(9, "smtp_do_bounce() called\n");
1088 strcpy(bounceto, "");
1090 lines = num_tokens(instr, '\n');
1093 /* See if it's time to give up on delivery of this message */
1094 for (i=0; i<lines; ++i) {
1095 extract_token(buf, instr, i, '\n');
1096 extract(key, buf, 0);
1097 extract(addr, buf, 1);
1098 if (!strcasecmp(key, "submitted")) {
1099 submitted = atol(addr);
1103 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1109 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1110 if (bmsg == NULL) return;
1111 memset(bmsg, 0, sizeof(struct CtdlMessage));
1113 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1114 bmsg->cm_anon_type = MES_NORMAL;
1115 bmsg->cm_format_type = 1;
1116 bmsg->cm_fields['A'] = strdoop("Citadel");
1117 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1118 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1120 if (give_up) bmsg->cm_fields['M'] = strdoop(
1121 "A message you sent could not be delivered to some or all of its recipients\n"
1122 "due to prolonged unavailability of its destination(s).\n"
1123 "Giving up on the following addresses:\n\n"
1126 else bmsg->cm_fields['M'] = strdoop(
1127 "A message you sent could not be delivered to some or all of its recipients.\n"
1128 "The following addresses were undeliverable:\n\n"
1132 * Now go through the instructions checking for stuff.
1135 for (i=0; i<lines; ++i) {
1136 extract_token(buf, instr, i, '\n');
1137 extract(key, buf, 0);
1138 extract(addr, buf, 1);
1139 status = extract_int(buf, 2);
1140 extract(dsn, buf, 3);
1143 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1144 key, addr, status, dsn);
1146 if (!strcasecmp(key, "bounceto")) {
1147 strcpy(bounceto, addr);
1151 (!strcasecmp(key, "local"))
1152 || (!strcasecmp(key, "remote"))
1153 || (!strcasecmp(key, "ignet"))
1154 || (!strcasecmp(key, "room"))
1156 if (status == 5) bounce_this = 1;
1157 if (give_up) bounce_this = 1;
1163 if (bmsg->cm_fields['M'] == NULL) {
1164 lprintf(2, "ERROR ... M field is null "
1165 "(%s:%d)\n", __FILE__, __LINE__);
1168 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1169 strlen(bmsg->cm_fields['M']) + 1024 );
1170 strcat(bmsg->cm_fields['M'], addr);
1171 strcat(bmsg->cm_fields['M'], ": ");
1172 strcat(bmsg->cm_fields['M'], dsn);
1173 strcat(bmsg->cm_fields['M'], "\n");
1175 remove_token(instr, i, '\n');
1181 /* Deliver the bounce if there's anything worth mentioning */
1182 lprintf(9, "num_bounces = %d\n", num_bounces);
1183 if (num_bounces > 0) {
1185 /* First try the user who sent the message */
1186 lprintf(9, "bounce to user? <%s>\n", bounceto);
1188 if (strlen(bounceto) == 0) {
1189 lprintf(7, "No bounce address specified\n");
1190 bounce_msgid = (-1L);
1192 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1193 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1194 bounce_msgid = (-1L);
1197 bounce_msgid = CtdlSaveMsg(bmsg,
1203 /* Otherwise, go to the Aide> room */
1204 lprintf(9, "bounce to room?\n");
1205 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1210 CtdlFreeMessage(bmsg);
1211 lprintf(9, "Done processing bounces\n");
1216 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1217 * set of delivery instructions for completed deliveries and remove them.
1219 * It returns the number of incomplete deliveries remaining.
1221 int smtp_purge_completed_deliveries(char *instr) {
1232 lines = num_tokens(instr, '\n');
1233 for (i=0; i<lines; ++i) {
1234 extract_token(buf, instr, i, '\n');
1235 extract(key, buf, 0);
1236 extract(addr, buf, 1);
1237 status = extract_int(buf, 2);
1238 extract(dsn, buf, 3);
1243 (!strcasecmp(key, "local"))
1244 || (!strcasecmp(key, "remote"))
1245 || (!strcasecmp(key, "ignet"))
1246 || (!strcasecmp(key, "room"))
1248 if (status == 2) completed = 1;
1253 remove_token(instr, i, '\n');
1266 * Called by smtp_do_queue() to handle an individual message.
1268 void smtp_do_procmsg(long msgnum, void *userdata) {
1269 struct CtdlMessage *msg;
1271 char *results = NULL;
1279 long text_msgid = (-1);
1280 int incomplete_deliveries_remaining;
1281 time_t attempted = 0L;
1282 time_t last_attempted = 0L;
1283 time_t retry = SMTP_RETRY_INTERVAL;
1285 msg = CtdlFetchMessage(msgnum);
1287 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1291 instr = strdoop(msg->cm_fields['M']);
1292 CtdlFreeMessage(msg);
1294 /* Strip out the headers amd any other non-instruction line */
1295 lines = num_tokens(instr, '\n');
1296 for (i=0; i<lines; ++i) {
1297 extract_token(buf, instr, i, '\n');
1298 if (num_tokens(buf, '|') < 2) {
1299 lprintf(9, "removing <%s>\n", buf);
1300 remove_token(instr, i, '\n');
1306 /* Learn the message ID and find out about recent delivery attempts */
1307 lines = num_tokens(instr, '\n');
1308 for (i=0; i<lines; ++i) {
1309 extract_token(buf, instr, i, '\n');
1310 extract(key, buf, 0);
1311 if (!strcasecmp(key, "msgid")) {
1312 text_msgid = extract_long(buf, 1);
1314 if (!strcasecmp(key, "retry")) {
1315 /* double the retry interval after each attempt */
1316 retry = extract_long(buf, 1) * 2L;
1317 remove_token(instr, i, '\n');
1319 if (!strcasecmp(key, "attempted")) {
1320 attempted = extract_long(buf, 1);
1321 if (attempted > last_attempted)
1322 last_attempted = attempted;
1328 * Postpone delivery if we've already tried recently.
1330 if ( (time(NULL) - last_attempted) < retry) {
1331 lprintf(7, "Retry time not yet reached.\n");
1338 * Bail out if there's no actual message associated with this
1340 if (text_msgid < 0L) {
1341 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1346 /* Plow through the instructions looking for 'remote' directives and
1347 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1348 * were experienced and it's time to try again)
1350 lines = num_tokens(instr, '\n');
1351 for (i=0; i<lines; ++i) {
1352 extract_token(buf, instr, i, '\n');
1353 extract(key, buf, 0);
1354 extract(addr, buf, 1);
1355 status = extract_int(buf, 2);
1356 extract(dsn, buf, 3);
1357 if ( (!strcasecmp(key, "remote"))
1358 && ((status==0)||(status==3)) ) {
1359 remove_token(instr, i, '\n');
1362 lprintf(9, "SMTP: Trying <%s>\n", addr);
1363 smtp_try(key, addr, &status, dsn, text_msgid);
1365 if (results == NULL) {
1366 results = mallok(1024);
1367 memset(results, 0, 1024);
1370 results = reallok(results,
1371 strlen(results) + 1024);
1373 sprintf(&results[strlen(results)],
1375 key, addr, status, dsn);
1380 if (results != NULL) {
1381 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1382 strcat(instr, results);
1387 /* Generate 'bounce' messages */
1388 smtp_do_bounce(instr);
1390 /* Go through the delivery list, deleting completed deliveries */
1391 incomplete_deliveries_remaining =
1392 smtp_purge_completed_deliveries(instr);
1396 * No delivery instructions remain, so delete both the instructions
1397 * message and the message message.
1399 if (incomplete_deliveries_remaining <= 0) {
1400 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1401 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1406 * Uncompleted delivery instructions remain, so delete the old
1407 * instructions and replace with the updated ones.
1409 if (incomplete_deliveries_remaining > 0) {
1410 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1411 msg = mallok(sizeof(struct CtdlMessage));
1412 memset(msg, 0, sizeof(struct CtdlMessage));
1413 msg->cm_magic = CTDLMESSAGE_MAGIC;
1414 msg->cm_anon_type = MES_NORMAL;
1415 msg->cm_format_type = FMT_RFC822;
1416 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1417 snprintf(msg->cm_fields['M'],
1419 "Content-type: %s\n\n%s\n"
1422 SPOOLMIME, instr, time(NULL), retry );
1424 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1425 CtdlFreeMessage(msg);
1435 * Run through the queue sending out messages.
1437 void smtp_do_queue(void) {
1438 static int doing_queue = 0;
1441 * This is a simple concurrency check to make sure only one queue run
1442 * is done at a time. We could do this with a mutex, but since we
1443 * don't really require extremely fine granularity here, we'll do it
1444 * with a static variable instead.
1446 if (doing_queue) return;
1450 * Go ahead and run the queue
1452 lprintf(7, "SMTP: processing outbound queue\n");
1454 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1455 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1458 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1459 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1461 lprintf(7, "SMTP: queue run completed\n");
1467 /*****************************************************************************/
1468 /* MODULE INITIALIZATION STUFF */
1469 /*****************************************************************************/
1472 char *Dynamic_Module_Init(void)
1474 SYM_SMTP = CtdlGetDynamicSymbol();
1475 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1477 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1482 CtdlRegisterServiceHook(0, /* ...and locally */
1487 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1488 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);