gnewprocess_unix_exec_enabled.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2013 Graeme Walker <graeme_walker@users.sourceforge.net>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gnewprocess_unix_exec_enabled.cpp
19 //
20 
21 #include "gdef.h"
22 #include "glimits.h"
23 #include "gnewprocess.h"
24 #include "gprocess.h"
25 #include "gidentity.h"
26 #include "gassert.h"
27 #include "gfs.h"
28 #include "glog.h"
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <sys/stat.h>
33 #include <fcntl.h> // open()
34 #include <unistd.h> // setuid() etc
35 #include <algorithm> // std::swap()
36 #include <utility> // std::swap()
37 #include <iostream>
38 
39 namespace
40 {
41  void noCloseOnExec( int fd )
42  {
43  ::fcntl( fd , F_SETFD , 0 ) ;
44  }
45 }
46 
47 namespace G
48 {
49  class Pipe ;
50 }
51 
55 class G::Pipe
56 {
57 public:
58  explicit Pipe( bool active ) ;
59  ~Pipe() ;
60  void inChild() ; // writer
61  void inParent() ; // reader
62  int fd() const ;
63  void dup() ; // onto stdout
64  std::string read() ; // size-limited
65  void write( const std::string & ) ;
66 private:
67  G_EXCEPTION( Error , "pipe error" ) ;
68  int m_fds[2] ;
69  int m_fd ;
70 } ;
71 
76 {
77 public:
78  ChildProcessImp() ;
79  unsigned long m_ref_count ;
82 private:
83  void operator=( const ChildProcessImp & ) ;
84  ChildProcessImp( const ChildProcessImp & ) ;
85 } ;
86 
87 // ===
88 
89 G::NewProcess::ChildProcess::ChildProcess( ChildProcessImp * imp ) :
90  m_imp(imp)
91 {
92  m_imp->m_ref_count = 1 ;
93 }
94 
96 {
97  m_imp->m_ref_count-- ;
98  if( m_imp->m_ref_count == 0 )
99  delete m_imp ;
100 }
101 
102 G::NewProcess::ChildProcess::ChildProcess( const ChildProcess & other ) :
103  m_imp(other.m_imp)
104 {
105  m_imp->m_ref_count++ ;
106 }
107 
109 {
110  ChildProcess temp( rhs ) ;
111  std::swap( m_imp , temp.m_imp ) ;
112 }
113 
115 {
116  return G::NewProcess::wait( m_imp->m_id , 127 ) ;
117 }
118 
120 {
121  return m_imp->m_pipe.read() ;
122 }
123 
124 // ===
125 
127  m_ref_count(0UL) ,
128  m_pipe(true)
129 {
130 }
131 
132 // ===
133 
135 {
136  Process::Id id ;
137  return fork( id ) ;
138 }
139 
141 {
142  std::cout << std::flush ;
143  std::cerr << std::flush ;
144  pid_t rc = ::fork() ;
145  const bool ok = rc != -1 ;
146  if( ok )
147  {
148  if( rc != 0 )
149  child_pid.m_pid = rc ;
150  }
151  else
152  {
153  throw CannotFork() ;
154  }
155  return rc == 0 ? Child : Parent ;
156 }
157 
158 int G::NewProcess::wait( const Process::Id & child_pid )
159 {
160  int status ;
161  for(;;)
162  {
163  G_DEBUG( "G::NewProcess::wait: waiting" ) ;
164  int rc = ::waitpid( child_pid.m_pid , &status , 0 ) ;
165  if( rc == -1 && Process::errno_() == EINTR )
166  {
167  ; // signal in parent -- keep waiting
168  }
169  else if( rc == -1 )
170  {
171  int error = Process::errno_() ;
172  std::ostringstream ss ;
173  ss << "errno=" << error ;
174  throw WaitError( ss.str() ) ;
175  }
176  else
177  {
178  break ;
179  }
180  }
181  G_DEBUG( "G::NewProcess::wait: done" ) ;
182 
183  if( ! WIFEXITED(status) )
184  {
185  // uncaught signal or stopped
186  std::ostringstream ss ;
187  ss << "status=" << status ;
188  throw ChildError( ss.str() ) ;
189  }
190 
191  const int exit_status = WEXITSTATUS(status) ;
192  return exit_status ;
193 }
194 
195 int G::NewProcess::wait( const Process::Id & child_pid , int error_return )
196 {
197  try
198  {
199  return wait( child_pid ) ;
200  }
201  catch(...)
202  {
203  }
204  return error_return ;
205 }
206 
208 {
209  ChildProcess child( new ChildProcessImp ) ;
210  if( fork(child.m_imp->m_id) == Child )
211  {
212  try
213  {
214  child.m_imp->m_pipe.inChild() ;
215  Process::closeFiles( child.m_imp->m_pipe.fd() ) ;
216  child.m_imp->m_pipe.dup() ;
217  execCore( exe , args ) ;
218  }
219  catch(...)
220  {
221  }
222  ::_exit( 127 ) ;
223  return ChildProcess(0) ; // pacify the compiler
224  }
225  else
226  {
227  child.m_imp->m_pipe.inParent() ;
228  return child ;
229  }
230 }
231 
232 int G::NewProcess::spawn( Identity nobody , const Path & exe , const Strings & args ,
233  std::string * pipe_result_p , int error_return , std::string (*fn)(int) )
234 {
235  if( exe.isRelative() )
236  throw InvalidPath( exe.str() ) ;
237 
238  if( Identity::effective().isRoot() || nobody.isRoot() )
239  throw Insecure() ;
240 
241  Pipe pipe( pipe_result_p != NULL ) ;
242  Process::Id child_pid ;
243  if( fork(child_pid) == Child )
244  {
245  try
246  {
247  Process::beNobody( nobody ) ;
248  G_ASSERT( ::getuid() != 0U && ::geteuid() != 0U ) ;
249  pipe.inChild() ;
250  Process::closeFiles( pipe.fd() ) ;
251  pipe.dup() ; // dup() onto stdout
252  int error = execCore( exe , args ) ;
253  if( fn != 0 )
254  {
255  std::string s = (*fn)(error) ;
256  ssize_t rc = ::write( STDOUT_FILENO , s.c_str() , s.length() ) ;
257  G_IGNORE_VARIABLE(rc) ;
258  }
259  }
260  catch(...)
261  {
262  }
263  ::_exit( error_return ) ;
264  return error_return ; // pacify the compiler
265  }
266  else
267  {
268  pipe.inParent() ;
269  int exit_status = wait( child_pid , error_return ) ;
270  if( pipe_result_p != NULL ) *pipe_result_p = pipe.read() ;
271  return exit_status ;
272  }
273 }
274 
275 int G::NewProcess::execCore( const G::Path & exe , const Strings & args )
276 {
277  char * env[3U] ;
278  std::string path( "PATH=/usr/bin:/bin" ) ; // no "."
279  std::string ifs( "IFS= \t\n" ) ;
280  env[0U] = const_cast<char*>( path.c_str() ) ;
281  env[1U] = const_cast<char*>( ifs.c_str() ) ;
282  env[2U] = NULL ;
283 
284  char ** argv = new char* [ args.size() + 2U ] ;
285  std::string str_exe = exe.str() ;
286  argv[0U] = const_cast<char*>( str_exe.c_str() ) ;
287  unsigned int argc = 1U ;
288  for( Strings::const_iterator arg_p = args.begin() ; arg_p != args.end() ; ++arg_p , argc++ )
289  argv[argc] = const_cast<char*>(arg_p->c_str()) ;
290  argv[argc] = NULL ;
291 
292  ::execve( exe.str().c_str() , argv , env ) ;
293  const int error = Process::errno_() ;
294  delete [] argv ;
295 
296  G_DEBUG( "G::NewProcess::exec: execve() returned: errno=" << error << ": " << exe ) ;
297  return error ;
298 }
299 
300 // ===
301 
302 G::Pipe::Pipe( bool active ) :
303  m_fd(-1)
304 {
305  m_fds[0] = m_fds[1] = -1 ;
306  if( active && ::pipe( m_fds ) < 0 )
307  throw Error() ;
308  G_DEBUG( "G::Pipe::ctor: " << m_fds[0] << " " << m_fds[1] ) ;
309 }
310 
312 {
313  if( m_fds[0] >= 0 ) ::close( m_fds[0] ) ;
314  if( m_fds[1] >= 0 ) ::close( m_fds[1] ) ;
315 }
316 
318 {
319  ::close( m_fds[0] ) ;
320  m_fds[0] = -1 ;
321  m_fd = m_fds[1] ; // writer
322 }
323 
325 {
326  ::close( m_fds[1] ) ;
327  m_fds[1] = -1 ;
328  m_fd = m_fds[0] ; // reader
329 }
330 
331 int G::Pipe::fd() const
332 {
333  return m_fd ;
334 }
335 
337 {
338  if( m_fd != -1 && m_fd != STDOUT_FILENO )
339  {
340  if( ::dup2(m_fd,STDOUT_FILENO) != STDOUT_FILENO )
341  throw Error() ;
342  ::close( m_fd ) ;
343  m_fd = -1 ;
344  m_fds[1] = -1 ;
345  noCloseOnExec( STDOUT_FILENO ) ;
346  }
347 }
348 
349 std::string G::Pipe::read()
350 {
351  char buffer[limits::pipe_buffer] ;
352  ssize_t rc = m_fd == -1 ? 0 : ::read( m_fd , buffer , sizeof(buffer) ) ;
353  if( rc < 0 ) throw Error("read") ;
354  const size_t buffer_size = static_cast<size_t>(rc) ;
355  return std::string(buffer,buffer_size) ;
356 }
357 
static int spawn(Identity nobody, const Path &exe, const Strings &args, std::string *pipe_result_p=NULL, int error_return=127, std::string(*error_decode_fn)(int)=0)
Runs a command in an unprivileged child process.
std::string str() const
Returns the path string.
Definition: gpath.cpp:135
bool isRoot() const
Returns true if the userid is zero.
friend class ChildProcess
Definition: gnewprocess.h:96
void write(const std::string &)
std::list< std::string > Strings
A std::list of std::strings.
Definition: gstrings.h:39
A very low-level interface to getpwnam() and the get/set/e/uid/gid functions.
Definition: gidentity.h:41
static Identity effective()
Returns the current effective identity.
bool isRelative() const
Returns true if the path is a relative path.
Definition: gpath.cpp:145
static int errno_()
Returns the process's current 'errno' value.
#define G_ASSERT(test)
Definition: gassert.h:30
Low-level classes.
Represents the state of a child process.
Definition: gnewprocess.h:56
static Who fork()
Forks a child process.
#define G_DEBUG(expr)
Definition: glog.h:95
A private implementation class used by G::NewProcess.
#define G_EXCEPTION(class_name, description)
define as a function rather than a type if optimising for size
Definition: gexception.h:93
Process-id class.
Definition: gprocess.h:52
static void closeFiles(bool keep_stderr=false)
Closes all open file descriptors.
static void beNobody(Identity)
If currently running with a real identity of root then the real identity is set to the nobody identit...
A Path object represents a file system path.
Definition: gpath.h:44
A private implementation class used by G::NewProcess.