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