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,
520 instr = mallok(1024);
521 snprintf(instr, 1024,
522 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
524 SPOOLMIME, msgid, time(NULL),
527 for (i=0; i<SMTP->number_of_recipients; ++i) {
528 extract_token(buf, SMTP_RECP, i, '\n');
529 extract(dtype, buf, 0);
531 /* Stuff local mailboxes */
532 if (!strcasecmp(dtype, "local")) {
533 extract(user, buf, 1);
534 if (getuser(&userbuf, user) == 0) {
535 MailboxName(room, &userbuf, MAILROOM);
536 CtdlSaveMsgPointerInRoom(room, msgid, 0);
544 /* Delivery to local non-mailbox rooms */
545 if (!strcasecmp(dtype, "room")) {
546 extract(room, buf, 1);
547 CtdlSaveMsgPointerInRoom(room, msgid, 0);
551 /* Delivery over the local Citadel network (IGnet) */
552 if (!strcasecmp(dtype, "ignet")) {
553 extract(user, buf, 1);
554 extract(node, buf, 2);
555 smtp_deliver_ignet(msg, user, node);
558 /* Remote delivery */
559 if (!strcasecmp(dtype, "remote")) {
560 extract(user, buf, 1);
561 instr = reallok(instr, strlen(instr) + 1024);
562 snprintf(&instr[strlen(instr)],
563 strlen(instr) + 1024,
571 /* If there are remote spools to be done, save the instructions */
572 if (remote_spools > 0) {
573 imsg = mallok(sizeof(struct CtdlMessage));
574 memset(imsg, 0, sizeof(struct CtdlMessage));
575 imsg->cm_magic = CTDLMESSAGE_MAGIC;
576 imsg->cm_anon_type = MES_NORMAL;
577 imsg->cm_format_type = FMT_RFC822;
578 imsg->cm_fields['M'] = instr;
579 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
580 CtdlFreeMessage(imsg);
583 /* If there are no remote spools, delete the message */
585 phree(instr); /* only needed here, because CtdlSaveMsg()
586 * would free this buffer otherwise */
587 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL);
590 return(failed_saves);
596 * Implements the DATA command
598 void smtp_data(void) {
600 struct CtdlMessage *msg;
604 if (strlen(SMTP->from) == 0) {
605 cprintf("503 Need MAIL command first.\r\n");
609 if (SMTP->number_of_recipients < 1) {
610 cprintf("503 Need RCPT command first.\r\n");
614 cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
616 generate_rfc822_datestamp(nowstamp, time(NULL));
619 if (body != NULL) snprintf(body, 4096,
620 "Received: from %s\n"
627 body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
629 cprintf("550 Unable to save message text: internal error.\r\n");
633 lprintf(9, "Converting message...\n");
634 msg = convert_internet_message(body);
636 /* If the user is locally authenticated, FORCE the From: header to
637 * show up as the real sender
640 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
641 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
642 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
643 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
644 msg->cm_fields['N'] = strdoop(config.c_nodename);
645 msg->cm_fields['H'] = strdoop(config.c_humannode);
648 retval = smtp_message_delivery(msg);
649 CtdlFreeMessage(msg);
652 cprintf("250 Message accepted for delivery.\r\n");
655 cprintf("550 Internal delivery errors: %d\r\n", retval);
663 * Main command loop for SMTP sessions.
665 void smtp_command_loop(void) {
669 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
670 if (client_gets(cmdbuf) < 1) {
671 lprintf(3, "SMTP socket is broken. Ending session.\n");
675 lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
676 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
678 if (SMTP->command_state == smtp_user) {
679 smtp_get_user(cmdbuf);
682 else if (SMTP->command_state == smtp_password) {
683 smtp_get_pass(cmdbuf);
686 else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
687 smtp_auth(&cmdbuf[5]);
690 else if (!strncasecmp(cmdbuf, "DATA", 4)) {
694 else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
695 smtp_hello(&cmdbuf[5], 1);
698 else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
699 smtp_expn(&cmdbuf[5]);
702 else if (!strncasecmp(cmdbuf, "HELO", 4)) {
703 smtp_hello(&cmdbuf[5], 0);
706 else if (!strncasecmp(cmdbuf, "HELP", 4)) {
710 else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
711 smtp_mail(&cmdbuf[5]);
714 else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
715 cprintf("250 This command successfully did nothing.\r\n");
718 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
719 cprintf("221 Goodbye...\r\n");
724 else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
725 smtp_rcpt(&cmdbuf[5]);
728 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
732 else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
733 smtp_vrfy(&cmdbuf[5]);
737 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
745 /*****************************************************************************/
746 /* SMTP CLIENT (OUTBOUND PROCESSING) STUFF */
747 /*****************************************************************************/
754 * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
757 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
764 char user[256], node[256], name[256];
770 size_t blocksize = 0;
773 /* Parse out the host portion of the recipient address */
774 process_rfc822_addr(addr, user, node, name);
776 lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
779 /* Load the message out of the database into a temp file */
781 if (msg_fp == NULL) {
783 sprintf(dsn, "Error creating temporary file");
787 CtdlRedirectOutput(msg_fp, -1);
788 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
789 CtdlRedirectOutput(NULL, -1);
790 fseek(msg_fp, 0L, SEEK_END);
791 msg_size = ftell(msg_fp);
795 /* Extract something to send later in the 'MAIL From:' command */
796 strcpy(mailfrom, "");
800 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
801 if (!strncasecmp(buf, "From:", 5)) {
802 safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
804 for (i=0; i<strlen(mailfrom); ++i) {
805 if (!isprint(mailfrom[i])) {
806 strcpy(&mailfrom[i], &mailfrom[i+1]);
811 /* Strip out parenthesized names */
814 for (i=0; i<strlen(mailfrom); ++i) {
815 if (mailfrom[i] == '(') lp = i;
816 if (mailfrom[i] == ')') rp = i;
818 if ((lp>0)&&(rp>lp)) {
819 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
822 /* Prefer brokketized names */
825 for (i=0; i<strlen(mailfrom); ++i) {
826 if (mailfrom[i] == '<') lp = i;
827 if (mailfrom[i] == '>') rp = i;
829 if ((lp>=0)&&(rp>lp)) {
831 strcpy(mailfrom, &mailfrom[lp]);
836 } while (scan_done == 0);
837 if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
840 /* Figure out what mail exchanger host we have to connect to */
841 num_mxhosts = getmx(mxhosts, node);
842 lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
843 if (num_mxhosts < 1) {
845 snprintf(dsn, 256, "No MX hosts found for <%s>", node);
849 for (mx=0; mx<num_mxhosts; ++mx) {
850 extract(buf, mxhosts, mx);
851 lprintf(9, "Trying <%s>\n", buf);
852 sock = sock_connect(buf, "25", "tcp");
853 snprintf(dsn, 256, "Could not connect: %s", strerror(errno));
854 if (sock >= 0) lprintf(9, "Connected!\n");
855 if (sock < 0) snprintf(dsn, 256, "%s", strerror(errno));
856 if (sock >= 0) break;
860 *status = 4; /* dsn is already filled in */
864 /* Process the SMTP greeting from the server */
865 if (sock_gets(sock, buf) < 0) {
867 strcpy(dsn, "Connection broken during SMTP conversation");
870 lprintf(9, "<%s\n", buf);
874 safestrncpy(dsn, &buf[4], 1023);
879 safestrncpy(dsn, &buf[4], 1023);
884 /* At this point we know we are talking to a real SMTP server */
886 /* Do a HELO command */
887 snprintf(buf, sizeof buf, "HELO %s", config.c_fqdn);
888 lprintf(9, ">%s\n", buf);
889 sock_puts(sock, buf);
890 if (sock_gets(sock, buf) < 0) {
892 strcpy(dsn, "Connection broken during SMTP conversation");
895 lprintf(9, "<%s\n", buf);
899 safestrncpy(dsn, &buf[4], 1023);
904 safestrncpy(dsn, &buf[4], 1023);
910 /* HELO succeeded, now try the MAIL From: command */
911 snprintf(buf, sizeof buf, "MAIL From: %s", mailfrom);
912 lprintf(9, ">%s\n", buf);
913 sock_puts(sock, buf);
914 if (sock_gets(sock, buf) < 0) {
916 strcpy(dsn, "Connection broken during SMTP conversation");
919 lprintf(9, "<%s\n", buf);
923 safestrncpy(dsn, &buf[4], 1023);
928 safestrncpy(dsn, &buf[4], 1023);
934 /* MAIL succeeded, now try the RCPT To: command */
935 snprintf(buf, sizeof buf, "RCPT To: %s", addr);
936 lprintf(9, ">%s\n", buf);
937 sock_puts(sock, buf);
938 if (sock_gets(sock, buf) < 0) {
940 strcpy(dsn, "Connection broken during SMTP conversation");
943 lprintf(9, "<%s\n", buf);
947 safestrncpy(dsn, &buf[4], 1023);
952 safestrncpy(dsn, &buf[4], 1023);
958 /* RCPT succeeded, now try the DATA command */
959 lprintf(9, ">DATA\n");
960 sock_puts(sock, "DATA");
961 if (sock_gets(sock, buf) < 0) {
963 strcpy(dsn, "Connection broken during SMTP conversation");
966 lprintf(9, "<%s\n", buf);
970 safestrncpy(dsn, &buf[4], 1023);
975 safestrncpy(dsn, &buf[4], 1023);
980 /* If we reach this point, the server is expecting data */
982 while (msg_size > 0) {
983 blocksize = sizeof(buf);
984 if (blocksize > msg_size) blocksize = msg_size;
985 fread(buf, blocksize, 1, msg_fp);
986 sock_write(sock, buf, blocksize);
987 msg_size -= blocksize;
989 if (buf[blocksize-1] != 10) {
990 lprintf(5, "Possible problem: message did not correctly "
991 "terminate. (expecting 0x10, got 0x%02x)\n",
995 sock_write(sock, ".\r\n", 3);
996 if (sock_gets(sock, buf) < 0) {
998 strcpy(dsn, "Connection broken during SMTP conversation");
1001 lprintf(9, "%s\n", buf);
1002 if (buf[0] != '2') {
1003 if (buf[0] == '4') {
1005 safestrncpy(dsn, &buf[4], 1023);
1010 safestrncpy(dsn, &buf[4], 1023);
1016 safestrncpy(dsn, &buf[4], 1023);
1019 lprintf(9, ">QUIT\n");
1020 sock_puts(sock, "QUIT");
1021 sock_gets(sock, buf);
1022 lprintf(9, "<%s\n", buf);
1024 bail: if (msg_fp != NULL) fclose(msg_fp);
1032 * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1033 * instructions for "5" codes (permanent fatal errors) and produce/deliver
1034 * a "bounce" message (delivery status notification).
1036 void smtp_do_bounce(char *instr) {
1044 char bounceto[1024];
1045 int num_bounces = 0;
1046 int bounce_this = 0;
1047 long bounce_msgid = (-1);
1048 time_t submitted = 0L;
1049 struct CtdlMessage *bmsg = NULL;
1053 lprintf(9, "smtp_do_bounce() called\n");
1054 strcpy(bounceto, "");
1056 lines = num_tokens(instr, '\n');
1059 /* See if it's time to give up on delivery of this message */
1060 for (i=0; i<lines; ++i) {
1061 extract_token(buf, instr, i, '\n');
1062 extract(key, buf, 0);
1063 extract(addr, buf, 1);
1064 if (!strcasecmp(key, "submitted")) {
1065 submitted = atol(addr);
1069 if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1075 bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1076 if (bmsg == NULL) return;
1077 memset(bmsg, 0, sizeof(struct CtdlMessage));
1079 bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1080 bmsg->cm_anon_type = MES_NORMAL;
1081 bmsg->cm_format_type = 1;
1082 bmsg->cm_fields['A'] = strdoop("Citadel");
1083 bmsg->cm_fields['O'] = strdoop(MAILROOM);
1084 bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1086 if (give_up) bmsg->cm_fields['M'] = strdoop(
1087 "A message you sent could not be delivered to some or all of its recipients\n"
1088 "due to prolonged unavailability of its destination(s).\n"
1089 "Giving up on the following addresses:\n\n"
1092 else bmsg->cm_fields['M'] = strdoop(
1093 "A message you sent could not be delivered to some or all of its recipients.\n"
1094 "The following addresses were undeliverable:\n\n"
1098 * Now go through the instructions checking for stuff.
1101 for (i=0; i<lines; ++i) {
1102 extract_token(buf, instr, i, '\n');
1103 extract(key, buf, 0);
1104 extract(addr, buf, 1);
1105 status = extract_int(buf, 2);
1106 extract(dsn, buf, 3);
1109 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1110 key, addr, status, dsn);
1112 if (!strcasecmp(key, "bounceto")) {
1113 strcpy(bounceto, addr);
1117 (!strcasecmp(key, "local"))
1118 || (!strcasecmp(key, "remote"))
1119 || (!strcasecmp(key, "ignet"))
1120 || (!strcasecmp(key, "room"))
1122 if (status == 5) bounce_this = 1;
1123 if (give_up) bounce_this = 1;
1129 if (bmsg->cm_fields['M'] == NULL) {
1130 lprintf(2, "ERROR ... M field is null "
1131 "(%s:%d)\n", __FILE__, __LINE__);
1134 bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1135 strlen(bmsg->cm_fields['M']) + 1024 );
1136 strcat(bmsg->cm_fields['M'], addr);
1137 strcat(bmsg->cm_fields['M'], ": ");
1138 strcat(bmsg->cm_fields['M'], dsn);
1139 strcat(bmsg->cm_fields['M'], "\n");
1141 remove_token(instr, i, '\n');
1147 /* Deliver the bounce if there's anything worth mentioning */
1148 lprintf(9, "num_bounces = %d\n", num_bounces);
1149 if (num_bounces > 0) {
1151 /* First try the user who sent the message */
1152 lprintf(9, "bounce to user? <%s>\n", bounceto);
1153 if (strlen(bounceto) == 0) {
1154 lprintf(7, "No bounce address specified\n");
1155 bounce_msgid = (-1L);
1157 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1158 lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1159 bounce_msgid = (-1L);
1162 bounce_msgid = CtdlSaveMsg(bmsg,
1167 /* Otherwise, go to the Aide> room */
1168 lprintf(9, "bounce to room?\n");
1169 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1174 CtdlFreeMessage(bmsg);
1175 lprintf(9, "Done processing bounces\n");
1180 * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1181 * set of delivery instructions for completed deliveries and remove them.
1183 * It returns the number of incomplete deliveries remaining.
1185 int smtp_purge_completed_deliveries(char *instr) {
1196 lines = num_tokens(instr, '\n');
1197 for (i=0; i<lines; ++i) {
1198 extract_token(buf, instr, i, '\n');
1199 extract(key, buf, 0);
1200 extract(addr, buf, 1);
1201 status = extract_int(buf, 2);
1202 extract(dsn, buf, 3);
1207 (!strcasecmp(key, "local"))
1208 || (!strcasecmp(key, "remote"))
1209 || (!strcasecmp(key, "ignet"))
1210 || (!strcasecmp(key, "room"))
1212 if (status == 2) completed = 1;
1217 remove_token(instr, i, '\n');
1230 * Called by smtp_do_queue() to handle an individual message.
1232 void smtp_do_procmsg(long msgnum) {
1233 struct CtdlMessage *msg;
1235 char *results = NULL;
1243 long text_msgid = (-1);
1244 int incomplete_deliveries_remaining;
1245 time_t attempted = 0L;
1246 time_t last_attempted = 0L;
1248 msg = CtdlFetchMessage(msgnum);
1250 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1254 instr = strdoop(msg->cm_fields['M']);
1255 CtdlFreeMessage(msg);
1257 /* Strip out the headers amd any other non-instruction line */
1258 lines = num_tokens(instr, '\n');
1259 for (i=0; i<lines; ++i) {
1260 extract_token(buf, instr, i, '\n');
1261 if (num_tokens(buf, '|') < 2) {
1262 lprintf(9, "removing <%s>\n", buf);
1263 remove_token(instr, i, '\n');
1269 /* Learn the message ID and find out about recent delivery attempts */
1270 lines = num_tokens(instr, '\n');
1271 for (i=0; i<lines; ++i) {
1272 extract_token(buf, instr, i, '\n');
1273 extract(key, buf, 0);
1274 if (!strcasecmp(key, "msgid")) {
1275 text_msgid = extract_long(buf, 1);
1277 if (!strcasecmp(key, "attempted")) {
1278 attempted = extract_long(buf, 1);
1279 if (attempted > last_attempted)
1280 last_attempted = attempted;
1286 * Postpone delivery if we've already tried recently.
1288 if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
1289 lprintf(7, "Retry time not yet reached.\n");
1296 * Bail out if there's no actual message associated with this
1298 if (text_msgid < 0L) {
1299 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1304 /* Plow through the instructions looking for 'remote' directives and
1305 * a status of 0 (no delivery yet attempted) or 3 (transient errors
1306 * were experienced and it's time to try again)
1308 lines = num_tokens(instr, '\n');
1309 for (i=0; i<lines; ++i) {
1310 extract_token(buf, instr, i, '\n');
1311 extract(key, buf, 0);
1312 extract(addr, buf, 1);
1313 status = extract_int(buf, 2);
1314 extract(dsn, buf, 3);
1315 if ( (!strcasecmp(key, "remote"))
1316 && ((status==0)||(status==3)) ) {
1317 remove_token(instr, i, '\n');
1320 lprintf(9, "SMTP: Trying <%s>\n", addr);
1321 smtp_try(key, addr, &status, dsn, text_msgid);
1323 if (results == NULL) {
1324 results = mallok(1024);
1325 memset(results, 0, 1024);
1328 results = reallok(results,
1329 strlen(results) + 1024);
1331 sprintf(&results[strlen(results)],
1333 key, addr, status, dsn);
1338 if (results != NULL) {
1339 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1340 strcat(instr, results);
1345 /* Generate 'bounce' messages */
1346 smtp_do_bounce(instr);
1348 /* Go through the delivery list, deleting completed deliveries */
1349 incomplete_deliveries_remaining =
1350 smtp_purge_completed_deliveries(instr);
1354 * No delivery instructions remain, so delete both the instructions
1355 * message and the message message.
1357 if (incomplete_deliveries_remaining <= 0) {
1358 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1359 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);
1364 * Uncompleted delivery instructions remain, so delete the old
1365 * instructions and replace with the updated ones.
1367 if (incomplete_deliveries_remaining > 0) {
1368 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);
1369 msg = mallok(sizeof(struct CtdlMessage));
1370 memset(msg, 0, sizeof(struct CtdlMessage));
1371 msg->cm_magic = CTDLMESSAGE_MAGIC;
1372 msg->cm_anon_type = MES_NORMAL;
1373 msg->cm_format_type = FMT_RFC822;
1374 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1375 snprintf(msg->cm_fields['M'],
1377 "Content-type: %s\n\n%s\nattempted|%ld\n",
1378 SPOOLMIME, instr, time(NULL) );
1380 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1381 CtdlFreeMessage(msg);
1391 * Run through the queue sending out messages.
1393 void smtp_do_queue(void) {
1394 static int doing_queue = 0;
1397 * This is a simple concurrency check to make sure only one queue run
1398 * is done at a time. We could do this with a mutex, but since we
1399 * don't really require extremely fine granularity here, we'll do it
1400 * with a static variable instead.
1402 if (doing_queue) return;
1406 * Go ahead and run the queue
1408 lprintf(5, "SMTP: processing outbound queue\n");
1410 if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1411 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1414 CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1416 lprintf(5, "SMTP: queue run completed\n");
1422 /*****************************************************************************/
1423 /* MODULE INITIALIZATION STUFF */
1424 /*****************************************************************************/
1427 char *Dynamic_Module_Init(void)
1429 SYM_SMTP = CtdlGetDynamicSymbol();
1430 SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1432 CtdlRegisterServiceHook(config.c_smtp_port, /* On the net... */
1437 CtdlRegisterServiceHook(0, /* ...and locally */
1442 create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1443 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);