#!/usr/local/bin/perl

use strict;
use warnings;
use DBI;
use CGI qw(-nosticky);
use Mail::VRFY;
use Captcha::reCAPTCHA;
use Crypt::PasswdMD5 qw(unix_md5_crypt);

my $q = CGI->new();

my $me = $ENV{SCRIPT_NAME};

my $title = 'SMSpy';
my $style = '/site.css';
my %captcha = ( private => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
                public  => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
              );
my $HOME = '/home/hostedsites/www.jeremykister.com';
my $file = "${HOME}/data/sql/smspy.db";
my $dsn = "DBI:SQLite:dbname=$file";
my $dbh = DBI->connect($dsn);
$dbh->do( 'PRAGMA foreign_keys = ON' );


my $sess_id = $q->cookie('sess_id');
unless( $sess_id ){
    my ($random,$buf,$x);
    open(D, '/dev/urandom') || die "cannot open /dev/urandom: $!\n";
    my @set = ('A'..'Z', 'a'..'z', '0'..'9', '_', '.');
    for(1..32){
        sysread(D, $buf, 1);
        my $v = ord($buf);
        $x ^= ($v & ~63) >> (rand(7)+1);
        $random .= $set[ ($x ^ ord($buf)) & 63 ]; 
    }       
    my $cookie = $q->cookie(-name=>'sess_id',-value=>$random,-expires=>'+1M');
    print $q->header(-cookie=>[$cookie],-expires=>'now'), "\n",
          $q->start_html(-title=>$title,-style=>{-src=>$style}), "\n",
          $q->start_form('post',$me), "\n",
          qq{ <table border="0">
              <tr>
               <td>username:</td>
               <td><input type="text" name="username" maxlength="255"></td>
              </tr>
              <tr>
               <td>password:</td>
               <td><input type="password" name="password" maxlength="255"></td>
              </tr>
             </table>
             <font size="1"><a href="${me}?signup=1">Sign Up</a></font><br><br>\n },
          $q->submit('Login'), "\n",
          $q->end_form(), "\n",
          $q->end_html();
    exit;
}

