#!/bin/sh # configuration-file reader utility # Copyright (C) 1999-2002 Henry Spencer. # # 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 of the License, or (at your # option) any later version. See . # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # RCSID $Id: _confread.in,v 1.79 2004/11/12 04:18:03 mcr Exp $ # # Extract configuration info from /etc/ipsec.conf, repackage as assignments # to shell variables or tab-delimited fields. Success or failure is reported # inline, as extra data, due to the vagaries of shell backquote handling. # In the absence of --varprefix, output is tab-separated fields, like: # = sectionname # : parameter value # ! status (empty for success, else complaint) # In the presence of (say) "--varprefix IPSEC", output is like: # IPSEC_confreadsection="sectionname" # IPSECparameter="value" # IPSEC_confreadstatus="status" (same empty/complaint convention) # # The "--search parametername" option inverts the search: instead of # yielding the parameters of the specified name(s), it yields the names # of sections with parameter having (one of) the # specified value(s). In this case, --varprefix output is a list of # names in the _confreadnames variable. Search values with # white space in them are currently not handled properly. # # Typical usage: # eval `ipsec _confread --varprefix IPSEC --type config setup` # if test " $IPSEC_confreadstatus" != " " # then # echo "$0: $IPSEC_confreadstatus -- aborting" 2>&1 # exit 1 # fi # absent default config file treated as empty config=${IPSEC_CONFS-/etc}/ipsec.conf if test ! -f "$config" ; then config=/dev/null ; fi include=yes type=conn fieldfmt=yes prefix= search= export=0 version= optional=0 me="ipsec _confread" for dummy do case "$1" in --config) config="$2" ; shift ;; --noinclude) include= ;; --type) type="$2" ; shift ;; --varprefix) fieldfmt= prefix="$2" shift ;; --export) export=1 ;; --search) search="$2" ; shift ;; --version) echo "$me $IPSEC_VERSION" ; exit 0 ;; --optional) optional=1 ;; --) shift ; break ;; -*) echo "$0: unknown option \`$1'" >&2 ; exit 2 ;; *) break ;; esac shift done if test "$include" then ipsec _include --inband $config else cat $config fi | awk 'BEGIN { type = "'"$type"'" names = "'"$*"'" prefix = "'"$prefix"'" export = "'"$export"'" optional = 0 + '"$optional"' myid = "'"$IPSECmyid"'" search = "'"$search"'" searching = 0 if (search != "") { searching = 1 searchpat = search "[ \t]*=[ \t]*" } fieldfmt = 0 if ("'"$fieldfmt"'" == "yes") fieldfmt = 1 including = 0 if ("'"$include"'" == "yes") including = 1 filename = "'"$config"'" lineno = 0 originalfilename = filename if (fieldfmt) bq = eq = "\"" else bq = eq = "\\\"" failed = 0 insection = 0 indefault = 0 outputting = 0 sawnondefault = 0 OFS = "\t" o_status = "!" o_parm = ":" o_section = "=" o_names = "%" o_end = "." n = split(names, na, " ") if (n == 0) fail("no section names supplied") for (i = 1; i <= n; i++) { if (na[i] in wanted) fail("section " bq na[i] eq " requested more than once") wanted[na[i]] = 1 pending[na[i]] = 1 if (!searching && na[i] !~ /^[a-zA-Z][a-zA-Z0-9._-]*$/) fail("invalid section name " bq na[i] eq) } good = "also alsoflip type auto authby _plutodevel" good = good " connaddrfamily forceencaps" good = good " modecfgpull" left = " left leftsubnet leftnexthop leftupdown" akey = " keyexchange auth pfs keylife rekey rekeymargin rekeyfuzz" akey = akey " dpddelay dpdtimeout dpdaction" akey = akey " xauth" akey = akey " aggrmode" akey = akey " compress" akey = akey " keyingtries ikelifetime disablearrivalcheck failureshunt ike" mkey = " spibase spi esp espenckey espauthkey espreplay_window" left = left " leftespenckey leftespauthkey leftahkey" left = left " leftespspi leftahspi leftid leftrsasigkey leftrsasigkey2" left = left " leftcert leftcerttype leftca leftsubnetwithin leftprotoport leftgroups" left = left " leftxauthclient leftxauthserver leftsendcert" left = left " leftmodecfgclient leftmodecfgserver" left = left " leftsourceip" mkey = mkey " ah ahkey ahreplay_window" right = left gsub(/left/, "right", right) n = split(good left right akey mkey, g) for (i = 1; i <= n; i++) goodnames["conn:" g[i]] = 1 good = "also interfaces forwardcontrol myid" good = good " syslog klipsdebug plutodebug plutoopts plutostderrlog" good = good " plutorestartoncrash" good = good " dumpdir manualstart pluto" good = good " plutowait prepluto postpluto nhelpers" good = good " fragicmp hidetos rp_filter uniqueids" good = good " overridemtu" good = good " nocrsend strictcrlpolicy crlcheckinterval ocspuri" good = good " nat_traversal keep_alive force_keepalive" good = good " disable_port_floating virtual_private" n = split(good, g) for (i = 1; i <= n; i++) goodnames["config:" g[i]] = 1 goodtypes["conn"] = 1 goodtypes["config"] = 1 badchars = "" for (i = 1; i < 32; i++) badchars = badchars sprintf("%c", i) for (i = 127; i < 128+32; i++) badchars = badchars sprintf("%c", i) badchar = "[" badchars "]" # if searching, seen is set of sectionnames which match # if not searching, seen is set of parameter names found seen[""] = "" default[""] = "" usesdefault[""] = "" orientation = 1 } function output(code, v1, v2) { if (code == o_parm) { if (v2 == "") # suppress empty parameters return if (privatename(v1)) # and private ones return if (v2 ~ badchar) fail("parameter value " bq v2 eq " contains unprintable character") } if (fieldfmt) { print code, v1, v2 return } if (code == o_status) { v2 = v1 v1 = "_confreadstatus" } else if (code == o_section) { v2 = v1 v1 = "_confreadsection" } else if (code == o_names) { v2 = v1 v1 = "_confreadnames" } else if (code != o_parm) return # currently no variable version of o_end print prefix v1 "=\"" v2 "\"" if (export) print "export " prefix v1 } function searchfound(sectionname, n, i, reflist) { # a hit in x is a hit in everybody who refers to x too n = split(refsto[sectionname], reflist, ";") for (i = 1; i <= n; i++) if (reflist[i] in seen) fail("duplicated parameter " bq search eq) else seen[reflist[i]] = 1 seen[sectionname] = 1 } function fail(msg) { output(o_status, ("(" filename ", line " lineno ") " msg)) failed = 1 while ((getline junk) > 0) continue exit } function badname(n) { if ((type ":" n) in goodnames) return 0 if (privatename(n)) return 0 return 1 } function privatename(n) { if (n ~ /^[xX][-_]/) return 1 return 0 } function orient(n) { if (orientation == -1) { if (n ~ /left/) gsub(/left/, "right", n) else if (n ~ /right/) gsub(/right/, "left", n) } return n } # in searching, referencing is transitive: xyz->from->to function chainref(from, to, i, reflist, listnum) { if (from in refsto) { listnum = split(refsto[from], reflist, ";") for (i = 1; i <= listnum; i++) chainref(reflist[i], to) } if (to in refsto) refsto[to] = refsto[to] ";" from else refsto[to] = from } function jam(sn, au) { if (searching) { if (!(sn in usesdefault)) { usesdefault[sn] = 0 if ("auto=" ~ searchpat && au in wanted) searchfound(sn) } } else { if (sn in pending) { delete pending[sn] orientation = wanted[sn] tag = bq type " " sn eq outputting = 1 insection = 1 output(o_section, sn) # do not accept anything from conn %default for (i in default) delete default[i] output(o_parm, orient("left"), "%defaultroute") output(o_parm, orient("leftid"), "%myid") output(o_parm, "leftrsasigkey", "%dnsondemand") output(o_parm, "rightrsasigkey", "%dnsondemand") output(o_parm, "auto", au) return 1 } } return 0 } # start of rules { lineno++ # lineno is now the number of this line # we must remember indentation because comment stripping loses it exdented = $0 !~ /^[ \t]/ sub(/^[ \t]+/, "") # get rid of leading white space sub(/[ \t]+$/, "") # get rid of trailing white space } including && $0 ~ /^#[<>:]/ { # _include control line if ($1 ~ /^#[<>]$/) { filename = $2 lineno = $3 - 1 } else if ($0 ~ /^#:/) { msg = substr($0, 3) gsub(/"/, "\\\"", msg) fail(msg) } next } exdented { # any non-leading-white-space line is a section end ### but not the end of relevant stuff, might be also= sections later ###if (insection && !indefault && !searching && outputting) ### output(o_end) insection = 0 indefault = 0 outputting = 0 } /[ \t]#/ { # strip trailing comments including the leading whitespace # tricky because we must respect quotes q = 0 for (i = 1; i <= NF; i++) { if ($i ~ /^#/ && q % 2 == 0) { NF = i - 1; break } # using $i in gsub loses whitespace?!? junk = $i q += gsub(/"/, "&", junk) } } $0 == "" || $0 ~ /^#/ { # empty lines and comments are ignored next } exdented && NF != 2 { # bad section header fail("section header " bq $0 eq " has wrong number of fields (" NF ")") } exdented && $1 == "version" { version = $2 + 0 if (version < 2.0 || 2.0 < version) fail("we only support version 2.0 ipsec.conf files, not " bq version eq) next } version == "" { fail("we only support version 2 ipsec.conf files") } exdented && !($1 in goodtypes) { # unknown section type fail("section type " bq $1 eq " not recognized") } exdented && $1 != type { # section header, but not one we want insection = 1 next } exdented && $1 == "config" && $2 != "setup" { fail("unknown config section " bq $2 eq) } exdented && $2 != "%default" { # non-default section header of our type sawnondefault = 1 } exdented && searching && $2 != "%default" { # section header, during search insection = 1 sectionname = $2 usesdefault[sectionname] = 1 # tentatively next } exdented && !searching && $2 in wanted { # one of our wanted section headers if (!($2 in pending)) fail("duplicate " type " section " bq $2 eq) delete pending[$2] tag = bq type " " $2 eq outputting = 1 insection = 1 orientation = wanted[$2] output(o_section, $2) next } exdented && $2 == "%default" { # relevant default section header if (sawnondefault) fail(bq $1 " %default" eq " sections must precede non-default ones") tag = bq type " " $2 eq indefault = 1 next } exdented { # section header, but not one we want insection = 1 next } !insection && !indefault { # starts with white space but not in a section... oops fail("parameter is not within a section") } searching && $0 ~ searchpat { # search found the right parameter name match($0, searchpat) rest = substr($0, RLENGTH+1) if (rest ~ /^".*"$/) rest = substr(rest, 2, length(rest)-2) if (!indefault) { if (!usesdefault[sectionname]) fail("duplicated parameter " bq search eq) usesdefault[sectionname] = 0 } else if (search in default) fail("duplicated parameter " bq search eq) if (rest in wanted) { # a hit if (indefault) default[search] = rest else searchfound(sectionname) } else { # rather a kludge, but must check this somewhere if (search == "auto" && rest !~ /^(add|route|start|ignore|manual)$/) fail("illegal auto value " bq rest eq) } next } !searching && !outputting && !indefault { # uninteresting line next } $0 ~ /"/ && $0 !~ /^[^=]+=[ \t]*"[^"]*"$/ { if (!searching) fail("mismatched quotes in parameter value") else gsub(/"/, "", $0) } $0 !~ /^[a-zA-Z_][a-zA-Z0-9_-]*[ \t]*=/ { if (searching) next # just ignore it fail("syntax error or illegal parameter name") } { sub(/[ \t]*=[ \t]*/, "=") # get rid of white space around = } $0 ~ /^(also|alsoflip)=/ { v = orientation if ($0 ~ /^alsoflip/) v = -v; if (indefault) fail("%default section may not contain " bq "also" eq " or " bq "alsoflip" eq " parameter") sub(/^(also|alsoflip)=/, "") if ($0 !~ /^[a-zA-Z][a-zA-Z0-9._-]*$/) fail("invalid section name " bq $0 eq) if (!searching) { if ($0 in wanted) fail("section " bq $0 eq " requested more than once") wanted[$0] = v pending[$0] = 1 } else chainref(sectionname, $0) next } !outputting && !indefault { # uninteresting line even for a search next } { equal = match($0, /[=]/) name = substr($0, 1, equal-1) if (badname(name)) fail("unknown parameter name " bq name eq) value = substr($0, equal+1) if (value ~ /^"/) value = substr(value, 2, length(value)-2) else if (value ~ /[ \t]/) fail("white space within non-quoted parameter " bq name eq) } indefault { if (name in default) fail("duplicated default parameter " bq name eq) default[name] = value next } { name = orient(name) if (name in seen) fail("duplicated parameter " bq name eq) seen[name] = 1 output(o_parm, name, value) } END { if (failed) exit 1 # supply default conns if relevant and not found if (type == "conn") { if (jam("packetdefault", "route")) { output(o_parm, "type", "tunnel") output(o_parm, "leftsubnet", "0.0.0.0/0") output(o_parm, "right", "%opportunistic") output(o_parm, "failureshunt", "passthrough") output(o_parm, "keyingtries", "3") output(o_parm, "ikelifetime", "1h") output(o_parm, "keylife", "1h") output(o_parm, "rekey", "no") } if (jam("clear", "route")) { output(o_parm, "type", "passthrough") output(o_parm, "authby", "never") output(o_parm, "right", "%group") } if (jam("clear-or-private", "route")) { output(o_parm, "type", "passthrough") output(o_parm, "right", "%opportunisticgroup") output(o_parm, "failureshunt", "passthrough") output(o_parm, "keyingtries", "3") output(o_parm, "ikelifetime", "1h") output(o_parm, "keylife", "1h") output(o_parm, "rekey", "no") } if (jam("private-or-clear", "route")) { output(o_parm, "type", "tunnel") output(o_parm, "right", "%opportunisticgroup") output(o_parm, "failureshunt", "passthrough") output(o_parm, "keyingtries", "3") output(o_parm, "ikelifetime", "1h") output(o_parm, "keylife", "1h") output(o_parm, "rekey", "no") } if (jam("private", "route")) { output(o_parm, "type", "tunnel") output(o_parm, "right", "%opportunisticgroup") output(o_parm, "failureshunt", "drop") output(o_parm, "keyingtries", "3") output(o_parm, "ikelifetime", "1h") output(o_parm, "keylife", "1h") output(o_parm, "rekey", "no") } if (jam("block", "route")) { output(o_parm, "type", "reject") output(o_parm, "authby", "never") output(o_parm, "right", "%group") } } filename = originalfilename unseen = "" for (i in pending) unseen = unseen " " i if (!optional && !searching && unseen != "") fail("did not find " type " section(s) " bq substr(unseen, 2) eq) if (!searching) { for (name in default) if (!(name in seen)) output(o_parm, name, default[name]) } else { if (default[search] in wanted) for (name in usesdefault) if (usesdefault[name]) seen[name] = 1 delete seen[""] if (fieldfmt) for (name in seen) output(o_section, name) else { outlist = "" for (name in seen) if (outlist == "") outlist = name else outlist = outlist " " name output(o_names, outlist) } } output(o_status, "") }'