E-MailRelay
gprocess_unix.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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/// \file gprocess_unix.cpp
19///
20
21#include "gdef.h"
22#include "gprocess.h"
23#include "gidentity.h"
24#include "gstr.h"
25#include "gfile.h"
26#include "gpath.h"
27#include "glimits.h"
28#include "glog.h"
29#include <iostream>
30#include <stdexcept>
31#include <array>
32#include <cstring> // std::strerror()
33#include <climits> // PATH_MAX
34#include <cerrno> // errno
35#include <fcntl.h>
36
37namespace G
38{
39 namespace ProcessImp
40 {
41 void noCloseOnExec( int fd ) noexcept ;
42 void reopen( int fd , int mode ) ;
43 mode_t umaskValue( G::Process::Umask::Mode mode ) ;
44 bool readlink_( const char * path , std::string & value ) ;
45 bool setRealUser( Identity id , std::nothrow_t ) noexcept ;
46 bool setRealGroup( Identity id , std::nothrow_t ) noexcept ;
47 void setEffectiveUser( Identity id ) ;
48 bool setEffectiveUser( Identity id , std::nothrow_t ) noexcept ;
49 void setEffectiveGroup( Identity id ) ;
50 bool setEffectiveGroup( Identity id , std::nothrow_t ) noexcept ;
51 void throwError() ;
52 void beSpecial( Identity special_identity , bool change_group ) ;
53 void beSpecialForExit( SignalSafe , Identity special_identity ) noexcept ;
54 Identity beOrdinaryAtStartup( Identity , bool change_group ) ;
55 Identity beOrdinary( Identity , bool change_group ) ;
56 void beOrdinaryForExec( Identity run_as_id ) noexcept ;
57 void revokeExtraGroups() ;
58 }
59}
60
61class G::Process::UmaskImp /// A private implemetation class for G::Process::Umask that hides mode_t.
62{
63public:
64 mode_t m_old_mode ;
65} ;
66
67// ==
68
69void G::Process::cd( const Path & dir )
70{
71 if( ! cd(dir,std::nothrow) )
72 throw CannotChangeDirectory( dir.str() ) ;
73}
74
75bool G::Process::cd( const Path & dir , std::nothrow_t )
76{
77 return 0 == ::chdir( dir.cstr() ) ;
78}
79
81{
82 ProcessImp::reopen( STDERR_FILENO , O_WRONLY ) ;
83}
84
85void G::Process::closeFiles( bool keep_stderr )
86{
87 std::cout << std::flush ;
88 std::cerr << std::flush ;
89
90 ProcessImp::reopen( STDIN_FILENO , O_RDONLY ) ;
91 ProcessImp::reopen( STDOUT_FILENO , O_WRONLY ) ;
92 if( !keep_stderr )
93 ProcessImp::reopen( STDERR_FILENO , O_WRONLY ) ;
94
95 closeOtherFiles() ;
96}
97
98void G::Process::closeOtherFiles( int fd_keep )
99{
100 int n = 256U ;
101 long rc = ::sysconf( _SC_OPEN_MAX ) ;
102 if( rc > 0L )
103 n = static_cast<int>( rc ) ;
104
105 for( int fd = 0 ; fd < n ; fd++ )
106 {
107 if( fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO && fd != fd_keep )
108 ::close( fd ) ;
109 }
110 ProcessImp::noCloseOnExec( STDIN_FILENO ) ;
111 ProcessImp::noCloseOnExec( STDOUT_FILENO ) ;
112 ProcessImp::noCloseOnExec( STDERR_FILENO ) ;
113}
114
115int G::Process::errno_( const SignalSafe & ) noexcept
116{
117 return errno ; // possible macro, not ::errno or std::errno
118}
119
120int G::Process::errno_( const SignalSafe & , int e_new ) noexcept
121{
122 int e_old = errno ;
123 errno = e_new ;
124 return e_old ;
125}
126
127std::string G::Process::strerror( int errno_ )
128{
129 char * p = std::strerror( errno_ ) ;
130 std::string s( p ? p : "" ) ;
131 if( s.empty() ) s = "unknown error" ;
132 return Str::isPrintableAscii(s) ? Str::lower(s) : s ;
133}
134
135void G::Process::beSpecial( Identity special_identity , bool change_group )
136{
137 ProcessImp::beSpecial( special_identity , change_group ) ;
138}
139
140void G::Process::beSpecialForExit( SignalSafe , Identity special_identity ) noexcept
141{
142 ProcessImp::beSpecialForExit( SignalSafe() , special_identity ) ;
143}
144
145G::Identity G::Process::beOrdinaryAtStartup( Identity ordinary_id , bool change_group )
146{
147 // revoke extra groups, but not if we are leaving groups alone
148 // or we have been given root as the non-root user or we
149 // are running vanilla
150 if( change_group && !ordinary_id.isRoot() )
151 {
152 Identity real = Identity::real() ;
153 if( real.isRoot() || real != Identity::effective() )
154 ProcessImp::revokeExtraGroups() ;
155 }
156
157 return ProcessImp::beOrdinary( ordinary_id , change_group ) ;
158}
159
160void G::Process::beOrdinary( Identity ordinary_id , bool change_group )
161{
162 ProcessImp::beOrdinary( ordinary_id , change_group ) ;
163}
164
165void G::Process::beOrdinaryForExec( Identity run_as_id ) noexcept
166{
167 ProcessImp::beOrdinaryForExec( run_as_id ) ;
168}
169
171{
172 G::ProcessImp::setEffectiveUser( id ) ;
173}
174
176{
177 G::ProcessImp::setEffectiveGroup( id ) ;
178}
179
180std::string G::Process::cwd( bool no_throw )
181{
182 std::string result ;
183 std::array<std::size_t,2U> sizes = {{ G::limits::path_buffer , PATH_MAX+1U }} ;
184 for( std::size_t n : sizes )
185 {
186 std::vector<char> buffer( n ) ;
187 char * p = getcwd( &buffer[0] , buffer.size() ) ;
188 int error = errno_() ;
189 if( p != nullptr )
190 {
191 buffer.push_back( '\0' ) ;
192 result.assign( &buffer[0] ) ;
193 break ;
194 }
195 else if( error != ERANGE )
196 {
197 break ;
198 }
199 }
200 if( result.empty() && !no_throw )
201 throw std::runtime_error( "getcwd() failed" ) ;
202 return result ;
203}
204
205#ifdef G_UNIX_MAC
206#include <libproc.h>
207std::string G::Process::exe()
208{
209 // (see also _NSGetExecutablePath())
210 std::vector<char> buffer( std::max(100,PROC_PIDPATHINFO_MAXSIZE) ) ;
211 buffer[0] = '\0' ;
212 int rc = proc_pidpath( getpid() , &buffer[0] , buffer.size() ) ;
213 if( rc > 0 )
214 {
215 std::size_t n = static_cast<std::size_t>(rc) ;
216 if( n > buffer.size() ) n = buffer.size() ;
217 return std::string( &buffer[0] , n ) ;
218 }
219 else
220 {
221 return std::string() ;
222 }
223}
224#else
225std::string G::Process::exe()
226{
227 // best effort, not guaranteed
228 std::string result ;
229 ProcessImp::readlink_( "/proc/self/exe" , result ) ||
230 ProcessImp::readlink_( "/proc/curproc/file" , result ) ||
231 ProcessImp::readlink_( "/proc/curproc/exe" , result ) ;
232 return result ;
233}
234#endif
235
236// ==
237
238G::Process::Id::Id() noexcept :
239 m_pid(::getpid())
240{
241}
242
243std::string G::Process::Id::str() const
244{
245 std::ostringstream ss ;
246 ss << m_pid ;
247 return ss.str() ;
248}
249
250bool G::Process::Id::operator==( const Id & other ) const noexcept
251{
252 return m_pid == other.m_pid ;
253}
254
255bool G::Process::Id::operator!=( const Id & other ) const noexcept
256{
257 return m_pid != other.m_pid ;
258}
259
260// ==
261
262G::Process::Umask::Umask( Mode mode ) :
263 m_imp(std::make_unique<UmaskImp>())
264{
265 m_imp->m_old_mode = ::umask( ProcessImp::umaskValue(mode) ) ;
266}
267
268G::Process::Umask::~Umask()
269{
270 GDEF_IGNORE_RETURN ::umask( m_imp->m_old_mode ) ;
271}
272
273void G::Process::Umask::set( Mode mode )
274{
275 GDEF_IGNORE_RETURN ::umask( ProcessImp::umaskValue(mode) ) ;
276}
277
278void G::Process::Umask::tighten()
279{
280 ::umask( ::umask(2) | mode_t(7) ) ; // -xxxxxx---
281}
282
283// ==
284
285void G::ProcessImp::noCloseOnExec( int fd ) noexcept
286{
287 ::fcntl( fd , F_SETFD , 0 ) ;
288}
289
290void G::ProcessImp::reopen( int fd , int mode )
291{
292 int fd_null = ::open( Path::nullDevice().cstr() , mode ) ;
293 if( fd_null < 0 ) throw std::runtime_error( "cannot open /dev/null" ) ;
294 ::dup2( fd_null , fd ) ;
295 ::close( fd_null ) ;
296}
297
298mode_t G::ProcessImp::umaskValue( Process::Umask::Mode mode )
299{
300 mode_t m = 0 ;
301 if( mode == Process::Umask::Mode::Tightest ) m = 0177 ; // -rw-------
302 if( mode == Process::Umask::Mode::Tighter ) m = 0117 ; // -rw-rw----
303 if( mode == Process::Umask::Mode::Readable ) m = 0133 ; // -rw-r--r--
304 if( mode == Process::Umask::Mode::GroupOpen ) m = 0113 ;// -rw-rw-r--
305 if( mode == Process::Umask::Mode::Open ) m = 0111 ; // -rw-rw-rw-
306 return m ;
307}
308
309bool G::ProcessImp::readlink_( const char * path , std::string & value )
310{
311 Path target = File::readlink( path , std::nothrow ) ;
312 if( !target.empty() ) value = target.str() ;
313 return !target.empty() ;
314}
315
316// ==
317
318G::Identity G::ProcessImp::beOrdinary( Identity nobody_id , bool change_group )
319{
320 Identity old_id = Identity::effective() ;
321 Identity real_id = Identity::real() ;
322 if( real_id.isRoot() )
323 {
324 if( change_group )
325 {
326 // make sure we have privilege to change group
327 if( !setEffectiveUser( Identity::root() , std::nothrow ) )
328 throwError() ;
329
330 if( !setEffectiveGroup( nobody_id , std::nothrow ) )
331 {
332 setEffectiveUser( old_id , std::nothrow ) ; // rollback
333 throwError() ;
334 }
335 }
336 if( !setEffectiveUser( nobody_id , std::nothrow ) )
337 throwError() ;
338 }
339 else
340 {
341 // change to real id -- drops suid privileges
342 if( !setEffectiveUser( real_id , std::nothrow ) )
343 throwError() ;
344
345 if( change_group && !setEffectiveGroup( real_id , std::nothrow ) )
346 {
347 setEffectiveUser( old_id , std::nothrow ) ; // rollback
348 throwError() ;
349 }
350 }
351 return old_id ;
352}
353
354void G::ProcessImp::beOrdinaryForExec( Identity run_as_id ) noexcept
355{
356 if( run_as_id != Identity::invalid() )
357 {
358 setEffectiveUser( Identity::root() , std::nothrow ) ; // for root-suid
359 setRealGroup( run_as_id , std::nothrow ) ;
360 setEffectiveGroup( run_as_id , std::nothrow ) ;
361 setRealUser( run_as_id , std::nothrow ) ;
362 setEffectiveUser( run_as_id , std::nothrow ) ;
363 }
364}
365
366void G::ProcessImp::beSpecial( Identity special_identity , bool change_group )
367{
368 setEffectiveUser( special_identity ) ;
369 if( change_group )
370 setEffectiveGroup( special_identity ) ;
371}
372
373void G::ProcessImp::beSpecialForExit( SignalSafe , Identity special_identity ) noexcept
374{
375 // changing effective ids is not strictly signal-safe :-<
376 setEffectiveUser( special_identity , std::nothrow ) ;
377 setEffectiveGroup( special_identity , std::nothrow ) ;
378}
379
380void G::ProcessImp::revokeExtraGroups()
381{
382 if( Identity::real().isRoot() || Identity::effective() != Identity::real() )
383 {
384 // set supplementary group-ids to a zero-length list
385 gid_t dummy = 0 ;
386 GDEF_IGNORE_RETURN ::setgroups( 0U , &dummy ) ; // (only works for root, so ignore the return code)
387 }
388}
389
390bool G::ProcessImp::setRealUser( Identity id , std::nothrow_t ) noexcept
391{
392 return 0 == ::setuid( id.userid() ) ;
393}
394
395void G::ProcessImp::setEffectiveUser( Identity id )
396{
397 if( ::seteuid(id.userid()) )
398 {
399 int e = errno ;
400 throw Process::UidError( Process::strerror(e) ) ;
401 }
402}
403
404bool G::ProcessImp::setEffectiveUser( Identity id , std::nothrow_t ) noexcept
405{
406 return 0 == ::seteuid( id.userid() ) ;
407}
408
409bool G::ProcessImp::setRealGroup( Identity id , std::nothrow_t ) noexcept
410{
411 return 0 == ::setgid( id.groupid() ) ;
412}
413
414void G::ProcessImp::setEffectiveGroup( Identity id )
415{
416 if( ::setegid(id.groupid()) )
417 {
418 int e = errno ;
419 throw Process::GidError( Process::strerror(e) ) ;
420 }
421}
422
423bool G::ProcessImp::setEffectiveGroup( Identity id , std::nothrow_t ) noexcept
424{
425 return 0 == ::setegid( id.groupid() ) ;
426}
427
428void G::ProcessImp::throwError()
429{
430 // typically we are about to std::terminate() so make sure there is an error message
431 G_ERROR( "G::ProcessImp::throwError: failed to give up process privileges" ) ;
432 throw G::Exception( "cannot give up process privileges" ) ;
433}
434
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:45
static G::Path readlink(const Path &link)
Reads a symlink. Throws on error.
Definition: gfile_unix.cpp:349
A combination of user-id and group-id, with a very low-level interface to the get/set/e/uid/gid funct...
Definition: gidentity.h:43
bool isRoot() const noexcept
Returns true if the userid is zero.
static Identity invalid() noexcept
Returns an invalid identity.
static Identity root() noexcept
Returns the superuser identity.
static Identity effective() noexcept
Returns the current effective identity.
static Identity real(bool with_cache=true) noexcept
Returns the calling process's real identity.
A Path object represents a file system path.
Definition: gpath.h:72
const char * cstr() const noexcept
Returns the path string.
Definition: gpath.h:221
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:308
std::string str() const
Returns the path string.
Definition: gpath.h:215
A private implemetation class for G::Process::Umask that hides mode_t.
static void beSpecialForExit(SignalSafe, Identity special_id) noexcept
A signal-safe version of beSpecial() that should only be used just before process exit.
static void closeOtherFiles(int fd_keep=-1)
Closes all open file descriptors except the three standard ones and possibly one other.
static void beSpecial(Identity special_id, bool change_group=true)
Re-acquires special privileges (either root or suid).
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
static void beOrdinaryForExec(Identity run_as_id) noexcept
Sets the real and effective user and group ids to those given, on a best-effort basis.
static void setEffectiveGroup(Identity)
Sets the effective group. Throws on error.
static std::string cwd(bool no_throw=false)
Returns the current working directory.
static void closeStderr()
Closes stderr and reopens it to the null device.
static void beOrdinary(Identity ordinary_id, bool change_group)
Releases special privileges.
static std::string exe()
Returns the absolute path of the current executable, independent of the argv array passed to main().
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
static void setEffectiveUser(Identity)
Sets the effective user. Throws on error.
static void closeFiles(bool keep_stderr=false)
Closes all open file descriptors and reopen stdin, stdout and possibly stderr to the null device.
static void cd(const Path &dir)
Changes directory.
static Identity beOrdinaryAtStartup(Identity ordinary_id, bool change_group)
Revokes special privileges (root or suid), possibly including extra group membership.
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
static bool isPrintableAscii(const std::string &s)
Returns true if every character is a 7-bit, non-control character (ie.
Definition: gstr.cpp:420
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 upper-case characters have been replaced by lower-case cha...
Definition: gstr.cpp:741
Low-level classes.
Definition: galign.h:28