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