1/*-
2 * Copyright (C) 2005 Diomidis Spinellis. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 *
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <ctype.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37
38#include <sys/types.h>
39#include <sys/param.h>
40#include <sys/syslog.h>
41
42#include "portald.h"
43
44/* Usage conventions for the pipe's endpoints. */
45#define READ_END	0
46#define WRITE_END	1
47
48static int  errlog(void);
49static int  parse_argv(char *args, char **argv);
50
51int portal_pipe(struct portal_cred *pcr, char *key, char **v,
52    int kso __unused, int *fdp)
53{
54	int fd[2];		/* Pipe endpoints. */
55	int caller_end;		/* The pipe end we will use. */
56	int process_end;	/* The pipe end the spawned process will use. */
57	int redirect_fd;	/* The fd to redirect on the spawned process. */
58	char pbuf[MAXPATHLEN];
59	int error = 0;
60	int i;
61	char **argv;
62	int argc;
63	struct portal_cred save_area;
64
65	/* Validate open mode, and assign roles. */
66	if ((pcr->pcr_flag & FWRITE) && (pcr->pcr_flag & FREAD))
67		/* Don't allow both on a single fd. */
68		return (EINVAL);
69	else if (pcr->pcr_flag & FREAD) {
70		/*
71		 * The caller reads from the pipe,
72		 * the spawned process writes to it.
73		 */
74		caller_end = READ_END;
75		process_end = WRITE_END;
76		redirect_fd = STDOUT_FILENO;
77	} else if (pcr->pcr_flag & FWRITE) {
78		/*
79		 * The caller writes to the pipe,
80		 * the spawned process reads from it.
81		 */
82		caller_end = WRITE_END;
83		process_end = READ_END;
84		redirect_fd = STDIN_FILENO;
85	} else
86		return (EINVAL);
87
88	/* Get and check command line. */
89	pbuf[0] = '/';
90	strcpy(pbuf+1, key + (v[1] ? strlen(v[1]) : 0));
91	argc = parse_argv(pbuf, NULL);
92	if (argc == 0)
93		return (ENOENT);
94
95	/* Swap priviledges. */
96	if (set_user_credentials(pcr, &save_area) < 0)
97		return (errno);
98
99	/* Redirect and spawn the specified process. */
100	fd[READ_END] = fd[WRITE_END] = -1;
101	if (pipe(fd) < 0) {
102		error = errno;
103		goto done;
104	}
105	switch (fork()) {
106	case -1: /* Error */
107		error = errno;
108		break;
109	default: /* Parent */
110		(void)close(fd[process_end]);
111		break;
112	case 0: /* Child */
113		argv = (char **)malloc((argc + 1) * sizeof(char *));
114		if (argv == 0) {
115			syslog(LOG_ALERT,
116			    "malloc: failed to get space for %d pointers",
117			    argc + 1);
118			exit(EXIT_FAILURE);
119		}
120		parse_argv(pbuf, argv);
121
122		if (dup2(fd[process_end], redirect_fd) < 0) {
123			syslog(LOG_ERR, "dup2: %m");
124			exit(EXIT_FAILURE);
125		}
126		(void)close(fd[caller_end]);
127		(void)close(fd[process_end]);
128		if (errlog() < 0) {
129			syslog(LOG_ERR, "errlog: %m");
130			exit(EXIT_FAILURE);
131		}
132		if (execv(argv[0], argv) < 0) {
133			syslog(LOG_ERR, "execv(%s): %m", argv[0]);
134			exit(EXIT_FAILURE);
135		}
136		/* NOTREACHED */
137	}
138
139done:
140	/* Re-establish our priviledges. */
141	if (restore_credentials(&save_area) < 0)
142		error = errno;
143
144	/* Set return fd value. */
145	if (error == 0)
146		*fdp = fd[caller_end];
147	else {
148		for (i = 0; i < 2; i++)
149			if (fd[i] >= 0)
150				(void)close(fd[i]);
151		*fdp = -1;
152	}
153
154	return (error);
155}
156
157/*
158 * Redirect stderr to the system log.
159 * Return 0 if ok.
160 * Return -1 with errno set on error.
161 */
162static int
163errlog(void)
164{
165	int fd[2];
166	char buff[1024];
167	FILE *f;
168	int ret = 0;
169
170	if (pipe(fd) < 0)
171		return (-1);
172	switch (fork()) {
173	case -1: /* Error */
174		return (-1);
175	case 0: /* Child */
176		if ((f = fdopen(fd[READ_END], "r")) == NULL) {
177			syslog(LOG_ERR, "fdopen: %m");
178			exit(EXIT_FAILURE);
179		}
180		(void)close(fd[WRITE_END]);
181		while (fgets(buff, sizeof(buff), f) != NULL)
182			syslog(LOG_ERR, "exec: %s", buff);
183		exit(EXIT_SUCCESS);
184		/* NOTREACHED */
185	default: /* Parent */
186		if (dup2(fd[WRITE_END], STDERR_FILENO) < 0)
187			ret = -1;
188		(void)close(fd[READ_END]);
189		(void)close(fd[WRITE_END]);
190		break;
191	}
192	return (ret);
193}
194
195/*
196 * Parse the args string as a space-separated argument vector.
197 * If argv is not NULL, split the string into its constituent
198 * components, and set argv to point to the beginning  of each
199 * string component; NULL-terminating argv.
200 * Return the number of string components.
201 */
202static int
203parse_argv(char *args, char **argv)
204{
205	int count = 0;
206	char *p;
207	enum {WORD, SPACE} state = SPACE;
208
209	for (p = args; *p; p++)
210		switch (state) {
211		case WORD:
212			if (isspace(*p)) {
213				if (argv)
214					*p = '\0';
215				state = SPACE;
216			}
217			break;
218		case SPACE:
219			if (!isspace(*p)) {
220				if (argv)
221					argv[count] = p;
222				count++;
223				state = WORD;
224			}
225		}
226	if (argv)
227		argv[count] = NULL;
228	return (count);
229}
230