Emacs BBDB linked with ISDNLog and ESTIC

This was the status quo: I wanted a complete Big Brother tool which - This is what I did:
  1. Turned ESTIC into a daemon, /usr/local/sbin/esticd.

    This was accomplished by some brute hacks in the ESTIC sources:

    Here are the modified sources: estic-daemon.tar.gz

    The daemon is started and stopped with this SuSE-style script, /sbin/init.d/estic:

    #! /bin/bash
    . /etc/rc.config
    case "$1" in
    	echo -n "Starting service estic"
    	(cd /etc/estic; /usr/local/sbin/esticd > /dev/null &)
    	echo -e "$return"
    	echo -n "Shutting down service estic"
    	killproc -TERM /usr/local/sbin/esticd || return=$rc_failed
    	echo -e "$return"
    	$0 stop && $0 start || return=$rc_failed
    	echo "Usage: $0 {start|stop|restart}"
    	exit 1
    test "$return" = "$rc_done" || exit 1
    exit 0

    This is my ESTIC configuration file, /etc/estic/estic.ini:

    SettingsFile    = ""
    PortName        = "ttyS1"
    ShowDateTime    = None
    ShowInfoOnStartup = no
    AreaCodeFile    = "/usr/lib/isdn/areacodes"
    CountryCode = "49"
    AreaCode = "391"
    DialPrefix = "0"
    OutgoingLog1 = "/var/log/estic-outgoing.log"
    IncomingLog1 = "/var/log/estic-incoming.log"
    OutgoingLog2 = "/dev/console"
    LogZeroCostCalls        = yes
    AliasFile       = "/etc/estic/alias.dat"
    AutoReadAliases = yes
    CronFile        = "/etc/estic/cron.dat"
    WaitAfterCall   = 700
    ShortWaitAfterMsg = on
    DiagModeUpdate  = auto
  2. Wrote a script which pops up an X window, showing information about an incoming call.

    The tricky part is to get it work even if the display is locked with XLock or the XDM login screen is active.

    1. Make XDM store the display authorization in a file with a fixed name, not a name computed at run-time. Put this in the X11/xdm/xdm-config file:
      ! We use a named authfile for :0; this allows root to take this
      ! authorization and provide funny services.
      DisplayManager._0.authFile:	/var/lib/xdm/authdir/authfiles/the-console-xauthority
    2. Make Isdnlog run a script on an incoming call. Put this into the [ISDNLOG] section of the /etc/isdn/isdn.conf file:
      	PROGRAM=/usr/local/bin/show-callerid "\$2" "\$19" "\$17"
    3. This is the script to run, /usr/local/bin/show-callerid:
      #! /bin/sh
      if [ -r /var/lib/xdm/authdir/authfiles/the-console-xauthority ]; then
          export XAUTHORITY=/var/lib/xdm/authdir/authfiles/the-console-xauthority
      export DISPLAY=:0
      # for debugging purposes:
      echo "show-callerid: NUMBER $1 ALIAS $2 LOCATION $3" > /dev/console
      if [ "$1" != "?" ]; then
          # Build the string to be shown
          if [ "$2" != "?" ]; then
              # Alias is known
      	export n="$2"
              # Alias is not known, show number and location
      	export n="$3\n$1"
          # Deactivate screen saver
          /usr/X11R6/bin/xset s reset
          /usr/X11R6/bin/xset q | grep "Monitor is Off" > /dev/null
          export o=$?
          /usr/X11R6/bin/xset dpms force on 2> /dev/null
          # Suspend xlock
          killall -STOP xlock 2> /dev/null
          # Display id
          xterm -title "Phone call from:" \
            -font "-bitstream-courier-bold-r-*-*-50-400-*-*-*-*-*-*" \
            -bg IndianRed -b 10 -fg White +sb -cr White -name callerid \
            -geometry 25x2+260+610 -e /bin/sh -c "(echo -n -e \"$n\" && read)" &
          export xtermpid=$!
          (sleep 35; kill $xtermpid 2> /dev/null; \
            killall -CONT xlock 2> /dev/null; \
            [ $o == 0 ] &&  /usr/X11R6/bin/xset dpms force off > /dev/null 2> /dev/null) &
    4. Set window-manager style for the window showing the caller information. Put this into ~/.fvwm2rc:
      Style  "callerid"	Sticky, StaysOnTop, DumbPlacement
  3. Wrote an Emacs Lisp program which extracts BBDB phone entries and writes alias files for ESTIC and Isdnlog, ~/.estic-alias and ~/.isdnlog-alias; this is ~/lisp/bbdb-phone.el.
    ;;; bbdb-phone.el --- BBDB/Isdnlog/ESTIC integration
    ;; Copyright (C) 1999 by Free Software Foundation, Inc.
    ;; Author: Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
    ;; Keywords: local
    ;; This file is NOT part of GNU Emacs.
    ;; 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; either version 2, or (at
    ;; your option) any later version.
    ;; This program is distributed in the hope that it will be useful, but
    ;; WITHOUT ANY WARRANTY; without even the implied warranty of
    ;; General Public License for more details.
    ;; You should have received a copy of the GNU General Public License
    ;; along with this program; see the file COPYING.  If not, write to
    ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    ;; Boston, MA 02111-1307, USA.
    ;;; Commentary:
    ;; BBDB & phone integration
    ;;; Code:
    (require 'bbdb)
    (defvar phone-area-prefix "391")
    (defun my-canonicalize-phone-number (number)
      "Canonicalize phone number NUMBER, which must be a string."
      (cond ((= (aref number 0) ?+) number)	; already has international prefix
    	((= (aref number 0) ?0) (concat "+49 " (substring number 1)))
    	(t (concat "+49 " phone-area-prefix number))))	 
    (defun my-localize-phone-number (number)
      "Make NUMBER ready for dialing from local site."
      (cond ((string-match (concat "^\\(\\++49[^0-9]*\\|0\\)" 
    		       number)		; make local number
    	 (substring number (match-end 0)))
    	((string-match "^\\++49[^0-9]*"
    		       number)		; make long-distance number
    	 (concat "0" (substring number (match-end 0))))
    	((string-match "^\\++"
    		       number)		; make international number
    	 (concat "00" (substring number (match-end 0))))
    	(t number)))			; number is ok
    (defun my-strip-nondigits (string)
      "Return STRING stripped of all non-digits."
      (while (string-match "[^0-9]+" string)
        (setq string (replace-match "" t t string)))
    (defun my-extract-phone-aliases-from-bbdb ()
      "Extract phone aliases from BBDB."
      (let ((records (bbdb-records))
    	(isdnlog-alias-buffer (get-buffer-create " *isdnlog-alias*"))
    	(estic-alias-buffer (get-buffer-create " *estic-alias*")))
        (while (not (null records))
          (let* ((record (car records))
    	     (phones (bbdb-record-phones record))
    	     (name (bbdb-record-name record))
    	     (comp (bbdb-record-company record))
    	      (cond ((and name comp) (concat name " - " comp))
    		    ((or name comp))
    		    (t "???"))))	
    	(while phones
    	  (let* ((phone (car phones))
    		 (location (aref phone 0))
    		 (number (aref phone 1))
    		  (concat name-comp " (" location ")")))
    	    (set-buffer isdnlog-alias-buffer)
    	    (insert "[number]\n"
    		    "NUMBER = " (my-canonicalize-phone-number number) "\n"
    		    "SI = 1\n"		; service indicator is voice
    		    "ZONE = 1\n"	; this is incorrect but who cares?
    		    "ALIAS = " name-comp-loc "\n\n")
    	    (set-buffer estic-alias-buffer)
    	    (insert (my-strip-nondigits (my-localize-phone-number number))
    		    " \"" name-comp-loc "\"\n"))
    	  (setq phones (cdr phones))))
          (setq records (cdr records)))
        (set-buffer isdnlog-alias-buffer)
        (set-buffer-file-coding-system 'latin-1-unix)
        (write-file "~/.isdnlog-alias")
        (kill-buffer isdnlog-alias-buffer)
        (set-buffer estic-alias-buffer)
        (set-buffer-file-coding-system 'latin-1-unix)
        (write-file "~/.estic-alias")
        (kill-buffer estic-alias-buffer)))
    (defvar incoming-phone-messages-file "/var/log/messages")
    (defun last-unnamed-caller (output-to-buffer-p)
      "Show the phone number of the last unnamed caller.
    With prefix argument, insert in current buffer."
      (interactive "P")
      (let ((buffer (get-buffer-create " *last-caller*"))
    	(old-buffer (current-buffer)))
        (set-buffer buffer)
        (let ((len (nth 7 (file-attributes incoming-phone-messages-file))))
          (insert-file-contents incoming-phone-messages-file nil 
    			    (max 0 (- len 20000)) len))
        (goto-char (point-max))
        (if (search-backward-regexp "Call from \\(\\+[0-9]+ [0-9]+/[0-9]+\\).*RING" nil t)
    	(if output-to-buffer-p
    	      (set-buffer old-buffer)
    	      (insert-buffer-substring buffer (match-beginning 1) (match-end 1)))
    	  (message "Last unnamed caller: %s" 
    		   (buffer-substring (match-beginning 1) (match-end 1))))
          (error "No unnamed caller."))
        (set-buffer old-buffer)
        (kill-buffer buffer)))
    (defvar outgoing-phone-messages-file "/var/log/estic-outgoing.log")
    (defun last-unnamed-destination (output-to-buffer-p)
      "Show the phone number of the last unnamed call destination.
    With prefix argument, insert in current buffer."
      (interactive "P")
      (let ((buffer (get-buffer-create " *last-caller*"))
    	(old-buffer (current-buffer)))
        (set-buffer buffer)
        (let ((len (nth 7 (file-attributes outgoing-phone-messages-file))))
          (insert-file-contents outgoing-phone-messages-file nil 
    			    (max 0 (- len 20000)) len))
        (goto-char (point-max))
        (if (search-backward-regexp "Called \\([0-9]+\\) with" nil t)
    	(if output-to-buffer-p
    	      (set-buffer old-buffer)
    	      (insert-buffer-substring buffer (match-beginning 1) (match-end 1)))
    	  (message "Last unnamed call destination: %s" 
    		   (buffer-substring (match-beginning 1) (match-end 1))))
          (error "No unnamed call destination."))
        (set-buffer old-buffer)
        (kill-buffer buffer)))
    ;;; bbdb-phone.el ends here
  4. This is a one-line shell script which runs this program in a batch-mode Emacs, /usr/local/bin/frobnicate-phone-aliases:
    #! /bin/sh
    emacs -q -batch -l ~/lisp/bbdb/bbdb.el -l ~/lisp/bbdb-phone.el \
       -f my-extract-phone-aliases-from-bbdb -kill
  5. This shell script, /usr/local/sbin/make-phone-aliases, calls frobnicate-phone-aliases for some users and concatenates the generated user alias files and the system alias templates, /etc/estic/alias.dat.in and /etc/isdn/callerid.conf.in, creating the system alias files, /etc/estic/alias.dat and /etc/isdn/callerid.conf.
    #! /bin/sh
    # These users can generate .isdnlog-alias and .estic-alias files 
    # from their Emacs/BBDB.
    # So let them generate these files.
    for a in $PHONE_ALIAS_USERS; do
        su -c /usr/local/bin/frobnicate-phone-aliases $a
    # Write the ESTIC aliases
    if [ -f /etc/estic/alias.dat ]; then
        mv /etc/estic/alias.dat /etc/estic/alias.dat.orig
    (echo "; Automagically generated by $0; do not edit."; \
     cat /etc/estic/alias.dat.in; \
     for a in $PHONE_ALIAS_USERS; do \
         echo "; From ~$a/.estic-alias:"; \
         cat /home/$a/.estic-alias; \
     done) > /etc/estic/alias.dat
    # Write the Isdnlog aliases
    if [ -f /etc/isdn/callerid.conf ]; then
        mv /etc/isdn/callerid.conf /etc/isdn/callerid.conf.orig
    (echo "# Automagically generated by $0; do not edit."; \
     cat /etc/isdn/callerid.conf.in; \
     for a in $PHONE_ALIAS_USERS; do \
         echo "# From ~$a/.isdnlog-alias:"; \
         cat /home/$a/.isdnlog-alias; \
     done) > /etc/isdn/callerid.conf
  6. This shell script, /usr/local/sbin/update-phone-aliases, rebuilds the alias files and restarts Isdnlog, so that the changes take effect. Nothing special must be done for ESTIC because it reads its alias file at every call.
    #! /bin/sh
    ## Rebuild the phone alias files
    ## Restart isdnlog
    kill -HUP $(cat /var/run/isdnlog.isdnctrl0.pid)
  7. A cron job calls /usr/local/sbin/update-phone-aliases as root every day.
Matthias Koeppe
$Id: index.html,v 1.4 2005/02/20 20:43:03 mkoeppe Exp $