]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* CtdlSaveMsg() is now CtdlSubmitMsg() and can accept any combination of
[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-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, SIZ);
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, SIZ);
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                 cvt = convert_internet_address(user, node, SMTP->from);
374                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
375                 if (CtdlHostAlias(node) == hostalias_localhost) {
376                         cprintf("550 You must log in to send mail from %s\r\n",
377                                 node);
378                         strcpy(SMTP->from, "");
379                         return;
380                 }
381         }
382
383         cprintf("250 Sender ok\r\n");
384 }
385
386
387
388 /*
389  * Implements the "RCPT To:" command
390  */
391 void smtp_rcpt(char *argbuf) {
392         int cvt;
393         char user[SIZ];
394         char node[SIZ];
395         char recp[SIZ];
396
397         if (strlen(SMTP->from) == 0) {
398                 cprintf("503 Need MAIL before RCPT\r\n");
399                 return;
400         }
401
402         if (strncasecmp(argbuf, "To:", 3)) {
403                 cprintf("501 Syntax error\r\n");
404                 return;
405         }
406
407         strcpy(recp, &argbuf[3]);
408         striplt(recp);
409         alias(recp);
410
411         cvt = convert_internet_address(user, node, recp);
412         snprintf(recp, sizeof recp, "%s@%s", user, node);
413         lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
414
415         switch(cvt) {
416                 case rfc822_address_locally_validated:
417                         cprintf("250 %s is a valid recipient.\r\n", user);
418                         ++SMTP->number_of_recipients;
419                         CtdlReallocUserData(SYM_SMTP_RECP,
420                                 strlen(SMTP_RECP) + 1024 );
421                         strcat(SMTP_RECP, "local|");
422                         strcat(SMTP_RECP, user);
423                         strcat(SMTP_RECP, "|0\n");
424                         return;
425
426                 case rfc822_room_delivery:
427                         cprintf("250 Delivering to room '%s'\r\n", user);
428                         ++SMTP->number_of_recipients;
429                         CtdlReallocUserData(SYM_SMTP_RECP,
430                                 strlen(SMTP_RECP) + 1024 );
431                         strcat(SMTP_RECP, "room|");
432                         strcat(SMTP_RECP, user);
433                         strcat(SMTP_RECP, "|0|\n");
434                         return;
435
436                 case rfc822_no_such_user:
437                         cprintf("550 %s: no such user\r\n", recp);
438                         return;
439
440                 case rfc822_address_on_citadel_network:
441                         cprintf("250 %s is on the local network\r\n", recp);
442                         ++SMTP->number_of_recipients;
443                         CtdlReallocUserData(SYM_SMTP_RECP,
444                                 strlen(SMTP_RECP) + 1024 );
445                         strcat(SMTP_RECP, "ignet|");
446                         strcat(SMTP_RECP, user);
447                         strcat(SMTP_RECP, "|");
448                         strcat(SMTP_RECP, node);
449                         strcat(SMTP_RECP, "|0|\n");
450                         return;
451
452                 case rfc822_address_nonlocal:
453                         if (SMTP->message_originated_locally == 0) {
454                                 cprintf("551 Relaying denied.\r\n");
455                         }
456                         else {
457                                 cprintf("250 Remote recipient %s ok\r\n", recp);
458                                 ++SMTP->number_of_recipients;
459                                 CtdlReallocUserData(SYM_SMTP_RECP,
460                                         strlen(SMTP_RECP) + 1024 );
461                                 strcat(SMTP_RECP, "remote|");
462                                 strcat(SMTP_RECP, recp);
463                                 strcat(SMTP_RECP, "|0|\n");
464                                 return;
465                         }
466                         return;
467         }
468
469         cprintf("599 Unknown error\r\n");
470 }
471
472
473
474 /*
475  * Send a message out through the local network
476  * (This is kind of ugly.  IGnet should be done using clean server-to-server
477  * code instead of the old style spool.)
478  */
479 void smtp_deliver_ignet(struct CtdlMessage *msg, char *user, char *dest) {
480         struct ser_ret smr;
481         char *hold_R, *hold_D, *hold_O;
482         FILE *fp;
483         char filename[SIZ];
484         static int seq = 0;
485
486         lprintf(9, "smtp_deliver_ignet(msg, %s, %s)\n", user, dest);
487
488         hold_R = msg->cm_fields['R'];
489         hold_D = msg->cm_fields['D'];
490         hold_O = msg->cm_fields['O'];
491         msg->cm_fields['R'] = user;
492         msg->cm_fields['D'] = dest;
493         msg->cm_fields['O'] = MAILROOM;
494
495         serialize_message(&smr, msg);
496
497         msg->cm_fields['R'] = hold_R;
498         msg->cm_fields['D'] = hold_D;
499         msg->cm_fields['O'] = hold_O;
500
501         if (smr.len != 0) {
502                 snprintf(filename, sizeof filename,
503                         "./network/spoolin/%s.%04x.%04x",
504                         dest, getpid(), ++seq);
505                 lprintf(9, "spool file name is <%s>\n", filename);
506                 fp = fopen(filename, "wb");
507                 if (fp != NULL) {
508                         fwrite(smr.ser, smr.len, 1, fp);
509                         fclose(fp);
510                 }
511                 phree(smr.ser);
512         }
513
514 }
515
516
517
518 /*
519  * Back end for smtp_data()  ... this does the actual delivery of the message
520  * Returns 0 on success, nonzero on failure
521  */
522 int smtp_message_delivery(struct CtdlMessage *msg) {
523         char user[1024];
524         char node[1024];
525         char name[1024];
526         char buf[1024];
527         char dtype[1024];
528         char room[1024];
529         int successful_saves = 0;       /* number of successful local saves */
530         int failed_saves = 0;           /* number of failed deliveries */
531         int remote_spools = 0;          /* number of copies to send out */
532         long msgid = (-1L);
533         int i;
534         struct usersupp userbuf;
535         char *instr;                    /* Remote delivery instructions */
536         struct CtdlMessage *imsg;
537
538         lprintf(9, "smtp_message_delivery() called\n");
539
540         /* Fill in 'from' fields with envelope information if missing */
541         process_rfc822_addr(SMTP->from, user, node, name);
542         if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
543         if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
544         if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
545         if (msg->cm_fields['O']==NULL) msg->cm_fields['O'] = strdoop(MAILROOM);
546
547         /* Save the message in the queue */
548         msgid = CtdlSubmitMsg(msg,
549                 NULL,
550                 SMTP_SPOOLOUT_ROOM);
551         ++successful_saves;
552
553         instr = mallok(1024);
554         snprintf(instr, 1024,
555                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
556                         "bounceto|%s\n",
557                 SPOOLMIME, msgid, (long)time(NULL),
558                 SMTP->from );
559
560         for (i=0; i<SMTP->number_of_recipients; ++i) {
561                 extract_token(buf, SMTP_RECP, i, '\n');
562                 extract(dtype, buf, 0);
563
564                 /* Stuff local mailboxes */
565                 if (!strcasecmp(dtype, "local")) {
566                         extract(user, buf, 1);
567                         if (getuser(&userbuf, user) == 0) {
568                                 MailboxName(room, &userbuf, MAILROOM);
569                                 CtdlSaveMsgPointerInRoom(room, msgid, 0);
570                                 ++successful_saves;
571                         }
572                         else {
573                                 ++failed_saves;
574                         }
575                 }
576
577                 /* Delivery to local non-mailbox rooms */
578                 if (!strcasecmp(dtype, "room")) {
579                         extract(room, buf, 1);
580                         CtdlSaveMsgPointerInRoom(room, msgid, 0);
581                         ++successful_saves;
582                 }
583
584                 /* Delivery over the local Citadel network (IGnet) */
585                 if (!strcasecmp(dtype, "ignet")) {
586                         extract(user, buf, 1);
587                         extract(node, buf, 2);
588                         smtp_deliver_ignet(msg, user, node);
589                 }
590
591                 /* Remote delivery */
592                 if (!strcasecmp(dtype, "remote")) {
593                         extract(user, buf, 1);
594                         instr = reallok(instr, strlen(instr) + 1024);
595                         snprintf(&instr[strlen(instr)],
596                                 strlen(instr) + 1024,
597                                 "remote|%s|0\n",
598                                 user);
599                         ++remote_spools;
600                 }
601
602         }
603
604         /* If there are remote spools to be done, save the instructions */
605         if (remote_spools > 0) {
606                 imsg = mallok(sizeof(struct CtdlMessage));
607                 memset(imsg, 0, sizeof(struct CtdlMessage));
608                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
609                 imsg->cm_anon_type = MES_NORMAL;
610                 imsg->cm_format_type = FMT_RFC822;
611                 imsg->cm_fields['M'] = instr;
612                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
613                 CtdlFreeMessage(imsg);
614         }
615
616         /* If there are no remote spools, delete the message */ 
617         else {
618                 phree(instr);   /* only needed here, because CtdlSubmitMsg()
619                                  * would free this buffer otherwise */
620                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, ""); 
621         }
622
623         return(failed_saves);
624 }
625
626
627
628 /*
629  * Implements the DATA command
630  */
631 void smtp_data(void) {
632         char *body;
633         struct CtdlMessage *msg;
634         int retval;
635         char nowstamp[SIZ];
636
637         if (strlen(SMTP->from) == 0) {
638                 cprintf("503 Need MAIL command first.\r\n");
639                 return;
640         }
641
642         if (SMTP->number_of_recipients < 1) {
643                 cprintf("503 Need RCPT command first.\r\n");
644                 return;
645         }
646
647         cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
648         
649         datestring(nowstamp, time(NULL), DATESTRING_RFC822);
650         body = mallok(4096);
651
652         if (body != NULL) snprintf(body, 4096,
653                 "Received: from %s\n"
654                 "       by %s;\n"
655                 "       %s\n",
656                         SMTP->helo_node,
657                         config.c_fqdn,
658                         nowstamp);
659         
660         body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
661         if (body == NULL) {
662                 cprintf("550 Unable to save message: internal error.\r\n");
663                 return;
664         }
665
666         lprintf(9, "Converting message...\n");
667         msg = convert_internet_message(body);
668
669         /* If the user is locally authenticated, FORCE the From: header to
670          * show up as the real sender.  Yes, this violates the RFC standard,
671          * but IT MAKES SENSE.  Comment it out if you don't like this behavior.
672          */
673         if (CC->logged_in) {
674                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
675                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
676                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
677                 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
678                 msg->cm_fields['N'] = strdoop(config.c_nodename);
679                 msg->cm_fields['H'] = strdoop(config.c_humannode);
680         }
681
682         /* Submit the message into the Citadel system. */
683         retval = smtp_message_delivery(msg);
684         CtdlFreeMessage(msg);
685
686         if (!retval) {
687                 cprintf("250 Message accepted.\r\n");
688         }
689         else {
690                 cprintf("550 Internal delivery errors: %d\r\n", retval);
691         }
692
693         smtp_data_clear();      /* clear out the buffers now */
694 }
695
696
697
698
699 /* 
700  * Main command loop for SMTP sessions.
701  */
702 void smtp_command_loop(void) {
703         char cmdbuf[SIZ];
704
705         time(&CC->lastcmd);
706         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
707         if (client_gets(cmdbuf) < 1) {
708                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
709                 CC->kill_me = 1;
710                 return;
711         }
712         lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
713         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
714
715         if (SMTP->command_state == smtp_user) {
716                 smtp_get_user(cmdbuf);
717         }
718
719         else if (SMTP->command_state == smtp_password) {
720                 smtp_get_pass(cmdbuf);
721         }
722
723         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
724                 smtp_auth(&cmdbuf[5]);
725         }
726
727         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
728                 smtp_data();
729         }
730
731         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
732                 smtp_hello(&cmdbuf[5], 1);
733         }
734
735         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
736                 smtp_expn(&cmdbuf[5]);
737         }
738
739         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
740                 smtp_hello(&cmdbuf[5], 0);
741         }
742
743         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
744                 smtp_help();
745         }
746
747         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
748                 smtp_mail(&cmdbuf[5]);
749         }
750
751         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
752                 cprintf("250 NOOP\r\n");
753         }
754
755         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
756                 cprintf("221 Goodbye...\r\n");
757                 CC->kill_me = 1;
758                 return;
759         }
760
761         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
762                 smtp_rcpt(&cmdbuf[5]);
763         }
764
765         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
766                 smtp_rset();
767         }
768
769         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
770                 smtp_vrfy(&cmdbuf[5]);
771         }
772
773         else {
774                 cprintf("502 I'm afraid I can't do that.\r\n");
775         }
776
777 }
778
779
780
781
782 /*****************************************************************************/
783 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
784 /*****************************************************************************/
785
786
787
788 /*
789  * smtp_try()
790  *
791  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
792  *
793  */
794 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
795 {
796         int sock = (-1);
797         char mxhosts[1024];
798         int num_mxhosts;
799         int mx;
800         int i;
801         char user[SIZ], node[SIZ], name[SIZ];
802         char buf[1024];
803         char mailfrom[1024];
804         int lp, rp;
805         FILE *msg_fp = NULL;
806         size_t msg_size;
807         size_t blocksize = 0;
808         int scan_done;
809
810         /* Parse out the host portion of the recipient address */
811         process_rfc822_addr(addr, user, node, name);
812
813         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
814                 user, node, name);
815
816         /* Load the message out of the database into a temp file */
817         msg_fp = tmpfile();
818         if (msg_fp == NULL) {
819                 *status = 4;
820                 sprintf(dsn, "Error creating temporary file");
821                 return;
822         }
823         else {
824                 CtdlRedirectOutput(msg_fp, -1);
825                 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
826                 CtdlRedirectOutput(NULL, -1);
827                 fseek(msg_fp, 0L, SEEK_END);
828                 msg_size = ftell(msg_fp);
829         }
830
831
832         /* Extract something to send later in the 'MAIL From:' command */
833         strcpy(mailfrom, "");
834         rewind(msg_fp);
835         scan_done = 0;
836         do {
837                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
838                 if (!strncasecmp(buf, "From:", 5)) {
839                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
840                         striplt(mailfrom);
841                         for (i=0; i<strlen(mailfrom); ++i) {
842                                 if (!isprint(mailfrom[i])) {
843                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
844                                         i=0;
845                                 }
846                         }
847
848                         /* Strip out parenthesized names */
849                         lp = (-1);
850                         rp = (-1);
851                         for (i=0; i<strlen(mailfrom); ++i) {
852                                 if (mailfrom[i] == '(') lp = i;
853                                 if (mailfrom[i] == ')') rp = i;
854                         }
855                         if ((lp>0)&&(rp>lp)) {
856                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
857                         }
858
859                         /* Prefer brokketized names */
860                         lp = (-1);
861                         rp = (-1);
862                         for (i=0; i<strlen(mailfrom); ++i) {
863                                 if (mailfrom[i] == '<') lp = i;
864                                 if (mailfrom[i] == '>') rp = i;
865                         }
866                         if ((lp>=0)&&(rp>lp)) {
867                                 mailfrom[rp] = 0;
868                                 strcpy(mailfrom, &mailfrom[lp]);
869                         }
870
871                         scan_done = 1;
872                 }
873         } while (scan_done == 0);
874         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
875
876
877         /* Figure out what mail exchanger host we have to connect to */
878         num_mxhosts = getmx(mxhosts, node);
879         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
880         if (num_mxhosts < 1) {
881                 *status = 5;
882                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
883                 return;
884         }
885
886         for (mx=0; mx<num_mxhosts; ++mx) {
887                 extract(buf, mxhosts, mx);
888                 lprintf(9, "Trying <%s>\n", buf);
889                 sock = sock_connect(buf, "25", "tcp");
890                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
891                 if (sock >= 0) lprintf(9, "Connected!\n");
892                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
893                 if (sock >= 0) break;
894         }
895
896         if (sock < 0) {
897                 *status = 4;    /* dsn is already filled in */
898                 return;
899         }
900
901         /* Process the SMTP greeting from the server */
902         if (ml_sock_gets(sock, buf) < 0) {
903                 *status = 4;
904                 strcpy(dsn, "Connection broken during SMTP conversation");
905                 goto bail;
906         }
907         lprintf(9, "<%s\n", buf);
908         if (buf[0] != '2') {
909                 if (buf[0] == '4') {
910                         *status = 4;
911                         safestrncpy(dsn, &buf[4], 1023);
912                         goto bail;
913                 }
914                 else {
915                         *status = 5;
916                         safestrncpy(dsn, &buf[4], 1023);
917                         goto bail;
918                 }
919         }
920
921         /* At this point we know we are talking to a real SMTP server */
922
923         /* Do a HELO command */
924         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
925         lprintf(9, ">%s", buf);
926         sock_write(sock, buf, strlen(buf));
927         if (ml_sock_gets(sock, buf) < 0) {
928                 *status = 4;
929                 strcpy(dsn, "Connection broken during SMTP HELO");
930                 goto bail;
931         }
932         lprintf(9, "<%s\n", buf);
933         if (buf[0] != '2') {
934                 if (buf[0] == '4') {
935                         *status = 4;
936                         safestrncpy(dsn, &buf[4], 1023);
937                         goto bail;
938                 }
939                 else {
940                         *status = 5;
941                         safestrncpy(dsn, &buf[4], 1023);
942                         goto bail;
943                 }
944         }
945
946
947         /* HELO succeeded, now try the MAIL From: command */
948         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
949         lprintf(9, ">%s", buf);
950         sock_write(sock, buf, strlen(buf));
951         if (ml_sock_gets(sock, buf) < 0) {
952                 *status = 4;
953                 strcpy(dsn, "Connection broken during SMTP MAIL");
954                 goto bail;
955         }
956         lprintf(9, "<%s\n", buf);
957         if (buf[0] != '2') {
958                 if (buf[0] == '4') {
959                         *status = 4;
960                         safestrncpy(dsn, &buf[4], 1023);
961                         goto bail;
962                 }
963                 else {
964                         *status = 5;
965                         safestrncpy(dsn, &buf[4], 1023);
966                         goto bail;
967                 }
968         }
969
970
971         /* MAIL succeeded, now try the RCPT To: command */
972         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
973         lprintf(9, ">%s", buf);
974         sock_write(sock, buf, strlen(buf));
975         if (ml_sock_gets(sock, buf) < 0) {
976                 *status = 4;
977                 strcpy(dsn, "Connection broken during SMTP RCPT");
978                 goto bail;
979         }
980         lprintf(9, "<%s\n", buf);
981         if (buf[0] != '2') {
982                 if (buf[0] == '4') {
983                         *status = 4;
984                         safestrncpy(dsn, &buf[4], 1023);
985                         goto bail;
986                 }
987                 else {
988                         *status = 5;
989                         safestrncpy(dsn, &buf[4], 1023);
990                         goto bail;
991                 }
992         }
993
994
995         /* RCPT succeeded, now try the DATA command */
996         lprintf(9, ">DATA\n");
997         sock_write(sock, "DATA\r\n", 6);
998         if (ml_sock_gets(sock, buf) < 0) {
999                 *status = 4;
1000                 strcpy(dsn, "Connection broken during SMTP DATA");
1001                 goto bail;
1002         }
1003         lprintf(9, "<%s\n", buf);
1004         if (buf[0] != '3') {
1005                 if (buf[0] == '4') {
1006                         *status = 3;
1007                         safestrncpy(dsn, &buf[4], 1023);
1008                         goto bail;
1009                 }
1010                 else {
1011                         *status = 5;
1012                         safestrncpy(dsn, &buf[4], 1023);
1013                         goto bail;
1014                 }
1015         }
1016
1017         /* If we reach this point, the server is expecting data */
1018         rewind(msg_fp);
1019         while (msg_size > 0) {
1020                 blocksize = sizeof(buf);
1021                 if (blocksize > msg_size) blocksize = msg_size;
1022                 fread(buf, blocksize, 1, msg_fp);
1023                 sock_write(sock, buf, blocksize);
1024                 msg_size -= blocksize;
1025         }
1026         if (buf[blocksize-1] != 10) {
1027                 lprintf(5, "Possible problem: message did not correctly "
1028                         "terminate. (expecting 0x10, got 0x%02x)\n",
1029                                 buf[blocksize-1]);
1030         }
1031
1032         sock_write(sock, ".\r\n", 3);
1033         if (ml_sock_gets(sock, buf) < 0) {
1034                 *status = 4;
1035                 strcpy(dsn, "Connection broken during SMTP message transmit");
1036                 goto bail;
1037         }
1038         lprintf(9, "%s\n", buf);
1039         if (buf[0] != '2') {
1040                 if (buf[0] == '4') {
1041                         *status = 4;
1042                         safestrncpy(dsn, &buf[4], 1023);
1043                         goto bail;
1044                 }
1045                 else {
1046                         *status = 5;
1047                         safestrncpy(dsn, &buf[4], 1023);
1048                         goto bail;
1049                 }
1050         }
1051
1052         /* We did it! */
1053         safestrncpy(dsn, &buf[4], 1023);
1054         *status = 2;
1055
1056         lprintf(9, ">QUIT\n");
1057         sock_write(sock, "QUIT\r\n", 6);
1058         ml_sock_gets(sock, buf);
1059         lprintf(9, "<%s\n", buf);
1060
1061 bail:   if (msg_fp != NULL) fclose(msg_fp);
1062         sock_close(sock);
1063         return;
1064 }
1065
1066
1067
1068 /*
1069  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1070  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1071  * a "bounce" message (delivery status notification).
1072  */
1073 void smtp_do_bounce(char *instr) {
1074         int i;
1075         int lines;
1076         int status;
1077         char buf[1024];
1078         char key[1024];
1079         char addr[1024];
1080         char dsn[1024];
1081         char bounceto[1024];
1082         int num_bounces = 0;
1083         int bounce_this = 0;
1084         long bounce_msgid = (-1);
1085         time_t submitted = 0L;
1086         struct CtdlMessage *bmsg = NULL;
1087         int give_up = 0;
1088         int mes_type = 0;
1089
1090         lprintf(9, "smtp_do_bounce() called\n");
1091         strcpy(bounceto, "");
1092
1093         lines = num_tokens(instr, '\n');
1094
1095
1096         /* See if it's time to give up on delivery of this message */
1097         for (i=0; i<lines; ++i) {
1098                 extract_token(buf, instr, i, '\n');
1099                 extract(key, buf, 0);
1100                 extract(addr, buf, 1);
1101                 if (!strcasecmp(key, "submitted")) {
1102                         submitted = atol(addr);
1103                 }
1104         }
1105
1106         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1107                 give_up = 1;
1108         }
1109
1110
1111
1112         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1113         if (bmsg == NULL) return;
1114         memset(bmsg, 0, sizeof(struct CtdlMessage));
1115
1116         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1117         bmsg->cm_anon_type = MES_NORMAL;
1118         bmsg->cm_format_type = 1;
1119         bmsg->cm_fields['A'] = strdoop("Citadel");
1120         bmsg->cm_fields['O'] = strdoop(MAILROOM);
1121         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1122
1123         if (give_up) bmsg->cm_fields['M'] = strdoop(
1124 "A message you sent could not be delivered to some or all of its recipients\n"
1125 "due to prolonged unavailability of its destination(s).\n"
1126 "Giving up on the following addresses:\n\n"
1127 );
1128
1129         else bmsg->cm_fields['M'] = strdoop(
1130 "A message you sent could not be delivered to some or all of its recipients.\n"
1131 "The following addresses were undeliverable:\n\n"
1132 );
1133
1134         /*
1135          * Now go through the instructions checking for stuff.
1136          */
1137
1138         for (i=0; i<lines; ++i) {
1139                 extract_token(buf, instr, i, '\n');
1140                 extract(key, buf, 0);
1141                 extract(addr, buf, 1);
1142                 status = extract_int(buf, 2);
1143                 extract(dsn, buf, 3);
1144                 bounce_this = 0;
1145
1146                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1147                         key, addr, status, dsn);
1148
1149                 if (!strcasecmp(key, "bounceto")) {
1150                         strcpy(bounceto, addr);
1151                 }
1152
1153                 if (
1154                    (!strcasecmp(key, "local"))
1155                    || (!strcasecmp(key, "remote"))
1156                    || (!strcasecmp(key, "ignet"))
1157                    || (!strcasecmp(key, "room"))
1158                 ) {
1159                         if (status == 5) bounce_this = 1;
1160                         if (give_up) bounce_this = 1;
1161                 }
1162
1163                 if (bounce_this) {
1164                         ++num_bounces;
1165
1166                         if (bmsg->cm_fields['M'] == NULL) {
1167                                 lprintf(2, "ERROR ... M field is null "
1168                                         "(%s:%d)\n", __FILE__, __LINE__);
1169                         }
1170
1171                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1172                                 strlen(bmsg->cm_fields['M']) + 1024 );
1173                         strcat(bmsg->cm_fields['M'], addr);
1174                         strcat(bmsg->cm_fields['M'], ": ");
1175                         strcat(bmsg->cm_fields['M'], dsn);
1176                         strcat(bmsg->cm_fields['M'], "\n");
1177
1178                         remove_token(instr, i, '\n');
1179                         --i;
1180                         --lines;
1181                 }
1182         }
1183
1184         /* Deliver the bounce if there's anything worth mentioning */
1185         lprintf(9, "num_bounces = %d\n", num_bounces);
1186         if (num_bounces > 0) {
1187
1188                 /* First try the user who sent the message */
1189                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1190                 TRACE;
1191                 if (strlen(bounceto) == 0) {
1192                         lprintf(7, "No bounce address specified\n");
1193                         bounce_msgid = (-1L);
1194                 }
1195 /* FIXME this won't work
1196                 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1197                         lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1198                         bounce_msgid = (-1L);
1199                 }
1200                 else {
1201                         bounce_msgid = CtdlSubmitMsg(bmsg,
1202                                 bounceto,
1203                                 "", mes_type);
1204                 }
1205  */
1206                 TRACE;
1207
1208                 /* Otherwise, go to the Aide> room */
1209                 lprintf(9, "bounce to room?\n");
1210                 if (bounce_msgid < 0L) bounce_msgid = CtdlSubmitMsg(bmsg,
1211                         NULL, AIDEROOM);
1212         }
1213
1214         CtdlFreeMessage(bmsg);
1215         lprintf(9, "Done processing bounces\n");
1216 }
1217
1218
1219 /*
1220  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1221  * set of delivery instructions for completed deliveries and remove them.
1222  *
1223  * It returns the number of incomplete deliveries remaining.
1224  */
1225 int smtp_purge_completed_deliveries(char *instr) {
1226         int i;
1227         int lines;
1228         int status;
1229         char buf[1024];
1230         char key[1024];
1231         char addr[1024];
1232         char dsn[1024];
1233         int completed;
1234         int incomplete = 0;
1235
1236         lines = num_tokens(instr, '\n');
1237         for (i=0; i<lines; ++i) {
1238                 extract_token(buf, instr, i, '\n');
1239                 extract(key, buf, 0);
1240                 extract(addr, buf, 1);
1241                 status = extract_int(buf, 2);
1242                 extract(dsn, buf, 3);
1243
1244                 completed = 0;
1245
1246                 if (
1247                    (!strcasecmp(key, "local"))
1248                    || (!strcasecmp(key, "remote"))
1249                    || (!strcasecmp(key, "ignet"))
1250                    || (!strcasecmp(key, "room"))
1251                 ) {
1252                         if (status == 2) completed = 1;
1253                         else ++incomplete;
1254                 }
1255
1256                 if (completed) {
1257                         remove_token(instr, i, '\n');
1258                         --i;
1259                         --lines;
1260                 }
1261         }
1262
1263         return(incomplete);
1264 }
1265
1266
1267 /*
1268  * smtp_do_procmsg()
1269  *
1270  * Called by smtp_do_queue() to handle an individual message.
1271  */
1272 void smtp_do_procmsg(long msgnum, void *userdata) {
1273         struct CtdlMessage *msg;
1274         char *instr = NULL;
1275         char *results = NULL;
1276         int i;
1277         int lines;
1278         int status;
1279         char buf[1024];
1280         char key[1024];
1281         char addr[1024];
1282         char dsn[1024];
1283         long text_msgid = (-1);
1284         int incomplete_deliveries_remaining;
1285         time_t attempted = 0L;
1286         time_t last_attempted = 0L;
1287         time_t retry = SMTP_RETRY_INTERVAL;
1288
1289         lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1290
1291         msg = CtdlFetchMessage(msgnum);
1292         if (msg == NULL) {
1293                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1294                 return;
1295         }
1296
1297         instr = strdoop(msg->cm_fields['M']);
1298         CtdlFreeMessage(msg);
1299
1300         /* Strip out the headers amd any other non-instruction line */
1301         lines = num_tokens(instr, '\n');
1302         for (i=0; i<lines; ++i) {
1303                 extract_token(buf, instr, i, '\n');
1304                 if (num_tokens(buf, '|') < 2) {
1305                         remove_token(instr, i, '\n');
1306                         --lines;
1307                         --i;
1308                 }
1309         }
1310
1311         /* Learn the message ID and find out about recent delivery attempts */
1312         lines = num_tokens(instr, '\n');
1313         for (i=0; i<lines; ++i) {
1314                 extract_token(buf, instr, i, '\n');
1315                 extract(key, buf, 0);
1316                 if (!strcasecmp(key, "msgid")) {
1317                         text_msgid = extract_long(buf, 1);
1318                 }
1319                 if (!strcasecmp(key, "retry")) {
1320                         /* double the retry interval after each attempt */
1321                         retry = extract_long(buf, 1) * 2L;
1322                         if (retry > SMTP_RETRY_MAX) {
1323                                 retry = SMTP_RETRY_MAX;
1324                         }
1325                         remove_token(instr, i, '\n');
1326                 }
1327                 if (!strcasecmp(key, "attempted")) {
1328                         attempted = extract_long(buf, 1);
1329                         if (attempted > last_attempted)
1330                                 last_attempted = attempted;
1331                 }
1332         }
1333
1334         /*
1335          * Postpone delivery if we've already tried recently.
1336          */
1337         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1338                 lprintf(7, "Retry time not yet reached.\n");
1339                 phree(instr);
1340                 return;
1341         }
1342
1343
1344         /*
1345          * Bail out if there's no actual message associated with this
1346          */
1347         if (text_msgid < 0L) {
1348                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1349                 phree(instr);
1350                 return;
1351         }
1352
1353         /* Plow through the instructions looking for 'remote' directives and
1354          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1355          * were experienced and it's time to try again)
1356          */
1357         lines = num_tokens(instr, '\n');
1358         for (i=0; i<lines; ++i) {
1359                 extract_token(buf, instr, i, '\n');
1360                 extract(key, buf, 0);
1361                 extract(addr, buf, 1);
1362                 status = extract_int(buf, 2);
1363                 extract(dsn, buf, 3);
1364                 if ( (!strcasecmp(key, "remote"))
1365                    && ((status==0)||(status==3)||(status==4)) ) {
1366                         remove_token(instr, i, '\n');
1367                         --i;
1368                         --lines;
1369                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1370                         smtp_try(key, addr, &status, dsn, text_msgid);
1371                         if (status != 2) {
1372                                 if (results == NULL) {
1373                                         results = mallok(1024);
1374                                         memset(results, 0, 1024);
1375                                 }
1376                                 else {
1377                                         results = reallok(results,
1378                                                 strlen(results) + 1024);
1379                                 }
1380                                 sprintf(&results[strlen(results)],
1381                                         "%s|%s|%d|%s\n",
1382                                         key, addr, status, dsn);
1383                         }
1384                 }
1385         }
1386
1387         if (results != NULL) {
1388                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1389                 strcat(instr, results);
1390                 phree(results);
1391         }
1392
1393
1394         /* Generate 'bounce' messages */
1395         smtp_do_bounce(instr);
1396
1397         /* Go through the delivery list, deleting completed deliveries */
1398         incomplete_deliveries_remaining = 
1399                 smtp_purge_completed_deliveries(instr);
1400
1401
1402         /*
1403          * No delivery instructions remain, so delete both the instructions
1404          * message and the message message.
1405          */
1406         if (incomplete_deliveries_remaining <= 0) {
1407                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1408                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1409         }
1410
1411
1412         /*
1413          * Uncompleted delivery instructions remain, so delete the old
1414          * instructions and replace with the updated ones.
1415          */
1416         if (incomplete_deliveries_remaining > 0) {
1417                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1418                 msg = mallok(sizeof(struct CtdlMessage));
1419                 memset(msg, 0, sizeof(struct CtdlMessage));
1420                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1421                 msg->cm_anon_type = MES_NORMAL;
1422                 msg->cm_format_type = FMT_RFC822;
1423                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1424                 snprintf(msg->cm_fields['M'],
1425                         strlen(instr)+SIZ,
1426                         "Content-type: %s\n\n%s\n"
1427                         "attempted|%ld\n"
1428                         "retry|%ld\n",
1429                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1430                 phree(instr);
1431                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1432                 CtdlFreeMessage(msg);
1433         }
1434
1435 }
1436
1437
1438
1439 /*
1440  * smtp_do_queue()
1441  * 
1442  * Run through the queue sending out messages.
1443  */
1444 void smtp_do_queue(void) {
1445         static int doing_queue = 0;
1446
1447         /*
1448          * This is a simple concurrency check to make sure only one queue run
1449          * is done at a time.  We could do this with a mutex, but since we
1450          * don't really require extremely fine granularity here, we'll do it
1451          * with a static variable instead.
1452          */
1453         if (doing_queue) return;
1454         doing_queue = 1;
1455
1456         /* 
1457          * Go ahead and run the queue
1458          */
1459         lprintf(7, "SMTP: processing outbound queue\n");
1460
1461         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1462                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1463                 return;
1464         }
1465         CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1466                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1467
1468         lprintf(7, "SMTP: queue run completed\n");
1469         run_queue_now = 0;
1470         doing_queue = 0;
1471 }
1472
1473
1474
1475 /*****************************************************************************/
1476 /*                          SMTP UTILITY COMMANDS                            */
1477 /*****************************************************************************/
1478
1479 void cmd_smtp(char *argbuf) {
1480         char cmd[SIZ];
1481         char node[SIZ];
1482         char buf[SIZ];
1483         int i;
1484         int num_mxhosts;
1485
1486         if (CtdlAccessCheck(ac_aide)) return;
1487
1488         extract(cmd, argbuf, 0);
1489
1490         if (!strcasecmp(cmd, "mx")) {
1491                 extract(node, argbuf, 1);
1492                 num_mxhosts = getmx(buf, node);
1493                 cprintf("%d %d MX hosts listed for %s\n",
1494                         LISTING_FOLLOWS, num_mxhosts, node);
1495                 for (i=0; i<num_mxhosts; ++i) {
1496                         extract(node, buf, i);
1497                         cprintf("%s\n", node);
1498                 }
1499                 cprintf("000\n");
1500                 return;
1501         }
1502
1503         else if (!strcasecmp(cmd, "runqueue")) {
1504                 run_queue_now = 1;
1505                 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1506                 return;
1507         }
1508
1509         else {
1510                 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1511         }
1512
1513 }
1514
1515
1516
1517
1518 /*****************************************************************************/
1519 /*                      MODULE INITIALIZATION STUFF                          */
1520 /*****************************************************************************/
1521
1522
1523 char *Dynamic_Module_Init(void)
1524 {
1525         SYM_SMTP = CtdlGetDynamicSymbol();
1526         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1527
1528         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1529                                 NULL,
1530                                 smtp_greeting,
1531                                 smtp_command_loop);
1532
1533         CtdlRegisterServiceHook(0,                      /* ...and locally */
1534                                 "smtp.socket",
1535                                 smtp_greeting,
1536                                 smtp_command_loop);
1537
1538         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1539         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1540         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1541         return "$Id$";
1542 }