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