#!/bin/bash
#
# This is netprofile, an application which allows to use
# different sets of network profiles on same physical network connections.
#
# Copyright, (C) Mandriva, 2003-2009.
# Copyright, (C) Frederic Lepied, 2003-2005
# Copyright, (C) Eugeni Dodonov, 2009
#
# TODO: security concerns (profiles containing '/', ';' or '..')
# TODO: implement check command (to check for changes in profile)
# TODO: implement getopt support

NETPROFILE=/etc/netprofile
PROFILES=$NETPROFILE/profiles
MODULES=$NETPROFILE/modules
CURRENT=$NETPROFILE/current
VERBOSE=0
LOG=/var/log/netprofile.log
DATE=$(date)
LOG_TMP=$(mktemp)

DEFAULT_PROFILE=default

function cleanup {
        rm -f $LOG_TMP
}
trap cleanup 0
# TODO: handle other signals gracefully

function restore_files {
        PROFILE=$1
        MODULE=$2
        FILES=$3
        # restoring old files
        SAVED_FILES="$PROFILES/$PROFILE/$MODULE"
        if [ ! -d "$SAVED_FILES" ]; then
                log " ** no saved files for $MODULE were found in $PROFILE"
        else
                log " ** restoring files for $MODULE from $SAVED_FILES"
                rm -f $LOG_TMP
                (cd $SAVED_FILES && tar cpf - . 2>>$LOG_TMP) | (cd / && tar xpf - 2>>$LOG_TMP)
                catlog
        fi
}

function remove_files {
        PROFILE=$1
        MODULE=$2
        FILES=$3
        SAVED_FILES="$PROFILES/$PROFILE/$MODULE"
        if [ ! -d "$SAVED_FILES" ]; then
                log " ** not removing files for $MODULE in $PROFILE as no saved files were found"
        else
                log " ** removing files to restore $MODULE from $PROFILE"
                rm -f $LOG_TMP
                rm -rf -- $(echo $FILES) 2>>$LOG_TMP
                catlog
        fi
}

function save_files {
        PROFILE=$1
        MODULE=$2
        FILES=$3
        # saving files
        SAVED_FILES="$PROFILES/$PROFILE/$MODULE"
        if [ ! -d "$SAVED_FILES" ]; then
                log "creating profile directory: $SAVED_FILES"
                mkdir -p $SAVED_FILES
        fi
        log " ** copying files for $MODULE into $SAVED_FILES"
        rm -f $LOG_TMP
        tar cpf - $(echo $FILES) 2>>$LOG_TMP | (cd $SAVED_FILES && tar xpf -) 2>>$LOG_TMP
        catlog
}

function restart_services {
        SERVICES=$1
        for service in $SERVICES; do
                log " ** restarting service $service"
                rm -f $LOG_TMP
                chkconfig $service && service $service restart 2>>$LOG_TMP
                catlog
        done
}

function print_debug {
        if [ "a$VERBOSE" = "a1" ]; then
                echo "$*"
        fi
}

function log {
        print_debug "$1"
        echo "$DATE: $1" >> $LOG
}

function catlog {
        if [ -s $LOG_TMP ]; then
                while read line; do
                        echo "$DATE: $line" >> $LOG
                done < $LOG_TMP
        fi
}

