#!/usr/bin/perl
#
#  x509viewer 0.1.0
#
#  (c) 2009-2017 by Robert Scheck <x509viewer@robert-scheck.de>
#
#  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.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

use strict;
use Fcntl;
use Getopt::Long qw(:config no_ignore_case permute);
use IPC::Open3;

# Default configuration values
my %config = (
  openssl => "/usr/bin/openssl",
  help    => 0
);

# Handle given parameters and options
GetOptions(
  "openssl|o=s"   => \$config{'openssl'},
  "help|?!"       => \$config{'help'},
  "version|V!"    => \$config{'help'}
) || do { $config{'help'} = 2; };

# If wished, print help and version information
if($#ARGV == -1 || $config{'help'})
{
  print "\n" if($config{'help'} == 2);
  print "x509viewer 0.1.0, (c) 2009-2017 by Robert Scheck\n";
  print "This program is free software with ABSOLUTELY NO WARRANTY;\n";
  print "you may redistribute it under the terms of the GNU General\n";
  print "Public License version 2 or later.\n\n";
  print "Usage: x509viewer [OPTIONS] <file> [<file>] ...\n";
  print "  -o, --openssl=path    Alternative path to OpenSSL binary;\n";
  print "                        default: " . $config{'openssl'} . "\n";
  print "  -?, --help            Display this help and exit\n";
  print "  -V, --version         Output version information and exit\n\n";
  print "Decodes given X.509 certificates, such as SSL certificates,\n";
  print "CSRs (certificate signing requests) and also private keys.\n";
  exit(0);
}

# Check whether given alternative OpenSSL exists
if(!-x $config{'openssl'})
{
  print STDERR "Error: OpenSSL \"" . $config{'openssl'} . "\" does not exist or is not executable!\n";
  exit(1);
}

# Variable declaration
my ($output, $ret, $text, $match);

# Walk through all given files
foreach my $file (@ARGV)
{
  # Check whether file is readable
  if(-r $file)
  {
    # Open file for later handling
    sysopen(FILE, $file, O_RDONLY) || do { print STDERR "Error: File \"$file\" is not readable!\n"; $ret++; };
    while(<FILE>)
    {
      $text .= $_;

      # Handle multiple requests, certificates or keys in one file
      if($_ =~ /^\-+END(\s\w+)?\s(CERTIFICATE(\sREQUEST)?|PRIVATE\sKEY)\-+$/)
      {
        my ($type) = ($2 =~ /^CERTIFICATE\sREQUEST$/) ? "req" : (($2 =~ /^PRIVATE\sKEY$/) ? "pkey" : "x509");
        local (*WRITER, *READER, *ERROR);
        $match++;

        # Open the OpenSSL process for writing and reading
        my $pid = open3(*WRITER, *READER, *ERROR, $config{'openssl'}, $type, "-text") || do { print STDERR "Error: IPC::Open3 failed!\n"; $ret++; };
        print WRITER $text;
        close(WRITER);
        waitpid($pid, 0);
        undef($text);

        # Check whether result is successful or unsuccessful
        if($? == 0)
        {
          $output .= join("", <READER>);
        }
        else
        {
          print STDERR "Error: OpenSSL handling of file \"$file\" failed:\n" . join("", <ERROR>);
          $ret++;
        }
        close(READER);
        close(ERROR);
      }
    }
    close(FILE);

    # Report lack of X.509 certificate
    if(!defined($match))
    {
      print STDERR "Error: File \"$file\" does not contain any X.509 certificate!\n";
      $ret++;
    }

    undef($match);
  }
  else
  {
    # Check why file isn't readable
    if(-l $file)
    {
      print STDERR "Error: File \"$file\" is a dangling symlink!\n";
    }
    else
    {
      print STDERR "Error: File \"$file\" is not readable!\n";
    }

    $ret++;
  }
}

# Pipe results to pager or print them to console
if($output)
{
  $ENV{'PAGER'} = "less" if(!defined($ENV{'PAGER'}));

  if(-t STDOUT && $ENV{'PAGER'})
  {
    open(PIPE, "|-", $ENV{'PAGER'});
    print PIPE $output;
    close(PIPE);
  }
  else
  {
    print $output;
  }
}

exit($ret);
