9a2b03179a9f452028c998c1f9b90b487a8b25be
[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
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_invalid:
403                         if (is_spam) {
404                                 cprintf("551 Away with thee, spammer!\r\n");
405                         }
406                         else {
407                                 cprintf("250 Remote recipient %s ok\r\n", recp);
408                                 ++SMTP->number_of_recipients;
409                                 CtdlReallocUserData(SYM_SMTP_RECP,
410                                         strlen(SMTP_RECP) + 1024 );
411                                 strcat(SMTP_RECP, "remote|");
412                                 strcat(SMTP_RECP, recp);
413                                 strcat(SMTP_RECP, "|0|\n");
414                                 return;
415                         }
416                         return;
417         }
418
419         cprintf("599 Unknown error\r\n");
420 }
421
422
423
424
425
426 /*
427  * Back end for smtp_data()  ... this does the actual delivery of the message
428  * Returns 0 on success, nonzero on failure
429  */
430 int smtp_message_delivery(struct CtdlMessage *msg) {
431         char user[1024];
432         char node[1024];
433         char name[1024];
434         char buf[1024];
435         char dtype[1024];
436         char room[1024];
437         int successful_saves = 0;       /* number of successful local saves */
438         int failed_saves = 0;           /* number of failed deliveries */
439         int remote_spools = 0;          /* number of copies to send out */
440         long msgid = (-1L);
441         int i;
442         struct usersupp userbuf;
443         char *instr;                    /* Remote delivery instructions */
444         struct CtdlMessage *imsg;
445
446         lprintf(9, "smtp_message_delivery() called\n");
447
448         /* Fill in 'from' fields with envelope information if missing */
449         process_rfc822_addr(SMTP->from, user, node, name);
450         if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
451         if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
452         if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
453
454         /* Save the message in the queue */
455         msgid = CtdlSaveMsg(msg,
456                 "",
457                 SMTP_SPOOLOUT_ROOM,
458                 MES_LOCAL,
459                 1);
460         ++successful_saves;
461
462         instr = mallok(1024);
463         sprintf(instr, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
464                         "bounceto|%s\n",
465                 SPOOLMIME, msgid, time(NULL),
466                 SMTP->from );
467
468         for (i=0; i<SMTP->number_of_recipients; ++i) {
469                 extract_token(buf, SMTP_RECP, i, '\n');
470                 extract(dtype, buf, 0);
471
472                 /* Stuff local mailboxes */
473                 if (!strcasecmp(dtype, "local")) {
474                         extract(user, buf, 1);
475                         if (getuser(&userbuf, user) == 0) {
476                                 MailboxName(room, &userbuf, MAILROOM);
477                                 CtdlSaveMsgPointerInRoom(room, msgid, 0);
478                                 ++successful_saves;
479                         }
480                         else {
481                                 ++failed_saves;
482                         }
483                 }
484
485                 /* Delivery to local non-mailbox rooms */
486                 if (!strcasecmp(dtype, "room")) {
487                         extract(room, buf, 1);
488                         CtdlSaveMsgPointerInRoom(room, msgid, 0);
489                         ++successful_saves;
490                 }
491
492                 /* Remote delivery */
493                 if (!strcasecmp(dtype, "remote")) {
494                         extract(user, buf, 1);
495                         instr = reallok(instr, strlen(instr) + 1024);
496                         sprintf(&instr[strlen(instr)],
497                                 "remote|%s|0\n",
498                                 user);
499                         ++remote_spools;
500                 }
501
502         }
503
504         /* If there are remote spools to be done, save the instructions */
505         if (remote_spools > 0) {
506                 imsg = mallok(sizeof(struct CtdlMessage));
507                 memset(imsg, 0, sizeof(struct CtdlMessage));
508                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
509                 imsg->cm_anon_type = MES_NORMAL;
510                 imsg->cm_format_type = FMT_RFC822;
511                 imsg->cm_fields['M'] = instr;
512                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
513                 CtdlFreeMessage(imsg);
514         }
515
516         /* If there are no remote spools, delete the message */ 
517         else {
518                 phree(instr);   /* only needed here, because CtdlSaveMsg()
519                                  * would free this buffer otherwise */
520                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL); 
521         }
522
523         return(failed_saves);
524 }
525
526
527
528 /*
529  * Implements the DATA command
530  */
531 void smtp_data(void) {
532         char *body;
533         struct CtdlMessage *msg;
534         int retval;
535         char nowstamp[256];
536
537         if (strlen(SMTP->from) == 0) {
538                 cprintf("503 Need MAIL command first.\r\n");
539                 return;
540         }
541
542         if (SMTP->number_of_recipients < 1) {
543                 cprintf("503 Need RCPT command first.\r\n");
544                 return;
545         }
546
547         cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
548         
549         generate_rfc822_datestamp(nowstamp, time(NULL));
550         body = mallok(4096);
551
552         if (body != NULL) sprintf(body,
553                 "Received: from %s\n"
554                 "       by %s;\n"
555                 "       %s\n",
556                         SMTP->helo_node,
557                         config.c_fqdn,
558                         nowstamp);
559         
560         body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
561         if (body == NULL) {
562                 cprintf("550 Unable to save message text: internal error.\r\n");
563                 return;
564         }
565
566         lprintf(9, "Converting message...\n");
567         msg = convert_internet_message(body);
568
569         /* If the user is locally authenticated, FORCE the From: header to
570          * show up as the real sender
571          */
572         if (CC->logged_in) {
573                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
574                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
575                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
576                 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
577                 msg->cm_fields['N'] = strdoop(config.c_nodename);
578                 msg->cm_fields['H'] = strdoop(config.c_humannode);
579         }
580
581         retval = smtp_message_delivery(msg);
582         CtdlFreeMessage(msg);
583
584         if (!retval) {
585                 cprintf("250 Message accepted for delivery.\r\n");
586         }
587         else {
588                 cprintf("550 Internal delivery errors: %d\r\n", retval);
589         }
590 }
591
592
593
594
595 /* 
596  * Main command loop for SMTP sessions.
597  */
598 void smtp_command_loop(void) {
599         char cmdbuf[256];
600
601         time(&CC->lastcmd);
602         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
603         if (client_gets(cmdbuf) < 1) {
604                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
605                 CC->kill_me = 1;
606                 return;
607         }
608         lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
609         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
610
611         if (SMTP->command_state == smtp_user) {
612                 smtp_get_user(cmdbuf);
613         }
614
615         else if (SMTP->command_state == smtp_password) {
616                 smtp_get_pass(cmdbuf);
617         }
618
619         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
620                 smtp_auth(&cmdbuf[5]);
621         }
622
623         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
624                 smtp_data();
625         }
626
627         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
628                 smtp_hello(&cmdbuf[5], 1);
629         }
630
631         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
632                 smtp_expn(&cmdbuf[5]);
633         }
634
635         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
636                 smtp_hello(&cmdbuf[5], 0);
637         }
638
639         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
640                 smtp_help();
641         }
642
643         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
644                 smtp_mail(&cmdbuf[5]);
645         }
646
647         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
648                 cprintf("250 This command successfully did nothing.\r\n");
649         }
650
651         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
652                 cprintf("221 Goodbye...\r\n");
653                 CC->kill_me = 1;
654                 return;
655                 }
656
657         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
658                 smtp_rcpt(&cmdbuf[5]);
659         }
660
661         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
662                 smtp_rset();
663         }
664
665         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
666                 smtp_vrfy(&cmdbuf[5]);
667         }
668
669         else {
670                 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
671         }
672
673 }
674
675
676
677
678 /*****************************************************************************/
679 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
680 /*****************************************************************************/
681
682
683
684 /*
685  * smtp_try()
686  *
687  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
688  *
689  */
690 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
691 {
692         int sock = (-1);
693         char mxhosts[1024];
694         int num_mxhosts;
695         int mx;
696         int i;
697         char user[256], node[256], name[256];
698         char buf[1024];
699         char mailfrom[1024];
700         int lp, rp;
701         FILE *msg_fp = NULL;
702         size_t msg_size;
703         size_t blocksize = 0;
704         int scan_done;
705
706         /* Parse out the host portion of the recipient address */
707         process_rfc822_addr(addr, user, node, name);
708         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
709                 user, node, name);
710
711         /* Load the message out of the database into a temp file */
712         msg_fp = tmpfile();
713         if (msg_fp == NULL) {
714                 *status = 4;
715                 sprintf(dsn, "Error creating temporary file");
716                 return;
717         }
718         else {
719                 CtdlRedirectOutput(msg_fp, -1);
720                 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
721                 CtdlRedirectOutput(NULL, -1);
722                 fseek(msg_fp, 0L, SEEK_END);
723                 msg_size = ftell(msg_fp);
724         }
725
726
727         /* Extract something to send later in the 'MAIL From:' command */
728         strcpy(mailfrom, "");
729         rewind(msg_fp);
730         scan_done = 0;
731         do {
732                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
733                 if (!strncasecmp(buf, "From:", 5)) {
734                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
735                         striplt(mailfrom);
736                         for (i=0; i<strlen(mailfrom); ++i) {
737                                 if (!isprint(mailfrom[i])) {
738                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
739                                         i=0;
740                                 }
741                         }
742
743                         /* Strip out parenthesized names */
744                         lp = (-1);
745                         rp = (-1);
746                         for (i=0; i<strlen(mailfrom); ++i) {
747                                 if (mailfrom[i] == '(') lp = i;
748                                 if (mailfrom[i] == ')') rp = i;
749                         }
750                         if ((lp>0)&&(rp>lp)) {
751                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
752                         }
753
754                         /* Prefer brokketized names */
755                         lp = (-1);
756                         rp = (-1);
757                         for (i=0; i<strlen(mailfrom); ++i) {
758                                 if (mailfrom[i] == '<') lp = i;
759                                 if (mailfrom[i] == '>') rp = i;
760                         }
761                         if ((lp>=0)&&(rp>lp)) {
762                                 mailfrom[rp] = 0;
763                                 strcpy(mailfrom, &mailfrom[lp]);
764                         }
765
766                         scan_done = 1;
767                 }
768         } while (scan_done == 0);
769         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
770
771
772         /* Figure out what mail exchanger host we have to connect to */
773         num_mxhosts = getmx(mxhosts, node);
774         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
775         if (num_mxhosts < 1) {
776                 *status = 5;
777                 sprintf(dsn, "No MX hosts found for <%s>", node);
778                 return;
779         }
780
781         for (mx=0; mx<num_mxhosts; ++mx) {
782                 extract(buf, mxhosts, mx);
783                 lprintf(9, "Trying <%s>\n", buf);
784                 sock = sock_connect(buf, "25", "tcp");
785                 sprintf(dsn, "Could not connect: %s", strerror(errno));
786                 if (sock >= 0) lprintf(9, "Connected!\n");
787                 if (sock < 0) sprintf(dsn, "%s", strerror(errno));
788                 if (sock >= 0) break;
789         }
790
791         if (sock < 0) {
792                 *status = 4;    /* dsn is already filled in */
793                 return;
794         }
795
796         /* Process the SMTP greeting from the server */
797         if (sock_gets(sock, buf) < 0) {
798                 *status = 4;
799                 strcpy(dsn, "Connection broken during SMTP conversation");
800                 goto bail;
801         }
802         lprintf(9, "<%s\n", buf);
803         if (buf[0] != '2') {
804                 if (buf[0] == '4') {
805                         *status = 4;
806                         strcpy(dsn, &buf[4]);
807                         goto bail;
808                 }
809                 else {
810                         *status = 5;
811                         strcpy(dsn, &buf[4]);
812                         goto bail;
813                 }
814         }
815
816         /* At this point we know we are talking to a real SMTP server */
817
818         /* Do a HELO command */
819         sprintf(buf, "HELO %s", config.c_fqdn);
820         lprintf(9, ">%s\n", buf);
821         sock_puts(sock, buf);
822         if (sock_gets(sock, buf) < 0) {
823                 *status = 4;
824                 strcpy(dsn, "Connection broken during SMTP conversation");
825                 goto bail;
826         }
827         lprintf(9, "<%s\n", buf);
828         if (buf[0] != '2') {
829                 if (buf[0] == '4') {
830                         *status = 4;
831                         strcpy(dsn, &buf[4]);
832                         goto bail;
833                 }
834                 else {
835                         *status = 5;
836                         strcpy(dsn, &buf[4]);
837                         goto bail;
838                 }
839         }
840
841
842         /* HELO succeeded, now try the MAIL From: command */
843         sprintf(buf, "MAIL From: %s", mailfrom);
844         lprintf(9, ">%s\n", buf);
845         sock_puts(sock, buf);
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
866         /* MAIL succeeded, now try the RCPT To: command */
867         sprintf(buf, "RCPT To: %s", addr);
868         lprintf(9, ">%s\n", buf);
869         sock_puts(sock, buf);
870         if (sock_gets(sock, buf) < 0) {
871                 *status = 4;
872                 strcpy(dsn, "Connection broken during SMTP conversation");
873                 goto bail;
874         }
875         lprintf(9, "<%s\n", buf);
876         if (buf[0] != '2') {
877                 if (buf[0] == '4') {
878                         *status = 4;
879                         strcpy(dsn, &buf[4]);
880                         goto bail;
881                 }
882                 else {
883                         *status = 5;
884                         strcpy(dsn, &buf[4]);
885                         goto bail;
886                 }
887         }
888
889
890         /* RCPT succeeded, now try the DATA command */
891         lprintf(9, ">DATA\n");
892         sock_puts(sock, "DATA");
893         if (sock_gets(sock, buf) < 0) {
894                 *status = 4;
895                 strcpy(dsn, "Connection broken during SMTP conversation");
896                 goto bail;
897         }
898         lprintf(9, "<%s\n", buf);
899         if (buf[0] != '3') {
900                 if (buf[0] == '4') {
901                         *status = 3;
902                         strcpy(dsn, &buf[4]);
903                         goto bail;
904                 }
905                 else {
906                         *status = 5;
907                         strcpy(dsn, &buf[4]);
908                         goto bail;
909                 }
910         }
911
912         /* If we reach this point, the server is expecting data */
913         rewind(msg_fp);
914         while (msg_size > 0) {
915                 blocksize = sizeof(buf);
916                 if (blocksize > msg_size) blocksize = msg_size;
917                 fread(buf, blocksize, 1, msg_fp);
918                 sock_write(sock, buf, blocksize);
919                 msg_size -= blocksize;
920         }
921         if (buf[blocksize-1] != 10) {
922                 lprintf(5, "Possible problem: message did not correctly "
923                         "terminate. (expecting 0x10, got 0x%02x)\n",
924                                 buf[blocksize-1]);
925         }
926
927         sock_write(sock, ".\r\n", 3);
928         if (sock_gets(sock, buf) < 0) {
929                 *status = 4;
930                 strcpy(dsn, "Connection broken during SMTP conversation");
931                 goto bail;
932         }
933         lprintf(9, "%s\n", buf);
934         if (buf[0] != '2') {
935                 if (buf[0] == '4') {
936                         *status = 4;
937                         strcpy(dsn, &buf[4]);
938                         goto bail;
939                 }
940                 else {
941                         *status = 5;
942                         strcpy(dsn, &buf[4]);
943                         goto bail;
944                 }
945         }
946
947         /* We did it! */
948         strcpy(dsn, &buf[4]);
949         *status = 2;
950
951         lprintf(9, ">QUIT\n");
952         sock_puts(sock, "QUIT");
953         sock_gets(sock, buf);
954         lprintf(9, "<%s\n", buf);
955
956 bail:   if (msg_fp != NULL) fclose(msg_fp);
957         sock_close(sock);
958         return;
959 }
960
961
962
963 /*
964  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
965  * instructions for "5" codes (permanent fatal errors) and produce/deliver
966  * a "bounce" message (delivery status notification).
967  */
968 void smtp_do_bounce(char *instr) {
969         int i;
970         int lines;
971         int status;
972         char buf[1024];
973         char key[1024];
974         char addr[1024];
975         char dsn[1024];
976         char bounceto[1024];
977         int num_bounces = 0;
978         int bounce_this = 0;
979         long bounce_msgid = (-1);
980         time_t submitted = 0L;
981         struct CtdlMessage *bmsg = NULL;
982         int give_up = 0;
983         int mes_type = 0;
984
985         lprintf(9, "smtp_do_bounce() called\n");
986         strcpy(bounceto, "");
987
988         lines = num_tokens(instr, '\n');
989
990
991         /* See if it's time to give up on delivery of this message */
992         for (i=0; i<lines; ++i) {
993                 extract_token(buf, instr, i, '\n');
994                 extract(key, buf, 0);
995                 extract(addr, buf, 1);
996                 if (!strcasecmp(key, "submitted")) {
997                         submitted = atol(addr);
998                 }
999         }
1000
1001         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1002                 give_up = 1;
1003         }
1004
1005
1006
1007         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1008         if (bmsg == NULL) return;
1009         memset(bmsg, 0, sizeof(struct CtdlMessage));
1010
1011         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1012         bmsg->cm_anon_type = MES_NORMAL;
1013         bmsg->cm_format_type = 1;
1014         bmsg->cm_fields['A'] = strdoop("Citadel");
1015         bmsg->cm_fields['O'] = strdoop(MAILROOM);
1016         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1017
1018         if (give_up) bmsg->cm_fields['M'] = strdoop(
1019 "A message you sent could not be delivered to some or all of its recipients.\n"
1020 "The following addresses were undeliverable:\n\n"
1021 );
1022
1023         else bmsg->cm_fields['M'] = strdoop(
1024 "A message you sent could not be delivered to some or all of its recipients\n"
1025 "due to prolonged unavailability of its destination(s).\n"
1026 "Giving up on the following addresses:\n\n"
1027 );
1028
1029         /*
1030          * Now go through the instructions checking for stuff.
1031          */
1032
1033         for (i=0; i<lines; ++i) {
1034                 extract_token(buf, instr, i, '\n');
1035                 extract(key, buf, 0);
1036                 extract(addr, buf, 1);
1037                 status = extract_int(buf, 2);
1038                 extract(dsn, buf, 3);
1039                 bounce_this = 0;
1040
1041                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1042                         key, addr, status, dsn);
1043
1044                 if (!strcasecmp(key, "bounceto")) {
1045                         strcpy(bounceto, addr);
1046                 }
1047
1048                 if (
1049                    (!strcasecmp(key, "local"))
1050                    || (!strcasecmp(key, "remote"))
1051                    || (!strcasecmp(key, "ignet"))
1052                    || (!strcasecmp(key, "room"))
1053                 ) {
1054                         if (status == 5) bounce_this = 1;
1055                         if (give_up) bounce_this = 1;
1056                 }
1057
1058                 if (bounce_this) {
1059                         ++num_bounces;
1060
1061                         if (bmsg->cm_fields['M'] == NULL) {
1062                                 lprintf(2, "ERROR ... M field is null "
1063                                         "(%s:%d)\n", __FILE__, __LINE__);
1064                         }
1065
1066                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1067                                 strlen(bmsg->cm_fields['M']) + 1024 );
1068                         strcat(bmsg->cm_fields['M'], addr);
1069                         strcat(bmsg->cm_fields['M'], ": ");
1070                         strcat(bmsg->cm_fields['M'], dsn);
1071                         strcat(bmsg->cm_fields['M'], "\n");
1072
1073                         remove_token(instr, i, '\n');
1074                         --i;
1075                         --lines;
1076                 }
1077         }
1078
1079         /* Deliver the bounce if there's anything worth mentioning */
1080         lprintf(9, "num_bounces = %d\n", num_bounces);
1081         if (num_bounces > 0) {
1082
1083                 /* First try the user who sent the message */
1084                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1085                 if (strlen(bounceto) == 0) {
1086                         lprintf(7, "No bounce address specified\n");
1087                         bounce_msgid = (-1L);
1088                 }
1089                 else if (mes_type = alias(bounceto), mes_type == MES_ERROR) {
1090                         lprintf(7, "Invalid bounce address <%s>\n", bounceto);
1091                         bounce_msgid = (-1L);
1092                 }
1093                 else {
1094                         bounce_msgid = CtdlSaveMsg(bmsg,
1095                                 bounceto,
1096                                 "", mes_type, 1);
1097                 }
1098
1099                 /* Otherwise, go to the Aide> room */
1100                 lprintf(9, "bounce to room?\n");
1101                 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1102                         "", AIDEROOM,
1103                         MES_LOCAL, 1);
1104         }
1105
1106         CtdlFreeMessage(bmsg);
1107         lprintf(9, "Done processing bounces\n");
1108 }
1109
1110
1111 /*
1112  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1113  * set of delivery instructions for completed deliveries and remove them.
1114  *
1115  * It returns the number of incomplete deliveries remaining.
1116  */
1117 int smtp_purge_completed_deliveries(char *instr) {
1118         int i;
1119         int lines;
1120         int status;
1121         char buf[1024];
1122         char key[1024];
1123         char addr[1024];
1124         char dsn[1024];
1125         int completed;
1126         int incomplete = 0;
1127
1128         lines = num_tokens(instr, '\n');
1129         for (i=0; i<lines; ++i) {
1130                 extract_token(buf, instr, i, '\n');
1131                 extract(key, buf, 0);
1132                 extract(addr, buf, 1);
1133                 status = extract_int(buf, 2);
1134                 extract(dsn, buf, 3);
1135
1136                 completed = 0;
1137
1138                 if (
1139                    (!strcasecmp(key, "local"))
1140                    || (!strcasecmp(key, "remote"))
1141                    || (!strcasecmp(key, "ignet"))
1142                    || (!strcasecmp(key, "room"))
1143                 ) {
1144                         if (status == 2) completed = 1;
1145                         else ++incomplete;
1146                 }
1147
1148                 if (completed) {
1149                         remove_token(instr, i, '\n');
1150                         --i;
1151                         --lines;
1152                 }
1153         }
1154
1155         return(incomplete);
1156 }
1157
1158
1159 /*
1160  * smtp_do_procmsg()
1161  *
1162  * Called by smtp_do_queue() to handle an individual message.
1163  */
1164 void smtp_do_procmsg(long msgnum) {
1165         struct CtdlMessage *msg;
1166         char *instr = NULL;
1167         char *results = NULL;
1168         int i;
1169         int lines;
1170         int status;
1171         char buf[1024];
1172         char key[1024];
1173         char addr[1024];
1174         char dsn[1024];
1175         long text_msgid = (-1);
1176         int incomplete_deliveries_remaining;
1177         time_t attempted = 0L;
1178         time_t last_attempted = 0L;
1179
1180         msg = CtdlFetchMessage(msgnum);
1181         if (msg == NULL) {
1182                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1183                 return;
1184         }
1185
1186         instr = strdoop(msg->cm_fields['M']);
1187         CtdlFreeMessage(msg);
1188
1189         /* Strip out the headers amd any other non-instruction line */
1190         lines = num_tokens(instr, '\n');
1191         for (i=0; i<lines; ++i) {
1192                 extract_token(buf, instr, i, '\n');
1193                 if (num_tokens(buf, '|') < 2) {
1194                         lprintf(9, "removing <%s>\n", buf);
1195                         remove_token(instr, i, '\n');
1196                         --lines;
1197                         --i;
1198                 }
1199         }
1200
1201         /* Learn the message ID and find out about recent delivery attempts */
1202         lines = num_tokens(instr, '\n');
1203         for (i=0; i<lines; ++i) {
1204                 extract_token(buf, instr, i, '\n');
1205                 extract(key, buf, 0);
1206                 if (!strcasecmp(key, "msgid")) {
1207                         text_msgid = extract_long(buf, 1);
1208                 }
1209                 if (!strcasecmp(key, "attempted")) {
1210                         attempted = extract_long(buf, 1);
1211                         if (attempted > last_attempted)
1212                                 last_attempted = attempted;
1213                 }
1214         }
1215
1216
1217         /*
1218          * Postpone delivery if we've already tried recently.
1219          */
1220         if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
1221                 lprintf(7, "Retry time not yet reached.\n");
1222                 phree(instr);
1223                 return;
1224         }
1225
1226
1227         /*
1228          * Bail out if there's no actual message associated with this
1229          */
1230         if (text_msgid < 0L) {
1231                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1232                 phree(instr);
1233                 return;
1234         }
1235
1236         /* Plow through the instructions looking for 'remote' directives and
1237          * a status of 0 (no delivery yet attempted) or 3 (transient errors
1238          * were experienced and it's time to try again)
1239          */
1240         lines = num_tokens(instr, '\n');
1241         for (i=0; i<lines; ++i) {
1242                 extract_token(buf, instr, i, '\n');
1243                 extract(key, buf, 0);
1244                 extract(addr, buf, 1);
1245                 status = extract_int(buf, 2);
1246                 extract(dsn, buf, 3);
1247                 if ( (!strcasecmp(key, "remote"))
1248                    && ((status==0)||(status==3)) ) {
1249                         remove_token(instr, i, '\n');
1250                         --i;
1251                         --lines;
1252                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1253                         smtp_try(key, addr, &status, dsn, text_msgid);
1254                         if (status != 2) {
1255                                 if (results == NULL) {
1256                                         results = mallok(1024);
1257                                         memset(results, 0, 1024);
1258                                 }
1259                                 else {
1260                                         results = reallok(results,
1261                                                 strlen(results) + 1024);
1262                                 }
1263                                 sprintf(&results[strlen(results)],
1264                                         "%s|%s|%d|%s\n",
1265                                         key, addr, status, dsn);
1266                         }
1267                 }
1268         }
1269
1270         if (results != NULL) {
1271                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1272                 strcat(instr, results);
1273                 phree(results);
1274         }
1275
1276
1277         /* Generate 'bounce' messages */
1278         smtp_do_bounce(instr);
1279
1280         /* Go through the delivery list, deleting completed deliveries */
1281         incomplete_deliveries_remaining = 
1282                 smtp_purge_completed_deliveries(instr);
1283
1284
1285         /*
1286          * No delivery instructions remain, so delete both the instructions
1287          * message and the message message.
1288          */
1289         if (incomplete_deliveries_remaining <= 0)  {
1290                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1291                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);    
1292         }
1293
1294
1295         /*
1296          * Uncompleted delivery instructions remain, so delete the old
1297          * instructions and replace with the updated ones.
1298          */
1299         if (incomplete_deliveries_remaining > 0) {
1300                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1301                 msg = mallok(sizeof(struct CtdlMessage));
1302                 memset(msg, 0, sizeof(struct CtdlMessage));
1303                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1304                 msg->cm_anon_type = MES_NORMAL;
1305                 msg->cm_format_type = FMT_RFC822;
1306                 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1307                 sprintf(msg->cm_fields['M'],
1308                         "Content-type: %s\n\n%s\nattempted|%ld\n",
1309                         SPOOLMIME, instr, time(NULL) );
1310                 phree(instr);
1311                 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1312                 CtdlFreeMessage(msg);
1313         }
1314
1315 }
1316
1317
1318
1319 /*
1320  * smtp_do_queue()
1321  * 
1322  * Run through the queue sending out messages.
1323  */
1324 void smtp_do_queue(void) {
1325         lprintf(5, "SMTP: processing outbound queue\n");
1326
1327         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1328                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1329                 return;
1330         }
1331         CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1332
1333         lprintf(5, "SMTP: queue run completed\n");
1334 }
1335
1336
1337
1338 /*****************************************************************************/
1339 /*                      MODULE INITIALIZATION STUFF                          */
1340 /*****************************************************************************/
1341
1342
1343 char *Dynamic_Module_Init(void)
1344 {
1345         SYM_SMTP = CtdlGetDynamicSymbol();
1346         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1347         CtdlRegisterServiceHook(SMTP_PORT,
1348                                 smtp_greeting,
1349                                 smtp_command_loop);
1350         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1351         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1352         return "$Id$";
1353 }