1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                  David Korn <dgk@research.att.com>                   *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21/*
22 * regression test intercept control
23 * enable with SHOPT_REGRESS==1 in Makefile
24 * not for production use
25 * see --man for details
26 * all string constants inline here instead of in data/...
27 *
28 *   David Korn
29 *   at&t research
30 */
31
32#include	"defs.h"
33
34#if SHOPT_REGRESS
35
36#include	<error.h>
37#include	<ls.h>
38#include	"io.h"
39#include	"builtins.h"
40#include	<tmx.h>
41
42#define REGRESS_HEADER	"ksh:REGRESS:"
43
44#define TRACE(r,i,f)		sh_regress(REGRESS_##r, i, sfprints f, __LINE__, __FILE__)
45
46static const char	usage[] =
47"[-1p0?\n@(#)$Id: __regress__ (AT&T Research) 2009-03-29 $\n]"
48USAGE_LICENSE
49"[+NAME?__regress__ - shell regression test intercept control]"
50"[+DESCRIPTION?\b__regress__\b controls the regression test intercepts "
51    "for shells compiled with SHOPT_REGRESS==1. Shells compiled this way are "
52    "for testing only. In addition to \b__regress__\b and the \b--regress\b "
53    "command line option, these shells may contain system library function "
54    "intercepts that behave different from the native counterparts.]"
55"[+?Each option controls a different test and possibly a different set "
56    "of intercepts. The options are interpreted \bdd\b(1) style -- '-' or "
57    "'--' prefix not required. This simplifies the specification of the "
58    "command line \b--regress\b=\avalue\a option, where \avalue\a is passed "
59    "as an option to the \b__regress__\b builtin. Typically regression test "
60    "intercepts are enabled with one or more command line \b--regress\b "
61    "options, with optional specific calls to \b__regress__\b in test "
62    "scripts to enable/disable intercepts as the test progresses.]"
63"[+?Each enabled intercept may result in trace lines of the form \b" REGRESS_HEADER
64    "\aoption\a:\aintercept\a:\ainfo\a on the standard error, where "
65    "\aoption\a is one of the options below, \aintercept\a is the name of "
66    "the specific intercept for \aoption\a, and \ainfo\a is \aoption\a "
67    "specific information. Unless noted otherwise, one regression test trace "
68    "line is produced each time an enabled intercept is called.]"
69"[101:egid?The intercept effective gid is set to \aoriginal-egid\a. The "
70    "effective gid of the underlying system process is not affected. The "
71    "trace line info is either \begid==rgid\b or \begid!=rgid\b. The "
72    "intercepts are:]#?[original-egid:=1]"
73    "{"
74        "[+getegid()?The intercept effecive gid is returned. The "
75            "\bsetgid\b() intercept may change this between the real gid and "
76            "\aoriginal-egid\a.]"
77        "[+setgid(gid)?Sets the intercept effective gid to \agid\a. "
78            "Fails if \agid\a is neither the real gid nor "
79            "\aoriginal-egid\a.]"
80    "}"
81"[102:euid?The intercept effective uid is set to \aoriginal-euid\a. The "
82    "effective uid of the underlying system process is not affected. The "
83    "trace line info is either \beuid==ruid\b or \beuid!=ruid\b. The "
84    "intercepts are:]#?[original-euid:=1]"
85    "{"
86        "[+geteuid()?The intercept effecive uid is returned. The "
87            "\bsetuid\b() intercept may change this between the real uid and "
88            "\aoriginal-euid\a.]"
89        "[+setuid(uid)?Sets the intercept effective uid to \auid\a. "
90            "Fails if \auid\a is neither the real uid nor "
91            "\aoriginal-euid\a.]"
92    "}"
93"[103:p_suid?Specifies a value for SHOPT_P_SUID. Effective uids greater "
94    "than the non-privileged-uid disable the priveleged mode. The intercepts "
95    "are:]#?[non-privileged-uid:=1]"
96    "{"
97        "[+SHOPT_P_SUID?The SHOPT_P_SUID macro value is overridden by "
98            "\bp_suid\b. A trace line is output for each SHOPT_P_SUID "
99            "access.]"
100    "}"
101"[104:source?The intercepts are:]"
102    "{"
103        "[+sh_source()?The trace line info is the path of the script "
104            "being sourced. Used to trace shell startup scripts.]"
105    "}"
106"[105:etc?Map file paths matching \b/etc/\b* to \aetc-dir\a/*. The "
107    "intercepts are:]:[etc-dir:=/etc]"
108    "{"
109        "[+sh_open()?Paths matching \b/etc/\b* are changed to "
110            "\aetc-dir\a/*.]"
111    "}"
112"[+SEE ALSO?\bksh\b(1), \bregress\b(1), \brt\b(1)]"
113;
114
115static const char*	regress_options[] =
116{
117	"ERROR",
118	"egid",
119	"euid",
120	"p_suid",
121	"source",
122	"etc",
123};
124
125void sh_regress_init(Shell_t* shp)
126{
127	static Regress_t	state;
128
129	shp->regress = &state;
130}
131
132/*
133 * regress info trace output
134 */
135
136void sh_regress(unsigned int index, const char* intercept, const char* info, unsigned int line, const char* file)
137{
138	char*	name;
139	char	buf[16];
140
141	if (index >= 1 && index <= elementsof(regress_options))
142		name = (char*)regress_options[index];
143	else
144		sfsprintf(name = buf, sizeof(buf), "%u", index);
145	sfprintf(sfstderr, REGRESS_HEADER "%s:%s:%s\n", name, intercept, fmtesc(info));
146}
147
148/*
149 * egid intercepts
150 */
151
152static gid_t	intercept_sgid = 0;
153static gid_t	intercept_egid = -1;
154static gid_t	intercept_rgid = -1;
155
156gid_t getegid(void)
157{
158	if (intercept_rgid == -1)
159		intercept_rgid = getgid();
160	if (sh_isregress(REGRESS_egid))
161	{
162		TRACE(egid, "getegid", ("%s", intercept_egid == intercept_rgid ? "egid==rgid" : "egid!=rgid"));
163		return intercept_egid;
164	}
165	return intercept_rgid;
166}
167
168int setgid(gid_t gid)
169{
170	if (intercept_rgid == -1)
171		intercept_rgid = getgid();
172	if (sh_isregress(REGRESS_egid))
173	{
174		if (gid != intercept_rgid && gid != intercept_sgid)
175		{
176			TRACE(egid, "setgid", ("%s", "EPERM"));
177			errno = EPERM;
178			return -1;
179		}
180		intercept_egid = gid;
181		TRACE(egid, "setgid", ("%s", intercept_egid == intercept_rgid ? "egid==rgid" : "egid!=rgid"));
182	}
183	else if (gid != intercept_rgid)
184	{
185		errno = EPERM;
186		return -1;
187	}
188	return 0;
189}
190
191/*
192 * euid intercepts
193 */
194
195static uid_t	intercept_suid = 0;
196static uid_t	intercept_euid = -1;
197static uid_t	intercept_ruid = -1;
198
199uid_t geteuid(void)
200{
201	if (intercept_ruid == -1)
202		intercept_ruid = getuid();
203	if (sh_isregress(REGRESS_euid))
204	{
205		TRACE(euid, "geteuid", ("%s", intercept_euid == intercept_ruid ? "euid==ruid" : "euid!=ruid"));
206		return intercept_euid;
207	}
208	return intercept_ruid;
209}
210
211int setuid(uid_t uid)
212{
213	if (intercept_ruid == -1)
214		intercept_ruid = getuid();
215	if (sh_isregress(REGRESS_euid))
216	{
217		if (uid != intercept_ruid && uid != intercept_suid)
218		{
219			TRACE(euid, "setuid", ("%s", "EPERM"));
220			errno = EPERM;
221			return -1;
222		}
223		intercept_euid = uid;
224		TRACE(euid, "setuid", ("%s", intercept_euid == intercept_ruid ? "euid==ruid" : "euid!=ruid"));
225	}
226	else if (uid != intercept_ruid)
227	{
228		errno = EPERM;
229		return -1;
230	}
231	return 0;
232}
233
234/*
235 * p_suid intercept
236 */
237
238static uid_t	intercept_p_suid = 0x7fffffff;
239
240uid_t sh_regress_p_suid(unsigned int line, const char* file)
241{
242	REGRESS(p_suid, "SHOPT_P_SUID", ("%d", intercept_p_suid));
243	return intercept_p_suid;
244}
245
246/*
247 * p_suid intercept
248 */
249
250static char*	intercept_etc = 0;
251
252char* sh_regress_etc(const char* path, unsigned int line, const char* file)
253{
254	REGRESS(etc, "sh_open", ("%s => %s%s", path, intercept_etc, path+4));
255	return intercept_etc;
256}
257
258/*
259 * __regress__ builtin
260 */
261
262int b___regress__(int argc, char** argv, Shbltin_t *context)
263{
264	register Shell_t*	shp = context->shp;
265	int			n;
266
267	for (;;)
268	{
269		switch (n = optget(argv, usage))
270		{
271		case '?':
272			errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg);
273			break;
274		case ':':
275			errormsg(SH_DICT, 2, "%s", opt_info.arg);
276			break;
277		case 0:
278			break;
279		default:
280			if (n < -100)
281			{
282				n = -(n + 100);
283				if (opt_info.arg || opt_info.number)
284					sh_onregress(n);
285				else
286					sh_offregress(n);
287				switch (n)
288				{
289				case REGRESS_egid:
290					if (sh_isregress(n))
291					{
292						intercept_egid = intercept_sgid = (gid_t)opt_info.number;
293						TRACE(egid, argv[0], ("%d", intercept_egid));
294					}
295					else
296						TRACE(egid, argv[0], ("%s", "off"));
297					break;
298				case REGRESS_euid:
299					if (sh_isregress(n))
300					{
301						intercept_euid = intercept_suid = (uid_t)opt_info.number;
302						TRACE(euid, argv[0], ("%d", intercept_euid));
303					}
304					else
305						TRACE(euid, argv[0], ("%s", "off"));
306					break;
307				case REGRESS_p_suid:
308					if (sh_isregress(n))
309					{
310						intercept_p_suid = (uid_t)opt_info.number;
311						TRACE(p_suid, argv[0], ("%d", intercept_p_suid));
312					}
313					else
314						TRACE(p_suid, argv[0], ("%s", "off"));
315					break;
316				case REGRESS_source:
317					TRACE(source, argv[0], ("%s", sh_isregress(n) ? "on" : "off"));
318					break;
319				case REGRESS_etc:
320					if (sh_isregress(n))
321					{
322						intercept_etc = opt_info.arg;
323						TRACE(etc, argv[0], ("%s", intercept_etc));
324					}
325					else
326						TRACE(etc, argv[0], ("%s", "off"));
327					break;
328				}
329			}
330			continue;
331		}
332		break;
333	}
334	if (error_info.errors || *(argv + opt_info.index))
335		errormsg(SH_DICT, ERROR_usage(2), "%s", optusage(NiL));
336	return 0;
337}
338
339#else
340
341NoN(regress)
342
343#endif
344