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-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, SIZ);
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, SIZ);
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
373 cvt = convert_internet_address(user, node, SMTP->from);
374 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
375 if (CtdlHostAlias(node) == hostalias_localhost) {
376 cprintf("550 You must log in to send mail from %s\r\n",
378 strcpy(SMTP->from, "");
383 cprintf("250 Sender ok\r\n");
389 * Implements the "RCPT To:" command
391 void smtp_rcpt(char *argbuf) {
397 if (strlen(SMTP->from) == 0) {
398 cprintf("503 Need MAIL before RCPT\r\n");
402 if (strncasecmp(argbuf, "To:", 3)) {
403 cprintf("501 Syntax error\r\n");
407 strcpy(recp, &argbuf[3]);
411 cvt = convert_internet_address(user, node, recp);
412 snprintf(recp, sizeof recp, "%s@%s", user, node);
413 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
416 case rfc822_address_locally_validated:
417 cprintf("250 %s is a valid recipient.\r\n", user);
418 ++SMTP->number_of_recipients;
419 CtdlReallocUserData(SYM_SMTP_RECP,
420 strlen(SMTP_RECP) + 1024 );
421 strcat(SMTP_RECP, "local|");
422 strcat(SMTP_RECP, user);
423 strcat(SMTP_RECP, "|0\n");
426 case rfc822_room_delivery:
427 cprintf("250 Delivering to room '%s'\r\n", user);
428 ++SMTP->number_of_recipients;
429 CtdlReallocUserData(SYM_SMTP_RECP,
430 strlen(SMTP_RECP) + 1024 );
431 strcat(SMTP_RECP, "room|");
432 strcat(SMTP_RECP, user);
433 strcat(SMTP_RECP, "|0|\n");
436 case rfc822_no_such_user:
437 cprintf("550 %s: no such user\r\n", recp);
440 case rfc822_address_on_citadel_network:
441 cprintf("250 %s is on the local network\r\n", recp);
442 ++SMTP->number_of_recipients;
443 CtdlReallocUserData(SYM_SMTP_RECP,
444 strlen(SMTP_RECP) + 1024 );
445 strcat(SMTP_RECP, "ignet|");
446 strcat(SMTP_RECP, user);
447 strcat(SMTP_RECP, "|");
448 strcat(SMTP_RECP, node);
449 strcat(SMTP_RECP, "|0|\n");
452 case rfc822_address_nonlocal:
453 if (SMTP->message_originated_locally == 0) {
454 cprintf("551 Relaying denied.\r\n");
457 cprintf("250 Remote recipient %s ok\r\n", recp);
458 ++SMTP->number_of_recipients;
459 CtdlReallocUserData(SYM_SMTP_RECP,
460 strlen(SMTP_RECP) + 1024 );
461 strcat(SMTP_RECP, "remote|");
462 strcat(SMTP_RECP, recp);
463 strcat(SMTP_RECP, "|0|\n");
469 cprintf("599 Unknown error\r\n");
475 * Send a message out through the local network
476 * (This is kind of ugly. IGnet should be done using clean server-to-server
477 * code instead of the old style spool.)
479 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
481 char *hold_R, *hold_D, *hold_O;
486 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
488 hold_R = msg->cm_fields['R'];
489 hold_D = msg->cm_fields['D'];
490 hold_O = msg->cm_fields['O'];
491 msg->cm_fields['R'] = user;
492 msg->cm_fields['D'] = dest;
493 msg->cm_fields['O'] = MAILROOM;
495 serialize_message(&smr, msg);
497 msg->cm_fields['R'] = hold_R;
498 msg->cm_fields['D'] = hold_D;
499 msg->cm_fields['O'] = hold_O;
502 snprintf(filename, sizeof filename,
503 "./network/spoolin/%s.%04x.%04x",
504 dest, getpid(), ++seq);
505 lprintf(9, "spool file name is <%s>\n", filename);
506 fp = fopen(filename, "wb");
508 fwrite(smr.ser, smr.len, 1, fp);
519 * Back end for smtp_data() ... this does the actual delivery of the message
520 * Returns 0 on success, nonzero on failure
522 int smtp_message_delivery(struct CtdlMessage *msg) {
529 int successful_saves = 0; /* number of successful local saves */
530 int failed_saves = 0; /* number of failed deliveries */
531 int remote_spools = 0; /* number of copies to send out */
534 struct usersupp userbuf;
535 char *instr; /* Remote delivery instructions */
536 struct CtdlMessage *imsg;
538 lprintf(9, "smtp_message_delivery() called\n");
540 /* Fill in 'from' fields with envelope information if missing */
541 process_rfc822_addr(SMTP->from, user, node, name);
542 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
543 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
544 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
545 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
547 /* Save the message in the queue */
548 msgid = CtdlSubmitMsg(msg,
553 instr = mallok(1024);
554 snprintf(instr, 1024,
555 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
557 SPOOLMIME, msgid, (long)time(NULL),
560 for (i=0; i<SMTP->number_of_recipients; ++i) {
561 extract_token(buf, SMTP_RECP, i, '\n');
562 extract(dtype, buf, 0);
564 /* Stuff local mailboxes */
565 if (!strcasecmp(dtype, "local")) {
566 extract(user, buf, 1);
567 if (getuser(&userbuf, user) == 0) {
568 MailboxName(room, &userbuf, MAILROOM);
569 CtdlSaveMsgPointerInRoom(room, msgid, 0);
577 /* Delivery to local non-mailbox rooms */
578 if (!strcasecmp(dtype, "room")) {
579 extract(room, buf, 1);
580 CtdlSaveMsgPointerInRoom(room, msgid, 0);
584 /* Delivery over the local Citadel network (IGnet) */
585 if (!strcasecmp(dtype, "ignet")) {
586 extract(user, buf, 1);
587 extract(node, buf, 2);
588 smtp_deliver_ignet(msg, user, node);
591 /* Remote delivery */
592 if (!strcasecmp(dtype, "remote")) {
593 extract(user, buf, 1);
594 instr = reallok(instr, strlen(instr) + 1024);
595 snprintf(&instr[strlen(instr)],
596 strlen(instr) + 1024,
604 /* If there are remote spools to be done, save the instructions */
605 if (remote_spools > 0) {
606 imsg = mallok(sizeof(struct CtdlMessage));
607 memset(imsg, 0, sizeof(struct CtdlMessage));
608 imsg->cm_magic = CTDLMESSAGE_MAGIC;
609 imsg->cm_anon_type = MES_NORMAL;
610 imsg->cm_format_type = FMT_RFC822;
611 imsg->cm_fields['M'] = instr;
612 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
613 CtdlFreeMessage(imsg);
616 /* If there are no remote spools, delete the message */
618 phree(instr); /* only needed here, because CtdlSubmitMsg()
619 * would free this buffer otherwise */
620 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
623 return(failed_saves);
629 * Implements the DATA command
631 void smtp_data(void) {
633 struct CtdlMessage *msg;
637 if (strlen(SMTP->from) == 0) {
638 cprintf("503 Need MAIL command first.\r\n");
642 if (SMTP->number_of_recipients < 1) {
643 cprintf("503 Need RCPT command first.\r\n");
647 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
649 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
652 if (body != NULL) snprintf(body, 4096,
653 "Received: from %s\n"
660 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
662 cprintf("550 Unable to save message: internal error.\r\n");
666 lprintf(9, "Converting message...\n");
667 msg = convert_internet_message(body);
669 /* If the user is locally authenticated, FORCE the From: header to
670 * show up as the real sender. Yes, this violates the RFC standard,
671 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
674 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
675 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
676 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
677 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
678 msg->cm_fields['N'] = strdoop(config.c_nodename);
679 msg->cm_fields['H'] = strdoop(config.c_humannode);
682 /* Submit the message into the Citadel system. */
683 retval = smtp_message_delivery(msg);
684 CtdlFreeMessage(msg);
687 cprintf("250 Message accepted.\r\n");
690 cprintf("550 Internal delivery errors: %d\r\n", retval);
693 smtp_data_clear(); /* clear out the buffers now */
700 * Main command loop for SMTP sessions.
702 void smtp_command_loop(void) {
706 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
707 if (client_gets(cmdbuf) < 1) {
708 lprintf(3, "SMTP socket is broken. Ending session.\n");
712 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
713 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
715 if (SMTP->command_state == smtp_user) {
716 smtp_get_user(cmdbuf);
719 else if (SMTP->command_state == smtp_password) {
720 smtp_get_pass(cmdbuf);
723 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
724 smtp_auth(&cmdbuf[5]);
727 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
731 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
732 smtp_hello(&cmdbuf[5], 1);
735 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
736 smtp_expn(&cmdbuf[5]);
739 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
740 smtp_hello(&cmdbuf[5], 0);
743 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
747 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
748 smtp_mail(&cmdbuf[5]);
751 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
752 cprintf("250 NOOP\r\n");
755 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
756 cprintf("221 Goodbye...\r\n");
761 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
762 smtp_rcpt(&cmdbuf[5]);
765 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
769 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
770 smtp_vrfy(&cmdbuf[5]);
774 cprintf("502 I'm afraid I can't do that.\r\n");
782 /*****************************************************************************/
783 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
784 /*****************************************************************************/
791 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
794 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
801 char user[SIZ], node[SIZ], name[SIZ];
807 size_t blocksize = 0;
810 /* Parse out the host portion of the recipient address */
811 process_rfc822_addr(addr, user, node, name);
813 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
816 /* Load the message out of the database into a temp file */
818 if (msg_fp == NULL) {
820 sprintf(dsn, "Error creating temporary file");
824 CtdlRedirectOutput(msg_fp, -1);
825 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
826 CtdlRedirectOutput(NULL, -1);
827 fseek(msg_fp, 0L, SEEK_END);
828 msg_size = ftell(msg_fp);
832 /* Extract something to send later in the 'MAIL From:' command */
833 strcpy(mailfrom, "");
837 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
838 if (!strncasecmp(buf, "From:", 5)) {
839 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
841 for (i=0; i<strlen(mailfrom); ++i) {
842 if (!isprint(mailfrom[i])) {
843 strcpy(&mailfrom[i], &mailfrom[i+1]);
848 /* Strip out parenthesized names */
851 for (i=0; i<strlen(mailfrom); ++i) {
852 if (mailfrom[i] == '(') lp = i;
853 if (mailfrom[i] == ')') rp = i;
855 if ((lp>0)&&(rp>lp)) {
856 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
859 /* Prefer brokketized names */
862 for (i=0; i<strlen(mailfrom); ++i) {
863 if (mailfrom[i] == '<') lp = i;
864 if (mailfrom[i] == '>') rp = i;
866 if ((lp>=0)&&(rp>lp)) {
868 strcpy(mailfrom, &mailfrom[lp]);
873 } while (scan_done == 0);
874 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
877 /* Figure out what mail exchanger host we have to connect to */
878 num_mxhosts = getmx(mxhosts, node);
879 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
880 if (num_mxhosts < 1) {
882 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
886 for (mx=0; mx<num_mxhosts; ++mx) {
887 extract(buf, mxhosts, mx);
888 lprintf(9, "Trying <%s>\n", buf);
889 sock = sock_connect(buf, "25", "tcp");
890 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
891 if (sock >= 0) lprintf(9, "Connected!\n");
892 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
893 if (sock >= 0) break;
897 *status = 4; /* dsn is already filled in */
901 /* Process the SMTP greeting from the server */
902 if (ml_sock_gets(sock, buf) < 0) {
904 strcpy(dsn, "Connection broken during SMTP conversation");
907 lprintf(9, "<%s\n", buf);
911 safestrncpy(dsn, &buf[4], 1023);
916 safestrncpy(dsn, &buf[4], 1023);
921 /* At this point we know we are talking to a real SMTP server */
923 /* Do a HELO command */
924 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
925 lprintf(9, ">%s", buf);
926 sock_write(sock, buf, strlen(buf));
927 if (ml_sock_gets(sock, buf) < 0) {
929 strcpy(dsn, "Connection broken during SMTP HELO");
932 lprintf(9, "<%s\n", buf);
936 safestrncpy(dsn, &buf[4], 1023);
941 safestrncpy(dsn, &buf[4], 1023);
947 /* HELO succeeded, now try the MAIL From: command */
948 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
949 lprintf(9, ">%s", buf);
950 sock_write(sock, buf, strlen(buf));
951 if (ml_sock_gets(sock, buf) < 0) {
953 strcpy(dsn, "Connection broken during SMTP MAIL");
956 lprintf(9, "<%s\n", buf);
960 safestrncpy(dsn, &buf[4], 1023);
965 safestrncpy(dsn, &buf[4], 1023);
971 /* MAIL succeeded, now try the RCPT To: command */
972 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
973 lprintf(9, ">%s", buf);
974 sock_write(sock, buf, strlen(buf));
975 if (ml_sock_gets(sock, buf) < 0) {
977 strcpy(dsn, "Connection broken during SMTP RCPT");
980 lprintf(9, "<%s\n", buf);
984 safestrncpy(dsn, &buf[4], 1023);
989 safestrncpy(dsn, &buf[4], 1023);
995 /* RCPT succeeded, now try the DATA command */
996 lprintf(9, ">DATA\n");
997 sock_write(sock, "DATA\r\n", 6);
998 if (ml_sock_gets(sock, buf) < 0) {
1000 strcpy(dsn, "Connection broken during SMTP DATA");
1003 lprintf(9, "<%s\n", buf);
1004 if (buf[0] != '3') {
1005 if (buf[0] == '4') {
1007 safestrncpy(dsn, &buf[4], 1023);
1012 safestrncpy(dsn, &buf[4], 1023);
1017 /* If we reach this point, the server is expecting data */
1019 while (msg_size > 0) {
1020 blocksize = sizeof(buf);
1021 if (blocksize > msg_size) blocksize = msg_size;
1022 fread(buf, blocksize, 1, msg_fp);
1023 sock_write(sock, buf, blocksize);
1024 msg_size -= blocksize;
1026 if (buf[blocksize-1] != 10) {
1027 lprintf(5, "Possible problem: message did not correctly "
1028 "terminate. (expecting 0x10, got 0x%02x)\n",
1032 sock_write(sock, ".\r\n", 3);
1033 if (ml_sock_gets(sock, buf) < 0) {
1035 strcpy(dsn, "Connection broken during SMTP message transmit");
1038 lprintf(9, "%s\n", buf);
1039 if (buf[0] != '2') {
1040 if (buf[0] == '4') {
1042 safestrncpy(dsn, &buf[4], 1023);
1047 safestrncpy(dsn, &buf[4], 1023);
1053 safestrncpy(dsn, &buf[4], 1023);
1056 lprintf(9, ">QUIT\n");
1057 sock_write(sock, "QUIT\r\n", 6);
1058 ml_sock_gets(sock, buf);
1059 lprintf(9, "<%s\n", buf);
1061 bail: if (msg_fp != NULL) fclose(msg_fp);
1069 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1070 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1071 * a "bounce" message (delivery status notification).
1073 void smtp_do_bounce(char *instr) {
1081 char bounceto[1024];
1082 int num_bounces = 0;
1083 int bounce_this = 0;
1084 long bounce_msgid = (-1);
1085 time_t submitted = 0L;
1086 struct CtdlMessage *bmsg = NULL;
1090 lprintf(9, "smtp_do_bounce() called\n");
1091 strcpy(bounceto, "");
1093 lines = num_tokens(instr, '\n');
1096 /* See if it's time to give up on delivery of this message */
1097 for (i=0; i<lines; ++i) {
1098 extract_token(buf, instr, i, '\n');
1099 extract(key, buf, 0);
1100 extract(addr, buf, 1);
1101 if (!strcasecmp(key, "submitted")) {
1102 submitted = atol(addr);
1106 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1112 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1113 if (bmsg == NULL) return;
1114 memset(bmsg, 0, sizeof(struct CtdlMessage));
1116 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1117 bmsg->cm_anon_type = MES_NORMAL;
1118 bmsg->cm_format_type = 1;
1119 bmsg->cm_fields['A'] = strdoop("Citadel");
1120 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1121 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1123 if (give_up) bmsg->cm_fields['M'] = strdoop(
1124 "A message you sent could not be delivered to some or all of its recipients\n"
1125 "due to prolonged unavailability of its destination(s).\n"
1126 "Giving up on the following addresses:\n\n"
1129 else bmsg->cm_fields['M'] = strdoop(
1130 "A message you sent could not be delivered to some or all of its recipients.\n"
1131 "The following addresses were undeliverable:\n\n"
1135 * Now go through the instructions checking for stuff.
1138 for (i=0; i<lines; ++i) {
1139 extract_token(buf, instr, i, '\n');
1140 extract(key, buf, 0);
1141 extract(addr, buf, 1);
1142 status = extract_int(buf, 2);
1143 extract(dsn, buf, 3);
1146 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1147 key, addr, status, dsn);
1149 if (!strcasecmp(key, "bounceto")) {
1150 strcpy(bounceto, addr);
1154 (!strcasecmp(key, "local"))
1155 || (!strcasecmp(key, "remote"))
1156 || (!strcasecmp(key, "ignet"))
1157 || (!strcasecmp(key, "room"))
1159 if (status == 5) bounce_this = 1;
1160 if (give_up) bounce_this = 1;
1166 if (bmsg->cm_fields['M'] == NULL) {
1167 lprintf(2, "ERROR ... M field is null "
1168 "(%s:%d)\n", __FILE__, __LINE__);
1171 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1172 strlen(bmsg->cm_fields['M']) + 1024 );
1173 strcat(bmsg->cm_fields['M'], addr);
1174 strcat(bmsg->cm_fields['M'], ": ");
1175 strcat(bmsg->cm_fields['M'], dsn);
1176 strcat(bmsg->cm_fields['M'], "\n");
1178 remove_token(instr, i, '\n');
1184 /* Deliver the bounce if there's anything worth mentioning */
1185 lprintf(9, "num_bounces = %d\n", num_bounces);
1186 if (num_bounces > 0) {
1188 /* First try the user who sent the message */
1189 lprintf(9, "bounce to user? <%s>\n", bounceto);
1191 if (strlen(bounceto) == 0) {
1192 lprintf(7, "No bounce address specified\n");
1193 bounce_msgid = (-1L);
1195 /* FIXME this won't work
1196 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1197 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1198 bounce_msgid = (-1L);
1201 bounce_msgid = CtdlSubmitMsg(bmsg,
1208 /* Otherwise, go to the Aide> room */
1209 lprintf(9, "bounce to room?\n");
1210 if (bounce_msgid < 0L) bounce_msgid = CtdlSubmitMsg(bmsg,
1214 CtdlFreeMessage(bmsg);
1215 lprintf(9, "Done processing bounces\n");
1220 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1221 * set of delivery instructions for completed deliveries and remove them.
1223 * It returns the number of incomplete deliveries remaining.
1225 int smtp_purge_completed_deliveries(char *instr) {
1236 lines = num_tokens(instr, '\n');
1237 for (i=0; i<lines; ++i) {
1238 extract_token(buf, instr, i, '\n');
1239 extract(key, buf, 0);
1240 extract(addr, buf, 1);
1241 status = extract_int(buf, 2);
1242 extract(dsn, buf, 3);
1247 (!strcasecmp(key, "local"))
1248 || (!strcasecmp(key, "remote"))
1249 || (!strcasecmp(key, "ignet"))
1250 || (!strcasecmp(key, "room"))
1252 if (status == 2) completed = 1;
1257 remove_token(instr, i, '\n');
1270 * Called by smtp_do_queue() to handle an individual message.
1272 void smtp_do_procmsg(long msgnum, void *userdata) {
1273 struct CtdlMessage *msg;
1275 char *results = NULL;
1283 long text_msgid = (-1);
1284 int incomplete_deliveries_remaining;
1285 time_t attempted = 0L;
1286 time_t last_attempted = 0L;
1287 time_t retry = SMTP_RETRY_INTERVAL;
1289 lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1291 msg = CtdlFetchMessage(msgnum);
1293 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1297 instr = strdoop(msg->cm_fields['M']);
1298 CtdlFreeMessage(msg);
1300 /* Strip out the headers amd any other non-instruction line */
1301 lines = num_tokens(instr, '\n');
1302 for (i=0; i<lines; ++i) {
1303 extract_token(buf, instr, i, '\n');
1304 if (num_tokens(buf, '|') < 2) {
1305 remove_token(instr, i, '\n');
1311 /* Learn the message ID and find out about recent delivery attempts */
1312 lines = num_tokens(instr, '\n');
1313 for (i=0; i<lines; ++i) {
1314 extract_token(buf, instr, i, '\n');
1315 extract(key, buf, 0);
1316 if (!strcasecmp(key, "msgid")) {
1317 text_msgid = extract_long(buf, 1);
1319 if (!strcasecmp(key, "retry")) {
1320 /* double the retry interval after each attempt */
1321 retry = extract_long(buf, 1) * 2L;
1322 if (retry > SMTP_RETRY_MAX) {
1323 retry = SMTP_RETRY_MAX;
1325 remove_token(instr, i, '\n');
1327 if (!strcasecmp(key, "attempted")) {
1328 attempted = extract_long(buf, 1);
1329 if (attempted > last_attempted)
1330 last_attempted = attempted;
1335 * Postpone delivery if we've already tried recently.
1337 if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1338 lprintf(7, "Retry time not yet reached.\n");
1345 * Bail out if there's no actual message associated with this
1347 if (text_msgid < 0L) {
1348 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1353 /* Plow through the instructions looking for 'remote' directives and
1354 * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1355 * were experienced and it's time to try again)
1357 lines = num_tokens(instr, '\n');
1358 for (i=0; i<lines; ++i) {
1359 extract_token(buf, instr, i, '\n');
1360 extract(key, buf, 0);
1361 extract(addr, buf, 1);
1362 status = extract_int(buf, 2);
1363 extract(dsn, buf, 3);
1364 if ( (!strcasecmp(key, "remote"))
1365 && ((status==0)||(status==3)||(status==4)) ) {
1366 remove_token(instr, i, '\n');
1369 lprintf(9, "SMTP: Trying <%s>\n", addr);
1370 smtp_try(key, addr, &status, dsn, text_msgid);
1372 if (results == NULL) {
1373 results = mallok(1024);
1374 memset(results, 0, 1024);
1377 results = reallok(results,
1378 strlen(results) + 1024);
1380 sprintf(&results[strlen(results)],
1382 key, addr, status, dsn);
1387 if (results != NULL) {
1388 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1389 strcat(instr, results);
1394 /* Generate 'bounce' messages */
1395 smtp_do_bounce(instr);
1397 /* Go through the delivery list, deleting completed deliveries */
1398 incomplete_deliveries_remaining =
1399 smtp_purge_completed_deliveries(instr);
1403 * No delivery instructions remain, so delete both the instructions
1404 * message and the message message.
1406 if (incomplete_deliveries_remaining <= 0) {
1407 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1408 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1413 * Uncompleted delivery instructions remain, so delete the old
1414 * instructions and replace with the updated ones.
1416 if (incomplete_deliveries_remaining > 0) {
1417 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1418 msg = mallok(sizeof(struct CtdlMessage));
1419 memset(msg, 0, sizeof(struct CtdlMessage));
1420 msg->cm_magic = CTDLMESSAGE_MAGIC;
1421 msg->cm_anon_type = MES_NORMAL;
1422 msg->cm_format_type = FMT_RFC822;
1423 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1424 snprintf(msg->cm_fields['M'],
1426 "Content-type: %s\n\n%s\n"
1429 SPOOLMIME, instr, (long)time(NULL), (long)retry );
1431 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1432 CtdlFreeMessage(msg);
1442 * Run through the queue sending out messages.
1444 void smtp_do_queue(void) {
1445 static int doing_queue = 0;
1448 * This is a simple concurrency check to make sure only one queue run
1449 * is done at a time. We could do this with a mutex, but since we
1450 * don't really require extremely fine granularity here, we'll do it
1451 * with a static variable instead.
1453 if (doing_queue) return;
1457 * Go ahead and run the queue
1459 lprintf(7, "SMTP: processing outbound queue\n");
1461 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1462 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1465 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1466 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1468 lprintf(7, "SMTP: queue run completed\n");
1475 /*****************************************************************************/
1476 /* SMTP UTILITY COMMANDS */
1477 /*****************************************************************************/
1479 void cmd_smtp(char *argbuf) {
1486 if (CtdlAccessCheck(ac_aide)) return;
1488 extract(cmd, argbuf, 0);
1490 if (!strcasecmp(cmd, "mx")) {
1491 extract(node, argbuf, 1);
1492 num_mxhosts = getmx(buf, node);
1493 cprintf("%d %d MX hosts listed for %s\n",
1494 LISTING_FOLLOWS, num_mxhosts, node);
1495 for (i=0; i<num_mxhosts; ++i) {
1496 extract(node, buf, i);
1497 cprintf("%s\n", node);
1503 else if (!strcasecmp(cmd, "runqueue")) {
1505 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1510 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1518 /*****************************************************************************/
1519 /* MODULE INITIALIZATION STUFF */
1520 /*****************************************************************************/
1523 char *Dynamic_Module_Init(void)
1525 SYM_SMTP = CtdlGetDynamicSymbol();
1526 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1528 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1533 CtdlRegisterServiceHook(0, /* ...and locally */
1538 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1539 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1540 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");