4 * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
17 #include <sys/types.h>
26 #include "sysdep_decls.h"
27 #include "citserver.h"
31 #include "dynloader.h"
38 #include "internet_addressing.h"
41 #include "clientsocket.h"
44 struct citsmtp { /* Information about the current session */
47 struct usersupp vrfy_buffer;
51 int number_of_recipients;
53 int message_originated_locally;
56 enum { /* Command states for login authentication */
62 enum { /* Delivery modes */
67 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
68 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
75 /*****************************************************************************/
76 /* SMTP SERVER (INBOUND) STUFF */
77 /*****************************************************************************/
83 * Here's where our SMTP session begins its happy day.
85 void smtp_greeting(void) {
87 strcpy(CC->cs_clientname, "SMTP session");
89 CC->cs_flags |= CS_STEALTH;
90 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
91 CtdlAllocUserData(SYM_SMTP_RECP, SIZ);
92 sprintf(SMTP_RECP, "%s", "");
94 cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\r\n",
100 * Implement HELO and EHLO commands.
102 void smtp_hello(char *argbuf, int is_esmtp) {
104 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
107 cprintf("250 Greetings and joyous salutations.\r\n");
110 cprintf("250-Greetings and joyous salutations.\r\n");
111 cprintf("250-HELP\r\n");
112 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
113 cprintf("250 AUTH=LOGIN\r\n");
119 * Implement HELP command.
121 void smtp_help(void) {
122 cprintf("214-Here's the frequency, Kenneth:\r\n");
123 cprintf("214- DATA\r\n");
124 cprintf("214- EHLO\r\n");
125 cprintf("214- EXPN\r\n");
126 cprintf("214- HELO\r\n");
127 cprintf("214- HELP\r\n");
128 cprintf("214- MAIL\r\n");
129 cprintf("214- NOOP\r\n");
130 cprintf("214- QUIT\r\n");
131 cprintf("214- RCPT\r\n");
132 cprintf("214- RSET\r\n");
133 cprintf("214- VRFY\r\n");
134 cprintf("214 I could tell you more, but then I'd have to kill you.\r\n");
141 void smtp_get_user(char *argbuf) {
145 decode_base64(username, argbuf);
146 lprintf(9, "Trying <%s>\n", username);
147 if (CtdlLoginExistingUser(username) == login_ok) {
148 encode_base64(buf, "Password:");
149 cprintf("334 %s\r\n", buf);
150 SMTP->command_state = smtp_password;
153 cprintf("500 No such user.\r\n");
154 SMTP->command_state = smtp_command;
162 void smtp_get_pass(char *argbuf) {
165 decode_base64(password, argbuf);
166 lprintf(9, "Trying <%s>\n", password);
167 if (CtdlTryPassword(password) == pass_ok) {
168 cprintf("235 Authentication successful.\r\n");
169 lprintf(9, "SMTP authenticated login successful\n");
170 CC->internal_pgm = 0;
171 CC->cs_flags &= ~CS_STEALTH;
174 cprintf("500 Authentication failed.\r\n");
176 SMTP->command_state = smtp_command;
183 void smtp_auth(char *argbuf) {
186 if (strncasecmp(argbuf, "login", 5) ) {
187 cprintf("550 We only support LOGIN authentication.\r\n");
191 if (strlen(argbuf) >= 7) {
192 smtp_get_user(&argbuf[6]);
196 encode_base64(buf, "Username:");
197 cprintf("334 %s\r\n", buf);
198 SMTP->command_state = smtp_user;
204 * Back end for smtp_vrfy() command
206 void smtp_vrfy_backend(struct usersupp *us, void *data) {
208 if (!fuzzy_match(us, SMTP->vrfy_match)) {
210 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
216 * Implements the VRFY (verify user name) command.
217 * Performs fuzzy match on full user names.
219 void smtp_vrfy(char *argbuf) {
220 SMTP->vrfy_count = 0;
221 strcpy(SMTP->vrfy_match, argbuf);
222 ForEachUser(smtp_vrfy_backend, NULL);
224 if (SMTP->vrfy_count < 1) {
225 cprintf("550 String does not match anything.\r\n");
227 else if (SMTP->vrfy_count == 1) {
228 cprintf("250 %s <cit%ld@%s>\r\n",
229 SMTP->vrfy_buffer.fullname,
230 SMTP->vrfy_buffer.usernum,
233 else if (SMTP->vrfy_count > 1) {
234 cprintf("553 Request ambiguous: %d users matched.\r\n",
243 * Back end for smtp_expn() command
245 void smtp_expn_backend(struct usersupp *us, void *data) {
247 if (!fuzzy_match(us, SMTP->vrfy_match)) {
249 if (SMTP->vrfy_count >= 1) {
250 cprintf("250-%s <cit%ld@%s>\r\n",
251 SMTP->vrfy_buffer.fullname,
252 SMTP->vrfy_buffer.usernum,
257 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
263 * Implements the EXPN (expand user name) command.
264 * Performs fuzzy match on full user names.
266 void smtp_expn(char *argbuf) {
267 SMTP->vrfy_count = 0;
268 strcpy(SMTP->vrfy_match, argbuf);
269 ForEachUser(smtp_expn_backend, NULL);
271 if (SMTP->vrfy_count < 1) {
272 cprintf("550 String does not match anything.\r\n");
274 else if (SMTP->vrfy_count >= 1) {
275 cprintf("250 %s <cit%ld@%s>\r\n",
276 SMTP->vrfy_buffer.fullname,
277 SMTP->vrfy_buffer.usernum,
284 * Implements the RSET (reset state) command.
285 * Currently this just zeroes out the state buffer. If pointers to data
286 * allocated with mallok() are ever placed in the state buffer, we have to
287 * be sure to phree() them first!
289 void smtp_rset(void) {
290 memset(SMTP, 0, sizeof(struct citsmtp));
291 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
292 if (CC->logged_in) logout(CC);
293 cprintf("250 Zap!\r\n");
297 * Clear out the portions of the state buffer that need to be cleared out
298 * after the DATA command finishes.
300 void smtp_data_clear(void) {
301 strcpy(SMTP->from, "");
302 SMTP->number_of_recipients = 0;
303 SMTP->delivery_mode = 0;
304 SMTP->message_originated_locally = 0;
305 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
311 * Implements the "MAIL From:" command
313 void smtp_mail(char *argbuf) {
318 if (strlen(SMTP->from) != 0) {
319 cprintf("503 Only one sender permitted\r\n");
323 if (strncasecmp(argbuf, "From:", 5)) {
324 cprintf("501 Syntax error\r\n");
328 strcpy(SMTP->from, &argbuf[5]);
331 if (strlen(SMTP->from) == 0) {
332 cprintf("501 Empty sender name is not permitted\r\n");
337 /* If this SMTP connection is from a logged-in user, make sure that
338 * the user only sends email from his/her own address.
341 cvt = convert_internet_address(user, node, SMTP->from);
342 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
343 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
344 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
345 strcpy(SMTP->from, "");
349 SMTP->message_originated_locally = 1;
353 /* Otherwise, make sure outsiders aren't trying to forge mail from
358 cvt = convert_internet_address(user, node, SMTP->from);
360 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
361 if (CtdlHostAlias(node) == hostalias_localhost) {
363 cprintf("550 You must log in to send mail from %s\r\n",
365 strcpy(SMTP->from, "");
370 cprintf("250 Sender ok\r\n");
376 * Implements the "RCPT To:" command
378 void smtp_rcpt(char *argbuf) {
384 if (strlen(SMTP->from) == 0) {
385 cprintf("503 Need MAIL before RCPT\r\n");
389 if (strncasecmp(argbuf, "To:", 3)) {
390 cprintf("501 Syntax error\r\n");
394 strcpy(recp, &argbuf[3]);
400 cvt = convert_internet_address(user, node, recp);
401 snprintf(recp, sizeof recp, "%s@%s", user, node);
402 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
405 case rfc822_address_locally_validated:
406 cprintf("250 %s is a valid recipient.\r\n", user);
407 ++SMTP->number_of_recipients;
408 CtdlReallocUserData(SYM_SMTP_RECP,
409 strlen(SMTP_RECP) + 1024 );
410 strcat(SMTP_RECP, "local|");
411 strcat(SMTP_RECP, user);
412 strcat(SMTP_RECP, "|0\n");
415 case rfc822_room_delivery:
416 cprintf("250 Delivering to room '%s'\r\n", user);
417 ++SMTP->number_of_recipients;
418 CtdlReallocUserData(SYM_SMTP_RECP,
419 strlen(SMTP_RECP) + 1024 );
420 strcat(SMTP_RECP, "room|");
421 strcat(SMTP_RECP, user);
422 strcat(SMTP_RECP, "|0|\n");
425 case rfc822_no_such_user:
426 cprintf("550 %s: no such user\r\n", recp);
429 case rfc822_address_on_citadel_network:
430 cprintf("250 %s is on the local network\r\n", recp);
431 ++SMTP->number_of_recipients;
432 CtdlReallocUserData(SYM_SMTP_RECP,
433 strlen(SMTP_RECP) + 1024 );
434 strcat(SMTP_RECP, "ignet|");
435 strcat(SMTP_RECP, user);
436 strcat(SMTP_RECP, "|");
437 strcat(SMTP_RECP, node);
438 strcat(SMTP_RECP, "|0|\n");
441 case rfc822_address_nonlocal:
442 if (SMTP->message_originated_locally == 0) {
443 cprintf("551 Relaying denied\r\n");
446 cprintf("250 Remote recipient %s ok\r\n", recp);
447 ++SMTP->number_of_recipients;
448 CtdlReallocUserData(SYM_SMTP_RECP,
449 strlen(SMTP_RECP) + 1024 );
450 strcat(SMTP_RECP, "remote|");
451 strcat(SMTP_RECP, recp);
452 strcat(SMTP_RECP, "|0|\n");
458 cprintf("599 Unknown error\r\n");
464 * Send a message out through the local network
465 * (This is kind of ugly. IGnet should be done using clean server-to-server
466 * code instead of the old style spool.)
468 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
470 char *hold_R, *hold_D, *hold_O;
475 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
477 hold_R = msg->cm_fields['R'];
478 hold_D = msg->cm_fields['D'];
479 hold_O = msg->cm_fields['O'];
480 msg->cm_fields['R'] = user;
481 msg->cm_fields['D'] = dest;
482 msg->cm_fields['O'] = MAILROOM;
484 serialize_message(&smr, msg);
486 msg->cm_fields['R'] = hold_R;
487 msg->cm_fields['D'] = hold_D;
488 msg->cm_fields['O'] = hold_O;
491 snprintf(filename, sizeof filename,
492 "./network/spoolin/%s.%04x.%04x",
493 dest, getpid(), ++seq);
494 lprintf(9, "spool file name is <%s>\n", filename);
495 fp = fopen(filename, "wb");
497 fwrite(smr.ser, smr.len, 1, fp);
508 * Back end for smtp_data() ... this does the actual delivery of the message
509 * Returns 0 on success, nonzero on failure
511 int smtp_message_delivery(struct CtdlMessage *msg) {
518 int successful_saves = 0; /* number of successful local saves */
519 int failed_saves = 0; /* number of failed deliveries */
520 int remote_spools = 0; /* number of copies to send out */
523 struct usersupp userbuf;
524 char *instr; /* Remote delivery instructions */
525 struct CtdlMessage *imsg;
527 lprintf(9, "smtp_message_delivery() called\n");
529 /* Fill in 'from' fields with envelope information if missing */
530 process_rfc822_addr(SMTP->from, user, node, name);
531 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
532 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
533 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
534 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
536 /* Save the message in the queue */
537 msgid = CtdlSaveMsg(msg,
543 instr = mallok(1024);
544 snprintf(instr, 1024,
545 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
547 SPOOLMIME, msgid, time(NULL),
550 for (i=0; i<SMTP->number_of_recipients; ++i) {
551 extract_token(buf, SMTP_RECP, i, '\n');
552 extract(dtype, buf, 0);
554 /* Stuff local mailboxes */
555 if (!strcasecmp(dtype, "local")) {
556 extract(user, buf, 1);
557 if (getuser(&userbuf, user) == 0) {
558 MailboxName(room, &userbuf, MAILROOM);
559 CtdlSaveMsgPointerInRoom(room, msgid, 0);
567 /* Delivery to local non-mailbox rooms */
568 if (!strcasecmp(dtype, "room")) {
569 extract(room, buf, 1);
570 CtdlSaveMsgPointerInRoom(room, msgid, 0);
574 /* Delivery over the local Citadel network (IGnet) */
575 if (!strcasecmp(dtype, "ignet")) {
576 extract(user, buf, 1);
577 extract(node, buf, 2);
578 smtp_deliver_ignet(msg, user, node);
581 /* Remote delivery */
582 if (!strcasecmp(dtype, "remote")) {
583 extract(user, buf, 1);
584 instr = reallok(instr, strlen(instr) + 1024);
585 snprintf(&instr[strlen(instr)],
586 strlen(instr) + 1024,
594 /* If there are remote spools to be done, save the instructions */
595 if (remote_spools > 0) {
596 imsg = mallok(sizeof(struct CtdlMessage));
597 memset(imsg, 0, sizeof(struct CtdlMessage));
598 imsg->cm_magic = CTDLMESSAGE_MAGIC;
599 imsg->cm_anon_type = MES_NORMAL;
600 imsg->cm_format_type = FMT_RFC822;
601 imsg->cm_fields['M'] = instr;
602 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
603 CtdlFreeMessage(imsg);
606 /* If there are no remote spools, delete the message */
608 phree(instr); /* only needed here, because CtdlSaveMsg()
609 * would free this buffer otherwise */
610 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
613 return(failed_saves);
619 * Implements the DATA command
621 void smtp_data(void) {
623 struct CtdlMessage *msg;
627 if (strlen(SMTP->from) == 0) {
628 cprintf("503 Need MAIL command first.\r\n");
632 if (SMTP->number_of_recipients < 1) {
633 cprintf("503 Need RCPT command first.\r\n");
637 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
639 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
642 if (body != NULL) snprintf(body, 4096,
643 "Received: from %s\n"
650 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
652 cprintf("550 Unable to save message text: internal error.\r\n");
656 lprintf(9, "Converting message...\n");
657 msg = convert_internet_message(body);
659 /* If the user is locally authenticated, FORCE the From: header to
660 * show up as the real sender
663 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
664 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
665 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
666 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
667 msg->cm_fields['N'] = strdoop(config.c_nodename);
668 msg->cm_fields['H'] = strdoop(config.c_humannode);
671 retval = smtp_message_delivery(msg);
672 CtdlFreeMessage(msg);
675 cprintf("250 Message accepted for delivery.\r\n");
678 cprintf("550 Internal delivery errors: %d\r\n", retval);
681 smtp_data_clear(); /* clear out the buffers now */
688 * Main command loop for SMTP sessions.
690 void smtp_command_loop(void) {
694 if (client_gets(&icmdbuf) < 1) {
695 lprintf(3, "SMTP socket is broken. Ending session.\n");
699 /* Rather than fix the dynamic buffer a zillion places in here... */
700 if (strlen(icmdbuf) >= SIZ)
701 *(icmdbuf+SIZ)= '\0'; /* no SMTP command should be this big */
702 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, icmdbuf);
703 while (strlen(icmdbuf) < 5) strcat(icmdbuf, " ");
705 if (SMTP->command_state == smtp_user) {
706 smtp_get_user(icmdbuf);
709 else if (SMTP->command_state == smtp_password) {
710 smtp_get_pass(icmdbuf);
713 else if (!strncasecmp(icmdbuf, "AUTH", 4)) {
714 smtp_auth(&icmdbuf[5]);
717 else if (!strncasecmp(icmdbuf, "DATA", 4)) {
721 else if (!strncasecmp(icmdbuf, "EHLO", 4)) {
722 smtp_hello(&icmdbuf[5], 1);
725 else if (!strncasecmp(icmdbuf, "EXPN", 4)) {
726 smtp_expn(&icmdbuf[5]);
729 else if (!strncasecmp(icmdbuf, "HELO", 4)) {
730 smtp_hello(&icmdbuf[5], 0);
733 else if (!strncasecmp(icmdbuf, "HELP", 4)) {
737 else if (!strncasecmp(icmdbuf, "MAIL", 4)) {
738 smtp_mail(&icmdbuf[5]);
741 else if (!strncasecmp(icmdbuf, "NOOP", 4)) {
742 cprintf("250 This command successfully did nothing.\r\n");
745 else if (!strncasecmp(icmdbuf, "QUIT", 4)) {
746 cprintf("221 Goodbye...\r\n");
751 else if (!strncasecmp(icmdbuf, "RCPT", 4)) {
752 smtp_rcpt(&icmdbuf[5]);
755 else if (!strncasecmp(icmdbuf, "RSET", 4)) {
759 else if (!strncasecmp(icmdbuf, "VRFY", 4)) {
760 smtp_vrfy(&icmdbuf[5]);
764 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
772 /*****************************************************************************/
773 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
774 /*****************************************************************************/
781 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
784 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
791 char user[SIZ], node[SIZ], name[SIZ];
797 size_t blocksize = 0;
800 /* Parse out the host portion of the recipient address */
801 process_rfc822_addr(addr, user, node, name);
803 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
806 /* Load the message out of the database into a temp file */
808 if (msg_fp == NULL) {
810 sprintf(dsn, "Error creating temporary file");
814 CtdlRedirectOutput(msg_fp, -1);
815 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
816 CtdlRedirectOutput(NULL, -1);
817 fseek(msg_fp, 0L, SEEK_END);
818 msg_size = ftell(msg_fp);
822 /* Extract something to send later in the 'MAIL From:' command */
823 strcpy(mailfrom, "");
827 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
828 if (!strncasecmp(buf, "From:", 5)) {
829 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
831 for (i=0; i<strlen(mailfrom); ++i) {
832 if (!isprint(mailfrom[i])) {
833 strcpy(&mailfrom[i], &mailfrom[i+1]);
838 /* Strip out parenthesized names */
841 for (i=0; i<strlen(mailfrom); ++i) {
842 if (mailfrom[i] == '(') lp = i;
843 if (mailfrom[i] == ')') rp = i;
845 if ((lp>0)&&(rp>lp)) {
846 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
849 /* Prefer brokketized names */
852 for (i=0; i<strlen(mailfrom); ++i) {
853 if (mailfrom[i] == '<') lp = i;
854 if (mailfrom[i] == '>') rp = i;
856 if ((lp>=0)&&(rp>lp)) {
858 strcpy(mailfrom, &mailfrom[lp]);
863 } while (scan_done == 0);
864 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
867 /* Figure out what mail exchanger host we have to connect to */
868 num_mxhosts = getmx(mxhosts, node);
869 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
870 if (num_mxhosts < 1) {
872 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
876 for (mx=0; mx<num_mxhosts; ++mx) {
877 extract(buf, mxhosts, mx);
878 lprintf(9, "Trying <%s>\n", buf);
879 sock = sock_connect(buf, "25", "tcp");
880 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
881 if (sock >= 0) lprintf(9, "Connected!\n");
882 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
883 if (sock >= 0) break;
887 *status = 4; /* dsn is already filled in */
891 /* Process the SMTP greeting from the server */
892 if (ml_sock_gets(sock, buf) < 0) {
894 strcpy(dsn, "Connection broken during SMTP conversation");
897 lprintf(9, "<%s\n", buf);
901 safestrncpy(dsn, &buf[4], 1023);
906 safestrncpy(dsn, &buf[4], 1023);
911 /* At this point we know we are talking to a real SMTP server */
913 /* Do a HELO command */
914 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
915 lprintf(9, ">%s\n", buf);
916 sock_puts_crlf(sock, buf);
917 if (ml_sock_gets(sock, buf) < 0) {
919 strcpy(dsn, "Connection broken during SMTP conversation");
922 lprintf(9, "<%s\n", buf);
926 safestrncpy(dsn, &buf[4], 1023);
931 safestrncpy(dsn, &buf[4], 1023);
937 /* HELO succeeded, now try the MAIL From: command */
938 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
939 lprintf(9, ">%s\n", buf);
940 sock_puts_crlf(sock, buf);
941 if (ml_sock_gets(sock, buf) < 0) {
943 strcpy(dsn, "Connection broken during SMTP conversation");
946 lprintf(9, "<%s\n", buf);
950 safestrncpy(dsn, &buf[4], 1023);
955 safestrncpy(dsn, &buf[4], 1023);
961 /* MAIL succeeded, now try the RCPT To: command */
962 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
963 lprintf(9, ">%s\n", buf);
964 sock_puts_crlf(sock, buf);
965 if (ml_sock_gets(sock, buf) < 0) {
967 strcpy(dsn, "Connection broken during SMTP conversation");
970 lprintf(9, "<%s\n", buf);
974 safestrncpy(dsn, &buf[4], 1023);
979 safestrncpy(dsn, &buf[4], 1023);
985 /* RCPT succeeded, now try the DATA command */
986 lprintf(9, ">DATA\n");
987 sock_puts_crlf(sock, "DATA");
988 if (ml_sock_gets(sock, buf) < 0) {
990 strcpy(dsn, "Connection broken during SMTP conversation");
993 lprintf(9, "<%s\n", buf);
997 safestrncpy(dsn, &buf[4], 1023);
1002 safestrncpy(dsn, &buf[4], 1023);
1007 /* If we reach this point, the server is expecting data */
1009 while (msg_size > 0) {
1010 blocksize = sizeof(buf);
1011 if (blocksize > msg_size) blocksize = msg_size;
1012 fread(buf, blocksize, 1, msg_fp);
1013 sock_write(sock, buf, blocksize);
1014 msg_size -= blocksize;
1016 if (buf[blocksize-1] != 10) {
1017 lprintf(5, "Possible problem: message did not correctly "
1018 "terminate. (expecting 0x10, got 0x%02x)\n",
1022 sock_write(sock, ".\r\n", 3);
1023 if (ml_sock_gets(sock, buf) < 0) {
1025 strcpy(dsn, "Connection broken during SMTP conversation");
1028 lprintf(9, "%s\n", buf);
1029 if (buf[0] != '2') {
1030 if (buf[0] == '4') {
1032 safestrncpy(dsn, &buf[4], 1023);
1037 safestrncpy(dsn, &buf[4], 1023);
1043 safestrncpy(dsn, &buf[4], 1023);
1046 lprintf(9, ">QUIT\n");
1047 sock_puts_crlf(sock, "QUIT");
1048 ml_sock_gets(sock, buf);
1049 lprintf(9, "<%s\n", buf);
1051 bail: if (msg_fp != NULL) fclose(msg_fp);
1059 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1060 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1061 * a "bounce" message (delivery status notification).
1063 void smtp_do_bounce(char *instr) {
1071 char bounceto[1024];
1072 int num_bounces = 0;
1073 int bounce_this = 0;
1074 long bounce_msgid = (-1);
1075 time_t submitted = 0L;
1076 struct CtdlMessage *bmsg = NULL;
1080 lprintf(9, "smtp_do_bounce() called\n");
1081 strcpy(bounceto, "");
1083 lines = num_tokens(instr, '\n');
1086 /* See if it's time to give up on delivery of this message */
1087 for (i=0; i<lines; ++i) {
1088 extract_token(buf, instr, i, '\n');
1089 extract(key, buf, 0);
1090 extract(addr, buf, 1);
1091 if (!strcasecmp(key, "submitted")) {
1092 submitted = atol(addr);
1096 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1102 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1103 if (bmsg == NULL) return;
1104 memset(bmsg, 0, sizeof(struct CtdlMessage));
1106 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1107 bmsg->cm_anon_type = MES_NORMAL;
1108 bmsg->cm_format_type = 1;
1109 bmsg->cm_fields['A'] = strdoop("Citadel");
1110 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1111 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1113 if (give_up) bmsg->cm_fields['M'] = strdoop(
1114 "A message you sent could not be delivered to some or all of its recipients\n"
1115 "due to prolonged unavailability of its destination(s).\n"
1116 "Giving up on the following addresses:\n\n"
1119 else bmsg->cm_fields['M'] = strdoop(
1120 "A message you sent could not be delivered to some or all of its recipients.\n"
1121 "The following addresses were undeliverable:\n\n"
1125 * Now go through the instructions checking for stuff.
1128 for (i=0; i<lines; ++i) {
1129 extract_token(buf, instr, i, '\n');
1130 extract(key, buf, 0);
1131 extract(addr, buf, 1);
1132 status = extract_int(buf, 2);
1133 extract(dsn, buf, 3);
1136 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1137 key, addr, status, dsn);
1139 if (!strcasecmp(key, "bounceto")) {
1140 strcpy(bounceto, addr);
1144 (!strcasecmp(key, "local"))
1145 || (!strcasecmp(key, "remote"))
1146 || (!strcasecmp(key, "ignet"))
1147 || (!strcasecmp(key, "room"))
1149 if (status == 5) bounce_this = 1;
1150 if (give_up) bounce_this = 1;
1156 if (bmsg->cm_fields['M'] == NULL) {
1157 lprintf(2, "ERROR ... M field is null "
1158 "(%s:%d)\n", __FILE__, __LINE__);
1161 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1162 strlen(bmsg->cm_fields['M']) + 1024 );
1163 strcat(bmsg->cm_fields['M'], addr);
1164 strcat(bmsg->cm_fields['M'], ": ");
1165 strcat(bmsg->cm_fields['M'], dsn);
1166 strcat(bmsg->cm_fields['M'], "\n");
1168 remove_token(instr, i, '\n');
1174 /* Deliver the bounce if there's anything worth mentioning */
1175 lprintf(9, "num_bounces = %d\n", num_bounces);
1176 if (num_bounces > 0) {
1178 /* First try the user who sent the message */
1179 lprintf(9, "bounce to user? <%s>\n", bounceto);
1181 if (strlen(bounceto) == 0) {
1182 lprintf(7, "No bounce address specified\n");
1183 bounce_msgid = (-1L);
1185 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1186 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1187 bounce_msgid = (-1L);
1190 bounce_msgid = CtdlSaveMsg(bmsg,
1196 /* Otherwise, go to the Aide> room */
1197 lprintf(9, "bounce to room?\n");
1198 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1203 CtdlFreeMessage(bmsg);
1204 lprintf(9, "Done processing bounces\n");
1209 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1210 * set of delivery instructions for completed deliveries and remove them.
1212 * It returns the number of incomplete deliveries remaining.
1214 int smtp_purge_completed_deliveries(char *instr) {
1225 lines = num_tokens(instr, '\n');
1226 for (i=0; i<lines; ++i) {
1227 extract_token(buf, instr, i, '\n');
1228 extract(key, buf, 0);
1229 extract(addr, buf, 1);
1230 status = extract_int(buf, 2);
1231 extract(dsn, buf, 3);
1236 (!strcasecmp(key, "local"))
1237 || (!strcasecmp(key, "remote"))
1238 || (!strcasecmp(key, "ignet"))
1239 || (!strcasecmp(key, "room"))
1241 if (status == 2) completed = 1;
1246 remove_token(instr, i, '\n');
1259 * Called by smtp_do_queue() to handle an individual message.
1261 void smtp_do_procmsg(long msgnum, void *userdata) {
1262 struct CtdlMessage *msg;
1264 char *results = NULL;
1272 long text_msgid = (-1);
1273 int incomplete_deliveries_remaining;
1274 time_t attempted = 0L;
1275 time_t last_attempted = 0L;
1276 time_t retry = SMTP_RETRY_INTERVAL;
1278 msg = CtdlFetchMessage(msgnum);
1280 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1284 instr = strdoop(msg->cm_fields['M']);
1285 CtdlFreeMessage(msg);
1287 /* Strip out the headers amd any other non-instruction line */
1288 lines = num_tokens(instr, '\n');
1289 for (i=0; i<lines; ++i) {
1290 extract_token(buf, instr, i, '\n');
1291 if (num_tokens(buf, '|') < 2) {
1292 lprintf(9, "removing <%s>\n", buf);
1293 remove_token(instr, i, '\n');
1299 /* Learn the message ID and find out about recent delivery attempts */
1300 lines = num_tokens(instr, '\n');
1301 for (i=0; i<lines; ++i) {
1302 extract_token(buf, instr, i, '\n');
1303 extract(key, buf, 0);
1304 if (!strcasecmp(key, "msgid")) {
1305 text_msgid = extract_long(buf, 1);
1307 if (!strcasecmp(key, "retry")) {
1308 /* double the retry interval after each attempt */
1309 retry = extract_long(buf, 1) * 2L;
1310 remove_token(instr, i, '\n');
1312 if (!strcasecmp(key, "attempted")) {
1313 attempted = extract_long(buf, 1);
1314 if (attempted > last_attempted)
1315 last_attempted = attempted;
1321 * Postpone delivery if we've already tried recently.
1323 if ( (time(NULL) - last_attempted) < retry) {
1324 lprintf(7, "Retry time not yet reached.\n");
1331 * Bail out if there's no actual message associated with this
1333 if (text_msgid < 0L) {
1334 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1339 /* Plow through the instructions looking for 'remote' directives and
1340 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1341 * were experienced and it's time to try again)
1343 lines = num_tokens(instr, '\n');
1344 for (i=0; i<lines; ++i) {
1345 extract_token(buf, instr, i, '\n');
1346 extract(key, buf, 0);
1347 extract(addr, buf, 1);
1348 status = extract_int(buf, 2);
1349 extract(dsn, buf, 3);
1350 if ( (!strcasecmp(key, "remote"))
1351 && ((status==0)||(status==3)) ) {
1352 remove_token(instr, i, '\n');
1355 lprintf(9, "SMTP: Trying <%s>\n", addr);
1356 smtp_try(key, addr, &status, dsn, text_msgid);
1358 if (results == NULL) {
1359 results = mallok(1024);
1360 memset(results, 0, 1024);
1363 results = reallok(results,
1364 strlen(results) + 1024);
1366 sprintf(&results[strlen(results)],
1368 key, addr, status, dsn);
1373 if (results != NULL) {
1374 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1375 strcat(instr, results);
1380 /* Generate 'bounce' messages */
1381 smtp_do_bounce(instr);
1383 /* Go through the delivery list, deleting completed deliveries */
1384 incomplete_deliveries_remaining =
1385 smtp_purge_completed_deliveries(instr);
1389 * No delivery instructions remain, so delete both the instructions
1390 * message and the message message.
1392 if (incomplete_deliveries_remaining <= 0) {
1393 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1394 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1399 * Uncompleted delivery instructions remain, so delete the old
1400 * instructions and replace with the updated ones.
1402 if (incomplete_deliveries_remaining > 0) {
1403 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1404 msg = mallok(sizeof(struct CtdlMessage));
1405 memset(msg, 0, sizeof(struct CtdlMessage));
1406 msg->cm_magic = CTDLMESSAGE_MAGIC;
1407 msg->cm_anon_type = MES_NORMAL;
1408 msg->cm_format_type = FMT_RFC822;
1409 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1410 snprintf(msg->cm_fields['M'],
1412 "Content-type: %s\n\n%s\n"
1415 SPOOLMIME, instr, time(NULL), retry );
1417 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1418 CtdlFreeMessage(msg);
1428 * Run through the queue sending out messages.
1430 void smtp_do_queue(void) {
1431 static int doing_queue = 0;
1434 * This is a simple concurrency check to make sure only one queue run
1435 * is done at a time. We could do this with a mutex, but since we
1436 * don't really require extremely fine granularity here, we'll do it
1437 * with a static variable instead.
1439 if (doing_queue) return;
1443 * Go ahead and run the queue
1445 lprintf(7, "SMTP: processing outbound queue\n");
1447 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1448 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1451 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1452 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1454 lprintf(7, "SMTP: queue run completed\n");
1460 /*****************************************************************************/
1461 /* MODULE INITIALIZATION STUFF */
1462 /*****************************************************************************/
1465 char *Dynamic_Module_Init(void)
1467 SYM_SMTP = CtdlGetDynamicSymbol();
1468 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1470 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1475 CtdlRegisterServiceHook(0, /* ...and locally */
1480 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1481 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);