#!/usr/bin/awk -f # @(#) mailalert.awk 1.1 94/03/24 # 92/03/20 John DuBois (john@armory.com) # 92/11/07 Added Uncontrol filtering # 93/02/01 Separated utty and id into functions # 94/02/27 Arrange for process to be killed eventually if one of the tty # writes hangs. # 94/03/14 Close kill command. # 94/03/24 Added nobeep option # Example of .maildelivery line, assuming this program has been put in # /usr/lib/maildelivery.awk: # * - pipe R /usr/bin/awk -f /usr/lib/mailalert.awk nobeep=1 BEGIN { User = id() if (!utty(User,TTYs)) exit(0) GetMailStat(MStat) MakeUncontrolTable() for (tty in TTYs) if (TTYs[tty]) NumWritable++ else delete TTYs[tty] # TTY open may hang in certain circumstances due to modem control, etc. # Give writes 5 seconds per tty. if (NumWritable) KillMe(5*NumWritable) for (tty in TTYs) biff("/dev/" tty,MStat,!nobeep) } # Arrange for this process to be killed in Sec seconds. # Stores PID of backgrounded shell in global KillPID, and also returns it, # so that this can be aborted. function KillMe(Sec, Cmd) { # Use ksh because it has $PPID. # Exec it so $PPID will be correct. # Go into background as a subshell within ksh because if the exec'ed # ksh is run in the background it will have a different $PPID since it # will have to be forked to go into the background, # while () preserves $PPID even when put in the background. # Echo $! so we will have a PID to kill to abort this. # Use newline to separate because ; doesn't work with &. Cmd = sprintf("exec /bin/ksh -c '(sleep %d; kill -9 $PPID 2>/dev/null)&\n"\ "echo $!'", Sec) Cmd | getline KillPID close (Cmd) return KillPID } #function AbortKill() { # return system("kill -9 " KillPID) #} # utty: find ttys a user is logged in on. # For each tty User is logged in on, an element is created in TTYs[]. # The index is the name of the tty, with a leading "/dev/". # The value is set to 1 if the user is writable on that tty, 0 if not. # The number of ttys the user is logged in on is returned. function utty(User,TTYs, Cmd,Count) { Cmd = "who -T" Count = 0 while ((Cmd | getline) == 1) if ($1 == User) { if ($2 == "+") TTYs[$3] = 1 else TTYs[$3] = 0 Count++ } close(Cmd) return Count } # id returns the login name of the user who owns the current process function id( Cmd,line,elem) { Cmd = "/usr/bin/id" Cmd | getline line split(line,elem,"[()]") close(Cmd) return elem[2] } function biff(tty,MStat,beep) { if (beep) printf "\007" > tty printf "\r\nNew mail (%d lines):\r\n",MStat["Lines"] > tty if ("From" in MStat) printf "%s\r\n",Uncontrol(MStat["From"]) > tty if ("Subject" in MStat) printf "%s\r\n",Uncontrol(MStat["Subject"]) > tty close(tty) } function GetMailStat(MStat, InHeader,LineCt) { InHeader = 1 LineCt = 0 while (getline == 1) { LineCt++ if (InHeader) { if (!NF) { InHeader = 0 continue } if ($1 == "From:") MStat["From"] = $0 else if ($1 == "Subject:") MStat["Subject"] = $0 } } MStat["Lines"] = LineCt } # Uncontrol(S): Convert control characters in S to symbolic form. # Characters in S with values < 32 are converted to the form ^X. # The resulting string is returned. # Global variables: the array UncTable must be initialized to a # lookup table translating characters 0..31 to symbolic values # before using this function. function Uncontrol(S, i,len,Output) { len = length(S) Output = "" for (i = 1; i <= len; i++) Output = Output UncTable[substr(S,i,1)] return Output } # MakeUncontrolTable: Make a table for use by Uncontrol(). # Global variables: # UncTable[] is made into a character -> symbolic character lookup table. function MakeUncontrolTable( i) { for (i = 0; i < 32; i++) UncTable[sprintf("%c",i)] = "^" sprintf("%c",i + 64) for (i = 32; i < 127; i++) UncTable[sprintf("%c",i)] = sprintf("%c",i) UncTable[sprintf("%c",127)] = "^?" for (i = 128; i < 256; i++) UncTable[sprintf("%c",i)] = "\\" sprintf("%03o",i) }