Index: create.patch.sh
===================================================================
--- create.patch.sh (nonexistent)
+++ create.patch.sh (revision 5)
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+VERSION=5.9
+
+tar --files-from=file.list -xzvf ../net-snmp-$VERSION.tar.gz
+mv net-snmp-$VERSION net-snmp-$VERSION-orig
+
+cp -rf ./net-snmp-$VERSION-new ./net-snmp-$VERSION
+
+diff --unified -Nr net-snmp-$VERSION-orig net-snmp-$VERSION > net-snmp-$VERSION-cert-path.patch
+
+mv net-snmp-$VERSION-cert-path.patch ../patches
+
+rm -rf ./net-snmp-$VERSION
+rm -rf ./net-snmp-$VERSION-orig
Property changes on: create.patch.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: file.list
===================================================================
--- file.list (nonexistent)
+++ file.list (revision 5)
@@ -0,0 +1 @@
+net-snmp-5.9/local/net-snmp-cert
Index: net-snmp-5.9-new/local/net-snmp-cert
===================================================================
--- net-snmp-5.9-new/local/net-snmp-cert (nonexistent)
+++ net-snmp-5.9-new/local/net-snmp-cert (revision 5)
@@ -0,0 +1,2689 @@
+#!/usr/bin/perl
+
+use strict;
+use 5.8.0;
+
+use Getopt::Long qw(:config gnu_getopt no_ignore_case pass_through);
+use Cwd qw(getcwd realpath);
+use File::Basename;
+use File::Copy;
+use File::Path;
+use Sys::Hostname;
+use Carp;
+
+our @CC = (
+["AF","AFGHANISTAN"],
+["AX","ÅLAND ISLANDS"],
+["AL","ALBANIA"],
+["DZ","ALGERIA"],
+["AS","AMERICAN SAMOA"],
+["AD","ANDORRA"],
+["AO","ANGOLA"],
+["AI","ANGUILLA"],
+["AQ","ANTARCTICA"],
+["AG","ANTIGUA AND BARBUDA"],
+["AR","ARGENTINA"],
+["AM","ARMENIA"],
+["AW","ARUBA"],
+["AU","AUSTRALIA"],
+["AT","AUSTRIA"],
+["AZ","AZERBAIJAN"],
+["BS","BAHAMAS"],
+["BH","BAHRAIN"],
+["BD","BANGLADESH"],
+["BB","BARBADOS"],
+["BY","BELARUS"],
+["BE","BELGIUM"],
+["BZ","BELIZE"],
+["BJ","BENIN"],
+["BM","BERMUDA"],
+["BT","BHUTAN"],
+["BO","BOLIVIA, PLURINATIONAL STATE OF"],
+["BA","BOSNIA AND HERZEGOVINA"],
+["BW","BOTSWANA"],
+["BV","BOUVET ISLAND"],
+["BR","BRAZIL"],
+["IO","BRITISH INDIAN OCEAN TERRITORY"],
+["BN","BRUNEI DARUSSALAM"],
+["BG","BULGARIA"],
+["BF","BURKINA FASO"],
+["BI","BURUNDI"],
+["KH","CAMBODIA"],
+["CM","CAMEROON"],
+["CA","CANADA"],
+["CV","CAPE VERDE"],
+["KY","CAYMAN ISLANDS"],
+["CF","CENTRAL AFRICAN REPUBLIC"],
+["TD","CHAD"],
+["CL","CHILE"],
+["CN","CHINA"],
+["CX","CHRISTMAS ISLAND"],
+["CC","COCOS (KEELING) ISLANDS"],
+["CO","COLOMBIA"],
+["KM","COMOROS"],
+["CG","CONGO"],
+["CD","CONGO, THE DEMOCRATIC REPUBLIC OF THE"],
+["CK","COOK ISLANDS"],
+["CR","COSTA RICA"],
+["CI","CÔTE D'IVOIRE"],
+["HR","CROATIA"],
+["CU","CUBA"],
+["CY","CYPRUS"],
+["CZ","CZECH REPUBLIC"],
+["DK","DENMARK"],
+["DJ","DJIBOUTI"],
+["DM","DOMINICA"],
+["DO","DOMINICAN REPUBLIC"],
+["EC","ECUADOR"],
+["EG","EGYPT"],
+["SV","EL SALVADOR"],
+["GQ","EQUATORIAL GUINEA"],
+["ER","ERITREA"],
+["EE","ESTONIA"],
+["ET","ETHIOPIA"],
+["FK","FALKLAND ISLANDS (MALVINAS)"],
+["FO","FAROE ISLANDS"],
+["FJ","FIJI"],
+["FI","FINLAND"],
+["FR","FRANCE"],
+["GF","FRENCH GUIANA"],
+["PF","FRENCH POLYNESIA"],
+["TF","FRENCH SOUTHERN TERRITORIES"],
+["GA","GABON"],
+["GM","GAMBIA"],
+["GE","GEORGIA"],
+["DE","GERMANY"],
+["GH","GHANA"],
+["GI","GIBRALTAR"],
+["GR","GREECE"],
+["GL","GREENLAND"],
+["GD","GRENADA"],
+["GP","GUADELOUPE"],
+["GU","GUAM"],
+["GT","GUATEMALA"],
+["GG","GUERNSEY"],
+["GN","GUINEA"],
+["GW","GUINEA-BISSAU"],
+["GY","GUYANA"],
+["HT","HAITI"],
+["HM","HEARD ISLAND AND MCDONALD ISLANDS"],
+["VA","HOLY SEE (VATICAN CITY STATE)"],
+["HN","HONDURAS"],
+["HK","HONG KONG"],
+["HU","HUNGARY"],
+["IS","ICELAND"],
+["IN","INDIA"],
+["ID","INDONESIA"],
+["IR","IRAN, ISLAMIC REPUBLIC OF"],
+["IQ","IRAQ"],
+["IE","IRELAND"],
+["IM","ISLE OF MAN"],
+["IL","ISRAEL"],
+["IT","ITALY"],
+["JM","JAMAICA"],
+["JP","JAPAN"],
+["JE","JERSEY"],
+["JO","JORDAN"],
+["KZ","KAZAKHSTAN"],
+["KE","KENYA"],
+["KI","KIRIBATI"],
+["KP","KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF"],
+["KR","KOREA, REPUBLIC OF"],
+["KW","KUWAIT"],
+["KG","KYRGYZSTAN"],
+["LA","LAO PEOPLE'S DEMOCRATIC REPUBLIC"],
+["LV","LATVIA"],
+["LB","LEBANON"],
+["LS","LESOTHO"],
+["LR","LIBERIA"],
+["LY","LIBYAN ARAB JAMAHIRIYA"],
+["LI","LIECHTENSTEIN"],
+["LT","LITHUANIA"],
+["LU","LUXEMBOURG"],
+["MO","MACAO"],
+["MK","MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF"],
+["MG","MADAGASCAR"],
+["MW","MALAWI"],
+["MY","MALAYSIA"],
+["MV","MALDIVES"],
+["ML","MALI"],
+["MT","MALTA"],
+["MH","MARSHALL ISLANDS"],
+["MQ","MARTINIQUE"],
+["MR","MAURITANIA"],
+["MU","MAURITIUS"],
+["YT","MAYOTTE"],
+["MX","MEXICO"],
+["FM","MICRONESIA, FEDERATED STATES OF"],
+["MD","MOLDOVA, REPUBLIC OF"],
+["MC","MONACO"],
+["MN","MONGOLIA"],
+["ME","MONTENEGRO"],
+["MS","MONTSERRAT"],
+["MA","MOROCCO"],
+["MZ","MOZAMBIQUE"],
+["MM","MYANMAR"],
+["NA","NAMIBIA"],
+["NR","NAURU"],
+["NP","NEPAL"],
+["NL","NETHERLANDS"],
+["AN","NETHERLANDS ANTILLES"],
+["NC","NEW CALEDONIA"],
+["NZ","NEW ZEALAND"],
+["NI","NICARAGUA"],
+["NE","NIGER"],
+["NG","NIGERIA"],
+["NU","NIUE"],
+["NF","NORFOLK ISLAND"],
+["MP","NORTHERN MARIANA ISLANDS"],
+["NO","NORWAY"],
+["OM","OMAN"],
+["PK","PAKISTAN"],
+["PW","PALAU"],
+["PS","PALESTINIAN TERRITORY, OCCUPIED"],
+["PA","PANAMA"],
+["PG","PAPUA NEW GUINEA"],
+["PY","PARAGUAY"],
+["PE","PERU"],
+["PH","PHILIPPINES"],
+["PN","PITCAIRN"],
+["PL","POLAND"],
+["PT","PORTUGAL"],
+["PR","PUERTO RICO"],
+["QA","QATAR"],
+["RE","RÉUNION"],
+["RO","ROMANIA"],
+["RU","RUSSIAN FEDERATION"],
+["RW","RWANDA"],
+["BL","SAINT BARTHÉLEMY"],
+["SH","SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA"],
+["KN","SAINT KITTS AND NEVIS"],
+["LC","SAINT LUCIA"],
+["MF","SAINT MARTIN"],
+["PM","SAINT PIERRE AND MIQUELON"],
+["VC","SAINT VINCENT AND THE GRENADINES"],
+["WS","SAMOA"],
+["SM","SAN MARINO"],
+["ST","SAO TOME AND PRINCIPE"],
+["SA","SAUDI ARABIA"],
+["SN","SENEGAL"],
+["RS","SERBIA"],
+["SC","SEYCHELLES"],
+["SL","SIERRA LEONE"],
+["SG","SINGAPORE"],
+["SK","SLOVAKIA"],
+["SI","SLOVENIA"],
+["SB","SOLOMON ISLANDS"],
+["SO","SOMALIA"],
+["ZA","SOUTH AFRICA"],
+["GS","SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS"],
+["ES","SPAIN"],
+["LK","SRI LANKA"],
+["SD","SUDAN"],
+["SR","SURINAME"],
+["SJ","SVALBARD AND JAN MAYEN"],
+["SZ","SWAZILAND"],
+["SE","SWEDEN"],
+["CH","SWITZERLAND"],
+["SY","SYRIAN ARAB REPUBLIC"],
+["TW","TAIWAN, PROVINCE OF CHINA"],
+["TJ","TAJIKISTAN"],
+["TZ","TANZANIA, UNITED REPUBLIC OF"],
+["TH","THAILAND"],
+["TL","TIMOR-LESTE"],
+["TG","TOGO"],
+["TK","TOKELAU"],
+["TO","TONGA"],
+["TT","TRINIDAD AND TOBAGO"],
+["TN","TUNISIA"],
+["TR","TURKEY"],
+["TM","TURKMENISTAN"],
+["TC","TURKS AND CAICOS ISLANDS"],
+["TV","TUVALU"],
+["UG","UGANDA"],
+["UA","UKRAINE"],
+["AE","UNITED ARAB EMIRATES"],
+["GB","UNITED KINGDOM"],
+["US","UNITED STATES"],
+["UM","UNITED STATES MINOR OUTLYING ISLANDS"],
+["UY","URUGUAY"],
+["UZ","UZBEKISTAN"],
+["VU","VANUATU"],
+["VE","VENEZUELA, BOLIVARIAN REPUBLIC OF"],
+["VN","VIET NAM"],
+["VG","VIRGIN ISLANDS, BRITISH"],
+["VI","VIRGIN ISLANDS, U.S."],
+["WF","WALLIS AND FUTUNA"],
+["EH","WESTERN SAHARA"],
+["YE","YEMEN"],
+["ZM","ZAMBIA"],
+["ZW","ZIMBABWE"],
+);
+
+package NetSNMP::Term;
+# gratefully taken from Wayne Thompson's Term::Complete
+# if newer CORE modules could be used this could be removed
+our($complete, $exit, $done, $kill, $erase1, $erase2, $tty_raw_noecho, $tty_restore, $stty, $tty_safe_restore);
+our($tty_saved_state) = '';
+CONFIG: {
+ $exit = "\003";
+ $done = "\004";
+ $kill = "\025";
+ $erase1 = "\177";
+ $erase2 = "\010";
+ foreach my $s (qw(/bin/stty /usr/bin/stty)) {
+ if (-x $s) {
+ $tty_raw_noecho = "$s raw -echo";
+ $tty_restore = "$s -raw echo";
+ $tty_safe_restore = $tty_restore;
+ $stty = $s;
+ last;
+ }
+ }
+}
+
+sub Complete {
+ my($prompt, $dflt, $help, $match, @cmp_lst);
+ my (%help, $cmp, $test, $l, @match);
+ my ($return, $r, $exitting) = ("", 0, 0);
+ my $tab;
+
+ $prompt = shift;
+ $dflt = shift;
+ $help = shift;
+ $match = shift;
+
+ if (ref $_[0] and ref($_[0][0])) {
+ @cmp_lst = @{$_[0]};
+ } else {
+ @cmp_lst = @_;
+ }
+ @cmp_lst = map {if (ref($_)) { $help{$_->[0]}=$_->[1]; $_->[0]} else {$_;}} @cmp_lst;
+
+ # Attempt to save the current stty state, to be restored later
+ if (defined $stty && defined $tty_saved_state && $tty_saved_state eq '') {
+ $tty_saved_state = qx($stty -g 2>/dev/null);
+ if ($?) {
+ # stty -g not supported
+ $tty_saved_state = undef;
+ } else {
+ $tty_saved_state =~ s/\s+$//g;
+ $tty_restore = qq($stty "$tty_saved_state" 2>/dev/null);
+ }
+ }
+ system $tty_raw_noecho if defined $tty_raw_noecho;
+ LOOP: {
+ local $_;
+ print($prompt, $return);
+ while (($_ = getc(STDIN)) ne "\r") {
+ CASE: {
+ # (TAB) attempt completion
+ $_ eq "\t" && do {
+ if ($tab) {
+ print(join("\r\n", '', map {exists $help{$_} ? sprintf("\t%-10.10s - %s", $_, $help{$_}) :
+ "\t$_"} grep(/^\Q$return/, @cmp_lst)), "\r\n");
+ $tab--;
+ redo LOOP;
+ }
+ @match = grep(/^\Q$return/, @cmp_lst);
+ unless ($#match < 0) {
+ $l = length($test = shift(@match));
+ foreach $cmp (@match) {
+ until (substr($cmp, 0, $l) eq substr($test, 0, $l)) {
+ $l--;
+ }
+ }
+ print("\a");
+ print($test = substr($test, $r, $l - $r));
+ $r = length($return .= $test);
+ }
+ $tab++;
+ last CASE;
+ };
+
+ # (^C) exit
+ $_ eq $exit && do {
+ print("\r\naborting application...\r\n");
+ $exitting++;
+ undef $return;
+ last LOOP;
+ };
+
+ # (^D) done
+ $_ eq $done && do {
+ undef $return;
+ last LOOP;
+ };
+
+ # (?) show help if available
+ $_ eq '?' && do {
+ print("\r\n$help\r\n");
+ if (exists $help{$return}) {
+ print("\t$return - $help{$return}\r\n");
+ } else {
+ print(join("\r\n", map {exists $help{$_} ? "\t$_ - $help{$_}" :
+ "\t$_"} grep(/^\Q$return/, @cmp_lst)), "\r\n");
+ }
+ redo LOOP;
+ };
+
+ # (^U) kill
+ $_ eq $kill && do {
+ if ($r) {
+ $r = 0;
+ $return = "";
+ print("\r\n");
+ redo LOOP;
+ }
+ last CASE;
+ };
+
+ # (DEL) || (BS) erase
+ ($_ eq $erase1 || $_ eq $erase2) && do {
+ if ($r) {
+ print("\b \b");
+ chop($return);
+ $r--;
+ }
+ last CASE;
+ };
+
+ # printable char
+ ord >= 32 && do {
+ $return .= $_;
+ $r++;
+ print;
+ last CASE;
+ };
+ }
+ }
+
+ if (defined $return) {
+ if (length($return) == 0 or $return eq $dflt) {
+ $return = $dflt;
+ } elsif ($match == $NetSNMP::Cert::MATCH and scalar(grep {/^$return$/} @cmp_lst) != 1 or
+ $match == $NetSNMP::Cert::PREMATCH and scalar(grep {$return=~/$_/} @cmp_lst) != 1) {
+ $r = 0;
+ $return = "";
+ print("\r\nChoose a valid option, or ^D to exit\r\n");
+ redo LOOP;
+ }
+ }
+ }
+ DONE:
+ # system $tty_restore if defined $tty_restore;
+ if (defined $tty_saved_state && defined $tty_restore && defined $tty_safe_restore) {
+ system $tty_restore;
+ if ($?) {
+ # tty_restore caused error
+ system $tty_safe_restore;
+ }
+ }
+
+ exit(1) if $exitting;
+ print("\n");
+ return $return;
+}
+
+package NetSNMP::Cert;
+
+our $VERSION = '0.2.9';
+
+our $PROG = ::basename($0);
+our $DEBUG = 0;
+our $OK = 0;
+our $ERR = -1;
+
+# user input param
+our $MATCH = 1;
+our $PREMATCH = 2;
+
+# Load LWP if possible to import cert from URL
+eval('use LWP::UserAgent;');
+our $haveUserAgent = ($@ ? 0 : 1);
+
+# required executables
+our $OPENSSL = $ENV{NET_SNMP_CRT_OPENSSL} || 'openssl';
+our $CFGTOOL = $ENV{NET_SNMP_CRT_CFGTOOL} || 'net-snmp-config';
+
+# default app config file
+our $CFGFILE = $ENV{NET_SNMP_CRT_CFGFILE} || 'net-snmp-cert.conf';
+
+# default OpenSSL config files
+our $SSLCFGIN = $ENV{NET_SNMP_CRT_SSLCFGIN} || 'openssl.in';
+our $SSLCFGOUT = $ENV{NET_SNMP_CRT_SSLCFGIN} || '.openssl.conf';
+our $SSLCFG = $ENV{NET_SNMP_CRT_SSLCFG};
+
+# default cmd logs
+our $OUTLOG = $ENV{NET_SNMP_CRT_OUTLOG} || '.cmd.out.log';
+our $ERRLOG = $ENV{NET_SNMP_CRT_ERRLOG} || '.cmd.err.log';
+
+# default cert dirs
+our $TLSDIR = $ENV{NET_SNMP_CRT_TLSDIR} || 'tls';
+our $CRTDIR = $ENV{NET_SNMP_CRT_CRTDIR} || 'certs';
+our $NEWCRTDIR = $ENV{NET_SNMP_CRT_NEWCRTDIR}|| 'newcerts';
+our $CADIR = $ENV{NET_SNMP_CRT_CADIR} || 'ca-certs';
+our $PRIVDIR = $ENV{NET_SNMP_CRT_PRIVDIR} || 'private';
+
+our $SERIAL = $ENV{NET_SNMP_CRT_SERIAL} || '.serial';
+our $INDEX = $ENV{NET_SNMP_CRT_INDEX} || '.index';
+
+our $DEFCADAYS = $ENV{NET_SNMP_CRT_DEFCADAYS} || 1825;
+our $DEFCRTDAYS = $ENV{NET_SNMP_CRT_DEFCRTDAYS}|| 365;
+
+our @TLSDIRS = ($CRTDIR, $NEWCRTDIR, $CADIR, $PRIVDIR);
+
+our @CRTSUFFIXES = qw(.pem .csr .der .crt);
+
+our @X509FMTS = qw(text modulus serial subject_hash issuer_hash hash subject
+ purpose issuer startdate enddate dates fingerprint C);
+
+sub dprint {
+ my $str = shift;
+ my $opts = shift;
+ my $dlevel = shift || 1;
+ my $flag = (defined $opts ? int($opts->{'debug'} >= $dlevel) : 1);
+ my ($pkg, $file, $line, $sub) = caller(1);
+ my ($pkg0, $file0, $line0) = caller(0);
+ print("${sub}():$line0: $str") if $flag;
+}
+
+sub dwarn {
+ my $str = shift;
+ my $opts = shift;
+ my $dlevel = shift || 1;
+ my $flag = (defined $opts ? $opts->{'debug'} >= $dlevel : 1);
+ my ($pkg, $file, $line, $sub) = caller(1);
+ my ($pkg0, $file0, $line0) = caller(0);
+ warn("${sub}():$line0: $str") if $flag;
+}
+
+sub vprint {
+ my $str = shift;
+ my $opts = shift;
+ my $flag = (defined $opts ? $opts->{'verbose'} : 1);
+ my $debug = (defined $opts ? $opts->{'debug'} : 1);
+ my ($pkg, $file, $line, $sub) = caller(1);
+ my ($pkg0, $file0, $line0) = caller(0);
+ $str = ($debug ? "${sub}():$line0: $str" : "$str");
+ print("$str") if $flag;
+}
+
+sub vwarn {
+ my $str = shift;
+ my $opts = shift;
+ my $flag = (defined $opts ? $opts->{'verbose'} : 1);
+ my ($pkg, $file, $line, $sub) = caller(1);
+ my ($pkg0, $file0, $line0) = caller(0);
+ warn("${sub}():$line0: $str") if $flag;
+}
+
+sub rsystem {
+ my $cmd = shift;
+ my $flag = shift;
+
+ # if not running as root try to use sudo
+ if ($>) {
+ $cmd = "sudo $flag $cmd";
+ } else {
+ if ($flag =~ /-b/) {
+ $cmd = "$cmd \&";
+ }
+ }
+ die("cmd failed($!): $cmd\n") if system("$cmd");
+}
+
+sub usystem {
+ my $cmd = shift;
+ my $opts = shift;
+ my $ret;
+
+ unlink $NetSNMP::Cert::OUTLOG if -e $NetSNMP::Cert::OUTLOG;
+ unlink $NetSNMP::Cert::ERRLOG if -e $NetSNMP::Cert::ERRLOG;
+
+ $ret = system("$cmd");
+
+ if ($ret) {
+ if ($opts->{'verbose'}) {
+ system("cat $NetSNMP::Cert::OUTLOG") if -r $NetSNMP::Cert::OUTLOG;
+ system("cat $NetSNMP::Cert::ERRLOG") if -r $NetSNMP::Cert::ERRLOG;
+ }
+ die("cmd failed($!): $cmd\n");
+ }
+}
+
+sub home_dir {
+ my $cdir = ::getcwd();
+
+ chdir("~");
+ my $dir = ::getcwd();
+ chdir($cdir);
+
+ return $dir;
+}
+
+sub find_bin_dir {
+ # This code finds the path to the currently invoked program and
+ my $dir;
+
+ $0 =~ m%^(([^/]*)(.*)/)([^/]+)$%;
+ if ($1) {
+ if ($2) {
+ # Invoked using a relative path. CD there and use pwd.
+ $dir = `cd $1 && pwd`;
+ chomp $dir;
+ } else {
+ # Invoked using an absolute path; that's it!
+ $dir = $3;
+ }
+ } else {
+ # No path. Look in PATH for the first instance.
+ foreach my $p (split /:/, $ENV{PATH}) {
+ $p ||= '.';
+ -x "$p/$4" or next;
+ $dir = $p;
+ }
+ }
+ $dir or die "Cannot locate program '$0'!";
+}
+
+sub fq_rel_path {
+ my $path = shift;
+ my $cwd = ::getcwd();
+ my $rdir = shift || $cwd;
+
+ chdir($rdir) or die("can't change directory: $rdir");
+ # get fully qualified path
+ if ($path !~ m|^/|) {
+ my $pwd = `pwd`; chomp $pwd;
+ $path = "$pwd/$path";
+ $path = ::realpath($path) if -e $path;
+ }
+ chdir($cwd) or die("can't change directory: $cwd");
+
+ return $path;
+}
+
+sub dir_empty
+ {
+ my $path = shift;
+ opendir(DIR, $path);
+ foreach (readdir(DIR)) {
+ next if /^\.\.?$/;
+ closedir(DIR);
+ return 0;
+ }
+ closedir(DIR);
+ return 1;
+ }
+
+sub make_dirs {
+ my $opts = shift;
+ my $dir = shift;
+ my $mode = shift;
+ my @dirs = @_;
+
+ my $wd = ::getcwd();
+
+ NetSNMP::Cert::dprint("make dirs [$dir:@dirs] from $wd\n", $opts);
+
+ ::mkpath($dir, $opts->{'debug'}, $mode) or die("error - can't make $dir")
+ if defined $dir and not -d $dir;
+
+ foreach my $subdir (@dirs) {
+ my $d = "$subdir";
+ $d = "$dir/$d" if defined $dir;
+ NetSNMP::Cert::dprint("making directory: $d\n", $opts) unless -d $d;
+ mkdir($d, $mode) or die("error - can't make $d") unless -d $d;
+ }
+}
+
+sub is_url {
+ my $url = shift;
+
+ return $url =~ /^(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?$/;
+}
+
+sub in_set {
+ my $elem = shift;
+ my $set = shift;
+ for my $e (eval($set)) {
+ return 1 if $e == $elem;
+ }
+ return 0;
+}
+
+sub in_arr {
+ my $elem = shift;
+ my $arr = shift;
+ for my $e (@{$arr}) {
+ return 1 if $e eq $elem;
+ }
+ return 0;
+}
+
+sub map_bool {
+ my $val = shift;
+
+ return 1 if $val =~ /^true$/i;
+ return 0 if $val =~ /^false$/i;
+ return $val;
+}
+
+sub version {
+ my $ret = (@_ ? shift : 1);
+ print "$NetSNMP::Cert::PROG: $NetSNMP::Cert::VERSION\n";
+ exit($ret);
+}
+
+sub GetOptsFromArray {
+ my $args = shift;
+ local *ARGV;
+ @ARGV = @$args;
+ my $ret = ::GetOptions(@_);
+ @$args = @ARGV; # GetOptions strips out the ones it handles and leaves others
+ return $ret;
+}
+sub pull_cmd {
+ my $args = shift;
+ my $cmd;
+
+ foreach (@{$args}) {
+ if (/^(gence?rt|genca|gencsr|signcsr|showcas?|showce?rts?|import)$/) {
+ $cmd = $1;
+ }
+ }
+
+ @{$args} = grep {!/^$cmd$/} @{$args};
+
+ return $cmd;
+}
+
+
+sub usage {
+ my $ret = (@_ ? shift : 1);
+ my $arg = shift;
+
+ print "Command not implmeneted yet: $arg\n" if $ret == 2;
+ print "Unknown: $arg\n" if $ret == 3;
+ print "\n NAME:\n";
+ print " $NetSNMP::Cert::PROG: [$NetSNMP::Cert::VERSION] - ";
+ print "Net-SNMP Certificate Management Tool\n";
+ print "\n DESCRIPTION:\n";
+ print " net-snmp-cert creates, signs, installs and displays X.509\n";
+ print " certificates used in the operation of Net-SNMP/(D)TLS.\n";
+ print "\n SYNOPSIS:\n";
+ print " net-snmp-cert [--help|-?]\n";
+ print " net-snmp-cert [--version|-V]\n";
+ print " net-snmp-cert genca [<flags>] [<dn-opts>] [--with-ca <ca>]\n";
+ print " net-snmp-cert gencert [<flags>] [<dn-opts>] [--with-ca <ca>]\n";
+ print " net-snmp-cert gencsr [<flags>] [<dn-opts>] [--from-crt <crt>]\n";
+ print " net-snmp-cert signcsr [<flags>] [--install] --csr <csr> --with-ca <ca>\n";
+ print " net-snmp-cert showca [<flags>] [<format-opts>] [<file>|<search-tag>]\n";
+ print " net-snmp-cert showcert [<flags>] [<format-opts>] [<file>|<search-tag>]\n";
+ print " net-snmp-cert import [<flags>] <file|url> [<key>]\n";
+ print "\n COMMANDS:\n";
+ print " genca -- generate a signed CA certificate suitable for signing other\n";
+ print " certificates. default: self-signed unless --with-ca <ca> supplied\n\n";
+ print " gencert -- generate a signed certificate suitable for identification, or\n";
+ print " validation. default: self-signed unless --with-ca <ca> supplied\n\n";
+ print " gencsr -- generate a certificate signing request. will create a new\n";
+ print " key and certificate unless --from-crt <crt> supplied\n\n";
+ print " signcsr -- sign a certificate signing request specified by --csr <csr>\n";
+ print " with the CA certificate specified by --with-ca <ca>\n";
+ print " import -- import an identity or CA certificate, optionally import <key>\n";
+ print " if an URL is passed, will attempt to import certificate from site\n";
+ print " showca,\n";
+ print " showcert -- show CA or identity certificate(s). may pass fully qualified\n";
+ print " file or directory name, or a search-tag which prefix matches\n";
+ print " installed CA or identity certificate file name(s)\n";
+ print " see FORMAT OPTIONS to specify output format\n\n";
+ print "\n FLAGS:\n";
+ print " -?, --help -- show this text and exit\n";
+ print " -V, --version -- show version string and exit\n";
+ print " -D, --debug -- raise debug level (-D -D for higher level)\n";
+ print " -F, --force -- force overwrite of existing output files\n";
+ print " -I, --nointeractive -- non-interactive run (default interactive)\n";
+ print " -Q, --noverbose -- non-verbose output (default verbose)\n";
+ print " -C, --cfgdir <dir> -- configuration dir (see man(5) snmp_config)\n";
+ print " -T, --tlsdir <dir> -- root for cert storage (default <cfgdir>/tls)\n";
+ print " -f, --cfgfile <file> -- config (default <cfgdir>/net-snmp-cert.conf)\n";
+ print " -i, --identity <id> -- identity to use from config\n";
+ print " -t, --tag <tag> -- application tag (default 'snmp')\n";
+ print " --<cfg-param>[=<val>] -- additional config params\n";
+ print "\n CERTIFICATE OPTIONS (<cert-opts>):\n";
+ print " -a, --with-ca <ca> -- CA certificate used to sign request\n";
+ print " -A, --ca <ca> -- explicit output CA certificate\n";
+ print " -r, --csr <csr> -- certificate signing request\n";
+ print " -x, --from-crt <crt> -- input certificate for current operation\n";
+ print " -X, --crt <crt> -- explicit output certificate\n";
+ print " -y, --install -- install result in local repository\n";
+ print "\n DISTINGUISHED NAME OPTIONS (<dn-opts>):\n";
+ print " -e, --email <email> -- email name\n";
+ print " -h, --host <host> -- DNS host name, or IP address\n";
+ print " -d, --days <days> -- number of days certificate is valid\n";
+ print " -n, --cn <cn> -- common name (CN)\n";
+ print " -o, --org <org> -- organiztion name\n";
+ print " -u, --unit <unit> -- organiztion unit name\n";
+ print " -c, --country <country> -- country code (e.g., US)\n";
+ print " -p, --province <province> -- province name (synomynous w/ state)\n";
+ print " -p, --state <state> -- state name (synomynous w/ province)\n";
+ print " -l, --locality <locality> -- locality name (e.g, town)\n";
+ print " -s, --san <san> -- subjectAltName, repeat for each <san>\n";
+ print " -- <san> value format (<FMT>:<VAL>):\n";
+ print " -- dirName:/usr/share/snmp/tls\n";
+ print " -- DNS:net-snmp.org\n";
+ print " -- email:admin\@net-snmp.org\n";
+ print " -- IP:192.168.1.1\n";
+ print " -- RID:1.1.3.6\n";
+ print " -- URI:http://net-snmp.org\n";
+ print "\n FORMAT OPTIONS (<format-opts>): \n";
+ print " --brief -- minimized output (values only where applicable)\n";
+ print " --text -- full text description\n";
+ print " --subject -- subject description\n";
+ print " --subject_hash -- hash of subject for indexing\n";
+ print " --issuer -- issuer description\n";
+ print " --issuer_hash -- hash of issuer for indexing\n";
+ print " --fingerprint -- SHA1 digest of DER\n";
+ print " --serial -- serial number\n";
+ print " --modulus -- modulus of the public key\n";
+ print " --dates -- start and end dates\n";
+ print " --purpose -- displays allowed uses\n";
+ print " --C -- C code description\n";
+ print "\n EXAMPLES: \n";
+ print " net-snmp-cert genca --cn ca-net-snmp.org --days 1000\n";
+ print " net-snmp-cert genca -f .snmp/net-snmp-cert.conf -i nocadm -I\n";
+ print " net-snmp-cert gencert -t snmpd --cn host.net-snmp.org\n";
+ print " net-snmp-cert gencsr -t snmpapp\n";
+ print " net-snmp-cert signcsr --csr snmpapp --with-ca ca-net-snmp.org\n";
+ print " net-snmp-cert showcerts --subject --issuer --dates snmpapp\n";
+ print " net-snmp-cert showcas --fingerprint ca-net-snmp.org --brief\n";
+ print " net-snmp-cert import ca-third-party.crt\n";
+ print " net-snmp-cert import signed-request.crt signed-request.key\n\n";
+
+ exit $ret;
+}
+
+sub set_default {
+ my $opts = shift;
+ my $config = shift;
+ my $field = shift;
+ my $val = shift;
+
+ if (not defined $opts->{$field}) {
+ my $cval = $config->inherit($field);
+ $opts->{$field} = (defined $cval ? $cval : $val);
+ }
+}
+
+sub cfg_path {
+ my $path;
+
+ $path = "/etc/snmp:/usr/share/snmp:/usr/lib/snmp:/var/lib/net-snmp";
+ return (wantarray ? split(':', $path) : $path);
+}
+
+sub find_cfgfile {
+ my $dir = shift;
+ my $file = shift;
+
+ if (defined $dir and -d $dir and
+ defined $file and $file !~ /^[\.\/]/) {
+ return fq_rel_path("$dir/$file") if -f "$dir/$file";
+ }
+
+ if (defined $file) {
+ if (-f $file) {
+ return fq_rel_path($file);
+ } else {
+ return $file; # file is not found, complain later
+ }
+ }
+
+ my @path = cfg_path();
+ unshift(@path, $dir) if defined $dir;
+ while (@path) {
+ my $p = pop(@path);
+ next if $p eq '/var/lib/snmp';
+ if (-r "$p/$NetSNMP::Cert::CFGFILE") {
+ return fq_rel_path("$p/$NetSNMP::Cert::CFGFILE");
+ }
+ }
+
+ return $file;
+}
+
+sub find_cfgdir {
+ my $dir = shift;
+ my $file = shift;
+
+ if (defined $dir) {
+ $dir = NetSNMP::Cert::fq_rel_path($dir);
+ return $dir;
+ }
+
+ if (defined $file and -f $file) {
+ $dir = ::dirname($file);
+ return NetSNMP::Cert::fq_rel_path($dir);
+ } else {
+ my @path = cfg_path();
+ # search first for writeable tls dir
+ # for root search top down, for user bottom up
+ while (@path) {
+ $dir = ($> ? pop(@path): shift(@path));
+ next if $dir eq '/var/lib/snmp';
+ return $dir if -d "$dir/$NetSNMP::Cert::TLSDIR" and -w "$dir/$NetSNMP::Cert::TLSDIR";
+ }
+ @path = cfg_path();
+ # if no tls dir found, pick first writable config dir
+ # for root search top down, for user bottom up
+ while (@path) {
+ $dir = ($> ? pop(@path): shift(@path));
+ next if $dir eq '/var/lib/snmp';
+ return $dir if -d $dir and -w $dir;
+ }
+ my $home = $ENV{HOME} || die "Unable to determine home directory: set \$HOME\n";
+ return ($> ? "$home/.snmp" : "/usr/share/snmp"); # XXX hack - no dirs existed or were writable
+ }
+ # this should never happen
+ return undef;
+}
+
+sub find_certs {
+ my $opts = shift;
+ my $dir = shift || 'certs';
+ my $targ = shift;
+ my $suffix_regex;
+ my $cwd = ::getcwd();
+
+ if ($dir eq 'csrs') {
+ $dir = $NetSNMP::Cert::NEWCRTDIR;
+ $suffix_regex = '\.csr|\.pem';
+ } else {
+ $suffix_regex = '\.crt|\.pem';
+ }
+
+ NetSNMP::Cert::dprint("find_certs(1:$cwd):$dir:$targ:$suffix_regex\n", $opts);
+
+ if ($targ =~ /\.?\//) {
+ # see if targ could be file - calc orig. rel. path
+ my $arg = NetSNMP::Cert::fq_rel_path($targ, $opts->{'rdir'});
+ NetSNMP::Cert::dprint("find_certs(2):$dir:$targ:$arg\n", $opts);
+ # targ is a file name - use it
+ return (wantarray ? ($arg) : $arg) if -f $arg;
+ # else mark as dir if it is
+ $targ = "$arg" if -d $arg;
+ }
+
+ $targ =~ s/\/*$/\// if -d $targ;
+
+ NetSNMP::Cert::dprint("find_certs(3):$dir:$targ\n", $opts);
+
+ # find certs in targ if a dir (in tlsdir, or relative)
+ $dir = $1 if $targ =~ s/^(\S+)\/([^\/\s]*)$/$2/ and -d $1;
+
+ NetSNMP::Cert::dprint("find_certs(4):${dir}:$targ:$cwd\n", $opts);
+
+ my @certs;
+ my $glob = "$dir/$targ";
+ foreach (<$dir/*>) {
+ NetSNMP::Cert::dprint("checking($dir:$targ): $_\n", $opts);
+ next unless /^$dir\/$targ(.*$suffix_regex)?$/;
+ # return exact match if not wantarray()
+ return $_ if (not wantarray()) and /^$dir\/$targ($suffix_regex)?$/;
+ NetSNMP::Cert::dprint("pushing: $_\n", $opts);
+ push(@certs, $_);
+ }
+
+ return (wantarray ? @certs : $certs[0]);
+}
+
+sub check_output_file {
+ my $opts = shift;
+ my $file = shift;
+ my $interactive = shift;
+ my $force = shift;
+ my $continue = 1;
+
+ if (-w $file) {
+ if ($interactive and not $force) {
+ print "Output file ($file) exists; will be overwritten\nContinue [y/N]: ";
+ $continue = <STDIN>; chomp $continue;
+ $continue = 0 if $continue =~ /^\s*$|^no?\s*$/i;
+ } else {
+ if ($force) {
+ NetSNMP::Cert::vprint("Output file ($file) exists; overwriting...\n", $opts);
+ } else {
+ NetSNMP::Cert::vprint("Output file ($file) exists; exiting...\n", $opts);
+ $continue = 0;
+ }
+ }
+ } elsif (-e $file) {
+ NetSNMP::Cert::vprint("Output file ($file) not writable; exiting...\n", $opts);
+ $continue = 0;
+ }
+ exit(1) unless $continue;
+}
+
+sub is_server {
+ my $tag = shift;
+ return 1 if $tag eq 'snmpd' or $tag eq 'snmptrapd';
+ return 0;
+}
+
+sub is_ca_cert {
+ my $crt = shift;
+ my $output = `openssl x509 -in '$crt' -text -noout`;
+ return ($output =~ /^\s*CA:TRUE\s*$/m ? 1 : 0);
+}
+
+sub x509_format {
+ my $opts = shift;
+ my $fmt;
+
+ foreach my $f (@NetSNMP::Cert::X509FMTS) {
+ $fmt .= " -$f" if defined $opts->{$f};
+ }
+
+ return $fmt;
+}
+
+sub make_openssl_conf {
+ my $file = shift;
+ return if -r $file;
+
+ open(F, ">$file") or die("could not open $file");
+
+ print F <<'END';
+#
+# Net-SNMP (D)TLS OpenSSL configuration file.
+#
+
+rdir = .
+dir = $ENV::DIR
+RANDFILE = $rdir/.rand
+MD = sha1
+KSIZE = 2048
+CN = net-snmp.org
+EMAIL = admin@net-snmp.org
+COUNTRY = US
+STATE = CA
+LOCALITY = Davis
+ORG = Net-SNMP
+ORG_UNIT = Development
+SAN = email:copy
+DAYS = 365
+CRLDAYS = 30
+
+default_days = $ENV::DAYS # how long to certify for
+default_crl_days= $ENV::CRLDAYS # how long before next CRL
+default_md = $ENV::MD # which md to use.
+
+database = $dir/.index # database index file.
+serial = $dir/.serial # The current serial number
+certs = $rdir/certs # identity certs
+new_certs_dir = $dir/newcerts # default place for new certs.
+ca_certs_dir = $rdir/ca-certs # default place for new certs.
+key_dir = $rdir/private
+
+crl_dir = $dir/crl # crl's
+crlnumber = $dir/.crlnumber # the current crl number
+ # must be commented out to leave V1 CRL
+crl = $crl_dir/crl.pem # The current CRL
+
+preserve = no # keep passed DN ordering
+unique_subject = yes # Set to 'no' to allow creation of
+ # certificates with same subject.
+# Extra OBJECT IDENTIFIER info:
+oid_section = new_oids
+
+[ new_oids ]
+
+# Add new OIDs in here for use by 'ca' and 'req'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+####################################################################
+[ ca ]
+default_ca = CA_default # The default ca section
+
+####################################################################
+[ CA_default ]
+# certificate = $ca_certs_dir/$ENV::TAG.crt # CA certificate so sign with
+name_opt = ca_default # Subject Name options
+cert_opt = ca_default # Certificate field options
+policy = policy_match
+copy_extensions = copy # copy v3 extensions (subjectAltName)
+subjectAltName = copy
+
+# For the CA policy
+[ policy_match ]
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+####################################################################
+[ req ]
+default_bits = $ENV::KSIZE
+default_md = $ENV::MD
+distinguished_name = req_distinguished_name
+string_mask = MASK:0x2002
+req_extensions = v3_req
+x509_extensions = v3_user_create
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = $ENV::COUNTRY
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = $ENV::STATE
+
+localityName = Locality Name (eg, city)
+localityName_default = $ENV::LOCALITY
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = $ENV::ORG
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+organizationalUnitName_default = $ENV::ORG_UNIT
+
+commonName = Common Name (eg, your name or your server\'s hostname)
+commonName_max = 64
+commonName_default = $ENV::CN
+
+emailAddress = Email Address
+emailAddress_max = 64
+emailAddress_default = $ENV::EMAIL
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+basicConstraints = CA:FALSE
+
+# Import the email address and/or specified SANs.
+%[subjectAltName = $ENV::SAN]
+
+[ v3_user_create ]
+
+# Extensions to add to a certificate request
+basicConstraints = CA:FALSE
+#keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# PKIX recommendation.
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+
+# Import the email address and/or specified SANs.
+%[subjectAltName = $ENV::SAN]
+
+[ v3_ca_create ]
+# Extensions to add to a CA certificate
+basicConstraints = CA:TRUE
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate (net-snmp)"
+
+# PKIX recommendation.
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+
+# Import the email address and/or specified SANs.
+%[subjectAltName = $ENV::SAN]
+
+[ v3_ca_sign ]
+# Extensions to add when 'ca' signs a request.
+basicConstraints = CA:FALSE
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate (net-snmp)"
+
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+
+[ v3_ca_sign_ca ]
+# Extensions to add when 'ca' signs a ca request.
+basicConstraints = CA:TRUE
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate (net-snmp)"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+
+END
+
+}
+
+
+package NetSNMP::Cert::Obj;
+
+sub new {
+ my $class = shift;
+ my $cfield = shift;
+ my $parent = shift;
+ my $ind = shift;
+ my $this = {};
+ bless($this, $class);
+
+ # initialize hash of keys which are dynamically created or internal
+ $this->{'AUTOKEYS'}{'AUTOKEYS'}++;
+
+ # store a reference to ourselves so our children can find us
+ $this->autoSet('CFIELD', $cfield);
+ $this->autoSet($cfield , $this);
+ $this->autoSet('CHILDREN', []);
+ $this->autoSet('INDEX', $ind) if defined $ind;
+
+ if (defined $parent) {
+ # cache 'APP' in all objs for easy reference
+ $this->autoSet('APP', $parent->inherit('APP'));
+ my $app = $this->{'APP'};
+
+ die("net-snmp-cert: error: no NetSNMP::Cert::App context provided")
+ if not defined $app or not ref $app eq 'NetSNMP::Cert::App';
+ # save children for list traversal
+ push(@{$parent->{'CHILDREN'}}, $this) unless $parent eq $this;
+ } else {
+ # we are the top of the list
+ $parent = $this;
+ }
+ $this->autoSet('PARENT' , $parent);
+
+ return $this;
+}
+
+sub autoSet {
+ my $this = shift;
+ my $key = shift;
+ if (@_) {
+ my $val = shift;
+ $this->{'AUTOKEYS'}{$key}++;
+ $this->{$key} = $val;
+ }
+ return exists $this->{'AUTOKEYS'}{$key};
+}
+
+sub inherit {
+ my $this = shift;
+ my $field = shift;
+ my $id;
+
+ # cmd opts override config settings
+ if (exists $this->{'APP'} and exists $this->{'APP'}{'OPTS'}) {
+ my $opts = $this->{'APP'}{'OPTS'};
+ $id = $opts->{'identity'};
+ return $opts->{$field} if defined $opts->{$field};
+ }
+
+ if (defined $id and exists $this->{'identity'} and
+ exists $this->{'identity'}{$id} and
+ exists $this->{'identity'}{$id}{$field}) {
+ return $this->{'identity'}{$id}{$field};
+ }
+
+ # return our field if we have it
+ return $this->{$field} if defined $this->{$field};
+
+ # terminate recursion at top and return undef if not found
+ return undef if not defined $this->{'PARENT'} or $this->{'PARENT'} eq $this;
+
+ # recurse to parent
+ $this->{'PARENT'}->inherit($field);
+}
+
+sub resolve {
+ my $this = shift;
+ my $opts = $this->inherit('OPTS');
+ my $val = shift;
+
+ NetSNMP::Cert::dprint("resolving: $val\n", $opts);
+
+ $val =~ s/(\$(\w+))/$this->inherit($2) or die("unresolved reference in config: $1")/ge;
+ $val =~ s/(\&\{(.*?)\})/$2/gee;
+
+ NetSNMP::Cert::dprint("resolved: $val\n", $opts);
+
+ return $val
+}
+
+sub delete {
+ my $this = shift;
+ my $opts = $this->inherit('OPTS');
+
+ NetSNMP::Cert::dprint("Obj::delete: ($this) [$this->{CFIELD}]\n", $opts);
+
+ my $parent = $this->{'PARENT'};
+ my @children = @{$this->{'CHILDREN'}};
+
+ foreach my $child (@children) {
+ $child->delete();
+ }
+
+ NetSNMP::Cert::dwarn("Obj: children not freed\n", $opts) if @{$this->{'CHILDREN'}};
+
+ # delete all our self-references
+ delete($this->{$this->{'CFIELD'}}) if exists $this->{'CFIELD'};
+ delete($this->{'PARENT'});
+ delete($this->{'CHILDREN'});
+
+ $parent->disown($this) if defined $parent and ref($parent) =~ /NetSNMP::Cert/;
+}
+
+sub disown {
+ my $this = shift;
+ my $thechild = shift;
+ my $opts = $this->inherit('OPTS');
+ my $ind = 0;
+
+ NetSNMP::Cert::dprint("Obj::disown: ($this) [$this->{CFIELD}] disowning ($thechild) [$thechild->{CFIELD}]\n", $opts);
+
+ foreach my $child (@{$this->{'CHILDREN'}}) {
+ last if $child eq $thechild;
+ $ind++;
+ }
+ if ($ind < @{$this->{'CHILDREN'}}) {
+ splice(@{$this->{'CHILDREN'}}, $ind, 1);
+ } else {
+ NetSNMP::Cert::dwarn("Child ($thechild) not found in object ($this)\n", $opts);
+ }
+}
+
+sub disabled {
+ my $this = shift;
+ return (exists $this->{'disable'} ? $this->{'disable'} : 0);
+}
+
+my %cfg = (
+ 'name' => 1,
+ 'type' => 2,
+ 'id' => 6,
+ );
+
+sub cfgsort {
+ my $self = shift;
+ my $a = shift;
+ my $b = shift;
+ return -1 if exists $cfg{$a} and not exists $cfg{$b};
+ return 1 if exists $cfg{$b} and not exists $cfg{$a};
+ return $cfg{$a} <=> $cfg{$b} if exists $cfg{$a} and exists $cfg{$b};
+ return -1 if !ref($self->{$a}) and ref($self->{$b});
+ return 1 if !ref($self->{$b}) and ref($self->{$a});
+ return -1 if ref($self->{$a}) =~ /NetSNMP::Cert/ and ref($self->{$b}) !~ /NetSNMP::Cert/;
+ return 1 if ref($self->{$b}) =~ /NetSNMP::Cert/ and ref($self->{$a}) !~ /NetSNMP::Cert/;
+ return 0;
+}
+
+sub dump {
+ my $self = shift;
+ my $indent = shift;
+ my $self_str = $self->{'CFIELD'};
+ my @data_keys = grep {!$self->autoSet($_)} keys %$self;
+ my @lines;
+
+ push(@lines, "\n" . ' ' x $indent . "$self_str = {\n") if defined $indent;
+
+ {
+ my $indent = (defined $indent ? $indent + 3 : 0);
+ foreach my $key (sort {cfgsort($self,$NetSNMP::Cert::Obj::a,
+ $NetSNMP::Cert::Obj::b)} (sort @data_keys)) {
+ if (ref($self->{$key}) =~ /NetSNMP::Cert/) {
+ push(@lines, $self->{$key}->dump($indent));
+ } elsif (ref($self->{$key}) =~ /ARRAY/) {
+ push(@lines, "\n") if ref(${$self->{$key}}[0]) !~ /NetSNMP::Cert/;
+ foreach my $elem (@{$self->{$key}}) {
+ if (ref($elem) =~ /NetSNMP::Cert/) {
+ push(@lines, $elem->dump($indent));
+ } else {
+ push(@lines, ' ' x $indent . "$key = $elem\n");
+ }
+ }
+ } else {
+ my $str = $key . (defined $self->{$key} ? " = $self->{$key}\n" : "\n");
+ push(@lines, ' ' x $indent . $str);
+ }
+ }
+ }
+
+ push(@lines, ' ' x $indent . "}; # end $self_str\n") if defined $indent;
+ return @lines;
+}
+
+sub DESTROY {
+ my $this = shift;
+
+ print("Obj::DESTROY $this [", ref $this, "]\n") if $NetSNMP::Cert::DEBUG >= 3;
+}
+
+package NetSNMP::Cert::App;
+use vars qw(@ISA);
+
+@ISA = qw(NetSNMP::Cert::Obj);
+
+sub new {
+ my $class = shift;
+
+ # the app is god, it is its own parent
+ my $this = $class->SUPER::new('APP');
+
+ bless($this, $class);
+
+ # verify required tools or die
+ $this->checkReqs();
+
+ # internal intitialization
+ $this->initOpts();
+
+ # make a new empty config and init (not parsed)
+ $this->{'config'} = new NetSNMP::Cert::Config($this);
+
+ return $this;
+}
+
+sub checkReqs {
+ my $app = shift;
+
+ die("$NetSNMP::Cert::OPENSSL does not exist or is not executable")
+ if system("$NetSNMP::Cert::OPENSSL version > /dev/null 2>&1");
+
+ my $ossl_ver = `$NetSNMP::Cert::OPENSSL version`; chomp $ossl_ver;
+ $ossl_ver =~ s/^OpenSSL\s+([\d\.]+).*$/$1/;
+ my $ossl_min_ver = $NetSNMP::Cert::OPENSSL_MIN_VER;
+
+ die("$NetSNMP::Cert::OPENSSL (v$ossl_ver): must be $ossl_min_ver or later")
+ if ($ossl_ver cmp $ossl_min_ver) < 0;
+
+# die("$NetSNMP::Cert::CFGTOOL not found: please install")
+# if system("$NetSNMP::Cert::CFGTOOL > /dev/null 2>&1");
+}
+
+sub initOpts {
+ my $app = shift;
+ my $opts = {};
+ $app->autoSet('OPTS', $opts);
+
+ # Define directories we need.
+ $opts->{'bindir'} = NetSNMP::Cert::find_bin_dir();
+
+ $opts->{'out'} = "> $NetSNMP::Cert::OUTLOG";
+ $opts->{'err'} = "2> $NetSNMP::Cert::ERRLOG";
+
+ # set up paths for app install and runtime env
+ $ENV{PATH} = "/sbin:/usr/sbin:$ENV{PATH}";
+ $ENV{PATH} = "$opts->{'bindir'}:$ENV{PATH}";
+
+ # default all privs to -rw-------
+ umask(077);
+}
+
+sub init {
+ my $app = shift;
+ my $opts = $app->{'OPTS'};
+ my $config = $app->{'config'};
+ my @args = @_; # pass external args (typically ARGV)
+
+ # parse command line
+ $app->parseArgs(@args);
+
+ # lazy config parsing postponed until here, will not reparse
+ $config->parse();
+
+ # set defaults here if not already set in cmdline or config
+ # guided interactive mode by default
+ NetSNMP::Cert::set_default($opts, $config, 'interactive', 1);
+ # verbose output
+ NetSNMP::Cert::set_default($opts, $config, 'verbose', 1);
+
+ # find tlsdir/subdirs or make it
+ $opts->{'tlsdir'} = $app->createTlsDir();
+}
+
+sub parseArgs {
+ my $app = shift;
+ my $opts = $app->{'OPTS'};
+ my @args = @_;
+
+ NetSNMP::Cert::GetOptsFromArray(\@args, $opts, 'help|?', 'version|V',
+ 'interactive!', 'I', 'verbose!', 'Q', 'force|F',
+ 'cfgfile|f=s', 'cfgdir|C=s', 'tlsdir|tlsDir|T=s',
+ 'tag|t=s', 'identity|i=s', 'debug|D+',);
+
+ NetSNMP::Cert::version(0) if $opts->{'version'};
+
+ NetSNMP::Cert::usage(0) if $opts->{'help'};
+
+ # pull out the cmd - getOpts should leave it untouched for us
+ $opts->{'cmd'} = NetSNMP::Cert::pull_cmd(\@args);
+
+ # save extra args for command specific processing
+ $opts->{'cmdargs'} = [@args];
+
+ # Check debug option first
+ $NetSNMP::Cert::DEBUG = $opts->{'debug'};
+ $opts->{'err'} = $opts->{'out'} = "" if $opts->{'debug'};
+ $opts->{'interactive'} = not $opts->{'I'} if defined $opts->{'I'};
+ $opts->{'verbose'} = not $opts->{'Q'} if defined $opts->{'Q'};
+
+ # search for cfgdir and cfgfile based on opts and confpath
+ $opts->{'cfgdir'} = NetSNMP::Cert::find_cfgdir($opts->{'cfgdir'},
+ $opts->{'cfgfile'});
+ $opts->{'cfgfile'} = NetSNMP::Cert::find_cfgfile($opts->{'cfgdir'},
+ $opts->{'cfgfile'});
+}
+
+sub createTlsDir {
+ my $app = shift;
+ my $opts = $app->{'OPTS'};
+ my $config = $app->{'config'};
+ my $dir;
+ my $file;
+ my $cmd;
+
+ $dir = $config->inherit('tlsDir');
+
+ if (not defined $dir) {
+ my $cfgdir = $opts->{'cfgdir'};
+ die("undefined cfgdir: unable to creat tlsdir: exiting...\n") unless defined $cfgdir;
+ $dir = "$cfgdir/$NetSNMP::Cert::TLSDIR";
+ }
+
+ NetSNMP::Cert::dprint("tlsDir is: $dir\n", $opts);
+ $dir = NetSNMP::Cert::fq_rel_path($dir);
+ NetSNMP::Cert::dprint("tlsDir is: $dir\n", $opts);
+
+ NetSNMP::Cert::dprint("tlsDir not found, creating\n", $opts) unless -d $dir;
+ NetSNMP::Cert::make_dirs($opts, $dir, 0700, @NetSNMP::Cert::TLSDIRS);
+
+ my $ssl_cfg_in = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGIN,$dir);
+
+ # Existing openssl.conf tmpl will be preserved
+ if (-f $ssl_cfg_in) {
+ NetSNMP::Cert::dwarn("OpenSSL template exists ($ssl_cfg_in): preserving...", $opts);
+ }
+
+ if (not -f $ssl_cfg_in) {
+ NetSNMP::Cert::dprint("make_openssl_conf($ssl_cfg_in)", $opts);
+ NetSNMP::Cert::make_openssl_conf($ssl_cfg_in);
+ chmod(0600, $ssl_cfg_in);
+ }
+
+ NetSNMP::Cert::dprint("createTlsDir: done\n", $opts);
+ return $dir;
+}
+
+my @interactive_ops = (['gencert', "Generate a signed certificate"],
+ ['genca', "Generate a CA certificate"],
+ ['gencsr', "Generate a Certificate Signing Request"],
+ ['signcsr', "Sign a Certificate Signing Request"],
+ );
+
+sub run {
+ my $app = shift;
+ my $opts = $app->{'OPTS'};
+ my $cmd = $opts->{'cmd'};
+
+ # must have a command in non-Interactive mode
+ NetSNMP::Cert::usage(3) if !$opts->{'interactive'} and !$opts->{'cmd'};
+
+ # change dir tls dir - the cwd for all commands - save cwd first
+ $opts->{'rdir'} = ::getcwd();
+ chdir($opts->{'tlsdir'}) or die("could'nt change directory: $opts->{tlsdir}");
+ # display context
+ NetSNMP::Cert::dprint("PATH: $ENV{PATH}\n\n", $opts);
+ NetSNMP::Cert::dprint("config file: $opts->{cfgfile}\n", $opts);
+ NetSNMP::Cert::dprint("config dir: $opts->{cfgdir}\n", $opts);
+ NetSNMP::Cert::dprint("tls dir: $opts->{tlsdir}\n", $opts);
+
+ my $cmdstr = join(' ', $cmd, @{$opts->{'cmdargs'}});
+ NetSNMP::Cert::dprint("command: $cmdstr\n", $opts);
+
+ NetSNMP::Cert::GetOptsFromArray(\@{$opts->{'cmdargs'}}, $opts,
+ 'with-ca|a=s', 'ca|A=s','csr|r=s',
+ 'from-crt|x=s', 'crt|X=s', 'days|d=s',
+ 'cn|n=s', 'email|e=s', 'host|h=s',
+ 'san|s=s@', 'org|o=s', 'unit|u=s',
+ 'country|c=s', 'state|province|p=s',
+ 'locality|l=s', 'brief|b',
+ @NetSNMP::Cert::X509FMTS);
+
+ # process extra args; --<cfg-name>[=<val>]
+ $app->processExtraArgs();
+
+ # If in interactive mode - fill missing info by interviewing user
+ if (not defined $cmd or grep {/$cmd/} map {$_->[0]} @interactive_ops) {
+ $app->userInput() if $opts->{interactive};
+ $cmd = $opts->{'cmd'}; # may have changed
+ }
+
+ # resolve input args to filenames
+ $app->resolveCrtArgs();
+
+ # use env. or merge template for OpenSSL config
+ $NetSNMP::Cert::SSLCFG ||= $app->opensslCfg();
+
+ if ($cmd =~ /^genca$/) {
+ NetSNMP::Cert::usage(1) if defined $opts->{'from-crt'} or defined $opts->{'csr'};
+ $app->genCa($opts->{'with-ca'});
+ } elsif ($cmd =~ /^gence?rt$/) {
+ NetSNMP::Cert::usage(1) if defined $opts->{'from-crt'} or defined $opts->{'csr'};
+ $app->genCert($opts->{'with-ca'});
+ } elsif ($cmd =~ /^gencsr$/) {
+ NetSNMP::Cert::usage(1) if defined $opts->{'with-ca'} or defined $opts->{'crt'};
+ $app->genCsr();
+ } elsif ($cmd =~ /^signcsr$/) {
+ NetSNMP::Cert::usage(1) unless defined $opts->{'with-ca'} and defined $opts->{'csr'};
+ $app->signCsr();
+ } elsif ($cmd =~ /^show(ce?rts?)?$/) {
+ $app->show('certs');
+ } elsif ($cmd =~ /^showcas?$/) {
+ $app->show('ca-certs');
+ } elsif ($cmd =~ /^import$/) {
+ $app->import();
+ } else {
+ NetSNMP::Cert::usage();
+ }
+}
+
+sub processExtraArgs {
+ my $app = shift;
+ my $opts = $app->{'OPTS'};
+ my @args;
+
+ NetSNMP::Cert::dprint("processing extra args...\n", $opts);
+
+ while (@{$opts->{'cmdargs'}}) {
+ my $arg = shift(@{$opts->{'cmdargs'}});
+ NetSNMP::Cert::dprint("found: arg --> $arg\n", $opts);
+ if ($arg =~ /^-+(\w+)(?:=(.*?))?\s*$/) {
+ NetSNMP::Cert::dprint("found: arg($1) val($2)\n", $opts);
+ if (exists $opts->{$1}) {
+ NetSNMP::Cert::vwarn("option ($1) already set: overwriting\n", $opts);
+ }
+ $opts->{$1} = (defined $2 ? $2 : 1);
+ } else {
+ push(@args, $arg);
+ }
+ }
+ @{$opts->{'cmdargs'}} = @args;
+}
+
+sub resolveCrtArgs {
+ my $app = shift;
+ my $opts = $app->{'OPTS'};
+
+ # find ca, crt, csr args if present and return fully qualified path
+ if (defined $opts->{'with-ca'}) {
+ my $ca;
+ $ca = NetSNMP::Cert::find_certs($opts, 'ca-certs', $opts->{'with-ca'});
+ die("unable to locate CA certificate ($opts->{'with-ca'})\n") unless -e $ca;
+ die("unable read CA certificate ($opts->{'with-ca'})\n") unless -r $ca;
+ $opts->{'with-ca'} = $ca;
+ }
+
+ if (defined $opts->{'from-crt'}) {
+ my $crt;
+ $crt = NetSNMP::Cert::find_certs($opts, 'certs', $opts->{'from-crt'});
+ die("unable to locate certificate ($opts->{'from-crt'})\n") unless -e $crt;
+ die("unable read certificate ($opts->{'from-crt'})\n") unless -r $crt;
+ $opts->{'from-crt'} = $crt;
+ }
+
+ if (defined $opts->{'csr'}) {
+ my $csr;
+ $csr = NetSNMP::Cert::find_certs($opts, 'csrs', $opts->{'csr'});
+ die("unable to locate CSR certificate ($opts->{csr})\n") unless -e $csr;
+ die("unable read CSR certificate ($opts->{csr})\n") unless -r $csr;
+ $opts->{'csr'} = $csr;
+ }
+}
+
+sub opensslCfg {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $san = $config->inherit('san') || $config->inherit('subjectAltName');
+ my $ssl_cfg_in = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGIN);
+ my $ssl_cfg_out = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGOUT);
+
+ if (not -f $ssl_cfg_in) {
+ NetSNMP::Cert::vwarn("OpenSSL template not found: $ssl_cfg_in\n", $opts);
+ die("no OpenSSL template");
+ }
+
+ open(IN, $ssl_cfg_in) or die("unable to open OpenSSL template: $ssl_cfg_in\n");
+ open(OUT, ">$ssl_cfg_out") or die("unable to open OpenSSL config: $ssl_cfg_out\n");
+
+ print OUT "#######################################################\n";
+ print OUT "##### Warning: Do Not Edit - Generated File #####\n";
+ print OUT "#######################################################\n";
+ while (<IN>) {
+ if ($san) {
+ s/\%\[([^\]]*?)\]/$1/;
+ } else {
+ s/\%\[([^\]]*?)\]//;
+ }
+ print OUT $_;
+ }
+ close(IN);
+ close(OUT);
+
+ return $ssl_cfg_out;
+}
+
+sub opensslEnv {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+
+ my $cn = shift;
+ my $days = shift;
+ my $dir = shift || ".";
+ # XXX - need to handle config'd identity here
+ my $name = $config->inherit("name");
+ my $host = $config->inherit("host");
+ my $email = $config->inherit("email");
+ my $country = $config->inherit("country");
+ my $state = $config->inherit("state");
+ my $locality = $config->inherit("locality");
+ my $org = $config->inherit("org");
+ my $org_unit = $config->inherit("unit") || $config->inherit("orgUnit");
+ my $san;
+ my $san_arr_ref;
+ my $md = $config->inherit("msgDigest");
+ my $ksize = $config->inherit("keySize");
+
+ my $env;
+
+ $env .= " KSIZE=$ksize" if defined $ksize;
+ $env .= " DAYS=$days" if defined $days;
+ $env .= " MD=$md" if defined $md;
+
+ $env .= " DIR='$dir'" if defined $dir;
+
+ $env .= " EMAIL=$email" if defined $email;
+ $env .= " CN='$cn'" if defined $cn;
+ $env .= " ORG='$org'" if defined $org;
+ $env .= " ORG_UNIT='$org_unit'" if defined $org_unit;
+ $env .= " COUNTRY=$country" if defined $country;
+ $env .= " STATE=$state" if defined $state;
+ $env .= " LOCALITY=$locality" if defined $locality;
+
+ $san_arr_ref = $config->inherit('subjectAltName');
+ $san = join('\,\ ', @{$san_arr_ref}) if ref $san_arr_ref;
+ $san_arr_ref = $config->inherit('san');
+ $san .= join('\,\ ', @{$san_arr_ref}) if ref $san_arr_ref;
+ $san =~ s/EMAIL:/email:/g;
+ $env .= " SAN=$san" if defined $san;
+
+ NetSNMP::Cert::dprint("opensslEnv: $env\n", $opts);
+
+ return $env;
+}
+our @san_prefix = (['dirName:', "e.g., dirName:/usr/share/snmp/tls"],
+ ['DNS:', "e.g., DNS:test.net-snmp.org)"],
+ ['email:', "e.g., email:admin\@net-snmp.org"],
+ ['IP:', "e.g., IP:192.168.1.1"],
+ ['RID:', "e.g., RID:1.1.3.6.20"],
+ ['URI:', "e.g., URI:http://www.net-snmp.org"]);
+
+sub userInputDN {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $prompt;
+ my $ret;
+ my $host = $config->inherit("host") || ::hostname();
+ my $email = $config->inherit('email') || getlogin() . "\@$host";
+
+ # get EMAIL
+ $prompt = "Enter Email";
+ $ret = $email;
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "\tEmail Address - (e.g., <name>@<domain>)");
+ $email = $opts->{'email'} = $ret if defined $ret;
+ # get CN
+ $prompt = "Enter Common Name";
+ $ret = ($opts->{'cmd'} eq 'genca' ? "ca-$host" : $email);
+ $ret = $config->inherit('cn') || $config->inherit('commonName') || $ret;
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "\tCommon Name - (e.g., net-snmp.org)");
+ $opts->{'cn'} = $ret if defined $ret;
+ # get ORG
+ $prompt = "Enter Organization";
+ $ret = $config->inherit('org') || 'Net-SNMP';
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "\tOrganization - (e.g., Net-SNMP)");
+ $opts->{'org'} = $ret if defined $ret;
+ # get ORG_UNIT
+ $prompt = "Enter Organizational Unit";
+ $ret = $config->inherit('unit') || 'Development';
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "\tOrganizational Unit - (e.g., Development)");
+ $opts->{'unit'} = $ret if defined $ret;
+ # get COUNTRY
+ $prompt = "Enter Country Code";
+ $ret = $config->inherit('country') || 'US';
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "Country Code, 2 letters (<tab> for options)",
+ $NetSNMP::Cert::MATCH, \@CC);
+ $opts->{'country'} = $ret if defined $ret;
+ # get STATE(Province)
+ $prompt = "Enter State or Province";
+ $ret = $config->inherit('state') || 'CA';
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "\tState or Province - (e.g., CA)");
+ $opts->{'state'} = $ret if defined $ret;
+ # get LOCALITY
+ $prompt = "Enter Locality";
+ $ret = $config->inherit('locality') || 'Davis';
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret, "\tLocality - (e.g., Davis)");
+ $opts->{'locality'} = $ret if defined $ret;
+ # get SAN (loop)
+ if (!$config->{'brief'}) {
+ print "Enter Subject Alt Names. Examples:\n";
+ foreach my $pair (@san_prefix) {
+ printf("\t%-10.10s %s\n", $pair->[0], $pair->[1]);
+ }
+ }
+ do {
+ $ret = 'done';
+ $prompt = "Enter Subject Alt Name (enter 'done' when finished) [$ret]: ";
+ $ret = NetSNMP::Term::Complete ($prompt, $ret,
+ "\tSubject Alt Name - (<type>:<val>)",
+ $NetSNMP::Cert::PREMATCH, \@san_prefix);
+ push(@{$opts->{'san'}}, $ret) if defined $ret and $ret ne 'done';
+ } while (defined $ret and $ret ne 'done');
+}
+
+our @snmp_apps = (['snmp', 'Generic Certificate'],
+ ['snmpapp','Client Certificate'],
+ ['snmpd','Agent Certificate'],
+ ['snmptrapd','Trap-agent Certificate']);
+
+sub userInputTag {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $ret;
+ my $prompt;
+
+ print "Application Tag:\n\tused to name the certificate and dictate its filename\n";
+ print "\tIt may also associate it with a particular application (eg \"snmpd\")\n";
+ print "\tif 'none', a name will be generated from other parameters\n";
+ print "\tenter <tab><tab> for typical options, or enter new name\n";
+ $prompt = "Enter Application Tag";
+ $ret = $config->inherit('tag') || 'none';
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "Application Tag assocaiated with certificate",
+ (not $NetSNMP::Cert::MATCH), \@snmp_apps);
+ $opts->{'tag'} = $ret if defined $ret and $ret ne 'none';
+}
+
+sub userInput {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $prompt;
+ my $ret;
+
+ print "Choose an operation:\n";
+ foreach my $op (@interactive_ops) {
+ print "\t$op->[0]\t- $op->[1]\n";
+ }
+
+ $prompt = "Operation";
+ $ret = $config->inherit('cmd') || $interactive_ops[0][0];
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret,
+ "Certifciate Operation to perform",
+ $NetSNMP::Cert::MATCH, \@interactive_ops);
+
+ $opts->{'cmd'} = $ret;
+
+ if ($ret =~ /^gencert$/) {
+ # get tag
+ $app->userInputTag();
+ # get DN
+ $app->userInputDN();
+ # self-signed/CA-signed(ca-cert)
+ } elsif ($ret =~ /^genca$/) {
+ # get DN
+ $app->userInputDN();
+ } elsif ($ret =~ /^gencsr$/) {
+ # get tag
+ $app->userInputTag();
+ # get DN
+ $app->userInputDN();
+ } elsif ($ret =~ /^signcsr$/) {
+ # get csr
+ $prompt = "Choose Certificate Signing Request";
+ $ret = $config->inherit('csr');
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret);
+ $opts->{'csr'} = $ret if defined $ret;
+ # get ca
+ $prompt = "Choose CA Certificate";
+ $ret = $config->inherit('with-ca');
+ $prompt .= (defined $ret ? " [$ret]: " : ": ");
+ $ret = NetSNMP::Term::Complete($prompt, $ret);
+ $opts->{'with-ca'} = $ret if defined $ret;
+ } else {
+ NetSNMP::Cert::vwarn("aborting operation: exiting...\n", $opts);
+ exit(1);
+ }
+}
+
+sub createCaDir {
+ my $app = shift;
+ my $dir = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $file;
+ my $cmd;
+
+ NetSNMP::Cert::make_dirs($opts, $dir, 0700,'newcerts','private');
+
+ $file = "$dir/$NetSNMP::Cert::SERIAL";
+ if (not -f $file) {
+ $cmd = "echo '01' > '$file'";
+ NetSNMP::Cert::dprint("$cmd\n", $opts);
+ NetSNMP::Cert::usystem($cmd, $opts);
+ chmod(0600, $file);
+ }
+
+ $file = "$dir/$NetSNMP::Cert::INDEX";
+ if (not -f $file) {
+ $cmd = "touch '$file'";
+ NetSNMP::Cert::dprint("$cmd\n", $opts);
+ NetSNMP::Cert::usystem($cmd, $opts);
+ chmod(0600, $file);
+ }
+}
+
+sub genCa {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $host = $config->inherit('host') || ::hostname();
+ my $days = $config->inherit('days') || $config->inherit('caDays') ||
+ $NetSNMP::Cert::DEFCADAYS;
+ my $cn = $config->inherit('cn') || $config->inherit('commonName') ||
+ "ca-$host";
+ my $ca = $config->inherit('with-ca');
+ my $tag = $config->inherit('tag') || $cn;
+
+ # create CA dir
+ my $dir = ".ca/$tag";
+ $app->createCaDir($dir);
+
+ my $outCrt = "$NetSNMP::Cert::CADIR/$tag.crt";
+ my $outKey = "$NetSNMP::Cert::PRIVDIR/$tag.key";
+
+ # set command env
+ my $env = $app->opensslEnv($cn, $days);
+
+ NetSNMP::Cert::check_output_file($opts, $outCrt,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+ NetSNMP::Cert::check_output_file($opts, $outKey,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+
+ my $cmd = "$env openssl req -extensions v3_ca_create -new -days $days -batch -config $NetSNMP::Cert::SSLCFG -keyout '$outKey'";
+ $cmd .= " -nodes";
+
+ if (defined $ca) {
+ # we have to gen a csr and then sign it, must preserve CA:TRUE
+ my $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr";
+ NetSNMP::Cert::check_output_file($opts, $outCsr,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+ $cmd .= " -out '$outCsr'";
+
+ NetSNMP::Cert::dprint("genCa (gencsr): $cmd\n", $opts);
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+
+ my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES);
+ NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts);
+
+ # set command env
+ $env = $app->opensslEnv($cn, $days, ".ca/$ca_base");
+
+ $cmd = "$env openssl ca -extensions v3_ca_sign_ca -days $days -cert '$ca' -keyfile '$NetSNMP::Cert::PRIVDIR/$ca_base.key' -in '$outCsr' -batch -config $NetSNMP::Cert::SSLCFG -out '$outCrt'";
+
+ NetSNMP::Cert::dprint("genCa (signcsr): $cmd\n", $opts);
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+ } else {
+ $cmd .= " -x509 -out '$outCrt'";
+
+ NetSNMP::Cert::dprint("genCa: $cmd\n", $opts);
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+ }
+
+ NetSNMP::Cert::vprint("CA Generated:\n", $opts);
+ NetSNMP::Cert::vprint(" $outCrt\n", $opts);
+ NetSNMP::Cert::vprint(" $outKey\n", $opts);
+}
+
+sub genCert {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $host = $config->inherit("host") || ::hostname();
+ my $email = $config->inherit("email") || getlogin() . "\@$host";
+ my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS;
+ my $cn = $config->inherit('cn') || $config->inherit('commonName');
+ my $ca = $config->inherit('with-ca');
+ my $cmd;
+
+ my $tag = $opts->{'tag'} || 'snmp';
+ $cn ||= (NetSNMP::Cert::is_server($tag) ? $host : $email);
+
+ my $env = $app->opensslEnv($cn, $days);
+
+ my $outCrt = "$NetSNMP::Cert::CRTDIR/$tag.crt";
+ my $outKey = "$NetSNMP::Cert::PRIVDIR/$tag.key";
+
+ NetSNMP::Cert::check_output_file($opts, $outCrt,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+ NetSNMP::Cert::check_output_file($opts, $outKey,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+
+ $cmd = "$env openssl req -extensions v3_user_create -new -days $days -keyout '$outKey' -batch -config $NetSNMP::Cert::SSLCFG";
+ $cmd .= " -nodes";
+
+ if (defined $ca) {
+ my $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr";
+ NetSNMP::Cert::check_output_file($opts, $outCsr,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+ $cmd .= " -out '$outCsr'";
+
+ NetSNMP::Cert::dprint("genCert (gencsr): $cmd\n", $opts);
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+
+ # XXX cleanup this temp CSR
+ my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES);
+ NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts);
+
+ # set command env
+ $env = $app->opensslEnv($cn, $days, ".ca/$ca_base");
+
+ $cmd = "$env openssl ca -extensions v3_ca_sign -days $days -cert '$ca' -keyfile '$NetSNMP::Cert::PRIVDIR/$ca_base.key' -in '$outCsr' -batch -config $NetSNMP::Cert::SSLCFG -out '$outCrt'";
+
+ NetSNMP::Cert::dprint("gencert (signcsr): $cmd\n", $opts);
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+ } else {
+ $cmd .= " -x509 -out '$outCrt'";
+
+ NetSNMP::Cert::dprint("genCert: $cmd\n", $opts);
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+ }
+
+ NetSNMP::Cert::vprint("Certificate Generated:\n", $opts);
+ NetSNMP::Cert::vprint(" $outCrt\n", $opts);
+ NetSNMP::Cert::vprint(" $outKey\n", $opts);
+}
+
+sub genCsr {
+ my $app = shift;
+ my $isCa = shift; # XXX - not implemented yet
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $host = $config->inherit("host") || ::hostname();
+ my $email = $config->inherit("email") || getlogin() . "\@$host";
+ my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS;
+ my $cn = $config->inherit('cn') || $config->inherit('commonName');
+ my $tag = $config->inherit('tag');
+ my $inCrt = $config->inherit('from-crt') || $config->inherit('fromCert');
+ my $outCsr;
+ my $csrKey;
+
+ if (defined $inCrt) {
+ $inCrt = NetSNMP::Cert::find_certs($opts, 'certs', $inCrt);
+ my $crt = ::basename($inCrt, @NetSNMP::Cert::CRTSUFFIXES);
+ $csrKey = "$NetSNMP::Cert::PRIVDIR/$crt.key";
+ $tag ||= $crt;
+ } else {
+ $tag ||= 'snmp';
+ $csrKey ||= "$NetSNMP::Cert::PRIVDIR/$tag.key";
+ }
+
+ $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr";
+
+ $cn ||= (NetSNMP::Cert::is_server($tag) ? $host : $email);
+
+ my $env = $app->opensslEnv($cn, $days);
+
+ NetSNMP::Cert::check_output_file($opts, $outCsr,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+ NetSNMP::Cert::check_output_file($opts, $csrKey,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+
+ my $cmd = (defined $inCrt ?
+ "$env openssl x509 -x509toreq -in $inCrt -out '$outCsr' -signkey '$csrKey' -nodes -days $days -batch -config $NetSNMP::Cert::SSLCFG" :
+ "$env openssl req -new -nodes -days $days -batch -keyout '$csrKey' -out '$outCsr' -config $NetSNMP::Cert::SSLCFG");
+
+ $cmd .= ($isCa ? " -extensions v3_ca_create" : " -extensions v3_user_create");
+
+ NetSNMP::Cert::dprint("genCsr: $cmd\n", $opts);
+
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+
+ NetSNMP::Cert::vprint("Certificate Signing Request Generated:\n", $opts);
+ NetSNMP::Cert::vprint(" $outCsr\n", $opts);
+ NetSNMP::Cert::vprint(" $csrKey\n", $opts);
+}
+
+sub signCsr {
+ my $app = shift;
+ my $isCa = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $host = $config->inherit("host") || ::hostname();
+ my $email = $config->inherit("email") || getlogin() . "\@$host";
+ my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS;
+ my $cn = $config->inherit('cn') || $config->inherit('commonName');
+ my $tag = $config->inherit('tag') || 'snmp';
+ my $install = $config->inherit('install');
+
+ $cn = (NetSNMP::Cert::is_server($tag) ? $host : $email);
+
+ my $ca = $opts->{'with-ca'};
+ NetSNMP::Cert::dprint("ca: $ca\n", $opts);
+ my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES);
+ NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts);
+ my $ca_key = "$NetSNMP::Cert::PRIVDIR/$ca_base.key";
+
+ my $csr = $opts->{'csr'};
+ NetSNMP::Cert::dprint("csr: $csr\n", $opts);
+ my $csr_base = ::basename($csr, @NetSNMP::Cert::CRTSUFFIXES);
+ NetSNMP::Cert::dprint("csr_base: $csr_base\n", $opts);
+ my $outdir = ($install ? $NetSNMP::Cert::CRTDIR : $NetSNMP::Cert::NEWCRTDIR);
+ my $outCrt = "$outdir/$csr_base.crt";
+
+ my $env = $app->opensslEnv($cn, $days, ".ca/$ca_base");
+
+ NetSNMP::Cert::check_output_file($opts, $outCrt,
+ $config->inherit('interactive'),
+ $config->inherit('force'));
+
+ # XXX - handle keyfile search??
+ my $cmd = "$env openssl ca -batch -days $days -extensions v3_ca_sign -cert '$ca' -keyfile '$ca_key' -in '$csr' -out '$outCrt' -config $NetSNMP::Cert::SSLCFG";
+
+ # $cmd .= ($isCa ? " -extensions v3_ca_sign_ca" : " -extensions v3_ca_sign");
+
+ NetSNMP::Cert::dprint("signCsr: $cmd\n", $opts);
+
+ NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
+
+ NetSNMP::Cert::vprint("Signed Certificate Signing Request:\n", $opts);
+ NetSNMP::Cert::vprint(" $csr\n", $opts);
+ NetSNMP::Cert::vprint("with CA:\n", $opts);
+ NetSNMP::Cert::vprint(" $ca\n", $opts);
+ NetSNMP::Cert::vprint(" $ca_key\n", $opts);
+ NetSNMP::Cert::vprint("Generated Certificate:\n", $opts);
+ NetSNMP::Cert::vprint(" $outCrt\n", $opts);
+}
+
+sub show {
+ my $app = shift;
+ my $type = shift || 'certs';
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $stag = shift(@{$opts->{'cmdargs'}});
+ my $fmt = NetSNMP::Cert::x509_format($opts) || '-subject';
+ my $brief = $config->inherit('brief');
+ my $output;
+ my $cmd;
+
+ my $cwd = ::getcwd();
+ NetSNMP::Cert::dprint("show ($cwd):$type:$stag:$fmt\n", $opts);
+ NetSNMP::Cert::vprint("$opts->{'tlsdir'}:\n", $opts) unless $brief;
+
+ foreach my $c (NetSNMP::Cert::find_certs($opts, $type, $stag)) {
+ print "\n$c:\n" unless $brief;
+ $cmd = "openssl x509 -in '$c' -noout $fmt";
+ NetSNMP::Cert::dprint("show: $cmd\n", $opts);
+
+ $output = `$cmd`; chomp $output;
+ NetSNMP::Cert::vwarn("show-$type failed ($?): $output\n", $opts) if $?;
+
+ $output =~ s/^[^\n=]+=// if $brief;
+
+ print "$output\n";
+ print "\n" unless $brief;
+ }
+}
+
+sub import_file {
+ my ($file, $suffix, $targ, $rdir, $tag) = @_;
+
+ if (NetSNMP::Cert::is_url($file)) {
+ if ($NetSNMP::Cert::haveUserAgent) {
+
+ require File::Temp;
+ import File::Temp qw(tempfile);
+
+ my ($fh, $newfilename) = tempfile(SUFFIX => $suffix);
+ return if (!$fh || !$newfilename);
+ my $ua = LWP::UserAgent->new;
+ my $response = $ua->get($file);
+ if ($response->is_success) {
+ print $fh $response->decoded_content();
+ } else {
+ NetSNMP::Cert::vwarn("failed to download a certificate from $file");
+ return;
+ }
+ $fh->close;
+ $file = $newfilename;
+ } else {
+ NetSNMP::Cert::vwarn("LWP::UserAgent not installed: unable to import certificate");
+ return;
+ }
+ }
+
+ $file = NetSNMP::Cert::fq_rel_path($file, $rdir);
+ die("file unreadable: $file\n") unless -r $file;
+
+
+ if (! $targ) {
+ $targ = (NetSNMP::Cert::is_ca_cert($file) ? $NetSNMP::Cert::CADIR : $NetSNMP::Cert::CRTDIR);
+ }
+
+ $targ .= "/" . $tag . $suffix if ($tag);
+ ::copy($file, $targ);
+}
+
+
+sub import {
+ my $app = shift;
+ my $config = $app->{'config'};
+ my $opts = $app->{'OPTS'};
+ my $carg = shift(@{$opts->{'cmdargs'}});
+ my $karg = shift(@{$opts->{'cmdargs'}});
+ my $targ;
+
+ if (not defined $carg) {
+ NetSNMP::Cert::vwarn("import: no certificate supplied\n", $opts);
+ NetSNMP::Cert::usage(1);
+ }
+
+ import_file($carg, '.crt', '',,
+ $opts->{'rdir'}, $opts->{'tag'});
+
+ return unless defined $karg;
+
+ import_file($karg, '.key', 'private',,
+ $opts->{'rdir'}, $opts->{'tag'});
+}
+
+
+package NetSNMP::Cert::Config;
+use vars qw(@ISA);
+@ISA = qw(NetSNMP::Cert::Obj);
+
+sub new {
+ my $class = shift;
+ my $parent = shift;
+ my $this = $class->SUPER::new('config', $parent);
+
+ bless($this, $class);
+}
+
+sub parse {
+ my $config = shift;
+ my $app = $config->{'APP'};
+ my $opts = $app->{'OPTS'};
+ my $cfgfile = shift;
+ $cfgfile ||= $opts->{'cfgfile'};
+
+ return '0 but true' if $config->{'PARSED'};
+ return '0 but true' unless defined $cfgfile;
+
+ open( CONFIG, "<$cfgfile" )
+ or die "error - could not open configuration file: $cfgfile";
+
+ while (<CONFIG>) {
+ next if /^\s*#/ or /^\s*$/;
+
+ if (/^\s*(\w+)(?:\(([\)\(\d\,\.]+)\))?\s*=\s*{/) {
+ my $obj = $1;
+
+ NetSNMP::Cert::dprint("object: $obj ($2) = {\n", $opts);
+ die "error - identity: indices not supported: $2" if defined $2;
+
+ # Found an object.
+ if ( $obj eq 'identity' ) {
+ my $identity = NetSNMP::Cert::Identity::parse(*CONFIG, $config);
+ my $id = $identity->{'id'};
+ die "error - identity: 'id' not defined" unless defined $id;
+ die "error - identity: duplicate '$id'" if exists $config->{$obj}{$id};
+ $config->{$obj}{$id} = $identity;
+ } else {
+ die "error - unrecognized object ($1) at scope (config)";
+ }
+ } elsif (/^\s*(\w+)\s*=?\s*(.*?)\s*$/) {
+ my $key = $1;
+ my $val = $2;
+
+ $val = $config->resolve($val) if $val =~ /\$\w+|\&\{.*?\}/;
+ # Found a symbol.
+ NetSNMP::Cert::dprint(" $key = $val\n", $opts);
+ if ($key eq 'subjectAltName' or $key eq 'san') {
+ push(@{$config->{$key}}, $val);
+ } elsif ( defined $config->{$key} ) {
+ die "error - duplicate symbol $key";
+ } else {
+ $config->{$key} = (defined $val ? NetSNMP::Cert::map_bool($val) : 1 );
+ }
+ } elsif (/^\s*env\s*(\w+=\S+)\s*$/) {
+ # Found an environment variable.
+ NetSNMP::Cert::dprint("$&\n", $opts);
+ push(@{$config->{'env'}}, $1);
+ } else {
+ die("error in config file [$cfgfile:line $.]");
+ }
+ }
+
+ NetSNMP::Cert::dprint("end parse config\n", $opts);
+ close(CONFIG);
+
+ # augment with any config directives supplied in opts
+ foreach my $cfg (@{$opts->{'config'}}) {
+ NetSNMP::Cert::dprint("augmenting config: $cfg\n", $opts);
+
+ $config->autoSet($1, (defined($2) ? $2 : 1 ))
+ if $cfg =~ /^\s*(\w+)\s*=?\s*(.*?)\s*$/;
+ }
+ $config->autoSet('PARSED', 1);
+ return $config->{'PARSED'};
+}
+
+package NetSNMP::Cert::Identity;
+use vars qw(@ISA);
+@ISA = qw(NetSNMP::Cert::Obj);
+
+sub new {
+ my $class = shift;
+ my $this = shift || $class->SUPER::new('identity', @_);
+ my $ind = $this->{'INDEX'} || 1;
+
+ $this->autoSet('name', "$this->{type}.$ind") unless exists $this->{'name'};
+
+ $this->autoSet('LOG','') unless exists $this->{'LOG'};
+ $this->autoSet('TTY_LOG','') unless exists $this->{TTY_LOG};
+
+ bless($this, $class);
+}
+
+
+sub parse {
+ my $FILE = shift;
+ my $parent = shift;
+ my $opts = $parent->inherit('OPTS');
+ my $identity = new NetSNMP::Cert::Obj('identity', $parent);
+
+ NetSNMP::Cert::dprint("parse identity\n", $opts);
+
+ while (<$FILE>) {
+ next if /^\s*#/ or /^\s*$/;
+
+ if (/^\s*(\w+)\s*=\s*{/) {
+ # Found an object.
+ die "error - can't have nested $1";
+ } elsif (/^\s*(\w+)\s*=?\s*(.*?)\s*$/) {
+ my $key = $1;
+ my $val = $2;
+
+ # Found a symbol.
+ NetSNMP::Cert::dprint(" $key = $val\n", $opts);
+
+ $val = $identity->resolve($val) if $val =~ /\$\w+|\&\{.*?\}/;
+
+ if ( $key eq 'subjectAltName' or $key eq 'san') {
+ push(@{$identity->{$key}}, $val);
+ } elsif ( defined $identity->{$key} ) {
+ die "error - duplicate symbol $key";
+ } else {
+ $identity->{$key} = (defined $val ? NetSNMP::Cert::map_bool($val) : 1 );
+ }
+ } elsif (/\s*\}\s*\;/) {
+ # End of definition.
+ NetSNMP::Cert::dprint("end parse identity\n", $opts);
+ return new NetSNMP::Cert::Identity($identity);
+ } else {
+ die("error in config file [$opts->{cfgfile}:line $.]");
+ }
+ }
+ die "error - unexpected end of conf file";
+}
+
+package main;
+
+my $app = new NetSNMP::Cert::App();
+$app->init(@ARGV);
+$app->run();
+
+__END__
+=pod
+
+=head1 NAME
+
+net-snmp-cert - Net-SNMP Certificate Management Tool
+
+=head1 SYNOPSIS
+
+=over
+
+=item $ net-snmp-cert genca -I --cn ca-Net-SNMP
+
+=item $ net-snmp-cert gencert -I -t snmpapp --with-ca ca-Net-SNMP
+
+=item $ net-snmp-cert gencert -I -t snmpd --cn net-snmp.org
+
+=item $ net-snmp-cert showcas
+
+=item $ net-snmp-cert showcerts
+
+=back
+
+=head1 DESCRIPTION
+
+net-snmp-cert creates, signs, installs and displays X.509
+certificates used in the operation of Net-SNMP/(D)TLS.
+
+=head1 SYNTAX
+
+=over
+
+=item net-snmp-cert [--help|-?]
+
+=item net-snmp-cert [--version|-V]
+
+=item net-snmp-cert genca [<flags>] [<dn-opts>] [--with-ca <ca>]
+
+=item net-snmp-cert gencert [<flags>] [<dn-opts>] [--with-ca <ca>]
+
+=item net-snmp-cert gencsr [<flags>] [<dn-opts>] [--from-crt <crt>]
+
+=item net-snmp-cert signcsr [<flags>] [--install] --csr <csr> --with-ca <ca>
+
+=item net-snmp-cert showca [<flags>] [<format-opts>] [<file>|<search-tag>]
+
+=item net-snmp-cert showcert [<flags>] [<format-opts>] [<file>|<search-tag>]
+
+=item net-snmp-cert import [<flags>] <file|url> [<key>]
+
+=back
+
+=head1 COMMANDS
+
+=over
+
+=item genca
+
+generate a signed CA certificate suitable for signing other
+certificates. default: self-signed unless --with-ca <ca> supplied
+
+=item gencert
+
+generate a signed certificate suitable for identification, or
+validation. default: self-signed unless --with-ca <ca> supplied
+
+=item gencsr
+
+generate a certificate signing request. will create a new
+key and certificate unless --from-crt <crt> supplied
+
+=item signcsr
+
+sign a certificate signing request specified by --csr <csr>
+with the CA certificate specified by --with-ca <ca>
+
+=item import
+
+import an identity or CA certificate, optionally import <key>
+if an URL is passed, will attempt to import certificate from site
+
+=item showca, showcert
+
+show CA or identity certificate(s). may pass fully qualified
+file or directory name, or a search-tag which prefix matches
+installed CA or identity certificate file name(s)
+see FORMAT OPTIONS to specify output format
+
+
+=back
+
+=head1 FLAGS
+
+=over
+
+=item -?, --help -- show this text and exit
+
+=item -V, --version -- show version string and exit
+
+=item -D, --debug -- raise debug level (-D -D for higher level)
+
+=item -F, --force -- force overwrite of existing output files
+
+=item -I, --nointeractive -- non-interactive run (default interactive)
+
+=item -Q, --noverbose -- non-verbose output (default verbose)
+
+=item -C, --cfgdir <dir> -- configuration dir (see man(5) snmp_config)
+
+=item -T, --tlsdir <dir> -- root for cert storage (default <cfgdir>/tls)
+
+=item -f, --cfgfile <file> -- config (default <cfgdir>/net-snmp-cert.conf)
+
+=item -i, --identity <id> -- identity to use from config
+
+=item -t, --tag <tag> -- application tag (default 'snmp')
+
+=item --<cfg-param>[=<val>] -- additional config params
+
+=back
+
+=head1 CERTIFICATE OPTIONS (<cert-opts>)
+
+=over
+
+=item -a, --with-ca <ca> -- CA certificate used to sign request
+
+=item -A, --ca <ca> -- explicit output CA certificate
+
+=item -r, --csr <csr> -- certificate signing request
+
+=item -x, --from-crt <crt> -- input certificate for current operation
+
+=item -X, --crt <crt> -- explicit output certificate
+
+=item -y, --install -- install result in local repository
+
+=back
+
+=head1 DISTINGUISHED NAME OPTIONS (<dn-opts>)
+
+=over
+
+=item -e, --email <email> -- email name
+
+=item -h, --host <host> -- DNS host name, or IP address
+
+=item -d, --days <days> -- number of days certificate is valid
+
+=item -n, --cn <cn> -- common name (CN)
+
+=item -o, --org <org> -- organiztion name
+
+=item -u, --unit <unit> -- organiztion unit name
+
+=item -c, --country <country> -- country code (e.g., US)
+
+=item -p, --province <province> -- province name (synomynous w/ state)
+
+=item -p, --state <state> -- state name (synomynous w/ province)
+
+=item -l, --locality <locality> -- locality name (e.g, town)
+
+=item -s, --san <san> -- subjectAltName, repeat for each <san>
+
+=over 2
+
+=item <san> value format (<FMT>:<VAL>):
+
+=over 3
+
+=item dirName:/usr/share/snmp/tls
+
+=item DNS:net-snmp.org
+
+=item email:admin@net-snmp.org
+
+=item IP:192.168.1.1
+
+=item RID:1.1.3.6
+
+=item URI:http://net-snmp.org
+
+=back
+
+=back
+
+=back
+
+=head1 FORMAT OPTIONS (<format-opts>)
+
+=over
+
+=item --brief -- minimized output (values only where applicable)
+
+=item --text -- full text description
+
+=item --subject -- subject description
+
+=item --subject_hash -- hash of subject for indexing
+
+=item --issuer -- issuer description
+
+=item --issuer_hash -- hash of issuer for indexing
+
+=item --fingerprint -- SHA1 digest of DER
+
+=item --serial -- serial number
+
+=item --modulus -- modulus of the public key
+
+=item --dates -- start and end dates
+
+=item --purpose -- displays allowed uses
+
+=item --C -- C code description
+
+=back
+
+=head1 OPERATIONAL NOTES
+
+
+=head2 Interactive Mode
+
+The application operates in interactive mode by default. In this mode
+basic operations of offered and required input is queried through the
+command line.
+
+Typical <tab> completion is provided when possible and field specific
+help may be obtained by entering '?'.
+
+To turn off interactive mode, supply '--nointeractive' or '-I' on the
+initial command line. Equivalantly, 'interactive = false' maybe placed
+in the configuration file (see below).
+
+=head2 Configuration
+
+A configuration file may be supplied on the command line or found in a
+default location (<snmpconfpath>/net-snmp-cert.conf). This file may
+contain configuration parameters equivalent to those supplied on the
+command line and effectively provides default values for these
+values. If a command line parameter is supplied it will override the
+value in the config file. If neither is present then an application
+value will be used.
+
+=head2 Certificate Naming
+
+Filenames of created certificates, as stored in the configuration
+directory, are chosen in the following manner. If and application tag
+is supplied, it will be used as the basename for the certificate and
+key generated. Otherwise, for CA certificates, the basename will be
+derived from the Common Name. For non-CA certificates the application
+tag defaults to 'snmp' which will then be used as the basename of the
+certificate and key.
+
+=head1 EXAMPLES
+
+=over
+
+=item net-snmp-cert genca --cn ca-net-snmp.org --days 1000
+
+=item net-snmp-cert genca -f .snmp/net-snmp-cert.conf -i nocadm
+
+=item net-snmp-cert gencert -t snmpd --cn host.net-snmp.org
+
+=item net-snmp-cert gencsr -t snmpapp
+
+=item net-snmp-cert signcsr --csr snmpapp --with-ca ca-net-snmp.org
+
+=item net-snmp-cert showcerts --subject --issuer --dates snmpapp
+
+=item net-snmp-cert showcas --fingerprint ca-net-snmp.org --brief
+
+=item net-snmp-cert import ca-third-party.crt
+
+=item net-snmp-cert import signed-request.crt signed-request.key
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (c) 2010 Cobham Analytic Solutions - All rights reserved.
+Copyright (c) 2010 G. S. Marzot - All rights reserved.
+
+=head1 AUTHOR
+
+G. S. Marzot (marz@users.sourceforge.net)
+
+=cut
+
Property changes on: net-snmp-5.9-new/local/net-snmp-cert
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: net-snmp-5.9-new/local
===================================================================
--- net-snmp-5.9-new/local (nonexistent)
+++ net-snmp-5.9-new/local (revision 5)
Property changes on: net-snmp-5.9-new/local
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: net-snmp-5.9-new
===================================================================
--- net-snmp-5.9-new (nonexistent)
+++ net-snmp-5.9-new (revision 5)
Property changes on: net-snmp-5.9-new
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~
Index: .
===================================================================
--- . (nonexistent)
+++ . (revision 5)
Property changes on: .
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,73 ##
+
+# install dir
+dist
+
+# Target build dirs
+.a1x-newlib
+.a2x-newlib
+.at91sam7s-newlib
+
+.build-machine
+
+.a1x-glibc
+.a2x-glibc
+.h3-glibc
+.h5-glibc
+.i586-glibc
+.i686-glibc
+.imx6-glibc
+.jz47xx-glibc
+.makefile
+.am335x-glibc
+.omap543x-glibc
+.p5600-glibc
+.power8-glibc
+.power8le-glibc
+.power9-glibc
+.power9le-glibc
+.m1000-glibc
+.riscv64-glibc
+.rk328x-glibc
+.rk33xx-glibc
+.rk339x-glibc
+.s8xx-glibc
+.s9xx-glibc
+.x86_64-glibc
+
+# Hidden files (each file)
+.makefile
+.dist
+.rootfs
+
+# src & hw requires
+.src_requires
+.src_requires_depend
+.requires
+.requires_depend
+
+# Tarballs
+*.gz
+*.bz2
+*.lz
+*.xz
+*.tgz
+*.txz
+
+# Signatures
+*.asc
+*.sig
+*.sign
+*.sha1sum
+
+# Patches
+*.patch
+
+# Descriptions
+*.dsc
+*.txt
+
+# Default linux config files
+*.defconfig
+
+# backup copies
+*~