#!/usr/bin/perl
# vim: tabstop=2 smarttab expandtab softtabstop=2 shiftwidth=2
# in your vimrc, please add:
#   set modeline
#   set modelines=2

use strict;
use warnings;

BEGIN {
  use FindBin;
  do $FindBin::RealBin . '/../etc/config.pm' ;
}

use Data::Dumper;
use Sys::Syslog;
use File::Path;
use IO::File;
use IO::Pipe;
use MIME::Base64;
use FindBin;
use JSON;

use threads;
use threads::shared;
threads->import;
threads::shared->import;

use Fuse ':all';
use POSIX;
use Fcntl ':mode';


my %STRING_FILE_TYPES = (
  S_IFREG => '-',
  S_IFDIR => 'd',
  S_IFLNK => 'l',
  S_IFBLK => 'b',
  S_IFCHR => 'c',
  S_IFIFO => 'p',
  S_IFSOCK => 'S',
);
my %FILE_TYPES = map { eval($_) => $STRING_FILE_TYPES{$_} }
  keys %STRING_FILE_TYPES;
my @FILE_RIGHTS = (
  [ S_IRUSR, 1, 'r' ],
  [ S_IWUSR, 2, 'w' ],
  [ S_IXUSR, 3, 'x' ],

  [ S_IRGRP, 4, 'r' ],
  [ S_IWGRP, 5, 'w' ],
  [ S_IXGRP, 6, 'x' ],

  [ S_IROTH, 7, 'r' ],
  [ S_IWOTH, 8, 'w' ],
  [ S_IXOTH, 9, 'x' ],

  [ S_ISUID, 3, 's' ],
  [ S_ISGID, 6, 's' ],
);

my %BIN_SCRIPTS = (
  '/usr/bin/perl' => 1,
  '/bin/bash' => 1,
  '/usr/bin/php' => 1,
  '/usr/bin/python2' => 1,
  '/usr/bin/python3' => 1,
  '/usr/bin/expect' => 1,
);

my %cryptModes = (
  default => {
    myCryptInit => \&myCryptInitNone,
    myEncrypt   => \&myEncryptNone,
    myDecrypt   => \&myDecryptNone,
  },
  none => {
    myCryptInit => \&myCryptInitNone,
    myEncrypt   => \&myEncryptNone,
    myDecrypt   => \&myDecryptNone,
  },
  base64 => {
    myCryptInit => \&myCryptInitBase64,
    myEncrypt   => \&myEncryptBase64,
    myDecrypt   => \&myDecryptBase64,
  },
  ssl => {
    myCryptInit => \&myCryptInitSSL,
    myEncrypt   => \&myEncryptSSL,
    myDecrypt   => \&myDecryptSSL,
  },
);

# cryptmode: none, base64, ssl
my $cryptMode = 'ssl';
my %conf;
my %files;
my @rules;

my $myCryptInit;
my $myEncrypt;
my $myDecrypt;

my $nextSanityCheck = 0;

sub main;
sub init;
sub sanityChecks;
sub checkFileOwnerPerms;
sub getFileInfo;
sub spawn;
sub mydebug;
sub myinfo;
sub mywarn;
sub myerror;
sub infos;
sub checkaccess;
sub my_getattr;
sub my_getdir;
sub my_open;
sub my_release;
sub my_statfs;
sub my_read;
sub my_write;
sub my_unlink;
sub my_create;
sub my_truncate;
sub my_readlink;
sub my_mknod;
sub my_mkdir;
sub my_rmdir;
sub my_symlink;
sub my_rename;
sub my_link;
sub my_chmod;
sub my_chown;
sub my_utime;

sub myCryptInitNone;
sub myEncryptNone;
sub myDecryptNone;
sub myCryptInitBase64;
sub myEncryptBase64;
sub myDecryptBase64;
sub myCryptInitSSL;
sub myEncryptSSL;
sub myDecryptSSL;

main;

