xref: /illumos-gate/usr/src/cmd/bnu/uuxqt.c (revision 7c478bd9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 /*	from SVR4 bnu:uuxqt.c 2.12.1.12				*/
26 
27 /*
28  * Copyright 2000, 2003 Sun Microsystems, Inc.  All rights reserved.
29  * Use is subject to license terms.
30  */
31 
32 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 #include "uucp.h"
35 #include "log.h"
36 
37 /*
38  * execute commands set up by a uux command,
39  * usually from a remote machine - set by uucp.
40  */
41 
42 #ifndef	V7
43 #define	LOGNAME	"LOGNAME=uucp"
44 #else
45 #define	LOGNAME	"USER=uucp"
46 #endif
47 
48 #define	C_COMMAND	1
49 #define	C_FILE		2
50 #define	BAD_COMMAND	1
51 #define	BAD_FILE	2
52 #define	USAGEPREFIX	"Usage:"
53 #define	USAGE		"[-x DEBUG] [-s SYSTEM]"
54 
55 char	_Xfile[MAXFULLNAME];
56 char	_Cmd[2 * BUFSIZ];	/* build up command buffer */
57 int	_CargType;		/* argument type of next C argument */
58 
59 static void retosndr(), uucpst();
60 static int chkFile();
61 static int doFileChk();
62 void cleanup(), xprocess();
63 
64 main(argc, argv, envp)
65 char *argv[];
66 char *envp[];
67 {
68 	DIR	 *fp1;
69 	struct	limits limitval;
70 	int	ret, maxnumb;
71 	char	dirname[MAXFULLNAME], lockname[MAXFULLNAME];
72 	void	onintr();
73 
74 	/* Set locale environment variables local definitions */
75 	(void) setlocale(LC_ALL, "");
76 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
77 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
78 #endif
79 	(void) textdomain(TEXT_DOMAIN);
80 
81 	(void) signal(SIGILL, onintr);
82 	(void) signal(SIGTRAP, onintr);
83 	(void) signal(SIGIOT, onintr);
84 	(void) signal(SIGEMT, onintr);
85 	(void) signal(SIGFPE, onintr);
86 	(void) signal(SIGBUS, onintr);
87 	(void) signal(SIGSEGV, onintr);
88 	(void) signal(SIGSYS, onintr);
89 	(void) signal(SIGPIPE, onintr);
90 	(void) signal(SIGTERM, SIG_IGN);
91 
92 	/* choose LOGFILE */
93 	(void) strcpy(Logfile, LOGUUXQT);
94 
95 	/*
96 	 * get local system name
97 	 */
98 	Env = envp;
99 	Nstat.t_qtime = time((time_t *)0);
100 	(void) strcpy(Progname, "uuxqt");
101 	Pchar = 'Q';
102 	uucpname(Myname);
103 	Ofn = 1;
104 	Ifn = 0;
105 	dirname[0] = dirname[MAXFULLNAME-1] = NULLCHAR;
106 	while ((ret = getopt(argc, argv, "s:x:")) != EOF) {
107 		switch (ret) {
108 
109 		/*
110 		 * debugging level
111 		 */
112 		case 'x':
113 			Debug = atoi(optarg);
114 			if (Debug <= 0)
115 				Debug = 1;
116 			break;
117 
118 		case 's':
119 			/*
120 			 * fake out uuxqt and use the argument as if
121 			 * it were the spool directory for the purpose
122 			 * of determining what subdirectories to search
123 			 *  EX:	mkdir /tmp/foo; touch /tmp/foo/[baz, gorp]
124 			 *	uuxqt -s/tmp/foo
125 			 * this will cause uuxqt to only run on the sub
126 			 * baz and gorp in the Spool directory.  Trust me.
127 			 */
128 			(void) strlcpy(dirname, optarg,
129 			    (MAXFULLNAME - sizeof (SEQLOCK)));
130 			break;
131 
132 		default:
133 			(void) fprintf(stderr, "%s %s %s\n",
134 			    gettext(USAGEPREFIX), Progname, gettext(USAGE));
135 			exit(1);
136 		}
137 	}
138 	if (argc != optind) {
139 		(void) fprintf(stderr, "%s %s %s\n",
140 		    gettext(USAGEPREFIX), Progname, gettext(USAGE));
141 		exit(1);
142 	}
143 
144 	DEBUG(4, "\n\n** START **\n%s", "");
145 	acInit("rexe");
146 	scInit("rexe");
147 	if (scanlimit("uuxqt", &limitval) == FAIL) {
148 	    DEBUG(1, "No limits for uuxqt in %s\n", LIMITS);
149 	} else {
150 	    maxnumb = limitval.totalmax;
151 	    if (maxnumb < 0) {
152 		DEBUG(4, "Non-positive limit for uuxqt in %s\n", LIMITS);
153 		DEBUG(1, "No limits for uuxqt\n%s", "");
154 	    } else {
155 		DEBUG(4, "Uuxqt limit %d -- ", maxnumb);
156 		ret = cuantos(X_LOCKPRE, X_LOCKDIR);
157 		DEBUG(4, "found %d -- ", ret);
158 		if (maxnumb >= 0 && ret >= maxnumb) {
159 		    DEBUG(4, "exiting.%s\n", "");
160 		    exit(0);
161 		}
162 		DEBUG(4, "continuing.%s\n", "");
163 	    }
164 	}
165 
166 	/*
167 	 * determine user who started uuxqt (in principle)
168 	 */
169 	strcpy(User, "uucp");	/* in case all else fails (can't happen) */
170 	Uid = getuid();
171 	Euid = geteuid();	/* this should be UUCPUID */
172 	guinfo(Euid, User);
173 
174 	if (Uid == 0)
175 		(void) setuid(UUCPUID);
176 
177 	setuucp(User);
178 	DEBUG(4, "User - %s\n", User);
179 	guinfo(Uid, Loginuser);
180 
181 
182 
183 	DEBUG(4, "process\n%s", "");
184 
185 	fp1 = opendir(Spool);
186 	ASSERT(fp1 != NULL, Ct_OPEN, Spool, errno);
187 	if (dirname[0] != NULLCHAR) {
188 		/* look for special characters in remote name */
189 		if (strpbrk(dirname, Shchar) != NULL) {
190 		    /* ignore naughty name */
191 		    DEBUG(4, "Bad remote name '%s'", dirname);
192 		    errent("BAD REMOTE NAME", dirname, 0, __FILE__, __LINE__);
193 		    closedir(fp1);
194 		    cleanup(101);
195 		}
196 
197 
198 		(void) snprintf(lockname, sizeof (lockname), "%s.%s",
199 		    X_LOCK, dirname);
200 		if (mklock(lockname) == SUCCESS) {
201 			xprocess(dirname);
202 			rmlock(CNULL);
203 		}
204 	} else {
205 		while (gdirf(fp1, dirname, Spool) == TRUE) {
206 			if (strpbrk(dirname, Shchar) != NULL) {
207 				/* skip naughty names */
208 				errent("BAD REMOTE NAME", dirname, 0,
209 				    __FILE__, __LINE__);
210 				continue;
211 			}
212 			(void) snprintf(lockname, sizeof (lockname), "%s.%s",
213 			    X_LOCK, dirname);
214 			if (mklock(lockname) != SUCCESS)
215 				continue;
216 			xprocess(dirname);
217 			rmlock(CNULL);
218 		}
219 	}
220 
221 	closedir(fp1);
222 	cleanup(0);
223 	/* NOTREACHED */
224 }
225 
226 void
227 cleanup(code)
228 int	code;
229 {
230 	rmlock(CNULL);
231 	exit(code);
232 }
233 
234 /*
235  * catch signal then cleanup and exit
236  */
237 void
238 onintr(inter)
239 register int	inter;
240 {
241 	char	str[30];
242 	(void) signal(inter, SIG_IGN);
243 	(void) sprintf(str, "QSIGNAL %d", inter);
244 	logent(str, "QCAUGHT");
245 	acEndexe(cpucycle(), PARTIAL);	/* stop collecting accounting log */
246 	cleanup(-inter);
247 }
248 
249 #define	XCACHESIZE (4096 / (MAXBASENAME + 1))
250 static char	xcache[XCACHESIZE][MAXBASENAME + 1];	/* cache for X. files */
251 static int	xcachesize = 0;			/* how many left? */
252 
253 /*
254  * stash an X. file so we can process them sorted first by grade, then by
255  * sequence number
256  */
257 static void
258 xstash(file)
259 char	*file;
260 {
261 	if (xcachesize < XCACHESIZE) {
262 		DEBUG(4, "stashing %s\n", file);
263 		(void) strlcpy(xcache[xcachesize++], file, (MAXBASENAME + 1));
264 	}
265 }
266 
267 /*
268  * xcompare
269  *	comparison routine for for qsort()
270  */
271 static int
272 xcompare(f1, f2)
273 const register void	*f1, *f2;
274 {
275 	/* assumes file name is X.siteG1234 */
276 	/* use -strcmp() so that xstash is sorted largest first */
277 	/* pull files out of the stash from largest index to smallest */
278 
279 	return (-strcmp((char *)f1 + strlen((char *)f1) - 5,
280 	    (char *)f2 + strlen((char *)f2) - 5));
281 }
282 
283 /*
284  * xsort
285  *	sort the cached X. files,
286  *	largest (last) to smallest (next to be processed)
287  */
288 static void
289 xsort()
290 {
291 	DEBUG(4, "xsort:  first was %s\n", xcache[0]);
292 	qsort(xcache, xcachesize, MAXBASENAME + 1, xcompare);
293 	DEBUG(4, "xsort:  first is %s\n", xcache[0]);
294 }
295 
296 /*
297  * xget
298  *	return smallest X. file in cache
299  *	(hint:  it's the last one in the array)
300  */
301 static int
302 xget(file)
303 char	*file;
304 {
305 	if (xcachesize > 0) {
306 		strlcpy(file, xcache[--xcachesize], (MAXBASENAME + 1));
307 		DEBUG(4, "xget: returning %s\n", file);
308 		return (1);
309 	} else {
310 		/* avoid horror of xcachesize < 0 (impossible, you say?)! */
311 		xcachesize = 0;
312 		return (0);
313 	}
314 }
315 
316 
317 /*
318  * get a file to execute
319  *	file	-> a read to return filename in
320  * returns:
321  *	0	-> no file
322  *	1	-> file to execute
323  */
324 int
325 gt_Xfile(file, dir)
326 register char	*file, *dir;
327 {
328 	DIR *pdir;
329 
330 	if (xcachesize == 0) {
331 		/* open spool directory */
332 		pdir = opendir(dir);
333 		/* this was an ASSERT, but it's not so bad as all that */
334 		if (pdir == NULL)
335 			return (0);
336 
337 		/* scan spool directory looking for X. files to stash */
338 		while (gnamef(pdir, file) == TRUE) {
339 			DEBUG(4, "gt_Xfile got %s\n", file);
340 			/* look for x prefix */
341 			if (file[0] != XQTPRE)
342 				continue;
343 
344 			/* check to see if required files have arrived */
345 			if (gotfiles(file))
346 				xstash(file);
347 			if (xcachesize >= XCACHESIZE)
348 				break;
349 		}
350 		closedir(pdir);
351 		xsort();
352 	}
353 
354 	return (xget(file));
355 }
356 
357 /*
358  * check for needed files
359  *	file	-> name of file to check
360  * return:
361  *	0	-> not ready
362  *	1	-> all files ready
363  */
364 int
365 gotfiles(file)
366 register char	*file;
367 {
368 	register FILE *fp;
369 	struct stat stbuf;
370 	char	buf[BUFSIZ], rqfile[MAXNAMESIZE];
371 
372 	fp = fopen(file, "r");
373 	if (fp == NULL)
374 		return (FALSE);
375 
376 	while (fgets(buf, BUFSIZ, fp) != NULL) {
377 		DEBUG(4, "%s\n", buf);
378 
379 		/*
380 		 * look at required files
381 		 */
382 		if (buf[0] != X_RQDFILE)
383 			continue;
384 		(void) sscanf(&buf[1], "%63s", rqfile);
385 
386 		/*
387 		 * expand file name
388 		 */
389 		expfile(rqfile);
390 
391 		/*
392 		 * see if file exists
393 		 */
394 		if (stat(rqfile, &stbuf) == -1) {
395 			fclose(fp);
396 			return (FALSE);
397 		}
398 	}
399 
400 	fclose(fp);
401 	return (TRUE);
402 }
403 
404 /*
405  * remove execute files to x-directory
406  *
407  * _Xfile is a global
408  * return:
409  *	none
410  */
411 void
412 rm_Xfiles()
413 {
414 	register FILE *fp;
415 	char	buf[BUFSIZ], file[MAXNAMESIZE], tfile[MAXNAMESIZE];
416 	char	tfull[MAXFULLNAME];
417 
418 	if ((fp = fopen(_Xfile, "r")) == NULL) {
419 		DEBUG(4, "rm_Xfiles: can't read %s\n", _Xfile);
420 		return;
421 	}
422 
423 	/*
424 	 * (void) unlink each file belonging to job
425 	 */
426 	while (fgets(buf, BUFSIZ, fp) != NULL) {
427 		if (buf[0] != X_RQDFILE)
428 			continue;
429 		if (sscanf(&buf[1], "%63s%63s", file, tfile) < 2)
430 			continue;
431 		(void) snprintf(tfull, sizeof (tfull), "%s/%s", XQTDIR, tfile);
432 		(void) unlink(tfull);
433 	}
434 	fclose(fp);
435 }
436 
437 /*
438  * move execute files to x-directory
439  *	_Xfile is a global
440  * return:
441  *	none
442  */
443 void
444 mv_Xfiles()
445 {
446 	register FILE *fp;
447 	char	buf[BUFSIZ], ffile[MAXFULLNAME], tfile[MAXNAMESIZE];
448 	char	tfull[MAXFULLNAME];
449 
450 	if ((fp = fopen(_Xfile, "r")) == NULL) {
451 		DEBUG(4, "mv_Xfiles: can't read %s\n", _Xfile);
452 		return;
453 	}
454 
455 	while (fgets(buf, BUFSIZ, fp) != NULL) {
456 		if (buf[0] != X_RQDFILE)
457 			continue;
458 		if (sscanf(&buf[1], "%63s%63s", ffile, tfile) < 2)
459 			continue;
460 
461 		/*
462 		 * expand file names and move to
463 		 * execute directory
464 		 * Make files readable by anyone
465 		 */
466 		expfile(ffile);
467 		(void) snprintf(tfull, sizeof (tfull), "%s/%s", XQTDIR, tfile);
468 
469 		if (chkpth(ffile, CK_READ) == FAIL)
470 			continue;	/* execution will fail later */
471 		if (chkpth(tfull, CK_WRITE) == FAIL) {
472 			/*
473 			 * tfull will have been canonicalized. If
474 			 * it still points to XQTDIR, allow us to
475 			 * write there.
476 			 */
477 			if (!PREFIX(XQTDIR, tfull))
478 				continue;	/* execution will fail later */
479 			/* otherwise, keep going */
480 		}
481 
482 		ASSERT(xmv(ffile, tfull) == 0, "XMV ERROR", tfull, errno);
483 		chmod(tfull, PUB_FILEMODE);
484 	}
485 	fclose(fp);
486 }
487 
488 /*
489  * undo what mv_Xfiles did
490  *	_Xfile is a global
491  * return:
492  *	none
493  */
494 void
495 unmv_Xfiles()
496 {
497 	FILE *fp;
498 	char	buf[BUFSIZ], ffile[MAXNAMESIZE], tfile[MAXNAMESIZE];
499 	char	tfull[MAXFULLNAME], ffull[MAXFULLNAME], xfull[MAXFULLNAME];
500 
501 	(void) snprintf(xfull, MAXFULLNAME, "%s/%s", RemSpool, _Xfile);
502 	if ((fp = fopen(xfull, "r")) == NULL) {
503 		DEBUG(4, "unmv_Xfiles: can't read %s\n", xfull);
504 		return;
505 	}
506 
507 	while (fgets(buf, BUFSIZ, fp) != NULL) {
508 		if (buf[0] != X_RQDFILE)
509 			continue;
510 		if (sscanf(&buf[1], "%63s%63s", ffile, tfile) < 2)
511 			continue;
512 
513 		/*
514 		 * expand file names and move back to
515 		 * spool directory
516 		 * Make files readable by uucp
517 		 */
518 		(void) snprintf(ffull, MAXFULLNAME, "%s/%s", RemSpool, ffile);
519 		/* i know we're in .Xqtdir, but ... */
520 		(void) snprintf(tfull, MAXFULLNAME, "%s/%s", XQTDIR, tfile);
521 
522 		if (chkpth(ffull, CK_WRITE) == FAIL ||
523 		    chkpth(tfull, CK_READ) == FAIL)
524 			continue;
525 
526 		ASSERT(xmv(tfull, ffull) == 0, "XMV ERROR", ffull, errno);
527 		(void) chmod(ffull, (mode_t)0600);
528 	}
529 	fclose(fp);
530 }
531 
532 /*
533  * chkpart - checks the string (ptr points to it) for illegal command or
534  *  file permission restriction - called recursively
535  *  to check lines that have `string` or (string) form.
536  *  _Cmd is the buffer where the command is built up.
537  *  _CargType is the type of the next C line argument
538  *
539  * Return:
540  *	BAD_FILE if a non permitted file is found
541  *	BAD_COMMAND if non permitted command is found
542  *	0 - ok
543  */
544 
545 static int
546 chkpart(char *ptr)
547 {
548 	char	prm[BUFSIZ], whitesp[BUFSIZ], rqtcmd[BUFSIZ], xcmd[BUFSIZ];
549 	char	savechar[2]; /* one character string with NULL */
550 	int	ret;
551 
552 	/* _CargType is the arg type for this iteration (cmd or file) */
553 	while ((ptr = getprm(ptr, whitesp, prm)) != NULL) {
554 	    DEBUG(4, "prm='%s'\n", prm);
555 	    switch (*prm) {
556 
557 	    /* End of command delimiter */
558 	    case ';':
559 	    case '^':
560 	    case '&':
561 	    case '|':
562 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
563 		(void) strlcat(_Cmd, prm, sizeof (_Cmd));
564 		_CargType = C_COMMAND;
565 		continue;
566 
567 	    /* Other delimiter */
568 	    case '>':
569 	    case '<':
570 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
571 		(void) strlcat(_Cmd, prm, sizeof (_Cmd));
572 		continue;
573 
574 	    case '`':	/* don't allow any ` commands */
575 	    case '\\':
576 		return (BAD_COMMAND);
577 
578 	    /* Some allowable quoted string */
579 	    case '(':
580 	    case '"':
581 	    case '\'':
582 		/* must recurse */
583 		savechar[0] = *prm;
584 		savechar[1] = NULLCHAR;
585 		/* put leading white space & first char into command */
586 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
587 		(void) strlcat(_Cmd, savechar, sizeof (_Cmd));
588 		savechar[0] = prm[strlen(prm)-1];
589 		prm[strlen(prm)-1] = NULLCHAR; /* delete last character */
590 
591 		/* recurse */
592 		if (ret = chkpart(prm+1)) { /* failed */
593 			return (ret);
594 		}
595 		/* put last char into command */
596 		(void) strlcat(_Cmd, savechar, sizeof (_Cmd));
597 		continue;
598 
599 	    case '2':
600 		if (*(prm+1) == '>') {
601 		    (void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
602 		    (void) strlcat(_Cmd, prm, sizeof (_Cmd));
603 		    continue;
604 		}
605 		/* fall through if not "2>" */
606 
607 	    default:	/* check for command or file */
608 		break;
609 	    }
610 
611 	    if (_CargType == C_COMMAND) {
612 		(void) strlcpy(rqtcmd, prm, sizeof (rqtcmd));
613 		if (*rqtcmd == '~')
614 		    expfile(rqtcmd);
615 		if ((cmdOK(rqtcmd, xcmd)) == FALSE)
616 			return (BAD_COMMAND);
617 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
618 		(void) strlcat(_Cmd, xcmd, sizeof (_Cmd));
619 		_CargType = C_FILE;
620 		continue;
621 	    }
622 
623 	    (void) strlcpy(rqtcmd, prm, sizeof (rqtcmd));
624 	    if (*rqtcmd == '~')
625 		expfile(rqtcmd);
626 	    if (chkFile(rqtcmd)) {
627 		return (BAD_FILE);
628 	    } else {
629 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
630 		(void) strlcat(_Cmd, rqtcmd, sizeof (_Cmd));
631 	    }
632 	}
633 	if (whitesp[0] != '\0')
634 		/* restore any trailing white space */
635 		(void) strlcat(_Cmd, whitesp, sizeof (_Cmd));
636 	return (0);	/* all ok */
637 }
638 
639 /*
640  * chkFile - try to find a path name in the prm.
641  * 	if found, check it for access permission.
642  *
643  * check file access permissions
644  * if ! in name assume that access on local machine is required
645  *
646  * Return:
647  *	BAD_FILE - not permitted
648  *	0 - ok
649  */
650 
651 static int
652 chkFile(char *prm)
653 {
654 	char	*p, buf[BUFSIZ];
655 
656 	(void) strlcpy(buf, prm, sizeof (buf));
657 	switch (*prm) {
658 	case '~':
659 	case '/':
660 	    if (doFileChk(buf))
661 		return (BAD_FILE);
662 	    else
663 		return (0);
664 	    /*NOTREACHED*/
665 
666 	case '!':
667 	    return (chkFile(buf+1));
668 	    /*NOTREACHED*/
669 
670 	default:
671 	    break;
672 	}
673 
674 	if ((p = strchr(buf, '!')) == NULL) {  /* no "!", look for "/" */
675 	    if ((p = strchr(buf, '/')) == NULL) {  /* ok */
676 		return (0);
677 	    }
678 	    if (doFileChk(p))
679 		return (BAD_FILE);
680 	    else
681 		return (0);
682 	}
683 
684 	/* there is at least one '!' - see if it refers to my system */
685 	if (PREFIX(Myname, buf))	/*  my system so far, check further */
686 	    return (chkFile(p+1));	/* recurse with thing after '!' */
687 	else				/* not my system - not my worry */
688 	    return (0);
689 }
690 
691 /*
692  * doFileChk - check file path permission
693  * NOTE: file is assumed to be a buffer that expfile an
694  *  write into.
695  * Return
696  *	BAD_FILE - not allowed
697  *	0 - ok
698  */
699 
700 static int
701 doFileChk(char *file)
702 {
703 	expfile(file);
704 	DEBUG(7, "fullname: %s\n", file);
705 	if (chkpth(file, CK_READ) == FAIL ||
706 	    chkpth(file, CK_WRITE) == FAIL)
707 		return (BAD_FILE);
708 	else
709 		return (0);
710 }
711 
712 
713 /*
714  * return stuff to user
715  *	user	-> user to notify
716  *	rmt	-> system name where user resides
717  *	file	-> file to return (generally contains input)
718  *	cmd	-> command that was to be executed
719  *	buf	-> user friendly face saving uplifting edifying missive
720  *	errfile	-> stderr output from cmd xeqn
721  * return:
722  *	none
723  */
724 static void
725 retosndr(user, rmt, file, cmd, buf, errfile)
726 char	*user, *rmt, *file, *cmd, *buf, *errfile;
727 {
728 	char	ruser[BUFSIZ], msg[BUFSIZ], subj[BUFSIZ];
729 
730 	(void) snprintf(msg, sizeof (msg), "%s\t[%s %s (%s)]\n\t%s\n%s\n",
731 	    gettext("remote execution"), gettext("uucp job"),
732 	    *Jobid ? Jobid : &_Xfile[2], timeStamp(), cmd, buf);
733 
734 	DEBUG(5, "retosndr %s, ", msg);
735 
736 	if (EQUALS(rmt, Myname))
737 		(void) strlcpy(ruser, user, sizeof (ruser));
738 	else
739 		(void) snprintf(ruser, sizeof (ruser), "%s!%s", rmt, user);
740 
741 	(void) strlcpy(subj, gettext("remote execution status"), sizeof (subj));
742 	mailst(ruser, subj, msg, file, errfile);
743 }
744 
745 
746 /*
747  * uucpst - send the status message back using a uucp command
748  * NOTE - this would be better if the file could be appended.
749  * - suggestion for the future - if rmail would take a file name
750  * instead of just person, then that facility would be correct,
751  * and this routine would not be needed.
752  */
753 
754 static void
755 uucpst(rmt, tofile, errfile, cmd, buf)
756 char	*rmt, *tofile, *errfile, *cmd, *buf;
757 {
758 	char	arg[MAXFULLNAME], tmp[NAMESIZE], msg[BUFSIZ];
759 	pid_t pid, ret;
760 	int status;
761 	FILE *fp, *fi;
762 
763 	(void) snprintf(msg, sizeof (msg), "%s %s (%s) %s\n\t%s\n%s\n",
764 	    gettext("uucp job"), *Jobid ? Jobid : &_Xfile[2], timeStamp(),
765 	    gettext("remote execution"), cmd, buf);
766 
767 	(void) snprintf(tmp, sizeof (tmp), "%s.%ld", rmt, (long)getpid());
768 	if ((fp = fopen(tmp, "w")) == NULL)
769 		return;
770 	(void) fprintf(fp, "%s\n", msg);
771 
772 	/* copy back stderr */
773 	if (*errfile != '\0' && NOTEMPTY(errfile) &&
774 	    (fi = fopen(errfile, "r")) != NULL) {
775 		fputs("\n\t===== stderr was =====\n", fp);
776 		if (xfappend(fi, fp) != SUCCESS)
777 			fputs("\n\t===== well, i tried =====\n", fp);
778 		(void) fclose(fi);
779 		fputc('\n', fp);
780 	}
781 
782 
783 	(void) fclose(fp);
784 	(void) snprintf(arg, sizeof (arg), "%s!%s", rmt, tofile);
785 
786 	/* start uucp */
787 
788 	if ((pid = vfork()) == 0) {
789 		(void) close(0);
790 		(void) close(1);
791 		(void) close(2);
792 		(void) open("/dev/null", 2);
793 		(void) open("/dev/null", 2);
794 		(void) open("/dev/null", 2);
795 		(void) signal(SIGINT, SIG_IGN);
796 		(void) signal(SIGHUP, SIG_IGN);
797 		(void) signal(SIGQUIT, SIG_IGN);
798 		ucloselog();
799 
800 		(void) execle("/usr/bin/uucp", "UUCP",
801 		    "-C", tmp, arg, (char *)0, Env);
802 		_exit(100);
803 	}
804 
805 	if (pid == -1)
806 	    return;
807 
808 	while ((ret = wait(&status)) != pid)
809 	    if (ret == -1 && errno != EINTR)
810 		break;
811 
812 	(void) unlink(tmp);
813 }
814 
815 void
816 xprocess(dirname)
817 char *dirname;
818 {
819     char 	fdgrade();	/* returns default service grade on system */
820     int		return_stdin;	/* return stdin for failed commands */
821     int		cmdok, ret, badfiles;
822     mode_t	mask;
823     int		send_zero;	/* return successful completion status */
824     int		send_nonzero;	/* return unsuccessful completion status */
825     int		send_nothing;	/* request for no exit status */
826     int		store_status;	/* store status of command in local file */
827     char	lbuf[BUFSIZ];
828     char	dqueue;		/* var to hold the default service grade */
829     char	*errname = "";	/* name of local stderr output file */
830     char	*p;
831     char	sendsys[MAXNAMESIZE];
832     char	dfile[MAXFULLNAME], cfile[MAXFULLNAME], incmd[BUFSIZ];
833     char	errDfile[BUFSIZ];
834     char	fin[MAXFULLNAME];
835     char	fout[MAXFULLNAME], sysout[NAMESIZE];
836     char	ferr[MAXFULLNAME], syserr[NAMESIZE];
837     char	file[MAXFULLNAME], tempname[NAMESIZE];
838     char	_Sfile[MAXFULLNAME];	/* name of local file for status */
839     FILE	*xfp, *fp;
840     struct	stat sb;
841     char	buf[BUFSIZ], user[BUFSIZ], retaddr[BUFSIZ], retuser[BUFSIZ],
842 		msgbuf[BUFSIZ];
843     char	origsys[MAXFULLNAME], origuser[MAXFULLNAME];
844 
845     (void) strlcpy(Rmtname, dirname, sizeof (Rmtname));
846     chremdir(Rmtname);
847     (void) mchFind(Rmtname);
848     while (gt_Xfile(_Xfile, RemSpool) > 0) {
849 	DEBUG(4, "_Xfile - %s\n", _Xfile);
850 
851 	if ((xfp = fopen(_Xfile, "r")) == NULL) {
852 	    toCorrupt(_Xfile);
853 	    continue;
854 	}
855 	ASSERT(xfp != NULL, Ct_OPEN, _Xfile, errno);
856 
857 	if (stat(_Xfile, &sb) != -1)
858 	    Nstat.t_qtime = sb.st_mtime;
859 	/*
860 	 * initialize to defaults
861 	 */
862 	(void) strlcpy(user, User, sizeof (user));
863 	(void) strcpy(fin, "/dev/null");
864 	(void) strcpy(fout, "/dev/null");
865 	(void) strcpy(ferr, "/dev/null");
866 	(void) sprintf(sysout, "%.*s", MAXBASENAME, Myname);
867 	(void) sprintf(syserr, "%.*s", MAXBASENAME, Myname);
868 	badfiles = 0;
869 	*incmd = *retaddr = *retuser = *Jobid = NULLCHAR;
870 	initSeq();
871 	send_zero = send_nonzero = send_nothing = 0;
872 	store_status = 0;
873 	return_stdin = 0;
874 
875 	while (fgets(buf, BUFSIZ, xfp) != NULL) {
876 	    /*
877 	     * interpret JCL card
878 	     */
879 	    switch (buf[0]) {
880 		case X_USER:
881 			/*
882 			 * user name
883 			 * (ignore Rmtname)
884 			 * The utmpx username field is 32 characters long;
885 			 * UUCP usage truncates system name to 14 bytes.
886 			 */
887 			(void) sscanf(&buf[1], "%32s%14s", user, origsys);
888 			(void) strlcpy(origuser, user, sizeof (origuser));
889 			break;
890 
891 		case X_STDIN:
892 			/*
893 			 * standard input
894 			 */
895 			(void) sscanf(&buf[1], "%256s", fin);
896 			expfile(fin);
897 			if (chkpth(fin, CK_READ)) {
898 				DEBUG(4, "badfile - in: %s\n", fin);
899 				badfiles = 1;
900 			}
901 			break;
902 
903 		case X_STDOUT:
904 			/*
905 			 * standard output
906 			 */
907 			(void) sscanf(&buf[1], "%256s%14s", fout, sysout);
908 			if ((p = strpbrk(sysout, "!/")) != NULL)
909 				*p = NULLCHAR;	/* these are dangerous */
910 			if (*sysout != NULLCHAR && !EQUALS(sysout, Myname))
911 				break;
912 
913 			expfile(fout);
914 			if (chkpth(fout, CK_WRITE)) {
915 				badfiles = 1;
916 				DEBUG(4, "badfile - out: %s\n", fout);
917 			}
918 			break;
919 
920 		case X_STDERR:	/* standard error */
921 			(void) sscanf(&buf[1], "%256s%14s", ferr, syserr);
922 			if ((p = strpbrk(syserr, "!/")) != NULL)
923 				*p = NULLCHAR;	/* these are dangerous */
924 			if (*syserr != NULLCHAR && !EQUALS(syserr, Myname))
925 				break;
926 
927 			expfile(ferr);
928 			if (chkpth(ferr, CK_WRITE)) {
929 				badfiles = 1;
930 				DEBUG(4, "badfile - error: %s\n", ferr);
931 			}
932 			break;
933 
934 
935 		case X_CMD:	/* command to execute */
936 			(void) strlcpy(incmd, &buf[2], sizeof (incmd));
937 			if (*(incmd + strlen(incmd) - 1) == '\n')
938 				*(incmd + strlen(incmd) - 1) = NULLCHAR;
939 			break;
940 
941 		case X_MAILF:	/* put status in _Sfile */
942 			store_status = 1;
943 			(void) sscanf(&buf[1], "%256s", _Sfile);
944 			break;
945 
946 		case X_SENDNOTHING:	/* no failure notification */
947 			send_nothing++;
948 			break;
949 
950 		case X_SENDZERO:	/* success notification */
951 			send_zero++;
952 			break;
953 
954 		case X_NONZERO:	/* failure notification */
955 			send_nonzero++;
956 			break;
957 
958 		case X_BRINGBACK:  /* return stdin on command failure */
959 			return_stdin = 1;
960 			break;
961 
962 
963 		case X_RETADDR:
964 			/*
965 			 * return address -- is user's name
966 			 * put "Rmtname!" in front of it so mail
967 			 * will always get back to remote system.
968 			 */
969 			(void) sscanf(&buf[1], "%s", retuser);
970 
971 			/*
972 			 * Creates string of Rmtname!Rmtname!user which
973 			 * confuses rmail.
974 			 * (void) strcat(strcat(strcpy(retaddr, Rmtname), "!"),
975 			 *	retuser);
976 			 */
977 			break;
978 
979 		case X_JOBID:
980 			/*
981 			 * job id for notification
982 			 * (should be MAXBASENAME, not 14, but no can do)
983 			 */
984 			(void) sscanf(&buf[1], "%14s", Jobid);
985 			break;
986 
987 		default:
988 			break;
989 	    }
990 	}
991 
992 	fclose(xfp);
993 	DEBUG(4, "fin - %s, ", fin);
994 	DEBUG(4, "fout - %s, ", fout);
995 	DEBUG(4, "ferr - %s, ", ferr);
996 	DEBUG(4, "sysout - %s, ", sysout);
997 	DEBUG(4, "syserr - %s, ", syserr);
998 	DEBUG(4, "user - %s\n", user);
999 	DEBUG(4, "incmd - %s\n", incmd);
1000 
1001 	scRexe(origsys, origuser, Loginuser, incmd);
1002 
1003 	if (retuser[0] != NULLCHAR)
1004 	    (void) strlcpy(user, retuser, sizeof (user)); /* pick on this guy */
1005 
1006 	/* get rid of stuff that can be dangerous */
1007 	if ((p = strpbrk(user, Shchar)) != NULL) {
1008 	    *p = NULLCHAR;
1009 	}
1010 
1011 	if (incmd[0] == NULLCHAR) {
1012 	    /* this is a bad X. file - just get rid of it */
1013 	    toCorrupt(_Xfile);
1014 	    continue;
1015 	}
1016 
1017 	/*
1018 	 * send_nothing must be explicitly requested to avert failure status
1019 	 * send_zero must be explicitly requested for success notification
1020 	 */
1021 	if (!send_nothing)
1022 		send_nonzero++;
1023 
1024 	/*
1025 	 * command execution
1026 	 */
1027 
1028 	/*
1029 	 * generate a temporary file (if necessary)
1030 	 * to hold output to be shipped back
1031 	 */
1032 	if (EQUALS(fout, "/dev/null"))
1033 	    (void) strcpy(dfile, "/dev/null");
1034 	else {
1035 	    gename(DATAPRE, sysout, 'O', tempname);
1036 	    (void) snprintf(dfile, sizeof (dfile), "%s/%s", WORKSPACE,
1037 		tempname);
1038 	}
1039 
1040 	/*
1041 	 * generate a temporary file (if necessary)
1042 	 * to hold errors to be shipped back
1043 	 */
1044 /*
1045  * This is what really should be done. However for compatibility
1046  * for the interim at least, we will always create temp file
1047  * so we can return error output. If this temp file IS conditionally
1048  * created, we must remove the unlink() of errDfile at the end
1049  * because it may REALLY be /dev/null.
1050  *	if (EQUALS(ferr, "/dev/null"))
1051  *	    (void) strcpy(errDfile, "/dev/null");
1052  *	else {
1053  */
1054 	    gename(DATAPRE, syserr, 'E', tempname);
1055 	    (void) snprintf(errDfile, sizeof (errDfile), "%s/%s",
1056 		WORKSPACE, tempname);
1057 /*
1058  *	}
1059  */
1060 
1061 	/* initialize command line */
1062 	/* set up two environment variables, remote machine name */
1063 	/* and remote user name if available from R line */
1064 	/*
1065 	 * xcu4 requires that uucp *does* expand wildcards and uux *does not*
1066 	 * expand wild cards... Further restrictions are that uux must work
1067 	 * with every other uucp / uux that initiated a request, so nothing
1068 	 * strange can been done to communicate that it was uucp that sent
1069 	 * the request and not uux,  What we settle on here is looking for
1070 	 * the command name uucp and expanding wildcards in only that case.
1071 	 * It is true that a user can spoof this using uux, but in reality
1072 	 * this would be identical to using the uucp command to start with.
1073 	 */
1074 	if (strncmp(incmd, "uucp ", 5) == 0) {
1075 		(void) snprintf(_Cmd, sizeof (_Cmd),
1076 		    "%s %s UU_MACHINE=%s UU_USER=%s "
1077 		    " export UU_MACHINE UU_USER PATH; ",
1078 		    PATH, LOGNAME, Rmtname, user);
1079 	} else {
1080 		(void) snprintf(_Cmd, sizeof (_Cmd),
1081 		    "%s %s UU_MACHINE=%s UU_USER=%s "
1082 		    " export UU_MACHINE UU_USER PATH; set -f; ",
1083 		    PATH, LOGNAME, Rmtname, user);
1084 	}
1085 
1086 	/*
1087 	 * check to see if command can be executed
1088 	 */
1089 	_CargType = C_COMMAND;	/* the first thing is a command */
1090 	cmdok = chkpart(incmd);
1091 
1092 	if (badfiles || (cmdok == BAD_COMMAND) || cmdok == BAD_FILE) {
1093 	    if (cmdok == BAD_COMMAND) {
1094 		(void) snprintf(lbuf, sizeof (lbuf), "%s!%s XQT DENIED",
1095 		    Rmtname, user);
1096 		(void) snprintf(msgbuf, sizeof (msgbuf),
1097 		    "execution permission denied to %s!%s", Rmtname, user);
1098 	    } else {
1099 		(void) snprintf(lbuf, sizeof (lbuf),
1100 		    "%s!%s XQT - STDIN/STDOUT/FILE ACCESS DENIED",
1101 		    Rmtname, user);
1102 		(void) snprintf(msgbuf, sizeof (msgbuf),
1103 		    "file access denied to %s!%s", Rmtname, user);
1104 	    }
1105 	    logent(incmd, lbuf);
1106 	    DEBUG(4, "bad command %s\n", incmd);
1107 
1108 	    scWlog(); /* log security vialotion */
1109 
1110 	    if (send_nonzero)
1111 		retosndr(user, Rmtname, return_stdin ? fin : "",
1112 		    incmd, msgbuf, "");
1113 	    if (store_status)
1114 		    uucpst(Rmtname, _Sfile, "", incmd, msgbuf);
1115 	    goto rmfiles;
1116 	}
1117 
1118 	(void) snprintf(lbuf, sizeof (lbuf), "%s!%s XQT", Rmtname, user);
1119 	logent(_Cmd, lbuf);
1120 	DEBUG(4, "cmd %s\n", _Cmd);
1121 
1122 	/* move files to execute directory and change to that directory */
1123 
1124 	mv_Xfiles();
1125 
1126 	ASSERT(chdir(XQTDIR) == 0, Ct_CHDIR, XQTDIR, errno);
1127 	acRexe(&_Xfile[2], origsys, origuser, Myname, Loginuser, incmd);
1128 
1129 	/* invoke shell to execute command */
1130 
1131 	mask = umask(0);
1132 	DEBUG(7, "full cmd: %s\n", _Cmd);
1133 
1134 	cpucycle();
1135 	ret = shio(_Cmd, fin, dfile, errDfile);
1136 	if (ret == 0)
1137 		acEndexe(cpucycle(), COMPLETE);
1138 	else
1139 		acEndexe(cpucycle(), PARTIAL);
1140 
1141 	umask(mask);
1142 	if (ret == -1) {	/* -1 means the fork() failed */
1143 		unmv_Xfiles();	/* put things back */
1144 		errent(Ct_FORK, buf, errno, __FILE__, __LINE__);
1145 		cleanup(1);
1146 	}
1147 
1148 	if (ret == 0) {				/* exit == signal == 0 */
1149 	    (void) strcpy(msgbuf, "exited normally");
1150 	} else {				/* exit != 0 */
1151 	    int exitcode = (ret >> 8) & 0377;
1152 
1153 	    if (exitcode) {
1154 		/* exit != 0 */
1155 		(void) snprintf(msgbuf, sizeof (msgbuf),
1156 		    "exited with status %d", exitcode);
1157 	    } else {
1158 		/* signal != 0 */
1159 		(void) snprintf(msgbuf, sizeof (msgbuf),
1160 		    "terminated by signal %d", ret & 0177);
1161 	    }
1162 	    DEBUG(5, "%s\n", msgbuf);
1163 	    (void) snprintf(lbuf, sizeof (lbuf), "%s - %s", incmd, msgbuf);
1164 	    logent(lbuf, "COMMAND FAIL");
1165 	}
1166 
1167 	/* change back to spool directory */
1168 
1169 	chremdir(Rmtname);
1170 
1171 	/* remove file */
1172 
1173 	rm_Xfiles();
1174 
1175 	/*
1176 	 * We used to append stderr to stdout. Since stderr can
1177 	 * now be specified separately, never append it to stdout.
1178 	 * It can still be gotten via -s status file option.
1179 	 */
1180 
1181 	if (!EQUALS(fout, "/dev/null")) {
1182 	    /*
1183 	     * if output is on this machine copy output
1184 	     * there, otherwise spawn job to send to send
1185 	     * output elsewhere.
1186 	     */
1187 
1188 	    if (EQUALS(sysout, Myname)) {
1189 		if ((xmv(dfile, fout)) != 0) {
1190 		    logent("FAILED", "COPY");
1191 		    scWrite();
1192 		    (void) snprintf(msgbuf + strlen(msgbuf),
1193 			(sizeof (msgbuf) - strlen(msgbuf)),
1194 			"\nCould not move stdout to %s,", fout);
1195 		    if (putinpub(fout, dfile, origuser) == 0)
1196 			(void) snprintf(msgbuf + strlen(msgbuf),
1197 			    (sizeof (msgbuf) - strlen(msgbuf)),
1198 			    "\n\tstdout left in %s.", fout);
1199 		    else
1200 			(void) strlcat(msgbuf, " stdout lost.",
1201 			    sizeof (msgbuf));
1202 		}
1203 	    } else {
1204 		if (eaccess(GRADES, 04) != -1)
1205 			dqueue = fdgrade();
1206 		else
1207 			dqueue = Grade;
1208 		gename(CMDPRE, sysout, dqueue, tempname);
1209 		(void) snprintf(cfile, sizeof (cfile), "%s/%s",
1210 		    WORKSPACE, tempname);
1211 		fp = fdopen(ret = creat(cfile, CFILEMODE), "w");
1212 		ASSERT(ret >= 0 && fp != NULL, Ct_OPEN, cfile, errno);
1213 		(void) fprintf(fp, "S %s %s %s -d %s 0666\n",
1214 		    BASENAME(dfile, '/'), fout, user, BASENAME(dfile, '/'));
1215 		fclose(fp);
1216 		(void) snprintf(sendsys, sizeof (sendsys), "%s/%c", sysout,
1217 		    dqueue);
1218 		sendsys[MAXNAMESIZE-1] = '\0';
1219 		wfcommit(dfile, BASENAME(dfile, '/'), sendsys);
1220 		wfcommit(cfile, BASENAME(cfile, '/'), sendsys);
1221 	    }
1222 	}
1223 	if (!EQUALS(ferr, "/dev/null")) {
1224 	    /*
1225 	     * if stderr is on this machine copy output
1226 	     * there, otherwise spawn job to send to send
1227 	     * it elsewhere.
1228 	     */
1229 	    if (EQUALS(syserr, Myname)) {
1230 		errname = ferr;
1231 		if ((xmv(errDfile, ferr)) != 0) {
1232 		    logent("FAILED", "COPY");
1233 		    scWrite();
1234 		    (void) snprintf(msgbuf + strlen(msgbuf),
1235 			(sizeof (msgbuf) - strlen(msgbuf)),
1236 			"\nCould not move stderr to %s,", ferr);
1237 		    if (putinpub(ferr, errDfile, origuser) == 0) {
1238 			(void) snprintf(msgbuf+strlen(msgbuf),
1239 			    (sizeof (msgbuf) - strlen(msgbuf)),
1240 			    "\n\tstderr left in %s", ferr);
1241 		    } else {
1242 			errname = errDfile;
1243 			(void) strlcat(msgbuf, " stderr lost.",
1244 			    sizeof (msgbuf));
1245 		    }
1246 		}
1247 	    } else {
1248 		if (eaccess(GRADES, 04) != -1)
1249 			dqueue = fdgrade();
1250 		else
1251 			dqueue = Grade;
1252 		gename(CMDPRE, syserr, dqueue, tempname);
1253 		(void) snprintf(cfile, sizeof (cfile), "%s/%s",
1254 		    WORKSPACE, tempname);
1255 		fp = fdopen(ret = creat(cfile, CFILEMODE), "w");
1256 		ASSERT(ret >= 0 && fp != NULL, Ct_OPEN, cfile, errno);
1257 		(void) fprintf(fp, "S %s %s %s -d %s 0666\n",
1258 		    BASENAME(errDfile, '/'), ferr, user,
1259 		    BASENAME(errDfile, '/'));
1260 		fclose(fp);
1261 		(void) snprintf(sendsys, sizeof (sendsys), "%s/%c",
1262 		    syserr, dqueue);
1263 		sendsys[MAXNAMESIZE-1] = '\0';
1264 		wfcommit(errDfile, BASENAME(errDfile, '/'), sendsys);
1265 		wfcommit(cfile, BASENAME(cfile, '/'), sendsys);
1266 	    }
1267 	} else {
1268 /*
1269  * If we conditionally create stderr tempfile, we must
1270  * remove this unlink() since errDfile may REALLY be /dev/null
1271  */
1272 	    unlink(errDfile);
1273 	}
1274 
1275 	if (ret == 0) {
1276 	    if (send_zero)
1277 		retosndr(user, Rmtname, "", incmd, msgbuf, "");
1278 	    if (store_status)
1279 		uucpst(Rmtname, _Sfile, "", incmd, msgbuf);
1280 	} else {
1281 	    if (send_nonzero)
1282 		retosndr(user, Rmtname, return_stdin ? fin : "",
1283 			incmd, msgbuf, errname);
1284 	    if (store_status)
1285 		uucpst(Rmtname, _Sfile, errname, incmd, msgbuf);
1286 	}
1287 
1288 rmfiles:
1289 
1290 	/* delete job files in spool directory */
1291 	xfp = fopen(_Xfile, "r");
1292 	ASSERT(xfp != NULL, Ct_OPEN, _Xfile, errno);
1293 	while (fgets(buf, BUFSIZ, xfp) != NULL) {
1294 		if (buf[0] != X_RQDFILE)
1295 			continue;
1296 		(void) sscanf(&buf[1], "%63s", file);
1297 		expfile(file);
1298 		if (chkpth(file, CK_WRITE) != FAIL)
1299 			(void) unlink(file);
1300 	}
1301 	(void) unlink(_Xfile);
1302 	fclose(xfp);
1303     }
1304 }
1305