1 #!/usr/local/bin/perl5 -w
3 # Caller-ID-logger, by jwz (19-Jan-97)
6 # Opens the modem and waits for it to print caller-ID data. When it does,
7 # it logs it to a file, parses it, and pops up a window using "xmessage".
8 # If the number is present in your .bbdb file, it shows the name (or company)
12 # My caller ID service (in San Francisco) only ever sends numbers, not names,
13 # so I've never seen a "name" line come in; I assume that it would send both
14 # a name and a number, so it would be nice to present both (with error
15 # checking against BBDB) but the code as currently structured only handles
16 # one-line-per-call. It should realize that consecutive lines with the same
17 # timestamp are the same call.
19 # Modems other than ZyXELs have different caller-ID formats, and this doesn't
22 ##############################################################################
24 # Some variables you might want to set...
27 # Set this to the device that your modem is attached to.
29 $modem_device = "/dev/ttyd1";
31 # This is your .bbdb file. (Set it to null if you don't want to do BBDB
32 # lookups at all, but why would you want to go and do a thing like that?)
34 $bbdb_file = "$ENV{HOME}/.bbdb";
36 # A shell command to use to cause emacs to pop up the BBDB buffer
37 # (bbdb-srv.pl is a good choice, so it defaults to the value of the
38 # shell environment variable $NS_MSG_DISPLAY_HOOK.)
40 $bbdb_cmd = $ENV{NS_MSG_DISPLAY_HOOK};
42 # If you want the $bbdb_cmd to be run on a different host, set it here.
46 # If you want each call to be logged to a file as well, name it here.
48 $logfile = "/usr/spool/fax/log/cid-log";
53 # How to pop up a dialog box.
55 $xmessage_cmd = "xmessage";
56 @xmessage_args = ("-display", ":0",
58 # roughly centered on my screen; YMMV.
59 "-geometry", "+400+400",
60 "-xrm", "*Font: -*-new cent*-bold-r-normal-*-240-*",
61 "-xrm", "*Foreground: black",
62 "-xrm", "*Background: lightgreen",
63 # no buttons on the window: dismiss it by clicking in it.
65 "-xrm", "*form.Translations: #override <BtnDown>: exit(0)",
66 "-xrm", "*Command.Font: -*-new cent*-bold-r-normal-*-120-*",
67 "-xrm", "*Command.horizDistance: 130"
70 # Uh, let's turn off the screensaver before popping up the window.
72 $pre_dialog_cmd = "xscreensaver-command -deactivate";
75 # commands (and their expected responses) used to initialize the modem.
77 @modem_init = ( "AT", "OK", # ping
79 "ATE0", "OK", # don't echo commands
80 "ATM0", "OK", # turn off speaker
81 "ATN0", "OK", # turn off ringer
82 "ATS40.2=1", "OK", # turn on caller ID
86 # for diagnostics: if the modem ever asynchronously prints something that
87 # doesn't match this, we issue a warning.
89 $expected_responses = "^CALLER NUMBER" . "|" .
90 "^REASON FOR NO CALLER " . "|" .
95 ##############################################################################
97 # Talking to the serial port...
109 # Close the terminal streams before forking `cu', because otherwise
110 # it fucks around with the stty settings.
112 open(SAVEIN, "<&STDIN") || die("can't dup stdin");
113 open(SAVEOUT, ">&STDOUT") || die("can't dup stdout");
114 open(SAVEERR, ">&STDERR") || die("can't dup stderr");
119 my $cu_pid = open2( \*MODEM_IN, \*MODEM_OUT,
120 "cu -l$modem_device -s2400 2>&1");
122 # Now that cu has been launched, we can restore them.
124 open(STDIN, "<&SAVEIN") || die("can't restore stdin");
125 open(STDOUT, ">&SAVEOUT") || die("can't restore stdout");
126 open(STDERR, ">&SAVEERR") || die("can't restore stderr");
131 # The following doesn't seem to work and I don't know why...
133 # Set up a signal handler to try and kill off the cu process
134 # when we exit, instead of waiting ~30 seconds for it to notice
135 # that the pipe is gone...
137 # $SIG{INT} = sub { my $signame = shift;
139 # print STDERR "sending $signame to $cu_pid\n";
141 # print MODEM_OUT "\r\n~.\r\n";
144 # kill ($signame, $cu_pid);
150 if ( !m/^Connected/ ) {
151 print STDERR "$0: cu printed `$_' instead of `Connected'\n";
157 $_ || die("got eof on modem");
161 $_ || die("got eof on modem");
168 my ( $command, $expected_response) = @_;
171 print STDERR "sending: $command\n";
174 print MODEM_OUT "$command\r\n";
175 my $line = read_line();
177 if ( $line eq $command ) {
179 print STDERR " got echo: reading next line too...\n";
184 if ( $line ne $expected_response ) {
185 print STDERR " got: $line ; expected: $expected_response\n";
187 print STDERR " got: $line\n";
194 my $len = $#modem_init + 1;
196 for ($i = 0; $i < $len; $i += 2) {
197 command($modem_init[$i], $modem_init[$i+1]);
201 sub handle_async_line {
204 if (!m/$expected_responses/) {
205 print STDERR "modem turd: $_\n";
207 } elsif (m/CALLER/) {
209 print STDERR "caller: $_\n";
215 print STDERR "ignored: blank line\n";
217 print STDERR "ignored: $_\n";
223 ##############################################################################
225 # Parsing BBDB and CID data...
228 sub find_bbdb_record {
229 my ( $area, $exchange, $suffix ) = @_;
231 if ( ! $bbdb_file ) {
235 # strip off leading 0's, to match the way it's stored in .bbdb.
236 $area =~ s/^0+(.)/$1/;
237 $exchange =~ s/^0+(.)/$1/;
238 $suffix =~ s/^0+(.)/$1/;
240 my $bbdb_rec = undef;
241 my $pat = "\\[\"[^\"]+\" $area $exchange $suffix (nil|[0-9]+)\\]";
243 open(BBDB, "<$bbdb_file") || die("error opening $bbdb_file: $!\n");
256 # note: global (kludge!)
259 sub make_message_string {
260 my ( $number, $date, $fn, $ln, $co, $error ) = @_;
263 my $line_prefix = " ";
264 my $line_suffix = " ";
266 # First print the date (reformatted.)
269 my ( $dotw, $mon, $day, $hr, $min, $sec, $year ) =
270 m/^([^ ]+) +([^ ]+) +([^ ]+) +([^:]+):([^:]+):([^:]+) +([^ ]+) *$/;
271 $year =~ s/^..(..)/$1/;
278 if ($hr > 12) { $hr -= 12 };
280 $date = "$hr:$min$ampm, $day-$mon-$year ($dotw)";
281 $msg = $line_prefix . $date . $line_suffix;
283 # Next print the caller name, company, or error message.
286 $msg .= "\n" . $line_prefix . $error . $line_suffix;
287 } elsif ( $co && !$fn && !$ln ) {
288 $msg .= "\n" . $line_prefix . $co . $line_suffix;
289 } elsif ( $fn || $ln ) {
290 $msg .= "\n" . $line_prefix . "$fn $ln" . $line_suffix;
293 # Next print the phone number (formatted nicely.)
300 ( $area, $exchange, $suffix ) =
301 m/^([0-9][0-9][0-9])([0-9][0-9][0-9])([0-9][0-9][0-9][0-9]+)/;
303 # note: global (kludge!)
304 $pretty_number = "($area) $exchange-$suffix";
305 $msg .= "\n" . $line_prefix . $pretty_number . $line_suffix;
313 $SIG{CHLD} = \&reaper; # loathe sysV
316 printf STDERR " (got SIG$signame...)\n";
319 while ( ( $child = waitpid(-1,WNOHANG) ),
322 printf STDERR " (pid $child exited with $?)\n";
330 $SIG{CHLD} = \&reaper;
335 print STDERR "forking for " . $_ . " at " . (localtime) . ".\n";
341 } elsif (!defined $pid) {
342 print STDERR "$0: fork failed: $!\n";
349 print STDERR "exec'ing " . $_ . " at " . (localtime) .
360 sub fork_and_exec_for_bbdb {
362 my $number = shift @cmd_list;
364 $SIG{CHLD} = \&reaper;
369 print STDERR "forking for " . $_ . " at " . (localtime) . ".\n";
375 } elsif (!defined $pid) {
376 print STDERR "$0: fork failed: $!\n";
384 print STDERR "exec'ing " . $_ . " at " . (localtime) .
387 if ( system @cmd_list ) {
388 my $cmd = "gnudoit -q '(bbdb-srv-add-phone \"$pretty_number\")'";
390 $cmd =~ s/([()\"])/\\$1/g;
391 $cmd = "rsh $bbdb_host $cmd";
401 my ( $msg, $buttonp, $number ) = @_;
403 fork_and_exec $pre_dialog_cmd;
406 fork_and_exec $xmessage_cmd, @xmessage_args, "\n$msg\n\n";
408 my @args = ( @xmessage_args, "-button", "Add To BBDB" );
409 fork_and_exec_for_bbdb $number, $xmessage_cmd, @args, "\n$msg\n\n";
413 sub pop_up_bbdb_buffer {
418 $cmd = "rsh $bbdb_host $cmd";
420 $caller =~ s/\\/\\\\/g;
421 $caller =~ s/\"/\\\\\"/g;
422 `echo "Path:\nFrom: \\\"$caller\\\" <>" | $cmd >&- 2>&- &`;
427 sub handle_cid_line {
430 my $date = localtime;
435 if (open(LOG, ">>$logfile")) {
436 print LOG "$date\t$line\r\n";
439 print STDERR "error opening $logfile: $!\n";
443 # Pull the phone number out of the message...
449 if ( m/^CALLER NUMBER/ ) {
450 ( $number ) = m/^[^:]+: *(.*) *$/;
462 if ( !$number || $number eq "" ) {
463 $error =~ tr#A-Z#a-z#;
464 $error =~ s/^REASON FOR NO CALLER (NUMBER|NAME)/Caller unknown/i;
472 ( $area, $exchange, $suffix ) =
473 m/^([0-9][0-9][0-9])([0-9][0-9][0-9])([0-9][0-9][0-9][0-9]+)/;
475 my $bbdb_rec = find_bbdb_record($area, $exchange, $suffix);
480 # This will lose if names or aliases have double-quotes in them.
481 # No doubt there's some hairier regexp magic that handles that...
482 ( $fn, $ln ) = m/^[\[]\"([^\"]*)\" *\"([^\"]*)\"/;
483 ( $junk, $junk, $junk, $co ) =
484 m/^[[](nil|\"[^\"]*\") *(nil|\"[^\"]*\") (nil|[(][^)]*[)]) \"([^\"]*)\"/;
494 my $msg = make_message_string($number, $date, $fn, $ln, $co, $error);
495 pop_up_dialog($msg, $buttonp, $pretty_number);
498 pop_up_bbdb_buffer($caller);
503 ##############################################################################
510 handle_async_line(read_line());