xref: /original-bsd/usr.bin/uucp/uuxqt/uuxqt.c (revision 7e7b101a)
1 #ifndef lint
2 static char sccsid[] = "@(#)uuxqt.c	5.5 (Berkeley) 04/10/85";
3 #endif
4 
5 #include "uucp.h"
6 #include <sys/stat.h>
7 #ifdef	NDIR
8 #include "ndir.h"
9 #else
10 #include <sys/dir.h>
11 #endif
12 #include <signal.h>
13 
14 #define BADCHARS	"&^|(`\\<>;\"{}\n'"
15 #define RECHECKTIME	60*10	/* 10 minutes */
16 
17 #define APPCMD(d) {\
18 char *p;\
19 for (p = d; *p != '\0';) *cmdp++ = *p++; *cmdp++ = ' '; *cmdp = '\0';}
20 
21 /*
22  *	uuxqt will execute commands set up by a uux command,
23  *	usually from a remote machine - set by uucp.
24  */
25 
26 #define	NCMDS	50
27 char *Cmds[NCMDS+1];
28 int Notify[NCMDS+1];
29 #define	NT_YES	0	/* if should notify on execution */
30 #define	NT_ERR	1	/* if should notify if non-zero exit status (-z equivalent) */
31 #define	NT_NO	2	/* if should not notify ever (-n equivalent) */
32 
33 extern int Nfiles;
34 char *strpbrk();
35 
36 int TransferSucceeded = 1;
37 int notiok = 1;
38 int nonzero = 0;
39 
40 char PATH[MAXFULLNAME] = "PATH=/bin:/usr/bin:/usr/ucb";
41 char Shell[MAXFULLNAME];
42 char HOME[MAXFULLNAME];
43 
44 extern char **environ;
45 char *nenv[] = {
46 	PATH,
47 	Shell,
48 	HOME,
49 	0
50 };
51 
52 /*  to remove restrictions from uuxqt
53  *  define ALLOK 1
54  *
55  *  to add allowable commands, add to the file CMDFILE
56  *  A line of form "PATH=..." changes the search path
57  */
58 main(argc, argv)
59 char *argv[];
60 {
61 	char xcmd[MAXFULLNAME];
62 	int argnok;
63 	int notiflg;
64 	char xfile[MAXFULLNAME], user[MAXFULLNAME], buf[BUFSIZ];
65 	char lbuf[MAXFULLNAME];
66 	char cfile[NAMESIZE], dfile[MAXFULLNAME];
67 	char file[NAMESIZE];
68 	char fin[MAXFULLNAME], sysout[NAMESIZE], fout[MAXFULLNAME];
69 	register FILE *xfp, *fp;
70 	FILE *dfp;
71 	char path[MAXFULLNAME];
72 	char cmd[BUFSIZ];
73 	char *cmdp, prm[1000], *ptr;
74 	char *getprm(), *lastpart();
75 	int uid, ret, ret2, badfiles;
76 	register int i;
77 	int stcico = 0;
78 	time_t xstart, xnow;
79 	char retstat[30];
80 	char **ep;
81 
82 	strcpy(Progname, "uuxqt");
83 	uucpname(Myname);
84 
85 	umask(WFMASK);
86 	Ofn = 1;
87 	Ifn = 0;
88 	while (argc>1 && argv[1][0] == '-') {
89 		switch(argv[1][1]){
90 		case 'x':
91 			chkdebug();
92 			Debug = atoi(&argv[1][2]);
93 			if (Debug <= 0)
94 				Debug = 1;
95 			break;
96 		default:
97 			fprintf(stderr, "unknown flag %s\n", argv[1]);
98 				break;
99 		}
100 		--argc;  argv++;
101 	}
102 
103 	DEBUG(4, "\n\n** START **\n", CNULL);
104 	ret = subchdir(Spool);
105 	ASSERT(ret >= 0, "CHDIR FAILED", Spool, ret);
106 	strcpy(Wrkdir, Spool);
107 	uid = getuid();
108 	guinfo(uid, User, path);
109 	setgid(getegid());
110 	setuid(geteuid());
111 
112 	DEBUG(4, "User - %s\n", User);
113 	if (ulockf(X_LOCK, (time_t)  X_LOCKTIME) != 0)
114 		exit(0);
115 
116 	fp = fopen(CMDFILE, "r");
117 	if (fp == NULL) {
118 		logent(CANTOPEN, CMDFILE);
119 		Cmds[0] = "rmail";
120 		Cmds[1] = "rnews";
121 		Cmds[2] = "ruusend";
122 		Cmds[3] = NULL;
123 		goto doprocess;
124 	}
125 	DEBUG(5, "%s opened\n", CMDFILE);
126 	for (i=0; i<NCMDS && cfgets(xcmd, sizeof(xcmd), fp) != NULL; i++) {
127 		int j;
128 		/* strip trailing whitespace */
129 		for (j = strlen(xcmd)-1; j >= 0; --j)
130 			if (xcmd[j] == '\n' || xcmd[j] == ' ' || xcmd[j] == '\t')
131 				xcmd[j] = '\0';
132 			else
133 				break;
134 		/* look for imbedded whitespace */
135 		for (; j >= 0; --j)
136 			if (xcmd[j] == '\n' || xcmd[j] == ' ' || xcmd[j] == '\t')
137 				break;
138 		/* skip this entry if it has embedded whitespace */
139 		/* This defends against a bad PATH=, for example */
140 		if (j >= 0) {
141 			logent(xcmd, "BAD WHITESPACE");
142 			continue;
143 		}
144 		if (strncmp(xcmd, "PATH=", 5) == 0) {
145 			strcpy(PATH, xcmd);
146 			i--;	/*kludge */
147 			continue;
148 		}
149 		DEBUG(5, "xcmd = %s\n", xcmd);
150 
151 		if ((ptr = index(xcmd, ',')) != NULL) {
152 			*ptr++ = '\0';
153 			if (strncmp(ptr, "Err", 3) == SAME)
154 				Notify[i] = NT_ERR;
155 			else if (strcmp(ptr, "No") == SAME)
156 				Notify[i] = NT_NO;
157 			else
158 				Notify[i] = NT_YES;
159 		} else
160 			Notify[i] = NT_YES;
161 		if ((Cmds[i] = malloc((unsigned)(strlen(xcmd)+1))) == NULL) {
162 			DEBUG(1, "MALLOC FAILED", CNULL);
163 			break;
164 		}
165 		strcpy(Cmds[i], xcmd);
166 	}
167 	Cmds[i] = CNULL;
168 	fclose(fp);
169 
170 doprocess:
171 
172 	(void) sprintf(HOME, "HOME=%s", Spool);
173 	(void) sprintf(Shell, "SHELL=%s", SHELL);
174 	environ = nenv; /* force use if our environment */
175 
176 	DEBUG(11,"path = %s\n", getenv("PATH"));
177 
178 	DEBUG(4, "process %s\n", CNULL);
179 	time(&xstart);
180 	while (gtxfile(xfile) > 0) {
181 		ultouch();
182 		/* if /etc/nologin exists, exit cleanly */
183 		if (nologinflag) {
184 			logent(NOLOGIN, "UUXQT SHUTDOWN");
185 			if (Debug)
186 				logent("debugging", "continuing anyway");
187 			else
188 				break;
189 		}
190 		DEBUG(4, "xfile - %s\n", xfile);
191 
192 		xfp = fopen(subfile(xfile), "r");
193 		ASSERT(xfp != NULL, CANTOPEN, xfile, 0);
194 
195 		/*  initialize to default  */
196 		strcpy(user, User);
197 		strcpy(fin, DEVNULL);
198 		strcpy(fout, DEVNULL);
199 		sprintf(sysout, "%.7s", Myname);
200 		badfiles = 0;
201 		while (fgets(buf, BUFSIZ, xfp) != NULL) {
202 			switch (buf[0]) {
203 			case X_USER:
204 				sscanf(&buf[1], "%s %s", user, Rmtname);
205 				break;
206 			case X_RETURNTO:
207 				sscanf(&buf[1], "%s", user);
208 				break;
209 			case X_STDIN:
210 				sscanf(&buf[1], "%s", fin);
211 				i = expfile(fin);
212 				/* rti!trt: do not check permissions of
213 				 * vanilla spool file */
214 				if (i != 0
215 				 && (chkpth("", "", fin) || anyread(fin) != 0))
216 					badfiles = 1;
217 				break;
218 			case X_STDOUT:
219 				sscanf(&buf[1], "%s%s", fout, sysout);
220 				sysout[7] = '\0';
221 				/* rti!trt: do not check permissions of
222 				 * vanilla spool file.  DO check permissions
223 				 * of writing on a non-vanilla file */
224 				i = 1;
225 				if (fout[0] != '~' || prefix(sysout, Myname))
226 					i = expfile(fout);
227 				if (i != 0
228 				 && (chkpth("", "", fout)
229 					|| chkperm(fout, (char *)1)))
230 					badfiles = 1;
231 				break;
232 			case X_CMD:
233 				strcpy(cmd, &buf[2]);
234 				if (*(cmd + strlen(cmd) - 1) == '\n')
235 					*(cmd + strlen(cmd) - 1) = '\0';
236 				break;
237 			case X_NONOTI:
238 				notiok = 0;
239 				break;
240 			case X_NONZERO:
241 				nonzero = 1;
242 				break;
243 			default:
244 				break;
245 			}
246 		}
247 
248 		fclose(xfp);
249 		DEBUG(4, "fin - %s, ", fin);
250 		DEBUG(4, "fout - %s, ", fout);
251 		DEBUG(4, "sysout - %s, ", sysout);
252 		DEBUG(4, "user - %s\n", user);
253 		DEBUG(4, "cmd - %s\n", cmd);
254 
255 		/*  command execution  */
256 		if (strcmp(fout, DEVNULL) == SAME)
257 			strcpy(dfile,DEVNULL);
258 		else
259 			gename(DATAPRE, sysout, 'O', dfile);
260 
261 		/* expand file names where necessary */
262 		expfile(dfile);
263 		cmdp = buf;
264 		ptr = cmd;
265 		xcmd[0] = '\0';
266 		argnok = 0;
267 		while ((ptr = getprm(ptr, prm)) != NULL) {
268 			if (prm[0] == ';' || prm[0] == '^'
269 			  || prm[0] == '&'  || prm[0] == '|') {
270 				xcmd[0] = '\0';
271 				APPCMD(prm);
272 				continue;
273 			}
274 
275 			if ((argnok = argok(xcmd, prm)) != SUCCESS)
276 				/*  command not valid  */
277 				break;
278 
279 			if (prm[0] == '~')
280 				expfile(prm);
281 			APPCMD(prm);
282 		}
283 		/*
284 		 * clean up trailing ' ' in command.
285 		 */
286 		if (cmdp > buf && cmdp[0] == '\0' && cmdp[-1] == ' ')
287 			*--cmdp = '\0';
288 		if (strpbrk(user, BADCHARS) != NULL) {
289 			sprintf(lbuf, "%s INVALID CHARACTER IN USERNAME", user);
290 			logent(cmd, lbuf);
291 			strcpy(user, "postmaster");
292 		}
293 		if (argnok || badfiles) {
294 			sprintf(lbuf, "%s XQT DENIED", user);
295 			logent(cmd, lbuf);
296 			DEBUG(4, "bad command %s\n", prm);
297 			notify(user, Rmtname, cmd, "DENIED");
298 			goto rmfiles;
299 		}
300 		sprintf(lbuf, "%s XQT", user);
301 		logent(buf, lbuf);
302 		DEBUG(4, "cmd %s\n", buf);
303 
304 		mvxfiles(xfile);
305 		ret = subchdir(XQTDIR);
306 		ASSERT(ret >= 0, "CHDIR FAILED", XQTDIR, ret);
307 		ret = shio(buf, fin, dfile);
308 		sprintf(retstat, "signal %d, exit %d", ret & 0377,
309 		  (ret>>8) & 0377);
310 		if (strcmp(xcmd, "rmail") == SAME)
311 			notiok = 0;
312 		if (strcmp(xcmd, "rnews") == SAME)
313 			nonzero = 1;
314 		notiflg = chknotify(xcmd);
315 		if (notiok && notiflg != NT_NO &&
316 		   (ret != 0 || (!nonzero && notiflg == NT_YES)))
317 			notify(user, Rmtname, cmd, retstat);
318 		else if (ret != 0 && strcmp(xcmd, "rmail") == SAME) {
319 			/* mail failed - return letter to sender  */
320 #ifdef	DANGEROUS
321 			/* NOT GUARANTEED SAFE!!! */
322 			if (!nonzero)
323 				retosndr(user, Rmtname, fin);
324 #else
325 			notify(user, Rmtname, cmd, retstat);
326 #endif
327 			sprintf(buf, "%s (%s) from %s!%s", buf, retstat, Rmtname, user);
328 			logent("MAIL FAIL", buf);
329 		}
330 		DEBUG(4, "exit cmd - %d\n", ret);
331 		ret2 = subchdir(Spool);
332 		ASSERT(ret2 >= 0, "CHDIR FAILED", Spool, ret);
333 		rmxfiles(xfile);
334 		if (ret != 0) {
335 			/*  exit status not zero */
336 			dfp = fopen(subfile(dfile), "a");
337 			ASSERT(dfp != NULL, CANTOPEN, dfile, 0);
338 			fprintf(dfp, "exit status %d", ret);
339 			fclose(dfp);
340 		}
341 		if (strcmp(fout, DEVNULL) != SAME) {
342 			if (prefix(sysout, Myname)) {
343 				xmv(dfile, fout);
344 				chmod(fout, BASEMODE);
345 			} else {
346 				char *cp = rindex(user, '!');
347 				gename(CMDPRE, sysout, 'O', cfile);
348 				fp = fopen(subfile(cfile), "w");
349 				ASSERT(fp != NULL, "OPEN", cfile, 0);
350 				fprintf(fp, "S %s %s %s - %s 0666\n", dfile,
351 					fout, cp ? cp : user, lastpart(dfile));
352 				fclose(fp);
353 			}
354 		}
355 	rmfiles:
356 		xfp = fopen(subfile(xfile), "r");
357 		ASSERT(xfp != NULL, CANTOPEN, xfile, 0);
358 		while (fgets(buf, BUFSIZ, xfp) != NULL) {
359 			if (buf[0] != X_RQDFILE)
360 				continue;
361 			sscanf(&buf[1], "%s", file);
362 			unlink(subfile(file));
363 		}
364 		unlink(subfile(xfile));
365 		fclose(xfp);
366 
367 		/* rescan X. for new work every RECHECKTIME seconds */
368 		time(&xnow);
369 		if (xnow > (xstart + RECHECKTIME)) {
370 			extern int Nfiles;
371 			Nfiles = 0; 	/*force rescan for new work */
372 		}
373 		xstart = xnow;
374 	}
375 
376 	if (stcico)
377 		xuucico("");
378 	cleanup(0);
379 }
380 
381 
382 cleanup(code)
383 int code;
384 {
385 	logcls();
386 	rmlock(CNULL);
387 #ifdef	VMS
388 	/*
389 	 *	Since we run as a BATCH job we must wait for all processes to
390 	 *	to finish
391 	 */
392 	while(wait(0) != -1)
393 		;
394 #endif VMS
395 	exit(code);
396 }
397 
398 
399 /*
400  *	get a file to execute
401  *
402  *	return codes:  0 - no file  |  1 - file to execute
403  */
404 
405 gtxfile(file)
406 register char *file;
407 {
408 	char pre[3];
409 	int rechecked;
410 	time_t ystrdy;		/* yesterday */
411 	extern time_t time();
412 	struct stat stbuf;	/* for X file age */
413 
414 	pre[0] = XQTPRE;
415 	pre[1] = '.';
416 	pre[2] = '\0';
417 	rechecked = 0;
418 retry:
419 	if (!gtwrkf(Spool, file)) {
420 		if (rechecked)
421 			return 0;
422 		rechecked = 1;
423 		DEBUG(4, "iswrk\n", CNULL);
424 		if (!iswrk(file, "get", Spool, pre))
425 			return 0;
426 	}
427 	DEBUG(4, "file - %s\n", file);
428 	/* skip spurious subdirectories */
429 	if (strcmp(pre, file) == SAME)
430 		goto retry;
431 	if (gotfiles(file))
432 		return 1;
433 	/* check for old X. file with no work files and remove them. */
434 	if (Nfiles > LLEN/2) {
435 	    time(&ystrdy);
436 	    ystrdy -= (4 * 3600L);		/* 4 hours ago */
437 	    DEBUG(4, "gtxfile: Nfiles > LLEN/2\n", CNULL);
438 	    while (gtwrkf(Spool, file) && !gotfiles(file)) {
439 		if (stat(subfile(file), &stbuf) == 0)
440 		    if (stbuf.st_mtime <= ystrdy) {
441 			char *bnp, cfilename[NAMESIZE];
442 			DEBUG(4, "gtxfile: move %s to CORRUPT \n", file);
443 			unlink(subfile(file));
444 			bnp = rindex(subfile(file), '/');
445 			sprintf(cfilename, "%s/%s", CORRUPT,
446 				bnp ? bnp + 1 : subfile(file));
447 			xmv(subfile(file), cfilename);
448 			logent(file, "X. FILE CORRUPTED");
449 		    }
450 	    }
451 	    DEBUG(4, "iswrk\n", CNULL);
452 	    if (!iswrk(file, "get", Spool, pre))
453 		return 0;
454 	}
455 	goto retry;
456 }
457 
458 /*
459  *	check for needed files
460  *
461  *	return codes:  0 - not ready  |  1 - all files ready
462  */
463 
464 gotfiles(file)
465 register char *file;
466 {
467 	struct stat stbuf;
468 	register FILE *fp;
469 	char buf[BUFSIZ], rqfile[MAXFULLNAME];
470 
471 	fp = fopen(subfile(file), "r");
472 	if (fp == NULL)
473 		return 0;
474 
475 	while (fgets(buf, BUFSIZ, fp) != NULL) {
476 		DEBUG(4, "%s\n", buf);
477 		if (buf[0] != X_RQDFILE)
478 			continue;
479 		sscanf(&buf[1], "%s", rqfile);
480 		expfile(rqfile);
481 		if (stat(subfile(rqfile), &stbuf) == -1) {
482 			fclose(fp);
483 			return 0;
484 		}
485 	}
486 
487 	fclose(fp);
488 	return 1;
489 }
490 
491 
492 /*
493  *	remove execute files to x-directory
494  */
495 
496 rmxfiles(xfile)
497 register char *xfile;
498 {
499 	register FILE *fp;
500 	char buf[BUFSIZ], file[NAMESIZE], tfile[NAMESIZE];
501 	char tfull[MAXFULLNAME];
502 
503 	if((fp = fopen(subfile(xfile), "r")) == NULL)
504 		return;
505 
506 	while (fgets(buf, BUFSIZ, fp) != NULL) {
507 		if (buf[0] != X_RQDFILE)
508 			continue;
509 		if (sscanf(&buf[1], "%s%s", file, tfile) < 2)
510 			continue;
511 		sprintf(tfull, "%s/%s", XQTDIR, tfile);
512 		unlink(subfile(tfull));
513 	}
514 	fclose(fp);
515 	return;
516 }
517 
518 
519 /*
520  *	move execute files to x-directory
521  */
522 
523 mvxfiles(xfile)
524 char *xfile;
525 {
526 	register FILE *fp;
527 	char buf[BUFSIZ], ffile[MAXFULLNAME], tfile[NAMESIZE];
528 	char tfull[MAXFULLNAME];
529 	int ret;
530 
531 	if((fp = fopen(subfile(xfile), "r")) == NULL)
532 		return;
533 
534 	while (fgets(buf, BUFSIZ, fp) != NULL) {
535 		if (buf[0] != X_RQDFILE)
536 			continue;
537 		if (sscanf(&buf[1], "%s%s", ffile, tfile) < 2)
538 			continue;
539 		expfile(ffile);
540 		sprintf(tfull, "%s/%s", XQTDIR, tfile);
541 		unlink(subfile(tfull));
542 		ret = xmv(ffile, tfull);
543 		ASSERT(ret == 0, "XQTDIR ERROR", CNULL, ret);
544 	}
545 	fclose(fp);
546 }
547 
548 /*
549  *	check for valid command/argument
550  *	*NOTE - side effect is to set xc to the	command to be executed.
551  *
552  *	return 0 - ok | 1 nok
553  */
554 
555 argok(xc, cmd)
556 register char *xc, *cmd;
557 {
558 	register char **ptr;
559 
560 #ifndef ALLOK
561 	if (strpbrk(cmd, BADCHARS) != NULL) {
562 		DEBUG(1,"MAGIC CHARACTER FOUND\n", CNULL);
563 		logent(cmd, "NASTY MAGIC CHARACTER FOUND");
564 		return FAIL;
565 	}
566 #endif !ALLOK
567 
568 	if (xc[0] != '\0')
569 		return SUCCESS;
570 
571 #ifndef ALLOK
572 	ptr = Cmds;
573 	DEBUG(9, "Compare %s and\n", cmd);
574 	while(*ptr != NULL) {
575 		DEBUG(9, "\t%s\n", *ptr);
576 		if (strcmp(cmd, *ptr) == SAME)
577 			break;
578 		ptr++;
579 	}
580 	if (*ptr == NULL) {
581 		DEBUG(1,"COMMAND NOT FOUND\n", CNULL);
582 		return FAIL;
583 	}
584 #endif
585 	strcpy(xc, cmd);
586 	DEBUG(9, "MATCHED %s\n", xc);
587 	return SUCCESS;
588 }
589 
590 
591 /*
592  *	if notification should be sent for successful execution of cmd
593  *
594  *	return NT_YES - do notification
595  *	       NT_ERR - do notification if exit status != 0
596  *	       NT_NO  - don't do notification ever
597  */
598 
599 chknotify(cmd)
600 char *cmd;
601 {
602 	register char **ptr;
603 	register int *nptr;
604 
605 	ptr = Cmds;
606 	nptr = Notify;
607 	while (*ptr != NULL) {
608 		if (strcmp(cmd, *ptr) == SAME)
609 			return *nptr;
610 		ptr++;
611 		nptr++;
612 	}
613 	return NT_YES;		/* "shouldn't happen" */
614 }
615 
616 
617 
618 /*
619  *	send mail to user giving execution results
620  */
621 
622 notify(user, rmt, cmd, str)
623 char *user, *rmt, *cmd, *str;
624 {
625 	char text[MAXFULLNAME];
626 	char ruser[MAXFULLNAME];
627 
628 	sprintf(text, "uuxqt cmd (%s) status (%s)", cmd, str);
629 	if (prefix(rmt, Myname))
630 		strcpy(ruser, user);
631 	else
632 		sprintf(ruser, "%s!%s", rmt, user);
633 	mailst(ruser, text, CNULL);
634 	return;
635 }
636 
637 /*
638  *	return mail to sender
639  *
640  */
641 
642 retosndr(user, rmt, file)
643 char *user, *rmt, *file;
644 {
645 	char ruser[MAXFULLNAME];
646 
647 	if (strcmp(rmt, Myname) == SAME)
648 		strcpy(ruser, user);
649 	else
650 		sprintf(ruser, "%s!%s", rmt, user);
651 
652 	if (anyread(file) == 0)
653 		mailst(ruser, "Mail failed.  Letter returned to sender.\n", file);
654 	else
655 		mailst(ruser, "Mail failed.  Letter returned to sender.\n", CNULL);
656 	return;
657 }
658 
659 /*
660  * this is like index, but takes a string as the second argument
661  */
662 char *
663 strpbrk(str, chars)
664 register char *str, *chars;
665 {
666 	register char *cp;
667 
668 	do {
669 		cp = chars - 1;
670 		while (*++cp) {
671 			if (*str == *cp)
672 				return str;
673 		}
674 	} while (*str++);
675 	return NULL;
676 }
677 
678 /*
679  *	execute shell of command with fi and fo as standard input/output
680  */
681 
682 shio(cmd, fi, fo)
683 char *cmd, *fi, *fo;
684 {
685 	int status, f;
686 	int uid, pid, ret;
687 	char path[MAXFULLNAME];
688 	char *args[20];
689 	extern int errno;
690 
691 	if (fi == NULL)
692 		fi = DEVNULL;
693 	if (fo == NULL)
694 		fo = DEVNULL;
695 
696 	getargs(cmd, args, 20);
697 	DEBUG(3, "shio - %s\n", cmd);
698 #ifdef SIGCHLD
699 	signal(SIGCHLD, SIG_IGN);
700 #endif SIGCHLD
701 	if ((pid = fork()) == 0) {
702 		signal(SIGINT, SIG_IGN);
703 		signal(SIGHUP, SIG_IGN);
704 		signal(SIGQUIT, SIG_IGN);
705 		signal(SIGKILL, SIG_IGN);
706 		close(Ifn);
707 		close(Ofn);
708 		close(0);
709 		setuid(getuid());
710 		f = open(subfile(fi), 0);
711 		if (f != 0) {
712 			logent(fi, "CAN'T READ");
713 			exit(-errno);
714 		}
715 		close(1);
716 		f = creat(subfile(fo), 0666);
717 		if (f != 1) {
718 			logent(fo, "CAN'T WRITE");
719 			exit(-errno);
720 		}
721 		execvp(args[0], args);
722 		exit(100+errno);
723 	}
724 	while ((ret = wait(&status)) != pid && ret != -1)
725 		;
726 	DEBUG(3, "status %d\n", status);
727 	return status;
728 }
729