#!/usr/local/bin/perl -w
# vim:aw:

# nsc.pl - curses-based console monitor for Netsaint
# 000206shj 
# $Id: nsc.pl,v 1.24 2000/03/06 18:07:41 shj Exp $


# --- Bugs ----------------------------------------------------------------
#  o Chg-time sometimes shows '*undef*' (from tdif)
#  o Background colours sometimes don't, depends on terminaltype + emulator


# --- TODO ----------------------------------------------------------------
#  o configurable beep-interval?
#  o configurable colours
#  o different sort types?
#  o complain if status.log doesn't get updated (changed?)
#  o record/history feature that is browseable?
#  o mc-like line graphics?
#  o search for string.. (highlight when found)
#  o collapse hosts, use cursor-bar to uncollapse -- or auto-collapse, when
#    the screen is full (first hosts with all 'ok' svcs, etc.)
#  o autosizing column headings and fields (based on longest node lenght, etc.)
#  o regexps to always filter out uninteresting services (zombies,
#    postgres, ..) as long as they have 'ok' status
#  o details display for full (uncut) service info msg + *all* 
#    status.log fields


# --- Stuff ---------------------------------------------------------------

use Curses;
#use strict;


# --- Options and their default values ------------------------------------

my $VERSION = "v0.51";

my $fnConfig = "$ENV{'HOME'}/.nsc.conf";

my %NSC_KEYWORDS = (
   'nslog',    '/usr/local/var/netsaint/status.log',
   'hostcfg',  '/usr/local/etc/netsaint/hosts.cfg',
   'reloadcmd','/usr/local/etc/rc.d/netsaint.sh reload',
   'showall',  '1',
   'details',  '1',
   'reverse',  '0',
   'bell',     '1',
   'debug',    '0',
   'upd_freq', '2',
   'version',  $VERSION
);


# --- Global variables ----------------------------------------------------

my $num_msglines;
my $first_msgline;
my $lastBeep = 0;
my $listoffs = 0;
my %AT = ();
my $myself;

my @montab = (
   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );

my %sVals = (
      'CRITICAL',    0,
      'HOST DOWN',   1,
      'UNREACHABLE', 2,
      'WARNING',     3,
      'UNKNOWN',     4,
      'RECOVERY',    5,
      'OK',          6,
      'PENDING',     7
      );

$ENV{'VISUAL'} = $ENV{'EDITOR'} if !defined($ENV{'VISUAL'});
$ENV{'VISUAL'} = 'vi' if !defined($ENV{'VISUAL'});
$ENV{'PAGER'} = 'more' if !defined($ENV{'PAGER'});


# --- Curses replacement functions ----------------------------------------

sub mvaddch {
   local($y,$x,$ch) = @_;
   move($y,$x);
   addch($ch);
}

sub mvaddstr {
   local($y,$x,$str) = @_;
   move($y,$x);
   addstr($str);
}


# --- Configuration -------------------------------------------------------

sub SaveConfig {

   open(CFG, ">$fnConfig") || die("Can't open $fnConfig for writing: $!");
   foreach $ky (sort keys %NSC_KEYWORDS) {
      print CFG "$ky=$CFG{$ky}\n";
      }
   close(CFG);

} #SaveConfig


sub LoadConfig {

   %CFG = ();

   #TODO: Version-check on loaded configfile?
   if (open(CFG, "$fnConfig")) {
      while (<CFG>) {
         chomp;
         ($kw,$val) = split(/=/);
         if (defined($NSC_KEYWORDS{$kw})) {
            $CFG{$kw} = $val;
         } else {
            print STDERR "Invalid keyword $kw\n";
            }
         } #eof CFG
      close(CFG);
      }

   #apply default values
   foreach $kw (keys %NSC_KEYWORDS) {
      $CFG{$kw} = $NSC_KEYWORDS{$kw}
         if (!defined($CFG{$kw}));
      }

   #override, cause this version to written when exiting
   $CFG{'version'} = $VERSION;

} #LoadConfig


# --- Miscellany ----------------------------------------------------------

sub numeric {
   return 0 if (!defined($_[0]) || ($_[0] eq ''));
   return ($_[0] =~ /^\d+$/);
}


sub xcmd {
   erase(); refresh(); endwin();
   system $_[0];
   screen_init();
   drawScreen();
} #xcmd


