11 #include <sys/types.h>
20 #include "sysdep_decls.h"
21 #include "citserver.h"
25 #include "dynloader.h"
32 #include "internet_addressing.h"
35 #include "clientsocket.h"
38 struct citsmtp { /* Information about the current session */
41 struct usersupp vrfy_buffer;
45 int number_of_recipients;
49 enum { /* Command states for login authentication */
55 enum { /* Delivery modes */
60 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
61 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
68 /*****************************************************************************/
69 /* SMTP SERVER (INBOUND) STUFF */
70 /*****************************************************************************/
76 * Here's where our SMTP session begins its happy day.
78 void smtp_greeting(void) {
80 strcpy(CC->cs_clientname, "SMTP session");
82 CC->cs_flags |= CS_STEALTH;
83 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
84 CtdlAllocUserData(SYM_SMTP_RECP, 256);
85 sprintf(SMTP_RECP, "%s", "");
87 cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\r\n",
93 * Implement HELO and EHLO commands.
95 void smtp_hello(char *argbuf, int is_esmtp) {
97 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
100 cprintf("250 Greetings and joyous salutations.\r\n");
103 cprintf("250-Greetings and joyous salutations.\r\n");
104 cprintf("250-HELP\r\n");
105 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
106 cprintf("250 AUTH=LOGIN\r\n");
112 * Implement HELP command.
114 void smtp_help(void) {
115 cprintf("214-Here's the frequency, Kenneth:\r\n");
116 cprintf("214- DATA\r\n");
117 cprintf("214- EHLO\r\n");
118 cprintf("214- EXPN\r\n");
119 cprintf("214- HELO\r\n");
120 cprintf("214- HELP\r\n");
121 cprintf("214- MAIL\r\n");
122 cprintf("214- NOOP\r\n");
123 cprintf("214- QUIT\r\n");
124 cprintf("214- RCPT\r\n");
125 cprintf("214- RSET\r\n");
126 cprintf("214- VRFY\r\n");
127 cprintf("214 I could tell you more, but then I'd have to kill you.\r\n");
134 void smtp_get_user(char *argbuf) {
138 decode_base64(username, argbuf);
139 lprintf(9, "Trying <%s>\n", username);
140 if (CtdlLoginExistingUser(username) == login_ok) {
141 encode_base64(buf, "Password:");
142 cprintf("334 %s\r\n", buf);
143 SMTP->command_state = smtp_password;
146 cprintf("500 No such user.\r\n");
147 SMTP->command_state = smtp_command;
155 void smtp_get_pass(char *argbuf) {
158 decode_base64(password, argbuf);
159 lprintf(9, "Trying <%s>\n", password);
160 if (CtdlTryPassword(password) == pass_ok) {
161 cprintf("235 Authentication successful.\r\n");
162 lprintf(9, "SMTP authenticated login successful\n");
163 CC->internal_pgm = 0;
164 CC->cs_flags &= ~CS_STEALTH;
167 cprintf("500 Authentication failed.\r\n");
169 SMTP->command_state = smtp_command;
176 void smtp_auth(char *argbuf) {
179 if (strncasecmp(argbuf, "login", 5) ) {
180 cprintf("550 We only support LOGIN authentication.\r\n");
184 if (strlen(argbuf) >= 7) {
185 smtp_get_user(&argbuf[6]);
189 encode_base64(buf, "Username:");
190 cprintf("334 %s\r\n", buf);
191 SMTP->command_state = smtp_user;
197 * Back end for smtp_vrfy() command
199 void smtp_vrfy_backend(struct usersupp *us, void *data) {
201 if (!fuzzy_match(us, SMTP->vrfy_match)) {
203 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
209 * Implements the VRFY (verify user name) command.
210 * Performs fuzzy match on full user names.
212 void smtp_vrfy(char *argbuf) {
213 SMTP->vrfy_count = 0;
214 strcpy(SMTP->vrfy_match, argbuf);
215 ForEachUser(smtp_vrfy_backend, NULL);
217 if (SMTP->vrfy_count < 1) {
218 cprintf("550 String does not match anything.\r\n");
220 else if (SMTP->vrfy_count == 1) {
221 cprintf("250 %s <cit%ld@%s>\r\n",
222 SMTP->vrfy_buffer.fullname,
223 SMTP->vrfy_buffer.usernum,
226 else if (SMTP->vrfy_count > 1) {
227 cprintf("553 Request ambiguous: %d users matched.\r\n",
236 * Back end for smtp_expn() command
238 void smtp_expn_backend(struct usersupp *us, void *data) {
240 if (!fuzzy_match(us, SMTP->vrfy_match)) {
242 if (SMTP->vrfy_count >= 1) {
243 cprintf("250-%s <cit%ld@%s>\r\n",
244 SMTP->vrfy_buffer.fullname,
245 SMTP->vrfy_buffer.usernum,
250 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
256 * Implements the EXPN (expand user name) command.
257 * Performs fuzzy match on full user names.
259 void smtp_expn(char *argbuf) {
260 SMTP->vrfy_count = 0;
261 strcpy(SMTP->vrfy_match, argbuf);
262 ForEachUser(smtp_expn_backend, NULL);
264 if (SMTP->vrfy_count < 1) {
265 cprintf("550 String does not match anything.\r\n");
267 else if (SMTP->vrfy_count >= 1) {
268 cprintf("250 %s <cit%ld@%s>\r\n",
269 SMTP->vrfy_buffer.fullname,
270 SMTP->vrfy_buffer.usernum,
277 * Implements the RSET (reset state) command.
278 * Currently this just zeroes out the state buffer. If pointers to data
279 * allocated with mallok() are ever placed in the state buffer, we have to
280 * be sure to phree() them first!
282 void smtp_rset(void) {
283 memset(SMTP, 0, sizeof(struct citsmtp));
284 if (CC->logged_in) logout(CC);
285 cprintf("250 Zap!\r\n");
291 * Implements the "MAIL From:" command
293 void smtp_mail(char *argbuf) {
298 if (strlen(SMTP->from) != 0) {
299 cprintf("503 Only one sender permitted\r\n");
303 if (strncasecmp(argbuf, "From:", 5)) {
304 cprintf("501 Syntax error\r\n");
308 strcpy(SMTP->from, &argbuf[5]);
311 if (strlen(SMTP->from) == 0) {
312 cprintf("501 Empty sender name is not permitted\r\n");
317 /* If this SMTP connection is from a logged-in user, make sure that
318 * the user only sends email from his/her own address.
321 cvt = convert_internet_address(user, node, SMTP->from);
322 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
323 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
324 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
325 strcpy(SMTP->from, "");
330 /* Otherwise, make sure outsiders aren't trying to forge mail from
334 cvt = convert_internet_address(user, node, SMTP->from);
335 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
336 if (CtdlHostAlias(node) == hostalias_localhost) {
337 cprintf("550 You must log in to send mail from %s\r\n",
339 strcpy(SMTP->from, "");
344 cprintf("250 Sender ok. Groovy.\r\n");
350 * Implements the "RCPT To:" command
352 void smtp_rcpt(char *argbuf) {
357 int is_spam = 0; /* FIXME implement anti-spamming */
359 if (strlen(SMTP->from) == 0) {
360 cprintf("503 MAIL first, then RCPT. Duh.\r\n");
364 if (strncasecmp(argbuf, "To:", 3)) {
365 cprintf("501 Syntax error\r\n");
369 strcpy(recp, &argbuf[3]);
373 cvt = convert_internet_address(user, node, recp);
374 snprintf(recp, sizeof recp, "%s@%s", user, node);
375 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
378 case rfc822_address_locally_validated:
379 cprintf("250 %s is a valid recipient.\r\n", user);
380 ++SMTP->number_of_recipients;
381 CtdlReallocUserData(SYM_SMTP_RECP,
382 strlen(SMTP_RECP) + 1024 );
383 strcat(SMTP_RECP, "local|");
384 strcat(SMTP_RECP, user);
385 strcat(SMTP_RECP, "|0\n");
388 case rfc822_room_delivery:
389 cprintf("250 Delivering to room '%s'\r\n", user);
390 ++SMTP->number_of_recipients;
391 CtdlReallocUserData(SYM_SMTP_RECP,
392 strlen(SMTP_RECP) + 1024 );
393 strcat(SMTP_RECP, "room|");
394 strcat(SMTP_RECP, user);
395 strcat(SMTP_RECP, "|0|\n");
398 case rfc822_no_such_user:
399 cprintf("550 %s: no such user\r\n", recp);
402 case rfc822_address_on_citadel_network:
403 cprintf("250 %s is on the local network\r\n", recp);
404 ++SMTP->number_of_recipients;
405 CtdlReallocUserData(SYM_SMTP_RECP,
406 strlen(SMTP_RECP) + 1024 );
407 strcat(SMTP_RECP, "ignet|");
408 strcat(SMTP_RECP, user);
409 strcat(SMTP_RECP, "|");
410 strcat(SMTP_RECP, node);
411 strcat(SMTP_RECP, "|0|\n");
414 case rfc822_address_nonlocal:
416 cprintf("551 Away with thee, spammer!\r\n");
419 cprintf("250 Remote recipient %s ok\r\n", recp);
420 ++SMTP->number_of_recipients;
421 CtdlReallocUserData(SYM_SMTP_RECP,
422 strlen(SMTP_RECP) + 1024 );
423 strcat(SMTP_RECP, "remote|");
424 strcat(SMTP_RECP, recp);
425 strcat(SMTP_RECP, "|0|\n");
431 cprintf("599 Unknown error\r\n");
437 * Send a message out through the local network
438 * (This is kind of ugly. IGnet should be done using clean server-to-server
439 * code instead of the old style spool.)
441 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
443 char *hold_R, *hold_D, *hold_O;
448 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
450 hold_R = msg->cm_fields['R'];
451 hold_D = msg->cm_fields['D'];
452 hold_O = msg->cm_fields['O'];
453 msg->cm_fields['R'] = user;
454 msg->cm_fields['D'] = dest;
455 msg->cm_fields['O'] = MAILROOM;
457 serialize_message(&smr, msg);
459 msg->cm_fields['R'] = hold_R;
460 msg->cm_fields['D'] = hold_D;
461 msg->cm_fields['O'] = hold_O;
464 snprintf(filename, sizeof filename,
465 "./network/spoolin/%s.%04x.%04x",
466 dest, getpid(), ++seq);
467 lprintf(9, "spool file name is <%s>\n", filename);
468 fp = fopen(filename, "wb");
470 fwrite(smr.ser, smr.len, 1, fp);
481 * Back end for smtp_data() ... this does the actual delivery of the message
482 * Returns 0 on success, nonzero on failure
484 int smtp_message_delivery(struct CtdlMessage *msg) {
491 int successful_saves = 0; /* number of successful local saves */
492 int failed_saves = 0; /* number of failed deliveries */
493 int remote_spools = 0; /* number of copies to send out */
496 struct usersupp userbuf;
497 char *instr; /* Remote delivery instructions */
498 struct CtdlMessage *imsg;
500 lprintf(9, "smtp_message_delivery() called\n");
502 /* Fill in 'from' fields with envelope information if missing */
503 process_rfc822_addr(SMTP->from, user, node, name);
504 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
505 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
506 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
507 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
509 /* Save the message in the queue */
510 msgid = CtdlSaveMsg(msg,
517 instr = mallok(1024);
518 snprintf(instr, 1024,
519 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
521 SPOOLMIME, msgid, time(NULL),
524 for (i=0; i<SMTP->number_of_recipients; ++i) {
525 extract_token(buf, SMTP_RECP, i, '\n');
526 extract(dtype, buf, 0);
528 /* Stuff local mailboxes */
529 if (!strcasecmp(dtype, "local")) {
530 extract(user, buf, 1);
531 if (getuser(&userbuf, user) == 0) {
532 MailboxName(room, &userbuf, MAILROOM);
533 CtdlSaveMsgPointerInRoom(room, msgid, 0);
541 /* Delivery to local non-mailbox rooms */
542 if (!strcasecmp(dtype, "room")) {
543 extract(room, buf, 1);
544 CtdlSaveMsgPointerInRoom(room, msgid, 0);
548 /* Delivery over the local Citadel network (IGnet) */
549 if (!strcasecmp(dtype, "ignet")) {
550 extract(user, buf, 1);
551 extract(node, buf, 2);
552 smtp_deliver_ignet(msg, user, node);
555 /* Remote delivery */
556 if (!strcasecmp(dtype, "remote")) {
557 extract(user, buf, 1);
558 instr = reallok(instr, strlen(instr) + 1024);
559 snprintf(&instr[strlen(instr)],
560 strlen(instr) + 1024,
568 /* If there are remote spools to be done, save the instructions */
569 if (remote_spools > 0) {
570 imsg = mallok(sizeof(struct CtdlMessage));
571 memset(imsg, 0, sizeof(struct CtdlMessage));
572 imsg->cm_magic = CTDLMESSAGE_MAGIC;
573 imsg->cm_anon_type = MES_NORMAL;
574 imsg->cm_format_type = FMT_RFC822;
575 imsg->cm_fields['M'] = instr;
576 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
577 CtdlFreeMessage(imsg);
580 /* If there are no remote spools, delete the message */
582 phree(instr); /* only needed here, because CtdlSaveMsg()
583 * would free this buffer otherwise */
584 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL);
587 return(failed_saves);
593 * Implements the DATA command
595 void smtp_data(void) {
597 struct CtdlMessage *msg;
601 if (strlen(SMTP->from) == 0) {
602 cprintf("503 Need MAIL command first.\r\n");
606 if (SMTP->number_of_recipients < 1) {
607 cprintf("503 Need RCPT command first.\r\n");
611 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
613 generate_rfc822_datestamp(nowstamp, time(NULL));
616 if (body != NULL) snprintf(body, 4096,
617 "Received: from %s\n"
624 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
626 cprintf("550 Unable to save message text: internal error.\r\n");
630 lprintf(9, "Converting message...\n");
631 msg = convert_internet_message(body);
633 /* If the user is locally authenticated, FORCE the From: header to
634 * show up as the real sender
637 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
638 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
639 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
640 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
641 msg->cm_fields['N'] = strdoop(config.c_nodename);
642 msg->cm_fields['H'] = strdoop(config.c_humannode);
645 retval = smtp_message_delivery(msg);
646 CtdlFreeMessage(msg);
649 cprintf("250 Message accepted for delivery.\r\n");
652 cprintf("550 Internal delivery errors: %d\r\n", retval);
660 * Main command loop for SMTP sessions.
662 void smtp_command_loop(void) {
666 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
667 if (client_gets(cmdbuf) < 1) {
668 lprintf(3, "SMTP socket is broken. Ending session.\n");
672 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
673 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
675 if (SMTP->command_state == smtp_user) {
676 smtp_get_user(cmdbuf);
679 else if (SMTP->command_state == smtp_password) {
680 smtp_get_pass(cmdbuf);
683 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
684 smtp_auth(&cmdbuf[5]);
687 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
691 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
692 smtp_hello(&cmdbuf[5], 1);
695 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
696 smtp_expn(&cmdbuf[5]);
699 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
700 smtp_hello(&cmdbuf[5], 0);
703 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
707 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
708 smtp_mail(&cmdbuf[5]);
711 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
712 cprintf("250 This command successfully did nothing.\r\n");
715 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
716 cprintf("221 Goodbye...\r\n");
721 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
722 smtp_rcpt(&cmdbuf[5]);
725 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
729 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
730 smtp_vrfy(&cmdbuf[5]);
734 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
742 /*****************************************************************************/
743 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
744 /*****************************************************************************/
751 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
754 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
761 char user[256], node[256], name[256];
767 size_t blocksize = 0;
770 /* Parse out the host portion of the recipient address */
771 process_rfc822_addr(addr, user, node, name);
773 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
776 /* Load the message out of the database into a temp file */
778 if (msg_fp == NULL) {
780 sprintf(dsn, "Error creating temporary file");
784 CtdlRedirectOutput(msg_fp, -1);
785 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
786 CtdlRedirectOutput(NULL, -1);
787 fseek(msg_fp, 0L, SEEK_END);
788 msg_size = ftell(msg_fp);
792 /* Extract something to send later in the 'MAIL From:' command */
793 strcpy(mailfrom, "");
797 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
798 if (!strncasecmp(buf, "From:", 5)) {
799 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
801 for (i=0; i<strlen(mailfrom); ++i) {
802 if (!isprint(mailfrom[i])) {
803 strcpy(&mailfrom[i], &mailfrom[i+1]);
808 /* Strip out parenthesized names */
811 for (i=0; i<strlen(mailfrom); ++i) {
812 if (mailfrom[i] == '(') lp = i;
813 if (mailfrom[i] == ')') rp = i;
815 if ((lp>0)&&(rp>lp)) {
816 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
819 /* Prefer brokketized names */
822 for (i=0; i<strlen(mailfrom); ++i) {
823 if (mailfrom[i] == '<') lp = i;
824 if (mailfrom[i] == '>') rp = i;
826 if ((lp>=0)&&(rp>lp)) {
828 strcpy(mailfrom, &mailfrom[lp]);
833 } while (scan_done == 0);
834 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
837 /* Figure out what mail exchanger host we have to connect to */
838 num_mxhosts = getmx(mxhosts, node);
839 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
840 if (num_mxhosts < 1) {
842 snprintf(dsn, 256, "No MX hosts found for <%s>", node);
846 for (mx=0; mx<num_mxhosts; ++mx) {
847 extract(buf, mxhosts, mx);
848 lprintf(9, "Trying <%s>\n", buf);
849 sock = sock_connect(buf, "25", "tcp");
850 snprintf(dsn, 256, "Could not connect: %s", strerror(errno));
851 if (sock >= 0) lprintf(9, "Connected!\n");
852 if (sock < 0) snprintf(dsn, 256, "%s", strerror(errno));
853 if (sock >= 0) break;
857 *status = 4; /* dsn is already filled in */
861 /* Process the SMTP greeting from the server */
862 if (sock_gets(sock, buf) < 0) {
864 strcpy(dsn, "Connection broken during SMTP conversation");
867 lprintf(9, "<%s\n", buf);
871 safestrncpy(dsn, &buf[4], 1023);
876 safestrncpy(dsn, &buf[4], 1023);
881 /* At this point we know we are talking to a real SMTP server */
883 /* Do a HELO command */
884 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
885 lprintf(9, ">%s\n", buf);
886 sock_puts(sock, buf);
887 if (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);
907 /* HELO succeeded, now try the MAIL From: command */
908 snprintf(buf, sizeof buf, "MAIL From: %s", mailfrom);
909 lprintf(9, ">%s\n", buf);
910 sock_puts(sock, buf);
911 if (sock_gets(sock, buf) < 0) {
913 strcpy(dsn, "Connection broken during SMTP conversation");
916 lprintf(9, "<%s\n", buf);
920 safestrncpy(dsn, &buf[4], 1023);
925 safestrncpy(dsn, &buf[4], 1023);
931 /* MAIL succeeded, now try the RCPT To: command */
932 snprintf(buf, sizeof buf, "RCPT To: %s", addr);
933 lprintf(9, ">%s\n", buf);
934 sock_puts(sock, buf);
935 if (sock_gets(sock, buf) < 0) {
937 strcpy(dsn, "Connection broken during SMTP conversation");
940 lprintf(9, "<%s\n", buf);
944 safestrncpy(dsn, &buf[4], 1023);
949 safestrncpy(dsn, &buf[4], 1023);
955 /* RCPT succeeded, now try the DATA command */
956 lprintf(9, ">DATA\n");
957 sock_puts(sock, "DATA");
958 if (sock_gets(sock, buf) < 0) {
960 strcpy(dsn, "Connection broken during SMTP conversation");
963 lprintf(9, "<%s\n", buf);
967 safestrncpy(dsn, &buf[4], 1023);
972 safestrncpy(dsn, &buf[4], 1023);
977 /* If we reach this point, the server is expecting data */
979 while (msg_size > 0) {
980 blocksize = sizeof(buf);
981 if (blocksize > msg_size) blocksize = msg_size;
982 fread(buf, blocksize, 1, msg_fp);
983 sock_write(sock, buf, blocksize);
984 msg_size -= blocksize;
986 if (buf[blocksize-1] != 10) {
987 lprintf(5, "Possible problem: message did not correctly "
988 "terminate. (expecting 0x10, got 0x%02x)\n",
992 sock_write(sock, ".\r\n", 3);
993 if (sock_gets(sock, buf) < 0) {
995 strcpy(dsn, "Connection broken during SMTP conversation");
998 lprintf(9, "%s\n", buf);
1000 if (buf[0] == '4') {
1002 safestrncpy(dsn, &buf[4], 1023);
1007 safestrncpy(dsn, &buf[4], 1023);
1013 safestrncpy(dsn, &buf[4], 1023);
1016 lprintf(9, ">QUIT\n");
1017 sock_puts(sock, "QUIT");
1018 sock_gets(sock, buf);
1019 lprintf(9, "<%s\n", buf);
1021 bail: if (msg_fp != NULL) fclose(msg_fp);
1029 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1030 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1031 * a "bounce" message (delivery status notification).
1033 void smtp_do_bounce(char *instr) {
1041 char bounceto[1024];
1042 int num_bounces = 0;
1043 int bounce_this = 0;
1044 long bounce_msgid = (-1);
1045 time_t submitted = 0L;
1046 struct CtdlMessage *bmsg = NULL;
1050 lprintf(9, "smtp_do_bounce() called\n");
1051 strcpy(bounceto, "");
1053 lines = num_tokens(instr, '\n');
1056 /* See if it's time to give up on delivery of this message */
1057 for (i=0; i<lines; ++i) {
1058 extract_token(buf, instr, i, '\n');
1059 extract(key, buf, 0);
1060 extract(addr, buf, 1);
1061 if (!strcasecmp(key, "submitted")) {
1062 submitted = atol(addr);
1066 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1072 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1073 if (bmsg == NULL) return;
1074 memset(bmsg, 0, sizeof(struct CtdlMessage));
1076 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1077 bmsg->cm_anon_type = MES_NORMAL;
1078 bmsg->cm_format_type = 1;
1079 bmsg->cm_fields['A'] = strdoop("Citadel");
1080 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1081 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1083 if (give_up) bmsg->cm_fields['M'] = strdoop(
1084 "A message you sent could not be delivered to some or all of its recipients\n"
1085 "due to prolonged unavailability of its destination(s).\n"
1086 "Giving up on the following addresses:\n\n"
1089 else bmsg->cm_fields['M'] = strdoop(
1090 "A message you sent could not be delivered to some or all of its recipients.\n"
1091 "The following addresses were undeliverable:\n\n"
1095 * Now go through the instructions checking for stuff.
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 status = extract_int(buf, 2);
1103 extract(dsn, buf, 3);
1106 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1107 key, addr, status, dsn);
1109 if (!strcasecmp(key, "bounceto")) {
1110 strcpy(bounceto, addr);
1114 (!strcasecmp(key, "local"))
1115 || (!strcasecmp(key, "remote"))
1116 || (!strcasecmp(key, "ignet"))
1117 || (!strcasecmp(key, "room"))
1119 if (status == 5) bounce_this = 1;
1120 if (give_up) bounce_this = 1;
1126 if (bmsg->cm_fields['M'] == NULL) {
1127 lprintf(2, "ERROR ... M field is null "
1128 "(%s:%d)\n", __FILE__, __LINE__);
1131 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1132 strlen(bmsg->cm_fields['M']) + 1024 );
1133 strcat(bmsg->cm_fields['M'], addr);
1134 strcat(bmsg->cm_fields['M'], ": ");
1135 strcat(bmsg->cm_fields['M'], dsn);
1136 strcat(bmsg->cm_fields['M'], "\n");
1138 remove_token(instr, i, '\n');
1144 /* Deliver the bounce if there's anything worth mentioning */
1145 lprintf(9, "num_bounces = %d\n", num_bounces);
1146 if (num_bounces > 0) {
1148 /* First try the user who sent the message */
1149 lprintf(9, "bounce to user? <%s>\n", bounceto);
1150 if (strlen(bounceto) == 0) {
1151 lprintf(7, "No bounce address specified\n");
1152 bounce_msgid = (-1L);
1154 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1155 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1156 bounce_msgid = (-1L);
1159 bounce_msgid = CtdlSaveMsg(bmsg,
1164 /* Otherwise, go to the Aide> room */
1165 lprintf(9, "bounce to room?\n");
1166 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1171 CtdlFreeMessage(bmsg);
1172 lprintf(9, "Done processing bounces\n");
1177 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1178 * set of delivery instructions for completed deliveries and remove them.
1180 * It returns the number of incomplete deliveries remaining.
1182 int smtp_purge_completed_deliveries(char *instr) {
1193 lines = num_tokens(instr, '\n');
1194 for (i=0; i<lines; ++i) {
1195 extract_token(buf, instr, i, '\n');
1196 extract(key, buf, 0);
1197 extract(addr, buf, 1);
1198 status = extract_int(buf, 2);
1199 extract(dsn, buf, 3);
1204 (!strcasecmp(key, "local"))
1205 || (!strcasecmp(key, "remote"))
1206 || (!strcasecmp(key, "ignet"))
1207 || (!strcasecmp(key, "room"))
1209 if (status == 2) completed = 1;
1214 remove_token(instr, i, '\n');
1227 * Called by smtp_do_queue() to handle an individual message.
1229 void smtp_do_procmsg(long msgnum) {
1230 struct CtdlMessage *msg;
1232 char *results = NULL;
1240 long text_msgid = (-1);
1241 int incomplete_deliveries_remaining;
1242 time_t attempted = 0L;
1243 time_t last_attempted = 0L;
1245 msg = CtdlFetchMessage(msgnum);
1247 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1251 instr = strdoop(msg->cm_fields['M']);
1252 CtdlFreeMessage(msg);
1254 /* Strip out the headers amd any other non-instruction line */
1255 lines = num_tokens(instr, '\n');
1256 for (i=0; i<lines; ++i) {
1257 extract_token(buf, instr, i, '\n');
1258 if (num_tokens(buf, '|') < 2) {
1259 lprintf(9, "removing <%s>\n", buf);
1260 remove_token(instr, i, '\n');
1266 /* Learn the message ID and find out about recent delivery attempts */
1267 lines = num_tokens(instr, '\n');
1268 for (i=0; i<lines; ++i) {
1269 extract_token(buf, instr, i, '\n');
1270 extract(key, buf, 0);
1271 if (!strcasecmp(key, "msgid")) {
1272 text_msgid = extract_long(buf, 1);
1274 if (!strcasecmp(key, "attempted")) {
1275 attempted = extract_long(buf, 1);
1276 if (attempted > last_attempted)
1277 last_attempted = attempted;
1283 * Postpone delivery if we've already tried recently.
1285 if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
1286 lprintf(7, "Retry time not yet reached.\n");
1293 * Bail out if there's no actual message associated with this
1295 if (text_msgid < 0L) {
1296 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1301 /* Plow through the instructions looking for 'remote' directives and
1302 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1303 * were experienced and it's time to try again)
1305 lines = num_tokens(instr, '\n');
1306 for (i=0; i<lines; ++i) {
1307 extract_token(buf, instr, i, '\n');
1308 extract(key, buf, 0);
1309 extract(addr, buf, 1);
1310 status = extract_int(buf, 2);
1311 extract(dsn, buf, 3);
1312 if ( (!strcasecmp(key, "remote"))
1313 && ((status==0)||(status==3)) ) {
1314 remove_token(instr, i, '\n');
1317 lprintf(9, "SMTP: Trying <%s>\n", addr);
1318 smtp_try(key, addr, &status, dsn, text_msgid);
1320 if (results == NULL) {
1321 results = mallok(1024);
1322 memset(results, 0, 1024);
1325 results = reallok(results,
1326 strlen(results) + 1024);
1328 sprintf(&results[strlen(results)],
1330 key, addr, status, dsn);
1335 if (results != NULL) {
1336 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1337 strcat(instr, results);
1342 /* Generate 'bounce' messages */
1343 smtp_do_bounce(instr);
1345 /* Go through the delivery list, deleting completed deliveries */
1346 incomplete_deliveries_remaining =
1347 smtp_purge_completed_deliveries(instr);
1351 * No delivery instructions remain, so delete both the instructions
1352 * message and the message message.
1354 if (incomplete_deliveries_remaining <= 0) {
1355 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1356 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);
1361 * Uncompleted delivery instructions remain, so delete the old
1362 * instructions and replace with the updated ones.
1364 if (incomplete_deliveries_remaining > 0) {
1365 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1366 msg = mallok(sizeof(struct CtdlMessage));
1367 memset(msg, 0, sizeof(struct CtdlMessage));
1368 msg->cm_magic = CTDLMESSAGE_MAGIC;
1369 msg->cm_anon_type = MES_NORMAL;
1370 msg->cm_format_type = FMT_RFC822;
1371 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1372 snprintf(msg->cm_fields['M'],
1374 "Content-type: %s\n\n%s\nattempted|%ld\n",
1375 SPOOLMIME, instr, time(NULL) );
1377 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1378 CtdlFreeMessage(msg);
1388 * Run through the queue sending out messages.
1390 void smtp_do_queue(void) {
1391 static int doing_queue = 0;
1394 * This is a simple concurrency check to make sure only one queue run
1395 * is done at a time. We could do this with a mutex, but since we
1396 * don't really require extremely fine granularity here, we'll do it
1397 * with a static variable instead.
1399 if (doing_queue) return;
1403 * Go ahead and run the queue
1405 lprintf(5, "SMTP: processing outbound queue\n");
1407 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1408 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1411 CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1413 lprintf(5, "SMTP: queue run completed\n");
1419 /*****************************************************************************/
1420 /* MODULE INITIALIZATION STUFF */
1421 /*****************************************************************************/
1424 char *Dynamic_Module_Init(void)
1426 SYM_SMTP = CtdlGetDynamicSymbol();
1427 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1429 CtdlRegisterServiceHook(SMTP_PORT, /* On the net... */
1434 CtdlRegisterServiceHook(0, /* ...and locally */
1439 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1440 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);