4 * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
17 #include <sys/types.h>
26 #include "sysdep_decls.h"
27 #include "citserver.h"
31 #include "dynloader.h"
38 #include "internet_addressing.h"
41 #include "clientsocket.h"
44 struct citsmtp { /* Information about the current session */
47 struct usersupp vrfy_buffer;
51 int number_of_recipients;
53 int message_originated_locally;
56 enum { /* Command states for login authentication */
62 enum { /* Delivery modes */
67 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
68 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
75 /*****************************************************************************/
76 /* SMTP SERVER (INBOUND) STUFF */
77 /*****************************************************************************/
83 * Here's where our SMTP session begins its happy day.
85 void smtp_greeting(void) {
87 strcpy(CC->cs_clientname, "SMTP session");
89 CC->cs_flags |= CS_STEALTH;
90 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
91 CtdlAllocUserData(SYM_SMTP_RECP, 256);
92 sprintf(SMTP_RECP, "%s", "");
94 cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\r\n",
100 * Implement HELO and EHLO commands.
102 void smtp_hello(char *argbuf, int is_esmtp) {
104 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
107 cprintf("250 Greetings and joyous salutations.\r\n");
110 cprintf("250-Greetings and joyous salutations.\r\n");
111 cprintf("250-HELP\r\n");
112 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
113 cprintf("250 AUTH=LOGIN\r\n");
119 * Implement HELP command.
121 void smtp_help(void) {
122 cprintf("214-Here's the frequency, Kenneth:\r\n");
123 cprintf("214- DATA\r\n");
124 cprintf("214- EHLO\r\n");
125 cprintf("214- EXPN\r\n");
126 cprintf("214- HELO\r\n");
127 cprintf("214- HELP\r\n");
128 cprintf("214- MAIL\r\n");
129 cprintf("214- NOOP\r\n");
130 cprintf("214- QUIT\r\n");
131 cprintf("214- RCPT\r\n");
132 cprintf("214- RSET\r\n");
133 cprintf("214- VRFY\r\n");
134 cprintf("214 I could tell you more, but then I'd have to kill you.\r\n");
141 void smtp_get_user(char *argbuf) {
145 decode_base64(username, argbuf);
146 lprintf(9, "Trying <%s>\n", username);
147 if (CtdlLoginExistingUser(username) == login_ok) {
148 encode_base64(buf, "Password:");
149 cprintf("334 %s\r\n", buf);
150 SMTP->command_state = smtp_password;
153 cprintf("500 No such user.\r\n");
154 SMTP->command_state = smtp_command;
162 void smtp_get_pass(char *argbuf) {
165 decode_base64(password, argbuf);
166 lprintf(9, "Trying <%s>\n", password);
167 if (CtdlTryPassword(password) == pass_ok) {
168 cprintf("235 Authentication successful.\r\n");
169 lprintf(9, "SMTP authenticated login successful\n");
170 CC->internal_pgm = 0;
171 CC->cs_flags &= ~CS_STEALTH;
174 cprintf("500 Authentication failed.\r\n");
176 SMTP->command_state = smtp_command;
183 void smtp_auth(char *argbuf) {
186 if (strncasecmp(argbuf, "login", 5) ) {
187 cprintf("550 We only support LOGIN authentication.\r\n");
191 if (strlen(argbuf) >= 7) {
192 smtp_get_user(&argbuf[6]);
196 encode_base64(buf, "Username:");
197 cprintf("334 %s\r\n", buf);
198 SMTP->command_state = smtp_user;
204 * Back end for smtp_vrfy() command
206 void smtp_vrfy_backend(struct usersupp *us, void *data) {
208 if (!fuzzy_match(us, SMTP->vrfy_match)) {
210 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
216 * Implements the VRFY (verify user name) command.
217 * Performs fuzzy match on full user names.
219 void smtp_vrfy(char *argbuf) {
220 SMTP->vrfy_count = 0;
221 strcpy(SMTP->vrfy_match, argbuf);
222 ForEachUser(smtp_vrfy_backend, NULL);
224 if (SMTP->vrfy_count < 1) {
225 cprintf("550 String does not match anything.\r\n");
227 else if (SMTP->vrfy_count == 1) {
228 cprintf("250 %s <cit%ld@%s>\r\n",
229 SMTP->vrfy_buffer.fullname,
230 SMTP->vrfy_buffer.usernum,
233 else if (SMTP->vrfy_count > 1) {
234 cprintf("553 Request ambiguous: %d users matched.\r\n",
243 * Back end for smtp_expn() command
245 void smtp_expn_backend(struct usersupp *us, void *data) {
247 if (!fuzzy_match(us, SMTP->vrfy_match)) {
249 if (SMTP->vrfy_count >= 1) {
250 cprintf("250-%s <cit%ld@%s>\r\n",
251 SMTP->vrfy_buffer.fullname,
252 SMTP->vrfy_buffer.usernum,
257 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
263 * Implements the EXPN (expand user name) command.
264 * Performs fuzzy match on full user names.
266 void smtp_expn(char *argbuf) {
267 SMTP->vrfy_count = 0;
268 strcpy(SMTP->vrfy_match, argbuf);
269 ForEachUser(smtp_expn_backend, NULL);
271 if (SMTP->vrfy_count < 1) {
272 cprintf("550 String does not match anything.\r\n");
274 else if (SMTP->vrfy_count >= 1) {
275 cprintf("250 %s <cit%ld@%s>\r\n",
276 SMTP->vrfy_buffer.fullname,
277 SMTP->vrfy_buffer.usernum,
284 * Implements the RSET (reset state) command.
285 * Currently this just zeroes out the state buffer. If pointers to data
286 * allocated with mallok() are ever placed in the state buffer, we have to
287 * be sure to phree() them first!
289 void smtp_rset(void) {
290 memset(SMTP, 0, sizeof(struct citsmtp));
291 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
292 if (CC->logged_in) logout(CC);
293 cprintf("250 Zap!\r\n");
297 * Clear out the portions of the state buffer that need to be cleared out
298 * after the DATA command finishes.
300 void smtp_data_clear(void) {
301 strcpy(SMTP->from, "");
302 SMTP->number_of_recipients = 0;
303 SMTP->delivery_mode = 0;
304 SMTP->message_originated_locally = 0;
305 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
311 * Implements the "MAIL From:" command
313 void smtp_mail(char *argbuf) {
318 if (strlen(SMTP->from) != 0) {
319 cprintf("503 Only one sender permitted\r\n");
323 if (strncasecmp(argbuf, "From:", 5)) {
324 cprintf("501 Syntax error\r\n");
328 strcpy(SMTP->from, &argbuf[5]);
331 if (strlen(SMTP->from) == 0) {
332 cprintf("501 Empty sender name is not permitted\r\n");
337 /* If this SMTP connection is from a logged-in user, make sure that
338 * the user only sends email from his/her own address.
341 cvt = convert_internet_address(user, node, SMTP->from);
342 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
343 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
344 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
345 strcpy(SMTP->from, "");
349 SMTP->message_originated_locally = 1;
353 /* Otherwise, make sure outsiders aren't trying to forge mail from
357 cvt = convert_internet_address(user, node, SMTP->from);
358 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
359 if (CtdlHostAlias(node) == hostalias_localhost) {
360 cprintf("550 You must log in to send mail from %s\r\n",
362 strcpy(SMTP->from, "");
367 cprintf("250 Sender ok\r\n");
373 * Implements the "RCPT To:" command
375 void smtp_rcpt(char *argbuf) {
381 if (strlen(SMTP->from) == 0) {
382 cprintf("503 Need MAIL before RCPT\r\n");
386 if (strncasecmp(argbuf, "To:", 3)) {
387 cprintf("501 Syntax error\r\n");
391 strcpy(recp, &argbuf[3]);
397 cvt = convert_internet_address(user, node, recp);
398 snprintf(recp, sizeof recp, "%s@%s", user, node);
399 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
402 case rfc822_address_locally_validated:
403 cprintf("250 %s is a valid recipient.\r\n", user);
404 ++SMTP->number_of_recipients;
405 CtdlReallocUserData(SYM_SMTP_RECP,
406 strlen(SMTP_RECP) + 1024 );
407 strcat(SMTP_RECP, "local|");
408 strcat(SMTP_RECP, user);
409 strcat(SMTP_RECP, "|0\n");
412 case rfc822_room_delivery:
413 cprintf("250 Delivering to room '%s'\r\n", user);
414 ++SMTP->number_of_recipients;
415 CtdlReallocUserData(SYM_SMTP_RECP,
416 strlen(SMTP_RECP) + 1024 );
417 strcat(SMTP_RECP, "room|");
418 strcat(SMTP_RECP, user);
419 strcat(SMTP_RECP, "|0|\n");
422 case rfc822_no_such_user:
423 cprintf("550 %s: no such user\r\n", recp);
426 case rfc822_address_on_citadel_network:
427 cprintf("250 %s is on the local network\r\n", recp);
428 ++SMTP->number_of_recipients;
429 CtdlReallocUserData(SYM_SMTP_RECP,
430 strlen(SMTP_RECP) + 1024 );
431 strcat(SMTP_RECP, "ignet|");
432 strcat(SMTP_RECP, user);
433 strcat(SMTP_RECP, "|");
434 strcat(SMTP_RECP, node);
435 strcat(SMTP_RECP, "|0|\n");
438 case rfc822_address_nonlocal:
439 if (SMTP->message_originated_locally == 0) {
440 cprintf("551 Relaying denied\r\n");
443 cprintf("250 Remote recipient %s ok\r\n", recp);
444 ++SMTP->number_of_recipients;
445 CtdlReallocUserData(SYM_SMTP_RECP,
446 strlen(SMTP_RECP) + 1024 );
447 strcat(SMTP_RECP, "remote|");
448 strcat(SMTP_RECP, recp);
449 strcat(SMTP_RECP, "|0|\n");
455 cprintf("599 Unknown error\r\n");
461 * Send a message out through the local network
462 * (This is kind of ugly. IGnet should be done using clean server-to-server
463 * code instead of the old style spool.)
465 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
467 char *hold_R, *hold_D, *hold_O;
472 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
474 hold_R = msg->cm_fields['R'];
475 hold_D = msg->cm_fields['D'];
476 hold_O = msg->cm_fields['O'];
477 msg->cm_fields['R'] = user;
478 msg->cm_fields['D'] = dest;
479 msg->cm_fields['O'] = MAILROOM;
481 serialize_message(&smr, msg);
483 msg->cm_fields['R'] = hold_R;
484 msg->cm_fields['D'] = hold_D;
485 msg->cm_fields['O'] = hold_O;
488 snprintf(filename, sizeof filename,
489 "./network/spoolin/%s.%04x.%04x",
490 dest, getpid(), ++seq);
491 lprintf(9, "spool file name is <%s>\n", filename);
492 fp = fopen(filename, "wb");
494 fwrite(smr.ser, smr.len, 1, fp);
505 * Back end for smtp_data() ... this does the actual delivery of the message
506 * Returns 0 on success, nonzero on failure
508 int smtp_message_delivery(struct CtdlMessage *msg) {
515 int successful_saves = 0; /* number of successful local saves */
516 int failed_saves = 0; /* number of failed deliveries */
517 int remote_spools = 0; /* number of copies to send out */
520 struct usersupp userbuf;
521 char *instr; /* Remote delivery instructions */
522 struct CtdlMessage *imsg;
524 lprintf(9, "smtp_message_delivery() called\n");
526 /* Fill in 'from' fields with envelope information if missing */
527 process_rfc822_addr(SMTP->from, user, node, name);
528 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
529 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
530 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
531 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
533 /* Save the message in the queue */
534 msgid = CtdlSaveMsg(msg,
540 instr = mallok(1024);
541 snprintf(instr, 1024,
542 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
544 SPOOLMIME, msgid, time(NULL),
547 for (i=0; i<SMTP->number_of_recipients; ++i) {
548 extract_token(buf, SMTP_RECP, i, '\n');
549 extract(dtype, buf, 0);
551 /* Stuff local mailboxes */
552 if (!strcasecmp(dtype, "local")) {
553 extract(user, buf, 1);
554 if (getuser(&userbuf, user) == 0) {
555 MailboxName(room, &userbuf, MAILROOM);
556 CtdlSaveMsgPointerInRoom(room, msgid, 0);
564 /* Delivery to local non-mailbox rooms */
565 if (!strcasecmp(dtype, "room")) {
566 extract(room, buf, 1);
567 CtdlSaveMsgPointerInRoom(room, msgid, 0);
571 /* Delivery over the local Citadel network (IGnet) */
572 if (!strcasecmp(dtype, "ignet")) {
573 extract(user, buf, 1);
574 extract(node, buf, 2);
575 smtp_deliver_ignet(msg, user, node);
578 /* Remote delivery */
579 if (!strcasecmp(dtype, "remote")) {
580 extract(user, buf, 1);
581 instr = reallok(instr, strlen(instr) + 1024);
582 snprintf(&instr[strlen(instr)],
583 strlen(instr) + 1024,
591 /* If there are remote spools to be done, save the instructions */
592 if (remote_spools > 0) {
593 imsg = mallok(sizeof(struct CtdlMessage));
594 memset(imsg, 0, sizeof(struct CtdlMessage));
595 imsg->cm_magic = CTDLMESSAGE_MAGIC;
596 imsg->cm_anon_type = MES_NORMAL;
597 imsg->cm_format_type = FMT_RFC822;
598 imsg->cm_fields['M'] = instr;
599 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
600 CtdlFreeMessage(imsg);
603 /* If there are no remote spools, delete the message */
605 phree(instr); /* only needed here, because CtdlSaveMsg()
606 * would free this buffer otherwise */
607 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
610 return(failed_saves);
616 * Implements the DATA command
618 void smtp_data(void) {
620 struct CtdlMessage *msg;
624 if (strlen(SMTP->from) == 0) {
625 cprintf("503 Need MAIL command first.\r\n");
629 if (SMTP->number_of_recipients < 1) {
630 cprintf("503 Need RCPT command first.\r\n");
634 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
636 generate_rfc822_datestamp(nowstamp, time(NULL));
639 if (body != NULL) snprintf(body, 4096,
640 "Received: from %s\n"
647 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
649 cprintf("550 Unable to save message text: internal error.\r\n");
653 lprintf(9, "Converting message...\n");
654 msg = convert_internet_message(body);
656 /* If the user is locally authenticated, FORCE the From: header to
657 * show up as the real sender
660 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
661 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
662 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
663 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
664 msg->cm_fields['N'] = strdoop(config.c_nodename);
665 msg->cm_fields['H'] = strdoop(config.c_humannode);
668 retval = smtp_message_delivery(msg);
669 CtdlFreeMessage(msg);
672 cprintf("250 Message accepted for delivery.\r\n");
675 cprintf("550 Internal delivery errors: %d\r\n", retval);
678 smtp_data_clear(); /* clear out the buffers now */
685 * Main command loop for SMTP sessions.
687 void smtp_command_loop(void) {
691 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
692 if (client_gets(cmdbuf) < 1) {
693 lprintf(3, "SMTP socket is broken. Ending session.\n");
697 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
698 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
700 if (SMTP->command_state == smtp_user) {
701 smtp_get_user(cmdbuf);
704 else if (SMTP->command_state == smtp_password) {
705 smtp_get_pass(cmdbuf);
708 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
709 smtp_auth(&cmdbuf[5]);
712 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
716 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
717 smtp_hello(&cmdbuf[5], 1);
720 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
721 smtp_expn(&cmdbuf[5]);
724 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
725 smtp_hello(&cmdbuf[5], 0);
728 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
732 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
733 smtp_mail(&cmdbuf[5]);
736 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
737 cprintf("250 This command successfully did nothing.\r\n");
740 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
741 cprintf("221 Goodbye...\r\n");
746 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
747 smtp_rcpt(&cmdbuf[5]);
750 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
754 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
755 smtp_vrfy(&cmdbuf[5]);
759 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
767 /*****************************************************************************/
768 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
769 /*****************************************************************************/
776 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
779 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
786 char user[256], node[256], name[256];
792 size_t blocksize = 0;
795 /* Parse out the host portion of the recipient address */
796 process_rfc822_addr(addr, user, node, name);
798 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
801 /* Load the message out of the database into a temp file */
803 if (msg_fp == NULL) {
805 sprintf(dsn, "Error creating temporary file");
809 CtdlRedirectOutput(msg_fp, -1);
810 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
811 CtdlRedirectOutput(NULL, -1);
812 fseek(msg_fp, 0L, SEEK_END);
813 msg_size = ftell(msg_fp);
817 /* Extract something to send later in the 'MAIL From:' command */
818 strcpy(mailfrom, "");
822 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
823 if (!strncasecmp(buf, "From:", 5)) {
824 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
826 for (i=0; i<strlen(mailfrom); ++i) {
827 if (!isprint(mailfrom[i])) {
828 strcpy(&mailfrom[i], &mailfrom[i+1]);
833 /* Strip out parenthesized names */
836 for (i=0; i<strlen(mailfrom); ++i) {
837 if (mailfrom[i] == '(') lp = i;
838 if (mailfrom[i] == ')') rp = i;
840 if ((lp>0)&&(rp>lp)) {
841 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
844 /* Prefer brokketized names */
847 for (i=0; i<strlen(mailfrom); ++i) {
848 if (mailfrom[i] == '<') lp = i;
849 if (mailfrom[i] == '>') rp = i;
851 if ((lp>=0)&&(rp>lp)) {
853 strcpy(mailfrom, &mailfrom[lp]);
858 } while (scan_done == 0);
859 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
862 /* Figure out what mail exchanger host we have to connect to */
863 num_mxhosts = getmx(mxhosts, node);
864 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
865 if (num_mxhosts < 1) {
867 snprintf(dsn, 256, "No MX hosts found for <%s>", node);
871 for (mx=0; mx<num_mxhosts; ++mx) {
872 extract(buf, mxhosts, mx);
873 lprintf(9, "Trying <%s>\n", buf);
874 sock = sock_connect(buf, "25", "tcp");
875 snprintf(dsn, 256, "Could not connect: %s", strerror(errno));
876 if (sock >= 0) lprintf(9, "Connected!\n");
877 if (sock < 0) snprintf(dsn, 256, "%s", strerror(errno));
878 if (sock >= 0) break;
882 *status = 4; /* dsn is already filled in */
886 /* Process the SMTP greeting from the server */
887 if (ml_sock_gets(sock, buf) < 0) {
889 strcpy(dsn, "Connection broken during SMTP conversation");
892 lprintf(9, "<%s\n", buf);
896 safestrncpy(dsn, &buf[4], 1023);
901 safestrncpy(dsn, &buf[4], 1023);
906 /* At this point we know we are talking to a real SMTP server */
908 /* Do a HELO command */
909 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
910 lprintf(9, ">%s\n", buf);
911 sock_puts_crlf(sock, buf);
912 if (ml_sock_gets(sock, buf) < 0) {
914 strcpy(dsn, "Connection broken during SMTP conversation");
917 lprintf(9, "<%s\n", buf);
921 safestrncpy(dsn, &buf[4], 1023);
926 safestrncpy(dsn, &buf[4], 1023);
932 /* HELO succeeded, now try the MAIL From: command */
933 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
934 lprintf(9, ">%s\n", buf);
935 sock_puts_crlf(sock, buf);
936 if (ml_sock_gets(sock, buf) < 0) {
938 strcpy(dsn, "Connection broken during SMTP conversation");
941 lprintf(9, "<%s\n", buf);
945 safestrncpy(dsn, &buf[4], 1023);
950 safestrncpy(dsn, &buf[4], 1023);
956 /* MAIL succeeded, now try the RCPT To: command */
957 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
958 lprintf(9, ">%s\n", buf);
959 sock_puts_crlf(sock, buf);
960 if (ml_sock_gets(sock, buf) < 0) {
962 strcpy(dsn, "Connection broken during SMTP conversation");
965 lprintf(9, "<%s\n", buf);
969 safestrncpy(dsn, &buf[4], 1023);
974 safestrncpy(dsn, &buf[4], 1023);
980 /* RCPT succeeded, now try the DATA command */
981 lprintf(9, ">DATA\n");
982 sock_puts_crlf(sock, "DATA");
983 if (ml_sock_gets(sock, buf) < 0) {
985 strcpy(dsn, "Connection broken during SMTP conversation");
988 lprintf(9, "<%s\n", buf);
992 safestrncpy(dsn, &buf[4], 1023);
997 safestrncpy(dsn, &buf[4], 1023);
1002 /* If we reach this point, the server is expecting data */
1004 while (msg_size > 0) {
1005 blocksize = sizeof(buf);
1006 if (blocksize > msg_size) blocksize = msg_size;
1007 fread(buf, blocksize, 1, msg_fp);
1008 sock_write(sock, buf, blocksize);
1009 msg_size -= blocksize;
1011 if (buf[blocksize-1] != 10) {
1012 lprintf(5, "Possible problem: message did not correctly "
1013 "terminate. (expecting 0x10, got 0x%02x)\n",
1017 sock_write(sock, ".\r\n", 3);
1018 if (ml_sock_gets(sock, buf) < 0) {
1020 strcpy(dsn, "Connection broken during SMTP conversation");
1023 lprintf(9, "%s\n", buf);
1024 if (buf[0] != '2') {
1025 if (buf[0] == '4') {
1027 safestrncpy(dsn, &buf[4], 1023);
1032 safestrncpy(dsn, &buf[4], 1023);
1038 safestrncpy(dsn, &buf[4], 1023);
1041 lprintf(9, ">QUIT\n");
1042 sock_puts_crlf(sock, "QUIT");
1043 ml_sock_gets(sock, buf);
1044 lprintf(9, "<%s\n", buf);
1046 bail: if (msg_fp != NULL) fclose(msg_fp);
1054 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1055 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1056 * a "bounce" message (delivery status notification).
1058 void smtp_do_bounce(char *instr) {
1066 char bounceto[1024];
1067 int num_bounces = 0;
1068 int bounce_this = 0;
1069 long bounce_msgid = (-1);
1070 time_t submitted = 0L;
1071 struct CtdlMessage *bmsg = NULL;
1075 lprintf(9, "smtp_do_bounce() called\n");
1076 strcpy(bounceto, "");
1078 lines = num_tokens(instr, '\n');
1081 /* See if it's time to give up on delivery of this message */
1082 for (i=0; i<lines; ++i) {
1083 extract_token(buf, instr, i, '\n');
1084 extract(key, buf, 0);
1085 extract(addr, buf, 1);
1086 if (!strcasecmp(key, "submitted")) {
1087 submitted = atol(addr);
1091 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1097 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1098 if (bmsg == NULL) return;
1099 memset(bmsg, 0, sizeof(struct CtdlMessage));
1101 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1102 bmsg->cm_anon_type = MES_NORMAL;
1103 bmsg->cm_format_type = 1;
1104 bmsg->cm_fields['A'] = strdoop("Citadel");
1105 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1106 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1108 if (give_up) bmsg->cm_fields['M'] = strdoop(
1109 "A message you sent could not be delivered to some or all of its recipients\n"
1110 "due to prolonged unavailability of its destination(s).\n"
1111 "Giving up on the following addresses:\n\n"
1114 else bmsg->cm_fields['M'] = strdoop(
1115 "A message you sent could not be delivered to some or all of its recipients.\n"
1116 "The following addresses were undeliverable:\n\n"
1120 * Now go through the instructions checking for stuff.
1123 for (i=0; i<lines; ++i) {
1124 extract_token(buf, instr, i, '\n');
1125 extract(key, buf, 0);
1126 extract(addr, buf, 1);
1127 status = extract_int(buf, 2);
1128 extract(dsn, buf, 3);
1131 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1132 key, addr, status, dsn);
1134 if (!strcasecmp(key, "bounceto")) {
1135 strcpy(bounceto, addr);
1139 (!strcasecmp(key, "local"))
1140 || (!strcasecmp(key, "remote"))
1141 || (!strcasecmp(key, "ignet"))
1142 || (!strcasecmp(key, "room"))
1144 if (status == 5) bounce_this = 1;
1145 if (give_up) bounce_this = 1;
1151 if (bmsg->cm_fields['M'] == NULL) {
1152 lprintf(2, "ERROR ... M field is null "
1153 "(%s:%d)\n", __FILE__, __LINE__);
1156 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1157 strlen(bmsg->cm_fields['M']) + 1024 );
1158 strcat(bmsg->cm_fields['M'], addr);
1159 strcat(bmsg->cm_fields['M'], ": ");
1160 strcat(bmsg->cm_fields['M'], dsn);
1161 strcat(bmsg->cm_fields['M'], "\n");
1163 remove_token(instr, i, '\n');
1169 /* Deliver the bounce if there's anything worth mentioning */
1170 lprintf(9, "num_bounces = %d\n", num_bounces);
1171 if (num_bounces > 0) {
1173 /* First try the user who sent the message */
1174 lprintf(9, "bounce to user? <%s>\n", bounceto);
1176 if (strlen(bounceto) == 0) {
1177 lprintf(7, "No bounce address specified\n");
1178 bounce_msgid = (-1L);
1180 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1181 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1182 bounce_msgid = (-1L);
1185 bounce_msgid = CtdlSaveMsg(bmsg,
1191 /* Otherwise, go to the Aide> room */
1192 lprintf(9, "bounce to room?\n");
1193 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1198 CtdlFreeMessage(bmsg);
1199 lprintf(9, "Done processing bounces\n");
1204 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1205 * set of delivery instructions for completed deliveries and remove them.
1207 * It returns the number of incomplete deliveries remaining.
1209 int smtp_purge_completed_deliveries(char *instr) {
1220 lines = num_tokens(instr, '\n');
1221 for (i=0; i<lines; ++i) {
1222 extract_token(buf, instr, i, '\n');
1223 extract(key, buf, 0);
1224 extract(addr, buf, 1);
1225 status = extract_int(buf, 2);
1226 extract(dsn, buf, 3);
1231 (!strcasecmp(key, "local"))
1232 || (!strcasecmp(key, "remote"))
1233 || (!strcasecmp(key, "ignet"))
1234 || (!strcasecmp(key, "room"))
1236 if (status == 2) completed = 1;
1241 remove_token(instr, i, '\n');
1254 * Called by smtp_do_queue() to handle an individual message.
1256 void smtp_do_procmsg(long msgnum) {
1257 struct CtdlMessage *msg;
1259 char *results = NULL;
1267 long text_msgid = (-1);
1268 int incomplete_deliveries_remaining;
1269 time_t attempted = 0L;
1270 time_t last_attempted = 0L;
1271 time_t retry = SMTP_RETRY_INTERVAL;
1273 msg = CtdlFetchMessage(msgnum);
1275 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1279 instr = strdoop(msg->cm_fields['M']);
1280 CtdlFreeMessage(msg);
1282 /* Strip out the headers amd any other non-instruction line */
1283 lines = num_tokens(instr, '\n');
1284 for (i=0; i<lines; ++i) {
1285 extract_token(buf, instr, i, '\n');
1286 if (num_tokens(buf, '|') < 2) {
1287 lprintf(9, "removing <%s>\n", buf);
1288 remove_token(instr, i, '\n');
1294 /* Learn the message ID and find out about recent delivery attempts */
1295 lines = num_tokens(instr, '\n');
1296 for (i=0; i<lines; ++i) {
1297 extract_token(buf, instr, i, '\n');
1298 extract(key, buf, 0);
1299 if (!strcasecmp(key, "msgid")) {
1300 text_msgid = extract_long(buf, 1);
1302 if (!strcasecmp(key, "retry")) {
1303 /* double the retry interval after each attempt */
1304 retry = extract_long(buf, 1) * 2L;
1305 remove_token(instr, i, '\n');
1307 if (!strcasecmp(key, "attempted")) {
1308 attempted = extract_long(buf, 1);
1309 if (attempted > last_attempted)
1310 last_attempted = attempted;
1316 * Postpone delivery if we've already tried recently.
1318 if ( (time(NULL) - last_attempted) < retry) {
1319 lprintf(7, "Retry time not yet reached.\n");
1326 * Bail out if there's no actual message associated with this
1328 if (text_msgid < 0L) {
1329 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1334 /* Plow through the instructions looking for 'remote' directives and
1335 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1336 * were experienced and it's time to try again)
1338 lines = num_tokens(instr, '\n');
1339 for (i=0; i<lines; ++i) {
1340 extract_token(buf, instr, i, '\n');
1341 extract(key, buf, 0);
1342 extract(addr, buf, 1);
1343 status = extract_int(buf, 2);
1344 extract(dsn, buf, 3);
1345 if ( (!strcasecmp(key, "remote"))
1346 && ((status==0)||(status==3)) ) {
1347 remove_token(instr, i, '\n');
1350 lprintf(9, "SMTP: Trying <%s>\n", addr);
1351 smtp_try(key, addr, &status, dsn, text_msgid);
1353 if (results == NULL) {
1354 results = mallok(1024);
1355 memset(results, 0, 1024);
1358 results = reallok(results,
1359 strlen(results) + 1024);
1361 sprintf(&results[strlen(results)],
1363 key, addr, status, dsn);
1368 if (results != NULL) {
1369 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1370 strcat(instr, results);
1375 /* Generate 'bounce' messages */
1376 smtp_do_bounce(instr);
1378 /* Go through the delivery list, deleting completed deliveries */
1379 incomplete_deliveries_remaining =
1380 smtp_purge_completed_deliveries(instr);
1384 * No delivery instructions remain, so delete both the instructions
1385 * message and the message message.
1387 if (incomplete_deliveries_remaining <= 0) {
1388 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1389 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1394 * Uncompleted delivery instructions remain, so delete the old
1395 * instructions and replace with the updated ones.
1397 if (incomplete_deliveries_remaining > 0) {
1398 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1399 msg = mallok(sizeof(struct CtdlMessage));
1400 memset(msg, 0, sizeof(struct CtdlMessage));
1401 msg->cm_magic = CTDLMESSAGE_MAGIC;
1402 msg->cm_anon_type = MES_NORMAL;
1403 msg->cm_format_type = FMT_RFC822;
1404 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1405 snprintf(msg->cm_fields['M'],
1407 "Content-type: %s\n\n%s\n"
1410 SPOOLMIME, instr, time(NULL), retry );
1412 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1413 CtdlFreeMessage(msg);
1423 * Run through the queue sending out messages.
1425 void smtp_do_queue(void) {
1426 static int doing_queue = 0;
1429 * This is a simple concurrency check to make sure only one queue run
1430 * is done at a time. We could do this with a mutex, but since we
1431 * don't really require extremely fine granularity here, we'll do it
1432 * with a static variable instead.
1434 if (doing_queue) return;
1438 * Go ahead and run the queue
1440 lprintf(7, "SMTP: processing outbound queue\n");
1442 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1443 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1446 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1447 SPOOLMIME, NULL, smtp_do_procmsg);
1449 lprintf(7, "SMTP: queue run completed\n");
1455 /*****************************************************************************/
1456 /* MODULE INITIALIZATION STUFF */
1457 /*****************************************************************************/
1460 char *Dynamic_Module_Init(void)
1462 SYM_SMTP = CtdlGetDynamicSymbol();
1463 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1465 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1470 CtdlRegisterServiceHook(0, /* ...and locally */
1475 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1476 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);