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"
54 struct citsmtp { /* Information about the current session */
57 struct usersupp vrfy_buffer;
61 int number_of_recipients;
63 int message_originated_locally;
66 enum { /* Command states for login authentication */
72 enum { /* Delivery modes */
77 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
78 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
85 /*****************************************************************************/
86 /* SMTP SERVER (INBOUND) STUFF */
87 /*****************************************************************************/
93 * Here's where our SMTP session begins its happy day.
95 void smtp_greeting(void) {
97 strcpy(CC->cs_clientname, "SMTP session");
99 CC->cs_flags |= CS_STEALTH;
100 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
101 CtdlAllocUserData(SYM_SMTP_RECP, SIZ);
102 sprintf(SMTP_RECP, "%s", "");
104 cprintf("220 Citadel/UX ESMTP server at %s ready.\r\n",
110 * Implement HELO and EHLO commands.
112 void smtp_hello(char *argbuf, int is_esmtp) {
114 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
117 cprintf("250 Greetings and joyous salutations.\r\n");
120 cprintf("250-Greetings and joyous salutations.\r\n");
121 cprintf("250-HELP\r\n");
122 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
123 cprintf("250 AUTH=LOGIN\r\n");
129 * Implement HELP command.
131 void smtp_help(void) {
132 cprintf("214-Commands accepted:\r\n");
133 cprintf("214- DATA\r\n");
134 cprintf("214- EHLO\r\n");
135 cprintf("214- EXPN\r\n");
136 cprintf("214- HELO\r\n");
137 cprintf("214- HELP\r\n");
138 cprintf("214- MAIL\r\n");
139 cprintf("214- NOOP\r\n");
140 cprintf("214- QUIT\r\n");
141 cprintf("214- RCPT\r\n");
142 cprintf("214- RSET\r\n");
143 cprintf("214- VRFY\r\n");
151 void smtp_get_user(char *argbuf) {
155 decode_base64(username, argbuf);
156 lprintf(9, "Trying <%s>\n", username);
157 if (CtdlLoginExistingUser(username) == login_ok) {
158 encode_base64(buf, "Password:");
159 cprintf("334 %s\r\n", buf);
160 SMTP->command_state = smtp_password;
163 cprintf("500 No such user.\r\n");
164 SMTP->command_state = smtp_command;
172 void smtp_get_pass(char *argbuf) {
175 decode_base64(password, argbuf);
176 lprintf(9, "Trying <%s>\n", password);
177 if (CtdlTryPassword(password) == pass_ok) {
178 cprintf("235 Authentication successful.\r\n");
179 lprintf(9, "SMTP authenticated login successful\n");
180 CC->internal_pgm = 0;
181 CC->cs_flags &= ~CS_STEALTH;
184 cprintf("500 Authentication failed.\r\n");
186 SMTP->command_state = smtp_command;
193 void smtp_auth(char *argbuf) {
196 if (strncasecmp(argbuf, "login", 5) ) {
197 cprintf("550 We only support LOGIN authentication.\r\n");
201 if (strlen(argbuf) >= 7) {
202 smtp_get_user(&argbuf[6]);
206 encode_base64(buf, "Username:");
207 cprintf("334 %s\r\n", buf);
208 SMTP->command_state = smtp_user;
214 * Back end for smtp_vrfy() command
216 void smtp_vrfy_backend(struct usersupp *us, void *data) {
218 if (!fuzzy_match(us, SMTP->vrfy_match)) {
220 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
226 * Implements the VRFY (verify user name) command.
227 * Performs fuzzy match on full user names.
229 void smtp_vrfy(char *argbuf) {
230 SMTP->vrfy_count = 0;
231 strcpy(SMTP->vrfy_match, argbuf);
232 ForEachUser(smtp_vrfy_backend, NULL);
234 if (SMTP->vrfy_count < 1) {
235 cprintf("550 String does not match anything.\r\n");
237 else if (SMTP->vrfy_count == 1) {
238 cprintf("250 %s <cit%ld@%s>\r\n",
239 SMTP->vrfy_buffer.fullname,
240 SMTP->vrfy_buffer.usernum,
243 else if (SMTP->vrfy_count > 1) {
244 cprintf("553 Request ambiguous: %d users matched.\r\n",
253 * Back end for smtp_expn() command
255 void smtp_expn_backend(struct usersupp *us, void *data) {
257 if (!fuzzy_match(us, SMTP->vrfy_match)) {
259 if (SMTP->vrfy_count >= 1) {
260 cprintf("250-%s <cit%ld@%s>\r\n",
261 SMTP->vrfy_buffer.fullname,
262 SMTP->vrfy_buffer.usernum,
267 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
273 * Implements the EXPN (expand user name) command.
274 * Performs fuzzy match on full user names.
276 void smtp_expn(char *argbuf) {
277 SMTP->vrfy_count = 0;
278 strcpy(SMTP->vrfy_match, argbuf);
279 ForEachUser(smtp_expn_backend, NULL);
281 if (SMTP->vrfy_count < 1) {
282 cprintf("550 String does not match anything.\r\n");
284 else if (SMTP->vrfy_count >= 1) {
285 cprintf("250 %s <cit%ld@%s>\r\n",
286 SMTP->vrfy_buffer.fullname,
287 SMTP->vrfy_buffer.usernum,
294 * Implements the RSET (reset state) command.
295 * Currently this just zeroes out the state buffer. If pointers to data
296 * allocated with mallok() are ever placed in the state buffer, we have to
297 * be sure to phree() them first!
299 void smtp_rset(void) {
300 memset(SMTP, 0, sizeof(struct citsmtp));
301 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
302 if (CC->logged_in) logout(CC);
303 cprintf("250 Zap!\r\n");
307 * Clear out the portions of the state buffer that need to be cleared out
308 * after the DATA command finishes.
310 void smtp_data_clear(void) {
311 strcpy(SMTP->from, "");
312 SMTP->number_of_recipients = 0;
313 SMTP->delivery_mode = 0;
314 SMTP->message_originated_locally = 0;
315 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
321 * Implements the "MAIL From:" command
323 void smtp_mail(char *argbuf) {
328 if (strlen(SMTP->from) != 0) {
329 cprintf("503 Only one sender permitted\r\n");
333 if (strncasecmp(argbuf, "From:", 5)) {
334 cprintf("501 Syntax error\r\n");
338 strcpy(SMTP->from, &argbuf[5]);
341 if (strlen(SMTP->from) == 0) {
342 cprintf("501 Empty sender name is not permitted\r\n");
347 /* If this SMTP connection is from a logged-in user, make sure that
348 * the user only sends email from his/her own address.
351 cvt = convert_internet_address(user, node, SMTP->from);
352 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
353 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
354 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
355 strcpy(SMTP->from, "");
359 SMTP->message_originated_locally = 1;
363 /* Otherwise, make sure outsiders aren't trying to forge mail from
368 cvt = convert_internet_address(user, node, SMTP->from);
370 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
371 if (CtdlHostAlias(node) == hostalias_localhost) {
373 cprintf("550 You must log in to send mail from %s\r\n",
375 strcpy(SMTP->from, "");
380 cprintf("250 Sender ok\r\n");
386 * Implements the "RCPT To:" command
388 void smtp_rcpt(char *argbuf) {
394 if (strlen(SMTP->from) == 0) {
395 cprintf("503 Need MAIL before RCPT\r\n");
399 if (strncasecmp(argbuf, "To:", 3)) {
400 cprintf("501 Syntax error\r\n");
404 strcpy(recp, &argbuf[3]);
410 cvt = convert_internet_address(user, node, recp);
411 snprintf(recp, sizeof recp, "%s@%s", user, node);
412 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
415 case rfc822_address_locally_validated:
416 cprintf("250 %s is a valid recipient.\r\n", user);
417 ++SMTP->number_of_recipients;
418 CtdlReallocUserData(SYM_SMTP_RECP,
419 strlen(SMTP_RECP) + 1024 );
420 strcat(SMTP_RECP, "local|");
421 strcat(SMTP_RECP, user);
422 strcat(SMTP_RECP, "|0\n");
425 case rfc822_room_delivery:
426 cprintf("250 Delivering to room '%s'\r\n", user);
427 ++SMTP->number_of_recipients;
428 CtdlReallocUserData(SYM_SMTP_RECP,
429 strlen(SMTP_RECP) + 1024 );
430 strcat(SMTP_RECP, "room|");
431 strcat(SMTP_RECP, user);
432 strcat(SMTP_RECP, "|0|\n");
435 case rfc822_no_such_user:
436 cprintf("550 %s: no such user\r\n", recp);
439 case rfc822_address_on_citadel_network:
440 cprintf("250 %s is on the local network\r\n", recp);
441 ++SMTP->number_of_recipients;
442 CtdlReallocUserData(SYM_SMTP_RECP,
443 strlen(SMTP_RECP) + 1024 );
444 strcat(SMTP_RECP, "ignet|");
445 strcat(SMTP_RECP, user);
446 strcat(SMTP_RECP, "|");
447 strcat(SMTP_RECP, node);
448 strcat(SMTP_RECP, "|0|\n");
451 case rfc822_address_nonlocal:
452 if (SMTP->message_originated_locally == 0) {
453 cprintf("551 Third-party relaying denied.\r\n");
456 cprintf("250 Remote recipient %s ok\r\n", recp);
457 ++SMTP->number_of_recipients;
458 CtdlReallocUserData(SYM_SMTP_RECP,
459 strlen(SMTP_RECP) + 1024 );
460 strcat(SMTP_RECP, "remote|");
461 strcat(SMTP_RECP, recp);
462 strcat(SMTP_RECP, "|0|\n");
468 cprintf("599 Unknown error\r\n");
474 * Send a message out through the local network
475 * (This is kind of ugly. IGnet should be done using clean server-to-server
476 * code instead of the old style spool.)
478 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
480 char *hold_R, *hold_D, *hold_O;
485 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
487 hold_R = msg->cm_fields['R'];
488 hold_D = msg->cm_fields['D'];
489 hold_O = msg->cm_fields['O'];
490 msg->cm_fields['R'] = user;
491 msg->cm_fields['D'] = dest;
492 msg->cm_fields['O'] = MAILROOM;
494 serialize_message(&smr, msg);
496 msg->cm_fields['R'] = hold_R;
497 msg->cm_fields['D'] = hold_D;
498 msg->cm_fields['O'] = hold_O;
501 snprintf(filename, sizeof filename,
502 "./network/spoolin/%s.%04x.%04x",
503 dest, getpid(), ++seq);
504 lprintf(9, "spool file name is <%s>\n", filename);
505 fp = fopen(filename, "wb");
507 fwrite(smr.ser, smr.len, 1, fp);
518 * Back end for smtp_data() ... this does the actual delivery of the message
519 * Returns 0 on success, nonzero on failure
521 int smtp_message_delivery(struct CtdlMessage *msg) {
528 int successful_saves = 0; /* number of successful local saves */
529 int failed_saves = 0; /* number of failed deliveries */
530 int remote_spools = 0; /* number of copies to send out */
533 struct usersupp userbuf;
534 char *instr; /* Remote delivery instructions */
535 struct CtdlMessage *imsg;
537 lprintf(9, "smtp_message_delivery() called\n");
539 /* Fill in 'from' fields with envelope information if missing */
540 process_rfc822_addr(SMTP->from, user, node, name);
541 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
542 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
543 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
544 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
546 /* Save the message in the queue */
547 msgid = CtdlSaveMsg(msg,
553 instr = mallok(1024);
554 snprintf(instr, 1024,
555 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
557 SPOOLMIME, msgid, 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 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
613 CtdlFreeMessage(imsg);
616 /* If there are no remote spools, delete the message */
618 phree(instr); /* only needed here, because CtdlSaveMsg()
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 text: 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
673 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
674 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
675 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
676 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
677 msg->cm_fields['N'] = strdoop(config.c_nodename);
678 msg->cm_fields['H'] = strdoop(config.c_humannode);
681 retval = smtp_message_delivery(msg);
682 CtdlFreeMessage(msg);
685 cprintf("250 ok terrific\r\n");
688 cprintf("550 Internal delivery errors: %d\r\n", retval);
691 smtp_data_clear(); /* clear out the buffers now */
698 * Main command loop for SMTP sessions.
700 void smtp_command_loop(void) {
704 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
705 if (client_gets(cmdbuf) < 1) {
706 lprintf(3, "SMTP socket is broken. Ending session.\n");
710 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
711 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
713 if (SMTP->command_state == smtp_user) {
714 smtp_get_user(cmdbuf);
717 else if (SMTP->command_state == smtp_password) {
718 smtp_get_pass(cmdbuf);
721 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
722 smtp_auth(&cmdbuf[5]);
725 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
729 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
730 smtp_hello(&cmdbuf[5], 1);
733 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
734 smtp_expn(&cmdbuf[5]);
737 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
738 smtp_hello(&cmdbuf[5], 0);
741 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
745 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
746 smtp_mail(&cmdbuf[5]);
749 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
750 cprintf("250 This command successfully did nothing.\r\n");
753 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
754 cprintf("221 Goodbye...\r\n");
759 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
760 smtp_rcpt(&cmdbuf[5]);
763 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
767 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
768 smtp_vrfy(&cmdbuf[5]);
772 cprintf("502 I'm afraid I can't do that.\r\n");
780 /*****************************************************************************/
781 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
782 /*****************************************************************************/
789 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
792 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
799 char user[SIZ], node[SIZ], name[SIZ];
805 size_t blocksize = 0;
808 /* Parse out the host portion of the recipient address */
809 process_rfc822_addr(addr, user, node, name);
811 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
814 /* Load the message out of the database into a temp file */
816 if (msg_fp == NULL) {
818 sprintf(dsn, "Error creating temporary file");
822 CtdlRedirectOutput(msg_fp, -1);
823 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
824 CtdlRedirectOutput(NULL, -1);
825 fseek(msg_fp, 0L, SEEK_END);
826 msg_size = ftell(msg_fp);
830 /* Extract something to send later in the 'MAIL From:' command */
831 strcpy(mailfrom, "");
835 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
836 if (!strncasecmp(buf, "From:", 5)) {
837 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
839 for (i=0; i<strlen(mailfrom); ++i) {
840 if (!isprint(mailfrom[i])) {
841 strcpy(&mailfrom[i], &mailfrom[i+1]);
846 /* Strip out parenthesized names */
849 for (i=0; i<strlen(mailfrom); ++i) {
850 if (mailfrom[i] == '(') lp = i;
851 if (mailfrom[i] == ')') rp = i;
853 if ((lp>0)&&(rp>lp)) {
854 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
857 /* Prefer brokketized names */
860 for (i=0; i<strlen(mailfrom); ++i) {
861 if (mailfrom[i] == '<') lp = i;
862 if (mailfrom[i] == '>') rp = i;
864 if ((lp>=0)&&(rp>lp)) {
866 strcpy(mailfrom, &mailfrom[lp]);
871 } while (scan_done == 0);
872 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
875 /* Figure out what mail exchanger host we have to connect to */
876 num_mxhosts = getmx(mxhosts, node);
877 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
878 if (num_mxhosts < 1) {
880 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
884 for (mx=0; mx<num_mxhosts; ++mx) {
885 extract(buf, mxhosts, mx);
886 lprintf(9, "Trying <%s>\n", buf);
887 sock = sock_connect(buf, "25", "tcp");
888 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
889 if (sock >= 0) lprintf(9, "Connected!\n");
890 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
891 if (sock >= 0) break;
895 *status = 4; /* dsn is already filled in */
899 /* Process the SMTP greeting from the server */
900 if (ml_sock_gets(sock, buf) < 0) {
902 strcpy(dsn, "Connection broken during SMTP conversation");
905 lprintf(9, "<%s\n", buf);
909 safestrncpy(dsn, &buf[4], 1023);
914 safestrncpy(dsn, &buf[4], 1023);
919 /* At this point we know we are talking to a real SMTP server */
921 /* Do a HELO command */
922 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
923 lprintf(9, ">%s\n", buf);
924 sock_puts_crlf(sock, buf);
925 if (ml_sock_gets(sock, buf) < 0) {
927 strcpy(dsn, "Connection broken during SMTP conversation");
930 lprintf(9, "<%s\n", buf);
934 safestrncpy(dsn, &buf[4], 1023);
939 safestrncpy(dsn, &buf[4], 1023);
945 /* HELO succeeded, now try the MAIL From: command */
946 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
947 lprintf(9, ">%s\n", buf);
948 sock_puts_crlf(sock, buf);
949 if (ml_sock_gets(sock, buf) < 0) {
951 strcpy(dsn, "Connection broken during SMTP conversation");
954 lprintf(9, "<%s\n", buf);
958 safestrncpy(dsn, &buf[4], 1023);
963 safestrncpy(dsn, &buf[4], 1023);
969 /* MAIL succeeded, now try the RCPT To: command */
970 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
971 lprintf(9, ">%s\n", buf);
972 sock_puts_crlf(sock, buf);
973 if (ml_sock_gets(sock, buf) < 0) {
975 strcpy(dsn, "Connection broken during SMTP conversation");
978 lprintf(9, "<%s\n", buf);
982 safestrncpy(dsn, &buf[4], 1023);
987 safestrncpy(dsn, &buf[4], 1023);
993 /* RCPT succeeded, now try the DATA command */
994 lprintf(9, ">DATA\n");
995 sock_puts_crlf(sock, "DATA");
996 if (ml_sock_gets(sock, buf) < 0) {
998 strcpy(dsn, "Connection broken during SMTP conversation");
1001 lprintf(9, "<%s\n", buf);
1002 if (buf[0] != '3') {
1003 if (buf[0] == '4') {
1005 safestrncpy(dsn, &buf[4], 1023);
1010 safestrncpy(dsn, &buf[4], 1023);
1015 /* If we reach this point, the server is expecting data */
1017 while (msg_size > 0) {
1018 blocksize = sizeof(buf);
1019 if (blocksize > msg_size) blocksize = msg_size;
1020 fread(buf, blocksize, 1, msg_fp);
1021 sock_write(sock, buf, blocksize);
1022 msg_size -= blocksize;
1024 if (buf[blocksize-1] != 10) {
1025 lprintf(5, "Possible problem: message did not correctly "
1026 "terminate. (expecting 0x10, got 0x%02x)\n",
1030 sock_write(sock, ".\r\n", 3);
1031 if (ml_sock_gets(sock, buf) < 0) {
1033 strcpy(dsn, "Connection broken during SMTP conversation");
1036 lprintf(9, "%s\n", buf);
1037 if (buf[0] != '2') {
1038 if (buf[0] == '4') {
1040 safestrncpy(dsn, &buf[4], 1023);
1045 safestrncpy(dsn, &buf[4], 1023);
1051 safestrncpy(dsn, &buf[4], 1023);
1054 lprintf(9, ">QUIT\n");
1055 sock_puts_crlf(sock, "QUIT");
1056 ml_sock_gets(sock, buf);
1057 lprintf(9, "<%s\n", buf);
1059 bail: if (msg_fp != NULL) fclose(msg_fp);
1067 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1068 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1069 * a "bounce" message (delivery status notification).
1071 void smtp_do_bounce(char *instr) {
1079 char bounceto[1024];
1080 int num_bounces = 0;
1081 int bounce_this = 0;
1082 long bounce_msgid = (-1);
1083 time_t submitted = 0L;
1084 struct CtdlMessage *bmsg = NULL;
1088 lprintf(9, "smtp_do_bounce() called\n");
1089 strcpy(bounceto, "");
1091 lines = num_tokens(instr, '\n');
1094 /* See if it's time to give up on delivery of this message */
1095 for (i=0; i<lines; ++i) {
1096 extract_token(buf, instr, i, '\n');
1097 extract(key, buf, 0);
1098 extract(addr, buf, 1);
1099 if (!strcasecmp(key, "submitted")) {
1100 submitted = atol(addr);
1104 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1110 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1111 if (bmsg == NULL) return;
1112 memset(bmsg, 0, sizeof(struct CtdlMessage));
1114 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1115 bmsg->cm_anon_type = MES_NORMAL;
1116 bmsg->cm_format_type = 1;
1117 bmsg->cm_fields['A'] = strdoop("Citadel");
1118 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1119 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1121 if (give_up) bmsg->cm_fields['M'] = strdoop(
1122 "A message you sent could not be delivered to some or all of its recipients\n"
1123 "due to prolonged unavailability of its destination(s).\n"
1124 "Giving up on the following addresses:\n\n"
1127 else bmsg->cm_fields['M'] = strdoop(
1128 "A message you sent could not be delivered to some or all of its recipients.\n"
1129 "The following addresses were undeliverable:\n\n"
1133 * Now go through the instructions checking for stuff.
1136 for (i=0; i<lines; ++i) {
1137 extract_token(buf, instr, i, '\n');
1138 extract(key, buf, 0);
1139 extract(addr, buf, 1);
1140 status = extract_int(buf, 2);
1141 extract(dsn, buf, 3);
1144 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1145 key, addr, status, dsn);
1147 if (!strcasecmp(key, "bounceto")) {
1148 strcpy(bounceto, addr);
1152 (!strcasecmp(key, "local"))
1153 || (!strcasecmp(key, "remote"))
1154 || (!strcasecmp(key, "ignet"))
1155 || (!strcasecmp(key, "room"))
1157 if (status == 5) bounce_this = 1;
1158 if (give_up) bounce_this = 1;
1164 if (bmsg->cm_fields['M'] == NULL) {
1165 lprintf(2, "ERROR ... M field is null "
1166 "(%s:%d)\n", __FILE__, __LINE__);
1169 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1170 strlen(bmsg->cm_fields['M']) + 1024 );
1171 strcat(bmsg->cm_fields['M'], addr);
1172 strcat(bmsg->cm_fields['M'], ": ");
1173 strcat(bmsg->cm_fields['M'], dsn);
1174 strcat(bmsg->cm_fields['M'], "\n");
1176 remove_token(instr, i, '\n');
1182 /* Deliver the bounce if there's anything worth mentioning */
1183 lprintf(9, "num_bounces = %d\n", num_bounces);
1184 if (num_bounces > 0) {
1186 /* First try the user who sent the message */
1187 lprintf(9, "bounce to user? <%s>\n", bounceto);
1189 if (strlen(bounceto) == 0) {
1190 lprintf(7, "No bounce address specified\n");
1191 bounce_msgid = (-1L);
1193 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1194 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1195 bounce_msgid = (-1L);
1198 bounce_msgid = CtdlSaveMsg(bmsg,
1204 /* Otherwise, go to the Aide> room */
1205 lprintf(9, "bounce to room?\n");
1206 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1211 CtdlFreeMessage(bmsg);
1212 lprintf(9, "Done processing bounces\n");
1217 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1218 * set of delivery instructions for completed deliveries and remove them.
1220 * It returns the number of incomplete deliveries remaining.
1222 int smtp_purge_completed_deliveries(char *instr) {
1233 lines = num_tokens(instr, '\n');
1234 for (i=0; i<lines; ++i) {
1235 extract_token(buf, instr, i, '\n');
1236 extract(key, buf, 0);
1237 extract(addr, buf, 1);
1238 status = extract_int(buf, 2);
1239 extract(dsn, buf, 3);
1244 (!strcasecmp(key, "local"))
1245 || (!strcasecmp(key, "remote"))
1246 || (!strcasecmp(key, "ignet"))
1247 || (!strcasecmp(key, "room"))
1249 if (status == 2) completed = 1;
1254 remove_token(instr, i, '\n');
1267 * Called by smtp_do_queue() to handle an individual message.
1269 void smtp_do_procmsg(long msgnum, void *userdata) {
1270 struct CtdlMessage *msg;
1272 char *results = NULL;
1280 long text_msgid = (-1);
1281 int incomplete_deliveries_remaining;
1282 time_t attempted = 0L;
1283 time_t last_attempted = 0L;
1284 time_t retry = SMTP_RETRY_INTERVAL;
1286 msg = CtdlFetchMessage(msgnum);
1288 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1292 instr = strdoop(msg->cm_fields['M']);
1293 CtdlFreeMessage(msg);
1295 /* Strip out the headers amd any other non-instruction line */
1296 lines = num_tokens(instr, '\n');
1297 for (i=0; i<lines; ++i) {
1298 extract_token(buf, instr, i, '\n');
1299 if (num_tokens(buf, '|') < 2) {
1300 lprintf(9, "removing <%s>\n", buf);
1301 remove_token(instr, i, '\n');
1307 /* Learn the message ID and find out about recent delivery attempts */
1308 lines = num_tokens(instr, '\n');
1309 for (i=0; i<lines; ++i) {
1310 extract_token(buf, instr, i, '\n');
1311 extract(key, buf, 0);
1312 if (!strcasecmp(key, "msgid")) {
1313 text_msgid = extract_long(buf, 1);
1315 if (!strcasecmp(key, "retry")) {
1316 /* double the retry interval after each attempt */
1317 retry = extract_long(buf, 1) * 2L;
1318 remove_token(instr, i, '\n');
1320 if (!strcasecmp(key, "attempted")) {
1321 attempted = extract_long(buf, 1);
1322 if (attempted > last_attempted)
1323 last_attempted = attempted;
1329 * Postpone delivery if we've already tried recently.
1331 if ( (time(NULL) - last_attempted) < retry) {
1332 lprintf(7, "Retry time not yet reached.\n");
1339 * Bail out if there's no actual message associated with this
1341 if (text_msgid < 0L) {
1342 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1347 /* Plow through the instructions looking for 'remote' directives and
1348 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1349 * were experienced and it's time to try again)
1351 lines = num_tokens(instr, '\n');
1352 for (i=0; i<lines; ++i) {
1353 extract_token(buf, instr, i, '\n');
1354 extract(key, buf, 0);
1355 extract(addr, buf, 1);
1356 status = extract_int(buf, 2);
1357 extract(dsn, buf, 3);
1358 if ( (!strcasecmp(key, "remote"))
1359 && ((status==0)||(status==3)) ) {
1360 remove_token(instr, i, '\n');
1363 lprintf(9, "SMTP: Trying <%s>\n", addr);
1364 smtp_try(key, addr, &status, dsn, text_msgid);
1366 if (results == NULL) {
1367 results = mallok(1024);
1368 memset(results, 0, 1024);
1371 results = reallok(results,
1372 strlen(results) + 1024);
1374 sprintf(&results[strlen(results)],
1376 key, addr, status, dsn);
1381 if (results != NULL) {
1382 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1383 strcat(instr, results);
1388 /* Generate 'bounce' messages */
1389 smtp_do_bounce(instr);
1391 /* Go through the delivery list, deleting completed deliveries */
1392 incomplete_deliveries_remaining =
1393 smtp_purge_completed_deliveries(instr);
1397 * No delivery instructions remain, so delete both the instructions
1398 * message and the message message.
1400 if (incomplete_deliveries_remaining <= 0) {
1401 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1402 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1407 * Uncompleted delivery instructions remain, so delete the old
1408 * instructions and replace with the updated ones.
1410 if (incomplete_deliveries_remaining > 0) {
1411 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1412 msg = mallok(sizeof(struct CtdlMessage));
1413 memset(msg, 0, sizeof(struct CtdlMessage));
1414 msg->cm_magic = CTDLMESSAGE_MAGIC;
1415 msg->cm_anon_type = MES_NORMAL;
1416 msg->cm_format_type = FMT_RFC822;
1417 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1418 snprintf(msg->cm_fields['M'],
1420 "Content-type: %s\n\n%s\n"
1423 SPOOLMIME, instr, time(NULL), retry );
1425 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1426 CtdlFreeMessage(msg);
1436 * Run through the queue sending out messages.
1438 void smtp_do_queue(void) {
1439 static int doing_queue = 0;
1442 * This is a simple concurrency check to make sure only one queue run
1443 * is done at a time. We could do this with a mutex, but since we
1444 * don't really require extremely fine granularity here, we'll do it
1445 * with a static variable instead.
1447 if (doing_queue) return;
1451 * Go ahead and run the queue
1453 lprintf(7, "SMTP: processing outbound queue\n");
1455 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1456 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1459 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1460 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1462 lprintf(7, "SMTP: queue run completed\n");
1468 /*****************************************************************************/
1469 /* MODULE INITIALIZATION STUFF */
1470 /*****************************************************************************/
1473 char *Dynamic_Module_Init(void)
1475 SYM_SMTP = CtdlGetDynamicSymbol();
1476 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1478 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1483 CtdlRegisterServiceHook(0, /* ...and locally */
1488 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1489 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);