#!/bin/bash
#
# Copyright (c) 2008 SUSE LINUX Products GmbH, Nuernberg, Germany.
# 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.
#
# 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.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
#
# Author: Michael Calmer <mc@suse.de>
#         Marius Tomaschewski <mt@suse.de>
#
# $Id: netconfig 1857 2009-08-04 07:53:57Z mt $
#

#
# Exit codes
#
# 0  success
# 1  error
# 20 a module was not able to change the configuration
#    because it was changed since the last run
#


# The environment variable ROOT indicates the root of the system to be
# managed by SuSEconfig when that root is not '/'
r=$ROOT

. $r/etc/sysconfig/network/scripts/functions.netconfig

PROGNAME=${0##*/}
STATEDIR=$r/var/run/netconfig/

MODULEDIR=$r/etc/netconfig.d/

. $r/etc/sysconfig/network/config

INPUTFORMAT=dhcpcd

function modify() {
    if [ "x$SERVICE" = "x" ]; then
        warn "No service informations available"
        unLock "$PROGNAME"
        exit 1
    fi

    if test "x${LEASEFILE}" != x -a ! -r "${LEASEFILE}" ; then
        echo "Unable to read specified input file '${LEASEFILE}'" >&2
        exit 1
    fi

    local a b
    IFS=. read a b < /proc/uptime
    local CREATETIME=$a
    local _INTERFACE='' _SERVICE=''
    local -a KEYS VALS

    if [ "$INPUTFORMAT" == "dhcpcd" ]; then
        local key val err=0
        # add CREATETIME at index 0 !!
        KEYS=('CREATETIME'  'SERVICE')
        VALS=("$CREATETIME" "$SERVICE")
        while IFS== read -sr key val ; do
            test -n "$key" || {
                test -n "$val" || continue
                err=$val ; break
            }
            case $key in
                (CREATETIME) continue ;;
                (SERVICE|INTERFACE)
                    eval "_$key='$val'"
                    if test "x${!key}" != x -a "x${!key}" != "x$val" ; then
                        echo "Input value $key conflicts with command line argument" >&1
                        exit 1
                    fi
                ;;
            esac
            for k in ${KEYS[@]} ; do
                test "x$k" = "x$key" && continue 2
            done
            KEYS[${#KEYS[@]}]=$key
            VALS[${#VALS[@]}]=$val
        done < <(netconfig_kv_filter ${LEASEFILE:+"$LEASEFILE"})
        if test $err -ne 0 ; then
            echo "Invalid syntax in ${LEASEFILE:+${LEASEFILE}, }line $err" >&2
            exit 1
        fi
    else
	echo "Unsupported lease file / input format '$INPUTFORMAT'" >&2
	exit 1
    fi

    # NetworkManager policy merged settings
    if [ "x$SERVICE"        = "xNetworkManager" -a \
         "x$INTERFACE"      = "x" ] ;
    then
        # NetworkManager policy merged config
        CFGFILE="$STATEDIR/NetworkManager.netconfig"

        # first get the createtime. we want to keep it.
	get_variable CREATETIME "$CFGFILE"
	KEYS[0]='CREATETIME'
	VALS[0]="$CREATETIME"

        # remove it also in modify.
        # We want to create it with the same name again.
	rm -f "$CFGFILE"
    else
	# use interface name from config if provided
	if [ "x$INTERFACE" = "x" -a "x$_INTERFACE" != "x" ]; then
	     INTERFACE="$_INTERFACE"
        fi

	if [ "x$INTERFACE" = "x" ]; then
            warn "No interface informations available"
            unLock "$PROGNAME"
            exit 1
	fi

        if [ ! -d "$STATEDIR/$INTERFACE" ]; then
		mkdir -p "$STATEDIR/$INTERFACE" || {
			warn "Can not create interface state dir"
			unLock "$PROGNAME"
			exit 1
		}
	fi

        local CFGFILE=`find_cfgfile "$INTERFACE" "$SERVICE"`
        if [ "x$CFGFILE" != "x" ]; then
            # first get the createtime. we want to keep it.
            get_variable CREATETIME "$CFGFILE"
            KEYS[0]='CREATETIME'
            VALS[0]="$CREATETIME"

            # remove it also in modify. 
            # We want to create it with the same name again.
            rm -f "$CFGFILE"
        fi
    
        if [ "x$CFGFILE" = "x" ]; then
            local CFGNAME=`get_new_cfgname`
            CFGFILE="$STATEDIR/$INTERFACE/$CFGNAME"
        fi
   fi

   debug "write new STATE file $CFGFILE"
   for((i=0; i<${#KEYS[@]} && i<${#VALS[@]}; i++)) ; do
       printf "%s='%s'\n" "${KEYS[$i]}" "${VALS[$i]}"
   done > "$CFGFILE"

   return 0
}

function remove() {
    if [ "x$SERVICE" = "x" ]; then
        warn "No service informations available"
        unLock "$PROGNAME"
        exit 1
    fi

    # NetworkManager policy merged settings
    if [ "x$SERVICE"        = "xNetworkManager" -a \
         "x$INTERFACE"      = "x" ] ;
    then
        # NetworkManager policy merged config
        CFGFILE="$STATEDIR/NetworkManager.netconfig"

    else
	if [ "x$INTERFACE" = "x" ]; then
            warn "No interface informations available"
            unLock "$PROGNAME"
            exit 1
        fi

        CFGFILE=`find_cfgfile "$INTERFACE" "$SERVICE"`
    fi

    if [ "x$CFGFILE" != "x" ]; then
        debug "remove STATE file $CFGFILE"
        rm -f "$CFGFILE"
    fi

    return 0
}

#
# find_cfgfile <interface> <service>
#
function find_cfgfile() {

    test -z "$1" && return 1
    test -z "$2" && return 1

    local INTERFACE=$1
    local CUR_SERVICE=$2

    for CFG in `ls -X $STATEDIR/$INTERFACE/`; do
        get_variable "SERVICE" $STATEDIR/$INTERFACE/$CFG
            
        if [ "$CUR_SERVICE" = "$SERVICE" ]; then
                CFGNAME=$CFG
                echo "$STATEDIR/$INTERFACE/$CFG"
                return;
        fi
    done
    return 0;
}

function get_new_cfgname() {
    local CFGNAME=""

    for CFG in `ls -X -r $STATEDIR/$INTERFACE/`; do
        CNT=`echo "$CFG" | sed 's/netconfig//'`
        ((CNT++))
        CFGNAME="netconfig$CNT"
        break
    done
    
    if [ "x$CFGNAME" = "x" ]; then
        CFGNAME="netconfig0"
    fi
    echo $CFGNAME;
}

function run_modules() {
    local errors=()
    local msg=""
    local ret=0

    if [ -n "$FORCE_REPLACE" ]; then
        export FORCE_REPLACE=$FORCE_REPLACE
    fi
    if [ -n "$r" ]; then
        export ROOT=$r
    fi

    debug "Module order: $NETCONFIG_MODULES_ORDER"
    for plg in $NETCONFIG_MODULES_ORDER; do
	case $plg in
	  -*) continue ;;
	esac
        if [ ! -x $MODULEDIR/$plg ]; then
            continue
        fi
        msg=`$MODULEDIR/$plg 2>&1`
        if [ "x$msg" != x ]; then
            errors[${#errors[@]}]="$msg"
        fi
        if [ "$?" != "0" ]; then
            ret=$?
        fi
    done

    if [ ${#errors[@]} -gt 0 ]; then
        for msg in "${errors[@]}" ; do
            echo "$msg"
        done
    fi
    return $ret
}


function usage () 
{
    cat << EOT >&2
  usage:   $PROGNAME <action> <options>
  action:  modify, update, remove
  options:                               mandatory for:
    -s|--service <service>               modify, remove
    -i|--interface <interface>           remove
    -l|--lease-file <path>
    -F|--input-format <format>
    -f|--force-replace
    -v|--verbose
    -h|--help                            (does not need an action)


  --force_replace tell the modules to do file changes even if the user
                  touched them.
  --input-format  specifies the modify lease / input format; currently
                  only dhcpcd compatible input format is supported.

  modify requires an interface and service specific settings via STDIN
         or as file using the --lease-file option.
         Already existing settings for this interface and service will
	 be replaced with the new one, otherwise netconfig creates a
 	 new state file. Finaly, netconfig updates the managed files.

  update updates the managed files with the current set of settings.

  remove removes the interface and service specific settings.
 
EOT
  if [ -n "$1" ] ; then
     echo "" >&2
     ERROR="  ERROR:   "
     echo -e "$*\n" | while read line; do
       echo "${ERROR}${line}" >&2
       ERROR="           "
     done
  fi
  exit 1
}

COMMANDLINE="$@"
ACTION=""
SERVICE=""
INTERFACE=""
VARIABLE=""
while true ; do
    # Interface is checked against sysfs before the data is used;
    # reject just the most the ugliest characters...
    REGEX=''
    case "$1" in
        -s|--service)   VARIABLE=SERVICE ;
                        REGEX='^[[:alnum:]_-]+$' ;;
        -i|--interface) VARIABLE=INTERFACE ;
                        REGEX='^[^'"'"'`"\\/[:space:][:cntrl:]]+$' ;;
        -l|--lease-file) VARIABLE=LEASEFILE;;
        -F|--input-format) VARIABLE=INPUTFORMAT;;
        -f|--force-replace) FORCE_REPLACE="true";;
        -v|--verbose) VERBOSE="yes";;
        -h|--help) usage;;
        "") break ;;
        --)
            shift
            test -n "$ACTION" -o $# -gt 1 \
            && usage "Exactly one action may be given.\n"\
                "Currently given actions: $ACTION $*"
            ACTION="$1"
            shift
            break
            ;;
        *)
        test -n "$ACTION" && usage "Exactly one action may be given.\n"\
            "Currently given actions: $ACTION $1"
        ACTION="$1"
        ;;
        -*) usage Unknown option $1;;
    esac
    if [ -n "$VARIABLE" ] ; then
        case $2 in
        (""|-*) usage "Option $1 needs an argument" ;;
        esac
        if [ -n "$REGEX" ] ; then
            if ! [[ ${1} =~ ${REGEX} ]] ; then
                usage "Invalid character in value for option $VARIABLE"
            fi
        fi
        eval $VARIABLE=\$2
        shift
        VARIABLE=""
    fi
    shift
