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