#!/usr/bin/perl
# 
# ***** BEGIN LICENSE BLOCK *****
# Zimbra Collaboration Suite Server
# Copyright (C) 2007, 2009, 2010, 2012, 2013, 2014, 2016 Synacor, Inc.
#
# 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,
# version 2 of the License.
#
# 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, see <https://www.gnu.org/licenses/>.
# ***** END LICENSE BLOCK *****
# 


use strict;
BEGIN {
  eval { require Proc::ProcessTable } || exit 0;
  eval { require Mail::Mailer } || exit 0;
}

use Getopt::Long;
use IPC::Open3;
use FileHandle;
use XML::Simple;
my ($sendmail,$mailto,$verbose,$debug,$help);
unless ( GetOptions("verbose" => \$verbose,
                    "mailto:s"    => \$mailto,
                    "emailreport" => \$sendmail,
                    "debug"       => \$debug,
                    "help"        => \$help)
) { usage(); }

usage() if $help;
my $report = [];
my %mysqld;
my $maxcnt=1;

my $localxml = XMLin("/opt/zimbra/conf/localconfig.xml");
my $table = new Proc::ProcessTable;
foreach my $p ( @{$table->table} ) {
  my ($pid,$ppid,$pgrp,$cmdline) = ($p->pid, $p->ppid, $p->pgrp, $p->cmndline);
  if ($p->fname eq "mysqld" && $cmdline =~ /\/opt\/zimbra\/db/) {
  #if ($p->fname eq "mysqld") {
    addToReport("PID:$pid PPID:$ppid PGRP:$pgrp\nCMD:\t$cmdline\n\n")
      if ($p->fname eq "mysqld" && $debug);
    $mysqld{$ppid}{$pid}{cmdline} = $cmdline;    
  }
}

if (scalar keys %mysqld > $maxcnt) {
 addToReport("More than $maxcnt mysqld processes are running\n");
 my $procs = join " ", keys %mysqld;
 addToReport("Parent processes include: $procs\n\n");
 addToReport("This should be investigated immediately as it may lead\n");
 addToReport("to database corruption.\n");
}

$sendmail && scalar(@$report) ?  sendEmailReport($report) : print @$report;

# Functions

sub sendEmailReport {
  my $msg = shift;
  my $subject =  "ZCS: Duplicate mysqld processes detected!";
  my $from_address = $localxml->{key}->{smtp_source}->{value};
  chomp($from_address);
  my $alt_to = $localxml->{key}->{smtp_destination}->{value};
  chomp($alt_to);
  my $to_address = ($mailto ? $mailto : $alt_to);
  my $smtphost = getLdapConfigValue("zimbraSmtpHostname") || "localhost";
  my $smtpport = getLdapConfigValue("zimbraSmtpPort") || "25";
    
  print "Sending report to $to_address via $smtphost\n" if $debug;
  eval {
    my $mailer = Mail::Mailer->new("smtp", Server => $smtphost, Port=> $smtpport);
    $mailer->open( { From => $from_address,
                   To   => $to_address,
                   Subject => $subject,
                })
    or warn "ERROR: Can't open: $!\n";
    print $mailer $msg;
    $mailer->close();
  };
  if ($@) {
    logError("Failed to email report: $@\n");
  } else {
    print "Email report sent to $to_address\n" if $debug;
  }
}

sub getLdapConfigValue {
  my $attrib = shift;
  my ($val,$err);
  $val = getLdapServerConfigValue($attrib);
  $val = getLdapGlobalConfigValue($attrib) if ($val eq "");
  logError("Failed to lookup $attrib\n") if ($val eq "");
  return $val;
}

sub getLdapServerConfigValue {
  my $attrib = shift;
  my ($val,$err);
  my ($rfh,$wfh,$efh,$cmd,$rc);
  my $server = $localxml->{key}->{zimbra_server_hostname}->{value};
  chomp($server);
  $rfh = new FileHandle;
  $wfh = new FileHandle;
  $efh = new FileHandle;
  $cmd = "/opt/zimbra/bin/zmprov -l gs $server $attrib";
  my $pid = open3($wfh,$rfh,$efh, $cmd);
  unless(defined($pid)) {
    return undef;
  } 
  close $wfh;
  my @d = <$rfh>;
  chomp($val = (split(/\s+/, $d[-2]))[-1]);
  chomp($err = join "", <$efh>);
  waitpid($pid,0);
  if ($? == -1) {
    # failed to execute
    return undef;
  } elsif ($? & 127) {
    # died with signal 
    return undef;
  } else {
    $rc = $? >> 8;
    return undef if ($rc != 0);
  }
  return $val;
}
sub getLdapGlobalConfigValue {
  my $attrib = shift;
  my ($val,$err);
  my ($rfh,$wfh,$efh,$cmd,$rc);
  $rfh = new FileHandle;
  $wfh = new FileHandle;
  $efh = new FileHandle;
  $cmd = "/opt/zimbra/bin/zmprov -l gcf $attrib";
  my $pid = open3($wfh,$rfh,$efh, $cmd);
  unless(defined($pid)) {
    return undef;
  } 
  close $wfh;
  chomp($val = (split(/\s+/, <$rfh>))[-1]);
  chomp($err = join "", <$efh>);
  waitpid($pid,0);
  if ($? == -1) {
    # failed to execute
    return undef;
  } elsif ($? & 127) {
    # died with signal 
    return undef;
  } else {
    $rc = $? >> 8;
    return undef if ($rc != 0);
  }

  return $val;
}

sub addToReport {
  my ($line) = @_;
  push(@$report, $line);
}

sub logError {
  my $msg = shift;
  print STDERR $msg;
  return;
}

sub usage {
  print STDERR "Usage: $0 [-h] [-v] [-d] [-m]\n";
  print STDERR "-d   Add basic debug information to output.\n";
  print STDERR "-h   This usage page.\n";
  print STDERR "-v   Verbose output.\n";
  print STDERR "-m   Mail output to admin account.\n";
}
