What I do is I have a C program which is suid. It looks in a compiled in path for a script and conf file of the same base name (i.e. prog is foo.cgi it looks for foo.pl and foo.conf in a special path). The conf file is readable (can also be writeable) by the suid user, the perl script is typical read by all who can run it. The C prog open the file to a specified file descriptor, then drops suid and execs the perl script as the calling user. My module then provides routines to open the file descriptor and parse the data into a hash for the program's consumption. Voila.
the conf file looks like
which will put all these in a hash accordingly, with multiple keys turning into an array.key1: value key2: value key2: value2 #key3: this is a key3 comment key3: value
This is all very rough, but please have a look at the code below, voice your thoughts and concerns... point out better ways to do it... oh... and I don't know why I didn't do it as OO... I just didn't want to... the closures were fun :)
Here is the C program (which could be done in perl, too)
I know, I know... it's not the best C in the world... I don't use C much any more... :)#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> /* path to look for .pl scripts, .conf should be there too */ #define SCRIPT_PATH "/home/ant/scripts:/var/scripts/" char *find_script(char *); int main(int argc, char **argv) { struct stat caller; int fd; int euid,uid,loc; char script[1024]; char cmd[4096]; char key[256]; char conf[1024]; char path[10240]; char *tmp,*tmp2; char *scr_path; int i = 0; /* strip base name */ if(tmp = rindex(argv[0], '/')) { tmp++; } else { tmp = argv[0]; } /* remove extension */ if(tmp2 = index(tmp,'.')) { *tmp2 = '\0'; } /* save it */ strcpy(key, tmp); /* find where script with same base name is */ scr_path = find_script(key); /* has path info */ if(index(argv[0],'/')) { lstat(argv[0], &caller); } else { /* was called from PATH... seek it out and make sure it is legal * +/ strcpy(path,(char *)getenv("PATH")); tmp = path; while(tmp = strtok(tmp,":")) { strcpy(cmd,tmp); strcat(cmd,"/"); strcat(cmd,argv[0]); if(!lstat(cmd, &caller)) { break; } tmp = NULL; } } euid = geteuid(); uid = getuid(); /* is link owned by suid user or root? */ if(caller.st_uid != euid && uid != 0) { fprintf(stderr, "Invalid attempt to call loader from an unauthoriz +ed link, bad user, no biscuit!\n"); exit(1); } /* build paths to script and conf */ sprintf(conf, "%s/%s.conf", scr_path, key); sprintf(script, "%s/%s.pl", scr_path, key); /* open conf... RW if can, RO if not put on fd why 27 and 28? why not? doubt they will be used? */ fd = open(conf, O_RDWR); if(fd >= 0) dup2(fd, 27); else { fd = open(conf, O_RDONLY); dup2(fd, 28); } close(fd); /* we don't want to be special any more */ setuid(getuid()); seteuid(getuid()); setgid(getgid()); setegid(getgid()); /* become the perl script */ execvp(script,NULL); } char *find_script(char *key) { char *tmp; char path[4096]; char cmd[1024]; struct stat script; strcpy(path, SCRIPT_PATH); tmp = path; while(tmp = strtok(tmp,":")) { strcpy(cmd,tmp); strcat(cmd,"/"); strcat(cmd,key); strcat(cmd,".pl"); if(!lstat(cmd, &script)) { break; } tmp = NULL; } return tmp; }
for now it is called loader... this is not the permanent name... anyone have a good CPAN name for it?#!/usr/bin/perl package loader; use Fcntl; use strict; use vars qw(@EXPORT_OK); @EXPORT_OK = qw(get_conf write_conf); #F_SETFD w/ fcntl() and $^F # This routine opens the file, then memoizes itself sub _open { local $^W = 0; my($fh,$type); if(open $fh, "+<&=27") { $type = 1; } elsif(open $fh, "<&=28") { $type = 0; } else { *_open = sub {}; warn "No conf file available: $!"; return; } fcntl($fh, F_GETFL, $_); *_open = sub { return($fh,$type); }; _open(); } # read conf and make it a data structure sub get_conf { my ($fh,$type) = _open(); my (%conf,%loader_comments); unless($fh) { warn("get_conf failed... no filehandle"); return; } local $_; while(<$fh>) { my($k,$v); next if /^\s+\#/; chomp; if(/^\#\s*(?:([^:]+):)?/) { push @{$loader_comments{$1}}, $_; } elsif((($k,$v) = split /:\s*/, $_, 2) == 2) { if($conf{$k}) { if(ref $conf{$k}) { push @{$conf{$k}}, $v; } else { $conf{$k} = [$conf{$k}, $v]; } } else { $conf{$k} = $v; } } } # save the comments for the nice people *_comments = sub { \%loader_comments; }; return wantarray ? %conf : \%conf; } # write conf data back to file, if it is writeable sub write_conf { my ($fh,$type) = _open(); unless($type) { warn("Conf file not writeable\n"); return; } my %conf; if(@_ == 1) { if(ref $_[0] eq 'HASH') { %conf = %{$_[0]}; } else { warn "Need to pass write_conf a hash or hash ref"; return; } } else { if(@_ % 2) { warn "Odd number of arguments to write_hash. Need to pass +write_conf a hash or hash ref"; return; } %conf = @_; } seek($fh, 0, 0); my $c = &_comments; # write general comments at top for my $comment (@{$c->{''}}) { print $fh "$comment\n"; } print $fh "\n"; for my $k (sort keys %conf) { $c->{$k}[0] ||= "# $k: "; # write comments for indivisual keys for my $comment (@{$c->{$k}}) { print $fh "$comment\n"; } if(ref $conf{$k}) { for my $a (@{$conf{$k}}) { print $fh "$k: $a\n"; } } else { print $fh "$k: $conf{$k}\n"; } print $fh "\n"; } truncate $fh, tell $fh; } # close your file handles! END { my $fh = (_open())[0]; close $fh if $fh; } 1;
you would also have your script foo.pl in /var/scriptspass: tonkatruck
and somewhere you would have the C program, suid to a user that can read the conf file, or a link to that program, with a name in the form of foo.extension (extension can be anything, or you can not have one... the foo is what matters, it is your key into /var/scripts)#!/usr/bin/perl -w use loader; use strict; my %conf = &loader::get_conf(); print "Pass is $conf{pass}\n";
WHEW! That was a long post... any questions?
- Ant
- Some of my best work - Fish Dinner
In reply to Protect your passwords for perl scripts by suaveant
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |