dfea49ab85989d5e4f01d0344c5df92c91783410
[citadel.git] / citadel / serv_smtp.c
1 /*
2  * $Id$
3  *
4  * This module is an SMTP and ESMTP implementation for the Citadel system.
5  * It is compliant with all of the following:
6  *
7  * RFC  821 - Simple Mail Transfer Protocol
8  * RFC  876 - Survey of SMTP Implementations
9  * RFC 1047 - Duplicate messages and SMTP
10  * RFC 1854 - command pipelining
11  * RFC 1869 - Extended Simple Mail Transfer Protocol
12  * RFC 1870 - SMTP Service Extension for Message Size Declaration
13  * RFC 1893 - Enhanced Mail System Status Codes
14  * RFC 2033 - Local Mail Transfer Protocol
15  * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16  * RFC 2197 - SMTP Service Extension for Command Pipelining
17  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
18  * RFC 2554 - SMTP Service Extension for Authentication
19  * RFC 2821 - Simple Mail Transfer Protocol
20  * RFC 2822 - Internet Message Format
21  * RFC 2920 - SMTP Service Extension for Command Pipelining
22  *
23  */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <sys/types.h>
34
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
37 # include <time.h>
38 #else
39 # if HAVE_SYS_TIME_H
40 #  include <sys/time.h>
41 # else
42 #  include <time.h>
43 # endif
44 #endif
45
46 #include <sys/wait.h>
47 #include <ctype.h>
48 #include <string.h>
49 #include <limits.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
53 #include "citadel.h"
54 #include "server.h"
55 #include "sysdep_decls.h"
56 #include "citserver.h"
57 #include "support.h"
58 #include "config.h"
59 #include "control.h"
60 #include "serv_extensions.h"
61 #include "room_ops.h"
62 #include "user_ops.h"
63 #include "policy.h"
64 #include "database.h"
65 #include "msgbase.h"
66 #include "tools.h"
67 #include "internet_addressing.h"
68 #include "genstamp.h"
69 #include "domain.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
72
73 #ifdef HAVE_OPENSSL
74 #include "serv_crypto.h"
75 #endif
76
77
78
79 #ifndef HAVE_SNPRINTF
80 #include "snprintf.h"
81 #endif
82
83 struct citsmtp {                /* Information about the current session */
84         int command_state;
85         char helo_node[SIZ];
86         struct ctdluser vrfy_buffer;
87         int vrfy_count;
88         char vrfy_match[SIZ];
89         char from[SIZ];
90         char recipients[SIZ];
91         int number_of_recipients;
92         int delivery_mode;
93         int message_originated_locally;
94         int is_lmtp;
95 };
96
97 enum {                          /* Command states for login authentication */
98         smtp_command,
99         smtp_user,
100         smtp_password
101 };
102
103 enum {                          /* Delivery modes */
104         smtp_deliver_local,
105         smtp_deliver_remote
106 };
107
108 #define SMTP            ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
109 #define SMTP_RECPS      ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
110 #define SMTP_ROOMS      ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
111
112
113 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
114
115
116
117 /*****************************************************************************/
118 /*                      SMTP SERVER (INBOUND) STUFF                          */
119 /*****************************************************************************/
120
121
122
123
124 /*
125  * Here's where our SMTP session begins its happy day.
126  */
127 void smtp_greeting(void) {
128
129         strcpy(CC->cs_clientname, "SMTP session");
130         CC->internal_pgm = 1;
131         CC->cs_flags |= CS_STEALTH;
132         CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
133         CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
134         CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
135         snprintf(SMTP_RECPS, SIZ, "%s", "");
136         snprintf(SMTP_ROOMS, SIZ, "%s", "");
137
138         cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
139 }
140
141 /*
142  * LMTP is like SMTP but with some extra bonus footage added.
143  */
144 void lmtp_greeting(void) {
145         smtp_greeting();
146         SMTP->is_lmtp = 1;
147 }
148
149
150 /*
151  * Login greeting common to all auth methods
152  */
153 void smtp_auth_greeting(void) {
154                 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
155                 lprintf(9, "SMTP authenticated login successful\n");
156                 CC->internal_pgm = 0;
157                 CC->cs_flags &= ~CS_STEALTH;
158 }
159
160
161 /*
162  * Implement HELO and EHLO commands.
163  *
164  * which_command:  0=HELO, 1=EHLO, 2=LHLO
165  */
166 void smtp_hello(char *argbuf, int which_command) {
167
168         safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
169
170         if ( (which_command != 2) && (SMTP->is_lmtp) ) {
171                 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
172                 return;
173         }
174
175         if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
176                 cprintf("500 LHLO is only allowed when running LMTP\r\n");
177                 return;
178         }
179
180         if (which_command == 0) {
181                 cprintf("250 Hello %s (%s [%s])\r\n",
182                         SMTP->helo_node,
183                         CC->cs_host,
184                         CC->cs_addr
185                 );
186         }
187         else {
188                 if (which_command == 1) {
189                         cprintf("250-Hello %s (%s [%s])\r\n",
190                                 SMTP->helo_node,
191                                 CC->cs_host,
192                                 CC->cs_addr
193                         );
194                 }
195                 else {
196                         cprintf("250-Greetings and joyous salutations.\r\n");
197                 }
198                 cprintf("250-HELP\r\n");
199                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
200                 cprintf("250-PIPELINING\r\n");
201                 cprintf("250-AUTH LOGIN PLAIN\r\n");
202                 cprintf("250-AUTH=LOGIN PLAIN\r\n");
203 #ifdef HAVE_OPENSSL
204                 cprintf("250-STARTTLS\r\n");
205 #endif
206                 cprintf("250 ENHANCEDSTATUSCODES\r\n");
207         }
208 }
209
210
211
212 /*
213  * Implement HELP command.
214  */
215 void smtp_help(void) {
216         cprintf("214-Commands accepted:\r\n");
217         cprintf("214-    DATA\r\n");
218         cprintf("214-    EHLO\r\n");
219         cprintf("214-    EXPN\r\n");
220         cprintf("214-    HELO\r\n");
221         cprintf("214-    HELP\r\n");
222         cprintf("214-    MAIL\r\n");
223         cprintf("214-    NOOP\r\n");
224         cprintf("214-    QUIT\r\n");
225         cprintf("214-    RCPT\r\n");
226         cprintf("214-    RSET\r\n");
227         cprintf("214-    VRFY\r\n");
228         cprintf("214     \r\n");
229 }
230
231
232 /*
233  *
234  */
235 void smtp_get_user(char *argbuf) {
236         char buf[SIZ];
237         char username[SIZ];
238
239         CtdlDecodeBase64(username, argbuf, SIZ);
240         lprintf(9, "Trying <%s>\n", username);
241         if (CtdlLoginExistingUser(username) == login_ok) {
242                 CtdlEncodeBase64(buf, "Password:", 9);
243                 cprintf("334 %s\r\n", buf);
244                 SMTP->command_state = smtp_password;
245         }
246         else {
247                 cprintf("500 5.7.0 No such user.\r\n");
248                 SMTP->command_state = smtp_command;
249         }
250 }
251
252
253 /*
254  *
255  */
256 void smtp_get_pass(char *argbuf) {
257         char password[SIZ];
258
259         CtdlDecodeBase64(password, argbuf, SIZ);
260         lprintf(9, "Trying <%s>\n", password);
261         if (CtdlTryPassword(password) == pass_ok) {
262                 smtp_auth_greeting();
263         }
264         else {
265                 cprintf("535 5.7.0 Authentication failed.\r\n");
266         }
267         SMTP->command_state = smtp_command;
268 }
269
270
271 /*
272  *
273  */
274 void smtp_auth(char *argbuf) {
275         char buf[SIZ];
276         char method[SIZ];
277         char encoded_authstring[SIZ];
278         char decoded_authstring[SIZ];
279         char ident[SIZ];
280         char user[SIZ];
281         char pass[SIZ];
282
283         if (CC->logged_in) {
284                 cprintf("504 5.7.4 Already logged in.\r\n");
285                 return;
286         }
287
288         extract_token(method, argbuf, 0, ' ');
289
290         if (!strncasecmp(method, "login", 5) ) {
291                 if (strlen(argbuf) >= 7) {
292                         smtp_get_user(&argbuf[6]);
293                 }
294                 else {
295                         CtdlEncodeBase64(buf, "Username:", 9);
296                         cprintf("334 %s\r\n", buf);
297                         SMTP->command_state = smtp_user;
298                 }
299                 return;
300         }
301
302         if (!strncasecmp(method, "plain", 5) ) {
303                 extract_token(encoded_authstring, argbuf, 1, ' ');
304                 CtdlDecodeBase64(decoded_authstring,
305                                 encoded_authstring,
306                                 strlen(encoded_authstring) );
307                 strcpy(ident, decoded_authstring);
308                 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
309                 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
310
311                 if (CtdlLoginExistingUser(user) == login_ok) {
312                         if (CtdlTryPassword(pass) == pass_ok) {
313                                 smtp_auth_greeting();
314                                 return;
315                         }
316                 }
317                 cprintf("504 5.7.4 Authentication failed.\r\n");
318         }
319
320         if (strncasecmp(method, "login", 5) ) {
321                 cprintf("504 5.7.4 Unknown authentication method.\r\n");
322                 return;
323         }
324
325 }
326
327
328 /*
329  * Back end for smtp_vrfy() command
330  */
331 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
332
333         if (!fuzzy_match(us, SMTP->vrfy_match)) {
334                 ++SMTP->vrfy_count;
335                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
336         }
337 }
338
339
340 /* 
341  * Implements the VRFY (verify user name) command.
342  * Performs fuzzy match on full user names.
343  */
344 void smtp_vrfy(char *argbuf) {
345         SMTP->vrfy_count = 0;
346         strcpy(SMTP->vrfy_match, argbuf);
347         ForEachUser(smtp_vrfy_backend, NULL);
348
349         if (SMTP->vrfy_count < 1) {
350                 cprintf("550 5.1.1 String does not match anything.\r\n");
351         }
352         else if (SMTP->vrfy_count == 1) {
353                 cprintf("250 %s <cit%ld@%s>\r\n",
354                         SMTP->vrfy_buffer.fullname,
355                         SMTP->vrfy_buffer.usernum,
356                         config.c_fqdn);
357         }
358         else if (SMTP->vrfy_count > 1) {
359                 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
360                         SMTP->vrfy_count);
361         }
362
363 }
364
365
366
367 /*
368  * Back end for smtp_expn() command
369  */
370 void smtp_expn_backend(struct ctdluser *us, void *data) {
371
372         if (!fuzzy_match(us, SMTP->vrfy_match)) {
373
374                 if (SMTP->vrfy_count >= 1) {
375                         cprintf("250-%s <cit%ld@%s>\r\n",
376                                 SMTP->vrfy_buffer.fullname,
377                                 SMTP->vrfy_buffer.usernum,
378                                 config.c_fqdn);
379                 }
380
381                 ++SMTP->vrfy_count;
382                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
383         }
384 }
385
386
387 /* 
388  * Implements the EXPN (expand user name) command.
389  * Performs fuzzy match on full user names.
390  */
391 void smtp_expn(char *argbuf) {
392         SMTP->vrfy_count = 0;
393         strcpy(SMTP->vrfy_match, argbuf);
394         ForEachUser(smtp_expn_backend, NULL);
395
396         if (SMTP->vrfy_count < 1) {
397                 cprintf("550 5.1.1 String does not match anything.\r\n");
398         }
399         else if (SMTP->vrfy_count >= 1) {
400                 cprintf("250 %s <cit%ld@%s>\r\n",
401                         SMTP->vrfy_buffer.fullname,
402                         SMTP->vrfy_buffer.usernum,
403                         config.c_fqdn);
404         }
405 }
406
407
408 /*
409  * Implements the RSET (reset state) command.
410  * Currently this just zeroes out the state buffer.  If pointers to data
411  * allocated with mallok() are ever placed in the state buffer, we have to
412  * be sure to phree() them first!
413  *
414  * Set do_response to nonzero to output the SMTP RSET response code.
415  */
416 void smtp_rset(int do_response) {
417         int is_lmtp;
418
419         /*
420          * Our entire SMTP state is discarded when a RSET command is issued,
421          * but we need to preserve this one little piece of information, so
422          * we save it for later.
423          */
424         is_lmtp = SMTP->is_lmtp;
425
426         memset(SMTP, 0, sizeof(struct citsmtp));
427
428         /*
429          * It is somewhat ambiguous whether we want to log out when a RSET
430          * command is issued.  Here's the code to do it.  It is commented out
431          * because some clients (such as Pine) issue RSET commands before
432          * each message, but still expect to be logged in.
433          *
434          * if (CC->logged_in) {
435          *      logout(CC);
436          * }
437          */
438
439         /*
440          * Reinstate this little piece of information we saved (see above).
441          */
442         SMTP->is_lmtp = is_lmtp;
443
444         if (do_response) {
445                 cprintf("250 2.0.0 Zap!\r\n");
446         }
447 }
448
449 /*
450  * Clear out the portions of the state buffer that need to be cleared out
451  * after the DATA command finishes.
452  */
453 void smtp_data_clear(void) {
454         strcpy(SMTP->from, "");
455         strcpy(SMTP->recipients, "");
456         SMTP->number_of_recipients = 0;
457         SMTP->delivery_mode = 0;
458         SMTP->message_originated_locally = 0;
459 }
460
461
462
463 /*
464  * Implements the "MAIL From:" command
465  */
466 void smtp_mail(char *argbuf) {
467         char user[SIZ];
468         char node[SIZ];
469         char name[SIZ];
470
471         if (strlen(SMTP->from) != 0) {
472                 cprintf("503 5.1.0 Only one sender permitted\r\n");
473                 return;
474         }
475
476         if (strncasecmp(argbuf, "From:", 5)) {
477                 cprintf("501 5.1.7 Syntax error\r\n");
478                 return;
479         }
480
481         strcpy(SMTP->from, &argbuf[5]);
482         striplt(SMTP->from);
483         stripallbut(SMTP->from, '<', '>');
484
485         /* We used to reject empty sender names, until it was brought to our
486          * attention that RFC1123 5.2.9 requires that this be allowed.  So now
487          * we allow it, but replace the empty string with a fake
488          * address so we don't have to contend with the empty string causing
489          * other code to fail when it's expecting something there.
490          */
491         if (strlen(SMTP->from) == 0) {
492                 strcpy(SMTP->from, "someone@somewhere.org");
493         }
494
495         /* If this SMTP connection is from a logged-in user, force the 'from'
496          * to be the user's Internet e-mail address as Citadel knows it.
497          */
498         if (CC->logged_in) {
499                 strcpy(SMTP->from, CC->cs_inet_email);
500                 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
501                 SMTP->message_originated_locally = 1;
502                 return;
503         }
504
505         else if (SMTP->is_lmtp) {
506                 /* Bypass forgery checking for LMTP */
507         }
508
509         /* Otherwise, make sure outsiders aren't trying to forge mail from
510          * this system.
511          */
512         else {
513                 process_rfc822_addr(SMTP->from, user, node, name);
514                 if (CtdlHostAlias(node) != hostalias_nomatch) {
515                         cprintf("550 5.1.8 "
516                                 "You must log in to send mail from %s\r\n",
517                                 node);
518                         strcpy(SMTP->from, "");
519                         return;
520                 }
521         }
522
523         cprintf("250 2.0.0 Sender ok\r\n");
524 }
525
526
527
528 /*
529  * Implements the "RCPT To:" command
530  */
531 void smtp_rcpt(char *argbuf) {
532         char recp[SIZ];
533         char message_to_spammer[SIZ];
534         struct recptypes *valid = NULL;
535
536         if (strlen(SMTP->from) == 0) {
537                 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
538                 return;
539         }
540
541         if (strncasecmp(argbuf, "To:", 3)) {
542                 cprintf("501 5.1.7 Syntax error\r\n");
543                 return;
544         }
545
546         strcpy(recp, &argbuf[3]);
547         striplt(recp);
548         stripallbut(recp, '<', '>');
549
550         if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
551                 cprintf("452 4.5.3 Too many recipients\r\n");
552                 return;
553         }
554
555         /* RBL check */
556         if ( (!CC->logged_in)
557            && (!SMTP->is_lmtp) ) {
558                 if (rbl_check(message_to_spammer)) {
559                         cprintf("550 %s\r\n", message_to_spammer);
560                         /* no need to phree(valid), it's not allocated yet */
561                         return;
562                 }
563         }
564
565         valid = validate_recipients(recp);
566         if (valid->num_error > 0) {
567                 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
568                 phree(valid);
569                 return;
570         }
571
572         if (valid->num_internet > 0) {
573                 if ( (SMTP->message_originated_locally == 0)
574                    && (SMTP->is_lmtp == 0) ) {
575                         cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
576                         phree(valid);
577                         return;
578                 }
579         }
580
581         cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
582         if (strlen(SMTP->recipients) > 0) {
583                 strcat(SMTP->recipients, ",");
584         }
585         strcat(SMTP->recipients, recp);
586         SMTP->number_of_recipients += 1;
587 }
588
589
590
591
592 /*
593  * Implements the DATA command
594  */
595 void smtp_data(void) {
596         char *body;
597         struct CtdlMessage *msg;
598         long msgnum;
599         char nowstamp[SIZ];
600         struct recptypes *valid;
601         int scan_errors;
602         int i;
603         char result[SIZ];
604
605         if (strlen(SMTP->from) == 0) {
606                 cprintf("503 5.5.1 Need MAIL command first.\r\n");
607                 return;
608         }
609
610         if (SMTP->number_of_recipients < 1) {
611                 cprintf("503 5.5.1 Need RCPT command first.\r\n");
612                 return;
613         }
614
615         cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
616         
617         datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
618         body = mallok(4096);
619
620         if (body != NULL) snprintf(body, 4096,
621                 "Received: from %s (%s [%s])\n"
622                 "       by %s; %s\n",
623                         SMTP->helo_node,
624                         CC->cs_host,
625                         CC->cs_addr,
626                         config.c_fqdn,
627                         nowstamp);
628         
629         body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
630         if (body == NULL) {
631                 cprintf("550 5.6.5 "
632                         "Unable to save message: internal error.\r\n");
633                 return;
634         }
635
636         lprintf(9, "Converting message...\n");
637         msg = convert_internet_message(body);
638
639         /* If the user is locally authenticated, FORCE the From: header to
640          * show up as the real sender.  Yes, this violates the RFC standard,
641          * but IT MAKES SENSE.  If you prefer strict RFC adherence over
642          * common sense, you can disable this in the configuration.
643          *
644          * We also set the "message room name" ('O' field) to MAILROOM
645          * (which is Mail> on most systems) to prevent it from getting set
646          * to something ugly like "0000058008.Sent Items>" when the message
647          * is read with a Citadel client.
648          */
649         if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
650                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
651                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
652                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
653                 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
654                 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
655                 msg->cm_fields['A'] = strdoop(CC->user.fullname);
656                 msg->cm_fields['N'] = strdoop(config.c_nodename);
657                 msg->cm_fields['H'] = strdoop(config.c_humannode);
658                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
659                 msg->cm_fields['O'] = strdoop(MAILROOM);
660         }
661
662         /* Submit the message into the Citadel system. */
663         valid = validate_recipients(SMTP->recipients);
664
665         /* If there are modules that want to scan this message before final
666          * submission (such as virus checkers or spam filters), call them now
667          * and give them an opportunity to reject the message.
668          */
669         scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
670
671         if (scan_errors > 0) {  /* We don't want this message! */
672
673                 if (msg->cm_fields['0'] == NULL) {
674                         msg->cm_fields['0'] = strdoop(
675                                 "5.7.1 Message rejected by filter");
676                 }
677
678                 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
679         }
680         
681         else {                  /* Ok, we'll accept this message. */
682                 msgnum = CtdlSubmitMsg(msg, valid, "");
683                 if (msgnum > 0L) {
684                         sprintf(result, "250 2.0.0 Message accepted.\r\n");
685                 }
686                 else {
687                         sprintf(result, "550 5.5.0 Internal delivery error\r\n");
688                 }
689         }
690
691         /* For SMTP and ESTMP, just print the result message.  For LMTP, we
692          * have to print one result message for each recipient.  Since there
693          * is nothing in Citadel which would cause different recipients to
694          * have different results, we can get away with just spitting out the
695          * same message once for each recipient.
696          */
697         if (SMTP->is_lmtp) {
698                 for (i=0; i<SMTP->number_of_recipients; ++i) {
699                         cprintf("%s", result);
700                 }
701         }
702         else {
703                 cprintf("%s", result);
704         }
705
706         CtdlFreeMessage(msg);
707         phree(valid);
708         smtp_data_clear();      /* clear out the buffers now */
709 }
710
711
712 /*
713  * implements the STARTTLS command (Citadel API version)
714  */
715 #ifdef HAVE_OPENSSL
716 void smtp_starttls(void)
717 {
718         char ok_response[SIZ];
719         char nosup_response[SIZ];
720         char error_response[SIZ];
721
722         sprintf(ok_response,
723                 "200 2.0.0 Begin TLS negotiation now\r\n");
724         sprintf(nosup_response,
725                 "554 5.7.3 TLS not supported here\r\n");
726         sprintf(error_response,
727                 "554 5.7.3 Internal error\r\n");
728         CtdlStartTLS(ok_response, nosup_response, error_response);
729         smtp_rset(0);
730 }
731 #endif
732
733
734
735 /* 
736  * Main command loop for SMTP sessions.
737  */
738 void smtp_command_loop(void) {
739         char cmdbuf[SIZ];
740
741         time(&CC->lastcmd);
742         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
743         if (client_gets(cmdbuf) < 1) {
744                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
745                 CC->kill_me = 1;
746                 return;
747         }
748         lprintf(5, "SMTP: %s\n", cmdbuf);
749         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
750
751         if (SMTP->command_state == smtp_user) {
752                 smtp_get_user(cmdbuf);
753         }
754
755         else if (SMTP->command_state == smtp_password) {
756                 smtp_get_pass(cmdbuf);
757         }
758
759         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
760                 smtp_auth(&cmdbuf[5]);
761         }
762
763         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
764                 smtp_data();
765         }
766
767         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
768                 smtp_expn(&cmdbuf[5]);
769         }
770
771         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
772                 smtp_hello(&cmdbuf[5], 0);
773         }
774
775         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
776                 smtp_hello(&cmdbuf[5], 1);
777         }
778
779         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
780                 smtp_hello(&cmdbuf[5], 2);
781         }
782
783         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
784                 smtp_help();
785         }
786
787         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
788                 smtp_mail(&cmdbuf[5]);
789         }
790
791         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
792                 cprintf("250 NOOP\r\n");
793         }
794
795         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
796                 cprintf("221 Goodbye...\r\n");
797                 CC->kill_me = 1;
798                 return;
799         }
800
801         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
802                 smtp_rcpt(&cmdbuf[5]);
803         }
804
805         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
806                 smtp_rset(1);
807         }
808 #ifdef HAVE_OPENSSL
809         else if (!strcasecmp(cmdbuf, "STARTTLS")) {
810                 smtp_starttls();
811         }
812 #endif
813         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
814                 smtp_vrfy(&cmdbuf[5]);
815         }
816
817         else {
818                 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
819         }
820
821 }
822
823
824
825
826 /*****************************************************************************/
827 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
828 /*****************************************************************************/
829
830
831
832 /*
833  * smtp_try()
834  *
835  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
836  *
837  */
838 void smtp_try(const char *key, const char *addr, int *status,
839               char *dsn, size_t n, long msgnum)
840 {
841         int sock = (-1);
842         char mxhosts[1024];
843         int num_mxhosts;
844         int mx;
845         int i;
846         char user[SIZ], node[SIZ], name[SIZ];
847         char buf[1024];
848         char mailfrom[1024];
849         int lp, rp;
850         FILE *msg_fp = NULL;
851         size_t msg_size;
852         size_t blocksize = 0;
853         int scan_done;
854
855         /* Parse out the host portion of the recipient address */
856         process_rfc822_addr(addr, user, node, name);
857
858         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
859                 user, node, name);
860
861         /* Load the message out of the database into a temp file */
862         msg_fp = tmpfile();
863         if (msg_fp == NULL) {
864                 *status = 4;
865                 snprintf(dsn, n, "Error creating temporary file");
866                 return;
867         }
868         else {
869                 CtdlRedirectOutput(msg_fp, -1);
870                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
871                 CtdlRedirectOutput(NULL, -1);
872                 fseek(msg_fp, 0L, SEEK_END);
873                 msg_size = ftell(msg_fp);
874         }
875
876
877         /* Extract something to send later in the 'MAIL From:' command */
878         strcpy(mailfrom, "");
879         rewind(msg_fp);
880         scan_done = 0;
881         do {
882                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
883                 if (!strncasecmp(buf, "From:", 5)) {
884                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
885                         striplt(mailfrom);
886                         for (i=0; i<strlen(mailfrom); ++i) {
887                                 if (!isprint(mailfrom[i])) {
888                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
889                                         i=0;
890                                 }
891                         }
892
893                         /* Strip out parenthesized names */
894                         lp = (-1);
895                         rp = (-1);
896                         for (i=0; i<strlen(mailfrom); ++i) {
897                                 if (mailfrom[i] == '(') lp = i;
898                                 if (mailfrom[i] == ')') rp = i;
899                         }
900                         if ((lp>0)&&(rp>lp)) {
901                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
902                         }
903
904                         /* Prefer brokketized names */
905                         lp = (-1);
906                         rp = (-1);
907                         for (i=0; i<strlen(mailfrom); ++i) {
908                                 if (mailfrom[i] == '<') lp = i;
909                                 if (mailfrom[i] == '>') rp = i;
910                         }
911                         if ( (lp>=0) && (rp>lp) ) {
912                                 mailfrom[rp] = 0;
913                                 strcpy(mailfrom, &mailfrom[lp]);
914                         }
915
916                         scan_done = 1;
917                 }
918         } while (scan_done == 0);
919         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
920
921         /* Figure out what mail exchanger host we have to connect to */
922         num_mxhosts = getmx(mxhosts, node);
923         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
924         if (num_mxhosts < 1) {
925                 *status = 5;
926                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
927                 return;
928         }
929
930         sock = (-1);
931         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
932                 extract(buf, mxhosts, mx);
933                 lprintf(9, "Trying <%s>\n", buf);
934                 sock = sock_connect(buf, "25", "tcp");
935                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
936                 if (sock >= 0) lprintf(9, "Connected!\n");
937                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
938         }
939
940         if (sock < 0) {
941                 *status = 4;    /* dsn is already filled in */
942                 return;
943         }
944
945         /* Process the SMTP greeting from the server */
946         if (ml_sock_gets(sock, buf) < 0) {
947                 *status = 4;
948                 strcpy(dsn, "Connection broken during SMTP conversation");
949                 goto bail;
950         }
951         lprintf(9, "<%s\n", buf);
952         if (buf[0] != '2') {
953                 if (buf[0] == '4') {
954                         *status = 4;
955                         safestrncpy(dsn, &buf[4], 1023);
956                         goto bail;
957                 }
958                 else {
959                         *status = 5;
960                         safestrncpy(dsn, &buf[4], 1023);
961                         goto bail;
962                 }
963         }
964
965         /* At this point we know we are talking to a real SMTP server */
966
967         /* Do a HELO command */
968         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
969         lprintf(9, ">%s", buf);
970         sock_write(sock, buf, strlen(buf));
971         if (ml_sock_gets(sock, buf) < 0) {
972                 *status = 4;
973                 strcpy(dsn, "Connection broken during SMTP HELO");
974                 goto bail;
975         }
976         lprintf(9, "<%s\n", buf);
977         if (buf[0] != '2') {
978                 if (buf[0] == '4') {
979                         *status = 4;
980                         safestrncpy(dsn, &buf[4], 1023);
981                         goto bail;
982                 }
983                 else {
984                         *status = 5;
985                         safestrncpy(dsn, &buf[4], 1023);
986                         goto bail;
987                 }
988         }
989
990
991         /* HELO succeeded, now try the MAIL From: command */
992         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
993         lprintf(9, ">%s", buf);
994         sock_write(sock, buf, strlen(buf));
995         if (ml_sock_gets(sock, buf) < 0) {
996                 *status = 4;
997                 strcpy(dsn, "Connection broken during SMTP MAIL");
998                 goto bail;
999         }
1000         lprintf(9, "<%s\n", buf);
1001         if (buf[0] != '2') {
1002                 if (buf[0] == '4') {
1003                         *status = 4;
1004                         safestrncpy(dsn, &buf[4], 1023);
1005                         goto bail;
1006                 }
1007                 else {
1008                         *status = 5;
1009                         safestrncpy(dsn, &buf[4], 1023);
1010                         goto bail;
1011                 }
1012         }
1013
1014
1015         /* MAIL succeeded, now try the RCPT To: command */
1016         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1017         lprintf(9, ">%s", buf);
1018         sock_write(sock, buf, strlen(buf));
1019         if (ml_sock_gets(sock, buf) < 0) {
1020                 *status = 4;
1021                 strcpy(dsn, "Connection broken during SMTP RCPT");
1022                 goto bail;
1023         }
1024         lprintf(9, "<%s\n", buf);
1025         if (buf[0] != '2') {
1026                 if (buf[0] == '4') {
1027                         *status = 4;
1028                         safestrncpy(dsn, &buf[4], 1023);
1029                         goto bail;
1030                 }
1031                 else {
1032                         *status = 5;
1033                         safestrncpy(dsn, &buf[4], 1023);
1034                         goto bail;
1035                 }
1036         }
1037
1038
1039         /* RCPT succeeded, now try the DATA command */
1040         lprintf(9, ">DATA\n");
1041         sock_write(sock, "DATA\r\n", 6);
1042         if (ml_sock_gets(sock, buf) < 0) {
1043                 *status = 4;
1044                 strcpy(dsn, "Connection broken during SMTP DATA");
1045                 goto bail;
1046         }
1047         lprintf(9, "<%s\n", buf);
1048         if (buf[0] != '3') {
1049                 if (buf[0] == '4') {
1050                         *status = 3;
1051                         safestrncpy(dsn, &buf[4], 1023);
1052                         goto bail;
1053                 }
1054                 else {
1055                         *status = 5;
1056                         safestrncpy(dsn, &buf[4], 1023);
1057                         goto bail;
1058                 }
1059         }
1060
1061         /* If we reach this point, the server is expecting data */
1062         rewind(msg_fp);
1063         while (msg_size > 0) {
1064                 blocksize = sizeof(buf);
1065                 if (blocksize > msg_size) blocksize = msg_size;
1066                 fread(buf, blocksize, 1, msg_fp);
1067                 sock_write(sock, buf, blocksize);
1068                 msg_size -= blocksize;
1069         }
1070         if (buf[blocksize-1] != 10) {
1071                 lprintf(5, "Possible problem: message did not correctly "
1072                         "terminate. (expecting 0x10, got 0x%02x)\n",
1073                                 buf[blocksize-1]);
1074         }
1075
1076         sock_write(sock, ".\r\n", 3);
1077         if (ml_sock_gets(sock, buf) < 0) {
1078                 *status = 4;
1079                 strcpy(dsn, "Connection broken during SMTP message transmit");
1080                 goto bail;
1081         }
1082         lprintf(9, "%s\n", buf);
1083         if (buf[0] != '2') {
1084                 if (buf[0] == '4') {
1085                         *status = 4;
1086                         safestrncpy(dsn, &buf[4], 1023);
1087                         goto bail;
1088                 }
1089                 else {
1090                         *status = 5;
1091                         safestrncpy(dsn, &buf[4], 1023);
1092                         goto bail;
1093                 }
1094         }
1095
1096         /* We did it! */
1097         safestrncpy(dsn, &buf[4], 1023);
1098         *status = 2;
1099
1100         lprintf(9, ">QUIT\n");
1101         sock_write(sock, "QUIT\r\n", 6);
1102         ml_sock_gets(sock, buf);
1103         lprintf(9, "<%s\n", buf);
1104
1105 bail:   if (msg_fp != NULL) fclose(msg_fp);
1106         sock_close(sock);
1107         return;
1108 }
1109
1110
1111
1112 /*
1113  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1114  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1115  * a "bounce" message (delivery status notification).
1116  */
1117 void smtp_do_bounce(char *instr) {
1118         int i;
1119         int lines;
1120         int status;
1121         char buf[1024];
1122         char key[1024];
1123         char addr[1024];
1124         char dsn[1024];
1125         char bounceto[1024];
1126         int num_bounces = 0;
1127         int bounce_this = 0;
1128         long bounce_msgid = (-1);
1129         time_t submitted = 0L;
1130         struct CtdlMessage *bmsg = NULL;
1131         int give_up = 0;
1132         struct recptypes *valid;
1133         int successful_bounce = 0;
1134
1135         lprintf(9, "smtp_do_bounce() called\n");
1136         strcpy(bounceto, "");
1137
1138         lines = num_tokens(instr, '\n');
1139
1140
1141         /* See if it's time to give up on delivery of this message */
1142         for (i=0; i<lines; ++i) {
1143                 extract_token(buf, instr, i, '\n');
1144                 extract(key, buf, 0);
1145                 extract(addr, buf, 1);
1146                 if (!strcasecmp(key, "submitted")) {
1147                         submitted = atol(addr);
1148                 }
1149         }
1150
1151         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1152                 give_up = 1;
1153         }
1154
1155
1156
1157         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1158         if (bmsg == NULL) return;
1159         memset(bmsg, 0, sizeof(struct CtdlMessage));
1160
1161         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1162         bmsg->cm_anon_type = MES_NORMAL;
1163         bmsg->cm_format_type = 1;
1164         bmsg->cm_fields['A'] = strdoop("Citadel");
1165         bmsg->cm_fields['O'] = strdoop(MAILROOM);
1166         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1167
1168         if (give_up) bmsg->cm_fields['M'] = strdoop(
1169 "A message you sent could not be delivered to some or all of its recipients\n"
1170 "due to prolonged unavailability of its destination(s).\n"
1171 "Giving up on the following addresses:\n\n"
1172 );
1173
1174         else bmsg->cm_fields['M'] = strdoop(
1175 "A message you sent could not be delivered to some or all of its recipients.\n"
1176 "The following addresses were undeliverable:\n\n"
1177 );
1178
1179         /*
1180          * Now go through the instructions checking for stuff.
1181          */
1182         for (i=0; i<lines; ++i) {
1183                 extract_token(buf, instr, i, '\n');
1184                 extract(key, buf, 0);
1185                 extract(addr, buf, 1);
1186                 status = extract_int(buf, 2);
1187                 extract(dsn, buf, 3);
1188                 bounce_this = 0;
1189
1190                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1191                         key, addr, status, dsn);
1192
1193                 if (!strcasecmp(key, "bounceto")) {
1194                         strcpy(bounceto, addr);
1195                 }
1196
1197                 if (
1198                    (!strcasecmp(key, "local"))
1199                    || (!strcasecmp(key, "remote"))
1200                    || (!strcasecmp(key, "ignet"))
1201                    || (!strcasecmp(key, "room"))
1202                 ) {
1203                         if (status == 5) bounce_this = 1;
1204                         if (give_up) bounce_this = 1;
1205                 }
1206
1207                 if (bounce_this) {
1208                         ++num_bounces;
1209
1210                         if (bmsg->cm_fields['M'] == NULL) {
1211                                 lprintf(2, "ERROR ... M field is null "
1212                                         "(%s:%d)\n", __FILE__, __LINE__);
1213                         }
1214
1215                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1216                                 strlen(bmsg->cm_fields['M']) + 1024 );
1217                         strcat(bmsg->cm_fields['M'], addr);
1218                         strcat(bmsg->cm_fields['M'], ": ");
1219                         strcat(bmsg->cm_fields['M'], dsn);
1220                         strcat(bmsg->cm_fields['M'], "\n");
1221
1222                         remove_token(instr, i, '\n');
1223                         --i;
1224                         --lines;
1225                 }
1226         }
1227
1228         /* Deliver the bounce if there's anything worth mentioning */
1229         lprintf(9, "num_bounces = %d\n", num_bounces);
1230         if (num_bounces > 0) {
1231
1232                 /* First try the user who sent the message */
1233                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1234                 if (strlen(bounceto) == 0) {
1235                         lprintf(7, "No bounce address specified\n");
1236                         bounce_msgid = (-1L);
1237                 }
1238
1239                 /* Can we deliver the bounce to the original sender? */
1240                 valid = validate_recipients(bounceto);
1241                 if (valid != NULL) {
1242                         if (valid->num_error == 0) {
1243                                 CtdlSubmitMsg(bmsg, valid, "");
1244                                 successful_bounce = 1;
1245                         }
1246                 }
1247
1248                 /* If not, post it in the Aide> room */
1249                 if (successful_bounce == 0) {
1250                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1251                 }
1252
1253                 /* Free up the memory we used */
1254                 if (valid != NULL) {
1255                         phree(valid);
1256                 }
1257         }
1258
1259         CtdlFreeMessage(bmsg);
1260         lprintf(9, "Done processing bounces\n");
1261 }
1262
1263
1264 /*
1265  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1266  * set of delivery instructions for completed deliveries and remove them.
1267  *
1268  * It returns the number of incomplete deliveries remaining.
1269  */
1270 int smtp_purge_completed_deliveries(char *instr) {
1271         int i;
1272         int lines;
1273         int status;
1274         char buf[1024];
1275         char key[1024];
1276         char addr[1024];
1277         char dsn[1024];
1278         int completed;
1279         int incomplete = 0;
1280
1281         lines = num_tokens(instr, '\n');
1282         for (i=0; i<lines; ++i) {
1283                 extract_token(buf, instr, i, '\n');
1284                 extract(key, buf, 0);
1285                 extract(addr, buf, 1);
1286                 status = extract_int(buf, 2);
1287                 extract(dsn, buf, 3);
1288
1289                 completed = 0;
1290
1291                 if (
1292                    (!strcasecmp(key, "local"))
1293                    || (!strcasecmp(key, "remote"))
1294                    || (!strcasecmp(key, "ignet"))
1295                    || (!strcasecmp(key, "room"))
1296                 ) {
1297                         if (status == 2) completed = 1;
1298                         else ++incomplete;
1299                 }
1300
1301                 if (completed) {
1302                         remove_token(instr, i, '\n');
1303                         --i;
1304                         --lines;
1305                 }
1306         }
1307
1308         return(incomplete);
1309 }
1310
1311
1312 /*
1313  * smtp_do_procmsg()
1314  *
1315  * Called by smtp_do_queue() to handle an individual message.
1316  */
1317 void smtp_do_procmsg(long msgnum, void *userdata) {
1318         struct CtdlMessage *msg;
1319         char *instr = NULL;
1320         char *results = NULL;
1321         int i;
1322         int lines;
1323         int status;
1324         char buf[1024];
1325         char key[1024];
1326         char addr[1024];
1327         char dsn[1024];
1328         long text_msgid = (-1);
1329         int incomplete_deliveries_remaining;
1330         time_t attempted = 0L;
1331         time_t last_attempted = 0L;
1332         time_t retry = SMTP_RETRY_INTERVAL;
1333
1334         lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1335
1336         msg = CtdlFetchMessage(msgnum);
1337         if (msg == NULL) {
1338                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1339                 return;
1340         }
1341
1342         instr = strdoop(msg->cm_fields['M']);
1343         CtdlFreeMessage(msg);
1344
1345         /* Strip out the headers amd any other non-instruction line */
1346         lines = num_tokens(instr, '\n');
1347         for (i=0; i<lines; ++i) {
1348                 extract_token(buf, instr, i, '\n');
1349                 if (num_tokens(buf, '|') < 2) {
1350                         remove_token(instr, i, '\n');
1351                         --lines;
1352                         --i;
1353                 }
1354         }
1355
1356         /* Learn the message ID and find out about recent delivery attempts */
1357         lines = num_tokens(instr, '\n');
1358         for (i=0; i<lines; ++i) {
1359                 extract_token(buf, instr, i, '\n');
1360                 extract(key, buf, 0);
1361                 if (!strcasecmp(key, "msgid")) {
1362                         text_msgid = extract_long(buf, 1);
1363                 }
1364                 if (!strcasecmp(key, "retry")) {
1365                         /* double the retry interval after each attempt */
1366                         retry = extract_long(buf, 1) * 2L;
1367                         if (retry > SMTP_RETRY_MAX) {
1368                                 retry = SMTP_RETRY_MAX;
1369                         }
1370                         remove_token(instr, i, '\n');
1371                 }
1372                 if (!strcasecmp(key, "attempted")) {
1373                         attempted = extract_long(buf, 1);
1374                         if (attempted > last_attempted)
1375                                 last_attempted = attempted;
1376                 }
1377         }
1378
1379         /*
1380          * Postpone delivery if we've already tried recently.
1381          */
1382         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1383                 lprintf(7, "Retry time not yet reached.\n");
1384                 phree(instr);
1385                 return;
1386         }
1387
1388
1389         /*
1390          * Bail out if there's no actual message associated with this
1391          */
1392         if (text_msgid < 0L) {
1393                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1394                 phree(instr);
1395                 return;
1396         }
1397
1398         /* Plow through the instructions looking for 'remote' directives and
1399          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1400          * were experienced and it's time to try again)
1401          */
1402         lines = num_tokens(instr, '\n');
1403         for (i=0; i<lines; ++i) {
1404                 extract_token(buf, instr, i, '\n');
1405                 extract(key, buf, 0);
1406                 extract(addr, buf, 1);
1407                 status = extract_int(buf, 2);
1408                 extract(dsn, buf, 3);
1409                 if ( (!strcasecmp(key, "remote"))
1410                    && ((status==0)||(status==3)||(status==4)) ) {
1411
1412                         /* Remove this "remote" instruction from the set,
1413                          * but replace the set's final newline if
1414                          * remove_token() stripped it.  It has to be there.
1415                          */
1416                         remove_token(instr, i, '\n');
1417                         if (instr[strlen(instr)-1] != '\n') {
1418                                 strcat(instr, "\n");
1419                         }
1420
1421                         --i;
1422                         --lines;
1423                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1424                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1425                         if (status != 2) {
1426                                 if (results == NULL) {
1427                                         results = mallok(1024);
1428                                         memset(results, 0, 1024);
1429                                 }
1430                                 else {
1431                                         results = reallok(results,
1432                                                 strlen(results) + 1024);
1433                                 }
1434                                 snprintf(&results[strlen(results)], 1024,
1435                                         "%s|%s|%d|%s\n",
1436                                         key, addr, status, dsn);
1437                         }
1438                 }
1439         }
1440
1441         if (results != NULL) {
1442                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1443                 strcat(instr, results);
1444                 phree(results);
1445         }
1446
1447
1448         /* Generate 'bounce' messages */
1449         smtp_do_bounce(instr);
1450
1451         /* Go through the delivery list, deleting completed deliveries */
1452         incomplete_deliveries_remaining = 
1453                 smtp_purge_completed_deliveries(instr);
1454
1455
1456         /*
1457          * No delivery instructions remain, so delete both the instructions
1458          * message and the message message.
1459          */
1460         if (incomplete_deliveries_remaining <= 0) {
1461                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1462                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1463         }
1464
1465
1466         /*
1467          * Uncompleted delivery instructions remain, so delete the old
1468          * instructions and replace with the updated ones.
1469          */
1470         if (incomplete_deliveries_remaining > 0) {
1471                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1472                 msg = mallok(sizeof(struct CtdlMessage));
1473                 memset(msg, 0, sizeof(struct CtdlMessage));
1474                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1475                 msg->cm_anon_type = MES_NORMAL;
1476                 msg->cm_format_type = FMT_RFC822;
1477                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1478                 snprintf(msg->cm_fields['M'],
1479                         strlen(instr)+SIZ,
1480                         "Content-type: %s\n\n%s\n"
1481                         "attempted|%ld\n"
1482                         "retry|%ld\n",
1483                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1484                 phree(instr);
1485                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1486                 CtdlFreeMessage(msg);
1487         }
1488
1489 }
1490
1491
1492
1493 /*
1494  * smtp_do_queue()
1495  * 
1496  * Run through the queue sending out messages.
1497  */
1498 void smtp_do_queue(void) {
1499         static int doing_queue = 0;
1500
1501         /*
1502          * This is a simple concurrency check to make sure only one queue run
1503          * is done at a time.  We could do this with a mutex, but since we
1504          * don't really require extremely fine granularity here, we'll do it
1505          * with a static variable instead.
1506          */
1507         if (doing_queue) return;
1508         doing_queue = 1;
1509
1510         /* 
1511          * Go ahead and run the queue
1512          */
1513         lprintf(7, "SMTP: processing outbound queue\n");
1514
1515         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1516                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1517                 return;
1518         }
1519         CtdlForEachMessage(MSGS_ALL, 0L,
1520                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1521
1522         lprintf(7, "SMTP: queue run completed\n");
1523         run_queue_now = 0;
1524         doing_queue = 0;
1525 }
1526
1527
1528
1529 /*****************************************************************************/
1530 /*                          SMTP UTILITY COMMANDS                            */
1531 /*****************************************************************************/
1532
1533 void cmd_smtp(char *argbuf) {
1534         char cmd[SIZ];
1535         char node[SIZ];
1536         char buf[SIZ];
1537         int i;
1538         int num_mxhosts;
1539
1540         if (CtdlAccessCheck(ac_aide)) return;
1541
1542         extract(cmd, argbuf, 0);
1543
1544         if (!strcasecmp(cmd, "mx")) {
1545                 extract(node, argbuf, 1);
1546                 num_mxhosts = getmx(buf, node);
1547                 cprintf("%d %d MX hosts listed for %s\n",
1548                         LISTING_FOLLOWS, num_mxhosts, node);
1549                 for (i=0; i<num_mxhosts; ++i) {
1550                         extract(node, buf, i);
1551                         cprintf("%s\n", node);
1552                 }
1553                 cprintf("000\n");
1554                 return;
1555         }
1556
1557         else if (!strcasecmp(cmd, "runqueue")) {
1558                 run_queue_now = 1;
1559                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1560                 return;
1561         }
1562
1563         else {
1564                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1565         }
1566
1567 }
1568
1569
1570 /*
1571  * Initialize the SMTP outbound queue
1572  */
1573 void smtp_init_spoolout(void) {
1574         struct ctdlroom qrbuf;
1575
1576         /*
1577          * Create the room.  This will silently fail if the room already
1578          * exists, and that's perfectly ok, because we want it to exist.
1579          */
1580         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1581
1582         /*
1583          * Make sure it's set to be a "system room" so it doesn't show up
1584          * in the <K>nown rooms list for Aides.
1585          */
1586         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1587                 qrbuf.QRflags2 |= QR2_SYSTEM;
1588                 lputroom(&qrbuf);
1589         }
1590 }
1591
1592
1593
1594
1595 /*****************************************************************************/
1596 /*                      MODULE INITIALIZATION STUFF                          */
1597 /*****************************************************************************/
1598
1599
1600 char *serv_smtp_init(void)
1601 {
1602         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1603                                 NULL,
1604                                 smtp_greeting,
1605                                 smtp_command_loop);
1606
1607         CtdlRegisterServiceHook(0,                      /* ...and locally */
1608                                 "lmtp.socket",
1609                                 lmtp_greeting,
1610                                 smtp_command_loop);
1611
1612         smtp_init_spoolout();
1613         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1614         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1615         return "$Id$";
1616 }