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 sprintf(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;
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 msg->cm_fields['R'] = user;
453 msg->cm_fields['D'] = dest;
455 serialize_message(&smr, msg);
457 msg->cm_fields['R'] = hold_R;
458 msg->cm_fields['D'] = hold_D;
461 sprintf(filename, "./network/spoolin/%s.%04x.%04x",
462 dest, getpid(), ++seq);
463 lprintf(9, "spool file name is <%s>\n", filename);
464 fp = fopen(filename, "wb");
466 fwrite(smr.ser, smr.len, 1, fp);
477 * Back end for smtp_data() ... this does the actual delivery of the message
478 * Returns 0 on success, nonzero on failure
480 int smtp_message_delivery(struct CtdlMessage *msg) {
487 int successful_saves = 0; /* number of successful local saves */
488 int failed_saves = 0; /* number of failed deliveries */
489 int remote_spools = 0; /* number of copies to send out */
492 struct usersupp userbuf;
493 char *instr; /* Remote delivery instructions */
494 struct CtdlMessage *imsg;
496 lprintf(9, "smtp_message_delivery() called\n");
498 /* Fill in 'from' fields with envelope information if missing */
499 process_rfc822_addr(SMTP->from, user, node, name);
500 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
501 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
502 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
504 /* Save the message in the queue */
505 msgid = CtdlSaveMsg(msg,
512 instr = mallok(1024);
513 sprintf(instr, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
515 SPOOLMIME, msgid, time(NULL),
518 for (i=0; i<SMTP->number_of_recipients; ++i) {
519 extract_token(buf, SMTP_RECP, i, '\n');
520 extract(dtype, buf, 0);
522 /* Stuff local mailboxes */
523 if (!strcasecmp(dtype, "local")) {
524 extract(user, buf, 1);
525 if (getuser(&userbuf, user) == 0) {
526 MailboxName(room, &userbuf, MAILROOM);
527 CtdlSaveMsgPointerInRoom(room, msgid, 0);
535 /* Delivery to local non-mailbox rooms */
536 if (!strcasecmp(dtype, "room")) {
537 extract(room, buf, 1);
538 CtdlSaveMsgPointerInRoom(room, msgid, 0);
542 /* Delivery over the local Citadel network (IGnet) */
543 if (!strcasecmp(dtype, "ignet")) {
544 extract(user, buf, 1);
545 extract(node, buf, 2);
546 smtp_deliver_ignet(msg, user, node);
549 /* Remote delivery */
550 if (!strcasecmp(dtype, "remote")) {
551 extract(user, buf, 1);
552 instr = reallok(instr, strlen(instr) + 1024);
553 sprintf(&instr[strlen(instr)],
561 /* If there are remote spools to be done, save the instructions */
562 if (remote_spools > 0) {
563 imsg = mallok(sizeof(struct CtdlMessage));
564 memset(imsg, 0, sizeof(struct CtdlMessage));
565 imsg->cm_magic = CTDLMESSAGE_MAGIC;
566 imsg->cm_anon_type = MES_NORMAL;
567 imsg->cm_format_type = FMT_RFC822;
568 imsg->cm_fields['M'] = instr;
569 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
570 CtdlFreeMessage(imsg);
573 /* If there are no remote spools, delete the message */
575 phree(instr); /* only needed here, because CtdlSaveMsg()
576 * would free this buffer otherwise */
577 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL);
580 return(failed_saves);
586 * Implements the DATA command
588 void smtp_data(void) {
590 struct CtdlMessage *msg;
594 if (strlen(SMTP->from) == 0) {
595 cprintf("503 Need MAIL command first.\r\n");
599 if (SMTP->number_of_recipients < 1) {
600 cprintf("503 Need RCPT command first.\r\n");
604 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
606 generate_rfc822_datestamp(nowstamp, time(NULL));
609 if (body != NULL) sprintf(body,
610 "Received: from %s\n"
617 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
619 cprintf("550 Unable to save message text: internal error.\r\n");
623 lprintf(9, "Converting message...\n");
624 msg = convert_internet_message(body);
626 /* If the user is locally authenticated, FORCE the From: header to
627 * show up as the real sender
630 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
631 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
632 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
633 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
634 msg->cm_fields['N'] = strdoop(config.c_nodename);
635 msg->cm_fields['H'] = strdoop(config.c_humannode);
638 retval = smtp_message_delivery(msg);
639 CtdlFreeMessage(msg);
642 cprintf("250 Message accepted for delivery.\r\n");
645 cprintf("550 Internal delivery errors: %d\r\n", retval);
653 * Main command loop for SMTP sessions.
655 void smtp_command_loop(void) {
659 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
660 if (client_gets(cmdbuf) < 1) {
661 lprintf(3, "SMTP socket is broken. Ending session.\n");
665 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
666 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
668 if (SMTP->command_state == smtp_user) {
669 smtp_get_user(cmdbuf);
672 else if (SMTP->command_state == smtp_password) {
673 smtp_get_pass(cmdbuf);
676 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
677 smtp_auth(&cmdbuf[5]);
680 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
684 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
685 smtp_hello(&cmdbuf[5], 1);
688 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
689 smtp_expn(&cmdbuf[5]);
692 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
693 smtp_hello(&cmdbuf[5], 0);
696 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
700 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
701 smtp_mail(&cmdbuf[5]);
704 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
705 cprintf("250 This command successfully did nothing.\r\n");
708 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
709 cprintf("221 Goodbye...\r\n");
714 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
715 smtp_rcpt(&cmdbuf[5]);
718 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
722 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
723 smtp_vrfy(&cmdbuf[5]);
727 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
735 /*****************************************************************************/
736 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
737 /*****************************************************************************/
744 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
747 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
754 char user[256], node[256], name[256];
760 size_t blocksize = 0;
763 /* Parse out the host portion of the recipient address */
764 process_rfc822_addr(addr, user, node, name);
766 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
769 /* Load the message out of the database into a temp file */
771 if (msg_fp == NULL) {
773 sprintf(dsn, "Error creating temporary file");
777 CtdlRedirectOutput(msg_fp, -1);
778 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
779 CtdlRedirectOutput(NULL, -1);
780 fseek(msg_fp, 0L, SEEK_END);
781 msg_size = ftell(msg_fp);
785 /* Extract something to send later in the 'MAIL From:' command */
786 strcpy(mailfrom, "");
790 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
791 if (!strncasecmp(buf, "From:", 5)) {
792 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
794 for (i=0; i<strlen(mailfrom); ++i) {
795 if (!isprint(mailfrom[i])) {
796 strcpy(&mailfrom[i], &mailfrom[i+1]);
801 /* Strip out parenthesized names */
804 for (i=0; i<strlen(mailfrom); ++i) {
805 if (mailfrom[i] == '(') lp = i;
806 if (mailfrom[i] == ')') rp = i;
808 if ((lp>0)&&(rp>lp)) {
809 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
812 /* Prefer brokketized names */
815 for (i=0; i<strlen(mailfrom); ++i) {
816 if (mailfrom[i] == '<') lp = i;
817 if (mailfrom[i] == '>') rp = i;
819 if ((lp>=0)&&(rp>lp)) {
821 strcpy(mailfrom, &mailfrom[lp]);
826 } while (scan_done == 0);
827 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
830 /* Figure out what mail exchanger host we have to connect to */
831 num_mxhosts = getmx(mxhosts, node);
832 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
833 if (num_mxhosts < 1) {
835 sprintf(dsn, "No MX hosts found for <%s>", node);
839 for (mx=0; mx<num_mxhosts; ++mx) {
840 extract(buf, mxhosts, mx);
841 lprintf(9, "Trying <%s>\n", buf);
842 sock = sock_connect(buf, "25", "tcp");
843 sprintf(dsn, "Could not connect: %s", strerror(errno));
844 if (sock >= 0) lprintf(9, "Connected!\n");
845 if (sock < 0) sprintf(dsn, "%s", strerror(errno));
846 if (sock >= 0) break;
850 *status = 4; /* dsn is already filled in */
854 /* Process the SMTP greeting from the server */
855 if (sock_gets(sock, buf) < 0) {
857 strcpy(dsn, "Connection broken during SMTP conversation");
860 lprintf(9, "<%s\n", buf);
864 safestrncpy(dsn, &buf[4], 1023);
869 safestrncpy(dsn, &buf[4], 1023);
874 /* At this point we know we are talking to a real SMTP server */
876 /* Do a HELO command */
877 sprintf(buf, "HELO %s", config.c_fqdn);
878 lprintf(9, ">%s\n", buf);
879 sock_puts(sock, buf);
880 if (sock_gets(sock, buf) < 0) {
882 strcpy(dsn, "Connection broken during SMTP conversation");
885 lprintf(9, "<%s\n", buf);
889 safestrncpy(dsn, &buf[4], 1023);
894 safestrncpy(dsn, &buf[4], 1023);
900 /* HELO succeeded, now try the MAIL From: command */
901 sprintf(buf, "MAIL From: %s", mailfrom);
902 lprintf(9, ">%s\n", buf);
903 sock_puts(sock, buf);
904 if (sock_gets(sock, buf) < 0) {
906 strcpy(dsn, "Connection broken during SMTP conversation");
909 lprintf(9, "<%s\n", buf);
913 safestrncpy(dsn, &buf[4], 1023);
918 safestrncpy(dsn, &buf[4], 1023);
924 /* MAIL succeeded, now try the RCPT To: command */
925 sprintf(buf, "RCPT To: %s", addr);
926 lprintf(9, ">%s\n", buf);
927 sock_puts(sock, buf);
928 if (sock_gets(sock, buf) < 0) {
930 strcpy(dsn, "Connection broken during SMTP conversation");
933 lprintf(9, "<%s\n", buf);
937 safestrncpy(dsn, &buf[4], 1023);
942 safestrncpy(dsn, &buf[4], 1023);
948 /* RCPT succeeded, now try the DATA command */
949 lprintf(9, ">DATA\n");
950 sock_puts(sock, "DATA");
951 if (sock_gets(sock, buf) < 0) {
953 strcpy(dsn, "Connection broken during SMTP conversation");
956 lprintf(9, "<%s\n", buf);
960 safestrncpy(dsn, &buf[4], 1023);
965 safestrncpy(dsn, &buf[4], 1023);
970 /* If we reach this point, the server is expecting data */
972 while (msg_size > 0) {
973 blocksize = sizeof(buf);
974 if (blocksize > msg_size) blocksize = msg_size;
975 fread(buf, blocksize, 1, msg_fp);
976 sock_write(sock, buf, blocksize);
977 msg_size -= blocksize;
979 if (buf[blocksize-1] != 10) {
980 lprintf(5, "Possible problem: message did not correctly "
981 "terminate. (expecting 0x10, got 0x%02x)\n",
985 sock_write(sock, ".\r\n", 3);
986 if (sock_gets(sock, buf) < 0) {
988 strcpy(dsn, "Connection broken during SMTP conversation");
991 lprintf(9, "%s\n", buf);
995 safestrncpy(dsn, &buf[4], 1023);
1000 safestrncpy(dsn, &buf[4], 1023);
1006 safestrncpy(dsn, &buf[4], 1023);
1009 lprintf(9, ">QUIT\n");
1010 sock_puts(sock, "QUIT");
1011 sock_gets(sock, buf);
1012 lprintf(9, "<%s\n", buf);
1014 bail: if (msg_fp != NULL) fclose(msg_fp);
1022 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1023 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1024 * a "bounce" message (delivery status notification).
1026 void smtp_do_bounce(char *instr) {
1034 char bounceto[1024];
1035 int num_bounces = 0;
1036 int bounce_this = 0;
1037 long bounce_msgid = (-1);
1038 time_t submitted = 0L;
1039 struct CtdlMessage *bmsg = NULL;
1043 lprintf(9, "smtp_do_bounce() called\n");
1044 strcpy(bounceto, "");
1046 lines = num_tokens(instr, '\n');
1049 /* See if it's time to give up on delivery of this message */
1050 for (i=0; i<lines; ++i) {
1051 extract_token(buf, instr, i, '\n');
1052 extract(key, buf, 0);
1053 extract(addr, buf, 1);
1054 if (!strcasecmp(key, "submitted")) {
1055 submitted = atol(addr);
1059 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1065 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1066 if (bmsg == NULL) return;
1067 memset(bmsg, 0, sizeof(struct CtdlMessage));
1069 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1070 bmsg->cm_anon_type = MES_NORMAL;
1071 bmsg->cm_format_type = 1;
1072 bmsg->cm_fields['A'] = strdoop("Citadel");
1073 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1074 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1076 if (give_up) bmsg->cm_fields['M'] = strdoop(
1077 "A message you sent could not be delivered to some or all of its recipients.\n"
1078 "The following addresses were undeliverable:\n\n"
1081 else bmsg->cm_fields['M'] = strdoop(
1082 "A message you sent could not be delivered to some or all of its recipients\n"
1083 "due to prolonged unavailability of its destination(s).\n"
1084 "Giving up on the following addresses:\n\n"
1088 * Now go through the instructions checking for stuff.
1091 for (i=0; i<lines; ++i) {
1092 extract_token(buf, instr, i, '\n');
1093 extract(key, buf, 0);
1094 extract(addr, buf, 1);
1095 status = extract_int(buf, 2);
1096 extract(dsn, buf, 3);
1099 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1100 key, addr, status, dsn);
1102 if (!strcasecmp(key, "bounceto")) {
1103 strcpy(bounceto, addr);
1107 (!strcasecmp(key, "local"))
1108 || (!strcasecmp(key, "remote"))
1109 || (!strcasecmp(key, "ignet"))
1110 || (!strcasecmp(key, "room"))
1112 if (status == 5) bounce_this = 1;
1113 if (give_up) bounce_this = 1;
1119 if (bmsg->cm_fields['M'] == NULL) {
1120 lprintf(2, "ERROR ... M field is null "
1121 "(%s:%d)\n", __FILE__, __LINE__);
1124 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1125 strlen(bmsg->cm_fields['M']) + 1024 );
1126 strcat(bmsg->cm_fields['M'], addr);
1127 strcat(bmsg->cm_fields['M'], ": ");
1128 strcat(bmsg->cm_fields['M'], dsn);
1129 strcat(bmsg->cm_fields['M'], "\n");
1131 remove_token(instr, i, '\n');
1137 /* Deliver the bounce if there's anything worth mentioning */
1138 lprintf(9, "num_bounces = %d\n", num_bounces);
1139 if (num_bounces > 0) {
1141 /* First try the user who sent the message */
1142 lprintf(9, "bounce to user? <%s>\n", bounceto);
1143 if (strlen(bounceto) == 0) {
1144 lprintf(7, "No bounce address specified\n");
1145 bounce_msgid = (-1L);
1147 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1148 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1149 bounce_msgid = (-1L);
1152 bounce_msgid = CtdlSaveMsg(bmsg,
1157 /* Otherwise, go to the Aide> room */
1158 lprintf(9, "bounce to room?\n");
1159 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1164 CtdlFreeMessage(bmsg);
1165 lprintf(9, "Done processing bounces\n");
1170 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1171 * set of delivery instructions for completed deliveries and remove them.
1173 * It returns the number of incomplete deliveries remaining.
1175 int smtp_purge_completed_deliveries(char *instr) {
1186 lines = num_tokens(instr, '\n');
1187 for (i=0; i<lines; ++i) {
1188 extract_token(buf, instr, i, '\n');
1189 extract(key, buf, 0);
1190 extract(addr, buf, 1);
1191 status = extract_int(buf, 2);
1192 extract(dsn, buf, 3);
1197 (!strcasecmp(key, "local"))
1198 || (!strcasecmp(key, "remote"))
1199 || (!strcasecmp(key, "ignet"))
1200 || (!strcasecmp(key, "room"))
1202 if (status == 2) completed = 1;
1207 remove_token(instr, i, '\n');
1220 * Called by smtp_do_queue() to handle an individual message.
1222 void smtp_do_procmsg(long msgnum) {
1223 struct CtdlMessage *msg;
1225 char *results = NULL;
1233 long text_msgid = (-1);
1234 int incomplete_deliveries_remaining;
1235 time_t attempted = 0L;
1236 time_t last_attempted = 0L;
1238 msg = CtdlFetchMessage(msgnum);
1240 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1244 instr = strdoop(msg->cm_fields['M']);
1245 CtdlFreeMessage(msg);
1247 /* Strip out the headers amd any other non-instruction line */
1248 lines = num_tokens(instr, '\n');
1249 for (i=0; i<lines; ++i) {
1250 extract_token(buf, instr, i, '\n');
1251 if (num_tokens(buf, '|') < 2) {
1252 lprintf(9, "removing <%s>\n", buf);
1253 remove_token(instr, i, '\n');
1259 /* Learn the message ID and find out about recent delivery attempts */
1260 lines = num_tokens(instr, '\n');
1261 for (i=0; i<lines; ++i) {
1262 extract_token(buf, instr, i, '\n');
1263 extract(key, buf, 0);
1264 if (!strcasecmp(key, "msgid")) {
1265 text_msgid = extract_long(buf, 1);
1267 if (!strcasecmp(key, "attempted")) {
1268 attempted = extract_long(buf, 1);
1269 if (attempted > last_attempted)
1270 last_attempted = attempted;
1276 * Postpone delivery if we've already tried recently.
1278 if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
1279 lprintf(7, "Retry time not yet reached.\n");
1286 * Bail out if there's no actual message associated with this
1288 if (text_msgid < 0L) {
1289 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1294 /* Plow through the instructions looking for 'remote' directives and
1295 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1296 * were experienced and it's time to try again)
1298 lines = num_tokens(instr, '\n');
1299 for (i=0; i<lines; ++i) {
1300 extract_token(buf, instr, i, '\n');
1301 extract(key, buf, 0);
1302 extract(addr, buf, 1);
1303 status = extract_int(buf, 2);
1304 extract(dsn, buf, 3);
1305 if ( (!strcasecmp(key, "remote"))
1306 && ((status==0)||(status==3)) ) {
1307 remove_token(instr, i, '\n');
1310 lprintf(9, "SMTP: Trying <%s>\n", addr);
1311 smtp_try(key, addr, &status, dsn, text_msgid);
1313 if (results == NULL) {
1314 results = mallok(1024);
1315 memset(results, 0, 1024);
1318 results = reallok(results,
1319 strlen(results) + 1024);
1321 sprintf(&results[strlen(results)],
1323 key, addr, status, dsn);
1328 if (results != NULL) {
1329 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1330 strcat(instr, results);
1335 /* Generate 'bounce' messages */
1336 smtp_do_bounce(instr);
1338 /* Go through the delivery list, deleting completed deliveries */
1339 incomplete_deliveries_remaining =
1340 smtp_purge_completed_deliveries(instr);
1344 * No delivery instructions remain, so delete both the instructions
1345 * message and the message message.
1347 if (incomplete_deliveries_remaining <= 0) {
1348 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1349 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);
1354 * Uncompleted delivery instructions remain, so delete the old
1355 * instructions and replace with the updated ones.
1357 if (incomplete_deliveries_remaining > 0) {
1358 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1359 msg = mallok(sizeof(struct CtdlMessage));
1360 memset(msg, 0, sizeof(struct CtdlMessage));
1361 msg->cm_magic = CTDLMESSAGE_MAGIC;
1362 msg->cm_anon_type = MES_NORMAL;
1363 msg->cm_format_type = FMT_RFC822;
1364 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1365 sprintf(msg->cm_fields['M'],
1366 "Content-type: %s\n\n%s\nattempted|%ld\n",
1367 SPOOLMIME, instr, time(NULL) );
1369 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1370 CtdlFreeMessage(msg);
1380 * Run through the queue sending out messages.
1382 void smtp_do_queue(void) {
1383 static int doing_queue = 0;
1386 * This is a simple concurrency check to make sure only one queue run
1387 * is done at a time. We could do this with a mutex, but since we
1388 * don't really require extremely fine granularity here, we'll do it
1389 * with a static variable instead.
1391 if (doing_queue) return;
1395 * Go ahead and run the queue
1397 lprintf(5, "SMTP: processing outbound queue\n");
1399 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1400 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1403 CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1405 lprintf(5, "SMTP: queue run completed\n");
1411 /*****************************************************************************/
1412 /* MODULE INITIALIZATION STUFF */
1413 /*****************************************************************************/
1416 char *Dynamic_Module_Init(void)
1418 SYM_SMTP = CtdlGetDynamicSymbol();
1419 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1420 CtdlRegisterServiceHook(SMTP_PORT,
1423 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1424 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);