1 /*:ts=8*/
2 /*****************************************************************************
3 * FIDOGATE --- Gateway UNIX Mail/News <-> FIDO NetMail/EchoMail
4 *
5 * $Id: ffxqt.c,v 4.27 2004/08/22 20:19:11 n0ll Exp $
6 *
7 * Process incoming ffx control and data files
8 *
9 * With full supporting cast of busy files and locking. ;-)
10 *
11 *****************************************************************************
12 * Copyright (C) 1990-2004
13 * _____ _____
14 * | |___ | Martin Junius <mj.at.n0ll.dot.net>
15 * | | | | | | Radiumstr. 18
16 * |_|_|_|@home| D-51069 Koeln, Germany
17 *
18 * This file is part of FIDOGATE.
19 *
20 * FIDOGATE is free software; you can redistribute it and/or modify it
21 * under the terms of the GNU General Public License as published by the
22 * Free Software Foundation; either version 2, or (at your option) any
23 * later version.
24 *
25 * FIDOGATE is distributed in the hope that it will be useful, but
26 * WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 * General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with FIDOGATE; see the file COPYING. If not, write to the Free
32 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
33 *****************************************************************************/
34
35 #include "fidogate.h"
36 #include "getopt.h"
37
38 #include <sys/wait.h>
39
40
41
42 #define PROGRAM "ffxqt"
43 #define VERSION "$Revision: 4.27 $"
44 #define CONFIG DEFAULT_CONFIG_FFX
45
46
47 #define MAXFFXCMD 16
48
49
50
51 /*
52 * Info from ffx control file
53 */
54 typedef struct st_ffx
55 {
56 char *job; /* Job name */
57 char *name; /* .ffx file name */
58 Node from, to; /* FTN addresses */
59 char *fqdn; /* Sender FQDN */
60 char *passwd; /* Password */
61 char *cmd; /* Command with args */
62 char *in; /* stdin file */
63 char *decompr; /* Decompressor */
64 char *file;
65 int status; /* Status: TRUE=read # EOF */
66 } FFX;
67
68
69
70 /*
71 * List of command from config.ffx
72 */
73 typedef struct st_ffxcmd
74 {
75 char type; /* 'C'=command, 'U'=uncompress */
76 char *name; /* Command name */
77 char *cmd; /* Command to execute */
78 } FFXCmd;
79
80
81
82 /*
83 * Prototypes
84 */
85 void parse_ffxcmd (void);
86 char *find_ffxcmd (int, char *);
87 int do_ffx (int);
88 void remove_bad (char *);
89 FFX *parse_ffx (char *);
90 int exec_ffx (FFX *);
91 int run_ffx_cmd (char *, char *, char *, char *, char *);
92
93 void short_usage (void);
94 void usage (void);
95
96
97
98 /*
99 * Command line options
100 */
101 static int g_flag = 0; /* Processing grade */
102
103
104 /*
105 * Commands
106 */
107 static FFXCmd l_cmd[MAXFFXCMD];
108 static int n_cmd = 0;
109
110
111
112 /*
113 * Parse FFXCommand / FFXUncompress config.ffx parameters
114 */
parse_ffxcmd()115 void parse_ffxcmd()
116 {
117 char *p, *name, *cmd;
118
119 /* Commands */
120 for(p = cf_get_string("FFXCommand", TRUE);
121 p && *p;
122 p = cf_get_string("FFXCommand", FALSE) )
123 {
124 if(n_cmd >= MAXFFXCMD)
125 continue;
126 name = xstrtok(p , "\n\t ");
127 cmd = xstrtok(NULL, "\n");
128 if(!name || !cmd)
129 continue;
130 while(isspace(*cmd))
131 cmd++;
132
133 debug(8, "config: FFXCommand %s %s", name, cmd);
134
135 BUF_EXPAND(buffer, cmd);
136 l_cmd[n_cmd].type = 'C';
137 l_cmd[n_cmd].name = name;
138 l_cmd[n_cmd].cmd = strsave(buffer);
139 n_cmd++;
140 }
141 }
142
143
144
145 /*
146 * Find FFXCommand / FFXUncompress
147 */
find_ffxcmd(int type,char * name)148 char *find_ffxcmd(int type, char *name)
149 {
150 int i;
151
152 for(i=0; i<n_cmd; i++)
153 if(type==l_cmd[i].type && strieq(name, l_cmd[i].name))
154 return l_cmd[i].cmd;
155
156 return NULL;
157 }
158
159
160
161 /*
162 * Processs *.ffx control files in Binkley inbound directory
163 */
do_ffx(int t_flag)164 int do_ffx(int t_flag)
165 {
166 FFX *ffx;
167 char *name;
168 Passwd *pwd;
169 char *passwd;
170 char buf[MAXPATH];
171 char pattern[16];
172
173 BUF_COPY(pattern, "f???????.ffx");
174 if(g_flag)
175 pattern[1] = g_flag;
176
177 BUF_EXPAND(buffer, cf_p_pinbound());
178 if( chdir(buffer) == -1 )
179 {
180 logit("$ERROR: can't chdir %s", buffer);
181 return ERROR;
182 }
183
184 dir_sortmode(DIR_SORTMTIME);
185 if(dir_open(".", pattern, TRUE) == ERROR)
186 {
187 logit("$ERROR: can't open directory .");
188 return ERROR;
189 }
190
191 for(name=dir_get(TRUE); name; name=dir_get(FALSE))
192 {
193 debug(1, "ffxqt: control file %s", name);
194
195 ffx = parse_ffx(name);
196 if(!ffx)
197 /* No error, this might be a file still being written to */
198 continue;
199
200 if(!ffx->status) /* BSY test, if not EOF */
201 if(bink_bsy_test(&ffx->from)) /* Skip if busy */
202 {
203 debug(3, "ffxqt: %s busy, skipping", znfp1(&ffx->from));
204 continue;
205 }
206
207 /*
208 * Get password for from node
209 */
210 if( (pwd = passwd_lookup("ffx", &ffx->from)) )
211 passwd = pwd->passwd;
212 else
213 passwd = NULL;
214 if(passwd)
215 debug(3, "ffxqt: password %s", passwd);
216
217 /*
218 * Require password unless -t option is given
219 */
220 if(!t_flag && !passwd)
221 {
222 logit("ERROR: %s: no password for %s in PASSWD",
223 name, znfp1(&ffx->from) );
224 goto rename_to_bad;
225 }
226
227 /*
228 * Check password
229 */
230 if(passwd)
231 {
232 if(ffx->passwd)
233 {
234 if(stricmp(passwd, ffx->passwd))
235 {
236 logit("ERROR: %s: wrong password from %s: ours=%s his=%s",
237 name, znfp1(&ffx->from), passwd,
238 ffx->passwd );
239 goto rename_to_bad;
240 }
241 }
242 else
243 {
244 logit("ERROR: %s: no password from %s: ours=%s", name,
245 znfp1(&ffx->from), passwd );
246 goto rename_to_bad;
247 }
248 }
249
250 logit("job %s: from %s data %s (%ldb) / %s",
251 ffx->job, znfp1(&ffx->from), ffx->file, check_size(ffx->file),
252 ffx->cmd);
253
254 if(exec_ffx(ffx) == ERROR)
255 {
256 logit("%s: command failed", name);
257 rename_to_bad:
258 /*
259 * Error: rename .ffx -> .bad
260 */
261 str_change_ext(buf, sizeof(buf), name, "bad");
262 rename(name, buf);
263 logit("%s: renamed to %s", name, buf);
264 }
265
266 tmps_freeall();
267 }
268
269 dir_close();
270
271 return OK;
272 }
273
274
275
276 /*
277 * Remove "bad" character from string
278 */
remove_bad(char * s)279 void remove_bad(char *s)
280 {
281 char *p = s;
282
283 while(*p)
284 if(*p>=' ' && *p < 127)
285 switch(*p)
286 {
287 case '$':
288 case '&':
289 case '(':
290 case ')':
291 case ';':
292 case '<':
293 case '>':
294 case '^':
295 case '`':
296 case '|':
297 p++; /* skip */
298 break;
299 default:
300 *s++ = *p++;
301 break;
302 }
303 else
304 p++;
305
306 *s = 0;
307 }
308
309
310
311 /*
312 * Parse control file and read into memory
313 */
314 #define SEP " \t\r\n"
315
parse_ffx(char * name)316 FFX *parse_ffx(char *name)
317 {
318 FILE *fp;
319 static FFX ffx;
320 char *buf, *p;
321
322 /**FIXME: this isn't really clean**/
323 xfree(ffx.job); ffx.job = NULL;
324 xfree(ffx.name); ffx.name = NULL;
325 xfree(ffx.fqdn); ffx.fqdn = NULL;
326 xfree(ffx.passwd); ffx.passwd = NULL;
327 xfree(ffx.cmd); ffx.cmd = NULL;
328 xfree(ffx.in); ffx.in = NULL;
329 xfree(ffx.decompr); ffx.decompr = NULL;
330 xfree(ffx.file); ffx.file = NULL;
331 ffx.from.zone = ffx.from.net = ffx.from.node = ffx.from.point = 0;
332 ffx.to .zone = ffx.to .net = ffx.to .node = ffx.to .point = 0;
333 ffx.status = FALSE;
334 ffx.name = strsave(name);
335
336 fp = fopen(ffx.name, R_MODE);
337 if(!fp)
338 {
339 logit("$ERROR: can't open %s", ffx.name);
340 return NULL;
341 }
342
343 while((buf = fgets(buffer, BUFFERSIZE, fp)))
344 {
345 strip_crlf(buf);
346
347 switch(*buf)
348 {
349 case '#':
350 /* Comment */
351 if(!strncmp(buf, "# EOF", 5))
352 ffx.status = TRUE;
353 continue;
354
355 case 'J': /* Job name */
356 ffx.job = strsave(buf + 2);
357 break;
358
359 case 'U':
360 /* User name */
361 p = strtok(buf+2, SEP);
362 /* From node */
363 p = strtok(NULL , SEP);
364 if(p)
365 asc_to_node(p, &ffx.from, FALSE);
366 /* To node */
367 p = strtok(NULL , SEP);
368 if(p)
369 asc_to_node(p, &ffx.to, FALSE);
370 /* FQDN */
371 p = strtok(NULL , SEP);
372 if(p)
373 ffx.fqdn = strsave(p);
374 break;
375
376 case 'I':
377 /* data file */
378 p = strtok(buf+2, SEP);
379 if(p)
380 {
381 ffx.in = strsave(p);
382 remove_bad(ffx.in);
383 }
384 /* decompressor */
385 p = strtok(NULL , SEP);
386 if(p)
387 {
388 ffx.decompr = strsave(p);
389 remove_bad(ffx.decompr);
390 }
391 break;
392
393 case 'F':
394 ffx.file = strsave(buf + 2);
395 remove_bad(ffx.file);
396 break;
397
398 case 'C':
399 /* command */
400 ffx.cmd = strsave(buf + 2);
401 remove_bad(ffx.cmd);
402 break;
403
404 case 'P':
405 ffx.passwd = strsave(buf + 2);
406 break;
407
408 }
409 }
410
411 fclose(fp);
412
413 if(!ffx.cmd)
414 return NULL;
415
416 debug(3, "ffx: user=%s fqdn=%s", ffx.name, ffx.fqdn ? ffx.fqdn : "-none-");
417 debug(3, " %s -> %s", znfp1(&ffx.from), znfp2(&ffx.to));
418 debug(3, " J %s", ffx.job ? ffx.job : "");
419 debug(3, " I %s %s",
420 ffx.in ? ffx.in : "",
421 ffx.decompr ? ffx.decompr : "" );
422 debug(3, " F %s", ffx.file);
423 debug(3, " C %s", ffx.cmd);
424 debug(3, " P %s", ffx.passwd ? ffx.passwd : "");
425
426 return &ffx;
427 }
428
429
430
431 /*
432 * Execute command in ffx
433 */
exec_ffx(FFX * ffx)434 int exec_ffx(FFX *ffx)
435 {
436 int ret;
437 char *name, *args=NULL, *cmd_c=NULL;
438
439 /* Extract command name and args */
440 name = strtok(ffx->cmd, "\n\t ");
441 args = strtok(NULL, "\n" );
442 if(!name)
443 return ERROR;
444 if(!args)
445 args = "";
446 while(isspace(*args))
447 args++;
448
449 /* Find command and uncompressor */
450 cmd_c = find_ffxcmd('C', name);
451 if(!cmd_c)
452 {
453 logit("ERROR: no FFXCommand found for \"%s\"", name);
454 return ERROR;
455 }
456 if(ffx->decompr)
457 {
458 logit("ERROR: uncompressing no longer supported in this version");
459 return ERROR;
460 }
461
462 /* Execute */
463 debug(2, "Command: %s", cmd_c);
464 ret = run_ffx_cmd(cmd_c, name, ffx->fqdn, args, ffx->in);
465 debug(2, "Exit code=%d", ret);
466
467 if(ret == 0)
468 {
469 unlink(ffx->name);
470 if(ffx->file)
471 unlink(ffx->file);
472 return OK;
473 }
474
475 return ERROR;
476 }
477
478
479
480 /*
481 * Run ffx cmd using fork() and exec()
482 */
483 #define MAXARGS 256
484
run_ffx_cmd(char * cmd,char * cmd_name,char * cmd_fqdn,char * cmd_args,char * data)485 int run_ffx_cmd(char *cmd,
486 char *cmd_name, char *cmd_fqdn, char *cmd_args, char *data)
487 {
488 char *args[MAXARGS];
489 int n=0;
490 char *p;
491 pid_t pid;
492 int status;
493
494 /* compile args[] vector */
495 args[n++] = cmd_name;
496 if(cmd_fqdn && streq(cmd_name, "rmail"))
497 {
498 args[n++] = "-f";
499 args[n++] = cmd_fqdn;
500 }
501
502 for(p=strtok(cmd_args, SEP); p; p=strtok(NULL, SEP))
503 {
504 if(n >= MAXARGS-1)
505 {
506 logit("ERROR: too many args in run_ffx_cmd()");
507 return ERROR;
508 }
509 args[n++] = p;
510 }
511
512 args[n] = NULL;
513
514 #if 0
515 {
516 int i;
517 debug(7, "cmd=%s", cmd);
518 for(i=0; i<=n; i++)
519 debug(7, "args[%d] = %s", i, args[i]);
520 debug(7, "data=%s", data);
521 }
522 #endif
523
524 /* fork() and exec() */
525 pid = fork();
526 if(pid == ERROR)
527 {
528 logit("$ERROR: fork failed");
529 return ERROR;
530 }
531
532 if(pid)
533 {
534 /* parent */
535 if(waitpid(pid, &status, 0) == ERROR)
536 {
537 logit("$ERROR: waitpid failed");
538 return ERROR;
539 }
540 if(WIFEXITED(status))
541 return WEXITSTATUS(status);
542 if(WIFSIGNALED(status))
543 logit("ERROR: child %s caught signal %d", cmd, WTERMSIG(status));
544 else
545 logit("ERROR: child %s, exit status=%04x", cmd, status);
546 return ERROR;
547 }
548 else
549 {
550 /* child */
551 if(! freopen(data, R_MODE, stdin))
552 {
553 logit("ERROR: can't freopen stdin to %s", data);
554 exit(1);
555 }
556 if( execv(cmd, args) == ERROR )
557 logit("ERROR: execv %s failed", cmd);
558 exit(1);
559 }
560
561 /**NOT REACHED**/
562 return ERROR;
563 }
564
565
566
567 /*
568 * Usage messages
569 */
short_usage(void)570 void short_usage(void)
571 {
572 fprintf(stderr, "usage: %s [-options]\n", PROGRAM);
573 fprintf(stderr, " %s --help for more information\n", PROGRAM);
574 exit(EX_USAGE);
575 }
576
577
usage(void)578 void usage(void)
579 {
580 fprintf(stderr, "FIDOGATE %s %s %s\n\n",
581 version_global(), PROGRAM, version_local(VERSION) );
582
583 fprintf(stderr, "usage: %s [-options]\n\n", PROGRAM);
584 fprintf(stderr, "\
585 options: -g --grade G processing grade\n\
586 -I --inbound dir set inbound dir (default: PINBOUND)\n\
587 -t --insecure process ffx files without password\n\
588 \n\
589 -v --verbose more verbose\n\
590 -h --help this help\n\
591 -c --config name read config file (\"\" = none)\n\
592 -a --addr Z:N/F.P set FTN address\n\
593 -u --uplink-addr Z:N/F.P set FTN uplink address\n");
594
595 exit(0);
596 }
597
598
599
600 /***** main() ****************************************************************/
601
main(int argc,char ** argv)602 int main(int argc, char **argv)
603 {
604 int c;
605 char *I_flag=NULL;
606 int t_flag=FALSE;
607 char *c_flag=NULL;
608 char *a_flag=NULL, *u_flag=NULL;
609
610 int option_index;
611 static struct option long_options[] =
612 {
613 { "grade", 1, 0, 'g'}, /* grade */
614 { "insecure", 0, 0, 't'}, /* Insecure */
615 { "inbound", 1, 0, 'I'}, /* Set Binkley inbound */
616
617 { "verbose", 0, 0, 'v'}, /* More verbose */
618 { "help", 0, 0, 'h'}, /* Help */
619 { "config", 1, 0, 'c'}, /* Config file */
620 { "addr", 1, 0, 'a'}, /* Set FIDO address */
621 { "uplink-addr", 1, 0, 'u'}, /* Set FIDO uplink address */
622 { 0, 0, 0, 0 }
623 };
624
625 log_program(PROGRAM);
626
627 /* Init configuration */
628 cf_initialize();
629
630
631 while ((c = getopt_long(argc, argv, "g:tI:vhc:a:u:",
632 long_options, &option_index )) != EOF)
633 switch (c) {
634 /***** ffxqt options *****/
635 case 'g':
636 g_flag = *optarg;
637 break;
638 case 't':
639 t_flag = TRUE;
640 break;
641 case 'I':
642 I_flag = optarg;
643 break;
644
645 /***** Common options *****/
646 case 'v':
647 verbose++;
648 break;
649 case 'h':
650 usage();
651 break;
652 case 'c':
653 c_flag = optarg;
654 break;
655 case 'a':
656 a_flag = optarg;
657 break;
658 case 'u':
659 u_flag = optarg;
660 break;
661 default:
662 short_usage();
663 break;
664 }
665
666 /* Read config file */
667 cf_read_config_file(c_flag ? c_flag : CONFIG);
668
669 /* Process config options */
670 if(a_flag)
671 cf_set_addr(a_flag);
672 if(u_flag)
673 cf_set_uplink(u_flag);
674
675 cf_debug();
676
677 /*
678 * Process additional config statements
679 */
680 parse_ffxcmd();
681
682 if(I_flag)
683 cf_s_pinbound(I_flag);
684
685 passwd_init();
686
687
688 if(lock_program(PROGRAM, NOWAIT) == ERROR)
689 exit(EXIT_BUSY);
690
691 do_ffx(t_flag);
692
693 unlock_program(PROGRAM);
694
695
696 exit(0);
697 }
698