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;
68 int message_originated_locally;
71 enum { /* Command states for login authentication */
77 enum { /* Delivery modes */
82 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
83 #define SMTP_RECPS ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
84 #define SMTP_ROOMS ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
90 int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */
94 /*****************************************************************************/
95 /* SMTP SERVER (INBOUND) STUFF */
96 /*****************************************************************************/
102 * Here's where our SMTP session begins its happy day.
104 void smtp_greeting(void) {
106 strcpy(CC->cs_clientname, "SMTP session");
107 CC->internal_pgm = 1;
108 CC->cs_flags |= CS_STEALTH;
109 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
110 CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
111 CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
112 sprintf(SMTP_RECPS, "%s", "");
113 sprintf(SMTP_ROOMS, "%s", "");
115 cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
120 * Implement HELO and EHLO commands.
122 void smtp_hello(char *argbuf, int is_esmtp) {
124 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
127 cprintf("250 Greetings and joyous salutations.\r\n");
130 cprintf("250-Greetings and joyous salutations.\r\n");
131 cprintf("250-HELP\r\n");
132 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
133 cprintf("250-PIPELINING\r\n");
134 cprintf("250 AUTH=LOGIN\r\n");
140 * Implement HELP command.
142 void smtp_help(void) {
143 cprintf("214-Commands accepted:\r\n");
144 cprintf("214- DATA\r\n");
145 cprintf("214- EHLO\r\n");
146 cprintf("214- EXPN\r\n");
147 cprintf("214- HELO\r\n");
148 cprintf("214- HELP\r\n");
149 cprintf("214- MAIL\r\n");
150 cprintf("214- NOOP\r\n");
151 cprintf("214- QUIT\r\n");
152 cprintf("214- RCPT\r\n");
153 cprintf("214- RSET\r\n");
154 cprintf("214- VRFY\r\n");
162 void smtp_get_user(char *argbuf) {
166 decode_base64(username, argbuf, SIZ);
167 lprintf(9, "Trying <%s>\n", username);
168 if (CtdlLoginExistingUser(username) == login_ok) {
169 encode_base64(buf, "Password:");
170 cprintf("334 %s\r\n", buf);
171 SMTP->command_state = smtp_password;
174 cprintf("500 No such user.\r\n");
175 SMTP->command_state = smtp_command;
183 void smtp_get_pass(char *argbuf) {
186 decode_base64(password, argbuf, SIZ);
187 lprintf(9, "Trying <%s>\n", password);
188 if (CtdlTryPassword(password) == pass_ok) {
189 cprintf("235 Authentication successful.\r\n");
190 lprintf(9, "SMTP authenticated login successful\n");
191 CC->internal_pgm = 0;
192 CC->cs_flags &= ~CS_STEALTH;
195 cprintf("500 Authentication failed.\r\n");
197 SMTP->command_state = smtp_command;
204 void smtp_auth(char *argbuf) {
207 if (strncasecmp(argbuf, "login", 5) ) {
208 cprintf("550 We only support LOGIN authentication.\r\n");
212 if (strlen(argbuf) >= 7) {
213 smtp_get_user(&argbuf[6]);
217 encode_base64(buf, "Username:");
218 cprintf("334 %s\r\n", buf);
219 SMTP->command_state = smtp_user;
225 * Back end for smtp_vrfy() command
227 void smtp_vrfy_backend(struct usersupp *us, void *data) {
229 if (!fuzzy_match(us, SMTP->vrfy_match)) {
231 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
237 * Implements the VRFY (verify user name) command.
238 * Performs fuzzy match on full user names.
240 void smtp_vrfy(char *argbuf) {
241 SMTP->vrfy_count = 0;
242 strcpy(SMTP->vrfy_match, argbuf);
243 ForEachUser(smtp_vrfy_backend, NULL);
245 if (SMTP->vrfy_count < 1) {
246 cprintf("550 String does not match anything.\r\n");
248 else if (SMTP->vrfy_count == 1) {
249 cprintf("250 %s <cit%ld@%s>\r\n",
250 SMTP->vrfy_buffer.fullname,
251 SMTP->vrfy_buffer.usernum,
254 else if (SMTP->vrfy_count > 1) {
255 cprintf("553 Request ambiguous: %d users matched.\r\n",
264 * Back end for smtp_expn() command
266 void smtp_expn_backend(struct usersupp *us, void *data) {
268 if (!fuzzy_match(us, SMTP->vrfy_match)) {
270 if (SMTP->vrfy_count >= 1) {
271 cprintf("250-%s <cit%ld@%s>\r\n",
272 SMTP->vrfy_buffer.fullname,
273 SMTP->vrfy_buffer.usernum,
278 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
284 * Implements the EXPN (expand user name) command.
285 * Performs fuzzy match on full user names.
287 void smtp_expn(char *argbuf) {
288 SMTP->vrfy_count = 0;
289 strcpy(SMTP->vrfy_match, argbuf);
290 ForEachUser(smtp_expn_backend, NULL);
292 if (SMTP->vrfy_count < 1) {
293 cprintf("550 String does not match anything.\r\n");
295 else if (SMTP->vrfy_count >= 1) {
296 cprintf("250 %s <cit%ld@%s>\r\n",
297 SMTP->vrfy_buffer.fullname,
298 SMTP->vrfy_buffer.usernum,
305 * Implements the RSET (reset state) command.
306 * Currently this just zeroes out the state buffer. If pointers to data
307 * allocated with mallok() are ever placed in the state buffer, we have to
308 * be sure to phree() them first!
310 void smtp_rset(void) {
311 memset(SMTP, 0, sizeof(struct citsmtp));
312 if (SMTP_RECPS != NULL) strcpy(SMTP_RECPS, "");
313 if (SMTP_ROOMS != NULL) strcpy(SMTP_ROOMS, "");
314 if (CC->logged_in) logout(CC);
315 cprintf("250 Zap!\r\n");
319 * Clear out the portions of the state buffer that need to be cleared out
320 * after the DATA command finishes.
322 void smtp_data_clear(void) {
323 strcpy(SMTP->from, "");
324 SMTP->number_of_recipients = 0;
325 SMTP->number_of_rooms = 0;
326 SMTP->delivery_mode = 0;
327 SMTP->message_originated_locally = 0;
328 if (SMTP_RECPS != NULL) strcpy(SMTP_RECPS, "");
329 if (SMTP_ROOMS != NULL) strcpy(SMTP_ROOMS, "");
335 * Implements the "MAIL From:" command
337 void smtp_mail(char *argbuf) {
342 if (strlen(SMTP->from) != 0) {
343 cprintf("503 Only one sender permitted\r\n");
347 if (strncasecmp(argbuf, "From:", 5)) {
348 cprintf("501 Syntax error\r\n");
352 strcpy(SMTP->from, &argbuf[5]);
355 if (strlen(SMTP->from) == 0) {
356 cprintf("501 Empty sender name is not permitted\r\n");
361 /* If this SMTP connection is from a logged-in user, make sure that
362 * the user only sends email from his/her own address.
365 cvt = convert_internet_address(user, node, SMTP->from);
366 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
367 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
368 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
369 strcpy(SMTP->from, "");
373 SMTP->message_originated_locally = 1;
377 /* Otherwise, make sure outsiders aren't trying to forge mail from
381 cvt = convert_internet_address(user, node, SMTP->from);
382 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
383 if (CtdlHostAlias(node) == hostalias_localhost) {
384 cprintf("550 You must log in to send mail from %s\r\n",
386 strcpy(SMTP->from, "");
391 cprintf("250 Sender ok\r\n");
397 * Implements the "RCPT To:" command
399 void smtp_rcpt(char *argbuf) {
405 if (strlen(SMTP->from) == 0) {
406 cprintf("503 Need MAIL before RCPT\r\n");
410 if (strncasecmp(argbuf, "To:", 3)) {
411 cprintf("501 Syntax error\r\n");
415 strcpy(recp, &argbuf[3]);
419 cvt = convert_internet_address(user, node, recp);
420 snprintf(recp, sizeof recp, "%s@%s", user, node);
421 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
424 case rfc822_address_locally_validated:
425 case rfc822_address_on_citadel_network:
426 cprintf("250 %s is a valid recipient.\r\n", user);
427 CtdlReallocUserData(SYM_SMTP_RECPS,
428 strlen(SMTP_RECPS) + 1024 );
429 if (strlen(SMTP_RECPS) > 0) {
430 strcat(SMTP_RECPS, "|");
432 strcat(SMTP_RECPS, user);
435 case rfc822_room_delivery:
436 cprintf("250 Delivering to room '%s'\r\n", user);
437 CtdlReallocUserData(SYM_SMTP_ROOMS,
438 strlen(SMTP_ROOMS) + 1024 );
439 if (strlen(SMTP_ROOMS) > 0) {
440 strcat(SMTP_ROOMS, "|");
442 strcat(SMTP_RECPS, user);
445 case rfc822_no_such_user:
446 cprintf("550 %s: no such user\r\n", recp);
449 case rfc822_address_nonlocal:
450 if (SMTP->message_originated_locally == 0) {
451 cprintf("551 Relaying denied.\r\n");
454 cprintf("250 Remote recipient %s ok\r\n", recp);
455 CtdlReallocUserData(SYM_SMTP_RECPS,
456 strlen(SMTP_RECPS) + 1024 );
457 if (strlen(SMTP_RECPS) > 0) {
458 strcat(SMTP_RECPS, "|");
460 strcat(SMTP_RECPS, user);
466 cprintf("599 Unknown error\r\n");
472 * Back end for smtp_data() ... this does the actual delivery of the message
473 * Returns 0 on success, nonzero on failure
475 int smtp_message_delivery(struct CtdlMessage *msg) {
482 int successful_saves = 0; /* number of successful local saves */
483 int failed_saves = 0; /* number of failed deliveries */
484 int remote_spools = 0; /* number of copies to send out */
487 struct usersupp userbuf;
488 char *instr; /* Remote delivery instructions */
489 struct CtdlMessage *imsg;
490 struct recptypes *valid;
492 lprintf(9, "smtp_message_delivery() called\n");
494 /* Fill in 'from' fields with envelope information if missing */
495 process_rfc822_addr(SMTP->from, user, node, name);
496 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
497 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
498 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
499 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
501 /* Save the message in the queue */
502 msgid = CtdlSubmitMsg(msg,
507 valid = validate_recipients(char *recipients) ;
509 for (i=0; i<SMTP->number_of_recipients; ++i) {
510 extract_token(buf, SMTP_RECP, i, '\n');
511 extract(dtype, buf, 0);
513 /* Stuff local mailboxes */
514 if (!strcasecmp(dtype, "local")) {
515 extract(user, buf, 1);
516 if (getuser(&userbuf, user) == 0) {
517 MailboxName(room, &userbuf, MAILROOM);
518 CtdlSaveMsgPointerInRoom(room, msgid, 0);
526 /* Delivery to local non-mailbox rooms */
527 if (!strcasecmp(dtype, "room")) {
528 extract(room, buf, 1);
529 CtdlSaveMsgPointerInRoom(room, msgid, 0);
533 /* Delivery over the local Citadel network (IGnet) */
534 if (!strcasecmp(dtype, "ignet")) {
535 extract(user, buf, 1);
536 extract(node, buf, 2);
537 smtp_deliver_ignet(msg, user, node);
540 /* Remote delivery */
541 if (!strcasecmp(dtype, "remote")) {
542 extract(user, buf, 1);
543 instr = reallok(instr, strlen(instr) + 1024);
544 snprintf(&instr[strlen(instr)],
545 strlen(instr) + 1024,
553 /* If there are remote spools to be done, save the instructions */
554 if (remote_spools > 0) {
555 imsg = mallok(sizeof(struct CtdlMessage));
556 memset(imsg, 0, sizeof(struct CtdlMessage));
557 imsg->cm_magic = CTDLMESSAGE_MAGIC;
558 imsg->cm_anon_type = MES_NORMAL;
559 imsg->cm_format_type = FMT_RFC822;
560 imsg->cm_fields['M'] = instr;
561 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
562 CtdlFreeMessage(imsg);
565 /* If there are no remote spools, delete the message */
567 phree(instr); /* only needed here, because CtdlSubmitMsg()
568 * would free this buffer otherwise */
569 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
572 return(failed_saves);
578 * Implements the DATA command
580 void smtp_data(void) {
582 struct CtdlMessage *msg;
586 if (strlen(SMTP->from) == 0) {
587 cprintf("503 Need MAIL command first.\r\n");
591 if (SMTP->number_of_recipients < 1) {
592 cprintf("503 Need RCPT command first.\r\n");
596 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
598 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
601 if (body != NULL) snprintf(body, 4096,
602 "Received: from %s\n"
609 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
611 cprintf("550 Unable to save message: internal error.\r\n");
615 lprintf(9, "Converting message...\n");
616 msg = convert_internet_message(body);
618 /* If the user is locally authenticated, FORCE the From: header to
619 * show up as the real sender. Yes, this violates the RFC standard,
620 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
623 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
624 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
625 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
626 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
627 msg->cm_fields['N'] = strdoop(config.c_nodename);
628 msg->cm_fields['H'] = strdoop(config.c_humannode);
631 /* Submit the message into the Citadel system. */
632 retval = smtp_message_delivery(msg);
633 CtdlFreeMessage(msg);
636 cprintf("250 Message accepted.\r\n");
639 cprintf("550 Internal delivery errors: %d\r\n", retval);
642 smtp_data_clear(); /* clear out the buffers now */
649 * Main command loop for SMTP sessions.
651 void smtp_command_loop(void) {
655 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
656 if (client_gets(cmdbuf) < 1) {
657 lprintf(3, "SMTP socket is broken. Ending session.\n");
661 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
662 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
664 if (SMTP->command_state == smtp_user) {
665 smtp_get_user(cmdbuf);
668 else if (SMTP->command_state == smtp_password) {
669 smtp_get_pass(cmdbuf);
672 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
673 smtp_auth(&cmdbuf[5]);
676 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
680 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
681 smtp_hello(&cmdbuf[5], 1);
684 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
685 smtp_expn(&cmdbuf[5]);
688 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
689 smtp_hello(&cmdbuf[5], 0);
692 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
696 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
697 smtp_mail(&cmdbuf[5]);
700 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
701 cprintf("250 NOOP\r\n");
704 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
705 cprintf("221 Goodbye...\r\n");
710 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
711 smtp_rcpt(&cmdbuf[5]);
714 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
718 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
719 smtp_vrfy(&cmdbuf[5]);
723 cprintf("502 I'm afraid I can't do that.\r\n");
731 /*****************************************************************************/
732 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
733 /*****************************************************************************/
740 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
743 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
750 char user[SIZ], node[SIZ], name[SIZ];
756 size_t blocksize = 0;
759 /* Parse out the host portion of the recipient address */
760 process_rfc822_addr(addr, user, node, name);
762 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
765 /* Load the message out of the database into a temp file */
767 if (msg_fp == NULL) {
769 sprintf(dsn, "Error creating temporary file");
773 CtdlRedirectOutput(msg_fp, -1);
774 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
775 CtdlRedirectOutput(NULL, -1);
776 fseek(msg_fp, 0L, SEEK_END);
777 msg_size = ftell(msg_fp);
781 /* Extract something to send later in the 'MAIL From:' command */
782 strcpy(mailfrom, "");
786 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
787 if (!strncasecmp(buf, "From:", 5)) {
788 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
790 for (i=0; i<strlen(mailfrom); ++i) {
791 if (!isprint(mailfrom[i])) {
792 strcpy(&mailfrom[i], &mailfrom[i+1]);
797 /* Strip out parenthesized names */
800 for (i=0; i<strlen(mailfrom); ++i) {
801 if (mailfrom[i] == '(') lp = i;
802 if (mailfrom[i] == ')') rp = i;
804 if ((lp>0)&&(rp>lp)) {
805 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
808 /* Prefer brokketized names */
811 for (i=0; i<strlen(mailfrom); ++i) {
812 if (mailfrom[i] == '<') lp = i;
813 if (mailfrom[i] == '>') rp = i;
815 if ((lp>=0)&&(rp>lp)) {
817 strcpy(mailfrom, &mailfrom[lp]);
822 } while (scan_done == 0);
823 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
826 /* Figure out what mail exchanger host we have to connect to */
827 num_mxhosts = getmx(mxhosts, node);
828 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
829 if (num_mxhosts < 1) {
831 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
835 for (mx=0; mx<num_mxhosts; ++mx) {
836 extract(buf, mxhosts, mx);
837 lprintf(9, "Trying <%s>\n", buf);
838 sock = sock_connect(buf, "25", "tcp");
839 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
840 if (sock >= 0) lprintf(9, "Connected!\n");
841 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
842 if (sock >= 0) break;
846 *status = 4; /* dsn is already filled in */
850 /* Process the SMTP greeting from the server */
851 if (ml_sock_gets(sock, buf) < 0) {
853 strcpy(dsn, "Connection broken during SMTP conversation");
856 lprintf(9, "<%s\n", buf);
860 safestrncpy(dsn, &buf[4], 1023);
865 safestrncpy(dsn, &buf[4], 1023);
870 /* At this point we know we are talking to a real SMTP server */
872 /* Do a HELO command */
873 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
874 lprintf(9, ">%s", buf);
875 sock_write(sock, buf, strlen(buf));
876 if (ml_sock_gets(sock, buf) < 0) {
878 strcpy(dsn, "Connection broken during SMTP HELO");
881 lprintf(9, "<%s\n", buf);
885 safestrncpy(dsn, &buf[4], 1023);
890 safestrncpy(dsn, &buf[4], 1023);
896 /* HELO succeeded, now try the MAIL From: command */
897 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
898 lprintf(9, ">%s", buf);
899 sock_write(sock, buf, strlen(buf));
900 if (ml_sock_gets(sock, buf) < 0) {
902 strcpy(dsn, "Connection broken during SMTP MAIL");
905 lprintf(9, "<%s\n", buf);
909 safestrncpy(dsn, &buf[4], 1023);
914 safestrncpy(dsn, &buf[4], 1023);
920 /* MAIL succeeded, now try the RCPT To: command */
921 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
922 lprintf(9, ">%s", buf);
923 sock_write(sock, buf, strlen(buf));
924 if (ml_sock_gets(sock, buf) < 0) {
926 strcpy(dsn, "Connection broken during SMTP RCPT");
929 lprintf(9, "<%s\n", buf);
933 safestrncpy(dsn, &buf[4], 1023);
938 safestrncpy(dsn, &buf[4], 1023);
944 /* RCPT succeeded, now try the DATA command */
945 lprintf(9, ">DATA\n");
946 sock_write(sock, "DATA\r\n", 6);
947 if (ml_sock_gets(sock, buf) < 0) {
949 strcpy(dsn, "Connection broken during SMTP DATA");
952 lprintf(9, "<%s\n", buf);
956 safestrncpy(dsn, &buf[4], 1023);
961 safestrncpy(dsn, &buf[4], 1023);
966 /* If we reach this point, the server is expecting data */
968 while (msg_size > 0) {
969 blocksize = sizeof(buf);
970 if (blocksize > msg_size) blocksize = msg_size;
971 fread(buf, blocksize, 1, msg_fp);
972 sock_write(sock, buf, blocksize);
973 msg_size -= blocksize;
975 if (buf[blocksize-1] != 10) {
976 lprintf(5, "Possible problem: message did not correctly "
977 "terminate. (expecting 0x10, got 0x%02x)\n",
981 sock_write(sock, ".\r\n", 3);
982 if (ml_sock_gets(sock, buf) < 0) {
984 strcpy(dsn, "Connection broken during SMTP message transmit");
987 lprintf(9, "%s\n", buf);
991 safestrncpy(dsn, &buf[4], 1023);
996 safestrncpy(dsn, &buf[4], 1023);
1002 safestrncpy(dsn, &buf[4], 1023);
1005 lprintf(9, ">QUIT\n");
1006 sock_write(sock, "QUIT\r\n", 6);
1007 ml_sock_gets(sock, buf);
1008 lprintf(9, "<%s\n", buf);
1010 bail: if (msg_fp != NULL) fclose(msg_fp);
1018 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1019 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1020 * a "bounce" message (delivery status notification).
1022 void smtp_do_bounce(char *instr) {
1030 char bounceto[1024];
1031 int num_bounces = 0;
1032 int bounce_this = 0;
1033 long bounce_msgid = (-1);
1034 time_t submitted = 0L;
1035 struct CtdlMessage *bmsg = NULL;
1039 lprintf(9, "smtp_do_bounce() called\n");
1040 strcpy(bounceto, "");
1042 lines = num_tokens(instr, '\n');
1045 /* See if it's time to give up on delivery of this message */
1046 for (i=0; i<lines; ++i) {
1047 extract_token(buf, instr, i, '\n');
1048 extract(key, buf, 0);
1049 extract(addr, buf, 1);
1050 if (!strcasecmp(key, "submitted")) {
1051 submitted = atol(addr);
1055 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1061 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1062 if (bmsg == NULL) return;
1063 memset(bmsg, 0, sizeof(struct CtdlMessage));
1065 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1066 bmsg->cm_anon_type = MES_NORMAL;
1067 bmsg->cm_format_type = 1;
1068 bmsg->cm_fields['A'] = strdoop("Citadel");
1069 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1070 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1072 if (give_up) bmsg->cm_fields['M'] = strdoop(
1073 "A message you sent could not be delivered to some or all of its recipients\n"
1074 "due to prolonged unavailability of its destination(s).\n"
1075 "Giving up on the following addresses:\n\n"
1078 else bmsg->cm_fields['M'] = strdoop(
1079 "A message you sent could not be delivered to some or all of its recipients.\n"
1080 "The following addresses were undeliverable:\n\n"
1084 * Now go through the instructions checking for stuff.
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 status = extract_int(buf, 2);
1092 extract(dsn, buf, 3);
1095 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1096 key, addr, status, dsn);
1098 if (!strcasecmp(key, "bounceto")) {
1099 strcpy(bounceto, addr);
1103 (!strcasecmp(key, "local"))
1104 || (!strcasecmp(key, "remote"))
1105 || (!strcasecmp(key, "ignet"))
1106 || (!strcasecmp(key, "room"))
1108 if (status == 5) bounce_this = 1;
1109 if (give_up) bounce_this = 1;
1115 if (bmsg->cm_fields['M'] == NULL) {
1116 lprintf(2, "ERROR ... M field is null "
1117 "(%s:%d)\n", __FILE__, __LINE__);
1120 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1121 strlen(bmsg->cm_fields['M']) + 1024 );
1122 strcat(bmsg->cm_fields['M'], addr);
1123 strcat(bmsg->cm_fields['M'], ": ");
1124 strcat(bmsg->cm_fields['M'], dsn);
1125 strcat(bmsg->cm_fields['M'], "\n");
1127 remove_token(instr, i, '\n');
1133 /* Deliver the bounce if there's anything worth mentioning */
1134 lprintf(9, "num_bounces = %d\n", num_bounces);
1135 if (num_bounces > 0) {
1137 /* First try the user who sent the message */
1138 lprintf(9, "bounce to user? <%s>\n", bounceto);
1140 if (strlen(bounceto) == 0) {
1141 lprintf(7, "No bounce address specified\n");
1142 bounce_msgid = (-1L);
1144 /* FIXME this won't work
1145 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1146 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1147 bounce_msgid = (-1L);
1150 bounce_msgid = CtdlSubmitMsg(bmsg,
1157 /* Otherwise, go to the Aide> room */
1158 lprintf(9, "bounce to room?\n");
1159 if (bounce_msgid < 0L) bounce_msgid = CtdlSubmitMsg(bmsg,
1163 CtdlFreeMessage(bmsg);
1164 lprintf(9, "Done processing bounces\n");
1169 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1170 * set of delivery instructions for completed deliveries and remove them.
1172 * It returns the number of incomplete deliveries remaining.
1174 int smtp_purge_completed_deliveries(char *instr) {
1185 lines = num_tokens(instr, '\n');
1186 for (i=0; i<lines; ++i) {
1187 extract_token(buf, instr, i, '\n');
1188 extract(key, buf, 0);
1189 extract(addr, buf, 1);
1190 status = extract_int(buf, 2);
1191 extract(dsn, buf, 3);
1196 (!strcasecmp(key, "local"))
1197 || (!strcasecmp(key, "remote"))
1198 || (!strcasecmp(key, "ignet"))
1199 || (!strcasecmp(key, "room"))
1201 if (status == 2) completed = 1;
1206 remove_token(instr, i, '\n');
1219 * Called by smtp_do_queue() to handle an individual message.
1221 void smtp_do_procmsg(long msgnum, void *userdata) {
1222 struct CtdlMessage *msg;
1224 char *results = NULL;
1232 long text_msgid = (-1);
1233 int incomplete_deliveries_remaining;
1234 time_t attempted = 0L;
1235 time_t last_attempted = 0L;
1236 time_t retry = SMTP_RETRY_INTERVAL;
1238 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1240 msg = CtdlFetchMessage(msgnum);
1242 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1246 instr = strdoop(msg->cm_fields['M']);
1247 CtdlFreeMessage(msg);
1249 /* Strip out the headers amd any other non-instruction line */
1250 lines = num_tokens(instr, '\n');
1251 for (i=0; i<lines; ++i) {
1252 extract_token(buf, instr, i, '\n');
1253 if (num_tokens(buf, '|') < 2) {
1254 remove_token(instr, i, '\n');
1260 /* Learn the message ID and find out about recent delivery attempts */
1261 lines = num_tokens(instr, '\n');
1262 for (i=0; i<lines; ++i) {
1263 extract_token(buf, instr, i, '\n');
1264 extract(key, buf, 0);
1265 if (!strcasecmp(key, "msgid")) {
1266 text_msgid = extract_long(buf, 1);
1268 if (!strcasecmp(key, "retry")) {
1269 /* double the retry interval after each attempt */
1270 retry = extract_long(buf, 1) * 2L;
1271 if (retry > SMTP_RETRY_MAX) {
1272 retry = SMTP_RETRY_MAX;
1274 remove_token(instr, i, '\n');
1276 if (!strcasecmp(key, "attempted")) {
1277 attempted = extract_long(buf, 1);
1278 if (attempted > last_attempted)
1279 last_attempted = attempted;
1284 * Postpone delivery if we've already tried recently.
1286 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1287 lprintf(7, "Retry time not yet reached.\n");
1294 * Bail out if there's no actual message associated with this
1296 if (text_msgid < 0L) {
1297 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1302 /* Plow through the instructions looking for 'remote' directives and
1303 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1304 * were experienced and it's time to try again)
1306 lines = num_tokens(instr, '\n');
1307 for (i=0; i<lines; ++i) {
1308 extract_token(buf, instr, i, '\n');
1309 extract(key, buf, 0);
1310 extract(addr, buf, 1);
1311 status = extract_int(buf, 2);
1312 extract(dsn, buf, 3);
1313 if ( (!strcasecmp(key, "remote"))
1314 && ((status==0)||(status==3)||(status==4)) ) {
1315 remove_token(instr, i, '\n');
1318 lprintf(9, "SMTP: Trying <%s>\n", addr);
1319 smtp_try(key, addr, &status, dsn, text_msgid);
1321 if (results == NULL) {
1322 results = mallok(1024);
1323 memset(results, 0, 1024);
1326 results = reallok(results,
1327 strlen(results) + 1024);
1329 sprintf(&results[strlen(results)],
1331 key, addr, status, dsn);
1336 if (results != NULL) {
1337 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1338 strcat(instr, results);
1343 /* Generate 'bounce' messages */
1344 smtp_do_bounce(instr);
1346 /* Go through the delivery list, deleting completed deliveries */
1347 incomplete_deliveries_remaining =
1348 smtp_purge_completed_deliveries(instr);
1352 * No delivery instructions remain, so delete both the instructions
1353 * message and the message message.
1355 if (incomplete_deliveries_remaining <= 0) {
1356 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1357 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1362 * Uncompleted delivery instructions remain, so delete the old
1363 * instructions and replace with the updated ones.
1365 if (incomplete_deliveries_remaining > 0) {
1366 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1367 msg = mallok(sizeof(struct CtdlMessage));
1368 memset(msg, 0, sizeof(struct CtdlMessage));
1369 msg->cm_magic = CTDLMESSAGE_MAGIC;
1370 msg->cm_anon_type = MES_NORMAL;
1371 msg->cm_format_type = FMT_RFC822;
1372 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1373 snprintf(msg->cm_fields['M'],
1375 "Content-type: %s\n\n%s\n"
1378 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1380 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1381 CtdlFreeMessage(msg);
1391 * Run through the queue sending out messages.
1393 void smtp_do_queue(void) {
1394 static int doing_queue = 0;
1397 * This is a simple concurrency check to make sure only one queue run
1398 * is done at a time. We could do this with a mutex, but since we
1399 * don't really require extremely fine granularity here, we'll do it
1400 * with a static variable instead.
1402 if (doing_queue) return;
1406 * Go ahead and run the queue
1408 lprintf(7, "SMTP: processing outbound queue\n");
1410 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1411 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1414 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1415 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1417 lprintf(7, "SMTP: queue run completed\n");
1424 /*****************************************************************************/
1425 /* SMTP UTILITY COMMANDS */
1426 /*****************************************************************************/
1428 void cmd_smtp(char *argbuf) {
1435 if (CtdlAccessCheck(ac_aide)) return;
1437 extract(cmd, argbuf, 0);
1439 if (!strcasecmp(cmd, "mx")) {
1440 extract(node, argbuf, 1);
1441 num_mxhosts = getmx(buf, node);
1442 cprintf("%d %d MX hosts listed for %s\n",
1443 LISTING_FOLLOWS, num_mxhosts, node);
1444 for (i=0; i<num_mxhosts; ++i) {
1445 extract(node, buf, i);
1446 cprintf("%s\n", node);
1452 else if (!strcasecmp(cmd, "runqueue")) {
1454 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1459 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1467 /*****************************************************************************/
1468 /* MODULE INITIALIZATION STUFF */
1469 /*****************************************************************************/
1472 char *Dynamic_Module_Init(void)
1474 SYM_SMTP = CtdlGetDynamicSymbol();
1475 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1477 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1482 CtdlRegisterServiceHook(0, /* ...and locally */
1487 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1488 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1489 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");