sub getFileInfo {
    my ($fnm) = @_;
    my $user = '';
    my $group = '';
    my $perms = '----------';

    -e $fnm and do {
      my ($mode, $uid, $gid) = (stat $fnm)[2,4,5];
      $user = getpwuid($uid);
      $group = getgrgid($gid);

      my $type = S_IFMT($mode);
      my $stype = $FILE_TYPES{$type};
      defined $stype or $stype = '?';
      substr($perms, 0, 1) = $stype;

      for my $e (@FILE_RIGHTS) {
        my ($n, $p, $c) = @$e;
        ($mode & $n) == $n or next;
        substr($perms, $p, 1) = $c;
      }
    };

    return $user . '.' . $group, $perms;
}

sub checkFileOwnerPerms {
  my ($fnm, $wowner, $wperms) = @_;
  my $result = 1;

  -e $fnm or do {
    myerror "$fnm does not exist";
    return 0;
  };

  my ($owner, $perms) = getFileInfo $fnm;

  $owner =~ /$wowner/ or do {
    myerror "$fnm is owned by $owner instead of $wowner";
    $result = 0;
  };
  $perms eq $wperms or do {
    myerror "$fnm permissions are $perms instead of $wperms";
    $result = 0;
  };

  return $result;
}

sub sanityChecks {
  time < $nextSanityCheck and return;

  my $result = 1;
  # directories containing these: root.nagios with drwxr-xr-x
  mydebug "Sanity check of scripts and execs";

  my %lexecs;
  my %ldirs;
  my %ladmin;
  for my $e (@rules) {
    my %rule = %$e;

    defined $rule{perl} and do {
      my $fnm = $rule{perl};

      $rule{admin} and do {
        $ladmin{$fnm} = 1;
        next;
      };

      $lexecs{$fnm} = 1;
      $fnm =~ /^(.*)\/[^\/]*$/ and $ldirs{$1} = 1;

      next;
    };

    defined $rule{exe} and do {
      my $fnm = $rule{exe};

      $lexecs{$fnm} = 1;
      $fnm =~ /^(.*)\/[^\/]*$/ and $ldirs{$1} = 1;
    };
  }

  # directories owned by root with drwxr-xr-x
  for my $fnm (keys %ldirs) {
    checkFileOwnerPerms($fnm, '^root\.', 'drwxr-xr-x') or $result = 0;
  }
  
  # executables and scripts owned by root with -rwxr-xr-x
  for my $fnm (keys %lexecs) {
    checkFileOwnerPerms($fnm, '^root\.', '-rwxr-xr-x') or $result = 0;
  } 

  # tools owned by root.root with -rwx------
  for my $fnm (keys %ladmin) {
    checkFileOwnerPerms($fnm, '^root\.root$', '-rwx------') or $result = 0;
  } 

  # check directories owned by root.root with drwx------
  # check files owned by root.root with -rw------- except for bin -rwx------
  checkFileOwnerPerms($FindBin::RealBin, '^root\.root$', 'drwx------')
    or $result = 0;

  checkFileOwnerPerms($FindBin::RealBin . '/' . $FindBin::RealScript,
    'root.root', '-rwx------') or $result = 0;

  $result and do {
    $nextSanityCheck = time + SANITY_OK_COOLDOWN;
    return;
  };
  $nextSanityCheck = time + SANITY_FAIL_COOLDOWN;
  mywarn "Sanity check: requirements not satisfied.";
}

