Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Generate and compile C-wrapper to start programs as daemon and unprivileged user in a chroot environment (linux/unix)

by Tomte (Priest)
on Feb 01, 2007 at 16:07 UTC ( #597765=CUFP: print w/ replies, xml ) Need Help??

From the pod:

This program will generate and compile a little c-program that must be run as root, and if run switches to unpriviliged user and group ids and subtitutes itself with another program via C<execve> with an empty environment, or the defined environment given via the --env parameter.
To function properly a working gcc must be installed.
Only root can excute the generated wrapper.
Such a wrapper is especially useful to run servers that aren't capable to switch user id by themselves in a chroot environment.

perl ./generate_runas_exe.pl -e /usr/bin/sleep -u 1001 -g 1001 -d -o m +ytest -- 20
to test the generator this command will generate a wrapper that runs '/usr/bin/sleep 20' as user 1000 in the background. Run the wrapper as root, and check your process table for 'sleep'.

In real life you would use such a wrapper to start a chrooted server:

/usr/bin/chroot /chroot/jail /bin/mywrapper

#!/usr/bin/perl # # Author : Tom Regner <t.regner@librics.de> # copyright : (c) 2007 webmaster.programm GmbH, Hannover, Germany # (c) 2007 Tom Regner <cpan@tomsdiner.org> # # Version: XXX # use strict; use warnings; use Scalar::Util qw(looks_like_number); use File::Temp qw(:mktemp); use Getopt::Long; use Pod::Usage; ## # constants use constant CODE_TEMPLATE_CD => ' @@WS@@if (chdir("@@DIR@@\0") != 0) { @@WS@@ fprintf(stderr, "couldn\'t change working directory - exitin +g"); @@WS@@ exit(5); @@WS@@} '; use constant CODE_TEMPLATE_SAVEPID => ' @@WS@@save_pid(pid, "@@PIDFILE@@\0"); '; # simply execve the wrapped program use constant CODE_TEMPLATE_EXEC => ' #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> void print_help(void); int main(int argc, char** argv) { if (getuid() != 0) { fprintf(stderr, "only root can execute %s - exiting\n", argv[0 +]); exit(1); } if (argc > 1) { print_help(); exit(0); } else { if (setgid(@@GID@@) == -1) { fprintf(stderr, "couldn\'t switch groupid - exiting\n"); exit(2); } if (setuid(@@UID@@) == -1) { fprintf(stderr, "couldn\'t switch userid - exiting\n"); exit(2); } @@CHDIR@@ char *prog = "@@EXE@@\0"; char *args[@@ARGC@@ + 2]; args[0] = "@@EXE@@\0"; @@ARGS@@ args[@@ARGC@@ + 1] = NULL; char* eenv[@@ENVC@@ + 1]; @@ENV@@ eenv[@@ENVC@@] = NULL; return execve(prog, args, eenv); } } void print_help(void) { fprintf(stdout, "executes \'@@EXE@@ @@ARG_LIST@@\' with user id \' +@@UID@@\'\nenvironment \'@@ENV_LIST@@\'\n"); } '; # code to use for a forking daemon use constant CODE_TEMPLATE_DAEMON => ' #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> void print_help(void); void daemonize(void); void save_pid(pid_t pid,char *pid_file); int main(int argc, char** argv) { pid_t pid; if (getuid() != 0) { fprintf(stderr, "only root can execute %s - exiting\n", argv[0 +]); exit(1); } if (argc > 1) { print_help(); exit(0); } else { if (setgid(@@GID@@) == -1) { fprintf(stderr, "couldn\'t switch groupid - exiting\n"); exit(2); } if (setuid(@@UID@@) == -1) { fprintf(stderr, "couldn\'t switch userid - exiting\n"); exit(2); } pid = fork(); if (pid == 0) { @@CHDIR@@ daemonize(); char *prog = "@@EXE@@\0"; char *args[@@ARGC@@ + 2]; args[0] = "@@EXE@@\0"; @@ARGS@@ args[@@ARGC@@ + 1] = NULL; char* eenv[@@ENVC@@ + 1]; @@ENV@@ eenv[@@ENVC@@] = NULL; return execve(prog, args, eenv); } else if (pid == -1) { fprintf(stderr, "couldn\'t fork the wrapped process - exit +ing\n"); exit(3); } /* parent */ @@SAVE_PID@@ exit(0); } } void print_help(void) { fprintf(stdout, "executes \'@@EXE@@ @@ARG_LIST@@\' with user id \' +@@UID@@\' in the background\nenvironment \'@@ENV_LIST@@\'\n"); } void daemonize(void) { int fd; if (setsid() == -1) { fprintf(stderr, "couldn\'t create new processgroup - exiting") +; exit(4); } if ((fd = open("/dev/null", O_RDWR, 0)) != -1) { (void)dup2(fd, STDIN_FILENO); (void)dup2(fd, STDOUT_FILENO); (void)dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) (void)close(fd); } else { fprintf(stderr, "couldn\'t redirect stdout, stderr, stdin - pr +oceeding nontheless\n"); } } void save_pid(pid_t pid,char *pid_file) { FILE *fp; if (!pid_file) return; if (!(fp = fopen(pid_file,"w"))) { fprintf(stderr,"Could not open the pid file %s for writing\n", +pid_file); return; } fprintf(fp,"%ld\n",(long) pid); if (fclose(fp) == -1) { fprintf(stderr,"Could not close the pid file %s.\n",pid_file); return; } } '; use constant REVISION => '6454'; #### # configuration and default values my ( $exe, $uid, $gid, $outfile, $tdir, $pidfile, $daemonize, $dontstr +ip, $verbose, $help, $man, @env ) = ( undef, undef, undef, undef, undef, undef, 0, 0, 0, 0, 0, ()); Getopt::Long::Configure("bundling"); GetOptions( "exe|e=s" => \$exe, "uid|u=i" => \$uid, "gid|g=i" => \$gid, "env=s" => \@env, "dir=s" => \$tdir, "outfile|o=s" => \$outfile, "daemonize|d" => \$daemonize, "nostrip|n" => \$dontstrip, "pidfile|p=s" => \$pidfile, "verbose|v+" => \$verbose, "help|h|?" => \$help, "man|m" => \$man, ) or pod2usage( -verbose => 0, -exitval => 1 ); pod2usage( -verbose => 1, -exitval => 0 ) if ($help); pod2usage( -verbose => 2, -exitval => 0 ) if ($man); my $compiler = `/usr/bin/which gcc`; chomp($compiler); my $strip = `/usr/bin/which strip`; chomp($strip); unless ($compiler =~ m!/[a-z0-9/]+/gcc!i) { print "no valid gcc found - exiting\n"; pod2usage( -verbose => 0, -exitval => 1 ); } unless($dontstrip || $strip =~ m!/[a-z0-9/]+/strip!i) { print "no valid strip found - use --nostrip if I shall proceed non +theless - exiting\n"; pod2usage( -verbose => 0, -exitval => 1 ); } unless ($exe && $exe =~ m!^/[a-z].*$! && $exe !~ m!\.\.!) { print "no exe to wrap given or invalid path (no ..!) - exiting\n"; pod2usage( -verbose => 0, -exitval => 1 ); } unless ($outfile && $outfile =~ m!^[a-z][a-z0-9_]+$!) { print "no filename to write to given - exiting\n"; pod2usage( -verbose => 0, -exitval => 1 ); } unless (looks_like_number($uid) && looks_like_number($gid) && $uid > 0 + && $gid > 0) { print "uid and gid have to specified as numbers >0 (non root accou +nts)\n"; pod2usage( -verbose => 0, -exitval => 1 ); } if ($tdir && ($tdir =~ m!\.\.! || $tdir !~ m!/[a-z].*!)) { print "--dir <path> path must be absolute - no .. allowed\n"; pod2usage( -verbose => 0, -exitval => 1 ); } elsif (!$tdir) { $tdir = "/"; # we always change the working directory # (just in case the wrapper runs in a chroot without +--dir specified)! } if ($pidfile && ($pidfile =~ m!\.\.! || $pidfile !~ m!/[a-z].*!)) { print "--pidfile <path> path must be absolute - no .. allowed\n" +; pod2usage( -verbose => 0, -exitval => 1 ); } my @args = (); my $count = 1; foreach my $arg (@ARGV) { $arg =~ s/"/\\"/g; $arg =~ s/\0//g; push @args, "args[$count] = \"$arg\\0\";"; ++$count; } $count = 0; my @envc = (); foreach my $var (@env) { $var =~ s/"/\\"/g; $var =~ s/\0//g; push @envc, "eenv[$count] = \"$var\\0\";"; ++$count; } my $cd_source = CODE_TEMPLATE_CD; $cd_source =~ s/\@\@DIR\@\@/$tdir/; if ($daemonize) { $cd_source =~ s/\@\@WS\@\@/ /g; } else { $cd_source =~ s/\@\@WS\@\@/ /g; } my $pid_source = ''; if ($daemonize && $pidfile) { $pid_source = CODE_TEMPLATE_SAVEPID; $pid_source =~ s/\@\@WS\@\@/ /g; $pid_source =~ s/\@\@PIDFILE\@\@/$pidfile/g; } my $parameters = { '@@GID@@' => $gid, '@@UID@@' => $uid, '@@EXE@@' => $exe, '@@ARGC@@' => scalar @ARGV, '@@ENVC@@' => scalar @envc, '@@ENV@@' => join ("\n ", @envc), '@@ENV_LIST@@' => join ("; ", @env), '@@ARGS@@' => join ("\n ", @args), '@@ARG_LIST@@' => join (" ", @ARGV), '@@CHDIR@@' => $cd_source, '@@SAVE_PID@@' => $pid_source, }; if ($verbose) { print STDOUT "$0 - Revision " . REVISION . "\n"; print STDOUT "generating wrapper for '${exe}' \n"; print STDOUT "\tuser_id: '${uid}'\n"; print STDOUT "\tgroup_id: '${gid}'\n"; print STDOUT "\targuments: '" . $parameters->{'@@ARG_LIST@@'} . + "'\n"; print STDOUT "\tenvironment: '" . $parameters->{'@@ENV_LIST@@'} . + "'\n"; print STDOUT "\tworking dir: '" . $tdir . "'\n"; print STDOUT "\trun as daemon: '" . ($daemonize ? "true" : "false") + . "'\n"; print STDOUT "\tpidfile: '" . $pidfile . "'\n" if ($daemonize + && $pidfile); } my $source = ''; if ($daemonize) { $source = CODE_TEMPLATE_DAEMON; } else { $source = CODE_TEMPLATE_EXEC; } foreach my $par (keys %$parameters) { $source =~ s/$par/$parameters->{$par}/g; } my ($tmpfile, $tmpfilename) = mkstemps("greXXXXXX", '.c'); print $tmpfile $source; close($tmpfile); print STDOUT "source generated\n" if $verbose; my $status = system($compiler, $tmpfilename, '-o', $outfile); print STDOUT "$outfile compiled\n" if $verbose; if ($status == 0) { unlink($tmpfilename); $status = system($strip, $outfile) unless ($dontstrip); if ($status == 0) { print STDOUT "$outfile stripped\n" if $verbose; } else { print STDERR "couldn't strip $outfile\n"; } print STDOUT "$outfile generated\n"; } else { print STDOUT "couldn't generate $outfile - check $tmpfilename for +c syntax errors\n"; } __END__ =head1 NAME generate_runas_exe.pl - generate wrapper programs, that start a fixed +program with a fixed set of parameters as a fixed user =head1 SYNOPSIS generate_runas_exe.pl -e /path/to/command -u user_id -g group_id -o ou +tfile ARG1 ARG2 ... Options: --exe|-e /path/to/command the command to execute --gid|-g id the group-id to switch to --uid|-u id the user-id to switch to --env NAME=value define environment variables to define +for the wrapped program --outfile|-o filename the name of the file the executable is +written to --nostrip|-n dont't strip the resulting executable --daemonize|-d create a daemonizing wrapper --pidfile|-p path if --daemonize is given, wrapper will w +rite child pid to pidfile --verbose|-v be a bit more verbose --help|-h brief help message --man|-m full documentation =head1 OPTIONS =over 8 =item B<--exe program> The program to execute - must be an absolute path. The program is not +required to exist on the machine you generate the wrapper at. =item B<--gid id> The generated wrapper switches to this group-id prior to executing the + given program. If the wrapper can't switch to this id, it will exit +with status 2. =item B<--uid id> The generated wrapper switches to this user-id prior to executing the +given program. If the wrapper can't switch to this id, it will exit w +ith status 2. =item B<--outfile filename> The name of the file the generated wrapper is written to =item B<--daemonize> If given the resulting wrapper will detach itself from the console/sta +rting process prior to executing the wrapped program. =item B<--pidfile path> If C<--daemonize> is given, the generated wrapper will write the forke +d childs pid to C<path>, after it dropped to the unprivileged user/gr +oup. Attention: The written pidfile is not deleted after execution of the w +rapped program ends - if you use a pidfile, you will have to take car +e that it's removed properly when needed. =item B<--env NAME=value> With this option one can define an environment for the wrapped program +. This option may be given multiple times. =item B<--dir path> Absolute path the wrapper changes the working directory to prior to ex +ecuting the wrapped program =item B<--nostrip> Don't strip the resulting executable; default is to strip the executab +le. =item B<--verbose> Puts generate_runas_exe.pl in a kind of chatty mode. =item B<--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =back Supply the options for the wrapped programm after all generate_runas_e +xe.pl options seperated with a double-dash '--'. =head1 DESCRIPTION B<This program> will generate and compile a little c-program that must + be run as root, and if run switches to unpriviliged user and group i +ds and subtitutes itself with another program via C<execve> with an e +mpty environment, or the defined environment given via the --env para +meter. To function properly a working C<gcc> must be installed. Only C<root> can excute the generated wrapper. Such a wrapper is especially useful to run servers that aren't capable + to switch user id by themselves in a chroot environment. This generator shouldn't be present on a production server - use it on + a staging machine to generate wrappers for the production systems. =head2 WRAPPER EXITCODES =over 8 =item C<0> ok =item C<1> only root can execute generated wrappers. =item C<2> the wrapper couldn't switch the user or the group id =item C<3> a daemonizing wrapper couldn't fork =item C<4> the forked process couldn't create a new session. =item C<5> couldn't change the working directory =back =head1 EXAMPLES =over 8 =item C<perl ./generate_runas_exe.pl -e /usr/bin/sleep -u 1001 -g 1001 + -d -o mytest -- 20> to test the generator this command will generate a wrapper that ru +ns '/usr/bin/sleep 20' as user 1000 in the background. Run the wrappe +r as root, and check your process table for 'sleep' =back =head1 TODO test and think. So far only tested on different Linux installations. =head1 BUGS So far only tested on different Linux installations. No real bugs known so far - comments and bugreports to <t.regner@libri +cs.de> or <cpan@tomsdiner.org> =head1 AUTHOR Tom Regner <t.regner@librics.de> =head1 COPYRIGHT Copyright (c) 2007 webmaster.programm GmbH, Hannover, Germany Copyright (c) 2007 Tom Regner <cpan@tomsdiner.org> =head1 LICENSE This program is free software; you can redistribute it and/or modify i +t under the terms of the Perl Artistic License or the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at yo +ur option) any later version. This program is distributed in the hope that it will be useful, but WI +THOUT ANY WARRANTY; without even the implied warranty of MERCHANTABIL +ITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. If you do not have a copy of the GNU General Public License write to t +he Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, + USA. =cut # vim: sw=4 ts=4 et


regards,
tomte


An intellectual is someone whose mind watches itself.
-- Albert Camus

Comment on Generate and compile C-wrapper to start programs as daemon and unprivileged user in a chroot environment (linux/unix)
Select or Download Code

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: CUFP [id://597765]
Approved by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (3)
As of 2015-07-28 02:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (251 votes), past polls