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


In reply to Protect your passwords for perl scripts by suaveant

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.