# This is some of my weirder code
# Return difference between two time_t's as a string
# (ex. '1h23m40s'..)
sub tdif {

   my $dif;

   return '*undef*' if (!$_[0] || !$_[1]);

#   #oh well..
#  $dif = ($_[0]>$_[1])?$_[0]-$_[1]:$_[1]-$_[0];                

# same shit, different code:
   $dif = $_[0]-$_[1];
   $dif = -$dif if ($dif<0);

   return "now"
      if ($dif == 0);

   my $res = '';
   my %sv = (
      'Y', 365*24*60*60,
      'M', 30*24*60*60,
      'W', 7*24*60*60,
      'd', 24*60*60,
      'h', 60*60,
      'm', 60);
   my $details = 0;
   foreach $key ('Y','M','W','d','h','m') {
      if ($dif > $sv{$key}) {
         $i = int($dif / $sv{$key});
         $dif %= $sv{$key};
         $res .= "${i}$key";
         $details++;
         }
      last if ($details >= 2);
      }
   $res .= "${dif}s"
      if (($dif > 0) && ($details < 2));
   if (defined($_[2])) {   #wants just first element?
      #retain only first component
      $res = $1 if ($res =~ /^(\d+[a-z])\d/);
      }
   return $res;

} #tdif


# --- Low level "graphics" ------------------------------------------------

sub AT {
   die "$_[0] is not a defined colour" if !defined($AT{$_[0]});
   return $AT{$_[0]};
}


sub rdKey {
   move($BOTLINE,$COLS-1);
   refresh;
   timeout(1000*$_[0]) if defined($_[0]);
   getch();
} #rdKey


# at(int x, int y, const char *str)
sub at {

   my($x,$y,$str) = @_;

   if (substr($str,0,1) eq '!') {         #blank rest of line?
      $str = substr($str, 1);
      $str .= (' ' x ($COLS - length($str) - $x));
   } elsif (substr($str,0,1) eq '>') {    #relative to right side of display?
      $str = substr($str, 1);
      $x = $COLS - length($str) - $x;
      }

   #please control yourself or I will (we never wrap)
   $str = substr($str, 0, ($COLS-$x))
      if (length($str) > ($COLS-$x));

   mvaddstr($y,$x,$str);
  return length($str);

} #at


# aat(int x, int y, attrib, const char *str)
sub aat {
  attron($_[2]);
  at($_[0], $_[1], $_[3]);
  attroff($_[2]);
  return length($_[3]);
}


sub botline {
   aat(0, $BOTLINE, AT('botline'), "!$_[0]");
}


sub mLine {
   my ($line,$attrib,$msg) = @_;
   $attrib = AT('normal') if (!defined($attrib));
   aat(0, $line+$first_msgline, $attrib, "!$msg");
} #mLine


# --- Static screen stuff -------------------------------------------------

sub drawScreen {

   if ($opt_colour) {
      start_color();
      #colorpair 0 resets to zero?
      init_pair(0, COLOR_WHITE, COLOR_BLUE);
      $AT{'xxskod'} = COLOR_PAIR(0);
      init_pair(1, COLOR_WHITE, COLOR_BLUE);
      $AT{'normal'} = COLOR_PAIR(1);
      $AT{'notok'} = $AT{'normal'} | A_BOLD;
      init_pair(3, COLOR_WHITE, COLOR_RED);
      $AT{'critical'} = COLOR_PAIR(3);
      init_pair(4, COLOR_WHITE, COLOR_RED);
      $AT{'noisy'} = COLOR_PAIR(4);
      $AT{'dim'} = $AT{'normal'};
      $AT{'heading'} = $AT{'normal'} | A_BOLD;
      $AT{'total'} = $AT{'normal'};
      $AT{'botline'} = $AT{'normal'};
      init_pair(9, COLOR_WHITE, COLOR_YELLOW);
      $AT{'warning'} = COLOR_PAIR(9) | A_BOLD;
      $AT{'isok'} = $AT{'normal'};
   } else {
      $AT{'botline'} = A_BOLD;
      $AT{'normal'} = A_NORMAL;
      $AT{'notok'} = A_BOLD;
      $AT{'critical'} = A_REVERSE;
      $AT{'noisy'} = (A_REVERSE|A_BOLD|A_BLINK);
      $AT{'dim'} = A_DIM;
      $AT{'heading'} = A_UNDERLINE;
      $AT{'total'} = A_BOLD;
      $AT{'warning'} = A_BOLD;
      $AT{'isok'} = A_NORMAL;
      }

   #force update with background colour
   attron(AT('normal'));
   erase();
   for ($i=0; $i<$LINES; $i++) {
      aat(0, $i, AT('normal'), ' ' x $COLS);
      }
   refresh();

   aat(0, 0, AT('dim'), "NetSaint Console $VERSION");

} #drawScreen


