################################## ## # /usr/lib/network/network ## ################################## . /usr/lib/network/globals ### Profile listing and loading ## # list_profiles # Outputs a list of all profiles list_profiles() { # JP: follow aliases with -L, also skip profiles that start with '.' or end with '~' or '.conf' (so profile.conf can be the wpa.conf file for profile) find -L "$PROFILE_DIR/" -maxdepth 1 -type f -not -name '*~' -not -name '*.conf' -not -name '.*' -printf "%f\n" } # load_profile profile # source the profile load_profile() { validate_profile "$1" || return 1 . "$PROFILE_DIR/$1" } # validate_profile profile # check whether profile exists and is usable validate_profile() { [[ -z "$1" ]] && return 1 if [[ ! -f "$PROFILE_DIR/$1" ]]; then report_err "Profile \"$1\" does not exist." return 1 fi . "$PROFILE_DIR/$1" if [[ -z "$INTERFACE" ]]; then report_err "Profile error: no INTERFACE to configure." return 1 fi if [[ ! -f "$CONN_DIR/$CONNECTION" ]]; then report_err "Profile error: $CONNECTION is not a valid connection, check spelling or look at examples." return 1 fi } ### Profile suspend/resume ## # interface_suspend interface/all [call_profile_down? default=yes] # store a list of running profiles and take them down (unless $2 is no) interface_suspend() { report_debug interface_suspend "$@" [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles} [[ ! -d "$STATE_DIR/suspend" ]] && mkdir "$STATE_DIR/suspend" find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \ | while read prof; do # the pipe to "while read" will create a subshell, so sourced variables will already be in a sandbox # we just need to clear INTERFACE which is all we care about unset INTERFACE . "$STATE_DIR/profiles/$prof" if [[ "$1" == all || "$1" == "$INTERFACE" ]]; then report_notify "suspending interface $INTERFACE with profile $prof" cp "$STATE_DIR/profiles/$prof" "$STATE_DIR/suspend/" if checkyesno "${2:-yes}"; then profile_down "$prof" fi fi done } # all_suspend # store a list of running profiles and take them down all_suspend() { interface_suspend all } # all_resume # resume suspended interfaces # optional arguments: interfaces not to resume (e.g., because they're disabled) all_resume() { report_debug all_resume "$@" find "$STATE_DIR/suspend/" -maxdepth 1 -type f -printf '%f\n' \ | while read prof; do # the pipe to "while read" will create a subshell, so sourced variables will already be in a sandbox # we just need to clear INTERFACE which is all we care about unset INTERFACE . "$STATE_DIR/suspend/$prof" if [[ $# -eq 0 || ! " $* " =~ " $INTERFACE " ]]; then report_notify "resuming interface $INTERFACE with profile $prof" profile_up "$prof" rm -f "$STATE_DIR/suspend/$prof" # if profile_up succeeds, it will have already removed this fi done } ### Profile up/down ## # profile_up profile [literal essid] # put all profiles up # profile_up() { ( # Keep inside subshell so that options from one profile don't cross to others # exit 1 used in a subshell is effectively exiting a new process [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles,suspend} local status PROFILE="$1" # save PROFILE in a variable so that it's available to PREUP/POSTDOWN etc hooks load_profile "$PROFILE" || exit 1 if check_profile "$PROFILE"; then report_err "$PROFILE already connected." exit 1 fi # NETWORKS_EXCLUSIVE, rc.conf: Profiles are globally mutually exclusive # EXCLUSIVE, network.d/profile: Individual profile is mutually exclusive if checkyesno "$NETWORKS_EXCLUSIVE" || checkyesno "$EXCLUSIVE"; then all_down fi report_try "$PROFILE up" status=$(query_iface "$INTERFACE" "$CONNECTION") report_debug "status reported to profile_up as: $status" if [[ -n "$status" && "$status" != "$PROFILE" ]]; then case "$status" in disabled) # interface radio-switch disabled report_fail "INTERFACE $INTERFACE is disabled." exit 1 ;; external) # interface being controlled externally report_fail "INTERFACE $INTERFACE was configured by another application." exit 1 ;; *) # report_fail if iface in use by another profile? (default no) if checkyesno "$CHECK"; then report_fail "INTERFACE $INTERFACE already in use by another profile." exit 1 elif ! ( # use subshell so interface_down -> profile_down doesn't overwrite loaded profile interface_down "$INTERFACE" "$CONNECTION" # supply current CONNECTION-type as hint ); then report_fail exit 1 fi ;; esac fi if ! ( eval $PRE_UP ); then # JP: sandbox the eval so variables don't bleed into current function report_debug profile_up "PRE_UP failed" report_fail exit 1 fi if ! "$CONN_DIR/$CONNECTION" up "$PROFILE" "$2"; then report_debug profile_up "connect failed" report_fail # "$CONN_DIR/$CONNECTION" down "$PROFILE" "$2" # JP: should we do this to make sure? exit 1 fi if ! ( eval $POST_UP ); then # JP: sandbox the eval report_debug profile_up "POST_UP failed" report_fail # failing POST_UP will take interface down "$CONN_DIR/$CONNECTION" down "$PROFILE" "$2" exit 1 fi set_profile up "$PROFILE" # unset EXCLUSIVE report_success ); return $? } # profile_down profile # take profile down # profile_down() { ( [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles,suspend} local status PROFILE="$1" # save PROFILE in a variable so that it's available to PREUP/POSTDOWN etc hooks load_profile "$PROFILE" || exit 1 status=$(query_iface "$INTERFACE" "$CONNECTION") report_debug "status reported to profile_down as: $status" if [[ "$status" != "$PROFILE" ]]; then # if interface not available to be controlled by netcfg, then # any profiles will have been removed by check_iface # else another profile is running report_err "Profile wasn't connected." exit 1 fi report_try "$PROFILE down" # JP: the following will have already been caught by the block above... # if [[ "$status" == external ]]; then # report_fail "Interface $INTERFACE was configured by another application." # exit 1 # fi if ! ( eval $PRE_DOWN ); then # JP: sandbox the eval report_debug profile_down "PRE_DOWN failed" true # JP: did we want failing PRE_DOWN to leave the profile active? # report_fail # exit 1 fi if ! "$CONN_DIR/$CONNECTION" down "$PROFILE" "$2"; then report_debug profile_up "disconnect failed" report_fail exit 1 fi if ! ( eval $POST_DOWN ); then # JP: sandbox the eval report_debug profile_down "POST_DOWN failed" report_fail exit 1 fi set_profile down "$PROFILE" report_success ); return $? } # all_down # take all registered profiles down all_down() { find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \ | while read prof; do profile_down "$prof" done } # interface_down interface # take interface down # interface_down() { local status=$(query_iface "$@") # we may not be hinting what CONNECTION type to use, e.g. when called from /usr/bin/netcfg case "$status" in none|disabled) return 0;; external) return 1;; *) profile_down "$status";; esac } ### Status setting functions ## # set_profile up/down profile # Set profile state, either up or down # set_profile() { if [[ "$1" == up ]]; then ( # subshell creates sandbox for sourced variables . "$PROFILE_DIR/$2" cp "$PROFILE_DIR/$2" "$STATE_DIR/profiles/" echo "$2" > "$STATE_DIR/last_profile" set_iface up "$INTERFACE" "$2" ) elif [[ "$1" == down && -f "$STATE_DIR/profiles/$2" ]]; then ( # subshell . "$STATE_DIR/profiles/$2" rm "$STATE_DIR/profiles/$2" set_iface down "$INTERFACE" "$2" ) fi } # set_iface up/down interface [profile/disabled/external] # Set interface status to up/down (really up means "unavailable" (may be down & disabled) and down means "available" # optionally link it to a profile. # set_iface() { local PROFILE="${3:-external}" # JP: this is equivalent to the two-line block that was here before if [[ "$1" == up ]]; then echo "PROFILE=$PROFILE" > "$STATE_DIR/interfaces/$2" elif [[ "$1" == down ]]; then rm -f "$STATE_DIR/interfaces/$2" # JP: add -f so we don't complain if the interface isn't up fi } ### Checking/querying functions ## # check_iface interface # Return 0 if interface up # Return 1 if interface down # check_iface() { [[ -f "$STATE_DIR/interfaces/$1" ]] && return 0 return 1 } ############################################################################ # Note: get_iface_prof left in for compatibility with any external code that calls it # but in this codebase, it's superceded by query_iface # get_iface_prof interface # Echo interface profile and return 0 if up # Return 1 if down. # get_iface_prof() { if check_iface "$1"; then . "$STATE_DIR/interfaces/$1" echo $PROFILE else return 1 fi } ############################################################################ # check_profile profile # Return 0 if profile registered as being up # Return 1 if profile not registered # check_profile() { [[ -f "$STATE_DIR/profiles/$1" && ! -f "$STATE_DIR/suspend/$1" ]] && return 0 return 1 } # query_iface interface [connection] # Return 0 if interface up or otherwise unavailable for connecting # Return 1 if interface down (and so free to be connected) # echoes which profile controls the interface (or "external" or "disabled") query_iface() { local INTERFACE="$1" PROFILE connection="$2" if [[ -z "$connection" ]]; then # JP: the difficulty with factoring this code into the CONNECTION routines # is that we sometimes want to query/control the interface without knowing what CONNECTION type it instantiates # so we supply the CONNECTION type as a hint ($2) when possible # and when CONNECTION wasn't hinted, we default to wireless-dbus if iface verifies as wireless, else ethernet-iproute if "$CONN_DIR/wireless-dbus" verify "$INTERFACE"; then connection=wireless-dbus else connection=ethernet-iproute fi fi if ! "$CONN_DIR/$connection" query "$INTERFACE" active; then # interface is not up if [[ -f "$STATE_DIR/interfaces/$INTERFACE" ]]; then # but the interface thinks it's running a profile... . "$STATE_DIR/interfaces/$INTERFACE" # these files should only contain a PROFILE=... statement if [[ "$PROFILE" == disabled ]]; then echo "disabled" return 0 elif [[ "$PROFILE" != external ]]; then report_warn "INTERFACE $INTERFACE no longer being controlled by $PROFILE" set_profile down "$PROFILE" PROFILE= fi set_iface down "$INTERFACE" # update iface status: no longer profile/external fi echo "$PROFILE" return 1 else "$CONN_DIR/$connection" query "$INTERFACE" profile return $? fi } ### Utility functions ## # Check if variable is a member of an array inarray() { local item search="$1" shift for item; do if [[ "$item" == "$search" ]]; then return 0 fi done return 1 } quirk() { inarray "$1" "${QUIRKS[@]}" } ### From FreeBSD's /etc/rc.subr ## # checkyesno var # Test $1 variable, and warn if not set to YES or NO. # Return 0 if it's "yes" (et al), nonzero otherwise. # checkyesno() { local _value="$1" #debug "checkyesno: $1 is set to $_value." case "$_value" in # "yes", "true", "on", or "1" [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;; # "no", "false", "off", or "0" [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;; *) #warn "\$${1} is not set properly - see ${rcvar_manpage}." return 1 ;; esac }