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 = CtdlSaveMsg(msg,
554 instr = mallok(1024);
555 snprintf(instr, 1024,
556 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
558 SPOOLMIME, msgid, (long)time(NULL),
561 for (i=0; i<SMTP->number_of_recipients; ++i) {
562 extract_token(buf, SMTP_RECP, i, '\n');
563 extract(dtype, buf, 0);
565 /* Stuff local mailboxes */
566 if (!strcasecmp(dtype, "local")) {
567 extract(user, buf, 1);
568 if (getuser(&userbuf, user) == 0) {
569 MailboxName(room, &userbuf, MAILROOM);
570 CtdlSaveMsgPointerInRoom(room, msgid, 0);
578 /* Delivery to local non-mailbox rooms */
579 if (!strcasecmp(dtype, "room")) {
580 extract(room, buf, 1);
581 CtdlSaveMsgPointerInRoom(room, msgid, 0);
585 /* Delivery over the local Citadel network (IGnet) */
586 if (!strcasecmp(dtype, "ignet")) {
587 extract(user, buf, 1);
588 extract(node, buf, 2);
589 smtp_deliver_ignet(msg, user, node);
592 /* Remote delivery */
593 if (!strcasecmp(dtype, "remote")) {
594 extract(user, buf, 1);
595 instr = reallok(instr, strlen(instr) + 1024);
596 snprintf(&instr[strlen(instr)],
597 strlen(instr) + 1024,
605 /* If there are remote spools to be done, save the instructions */
606 if (remote_spools > 0) {
607 imsg = mallok(sizeof(struct CtdlMessage));
608 memset(imsg, 0, sizeof(struct CtdlMessage));
609 imsg->cm_magic = CTDLMESSAGE_MAGIC;
610 imsg->cm_anon_type = MES_NORMAL;
611 imsg->cm_format_type = FMT_RFC822;
612 imsg->cm_fields['M'] = instr;
613 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
614 CtdlFreeMessage(imsg);
617 /* If there are no remote spools, delete the message */
619 phree(instr); /* only needed here, because CtdlSaveMsg()
620 * would free this buffer otherwise */
621 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
624 return(failed_saves);
630 * Implements the DATA command
632 void smtp_data(void) {
634 struct CtdlMessage *msg;
638 if (strlen(SMTP->from) == 0) {
639 cprintf("503 Need MAIL command first.\r\n");
643 if (SMTP->number_of_recipients < 1) {
644 cprintf("503 Need RCPT command first.\r\n");
648 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
650 datestring(nowstamp, time(NULL), DATESTRING_RFC822);
653 if (body != NULL) snprintf(body, 4096,
654 "Received: from %s\n"
661 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
663 cprintf("550 Unable to save message: internal error.\r\n");
667 lprintf(9, "Converting message...\n");
668 msg = convert_internet_message(body);
670 /* If the user is locally authenticated, FORCE the From: header to
671 * show up as the real sender. Yes, this violates the RFC standard,
672 * but IT MAKES SENSE. Comment it out if you don't like this behavior.
675 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
676 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
677 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
678 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
679 msg->cm_fields['N'] = strdoop(config.c_nodename);
680 msg->cm_fields['H'] = strdoop(config.c_humannode);
683 /* Submit the message into the Citadel system. */
684 retval = smtp_message_delivery(msg);
685 CtdlFreeMessage(msg);
688 cprintf("250 Message accepted.\r\n");
691 cprintf("550 Internal delivery errors: %d\r\n", retval);
694 smtp_data_clear(); /* clear out the buffers now */
701 * Main command loop for SMTP sessions.
703 void smtp_command_loop(void) {
707 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
708 if (client_gets(cmdbuf) < 1) {
709 lprintf(3, "SMTP socket is broken. Ending session.\n");
713 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
714 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
716 if (SMTP->command_state == smtp_user) {
717 smtp_get_user(cmdbuf);
720 else if (SMTP->command_state == smtp_password) {
721 smtp_get_pass(cmdbuf);
724 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
725 smtp_auth(&cmdbuf[5]);
728 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
732 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
733 smtp_hello(&cmdbuf[5], 1);
736 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
737 smtp_expn(&cmdbuf[5]);
740 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
741 smtp_hello(&cmdbuf[5], 0);
744 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
748 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
749 smtp_mail(&cmdbuf[5]);
752 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
753 cprintf("250 NOOP\r\n");
756 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
757 cprintf("221 Goodbye...\r\n");
762 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
763 smtp_rcpt(&cmdbuf[5]);
766 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
770 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
771 smtp_vrfy(&cmdbuf[5]);
775 cprintf("502 I'm afraid I can't do that.\r\n");
783 /*****************************************************************************/
784 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
785 /*****************************************************************************/
792 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
795 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
802 char user[SIZ], node[SIZ], name[SIZ];
808 size_t blocksize = 0;
811 /* Parse out the host portion of the recipient address */
812 process_rfc822_addr(addr, user, node, name);
814 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
817 /* Load the message out of the database into a temp file */
819 if (msg_fp == NULL) {
821 sprintf(dsn, "Error creating temporary file");
825 CtdlRedirectOutput(msg_fp, -1);
826 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
827 CtdlRedirectOutput(NULL, -1);
828 fseek(msg_fp, 0L, SEEK_END);
829 msg_size = ftell(msg_fp);
833 /* Extract something to send later in the 'MAIL From:' command */
834 strcpy(mailfrom, "");
838 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
839 if (!strncasecmp(buf, "From:", 5)) {
840 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
842 for (i=0; i<strlen(mailfrom); ++i) {
843 if (!isprint(mailfrom[i])) {
844 strcpy(&mailfrom[i], &mailfrom[i+1]);
849 /* Strip out parenthesized names */
852 for (i=0; i<strlen(mailfrom); ++i) {
853 if (mailfrom[i] == '(') lp = i;
854 if (mailfrom[i] == ')') rp = i;
856 if ((lp>0)&&(rp>lp)) {
857 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
860 /* Prefer brokketized names */
863 for (i=0; i<strlen(mailfrom); ++i) {
864 if (mailfrom[i] == '<') lp = i;
865 if (mailfrom[i] == '>') rp = i;
867 if ((lp>=0)&&(rp>lp)) {
869 strcpy(mailfrom, &mailfrom[lp]);
874 } while (scan_done == 0);
875 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
878 /* Figure out what mail exchanger host we have to connect to */
879 num_mxhosts = getmx(mxhosts, node);
880 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
881 if (num_mxhosts < 1) {
883 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
887 for (mx=0; mx<num_mxhosts; ++mx) {
888 extract(buf, mxhosts, mx);
889 lprintf(9, "Trying <%s>\n", buf);
890 sock = sock_connect(buf, "25", "tcp");
891 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
892 if (sock >= 0) lprintf(9, "Connected!\n");
893 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
894 if (sock >= 0) break;
898 *status = 4; /* dsn is already filled in */
902 /* Process the SMTP greeting from the server */
903 if (ml_sock_gets(sock, buf) < 0) {
905 strcpy(dsn, "Connection broken during SMTP conversation");
908 lprintf(9, "<%s\n", buf);
912 safestrncpy(dsn, &buf[4], 1023);
917 safestrncpy(dsn, &buf[4], 1023);
922 /* At this point we know we are talking to a real SMTP server */
924 /* Do a HELO command */
925 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
926 lprintf(9, ">%s", buf);
927 sock_write(sock, buf, strlen(buf));
928 if (ml_sock_gets(sock, buf) < 0) {
930 strcpy(dsn, "Connection broken during SMTP HELO");
933 lprintf(9, "<%s\n", buf);
937 safestrncpy(dsn, &buf[4], 1023);
942 safestrncpy(dsn, &buf[4], 1023);
948 /* HELO succeeded, now try the MAIL From: command */
949 snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
950 lprintf(9, ">%s", buf);
951 sock_write(sock, buf, strlen(buf));
952 if (ml_sock_gets(sock, buf) < 0) {
954 strcpy(dsn, "Connection broken during SMTP MAIL");
957 lprintf(9, "<%s\n", buf);
961 safestrncpy(dsn, &buf[4], 1023);
966 safestrncpy(dsn, &buf[4], 1023);
972 /* MAIL succeeded, now try the RCPT To: command */
973 snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
974 lprintf(9, ">%s", buf);
975 sock_write(sock, buf, strlen(buf));
976 if (ml_sock_gets(sock, buf) < 0) {
978 strcpy(dsn, "Connection broken during SMTP RCPT");
981 lprintf(9, "<%s\n", buf);
985 safestrncpy(dsn, &buf[4], 1023);
990 safestrncpy(dsn, &buf[4], 1023);
996 /* RCPT succeeded, now try the DATA command */
997 lprintf(9, ">DATA\n");
998 sock_write(sock, "DATA\r\n", 6);
999 if (ml_sock_gets(sock, buf) < 0) {
1001 strcpy(dsn, "Connection broken during SMTP DATA");
1004 lprintf(9, "<%s\n", buf);
1005 if (buf[0] != '3') {
1006 if (buf[0] == '4') {
1008 safestrncpy(dsn, &buf[4], 1023);
1013 safestrncpy(dsn, &buf[4], 1023);
1018 /* If we reach this point, the server is expecting data */
1020 while (msg_size > 0) {
1021 blocksize = sizeof(buf);
1022 if (blocksize > msg_size) blocksize = msg_size;
1023 fread(buf, blocksize, 1, msg_fp);
1024 sock_write(sock, buf, blocksize);
1025 msg_size -= blocksize;
1027 if (buf[blocksize-1] != 10) {
1028 lprintf(5, "Possible problem: message did not correctly "
1029 "terminate. (expecting 0x10, got 0x%02x)\n",
1033 sock_write(sock, ".\r\n", 3);
1034 if (ml_sock_gets(sock, buf) < 0) {
1036 strcpy(dsn, "Connection broken during SMTP message transmit");
1039 lprintf(9, "%s\n", buf);
1040 if (buf[0] != '2') {
1041 if (buf[0] == '4') {
1043 safestrncpy(dsn, &buf[4], 1023);
1048 safestrncpy(dsn, &buf[4], 1023);
1054 safestrncpy(dsn, &buf[4], 1023);
1057 lprintf(9, ">QUIT\n");
1058 sock_write(sock, "QUIT\r\n", 6);
1059 ml_sock_gets(sock, buf);
1060 lprintf(9, "<%s\n", buf);
1062 bail: if (msg_fp != NULL) fclose(msg_fp);
1070 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1071 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1072 * a "bounce" message (delivery status notification).
1074 void smtp_do_bounce(char *instr) {
1082 char bounceto[1024];
1083 int num_bounces = 0;
1084 int bounce_this = 0;
1085 long bounce_msgid = (-1);
1086 time_t submitted = 0L;
1087 struct CtdlMessage *bmsg = NULL;
1091 lprintf(9, "smtp_do_bounce() called\n");
1092 strcpy(bounceto, "");
1094 lines = num_tokens(instr, '\n');
1097 /* See if it's time to give up on delivery of this message */
1098 for (i=0; i<lines; ++i) {
1099 extract_token(buf, instr, i, '\n');
1100 extract(key, buf, 0);
1101 extract(addr, buf, 1);
1102 if (!strcasecmp(key, "submitted")) {
1103 submitted = atol(addr);
1107 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1113 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1114 if (bmsg == NULL) return;
1115 memset(bmsg, 0, sizeof(struct CtdlMessage));
1117 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1118 bmsg->cm_anon_type = MES_NORMAL;
1119 bmsg->cm_format_type = 1;
1120 bmsg->cm_fields['A'] = strdoop("Citadel");
1121 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1122 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1124 if (give_up) bmsg->cm_fields['M'] = strdoop(
1125 "A message you sent could not be delivered to some or all of its recipients\n"
1126 "due to prolonged unavailability of its destination(s).\n"
1127 "Giving up on the following addresses:\n\n"
1130 else bmsg->cm_fields['M'] = strdoop(
1131 "A message you sent could not be delivered to some or all of its recipients.\n"
1132 "The following addresses were undeliverable:\n\n"
1136 * Now go through the instructions checking for stuff.
1139 for (i=0; i<lines; ++i) {
1140 extract_token(buf, instr, i, '\n');
1141 extract(key, buf, 0);
1142 extract(addr, buf, 1);
1143 status = extract_int(buf, 2);
1144 extract(dsn, buf, 3);
1147 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1148 key, addr, status, dsn);
1150 if (!strcasecmp(key, "bounceto")) {
1151 strcpy(bounceto, addr);
1155 (!strcasecmp(key, "local"))
1156 || (!strcasecmp(key, "remote"))
1157 || (!strcasecmp(key, "ignet"))
1158 || (!strcasecmp(key, "room"))
1160 if (status == 5) bounce_this = 1;
1161 if (give_up) bounce_this = 1;
1167 if (bmsg->cm_fields['M'] == NULL) {
1168 lprintf(2, "ERROR ... M field is null "
1169 "(%s:%d)\n", __FILE__, __LINE__);
1172 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1173 strlen(bmsg->cm_fields['M']) + 1024 );
1174 strcat(bmsg->cm_fields['M'], addr);
1175 strcat(bmsg->cm_fields['M'], ": ");
1176 strcat(bmsg->cm_fields['M'], dsn);
1177 strcat(bmsg->cm_fields['M'], "\n");
1179 remove_token(instr, i, '\n');
1185 /* Deliver the bounce if there's anything worth mentioning */
1186 lprintf(9, "num_bounces = %d\n", num_bounces);
1187 if (num_bounces > 0) {
1189 /* First try the user who sent the message */
1190 lprintf(9, "bounce to user? <%s>\n", bounceto);
1192 if (strlen(bounceto) == 0) {
1193 lprintf(7, "No bounce address specified\n");
1194 bounce_msgid = (-1L);
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 = CtdlSaveMsg(bmsg,
1207 /* Otherwise, go to the Aide> room */
1208 lprintf(9, "bounce to room?\n");
1209 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(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 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
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");