sub init {
  openlog($conf{service}, 'ndelay,pid', 'LOG_USER');

  $conf{service} = FEATURE;
  $conf{mountpoint} = MOUNT_DIR;
  $conf{maindir} = '/opt/' . PRODUCT;
  $conf{lockerdir} = $conf{maindir} . '/' . FEATURE;

  my $mntPtDash2Dot = $conf{mountpoint};
  $mntPtDash2Dot =~ s|/|.|g;
  $conf{datadir} = $conf{lockerdir} . '/locker' . $mntPtDash2Dot;
  $conf{ssl}->{keyfile} = $conf{lockerdir} . '/key';

  for my $nm (qw/nagios nagiostats nsca/) {
    for my $usr (qw/nagios root remoteop/) {
      push @rules, {
        exe => NAGIOS_BIN . '/' . $nm,
        read => 1,
        write => 0,
        user => $usr,
        group => $usr,
      };
    }
  }

  for my $nm (qw/autoSecUpdateSend.pl/) {
    push @rules, {
      exe => '/usr/bin/perl',
      script => NAGIOS_BIN . '/' . $nm,
      read => 1,
      user => 'root',
      group => 'root',
    };
  }

  for my $nm (qw/remoteOperationBox.pl/) {
    for my $usr (qw/nagios remoteop/) {
      push @rules, {
        exe => '/usr/bin/perl',
        script => NAGIOS_BIN . '/' . $nm,
        read => 1,
        write => 1,
        user => $usr,
        group => $usr,
      };
    }
  }

  for my $nm (qw/
      recensement.pl
      recupFailureFiles.pl
      retrieveAppsMonScreenShots.pl
      sendPushNotificationHost.pl
      sendPushNotificationService.pl
      topologydiscovery.pl
      updateUpdateBoxStatusInDB.pl
  /) {
    for my $usr (qw/nagios remoteop/) {
      push @rules, {
        exe => '/usr/bin/perl',
        script => NAGIOS_BIN . '/' . $nm,
        read => 1,
        user => $usr,
        group => $usr,
      };
    }
  }

  for my $fnm (qw/
      enable-cypher-nagios-files
      disable-cypher-nagios-files
      cat
      ls
      adminctl
  /) {
    push @rules, {
      exe => '/usr/bin/perl',
      script => $conf{lockerdir} . '/bin/' . $fnm,
      admin => 1,
      read => 1,
      write => 1,
      user => 'root',
      group => 'root',
    };
  }

  for my $usr (qw/nagios remoteop/) {
    push @rules, {
      exe => '/usr/bin/rsync',
      read => 1,
      write => 1,
      user => $usr,
      group => $usr,
    };
  }

  defined $cryptModes{$cryptMode} or $cryptMode = 'default';
  $myCryptInit = $cryptModes{$cryptMode}->{myCryptInit};
  $myEncrypt   = $cryptModes{$cryptMode}->{myEncrypt};
  $myDecrypt   = $cryptModes{$cryptMode}->{myDecrypt};

  -e $conf{maindir} or do {
    myinfo("Making path '$conf{maindir}'");
    File::Path::make_path($conf{maindir});
  };
  -e $conf{lockerdir} or do {
    myinfo("Making path '$conf{lockerdir}'");
    File::Path::make_path($conf{lockerdir}, { chmod => 0700 });
  };
  -e $conf{datadir} or do {
    myinfo("Making path '$conf{datadir}'");
    File::Path::make_path($conf{datadir}, { chmod => 0700 });
  };

  &$myCryptInit;
}

sub main {
  init;

  sanityChecks;

  mydebug('Starting');

  my %opt = (
    mountpoint  => $conf{mountpoint},
    mountopts   => 'allow_other',
    threaded    => 0,
    debug       => 0,
  );

  my @hooks = ();
  push @hooks, qw/
    getattr statfs
    chmod chown utime
    rename
    mknod
    symlink readlink
    getdir mkdir rmdir 
    create unlink link
    open release read write truncate 
  /;
  #  readdir releasedir
  #  open release read read_buf write write_buf statfs
  for my $hook (@hooks) {
    $opt{$hook} = "main::my_$hook";
  }

  system 'umount', '-l', $conf{mountpoint};

  Fuse::main(%opt);
}

sub spawn {
  my @cmd = @_;
  my $fdin = new IO::Pipe;
  my $fdout = new IO::Pipe;

#  $SIG{CHLD} = sub { print "child gone\n" ; };

  my $pid = fork;
  $pid == 0 and do {
    $fdin->reader;
    $fdout->writer;
    open STDIN, '<&=', $fdin->fileno or die $!;
    open STDOUT, '>&=', $fdout->fileno or die $!;
    open STDERR, '>/dev/null' or die $!;
    exec @cmd;
    exit;
  };
  $fdin->writer;
  $fdout->reader;

  return $pid, $fdout, $fdin;
}

sub mylog {
  my $level = shift;
  my $fmt = shift;
  my $msg = sprintf($fmt, @_);
  chomp $msg;
  -t STDOUT and print "$msg\n";
  syslog($level, $msg);
}

sub mydebug { mylog 'LOG_DEBUG', @_; }
sub myinfo  { mylog 'LOG_INFO', @_; }
sub mywarn  { mylog 'LOG_WARNING', @_; }
sub myerror { mylog 'LOG_ERR', @_; }

