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