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
37 namespace 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
61 class G::Process::UmaskImp /// A private implemetation class for G::Process::Umask that hides mode_t.
62{
63 public:
64 mode_t m_old_mode ;
65} ;
66
67// ==
68
69 void G::Process::cd( const Path & dir )
70{
71 if( ! cd(dir,std::nothrow) )
72 throw CannotChangeDirectory( dir.str() ) ;
73}
74
75 bool G::Process::cd( const Path & dir , std::nothrow_t )
76{
77 return 0 == ::chdir( dir.cstr() ) ;
78}
79
80 void G::Process::closeStderr()
81{
82 ProcessImp::reopen( STDERR_FILENO , O_WRONLY ) ;
83}
84
85 void 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
98 void 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
115 int G::Process::errno_( const SignalSafe & ) noexcept
116{
117 return errno ; // possible macro, not ::errno or std::errno
118}
119
120 int 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
135 void G::Process::beSpecial( Identity special_identity , bool change_group )
136{
137 ProcessImp::beSpecial( special_identity , change_group ) ;
138}
139
140 void G::Process::beSpecialForExit( SignalSafe , Identity special_identity ) noexcept
141{
142 ProcessImp::beSpecialForExit( SignalSafe() , special_identity ) ;
143}
144
145 G::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
160 void G::Process::beOrdinary( Identity ordinary_id , bool change_group )
161{
162 ProcessImp::beOrdinary( ordinary_id , change_group ) ;
163}
164
165 void G::Process::beOrdinaryForExec( Identity run_as_id ) noexcept
166{
167 ProcessImp::beOrdinaryForExec( run_as_id ) ;
168}
169
170 void G::Process::setEffectiveUser( Identity id )
171{
172 G::ProcessImp::setEffectiveUser( id ) ;
173}
174
175 void G::Process::setEffectiveGroup( Identity id )
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
250 bool G::Process::Id::operator==( const Id & other ) const noexcept
251{
252 return m_pid == other.m_pid ;
253}
254
255 bool 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
273 void G::Process::Umask::set( Mode mode )
274{
275 GDEF_IGNORE_RETURN ::umask( ProcessImp::umaskValue(mode) ) ;
276}
277
278 void G::Process::Umask::tighten()
279{
280 ::umask( ::umask(2) | mode_t(7) ) ; // -xxxxxx---
281}
282
283// ==
284
285 void G::ProcessImp::noCloseOnExec( int fd ) noexcept
286{
287 ::fcntl( fd , F_SETFD , 0 ) ;
288}
289
290 void 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
309 bool 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
318 G::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
354 void 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
366 void G::ProcessImp::beSpecial( Identity special_identity , bool change_group )
367{
368 setEffectiveUser( special_identity ) ;
369 if( change_group )
370 setEffectiveGroup( special_identity ) ;
371}
372
373 void 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
380 void 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
390 bool G::ProcessImp::setRealUser( Identity id , std::nothrow_t ) noexcept
391{
392 return 0 == ::setuid( id.userid() ) ;
393}
394
395 void 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
404 bool G::ProcessImp::setEffectiveUser( Identity id , std::nothrow_t ) noexcept
405{
406 return 0 == ::seteuid( id.userid() ) ;
407}
408
409 bool G::ProcessImp::setRealGroup( Identity id , std::nothrow_t ) noexcept
410{
411 return 0 == ::setgid( id.groupid() ) ;
412}
413
414 void 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
423 bool G::ProcessImp::setEffectiveGroup( Identity id , std::nothrow_t ) noexcept
424{
425 return 0 == ::setegid( id.groupid() ) ;
426}
427
428 void 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
G::Exception
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:45
G::File::readlink
static G::Path readlink(const Path &link)
Reads a symlink. Throws on error.
Definition: gfile_unix.cpp:349
G::Identity
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
G::Identity::isRoot
bool isRoot() const noexcept
Returns true if the userid is zero.
Definition: gidentity_unix.cpp:194
G::Identity::invalid
static Identity invalid() noexcept
Returns an invalid identity.
Definition: gidentity_unix.cpp:159
G::Identity::root
static Identity root() noexcept
Returns the superuser identity.
Definition: gidentity_unix.cpp:169
G::Identity::effective
static Identity effective() noexcept
Returns the current effective identity.
Definition: gidentity_unix.cpp:133
G::Identity::real
static Identity real(bool with_cache=true) noexcept
Returns the calling process's real identity.
Definition: gidentity_unix.cpp:142
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::cstr
const char * cstr() const noexcept
Returns the path string.
Definition: gpath.h:221
G::Path::nullDevice
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
Definition: gpath.cpp:308
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:215
G::Process::UmaskImp
A private implemetation class for G::Process::Umask that hides mode_t.
Definition: gprocess_unix.cpp:62
G::Process::beSpecialForExit
static void beSpecialForExit(SignalSafe, Identity special_id) noexcept
A signal-safe version of beSpecial() that should only be used just before process exit.
Definition: gprocess_unix.cpp:140
G::Process::closeOtherFiles
static void closeOtherFiles(int fd_keep=-1)
Closes all open file descriptors except the three standard ones and possibly one other.
Definition: gprocess_unix.cpp:98
G::Process::beSpecial
static void beSpecial(Identity special_id, bool change_group=true)
Re-acquires special privileges (either root or suid).
Definition: gprocess_unix.cpp:135
G::Process::strerror
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
Definition: gprocess_unix.cpp:127
G::Process::beOrdinaryForExec
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.
Definition: gprocess_unix.cpp:165
G::Process::setEffectiveGroup
static void setEffectiveGroup(Identity)
Sets the effective group. Throws on error.
Definition: gprocess_unix.cpp:175
G::Process::cwd
static std::string cwd(bool no_throw=false)
Returns the current working directory.
Definition: gprocess_unix.cpp:180
G::Process::closeStderr
static void closeStderr()
Closes stderr and reopens it to the null device.
Definition: gprocess_unix.cpp:80
G::Process::beOrdinary
static void beOrdinary(Identity ordinary_id, bool change_group)
Releases special privileges.
Definition: gprocess_unix.cpp:160
G::Process::exe
static std::string exe()
Returns the absolute path of the current executable, independent of the argv array passed to main().
Definition: gprocess_unix.cpp:225
G::Process::errno_
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
Definition: gprocess_unix.cpp:115
G::Process::setEffectiveUser
static void setEffectiveUser(Identity)
Sets the effective user. Throws on error.
Definition: gprocess_unix.cpp:170
G::Process::closeFiles
static void closeFiles(bool keep_stderr=false)
Closes all open file descriptors and reopen stdin, stdout and possibly stderr to the null device.
Definition: gprocess_unix.cpp:85
G::Process::cd
static void cd(const Path &dir)
Changes directory.
Definition: gprocess_unix.cpp:69
G::Process::beOrdinaryAtStartup
static Identity beOrdinaryAtStartup(Identity ordinary_id, bool change_group)
Revokes special privileges (root or suid), possibly including extra group membership.
Definition: gprocess_unix.cpp:145
G::SignalSafe
An empty structure that is used to indicate a signal-safe, reentrant implementation.
Definition: gsignalsafe.h:37
G::Str::isPrintableAscii
static bool isPrintableAscii(const std::string &s)
Returns true if every character is a 7-bit, non-control character (ie.
Definition: gstr.cpp:420
G::Str::lower
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
gfile.h
gidentity.h
glimits.h
glog.h
gpath.h
gprocess.h
gstr.h
G
Low-level classes.
Definition: galign.h:28