static FILE * my_popenv_impl( const char *const args[], const char * mode, int want_stderr, uid_t privsep_uid, Env *env_ptr = 0, bool drop_privs = true ) { int pipe_d[2], pipe_d2[2]; int parent_reads; uid_t euid; gid_t egid; pid_t pid; FILE* retp; /* Figure out who reads and who writes on the pipe */ parent_reads = (mode[0] == 'r'); /* Create the pipe */ if( pipe(pipe_d) < 0 ) { dprintf(D_ALWAYS, "my_popenv: Failed to create the pipe, " "errno=%d (%s)\n", errno, strerror(errno)); return NULL; } /* Prepare for PrivSep if needed */ PrivSepForkExec psforkexec; if ( privsep_uid != (uid_t)-1 ) { if (!psforkexec.init()) { dprintf(D_ALWAYS, "my_popenv failure on %s\n", args[0]); close(pipe_d[0]); close(pipe_d[1]); return NULL; } } /* Create a pipe to detect execv failures */ if ( pipe(pipe_d2) < 0) { dprintf(D_ALWAYS, "my_popenv: Failed to create the pre-exec pipe, " "errno=%d (%s)\n", errno, strerror(errno)); close(pipe_d[0]); close(pipe_d[1]); return NULL; } int fd_flags; if ((fd_flags = fcntl(pipe_d2[1], F_GETFD, NULL)) == -1) { dprintf(D_ALWAYS, "my_popenv: Failed to get fd flags: errno=%d (%s)\n", errno, strerror(errno)); close( pipe_d[0] ); close( pipe_d[1] ); close( pipe_d2[0] ); close( pipe_d2[1] ); return NULL; } if (fcntl(pipe_d2[1], F_SETFD, fd_flags | FD_CLOEXEC) == -1) { dprintf(D_ALWAYS, "my_popenv: Failed to set new fd flags: errno=%d (%s)\n", errno, strerror(errno)); close( pipe_d[0] ); close( pipe_d[1] ); close( pipe_d2[0] ); close( pipe_d2[1] ); return NULL; } /* Create a new process */ if( (pid=fork()) < 0 ) { dprintf(D_ALWAYS, "my_popenv: Failed to fork child, errno=%d (%s)\n", errno, strerror(errno)); /* Clean up file descriptors */ close( pipe_d[0] ); close( pipe_d[1] ); close( pipe_d2[0] ); close( pipe_d2[1] ); return NULL; } /* The child */ if( pid == 0 ) { /* Don't leak out fds from the parent to our child. * Wish there was a more efficient way to do this, but * this is how we do it in daemoncore CreateProcess... * Of course, do not close stdin/out/err or the fds to * the pipes we just created above. */ for (int jj=3; jj < getdtablesize(); jj++) { if (jj != pipe_d[0] && jj != pipe_d[1] && jj != pipe_d2[0] && jj != pipe_d2[1]) { close(jj); } } close(pipe_d2[0]); if( parent_reads ) { /* Close stdin, dup pipe to stdout */ close( pipe_d[READ_END] ); bool close_pipe_end = false; if( pipe_d[WRITE_END] != 1 ) { dup2( pipe_d[WRITE_END], 1 ); close_pipe_end = true; } if (want_stderr) { if ( pipe_d[WRITE_END] != 2 ) { dup2( pipe_d[WRITE_END], 2 ); } else { close_pipe_end = false; } } if (close_pipe_end) { close(pipe_d[WRITE_END]); } } else { /* Close stdout, dup pipe to stdin */ close( pipe_d[WRITE_END] ); if( pipe_d[READ_END] != 0 ) { dup2( pipe_d[READ_END], 0 ); close( pipe_d[READ_END] ); } } /* to be safe, we want to switch our real uid/gid to our effective uid/gid (shedding any privledges we've got). we also want to drop any supplimental groups we're in. we want to run this popen()'ed thing as our effective uid/gid, dropping the real uid/gid. all of these calls will fail if we don't have a ruid of 0 (root), but that's harmless. also, note that we have to stash our effective uid, then switch our euid to 0 to be able to set our real uid/gid. We wrap some of the calls in if-statements to quiet some compilers that object to us not checking the return values. */ if (drop_privs) { euid = geteuid(); egid = getegid(); if( seteuid( 0 ) ) { } setgroups( 1, &egid ); if( setgid( egid ) ) { } if( setuid( euid ) ) _exit(ENOEXEC); // Unsafe? } /* before we exec(), clear the signal mask and reset SIGPIPE to SIG_DFL */ install_sig_handler(SIGPIPE, SIG_DFL); sigset_t sigs; sigfillset(&sigs); sigprocmask(SIG_UNBLOCK, &sigs, NULL); /* handle PrivSep if needed */ MyString cmd = args[0]; if ( privsep_uid != (uid_t)-1 ) { ArgList al; psforkexec.in_child(cmd, al); args = al.GetStringArray(); } /* set environment if defined */ if (env_ptr) { char **m_unix_env = NULL; m_unix_env = env_ptr->getStringArray(); execve(cmd.Value(), const_cast<char *const*>(args), m_unix_env ); // delete the memory even though we're on our way out // if exec failed. if (m_unix_env) { int i = 0; while (m_unix_env[i]) { delete m_unix_env[i]; i++; } delete [] m_unix_env; } } else { execvp(cmd.Value(), const_cast<char *const*>(args) ); } /* If we get here, inform the parent of our errno */ char result_buf[10]; int e = errno; // capture real errno int len = snprintf(result_buf, 10, "%d", errno); int ret = write(pipe_d2[1], result_buf, len); // Jump through some hoops just to use ret. if (ret < 1) { _exit( e ); } else { _exit( e ); } } /* The parent */ /* First, wait until the exec is called - determine status */ close(pipe_d2[1]); int exit_code; FILE *fh; if ((fh = fdopen(pipe_d2[0], "r")) == NULL) { dprintf(D_ALWAYS, "my_popenv: Failed to reopen file descriptor as file handle: errno=%d (%s)", errno, strerror(errno)); close(pipe_d2[0]); close(pipe_d[0]); close(pipe_d[1]); /* Ensure child process is dead, then wait for it to exit */ kill(pid, SIGKILL); while( waitpid(pid,NULL,0) < 0 && errno == EINTR ) { /* NOOP */ } return NULL; } /* Handle case where exec fails */ if (fscanf(fh, "%d", &exit_code) == 1) { fclose(fh); close(pipe_d[0]); close(pipe_d[1]); /* Ensure child process is dead, then wait for it to exit */ kill(pid, SIGKILL); while( waitpid(pid,NULL,0) < 0 && errno == EINTR ) { /* NOOP */ } errno = exit_code; return NULL; } fclose(fh); if( parent_reads ) { close( pipe_d[WRITE_END] ); retp = fdopen(pipe_d[READ_END],mode); } else { close( pipe_d[READ_END] ); retp = fdopen(pipe_d[WRITE_END],mode); } add_child(retp, pid); /* handle PrivSep if needed */ if ( privsep_uid != (uid_t)-1 ) { FILE* fp = psforkexec.parent_begin(); privsep_exec_set_uid(fp, privsep_uid); privsep_exec_set_path(fp, args[0]); ArgList al; for (const char* const* arg = args; *arg != NULL; arg++) { al.AppendArg(*arg); } privsep_exec_set_args(fp, al); Env env; env.Import(); privsep_exec_set_env(fp, env); privsep_exec_set_iwd(fp, "."); if (parent_reads) { privsep_exec_set_inherit_fd(fp, 1); if (want_stderr) { privsep_exec_set_inherit_fd(fp, 2); } } else { privsep_exec_set_inherit_fd(fp, 0); } if (!psforkexec.parent_end()) { dprintf(D_ALWAYS, "my_popenv failure on %s\n", args[0]); fclose(retp); return NULL; } } return retp; }
/** * merge_stderr_with_stdout is intended for clients of this function * that wish to have the old behavior, where stderr and stdout were * both added to the same StringList. */ int systemCommand( ArgList &args, priv_state priv, StringList *cmd_out, StringList * cmd_in, StringList *cmd_err, bool merge_stderr_with_stdout) { int result = 0; FILE *fp = NULL; FILE * fp_for_stdin = NULL; FILE * childerr = NULL; MyString line; char buff[1024]; StringList *my_cmd_out = cmd_out; priv_state prev = PRIV_UNKNOWN; int stdout_pipes[2]; int stdin_pipes[2]; int pid; bool use_privsep = false; switch ( priv ) { case PRIV_ROOT: prev = set_root_priv(); break; case PRIV_USER: case PRIV_USER_FINAL: prev = set_user_priv(); #if !defined(WIN32) if ( privsep_enabled() && (job_user_uid != get_condor_uid()) ) { use_privsep = true; } #endif break; default: // Stay as Condor user ; } #if defined(WIN32) if((cmd_in != NULL) || (cmd_err != NULL)) { vmprintf(D_ALWAYS, "Invalid use of systemCommand() in Windows.\n"); return -1; } //if ( use_privsep ) { // fp = privsep_popen(args, "r", want_stderr, job_user_uid); //} //else { fp = my_popen( args, "r", merge_stderr_with_stdout ); //} #else // The old way of doing things (and the Win32 way of doing // things) // fp = my_popen( args, "r", want_stderr ); if((cmd_err != NULL) && merge_stderr_with_stdout) { vmprintf(D_ALWAYS, "Invalid use of systemCommand().\n"); return -1; } PrivSepForkExec psforkexec; char ** args_array = args.GetStringArray(); int error_pipe[2]; // AIX 5.2, Solaris 5.9, HPUX 11 don't have AF_LOCAL if(pipe(stdin_pipes) < 0) { vmprintf(D_ALWAYS, "Error creating pipe: %s\n", strerror(errno)); deleteStringArray( args_array ); return -1; } if(pipe(stdout_pipes) < 0) { vmprintf(D_ALWAYS, "Error creating pipe: %s\n", strerror(errno)); close(stdin_pipes[0]); close(stdin_pipes[1]); deleteStringArray( args_array ); return -1; } if ( use_privsep ) { if(!psforkexec.init()) { vmprintf(D_ALWAYS, "my_popenv failure on %s\n", args_array[0]); close(stdin_pipes[0]); close(stdin_pipes[1]); close(stdout_pipes[0]); close(stdout_pipes[1]); deleteStringArray( args_array ); return -1; } } if(cmd_err != NULL) { if(pipe(error_pipe) < 0) { vmprintf(D_ALWAYS, "Could not open pipe for error output: %s\n", strerror(errno)); close(stdin_pipes[0]); close(stdin_pipes[1]); close(stdout_pipes[0]); close(stdout_pipes[1]); deleteStringArray( args_array ); return -1; } } // Now fork and do what my_popen used to do pid = fork(); if(pid < 0) { vmprintf(D_ALWAYS, "Error forking: %s\n", strerror(errno)); close(stdin_pipes[0]); close(stdin_pipes[1]); close(stdout_pipes[0]); close(stdout_pipes[1]); if(cmd_err != NULL) { close(error_pipe[0]); close(error_pipe[1]); } deleteStringArray( args_array ); return -1; } if(pid == 0) { close(stdout_pipes[0]); close(stdin_pipes[1]); dup2(stdout_pipes[1], STDOUT_FILENO); dup2(stdin_pipes[0], STDIN_FILENO); if(merge_stderr_with_stdout) dup2(stdout_pipes[1], STDERR_FILENO); else if(cmd_err != NULL) { close(error_pipe[0]); dup2(error_pipe[1], STDERR_FILENO); } uid_t euid = geteuid(); gid_t egid = getegid(); seteuid( 0 ); setgroups( 1, &egid ); setgid( egid ); setuid( euid ); install_sig_handler(SIGPIPE, SIG_DFL); sigset_t sigs; sigfillset(&sigs); sigprocmask(SIG_UNBLOCK, &sigs, NULL); MyString cmd = args_array[0]; if ( use_privsep ) { ArgList al; psforkexec.in_child(cmd, al); deleteStringArray( args_array ); args_array = al.GetStringArray(); } execvp(cmd.Value(), args_array); vmprintf(D_ALWAYS, "Could not execute %s: %s\n", args_array[0], strerror(errno)); exit(-1); } close(stdin_pipes[0]); close(stdout_pipes[1]); fp_for_stdin = fdopen(stdin_pipes[1], "w"); fp = fdopen(stdout_pipes[0], "r"); if(cmd_err != NULL) { close(error_pipe[1]); childerr = fdopen(error_pipe[0],"r"); if(childerr == 0) { vmprintf(D_ALWAYS, "Could not open pipe for reading child error output: %s\n", strerror(errno)); close(error_pipe[0]); close(stdin_pipes[1]); close(stdout_pipes[0]); fclose(fp); fclose(fp_for_stdin); deleteStringArray( args_array ); return -1; } } if ( use_privsep ) { FILE* _fp = psforkexec.parent_begin(); privsep_exec_set_uid(_fp, job_user_uid); privsep_exec_set_path(_fp, args_array[0]); privsep_exec_set_args(_fp, args); Env env; env.MergeFrom(environ); privsep_exec_set_env(_fp, env); privsep_exec_set_iwd(_fp, "."); privsep_exec_set_inherit_fd(_fp, 1); privsep_exec_set_inherit_fd(_fp, 2); privsep_exec_set_inherit_fd(_fp, 0); if (!psforkexec.parent_end()) { vmprintf(D_ALWAYS, "my_popenv failure on %s\n", args_array[0]); fclose(fp); fclose(fp_for_stdin); if (childerr) { fclose(childerr); } deleteStringArray( args_array ); return -1; } } deleteStringArray( args_array ); #endif set_priv( prev ); if ( fp == NULL ) { MyString args_string; args.GetArgsStringForDisplay( &args_string, 0 ); vmprintf( D_ALWAYS, "Failed to execute command: %s\n", args_string.Value() ); if (childerr) fclose(childerr); return -1; } if(cmd_in != NULL) { cmd_in->rewind(); char * tmp; while((tmp = cmd_in->next()) != NULL) { fprintf(fp_for_stdin, "%s\n", tmp); fflush(fp_for_stdin); } } if (fp_for_stdin) { // So that we will not be waiting for output while the // script waits for stdin to be closed. fclose(fp_for_stdin); } if ( my_cmd_out == NULL ) { my_cmd_out = new StringList(); } while ( fgets( buff, sizeof(buff), fp ) != NULL ) { line += buff; if ( line.chomp() ) { my_cmd_out->append( line.Value() ); line = ""; } } if(cmd_err != NULL) { while(fgets(buff, sizeof(buff), childerr) != NULL) { line += buff; if(line.chomp()) { cmd_err->append(line.Value()); line = ""; } } fclose(childerr); } #if defined(WIN32) result = my_pclose( fp ); #else // Why close first? Just in case the child process is waiting // on a read, and we have nothing more to send it. It will // now receive a SIGPIPE. fclose(fp); if(waitpid(pid, &result, 0) < 0) { vmprintf(D_ALWAYS, "Unable to wait: %s\n", strerror(errno)); if ( cmd_out == NULL ) { delete my_cmd_out; } return -1; } #endif if( result != 0 ) { MyString args_string; args.GetArgsStringForDisplay(&args_string,0); vmprintf(D_ALWAYS, "Command returned non-zero: %s\n", args_string.Value()); my_cmd_out->rewind(); const char *next_line; while ( (next_line = my_cmd_out->next()) ) { vmprintf( D_ALWAYS, " %s\n", next_line ); } } if ( cmd_out == NULL ) { delete my_cmd_out; } return result; }