# gather information about context into which handlers are triggered
sub infos {
  my $hook = shift @_;

  my $fnm = '';
  @_ and $fnm = $_[0];

  my $ctxt = fuse_get_context;

  my ($uid, $gid, $pid) = map { $ctxt->{$_} } qw/uid gid pid/;
  my $exe = readlink "/proc/$pid/exe";

  my $msg = sprintf '%s fnm=%s uid=%s gid=%s pid=%s exe=%s',
    $hook, $fnm, $uid, $gid, $pid, (defined $exe ? $exe : '-');

  my ($nagiosuid, $nagiosgid) = (getpwnam('nagios'))[2..3];

  my $cwd = readlink "/proc/$pid/cwd";

  my $script;

  $exe and $BIN_SCRIPTS{$exe} and do {
    local $/ = undef;
    open FILE, '</proc/'.$pid.'/cmdline';
    my $env = <FILE>;
    close FILE;
    my @l = split /\000/, $env;
    shift @l;
    for (@l) {
      /^-/ and next;
      $script = $_;
      last;
    }
=pod
    # do not do that, this opens a backdoor
    $perl and not $perl =~ /^\// and do {
      $perl = $cwd . '/' . $perl;
      $perl =~ s|//+|/|g;
      while ($perl =~ s|/./|/|g) {}
      while ($perl =~ s|/[^/]*/../|/|) {}
    };
=cut
  };

  my @ancestors;
  my $p = $pid;
  my $n = 100;
  while (defined $p and $p != 1 and $n -- > 0) {
    open FILE, '</proc/' . $p . '/status' or last;
    $p = undef;
    while (<FILE>) {
      chomp;
      /^PPid:\s*(\d+)\s*$/ or next;
      $p = $1;
      push @ancestors, $p;
    }
    close FILE;
  }

  return {
    msg => $msg,
    hook => $hook,
    fnm => $fnm,
    uid => $uid,
    gid => $gid,
    pid => $pid,
    cwd => $cwd,
    exe => $exe,
    script => $script,
    ancestors => join(',', @ancestors),
    username => (getpwuid($uid))[0],
    groupname => (getgrgid($gid))[0],
    nagiosuid => $nagiosuid,
    nagiosgid => $nagiosgid,
    dst => $conf{mountpoint} . $fnm,
    src => $conf{datadir} . $fnm,
    arg => \@_,
  };
}

sub checkaccess {
  my ($infos) = @_;
  my %ret;

  sanityChecks;

  my $exe = $infos->{exe};
  my $user = $infos->{username};
  my $group = $infos->{groupname};
  my $script = $infos->{script};
  my $ancestors = $infos->{ancestors};

  for my $rule (@rules) {
    my %rule = %$rule;

    defined $rule{user} and $user ne $rule{user} and next;
    defined $rule{group} and $group ne $rule{group} and next;
    defined $rule{exe} and $exe ne $rule{exe} and next;
    defined $script and defined $rule{script} and $script ne $rule{script} and next;

    for my $nm (qw/admin read write/) {
      defined $rule{$nm} and $ret{$nm} = $rule{$nm};
    }
  }

  my $log = 'checkaccess';
  defined $script and $log .= ' script=' . $script;
  for my $nm (qw/admin read write/) {
    $log .= ' ' . $nm . '=' . (defined $ret{$nm} ? $ret{$nm} : '-');
  }

  $ancestors and $log .= ' ancestors=' . $ancestors;

  mydebug $log;

  return %ret;
}

=pod
             0 dev      device number of filesystem
             1 ino      inode number
             2 mode     file mode  (type and permissions)
             3 nlink    number of (hard) links to the file
             4 uid      numeric user ID of file's owner
             5 gid      numeric group ID of file's owner
             6 rdev     the device identifier (special files only)
             7 size     total size of file, in bytes
             8 atime    last access time in seconds since the epoch
             9 mtime    last modify time in seconds since the epoch
            10 ctime    inode change time in seconds since the epoch (*)
            11 blksize  preferred I/O size in bytes for interacting with the
                        file (may vary from file to file)
            12 blocks   actual number of system-specific blocks allocated
                        on disk (often, but not always, 512 bytes each)
