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"
58 struct citsmtp { /* Information about the current session */
61 struct usersupp vrfy_buffer;
65 int number_of_recipients;
67 int message_originated_locally;
70 enum { /* Command states for login authentication */
76 enum { /* Delivery modes */
81 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
82 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
89 /*****************************************************************************/
90 /* SMTP SERVER (INBOUND) STUFF */
91 /*****************************************************************************/
97 * Here's where our SMTP session begins its happy day.
99 void smtp_greeting(void) {
101 strcpy(CC->cs_clientname, "SMTP session");
102 CC->internal_pgm = 1;
103 CC->cs_flags |= CS_STEALTH;
104 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
105 CtdlAllocUserData(SYM_SMTP_RECP, SIZ);
106 sprintf(SMTP_RECP, "%s", "");
108 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
113 * Implement HELO and EHLO commands.
115 void smtp_hello(char *argbuf, int is_esmtp) {
117 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
120 cprintf("250 Greetings and joyous salutations.\r\n");
123 cprintf("250-Extended greetings and joyous salutations.\r\n");
124 cprintf("250-HELP\r\n");
125 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
126 cprintf("250-PIPELINING\r\n");
127 cprintf("250 AUTH=LOGIN\r\n");
133 * Implement HELP command.
135 void smtp_help(void) {
136 cprintf("214-Commands accepted:\r\n");
137 cprintf("214- DATA\r\n");
138 cprintf("214- EHLO\r\n");
139 cprintf("214- EXPN\r\n");
140 cprintf("214- HELO\r\n");
141 cprintf("214- HELP\r\n");
142 cprintf("214- MAIL\r\n");
143 cprintf("214- NOOP\r\n");
144 cprintf("214- QUIT\r\n");
145 cprintf("214- RCPT\r\n");
146 cprintf("214- RSET\r\n");
147 cprintf("214- VRFY\r\n");
155 void smtp_get_user(char *argbuf) {
159 decode_base64(username, argbuf);
160 lprintf(9, "Trying <%s>\n", username);
161 if (CtdlLoginExistingUser(username) == login_ok) {
162 encode_base64(buf, "Password:");
163 cprintf("334 %s\r\n", buf);
164 SMTP->command_state = smtp_password;
167 cprintf("500 No such user.\r\n");
168 SMTP->command_state = smtp_command;
176 void smtp_get_pass(char *argbuf) {
179 decode_base64(password, argbuf);
180 lprintf(9, "Trying <%s>\n", password);
181 if (CtdlTryPassword(password) == pass_ok) {
182 cprintf("235 Authentication successful.\r\n");
183 lprintf(9, "SMTP authenticated login successful\n");
184 CC->internal_pgm = 0;
185 CC->cs_flags &= ~CS_STEALTH;
188 cprintf("500 Authentication failed.\r\n");
190 SMTP->command_state = smtp_command;
197 void smtp_auth(char *argbuf) {
200 if (strncasecmp(argbuf, "login", 5) ) {
201 cprintf("550 We only support LOGIN authentication.\r\n");
205 if (strlen(argbuf) >= 7) {
206 smtp_get_user(&argbuf[6]);
210 encode_base64(buf, "Username:");
211 cprintf("334 %s\r\n", buf);
212 SMTP->command_state = smtp_user;
218 * Back end for smtp_vrfy() command
220 void smtp_vrfy_backend(struct usersupp *us, void *data) {
222 if (!fuzzy_match(us, SMTP->vrfy_match)) {
224 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
230 * Implements the VRFY (verify user name) command.
231 * Performs fuzzy match on full user names.
233 void smtp_vrfy(char *argbuf) {
234 SMTP->vrfy_count = 0;
235 strcpy(SMTP->vrfy_match, argbuf);
236 ForEachUser(smtp_vrfy_backend, NULL);
238 if (SMTP->vrfy_count < 1) {
239 cprintf("550 String does not match anything.\r\n");
241 else if (SMTP->vrfy_count == 1) {
242 cprintf("250 %s <cit%ld@%s>\r\n",
243 SMTP->vrfy_buffer.fullname,
244 SMTP->vrfy_buffer.usernum,
247 else if (SMTP->vrfy_count > 1) {
248 cprintf("553 Request ambiguous: %d users matched.\r\n",
257 * Back end for smtp_expn() command
259 void smtp_expn_backend(struct usersupp *us, void *data) {
261 if (!fuzzy_match(us, SMTP->vrfy_match)) {
263 if (SMTP->vrfy_count >= 1) {
264 cprintf("250-%s <cit%ld@%s>\r\n",
265 SMTP->vrfy_buffer.fullname,
266 SMTP->vrfy_buffer.usernum,
271 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
277 * Implements the EXPN (expand user name) command.
278 * Performs fuzzy match on full user names.
280 void smtp_expn(char *argbuf) {
281 SMTP->vrfy_count = 0;
282 strcpy(SMTP->vrfy_match, argbuf);
283 ForEachUser(smtp_expn_backend, NULL);
285 if (SMTP->vrfy_count < 1) {
286 cprintf("550 String does not match anything.\r\n");
288 else if (SMTP->vrfy_count >= 1) {
289 cprintf("250 %s <cit%ld@%s>\r\n",
290 SMTP->vrfy_buffer.fullname,
291 SMTP->vrfy_buffer.usernum,
298 * Implements the RSET (reset state) command.
299 * Currently this just zeroes out the state buffer. If pointers to data
300 * allocated with mallok() are ever placed in the state buffer, we have to
301 * be sure to phree() them first!
303 void smtp_rset(void) {
304 memset(SMTP, 0, sizeof(struct citsmtp));
305 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
306 if (CC->logged_in) logout(CC);
307 cprintf("250 Zap!\r\n");
311 * Clear out the portions of the state buffer that need to be cleared out
312 * after the DATA command finishes.
314 void smtp_data_clear(void) {
315 strcpy(SMTP->from, "");
316 SMTP->number_of_recipients = 0;
317 SMTP->delivery_mode = 0;
318 SMTP->message_originated_locally = 0;
319 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
325 * Implements the "MAIL From:" command
327 void smtp_mail(char *argbuf) {
332 if (strlen(SMTP->from) != 0) {
333 cprintf("503 Only one sender permitted\r\n");
337 if (strncasecmp(argbuf, "From:", 5)) {
338 cprintf("501 Syntax error\r\n");
342 strcpy(SMTP->from, &argbuf[5]);
345 if (strlen(SMTP->from) == 0) {
346 cprintf("501 Empty sender name is not permitted\r\n");
351 /* If this SMTP connection is from a logged-in user, make sure that
352 * the user only sends email from his/her own address.
355 cvt = convert_internet_address(user, node, SMTP->from);
356 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
357 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
358 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
359 strcpy(SMTP->from, "");
363 SMTP->message_originated_locally = 1;
367 /* Otherwise, make sure outsiders aren't trying to forge mail from
372 cvt = convert_internet_address(user, node, SMTP->from);
374 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
375 if (CtdlHostAlias(node) == hostalias_localhost) {
377 cprintf("550 You must log in to send mail from %s\r\n",
379 strcpy(SMTP->from, "");
384 cprintf("250 Sender ok\r\n");
390 * Implements the "RCPT To:" command
392 void smtp_rcpt(char *argbuf) {
398 if (strlen(SMTP->from) == 0) {
399 cprintf("503 Need MAIL before RCPT\r\n");
403 if (strncasecmp(argbuf, "To:", 3)) {
404 cprintf("501 Syntax error\r\n");
408 strcpy(recp, &argbuf[3]);
414 cvt = convert_internet_address(user, node, recp);
415 snprintf(recp, sizeof recp, "%s@%s", user, node);
416 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
419 case rfc822_address_locally_validated:
420 cprintf("250 %s is a valid recipient.\r\n", user);
421 ++SMTP->number_of_recipients;
422 CtdlReallocUserData(SYM_SMTP_RECP,
423 strlen(SMTP_RECP) + 1024 );
424 strcat(SMTP_RECP, "local|");
425 strcat(SMTP_RECP, user);
426 strcat(SMTP_RECP, "|0\n");
429 case rfc822_room_delivery:
430 cprintf("250 Delivering to room '%s'\r\n", user);
431 ++SMTP->number_of_recipients;
432 CtdlReallocUserData(SYM_SMTP_RECP,
433 strlen(SMTP_RECP) + 1024 );
434 strcat(SMTP_RECP, "room|");
435 strcat(SMTP_RECP, user);
436 strcat(SMTP_RECP, "|0|\n");
439 case rfc822_no_such_user:
440 cprintf("550 %s: no such user\r\n", recp);
443 case rfc822_address_on_citadel_network:
444 cprintf("250 %s is on the local network\r\n", recp);
445 ++SMTP->number_of_recipients;
446 CtdlReallocUserData(SYM_SMTP_RECP,
447 strlen(SMTP_RECP) + 1024 );
448 strcat(SMTP_RECP, "ignet|");
449 strcat(SMTP_RECP, user);
450 strcat(SMTP_RECP, "|");
451 strcat(SMTP_RECP, node);
452 strcat(SMTP_RECP, "|0|\n");
455 case rfc822_address_nonlocal:
456 if (SMTP->message_originated_locally == 0) {
457 cprintf("551 Third-party relaying denied.\r\n");
460 cprintf("250 Remote recipient %s ok\r\n", recp);
461 ++SMTP->number_of_recipients;
462 CtdlReallocUserData(SYM_SMTP_RECP,
463 strlen(SMTP_RECP) + 1024 );
464 strcat(SMTP_RECP, "remote|");
465 strcat(SMTP_RECP, recp);
466 strcat(SMTP_RECP, "|0|\n");
472 cprintf("599 Unknown error\r\n");
478 * Send a message out through the local network
479 * (This is kind of ugly. IGnet should be done using clean server-to-server
480 * code instead of the old style spool.)
482 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
484 char *hold_R, *hold_D, *hold_O;
489 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
491 hold_R = msg->cm_fields['R'];
492 hold_D = msg->cm_fields['D'];
493 hold_O = msg->cm_fields['O'];
494 msg->cm_fields['R'] = user;
495 msg->cm_fields['D'] = dest;
496 msg->cm_fields['O'] = MAILROOM;
498 serialize_message(&smr, msg);
500 msg->cm_fields['R'] = hold_R;
501 msg->cm_fields['D'] = hold_D;
502 msg->cm_fields['O'] = hold_O;
505 snprintf(filename, sizeof filename,
506 "./network/spoolin/%s.%04x.%04x",
507 dest, getpid(), ++seq);
508 lprintf(9, "spool file name is <%s>\n", filename);
509 fp = fopen(filename, "wb");
511 fwrite(smr.ser, smr.len, 1, fp);
522 * Back end for smtp_data() ... this does the actual delivery of the message
523 * Returns 0 on success, nonzero on failure
525 int smtp_message_delivery(struct CtdlMessage *msg) {
532 int successful_saves = 0; /* number of successful local saves */
533 int failed_saves = 0; /* number of failed deliveries */
534 int remote_spools = 0; /* number of copies to send out */
537 struct usersupp userbuf;
538 char *instr; /* Remote delivery instructions */
539 struct CtdlMessage *imsg;
541 lprintf(9, "smtp_message_delivery() called\n");
543 /* Fill in 'from' fields with envelope information if missing */
544 process_rfc822_addr(SMTP->from, user, node, name);
545 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
546 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
547 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
548 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
550 /* Save the message in the queue */
551 msgid = CtdlSaveMsg(msg,
557 instr = mallok(1024);
558 snprintf(instr, 1024,
559 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
561 SPOOLMIME, msgid, time(NULL),
564 for (i=0; i<SMTP->number_of_recipients; ++i) {
565 extract_token(buf, SMTP_RECP, i, '\n');
566 extract(dtype, buf, 0);
568 /* Stuff local mailboxes */
569 if (!strcasecmp(dtype, "local")) {
570 extract(user, buf, 1);
571 if (getuser(&userbuf, user) == 0) {
572 MailboxName(room, &userbuf, MAILROOM);
573 CtdlSaveMsgPointerInRoom(room, msgid, 0);
581 /* Delivery to local non-mailbox rooms */
582 if (!strcasecmp(dtype, "room")) {
583 extract(room, buf, 1);
584 CtdlSaveMsgPointerInRoom(room, msgid, 0);
588 /* Delivery over the local Citadel network (IGnet) */
589 if (!strcasecmp(dtype, "ignet")) {
590 extract(user, buf, 1);
591 extract(node, buf, 2);
592 smtp_deliver_ignet(msg, user, node);
595 /* Remote delivery */
596 if (!strcasecmp(dtype, "remote")) {
597 extract(user, buf, 1);
598 instr = reallok(instr, strlen(instr) + 1024);
599 snprintf(&instr[strlen(instr)],
600 strlen(instr) + 1024,
608 /* If there are remote spools to be done, save the instructions */
609 if (remote_spools > 0) {
610 imsg = mallok(sizeof(struct CtdlMessage));
611 memset(imsg, 0, sizeof(struct CtdlMessage));
612 imsg->cm_magic = CTDLMESSAGE_MAGIC;
613 imsg->cm_anon_type = MES_NORMAL;
614 imsg->cm_format_type = FMT_RFC822;
615 imsg->cm_fields['M'] = instr;
616 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
617 CtdlFreeMessage(imsg);
620 /* If there are no remote spools, delete the message */
622 phree(instr); /* only needed here, because CtdlSaveMsg()
623 * would free this buffer otherwise */
624 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
627 return(failed_saves);
633 * Implements the DATA command
635 void smtp_data(void) {
637 struct CtdlMessage *msg;
641 if (strlen(SMTP->from) == 0) {
642 cprintf("503 Need MAIL command first.\r\n");
646 if (SMTP->number_of_recipients < 1) {
647 cprintf("503 Need RCPT command first.\r\n");
651 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
653 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
656 if (body != NULL) snprintf(body, 4096,
657 "Received: from %s\n"
664 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
666 cprintf("550 Unable to save message text: internal error.\r\n");
670 lprintf(9, "Converting message...\n");
671 msg = convert_internet_message(body);
673 /* If the user is locally authenticated, FORCE the From: header to
674 * show up as the real sender
677 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
678 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
679 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
680 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
681 msg->cm_fields['N'] = strdoop(config.c_nodename);
682 msg->cm_fields['H'] = strdoop(config.c_humannode);
685 retval = smtp_message_delivery(msg);
686 CtdlFreeMessage(msg);
689 cprintf("250 ok terrific\r\n");
692 cprintf("550 Internal delivery errors: %d\r\n", retval);
695 smtp_data_clear(); /* clear out the buffers now */
702 * Main command loop for SMTP sessions.
704 void smtp_command_loop(void) {
708 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
709 if (client_gets(cmdbuf) < 1) {
710 lprintf(3, "SMTP socket is broken. Ending session.\n");
714 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
715 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
717 if (SMTP->command_state == smtp_user) {
718 smtp_get_user(cmdbuf);
721 else if (SMTP->command_state == smtp_password) {
722 smtp_get_pass(cmdbuf);
725 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
726 smtp_auth(&cmdbuf[5]);
729 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
733 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
734 smtp_hello(&cmdbuf[5], 1);
737 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
738 smtp_expn(&cmdbuf[5]);
741 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
742 smtp_hello(&cmdbuf[5], 0);
745 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
749 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
750 smtp_mail(&cmdbuf[5]);
753 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
754 cprintf("250 NOOP\r\n");
757 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
758 cprintf("221 Goodbye...\r\n");
763 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
764 smtp_rcpt(&cmdbuf[5]);
767 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
771 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
772 smtp_vrfy(&cmdbuf[5]);
776 cprintf("502 I'm afraid I can't do that.\r\n");
784 /*****************************************************************************/
785 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
786 /*****************************************************************************/
793 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
796 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
803 char user[SIZ], node[SIZ], name[SIZ];
809 size_t blocksize = 0;
812 /* Parse out the host portion of the recipient address */
813 process_rfc822_addr(addr, user, node, name);
815 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
818 /* Load the message out of the database into a temp file */
820 if (msg_fp == NULL) {
822 sprintf(dsn, "Error creating temporary file");
826 CtdlRedirectOutput(msg_fp, -1);
827 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
828 CtdlRedirectOutput(NULL, -1);
829 fseek(msg_fp, 0L, SEEK_END);
830 msg_size = ftell(msg_fp);
834 /* Extract something to send later in the 'MAIL From:' command */
835 strcpy(mailfrom, "");
839 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
840 if (!strncasecmp(buf, "From:", 5)) {
841 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
843 for (i=0; i<strlen(mailfrom); ++i) {
844 if (!isprint(mailfrom[i])) {
845 strcpy(&mailfrom[i], &mailfrom[i+1]);
850 /* Strip out parenthesized names */
853 for (i=0; i<strlen(mailfrom); ++i) {
854 if (mailfrom[i] == '(') lp = i;
855 if (mailfrom[i] == ')') rp = i;
857 if ((lp>0)&&(rp>lp)) {
858 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
861 /* Prefer brokketized names */
864 for (i=0; i<strlen(mailfrom); ++i) {
865 if (mailfrom[i] == '<') lp = i;
866 if (mailfrom[i] == '>') rp = i;
868 if ((lp>=0)&&(rp>lp)) {
870 strcpy(mailfrom, &mailfrom[lp]);
875 } while (scan_done == 0);
876 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
879 /* Figure out what mail exchanger host we have to connect to */
880 num_mxhosts = getmx(mxhosts, node);
881 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
882 if (num_mxhosts < 1) {
884 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
888 for (mx=0; mx<num_mxhosts; ++mx) {
889 extract(buf, mxhosts, mx);
890 lprintf(9, "Trying <%s>\n", buf);
891 sock = sock_connect(buf, "25", "tcp");
892 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
893 if (sock >= 0) lprintf(9, "Connected!\n");
894 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
895 if (sock >= 0) break;
899 *status = 4; /* dsn is already filled in */
903 /* Process the SMTP greeting from the server */
904 if (ml_sock_gets(sock, buf) < 0) {
906 strcpy(dsn, "Connection broken during SMTP conversation");
909 lprintf(9, "<%s\n", buf);
913 safestrncpy(dsn, &buf[4], 1023);
918 safestrncpy(dsn, &buf[4], 1023);
923 /* At this point we know we are talking to a real SMTP server */
925 /* Do a HELO command */
926 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
927 lprintf(9, ">%s\n", buf);
928 sock_puts_crlf(sock, buf);
929 if (ml_sock_gets(sock, buf) < 0) {
931 strcpy(dsn, "Connection broken during SMTP conversation");
934 lprintf(9, "<%s\n", buf);
938 safestrncpy(dsn, &buf[4], 1023);
943 safestrncpy(dsn, &buf[4], 1023);
949 /* HELO succeeded, now try the MAIL From: command */
950 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
951 lprintf(9, ">%s\n", buf);
952 sock_puts_crlf(sock, buf);
953 if (ml_sock_gets(sock, buf) < 0) {
955 strcpy(dsn, "Connection broken during SMTP conversation");
958 lprintf(9, "<%s\n", buf);
962 safestrncpy(dsn, &buf[4], 1023);
967 safestrncpy(dsn, &buf[4], 1023);
973 /* MAIL succeeded, now try the RCPT To: command */
974 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
975 lprintf(9, ">%s\n", buf);
976 sock_puts_crlf(sock, buf);
977 if (ml_sock_gets(sock, buf) < 0) {
979 strcpy(dsn, "Connection broken during SMTP conversation");
982 lprintf(9, "<%s\n", buf);
986 safestrncpy(dsn, &buf[4], 1023);
991 safestrncpy(dsn, &buf[4], 1023);
997 /* RCPT succeeded, now try the DATA command */
998 lprintf(9, ">DATA\n");
999 sock_puts_crlf(sock, "DATA");
1000 if (ml_sock_gets(sock, buf) < 0) {
1002 strcpy(dsn, "Connection broken during SMTP conversation");
1005 lprintf(9, "<%s\n", buf);
1006 if (buf[0] != '3') {
1007 if (buf[0] == '4') {
1009 safestrncpy(dsn, &buf[4], 1023);
1014 safestrncpy(dsn, &buf[4], 1023);
1019 /* If we reach this point, the server is expecting data */
1021 while (msg_size > 0) {
1022 blocksize = sizeof(buf);
1023 if (blocksize > msg_size) blocksize = msg_size;
1024 fread(buf, blocksize, 1, msg_fp);
1025 sock_write(sock, buf, blocksize);
1026 msg_size -= blocksize;
1028 if (buf[blocksize-1] != 10) {
1029 lprintf(5, "Possible problem: message did not correctly "
1030 "terminate. (expecting 0x10, got 0x%02x)\n",
1034 sock_write(sock, ".\r\n", 3);
1035 if (ml_sock_gets(sock, buf) < 0) {
1037 strcpy(dsn, "Connection broken during SMTP conversation");
1040 lprintf(9, "%s\n", buf);
1041 if (buf[0] != '2') {
1042 if (buf[0] == '4') {
1044 safestrncpy(dsn, &buf[4], 1023);
1049 safestrncpy(dsn, &buf[4], 1023);
1055 safestrncpy(dsn, &buf[4], 1023);
1058 lprintf(9, ">QUIT\n");
1059 sock_puts_crlf(sock, "QUIT");
1060 ml_sock_gets(sock, buf);
1061 lprintf(9, "<%s\n", buf);
1063 bail: if (msg_fp != NULL) fclose(msg_fp);
1071 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1072 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1073 * a "bounce" message (delivery status notification).
1075 void smtp_do_bounce(char *instr) {
1083 char bounceto[1024];
1084 int num_bounces = 0;
1085 int bounce_this = 0;
1086 long bounce_msgid = (-1);
1087 time_t submitted = 0L;
1088 struct CtdlMessage *bmsg = NULL;
1092 lprintf(9, "smtp_do_bounce() called\n");
1093 strcpy(bounceto, "");
1095 lines = num_tokens(instr, '\n');
1098 /* See if it's time to give up on delivery of this message */
1099 for (i=0; i<lines; ++i) {
1100 extract_token(buf, instr, i, '\n');
1101 extract(key, buf, 0);
1102 extract(addr, buf, 1);
1103 if (!strcasecmp(key, "submitted")) {
1104 submitted = atol(addr);
1108 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1114 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1115 if (bmsg == NULL) return;
1116 memset(bmsg, 0, sizeof(struct CtdlMessage));
1118 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1119 bmsg->cm_anon_type = MES_NORMAL;
1120 bmsg->cm_format_type = 1;
1121 bmsg->cm_fields['A'] = strdoop("Citadel");
1122 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1123 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1125 if (give_up) bmsg->cm_fields['M'] = strdoop(
1126 "A message you sent could not be delivered to some or all of its recipients\n"
1127 "due to prolonged unavailability of its destination(s).\n"
1128 "Giving up on the following addresses:\n\n"
1131 else bmsg->cm_fields['M'] = strdoop(
1132 "A message you sent could not be delivered to some or all of its recipients.\n"
1133 "The following addresses were undeliverable:\n\n"
1137 * Now go through the instructions checking for stuff.
1140 for (i=0; i<lines; ++i) {
1141 extract_token(buf, instr, i, '\n');
1142 extract(key, buf, 0);
1143 extract(addr, buf, 1);
1144 status = extract_int(buf, 2);
1145 extract(dsn, buf, 3);
1148 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1149 key, addr, status, dsn);
1151 if (!strcasecmp(key, "bounceto")) {
1152 strcpy(bounceto, addr);
1156 (!strcasecmp(key, "local"))
1157 || (!strcasecmp(key, "remote"))
1158 || (!strcasecmp(key, "ignet"))
1159 || (!strcasecmp(key, "room"))
1161 if (status == 5) bounce_this = 1;
1162 if (give_up) bounce_this = 1;
1168 if (bmsg->cm_fields['M'] == NULL) {
1169 lprintf(2, "ERROR ... M field is null "
1170 "(%s:%d)\n", __FILE__, __LINE__);
1173 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1174 strlen(bmsg->cm_fields['M']) + 1024 );
1175 strcat(bmsg->cm_fields['M'], addr);
1176 strcat(bmsg->cm_fields['M'], ": ");
1177 strcat(bmsg->cm_fields['M'], dsn);
1178 strcat(bmsg->cm_fields['M'], "\n");
1180 remove_token(instr, i, '\n');
1186 /* Deliver the bounce if there's anything worth mentioning */
1187 lprintf(9, "num_bounces = %d\n", num_bounces);
1188 if (num_bounces > 0) {
1190 /* First try the user who sent the message */
1191 lprintf(9, "bounce to user? <%s>\n", bounceto);
1193 if (strlen(bounceto) == 0) {
1194 lprintf(7, "No bounce address specified\n");
1195 bounce_msgid = (-1L);
1197 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1198 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1199 bounce_msgid = (-1L);
1202 bounce_msgid = CtdlSaveMsg(bmsg,
1208 /* Otherwise, go to the Aide> room */
1209 lprintf(9, "bounce to room?\n");
1210 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1215 CtdlFreeMessage(bmsg);
1216 lprintf(9, "Done processing bounces\n");
1221 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1222 * set of delivery instructions for completed deliveries and remove them.
1224 * It returns the number of incomplete deliveries remaining.
1226 int smtp_purge_completed_deliveries(char *instr) {
1237 lines = num_tokens(instr, '\n');
1238 for (i=0; i<lines; ++i) {
1239 extract_token(buf, instr, i, '\n');
1240 extract(key, buf, 0);
1241 extract(addr, buf, 1);
1242 status = extract_int(buf, 2);
1243 extract(dsn, buf, 3);
1248 (!strcasecmp(key, "local"))
1249 || (!strcasecmp(key, "remote"))
1250 || (!strcasecmp(key, "ignet"))
1251 || (!strcasecmp(key, "room"))
1253 if (status == 2) completed = 1;
1258 remove_token(instr, i, '\n');
1271 * Called by smtp_do_queue() to handle an individual message.
1273 void smtp_do_procmsg(long msgnum, void *userdata) {
1274 struct CtdlMessage *msg;
1276 char *results = NULL;
1284 long text_msgid = (-1);
1285 int incomplete_deliveries_remaining;
1286 time_t attempted = 0L;
1287 time_t last_attempted = 0L;
1288 time_t retry = SMTP_RETRY_INTERVAL;
1290 msg = CtdlFetchMessage(msgnum);
1292 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1296 instr = strdoop(msg->cm_fields['M']);
1297 CtdlFreeMessage(msg);
1299 /* Strip out the headers amd any other non-instruction line */
1300 lines = num_tokens(instr, '\n');
1301 for (i=0; i<lines; ++i) {
1302 extract_token(buf, instr, i, '\n');
1303 if (num_tokens(buf, '|') < 2) {
1304 lprintf(9, "removing <%s>\n", buf);
1305 remove_token(instr, i, '\n');
1311 /* Learn the message ID and find out about recent delivery attempts */
1312 lines = num_tokens(instr, '\n');
1313 for (i=0; i<lines; ++i) {
1314 extract_token(buf, instr, i, '\n');
1315 extract(key, buf, 0);
1316 if (!strcasecmp(key, "msgid")) {
1317 text_msgid = extract_long(buf, 1);
1319 if (!strcasecmp(key, "retry")) {
1320 /* double the retry interval after each attempt */
1321 retry = extract_long(buf, 1) * 2L;
1322 if (retry > SMTP_RETRY_MAX) {
1323 retry = SMTP_RETRY_MAX;
1325 remove_token(instr, i, '\n');
1327 if (!strcasecmp(key, "attempted")) {
1328 attempted = extract_long(buf, 1);
1329 if (attempted > last_attempted)
1330 last_attempted = attempted;
1336 * Postpone delivery if we've already tried recently.
1338 if ( (time(NULL) - last_attempted) < retry) {
1339 lprintf(7, "Retry time not yet reached.\n");
1346 * Bail out if there's no actual message associated with this
1348 if (text_msgid < 0L) {
1349 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1354 /* Plow through the instructions looking for 'remote' directives and
1355 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1356 * were experienced and it's time to try again)
1358 lines = num_tokens(instr, '\n');
1359 for (i=0; i<lines; ++i) {
1360 extract_token(buf, instr, i, '\n');
1361 extract(key, buf, 0);
1362 extract(addr, buf, 1);
1363 status = extract_int(buf, 2);
1364 extract(dsn, buf, 3);
1365 if ( (!strcasecmp(key, "remote"))
1366 && ((status==0)||(status==3)) ) {
1367 remove_token(instr, i, '\n');
1370 lprintf(9, "SMTP: Trying <%s>\n", addr);
1371 smtp_try(key, addr, &status, dsn, text_msgid);
1373 if (results == NULL) {
1374 results = mallok(1024);
1375 memset(results, 0, 1024);
1378 results = reallok(results,
1379 strlen(results) + 1024);
1381 sprintf(&results[strlen(results)],
1383 key, addr, status, dsn);
1388 if (results != NULL) {
1389 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1390 strcat(instr, results);
1395 /* Generate 'bounce' messages */
1396 smtp_do_bounce(instr);
1398 /* Go through the delivery list, deleting completed deliveries */
1399 incomplete_deliveries_remaining =
1400 smtp_purge_completed_deliveries(instr);
1404 * No delivery instructions remain, so delete both the instructions
1405 * message and the message message.
1407 if (incomplete_deliveries_remaining <= 0) {
1408 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1409 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1414 * Uncompleted delivery instructions remain, so delete the old
1415 * instructions and replace with the updated ones.
1417 if (incomplete_deliveries_remaining > 0) {
1418 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1419 msg = mallok(sizeof(struct CtdlMessage));
1420 memset(msg, 0, sizeof(struct CtdlMessage));
1421 msg->cm_magic = CTDLMESSAGE_MAGIC;
1422 msg->cm_anon_type = MES_NORMAL;
1423 msg->cm_format_type = FMT_RFC822;
1424 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1425 snprintf(msg->cm_fields['M'],
1427 "Content-type: %s\n\n%s\n"
1430 SPOOLMIME, instr, time(NULL), retry );
1432 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1433 CtdlFreeMessage(msg);
1443 * Run through the queue sending out messages.
1445 void smtp_do_queue(void) {
1446 static int doing_queue = 0;
1449 * This is a simple concurrency check to make sure only one queue run
1450 * is done at a time. We could do this with a mutex, but since we
1451 * don't really require extremely fine granularity here, we'll do it
1452 * with a static variable instead.
1454 if (doing_queue) return;
1458 * Go ahead and run the queue
1460 lprintf(7, "SMTP: processing outbound queue\n");
1462 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1463 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1466 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1467 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1469 lprintf(7, "SMTP: queue run completed\n");
1475 /*****************************************************************************/
1476 /* MODULE INITIALIZATION STUFF */
1477 /*****************************************************************************/
1480 char *Dynamic_Module_Init(void)
1482 SYM_SMTP = CtdlGetDynamicSymbol();
1483 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1485 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1490 CtdlRegisterServiceHook(0, /* ...and locally */
1495 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1496 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);