sub screen_init {

   initscr(); cbreak(); noecho();
   nonl();
#&stdscr=warns, $stdscr=errs
#   intrflush(&stdscr, $FALSE);
#   keypad(&stdscr, $TRUE);

   $opt_colour = has_colors();

   $num_msglines = $LINES - 5;
   $first_msgline = 2;
   $BOTLINE = $LINES-1;

} #screen_init


# --- Help ----------------------------------------------------------------

sub help {

   my @HLP = (
      '  h     This help',
      '  Space Next page',
      '  a     Toggle between showing all services and troubled ones',
      '  d     Toggle service details on/off',
      '  +/-   Increase/decrease display update frequency',
      '  q     Quit',
      '  r     Reverse sort order',
      '  g     Toggle bell on/off',
      '  ^L    Redraw screen',
      '  C     Toggle colour on/off',
      '  T     Run top(1)',
      '  V     View status.log',
      '  E     Edit hosts.cfg and reload',
      '',
      'nsc was written by Stig H. Jacobsen, <goth@pobox.com>',
      '',
      'Check http://pobox.com/~goth/nsc/ for new versions'
      );
   
   drawScreen();

   my $i;
   for ($i=0; defined($HLP[$i]) && ($i<$BOTLINE); $i++) {
      mLine($i, AT('normal'), "$HLP[$i]");
      }

   botline("Press any key to continue..");

   #should be enough for the most slow user..
   rdKey(60);

} #help


# --- Main loop -----------------------------------------------------------

sub by_prior {

   my @a = split(/;/, $a);
   my @b = split(/;/, $b);

   print "undef $a[3]\n" if !defined($sVals{$a[3]});
   print "undef $b[3]\n" if !defined($sVals{$b[3]});

   if (($i = ($sVals{$a[3]} - $sVals{$b[3]})) == 0) {
      if (($i = ($a[1] cmp $b[1])) == 0) {
         my $t;
         $a[0] =~ /\[(\d+)\]/;
         $t = $1;
         $b[0] =~ /\[(\d+)\]/;
         if (numeric($1) && numeric($t)) {
            $i = ($1 - $t);
         } else {
            $i = 0;
            }
         }
      }
   $i = -$i if ($CFG{'reverse'});
   return $i;

} #by_prior

sub sort_state {
   my @state = @_;
   @state = sort by_prior @state;
   return @state;
}


