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))
87 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
91 /*****************************************************************************/
92 /* SMTP SERVER (INBOUND) STUFF */
93 /*****************************************************************************/
99 * Here's where our SMTP session begins its happy day.
101 void smtp_greeting(void) {
103 strcpy(CC->cs_clientname, "SMTP session");
104 CC->internal_pgm = 1;
105 CC->cs_flags |= CS_STEALTH;
106 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
107 CtdlAllocUserData(SYM_SMTP_RECP, SIZ);
108 sprintf(SMTP_RECP, "%s", "");
110 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
115 * Implement HELO and EHLO commands.
117 void smtp_hello(char *argbuf, int is_esmtp) {
119 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
122 cprintf("250 Greetings and joyous salutations.\r\n");
125 cprintf("250-Extended greetings and joyous salutations.\r\n");
126 cprintf("250-HELP\r\n");
127 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
128 cprintf("250-PIPELINING\r\n");
129 cprintf("250 AUTH=LOGIN\r\n");
135 * Implement HELP command.
137 void smtp_help(void) {
138 cprintf("214-Commands accepted:\r\n");
139 cprintf("214- DATA\r\n");
140 cprintf("214- EHLO\r\n");
141 cprintf("214- EXPN\r\n");
142 cprintf("214- HELO\r\n");
143 cprintf("214- HELP\r\n");
144 cprintf("214- MAIL\r\n");
145 cprintf("214- NOOP\r\n");
146 cprintf("214- QUIT\r\n");
147 cprintf("214- RCPT\r\n");
148 cprintf("214- RSET\r\n");
149 cprintf("214- VRFY\r\n");
157 void smtp_get_user(char *argbuf) {
161 decode_base64(username, argbuf);
162 lprintf(9, "Trying <%s>\n", username);
163 if (CtdlLoginExistingUser(username) == login_ok) {
164 encode_base64(buf, "Password:");
165 cprintf("334 %s\r\n", buf);
166 SMTP->command_state = smtp_password;
169 cprintf("500 No such user.\r\n");
170 SMTP->command_state = smtp_command;
178 void smtp_get_pass(char *argbuf) {
181 decode_base64(password, argbuf);
182 lprintf(9, "Trying <%s>\n", password);
183 if (CtdlTryPassword(password) == pass_ok) {
184 cprintf("235 Authentication successful.\r\n");
185 lprintf(9, "SMTP authenticated login successful\n");
186 CC->internal_pgm = 0;
187 CC->cs_flags &= ~CS_STEALTH;
190 cprintf("500 Authentication failed.\r\n");
192 SMTP->command_state = smtp_command;
199 void smtp_auth(char *argbuf) {
202 if (strncasecmp(argbuf, "login", 5) ) {
203 cprintf("550 We only support LOGIN authentication.\r\n");
207 if (strlen(argbuf) >= 7) {
208 smtp_get_user(&argbuf[6]);
212 encode_base64(buf, "Username:");
213 cprintf("334 %s\r\n", buf);
214 SMTP->command_state = smtp_user;
220 * Back end for smtp_vrfy() command
222 void smtp_vrfy_backend(struct usersupp *us, void *data) {
224 if (!fuzzy_match(us, SMTP->vrfy_match)) {
226 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
232 * Implements the VRFY (verify user name) command.
233 * Performs fuzzy match on full user names.
235 void smtp_vrfy(char *argbuf) {
236 SMTP->vrfy_count = 0;
237 strcpy(SMTP->vrfy_match, argbuf);
238 ForEachUser(smtp_vrfy_backend, NULL);
240 if (SMTP->vrfy_count < 1) {
241 cprintf("550 String does not match anything.\r\n");
243 else if (SMTP->vrfy_count == 1) {
244 cprintf("250 %s <cit%ld@%s>\r\n",
245 SMTP->vrfy_buffer.fullname,
246 SMTP->vrfy_buffer.usernum,
249 else if (SMTP->vrfy_count > 1) {
250 cprintf("553 Request ambiguous: %d users matched.\r\n",
259 * Back end for smtp_expn() command
261 void smtp_expn_backend(struct usersupp *us, void *data) {
263 if (!fuzzy_match(us, SMTP->vrfy_match)) {
265 if (SMTP->vrfy_count >= 1) {
266 cprintf("250-%s <cit%ld@%s>\r\n",
267 SMTP->vrfy_buffer.fullname,
268 SMTP->vrfy_buffer.usernum,
273 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
279 * Implements the EXPN (expand user name) command.
280 * Performs fuzzy match on full user names.
282 void smtp_expn(char *argbuf) {
283 SMTP->vrfy_count = 0;
284 strcpy(SMTP->vrfy_match, argbuf);
285 ForEachUser(smtp_expn_backend, NULL);
287 if (SMTP->vrfy_count < 1) {
288 cprintf("550 String does not match anything.\r\n");
290 else if (SMTP->vrfy_count >= 1) {
291 cprintf("250 %s <cit%ld@%s>\r\n",
292 SMTP->vrfy_buffer.fullname,
293 SMTP->vrfy_buffer.usernum,
300 * Implements the RSET (reset state) command.
301 * Currently this just zeroes out the state buffer. If pointers to data
302 * allocated with mallok() are ever placed in the state buffer, we have to
303 * be sure to phree() them first!
305 void smtp_rset(void) {
306 memset(SMTP, 0, sizeof(struct citsmtp));
307 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
308 if (CC->logged_in) logout(CC);
309 cprintf("250 Zap!\r\n");
313 * Clear out the portions of the state buffer that need to be cleared out
314 * after the DATA command finishes.
316 void smtp_data_clear(void) {
317 strcpy(SMTP->from, "");
318 SMTP->number_of_recipients = 0;
319 SMTP->delivery_mode = 0;
320 SMTP->message_originated_locally = 0;
321 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
327 * Implements the "MAIL From:" command
329 void smtp_mail(char *argbuf) {
334 if (strlen(SMTP->from) != 0) {
335 cprintf("503 Only one sender permitted\r\n");
339 if (strncasecmp(argbuf, "From:", 5)) {
340 cprintf("501 Syntax error\r\n");
344 strcpy(SMTP->from, &argbuf[5]);
347 if (strlen(SMTP->from) == 0) {
348 cprintf("501 Empty sender name is not permitted\r\n");
353 /* If this SMTP connection is from a logged-in user, make sure that
354 * the user only sends email from his/her own address.
357 cvt = convert_internet_address(user, node, SMTP->from);
358 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
359 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
360 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
361 strcpy(SMTP->from, "");
365 SMTP->message_originated_locally = 1;
369 /* Otherwise, make sure outsiders aren't trying to forge mail from
374 cvt = convert_internet_address(user, node, SMTP->from);
376 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
377 if (CtdlHostAlias(node) == hostalias_localhost) {
379 cprintf("550 You must log in to send mail from %s\r\n",
381 strcpy(SMTP->from, "");
386 cprintf("250 Sender ok\r\n");
392 * Implements the "RCPT To:" command
394 void smtp_rcpt(char *argbuf) {
400 if (strlen(SMTP->from) == 0) {
401 cprintf("503 Need MAIL before RCPT\r\n");
405 if (strncasecmp(argbuf, "To:", 3)) {
406 cprintf("501 Syntax error\r\n");
410 strcpy(recp, &argbuf[3]);
416 cvt = convert_internet_address(user, node, recp);
417 snprintf(recp, sizeof recp, "%s@%s", user, node);
418 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
421 case rfc822_address_locally_validated:
422 cprintf("250 %s is a valid recipient.\r\n", user);
423 ++SMTP->number_of_recipients;
424 CtdlReallocUserData(SYM_SMTP_RECP,
425 strlen(SMTP_RECP) + 1024 );
426 strcat(SMTP_RECP, "local|");
427 strcat(SMTP_RECP, user);
428 strcat(SMTP_RECP, "|0\n");
431 case rfc822_room_delivery:
432 cprintf("250 Delivering to room '%s'\r\n", user);
433 ++SMTP->number_of_recipients;
434 CtdlReallocUserData(SYM_SMTP_RECP,
435 strlen(SMTP_RECP) + 1024 );
436 strcat(SMTP_RECP, "room|");
437 strcat(SMTP_RECP, user);
438 strcat(SMTP_RECP, "|0|\n");
441 case rfc822_no_such_user:
442 cprintf("550 %s: no such user\r\n", recp);
445 case rfc822_address_on_citadel_network:
446 cprintf("250 %s is on the local network\r\n", recp);
447 ++SMTP->number_of_recipients;
448 CtdlReallocUserData(SYM_SMTP_RECP,
449 strlen(SMTP_RECP) + 1024 );
450 strcat(SMTP_RECP, "ignet|");
451 strcat(SMTP_RECP, user);
452 strcat(SMTP_RECP, "|");
453 strcat(SMTP_RECP, node);
454 strcat(SMTP_RECP, "|0|\n");
457 case rfc822_address_nonlocal:
458 if (SMTP->message_originated_locally == 0) {
459 cprintf("551 Third-party relaying denied.\r\n");
462 cprintf("250 Remote recipient %s ok\r\n", recp);
463 ++SMTP->number_of_recipients;
464 CtdlReallocUserData(SYM_SMTP_RECP,
465 strlen(SMTP_RECP) + 1024 );
466 strcat(SMTP_RECP, "remote|");
467 strcat(SMTP_RECP, recp);
468 strcat(SMTP_RECP, "|0|\n");
474 cprintf("599 Unknown error\r\n");
480 * Send a message out through the local network
481 * (This is kind of ugly. IGnet should be done using clean server-to-server
482 * code instead of the old style spool.)
484 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
486 char *hold_R, *hold_D, *hold_O;
491 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
493 hold_R = msg->cm_fields['R'];
494 hold_D = msg->cm_fields['D'];
495 hold_O = msg->cm_fields['O'];
496 msg->cm_fields['R'] = user;
497 msg->cm_fields['D'] = dest;
498 msg->cm_fields['O'] = MAILROOM;
500 serialize_message(&smr, msg);
502 msg->cm_fields['R'] = hold_R;
503 msg->cm_fields['D'] = hold_D;
504 msg->cm_fields['O'] = hold_O;
507 snprintf(filename, sizeof filename,
508 "./network/spoolin/%s.%04x.%04x",
509 dest, getpid(), ++seq);
510 lprintf(9, "spool file name is <%s>\n", filename);
511 fp = fopen(filename, "wb");
513 fwrite(smr.ser, smr.len, 1, fp);
524 * Back end for smtp_data() ... this does the actual delivery of the message
525 * Returns 0 on success, nonzero on failure
527 int smtp_message_delivery(struct CtdlMessage *msg) {
534 int successful_saves = 0; /* number of successful local saves */
535 int failed_saves = 0; /* number of failed deliveries */
536 int remote_spools = 0; /* number of copies to send out */
539 struct usersupp userbuf;
540 char *instr; /* Remote delivery instructions */
541 struct CtdlMessage *imsg;
543 lprintf(9, "smtp_message_delivery() called\n");
545 /* Fill in 'from' fields with envelope information if missing */
546 process_rfc822_addr(SMTP->from, user, node, name);
547 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
548 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
549 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
550 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
552 /* Save the message in the queue */
553 msgid = CtdlSaveMsg(msg,
559 instr = mallok(1024);
560 snprintf(instr, 1024,
561 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
563 SPOOLMIME, msgid, (long)time(NULL),
566 for (i=0; i<SMTP->number_of_recipients; ++i) {
567 extract_token(buf, SMTP_RECP, i, '\n');
568 extract(dtype, buf, 0);
570 /* Stuff local mailboxes */
571 if (!strcasecmp(dtype, "local")) {
572 extract(user, buf, 1);
573 if (getuser(&userbuf, user) == 0) {
574 MailboxName(room, &userbuf, MAILROOM);
575 CtdlSaveMsgPointerInRoom(room, msgid, 0);
583 /* Delivery to local non-mailbox rooms */
584 if (!strcasecmp(dtype, "room")) {
585 extract(room, buf, 1);
586 CtdlSaveMsgPointerInRoom(room, msgid, 0);
590 /* Delivery over the local Citadel network (IGnet) */
591 if (!strcasecmp(dtype, "ignet")) {
592 extract(user, buf, 1);
593 extract(node, buf, 2);
594 smtp_deliver_ignet(msg, user, node);
597 /* Remote delivery */
598 if (!strcasecmp(dtype, "remote")) {
599 extract(user, buf, 1);
600 instr = reallok(instr, strlen(instr) + 1024);
601 snprintf(&instr[strlen(instr)],
602 strlen(instr) + 1024,
610 /* If there are remote spools to be done, save the instructions */
611 if (remote_spools > 0) {
612 imsg = mallok(sizeof(struct CtdlMessage));
613 memset(imsg, 0, sizeof(struct CtdlMessage));
614 imsg->cm_magic = CTDLMESSAGE_MAGIC;
615 imsg->cm_anon_type = MES_NORMAL;
616 imsg->cm_format_type = FMT_RFC822;
617 imsg->cm_fields['M'] = instr;
618 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
619 CtdlFreeMessage(imsg);
622 /* If there are no remote spools, delete the message */
624 phree(instr); /* only needed here, because CtdlSaveMsg()
625 * would free this buffer otherwise */
626 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
629 return(failed_saves);
635 * Implements the DATA command
637 void smtp_data(void) {
639 struct CtdlMessage *msg;
643 if (strlen(SMTP->from) == 0) {
644 cprintf("503 Need MAIL command first.\r\n");
648 if (SMTP->number_of_recipients < 1) {
649 cprintf("503 Need RCPT command first.\r\n");
653 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
655 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
658 if (body != NULL) snprintf(body, 4096,
659 "Received: from %s\n"
666 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
668 cprintf("550 Unable to save message text: internal error.\r\n");
672 lprintf(9, "Converting message...\n");
673 msg = convert_internet_message(body);
675 /* If the user is locally authenticated, FORCE the From: header to
676 * show up as the real sender
679 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
680 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
681 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
682 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
683 msg->cm_fields['N'] = strdoop(config.c_nodename);
684 msg->cm_fields['H'] = strdoop(config.c_humannode);
687 retval = smtp_message_delivery(msg);
688 CtdlFreeMessage(msg);
691 cprintf("250 ok terrific\r\n");
694 cprintf("550 Internal delivery errors: %d\r\n", retval);
697 smtp_data_clear(); /* clear out the buffers now */
704 * Main command loop for SMTP sessions.
706 void smtp_command_loop(void) {
710 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
711 if (client_gets(cmdbuf) < 1) {
712 lprintf(3, "SMTP socket is broken. Ending session.\n");
716 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
717 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
719 if (SMTP->command_state == smtp_user) {
720 smtp_get_user(cmdbuf);
723 else if (SMTP->command_state == smtp_password) {
724 smtp_get_pass(cmdbuf);
727 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
728 smtp_auth(&cmdbuf[5]);
731 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
735 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
736 smtp_hello(&cmdbuf[5], 1);
739 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
740 smtp_expn(&cmdbuf[5]);
743 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
744 smtp_hello(&cmdbuf[5], 0);
747 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
751 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
752 smtp_mail(&cmdbuf[5]);
755 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
756 cprintf("250 NOOP\r\n");
759 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
760 cprintf("221 Goodbye...\r\n");
765 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
766 smtp_rcpt(&cmdbuf[5]);
769 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
773 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
774 smtp_vrfy(&cmdbuf[5]);
778 cprintf("502 I'm afraid I can't do that.\r\n");
786 /*****************************************************************************/
787 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
788 /*****************************************************************************/
795 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
798 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
805 char user[SIZ], node[SIZ], name[SIZ];
811 size_t blocksize = 0;
814 /* Parse out the host portion of the recipient address */
815 process_rfc822_addr(addr, user, node, name);
817 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
820 /* Load the message out of the database into a temp file */
822 if (msg_fp == NULL) {
824 sprintf(dsn, "Error creating temporary file");
828 CtdlRedirectOutput(msg_fp, -1);
829 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
830 CtdlRedirectOutput(NULL, -1);
831 fseek(msg_fp, 0L, SEEK_END);
832 msg_size = ftell(msg_fp);
836 /* Extract something to send later in the 'MAIL From:' command */
837 strcpy(mailfrom, "");
841 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
842 if (!strncasecmp(buf, "From:", 5)) {
843 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
845 for (i=0; i<strlen(mailfrom); ++i) {
846 if (!isprint(mailfrom[i])) {
847 strcpy(&mailfrom[i], &mailfrom[i+1]);
852 /* Strip out parenthesized names */
855 for (i=0; i<strlen(mailfrom); ++i) {
856 if (mailfrom[i] == '(') lp = i;
857 if (mailfrom[i] == ')') rp = i;
859 if ((lp>0)&&(rp>lp)) {
860 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
863 /* Prefer brokketized names */
866 for (i=0; i<strlen(mailfrom); ++i) {
867 if (mailfrom[i] == '<') lp = i;
868 if (mailfrom[i] == '>') rp = i;
870 if ((lp>=0)&&(rp>lp)) {
872 strcpy(mailfrom, &mailfrom[lp]);
877 } while (scan_done == 0);
878 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
881 /* Figure out what mail exchanger host we have to connect to */
882 num_mxhosts = getmx(mxhosts, node);
883 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
884 if (num_mxhosts < 1) {
886 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
890 for (mx=0; mx<num_mxhosts; ++mx) {
891 extract(buf, mxhosts, mx);
892 lprintf(9, "Trying <%s>\n", buf);
893 sock = sock_connect(buf, "25", "tcp");
894 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
895 if (sock >= 0) lprintf(9, "Connected!\n");
896 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
897 if (sock >= 0) break;
901 *status = 4; /* dsn is already filled in */
905 /* Process the SMTP greeting from the server */
906 if (ml_sock_gets(sock, buf) < 0) {
908 strcpy(dsn, "Connection broken during SMTP conversation");
911 lprintf(9, "<%s\n", buf);
915 safestrncpy(dsn, &buf[4], 1023);
920 safestrncpy(dsn, &buf[4], 1023);
925 /* At this point we know we are talking to a real SMTP server */
927 /* Do a HELO command */
928 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
929 lprintf(9, ">%s", buf);
930 sock_write(sock, buf, strlen(buf));
931 if (ml_sock_gets(sock, buf) < 0) {
933 strcpy(dsn, "Connection broken during SMTP HELO");
936 lprintf(9, "<%s\n", buf);
940 safestrncpy(dsn, &buf[4], 1023);
945 safestrncpy(dsn, &buf[4], 1023);
951 /* HELO succeeded, now try the MAIL From: command */
952 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
953 lprintf(9, ">%s", buf);
954 sock_write(sock, buf, strlen(buf));
955 if (ml_sock_gets(sock, buf) < 0) {
957 strcpy(dsn, "Connection broken during SMTP MAIL");
960 lprintf(9, "<%s\n", buf);
964 safestrncpy(dsn, &buf[4], 1023);
969 safestrncpy(dsn, &buf[4], 1023);
975 /* MAIL succeeded, now try the RCPT To: command */
976 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
977 lprintf(9, ">%s", buf);
978 sock_write(sock, buf, strlen(buf));
979 if (ml_sock_gets(sock, buf) < 0) {
981 strcpy(dsn, "Connection broken during SMTP RCPT");
984 lprintf(9, "<%s\n", buf);
988 safestrncpy(dsn, &buf[4], 1023);
993 safestrncpy(dsn, &buf[4], 1023);
999 /* RCPT succeeded, now try the DATA command */
1000 lprintf(9, ">DATA\n");
1001 sock_write(sock, "DATA\r\n", 6);
1002 if (ml_sock_gets(sock, buf) < 0) {
1004 strcpy(dsn, "Connection broken during SMTP DATA");
1007 lprintf(9, "<%s\n", buf);
1008 if (buf[0] != '3') {
1009 if (buf[0] == '4') {
1011 safestrncpy(dsn, &buf[4], 1023);
1016 safestrncpy(dsn, &buf[4], 1023);
1021 /* If we reach this point, the server is expecting data */
1023 while (msg_size > 0) {
1024 blocksize = sizeof(buf);
1025 if (blocksize > msg_size) blocksize = msg_size;
1026 fread(buf, blocksize, 1, msg_fp);
1027 sock_write(sock, buf, blocksize);
1028 msg_size -= blocksize;
1030 if (buf[blocksize-1] != 10) {
1031 lprintf(5, "Possible problem: message did not correctly "
1032 "terminate. (expecting 0x10, got 0x%02x)\n",
1036 sock_write(sock, ".\r\n", 3);
1037 if (ml_sock_gets(sock, buf) < 0) {
1039 strcpy(dsn, "Connection broken during SMTP message transmit");
1042 lprintf(9, "%s\n", buf);
1043 if (buf[0] != '2') {
1044 if (buf[0] == '4') {
1046 safestrncpy(dsn, &buf[4], 1023);
1051 safestrncpy(dsn, &buf[4], 1023);
1057 safestrncpy(dsn, &buf[4], 1023);
1060 lprintf(9, ">QUIT\n");
1061 sock_write(sock, "QUIT\r\n", 6);
1062 ml_sock_gets(sock, buf);
1063 lprintf(9, "<%s\n", buf);
1065 bail: if (msg_fp != NULL) fclose(msg_fp);
1073 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1074 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1075 * a "bounce" message (delivery status notification).
1077 void smtp_do_bounce(char *instr) {
1085 char bounceto[1024];
1086 int num_bounces = 0;
1087 int bounce_this = 0;
1088 long bounce_msgid = (-1);
1089 time_t submitted = 0L;
1090 struct CtdlMessage *bmsg = NULL;
1094 lprintf(9, "smtp_do_bounce() called\n");
1095 strcpy(bounceto, "");
1097 lines = num_tokens(instr, '\n');
1100 /* See if it's time to give up on delivery of this message */
1101 for (i=0; i<lines; ++i) {
1102 extract_token(buf, instr, i, '\n');
1103 extract(key, buf, 0);
1104 extract(addr, buf, 1);
1105 if (!strcasecmp(key, "submitted")) {
1106 submitted = atol(addr);
1110 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1116 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1117 if (bmsg == NULL) return;
1118 memset(bmsg, 0, sizeof(struct CtdlMessage));
1120 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1121 bmsg->cm_anon_type = MES_NORMAL;
1122 bmsg->cm_format_type = 1;
1123 bmsg->cm_fields['A'] = strdoop("Citadel");
1124 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1125 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1127 if (give_up) bmsg->cm_fields['M'] = strdoop(
1128 "A message you sent could not be delivered to some or all of its recipients\n"
1129 "due to prolonged unavailability of its destination(s).\n"
1130 "Giving up on the following addresses:\n\n"
1133 else bmsg->cm_fields['M'] = strdoop(
1134 "A message you sent could not be delivered to some or all of its recipients.\n"
1135 "The following addresses were undeliverable:\n\n"
1139 * Now go through the instructions checking for stuff.
1142 for (i=0; i<lines; ++i) {
1143 extract_token(buf, instr, i, '\n');
1144 extract(key, buf, 0);
1145 extract(addr, buf, 1);
1146 status = extract_int(buf, 2);
1147 extract(dsn, buf, 3);
1150 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1151 key, addr, status, dsn);
1153 if (!strcasecmp(key, "bounceto")) {
1154 strcpy(bounceto, addr);
1158 (!strcasecmp(key, "local"))
1159 || (!strcasecmp(key, "remote"))
1160 || (!strcasecmp(key, "ignet"))
1161 || (!strcasecmp(key, "room"))
1163 if (status == 5) bounce_this = 1;
1164 if (give_up) bounce_this = 1;
1170 if (bmsg->cm_fields['M'] == NULL) {
1171 lprintf(2, "ERROR ... M field is null "
1172 "(%s:%d)\n", __FILE__, __LINE__);
1175 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1176 strlen(bmsg->cm_fields['M']) + 1024 );
1177 strcat(bmsg->cm_fields['M'], addr);
1178 strcat(bmsg->cm_fields['M'], ": ");
1179 strcat(bmsg->cm_fields['M'], dsn);
1180 strcat(bmsg->cm_fields['M'], "\n");
1182 remove_token(instr, i, '\n');
1188 /* Deliver the bounce if there's anything worth mentioning */
1189 lprintf(9, "num_bounces = %d\n", num_bounces);
1190 if (num_bounces > 0) {
1192 /* First try the user who sent the message */
1193 lprintf(9, "bounce to user? <%s>\n", bounceto);
1195 if (strlen(bounceto) == 0) {
1196 lprintf(7, "No bounce address specified\n");
1197 bounce_msgid = (-1L);
1199 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1200 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1201 bounce_msgid = (-1L);
1204 bounce_msgid = CtdlSaveMsg(bmsg,
1210 /* Otherwise, go to the Aide> room */
1211 lprintf(9, "bounce to room?\n");
1212 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1217 CtdlFreeMessage(bmsg);
1218 lprintf(9, "Done processing bounces\n");
1223 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1224 * set of delivery instructions for completed deliveries and remove them.
1226 * It returns the number of incomplete deliveries remaining.
1228 int smtp_purge_completed_deliveries(char *instr) {
1239 lines = num_tokens(instr, '\n');
1240 for (i=0; i<lines; ++i) {
1241 extract_token(buf, instr, i, '\n');
1242 extract(key, buf, 0);
1243 extract(addr, buf, 1);
1244 status = extract_int(buf, 2);
1245 extract(dsn, buf, 3);
1250 (!strcasecmp(key, "local"))
1251 || (!strcasecmp(key, "remote"))
1252 || (!strcasecmp(key, "ignet"))
1253 || (!strcasecmp(key, "room"))
1255 if (status == 2) completed = 1;
1260 remove_token(instr, i, '\n');
1273 * Called by smtp_do_queue() to handle an individual message.
1275 void smtp_do_procmsg(long msgnum, void *userdata) {
1276 struct CtdlMessage *msg;
1278 char *results = NULL;
1286 long text_msgid = (-1);
1287 int incomplete_deliveries_remaining;
1288 time_t attempted = 0L;
1289 time_t last_attempted = 0L;
1290 time_t retry = SMTP_RETRY_INTERVAL;
1292 msg = CtdlFetchMessage(msgnum);
1294 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1298 instr = strdoop(msg->cm_fields['M']);
1299 CtdlFreeMessage(msg);
1301 /* Strip out the headers amd any other non-instruction line */
1302 lines = num_tokens(instr, '\n');
1303 for (i=0; i<lines; ++i) {
1304 extract_token(buf, instr, i, '\n');
1305 if (num_tokens(buf, '|') < 2) {
1306 lprintf(9, "removing <%s>\n", buf);
1307 remove_token(instr, i, '\n');
1313 /* Learn the message ID and find out about recent delivery attempts */
1314 lines = num_tokens(instr, '\n');
1315 for (i=0; i<lines; ++i) {
1316 extract_token(buf, instr, i, '\n');
1317 extract(key, buf, 0);
1318 if (!strcasecmp(key, "msgid")) {
1319 text_msgid = extract_long(buf, 1);
1321 if (!strcasecmp(key, "retry")) {
1322 /* double the retry interval after each attempt */
1323 retry = extract_long(buf, 1) * 2L;
1324 if (retry > SMTP_RETRY_MAX) {
1325 retry = SMTP_RETRY_MAX;
1327 remove_token(instr, i, '\n');
1329 if (!strcasecmp(key, "attempted")) {
1330 attempted = extract_long(buf, 1);
1331 if (attempted > last_attempted)
1332 last_attempted = attempted;
1337 * Postpone delivery if we've already tried recently.
1339 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1340 lprintf(7, "Retry time not yet reached.\n");
1347 * Bail out if there's no actual message associated with this
1349 if (text_msgid < 0L) {
1350 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1355 /* Plow through the instructions looking for 'remote' directives and
1356 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1357 * were experienced and it's time to try again)
1359 lines = num_tokens(instr, '\n');
1360 for (i=0; i<lines; ++i) {
1361 extract_token(buf, instr, i, '\n');
1362 extract(key, buf, 0);
1363 extract(addr, buf, 1);
1364 status = extract_int(buf, 2);
1365 extract(dsn, buf, 3);
1366 if ( (!strcasecmp(key, "remote"))
1367 && ((status==0)||(status==3)) ) {
1368 remove_token(instr, i, '\n');
1371 lprintf(9, "SMTP: Trying <%s>\n", addr);
1372 smtp_try(key, addr, &status, dsn, text_msgid);
1374 if (results == NULL) {
1375 results = mallok(1024);
1376 memset(results, 0, 1024);
1379 results = reallok(results,
1380 strlen(results) + 1024);
1382 sprintf(&results[strlen(results)],
1384 key, addr, status, dsn);
1389 if (results != NULL) {
1390 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1391 strcat(instr, results);
1396 /* Generate 'bounce' messages */
1397 smtp_do_bounce(instr);
1399 /* Go through the delivery list, deleting completed deliveries */
1400 incomplete_deliveries_remaining =
1401 smtp_purge_completed_deliveries(instr);
1405 * No delivery instructions remain, so delete both the instructions
1406 * message and the message message.
1408 if (incomplete_deliveries_remaining <= 0) {
1409 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1410 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1415 * Uncompleted delivery instructions remain, so delete the old
1416 * instructions and replace with the updated ones.
1418 if (incomplete_deliveries_remaining > 0) {
1419 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1420 msg = mallok(sizeof(struct CtdlMessage));
1421 memset(msg, 0, sizeof(struct CtdlMessage));
1422 msg->cm_magic = CTDLMESSAGE_MAGIC;
1423 msg->cm_anon_type = MES_NORMAL;
1424 msg->cm_format_type = FMT_RFC822;
1425 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1426 snprintf(msg->cm_fields['M'],
1428 "Content-type: %s\n\n%s\n"
1431 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1433 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1434 CtdlFreeMessage(msg);
1444 * Run through the queue sending out messages.
1446 void smtp_do_queue(void) {
1447 static int doing_queue = 0;
1450 * This is a simple concurrency check to make sure only one queue run
1451 * is done at a time. We could do this with a mutex, but since we
1452 * don't really require extremely fine granularity here, we'll do it
1453 * with a static variable instead.
1455 if (doing_queue) return;
1459 * Go ahead and run the queue
1461 lprintf(7, "SMTP: processing outbound queue\n");
1463 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1464 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1467 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1468 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1470 lprintf(7, "SMTP: queue run completed\n");
1477 /*****************************************************************************/
1478 /* SMTP UTILITY COMMANDS */
1479 /*****************************************************************************/
1481 void cmd_smtp(char *argbuf) {
1488 if (CtdlAccessCheck(ac_aide)) return;
1490 extract(cmd, argbuf, 0);
1492 if (!strcasecmp(cmd, "mx")) {
1493 extract(node, argbuf, 1);
1494 num_mxhosts = getmx(buf, node);
1495 cprintf("%d %d MX hosts listed for %s\n",
1496 LISTING_FOLLOWS, num_mxhosts, node);
1497 for (i=0; i<num_mxhosts; ++i) {
1498 extract(node, buf, i);
1499 cprintf("%s\n", node);
1505 else if (!strcasecmp(cmd, "runqueue")) {
1507 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1512 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1520 /*****************************************************************************/
1521 /* MODULE INITIALIZATION STUFF */
1522 /*****************************************************************************/
1525 char *Dynamic_Module_Init(void)
1527 SYM_SMTP = CtdlGetDynamicSymbol();
1528 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1530 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1535 CtdlRegisterServiceHook(0, /* ...and locally */
1540 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1541 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1542 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");