f2c367fccbe689f2da6b592e2108057471a2bc3b
[citadel.git] / citadel / citmail.c
1 /*
2  * citmail.c v4.1
3  *
4  * This program may be used as a local mail delivery agent, which will allow
5  * all Citadel users to receive Internet e-mail.  To enable this functionality,
6  * you must tell sendmail, smail, or whatever mailer you are using, that this
7  * program is your local mail delivery agent.  This program is a direct
8  * replacement for lmail, deliver, or whatever.
9  *
10  * Usage:
11  *
12  * citmail <recipient>       - Deliver a message
13  * citmail -t <recipient>    - Address test mode (will not deliver)
14  * citmail -i                - Run as an SMTP daemon (typically from inetd)
15  *
16  */
17
18 #include "sysdep.h"
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <fcntl.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include <time.h>
26 #include <pwd.h>
27 #include <errno.h>
28 #include <syslog.h>
29 #include "citadel.h"
30
31 #define LOCAL 0
32 #define REMOTE 1
33 #define UUCP 2
34 #define CCITADEL 3
35
36 #undef tolower
37 #define tolower(x) isupper(x) ? (x+'a'-'A') : x
38
39 char *monthdesc[] = {   "Jan","Feb","Mar","Apr","May","Jun",
40                         "Jul","Aug","Sep","Oct","Nov","Dec" };
41
42
43
44 void LoadInternetConfig();
45 void get_config();
46 int IsHostLocal();
47 struct config config;
48
49 char ALIASES[128];
50 char CIT86NET[128];
51 char SENDMAIL[128];
52 char FALLBACK[128];
53 char GW_DOMAIN[128];
54 char TABLEFILE[128];
55 char OUTGOING_FQDN[128];
56 int RUN_NETPROC = 1;
57
58
59 long conv_date(sdbuf)
60 char sdbuf[]; {
61         int a,b,cpos,tend,tval;
62         long now;
63         struct tm *tmbuf;
64         char dbuf[128];
65
66         strcpy(dbuf,sdbuf);
67         time(&now);
68         tmbuf = (struct tm *)localtime(&now);
69
70         /* get rid of + or - timezone mods */
71         for (a=0; a<strlen(dbuf); ++a) 
72                 if ((dbuf[a]=='+')||(dbuf[a]=='-'))
73                         do {
74                                 strcpy(&dbuf[a],&dbuf[a+1]);
75                                 } while ((dbuf[a]!=32)&&(dbuf[a]!=0));
76
77         /* try and extract the time by looking for colons */
78         cpos = (-1);
79         for (a=strlen(dbuf); a>=0; --a)
80                 if ((dbuf[a]==':')&&(atoi(&dbuf[a-1])!=0)) cpos=a;
81         if (cpos>=0) {
82                 cpos = cpos - 2;
83                 tend = strlen(dbuf);
84                 for (a=tend; a>=cpos; --a) if (dbuf[a]==' ') tend=a;
85
86                 tmbuf->tm_hour = atoi(&dbuf[cpos]);
87                 tmbuf->tm_min = atoi(&dbuf[cpos+3]);
88                 tmbuf->tm_sec = atoi(&dbuf[cpos+6]);
89
90                 do {
91                         strcpy(&dbuf[cpos],&dbuf[cpos+1]);
92                         } while ((dbuf[cpos]!=32)&&(dbuf[cpos]!=0));
93                 }
94
95         /* next try to extract a month */
96         
97         tval = (-1);
98         for (a=0; a<strlen(dbuf); ++a)
99                 for (b=0; b<12; ++b)
100                         if (!strncmp(&dbuf[a],monthdesc[b],3)) {
101                                 tval = b;
102                                 cpos = a;
103                                 }
104         if (tval >= 0) {
105                 tmbuf->tm_mon = tval;
106                 strcpy(&dbuf[cpos],&dbuf[cpos+3]);
107                 }
108
109         /* now the year */
110
111         for (a=0; a<strlen(dbuf); ++a)
112                 if ((atoi(&dbuf[a])>=1900) && (dbuf[a]!=32)) {
113                         tmbuf->tm_year = atoi(&dbuf[a]) - 1900;
114                         strcpy(&dbuf[a],&dbuf[a+4]);
115                         }
116
117         /* whatever's left is the mday (hopefully) */
118
119         for (a=0; a<strlen(dbuf); ++a)
120                 if ((dbuf[a]!=32)&&(atoi(&dbuf[a])>=1)&&(atoi(&dbuf[a])<=31)
121                    && ( (a==0)||(dbuf[a-1]==' ') ) ) {
122                         tmbuf->tm_mday = atoi(&dbuf[a]);
123                         strcpy(&dbuf[a],&dbuf[a+2]);
124                         }
125
126         return((long)mktime(tmbuf));
127         }
128
129
130 #ifdef NO_STRERROR
131 /*
132  * replacement strerror() for systems that don't have it
133  */
134 char *strerror(e)
135 int e; {
136         static char buf[32];
137
138         sprintf(buf,"errno = %d",e);
139         return(buf);
140         }
141 #endif
142
143 int haschar(st,ch)
144 char st[];
145 int ch; {
146         int a,b;
147         b=0;
148         for (a=0; a<strlen(st); ++a) if (st[a]==ch) ++b;
149         return(b);
150         }
151
152 void strip_trailing_whitespace(buf)
153 char buf[]; {
154         while(isspace(buf[strlen(buf)-1]))
155                 buf[strlen(buf)-1]=0;
156         }
157
158 /* strip leading and trailing spaces */
159 void striplt(buf)
160 char buf[]; {
161         while ( (strlen(buf)>0) && (buf[0]==32) ) strcpy(buf,&buf[1]);
162         while (buf[strlen(buf)-1] == 32) buf[strlen(buf)-1] = 0;
163         }
164
165
166 /*
167  * Check to see if a given FQDN really maps to a Citadel network node
168  */
169 void host_alias(char host[]) {
170
171         int a;
172
173         /* What name is the local host known by? */
174         /* if (!strcasecmp(host, config.c_fqdn)) { */
175         if (IsHostLocal(host)) {
176                 strcpy(host, config.c_nodename);
177                 return;
178                 }
179
180         /* Other hosts in the gateway domain? */
181         for (a=0; a<strlen(host); ++a) {
182                 if ((host[a]=='.') && (!strcasecmp(&host[a+1], GW_DOMAIN))) {
183                         host[a] = 0;
184                         for (a=0; a<strlen(host); ++a) {
185                                 if (host[a]=='.') host[a] = 0;
186                                 }
187                         return;
188                         }
189                 }
190
191         /* Otherwise, do nothing... */
192         }
193
194
195
196 /*
197  * Split an RFC822-style address into userid, host, and full name
198  */
199 void process_rfc822_addr(rfc822,user,node,name)
200 char rfc822[];
201 char user[];
202 char node[];
203 char name[];  {
204         int a;
205
206         /* extract full name - first, it's From minus <userid> */
207         strcpy(name,rfc822);
208         for (a=0; a<strlen(name); ++a) if (name[a]=='<') {
209                 do {
210                         strcpy(&name[a],&name[a+1]);
211                         } while ( (strlen(name) > 0) && (name[a]!='>') );
212                 strcpy(&name[a],&name[a+1]);
213                 }
214
215         /* strip anything to the left of a bang */
216         while ( (strlen(name)>0) && (haschar(name,'!')>0) ) 
217                 strcpy(name,&name[1]);
218
219         /* and anything to the right of a @ or % */
220         for (a=0; a<strlen(name); ++a) {
221                 if (name[a]=='@') name[a]=0;
222                 if (name[a]=='%') name[a]=0;
223                 }
224
225         /* but if there are parentheses, that changes the rules... */
226         if ( (haschar(rfc822,'(') == 1) && (haschar(rfc822,')') == 1) ) {
227                 strcpy(name,rfc822);
228                 while ( (strlen(name) > 0) && (name[0]!='(') ) {
229                         strcpy(&name[0],&name[1]);
230                         }
231                 strcpy(&name[0],&name[1]);
232                 for (a=0; a<strlen(name); ++a)
233                          if (name[a]==')') name[a]=0;
234                 }
235
236         /* but if there are a set of quotes, that supersedes everything */
237         if (haschar(rfc822,34)==2) {
238                 strcpy(name,rfc822);
239                 while ( (strlen(name) > 0) && (name[0]!=34) ) {
240                         strcpy(&name[0],&name[1]);
241                         }
242                 strcpy(&name[0],&name[1]);
243                 for (a=0; a<strlen(name); ++a)
244                         if (name[a]==34) name[a]=0;
245                 }
246
247         /* extract user id */
248         strcpy(user,rfc822);
249
250         /* first get rid of anything in parens */
251         for (a=0; a<strlen(user); ++a) if (user[a]=='(') {
252                 do {
253                         strcpy(&user[a],&user[a+1]);
254                         } while ( (strlen(user) > 0) && (user[a]!=')') );
255                 strcpy(&user[a],&user[a+1]);
256                 }
257
258         /* if there's a set of angle brackets, strip it down to that */
259         if ( (haschar(user,'<') == 1) && (haschar(user,'>') == 1) ) {
260                 while ( (strlen(user) > 0) && (user[0]!='<') ) {
261                         strcpy(&user[0],&user[1]);
262                         }
263                 strcpy(&user[0],&user[1]);
264                 for (a=0; a<strlen(user); ++a)
265                          if (user[a]=='>') user[a]=0;
266                 }
267
268         /* strip anything to the left of a bang */
269         while ( (strlen(user)>0) && (haschar(user,'!')>0) ) 
270                 strcpy(user,&user[1]);
271
272         /* and anything to the right of a @ or % */
273         for (a=0; a<strlen(user); ++a) {
274                 if (user[a]=='@') user[a]=0;
275                 if (user[a]=='%') user[a]=0;
276                 }
277
278
279         
280         /* extract node name */
281         strcpy(node, rfc822);
282
283         /* first get rid of anything in parens */
284         for (a=0; a<strlen(node); ++a) if (node[a]=='(') {
285                 do {
286                         strcpy(&node[a],&node[a+1]);
287                         } while ( (strlen(node) > 0) && (node[a]!=')') );
288                 strcpy(&node[a],&node[a+1]);
289                 }
290
291         /* if there's a set of angle brackets, strip it down to that */
292         if ( (haschar(node,'<') == 1) && (haschar(node,'>') == 1) ) {
293                 while ( (strlen(node) > 0) && (node[0]!='<') ) {
294                         strcpy(&node[0],&node[1]);
295                         }
296                 strcpy(&node[0],&node[1]);
297                 for (a=0; a<strlen(node); ++a)
298                          if (node[a]=='>') node[a]=0;
299                 }
300
301         /* strip anything to the left of a @ */
302         while ( (strlen(node)>0) && (haschar(node,'@')>0) ) 
303                 strcpy(node,&node[1]);
304
305         /* strip anything to the left of a % */
306         while ( (strlen(node)>0) && (haschar(node,'%')>0) ) 
307                 strcpy(node,&node[1]);
308
309         /* reduce multiple system bang paths to node!user */
310         while ( (strlen(node)>0) && (haschar(node,'!')>1) ) 
311                 strcpy(node,&node[1]);
312
313         /* now get rid of the user portion of a node!user string */
314         for (a=0; a<strlen(node); ++a) if (node[a]=='!') node[a]=0;
315
316
317
318         /* strip leading and trailing spaces in all strings */
319         striplt(user);
320         striplt(node);
321         striplt(name);
322         }
323
324 /*
325  * Copy line by line, ending at EOF or a "." 
326  */
327 void loopcopy(FILE *to, FILE *from) {
328         char buf[1024];
329         char *r;
330         
331         while (1) {
332                 r = fgets(buf, sizeof(buf), from);
333                 if (r == NULL) return;
334                 strip_trailing_whitespace(buf);
335                 if (!strcmp(buf, ".")) return;
336                 fprintf(to, "%s\n", buf);
337                 }
338         }
339
340
341 /*
342  * pipe message through netproc
343  */
344 void do_citmail(char recp[], int dtype) {
345
346         long now;
347         FILE *temp;
348         int a;
349         char buf[128];
350         char from[512];
351         char userbuf[256];
352         char frombuf[256];
353         char nodebuf[256];
354         char destsys[256];
355         char subject[256];
356         char targetroom[256];
357
358
359         if (dtype==REMOTE) {
360
361                 /* get the Citadel node name out of the path */
362                 strncpy(destsys, recp, sizeof(destsys) );
363                 for (a=0; a<strlen(destsys); ++a) {
364                         if ((destsys[a]=='!')||(destsys[a]=='.')) {
365                                 destsys[a]=0;
366                                 }
367                         }
368
369                 /* chop the system name out, so we're left with a user */
370                 while (haschar(recp,'!')) strcpy(recp,&recp[1]);
371                 }
372
373         /* Convert underscores to spaces */
374         for (a=0; a<strlen(recp); ++a) if (recp[a]=='_') recp[a]=' ';
375
376         /* Are we delivering to a room instead of a user? */
377         if (!strncasecmp(recp, "room ", 5)) {
378                 strcpy(targetroom, &recp[5]);
379                 strcpy(recp, "");
380                 }
381         else {
382                 strcpy(targetroom, "Mail");
383                 }
384
385         time(&now);
386         sprintf(from, "postmaster@%s", config.c_nodename);
387
388         sprintf(buf, "./network/spoolin/citmail.%d", getpid());
389         temp = fopen(buf,"w");
390
391         putc(255,temp); putc(MES_NORMAL,temp); putc(1,temp);
392         strcpy(subject,"");
393         strcpy(nodebuf, config.c_nodename);
394         do {
395                 if (fgets(buf,128,stdin) == NULL) strcpy(buf, ".");
396                 strip_trailing_whitespace(buf);
397
398                 if (!strncmp(buf,"Subject: ",9)) strcpy(subject,&buf[9]);
399                 if (!strncmp(buf,"Date: ",6)) now = conv_date(&buf[6]);
400                 if (!strncmp(buf,"From: ",6)) strcpy(from, &buf[6]);
401                 } while ( (strcmp(buf, ".")) && (strcmp(buf, "")) );
402
403         process_rfc822_addr(from, userbuf, nodebuf, frombuf);
404
405         /* now convert it to Citadel format */
406         fprintf(temp,"P%s@%s%c", userbuf, nodebuf, 0);
407         fprintf(temp,"T%ld%c", now, 0);
408         fprintf(temp,"A%s%c", userbuf, 0);
409
410         if (strlen(targetroom) > 0) {
411                 fprintf(temp, "O%s%c", targetroom, 0);
412                 }
413         else {
414                 fprintf(temp, "OMail%c", 0);
415                 }
416
417         fprintf(temp,"N%s%c", nodebuf, 0);
418         fprintf(temp,"H%s%c", frombuf, 0);
419         if (dtype==REMOTE) {
420                 fprintf(temp,"D%s%c", destsys, 0);
421                 }
422
423         if (strlen(recp) > 0) {
424                 fprintf(temp,"R%s%c", recp, 0);
425                 }
426
427         if (strlen(subject)>0) {
428                 fprintf(temp,"U%s%c", subject, 0);
429                 }
430         putc('M',temp);
431         if (strcmp(buf, ".")) loopcopy(temp, stdin);
432         putc(0,temp);
433         fclose(temp);
434         }
435
436
437 void do_uudecode(target)
438 char *target;  {
439         static char buf[1024];
440         FILE *fp;
441         
442         sprintf(buf,"cd %s; uudecode",target);
443
444         fp=popen(buf,"w");
445         if (fp==NULL) return;
446         while (fgets(buf,1024,stdin)!=NULL) {
447                 fprintf(fp,"%s",buf);
448                 }
449         pclose(fp);
450
451         }
452
453 int alias(name)
454 char *name; {
455         FILE *fp;
456         int a;
457         char abuf[256];
458         
459         fp=fopen(ALIASES,"r");
460         if (fp==NULL) {
461                 syslog(LOG_ERR,"cannot open %s: %s",ALIASES,strerror(errno));
462                 return(2);
463                 }
464
465         while (fgets(abuf,256,fp)!=NULL) {
466                 strip_trailing_whitespace(abuf);
467                 for (a=0; a<strlen(abuf); ++a) {
468                         if (abuf[a]==',') {
469                                 abuf[a]=0;
470                                 if (!strcasecmp(name,abuf)) {
471                                         strcpy(name,&abuf[a+1]);
472                                         }
473                                 }
474                         }
475                 }
476         fclose(fp);
477         return(0);
478         }
479
480
481 void deliver(char recp[], int is_test, int deliver_to_ignet) {
482
483         /* various ways we can deliver mail... */
484
485         if (deliver_to_ignet) {
486                 syslog(LOG_NOTICE,"to Citadel network user %s",recp);
487                 if (is_test == 0) do_citmail(recp, REMOTE);
488                 }
489
490         else if (!strcmp(recp,"uudecode")) {
491                 syslog(LOG_NOTICE,"uudecoding to bit bucket directory");
492                 if (is_test == 0) do_uudecode(config.c_bucket_dir);
493                 }
494
495         else if (!strcmp(recp,"cit86net")) {
496                 syslog(LOG_NOTICE,"uudecoding to Cit86net spool");
497                 if (is_test == 0) {
498                         do_uudecode(CIT86NET);
499                         system("exec ./BatchTranslate86");
500                         }
501                 }
502
503         else if (!strcmp(recp,"null")) {
504                 syslog(LOG_NOTICE,"zapping nulled message");
505                 }
506
507         else {
508                 /* Otherwise, the user is local (or an unknown name was
509                  * specified, in which case we let netproc handle the bounce)
510                  */
511                 syslog(LOG_NOTICE,"to Citadel recipient %s",recp);
512                 if (is_test == 0) do_citmail(recp, LOCAL);
513                 }
514
515         }
516
517
518
519 void main(argc,argv)
520 int argc;
521 char *argv[]; {
522         int is_test = 0;
523         int deliver_to_ignet = 0;
524         int smtp = 0;
525         static char recp[1024], buf[1024];
526         static char user[1024], node[1024], name[1024];
527         int a;
528
529         openlog("citmail", LOG_PID, LOG_USER);
530         get_config();
531         LoadInternetConfig();
532
533         if (!strcmp(argv[1],"-t")) {
534                 is_test = 1;
535                 syslog(LOG_NOTICE,"test mode - will not deliver");
536                 }
537         if (!strcmp(argv[1], "-i")) {
538                 smtp = 1;
539                 syslog(LOG_NOTICE,"started as an SMTP daemon");
540                 }
541
542
543         if (smtp) {
544                 strcpy(recp, "");
545                 }
546         else if (is_test == 0) {
547                 strcpy(recp,argv[1]);
548                 }
549         else {
550                 strcpy(recp,argv[2]);
551                 }
552
553
554         if (smtp) {
555                 /*** SMTP delivery mode ***/
556
557                 printf("200 Citadel/UX SMTP gateway ready.\n");
558         
559                 do {
560                         fflush(stdout);
561                         fflush(stderr);
562                         fgets(buf, 1024, stdin);
563                         while ( (strlen(buf)>0) && (buf[strlen(buf)-1]>0) && (buf[strlen(buf)-1]<32) ) {
564                                 buf[strlen(buf)-1] = 0;
565                                 }
566
567                         /* null-pad to allow some lazy compares */
568                         buf[strlen(buf)+1] = 0;
569                         buf[strlen(buf)+2] = 0;
570                         buf[strlen(buf)+3] = 0;
571         
572                         if (!strncasecmp(buf, "QUIT", 4)) {
573                                 printf("221 Later, dude.\n");
574                                 }
575                         else if (!strncasecmp(buf, "HELP", 4)) {
576                                 printf("214 You think _you_ need help?\n");
577                                 }
578                         else if (!strncasecmp(buf, "HELO", 4)) {
579                                 printf("250 Howdy ho, Mr. Hankey!\n");
580                                 }
581                         else if (!strncasecmp(buf, "MAIL", 4)) {
582                                 printf("250 Sure, whatever...\n");
583                                 }
584
585
586                         else if (!strncasecmp(buf, "RCPT To: ", 9)) {
587                                 if (strlen(recp) > 0) {
588                                         printf("571 Multiple recipients not supported.\n");
589                                         }
590                                 else {
591                                         strcpy(recp, &buf[9]);
592                                         if (haschar(recp, ',')) {
593                                                 printf("571 Multiple recipients not supported.\n");
594                                                 strcpy(recp, "");
595                                                 }
596                                         else {
597                                                 syslog(LOG_NOTICE,"recp: %s",recp);
598                                                 for (a=0; a<2; ++a) {
599                                                         alias(recp);
600                                                         }
601
602                                                 /* did we alias it back to a remote address? */
603                                                 if (    (haschar(recp,'%'))
604                                                 ||      (haschar(recp,'@'))
605                                                 ||      (haschar(recp,'!')) ) {
606         
607                                                         process_rfc822_addr(recp, user, node, name);
608                                                         host_alias(node);
609                 
610                                                         /* If there are dots, it's an Internet host, so feed it
611                                                         * back to an external mail transport agent such as sendmail.
612                                                         */
613                                                         if (haschar(node, '.')) {
614                                                                 printf("571 Away with thee, spammer!\n");
615                                                                 strcpy(recp, "");
616                                                                 }
617                 
618                                                         /* Otherwise, we're dealing with Citadel mail. */
619                                                         else {
620                                                                 sprintf(recp, "%s!%s", node, user);
621                                                                 deliver_to_ignet = 1;
622                                                                 printf("250 IGnet recipient.\n");
623                                                                 }
624                                                         }
625                                                 else {
626                                                         printf("250 Local recipient.\n");
627                                                         }
628         
629                                                 }
630         
631                                         }
632                                 }
633
634
635
636                         else if (!strncasecmp(buf, "RCPT", 4)) {
637                                 printf("501 Only 'To:' commands are supported.\n");
638                                 }
639                         else if (!strncasecmp(buf, "DATA", 4)) {
640                                 if (strlen(recp) > 0) {
641                                         printf("354 Sock it to me, baby...\n");
642                                         fflush(stdout);
643                                         deliver(recp, is_test, deliver_to_ignet);
644                                         printf("250 Cool beans!\n");
645                                         }
646                                 else {
647                                         printf("503 No recipient has been specified.\n");
648                                         }
649                                 }
650                         else {
651                                 printf("500 Huh?\n");
652                                 }
653         
654                         } while (strncasecmp(buf,"QUIT",4));
655                 }
656
657         else {
658                 /*** Non-SMTP delivery mode ***/
659                 syslog(LOG_NOTICE,"recp: %s",recp);
660                 for (a=0; a<2; ++a) {
661                         alias(recp);
662                         }
663         
664                 /* did we alias it back to a remote address? */
665                 if (    (haschar(recp,'%'))
666                 ||      (haschar(recp,'@'))
667                 ||      (haschar(recp,'!')) ) {
668         
669                         process_rfc822_addr(recp, user, node, name);
670                         host_alias(node);
671                 
672                         /* If there are dots, it's an Internet host, so feed it
673                         * back to an external mail transport agent such as sendmail.
674                         */
675                         if (haschar(node, '.')) {
676                                 sprintf(buf, SENDMAIL, recp);
677                                 system(buf);
678                                 exit(0);
679                                 }
680         
681                         /* Otherwise, we're dealing with Citadel mail. */
682                         else {
683                                 sprintf(recp, "%s!%s", node, user);
684                                 deliver_to_ignet = 1;
685                                 }
686         
687                         }
688         
689                 deliver(recp, is_test, deliver_to_ignet);
690                 }
691         
692         closelog();
693         if (RUN_NETPROC) execlp("./netproc","netproc",NULL);
694         exit(0);
695         }
696