=cut
sub my_getattr {
  my $infos = infos 'getattr', @_;
  mydebug($infos->{msg});

  my %rights;
  my @stat = lstat($infos->{src});
  @stat or do {
    $infos->{fnm} eq STATUS_FILE or return -ENOENT;
    %rights or %rights = checkaccess $infos;
    $rights{admin} or return -ENOENT;

    return (
      0, # dev, not sure that is a good idea to answer 0 here
      0, # inode, not sure that is a good idea to answer 0 here
      S_IFREG | S_IRUSR, # mode, want to answer it is a file readable by user
      1, # nlink, have one and only one file having this content
      0, # uid, root
      0, # gid, root
      0, # rdev, not sure that is a good idea to answer 0 here
      4096, # size, arbitrary
      time, # atime, access time is now
      time, # mtime, modification time is now
      time, # ctime, creation time is now
      4096, # blksize, arbitrary
      1, # blocks, arbitrary
    );
  };
  my @ret = @stat;

  $ret[2] &= ~ (S_IRWXG | S_IRWXO);
  $ret[4] = $infos->{nagiosuid};
  $ret[5] = $infos->{nagiosgid};
  -f $infos->{src} and do {
    local $/ = undef;
    my $data;
    open FILE, '<' . $infos->{src} or return -EAGAIN;
    $data = <FILE>;
    $data = &$myDecrypt($data);
    close FILE;

    $ret[7] = length $data;
  };

  %rights or %rights = checkaccess $infos;
  if ($rights{read}) {
    $ret[2] |= S_IRUSR;
    $ret[2] |= S_IRGRP;
  } else {
    $ret[2] &= ~ S_IRUSR;
    $ret[2] &= ~ S_IRGRP;
  }
  if ($rights{write}) {
    $ret[2] |= S_IWUSR;
    $ret[2] |= S_IWGRP;
  } else {
    $ret[2] &= ~ S_IWUSR;
    $ret[2] &= ~ S_IWGRP;
  }

  return @ret;
}

sub my_getdir {
  my $infos = infos 'getdir', @_;
  mydebug($infos->{msg});

  my @ret;
  
  my %rights = checkaccess $infos;
  $rights{read} and do {
    opendir DIR, $infos->{src} and do {
      while (my $fnm = readdir DIR) {
        push @ret, $fnm;
      }
      closedir DIR;
    };
  };

  return (@ret), 0;
}

sub my_open {
  my $infos = infos 'open', @_;
  mydebug($infos->{msg});

  my ($fnm, $flags, $finfo) = @_;

  my $fh = 1;
  while (exists $files{$fh}) { $fh++; }

  $files{$fh} = {
    fnm => $fnm,
    flags => $flags,
    finfo => $finfo,
    infos => $infos,
  };
  
  return 0, $fh;
}

sub my_release {
  my $infos = infos 'release', @_;
  mydebug($infos->{msg});
  my ($fnm, $flags, $fh, $flock, $owner) = @_;

  defined $fh and delete $files{$fh};

  return 0;
}

sub my_statfs {
  my $infos = infos 'statfs', @_;
  mydebug($infos->{msg});
  return -1;
}

sub my_read {
  my $infos = infos 'read', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{read} or return -EACCES;

  my ($fnm, $size, $offset, $fh) = @_;

  $rights{admin} and $fnm eq STATUS_FILE and do {
    my %status = (
      running => 1,
    );
    return encode_json \%status;
  };

  local $/ = undef;
  my $data;
  open FILE, '<' . $infos->{src} or return -EAGAIN;
  $data = <FILE>;
  $data = &$myDecrypt($data);
  close FILE;

  return substr $data, $offset, $size;
}

sub my_write {
  my $infos = infos 'write', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm, $buffer, $offset, $fh) = @_;

  local $/ = undef;
  my $data;
  open FILE, '<' . $infos->{src} or return -EAGAIN;
  $data = <FILE>;
  $data = &$myDecrypt($data);
  close FILE;

  my $len0 = length $data;
  substr $data, $offset, length($data), $buffer;

  open FILE, '>' . $infos->{src} or return -EAGAIN;
  print FILE &$myEncrypt($data);
  close FILE ; 

  return length($data) - $len0;
}