if( my $manage=$q->param('manage') ){

    my $sql = q{ SELECT addr,name,devices.id
                   FROM devices,sessions,authorization
                  WHERE sessions.cred_id = authorization.c_id
                    AND devices.id = authorization.d_id
                    AND sessions.sess_id = ? };
    my $sth = $dbh->prepare($sql);
    $sth->execute($sess_id);
    my %devices;
    while(my $row=$sth->fetchrow_arrayref){
         $devices{$row->[0]} = { name => $row->[1],
                                 id   => $row->[2],
                               };
    }

    if( my $device=$q->param('device') ){
        my $name=$q->param("label_${device}") || die "confused";

        my $sql = q{ UPDATE devices
                        SET name = ?
                      WHERE id   = ? };
        $dbh->do( $sql,undef,($name,$devices{$device}{id}) );

        print $q->redirect("${me}?manage=${manage}");
        exit;
    }elsif( (my $wifi=$q->param('wifi')) && (my $addr=$q->param('addr')) ){
        my $sql = 'SELECT id FROM devices WHERE addr = ? AND wifi = ?';
        my $sth = $dbh->prepare($sql);
        $sth->execute($addr,$wifi);
        my $row = $sth->fetchrow_arrayref;
        my $new_did = $row->[0];

        unless( $new_did ){
            # QQQ need HTML
            print "error - device not found.\n";
            exit;
        }
        $sql = q{ SELECT c.id
                    FROM credentials c, sessions s
                   WHERE s.cred_id = c.id
                     AND s.sess_id = } . $dbh->quote($sess_id);
        my $aref = $dbh->selectrow_arrayref( $sql );
        my $c_id = $aref->[0];

        $sql = 'INSERT INTO authorization (c_id,d_id) VALUES (?,?)';
        $dbh->do( $sql,undef,($c_id,$new_did)) ;

        print $q->redirect("${me}?manage=${manage}");
        exit;
    }

    print $q->header(-expires=>'now'),
          $q->start_html(-title=>$title,style=>$style), "\n",
          $q->start_form('post',$me), "\n",
          $q->hidden('manage',$manage), "\n",
          q{ <table border="1">
              <tr><td align="center">
               <b>Current Devices</b>
              </td></tr>
              <tr><td>
               <table border="0">
                <tr>
                 <th>Number</th>
                 <th>Name</th>
                </tr> };
    for my $d (sort keys %devices){
        print qq{
            <tr>
             <td>
              <input type="radio" name="device" value="$d"> $d
             </td><td>
               <input type="text" name="label_$d" value="$devices{$d}{name}" maxlength="50">
             </td>
            </tr> };
    }
    print "  <tr>\n",
          '   <td align="left">', "\n",
          $q->submit('Delete'), "\n",
          '   </td><td align="center">', "\n",
          $q->submit('Save'), "\n",
          "   </td>\n",
          "  </tr>\n",
          " </table>\n",
          $q->end_form(), "\n",
          " </tr></td>\n",
          "</table>\n",
          "<br />\n",
          $q->start_form('post',$me), "\n",
          $q->hidden('manage','add'), "\n";

    print <<__EOM__
 <table border="1">
  <tr><td align="center">
   <b>Add Device</b>
  </td></tr>
  <tr><td>
   <table border="0">
    <tr><td><b>Device Details</b></td></tr>
    <tr>
     <td>Phone Number:</td>
     <td><input type="text" name="addr" maxlength="10"></td>
    </tr>
    <tr>
     <td>Last four of Wi-Fi MAC Addr (e.g. ABC1)</td>
     <td><input type="text" name="wifi" maxlength="5"></td>
    </tr>
   </table>
__EOM__
    ;
    print "<center>\n",
          $q->submit('Add'), "\n",
          q{   </center>
              </td>
             </tr>
            </table> },
          $q->end_form(), "\n",
          "<br>\n",
          '<h1><a href="', $me, '">Return to Main Page</a></h1>', "\n",
          $q->end_html;
    exit;

}elsif( $q->param('signup') ){
    my $c = Captcha::reCAPTCHA->new;

    print $q->header(-expires=>'now'),
          $q->start_html(-title=>$title,style=>$style), "\n";

    if( (my $addr=$q->param('addr')) && (my $wifi=$q->param('wifi')) &&
        (my $un=$q->param('un'))     && (my $pw=$q->param('pw')) &&
        (my $email=$q->param('email')) ){
        
        my $challenge = $q->param('recaptcha_challenge_field');
        my $response  = $q->param('recaptcha_response_field');

        my $result = $c->check_answer( $captcha{private}, $ENV{REMOTE_ADDR},
                                       $challenge, $response,
         );

        my @err;
        unless( $result->{is_valid} ){
            push @err, "Invalid Security Code";
        }

        push @err, "Warranty agreement is required." unless $q->param('warranty');
        push @err, "Consent to full release is required." unless $q->param('release');
        push @err, "Indemnification agreement is required." unless $q->param('indemnify');

        my $pn = 1 if $pw =~ /[a-z]/;
        $pn++ if $pw =~ /[A-Z]/;
        $pn++ if $pw =~ /[0-9]/;
        $pn++ if $pw =~ /[^a-zA-Z0-9]/;
        unless( $pn >= 3 ){
            push @err, "pn: $pn";
            push @err, q{ password must use at least 3 of the 4 groups:<br>
                          * lower case letters<br>
                          * upper case letters<br>
                          * numbers<br>
                          * special characters (e.g.: !@#$%^&*)<br> };
        }
        unless( length($pw) > 7 ){
            push @err, "password must be at least 7 characters.\n";
        }
        unless( $un =~ /^[a-z0-9]{5,255}$/i ){
            push @err, q{ problem with username.<br>
                          use only lower case letters and numbers<br>
                          at least five characters long.<br> };
        }
        unless( $addr =~ /^\d{10}$/ ){
            push @err, "problem with phone number.\n";
        }

        unless( $wifi =~ /^[0-9A-F]{2}:?[0-9A-F]{2}$/i ){
            push @err, "problem with wifi.  enter last four, like ABCD\n";
        }

        if( my $code = Mail::VRFY::CheckAddress($email) ){
            my $english = Mail::VRFY::English($code);
        
            push @err, "problem with email address: $english\n";
        }

        if( @err ){
            for my $e (@err){
                print "ERROR: $e <br>\n";
            }
            exit;
        }

        my $sql = 'SELECT id FROM devices WHERE addr = ? AND wifi = ?';
        my $sth = $dbh->prepare($sql);
        $sth->execute($addr,$wifi);
        my $row = $sth->fetchrow_arrayref;
        my $new_did = $row->[0];

        unless( $new_did ){
            print "error - please install SMSpy on your iDevice before creating this account.\n";
            exit;
        }

        my $aref = $dbh->selectrow_arrayref(
         q{ SELECT COUNT(*) FROM credentials 
             WHERE un = } . $dbh->quote($un) .
              ' OR email = ' . $dbh->quote($email) );
        if( $aref->[0] ){
            print "error: please choose different username and/or email addr.\n";
            exit;
        }

        my $crypt = unix_md5_crypt($pw);
        $dbh->do( 'INSERT INTO credentials (un,pw,email) VALUES (?,?,?)', undef, ($un,$crypt,$email) );
        
        $aref = $dbh->selectrow_arrayref( 'SELECT id FROM credentials WHERE un = ' .
                                          $dbh->quote($un) );
        my $c_id = $aref->[0];

        $dbh->do( 'INSERT INTO authorization (c_id,d_id) VALUES (?,?)', undef, ($c_id,$new_did) );
        
        print qq{ <h1>SMSpy Account Configured.</h1>
                  click <a href="${me}">here</a> to log in.\n },
              $q->end_html();
        exit;
    }

    print $q->start_form('post',$me), "\n",
          q{ <h1>SMSpy Signup Page</h1>
             <table border="0">
              <tr><td><b>Device Details</b></td></tr>
              <tr>
               <td>Phone Number:</td>
               <td><input type="text" name="addr" maxlength="10"></td>
              </tr>
              <tr>
               <td>Last four of Wi-Fi MAC Addr (e.g. ABC1)</td>
               <td><input type="text" name="wifi" maxlength="5"></td>
              </tr>
              <tr><td colspan="2">&nbsp;</td></tr>
              <tr><td><b>Login Details</b></td></tr>
              <tr>
               <td>Username:</td>
               <td><input type="text" name="un" maxlength="255"></td>
              </tr>
              <tr>
               <td>Password:</td>
               <td><input type="password" name="pw" maxlength="255"></td>
              </tr>
              <tr>
               <td>Email:<br>
                <font size="1">Never used for spam; only technical issues.</font>
               </td>
               <td><input type="text" name="email" maxlength="255"></td>
              </tr>
             </table>
             <br>
             <b>Legal Details</b><br>
             <br>
             <font size="1">
              <input type="checkbox" name="warranty">
              I understand this service is provided without warranty.
              <br>
	      <input type="checkbox" name="release">
              I consent to full release and hold harmless any/all maintainers,
              designers, owners, etc. of this service/product.
              <br>
              <input type="checkbox" name="indemnify">
              I agree to reimburse/indemnify <a href="http://jeremy.kister.net/">Jeremy Kister</a>
              for any and all costs associated with misuse or abuse of this product.
             </font>
             <br>
             <br> },
          $c->get_html($captcha{public}), "\n",
          "<br>\n",
          $q->submit('signup'), "\n",
          $q->end_form(), "\n",
          $q->end_html();
    
    exit;
}elsif((my $login=$q->param('username')) && (my $password=$q->param('password'))){
    # check
    my $sql = 'SELECT id,pw FROM credentials WHERE un = ' . $dbh->quote($login);
    my $sth = $dbh->prepare($sql);
    $sth->execute;
    my $row=$sth->fetchrow_arrayref;
    my $lid = $row->[0];
    my $crypt = $row->[1];
    (my $salt = $crypt) =~ s/^\$1\$//;

    print $q->header(-expires=>'now'), $q->start_html(-title=>$title,style=>$style);
    if(($crypt eq unix_md5_crypt($password, $salt)) && defined($crypt)){
        # good
        $dbh->do( 'INSERT INTO sessions (cred_id,sess_id,date) VALUES (?,?,?)', undef, ($lid,$sess_id,time()) );
        print qq{ <meta http-equiv="refresh" content="0; url=${me}">\n };
    }else{
        print q{ Invalid username/password.<br>
                 Click <a href="mailto:i_forgot_my_password1@jeremykister.com">here</a>
                 if you forgot your username/password.</a><br> };
            warn "JK -> bad un/pw: [${login}/${password}] from $ENV{REMOTE_ADDR}\n";
    }
    print $q->end_html();
    exit;
}else{
    my $sql = 'SELECT COUNT(*) FROM sessions WHERE sess_id = ' . $dbh->quote($sess_id);
    my $sth = $dbh->prepare($sql);
    $sth->execute;
    my $row=$sth->fetchrow_arrayref;
    if( ($row->[0] == 1) && (! $q->param('logout')) ){ # logout = hack.  meh.
        print $q->header(-expires=>'now');
    }else{
        my $cookie = $q->cookie(-name=>'sess_id',-value=>'',-expires=>'now');
        print $q->header(-cookie=>[$cookie],-expires=>'now'),
              qq{ <html><head><title>${title}</title>
                  <meta http-equiv="refresh" content="0; url=${me}">
                  </head><body>Redirecting.. },
              $q->end_html();
        exit;
    }
}

# user is logged in.

print $q->start_html(-title => $title,
                     -style => $style,
                     -bgcolor => '#d3d3d3',
                    ), "\n",
      '<table width="100%" border="0">', "\n",
      " <tr>\n";
    
# what is he authorized to see?
my $uid = $q->param('device');
print $q->start_form('post',$me), "\n",
      q{<td>Device:</td>
        <td>
         <SELECT NAME="device" ONCHANGE="this.form.submit();">
                <OPTION VALUE=""></OPTION>\n };

my $sql = q{ SELECT uid,addr,name
               FROM devices,sessions,authorization
              WHERE sessions.cred_id = authorization.c_id
                AND devices.id = authorization.d_id
                AND sessions.sess_id = ?
           ORDER BY addr ASC };
my $sth = $dbh->prepare($sql);
$sth->execute($sess_id);
while(my $row=$sth->fetchrow_arrayref){
    my $label = $row->[2] ? "$row->[1] ($row->[2])" : $row->[1];
    print '  <OPTION  ';
    print 'SELECTED ' if( $uid eq $row->[0] );
    print 'VALUE="' . $row->[0] . '">' . $label . "</OPTION>\n";
}
print "   </SELECT>\n",
      "  </td>\n",
      $q->end_form(), "\n",
      '  <td align="right">', "\n",
      '   <a href="' . "${me}?manage=1" . '">manage</a>', "\n",
      "  </td>\n",
      " </tr>\n";

unless( $uid ){
    print "</table>\n",
          $q->end_html();
    exit;
}


print " <tr>\n";

my $contact = $q->param('contact') || '';
print $q->start_form('post',$me), "\n",
      $q->hidden('device',$uid), "\n",
      qq{ <td>Contact:</td>
          <td>
           <SELECT NAME="contact" ONCHANGE="this.form.submit();">
            <OPTION VALUE=""></OPTION>\n };

$sql = q{ SELECT m.contact, c.name
            FROM messages m
            JOIN devices d ON m.d_id = d.id
       LEFT JOIN contacts c ON ( c.d_id = d.id AND c.contact = m.contact )
           WHERE d.uid = ?
        GROUP BY m.contact
        ORDER BY MAX(m.rowid) DESC };
$sth = $dbh->prepare($sql);
$sth->execute($uid) || die $sth->errstr;
while(my $row = $sth->fetchrow_arrayref){
    my $display = $row->[1] || $row->[0];
    print '  <OPTION ';
    print 'SELECTED ' if( $contact eq $row->[0] );
    print 'VALUE="', $row->[0] . '">' . $display . "</OPTION>\n";
}

print "   </SELECT>\n",
      "  </td>\n",
      '  <td align="right">', "\n",
      '   <a href="' . "${me}?logout=1" . '">logout</a>', "\n",
      "  </td>\n",
      " </tr>\n",
      $q->end_form(),
      "</table>\n";

unless( $contact ){
    print $q->end_html();
    exit;
}

$sql = q{ SELECT c.name, c.imguri
            FROM contacts c, devices d
           WHERE c.d_id = d.id
             AND c.contact = ?
             AND d.uid = ? };
$sth = $dbh->prepare($sql);
$sth->execute($contact,$uid);
my $row = $sth->fetchrow_arrayref;
my $from = $row->[0];
my $imguri = $row->[1];

my $pretty = $contact;
$pretty =~ s/^(\d{3})(\d{3})/$1.$2./;

print "<br>\n",
      "Contact:\n";
if( $imguri ){
    print '<br><img src="', $imguri, '"><br>', "\n";
}
print "$from " if $from;
print "($pretty) <br>\n",
      "<hr>\n",
      "<br>\n";

my %attachments;
$sql = q{ SELECT a.m_rowid, a.file
            FROM attachments a
            JOIN devices d ON d.id = a.d_id
           WHERE d.uid = } . $dbh->quote($uid);
$sth = $dbh->prepare($sql);
$sth->execute;
while(my $row = $sth->fetchrow_arrayref){
    push @{ $attachments{$row->[0]} }, $row->[1];
}

$sql = q{ SELECT rowid,date,datea,type,text
            FROM devices d
            JOIN messages m ON m.d_id = d.id
           WHERE d.uid = ?
             AND m.contact = ?
        ORDER BY rowid DESC };
$sql .= ' LIMIT 500' unless $q->param('more');
$sth = $dbh->prepare($sql);
$sth->execute($uid,$contact);

print q{ <table border="0" cellspacing="0" width="100%">
          <tr>
           <td></td><td></td><td></td>
          </tr> }, "\n";
while( my $row = $sth->fetchrow_hashref ){
    my $date = $row->{datea} || $row->{date};
    my ($sec,$min,$hour,$day,$mon,$year) = (localtime($date))[0,1,2,3,4,5];
    my $period = 'AM';
    if( $hour > 11 ){
        $hour -= 12 unless $hour == 12;
        $period = 'PM';
    }
    my $time = sprintf( '%d.%02d.%02d %02d:%02d:%02d %s',
                        $year+1900, $mon+1, $day,
                        $hour, $min, $sec, $period,
                      );

    print " <tr>\n";
    if( $row->{type} eq 'sent' ){
        print '  <td></td><td colspan="2" bgcolor="#B8B8B8" align="right">', "\n";
    }else{
        print '  <td colspan="2" bgcolor="#C8C8C8">', "\n";
    }

    print qq{ <div title="$time">\n };
    for my $attachment (@{ $attachments{$row->{rowid}} }){
        if( $attachment =~ /(.+)\.(mov|3gp|mp4)$/i ){
            my $preview = $1 . '-preview.jpg';
            my $label = (-f "${HOME}/www/sms/$uid/$row->{rowid}/$preview") ?
                        '<img src="/sms/' . "$uid/$row->{rowid}/$preview" . '" width="80" height="60">' :
                        '[-MOVIE-]';
            print qq{    <a href="/sms/$uid/$row->{rowid}/$attachment">$label</a><br>\n };
        }else{
            my $thumb = (-f "${HOME}/www/sms/$uid/$row->{rowid}/thumb-${attachment}") ?
                        "$uid/$row->{rowid}/thumb-${attachment}" :
                        "$uid/$row->{rowid}/${attachment}";
            print qq{    <a href="/sms/$uid/$row->{rowid}/${attachment}" target="_blank">
                  <img src="/sms/$thumb" height="60" width="80">
                  </a><br>\n };
        }
    }
    if( $row->{text} ){
        my $text = (length($row->{text}) > 400) ?
                   substr($row->{text}, 0, 397) . '...' :
                   $row->{text};

        $text =~ s( \b((?:https?|www\.|[A-Za-z0-9]+\.[a-zA-Z]+/)\S*) )
                  (<a href="$1" target="_blank">$1</a>)gx;
        while( $text =~ /href="(\S+)\."/ ){
            $text =~ s#href="(\S+)\."#href="$1"#g;
            $text =~ s#\.</a>#</a>.#g; # url that ends a sentance.  no period in url.
        }
        while( $text =~ m#href="((?:www\.)?[A-Z0-9]+\.[a-z]{2,4})#i ){
            my $uri = $1;
            # <a href="foo/bar"> -> ok
            # <a href="/foo/bar"> -> ok
            # <a href="http://foo.com/bar"> -> ok
            # <a href="foo.com"> -> notok, need http://
            # <a href="foo.com/bar"> -> notok, need http://
            $text =~ s#href="$uri#href="http://$uri#; 
        }
        $text =~ s( (\S+\@\S+\.\S+) )
                  (<a href="mailto:$1">$1</a>)gx;
        $text =~ s( [^A-Za-z0-9_]\@(\S+) )
                  ( <a href="https://twitter.com/$1" target="_blank">\@$1</a>)gx;
        $text =~ s/â¤/<3/g;    # heart
        $text =~ s/ðŸ˜œ/:)/g; # smiley
        $text =~ s/ï¿¼//;         # = file attachment
        #$text =~ s/?\‚.?/ /g;    # a space.  wtf.

        #$text =~ s/[^[:ascii:]]//g;

        $text =~ s/-Sent free from TextNow.com//;

        # can't \S{40} because of a URL being broken up
        # and below i a hack:  really want to ignore all href=" ... "
        $text =~ s/([^(?:href=")]{40})/$1 /ig;
        $text =~ s/([a-z0-9]{40})/$1 /ig;
        #$text =~ s/(\S{40})/$1 /ig;

        print "  $text\n";
    }
    print "   </div>\n";

    if( $row->{type} eq 'sent' ){
        print "  </td>\n";
    }else{
        print "  </td><td></td>\n";
    }
    print " </tr>\n",
          "<tr><td></td></tr>\n"; # put a white line between many sent/rcvd messages
}
print "</table>\n",
      "<br>\n";
unless( $q->param('more') ){
    print qq{ <a href="${me}?device=${uid}&contact=${contact}&more=1"><b>Load More</b></a>\n };
}
print qq{
 <br><br>
 <center><font size="1">
 $title Copyright &copy 2013 <a href="http://jeremy.kister.net/">Jeremy Kister</a>.<br>
 Service provided without warranty.<br>
 Usage implies consent to full release and hold harmless any/all maintainers, designers, owners, etc. of this service/product.<br>
 User agrees to reimburse/indemnify <a href="http://jeremy.kister.net/">Jeremy Kister</a> for any and all costs associated with misuse or abuse of this product.<br>
 </font></center>
},
 $q->end_html();