# --- Display single service line
sub dispService {
   my ($line,$state) = @_;
   my @s = split(/;/, $state);
   #my $tm = shift(@s);
   my ($f1,$node,$service,$status,$attempts,$x1,$x2,$x3,$change) = @s;

   my $serv_info = $s[$#s];

   return 0 if (!$CFG{'showall'} && ($status eq 'OK'));

   #get rid of html tags
   my $i1; my $i2;
   while ((($i1 = index($serv_info, '<')) >= 0) &&
          (($i2 = index($serv_info, '>')) >= 0) &&
          ($i2 > $i1) ) {
      $serv_info = substr($serv_info, 0, $i1) . substr($serv_info, $i2+1);
      }

   if ($f1 =~ /\[(\d+)\]/) {
      $date = tdif(time, $1, 'y');
   } else {
      $date = '';
      }

   if ($status eq 'OK') {
      $attrib = AT('isok');
   } else {
      if ($status eq 'CRITICAL') {
         $attrib = AT('critical');
      } elsif ($status eq 'WARNING') {
         $attrib = AT('warning');
      } else {
         $attrib = AT('notok');
         }
      if ( ($status !~ /^(RECOVERY|OK|PENDING)$/) &&
           ($CFG{'bell'} && ((time - $lastBeep) > 180)) ) {
         beep();
         $lastBeep = time;
         }
      }

   if ($CFG{'details'}) {
      my $s = '';
      if (numeric($change)) {
         $s = &tdif(time,$change,'y');
         }
      my $chg = "$date/$s";
      my $dispnode = $node;
      $dispnode = ' .' if (($node eq $lastNode) && ($status eq $lastState));

      my $dispstate = $status;
      $dispstate = ' .' if (($node eq $lastNode) && ($status eq $lastState));

      #show coloured status
      $i = aat(0, $line+$first_msgline, AT('normal'),
               sprintf("%-8.8s %-12.12s ", $dispnode,$service ));
      $i += aat($i, $line+$first_msgline, $attrib, sprintf("%-8.8s", $dispstate));
      $i += aat($i, $line+$first_msgline, AT('normal'),
                sprintf("! %7.7s %5.5s %s  ", $chg,$attempts,$serv_info));
#      aat($i, $line+$first_msgline, AT('normal'),
#          ' ' x ($COLS-$i));
   } else {
      mLine($line, $attrib,
            sprintf("%-14.14s %s", "$node:$service",$serv_info));
      }

   ($lastNode,$lastState) = ($node,$status);

   return 1;

} #dispService


# --- Counts, sorts & weeds servicelist
sub getServiceList {
   my @ol = @_;
   my @res = ();
   my $s;

   foreach $s (@ol) {
      my @s = split(/;/, $s);
      if ($s[0] =~ / SERVICE$/) {
         push(@res, $s);
#         $longest_node = length($s[1])
#            if (length($s[1]) > $longest_node);
#         $longest_service = length($s[2])
#            if (length($s[2]) > $longest_service);
         $sState{$s[3]}++;
      } elsif ($s[0] =~ / HOST$/) {
         $hState{$s[2]}++;
      } elsif ($s[0] =~ / PROGRAM$/) {
         #[1002743057] PROGRAM;1002058577;2180;1;ACTIVE;1002058577;0;1002405600;1;1;1;0;0
         $pgmUp = tdif(time, $s[1],'y');
         $pgmState = $s[4];
         }
      } #foreach @ol

   return sort_state(@res);

} #getServiceList


# ---

sub showServiceList {

   my ($NSLOG) = @_;

   if (!open(STATUS, $NSLOG)) {
      botline("*** Can't open statuslog $NSLOG: $!");
      return;
      }
   my @sysState = <STATUS>;
   close(STATUS);

#   my $longest_node = 0,
#      $longest_service = 0;
   $hState{'UP'} = 0;
   $hState{'DOWN'} = 0;
   $hState{'UNREACHABLE'} = 0;
   $sState{'OK'} = 0;
   $sState{'CRITICAL'} = 0;
   $sState{'WARNING'} = 0;
   $sState{'UNKNOWN'} = 0;
   $sState{'PENDING'} = 0;
   $sState{'HOST DOWN'} = 0;

   #count states, lengths of hosts, services
   my @svcList = getServiceList(@sysState);

   # --- Show netsaint update/state
   my $s = '';
   if (defined($pgmState)) {
      if ($pgmState eq 'ACTIVE') {
         $s .= '  run ';
      } else {
         $s .= '  STOPPED '."($pgmState)";
         }
      }
   $s .= "$pgmUp "
      if (defined($pgmUp));
   $s =~ s/\s+$//g;
   aat(0, 1, ($pgmState eq 'ACTIVE')?AT('dim'):AT('noisy'), ">$s $myself");

   # --- Show
   if ($CFG{'details'}) {
      mLine(0, AT('heading'),
            sprintf("%-8.8s %-12.12s %-8.8s %-7.7s %5.5s %s",
         'Node','Service','Status','Upd/Chg','Tries','Service information'));
   } else {
      mLine(0, AT('heading'),
            sprintf("%-14.14s %s", 
                    'Node:Service',
                    'Service information'));
      }

   ($lastNode,$lastState) = ('','');

   my $i = $listoffs;
   my $num_shown;

   if (!defined($svcList[$i])) {
      $i = $listoffs = 0;                    #reset as needed
      }

   for ($num_shown=0; ($num_shown<($num_msglines-1)) && defined($svcList[$i]); $i++) {
      $num_shown++ if (dispService($num_shown + 1, $svcList[$i]));
      }

   my $msg = '';
   if (($num_shown == ($num_msglines-1)) && defined($svcList[$i+1])) { 
      $msg = 'more';
      }
   if ($listoffs > 0) {
      $msg .= ',' if ($msg ne '');
      $msg .= "+$listoffs";
      }
   mLine($num_msglines+$first_msgline-2, AT('normal'), "($msg)")
      if ($msg ne '');

   for ($i=$num_shown+1; $i<$num_msglines; $i++) {
      mLine($i, undef, '~');
      }

   # --- Show total scores
   my $s1 = sprintf(">Nodes up/dn/unreach: %d/%d/%d",
                    $hState{'UP'},
                    $hState{'DOWN'},
                    $hState{'UNREACHABLE'});
   aat(1, $BOTLINE-1, AT('total'), $s1);

   $s1 = sprintf(">Svcs ok/warn/crit/other: %d/%d/%d/%d",
                 $sState{'OK'}, $sState{'WARNING'}, $sState{'CRITICAL'},
                 $sState{'UNKNOWN'} + $sState{'PENDING'} + $sState{'HOST DOWN'});
   aat(1, $BOTLINE, AT('total'), $s1);

} #showServiceList


# --- Main code here ------------------------------------------------------

$| = 1;           #unbuffered tty i/o
&LoadConfig();

chomp($myself = `hostname`);
$myself =~ s/\..*$//g;

{
   my @s = getpwuid($>);
   $myself = $s[0] . '@' . $myself;
}

my $opt_logfile = defined($ARGV[0])?$ARGV[0]:undef;

if (defined($opt_logfile)) {
   die "$opt_logfile: $!"
      if (! -f $opt_logfile);
} else {
   while (! -f $CFG{'nslog'}) {
      print "\nCan't open Netsaint status.log ($CFG{'nslog'})!\n" .
            "Enter location of status.log: ";
      chomp($CFG{'nslog'} = <STDIN>);
   } #can't open
}

screen_init();
&drawScreen();

do {

   #show curr config before status draw (clreol)
   $s = sprintf("!%s/%s/%s/%s/%ds    ", 
                ($CFG{'showall'}?'All':'all'),
                ($CFG{'details'}?'Det':'det'),
                ($CFG{'bell'}?'Bell':'bell'),
                ($CFG{'reverse'}?'Rev':'rev'),
                $CFG{'upd_freq'});
   aat(0, $BOTLINE-1, AT('botline'), $s);
   
   #main, live, moving, squirming display!
   showServiceList(defined($opt_logfile)?$opt_logfile:$CFG{'nslog'});

   my @l = localtime(time);
   my $s = sprintf(">Last updated: %s %02d %02d:%02d:%02d",
                   $montab[$l[4]], $l[3], $l[2], $l[1], $l[0]);
   aat(0, 0, AT('dim'), $s);

   if ($key = rdKey($CFG{'upd_freq'})) {
      botline('');                  #clear message on input (?)
      }

   # --- Keys handling
   if ($key eq 'a') {
      $CFG{'showall'} = !$CFG{'showall'};
   } elsif ($key eq 'd') {
      $CFG{'details'} = !$CFG{'details'};
   } elsif ($key eq ' ') {
      $listoffs += ($num_msglines-1);
   } elsif ($key eq 'D') {
      $CFG{'debug'} = !$CFG{'debug'};
   } elsif ($key eq 'g') {
      $CFG{'bell'} = !$CFG{'bell'};
   } elsif ($key eq 'r') {
      $CFG{'reverse'} = !$CFG{'reverse'};
   } elsif ($key eq '+') {
      $CFG{'upd_freq'} *= 2;
      botline(sprintf("Update frequency set to %.f seconds", $CFG{'upd_freq'}));
   } elsif ($key eq '-') {
      $CFG{'upd_freq'} /= 2;
      $CFG{'upd_freq'} = 0.5 if ($CFG{'upd_freq'} < 0.5);
      botline(sprintf("Update frequency set to %.f seconds", $CFG{'upd_freq'}));
   } elsif ($key eq sprintf("%c", ord('L')-64)) {
      drawScreen();
   } elsif ($key eq 'C') {
      if (!$opt_colour && !has_colors()) {
         botline("Your terminal can't show colours");
      } else {
         $opt_colour = !$opt_colour;
         drawScreen();
         }
   } elsif ($key eq 'T') {
      xcmd("top");             #debug
   } elsif ($key eq 'V') {
      xcmd("$ENV{'PAGER'} $CFG{'nslog'}");             #debug
   } elsif ($key eq 'E') {
      xcmd("$ENV{'VISUAL'} $CFG{'hostcfg'}; echo Enter to reload ..; 
           read; $CFG{'reloadcmd'}");             #debug
   } elsif ($key =~ /[h?]/) {
      help();
   } elsif ($key eq 'q') {
      botline("Please come back soon");
   } else {
      if ($CFG{'debug'}) {
         botline(sprintf("($num_msglines,$first_msgline,$LINES) (%d/%d/%d)", ord($key),
                        AT('normal'),AT('botline')));
      } else {
         botline(sprintf("Press 'h' for help"));
         }
      }
  
   } while ($key ne 'q');

endwin;
&SaveConfig();
print "\n";
exit 0;


# --- End of file ---------------------------------------------------------

__END__

[949803251] SERVICE;dax;processes;WARNING;3/3;HARD;949803551;1;949802658;WARNING;0;0;0;0;949802658;1;Absent processes: dhcpd
                                                                         state?
                                                               last chg
                                                             enabled
                                                   next check
                                              hard/soft
                                          tries
                                  state
                        servname
                    node
            status type
last updated

[949803458] PROGRAM;949802365;0;ACTIVE;949802365;949803267;0;949803458;0
[949803297] HOST;core;UP;949802386;0;0;0;0;1;(Host assumed to be up)
[949803291] SERVICE;core;ping;OK;1/3;HARD;949803591;1;949802386;OK;0;0;0;0;0;1;<A HREF='/cgi-bin/netsaint/traceroute.cgi?195.24.26.34'>PING ok - Packet loss = 0%, RTA = 4.30 ms</A>
[949803327] SERVICE;dax;smtp;OK;1/3;HARD;949803627;1;949802425;OK;0;0;0;0;0;1;SMTP ok - 0 second response time
[949803367] SERVICE;dax;dns;OK;1/3;HARD;949803667;1;949802467;OK;0;0;0;0;0;1;DNS ok - 0 seconds response time, Address(es) is/are 204.71.200.75, 204.71.200.67, 204.71.200.74, 204.71.202.160
[949803451] SERVICE;dax;Zombie Processes;OK;1/3;HARD;949803751;1;949802547;OK;0;0;0;0;0;1;OK - 0 processes with Z status
[949803191] SERVICE;dax;lavd;OK;1/3;HARD;949803491;1;949802587;OK;0;0;0;0;0;1;load average: 1.60, 1.33, 1.29
[949803230] SERVICE;dax;root free space;OK;1/3;HARD;949803530;1;949802627;OK;0;0;0;0;0;1;Disk ok - 513500 kB (26%) free on /dev/hda8
[949803270] SERVICE;dax;vol0 free space;OK;1/3;HARD;949803570;1;949802667;OK;0;0;0;0;0;1;Disk ok - 3901908 kB (26%) free on /dev/hda5
[949803309] SERVICE;dax;smb service;OK;1/3;HARD;949803609;1;949802407;OK;0;0;0;0;0;1;Disk ok - 3.72G (24%) free on \\localhost\lyde
[949803348] SERVICE;dax;swap usage;OK;1/3;HARD;949803648;1;949802446;OK;0;0;0;0;0;1;Swap ok - Swap used:  2% (47923200 bytes out of 1620295680)
[949803251] SERVICE;dax;processes;WARNING;3/3;HARD;949803551;1;949802658;WARNING;0;0;0;0;949802658;1;Absent processes: dhcpd
[949802806] SERVICE;dax;postgresql;UNKNOWN;3/3;HARD;949804006;1;949802817;UNKNOWN;0;0;0;0;949802817;1;/usr/local/netsaint/libexec/check_pgsql: Database Name (pdb_homes) is not valid!
[949803454] SERVICE;dax;inet link;WARNING;1/60;SOFT;949803514;1;949802606;OK;0;0;0;0;0;1;Link is up (1 channel)
[949803433] SERVICE;dax;TEST;OK;1/1;HARD;949803493;1;949802645;OK;0;0;0;0;0;1;OK (imapd=1247)
