#!/usr/skunk/bin/gawk -f #!/usr/bin/awk -f # mlookup: tell what a mail alias will expand to. # @(#) mlookup.awk 2.1 94/06/05 # 92/09/27 John DuBois (john@armory.com) # Was a shell script that invoked mail. # Rewrote as #!awk script; if mail is used, # filter out "unknown address" and "nameserver timeout"; # added checkaddr capability & made it the default; # removed 'whatis' link because whatis is now an OS command # 92/10/25 Added expansion of foo-outbound@list-processor and filtered lists # 93/06/08 Close Cmd in CheckAddrExpandName() # 93/12/07 Changed name from whois to mlookup; conflicted with whois command # 94/03/09 Use gawk so - options can be given & /dev/stderr used # 94/06/05 Added x option BEGIN { Name = "mlookup" Usage = "Usage: " Name " [-hoax] mail-address ..." ARGC = Opts(Name,Usage,"hoax",1) if (Options["h"]) { print \ Name ":: tell what mail addresses will expand to.\n" \ Usage "\n" \ "Options:\n" \ "-h prints this help.\n" \ "-o prints one address per line. The default is to print as many as will\n" \ " fit in 79 columns.\n" \ "-a uses mail to look up addresses instead of checkaddr.\n" \ " mail lookup displays names as they are recorded in mailing lists.\n" \ " The default is to use checkaddr, which does a more thorough expansion\n" \ " of aliases, determining who mail will actually be sent to.\n"\ "-x turns on debugging." exit(0) } if (Options["a"]) { FS=",[ \t]*" # Get rid of these MakeSet(BadNames,"(Nameserver Timeout),(Unknown address)," \ "(Nameserver,Timeout),(Unknown,address),",",") } else FS="'" Debug = "x" in Options for (AliasNum = 1; AliasNum < ARGC; AliasNum++) { if (ARGC > 2) { if (AliasNum > 1) print "" print ARGV[AliasNum] ":" } # Empty Addrs[] split("",Addrs) if (ExpandName(ARGV[AliasNum],Options["a"],BadNames,Addrs)) PrintAddrs(Addrs,Options["o"]) } } function ExpandName(Alias,UseMail,BadNames,Addrs, NotAddr,Addr,Elem) { # Convince awk that Addrs[] is an array delete Addrs[""] DebugPrint("ExpandName looking up \"" Alias "\"") if (UseMail) { MailExpandName(Alias,Addrs) SubtractSet(Addrs,BadNames) } else { Recurse = 1 if ((NotAddr = CheckaddrExpandName(Alias,Addrs)) != "") { printf "%s",NotAddr return 0 } } for (Addr in Addrs) { if (Addr ~ /^mmdf\|\/local\/admin\/listfilter /) { split(Addr,Elem,"[ \t]+") delete Addrs[Addr] if (!ExpandName(Elem[2] "-outbound",UseMail,BadNames,Addrs)) return 0 } } return 1 } function CheckaddrExpandName(Alias,Addrs, Cmd,GoodAddr,NotAddr,Addr,Ret,Line,LocAddrs) { Cmd = "/usr/mmdf/bin/checkaddr -w " Alias " 2>&1" delete Addrs[""] DebugPrint("CheckaddrExpandName looking up \"" Alias "\"") while ((Cmd | getline) == 1) { if ($0 ~ /via/) { Addr = $(NF - 1) DebugPrint("Address returned for " Alias ": " Addr) if (Addr in LocAddrs) { # This seems to happen randomly DebugPrint("Found " Addr " in " Alias " again; line was:\n"\ $0) continue } LocAddrs[Addr] if (Addr ~ "-outbound@list-processor$") { if (Addr == Alias "@list-processor") { DebugPrint("Found " Addr " in alias " Alias "; line was:\n"\ $0) continue } DebugPrint("This mailing list is handed by the list channel") # Limit to 3 levels of recursion to detect loops if (Recurse > 3) NotAddr = NotAddr "Bad mailing list: " Addr "\n" else { gsub("@list-processor$","",Addr) Recurse++ if ((Ret = CheckaddrExpandName(Addr,Addrs)) == "") GoodAddr = 1 else NotAddr = NotAddr Ret "\n" Recurse-- } } else { Addrs[Addr] GoodAddr = 1 } } else # Lines that don't end in ' are informational NotAddr = NotAddr $0 "\n" } close(Cmd) if (GoodAddr == 1) return "" else return NotAddr } function MailExpandName(Alias,Addrs, Cmd,AliasPat,i) { DebugPrint("MailExpandName looking up \"" Alias "\"") # Set save, and then unset it, to avoid complaints # about "undefined variable". Don't want it set because # mail will save a null message into dead.letter if save is set. Cmd = "echo '~:set save nosave\n~A " Alias "\n~q' | mail -s '' /dev/null" # If an error occurs, the first & only line written will begin # with the name queried on AliasPat = "^ *" Alias " \\(" Cmd | getline if ($0 ~ AliasPat) { print $0 close(Cmd) return } sub(" +","") # Get rid of leading spaces # Process the first line read for (i = 1; i <= NF; i++) if ($i != "") Addrs[$i] while ((Cmd | getline) == 1) { sub(" +","") # Get rid of leading spaces for (i = 1; i <= NF; i++) if ($i != "") Addrs[$i] } close(Cmd) } function PrintAddrs(Addrs,OnePerLine, i,NumAddrs,List,k,Num,Lines) { NumAddrs = qsort_by_index(Addrs,k) # Generate sorted map if (OnePerLine) { for (i = 1; i <= NumAddrs; i++) print k[i] return } List = k[1] for (Num = 2; Num <= NumAddrs; Num++) List = List ", " k[Num] # Last name may be followed by this message without being # separated from it by a comma sub("\\(Nameserver Timeout\\)","",List) Num = SplitLine(List,Lines,79,"^ ") for (i = 1; i <= Num; i++) print Lines[i] } # Splits Line into lines with maximum length of MaxLen. # If WordChars is non-empty, words are split on characters other than # those in the set it describes, if possible. # The default for WordChars is: a-zA-Z0-9_ # Lines are split on a non-alphanum character if possible. # The lines are put into Lines with indices starting with 1. # The number of lines put in Lines is returned. function SplitLine(Line,Lines,MaxLen,WordChars, Len,LineNum,MaxLine) { if (WordChars == "") WordChars = "a-zA-Z0-9_" WordChars = "[" WordChars "]" LineNum = 0 while (Len = length(Line)) { Lines[++LineNum] = substr(Line,1,MaxLen) if (substr(Line,MaxLen,2) ~ "^" WordChars WordChars) { MaxLine = Lines[LineNum] sub(WordChars "*$","",MaxLine) if (MaxLine != "") Lines[LineNum] = MaxLine } Line = substr(Line,length(Lines[LineNum]) + 1) } return LineNum } function DebugPrint(S) { if (Debug) print("*** " S) > "/dev/stderr" } # Deletes any elements that are in both Minuend and Subtrahend from Minuend. function SubtractSet(Minuend,Subtrahend, Elem) { for (Elem in Subtrahend) delete Minuend[Elem] } # MakeSet: make a set from a list. # An index with the name of each element of the list # is created in the given array. # Input variables: # Elements is a string containing the list of elements. # Sep is the character that separates the elements of the list. # Output variables: # Set is the array. function MakeSet(Set,Elements,Sep, Num,Names) { Num = split(Elements,Names,Sep) for (; Num; Num--) Set[Names[Num]]; } # Arr is an array of values with arbitrary indices. # Array k is returned with numeric indices 1..n. # The values in k are the indices of array arr, # ordered so that if array arr is stepped through # in the order arr[k[1]] .. arr[k[n]], it will be stepped # through in order of the values of its indices. # The return value is the number of elements in the array (n). function qsort_by_index(arr,k, ArrInd,end) { end = 0 for (ArrInd in arr) k[++end] = ArrInd; qsort_num_ind(k,1,end); return end } # qsort_num_ind: sort an array # arr is the array to be sorted. # It must have contiguous numeric indexes. # start and end are the starting and ending indexes of the range to be sorted. function qsort_num_ind(arr,start,end, left,right,sepval,tmp,tmpe,tmps) { # handle two-element case explicitely for a tiny speedup if ((start - end) == 1) { if ((tmps = arr[start]) > (tmpe = arr[end])) { arr[start] = tmpe arr[end] = tmps } return } left = start; right = end; sepval = arr[int((left + right) / 2)] while (left < right) { while (arr[left] < sepval) left++ while (arr[right] > sepval) right-- if (left <= right) { tmp = arr[left] arr[left++] = arr[right] arr[right--] = tmp } } if (start < right) qsort_num_ind(arr,start,right) if (left < end) qsort_num_ind(arr,left,end) } # @(#) ProcArgs 1.1 93/09/26 # 92/02/29 john h. dubois iii # 93/07/18 Added "#" arg type # 93/09/26 Don't count +h against MinArgs # optlist is a string which contains all of the possible command line options. # If a character is followed by a colon, # it indicates that that option takes an argument. # If a character is followed by a pound sign (#), # it indicates that that option takes an integer argument. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" or # "+" is not taken to be an option string (it is not acted on). # If an option takes an argument, the argument may either immedately # follow it or be given separately. # If an option that does not take an argument is given, # an index with its name is created in options and its value is set to "1". # If an option that does take an argument is given, # an index with its name is created in options and its value # is set to the value of the argument given for it. # Options and their arguments are deleted from argv. # Note that this means that there may be gaps # left in the indices of argv[]. # If compress is nonzero, argv[] is packed by moving its elements so that # they have contiguous integer indices starting with 0. # argv[0] is not examined. # An argument of "--" or "++" stops the scanning of argv[]. # The number of arguments left in argc is returned. # If an error occurs, # the string OptErr is set to an error message and -1 is returned. function ProcArgs(argc,argv,optlist,options,compress, ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos) { # ArgNum is the index of the argument being processed. # ArgsLeft is the number of arguments left in argv. # Arg is the argument being processed. # ArgLen is the length of the argument being processed. # ArgInd is the position of the character in Arg being processed. # Option is the character in Arg being processed. # Pos is the position in optlist of the option being processed. ArgsLeft = argc for (ArgNum = 1; ArgNum < argc; ArgNum++) { Arg = argv[ArgNum] if (Arg ~ "^[-+]") { if ((Arg == "-") || (Arg == "+")) continue delete argv[ArgNum] ArgsLeft-- if ((Arg == "--") || (Arg == "++")) break ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) Pos = index(optlist,Option) if (!Pos) { OptErr = "Invalid option: -" Option return -1 } if ((ArgType = substr(optlist,Pos + 1,1)) ~ "[:#]") { if (ArgInd < ArgLen) { options[Option] = substr(Arg,ArgInd + 1) ArgInd = ArgLen } else { if (ArgNum < (argc - 1)) { options[Option] = argv[++ArgNum] delete argv[ArgNum] ArgsLeft-- } else { OptErr = "Option -" Option " requires an argument." return -1 } } if (ArgType == "#" && options[Option] !~ "^[0-9]+$") { OptErr = \ "Option -" Option " requires an integer argument." return -1 } } else options[Option] = 1 } } } if (compress != 0) PackArr(argv,ArgsLeft) return ArgsLeft } # Packs Arr to indices starting with 0 # Num should be the number of elements in Arr function PackArr(Arr,Num, NewInd,OldInd) { NewInd = OldInd = 0 for (; Num; Num--) { while (!(OldInd in Arr)) OldInd++ if (NewInd != OldInd) { Arr[NewInd] = Arr[OldInd] delete Arr[OldInd] } OldInd++ NewInd++ } } # Opts: Process command line arguments. # Opts processes command line arguments using ProcArgs() # and checks for errors. If an error occurs, a message is printed # and the program is exited. # # Input variables: # Name is the name of the program, for error messages. # Usage is a usage message, for error messages. # OptList the option description string, as used by ProcArgs(). # MinArgs is the minimum number of non-option arguments that this # program should have, non including ARGV[0] and +h. # If the program does not require any non-option arguments, # MinArgs should be omitted or given as 0. # Global variables: # The command line arguments are taken from ARGV[]. # The arguments that are option specifiers and values are removed from # ARGV[], leaving only ARGV[0] and the non-option arguments. # The number of elements in ARGV[] should be in ARGC. # After processing, ARGC is set to the number of elements left in ARGV[]. # The option values are put in Options[]. # On error, Err is set to 1 so it can be checked for in an END block. # Return value: The number of elements left in ARGV is returned. function Opts(Name,Usage,OptList,MinArgs, ArgsLeft) { if (MinArgs == "") MinArgs = 0 ArgsLeft = ProcArgs(ARGC,ARGV,OptList,Options,1) if ((ArgsLeft + ("h" in Options)) < (MinArgs+1)) { if (ArgsLeft != -1) OptErr = "Not enough arguments" print Name ": " OptErr ". Use -h for help." print Usage Err = 1 exit 1 } return ArgsLeft }