* Aaron Poffenberger <[email protected]> [2017-09-26 21:53:24 -0500]:

> I should add, I thought about adding commands like rcctl's
> `get|getdef|set` commands to do full interface configuration (`set
> nwid wapname` or `set bssid <value>`) but wanted to get feedback on
> the current approach before going to far down the rabbit hole.
> 
> --Aaron
> 
> * Aaron Poffenberger <[email protected]> [2017-09-26 20:39:29 -0500]:
> 
> > Attached is a cli utility to manage network locations. It's modeled
> > after rcctl(8).
> > 
> > It doesn't attempt to replace netstart(8) or ifconfig(8). It works
> > with them by storing location information in a directory and
> > symlinking the hostname.if files to the hostname.if for the selected
> > location.
> > 
> > At the moment if doesn't handle auto discovery of WAPs, but I have
> > code I wrote earlier I'm rewriting and integrating.
> > 
> > netctl is written in pure shell so in theory it could be called at
> > boot time. To do that, I'd probably split netctl into two pieces
> > similar to the way rcctl(8) and rc.subr(8) work. One file for
> > inclusion in /etc and the main ctl for /usr.
> > 
> > The code could be integrated into netstart at some later date.
> > 
> > In addition to the netctl utility I've included a man page.
> > 
> > Comments or feedback would be appreciated.
> > 
> > --Aaron

Below is a new unified diff. I added auto-detection and joining of
wireless networks.

I haven't done the work to allow boot-time auto-detect and joining of
wireless networks, yet. It's on my todo list.

I added a simple example script to the man page showing how to use it
with apmd(8) for resuming.

I also included the Makefile and README which were not in the original
posting.

--Aaron

