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 (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
286 if (CC->logged_in) logout(CC);
287 cprintf("250 Zap!\r\n");
291 * Clear out the portions of the state buffer that need to be cleared out
292 * after the DATA command finishes.
294 void smtp_data_clear(void) {
295 strcpy(SMTP->from, "");
296 SMTP->number_of_recipients = 0;
297 SMTP->delivery_mode = 0;
298 SMTP->message_originated_locally = 0;
299 if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
305 * Implements the "MAIL From:" command
307 void smtp_mail(char *argbuf) {
312 if (strlen(SMTP->from) != 0) {
313 cprintf("503 Only one sender permitted\r\n");
317 if (strncasecmp(argbuf, "From:", 5)) {
318 cprintf("501 Syntax error\r\n");
322 strcpy(SMTP->from, &argbuf[5]);
325 if (strlen(SMTP->from) == 0) {
326 cprintf("501 Empty sender name is not permitted\r\n");
331 /* If this SMTP connection is from a logged-in user, make sure that
332 * the user only sends email from his/her own address.
335 cvt = convert_internet_address(user, node, SMTP->from);
336 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
337 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
338 cprintf("550 <%s> is not your address.\r\n", SMTP->from);
339 strcpy(SMTP->from, "");
343 SMTP->message_originated_locally = 1;
347 /* Otherwise, make sure outsiders aren't trying to forge mail from
351 cvt = convert_internet_address(user, node, SMTP->from);
352 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
353 if (CtdlHostAlias(node) == hostalias_localhost) {
354 cprintf("550 You must log in to send mail from %s\r\n",
356 strcpy(SMTP->from, "");
361 cprintf("250 Sender ok\r\n");
367 * Implements the "RCPT To:" command
369 void smtp_rcpt(char *argbuf) {
375 if (strlen(SMTP->from) == 0) {
376 cprintf("503 Need MAIL before RCPT\r\n");
380 if (strncasecmp(argbuf, "To:", 3)) {
381 cprintf("501 Syntax error\r\n");
385 strcpy(recp, &argbuf[3]);
389 cvt = convert_internet_address(user, node, recp);
390 snprintf(recp, sizeof recp, "%s@%s", user, node);
391 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
394 case rfc822_address_locally_validated:
395 cprintf("250 %s is a valid recipient.\r\n", user);
396 ++SMTP->number_of_recipients;
397 CtdlReallocUserData(SYM_SMTP_RECP,
398 strlen(SMTP_RECP) + 1024 );
399 strcat(SMTP_RECP, "local|");
400 strcat(SMTP_RECP, user);
401 strcat(SMTP_RECP, "|0\n");
404 case rfc822_room_delivery:
405 cprintf("250 Delivering to room '%s'\r\n", user);
406 ++SMTP->number_of_recipients;
407 CtdlReallocUserData(SYM_SMTP_RECP,
408 strlen(SMTP_RECP) + 1024 );
409 strcat(SMTP_RECP, "room|");
410 strcat(SMTP_RECP, user);
411 strcat(SMTP_RECP, "|0|\n");
414 case rfc822_no_such_user:
415 cprintf("550 %s: no such user\r\n", recp);
418 case rfc822_address_on_citadel_network:
419 cprintf("250 %s is on the local network\r\n", recp);
420 ++SMTP->number_of_recipients;
421 CtdlReallocUserData(SYM_SMTP_RECP,
422 strlen(SMTP_RECP) + 1024 );
423 strcat(SMTP_RECP, "ignet|");
424 strcat(SMTP_RECP, user);
425 strcat(SMTP_RECP, "|");
426 strcat(SMTP_RECP, node);
427 strcat(SMTP_RECP, "|0|\n");
430 case rfc822_address_nonlocal:
431 if (SMTP->message_originated_locally == 0) {
432 cprintf("551 Relaying denied\r\n");
435 cprintf("250 Remote recipient %s ok\r\n", recp);
436 ++SMTP->number_of_recipients;
437 CtdlReallocUserData(SYM_SMTP_RECP,
438 strlen(SMTP_RECP) + 1024 );
439 strcat(SMTP_RECP, "remote|");
440 strcat(SMTP_RECP, recp);
441 strcat(SMTP_RECP, "|0|\n");
447 cprintf("599 Unknown error\r\n");
453 * Send a message out through the local network
454 * (This is kind of ugly. IGnet should be done using clean server-to-server
455 * code instead of the old style spool.)
457 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
459 char *hold_R, *hold_D, *hold_O;
464 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
466 hold_R = msg->cm_fields['R'];
467 hold_D = msg->cm_fields['D'];
468 hold_O = msg->cm_fields['O'];
469 msg->cm_fields['R'] = user;
470 msg->cm_fields['D'] = dest;
471 msg->cm_fields['O'] = MAILROOM;
473 serialize_message(&smr, msg);
475 msg->cm_fields['R'] = hold_R;
476 msg->cm_fields['D'] = hold_D;
477 msg->cm_fields['O'] = hold_O;
480 snprintf(filename, sizeof filename,
481 "./network/spoolin/%s.%04x.%04x",
482 dest, getpid(), ++seq);
483 lprintf(9, "spool file name is <%s>\n", filename);
484 fp = fopen(filename, "wb");
486 fwrite(smr.ser, smr.len, 1, fp);
497 * Back end for smtp_data() ... this does the actual delivery of the message
498 * Returns 0 on success, nonzero on failure
500 int smtp_message_delivery(struct CtdlMessage *msg) {
507 int successful_saves = 0; /* number of successful local saves */
508 int failed_saves = 0; /* number of failed deliveries */
509 int remote_spools = 0; /* number of copies to send out */
512 struct usersupp userbuf;
513 char *instr; /* Remote delivery instructions */
514 struct CtdlMessage *imsg;
516 lprintf(9, "smtp_message_delivery() called\n");
518 /* Fill in 'from' fields with envelope information if missing */
519 process_rfc822_addr(SMTP->from, user, node, name);
520 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
521 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
522 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
523 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
525 /* Save the message in the queue */
526 msgid = CtdlSaveMsg(msg,
532 instr = mallok(1024);
533 snprintf(instr, 1024,
534 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
536 SPOOLMIME, msgid, time(NULL),
539 for (i=0; i<SMTP->number_of_recipients; ++i) {
540 extract_token(buf, SMTP_RECP, i, '\n');
541 extract(dtype, buf, 0);
543 /* Stuff local mailboxes */
544 if (!strcasecmp(dtype, "local")) {
545 extract(user, buf, 1);
546 if (getuser(&userbuf, user) == 0) {
547 MailboxName(room, &userbuf, MAILROOM);
548 CtdlSaveMsgPointerInRoom(room, msgid, 0);
556 /* Delivery to local non-mailbox rooms */
557 if (!strcasecmp(dtype, "room")) {
558 extract(room, buf, 1);
559 CtdlSaveMsgPointerInRoom(room, msgid, 0);
563 /* Delivery over the local Citadel network (IGnet) */
564 if (!strcasecmp(dtype, "ignet")) {
565 extract(user, buf, 1);
566 extract(node, buf, 2);
567 smtp_deliver_ignet(msg, user, node);
570 /* Remote delivery */
571 if (!strcasecmp(dtype, "remote")) {
572 extract(user, buf, 1);
573 instr = reallok(instr, strlen(instr) + 1024);
574 snprintf(&instr[strlen(instr)],
575 strlen(instr) + 1024,
583 /* If there are remote spools to be done, save the instructions */
584 if (remote_spools > 0) {
585 imsg = mallok(sizeof(struct CtdlMessage));
586 memset(imsg, 0, sizeof(struct CtdlMessage));
587 imsg->cm_magic = CTDLMESSAGE_MAGIC;
588 imsg->cm_anon_type = MES_NORMAL;
589 imsg->cm_format_type = FMT_RFC822;
590 imsg->cm_fields['M'] = instr;
591 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
592 CtdlFreeMessage(imsg);
595 /* If there are no remote spools, delete the message */
597 phree(instr); /* only needed here, because CtdlSaveMsg()
598 * would free this buffer otherwise */
599 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
602 return(failed_saves);
608 * Implements the DATA command
610 void smtp_data(void) {
612 struct CtdlMessage *msg;
616 if (strlen(SMTP->from) == 0) {
617 cprintf("503 Need MAIL command first.\r\n");
621 if (SMTP->number_of_recipients < 1) {
622 cprintf("503 Need RCPT command first.\r\n");
626 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
628 generate_rfc822_datestamp(nowstamp, time(NULL));
631 if (body != NULL) snprintf(body, 4096,
632 "Received: from %s\n"
639 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
641 cprintf("550 Unable to save message text: internal error.\r\n");
645 lprintf(9, "Converting message...\n");
646 msg = convert_internet_message(body);
648 /* If the user is locally authenticated, FORCE the From: header to
649 * show up as the real sender
652 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
653 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
654 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
655 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
656 msg->cm_fields['N'] = strdoop(config.c_nodename);
657 msg->cm_fields['H'] = strdoop(config.c_humannode);
660 retval = smtp_message_delivery(msg);
661 CtdlFreeMessage(msg);
664 cprintf("250 Message accepted for delivery.\r\n");
667 cprintf("550 Internal delivery errors: %d\r\n", retval);
670 smtp_data_clear(); /* clear out the buffers now */
677 * Main command loop for SMTP sessions.
679 void smtp_command_loop(void) {
683 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
684 if (client_gets(cmdbuf) < 1) {
685 lprintf(3, "SMTP socket is broken. Ending session.\n");
689 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
690 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
692 if (SMTP->command_state == smtp_user) {
693 smtp_get_user(cmdbuf);
696 else if (SMTP->command_state == smtp_password) {
697 smtp_get_pass(cmdbuf);
700 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
701 smtp_auth(&cmdbuf[5]);
704 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
708 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
709 smtp_hello(&cmdbuf[5], 1);
712 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
713 smtp_expn(&cmdbuf[5]);
716 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
717 smtp_hello(&cmdbuf[5], 0);
720 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
724 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
725 smtp_mail(&cmdbuf[5]);
728 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
729 cprintf("250 This command successfully did nothing.\r\n");
732 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
733 cprintf("221 Goodbye...\r\n");
738 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
739 smtp_rcpt(&cmdbuf[5]);
742 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
746 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
747 smtp_vrfy(&cmdbuf[5]);
751 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
759 /*****************************************************************************/
760 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
761 /*****************************************************************************/
768 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
771 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
778 char user[256], node[256], name[256];
784 size_t blocksize = 0;
787 /* Parse out the host portion of the recipient address */
788 process_rfc822_addr(addr, user, node, name);
790 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
793 /* Load the message out of the database into a temp file */
795 if (msg_fp == NULL) {
797 sprintf(dsn, "Error creating temporary file");
801 CtdlRedirectOutput(msg_fp, -1);
802 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
803 CtdlRedirectOutput(NULL, -1);
804 fseek(msg_fp, 0L, SEEK_END);
805 msg_size = ftell(msg_fp);
809 /* Extract something to send later in the 'MAIL From:' command */
810 strcpy(mailfrom, "");
814 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
815 if (!strncasecmp(buf, "From:", 5)) {
816 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
818 for (i=0; i<strlen(mailfrom); ++i) {
819 if (!isprint(mailfrom[i])) {
820 strcpy(&mailfrom[i], &mailfrom[i+1]);
825 /* Strip out parenthesized names */
828 for (i=0; i<strlen(mailfrom); ++i) {
829 if (mailfrom[i] == '(') lp = i;
830 if (mailfrom[i] == ')') rp = i;
832 if ((lp>0)&&(rp>lp)) {
833 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
836 /* Prefer brokketized names */
839 for (i=0; i<strlen(mailfrom); ++i) {
840 if (mailfrom[i] == '<') lp = i;
841 if (mailfrom[i] == '>') rp = i;
843 if ((lp>=0)&&(rp>lp)) {
845 strcpy(mailfrom, &mailfrom[lp]);
850 } while (scan_done == 0);
851 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
854 /* Figure out what mail exchanger host we have to connect to */
855 num_mxhosts = getmx(mxhosts, node);
856 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
857 if (num_mxhosts < 1) {
859 snprintf(dsn, 256, "No MX hosts found for <%s>", node);
863 for (mx=0; mx<num_mxhosts; ++mx) {
864 extract(buf, mxhosts, mx);
865 lprintf(9, "Trying <%s>\n", buf);
866 sock = sock_connect(buf, "25", "tcp");
867 snprintf(dsn, 256, "Could not connect: %s", strerror(errno));
868 if (sock >= 0) lprintf(9, "Connected!\n");
869 if (sock < 0) snprintf(dsn, 256, "%s", strerror(errno));
870 if (sock >= 0) break;
874 *status = 4; /* dsn is already filled in */
878 /* Process the SMTP greeting from the server */
879 if (ml_sock_gets(sock, buf) < 0) {
881 strcpy(dsn, "Connection broken during SMTP conversation");
884 lprintf(9, "<%s\n", buf);
888 safestrncpy(dsn, &buf[4], 1023);
893 safestrncpy(dsn, &buf[4], 1023);
898 /* At this point we know we are talking to a real SMTP server */
900 /* Do a HELO command */
901 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
902 lprintf(9, ">%s\n", buf);
903 sock_puts_crlf(sock, buf);
904 if (ml_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 /* HELO succeeded, now try the MAIL From: command */
925 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
926 lprintf(9, ">%s\n", buf);
927 sock_puts_crlf(sock, buf);
928 if (ml_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 /* MAIL succeeded, now try the RCPT To: command */
949 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
950 lprintf(9, ">%s\n", buf);
951 sock_puts_crlf(sock, buf);
952 if (ml_sock_gets(sock, buf) < 0) {
954 strcpy(dsn, "Connection broken during SMTP conversation");
957 lprintf(9, "<%s\n", buf);
961 safestrncpy(dsn, &buf[4], 1023);
966 safestrncpy(dsn, &buf[4], 1023);
972 /* RCPT succeeded, now try the DATA command */
973 lprintf(9, ">DATA\n");
974 sock_puts_crlf(sock, "DATA");
975 if (ml_sock_gets(sock, buf) < 0) {
977 strcpy(dsn, "Connection broken during SMTP conversation");
980 lprintf(9, "<%s\n", buf);
984 safestrncpy(dsn, &buf[4], 1023);
989 safestrncpy(dsn, &buf[4], 1023);
994 /* If we reach this point, the server is expecting data */
996 while (msg_size > 0) {
997 blocksize = sizeof(buf);
998 if (blocksize > msg_size) blocksize = msg_size;
999 fread(buf, blocksize, 1, msg_fp);
1000 sock_write(sock, buf, blocksize);
1001 msg_size -= blocksize;
1003 if (buf[blocksize-1] != 10) {
1004 lprintf(5, "Possible problem: message did not correctly "
1005 "terminate. (expecting 0x10, got 0x%02x)\n",
1009 sock_write(sock, ".\r\n", 3);
1010 if (ml_sock_gets(sock, buf) < 0) {
1012 strcpy(dsn, "Connection broken during SMTP conversation");
1015 lprintf(9, "%s\n", buf);
1016 if (buf[0] != '2') {
1017 if (buf[0] == '4') {
1019 safestrncpy(dsn, &buf[4], 1023);
1024 safestrncpy(dsn, &buf[4], 1023);
1030 safestrncpy(dsn, &buf[4], 1023);
1033 lprintf(9, ">QUIT\n");
1034 sock_puts_crlf(sock, "QUIT");
1035 ml_sock_gets(sock, buf);
1036 lprintf(9, "<%s\n", buf);
1038 bail: if (msg_fp != NULL) fclose(msg_fp);
1046 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1047 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1048 * a "bounce" message (delivery status notification).
1050 void smtp_do_bounce(char *instr) {
1058 char bounceto[1024];
1059 int num_bounces = 0;
1060 int bounce_this = 0;
1061 long bounce_msgid = (-1);
1062 time_t submitted = 0L;
1063 struct CtdlMessage *bmsg = NULL;
1067 lprintf(9, "smtp_do_bounce() called\n");
1068 strcpy(bounceto, "");
1070 lines = num_tokens(instr, '\n');
1073 /* See if it's time to give up on delivery of this message */
1074 for (i=0; i<lines; ++i) {
1075 extract_token(buf, instr, i, '\n');
1076 extract(key, buf, 0);
1077 extract(addr, buf, 1);
1078 if (!strcasecmp(key, "submitted")) {
1079 submitted = atol(addr);
1083 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1089 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1090 if (bmsg == NULL) return;
1091 memset(bmsg, 0, sizeof(struct CtdlMessage));
1093 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1094 bmsg->cm_anon_type = MES_NORMAL;
1095 bmsg->cm_format_type = 1;
1096 bmsg->cm_fields['A'] = strdoop("Citadel");
1097 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1098 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1100 if (give_up) bmsg->cm_fields['M'] = strdoop(
1101 "A message you sent could not be delivered to some or all of its recipients\n"
1102 "due to prolonged unavailability of its destination(s).\n"
1103 "Giving up on the following addresses:\n\n"
1106 else bmsg->cm_fields['M'] = strdoop(
1107 "A message you sent could not be delivered to some or all of its recipients.\n"
1108 "The following addresses were undeliverable:\n\n"
1112 * Now go through the instructions checking for stuff.
1115 for (i=0; i<lines; ++i) {
1116 extract_token(buf, instr, i, '\n');
1117 extract(key, buf, 0);
1118 extract(addr, buf, 1);
1119 status = extract_int(buf, 2);
1120 extract(dsn, buf, 3);
1123 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1124 key, addr, status, dsn);
1126 if (!strcasecmp(key, "bounceto")) {
1127 strcpy(bounceto, addr);
1131 (!strcasecmp(key, "local"))
1132 || (!strcasecmp(key, "remote"))
1133 || (!strcasecmp(key, "ignet"))
1134 || (!strcasecmp(key, "room"))
1136 if (status == 5) bounce_this = 1;
1137 if (give_up) bounce_this = 1;
1143 if (bmsg->cm_fields['M'] == NULL) {
1144 lprintf(2, "ERROR ... M field is null "
1145 "(%s:%d)\n", __FILE__, __LINE__);
1148 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1149 strlen(bmsg->cm_fields['M']) + 1024 );
1150 strcat(bmsg->cm_fields['M'], addr);
1151 strcat(bmsg->cm_fields['M'], ": ");
1152 strcat(bmsg->cm_fields['M'], dsn);
1153 strcat(bmsg->cm_fields['M'], "\n");
1155 remove_token(instr, i, '\n');
1161 /* Deliver the bounce if there's anything worth mentioning */
1162 lprintf(9, "num_bounces = %d\n", num_bounces);
1163 if (num_bounces > 0) {
1165 /* First try the user who sent the message */
1166 lprintf(9, "bounce to user? <%s>\n", bounceto);
1167 if (strlen(bounceto) == 0) {
1168 lprintf(7, "No bounce address specified\n");
1169 bounce_msgid = (-1L);
1171 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1172 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1173 bounce_msgid = (-1L);
1176 bounce_msgid = CtdlSaveMsg(bmsg,
1181 /* Otherwise, go to the Aide> room */
1182 lprintf(9, "bounce to room?\n");
1183 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1188 CtdlFreeMessage(bmsg);
1189 lprintf(9, "Done processing bounces\n");
1194 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1195 * set of delivery instructions for completed deliveries and remove them.
1197 * It returns the number of incomplete deliveries remaining.
1199 int smtp_purge_completed_deliveries(char *instr) {
1210 lines = num_tokens(instr, '\n');
1211 for (i=0; i<lines; ++i) {
1212 extract_token(buf, instr, i, '\n');
1213 extract(key, buf, 0);
1214 extract(addr, buf, 1);
1215 status = extract_int(buf, 2);
1216 extract(dsn, buf, 3);
1221 (!strcasecmp(key, "local"))
1222 || (!strcasecmp(key, "remote"))
1223 || (!strcasecmp(key, "ignet"))
1224 || (!strcasecmp(key, "room"))
1226 if (status == 2) completed = 1;
1231 remove_token(instr, i, '\n');
1244 * Called by smtp_do_queue() to handle an individual message.
1246 void smtp_do_procmsg(long msgnum) {
1247 struct CtdlMessage *msg;
1249 char *results = NULL;
1257 long text_msgid = (-1);
1258 int incomplete_deliveries_remaining;
1259 time_t attempted = 0L;
1260 time_t last_attempted = 0L;
1261 time_t retry = SMTP_RETRY_INTERVAL;
1263 msg = CtdlFetchMessage(msgnum);
1265 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1269 instr = strdoop(msg->cm_fields['M']);
1270 CtdlFreeMessage(msg);
1272 /* Strip out the headers amd any other non-instruction line */
1273 lines = num_tokens(instr, '\n');
1274 for (i=0; i<lines; ++i) {
1275 extract_token(buf, instr, i, '\n');
1276 if (num_tokens(buf, '|') < 2) {
1277 lprintf(9, "removing <%s>\n", buf);
1278 remove_token(instr, i, '\n');
1284 /* Learn the message ID and find out about recent delivery attempts */
1285 lines = num_tokens(instr, '\n');
1286 for (i=0; i<lines; ++i) {
1287 extract_token(buf, instr, i, '\n');
1288 extract(key, buf, 0);
1289 if (!strcasecmp(key, "msgid")) {
1290 text_msgid = extract_long(buf, 1);
1292 if (!strcasecmp(key, "retry")) {
1293 /* double the retry interval after each attempt */
1294 retry = extract_long(buf, 1) * 2L;
1295 remove_token(instr, i, '\n');
1297 if (!strcasecmp(key, "attempted")) {
1298 attempted = extract_long(buf, 1);
1299 if (attempted > last_attempted)
1300 last_attempted = attempted;
1306 * Postpone delivery if we've already tried recently.
1308 if ( (time(NULL) - last_attempted) < retry) {
1309 lprintf(7, "Retry time not yet reached.\n");
1316 * Bail out if there's no actual message associated with this
1318 if (text_msgid < 0L) {
1319 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1324 /* Plow through the instructions looking for 'remote' directives and
1325 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1326 * were experienced and it's time to try again)
1328 lines = num_tokens(instr, '\n');
1329 for (i=0; i<lines; ++i) {
1330 extract_token(buf, instr, i, '\n');
1331 extract(key, buf, 0);
1332 extract(addr, buf, 1);
1333 status = extract_int(buf, 2);
1334 extract(dsn, buf, 3);
1335 if ( (!strcasecmp(key, "remote"))
1336 && ((status==0)||(status==3)) ) {
1337 remove_token(instr, i, '\n');
1340 lprintf(9, "SMTP: Trying <%s>\n", addr);
1341 smtp_try(key, addr, &status, dsn, text_msgid);
1343 if (results == NULL) {
1344 results = mallok(1024);
1345 memset(results, 0, 1024);
1348 results = reallok(results,
1349 strlen(results) + 1024);
1351 sprintf(&results[strlen(results)],
1353 key, addr, status, dsn);
1358 if (results != NULL) {
1359 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1360 strcat(instr, results);
1365 /* Generate 'bounce' messages */
1366 smtp_do_bounce(instr);
1368 /* Go through the delivery list, deleting completed deliveries */
1369 incomplete_deliveries_remaining =
1370 smtp_purge_completed_deliveries(instr);
1374 * No delivery instructions remain, so delete both the instructions
1375 * message and the message message.
1377 if (incomplete_deliveries_remaining <= 0) {
1378 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1379 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1384 * Uncompleted delivery instructions remain, so delete the old
1385 * instructions and replace with the updated ones.
1387 if (incomplete_deliveries_remaining > 0) {
1388 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1389 msg = mallok(sizeof(struct CtdlMessage));
1390 memset(msg, 0, sizeof(struct CtdlMessage));
1391 msg->cm_magic = CTDLMESSAGE_MAGIC;
1392 msg->cm_anon_type = MES_NORMAL;
1393 msg->cm_format_type = FMT_RFC822;
1394 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1395 snprintf(msg->cm_fields['M'],
1397 "Content-type: %s\n\n%s\n"
1400 SPOOLMIME, instr, time(NULL), retry );
1402 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1403 CtdlFreeMessage(msg);
1413 * Run through the queue sending out messages.
1415 void smtp_do_queue(void) {
1416 static int doing_queue = 0;
1419 * This is a simple concurrency check to make sure only one queue run
1420 * is done at a time. We could do this with a mutex, but since we
1421 * don't really require extremely fine granularity here, we'll do it
1422 * with a static variable instead.
1424 if (doing_queue) return;
1428 * Go ahead and run the queue
1430 lprintf(7, "SMTP: processing outbound queue\n");
1432 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1433 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1436 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1437 SPOOLMIME, NULL, smtp_do_procmsg);
1439 lprintf(7, "SMTP: queue run completed\n");
1445 /*****************************************************************************/
1446 /* MODULE INITIALIZATION STUFF */
1447 /*****************************************************************************/
1450 char *Dynamic_Module_Init(void)
1452 SYM_SMTP = CtdlGetDynamicSymbol();
1453 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1455 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1460 CtdlRegisterServiceHook(0, /* ...and locally */
1465 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1466 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);