1 /* $NetBSD: vstream_popen.c,v 1.1.1.1 2009/06/23 10:09:01 tron Exp $ */ 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 113 typedef 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 127 static 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 195 VSTREAM *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 281 int 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 */ 319 int 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