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]);
391 cvt = convert_internet_address(user, node, recp);
392 snprintf(recp, sizeof recp, "%s@%s", user, node);
393 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
396 case rfc822_address_locally_validated:
397 cprintf("250 %s is a valid recipient.\r\n", user);
398 ++SMTP->number_of_recipients;
399 CtdlReallocUserData(SYM_SMTP_RECP,
400 strlen(SMTP_RECP) + 1024 );
401 strcat(SMTP_RECP, "local|");
402 strcat(SMTP_RECP, user);
403 strcat(SMTP_RECP, "|0\n");
406 case rfc822_room_delivery:
407 cprintf("250 Delivering to room '%s'\r\n", user);
408 ++SMTP->number_of_recipients;
409 CtdlReallocUserData(SYM_SMTP_RECP,
410 strlen(SMTP_RECP) + 1024 );
411 strcat(SMTP_RECP, "room|");
412 strcat(SMTP_RECP, user);
413 strcat(SMTP_RECP, "|0|\n");
416 case rfc822_no_such_user:
417 cprintf("550 %s: no such user\r\n", recp);
420 case rfc822_address_on_citadel_network:
421 cprintf("250 %s is on the local network\r\n", recp);
422 ++SMTP->number_of_recipients;
423 CtdlReallocUserData(SYM_SMTP_RECP,
424 strlen(SMTP_RECP) + 1024 );
425 strcat(SMTP_RECP, "ignet|");
426 strcat(SMTP_RECP, user);
427 strcat(SMTP_RECP, "|");
428 strcat(SMTP_RECP, node);
429 strcat(SMTP_RECP, "|0|\n");
432 case rfc822_address_nonlocal:
433 if (SMTP->message_originated_locally == 0) {
434 cprintf("551 Relaying denied\r\n");
437 cprintf("250 Remote recipient %s ok\r\n", recp);
438 ++SMTP->number_of_recipients;
439 CtdlReallocUserData(SYM_SMTP_RECP,
440 strlen(SMTP_RECP) + 1024 );
441 strcat(SMTP_RECP, "remote|");
442 strcat(SMTP_RECP, recp);
443 strcat(SMTP_RECP, "|0|\n");
449 cprintf("599 Unknown error\r\n");
455 * Send a message out through the local network
456 * (This is kind of ugly. IGnet should be done using clean server-to-server
457 * code instead of the old style spool.)
459 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
461 char *hold_R, *hold_D, *hold_O;
466 lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
468 hold_R = msg->cm_fields['R'];
469 hold_D = msg->cm_fields['D'];
470 hold_O = msg->cm_fields['O'];
471 msg->cm_fields['R'] = user;
472 msg->cm_fields['D'] = dest;
473 msg->cm_fields['O'] = MAILROOM;
475 serialize_message(&smr, msg);
477 msg->cm_fields['R'] = hold_R;
478 msg->cm_fields['D'] = hold_D;
479 msg->cm_fields['O'] = hold_O;
482 snprintf(filename, sizeof filename,
483 "./network/spoolin/%s.%04x.%04x",
484 dest, getpid(), ++seq);
485 lprintf(9, "spool file name is <%s>\n", filename);
486 fp = fopen(filename, "wb");
488 fwrite(smr.ser, smr.len, 1, fp);
499 * Back end for smtp_data() ... this does the actual delivery of the message
500 * Returns 0 on success, nonzero on failure
502 int smtp_message_delivery(struct CtdlMessage *msg) {
509 int successful_saves = 0; /* number of successful local saves */
510 int failed_saves = 0; /* number of failed deliveries */
511 int remote_spools = 0; /* number of copies to send out */
514 struct usersupp userbuf;
515 char *instr; /* Remote delivery instructions */
516 struct CtdlMessage *imsg;
518 lprintf(9, "smtp_message_delivery() called\n");
520 /* Fill in 'from' fields with envelope information if missing */
521 process_rfc822_addr(SMTP->from, user, node, name);
522 if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
523 if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
524 if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
525 if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
527 /* Save the message in the queue */
528 msgid = CtdlSaveMsg(msg,
534 instr = mallok(1024);
535 snprintf(instr, 1024,
536 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
538 SPOOLMIME, msgid, time(NULL),
541 for (i=0; i<SMTP->number_of_recipients; ++i) {
542 extract_token(buf, SMTP_RECP, i, '\n');
543 extract(dtype, buf, 0);
545 /* Stuff local mailboxes */
546 if (!strcasecmp(dtype, "local")) {
547 extract(user, buf, 1);
548 if (getuser(&userbuf, user) == 0) {
549 MailboxName(room, &userbuf, MAILROOM);
550 CtdlSaveMsgPointerInRoom(room, msgid, 0);
558 /* Delivery to local non-mailbox rooms */
559 if (!strcasecmp(dtype, "room")) {
560 extract(room, buf, 1);
561 CtdlSaveMsgPointerInRoom(room, msgid, 0);
565 /* Delivery over the local Citadel network (IGnet) */
566 if (!strcasecmp(dtype, "ignet")) {
567 extract(user, buf, 1);
568 extract(node, buf, 2);
569 smtp_deliver_ignet(msg, user, node);
572 /* Remote delivery */
573 if (!strcasecmp(dtype, "remote")) {
574 extract(user, buf, 1);
575 instr = reallok(instr, strlen(instr) + 1024);
576 snprintf(&instr[strlen(instr)],
577 strlen(instr) + 1024,
585 /* If there are remote spools to be done, save the instructions */
586 if (remote_spools > 0) {
587 imsg = mallok(sizeof(struct CtdlMessage));
588 memset(imsg, 0, sizeof(struct CtdlMessage));
589 imsg->cm_magic = CTDLMESSAGE_MAGIC;
590 imsg->cm_anon_type = MES_NORMAL;
591 imsg->cm_format_type = FMT_RFC822;
592 imsg->cm_fields['M'] = instr;
593 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
594 CtdlFreeMessage(imsg);
597 /* If there are no remote spools, delete the message */
599 phree(instr); /* only needed here, because CtdlSaveMsg()
600 * would free this buffer otherwise */
601 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, "");
604 return(failed_saves);
610 * Implements the DATA command
612 void smtp_data(void) {
614 struct CtdlMessage *msg;
618 if (strlen(SMTP->from) == 0) {
619 cprintf("503 Need MAIL command first.\r\n");
623 if (SMTP->number_of_recipients < 1) {
624 cprintf("503 Need RCPT command first.\r\n");
628 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
630 generate_rfc822_datestamp(nowstamp, time(NULL));
633 if (body != NULL) snprintf(body, 4096,
634 "Received: from %s\n"
641 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
643 cprintf("550 Unable to save message text: internal error.\r\n");
647 lprintf(9, "Converting message...\n");
648 msg = convert_internet_message(body);
650 /* If the user is locally authenticated, FORCE the From: header to
651 * show up as the real sender
654 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
655 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
656 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
657 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
658 msg->cm_fields['N'] = strdoop(config.c_nodename);
659 msg->cm_fields['H'] = strdoop(config.c_humannode);
662 retval = smtp_message_delivery(msg);
663 CtdlFreeMessage(msg);
666 cprintf("250 Message accepted for delivery.\r\n");
669 cprintf("550 Internal delivery errors: %d\r\n", retval);
672 smtp_data_clear(); /* clear out the buffers now */
679 * Main command loop for SMTP sessions.
681 void smtp_command_loop(void) {
685 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
686 if (client_gets(cmdbuf) < 1) {
687 lprintf(3, "SMTP socket is broken. Ending session.\n");
691 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
692 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
694 if (SMTP->command_state == smtp_user) {
695 smtp_get_user(cmdbuf);
698 else if (SMTP->command_state == smtp_password) {
699 smtp_get_pass(cmdbuf);
702 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
703 smtp_auth(&cmdbuf[5]);
706 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
710 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
711 smtp_hello(&cmdbuf[5], 1);
714 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
715 smtp_expn(&cmdbuf[5]);
718 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
719 smtp_hello(&cmdbuf[5], 0);
722 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
726 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
727 smtp_mail(&cmdbuf[5]);
730 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
731 cprintf("250 This command successfully did nothing.\r\n");
734 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
735 cprintf("221 Goodbye...\r\n");
740 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
741 smtp_rcpt(&cmdbuf[5]);
744 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
748 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
749 smtp_vrfy(&cmdbuf[5]);
753 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
761 /*****************************************************************************/
762 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
763 /*****************************************************************************/
770 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
773 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
780 char user[256], node[256], name[256];
786 size_t blocksize = 0;
789 /* Parse out the host portion of the recipient address */
790 process_rfc822_addr(addr, user, node, name);
792 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
795 /* Load the message out of the database into a temp file */
797 if (msg_fp == NULL) {
799 sprintf(dsn, "Error creating temporary file");
803 CtdlRedirectOutput(msg_fp, -1);
804 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
805 CtdlRedirectOutput(NULL, -1);
806 fseek(msg_fp, 0L, SEEK_END);
807 msg_size = ftell(msg_fp);
811 /* Extract something to send later in the 'MAIL From:' command */
812 strcpy(mailfrom, "");
816 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
817 if (!strncasecmp(buf, "From:", 5)) {
818 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
820 for (i=0; i<strlen(mailfrom); ++i) {
821 if (!isprint(mailfrom[i])) {
822 strcpy(&mailfrom[i], &mailfrom[i+1]);
827 /* Strip out parenthesized names */
830 for (i=0; i<strlen(mailfrom); ++i) {
831 if (mailfrom[i] == '(') lp = i;
832 if (mailfrom[i] == ')') rp = i;
834 if ((lp>0)&&(rp>lp)) {
835 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
838 /* Prefer brokketized names */
841 for (i=0; i<strlen(mailfrom); ++i) {
842 if (mailfrom[i] == '<') lp = i;
843 if (mailfrom[i] == '>') rp = i;
845 if ((lp>=0)&&(rp>lp)) {
847 strcpy(mailfrom, &mailfrom[lp]);
852 } while (scan_done == 0);
853 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
856 /* Figure out what mail exchanger host we have to connect to */
857 num_mxhosts = getmx(mxhosts, node);
858 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
859 if (num_mxhosts < 1) {
861 snprintf(dsn, 256, "No MX hosts found for <%s>", node);
865 for (mx=0; mx<num_mxhosts; ++mx) {
866 extract(buf, mxhosts, mx);
867 lprintf(9, "Trying <%s>\n", buf);
868 sock = sock_connect(buf, "25", "tcp");
869 snprintf(dsn, 256, "Could not connect: %s", strerror(errno));
870 if (sock >= 0) lprintf(9, "Connected!\n");
871 if (sock < 0) snprintf(dsn, 256, "%s", strerror(errno));
872 if (sock >= 0) break;
876 *status = 4; /* dsn is already filled in */
880 /* Process the SMTP greeting from the server */
881 if (ml_sock_gets(sock, buf) < 0) {
883 strcpy(dsn, "Connection broken during SMTP conversation");
886 lprintf(9, "<%s\n", buf);
890 safestrncpy(dsn, &buf[4], 1023);
895 safestrncpy(dsn, &buf[4], 1023);
900 /* At this point we know we are talking to a real SMTP server */
902 /* Do a HELO command */
903 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
904 lprintf(9, ">%s\n", buf);
905 sock_puts_crlf(sock, buf);
906 if (ml_sock_gets(sock, buf) < 0) {
908 strcpy(dsn, "Connection broken during SMTP conversation");
911 lprintf(9, "<%s\n", buf);
915 safestrncpy(dsn, &buf[4], 1023);
920 safestrncpy(dsn, &buf[4], 1023);
926 /* HELO succeeded, now try the MAIL From: command */
927 snprintf(buf, sizeof buf, "MAIL From: <%s>", mailfrom);
928 lprintf(9, ">%s\n", buf);
929 sock_puts_crlf(sock, buf);
930 if (ml_sock_gets(sock, buf) < 0) {
932 strcpy(dsn, "Connection broken during SMTP conversation");
935 lprintf(9, "<%s\n", buf);
939 safestrncpy(dsn, &buf[4], 1023);
944 safestrncpy(dsn, &buf[4], 1023);
950 /* MAIL succeeded, now try the RCPT To: command */
951 snprintf(buf, sizeof buf, "RCPT To: <%s>", addr);
952 lprintf(9, ">%s\n", buf);
953 sock_puts_crlf(sock, buf);
954 if (ml_sock_gets(sock, buf) < 0) {
956 strcpy(dsn, "Connection broken during SMTP conversation");
959 lprintf(9, "<%s\n", buf);
963 safestrncpy(dsn, &buf[4], 1023);
968 safestrncpy(dsn, &buf[4], 1023);
974 /* RCPT succeeded, now try the DATA command */
975 lprintf(9, ">DATA\n");
976 sock_puts_crlf(sock, "DATA");
977 if (ml_sock_gets(sock, buf) < 0) {
979 strcpy(dsn, "Connection broken during SMTP conversation");
982 lprintf(9, "<%s\n", buf);
986 safestrncpy(dsn, &buf[4], 1023);
991 safestrncpy(dsn, &buf[4], 1023);
996 /* If we reach this point, the server is expecting data */
998 while (msg_size > 0) {
999 blocksize = sizeof(buf);
1000 if (blocksize > msg_size) blocksize = msg_size;
1001 fread(buf, blocksize, 1, msg_fp);
1002 sock_write(sock, buf, blocksize);
1003 msg_size -= blocksize;
1005 if (buf[blocksize-1] != 10) {
1006 lprintf(5, "Possible problem: message did not correctly "
1007 "terminate. (expecting 0x10, got 0x%02x)\n",
1011 sock_write(sock, ".\r\n", 3);
1012 if (ml_sock_gets(sock, buf) < 0) {
1014 strcpy(dsn, "Connection broken during SMTP conversation");
1017 lprintf(9, "%s\n", buf);
1018 if (buf[0] != '2') {
1019 if (buf[0] == '4') {
1021 safestrncpy(dsn, &buf[4], 1023);
1026 safestrncpy(dsn, &buf[4], 1023);
1032 safestrncpy(dsn, &buf[4], 1023);
1035 lprintf(9, ">QUIT\n");
1036 sock_puts_crlf(sock, "QUIT");
1037 ml_sock_gets(sock, buf);
1038 lprintf(9, "<%s\n", buf);
1040 bail: if (msg_fp != NULL) fclose(msg_fp);
1048 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1049 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1050 * a "bounce" message (delivery status notification).
1052 void smtp_do_bounce(char *instr) {
1060 char bounceto[1024];
1061 int num_bounces = 0;
1062 int bounce_this = 0;
1063 long bounce_msgid = (-1);
1064 time_t submitted = 0L;
1065 struct CtdlMessage *bmsg = NULL;
1069 lprintf(9, "smtp_do_bounce() called\n");
1070 strcpy(bounceto, "");
1072 lines = num_tokens(instr, '\n');
1075 /* See if it's time to give up on delivery of this message */
1076 for (i=0; i<lines; ++i) {
1077 extract_token(buf, instr, i, '\n');
1078 extract(key, buf, 0);
1079 extract(addr, buf, 1);
1080 if (!strcasecmp(key, "submitted")) {
1081 submitted = atol(addr);
1085 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1091 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1092 if (bmsg == NULL) return;
1093 memset(bmsg, 0, sizeof(struct CtdlMessage));
1095 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1096 bmsg->cm_anon_type = MES_NORMAL;
1097 bmsg->cm_format_type = 1;
1098 bmsg->cm_fields['A'] = strdoop("Citadel");
1099 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1100 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1102 if (give_up) bmsg->cm_fields['M'] = strdoop(
1103 "A message you sent could not be delivered to some or all of its recipients\n"
1104 "due to prolonged unavailability of its destination(s).\n"
1105 "Giving up on the following addresses:\n\n"
1108 else bmsg->cm_fields['M'] = strdoop(
1109 "A message you sent could not be delivered to some or all of its recipients.\n"
1110 "The following addresses were undeliverable:\n\n"
1114 * Now go through the instructions checking for stuff.
1117 for (i=0; i<lines; ++i) {
1118 extract_token(buf, instr, i, '\n');
1119 extract(key, buf, 0);
1120 extract(addr, buf, 1);
1121 status = extract_int(buf, 2);
1122 extract(dsn, buf, 3);
1125 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1126 key, addr, status, dsn);
1128 if (!strcasecmp(key, "bounceto")) {
1129 strcpy(bounceto, addr);
1133 (!strcasecmp(key, "local"))
1134 || (!strcasecmp(key, "remote"))
1135 || (!strcasecmp(key, "ignet"))
1136 || (!strcasecmp(key, "room"))
1138 if (status == 5) bounce_this = 1;
1139 if (give_up) bounce_this = 1;
1145 if (bmsg->cm_fields['M'] == NULL) {
1146 lprintf(2, "ERROR ... M field is null "
1147 "(%s:%d)\n", __FILE__, __LINE__);
1150 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1151 strlen(bmsg->cm_fields['M']) + 1024 );
1152 strcat(bmsg->cm_fields['M'], addr);
1153 strcat(bmsg->cm_fields['M'], ": ");
1154 strcat(bmsg->cm_fields['M'], dsn);
1155 strcat(bmsg->cm_fields['M'], "\n");
1157 remove_token(instr, i, '\n');
1163 /* Deliver the bounce if there's anything worth mentioning */
1164 lprintf(9, "num_bounces = %d\n", num_bounces);
1165 if (num_bounces > 0) {
1167 /* First try the user who sent the message */
1168 lprintf(9, "bounce to user? <%s>\n", bounceto);
1170 if (strlen(bounceto) == 0) {
1171 lprintf(7, "No bounce address specified\n");
1172 bounce_msgid = (-1L);
1174 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1175 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1176 bounce_msgid = (-1L);
1179 bounce_msgid = CtdlSaveMsg(bmsg,
1185 /* Otherwise, go to the Aide> room */
1186 lprintf(9, "bounce to room?\n");
1187 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1192 CtdlFreeMessage(bmsg);
1193 lprintf(9, "Done processing bounces\n");
1198 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1199 * set of delivery instructions for completed deliveries and remove them.
1201 * It returns the number of incomplete deliveries remaining.
1203 int smtp_purge_completed_deliveries(char *instr) {
1214 lines = num_tokens(instr, '\n');
1215 for (i=0; i<lines; ++i) {
1216 extract_token(buf, instr, i, '\n');
1217 extract(key, buf, 0);
1218 extract(addr, buf, 1);
1219 status = extract_int(buf, 2);
1220 extract(dsn, buf, 3);
1225 (!strcasecmp(key, "local"))
1226 || (!strcasecmp(key, "remote"))
1227 || (!strcasecmp(key, "ignet"))
1228 || (!strcasecmp(key, "room"))
1230 if (status == 2) completed = 1;
1235 remove_token(instr, i, '\n');
1248 * Called by smtp_do_queue() to handle an individual message.
1250 void smtp_do_procmsg(long msgnum) {
1251 struct CtdlMessage *msg;
1253 char *results = NULL;
1261 long text_msgid = (-1);
1262 int incomplete_deliveries_remaining;
1263 time_t attempted = 0L;
1264 time_t last_attempted = 0L;
1265 time_t retry = SMTP_RETRY_INTERVAL;
1267 msg = CtdlFetchMessage(msgnum);
1269 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1273 instr = strdoop(msg->cm_fields['M']);
1274 CtdlFreeMessage(msg);
1276 /* Strip out the headers amd any other non-instruction line */
1277 lines = num_tokens(instr, '\n');
1278 for (i=0; i<lines; ++i) {
1279 extract_token(buf, instr, i, '\n');
1280 if (num_tokens(buf, '|') < 2) {
1281 lprintf(9, "removing <%s>\n", buf);
1282 remove_token(instr, i, '\n');
1288 /* Learn the message ID and find out about recent delivery attempts */
1289 lines = num_tokens(instr, '\n');
1290 for (i=0; i<lines; ++i) {
1291 extract_token(buf, instr, i, '\n');
1292 extract(key, buf, 0);
1293 if (!strcasecmp(key, "msgid")) {
1294 text_msgid = extract_long(buf, 1);
1296 if (!strcasecmp(key, "retry")) {
1297 /* double the retry interval after each attempt */
1298 retry = extract_long(buf, 1) * 2L;
1299 remove_token(instr, i, '\n');
1301 if (!strcasecmp(key, "attempted")) {
1302 attempted = extract_long(buf, 1);
1303 if (attempted > last_attempted)
1304 last_attempted = attempted;
1310 * Postpone delivery if we've already tried recently.
1312 if ( (time(NULL) - last_attempted) < retry) {
1313 lprintf(7, "Retry time not yet reached.\n");
1320 * Bail out if there's no actual message associated with this
1322 if (text_msgid < 0L) {
1323 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1328 /* Plow through the instructions looking for 'remote' directives and
1329 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1330 * were experienced and it's time to try again)
1332 lines = num_tokens(instr, '\n');
1333 for (i=0; i<lines; ++i) {
1334 extract_token(buf, instr, i, '\n');
1335 extract(key, buf, 0);
1336 extract(addr, buf, 1);
1337 status = extract_int(buf, 2);
1338 extract(dsn, buf, 3);
1339 if ( (!strcasecmp(key, "remote"))
1340 && ((status==0)||(status==3)) ) {
1341 remove_token(instr, i, '\n');
1344 lprintf(9, "SMTP: Trying <%s>\n", addr);
1345 smtp_try(key, addr, &status, dsn, text_msgid);
1347 if (results == NULL) {
1348 results = mallok(1024);
1349 memset(results, 0, 1024);
1352 results = reallok(results,
1353 strlen(results) + 1024);
1355 sprintf(&results[strlen(results)],
1357 key, addr, status, dsn);
1362 if (results != NULL) {
1363 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1364 strcat(instr, results);
1369 /* Generate 'bounce' messages */
1370 smtp_do_bounce(instr);
1372 /* Go through the delivery list, deleting completed deliveries */
1373 incomplete_deliveries_remaining =
1374 smtp_purge_completed_deliveries(instr);
1378 * No delivery instructions remain, so delete both the instructions
1379 * message and the message message.
1381 if (incomplete_deliveries_remaining <= 0) {
1382 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1383 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1388 * Uncompleted delivery instructions remain, so delete the old
1389 * instructions and replace with the updated ones.
1391 if (incomplete_deliveries_remaining > 0) {
1392 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1393 msg = mallok(sizeof(struct CtdlMessage));
1394 memset(msg, 0, sizeof(struct CtdlMessage));
1395 msg->cm_magic = CTDLMESSAGE_MAGIC;
1396 msg->cm_anon_type = MES_NORMAL;
1397 msg->cm_format_type = FMT_RFC822;
1398 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1399 snprintf(msg->cm_fields['M'],
1401 "Content-type: %s\n\n%s\n"
1404 SPOOLMIME, instr, time(NULL), retry );
1406 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1407 CtdlFreeMessage(msg);
1417 * Run through the queue sending out messages.
1419 void smtp_do_queue(void) {
1420 static int doing_queue = 0;
1423 * This is a simple concurrency check to make sure only one queue run
1424 * is done at a time. We could do this with a mutex, but since we
1425 * don't really require extremely fine granularity here, we'll do it
1426 * with a static variable instead.
1428 if (doing_queue) return;
1432 * Go ahead and run the queue
1434 lprintf(7, "SMTP: processing outbound queue\n");
1436 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1437 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1440 CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1441 SPOOLMIME, NULL, smtp_do_procmsg);
1443 lprintf(7, "SMTP: queue run completed\n");
1449 /*****************************************************************************/
1450 /* MODULE INITIALIZATION STUFF */
1451 /*****************************************************************************/
1454 char *Dynamic_Module_Init(void)
1456 SYM_SMTP = CtdlGetDynamicSymbol();
1457 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1459 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1464 CtdlRegisterServiceHook(0, /* ...and locally */
1469 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1470 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);