sub my_unlink {
  my $infos = infos 'unlink', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm) = @_;

  eval { unlink $infos->{src} ; };
  $@ and return -EACCES;

  return 0;
}

sub my_create {
  my $infos = infos 'create', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm, $mask, $flags) = @_;
  open FILE, '>' . $infos->{src} or return -EAGAIN;
  close FILE;

  return 0;
}

sub my_truncate {
  my $infos = infos 'truncate', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;
  
  my ($fnm, $offset) = @_;
  truncate $infos->{src}, $offset or return -EAGAIN;

  return 0;
}

sub my_readlink {
  my $infos = infos 'readlink', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{read} or return -EACCES;

  my $ret = readlink $infos->{src};

  return $ret;
}

sub my_mknod {
  my $infos = infos 'mknod', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm, $mode, $dev) = @_;

  mknod($infos->{src}, $mode, $dev) or return -1;

  return 0;
}

sub my_mkdir {
  my $infos = infos 'mkdir', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm, $mode) = @_;

  mkdir($infos->{src}, $mode) or return -1;

  return 0;
}

sub my_rmdir {
  my $infos = infos 'rmdir', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  rmdir($infos->{src}) or return -1;

  return 0;
}

sub my_symlink {
  my $infos = infos 'symlink', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($target, $name) = @_;
  symlink $conf{datadir} . $target, $conf{datadir} . $name or return -1;

  return 0;
}

sub my_rename {
  my $infos = infos 'rename', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($target, $name) = @_;
  rename $conf{datadir} . $target, $conf{datadir} . $name or return -1;

  return 0;
}

sub my_link {
  my $infos = infos 'link', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($target, $name) = @_;
  link $conf{datadir} . $target, $conf{datadir} . $name or return -1;

  return 0;
}

sub my_chmod {
  my $infos = infos 'chmod', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm, $mode) = @_;
  $mode &= 0700;
  chmod $mode, $infos->{src} or return -1;

  return 0;
}

sub my_chown {
  my $infos = infos 'chown', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  # never change that...

  return 0;
}

sub my_utime {
  my $infos = infos 'utime', @_;
  mydebug($infos->{msg});

  my %rights = checkaccess $infos;
  $rights{write} or return -EACCES;

  my ($fnm, $atime, $mtime) = @_;

  utime $atime, $mtime, $infos->{src};

  return 0;
}

sub myCryptInitNone {
}

sub myEncryptNone {
  return shift;
}

sub myDecryptNone {
  return shift;
}

sub myCryptInitBase64 {
}

sub myEncryptBase64 {
  return encode_base64(shift, '');
}

sub myDecryptBase64 {
  return decode_base64(shift);
}

sub myCryptInitSSL {
  my $keyfile = $conf{ssl}->{keyfile};

  my $private = $keyfile . '-private.pem';
  my $public = $keyfile . '-public.pem';

  (-e $private and -e $public) or do {
    myinfo("Making key file '$private' and '$public'");
    my @cmd = ('/usr/bin/openssl', 'req', '-batch', '-x509', '-nodes',
      '-newkey', 'rsa:2048', '-keyout', $private, '-out', $public);
    system @cmd and die "Error creating key file";
  };
}

sub mySSL {
  my %args = @_;

  my ($sslpid, $sslout, $sslin) = spawn @{$args{cmd}};

  my $pid = fork ;
  $pid < 0 and do {
    myerror "fork: $!";
    exit 1;
  };
  $pid == 0 and do {
    print $sslin $args{data};
    exit;
  };
  $sslin->close;

  local $/ = undef;
  my $output = <$sslout>;

  waitpid $pid, 0;
  waitpid $sslpid, 0;

  return $output;
}

sub myEncryptSSL {
  my $data = shift;
  my @cmd = ('/usr/bin/openssl', 'smime', '-encrypt', '-binary',
    '-aes-256-cbc', '-outform', 'DER', $conf{ssl}->{keyfile} . '-public.pem');
  return mySSL data => $data, cmd => \@cmd;
}
sub myDecryptSSL {
  my $data = shift;
  my @cmd = ('/usr/bin/openssl', 'smime', '-decrypt', '-binary',
    '-inform', 'DEM', '-inkey', $conf{ssl}->{keyfile} . '-private.pem');
  return mySSL data => $data, cmd => \@cmd;
}

