xref: /freebsd/usr.sbin/cron/cron/popen.c (revision 7098712bbfd60df9db324decbc1a5dd8f751356f)
1 /*
2  * SPDX-License-Identifier: BSD-4.3TAHOE
3  *
4  * Copyright (c) 1988 The Regents of the University of California.
5  * All rights reserved.
6  *
7  * This code is derived from software written by Ken Arnold and
8  * published in UNIX Review, Vol. 6, No. 8.
9  *
10  * Redistribution and use in source and binary forms are permitted
11  * provided that the above copyright notice and this paragraph are
12  * duplicated in all such forms and that any documentation,
13  * advertising materials, and other materials related to such
14  * distribution and use acknowledge that the software was developed
15  * by the University of California, Berkeley.  The name of the
16  * University may not be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21  *
22  */
23 
24 /* this came out of the ftpd sources; it's been modified to avoid the
25  * globbing stuff since we don't need it.  also execvp instead of execv.
26  */
27 
28 #ifndef lint
29 static const char rcsid[] =
30     "$Id: popen.c,v 1.3 1998/08/14 00:32:41 vixie Exp $";
31 #endif /* not lint */
32 
33 #include "cron.h"
34 #if defined(LOGIN_CAP)
35 # include <login_cap.h>
36 #endif
37 
38 #define MAX_ARGS 100
39 #define WANT_GLOBBING 0
40 
41 /*
42  * Special version of popen which avoids call to shell.  This insures no one
43  * may create a pipe to a hidden program as a side effect of a list or dir
44  * command.
45  */
46 static PID_T *pids;
47 static int fds;
48 
49 FILE *
cron_popen(char * program,char * type,entry * e,PID_T * pidptr)50 cron_popen(char *program, char *type, entry *e, PID_T *pidptr)
51 {
52 	char *cp;
53 	FILE *iop;
54 	int argc, pdes[2];
55 	PID_T pid;
56 	char *usernm;
57 	char *argv[MAX_ARGS + 1];
58 # if defined(LOGIN_CAP)
59 	struct passwd	*pwd;
60 	login_cap_t *lc;
61 # endif
62 #if WANT_GLOBBING
63 	char **pop, *vv[2];
64 	int gargc;
65 	char *gargv[1000];
66 	extern char **glob(), **copyblk();
67 #endif
68 
69 	if ((*type != 'r' && *type != 'w') || type[1] != '\0')
70 		return (NULL);
71 
72 	if (!pids) {
73 		if ((fds = sysconf(_SC_OPEN_MAX)) <= 0)
74 			return (NULL);
75 		if (!(pids = calloc(fds, sizeof(PID_T))))
76 			return (NULL);
77 	}
78 	if (pipe(pdes) < 0)
79 		return (NULL);
80 
81 	/* break up string into pieces */
82 	for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
83 		if (!(argv[argc++] = strtok(cp, " \t\n")))
84 			break;
85 	argv[MAX_ARGS] = NULL;
86 
87 #if WANT_GLOBBING
88 	/* glob each piece */
89 	gargv[0] = argv[0];
90 	for (gargc = argc = 1; argv[argc]; argc++) {
91 		if (!(pop = glob(argv[argc]))) {	/* globbing failed */
92 			vv[0] = argv[argc];
93 			vv[1] = NULL;
94 			pop = copyblk(vv);
95 		}
96 		argv[argc] = (char *)pop;		/* save to free later */
97 		while (*pop && gargc < 1000)
98 			gargv[gargc++] = *pop++;
99 	}
100 	gargv[gargc] = NULL;
101 #endif
102 
103 	iop = NULL;
104 	switch(pid = fork()) {
105 	case -1:			/* error */
106 		(void)close(pdes[0]);
107 		(void)close(pdes[1]);
108 		goto pfree;
109 		/* NOTREACHED */
110 	case 0:				/* child */
111 		if (e != NULL) {
112 #ifdef SYSLOG
113 			closelog();
114 #endif
115 
116 			/* get new pgrp, void tty, etc.
117 			 */
118 			(void) setsid();
119 		}
120 		if (*type == 'r') {
121 			/* Do not share our parent's stdin */
122 			(void)close(0);
123 			(void)open(_PATH_DEVNULL, O_RDWR);
124 			if (pdes[1] != 1) {
125 				dup2(pdes[1], 1);
126 				dup2(pdes[1], 2);	/* stderr, too! */
127 				(void)close(pdes[1]);
128 			}
129 			(void)close(pdes[0]);
130 		} else {
131 			if (pdes[0] != 0) {
132 				dup2(pdes[0], 0);
133 				(void)close(pdes[0]);
134 			}
135 			/* Hack: stdout gets revoked */
136 			(void)close(1);
137 			(void)open(_PATH_DEVNULL, O_RDWR);
138 			(void)close(2);
139 			(void)open(_PATH_DEVNULL, O_RDWR);
140 			(void)close(pdes[1]);
141 		}
142 		if (e != NULL) {
143 			/* Set user's entire context, but skip the environment
144 			 * as cron provides a separate interface for this
145 			 */
146 			usernm = env_get("LOGNAME", e->envp);
147 # if defined(LOGIN_CAP)
148 			if ((pwd = getpwnam(usernm)) == NULL)
149 				pwd = getpwuid(e->uid);
150 			lc = NULL;
151 			if (pwd != NULL) {
152 				pwd->pw_gid = e->gid;
153 				if (e->class != NULL)
154 					lc = login_getclass(e->class);
155 			}
156 			if (pwd &&
157 			    setusercontext(lc, pwd, e->uid,
158 				    LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
159 				(void) endpwent();
160 			else {
161 				/* fall back to the old method */
162 				(void) endpwent();
163 # endif
164 				/*
165 				 * Set our directory, uid and gid.  Set gid
166 				 * first since once we set uid, we've lost
167 				 * root privileges.
168 				 */
169 				if (setgid(e->gid) != 0)
170 					_exit(ERROR_EXIT);
171 # if defined(BSD)
172 				if (initgroups(usernm, e->gid) != 0)
173 					_exit(ERROR_EXIT);
174 # endif
175 				if (setlogin(usernm) != 0)
176 					_exit(ERROR_EXIT);
177 				if (setuid(e->uid) != 0)
178 					_exit(ERROR_EXIT);
179 				/* we aren't root after this..*/
180 #if defined(LOGIN_CAP)
181 			}
182 			if (lc != NULL)
183 				login_close(lc);
184 #endif
185 			chdir(env_get("HOME", e->envp));
186 		}
187 #if WANT_GLOBBING
188 		execvp(gargv[0], gargv);
189 #else
190 		execvp(argv[0], argv);
191 #endif
192 		_exit(1);
193 	}
194 	/* parent; assume fdopen can't fail...  */
195 	if (*type == 'r') {
196 		iop = fdopen(pdes[0], type);
197 		(void)close(pdes[1]);
198 	} else {
199 		iop = fdopen(pdes[1], type);
200 		(void)close(pdes[0]);
201 	}
202 	pids[fileno(iop)] = pid;
203 
204 pfree:
205 #if WANT_GLOBBING
206 	for (argc = 1; argv[argc] != NULL; argc++) {
207 /*		blkfree((char **)argv[argc]);	*/
208 		free((char *)argv[argc]);
209 	}
210 #endif
211 
212 	*pidptr = pid;
213 
214 	return (iop);
215 }
216 
217 int
cron_pclose(FILE * iop)218 cron_pclose(FILE *iop)
219 {
220 	int fdes;
221 	int omask;
222 	WAIT_T stat_loc;
223 	PID_T pid;
224 
225 	/*
226 	 * pclose returns -1 if stream is not associated with a
227 	 * `popened' command, or, if already `pclosed'.
228 	 */
229 	if (pids == 0 || pids[fdes = fileno(iop)] == 0)
230 		return (-1);
231 	(void)fclose(iop);
232 	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
233 	while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1)
234 		;
235 	(void)sigsetmask(omask);
236 	pids[fdes] = 0;
237 	return (pid == -1 ? -1 : WEXITSTATUS(stat_loc));
238 }
239