#!/usr/local/bin/gawk -f # @(#) awkmail.gawk 2.1 97/07/31 # 91/03/13 john h. dubois iii (john@armory.com) # 91/03/30 converted from a collection of ksh & awk programs # to ksh preliminaries & one awk program # 91/10/28 fixed incompatibilities with gawk # fixed bugs in parsing of user@machine, etc. # added alias expansion # changed to separate ksh & awk scripts when awk part got too big # 92/05/01 Changed to #!gawk script. # 93/12/09 Send local mail through rmail, not execmail, for more useful test. # Include mail to local uucp name or local domain names as local mail. # Added f option. # 93/12/13 Optimized so that mail that will only be fed to one process is not # stored, which can take a very long time in awk; it is read & # written to process. # Use vales of MAILRC and MUSHRC. # Put addressees on To: line. # 94/07/27 Added r option. # 96/03/18 Added b option. # 96/06/03 Added nix options; removed V option. # 96/09/10 Fixed x option and MUSHRC env var handling. Added t option. # 97/02/03 Added T option. # 97/02/09 Added V and L options. # 97/02/16 Added auk options. Use mail-send lib. # 97/02/18 Added o option. # 97/02/22 Added M option. # 97/02/26 Added Q option. # 97/06/08 Add X-Mailer field. # This program is used for debugging mail problems. BEGIN { Name = "awkmail" Usage = "Usage:\n"\ Name " -[abu] [-hiqQrtTw] [-V] [-s] [-x]\n"\ " [-S] [-k] [-L] [-o]\n"\ " [-M] recipient-addr ..." ARGC = Opts(Name,Usage,"abiqs:hrS;ux>tTV;wL;k>o:M:Q",1) if ((Err = ExclusiveOptions("b,u,r;x,Q",Options)) != "") { printf "Error: %s\n",Err > "/dev/stderr" Err = 1 exit(1) } TestMailMessage = "This is a test message." # the -L option does not appear to work; submit recognizes it and says it # has set the logfile to the given name, but still logs to default #"-L: Have submit log to the file instead of the logfile\n"\ #" specified in mmdftailor, or the default of /usr/mmdf/log/chan.log. Use\n"\ #" -L/dev/tty to display the information instead of writing it to a file.\n"\ #" As with -V, -L can only be used by root or mmdf.\n"\ if ("h" in Options) { printf \ "%s: send mail by various MTAs.\n"\ "%s\n"\ "Recipients is a list of user addresses to which a message is to be sent.\n"\ "Addresses must be of the form user@site, site![site! ... ]user, or user.\n"\ "site must be known to the uucp system of this system.\n"\ "In the last case, user must be a user on this system.\n"\ "Alias expansion from .mailrc is done.\n"\ "If no subject is given on the command line, a subject will be asked for.\n"\ "Therefore, if %s is used non-interactively, a subject should always\n"\ "be given on the command line.\n"\ "Mail is sent to local recipients through rmail, and to remote recipients\n"\ "through uux. \n"\ "Options:\n"\ "One of the following three options must be used:\n"\ "-b: Send mail to all recipients via submit.\n"\ "-r: Send mail to all recipients via rmail.\n"\ "-u: Send mail to local recipients via rmail, and remote recipients via uux.\n"\ "Other options:\n"\ "-a: Do not do alias expansion using .mailrc file.\n"\ "-s: Set the mail subject.\n"\ "-h: Print this help.\n"\ "-S: Set the address the mail is from for both the From_ and From:\n"\ " lines.\n"\ "-M: Sets the From: address only; overrides -S for that purpose.\n"\ "-x: Set debugging to , which should be a positive integer.\n"\ " Higher levels give more diagnostics.\n"\ "-T: Generate test mail. This prevents %s from reading the body of the\n"\ " message from the input; instead it is set to \"%s\"\n"\ " If -s is not given, the subject is set to \"Test Message\".\n"\ "-Q: Quiet operation. Do not print anything except error messages. In\n"\ " particular, when -s is used, the subject is not echoed to the output.\n"\ "The following options can only be used in conjunction with -b:\n"\ "-t: Trust the author identification. This will cause a Source-Info: field\n"\ " to be added to the header if the sender identity does not appear\n"\ " correct (without -t, submit will fail in that circumstance).\n"\ "-q: Don't return undeliverable mail. Remote systems may still return mail.\n"\ "-w: Watch submission progress and immediate deliver attempts.\n"\ "-V: Set the logging level to , which should be one\n"\ " of the MMDF log levels. These may also be referred to as 1 through 8:\n"\ " FAT logs fatal errors only. TMP logs temporary errors and fatal errors.\n"\ " GEN logs generally interesting diagnostics. BST logs basic statistics.\n"\ " FST gives full statistics. PTR gives program trace listing.\n"\ " BTR give more detail tracing. FTR gives every possible diagnostic.\n"\ " This option can only be used by root or MMDF. Attempts to use it by\n"\ " non-privileged users will result in submit giving an error, usually\n"\ " 'Invalid parameter character'.\n"\ "-k: Specify nameserver timeout period.\n"\ "-i: Deliver mail immediately, regardless of MMDF configuration.\n"\ "-o: Set options to be passed to submit. Multiple -o options\n"\ " may be given. Multiple flag options may be given together, as a\n"\ " string. Options that take a value must be given by themselves, with\n"\ " the value following the option letter in the same argument.\n"\ " Examples: -ouvc -ok10\n", Name,Usage,Name,Name,TestMailMessage exit(0) } if ("x" in Options) { Debug = Options["x"] printf "Debugging level set to %s\n",Debug > "/dev/stderr" printf "%d args left in ARGV:\n",ARGC > "/dev/stderr" for (i in ARGV) printf "arg %d: %s\n",i,ARGV[i] > "/dev/stderr" } ## Set up submit options array if ("i" in Options) { SubmitOpts["l"] SubmitOpts["n"] } if ("w" in Options) { SubmitOpts["w"] SubmitOpts["W"] } if ("q" in Options) SubmitOpts["q"] if ("t" in Options) SubmitOpts["t"] if ("k" in Options) SubmitOpts["k"] = Options["k"] if ("L" in Options) logFile = Options["L"] if (Opt2Set(Options,"o",oSet)) for (val in oSet) { c = substr(val,1,1) if ((r = SubmitParam(c)) == "") { printf "%s: Bad submit option (exiting): %s\n", Name,c > "/dev/stderr" exit 1 } else if (r "" == c) { len = length(val) for (i = 1; i <= len; i++) { c = substr(val,i,1) if ((r = SubmitParam(c)) != c) { if (r == "") { printf "%s: Bad submit option (exiting): %s\n", Name,c > "/dev/stderr" exit 1 } else { printf \ "%s: submit option '%s' takes a value; must be given separately.\n", Name,c > "/dev/stderr" exit 1 } } SubmitOpts[c] if (Debug) printf "Setting submit option %s\n",c > "/dev/stderr" } } else { SubmitOpts[c] = substr(val,2) if (Debug) printf "Setting submit option %s to %s\n", c,SubmitOpts[c] > "/dev/stderr" } } if ("V" in Options) { levList = "FAT,TMP,GEN,BST,FST,PTR,BTR,FTR" split(levList,levSet,",") if (logLevel in levSet) SubmitOpts["V"] = levSet[logLevel] else { MakeSet(levNames,levList,",") logLevel = toupper(Options["V"]) if (!(logLevel in levNames)) { printf "%s: Bad logging level: %s. Exiting.\n", Name,logLevel > "/dev/stderr" exit 1 } SubmitOpts["V"] = logLevel } printf "%s: Logging set to level %s\n", Name,SubmitOpts["V"] > "/dev/stderr" } UseSubmit = "b" in Options UseUux = "u" in Options UseRmail = "r" in Options if (!(UseSubmit || UseUux || UseRmail)) { print "Must give one of -b, -u, or -r." > "/dev/stderr" exit 1 } if (!UseSubmit && !IsEmpty(SubmitOpts)) { print "Must give -b option if any submit-specific options are used." \ > "/dev/stderr" exit 1 } ## General stuff Fields["X-Mailer"] = "AwkMail 2.1" Quiet = "Q" in Options TestMail = ("T" in Options) if ("S" in Options) { returnAddress = Options["S"] if (UseSubmit) Fields["From"] = returnAddress } if ("M" in Options) Fields["From"] = Options["M"] if (!("a" in Options)) GetAliases(Aliases,Debug > 5) split("",SeenAddrs) # for awk numAddrs = ExpandAliases(ARGV,Addrs,0,SeenAddrs,Debug > 2) # Ask for a subject if none given on cmd line; # if subject was given on cmd line, print it if ("s" in Options) subject = Options["s"] else if (TestMail) subject = "Test Mail" if (!TestMail) { if (!Quiet) printf "Subject: " if (subject == "") { if (!MesgIn()) exit 0 subject = MesgLine } else if (!Quiet) print subject if (!Quiet) print "" } if (TestMail) message = TestMailMessage if (UseSubmit) if ((Err = SubmitMessage(SubmitOpts,subject,returnAddress,Addrs, Fields,message)) != "") { print Err > "/dev/stderr" exit 1 } exit 0 } function SubmitMessage(SubmitOpts,subject,returnAddress,Addrs,Fields,Message, Cc,Bcc,Order,Cmd) { # Make awk believe these are arrays split("",Cc) split("",Bcc) split("",Order) Cmd = InitMail(Addrs,Cc,Bcc,Fields,Order,subject,returnAddress,1, SubmitOpts,Debug > 3) if (Cmd ~ "^!") return substr(Cmd,2) if (Message != "") print Message | Cmd else while (MesgIn()) print MesgLine | Cmd if (Debug) print "Closing " Cmd "..." > "/dev/stderr" close(Cmd) if (Debug) print "Done closing " Cmd "." > "/dev/stderr" return "" } function MesgIn () { if ((getline MesgLine < "/dev/stdin") == 1 && (MesgLine != ".")) return 1 else { MesgLine = "" return 0 } } # Read body of message. # If OneLine is true, only one line is read, just enough to confirm that # there will be a body to the message. # The returned buffer will not have a newline after the last line. function ReadBody(OneLine, message,LineCount) { # Read lines until EOF reached, # or a line consisting solely of "." is entered MesgLine = "" if (MesgIn()) { message = MesgLine LineCount = 1 if (!OneLine) while (MesgIn()) { message = "\n" message MesgLine LineCount++ } if (Debug > 4) printf "So far: %d lines in message body.\n", LineCount > "/dev/stderr" } if (!LineCount) print "Warning: No message body." > "/dev/stderr" return message } function GetAliases(Aliases,Verbose) { if ("MAILRC" in ENVIRON) ProcAliasFile(ENVIRON["MAILRC"],Aliases,Verbose) else if ("MUSHRC" in ENVIRON) ProcAliasFile(ENVIRON["MUSHRC"],Aliases,Verbose) else if ("HOME" in ENVIRON) ProcAliasFile(ENVIRON["HOME"] "/.mailrc",Aliases,Verbose) } function ProcAliasFile(file,Aliases,Verbose, extended,alias,lastalias) { # mailx allows "alias" or "group" alias = "(alias|group)" if (Verbose) print "Reading aliases from " file "..." while ((getline < file) == 1) { if (extended) { if ($0 ~ /\\[ \t]*$/) sub(/[ \t]*\\[ \t]*$/," ") else { if (Verbose) print lastalias ": " Aliases[lastalias] $0 extended = 0 } Aliases[lastalias] = Aliases[lastalias] $0 } else if ($1 ~ "^" alias "$") { lastalias = $2 sub("^[ \t]*" alias "[ \t]+" $2 "[ \t]+","") if ($0 ~ /\\[ \t]*$/) { sub(/[ \t]*\\[ \t]*$/," ") extended = 1 } else if (Verbose) print lastalias ": " $0 Aliases[lastalias] = $0 } } if (Verbose) print "Done reading aliases from " file "." close(file) } # ExpandAliases() is passed a set of addresses in Addrs[], which have integer # indexes starting with 1. They are copied to NewAddrs[] with indexes starting # with NewAddrsInd. Aliases are expanded recursively. The final value of # NewAddrsInd is returned. SeenAddrs[] is used to skip duplicate addresses and # self-referential aliases. function ExpandAliases(Addrs,NewAddrs,NewAddrsInd,SeenAddrs,Debug, Addr,AliasAddrs,AddrInd) { for (AddrInd = 1; AddrInd in Addrs; AddrInd++) { Addr = Addrs[AddrInd] if (!(Addr in SeenAddrs)) { SeenAddrs[Addr] if (Addr in Aliases) { if (Debug) printf "Expanding alias \"%s\" to: %s\n", Addr,Aliases[Addr] > "/dev/stderr" split(Aliases[Addr],AliasAddrs,"[ \t]+") NewAddrsInd = \ ExpandAliases(AliasAddrs,NewAddrs,NewAddrsInd,SeenAddrs,Debug) } else NewAddrs[++NewAddrsInd] = Addr } } return NewAddrsInd } ### Start of UUCP functions # Echo header & message into uux with command to rmail recipients # If ReadIn is true, after echoing read input & write to proc until done. function SendMessage(System,ReadIn,UseRmail,UseSubmit,Header,message,users, proc,UserList,i,User) { if (!UseSubmit && (System == "LOCAL" || System == machine || \ System == domname || System == locname)) proc = "/usr/bin/rmail " users[System] else if (UseRmail || UseSubmit) { split(users[System],UserList) if (UseRmail) proc = "/usr/bin/rmail" else proc = "/usr/mmdf/bin/submit -" "vk10*xto*" for (i in UserList) { if ((User = UserList[i]) != "") proc = proc " " System "!" User } } else # parenthesize recipients so their !s will not be interpreted by local # uux. Use -r since we might want to look at the result of this # program before it's xferred. proc = "uux -r - " System "!rmail \\(" users[System] "\\)" if (Debug) printf "\nExecuting command: %s\nHeader is:\n%s", proc,Header > "/dev/stderr" print Header message | proc if (ReadIn) while (MesgIn()) print MesgLine | proc if (Debug) print "Closing " proc "..." > "/dev/stderr" close(proc) if (Debug) print "Done closing " proc "." > "/dev/stderr" } function uusendMail() { SepMachine(ARGV,ARGC - 1,Aliases,users,Debug > 4) # Create date line of the form Wed, 13 Mar 91 01:46:09 PST date = fmtdate("%a, %d %h %y %T %Z") # Create date line of the form Wed Mar 13 01:46:09 1991 if (something) { fromdate = fmtdate("%a %h %d %T 19%y") from = from_ = returnAddress } else { from_ = ENVIRON["USER"] from = from_ "@" domname } h1 = "From " from_ " " fromdate " remote from " machine "\n" NumSys = 0 for (SysName in users) if (++NumSys == 2) break OneSys = NumSys < 2 CannedMessage = message == "" if (!CannedMessage) message = ReadBody(OneSys) if ((!OneSys && !Quiet) || Debug) printf "Mailing..." > "/dev/stderr" for (System in users) { SendMessage(System,OneSys && !CannedMessage,useRmail,UseSubmit, Header,message,users) if (Debug) printf "\nSending mail for users on system %s...", System > "/dev/stderr" } if (!Quiet) print "Done." > "/dev/stderr" exit 0 } # Separates addresses by the machine they are on # Sets users[site] to contain a space-separated list of users at that site function SepMachine(Addrs,NumAddrs,Aliases,users,verbose, elem,Addr,AddrInd,AliasAddrs) { for (AddrInd = 1; NumAddrs; AddrInd++) { Addr = Addrs[AddrInd] NumAddrs-- # If address contains an "@", field 1 is username, field 2 is sitename if (Addr ~ "@") { split(Addr,elem,"@") users[elem[2]] = users[elem[2]] elem[1] " " } else if (Addr ~ "!") { # If address contains "!"s, first field is site to send mail to, # rest of address is address to send to at site split(Addr,elem,"!") # remove first site and "!" that follows it from path sub(elem[1] "!","",Addr) users[elem[1]] = users[elem[1]] " " Addr } else if (Addr != "-") # Other addresses are local or aliases users["LOCAL"] = users["LOCAL"] " " Addr } } ### end of UUCP functions ### Begin set library # 96/05/23 added return values jhdiii # 96/05/25 added set2list() # 97/01/26 Added AOnly(), Exclusive() # Return value: the number of new elements added to Inter function Intersection(A,B,Inter, Elem,Count) { for (Elem in A) if (Elem in B && !(Elem in Inter)) { Inter[Elem] Count++ } return Count } # Any element that is in A or B but not both and which is not already in # Excl is added to Excl. # Return value: the number of new elements added to Excl function Exclusive(A,B,Excl) { return AOnly(A,B,Excl) + AOnly(B,A,Excl) } # Any element that is in A and not in B or aOnly is added to aOnly. # Return value: the number of new elements added to aOnly. function AOnly(A,B,aOnly, Elem,Count) { for (Elem in A) if (!(Elem in B) && !(Elem in aOnly)) { aOnly[Elem] Count++ } return Count } # Return value: the number of new elements added to Both function Union(A,B,Both) { return CopySet(A,Both) + CopySet(B,Both) } # Deletes any elements that are in both Minuend and Subtrahend from Minuend. # Return value: the number of elements deleted. function SubtractSet(Minuend,Subtrahend, Elem,nDel) { for (Elem in Subtrahend) if (Elem in Minuend) { delete Minuend[Elem] nDel++ } return nDel } # Return value: the number of new elements added to To function CopySet(From,To, Elem,n) { for (Elem in From) if (!(Elem in To)) { To[Elem] n++ } return n } # Returns 1 if Set is empty, 0 if not. function IsEmpty(Set, i) { for (i in Set) return 0 return 1 } # 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. # Return value: the number of new elements added to the set. function MakeSet(Set,Elements,Sep, i,Num,Names,nFound,ind) { nFound = 0 Num = split(Elements,Names,Sep) for (i = 1; i <= Num; i++) { ind = Names[i] if (!(ind in Set)) { Set[ind] nFound++ } } return nFound } # Returns the number of elements in set Set function NumElem(Set, elem,Num) { for (elem in Set) Num++ return Num } # Remove all elements from Set function DeleteAll(Set, i) { split("",Set,",") } # Returns a list of all of the elements in Set[], with each pair of elements # separated by Sep. function set2list(Set,Sep, list,elem) { for (elem in Set) list = list Sep elem return substr(list,2) # skip 1st separator } ### End set library ### Start of mail sending routines. # @(#) mail-send.gawk 2.0 97/02/22 # 96/01/29 john h. dubois iii (john@armory.com) # 97/02/15 Rewritten. # # Returns name of cmd to pipe into, ready for body of message. # To[], Cc[], Fields[], and Order[] are as described for header822() # Bcc[] is an additional list of recipients who should not be mentioned in # headers. # If a non-null Subject is passed, as a convenience it is added to Fields[] # before it is passed to header822(). # If a non-null From is passed, it is used as the return address. It does # not affect the headers unless the MTA records it in them. function InitMail(To,Cc,Bcc,Fields,Order,Subject,From,UseSubmit,SubmitOpts, Debug, Recips,i,j,Cmd) { for (i = 1; i in To; i++) Recips[++j] = To[i] for (i = 1; i in Cc; i++) Recips[++j] = Cc[i] for (i = 1; i in Bcc; i++) Recips[++j] = Bcc[i] if (UseSubmit) Cmd = InitSubmit(Recips,From,SubmitOpts,Debug) else Cmd = InitSendmail(Recips,From) if (Cmd ~ "^!") return Cmd if (Subject != "") Fields["Subject"] = Subject printf "%s\n",header822(Fields,To,Cc,Order) | Cmd return Cmd } # Sets up globals _ParamSOpts[] and _NoParamSOpts[] for use as sets of # submit options to do and do not take values. function SubmitParam(Opt,Val, i,c) { if (!("r" in _NoParamSOpts)) { for (i = 1; (c = substr("Wcdhjlmnqrstuvwz",i,1)) != ""; i++) _NoParamSOpts[c] for (i = 1; (c = substr("LUVfghikx",i,1)) != ""; i++) _ParamSOpts[c] } if (Opt in _ParamSOpts) return Opt Val "*" else if (Opt in _NoParamSOpts) return Opt else return "" } # Return value: Command to pipe into. If an invalid submit option is passed, # a null string is returned. # Submit options are: # i* Source channel # h* Source host # t Trust Sender/From line (root/mmdf only) # u Don't trust Sender/From line (add Source-Info line to header) # f* Don't trust Sender/From line; add given text # x* Extract recipient list from named fields (comma-separated list) # RecipList[] will not be used if this option is given. # g* Extract recipient list from named fields and use explicit list # v Report validity of each address given, rather than aborting on any bad # m Deliver to mailbox (default; there used to be a tty option as well). # m is always turned on for the sake of old versions. # l Deliver local mail immediately; overrides mod=reg in MMDF config # n Deliver netmail immediately; overrides mod=reg in MMDF config # w Watch immediate delivery attempts # r Return undelivered mail to submitter when it expires # s Return undelivered mail to address given by 'Sender:' when it expires # q Do not return undelivered mail (discard it when it expires). # Remote systems may still return mail. Use q with null From parameter # for no returns at all. # c If mail is returned, include only a citation of the contents # z Do not send warnings re undelivered (but not expired) mail # d Don't use delay channel. If 1st nameserver use fails, mail is returned # j Used by the delay channel to indicate that submission is by it # k* Specify nameserver timeout. Parameter is in seconds. # W Watch submission. Output is sent to fd 2 # L* Specify logfile (root/mmdf only) # V* Specify logging level (root/mmdf only). FAT TMP GEN BST FST PTR BTR FTR # U* Specify invoker's UID (root only) function InitSubmit(RecipList,From,SubmitOpts,Debug, Cmd,i,c,Opt) { # If not special return handling requested and no return address given, # send returns to submitter if (From == "" && !("s" in SubmitOpts || "q" in SubmitOpts)) SubmitOpts["r"] SubmitOpts["m"] for (Opt in SubmitOpts) if ((c = SubmitParam(Opt,SubmitOpts[Opt])) == "") return "!Error initializing submit: Bad option '" Opt "'." else Cmd = Cmd c if (Cmd != "") Cmd = " -" Cmd Cmd = "exec /usr/mmdf/bin/submit" Cmd if (Debug) { Cmd = "exec tee /dev/tty | " Cmd print "mail submission command: " Cmd > "/dev/stderr" } if (!("r" in SubmitOpts || "s" in SubmitOpts)) print From | Cmd # Explicit return address - maybe empty if (!("x" in SubmitOpts)) { # If explicit addresses may be given for (i = 1; i in RecipList; i++) print RecipList[i] | Cmd print "!" | Cmd # terminate recipient list } return Cmd } function InitSendmail(RecipList,From, ToList,i) { if (From != "") From = " -f '" From "'" for (i = 1; i in RecipList; i++) ToList = ToList " " RecipList[i] return "exec /usr/lib/sendmail" From ToList } # @(#) GetMailHostName 97/02/12 # 91/03/13 jhdiii # 97/02/12 Use hostname if unable to get name from mmdftailor. # Returns the name of the local host that should be used for mail purposes. # If mmdftailor is readable and both MLNAME and MLDOMAIN can be found, uses # MLNAME.MLDOMAIN. If not, uses 'hostname'. # The name is stored in the global _MailHostName for reuse by this function. function GetMailHostName( mlname,mldomain,proc,tailor,oFS,hostname) { if (_MailHostName != "") return _MailHostName tailor = "/usr/mmdf/mmdftailor" oFS = FS FS = " " # normal awk field splitting while ((getline < tailor) == 1) { if ($1 == "MLNAME") mlname = $2 else if ($1 == "MLDOMAIN") mldomain = $2 else continue if (mlname != "" && mldomain != "") { hostname = mlname "." mldomain gsub("\"","",hostname) # in case values are quoted break } } close(tailor) if (hostname == "") { proc = "/usr/bin/hostname" proc | getline hostname close(proc) } FS = oFS _MailHostName = hostname return hostname } # Returns an RFC822 recipient field, wrapped as neccessary, ending with a # newline. function WrapField822(FieldName,Values, Field,Line,len,w,i,val,indentLen,indentStr) { Line = FieldName ":" len = length(Line) indentLen = len+1 for (i = 1; i <= indentLen; i++) indentStr = indentStr " " for (i = 1; i in Values; i++) { val = Values[i] if ((i+1) in Values) val = val "," len += w = length(val)+1 if (len > 79) { Field = Field Line "\n" Line = indentStr val len = w + indentLen - 1 } else Line = Line " " val } return Field Line "\n" } # Create an RFC822-compliant mail header. A blank line is *not* appended; # the returned value ends with a single trailing newline. # Fields[] contains field values, indexed by name. The name is given without # a trailing :. To[] and Cc[] are the recipient lists. # The first line of the header is the Date: field. If there is no Date index # in Fields[], the current date & time in an RFC822-compliant format is used. # The second line of the header is the From: field. If there is no From index # in Fields[], the From: field is made "user@host (name)", where user is the # value of the USER environment variable, or the name from 'id' if it is not # set; host is as described for GetMailHostName(), and name is from the NAME # environment variable. If NAME is not set, (name) is not included in the # From: field. # The next lines of the header give the To: and (optionally) Cc: fields. # To[] and Cc[] should contain values indexed by integers starting with 1. # The fields are built by concatenating these values in the order of their # indices. Header extensions are used as neccessary to keep the length of # each physical line below 80 characters if possible. # After these are added, any other fields are added. # The order they are added in may be specified by assigning the header names to # consecutive integer indexes in Order[], starting with 1. # Any field named in Order[] that does not exist in Fields[] will not be added. # Date, From, To, and Cc should not be given in Order. # After all fields named in Order[] are added, any remaining fields are added. # Field wrapping is not done to any values in Fields. # All elements are removed from Fields[]. # Minimal RFC822 message has From, Date, and either To or Bcc line. function header822(Fields,To,Cc,Order, header,i,field) { if ("Date" in Fields) { header = Fields["Date"] delete Fields["Date"] } else header = fmtdate("%a, %d %h %Y %T %Z") header = "Date: " header "\nFrom: " if ("From" in Fields) { header = header Fields["From"] delete Fields["From"] } else { header = header WhoAmI() header = header "@" GetMailHostName() if ("NAME" in ENVIRON) header = header " (" ENVIRON["NAME"] ")" } header = header "\n" WrapField822("To",To) if (1 in Cc) header = header WrapField822("Cc",Cc) for (i = 1; i in Order; i++) if ((field = Order[i]) in Fields) { header = header field ": " Fields[field] "\n" delete Fields[field] } for (field in Fields) { header = header field ": " Fields[field] "\n" delete Fields[field] } return header } ### End of mail sending routines. # WhoAmI 1.0 97/02/14 # 97/02/14 john h. dubois iii (john@armory.com) # WhoAmI: return best attempt at determining what user owns this process. # First, get USER from environment. If that fails, try logname; it gives a # better indication of who the user is than the uid does, since multiple login # names may have the same uid. But, check that the name returned by logname # maps to the process' uid, as utmp may have bogus data or the user may have # su'd. If it doesn't, or logname fails, use the user name returned by id. # For efficiency in multiple invokations, the user name is stored in # _WhoAmI_user for reuse. function WhoAmI( Cmd,line,elem,logname,uiduser,uid,oFS) { if (_WhoAmI_user != "") return _WhoAmI_user if ("USER" in ENVIRON && ENVIRON["USER"] != "") return _WhoAmI_user = ENVIRON["USER"] Cmd = "exec /usr/bin/logname 2>/dev/null" Cmd | getline logname close(Cmd) Cmd = "exec /usr/bin/id" Cmd | getline line close(Cmd) split(line,elem,"[()=]") uiduser = elem[3] if (logname == uiduser) return _WhoAmI_user = logname uid = elem[2] oFS = FS FS = ":" while ((getline < "/etc/passwd") == 1) if ($1 == logname) { if ($3 == uid) return _WhoAmI_user = logname break } return _WhoAmI_user = uiduser } ### Start of ProcArgs library # @(#) ProcArgs 1.12 97/02/22 # 92/02/29 john h. dubois iii (john@armory.com) # 93/07/18 Added "#" arg type # 93/09/26 Do not count -h against MinArgs # 94/01/01 Stop scanning at first non-option arg. Added ">" option type. # Removed meaning of "+" or "-" by itself. # 94/03/08 Added & option and *()< option types. # 94/04/02 Added NoRCopt to Opts() # 94/06/11 Mark numeric variables as such. # 94/07/08 Opts(): Do not require any args if h option is given. # 95/01/22 Record options given more than once. Record option num in argv. # 95/06/08 Added ExclusiveOptions(). # 96/01/20 Let rcfiles be a colon-separated list of filenames. # Expand $VARNAME at the start of its filenames. # Let varname=0 and -option- turn off an option. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many # of the vars should be searched for in the environment. # Check for duplicate rcfiles. # 96/05/13 Return more specific error values. Note: ProcArgs() and InitOpts() # now return various negatives values on error, not just -1, and # Opts() may set Err to various positive values, not just 1. # Added AllowUnrecOpt. # 96/05/23 Check type given for & option # 96/06/15 Re-port to awk # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be # used by other functions. # 96/10/15 Added OptChars # 96/11/01 Added exOpts arg to Opts() # 96/11/16 Added ; type # 96/12/08 Added Opt2Set() & Opt2Sets() # 96/12/27 Added CmdLineOpt() # 97/02/22 Remove packed elements. # optlist is a string which contains all of the possible command line options. # A character followed by certain characters indicates that the option takes # an argument, with type as follows: # : String argument # ; Non-empty string argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # The only difference the type of argument makes is in the runtime argument # error checking that is done. # The & option is a special case used to get numeric options without the # user having to give an option character. It is shorthand for [-+.0-9]. # If & is included in optlist and an option string that begins with one of # these characters is seen, the value given to "&" will include the first # char of the option. & must be followed by a type character other than ":" # or ";". # Note that if e.g. &> is given, an option of -.5 will produce an error. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" # or "+" is taken to be a non-option string; like other non-option strings, # it stops the scanning of argv and is left in argv[]. # An argument of "--" or "++" also stops the scanning of argv[] but is removed. # If an option takes an argument, the argument may either immediately # follow it or be given separately. # "-" and "+" options are treated the same. "+" is allowed because most awks # take any -options to be arguments to themselves. gawk 2.15 was enhanced to # stop scanning when it encounters an unrecognized option, though until 2.15.5 # this feature had a flaw that caused problems in some cases. See the OptChars # parameter to explicitly set the option-specifier characters. # 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 the # number of times it occurs in argv[]. # 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, and Options[option-name,"count"] is (initially) set to the 1. # If an option that takes an argument is given more than once, # Options[option-name,"count"] is incremented, and the value is assigned to # the index (option-name,instance) where instance is 2 for the second occurance # of the option, etc. # In other words, the first time an option with a value is encountered, the # value is assigned to an index consisting only of its name; for any further # occurances of the option, the value index has an extra (count) dimension. # The sequence number for each option found in argv[] is stored in # Options[option-name,"num",instance], where instance is 1 for the first # occurance of the option, etc. The sequence number starts at 1 and is # incremented for each option, both those that have a value and those that # do not. Options set from a config file have a value of 0 assigned to this. # 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. # Option processing will stop with the first unrecognized option, just as # though -- was given except that unlike -- the unrecognized option will not be # removed from ARGV[]. Normally, an error value is returned in this case. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to # be found, so the number of remaining arguments is returned instead. # If OptChars is not a null string, it is the set of characters that indicate # that an argument is an option string if the string begins with one of the # characters. A string consisting solely of two of the same option-indicator # characters stops the scanning of argv[]. The default is "-+". # argv[0] is not examined. # The number of arguments left in argc is returned. # If an error occurs, the global string OptErr is set to an error message # and a negative value is returned. # Current error values: # -1: option that required an argument did not get it. # -2: argument of incorrect type supplied for an option. # -3: unrecognized (invalid) option. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars, ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven, NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet) { # 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. # NumOpt is true if a numeric option may be given. ArgsLeft = argc NumOpt = index(OptList,"&") OptionNum = 0 if (OptChars == "") OptChars = "-+" while (OptChars != "") { c = substr(OptChars,1,1) OptChars = substr(OptChars,2) OptCharSet[c] OptTerm[c c] } for (ArgNum = 1; ArgNum < argc; ArgNum++) { Arg = argv[ArgNum] if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet)) break # Not an option; quit if (Arg in OptTerm) { delete argv[ArgNum] ArgsLeft-- break } ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) if (NumOpt && Option ~ /[-+.0-9]/) { # If this option is a numeric option, make its flag be & and # its option string flag position be the position of & in # the option string. Option = "&" Pos = NumOpt # Prefix Arg with a char so that ArgInd will point to the # first char of the numeric option. Arg = "&" Arg ArgLen++ } # Find position of flag in option string, to get its type (if any). # Disallow & as literal flag. else if (!(Pos = index(OptList,Option)) || Option == "&") { if (AllowUnrecOpt) { Escape = 1 break } else { OptErr = "Invalid option: " specGiven Option return -3 } } # Find what the value of the option will be if it takes one. # NeedNextOpt is true if the option specifier is the last char of # this arg, which means that if the option requires a value it is # the next arg. if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg if (GotValue = ArgNum + 1 < argc) Value = argv[ArgNum+1] } else { # Value is included with option Value = substr(Arg,ArgInd + 1) GotValue = 1 } if (HadValue = AssignVal(Option,Value,Options, substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt, specGiven)) { if (HadValue < 0) # error occured return HadValue if (HadValue == 2) ArgInd++ # Account for the single-char value we used. else { if (NeedNextOpt) { # option took next arg as value delete argv[++ArgNum] ArgsLeft-- } break # This option has been used up } } } if (Escape) break # Do not delete arg until after processing of it, so that if it is not # recognized it can be left in ARGV[]. delete argv[ArgNum] ArgsLeft-- } if (compress != 0) { dest = 1 src = argc - ArgsLeft + 1 if (src != dest) { for (count = ArgsLeft - 1; count; count--) { ARGV[dest] = ARGV[src] dest++ src++ } for (; dest < src; dest++) delete ARGV[dest] } } return ArgsLeft } # Assignment to values in Options[] occurs only in this function. # Option: Option specifier character. # Value: Value to be assigned to option, if it takes a value. # Options[]: Options array to return values in. # ArgType: Argument type specifier character. # GotValue: Whether any value is available to be assigned to this option. # Name: Name of option being processed. # OptionNum: Number of this option (starting with 1) if set in argv[], # or 0 if it was given in a config file or in the environment. # SingleOpt: true if the value (if any) that is available for this option was # given as part of the same command line arg as the option. Used only for # options from the command line. # specGiven is the option specifier character use, if any (e.g. - or +), # for use in error messages. # Global variables: OptErr # Return value: negative value on error, 0 if option did not require an # argument, 1 if it did & used the whole arg, 2 if it required just one char of # the arg. # Current error values: # -1: Option that required an argument did not get it. # -2: Value of incorrect type supplied for option. # -3: Bad type given for option & function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum, SingleOpt,specGiven, UsedValue,Err,NumTypes) { # If option takes a value... [ NumTypes = "*()#<>]" if (Option == "&" && ArgType !~ "[" NumTypes) { # ] OptErr = "Bad type given for & option" return -3 } if (UsedValue = (ArgType ~ "[:;" NumTypes)) { # ] if (!GotValue) { if (Name != "") OptErr = "Variable requires a value -- " Name else OptErr = "option requires an argument -- " Option return -1 } if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") { OptErr = Err return -2 } # Mark this as a numeric variable; will be propogated to Options[] val. if (ArgType != ":" && ArgType != ";") Value += 0 if ((Instance = ++Options[Option,"count"]) > 1) Options[Option,Instance] = Value else Options[Option] = Value } # If this is an environ or rcfile assignment & it was given a value... else if (!OptionNum && Value != "") { UsedValue = 1 # If the value is "0" or "-" and this is the first instance of it, # do not set Options[Option]; this allows an assignment in an rcfile to # turn off an option (for the simple "Option in Options" test) in such # a way that it cannot be turned on in a later file. if (!(Option in Options) && (Value == "0" || Value == "-")) Instance = 1 else Instance = ++Options[Option] # Save the value even though this is a flag Options[Option,Instance] = Value } # If this is a command line flag and has a - following it in the same arg, # it is being turned off. else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") { UsedValue = 2 if (Option in Options) Instance = ++Options[Option] else Instance = 1 Options[Option,Instance] } # If this is a flag assignment without a value, increment the count for the # flag unless it was turned off. The indicator for a flag being turned off # is that the flag index has not been set in Options[] but it has an # instance count. else if (Option in Options || !((Option,1) in Options)) # Increment number of times this flag seen; will inc null value to 1 Instance = ++Options[Option] Options[Option,"num",Instance] = OptionNum return UsedValue } # Option is the option letter # Value is the value being assigned # Name is the var name of the option, if any # ArgType is one of: # : String argument # ; Non-null string argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # specGiven is the option specifier character use, if any (e.g. - or +), # for use in error messages. # Returns null on success, err string on error function CheckType(ArgType,Value,Option,Name,specGiven, Err,ErrStr) { if (ArgType == ":") return "" if (ArgType == ";") { if (Value == "") Err = "must be a non-empty string" } # A number begins with optional + or -, and is followed by a string of # digits or a decimal with digits before it, after it, or both else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/) Err = "must be a number" else if (ArgType ~ "[#<>]" && Value ~ /\./) Err = "may not include a fraction" else if (ArgType ~ "[()<>]" && Value < 0) Err = "may not be negative" # ( else if (ArgType ~ "[)>]" && Value == 0) Err = "must be a positive number" if (Err != "") { ErrStr = "Bad value \"" Value "\". Value assigned to " if (Name != "") return ErrStr "variable " substr(Name,1,1) " " Err else { if (Option == "&") Option = Value return ErrStr "option " specGiven substr(Option,1,1) " " Err } } else return "" } # Note: only the above functions are needed by ProcArgs. # The rest of these functions call ProcArgs() and also do other # option-processing stuff. # 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. # rcFiles, if given, is a colon-seprated list of filenames to read for # variable initialization. If a filename begins with ~/, the ~ is replaced # by the value of the environment variable HOME. If a filename begins with # $, the part from the character after the $ up until (but not including) # the first character not in [a-zA-Z0-9_] will be searched for in the # environment; if found its value will be substituted, if not the filename will # be discarded. # rcfiles are read in the order given. # Values given in them will not override values given on the command line, # and values given in later files will not override those set in earlier # files, because AssignVal() will store each with a different instance index. # The first instance of each variable, either on the command line or in an # rcfile, will be stored with no instance index, and this is the value # normally used by programs that call this function. # VarNames is a comma-separated list of variable names to map to options, # in the same order as the options are given in OptList. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be # searched for in the environment. If set to -1, all values will be searched # for in the environment. Values given in the environment will override # those given in the rcfiles but not those given on the command line. # NoRCopt, if given, is an additional letter option that if given on the # command line prevents the rcfiles from being read. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and # ExclusiveOptions() for a description of exOpts. # Special options: # If x is made an option and is given, some debugging info is output. # h is assumed to be the help option. # 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 a positive integer value so it can be checked for in # an END block. # Return value: The number of elements left in ARGV is returned. # Must keep OptErr global since it may be set by InitOpts(). function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt, AllowUnrecOpt,optChars,exOpts, ArgsLeft,e) { if (MinArgs == "") MinArgs = 0 ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt, optChars) if (ArgsLeft < (MinArgs+1) && !("h" in Options)) { if (ArgsLeft >= 0) { OptErr = "Not enough arguments" Err = 4 } else Err = -ArgsLeft printf "%s: %s.\nUse -h for help.\n%s\n", Name,OptErr,Usage > "/dev/stderr" exit 1 } if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) && (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0) { print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr" Err = -e exit 1 } if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != "")) { printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr" Err = 1 exit 1 } return ArgsLeft } # ReadConfFile(): Read a file containing var/value assignments, in the form # . # Whitespace (spaces and tabs) around a variable (leading whitespace on the # line and whitespace between the variable name and the assignment character) # is stripped. Lines that do not contain an assignment operator or which # contain a null variable name are ignored, other than possibly being noted in # the return value. If more than one assignment is made to a variable, the # first assignment is used. # Input variables: # File is the file to read. # Comment is the line-comment character. If it is found as the first non- # whitespace character on a line, the line is ignored. # Assign is the assignment string. The first instance of Assign on a line # separates the variable name from its value. # If StripWhite is true, whitespace around the value (whitespace between the # assignment char and trailing whitespace on the line) is stripped. # VarPat is a pattern that variable names must match. # Example: "^[a-zA-Z][a-zA-Z0-9]+$" # If FlagsOK is true, variables are allowed to be "set" by being put alone on # a line; no assignment operator is needed. These variables are set in # the output array with a null value. Lines containing nothing but # whitespace are still ignored. # Output variables: # Values[] contains the assignments, with the indexes being the variable names # and the values being the assigned values. # Lines[] contains the line number that each variable occured on. A flag set # is record by giving it an index in Lines[] but not in Values[]. # Return value: # If any errors occur, a string consisting of descriptions of the errors # separated by newlines is returned. In no case will the string start with a # numeric value. If no errors occur, the number of lines read is returned. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat, FlagsOK, Line,Status,Errs,AssignLen,LineNum,Var,Val) { if (Comment != "") Comment = "^" Comment AssignLen = length(Assign) if (VarPat == "") VarPat = "." # null varname not allowed while ((Status = (getline Line < File)) == 1) { LineNum++ sub("^[ \t]+","",Line) if (Line == "") # blank line continue if (Comment != "" && Line ~ Comment) continue if (Pos = index(Line,Assign)) { Var = substr(Line,1,Pos-1) Val = substr(Line,Pos+AssignLen) if (StripWhite) { sub("^[ \t]+","",Val) sub("[ \t]+$","",Val) } } else { Var = Line # If no value, var is entire line Val = "" } if (!FlagsOK && Val == "") { Errs = Errs \ sprintf("\nBad assignment on line %d of file %s: %s", LineNum,File,Line) continue } sub("[ \t]+$","",Var) if (Var !~ VarPat) { Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s", LineNum,File,Var) continue } if (!(Var in Lines)) { Lines[Var] = LineNum if (Pos) Values[Var] = Val } } if (Status) Errs = Errs "\nCould not read file " File close(File) return Errs == "" ? LineNum : substr(Errs,2) # Skip first newline } # Variables: # Data is stored in Options[]. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts(). # Global vars: # Sets OptErr. Uses ENVIRON[]. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch, Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile, fNames,numrcFiles,filesRead,Err,Values,retStr) { split("",filesRead,"") # make awk know this is an array NumVars = split(VarNames,Vars,",") TypesInd = Ret = 0 if (EnvSearch == -1) EnvSearch = NumVars for (i = 1; i <= NumVars; i++) { Var = Vars[i] CharOpt = substr(OptList,++TypesInd,1) if (CharOpt ~ "^[:;*()#<>&]$") CharOpt = substr(OptList,++TypesInd,1) Map[Var] = CharOpt Types[Var] = Type = substr(OptList,TypesInd+1,1) # Do not overwrite entries from environment if (i <= EnvSearch && Var in ENVIRON && (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0) return Err } numrcFiles = split(rcFiles,fNames,":") for (i = 1; i <= numrcFiles; i++) { rcFile = fNames[i] if (rcFile ~ "^~/") rcFile = ENVIRON["HOME"] substr(rcFile,2) else if (rcFile ~ /^\$/) { rcFile = substr(rcFile,2) match(rcFile,"^[a-zA-Z0-9_]*") envvar = substr(rcFile,1,RLENGTH) if (envvar in ENVIRON) rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1) else continue } if (rcFile in filesRead) continue # rcfiles are liable to be given more than once, e.g. UHOME and HOME # may be the same filesRead[rcFile] if ("x" in Options) printf "Reading configuration file %s\n",rcFile > "/dev/stderr" retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1) if (retStr > 0) READ_RCFILE = 1 else if (ret != "") { OptErr = retStr Ret = -1 } for (Var in Lines) if (Var in Map) { if ((Err = AssignVal(Map[Var], Var in Values ? Values[Var] : "",Options,Types[Var], Var in Values,Var,0)) < 0) return Err } else { OptErr = sprintf(\ "Unknown var \"%s\" assigned to on line %d\nof file %s",Var, Lines[Var],rcFile) Ret = -1 } } if ("x" in Options) for (Var in Map) if (Map[Var] in Options) printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \ "/dev/stderr" else printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr" return Ret } # OptSets is a semicolon-separated list of sets of option sets. # Within a list of option sets, the option sets are separated by commas. For # each set of sets, if any option in one of the sets is in Options[] AND any # option in one of the other sets is in Options[], an error string is returned. # If no conflicts are found, nothing is returned. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to # the exclusions presented by the first set of sets (ab,def,g) if: # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR # (a or b is in Options[]) AND (g is in Options) OR # (d, e, or f is in Options[]) AND (g is in Options) # An error will be returned due to the exclusions presented by the second set # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]). # todo: make options given on command line unset options given in config file # todo: that they conflict with. function ExclusiveOptions(OptSets,Options, Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets, SetNum,OSetNum) { NumSetSets = split(OptSets,SetSets,";") # For each set of sets... for (SetSet = 1; SetSet <= NumSetSets; SetSet++) { # NumSets is the number of sets in this set of sets. NumSets = split(SetSets[SetSet],Sets,",") # For each set in a set of sets except the last... for (SetNum = 1; SetNum < NumSets; SetNum++) { s1 = Sets[SetNum] L1 = length(s1) for (Pos1 = 1; Pos1 <= L1; Pos1++) # If any of the options in this set was given, check whether # any of the options in the other sets was given. Only check # later sets since earlier sets will have already been checked # against this set. if ((c1 = substr(s1,Pos1,1)) in Options) for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) { s2 = Sets[OSetNum] L2 = length(s2) for (Pos2 = 1; Pos2 <= L2; Pos2++) if ((c2 = substr(s2,Pos2,1)) in Options) ErrStr = ErrStr "\n"\ sprintf("Cannot give both %s and %s options.", c1,c2) } } } if (ErrStr != "") return substr(ErrStr,2) return "" } # The value of each instance of option Opt that occurs in Options[] is made an # index of Set[]. # The return value is the number of instances of Opt in Options. function Opt2Set(Options,Opt,Set, count) { if (!(Opt in Options)) return 0 Set[Options[Opt]] count = Options[Opt,"count"] for (; count > 1; count--) Set[Options[Opt,count]] return count } # The value of each instance of option Opt that occurs in Options[] that # begins with "!" is made an index of nSet[] (with the ! stripped from it). # Other values are made indexes of Set[]. # The return value is the number of instances of Opt in Options. function Opt2Sets(Options,Opt,Set,nSet, count,aSet,ret) { ret = Opt2Set(Options,Opt,aSet) for (value in aSet) if (substr(value,1,1) == "!") nSet[substr(value,2)] else Set[value] return ret } # Returns true if option Opt was given on the command line. function CmdLineOpt(Options,Opt, i) { for (i = 1; (Opt,"num",i) in Options; i++) if (Options[Opt,"num",i] != 0) return 1 return 0 } ### End of ProcArgs library function fmtdate(format, cmd,ret) { #return strftime(format) cmd = sprintf("date '+%s'",format) cmd | getline ret return ret }