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 Citadel/UX ESMTP server at %s ready.\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-Commands accepted:\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");
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 Third-party 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 ok terrific\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 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
695 if (client_gets(cmdbuf) < 1) {
696 lprintf(3, "SMTP socket is broken. Ending session.\n");
700 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
701 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
703 if (SMTP->command_state == smtp_user) {
704 smtp_get_user(cmdbuf);
707 else if (SMTP->command_state == smtp_password) {
708 smtp_get_pass(cmdbuf);
711 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
712 smtp_auth(&cmdbuf[5]);
715 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
719 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
720 smtp_hello(&cmdbuf[5], 1);
723 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
724 smtp_expn(&cmdbuf[5]);
727 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
728 smtp_hello(&cmdbuf[5], 0);
731 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
735 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
736 smtp_mail(&cmdbuf[5]);
739 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
740 cprintf("250 This command successfully did nothing.\r\n");
743 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
744 cprintf("221 Goodbye...\r\n");
749 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
750 smtp_rcpt(&cmdbuf[5]);
753 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
757 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
758 smtp_vrfy(&cmdbuf[5]);
762 cprintf("502 I'm afraid I can't do that.\r\n");
770 /*****************************************************************************/
771 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
772 /*****************************************************************************/
779 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
782 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
789 char user[SIZ], node[SIZ], name[SIZ];
795 size_t blocksize = 0;
798 /* Parse out the host portion of the recipient address */
799 process_rfc822_addr(addr, user, node, name);
801 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
804 /* Load the message out of the database into a temp file */
806 if (msg_fp == NULL) {
808 sprintf(dsn, "Error creating temporary file");
812 CtdlRedirectOutput(msg_fp, -1);
813 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
814 CtdlRedirectOutput(NULL, -1);
815 fseek(msg_fp, 0L, SEEK_END);
816 msg_size = ftell(msg_fp);
820 /* Extract something to send later in the 'MAIL From:' command */
821 strcpy(mailfrom, "");
825 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
826 if (!strncasecmp(buf, "From:", 5)) {
827 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
829 for (i=0; i<strlen(mailfrom); ++i) {
830 if (!isprint(mailfrom[i])) {
831 strcpy(&mailfrom[i], &mailfrom[i+1]);
836 /* Strip out parenthesized names */
839 for (i=0; i<strlen(mailfrom); ++i) {
840 if (mailfrom[i] == '(') lp = i;
841 if (mailfrom[i] == ')') rp = i;
843 if ((lp>0)&&(rp>lp)) {
844 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
847 /* Prefer brokketized names */
850 for (i=0; i<strlen(mailfrom); ++i) {
851 if (mailfrom[i] == '<') lp = i;
852 if (mailfrom[i] == '>') rp = i;
854 if ((lp>=0)&&(rp>lp)) {
856 strcpy(mailfrom, &mailfrom[lp]);
861 } while (scan_done == 0);
862 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
865 /* Figure out what mail exchanger host we have to connect to */
866 num_mxhosts = getmx(mxhosts, node);
867 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
868 if (num_mxhosts < 1) {
870 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
874 for (mx=0; mx<num_mxhosts; ++mx) {
875 extract(buf, mxhosts, mx);
876 lprintf(9, "Trying <%s>\n", buf);
877 sock = sock_connect(buf, "25", "tcp");
878 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
879 if (sock >= 0) lprintf(9, "Connected!\n");
880 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
881 if (sock >= 0) break;
885 *status = 4; /* dsn is already filled in */
889 /* Process the SMTP greeting from the server */
890 if (ml_sock_gets(sock, buf) < 0) {
892 strcpy(dsn, "Connection broken during SMTP conversation");
895 lprintf(9, "<%s\n", buf);
899 safestrncpy(dsn, &buf[4], 1023);
904 safestrncpy(dsn, &buf[4], 1023);
909 /* At this point we know we are talking to a real SMTP server */
911 /* Do a HELO command */
912 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
913 lprintf(9, ">%s\n", buf);
914 sock_puts_crlf(sock, buf);
915 if (ml_sock_gets(sock, buf) < 0) {
917 strcpy(dsn, "Connection broken during SMTP conversation");
920 lprintf(9, "<%s\n", buf);
924 safestrncpy(dsn, &buf[4], 1023);
929 safestrncpy(dsn, &buf[4], 1023);
935 /* HELO succeeded, now try the MAIL From: command */
936 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
937 lprintf(9, ">%s\n", buf);
938 sock_puts_crlf(sock, buf);
939 if (ml_sock_gets(sock, buf) < 0) {
941 strcpy(dsn, "Connection broken during SMTP conversation");
944 lprintf(9, "<%s\n", buf);
948 safestrncpy(dsn, &buf[4], 1023);
953 safestrncpy(dsn, &buf[4], 1023);
959 /* MAIL succeeded, now try the RCPT To: command */
960 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
961 lprintf(9, ">%s\n", buf);
962 sock_puts_crlf(sock, buf);
963 if (ml_sock_gets(sock, buf) < 0) {
965 strcpy(dsn, "Connection broken during SMTP conversation");
968 lprintf(9, "<%s\n", buf);
972 safestrncpy(dsn, &buf[4], 1023);
977 safestrncpy(dsn, &buf[4], 1023);
983 /* RCPT succeeded, now try the DATA command */
984 lprintf(9, ">DATA\n");
985 sock_puts_crlf(sock, "DATA");
986 if (ml_sock_gets(sock, buf) < 0) {
988 strcpy(dsn, "Connection broken during SMTP conversation");
991 lprintf(9, "<%s\n", buf);
995 safestrncpy(dsn, &buf[4], 1023);
1000 safestrncpy(dsn, &buf[4], 1023);
1005 /* If we reach this point, the server is expecting data */
1007 while (msg_size > 0) {
1008 blocksize = sizeof(buf);
1009 if (blocksize > msg_size) blocksize = msg_size;
1010 fread(buf, blocksize, 1, msg_fp);
1011 sock_write(sock, buf, blocksize);
1012 msg_size -= blocksize;
1014 if (buf[blocksize-1] != 10) {
1015 lprintf(5, "Possible problem: message did not correctly "
1016 "terminate. (expecting 0x10, got 0x%02x)\n",
1020 sock_write(sock, ".\r\n", 3);
1021 if (ml_sock_gets(sock, buf) < 0) {
1023 strcpy(dsn, "Connection broken during SMTP conversation");
1026 lprintf(9, "%s\n", buf);
1027 if (buf[0] != '2') {
1028 if (buf[0] == '4') {
1030 safestrncpy(dsn, &buf[4], 1023);
1035 safestrncpy(dsn, &buf[4], 1023);
1041 safestrncpy(dsn, &buf[4], 1023);
1044 lprintf(9, ">QUIT\n");
1045 sock_puts_crlf(sock, "QUIT");
1046 ml_sock_gets(sock, buf);
1047 lprintf(9, "<%s\n", buf);
1049 bail: if (msg_fp != NULL) fclose(msg_fp);
1057 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1058 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1059 * a "bounce" message (delivery status notification).
1061 void smtp_do_bounce(char *instr) {
1069 char bounceto[1024];
1070 int num_bounces = 0;
1071 int bounce_this = 0;
1072 long bounce_msgid = (-1);
1073 time_t submitted = 0L;
1074 struct CtdlMessage *bmsg = NULL;
1078 lprintf(9, "smtp_do_bounce() called\n");
1079 strcpy(bounceto, "");
1081 lines = num_tokens(instr, '\n');
1084 /* See if it's time to give up on delivery of this message */
1085 for (i=0; i<lines; ++i) {
1086 extract_token(buf, instr, i, '\n');
1087 extract(key, buf, 0);
1088 extract(addr, buf, 1);
1089 if (!strcasecmp(key, "submitted")) {
1090 submitted = atol(addr);
1094 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1100 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1101 if (bmsg == NULL) return;
1102 memset(bmsg, 0, sizeof(struct CtdlMessage));
1104 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1105 bmsg->cm_anon_type = MES_NORMAL;
1106 bmsg->cm_format_type = 1;
1107 bmsg->cm_fields['A'] = strdoop("Citadel");
1108 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1109 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1111 if (give_up) bmsg->cm_fields['M'] = strdoop(
1112 "A message you sent could not be delivered to some or all of its recipients\n"
1113 "due to prolonged unavailability of its destination(s).\n"
1114 "Giving up on the following addresses:\n\n"
1117 else bmsg->cm_fields['M'] = strdoop(
1118 "A message you sent could not be delivered to some or all of its recipients.\n"
1119 "The following addresses were undeliverable:\n\n"
1123 * Now go through the instructions checking for stuff.
1126 for (i=0; i<lines; ++i) {
1127 extract_token(buf, instr, i, '\n');
1128 extract(key, buf, 0);
1129 extract(addr, buf, 1);
1130 status = extract_int(buf, 2);
1131 extract(dsn, buf, 3);
1134 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1135 key, addr, status, dsn);
1137 if (!strcasecmp(key, "bounceto")) {
1138 strcpy(bounceto, addr);
1142 (!strcasecmp(key, "local"))
1143 || (!strcasecmp(key, "remote"))
1144 || (!strcasecmp(key, "ignet"))
1145 || (!strcasecmp(key, "room"))
1147 if (status == 5) bounce_this = 1;
1148 if (give_up) bounce_this = 1;
1154 if (bmsg->cm_fields['M'] == NULL) {
1155 lprintf(2, "ERROR ... M field is null "
1156 "(%s:%d)\n", __FILE__, __LINE__);
1159 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1160 strlen(bmsg->cm_fields['M']) + 1024 );
1161 strcat(bmsg->cm_fields['M'], addr);
1162 strcat(bmsg->cm_fields['M'], ": ");
1163 strcat(bmsg->cm_fields['M'], dsn);
1164 strcat(bmsg->cm_fields['M'], "\n");
1166 remove_token(instr, i, '\n');
1172 /* Deliver the bounce if there's anything worth mentioning */
1173 lprintf(9, "num_bounces = %d\n", num_bounces);
1174 if (num_bounces > 0) {
1176 /* First try the user who sent the message */
1177 lprintf(9, "bounce to user? <%s>\n", bounceto);
1179 if (strlen(bounceto) == 0) {
1180 lprintf(7, "No bounce address specified\n");
1181 bounce_msgid = (-1L);
1183 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1184 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1185 bounce_msgid = (-1L);
1188 bounce_msgid = CtdlSaveMsg(bmsg,
1194 /* Otherwise, go to the Aide> room */
1195 lprintf(9, "bounce to room?\n");
1196 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1201 CtdlFreeMessage(bmsg);
1202 lprintf(9, "Done processing bounces\n");
1207 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1208 * set of delivery instructions for completed deliveries and remove them.
1210 * It returns the number of incomplete deliveries remaining.
1212 int smtp_purge_completed_deliveries(char *instr) {
1223 lines = num_tokens(instr, '\n');
1224 for (i=0; i<lines; ++i) {
1225 extract_token(buf, instr, i, '\n');
1226 extract(key, buf, 0);
1227 extract(addr, buf, 1);
1228 status = extract_int(buf, 2);
1229 extract(dsn, buf, 3);
1234 (!strcasecmp(key, "local"))
1235 || (!strcasecmp(key, "remote"))
1236 || (!strcasecmp(key, "ignet"))
1237 || (!strcasecmp(key, "room"))
1239 if (status == 2) completed = 1;
1244 remove_token(instr, i, '\n');
1257 * Called by smtp_do_queue() to handle an individual message.
1259 void smtp_do_procmsg(long msgnum, void *userdata) {
1260 struct CtdlMessage *msg;
1262 char *results = NULL;
1270 long text_msgid = (-1);
1271 int incomplete_deliveries_remaining;
1272 time_t attempted = 0L;
1273 time_t last_attempted = 0L;
1274 time_t retry = SMTP_RETRY_INTERVAL;
1276 msg = CtdlFetchMessage(msgnum);
1278 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1282 instr = strdoop(msg->cm_fields['M']);
1283 CtdlFreeMessage(msg);
1285 /* Strip out the headers amd any other non-instruction line */
1286 lines = num_tokens(instr, '\n');
1287 for (i=0; i<lines; ++i) {
1288 extract_token(buf, instr, i, '\n');
1289 if (num_tokens(buf, '|') < 2) {
1290 lprintf(9, "removing <%s>\n", buf);
1291 remove_token(instr, i, '\n');
1297 /* Learn the message ID and find out about recent delivery attempts */
1298 lines = num_tokens(instr, '\n');
1299 for (i=0; i<lines; ++i) {
1300 extract_token(buf, instr, i, '\n');
1301 extract(key, buf, 0);
1302 if (!strcasecmp(key, "msgid")) {
1303 text_msgid = extract_long(buf, 1);
1305 if (!strcasecmp(key, "retry")) {
1306 /* double the retry interval after each attempt */
1307 retry = extract_long(buf, 1) * 2L;
1308 remove_token(instr, i, '\n');
1310 if (!strcasecmp(key, "attempted")) {
1311 attempted = extract_long(buf, 1);
1312 if (attempted > last_attempted)
1313 last_attempted = attempted;
1319 * Postpone delivery if we've already tried recently.
1321 if ( (time(NULL) - last_attempted) < retry) {
1322 lprintf(7, "Retry time not yet reached.\n");
1329 * Bail out if there's no actual message associated with this
1331 if (text_msgid < 0L) {
1332 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1337 /* Plow through the instructions looking for 'remote' directives and
1338 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1339 * were experienced and it's time to try again)
1341 lines = num_tokens(instr, '\n');
1342 for (i=0; i<lines; ++i) {
1343 extract_token(buf, instr, i, '\n');
1344 extract(key, buf, 0);
1345 extract(addr, buf, 1);
1346 status = extract_int(buf, 2);
1347 extract(dsn, buf, 3);
1348 if ( (!strcasecmp(key, "remote"))
1349 && ((status==0)||(status==3)) ) {
1350 remove_token(instr, i, '\n');
1353 lprintf(9, "SMTP: Trying <%s>\n", addr);
1354 smtp_try(key, addr, &status, dsn, text_msgid);
1356 if (results == NULL) {
1357 results = mallok(1024);
1358 memset(results, 0, 1024);
1361 results = reallok(results,
1362 strlen(results) + 1024);
1364 sprintf(&results[strlen(results)],
1366 key, addr, status, dsn);
1371 if (results != NULL) {
1372 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1373 strcat(instr, results);
1378 /* Generate 'bounce' messages */
1379 smtp_do_bounce(instr);
1381 /* Go through the delivery list, deleting completed deliveries */
1382 incomplete_deliveries_remaining =
1383 smtp_purge_completed_deliveries(instr);
1387 * No delivery instructions remain, so delete both the instructions
1388 * message and the message message.
1390 if (incomplete_deliveries_remaining <= 0) {
1391 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1392 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1397 * Uncompleted delivery instructions remain, so delete the old
1398 * instructions and replace with the updated ones.
1400 if (incomplete_deliveries_remaining > 0) {
1401 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1402 msg = mallok(sizeof(struct CtdlMessage));
1403 memset(msg, 0, sizeof(struct CtdlMessage));
1404 msg->cm_magic = CTDLMESSAGE_MAGIC;
1405 msg->cm_anon_type = MES_NORMAL;
1406 msg->cm_format_type = FMT_RFC822;
1407 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1408 snprintf(msg->cm_fields['M'],
1410 "Content-type: %s\n\n%s\n"
1413 SPOOLMIME, instr, time(NULL), retry );
1415 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1416 CtdlFreeMessage(msg);
1426 * Run through the queue sending out messages.
1428 void smtp_do_queue(void) {
1429 static int doing_queue = 0;
1432 * This is a simple concurrency check to make sure only one queue run
1433 * is done at a time. We could do this with a mutex, but since we
1434 * don't really require extremely fine granularity here, we'll do it
1435 * with a static variable instead.
1437 if (doing_queue) return;
1441 * Go ahead and run the queue
1443 lprintf(7, "SMTP: processing outbound queue\n");
1445 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1446 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1449 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1450 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1452 lprintf(7, "SMTP: queue run completed\n");
1458 /*****************************************************************************/
1459 /* MODULE INITIALIZATION STUFF */
1460 /*****************************************************************************/
1463 char *Dynamic_Module_Init(void)
1465 SYM_SMTP = CtdlGetDynamicSymbol();
1466 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1468 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1473 CtdlRegisterServiceHook(0, /* ...and locally */
1478 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1479 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);