]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Added a command "SMTP" to the Citadel protocol, to do some unimportant
[citadel.git] / citadel / serv_smtp.c
1 /*
2  * $Id$
3  *
4  * An implementation of RFC821 (Simple Mail Transfer Protocol) for the
5  * Citadel system.
6  *
7  */
8
9 #include "sysdep.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <signal.h>
15 #include <pwd.h>
16 #include <errno.h>
17 #include <sys/types.h>
18
19 #if TIME_WITH_SYS_TIME
20 # include <sys/time.h>
21 # include <time.h>
22 #else
23 # if HAVE_SYS_TIME_H
24 #  include <sys/time.h>
25 # else
26 #  include <time.h>
27 # endif
28 #endif
29
30 #include <sys/wait.h>
31 #include <ctype.h>
32 #include <string.h>
33 #include <limits.h>
34 #include "citadel.h"
35 #include "server.h"
36 #include "sysdep_decls.h"
37 #include "citserver.h"
38 #include "support.h"
39 #include "config.h"
40 #include "control.h"
41 #include "dynloader.h"
42 #include "room_ops.h"
43 #include "user_ops.h"
44 #include "policy.h"
45 #include "database.h"
46 #include "msgbase.h"
47 #include "tools.h"
48 #include "internet_addressing.h"
49 #include "genstamp.h"
50 #include "domain.h"
51 #include "clientsocket.h"
52
53
54 #ifndef HAVE_SNPRINTF
55 #include "snprintf.h"
56 #endif
57
58 struct citsmtp {                /* Information about the current session */
59         int command_state;
60         char helo_node[SIZ];
61         struct usersupp vrfy_buffer;
62         int vrfy_count;
63         char vrfy_match[SIZ];
64         char from[SIZ];
65         int number_of_recipients;
66         int delivery_mode;
67         int message_originated_locally;
68 };
69
70 enum {                          /* Command states for login authentication */
71         smtp_command,
72         smtp_user,
73         smtp_password
74 };
75
76 enum {                          /* Delivery modes */
77         smtp_deliver_local,
78         smtp_deliver_remote
79 };
80
81 #define SMTP            ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
82 #define SMTP_RECP       ((char *)CtdlGetUserData(SYM_SMTP_RECP))
83
84 long SYM_SMTP;
85 long SYM_SMTP_RECP;
86
87 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
88
89
90
91 /*****************************************************************************/
92 /*                      SMTP SERVER (INBOUND) STUFF                          */
93 /*****************************************************************************/
94
95
96
97
98 /*
99  * Here's where our SMTP session begins its happy day.
100  */
101 void smtp_greeting(void) {
102
103         strcpy(CC->cs_clientname, "SMTP session");
104         CC->internal_pgm = 1;
105         CC->cs_flags |= CS_STEALTH;
106         CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
107         CtdlAllocUserData(SYM_SMTP_RECP, SIZ);
108         sprintf(SMTP_RECP, "%s", "");
109
110         cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
111 }
112
113
114 /*
115  * Implement HELO and EHLO commands.
116  */
117 void smtp_hello(char *argbuf, int is_esmtp) {
118
119         safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
120
121         if (!is_esmtp) {
122                 cprintf("250 Greetings and joyous salutations.\r\n");
123         }
124         else {
125                 cprintf("250-Extended greetings and joyous salutations.\r\n");
126                 cprintf("250-HELP\r\n");
127                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
128                 cprintf("250-PIPELINING\r\n");
129                 cprintf("250 AUTH=LOGIN\r\n");
130         }
131 }
132
133
134 /*
135  * Implement HELP command.
136  */
137 void smtp_help(void) {
138         cprintf("214-Commands accepted:\r\n");
139         cprintf("214-    DATA\r\n");
140         cprintf("214-    EHLO\r\n");
141         cprintf("214-    EXPN\r\n");
142         cprintf("214-    HELO\r\n");
143         cprintf("214-    HELP\r\n");
144         cprintf("214-    MAIL\r\n");
145         cprintf("214-    NOOP\r\n");
146         cprintf("214-    QUIT\r\n");
147         cprintf("214-    RCPT\r\n");
148         cprintf("214-    RSET\r\n");
149         cprintf("214-    VRFY\r\n");
150         cprintf("214     \r\n");
151 }
152
153
154 /*
155  *
156  */
157 void smtp_get_user(char *argbuf) {
158         char buf[SIZ];
159         char username[SIZ];
160
161         decode_base64(username, argbuf);
162         lprintf(9, "Trying <%s>\n", username);
163         if (CtdlLoginExistingUser(username) == login_ok) {
164                 encode_base64(buf, "Password:");
165                 cprintf("334 %s\r\n", buf);
166                 SMTP->command_state = smtp_password;
167         }
168         else {
169                 cprintf("500 No such user.\r\n");
170                 SMTP->command_state = smtp_command;
171         }
172 }
173
174
175 /*
176  *
177  */
178 void smtp_get_pass(char *argbuf) {
179         char password[SIZ];
180
181         decode_base64(password, argbuf);
182         lprintf(9, "Trying <%s>\n", password);
183         if (CtdlTryPassword(password) == pass_ok) {
184                 cprintf("235 Authentication successful.\r\n");
185                 lprintf(9, "SMTP authenticated login successful\n");
186                 CC->internal_pgm = 0;
187                 CC->cs_flags &= ~CS_STEALTH;
188         }
189         else {
190                 cprintf("500 Authentication failed.\r\n");
191         }
192         SMTP->command_state = smtp_command;
193 }
194
195
196 /*
197  *
198  */
199 void smtp_auth(char *argbuf) {
200         char buf[SIZ];
201
202         if (strncasecmp(argbuf, "login", 5) ) {
203                 cprintf("550 We only support LOGIN authentication.\r\n");
204                 return;
205         }
206
207         if (strlen(argbuf) >= 7) {
208                 smtp_get_user(&argbuf[6]);
209         }
210
211         else {
212                 encode_base64(buf, "Username:");
213                 cprintf("334 %s\r\n", buf);
214                 SMTP->command_state = smtp_user;
215         }
216 }
217
218
219 /*
220  * Back end for smtp_vrfy() command
221  */
222 void smtp_vrfy_backend(struct usersupp *us, void *data) {
223
224         if (!fuzzy_match(us, SMTP->vrfy_match)) {
225                 ++SMTP->vrfy_count;
226                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
227         }
228 }
229
230
231 /* 
232  * Implements the VRFY (verify user name) command.
233  * Performs fuzzy match on full user names.
234  */
235 void smtp_vrfy(char *argbuf) {
236         SMTP->vrfy_count = 0;
237         strcpy(SMTP->vrfy_match, argbuf);
238         ForEachUser(smtp_vrfy_backend, NULL);
239
240         if (SMTP->vrfy_count < 1) {
241                 cprintf("550 String does not match anything.\r\n");
242         }
243         else if (SMTP->vrfy_count == 1) {
244                 cprintf("250 %s <cit%ld@%s>\r\n",
245                         SMTP->vrfy_buffer.fullname,
246                         SMTP->vrfy_buffer.usernum,
247                         config.c_fqdn);
248         }
249         else if (SMTP->vrfy_count > 1) {
250                 cprintf("553 Request ambiguous: %d users matched.\r\n",
251                         SMTP->vrfy_count);
252         }
253
254 }
255
256
257
258 /*
259  * Back end for smtp_expn() command
260  */
261 void smtp_expn_backend(struct usersupp *us, void *data) {
262
263         if (!fuzzy_match(us, SMTP->vrfy_match)) {
264
265                 if (SMTP->vrfy_count >= 1) {
266                         cprintf("250-%s <cit%ld@%s>\r\n",
267                                 SMTP->vrfy_buffer.fullname,
268                                 SMTP->vrfy_buffer.usernum,
269                                 config.c_fqdn);
270                 }
271
272                 ++SMTP->vrfy_count;
273                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
274         }
275 }
276
277
278 /* 
279  * Implements the EXPN (expand user name) command.
280  * Performs fuzzy match on full user names.
281  */
282 void smtp_expn(char *argbuf) {
283         SMTP->vrfy_count = 0;
284         strcpy(SMTP->vrfy_match, argbuf);
285         ForEachUser(smtp_expn_backend, NULL);
286
287         if (SMTP->vrfy_count < 1) {
288                 cprintf("550 String does not match anything.\r\n");
289         }
290         else if (SMTP->vrfy_count >= 1) {
291                 cprintf("250 %s <cit%ld@%s>\r\n",
292                         SMTP->vrfy_buffer.fullname,
293                         SMTP->vrfy_buffer.usernum,
294                         config.c_fqdn);
295         }
296 }
297
298
299 /*
300  * Implements the RSET (reset state) command.
301  * Currently this just zeroes out the state buffer.  If pointers to data
302  * allocated with mallok() are ever placed in the state buffer, we have to
303  * be sure to phree() them first!
304  */
305 void smtp_rset(void) {
306         memset(SMTP, 0, sizeof(struct citsmtp));
307         if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
308         if (CC->logged_in) logout(CC);
309         cprintf("250 Zap!\r\n");
310 }
311
312 /*
313  * Clear out the portions of the state buffer that need to be cleared out
314  * after the DATA command finishes.
315  */
316 void smtp_data_clear(void) {
317         strcpy(SMTP->from, "");
318         SMTP->number_of_recipients = 0;
319         SMTP->delivery_mode = 0;
320         SMTP->message_originated_locally = 0;
321         if (SMTP_RECP != NULL) strcpy(SMTP_RECP, "");
322 }
323
324
325
326 /*
327  * Implements the "MAIL From:" command
328  */
329 void smtp_mail(char *argbuf) {
330         char user[SIZ];
331         char node[SIZ];
332         int cvt;
333
334         if (strlen(SMTP->from) != 0) {
335                 cprintf("503 Only one sender permitted\r\n");
336                 return;
337         }
338
339         if (strncasecmp(argbuf, "From:", 5)) {
340                 cprintf("501 Syntax error\r\n");
341                 return;
342         }
343
344         strcpy(SMTP->from, &argbuf[5]);
345         striplt(SMTP->from);
346
347         if (strlen(SMTP->from) == 0) {
348                 cprintf("501 Empty sender name is not permitted\r\n");
349                 return;
350         }
351
352
353         /* If this SMTP connection is from a logged-in user, make sure that
354          * the user only sends email from his/her own address.
355          */
356         if (CC->logged_in) {
357                 cvt = convert_internet_address(user, node, SMTP->from);
358                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
359                 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
360                         cprintf("550 <%s> is not your address.\r\n", SMTP->from);
361                         strcpy(SMTP->from, "");
362                         return;
363                 }
364                 else {
365                         SMTP->message_originated_locally = 1;
366                 }
367         }
368
369         /* Otherwise, make sure outsiders aren't trying to forge mail from
370          * this system.
371          */
372         else {
373                 TRACE;
374                 cvt = convert_internet_address(user, node, SMTP->from);
375                 TRACE;
376                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
377                 if (CtdlHostAlias(node) == hostalias_localhost) {
378                         TRACE;
379                         cprintf("550 You must log in to send mail from %s\r\n",
380                                 node);
381                         strcpy(SMTP->from, "");
382                         return;
383                 }
384         }
385
386         cprintf("250 Sender ok\r\n");
387 }
388
389
390
391 /*
392  * Implements the "RCPT To:" command
393  */
394 void smtp_rcpt(char *argbuf) {
395         int cvt;
396         char user[SIZ];
397         char node[SIZ];
398         char recp[SIZ];
399
400         if (strlen(SMTP->from) == 0) {
401                 cprintf("503 Need MAIL before RCPT\r\n");
402                 return;
403         }
404
405         if (strncasecmp(argbuf, "To:", 3)) {
406                 cprintf("501 Syntax error\r\n");
407                 return;
408         }
409
410         strcpy(recp, &argbuf[3]);
411         striplt(recp);
412         TRACE;
413         alias(recp);
414         TRACE;
415
416         cvt = convert_internet_address(user, node, recp);
417         snprintf(recp, sizeof recp, "%s@%s", user, node);
418         lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
419
420         switch(cvt) {
421                 case rfc822_address_locally_validated:
422                         cprintf("250 %s is a valid recipient.\r\n", user);
423                         ++SMTP->number_of_recipients;
424                         CtdlReallocUserData(SYM_SMTP_RECP,
425                                 strlen(SMTP_RECP) + 1024 );
426                         strcat(SMTP_RECP, "local|");
427                         strcat(SMTP_RECP, user);
428                         strcat(SMTP_RECP, "|0\n");
429                         return;
430
431                 case rfc822_room_delivery:
432                         cprintf("250 Delivering to room '%s'\r\n", user);
433                         ++SMTP->number_of_recipients;
434                         CtdlReallocUserData(SYM_SMTP_RECP,
435                                 strlen(SMTP_RECP) + 1024 );
436                         strcat(SMTP_RECP, "room|");
437                         strcat(SMTP_RECP, user);
438                         strcat(SMTP_RECP, "|0|\n");
439                         return;
440
441                 case rfc822_no_such_user:
442                         cprintf("550 %s: no such user\r\n", recp);
443                         return;
444
445                 case rfc822_address_on_citadel_network:
446                         cprintf("250 %s is on the local network\r\n", recp);
447                         ++SMTP->number_of_recipients;
448                         CtdlReallocUserData(SYM_SMTP_RECP,
449                                 strlen(SMTP_RECP) + 1024 );
450                         strcat(SMTP_RECP, "ignet|");
451                         strcat(SMTP_RECP, user);
452                         strcat(SMTP_RECP, "|");
453                         strcat(SMTP_RECP, node);
454                         strcat(SMTP_RECP, "|0|\n");
455                         return;
456
457                 case rfc822_address_nonlocal:
458                         if (SMTP->message_originated_locally == 0) {
459                                 cprintf("551 Third-party relaying denied.\r\n");
460                         }
461                         else {
462                                 cprintf("250 Remote recipient %s ok\r\n", recp);
463                                 ++SMTP->number_of_recipients;
464                                 CtdlReallocUserData(SYM_SMTP_RECP,
465                                         strlen(SMTP_RECP) + 1024 );
466                                 strcat(SMTP_RECP, "remote|");
467                                 strcat(SMTP_RECP, recp);
468                                 strcat(SMTP_RECP, "|0|\n");
469                                 return;
470                         }
471                         return;
472         }
473
474         cprintf("599 Unknown error\r\n");
475 }
476
477
478
479 /*
480  * Send a message out through the local network
481  * (This is kind of ugly.  IGnet should be done using clean server-to-server
482  * code instead of the old style spool.)
483  */
484 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
485         struct ser_ret smr;
486         char *hold_R, *hold_D, *hold_O;
487         FILE *fp;
488         char filename[SIZ];
489         static int seq = 0;
490
491         lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
492
493         hold_R = msg->cm_fields['R'];
494         hold_D = msg->cm_fields['D'];
495         hold_O = msg->cm_fields['O'];
496         msg->cm_fields['R'] = user;
497         msg->cm_fields['D'] = dest;
498         msg->cm_fields['O'] = MAILROOM;
499
500         serialize_message(&smr, msg);
501
502         msg->cm_fields['R'] = hold_R;
503         msg->cm_fields['D'] = hold_D;
504         msg->cm_fields['O'] = hold_O;
505
506         if (smr.len != 0) {
507                 snprintf(filename, sizeof filename,
508                         "./network/spoolin/%s.%04x.%04x",
509                         dest, getpid(), ++seq);
510                 lprintf(9, "spool file name is <%s>\n", filename);
511                 fp = fopen(filename, "wb");
512                 if (fp != NULL) {
513                         fwrite(smr.ser, smr.len, 1, fp);
514                         fclose(fp);
515                 }
516                 phree(smr.ser);
517         }
518
519 }
520
521
522
523 /*
524  * Back end for smtp_data()  ... this does the actual delivery of the message
525  * Returns 0 on success, nonzero on failure
526  */
527 int smtp_message_delivery(struct CtdlMessage *msg) {
528         char user[1024];
529         char node[1024];
530         char name[1024];
531         char buf[1024];
532         char dtype[1024];
533         char room[1024];
534         int successful_saves = 0;       /* number of successful local saves */
535         int failed_saves = 0;           /* number of failed deliveries */
536         int remote_spools = 0;          /* number of copies to send out */
537         long msgid = (-1L);
538         int i;
539         struct usersupp userbuf;
540         char *instr;                    /* Remote delivery instructions */
541         struct CtdlMessage *imsg;
542
543         lprintf(9, "smtp_message_delivery() called\n");
544
545         /* Fill in 'from' fields with envelope information if missing */
546         process_rfc822_addr(SMTP->from, user, node, name);
547         if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
548         if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
549         if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
550         if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
551
552         /* Save the message in the queue */
553         msgid = CtdlSaveMsg(msg,
554                 "",
555                 SMTP_SPOOLOUT_ROOM,
556                 MES_LOCAL);
557         ++successful_saves;
558
559         instr = mallok(1024);
560         snprintf(instr, 1024,
561                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
562                         "bounceto|%s\n",
563                 SPOOLMIME, msgid, (long)time(NULL),
564                 SMTP->from );
565
566         for (i=0; i<SMTP->number_of_recipients; ++i) {
567                 extract_token(buf, SMTP_RECP, i, '\n');
568                 extract(dtype, buf, 0);
569
570                 /* Stuff local mailboxes */
571                 if (!strcasecmp(dtype, "local")) {
572                         extract(user, buf, 1);
573                         if (getuser(&userbuf, user) == 0) {
574                                 MailboxName(room, &userbuf, MAILROOM);
575                                 CtdlSaveMsgPointerInRoom(room, msgid, 0);
576                                 ++successful_saves;
577                         }
578                         else {
579                                 ++failed_saves;
580                         }
581                 }
582
583                 /* Delivery to local non-mailbox rooms */
584                 if (!strcasecmp(dtype, "room")) {
585                         extract(room, buf, 1);
586                         CtdlSaveMsgPointerInRoom(room, msgid, 0);
587                         ++successful_saves;
588                 }
589
590                 /* Delivery over the local Citadel network (IGnet) */
591                 if (!strcasecmp(dtype, "ignet")) {
592                         extract(user, buf, 1);
593                         extract(node, buf, 2);
594                         smtp_deliver_ignet(msg, user, node);
595                 }
596
597                 /* Remote delivery */
598                 if (!strcasecmp(dtype, "remote")) {
599                         extract(user, buf, 1);
600                         instr = reallok(instr, strlen(instr) + 1024);
601                         snprintf(&instr[strlen(instr)],
602                                 strlen(instr) + 1024,
603                                 "remote|%s|0\n",
604                                 user);
605                         ++remote_spools;
606                 }
607
608         }
609
610         /* If there are remote spools to be done, save the instructions */
611         if (remote_spools > 0) {
612                 imsg = mallok(sizeof(struct CtdlMessage));
613                 memset(imsg, 0, sizeof(struct CtdlMessage));
614                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
615                 imsg->cm_anon_type = MES_NORMAL;
616                 imsg->cm_format_type = FMT_RFC822;
617                 imsg->cm_fields['M'] = instr;
618                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
619                 CtdlFreeMessage(imsg);
620         }
621
622         /* If there are no remote spools, delete the message */ 
623         else {
624                 phree(instr);   /* only needed here, because CtdlSaveMsg()
625                                  * would free this buffer otherwise */
626                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, ""); 
627         }
628
629         return(failed_saves);
630 }
631
632
633
634 /*
635  * Implements the DATA command
636  */
637 void smtp_data(void) {
638         char *body;
639         struct CtdlMessage *msg;
640         int retval;
641         char nowstamp[SIZ];
642
643         if (strlen(SMTP->from) == 0) {
644                 cprintf("503 Need MAIL command first.\r\n");
645                 return;
646         }
647
648         if (SMTP->number_of_recipients < 1) {
649                 cprintf("503 Need RCPT command first.\r\n");
650                 return;
651         }
652
653         cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
654         
655         datestring(nowstamp, time(NULL), DATESTRING_RFC822);
656         body = mallok(4096);
657
658         if (body != NULL) snprintf(body, 4096,
659                 "Received: from %s\n"
660                 "       by %s;\n"
661                 "       %s\n",
662                         SMTP->helo_node,
663                         config.c_fqdn,
664                         nowstamp);
665         
666         body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
667         if (body == NULL) {
668                 cprintf("550 Unable to save message text: internal error.\r\n");
669                 return;
670         }
671
672         lprintf(9, "Converting message...\n");
673         msg = convert_internet_message(body);
674
675         /* If the user is locally authenticated, FORCE the From: header to
676          * show up as the real sender
677          */
678         if (CC->logged_in) {
679                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
680                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
681                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
682                 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
683                 msg->cm_fields['N'] = strdoop(config.c_nodename);
684                 msg->cm_fields['H'] = strdoop(config.c_humannode);
685         }
686
687         retval = smtp_message_delivery(msg);
688         CtdlFreeMessage(msg);
689
690         if (!retval) {
691                 cprintf("250 ok terrific\r\n");
692         }
693         else {
694                 cprintf("550 Internal delivery errors: %d\r\n", retval);
695         }
696
697         smtp_data_clear();      /* clear out the buffers now */
698 }
699
700
701
702
703 /* 
704  * Main command loop for SMTP sessions.
705  */
706 void smtp_command_loop(void) {
707         char cmdbuf[SIZ];
708
709         time(&CC->lastcmd);
710         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
711         if (client_gets(cmdbuf) < 1) {
712                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
713                 CC->kill_me = 1;
714                 return;
715         }
716         lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
717         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
718
719         if (SMTP->command_state == smtp_user) {
720                 smtp_get_user(cmdbuf);
721         }
722
723         else if (SMTP->command_state == smtp_password) {
724                 smtp_get_pass(cmdbuf);
725         }
726
727         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
728                 smtp_auth(&cmdbuf[5]);
729         }
730
731         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
732                 smtp_data();
733         }
734
735         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
736                 smtp_hello(&cmdbuf[5], 1);
737         }
738
739         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
740                 smtp_expn(&cmdbuf[5]);
741         }
742
743         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
744                 smtp_hello(&cmdbuf[5], 0);
745         }
746
747         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
748                 smtp_help();
749         }
750
751         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
752                 smtp_mail(&cmdbuf[5]);
753         }
754
755         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
756                 cprintf("250 NOOP\r\n");
757         }
758
759         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
760                 cprintf("221 Goodbye...\r\n");
761                 CC->kill_me = 1;
762                 return;
763         }
764
765         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
766                 smtp_rcpt(&cmdbuf[5]);
767         }
768
769         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
770                 smtp_rset();
771         }
772
773         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
774                 smtp_vrfy(&cmdbuf[5]);
775         }
776
777         else {
778                 cprintf("502 I'm afraid I can't do that.\r\n");
779         }
780
781 }
782
783
784
785
786 /*****************************************************************************/
787 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
788 /*****************************************************************************/
789
790
791
792 /*
793  * smtp_try()
794  *
795  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
796  *
797  */
798 void smtp_try(char *key, char *addr, int *status, char *dsn, 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                 sprintf(dsn, "Error creating temporary file");
825                 return;
826         }
827         else {
828                 CtdlRedirectOutput(msg_fp, -1);
829                 CtdlOutputMsg(msgnum, MT_RFC822, 0, 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
881         /* Figure out what mail exchanger host we have to connect to */
882         num_mxhosts = getmx(mxhosts, node);
883         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
884         if (num_mxhosts < 1) {
885                 *status = 5;
886                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
887                 return;
888         }
889
890         for (mx=0; mx<num_mxhosts; ++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                 if (sock >= 0) break;
898         }
899
900         if (sock < 0) {
901                 *status = 4;    /* dsn is already filled in */
902                 return;
903         }
904
905         /* Process the SMTP greeting from the server */
906         if (ml_sock_gets(sock, buf) < 0) {
907                 *status = 4;
908                 strcpy(dsn, "Connection broken during SMTP conversation");
909                 goto bail;
910         }
911         lprintf(9, "<%s\n", buf);
912         if (buf[0] != '2') {
913                 if (buf[0] == '4') {
914                         *status = 4;
915                         safestrncpy(dsn, &buf[4], 1023);
916                         goto bail;
917                 }
918                 else {
919                         *status = 5;
920                         safestrncpy(dsn, &buf[4], 1023);
921                         goto bail;
922                 }
923         }
924
925         /* At this point we know we are talking to a real SMTP server */
926
927         /* Do a HELO command */
928         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
929         lprintf(9, ">%s", buf);
930         sock_write(sock, buf, strlen(buf));
931         if (ml_sock_gets(sock, buf) < 0) {
932                 *status = 4;
933                 strcpy(dsn, "Connection broken during SMTP HELO");
934                 goto bail;
935         }
936         lprintf(9, "<%s\n", buf);
937         if (buf[0] != '2') {
938                 if (buf[0] == '4') {
939                         *status = 4;
940                         safestrncpy(dsn, &buf[4], 1023);
941                         goto bail;
942                 }
943                 else {
944                         *status = 5;
945                         safestrncpy(dsn, &buf[4], 1023);
946                         goto bail;
947                 }
948         }
949
950
951         /* HELO succeeded, now try the MAIL From: command */
952         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
953         lprintf(9, ">%s", buf);
954         sock_write(sock, buf, strlen(buf));
955         if (ml_sock_gets(sock, buf) < 0) {
956                 *status = 4;
957                 strcpy(dsn, "Connection broken during SMTP MAIL");
958                 goto bail;
959         }
960         lprintf(9, "<%s\n", buf);
961         if (buf[0] != '2') {
962                 if (buf[0] == '4') {
963                         *status = 4;
964                         safestrncpy(dsn, &buf[4], 1023);
965                         goto bail;
966                 }
967                 else {
968                         *status = 5;
969                         safestrncpy(dsn, &buf[4], 1023);
970                         goto bail;
971                 }
972         }
973
974
975         /* MAIL succeeded, now try the RCPT To: command */
976         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
977         lprintf(9, ">%s", buf);
978         sock_write(sock, buf, strlen(buf));
979         if (ml_sock_gets(sock, buf) < 0) {
980                 *status = 4;
981                 strcpy(dsn, "Connection broken during SMTP RCPT");
982                 goto bail;
983         }
984         lprintf(9, "<%s\n", buf);
985         if (buf[0] != '2') {
986                 if (buf[0] == '4') {
987                         *status = 4;
988                         safestrncpy(dsn, &buf[4], 1023);
989                         goto bail;
990                 }
991                 else {
992                         *status = 5;
993                         safestrncpy(dsn, &buf[4], 1023);
994                         goto bail;
995                 }
996         }
997
998
999         /* RCPT succeeded, now try the DATA command */
1000         lprintf(9, ">DATA\n");
1001         sock_write(sock, "DATA\r\n", 6);
1002         if (ml_sock_gets(sock, buf) < 0) {
1003                 *status = 4;
1004                 strcpy(dsn, "Connection broken during SMTP DATA");
1005                 goto bail;
1006         }
1007         lprintf(9, "<%s\n", buf);
1008         if (buf[0] != '3') {
1009                 if (buf[0] == '4') {
1010                         *status = 3;
1011                         safestrncpy(dsn, &buf[4], 1023);
1012                         goto bail;
1013                 }
1014                 else {
1015                         *status = 5;
1016                         safestrncpy(dsn, &buf[4], 1023);
1017                         goto bail;
1018                 }
1019         }
1020
1021         /* If we reach this point, the server is expecting data */
1022         rewind(msg_fp);
1023         while (msg_size > 0) {
1024                 blocksize = sizeof(buf);
1025                 if (blocksize > msg_size) blocksize = msg_size;
1026                 fread(buf, blocksize, 1, msg_fp);
1027                 sock_write(sock, buf, blocksize);
1028                 msg_size -= blocksize;
1029         }
1030         if (buf[blocksize-1] != 10) {
1031                 lprintf(5, "Possible problem: message did not correctly "
1032                         "terminate. (expecting 0x10, got 0x%02x)\n",
1033                                 buf[blocksize-1]);
1034         }
1035
1036         sock_write(sock, ".\r\n", 3);
1037         if (ml_sock_gets(sock, buf) < 0) {
1038                 *status = 4;
1039                 strcpy(dsn, "Connection broken during SMTP message transmit");
1040                 goto bail;
1041         }
1042         lprintf(9, "%s\n", buf);
1043         if (buf[0] != '2') {
1044                 if (buf[0] == '4') {
1045                         *status = 4;
1046                         safestrncpy(dsn, &buf[4], 1023);
1047                         goto bail;
1048                 }
1049                 else {
1050                         *status = 5;
1051                         safestrncpy(dsn, &buf[4], 1023);
1052                         goto bail;
1053                 }
1054         }
1055
1056         /* We did it! */
1057         safestrncpy(dsn, &buf[4], 1023);
1058         *status = 2;
1059
1060         lprintf(9, ">QUIT\n");
1061         sock_write(sock, "QUIT\r\n", 6);
1062         ml_sock_gets(sock, buf);
1063         lprintf(9, "<%s\n", buf);
1064
1065 bail:   if (msg_fp != NULL) fclose(msg_fp);
1066         sock_close(sock);
1067         return;
1068 }
1069
1070
1071
1072 /*
1073  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1074  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1075  * a "bounce" message (delivery status notification).
1076  */
1077 void smtp_do_bounce(char *instr) {
1078         int i;
1079         int lines;
1080         int status;
1081         char buf[1024];
1082         char key[1024];
1083         char addr[1024];
1084         char dsn[1024];
1085         char bounceto[1024];
1086         int num_bounces = 0;
1087         int bounce_this = 0;
1088         long bounce_msgid = (-1);
1089         time_t submitted = 0L;
1090         struct CtdlMessage *bmsg = NULL;
1091         int give_up = 0;
1092         int mes_type = 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
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                 status = extract_int(buf, 2);
1147                 extract(dsn, buf, 3);
1148                 bounce_this = 0;
1149
1150                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1151                         key, addr, status, dsn);
1152
1153                 if (!strcasecmp(key, "bounceto")) {
1154                         strcpy(bounceto, addr);
1155                 }
1156
1157                 if (
1158                    (!strcasecmp(key, "local"))
1159                    || (!strcasecmp(key, "remote"))
1160                    || (!strcasecmp(key, "ignet"))
1161                    || (!strcasecmp(key, "room"))
1162                 ) {
1163                         if (status == 5) bounce_this = 1;
1164                         if (give_up) bounce_this = 1;
1165                 }
1166
1167                 if (bounce_this) {
1168                         ++num_bounces;
1169
1170                         if (bmsg->cm_fields['M'] == NULL) {
1171                                 lprintf(2, "ERROR ... M field is null "
1172                                         "(%s:%d)\n", __FILE__, __LINE__);
1173                         }
1174
1175                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1176                                 strlen(bmsg->cm_fields['M']) + 1024 );
1177                         strcat(bmsg->cm_fields['M'], addr);
1178                         strcat(bmsg->cm_fields['M'], ": ");
1179                         strcat(bmsg->cm_fields['M'], dsn);
1180                         strcat(bmsg->cm_fields['M'], "\n");
1181
1182                         remove_token(instr, i, '\n');
1183                         --i;
1184                         --lines;
1185                 }
1186         }
1187
1188         /* Deliver the bounce if there's anything worth mentioning */
1189         lprintf(9, "num_bounces = %d\n", num_bounces);
1190         if (num_bounces > 0) {
1191
1192                 /* First try the user who sent the message */
1193                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1194                 TRACE;
1195                 if (strlen(bounceto) == 0) {
1196                         lprintf(7, "No bounce address specified\n");
1197                         bounce_msgid = (-1L);
1198                 }
1199                 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1200                         lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1201                         bounce_msgid = (-1L);
1202                 }
1203                 else {
1204                         bounce_msgid = CtdlSaveMsg(bmsg,
1205                                 bounceto,
1206                                 "", mes_type);
1207                 }
1208                 TRACE;
1209
1210                 /* Otherwise, go to the Aide> room */
1211                 lprintf(9, "bounce to room?\n");
1212                 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1213                         "", AIDEROOM,
1214                         MES_LOCAL);
1215         }
1216
1217         CtdlFreeMessage(bmsg);
1218         lprintf(9, "Done processing bounces\n");
1219 }
1220
1221
1222 /*
1223  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1224  * set of delivery instructions for completed deliveries and remove them.
1225  *
1226  * It returns the number of incomplete deliveries remaining.
1227  */
1228 int smtp_purge_completed_deliveries(char *instr) {
1229         int i;
1230         int lines;
1231         int status;
1232         char buf[1024];
1233         char key[1024];
1234         char addr[1024];
1235         char dsn[1024];
1236         int completed;
1237         int incomplete = 0;
1238
1239         lines = num_tokens(instr, '\n');
1240         for (i=0; i<lines; ++i) {
1241                 extract_token(buf, instr, i, '\n');
1242                 extract(key, buf, 0);
1243                 extract(addr, buf, 1);
1244                 status = extract_int(buf, 2);
1245                 extract(dsn, buf, 3);
1246
1247                 completed = 0;
1248
1249                 if (
1250                    (!strcasecmp(key, "local"))
1251                    || (!strcasecmp(key, "remote"))
1252                    || (!strcasecmp(key, "ignet"))
1253                    || (!strcasecmp(key, "room"))
1254                 ) {
1255                         if (status == 2) completed = 1;
1256                         else ++incomplete;
1257                 }
1258
1259                 if (completed) {
1260                         remove_token(instr, i, '\n');
1261                         --i;
1262                         --lines;
1263                 }
1264         }
1265
1266         return(incomplete);
1267 }
1268
1269
1270 /*
1271  * smtp_do_procmsg()
1272  *
1273  * Called by smtp_do_queue() to handle an individual message.
1274  */
1275 void smtp_do_procmsg(long msgnum, void *userdata) {
1276         struct CtdlMessage *msg;
1277         char *instr = NULL;
1278         char *results = NULL;
1279         int i;
1280         int lines;
1281         int status;
1282         char buf[1024];
1283         char key[1024];
1284         char addr[1024];
1285         char dsn[1024];
1286         long text_msgid = (-1);
1287         int incomplete_deliveries_remaining;
1288         time_t attempted = 0L;
1289         time_t last_attempted = 0L;
1290         time_t retry = SMTP_RETRY_INTERVAL;
1291
1292         msg = CtdlFetchMessage(msgnum);
1293         if (msg == NULL) {
1294                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1295                 return;
1296         }
1297
1298         instr = strdoop(msg->cm_fields['M']);
1299         CtdlFreeMessage(msg);
1300
1301         /* Strip out the headers amd any other non-instruction line */
1302         lines = num_tokens(instr, '\n');
1303         for (i=0; i<lines; ++i) {
1304                 extract_token(buf, instr, i, '\n');
1305                 if (num_tokens(buf, '|') < 2) {
1306                         lprintf(9, "removing <%s>\n", buf);
1307                         remove_token(instr, i, '\n');
1308                         --lines;
1309                         --i;
1310                 }
1311         }
1312
1313         /* Learn the message ID and find out about recent delivery attempts */
1314         lines = num_tokens(instr, '\n');
1315         for (i=0; i<lines; ++i) {
1316                 extract_token(buf, instr, i, '\n');
1317                 extract(key, buf, 0);
1318                 if (!strcasecmp(key, "msgid")) {
1319                         text_msgid = extract_long(buf, 1);
1320                 }
1321                 if (!strcasecmp(key, "retry")) {
1322                         /* double the retry interval after each attempt */
1323                         retry = extract_long(buf, 1) * 2L;
1324                         if (retry > SMTP_RETRY_MAX) {
1325                                 retry = SMTP_RETRY_MAX;
1326                         }
1327                         remove_token(instr, i, '\n');
1328                 }
1329                 if (!strcasecmp(key, "attempted")) {
1330                         attempted = extract_long(buf, 1);
1331                         if (attempted > last_attempted)
1332                                 last_attempted = attempted;
1333                 }
1334         }
1335
1336         /*
1337          * Postpone delivery if we've already tried recently.
1338          */
1339         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1340                 lprintf(7, "Retry time not yet reached.\n");
1341                 phree(instr);
1342                 return;
1343         }
1344
1345
1346         /*
1347          * Bail out if there's no actual message associated with this
1348          */
1349         if (text_msgid < 0L) {
1350                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1351                 phree(instr);
1352                 return;
1353         }
1354
1355         /* Plow through the instructions looking for 'remote' directives and
1356          * a status of 0 (no delivery yet attempted) or 3 (transient errors
1357          * were experienced and it's time to try again)
1358          */
1359         lines = num_tokens(instr, '\n');
1360         for (i=0; i<lines; ++i) {
1361                 extract_token(buf, instr, i, '\n');
1362                 extract(key, buf, 0);
1363                 extract(addr, buf, 1);
1364                 status = extract_int(buf, 2);
1365                 extract(dsn, buf, 3);
1366                 if ( (!strcasecmp(key, "remote"))
1367                    && ((status==0)||(status==3)) ) {
1368                         remove_token(instr, i, '\n');
1369                         --i;
1370                         --lines;
1371                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1372                         smtp_try(key, addr, &status, dsn, text_msgid);
1373                         if (status != 2) {
1374                                 if (results == NULL) {
1375                                         results = mallok(1024);
1376                                         memset(results, 0, 1024);
1377                                 }
1378                                 else {
1379                                         results = reallok(results,
1380                                                 strlen(results) + 1024);
1381                                 }
1382                                 sprintf(&results[strlen(results)],
1383                                         "%s|%s|%d|%s\n",
1384                                         key, addr, status, dsn);
1385                         }
1386                 }
1387         }
1388
1389         if (results != NULL) {
1390                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1391                 strcat(instr, results);
1392                 phree(results);
1393         }
1394
1395
1396         /* Generate 'bounce' messages */
1397         smtp_do_bounce(instr);
1398
1399         /* Go through the delivery list, deleting completed deliveries */
1400         incomplete_deliveries_remaining = 
1401                 smtp_purge_completed_deliveries(instr);
1402
1403
1404         /*
1405          * No delivery instructions remain, so delete both the instructions
1406          * message and the message message.
1407          */
1408         if (incomplete_deliveries_remaining <= 0) {
1409                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1410                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1411         }
1412
1413
1414         /*
1415          * Uncompleted delivery instructions remain, so delete the old
1416          * instructions and replace with the updated ones.
1417          */
1418         if (incomplete_deliveries_remaining > 0) {
1419                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1420                 msg = mallok(sizeof(struct CtdlMessage));
1421                 memset(msg, 0, sizeof(struct CtdlMessage));
1422                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1423                 msg->cm_anon_type = MES_NORMAL;
1424                 msg->cm_format_type = FMT_RFC822;
1425                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1426                 snprintf(msg->cm_fields['M'],
1427                         strlen(instr)+SIZ,
1428                         "Content-type: %s\n\n%s\n"
1429                         "attempted|%ld\n"
1430                         "retry|%ld\n",
1431                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1432                 phree(instr);
1433                 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1434                 CtdlFreeMessage(msg);
1435         }
1436
1437 }
1438
1439
1440
1441 /*
1442  * smtp_do_queue()
1443  * 
1444  * Run through the queue sending out messages.
1445  */
1446 void smtp_do_queue(void) {
1447         static int doing_queue = 0;
1448
1449         /*
1450          * This is a simple concurrency check to make sure only one queue run
1451          * is done at a time.  We could do this with a mutex, but since we
1452          * don't really require extremely fine granularity here, we'll do it
1453          * with a static variable instead.
1454          */
1455         if (doing_queue) return;
1456         doing_queue = 1;
1457
1458         /* 
1459          * Go ahead and run the queue
1460          */
1461         lprintf(7, "SMTP: processing outbound queue\n");
1462
1463         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1464                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1465                 return;
1466         }
1467         CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1468                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1469
1470         lprintf(7, "SMTP: queue run completed\n");
1471         run_queue_now = 0;
1472         doing_queue = 0;
1473 }
1474
1475
1476
1477 /*****************************************************************************/
1478 /*                          SMTP UTILITY COMMANDS                            */
1479 /*****************************************************************************/
1480
1481 void cmd_smtp(char *argbuf) {
1482         char cmd[SIZ];
1483         char node[SIZ];
1484         char buf[SIZ];
1485         int i;
1486         int num_mxhosts;
1487
1488         if (CtdlAccessCheck(ac_aide)) return;
1489
1490         extract(cmd, argbuf, 0);
1491
1492         if (!strcasecmp(cmd, "mx")) {
1493                 extract(node, argbuf, 1);
1494                 num_mxhosts = getmx(buf, node);
1495                 cprintf("%d %d MX hosts listed for %s\n",
1496                         LISTING_FOLLOWS, num_mxhosts, node);
1497                 for (i=0; i<num_mxhosts; ++i) {
1498                         extract(node, buf, i);
1499                         cprintf("%s\n", node);
1500                 }
1501                 cprintf("000\n");
1502                 return;
1503         }
1504
1505         else if (!strcasecmp(cmd, "runqueue")) {
1506                 run_queue_now = 1;
1507                 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1508                 return;
1509         }
1510
1511         else {
1512                 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1513         }
1514
1515 }
1516
1517
1518
1519
1520 /*****************************************************************************/
1521 /*                      MODULE INITIALIZATION STUFF                          */
1522 /*****************************************************************************/
1523
1524
1525 char *Dynamic_Module_Init(void)
1526 {
1527         SYM_SMTP = CtdlGetDynamicSymbol();
1528         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1529
1530         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1531                                 NULL,
1532                                 smtp_greeting,
1533                                 smtp_command_loop);
1534
1535         CtdlRegisterServiceHook(0,                      /* ...and locally */
1536                                 "smtp.socket",
1537                                 smtp_greeting,
1538                                 smtp_command_loop);
1539
1540         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1541         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1542         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1543         return "$Id$";
1544 }