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