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;
47 int message_originated_locally;
50 enum { /* Command states for login authentication */
56 enum { /* Delivery modes */
61 #define SMTP ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
62 #define SMTP_RECP ((char *)CtdlGetUserData(SYM_SMTP_RECP))
69 /*****************************************************************************/
70 /* SMTP SERVER (INBOUND) STUFF */
71 /*****************************************************************************/
77 * Here's where our SMTP session begins its happy day.
79 void smtp_greeting(void) {
81 strcpy(CC->cs_clientname, "SMTP session");
83 CC->cs_flags |= CS_STEALTH;
84 CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
85 CtdlAllocUserData(SYM_SMTP_RECP, 256);
86 sprintf(SMTP_RECP, "%s", "");
88 cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\r\n",
94 * Implement HELO and EHLO commands.
96 void smtp_hello(char *argbuf, int is_esmtp) {
98 safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
101 cprintf("250 Greetings and joyous salutations.\r\n");
104 cprintf("250-Greetings and joyous salutations.\r\n");
105 cprintf("250-HELP\r\n");
106 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
107 cprintf("250 AUTH=LOGIN\r\n");
113 * Implement HELP command.
115 void smtp_help(void) {
116 cprintf("214-Here's the frequency, Kenneth:\r\n");
117 cprintf("214- DATA\r\n");
118 cprintf("214- EHLO\r\n");
119 cprintf("214- EXPN\r\n");
120 cprintf("214- HELO\r\n");
121 cprintf("214- HELP\r\n");
122 cprintf("214- MAIL\r\n");
123 cprintf("214- NOOP\r\n");
124 cprintf("214- QUIT\r\n");
125 cprintf("214- RCPT\r\n");
126 cprintf("214- RSET\r\n");
127 cprintf("214- VRFY\r\n");
128 cprintf("214 I could tell you more, but then I'd have to kill you.\r\n");
135 void smtp_get_user(char *argbuf) {
139 decode_base64(username, argbuf);
140 lprintf(9, "Trying <%s>\n", username);
141 if (CtdlLoginExistingUser(username) == login_ok) {
142 encode_base64(buf, "Password:");
143 cprintf("334 %s\r\n", buf);
144 SMTP->command_state = smtp_password;
147 cprintf("500 No such user.\r\n");
148 SMTP->command_state = smtp_command;
156 void smtp_get_pass(char *argbuf) {
159 decode_base64(password, argbuf);
160 lprintf(9, "Trying <%s>\n", password);
161 if (CtdlTryPassword(password) == pass_ok) {
162 cprintf("235 Authentication successful.\r\n");
163 lprintf(9, "SMTP authenticated login successful\n");
164 CC->internal_pgm = 0;
165 CC->cs_flags &= ~CS_STEALTH;
168 cprintf("500 Authentication failed.\r\n");
170 SMTP->command_state = smtp_command;
177 void smtp_auth(char *argbuf) {
180 if (strncasecmp(argbuf, "login", 5) ) {
181 cprintf("550 We only support LOGIN authentication.\r\n");
185 if (strlen(argbuf) >= 7) {
186 smtp_get_user(&argbuf[6]);
190 encode_base64(buf, "Username:");
191 cprintf("334 %s\r\n", buf);
192 SMTP->command_state = smtp_user;
198 * Back end for smtp_vrfy() command
200 void smtp_vrfy_backend(struct usersupp *us, void *data) {
202 if (!fuzzy_match(us, SMTP->vrfy_match)) {
204 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
210 * Implements the VRFY (verify user name) command.
211 * Performs fuzzy match on full user names.
213 void smtp_vrfy(char *argbuf) {
214 SMTP->vrfy_count = 0;
215 strcpy(SMTP->vrfy_match, argbuf);
216 ForEachUser(smtp_vrfy_backend, NULL);
218 if (SMTP->vrfy_count < 1) {
219 cprintf("550 String does not match anything.\r\n");
221 else if (SMTP->vrfy_count == 1) {
222 cprintf("250 %s <cit%ld@%s>\r\n",
223 SMTP->vrfy_buffer.fullname,
224 SMTP->vrfy_buffer.usernum,
227 else if (SMTP->vrfy_count > 1) {
228 cprintf("553 Request ambiguous: %d users matched.\r\n",
237 * Back end for smtp_expn() command
239 void smtp_expn_backend(struct usersupp *us, void *data) {
241 if (!fuzzy_match(us, SMTP->vrfy_match)) {
243 if (SMTP->vrfy_count >= 1) {
244 cprintf("250-%s <cit%ld@%s>\r\n",
245 SMTP->vrfy_buffer.fullname,
246 SMTP->vrfy_buffer.usernum,
251 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
257 * Implements the EXPN (expand user name) command.
258 * Performs fuzzy match on full user names.
260 void smtp_expn(char *argbuf) {
261 SMTP->vrfy_count = 0;
262 strcpy(SMTP->vrfy_match, argbuf);
263 ForEachUser(smtp_expn_backend, NULL);
265 if (SMTP->vrfy_count < 1) {
266 cprintf("550 String does not match anything.\r\n");
268 else if (SMTP->vrfy_count >= 1) {
269 cprintf("250 %s <cit%ld@%s>\r\n",
270 SMTP->vrfy_buffer.fullname,
271 SMTP->vrfy_buffer.usernum,
278 * Implements the RSET (reset state) command.
279 * Currently this just zeroes out the state buffer. If pointers to data
280 * allocated with mallok() are ever placed in the state buffer, we have to
281 * be sure to phree() them first!
283 void smtp_rset(void) {
284 memset(SMTP, 0, sizeof(struct citsmtp));
285 if (CC->logged_in) logout(CC);
286 cprintf("250 Zap!\r\n");
292 * Implements the "MAIL From:" command
294 void smtp_mail(char *argbuf) {
299 if (strlen(SMTP->from) != 0) {
300 cprintf("503 Only one sender permitted\r\n");
304 if (strncasecmp(argbuf, "From:", 5)) {
305 cprintf("501 Syntax error\r\n");
309 strcpy(SMTP->from, &argbuf[5]);
312 if (strlen(SMTP->from) == 0) {
313 cprintf("501 Empty sender name is not permitted\r\n");
318 /* If this SMTP connection is from a logged-in user, make sure that
319 * the user only sends email from his/her own address.
322 cvt = convert_internet_address(user, node, SMTP->from);
323 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
324 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
325 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
326 strcpy(SMTP->from, "");
330 SMTP->message_originated_locally = 1;
334 /* Otherwise, make sure outsiders aren't trying to forge mail from
338 cvt = convert_internet_address(user, node, SMTP->from);
339 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
340 if (CtdlHostAlias(node) == hostalias_localhost) {
341 cprintf("550 You must log in to send mail from %s\r\n",
343 strcpy(SMTP->from, "");
348 cprintf("250 Sender ok\r\n");
354 * Implements the "RCPT To:" command
356 void smtp_rcpt(char *argbuf) {
362 if (strlen(SMTP->from) == 0) {
363 cprintf("503 Need MAIL before RCPT\r\n");
367 if (strncasecmp(argbuf, "To:", 3)) {
368 cprintf("501 Syntax error\r\n");
372 strcpy(recp, &argbuf[3]);
376 cvt = convert_internet_address(user, node, recp);
377 snprintf(recp, sizeof recp, "%s@%s", user, node);
378 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
381 case rfc822_address_locally_validated:
382 cprintf("250 %s is a valid recipient.\r\n", user);
383 ++SMTP->number_of_recipients;
384 CtdlReallocUserData(SYM_SMTP_RECP,
385 strlen(SMTP_RECP) + 1024 );
386 strcat(SMTP_RECP, "local|");
387 strcat(SMTP_RECP, user);
388 strcat(SMTP_RECP, "|0\n");
391 case rfc822_room_delivery:
392 cprintf("250 Delivering to room '%s'\r\n", user);
393 ++SMTP->number_of_recipients;
394 CtdlReallocUserData(SYM_SMTP_RECP,
395 strlen(SMTP_RECP) + 1024 );
396 strcat(SMTP_RECP, "room|");
397 strcat(SMTP_RECP, user);
398 strcat(SMTP_RECP, "|0|\n");
401 case rfc822_no_such_user:
402 cprintf("550 %s: no such user\r\n", recp);
405 case rfc822_address_on_citadel_network:
406 cprintf("250 %s is on the local network\r\n", recp);
407 ++SMTP->number_of_recipients;
408 CtdlReallocUserData(SYM_SMTP_RECP,
409 strlen(SMTP_RECP) + 1024 );
410 strcat(SMTP_RECP, "ignet|");
411 strcat(SMTP_RECP, user);
412 strcat(SMTP_RECP, "|");
413 strcat(SMTP_RECP, node);
414 strcat(SMTP_RECP, "|0|\n");
417 case rfc822_address_nonlocal:
418 if (SMTP->message_originated_locally == 0) {
419 cprintf("551 Relaying denied\r\n");
422 cprintf("250 Remote recipient %s ok\r\n", recp);
423 ++SMTP->number_of_recipients;
424 CtdlReallocUserData(SYM_SMTP_RECP,
425 strlen(SMTP_RECP) + 1024 );
426 strcat(SMTP_RECP, "remote|");
427 strcat(SMTP_RECP, recp);
428 strcat(SMTP_RECP, "|0|\n");
434 cprintf("599 Unknown error\r\n");
440 * Send a message out through the local network
441 * (This is kind of ugly. IGnet should be done using clean server-to-server
442 * code instead of the old style spool.)
444 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
446 char *hold_R, *hold_D, *hold_O;
451 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
453 hold_R = msg->cm_fields['R'];
454 hold_D = msg->cm_fields['D'];
455 hold_O = msg->cm_fields['O'];
456 msg->cm_fields['R'] = user;
457 msg->cm_fields['D'] = dest;
458 msg->cm_fields['O'] = MAILROOM;
460 serialize_message(&smr, msg);
462 msg->cm_fields['R'] = hold_R;
463 msg->cm_fields['D'] = hold_D;
464 msg->cm_fields['O'] = hold_O;
467 snprintf(filename, sizeof filename,
468 "./network/spoolin/%s.%04x.%04x",
469 dest, getpid(), ++seq);
470 lprintf(9, "spool file name is <%s>\n", filename);
471 fp = fopen(filename, "wb");
473 fwrite(smr.ser, smr.len, 1, fp);
484 * Back end for smtp_data() ... this does the actual delivery of the message
485 * Returns 0 on success, nonzero on failure
487 int smtp_message_delivery(struct CtdlMessage *msg) {
494 int successful_saves = 0; /* number of successful local saves */
495 int failed_saves = 0; /* number of failed deliveries */
496 int remote_spools = 0; /* number of copies to send out */
499 struct usersupp userbuf;
500 char *instr; /* Remote delivery instructions */
501 struct CtdlMessage *imsg;
503 lprintf(9, "smtp_message_delivery() called\n");
505 /* Fill in 'from' fields with envelope information if missing */
506 process_rfc822_addr(SMTP->from, user, node, name);
507 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
508 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
509 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
510 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
512 /* Save the message in the queue */
513 msgid = CtdlSaveMsg(msg,
519 instr = mallok(1024);
520 snprintf(instr, 1024,
521 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
523 SPOOLMIME, msgid, time(NULL),
526 for (i=0; i<SMTP->number_of_recipients; ++i) {
527 extract_token(buf, SMTP_RECP, i, '\n');
528 extract(dtype, buf, 0);
530 /* Stuff local mailboxes */
531 if (!strcasecmp(dtype, "local")) {
532 extract(user, buf, 1);
533 if (getuser(&userbuf, user) == 0) {
534 MailboxName(room, &userbuf, MAILROOM);
535 CtdlSaveMsgPointerInRoom(room, msgid, 0);
543 /* Delivery to local non-mailbox rooms */
544 if (!strcasecmp(dtype, "room")) {
545 extract(room, buf, 1);
546 CtdlSaveMsgPointerInRoom(room, msgid, 0);
550 /* Delivery over the local Citadel network (IGnet) */
551 if (!strcasecmp(dtype, "ignet")) {
552 extract(user, buf, 1);
553 extract(node, buf, 2);
554 smtp_deliver_ignet(msg, user, node);
557 /* Remote delivery */
558 if (!strcasecmp(dtype, "remote")) {
559 extract(user, buf, 1);
560 instr = reallok(instr, strlen(instr) + 1024);
561 snprintf(&instr[strlen(instr)],
562 strlen(instr) + 1024,
570 /* If there are remote spools to be done, save the instructions */
571 if (remote_spools > 0) {
572 imsg = mallok(sizeof(struct CtdlMessage));
573 memset(imsg, 0, sizeof(struct CtdlMessage));
574 imsg->cm_magic = CTDLMESSAGE_MAGIC;
575 imsg->cm_anon_type = MES_NORMAL;
576 imsg->cm_format_type = FMT_RFC822;
577 imsg->cm_fields['M'] = instr;
578 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
579 CtdlFreeMessage(imsg);
582 /* If there are no remote spools, delete the message */
584 phree(instr); /* only needed here, because CtdlSaveMsg()
585 * would free this buffer otherwise */
586 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL);
589 return(failed_saves);
595 * Implements the DATA command
597 void smtp_data(void) {
599 struct CtdlMessage *msg;
603 if (strlen(SMTP->from) == 0) {
604 cprintf("503 Need MAIL command first.\r\n");
608 if (SMTP->number_of_recipients < 1) {
609 cprintf("503 Need RCPT command first.\r\n");
613 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
615 generate_rfc822_datestamp(nowstamp, time(NULL));
618 if (body != NULL) snprintf(body, 4096,
619 "Received: from %s\n"
626 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
628 cprintf("550 Unable to save message text: internal error.\r\n");
632 lprintf(9, "Converting message...\n");
633 msg = convert_internet_message(body);
635 /* If the user is locally authenticated, FORCE the From: header to
636 * show up as the real sender
639 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
640 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
641 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
642 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
643 msg->cm_fields['N'] = strdoop(config.c_nodename);
644 msg->cm_fields['H'] = strdoop(config.c_humannode);
647 retval = smtp_message_delivery(msg);
648 CtdlFreeMessage(msg);
651 cprintf("250 Message accepted for delivery.\r\n");
654 cprintf("550 Internal delivery errors: %d\r\n", retval);
662 * Main command loop for SMTP sessions.
664 void smtp_command_loop(void) {
668 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
669 if (client_gets(cmdbuf) < 1) {
670 lprintf(3, "SMTP socket is broken. Ending session.\n");
674 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
675 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
677 if (SMTP->command_state == smtp_user) {
678 smtp_get_user(cmdbuf);
681 else if (SMTP->command_state == smtp_password) {
682 smtp_get_pass(cmdbuf);
685 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
686 smtp_auth(&cmdbuf[5]);
689 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
693 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
694 smtp_hello(&cmdbuf[5], 1);
697 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
698 smtp_expn(&cmdbuf[5]);
701 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
702 smtp_hello(&cmdbuf[5], 0);
705 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
709 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
710 smtp_mail(&cmdbuf[5]);
713 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
714 cprintf("250 This command successfully did nothing.\r\n");
717 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
718 cprintf("221 Goodbye...\r\n");
723 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
724 smtp_rcpt(&cmdbuf[5]);
727 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
731 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
732 smtp_vrfy(&cmdbuf[5]);
736 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
744 /*****************************************************************************/
745 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
746 /*****************************************************************************/
753 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
756 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
763 char user[256], node[256], name[256];
769 size_t blocksize = 0;
772 /* Parse out the host portion of the recipient address */
773 process_rfc822_addr(addr, user, node, name);
775 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
778 /* Load the message out of the database into a temp file */
780 if (msg_fp == NULL) {
782 sprintf(dsn, "Error creating temporary file");
786 CtdlRedirectOutput(msg_fp, -1);
787 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
788 CtdlRedirectOutput(NULL, -1);
789 fseek(msg_fp, 0L, SEEK_END);
790 msg_size = ftell(msg_fp);
794 /* Extract something to send later in the 'MAIL From:' command */
795 strcpy(mailfrom, "");
799 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
800 if (!strncasecmp(buf, "From:", 5)) {
801 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
803 for (i=0; i<strlen(mailfrom); ++i) {
804 if (!isprint(mailfrom[i])) {
805 strcpy(&mailfrom[i], &mailfrom[i+1]);
810 /* Strip out parenthesized names */
813 for (i=0; i<strlen(mailfrom); ++i) {
814 if (mailfrom[i] == '(') lp = i;
815 if (mailfrom[i] == ')') rp = i;
817 if ((lp>0)&&(rp>lp)) {
818 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
821 /* Prefer brokketized names */
824 for (i=0; i<strlen(mailfrom); ++i) {
825 if (mailfrom[i] == '<') lp = i;
826 if (mailfrom[i] == '>') rp = i;
828 if ((lp>=0)&&(rp>lp)) {
830 strcpy(mailfrom, &mailfrom[lp]);
835 } while (scan_done == 0);
836 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
839 /* Figure out what mail exchanger host we have to connect to */
840 num_mxhosts = getmx(mxhosts, node);
841 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
842 if (num_mxhosts < 1) {
844 snprintf(dsn, 256, "No MX hosts found for <%s>", node);
848 for (mx=0; mx<num_mxhosts; ++mx) {
849 extract(buf, mxhosts, mx);
850 lprintf(9, "Trying <%s>\n", buf);
851 sock = sock_connect(buf, "25", "tcp");
852 snprintf(dsn, 256, "Could not connect: %s", strerror(errno));
853 if (sock >= 0) lprintf(9, "Connected!\n");
854 if (sock < 0) snprintf(dsn, 256, "%s", strerror(errno));
855 if (sock >= 0) break;
859 *status = 4; /* dsn is already filled in */
863 /* Process the SMTP greeting from the server */
864 if (sock_gets(sock, buf) < 0) {
866 strcpy(dsn, "Connection broken during SMTP conversation");
869 lprintf(9, "<%s\n", buf);
873 safestrncpy(dsn, &buf[4], 1023);
878 safestrncpy(dsn, &buf[4], 1023);
883 /* At this point we know we are talking to a real SMTP server */
885 /* Do a HELO command */
886 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
887 lprintf(9, ">%s\n", buf);
888 sock_puts(sock, buf);
889 if (sock_gets(sock, buf) < 0) {
891 strcpy(dsn, "Connection broken during SMTP conversation");
894 lprintf(9, "<%s\n", buf);
898 safestrncpy(dsn, &buf[4], 1023);
903 safestrncpy(dsn, &buf[4], 1023);
909 /* HELO succeeded, now try the MAIL From: command */
910 snprintf(buf, sizeof buf, "MAIL From: %s", mailfrom);
911 lprintf(9, ">%s\n", buf);
912 sock_puts(sock, buf);
913 if (sock_gets(sock, buf) < 0) {
915 strcpy(dsn, "Connection broken during SMTP conversation");
918 lprintf(9, "<%s\n", buf);
922 safestrncpy(dsn, &buf[4], 1023);
927 safestrncpy(dsn, &buf[4], 1023);
933 /* MAIL succeeded, now try the RCPT To: command */
934 snprintf(buf, sizeof buf, "RCPT To: %s", addr);
935 lprintf(9, ">%s\n", buf);
936 sock_puts(sock, buf);
937 if (sock_gets(sock, buf) < 0) {
939 strcpy(dsn, "Connection broken during SMTP conversation");
942 lprintf(9, "<%s\n", buf);
946 safestrncpy(dsn, &buf[4], 1023);
951 safestrncpy(dsn, &buf[4], 1023);
957 /* RCPT succeeded, now try the DATA command */
958 lprintf(9, ">DATA\n");
959 sock_puts(sock, "DATA");
960 if (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);
979 /* If we reach this point, the server is expecting data */
981 while (msg_size > 0) {
982 blocksize = sizeof(buf);
983 if (blocksize > msg_size) blocksize = msg_size;
984 fread(buf, blocksize, 1, msg_fp);
985 sock_write(sock, buf, blocksize);
986 msg_size -= blocksize;
988 if (buf[blocksize-1] != 10) {
989 lprintf(5, "Possible problem: message did not correctly "
990 "terminate. (expecting 0x10, got 0x%02x)\n",
994 sock_write(sock, ".\r\n", 3);
995 if (sock_gets(sock, buf) < 0) {
997 strcpy(dsn, "Connection broken during SMTP conversation");
1000 lprintf(9, "%s\n", buf);
1001 if (buf[0] != '2') {
1002 if (buf[0] == '4') {
1004 safestrncpy(dsn, &buf[4], 1023);
1009 safestrncpy(dsn, &buf[4], 1023);
1015 safestrncpy(dsn, &buf[4], 1023);
1018 lprintf(9, ">QUIT\n");
1019 sock_puts(sock, "QUIT");
1020 sock_gets(sock, buf);
1021 lprintf(9, "<%s\n", buf);
1023 bail: if (msg_fp != NULL) fclose(msg_fp);
1031 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1032 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1033 * a "bounce" message (delivery status notification).
1035 void smtp_do_bounce(char *instr) {
1043 char bounceto[1024];
1044 int num_bounces = 0;
1045 int bounce_this = 0;
1046 long bounce_msgid = (-1);
1047 time_t submitted = 0L;
1048 struct CtdlMessage *bmsg = NULL;
1052 lprintf(9, "smtp_do_bounce() called\n");
1053 strcpy(bounceto, "");
1055 lines = num_tokens(instr, '\n');
1058 /* See if it's time to give up on delivery of this message */
1059 for (i=0; i<lines; ++i) {
1060 extract_token(buf, instr, i, '\n');
1061 extract(key, buf, 0);
1062 extract(addr, buf, 1);
1063 if (!strcasecmp(key, "submitted")) {
1064 submitted = atol(addr);
1068 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1074 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1075 if (bmsg == NULL) return;
1076 memset(bmsg, 0, sizeof(struct CtdlMessage));
1078 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1079 bmsg->cm_anon_type = MES_NORMAL;
1080 bmsg->cm_format_type = 1;
1081 bmsg->cm_fields['A'] = strdoop("Citadel");
1082 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1083 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1085 if (give_up) bmsg->cm_fields['M'] = strdoop(
1086 "A message you sent could not be delivered to some or all of its recipients\n"
1087 "due to prolonged unavailability of its destination(s).\n"
1088 "Giving up on the following addresses:\n\n"
1091 else bmsg->cm_fields['M'] = strdoop(
1092 "A message you sent could not be delivered to some or all of its recipients.\n"
1093 "The following addresses were undeliverable:\n\n"
1097 * Now go through the instructions checking for stuff.
1100 for (i=0; i<lines; ++i) {
1101 extract_token(buf, instr, i, '\n');
1102 extract(key, buf, 0);
1103 extract(addr, buf, 1);
1104 status = extract_int(buf, 2);
1105 extract(dsn, buf, 3);
1108 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1109 key, addr, status, dsn);
1111 if (!strcasecmp(key, "bounceto")) {
1112 strcpy(bounceto, addr);
1116 (!strcasecmp(key, "local"))
1117 || (!strcasecmp(key, "remote"))
1118 || (!strcasecmp(key, "ignet"))
1119 || (!strcasecmp(key, "room"))
1121 if (status == 5) bounce_this = 1;
1122 if (give_up) bounce_this = 1;
1128 if (bmsg->cm_fields['M'] == NULL) {
1129 lprintf(2, "ERROR ... M field is null "
1130 "(%s:%d)\n", __FILE__, __LINE__);
1133 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1134 strlen(bmsg->cm_fields['M']) + 1024 );
1135 strcat(bmsg->cm_fields['M'], addr);
1136 strcat(bmsg->cm_fields['M'], ": ");
1137 strcat(bmsg->cm_fields['M'], dsn);
1138 strcat(bmsg->cm_fields['M'], "\n");
1140 remove_token(instr, i, '\n');
1146 /* Deliver the bounce if there's anything worth mentioning */
1147 lprintf(9, "num_bounces = %d\n", num_bounces);
1148 if (num_bounces > 0) {
1150 /* First try the user who sent the message */
1151 lprintf(9, "bounce to user? <%s>\n", bounceto);
1152 if (strlen(bounceto) == 0) {
1153 lprintf(7, "No bounce address specified\n");
1154 bounce_msgid = (-1L);
1156 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1157 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1158 bounce_msgid = (-1L);
1161 bounce_msgid = CtdlSaveMsg(bmsg,
1166 /* Otherwise, go to the Aide> room */
1167 lprintf(9, "bounce to room?\n");
1168 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1173 CtdlFreeMessage(bmsg);
1174 lprintf(9, "Done processing bounces\n");
1179 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1180 * set of delivery instructions for completed deliveries and remove them.
1182 * It returns the number of incomplete deliveries remaining.
1184 int smtp_purge_completed_deliveries(char *instr) {
1195 lines = num_tokens(instr, '\n');
1196 for (i=0; i<lines; ++i) {
1197 extract_token(buf, instr, i, '\n');
1198 extract(key, buf, 0);
1199 extract(addr, buf, 1);
1200 status = extract_int(buf, 2);
1201 extract(dsn, buf, 3);
1206 (!strcasecmp(key, "local"))
1207 || (!strcasecmp(key, "remote"))
1208 || (!strcasecmp(key, "ignet"))
1209 || (!strcasecmp(key, "room"))
1211 if (status == 2) completed = 1;
1216 remove_token(instr, i, '\n');
1229 * Called by smtp_do_queue() to handle an individual message.
1231 void smtp_do_procmsg(long msgnum) {
1232 struct CtdlMessage *msg;
1234 char *results = NULL;
1242 long text_msgid = (-1);
1243 int incomplete_deliveries_remaining;
1244 time_t attempted = 0L;
1245 time_t last_attempted = 0L;
1247 msg = CtdlFetchMessage(msgnum);
1249 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1253 instr = strdoop(msg->cm_fields['M']);
1254 CtdlFreeMessage(msg);
1256 /* Strip out the headers amd any other non-instruction line */
1257 lines = num_tokens(instr, '\n');
1258 for (i=0; i<lines; ++i) {
1259 extract_token(buf, instr, i, '\n');
1260 if (num_tokens(buf, '|') < 2) {
1261 lprintf(9, "removing <%s>\n", buf);
1262 remove_token(instr, i, '\n');
1268 /* Learn the message ID and find out about recent delivery attempts */
1269 lines = num_tokens(instr, '\n');
1270 for (i=0; i<lines; ++i) {
1271 extract_token(buf, instr, i, '\n');
1272 extract(key, buf, 0);
1273 if (!strcasecmp(key, "msgid")) {
1274 text_msgid = extract_long(buf, 1);
1276 if (!strcasecmp(key, "attempted")) {
1277 attempted = extract_long(buf, 1);
1278 if (attempted > last_attempted)
1279 last_attempted = attempted;
1285 * Postpone delivery if we've already tried recently.
1287 if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
1288 lprintf(7, "Retry time not yet reached.\n");
1295 * Bail out if there's no actual message associated with this
1297 if (text_msgid < 0L) {
1298 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1303 /* Plow through the instructions looking for 'remote' directives and
1304 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1305 * were experienced and it's time to try again)
1307 lines = num_tokens(instr, '\n');
1308 for (i=0; i<lines; ++i) {
1309 extract_token(buf, instr, i, '\n');
1310 extract(key, buf, 0);
1311 extract(addr, buf, 1);
1312 status = extract_int(buf, 2);
1313 extract(dsn, buf, 3);
1314 if ( (!strcasecmp(key, "remote"))
1315 && ((status==0)||(status==3)) ) {
1316 remove_token(instr, i, '\n');
1319 lprintf(9, "SMTP: Trying <%s>\n", addr);
1320 smtp_try(key, addr, &status, dsn, text_msgid);
1322 if (results == NULL) {
1323 results = mallok(1024);
1324 memset(results, 0, 1024);
1327 results = reallok(results,
1328 strlen(results) + 1024);
1330 sprintf(&results[strlen(results)],
1332 key, addr, status, dsn);
1337 if (results != NULL) {
1338 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1339 strcat(instr, results);
1344 /* Generate 'bounce' messages */
1345 smtp_do_bounce(instr);
1347 /* Go through the delivery list, deleting completed deliveries */
1348 incomplete_deliveries_remaining =
1349 smtp_purge_completed_deliveries(instr);
1353 * No delivery instructions remain, so delete both the instructions
1354 * message and the message message.
1356 if (incomplete_deliveries_remaining <= 0) {
1357 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1358 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);
1363 * Uncompleted delivery instructions remain, so delete the old
1364 * instructions and replace with the updated ones.
1366 if (incomplete_deliveries_remaining > 0) {
1367 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1368 msg = mallok(sizeof(struct CtdlMessage));
1369 memset(msg, 0, sizeof(struct CtdlMessage));
1370 msg->cm_magic = CTDLMESSAGE_MAGIC;
1371 msg->cm_anon_type = MES_NORMAL;
1372 msg->cm_format_type = FMT_RFC822;
1373 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1374 snprintf(msg->cm_fields['M'],
1376 "Content-type: %s\n\n%s\nattempted|%ld\n",
1377 SPOOLMIME, instr, time(NULL) );
1379 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1380 CtdlFreeMessage(msg);
1390 * Run through the queue sending out messages.
1392 void smtp_do_queue(void) {
1393 static int doing_queue = 0;
1396 * This is a simple concurrency check to make sure only one queue run
1397 * is done at a time. We could do this with a mutex, but since we
1398 * don't really require extremely fine granularity here, we'll do it
1399 * with a static variable instead.
1401 if (doing_queue) return;
1405 * Go ahead and run the queue
1407 lprintf(7, "SMTP: processing outbound queue\n");
1409 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1410 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1413 CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1415 lprintf(7, "SMTP: queue run completed\n");
1421 /*****************************************************************************/
1422 /* MODULE INITIALIZATION STUFF */
1423 /*****************************************************************************/
1426 char *Dynamic_Module_Init(void)
1428 SYM_SMTP = CtdlGetDynamicSymbol();
1429 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1431 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1436 CtdlRegisterServiceHook(0, /* ...and locally */
1441 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1442 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);