--- /dev/null   Wed Sep 27 22:09:14 2017
+++ Makefile    Wed Sep 27 21:40:26 2017
@@ -0,0 +1,11 @@
+#      $OpenBSD$
+
+MAN=   netctl.8
+
+SCRIPT=        netctl.sh
+
+realinstall:
+       ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
+               ${.CURDIR}/${SCRIPT} ${DESTDIR}${BINDIR}/netctl
+
+.include <bsd.prog.mk>
--- /dev/null   Wed Sep 27 22:09:14 2017
+++ README.md   Wed Sep 27 20:43:01 2017
@@ -0,0 +1,159 @@
+About netctl
+============
+
+`netctl` is a utility to manage network locations, interface
+configuration files, and to start, stop, or restart interfaces on
+OpenBSD.
+
+`netctl` is not a replacement for `ifconfig(8)` or `netstart(8)`. It's
+utility to make managing locations easier. `netstart` and `ifconfig`
+still do the work. `netctl` makes the user's life, especially portable
+users, easier.
+
+
+Features
+--------
++ Create, delete and switch between **locations** (including restarting
+  **interfaces**)
++ Enable and disable **configurations**.
++ Start, stop and restart **interfaces**.
++ Detect and join known wireless networks (**waps**).
++ Scan for wireless access points.
++ List **locations**, **configurations**, **interfaces**, and **waps**.
+
+See the man page for further details.
+
+
+Install
+-------
+
+There's no installer for `netctl` at the moment. `doas make install`
+will install to **/usr/local** unless you override the PREFIX
+variable.
+
+`make install` will also create **/etc/hostname.d/** and
+**/etc/hostname.d/nwids/**
+
+
+Locations
+---------
+
+`netctl` works by symlinking **hostname.if** files in location
+directories to **/etc/hostname.if** so that the normal `netstart(8)`
+code can do what it already does well.
+
+`netctl` will create **location** directories for you by calling `doas
+netctl create location_name`. `netctl` will **not** at this time
+create the **hostname.if** files. They have to be created the same
+ways as documented in `hostname.if(5)`. See the `man` page for more
+details.
+
+
+Auto Detecting and Joining Networks
+-----------------------------------
+
+Auto detecting and joining requires some setup. `netctl` will not
+connect to wireless access points that are not known. To create a
+*known* wap, a standard wireless **hostname.if** files should be
+created in **/etc/hostname.d/nwids**. *E.g.,*
+
+```
+       $ cat /etc/hostname.d/nwids/Silly\ Wap.nwid
+       nwid "Silly Wap"
+       wpakey '$w@pau7h99'
+       dhcp
+```
+
+The filename **must** be the same as the **nwid** in the file which
+**must** match the **ESSID** of the wireless access point. Any valid
+configuration directive `ifconfig(8)` will accept may be placed in the
+file.
+
+Executing `doas netctl -a start iwm0` will cause `netctl` to scan the
+local network (with `ifconfig iwm0 scan`) and attempt to match the
+results with the names of the **nwids** found by `ls`-ing
+**/etc/hostname.d/nwids**.
+
+**N.B.** `ifconfig scan` is called dwith *each* wlan device unless one
+is specified after the **start** parameter.
+
+
+Auto Detecting and Joining Networks When Resuming
+-------------------------------------------------
+
+`netctl` is not a daemon so it doesn't know when a computer has
+resumed from sleep. `apmd(8)` does know and will call a script called
+**resume** if it exists in **/etc/apm** and is executable.
+
+A simple script like the following will work where the script is
+called **suspend** and the other scripts are symlinked to it (see the
+**man** page for `apmd(8)`):
+
+```
+#!/bin/sh
+
+cmd="${0##*([[:blank:]])/etc/apm/}"
+case "${cmd}" in
+               powerup|resume)
+                               /usr/local/bin/netctl -a restart
+                               ;;
+               *)
+               ;;
+esac
+```
+
+
+TODO
+----
+
++ Add boot time detecting and joining wireless networks
++ Create hostname.if files in locations
+
+
+Maybe TODO
+----------
++ Set and get values in hostname.if files. *E.g.,*
+```
+       $ doas netctl get home nwid
+       "Silly Wap"
+
+       $ doas netctl set home nwid "My WAP Name"
+
+       $ doas netctl set home dhcp on
+```
+
+
+Comments on Boot Time Configuration
+--------
+
+`netctl` is written in pure shell (using no commands outside of shell,
+**/bin** and **/sbin**), so that it can run at boot time when **/usr**
+may not be mounted yet.
+
+I think I can get automated location switching working at boot time. I
+already have code from an earlier project that will match wap scans to
+the correct configuration. I'm rewriting it in pure shell and
+integrating it for use during boot.
+
+
+Copyright and License
+---------------------
+
+Copyright (c) 2017 Aaron Poffenberger <[email protected]>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Updates and Suggestions
+-----------------------
+
+Let me know by `Issue` or email if you find bugs or have suggestions.
--- /dev/null   Wed Sep 27 22:09:14 2017
+++ netctl.8    Wed Sep 27 20:59:39 2017
@@ -0,0 +1,326 @@
+.\"    $OpenBSD$
+.\"
+.\" Copyright (c) 2017 Aaron Poffenberger <[email protected]>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: Sep 23 2017 $
+.Dt NETCTL 8
+.Os
+.Sh NAME
+.Nm netctl
+.Nd configure and control hostname.if definitions
+.Sh SYNOPSIS
+.Nm netctl
+.Op Fl h
+.Nm netctl
+.Cm ls
+.Op Ar lsarg ...
+.Nm netctl
+.Cm create|delete
+.Ar location ...
+.Nm netctl
+.Op Fl dr
+.Cm switch
+.Ar location
+.Op Ar interface ...
+.Nm netctl
+.Cm enable|disable
+.Op Ar configuration ...
+.Nm netctl
+.Op Fl a
+.Cm start|restart
+.Op Ar interface ...
+.Nm netctl
+.Cm stop
+.Op Ar interface ...
+.Nm netctl
+.Op Fl v
+.Cm scan
+.Op Ar interface ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to manage network locations, interface configuration
+files, and to start, stop, or restart interfaces.
+.Pp
+.Nm
+works with
+.Xr ifconfig 8
+and
+.Xr netstart 8 .
+It does not replace them.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl h
+Help message.
+.It Cm ls Op Ar lsarg ...
+Display a list of locations, configurations, interfaces and known wireless 
access points matching
+.Ar lsarg ,
+which can be one of:
+.Bl -tag -width "interfaces" -offset indent -compact
+.It Cm all
+all locations, configurations and interfaces
+.It Cm configurations Op Ar location ...
+all configurations for the current
+.Ar location
+or locations
+.It Cm interfaces
+all interfaces (exluding pseudo-devices)
+.It Cm locations
+all locations
+.It Cm waps
+all known (configured) wireless access points
+.El
+.It Ar location
+The
+.Ar location
+parameter is the name of a collection of related
+.Ar interface
+configuration files.
+.Pp
+.Nm
+locations are stored as directories in
+.Pa /etc/hostname.d .
+See
+.Sx FILES .
+.Pp
+.Sy N.B .
+May not include network pseudo-devices.
+.It Ar configuration
+The
+.Ar configuration
+parameter is a
+.Xr hostname.if 5
+file in a
+.Ar location
+directory.
+See
+.Sx FILES .
+.It Ar interface
+The
+.Ar interface
+parameter is an interface
+.Dq name unit
+as defined in
+.Xr ifconfig 8
+which includes groups.
+.El
+.Sh LOCATION
+The following commands are available for working with network locations:
+.Bl -tag -width Ds
+.It Cm create Ar location ...
+Create one or more locations.
+.It Cm delete Ar location ...
+Delete one or more locations.
+.Pp
+.Sy N.B .
+Deletes all
+.Ar interface
+.Ar configuration
+files in
+.Ar location .
+.It Oo Fl dr Oc Cm switch Ar location Oo Ar interface ... Oc
+Switch to
+.Ar location .
+.It Fl d
+Delete
+.Pa /etc/hostname.if
+not found in
+.Ar location .
+.Pp
+.Sy N.B .
+Does not affect actual interface.
+Removes symlink from previous location.
+Otherwise the symlink to the previous location configuration is left in place.
+.Pp
+.Sy Default :
+leave in place.
+.It Fl r
+Restart the interface(s) after switching.
+.El
+.Sh CONFIGURATION
+The following commands are available for working with interface configurations:
+.Bl -tag -width Ds
+.It Cm enable Ar configuration ...
+Enable an interface
+.Ar configuration .
+.It Cm disable Ar configuration ...
+Disable an interface
+.Ar configuration .
+.El
+.Sh INTERFACE
+The following commands are available for working with interfaces:
+.Bl -tag -width Ds
+.It Oo Fl a Oc Cm start Op Ar interface ...
+Start one or more interfaces.
+.It Fl a
+If the interface is in the wlan group, try to connect to a known wireless 
access point.
+.It Cm stop Op Ar interface ...
+Stop one or more interfaces.
+.It Oo Fl a Oc Cm restart Op Ar interface ...
+Restart one or more interfaces.
+.It Fl a
+If the interface is in the wlan group, try to connect to a known wireless 
access point.
+.It Oo Fl v Oc Cm scan Op Ar interface ...
+Scan for wireless access point with one or more interfaces.
+.It Fl v
+Verbose scan.
+.It Fl vv
+Detailed scan (Same as
+.Cm ifconfig Ar if Ar scan Ns
+).
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width MANPATHX
+.It Ev HN_DIR
+Directory to find
+.Ar location
+directories and interface configuration files.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/hostname.d/nwids/NAME.nwid" -compact
+.It Pa /etc/hostname.d
+default
+.Ar location
+directory.
+.Pp
+.It Pa /etc/hostname.d/*/hostname.XXX
+interface-specific
+.Ar configuration
+files by location.
+.Pp
+.It Pa /etc/hostname.d/nwids
+default location for known wireless access point
+.Ar configuration
+files.
+.Pp
+.It Pa /etc/hostname.d/nwids/NAME.nwid
+.Ar configuration
+files for known wireless access points named for the access point (including 
spaces).
+.El
+.Sh EXAMPLES
+Create new location:
+.Bd -literal -offset indent
+$ doas netctl create home
+.Ed
+.Pp
+Start
+.Ar em0 :
+.Bd -literal -offset indent
+$ doas netctl start em0
+.Ed
+.Pp
+Start
+.Ar iwm
+and connect to known wireless access point:
+.Bd -literal -offset indent
+$ doas netctl -a start iwm0
+Found "Silly Wap"
+Switch iwm0
+iwm0: no link ..... got link
+iwm0: DHCPREQUEST to 255.255.255.255
+iwm0: DHCPREQUEST to 255.255.255.255
+iwm0: DHCPACK from 192.168.1.1 (00:00:00:00:00:00)
+iwm0: bound to 192.168.1.41 -- renewal in 3600 seconds
+.Ed
+.Pp
+Switch location and restart all interfaces:
+.Bd -literal -offset indent
+netctl -r switch home
+.Ed
+.Pp
+Switch location and restart
+.Ar iwm0 :
+.Bd -literal -offset indent
+$ doas netctl -r switch home iwm0
+.Ed
+.Pp
+Switch location and remove unconfigured interfaces:
+.Bd -literal -offset indent
+$ ls /etc/hostname.{em,iwm}0
+/etc/hostname.em0@   /etc/hostname.iwm0@
+
+$ ls /etc/hostname.d/work
+/etc/hostname.d/work/hostname.iwm0
+
+$ doas netctl -d switch work
+
+$ ls /etc/hostname.{em,iwm}0
+/etc/hostname.iwm0@
+.Ed
+.Pp
+Scan for wireless access points with
+.Ar iwm0 :
+.Bd -literal -offset indent
+$ doas netctl -v scan iwm0
+iwm0:
+       supersecurewap
+       notsosecurewap
+       "Silly Wap"
+.Ed
+.Pp
+Simple integration with
+.Xr apmd 8
+might look like:
+.Bd -literal -offset indent
+$ ls -l
+lrwxr-xr-x  1 root  wheel    7 Sep 20 16:23 powerup@ -> suspend
+lrwxr-xr-x  1 root  wheel    7 Sep 20 16:23 resume@ -> suspend
+-rwxr--r--  1 root  wheel  225 Sep 27 20:41 suspend*
+
+$ cat /etc/apm/suspend
+#!/bin/sh
+
+cmd="${0##*([[:blank:]])/etc/apm/}"
+case "${cmd}" in
+               powerup|resume)
+                               /usr/local/bin/netctl -a restart
+                               ;;
+               *)
+               ;;
+esac
+.Ed
+.Sh DIAGNOSTICS
+.Nm
+utility always exits 0.
+.Sh SEE ALSO
+.Xr hostname.if 5 ,
+.Xr apmd 8 ,
+.Xr ifconfig 8 ,
+.Xr netstart 8
+.Sh HISTORY
+.Nm
+is a new utility but it draws inspiration and style from
+.Xr rcctl 8
+and
+.Xr netstart 8 .
+.Pp
+A great deal of credit is due to Antoine Jacoutot, Ingo Schwarze, and
+Robert Nagy for their work on
+.Xr rcctl 8
+and
+.Xr netstart 8 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Aaron Poffenberger Aq Mt [email protected] .
+.Sh BUGS
+.Nm
+should prevent users from running commands that require superuser.
+.Nm
+should al work with some network pseudo-devices like
+.Xr trunk 4 .
--- /dev/null   Wed Sep 27 22:09:14 2017
+++ netctl.sh   Wed Sep 27 20:34:36 2017
@@ -0,0 +1,543 @@
+#!/bin/sh
+#
+# $OpenBSD$
+#
+# Copyright (c) 2017 Aaron Poffenberger <[email protected]>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# cmd OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS cmd, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+# Turn off Strict Bourne shell mode.
+set +o sh
+
+readonly __progname="netctl"
+readonly TRUE=0
+readonly FALSE=1
+readonly HN_DIR=${HN_DIR:-/etc/hostname.d}
+
+# Log an error
+# Usage: log_err msg [exit code]
+# Cheerfully copied from rc.subr(8)
+# Copyright (c) 2010, 2011, 2014-2017 Antoine Jacoutot <[email protected]>
+# Copyright (c) 2010, 2011 Ingo Schwarze <[email protected]>
+# Copyright (c) 2010, 2011, 2014 Robert Nagy <[email protected]>
+log_err() {
+       [ -n "${1}" ] && echo "${1}" 1>&2
+       [ -n "${2}" ] && exit "${2}" || exit 1
+}
+
+# Log a warning
+# Usage: log_err msg [exit code]
+log_warning() {
+       log_msg "${1}" 1>&2
+}
+
+# Log a message
+# Usage: log_msg msg [right justify length]
+log_msg() {
+       local -R${2:-0} _msg="${1}"
+
+       [ -n "${_msg}" ] && echo "${_msg}"
+}
+
+# Log a message to stdout or stderr
+# Usage: usage [2]
+usage() {
+       echo \
+       "usage:  netctl [-h]
+       netctl ls [lsarg ...]
+       netctl create|delete location ...
+       netctl [-dr] switch location [interface ...]
+       netctl enable|disable [configuration ...]
+       netctl [-a] start|restart [interface ...]
+       netctl stop [interface ...]
+       netctl [-v] scan [interface ...]" 1>&${1:-1}
+}
+
+# Get interface configuration
+# Expects variables to be typeset/local from calling fn
+# Usage: get_if_conf if1
+get_if_conf() {
+       # use co-process to preserve values set in while loop
+       ifconfig $1 |&
+       while IFS='     ' read -r -p _l; do
+               _key=${_l%%*([[:blank:]])*(:) *}
+               _val=${_l##*([[:blank:]])*${_key}*(:)*([[:blank:]])}
+
+               [[ ${_key} == 'groups' ]] && _groups=${_val}
+               [[ ${_key} == 'media' ]] && _media=${_val}
+               [[ ${_key} == 'status' ]] && _status=${_val}
+               [[ ${_key} == 'ieee80211' ]] && _ieee80211=${_val}
+               [[ ${_key} == 'inet' ]] && _inet=${_val}
+               [[ ${_key} == 'inet6' ]] && _inet6=${_val}
+       done
+}
+
+# Get interfaces
+# Expects variable _ifs to be typeset/local from calling fn
+# Usage: get_ifs if1
+get_ifs() {
+       local _if _excl_keys
+       # exclude network pseudo-devices
+       set -A _pseudo_devices $(ifconfig -C)
+
+       # use co-process to preserve values set in while loop
+       ifconfig $1 |&
+       while IFS='     ' read -r -p _l; do
+               [[ "${_l}" == *flags* ]] || continue
+
+               _if=${_l%%*([[:blank:]])*():*}
+
+               [ -z "${_if}" ] && continue
+
+               #_if=${_l%%*([[:blank:]])*():*}
+
+               # exclude if-type (san _if num) in pseudo-devices
+               [[ " ${_pseudo_devices[*]}0 " == *" ${_if%%*([[:digit:]])} "* 
]] &&
+                       continue
+
+               _ifs[${#_ifs[*]}]="${_if}"
+       done
+
+       [ -n "${_ifs}" ] || return 1
+}
+
+get_locations() {
+       local _l
+
+       ls -p "${HN_DIR}" |&
+       # use co-process to preserve values set in while loop
+       while IFS='      ' read -r -p _l ; do
+               # skip known nwids, not a location
+               [[ "${_l}" == *nwids* ]] && continue
+               # skip files
+               [[ "${_l}" == *([[:blank:]])*/ ]] || continue
+               _locations[${#_locations[*]}]="${_l%%/}"
+       done
+}
+
+get_configurations() {
+       local _l _location
+
+       _location="$1"
+
+       ls -p "${HN_DIR}/${_location}" |&
+       # use co-process to preserve values set in while loop
+       while IFS='      ' read -r -p _l ; do
+               # skip directories
+               [[ "${_l}" == *([[:blank:]])*[!/] ]] || continue
+
+               _configurations[${#_configurations[*]}]="${_l}"
+       done
+}
+
+# Restart interface
+# Usage: if_restart if1
+if_restart() {
+       if_stop $1 && if_start $1
+}
+
+# Start interface
+# Usage: if_start if1
+if_start() {
+       local _if
+
+       _if=$1
+       [ ${dryrun} -eq 1 ] &&
+               log_msg /bin/sh /etc/netstart -p ${_if} ||
+                       /bin/sh /etc/netstart ${_if}
+}
+
+# Stop interface
+# Usage: if_stop if1
+if_stop() {
+       local _options _groups _media _status _ieee80211 _inet _inet6 \
+             _key _wlan_keys _inet_keys _mode_keys _if
+
+       set -A _wlan_keys -- nwid chan mode nwkey powersave wpakey wpa
+
+       _if=$1
+       get_if_conf ${_if}
+
+       # remove mode if the interface is wireless
+       [ -n "${_ieee80211}" ] && _options="${_options} -mode"
+
+       for _key in ${_wlan_keys[*]} ; do
+               [[ "${_ieee80211}" == *"${_key}"* ]] &&
+                       _options="${_options} -${_key}"
+       done
+
+       [ -n "${_inet}" ] && _options="${_options} -inet"
+       [ -n "${_inet6}" ] && _options="${_options} -inet6"
+
+       [ ${dryrun} -eq 1 ] &&
+               log_msg ifconfig ${_if} ${_options} down ||
+                       ifconfig ${_if} ${_options} down
+}
+
+# Create link from configuration to /etc/hostname.if
+# Usage: link_configuration if from to
+link_configuration() {
+       local _if _from _to
+
+       _if=$1
+       _from="${2}"
+       _to="${3}"
+
+       [ ! -f "${_to}" ] &&
+               [ -f "/etc/hostname.${_if}.disabled" ] &&
+               _to="/etc/hostname.${_if}.disabled"
+
+       [ ! -f "${_from}" ] &&
+               [ ${delete} -eq 1 ] &&
+               rm -f "${_to}" &&
+               return
+
+       [ -f "${_from}" ] &&
+               ln -fs "${_from}" "${_to}" &&
+               log_msg "Switch ${_if}" ||
+                       log_warning "No configuration for ${_if}"
+
+       [ -f "${_to}" ] &&
+               [ ${restart} -eq 1 ] &&
+               if_restart ${_if}
+}
+
+ls_locations() {
+       local _l _locations
+       set -A _locations
+
+       get_locations
+
+       log_msg "Locations:"
+       for _l in "${_locations[@]}" ; do
+               log_msg "\t${_l}"
+       done
+}
+
+ls_configurations(){
+       local _c _configurations _l _locations
+       set -A _locations
+       set -A _configurations
+
+       [[ -n "$@" ]] && set -A _locations "$@" ||
+                       get_locations
+
+       log_msg "Configurations:"
+       for _l in "${_locations[@]}" ; do
+               get_configurations "${_l}"
+
+               log_msg "\t${_l}:"
+               for _c in "${_configurations[@]}" ; do
+                       log_msg "\t\t${_c}"
+               done
+
+               set -A _configurations
+       done
+}
+
+ls_interfaces(){
+       local _if _ifs
+       set -A _ifs
+
+       get_ifs
+
+       log_msg "Interfaces:"
+       for _if in "${_ifs[@]}" ; do
+               log_msg "\t${_if}"
+       done
+}
+
+ls_waps() {
+       local _nwid _path _paths
+       set -A _paths -- ${HN_DIR}/nwids/*.nwid
+       log_msg "Wireless access points (known waps):"
+       for _path in "${_paths[@]}" ; do
+               _nwid=${_path##*([[:blank:]])${HN_DIR}/nwids/}
+               _nwid=${_nwid%%.nwid}
+               log_msg "\t${_nwid}"
+       done
+}
+
+# Get interface details
+# Expects variable _nwids to be typeset/local from calling fn
+# Usage: scan if1
+scan() {
+       local _nwid _i _iselem _if _verbose
+
+       _if=$1
+       _verbose=$2
+       # use co-process to preserve values set in while loop
+       ifconfig ${_if} scan |&
+       while IFS='     ' read -r -p _l ; do
+               [[ "${_l}" == *([[:blank::]])nwid* ]] || continue
+
+               [[ "${_verbose:-0}" -eq 1 ]] &&
+                       _nwids[${#_nwids[*]}]="${_l%%*([[:blank:]])*()% *}%" &&
+                       continue
+
+               [[ "${_verbose:-0}" -eq 2 ]] &&
+                       _nwids[${#_nwids[*]}]="${_l}" &&
+                       continue
+
+               _nwid=${_l%%*([[:blank:]])*()chan *}
+               _nwid=${_nwid##*nwid *()}
+
+               [[ ${_nwid} == '""' ]] && continue
+
+
+               _iselem=0 && _i=0
+               while ((_i < ${#_nwids[*]})) ; do
+                       [[ "${_nwids[_i]}" == "${_nwid}" ]] &&
+                               _iselem=1 && break
+                       ((_i++))
+               done
+               [ ${_iselem} -eq 0 ] && _nwids[${#_nwids[*]}]="${_nwid}"
+       done
+
+       [ -n "${_nwids}" ] || return 1
+}
+
+# Match scanned access points with first known wap
+# Usage: wap_match if1
+wap_match() {
+       local _if _nwids _nwid _path _paths _wap _waps
+       set -A _paths -- ${HN_DIR}/nwids/*.nwid
+
+       _if=$1
+
+       [ -z "${_paths}" ] &&
+               log_warning "No known waps found." &&
+               return 1
+
+       for _path in "${_paths[@]}" ; do
+               _wap=${_path##*([[:blank:]])${HN_DIR}/nwids/}
+               _wap=${_wap%%.nwid}
+               _waps[${#_waps[*]}]="${_wap}"
+       done
+
+       scan ${_if}
+       for _nwid in "${_nwids[@]}" ; do
+               _nwid=${_nwid##\"}
+               _nwid=${_nwid%%\"}
+               [[ " ${_waps[*]} " == *" ${_nwid} "* ]] &&
+                       log_msg "Found '${_nwid}'" &&
+                       _match="${HN_DIR}/nwids/${_nwid}.nwid" &&
+                       return
+       done
+}
+
+typeset -i autowap=0 delete=0 dryrun=0 restart=0 verbose=0
+while getopts :adhnqrv opt ; do
+       case ${opt} in
+               a)
+                       autowap=1;;
+               d)
+                       delete=1;;
+               h)
+                       usage
+                       exit
+                       ;;
+               n)
+                       dryrun=1;;
+               q)
+                       quiet=1;;
+               r)
+                       restart=1;;
+               v)
+                       ((verbose++))
+                       cmd="scan";;
+               :)
+                       log_msg ${__progname}: option requires an argument \
+                               -- ${OPTARG}
+                       usage 2
+                       exit
+                       ;;
+               \?)
+                       log_msg ${__progname}: invalid option -- ${OPTARG}
+                       usage 2
+                       exit
+                       ;;
+       esac
+done
+shift $(( OPTIND - 1 ))
+cmd=$1
+
+case ${cmd} in
+       ls)
+               shift 1
+               lsarg="$1"
+               [[ -z "$@" ]] && usage 2 && exit
+               shift 1
+               lsparms="$@"
+               [[ ${lsarg} == @(all|locations|configurations|interfaces|waps) 
]] ||
+                       usage 2
+               ;;
+       create)
+               shift 1
+               locations="$@"
+               ;;
+       delete)
+               shift 1
+               locations="$@"
+               ;;
+       switch)
+               shift 1
+               location="$1"
+               [ -d "${HN_DIR}/${location}" ] || usage 2
+               [[ -z "$@" ]] && usage 2 && exit
+               shift 1
+               ifs="$@"
+               get_ifs ${ifs} # throws its own errors
+               ifs="${_ifs[@]}"
+               ;;
+       enable|disable|stop)
+               shift 1
+               ifs="$@"
+               get_ifs ${ifs} # throws its own errors
+               ifs="${_ifs[@]}"
+               ;;
+       start|restart)
+               shift 1
+               ifs="$@"
+               if [ ${autowap} -eq 1 ] && [ -z "${ifs}" ] ; then
+                       get_ifs wlan # throws its own errors
+                       ifs="${_ifs[@]}"
+               elif [ ${autowap} -eq 1 ] && [ -n "${ifs}" ] ; then
+                       # Check whether $ifs are in wlan group
+                       get_ifs wlan # throws its own errors
+                       for _if in ${ifs} ; do
+                               [[ " ${_ifs} " == *" ${_if} "* ]] &&
+                                       continue ||
+                                               log_err "${_if} not in wlan 
group" 3
+                       done
+               else
+                       get_ifs ${ifs} # throws its own errors
+                       ifs="${_ifs[@]}"
+               fi
+               ;;
+       scan)
+               shift 1
+               ifs="$@"
+               get_ifs "${ifs:-wlan}" # throws its own errors
+               ifs="${_ifs[@]}"
+               ;;
+       *)
+               usage 2
+               exit
+               ;;
+esac
+
+case ${cmd} in
+       ls)
+               case ${lsarg} in
+                       all)
+                               ls_locations
+                               ls_configurations
+                               ls_interfaces
+                               ls_waps
+                               ;;
+                       locations)
+                               ls_locations
+                               ;;
+                       configurations)
+                               ls_configurations ${lsparms};;
+                       interfaces)
+                               ls_interfaces;;
+                       waps)
+                               ls_waps;;
+                       *)
+                               usage 2
+                               ;;
+               esac
+               ;;
+       create)
+               for _loc in ${locations} ; do
+                       mkdir -p "${HN_DIR}/${_loc}" ||
+                               log_warning "Unable to create location ${_loc}"
+               done
+               ;;
+       delete)
+               for _loc in ${locations} ; do
+                       rm -rf "${HN_DIR}/${_loc}" ||
+                               log_warning "Unable to delete location ${_loc}"
+               done
+               ;;
+       switch)
+               for _if in ${ifs} ; do
+                       _from="${HN_DIR}/${location}/hostname.${_if}"
+                       _to="/etc/hostname.${_if}"
+                       link_configuration ${_if} "${_from}" "${_to}"
+               done
+               ;;
+       enable)
+               for _if in ${ifs} ; do
+                       _from="/etc/hostname.${_if}.disabled"
+                       _to="/etc/hostname.${_if}"
+                       [ -f "${_from}" ] || continue
+
+                       [ -f "${_from}" ] && ! [ -f "${_to}" ] &&
+                               mv "${_from}" "${_to}" ||
+                                       log_warning "Unable to enable ${_if}"
+               done
+               ;;
+       disable)
+               for _if in ${ifs} ; do
+                       _from="/etc/hostname.${_if}"
+                       _to="/etc/hostname.${_if}.disabled"
+                       [ -f "${_from}" ] || continue
+
+                       [ -f "${_from}" ] &&
+                               mv "${_from}" "${_to}" ||
+                                       log_warning "Unable to disable ${_if}"
+               done
+               ;;
+       start|restart)
+               local _match _groups _media _status _ieee80211 _inet _inet6
+               set -A _match
+
+               for _if in ${ifs} ; do
+                       get_if_conf ${_if}
+                       # if _if status == "no network" scanning fails
+                       # stop the interface and reset
+                       [[ "${_status}" == 'no network' ]] &&
+                               log_warning "Stopping ${_if} ..." &&
+                               if_stop ${_if}
+                       wap_match ${_if}
+                       [ -n "${_match}" ] || return
+                       _to="/etc/hostname.${_if}"
+                       link_configuration ${_if} "${_match}" "${_to}"
+                       if_restart ${_if}
+                       ifconfig ${_if}
+               done
+               ;;
+       stop)
+               for _if in ${ifs} ; do
+                       if_stop ${_if}
+               done
+               ;;
+       scan)
+               local _nwids
+               set -A _nwids
+               for _if in ${ifs} ; do
+                       log_msg "${_if}:"
+                       scan ${_if} ${verbose}
+                       for _nwid in "${_nwids[@]}" ; do
+                               log_msg "\t${_nwid}"
+                       done
+               done
+               ;;
+       *)
+               usage 2
+               ;;
+esac

Reply via email to