done

test -z "$ACTION" && usage No action was given

if [ "$VERBOSE" = "yes" ]; then
    export VERBOSE=$VERBOSE
fi

#
# before we start, check for root
#
if test "$UID" != "0" -a "$USER" != root -a -z "$ROOT" ; then
    echo "You must be root to start $0." >&2
    exit 1
fi

# Check if we can write in /tmp
if ! touch $r/tmp &>/dev/null; then
    echo "Filesystem read only: Cannot modify anything" >&2
    exit 1
fi

# Set usefull default umask
umask 0022

# Create state directory
if ! mkdir -p "$STATEDIR" ; then
    echo "Unable to create netconfig state directory '$STATEDIR'" >&2
    exit 1
fi

#
# try to get the lock
#
openLockWait "$PROGNAME" 5
if [ "$?" != 0 ]; then
    log "open lock for $PROGNAME failed. Abort."
    exit 1;
fi

EXITVAL=0

case "$ACTION" in
    modify)
           modify
           if [ "$?" != "0" ]; then EXITVAL=$?; fi
           run_modules
           if [ "$?" != "0" ]; then EXITVAL=$?; fi
           ;;
    update)
           run_modules
           if [ "$?" != "0" ]; then EXITVAL=$?; fi
           ;;
    remove)
           remove
           if [ "$?" != "0" ]; then EXITVAL=$?; fi
           run_modules
           if [ "$?" != "0" ]; then EXITVAL=$?; fi
           ;;
    *)
    usage "Invalid action given.\n"
    ;;
esac
    
unLock "$PROGNAME"

exit $EXITVAL

# vim: set ts=8 sts=4 sw=4 ai et:
