Ok, for a while I have been looking at my system scripts and cgis that use perl and have to contain sensitive info like passwords and stuff... and that just bugged me, since perl scrits need to be readable by those who run them. I also didn't want to go through converting my scripts to suid, which, frankly, can be a pain, and opens up security holes... so, I finally came up with a nice, safe (I think) way of passing sensitive conf data to a script.

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

key1: value key2: value key2: value2 #key3: this is a key3 comment key3: value
which will put all these in a hash accordingly, with multiple keys turning into an array.

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)

#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; }
I know, I know... it's not the best C in the world... I don't use C much any more... :)
And here is the perl module
#!/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;
for now it is called loader... this is not the permanent name... anyone have a good CPAN name for it?
Here is an example of it's use...
say you have /var/scripts in the SCRIPT_PATH in the C... you would put foo.conf in /var/scripts with say
pass: tonkatruck
you would also have your script foo.pl in /var/scripts
#!/usr/bin/perl -w use loader; use strict; my %conf = &loader::get_conf(); print "Pass is $conf{pass}\n";
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)

WHEW! That was a long post... any questions?

                - Ant
                - Some of my best work - Fish Dinner

Replies are listed 'Best First'.
Re: Protect your passwords for perl scripts
by shadox (Priest) on Sep 06, 2001 at 01:45 UTC
    Your idea looks nice, but you should take a look at the Filter Module ,it is available from CPAN, with this you can write encripted Perl Code, so you would not have problems with the passwords in your scripts. In the Wolf Book ( Mastering Algorithms with Perl ), you can find more about it in Chapter 13:Cryptography.
    Shadox
      I am willing to bet that encrypted perl code is much slower to start that a setuid C wrapper... I envision this used for cgis and system scripts... cgis certainly need the speed, system scripts could go either way.

                      - Ant
                      - Some of my best work - Fish Dinner