# Copyright (c) Olaf Gellert and # Stephan Martin # # $Id: HELPERS.pm,v 1.6 2006/06/28 21:50:41 sm Exp $ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. use strict; package HELPERS; use POSIX; use Config::Tiny; my $version = "0.1"; my $true = 1; my $false = undef; # # read global configuration file # sub read_global_cfg { my $cfg = Config::Tiny->read("/etc/tinyca/tinyca.cnf", 'utf8'); return ($cfg); } # # read a per CA configuration # sub read_cfg { my $base = shift; my $cfg = Config::Tiny->read($base."/tinyca.cnf", 'utf8'); return ($cfg); } # # read per user configuration # e.g. last used ca, last window position # sub read_user_cfg { my $cfg = Config::Tiny->read($ENV{HOME}."/.tinycarc", 'utf8'); return ($cfg); } # # generate filename from Subject-DN # sub gen_name { my $opts = shift; my $name = ''; foreach (qw(CN EMAIL OU O L ST C)) { if((not defined($opts->{$_})) || ($opts->{$_} eq '')) { $opts->{$_} = "."; } if($opts->{$_} ne '.' && not ref($opts->{$_})) { $name .= $opts->{$_}; } elsif (ref($opts->{$_})) { if(defined($opts->{$_}->[0])) { $name .= $opts->{$_}->[0]; } else { $name .= " "; } } else { $name .= " "; } $name .= ":" if($_ ne 'C'); } return($name); } # # generate temporary filename # sub mktmp { my $base = shift; my @rand = (); my $ret = ''; do { for(my $i = 0; $i < 8; $i++) { push(@rand, int(rand 26)+65); } my $end = pack("C8", @rand); $ret = $base.$end; } while (-e $ret); return($ret); } # # finished... # sub exit_clean { my ($ret) = @_; $ret = 0 unless(defined $ret); # hack to avoid busy cursor my $rootwin = Gtk2::Gdk->get_default_root_window(); my $cursor = Gtk2::Gdk::Cursor->new('left-ptr'); $rootwin->set_cursor($cursor); Gtk2->main_quit(); exit($ret); } # # split Subject DN and return hash # sub parse_dn { my $dn = shift; my (@dn, $k, $v, $tmp); $tmp = {}; $dn =~ s/,/\//g; @dn = split(/\//, $dn); foreach(@dn) { s/^\s+//; s/\s+$//; ($k, $v) = split(/=/); next if(not defined($k)); # trim key and value $k =~ s/^\s+|\s+$//g; $v =~ s/^\s+|\s+$//g; if($k =~ /ou/i) { $tmp->{'OU'} or $tmp->{'OU'} = []; push(@{$tmp->{'OU'}}, $v); } else { if($k =~ /emailaddress/i) { $tmp->{'EMAIL'} = $v; } else { $tmp->{uc($k)} = $v; } } } return($tmp); } # # parse (requested) X509 extensions and return hash # sub parse_extensions { my ($lines, $mode) = @_; my ($sep, $i, $k, $v, $tmp); $sep = $mode eq "req"?"Requested extensions:":"X509v3 extensions:"; $tmp = {}; # skip everything before the extensions for($i = 0; defined($lines->[$i]) && $lines->[$i] !~ /^[\s\t]*$sep$/i; $i++) { return(undef) if not defined($lines->[$i]); } $i++; while($i < @{$lines}) { if(($lines->[$i] =~ /^[\s\t]*[^:]+:\s*$/) || ($lines->[$i] =~ /^[\s\t]*[^:]+:\s+.+$/)) { if($lines->[$i] =~ /^[\s\t]*Signature Algorithm/i) { $i++; next; } $k = $lines->[$i]; $k =~ s/[\s\t:]*$//g; $k =~ s/^[\s\t]*//g; $tmp->{$k} = []; $i++; while(($lines->[$i] !~ /^[\s\t].+:\s*$/) && ($lines->[$i] !~ /^[\s\t]*[^:]+:\s+.+$/) && ($lines->[$i] !~ /^[\s\t]*Signature Algorithm/i) && ($i < @{$lines})) { $v = $lines->[$i]; $v =~ s/^[\s]+//g; $v =~ s/[\s]+$//g; $i++; next if $v =~ /^$/; next if $v =~ /Signature Algorithm:/; my @vs = split(/,/, $v); foreach(@vs) { $_ =~ s/^\s//; $_ =~ s/\s$//; push(@{$tmp->{$k}}, $_); } } } else { $i++; } } return($tmp); } # # get last used export directory # sub get_export_dir { my $main = shift; open(EXPIN, "<$main->{'cadir'}/.exportdir") || return(undef); my $dir = ; chomp($dir); return($dir); } # # write last used export directory # sub write_export_dir { my ($main, $dir) = @_; $dir =~ s:/[^/]+$::; open(EXPOUT, ">$main->{'cadir'}/.exportdir") || do { my $t = sprintf(_("Can't write exportdir: %s, %s"), "$main->{'cadir'}/.exportdir", $!); GUI::HELPERS::print_warning($t); return; }; print EXPOUT "$dir\n"; close(EXPOUT); return($dir); } # # generate contents for subjectAltName # sub gen_subjectaltname_contents($@) { my $type = shift || ''; my @input = map { split/,\s*|\s+/, $_ } @_; # split on ',' and ' ' my %output = (); # uniq on the fly if ($type) { # type given => use that one for all foreach my $elem (@input) { $output{$type.$elem} = 1; } } else { # no type => use heuristigcs to guess type per element foreach my $elem (@input) { if ($elem =~ s/^(ip:|dns:)(.*)/$2/i) { $type = uc($1); } elsif ($elem =~ s/^(email:)(.*)/$2/i) { $type = lc($1); } else { if ($elem =~ /^\d+\.\d+\.\d+\.\d+$/) { # it's an IP address $type = 'IP:'; } elsif ($elem =~ /^.+\@.+\.\w+$/) { # it's a mail address $type = 'email:'; } else { $type = 'DNS:' # otherwise it's a DNS name } } $output{$type.$elem} = 1; } } return(wantarray ? keys(%output) : join(', ', keys(%output))); } sub enc_base64 { my $data = shift; my $ret = MIME::Base64::encode($data, ''); $ret =~ tr/\/+/-_/; return $ret; } sub dec_base64 { my $data = shift; $data =~ tr/-_/\/+/; return MIME::Base64::decode($data); } 1 __END__ =head1 NAME HELPERS - helper functions for TinyCA, doing small jobs not related to the GUI =head1 SYNOPSIS use HELPERS; $name = HELPERS::gen_name($opts); $tmpnam = HELPERS::mktmp($base); $dnhash = HELPERS::parse_dn($dnstring); $exthash = HELPERS::parse_extensions($mode, $lines); $subjaltname = HELPERS::gen_subjectaltname_contents($type, @list); exit_clean($retcode); =head1 DESCRIPTION HELPERS.pm is just a library, containing some useful functions used by other TinyCA modules. =head1 FUNCTIONS =head2 HELPERS::gen_name($opts) =over 1 returns a string with the TinyCA filename for a certificate, request or key. The filename is generated from the following parts of the Subject DN from the related request or certificate if present: CN EMAIL OU O L ST C These parts need to be elements in the given options hash. =back =head2 HELPERS::mktmp($base) =over 1 returns a string, containing a uniqe filename starting with $base, which is not existing yet. $base needs to be an absolute path to allow HELPERS::mktmp() reliable check that the filename is really uniqe. =back =head2 HELPERS::parse_dn($dnstring) =over 1 returns the reference to a hash containing all elements of the Subject DN, given in $dnstring. The element OU is included as an array refernce in the hash, with an array containing all values of OU. =back =head2 HELPERS::parse_extensions($mode, $lines) =over 1 returns the reference to a hash containing all X509 extensions of the given request or certificate. The request or certificate is given in textform as an array reference with the array containing one line per element. $mode contains one of the strings "req" or "cert" depending on the type of the data. =back =head2 HELPERS::exit_clean($retcode) =over 1 does nothing yet, than closing the Gtk application returning the exitcode given in $retcode. =back =head2 $main->HELPERS::get_export_dir() =over 1 Get last used export directory. =back =head2 $main->HELPERS::write_export-dir($dir) =over 1 Store last used export directory =back =head2 HELPERS::gen_subjectaltname_contents($type, @list) =over 1 Generate a string suitable for the use as subjhectAltname contets for OpenSSL. If $Type is not empty create the contents of that type only, otherwise use either the type prefix of the list elements or the following heuristics to find the type for the appropriate elements: If the element looks like an IP address in dotted quad notation set then treat it as one. If the element contains a '@' followed by a '.' and a sequence of letters then treat the element as an email address. In all other cases treat it as a DNS name. =back =cut