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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <assert.h>
27#include <libuutil.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <zone.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34
35#include "startd.h"
36
37/*
38 * This file contains functions for setting the environment for
39 * processes started by svc.startd.
40 */
41
42#define	MAXCMDL		512
43#define	DEF_PATH	"PATH=/usr/sbin:/usr/bin"
44
45static char *ENVFILE	= "/etc/default/init"; /* Default env. */
46
47static char **glob_envp;	/* Array of environment strings */
48static int glob_env_n;		/* Number of environment slots allocated. */
49
50static char zonename[ZONENAME_MAX];
51
52/*
53 * init_env()
54 *   A clone of the work init.c does to provide as much compatibility
55 *   for startup scripts as possible.
56 */
57void
58init_env()
59{
60	int	i;
61	char	line[MAXCMDL];
62	FILE	*fp;
63	int	inquotes, length, wslength;
64	char	*tokp, *cp1, *cp2;
65	char	**newp;
66
67	glob_env_n = 16;
68	glob_envp = startd_alloc(sizeof (*glob_envp) * glob_env_n);
69
70	glob_envp[0] = startd_alloc((unsigned)(strlen(DEF_PATH)+2));
71	(void) strcpy(glob_envp[0], DEF_PATH);
72
73	if ((fp = fopen(ENVFILE, "r")) == NULL) {
74		uu_warn("Cannot open %s. Environment not initialized.\n",
75		    ENVFILE);
76
77		glob_envp[1] = NULL;
78		return;
79	}
80
81	i = 1;
82
83	while (fgets(line, MAXCMDL - 1, fp) != NULL) {
84		/*
85		 * Toss newline
86		 */
87		length = strlen(line);
88		if (line[length - 1] == '\n')
89			line[length - 1] = '\0';
90
91		/*
92		 * Ignore blank or comment lines.
93		 */
94		if (line[0] == '#' || line[0] == '\0' ||
95		    (wslength = strspn(line, " \t\n")) == strlen(line) ||
96		    strchr(line, '#') == line + wslength)
97			continue;
98
99		/*
100		 * First make a pass through the line and change
101		 * any non-quoted semi-colons to blanks so they
102		 * will be treated as token separators below.
103		 */
104		inquotes = 0;
105		for (cp1 = line; *cp1 != '\0'; cp1++) {
106			if (*cp1 == '"') {
107				if (inquotes == 0)
108					inquotes = 1;
109				else
110					inquotes = 0;
111			} else if (*cp1 == ';') {
112				if (inquotes == 0)
113					*cp1 = ' ';
114			}
115		}
116
117		/*
118		 * Tokens within the line are separated by blanks
119		 *  and tabs.  For each token in the line which
120		 * contains a '=' we strip out any quotes and then
121		 * stick the token in the environment array.
122		 */
123		if ((tokp = strtok(line, " \t")) == NULL)
124			continue;
125
126		do {
127			cp1 = strchr(tokp, '=');
128			if (cp1 == NULL || cp1 == tokp)
129				continue;
130			length = strlen(tokp);
131			while ((cp1 = strpbrk(tokp, "\"\'")) != NULL) {
132				for (cp2 = cp1; cp2 < &tokp[length]; cp2++)
133					*cp2 = *(cp2 + 1);
134				length--;
135			}
136
137			/*
138			 * init already started us with this umask, and we
139			 * handled it in startd.c, so just skip it.
140			 */
141			if (strncmp(tokp, "CMASK=", 6) == 0 ||
142			    strncmp(tokp, "SMF_", 4) == 0)
143				continue;
144
145			glob_envp[i] = startd_alloc((unsigned)(length + 1));
146			(void) strcpy(glob_envp[i], tokp);
147
148			/*
149			 * Double the environment size whenever it is
150			 * full.
151			 */
152			if (++i == glob_env_n) {
153				glob_env_n *= 2;
154				newp = startd_alloc(sizeof (*glob_envp) *
155				    glob_env_n);
156				(void) memcpy(newp, glob_envp,
157				    sizeof (*glob_envp) * glob_env_n / 2);
158				startd_free(glob_envp,
159				    sizeof (*glob_envp) * glob_env_n / 2);
160				glob_envp = newp;
161			}
162		} while ((tokp = strtok(NULL, " \t")) != NULL);
163	}
164
165	startd_fclose(fp);
166
167	/* Append a null pointer to the environment array to mark its end. */
168	glob_envp[i] = NULL;
169
170	/*
171	 * Get the zonename once; it is used to set SMF_ZONENAME for methods.
172	 */
173	(void) getzonenamebyid(getzoneid(), zonename, sizeof (zonename));
174
175}
176
177static int
178valid_env_var(const char *var, const restarter_inst_t *inst, const char *path)
179{
180	char *cp = strchr(var, '=');
181
182	if (cp == NULL || cp == var) {
183		if (inst != NULL)
184			log_instance(inst, B_FALSE, "Invalid environment "
185			    "variable \"%s\".", var);
186		return (0);
187	} else if (strncmp(var, "SMF_", 4) == 0) {
188		if (inst != NULL)
189			log_instance(inst, B_FALSE, "Invalid environment "
190			    "variable \"%s\"; \"SMF_\" prefix is reserved.",
191			    var);
192		return (0);
193	} else if (path != NULL && strncmp(var, "PATH=", 5) == 0) {
194		return (0);
195	}
196
197	return (1);
198}
199
200static char **
201find_dup(const char *var, char **env, const restarter_inst_t *inst)
202{
203	char **p;
204	char *tmp;
205
206	for (p = env; *p != NULL; p++) {
207		assert((tmp = strchr(*p, '=')) != NULL);
208		tmp++;
209		if (strncmp(*p, var, tmp - *p) == 0)
210			break;
211	}
212
213	if (*p == NULL)
214		return (NULL);
215
216	/*
217	 * The first entry in the array can be ignored when it is the
218	 * default path.
219	 */
220	if (inst != NULL && p != env &&
221	    strncmp(*p, DEF_PATH, strlen(DEF_PATH)) != 0) {
222		log_instance(inst, B_FALSE, "Ignoring duplicate "
223		    "environment variable \"%s\".", *p);
224	}
225
226	return (p);
227}
228
229/*
230 * Create an environment which is appropriate for spawning an SMF
231 * aware process. The new environment will consist of the values from
232 * the global environment as modified by the supplied (local) environment.
233 *
234 * In order to preserve the correctness of the new environment,
235 * various checks are performed on the local environment (init_env()
236 * is relied upon to ensure the global environment is correct):
237 *
238 * - All SMF_ entries are ignored. All SMF_ entries should be provided
239 *   by this function.
240 * - Duplicates in the entry are eliminated.
241 * - Malformed entries are eliminated.
242 *
243 * Detected errors are logged as warnings to the appropriate instance
244 * logfile, since a single bad entry should not be enough to prevent
245 * an SMF_ functional environment from being created. The faulty entry
246 * is then ignored when building the environment.
247 *
248 * If env is NULL, then the return is an environment which contains
249 * all default values.
250 *
251 * If "path" is non-NULL, it will silently over-ride any previous
252 * PATH environment variable.
253 *
254 * NB: The returned env and strings are allocated using startd_alloc().
255 */
256char **
257set_smf_env(char **env, size_t env_sz, const char *path,
258    const restarter_inst_t *inst, const char *method)
259{
260	char **nenv;
261	char **p, **np;
262	size_t nenv_size;
263	size_t sz;
264
265	/*
266	 * Max. of glob_env, env, four SMF_ variables,
267	 * path, and terminating NULL.
268	 */
269	nenv_size = glob_env_n + env_sz + 4 + 1 + 1;
270
271	nenv = startd_zalloc(sizeof (char *) * nenv_size);
272
273	np = nenv;
274
275	if (path != NULL) {
276		sz = strlen(path) + 1;
277		*np = startd_alloc(sz);
278		(void) strlcpy(*np, path, sz);
279		np++;
280	}
281
282	if (inst) {
283		sz = sizeof ("SMF_FMRI=") + strlen(inst->ri_i.i_fmri);
284		*np = startd_alloc(sz);
285		(void) strlcpy(*np, "SMF_FMRI=", sz);
286		(void) strlcat(*np, inst->ri_i.i_fmri, sz);
287		np++;
288	}
289
290	if (method) {
291		sz = sizeof ("SMF_METHOD=") + strlen(method);
292		*np = startd_alloc(sz);
293		(void) strlcpy(*np, "SMF_METHOD=", sz);
294		(void) strlcat(*np, method, sz);
295		np++;
296	}
297
298	sz = sizeof ("SMF_RESTARTER=") + strlen(SCF_SERVICE_STARTD);
299	*np = startd_alloc(sz);
300	(void) strlcpy(*np, "SMF_RESTARTER=", sz);
301	(void) strlcat(*np, SCF_SERVICE_STARTD, sz);
302	np++;
303
304	sz = sizeof ("SMF_ZONENAME=") + strlen(zonename);
305	*np = startd_alloc(sz);
306	(void) strlcpy(*np, "SMF_ZONENAME=", sz);
307	(void) strlcat(*np, zonename, sz);
308	np++;
309
310	for (p = glob_envp; *p != NULL; p++) {
311		if (valid_env_var(*p, inst, path)) {
312			sz = strlen(*p) + 1;
313			*np = startd_alloc(sz);
314			(void) strlcpy(*np, *p, sz);
315			np++;
316		}
317	}
318
319	if (env) {
320		for (p = env; *p != NULL; p++) {
321			char **dup_pos;
322
323			if (!valid_env_var(*p, inst, path))
324				continue;
325
326			if ((dup_pos = find_dup(*p, nenv, inst)) != NULL) {
327				startd_free(*dup_pos, strlen(*dup_pos) + 1);
328				sz = strlen(*p) + 1;
329				*dup_pos = startd_alloc(sz);
330				(void) strlcpy(*dup_pos, *p, sz);
331			} else {
332				sz = strlen(*p) + 1;
333				*np = startd_alloc(sz);
334				(void) strlcpy(*np, *p, sz);
335				np++;
336			}
337		}
338	}
339	*np = NULL;
340
341	return (nenv);
342}
343