function restore_profile {
        # sets a network profile
        PROFILE=$1
        RESTART_SERVICES=${1:-0}
        # do we have a profile?
        if [ ! -d "$PROFILES/$PROFILE" ]; then
                log "Profile $PROFILE does not exists"
                return 1
        else
                log "Restoring profile $PROFILE from $PROFILES/$PROFILE"
        fi
        for module in $MODULES/*; do
                # if module is not executable, disable it
                test -x $module || continue
                # unsetting old variables
                unset NAME SERVICES FILES
                # unsetting old functions
                unset -f save restore
                . $module
                print_debug "Removing old files for $NAME"
                remove_files "$PROFILE" "$NAME" "$FILES"
                print_debug "Restoring files for $NAME"
                restore_files "$PROFILE" "$NAME" "$FILES"
                if [ ! "a$RESTART_SERVICES" = "a0" ]; then
                        print_debug "Restarting services for $NAME"
                        restart_services "$SERVICES"
                fi
                # calling module-specific hook
                if [ "`type -t restore`" == 'function' ]; then
                        restore "$PROFILE"
                fi
        done
}

function update_current {
        PROFILE=$1
        echo "$PROFILE" > $CURRENT
}

function save_profile {
        # saves current system settings into a profile
        PROFILE=$1
        test -d "$PROFILES/$PROFILE" || mkdir -p "$PROFILES/$PROFILE"
        log "Saving files for $PROFILE"
        for module in $MODULES/*; do
                # if module is not executable, disable it
                test -x $module || continue
                # unsetting old variables
                unset NAME SERVICES FILES
                # unsetting old functions
                unset -f save restore
                . $module
                save_files "$PROFILE" "$NAME" "$FILES"
                # calling module-specific hook
                if [ "`type -t save`" == 'function' ]; then
                        save "$PROFILE"
                fi
        done
}

function remove_profile {
        # removes a network profile
        PROFILE=$1
        # do we have a profile?
        if [ ! -d "$PROFILES/$PROFILE" ]; then
                log "Profile $PROFILE does not exists"
                return 1
        else
                log "Removing profile $PROFILE from $PROFILES/$PROFILE"
        fi
        # TODO: save backup
        rm -rf "$PROFILES/$PROFILE"
}

function check_root {
        # must be run as root
        if [ ! "a$UID" = "a0" ]; then
                echo "Error: $0 must be run as root!"
                exit 1
        fi
}

function check_current_profile {
        if [ -z "$NEW_PROFILE" ]; then
                echo "Error: no profile specified"
                exit 1
        fi

        if [ "a$NEW_PROFILE" = "a$CURPROFILE" ]; then
                echo "The $NEW_PROFILE is the current profile."
                exit 0
        fi
}

function get_boot_profile {
        if [ -z "$NEW_PROFILE" ]; then
                # get profile name from cmdline
                NEW_PROFILE=$(cat /proc/cmdline | tr ' ' '\n' | grep PROFILE | cut -f 2 -d =)
        fi
        # no profile passed on command line, show user a menu
        if [[ -z "$NEW_PROFILE" && -d /etc/netprofile/profiles/ ]]; then
                nb_profiles=`ls /etc/netprofile/profiles | wc -l`;
                if [[ "$nb_profiles" == "1" ]]; then
                        NEW_PROFILE=`basename /etc/netprofile/profiles/*`
                else 
                        pushd  /etc/netprofile/profiles/ > /dev/null 2>&1
			count=1
			profiles=""
			keys=""
			for z in *; do
				if [ "a$profiles" = "a" ]; then
					profiles="($count) $z"
				else
					profiles="$profiles ($count) $z"
				fi
				# is it current profile?
				if [ "a$z" = "a$CURPROFILE" ]; then
					profiles="${profiles}*"
				fi
				# update keystrokes
				keys="${keys}${count}"
				count=$((count + 1))

				# we stop at 9 profiles to fit on screen
				if [ $count -gt 9 ]; then
					profiles="$profiles ..."
					break
				fi
			done
                        popd > /dev/null 2>&1
			# show graphical choice if plymouth is running
			if [ -x /bin/plymouth ] && /bin/plymouth --ping ; then
				/bin/plymouth pause-progress
				message=$(echo -ne "Select network profile:\n$profiles")
				/bin/plymouth message --text="$message"
				# show the message on screen, as plymouth seems not to work well
				# with text mode :(
				echo
				echo $message
				# using /var/run/netprofile_boot as flag to indicate that user
				# has chosen a profile. If no profile was chosen, leave after
				# 5 seconds
				{
					# stop waiting for keystroke after timeout
					sleep 5
					if [ ! -f /var/run/netprofile_boot ]; then
						# kill pending plymouth process, otherwise
						# we'll hang later in boot stage
						kill $(pidof plymouth) > /dev/null 2>&1
						/bin/plymouth unpause-progress
						message="Using current network profile ($(netprofile current))"
						/bin/plymouth message --text="$message"
						echo
						echo netprofile: $message
					else
						rm -f /var/run/netprofile_boot
					fi
					exit 0
				}&
				rm -f /var/run/netprofile_boot
				exec 2>/dev/null
				/bin/plymouth watch-keystroke --keys=$keys --command=/sbin/read-netprofile
			else
				# plymouth not available, should use text-based choice (ncurses?)
				exit 0
			fi
			exit 0
                fi
        fi
}

function reload_netapplet {
        # make net_applet reload the configuration
        # (needs "current" file to be up to date)
        PID=`pidof -x net_applet`
        [[ -n "$PID" ]] && kill -HUP $PID
}

function notify {
        MESSAGE="$1"
        DBUS_SEND=`which dbus-send 2>/dev/null`
        if [ -x "$DBUS_SEND" ]; then
                $DBUS_SEND --system --type=signal /com/mandriva/user com.mandriva.user.custom_notification string:"NetProfile" string:"$MESSAGE"
        fi
}

function usage {
        cat << EOF
Usage: $0 [-options] <operation> [profile]

The following operations are supported:

operations on network profiles:
 switch [-v]    save the system configuration into current profile, and
                restore files belonging to the new profile. If the new
                profile does not exists, it will be created with base on
                current configuration.

 boot [-v]      determine the desired profile from the kernel command line,
                or show user a menu to select the desired profile.

 delete [-v]    delete an existent profile.

 current        display the currently used profile.

 list [-v]      list available profiles.

 reset          resets the network profiles configuration. This action will
                have the following effects:
                 - all existent profiles will be removed
                 - current system configuration will be saved as
                   '$DEFAULT_PROFILE' profile
                you might want to use this action when a big system change
                was performed (distro upgrade), or you simple want to start
                over with a new configuration.

advanced options:
 save [-v]      save current settings into the current profile without switching
                to a different profile
 load [-v]      load a different profile without saving current settings
                basically, the sequence of 'netprofile save; netprofile load'
                does exactly the same thing as 'netprofile switch', but with
                higher degree of control over the performed actions.

operations on netprofile modules:
 modules [-v]           list available modules

 module_enable [-v]     enables a netprofile module

 module_disable [-v]    disables a netprofile module


Options:
  -v, --verbose                 show detailed information for performed
                                operations
  -h, --help                    show this help message

EOF
exit 0
}

NO_RESTART_NETWORK=

if [ "a$1" = "a" ]; then
        usage
fi

args=`getopt -o v,h -l verbose,help -n "$0" -- "$@"`
eval set -- "$args"

while true; do
        case "$1" in
                -v|--verbose) VERBOSE=1;        shift 1;;
                -h|--help) usage;               shift 1;;
                --)                             shift ; break;;
                *) echo "Invalid argument: $1"; exit 1 ;;
        esac
done

ACTION="$1"
NEW_PROFILE="$2"
CURPROFILE=$(cat $CURRENT 2>/dev/null)

if [ "a$ACTION" = "aswitch" ]; then
        check_root
        check_current_profile
        if [ ! -n "$CURPROFILE" ]; then
                # no current profile
                log "Creating initial profile $NEW_PROFILE"
                save_profile "$NEW_PROFILE"
        else
                log "Switching from profile $CURPROFILE to profile $NEW_PROFILE"
                save_profile "$CURPROFILE"
                notify "Switching to network profile $NEW_PROFILE, please wait a few moments..."
                restore_profile "$NEW_PROFILE" 1 || (log "Creating new profile $NEW_PROFILE"; save_profile "$NEW_PROFILE")
        fi
        update_current "$NEW_PROFILE"
        notify "You are now using the network profile $NEW_PROFILE"
        reload_netapplet
elif [ "a$ACTION" = "asave" ]; then
        check_root
        save_profile "$CURPROFILE"
elif [ "a$ACTION" = "aload" ]; then
        check_root
        check_current_profile
        log "Switching from profile $CURPROFILE to profile $NEW_PROFILE"
        notify "Switching to network profile $NEW_PROFILE, please wait a few moments..."
        restore_profile "$NEW_PROFILE" 1
        update_current "$NEW_PROFILE"
        notify "You are now using the network profile $NEW_PROFILE"
        reload_netapplet
elif [ "a$ACTION" = "aboot" ]; then
        check_root
        get_boot_profile
        check_current_profile
        log "Switching from profile $CURPROFILE to profile $NEW_PROFILE"
        save_profile "$CURPROFILE"
        restore_profile "$NEW_PROFILE"
        update_current "$NEW_PROFILE"
        reload_netapplet
elif [ "a$ACTION" = "adelete" ]; then
        check_root
        check_current_profile
        print_debug "Removing profile $NEW_PROFILE"
        remove_profile "$NEW_PROFILE"
        reload_netapplet
elif [ "a$ACTION" = "acurrent" ]; then
        echo $CURPROFILE
        exit 0
elif [ "a$ACTION" = "alist" ]; then
        # shows available netprofiles
        (
                cd $PROFILES
                        for z in *; do
                                if [ "a$VERBOSE" = "a1" -a "$z" = "$CURPROFILE" ]; then
                                        echo "(current) $z"
                                else
                                        echo "$z"
                                fi
                        done
        )
        exit 0
elif [ "a$ACTION" = "areset" ]; then
        # resets netprofile configuration
        check_root
        (
                cd $PROFILES
                for z in *; do
                        if [ ! "$CURPROFILE" = "$z" ]; then
                                if [ "a$VERBOSE" = "a" ]; then
                                        echo "Removing profile $z"
                                fi
                                remove_profile $z
                        fi
                done
                if [ "a$VERBOSE" = "a" ]; then
                        echo "Setting profile $CURPROFILE as new $DEFAULT_PROFILE profile"
                fi
                mv "$CURPROFILE" $DEFAULT_PROFILE && update_current "$DEFAULT_PROFILE"
        )
        reload_netapplet
elif [ "a$ACTION" = "amodules" ]; then
        # show available modules
        (
                cd $MODULES
                for module in *; do
                        # is module enabled?
                        if [ -x "$module" ]; then
                                ENABLED="+"
                                ENABLED_VERBOSE="enabled"
                        else
                                ENABLED="-"
                                ENABLED_VERBOSE="disabled"
                        fi
                        . $module

                        if [ "a$VERBOSE" = "a1" ]; then
                                echo -e "$module\t($ENABLED_VERBOSE):\t$NAME ($DESCRIPTION)"
                        else
                                echo -e "$module\t$ENABLED\t$NAME\t$DESCRIPTION"
                        fi
                done
        )
elif [ "a$ACTION" = "amodule_enable" ]; then
        MODULE=$2
        check_root
        # enable a module
        (
                cd $MODULES
                if [ ! -f "$MODULE" ]; then
                        echo "Error: module $MODULE not found"
                        exit 1
                fi
                if [ "a$VERBOSE" = "a1" ]; then
                        echo "Enabling $MODULE"
                fi
                chmod +x $MODULE
        )
elif [ "a$ACTION" = "amodule_disable" ]; then
        MODULE=$2
        check_root
        # enable a module
        (
                cd $MODULES
                if [ ! -f "$MODULE" ]; then
                        echo "Error: module $MODULE not found"
                        exit 1
                fi
                if [ "a$VERBOSE" = "a1" ]; then
                        echo "Disabling $MODULE"
                fi
                chmod -x $MODULE
        )
else
        echo "$0: unknown action $ACTION"
        exit 1
fi

exit 0

# set-netprofile ends here
