1/* $NetBSD$ */ 2 3/*++ 4/* NAME 5/* vstream_popen 3 6/* SUMMARY 7/* open stream to child process 8/* SYNOPSIS 9/* #include <vstream.h> 10/* 11/* VSTREAM *vstream_popen(flags, key, value, ...) 12/* int flags; 13/* int key; 14/* 15/* int vstream_pclose(stream) 16/* VSTREAM *stream; 17/* DESCRIPTION 18/* vstream_popen() opens a one-way or two-way stream to a user-specified 19/* command, which is executed by a child process. The \fIflags\fR 20/* argument is as with vstream_fopen(). The child's standard input and 21/* standard output are redirected to the stream, which is based on a 22/* socketpair or other suitable local IPC. vstream_popen() takes a list 23/* of (key, value) arguments, terminated by VSTREAM_POPEN_END. The key 24/* argument specifies what value will follow. The following is a listing 25/* of key codes together with the expected value type. 26/* .RS 27/* .IP "VSTREAM_POPEN_COMMAND (char *)" 28/* Specifies the command to execute as a string. The string is 29/* passed to the shell when it contains shell meta characters 30/* or when it appears to be a shell built-in command, otherwise 31/* the command is executed without invoking a shell. 32/* One of VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. 33/* .IP "VSTREAM_POPEN_ARGV (char **)" 34/* The command is specified as an argument vector. This vector is 35/* passed without further inspection to the \fIexecvp\fR() routine. 36/* One of VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. 37/* See also the VSTREAM_POPEN_SHELL attribute below. 38/* .IP "VSTREAM_POPEN_ENV (char **)" 39/* Additional environment information, in the form of a null-terminated 40/* list of name, value, name, value, ... elements. By default only the 41/* command search path is initialized to _PATH_DEFPATH. 42/* .IP "VSTREAM_POPEN_EXPORT (char **)" 43/* This argument is passed to clean_env(). 44/* Null-terminated array of names of environment parameters 45/* that can be exported. By default, everything is exported. 46/* .IP "VSTREAM_POPEN_UID (uid_t)" 47/* The user ID to execute the command as. The user ID must be non-zero. 48/* .IP "VSTREAM_POPEN_GID (gid_t)" 49/* The group ID to execute the command as. The group ID must be non-zero. 50/* .IP "VSTREAM_POPEN_SHELL (char *)" 51/* The shell to use when executing the command specified with 52/* VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the 53/* command content. 54/* .IP "VSTREAM_POPEN_WAITPID_FN ((*)(pid_t, WAIT_STATUS_T *, int))" 55/* waitpid()-like function to reap the child exit status when 56/* vstream_pclose() is called. 57/* .RE 58/* .PP 59/* vstream_pclose() closes the named stream and returns the child 60/* exit status. It is an error to specify a stream that was not 61/* returned by vstream_popen() or that is no longer open. 62/* DIAGNOSTICS 63/* Panics: interface violations. Fatal errors: out of memory. 64/* 65/* vstream_popen() returns a null pointer in case of trouble. 66/* The nature of the problem is specified via the \fIerrno\fR 67/* global variable. 68/* 69/* vstream_pclose() returns -1 in case of trouble. 70/* The nature of the problem is specified via the \fIerrno\fR 71/* global variable. 72/* SEE ALSO 73/* vstream(3) light-weight buffered I/O 74/* BUGS 75/* The interface, stolen from popen()/pclose(), ignores errors 76/* returned when the stream is closed, and does not distinguish 77/* between exit status codes and kill signals. 78/* LICENSE 79/* .ad 80/* .fi 81/* The Secure Mailer license must be distributed with this software. 82/* AUTHOR(S) 83/* Wietse Venema 84/* IBM T.J. Watson Research 85/* P.O. Box 704 86/* Yorktown Heights, NY 10598, USA 87/*--*/ 88 89/* System library. */ 90 91#include <sys_defs.h> 92#include <sys/wait.h> 93#include <unistd.h> 94#include <stdlib.h> 95#include <errno.h> 96#ifdef USE_PATHS_H 97#include <paths.h> 98#endif 99#include <syslog.h> 100 101/* Utility library. */ 102 103#include <msg.h> 104#include <exec_command.h> 105#include <vstream.h> 106#include <argv.h> 107#include <set_ugid.h> 108#include <clean_env.h> 109#include <iostuff.h> 110 111/* Application-specific. */ 112 113typedef struct VSTREAM_POPEN_ARGS { 114 char **argv; 115 char *command; 116 uid_t uid; 117 gid_t gid; 118 int privileged; 119 char **env; 120 char **export; 121 char *shell; 122 VSTREAM_WAITPID_FN waitpid_fn; 123} VSTREAM_POPEN_ARGS; 124 125/* vstream_parse_args - get arguments from variadic list */ 126 127static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap) 128{ 129 const char *myname = "vstream_parse_args"; 130 int key; 131 132 /* 133 * First, set the default values (on all non-zero entries) 134 */ 135 args->argv = 0; 136 args->command = 0; 137 args->uid = 0; 138 args->gid = 0; 139 args->privileged = 0; 140 args->env = 0; 141 args->export = 0; 142 args->shell = 0; 143 args->waitpid_fn = 0; 144 145 /* 146 * Then, override the defaults with user-supplied inputs. 147 */ 148 while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) { 149 switch (key) { 150 case VSTREAM_POPEN_ARGV: 151 if (args->command != 0) 152 msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); 153 args->argv = va_arg(ap, char **); 154 break; 155 case VSTREAM_POPEN_COMMAND: 156 if (args->argv != 0) 157 msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); 158 args->command = va_arg(ap, char *); 159 break; 160 case VSTREAM_POPEN_UID: 161 args->privileged = 1; 162 args->uid = va_arg(ap, uid_t); 163 break; 164 case VSTREAM_POPEN_GID: 165 args->privileged = 1; 166 args->gid = va_arg(ap, gid_t); 167 break; 168 case VSTREAM_POPEN_ENV: 169 args->env = va_arg(ap, char **); 170 break; 171 case VSTREAM_POPEN_EXPORT: 172 args->export = va_arg(ap, char **); 173 break; 174 case VSTREAM_POPEN_SHELL: 175 args->shell = va_arg(ap, char *); 176 break; 177 case VSTREAM_POPEN_WAITPID_FN: 178 args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN); 179 break; 180 default: 181 msg_panic("%s: unknown key: %d", myname, key); 182 } 183 } 184 185 if (args->command == 0 && args->argv == 0) 186 msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname); 187 if (args->privileged != 0 && args->uid == 0) 188 msg_panic("%s: privileged uid", myname); 189 if (args->privileged != 0 && args->gid == 0) 190 msg_panic("%s: privileged gid", myname); 191} 192 193/* vstream_popen - open stream to child process */ 194 195VSTREAM *vstream_popen(int flags,...) 196{ 197 const char *myname = "vstream_popen"; 198 VSTREAM_POPEN_ARGS args; 199 va_list ap; 200 VSTREAM *stream; 201 int sockfd[2]; 202 int pid; 203 int fd; 204 ARGV *argv; 205 char **cpp; 206 207 va_start(ap, flags); 208 vstream_parse_args(&args, ap); 209 va_end(ap); 210 211 if (args.command == 0) 212 args.command = args.argv[0]; 213 214 if (duplex_pipe(sockfd) < 0) 215 return (0); 216 217 switch (pid = fork()) { 218 case -1: /* error */ 219 (void) close(sockfd[0]); 220 (void) close(sockfd[1]); 221 return (0); 222 case 0: /* child */ 223 (void) msg_cleanup((MSG_CLEANUP_FN) 0); 224 if (close(sockfd[1])) 225 msg_warn("close: %m"); 226 for (fd = 0; fd < 2; fd++) 227 if (sockfd[0] != fd) 228 if (DUP2(sockfd[0], fd) < 0) 229 msg_fatal("dup2: %m"); 230 if (sockfd[0] >= 2 && close(sockfd[0])) 231 msg_warn("close: %m"); 232 233 /* 234 * Don't try to become someone else unless the user specified it. 235 */ 236 if (args.privileged) 237 set_ugid(args.uid, args.gid); 238 239 /* 240 * Environment plumbing. Always reset the command search path. XXX 241 * That should probably be done by clean_env(). 242 */ 243 if (args.export) 244 clean_env(args.export); 245 if (setenv("PATH", _PATH_DEFPATH, 1)) 246 msg_fatal("%s: setenv: %m", myname); 247 if (args.env) 248 for (cpp = args.env; *cpp; cpp += 2) 249 if (setenv(cpp[0], cpp[1], 1)) 250 msg_fatal("setenv: %m"); 251 252 /* 253 * Process plumbing. If possible, avoid running a shell. 254 */ 255 closelog(); 256 if (args.argv) { 257 execvp(args.argv[0], args.argv); 258 msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); 259 } else if (args.shell && *args.shell) { 260 argv = argv_split(args.shell, " \t\r\n"); 261 argv_add(argv, args.command, (char *) 0); 262 argv_terminate(argv); 263 execvp(argv->argv[0], argv->argv); 264 msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); 265 } else { 266 exec_command(args.command); 267 } 268 /* NOTREACHED */ 269 default: /* parent */ 270 if (close(sockfd[0])) 271 msg_warn("close: %m"); 272 stream = vstream_fdopen(sockfd[1], flags); 273 stream->waitpid_fn = args.waitpid_fn; 274 stream->pid = pid; 275 return (stream); 276 } 277} 278 279/* vstream_pclose - close stream to child process */ 280 281int vstream_pclose(VSTREAM *stream) 282{ 283 pid_t saved_pid = stream->pid; 284 VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn; 285 pid_t pid; 286 WAIT_STATUS_T wait_status; 287 288 /* 289 * Close the pipe. Don't trigger an alarm in vstream_fclose(). 290 */ 291 if (saved_pid == 0) 292 msg_panic("vstream_pclose: stream has no process"); 293 stream->pid = 0; 294 vstream_fclose(stream); 295 296 /* 297 * Reap the child exit status. 298 */ 299 do { 300 if (saved_waitpid_fn != 0) 301 pid = saved_waitpid_fn(saved_pid, &wait_status, 0); 302 else 303 pid = waitpid(saved_pid, &wait_status, 0); 304 } while (pid == -1 && errno == EINTR); 305 return (pid == -1 ? -1 : 306 WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) : 307 WEXITSTATUS(wait_status)); 308} 309 310#ifdef TEST 311 312#include <fcntl.h> 313#include <vstring.h> 314#include <vstring_vstream.h> 315 316 /* 317 * Test program. Run a command and copy lines one by one. 318 */ 319int main(int argc, char **argv) 320{ 321 VSTRING *buf = vstring_alloc(100); 322 VSTREAM *stream; 323 int status; 324 325 /* 326 * Sanity check. 327 */ 328 if (argc < 2) 329 msg_fatal("usage: %s 'command'", argv[0]); 330 331 /* 332 * Open stream to child process. 333 */ 334 if ((stream = vstream_popen(O_RDWR, 335 VSTREAM_POPEN_ARGV, argv + 1, 336 VSTREAM_POPEN_END)) == 0) 337 msg_fatal("vstream_popen: %m"); 338 339 /* 340 * Copy loop, one line at a time. 341 */ 342 while (vstring_fgets(buf, stream) != 0) { 343 if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf)) 344 != VSTRING_LEN(buf)) 345 msg_fatal("vstream_fwrite: %m"); 346 if (vstream_fflush(VSTREAM_OUT) != 0) 347 msg_fatal("vstream_fflush: %m"); 348 if (vstring_fgets(buf, VSTREAM_IN) == 0) 349 break; 350 if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf)) 351 != VSTRING_LEN(buf)) 352 msg_fatal("vstream_fwrite: %m"); 353 } 354 355 /* 356 * Cleanup. 357 */ 358 vstring_free(buf); 359 if ((status = vstream_pclose(stream)) != 0) 360 msg_warn("exit status: %d", status); 361 362 exit(status); 363} 364 365#endif 366