#!/bin/sh
#----------------------------------------------------------------------------
# /var/install/bin/update-systemfiles - update system files
#
# Creation:     2004-07-31  jed
# Last Update:  $Id$
#
# Copyright (c) 2001-2010 the eisfair team, team(at)eisfair(dot)org
#
# 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.
#----------------------------------------------------------------------------
#exec 2>./updates-trace-$$.log
#set -x

# testroot
testroot=''

pgmname=`basename $0`

# include eislib
. /var/install/include/eislib

#----------------------------------------------------------------------------
# check if numeric value
# input : $1 - value
# return:  0 - numeric
#          1 - no numeric
#----------------------------------------------------------------------------
function is_numeric ()
{
    echo "$1" | grep -q '^[0-9]*$'
}

#----------------------------------------------------------------------------
# check if text value
# input : $1 - value
# return:  0 - text
#          1 - no text
#----------------------------------------------------------------------------
function is_text ()
{
    # used for service and service aliases
    # space and tab are not allowed
    # a-z, 0-9, and hyphen (-) would seem a sensible choice.
    echo "$1" | grep -q '^[[:alnum:]_.-]*$'
}

#----------------------------------------------------------------------------
# check if inittab-id value
# input : $1 - value
# return:  0 - id
#          1 - no id
#----------------------------------------------------------------------------
function is_inittab_id ()
{
    # The id is a unique sequence of 1-4 characters which identifies an entry
    # in inittab.

    echo "$1" | grep -q '^[[:alnum:]]\{1,4\}$'
}

#----------------------------------------------------------------------------
# check if runlevel value
# input : $1 - runlevel
#         $2 - action
# return:  0 - runlevel
#          1 - no runlevel
#----------------------------------------------------------------------------
function is_inittab_runlevel ()
{
    # The runlevels field may contain multiple characters for different
    # runlevels. For example, 123 specifies that the process should be
    # started in runlevels 1, 2, and 3. The runlevels for 'ondemand'
    # entries may contain an A, B, or C. The runlevels field of 'sysinit',
    # 'boot', and 'bootwait' entries are ignored.

    runlevel=0

    case "$2" in
        boot|bootwait|sysinit )
            # runlevels will be ignored
            ;;
        ondemand )
            # allowed runlevels: 0123456ABC
            tmpstr=`echo "$1"|sed 's/[0-6ABC]*//g'`
            ;;
        * )
            # allowed runlevels: 0123456
            tmpstr=`echo "$1"|sed 's/[0-6]*//g'`
            ;;
    esac

    if [ "$tmpstr" != "" ]
    then
        # false
        runlevel=1
    fi

    return $runlevel
}

#----------------------------------------------------------------------------
# check if inittab process exists
# input : $1 - value
# return:  0 - process ok
#          1 - process error
#----------------------------------------------------------------------------
function check_inittab_process ()
{
    process=0

    # remove leading + sign
    procstr=`echo "$1"|sed 's/^\+//g'`

    # check if file exists and is executable
    if [ ! -f "$procstr" -o ! -x "$procstr" ]
    then
        # false
        process=1
    fi

    return $process
}

#----------------------------------------------------------------------------
# check service port and protocol
# input : $1 - value port/protocol
# return:  0 - values ok
#          1 - values error
#----------------------------------------------------------------------------
function check_port_and_protocol ()
{
    values=0

    oldifs="$IFS"
    IFS='/'
    set $1
    IFS="$oldifs"

    # service port
    if ! is_numeric $1
    then
         print_debug -error "parameter $idx - PORT not allowed: $1"
         values=1
    fi

    # service protocol
    if [ "$2" != "tcp" -a "$2" != "udp" -a "$2" != "ddp" ]
    then
        print_debug -error "parameter $idx - PROTOCOL not allowed: $2"
        values=1
    fi

    return $values
}

#----------------------------------------------------------------------------
# check services line
# input : $1 - value
# return:  0 - ok
#          1 - error
#----------------------------------------------------------------------------
function check_services_line ()
{
    check_services_line_rtc=0

    # strip comments
    line=`echo "$1"|sed 's/\#.*//g'`

    if [ "$line" != "" ]
    then
        echo "$line"|grep -q "^#"

        if [ $? -ne 0 ]
        then
            set $line

            idx=1
            while [ $# -ne 0 -a $check_services_line_rtc -eq 0 ]
            do
                case $idx in
                    1 ) # service name
                        if ! is_text $1
                        then
                            print_debug -error "parameter $idx - NAME not allowed: $1"
                            check_services_line_rtc=$idx
                        fi
                        ;;
                    2 ) # service port and protocol
                        if ! check_port_and_protocol $1
                        then
                            check_services_line_rtc=$idx
                        fi
                        ;;
                    * ) # service alias
                        if ! is_text $1
                        then
                            print_debug -error "parameter $idx - ALIAS not allowed: $1"
                            check_services_line_rtc=$idx
                        fi
                        ;;
                esac

                # next parameter
                shift

                idx=`expr $idx + 1`
            done
        fi
    fi

    return $check_services_line_rtc
}

#----------------------------------------------------------------------------
# check inittab line
# input : $1 - value
# return:  0 - ok
#          1 - error
#----------------------------------------------------------------------------
function check_inittab_line ()
{
    # id:runlevels:action:process
    check_inittab_line_rtc=0

    # ignore lines starting with :
    echo "$1" | grep -q '^:' && return $check_inittab_line_rtc

    # strip comments
    line=`echo "$1"|sed 's/\#.*//g'`

    if [ "$line" != "" ]
    then
        echo "$line"|grep -q "^#"

        if [ $? -ne 0 ]
        then
            oldifs="$IFS"
            IFS=':'
            set $line
            IFS="$oldifs"

            idx=1
            while [ $# -ne 0 -a $check_inittab_line_rtc -eq 0 ]
            do
                case $idx in
                    1 ) # inittab id
                        if ! is_inittab_id $1
                        then
                            print_debug -error "parameter $idx - ID not allowed: $1"
                            check_inittab_line_rtc=$idx
                        fi
                        ;;
                    2 ) # inittab runlevel
                        if ! is_inittab_runlevel $1 $2
                        then
                            print_debug -error "parameter $idx - RUNLEVEL not allowed: $1:$2"
                            check_inittab_line_rtc=$idx
                        fi
                        ;;

                    3 ) # inittab action
                        case "$1" in
                            respawn|wait|once|boot|bootwait|off|ondemand|initdefault|sysinit|powerwait|powerfail|powerokwait|ctrlaltdel|kbrequest ) # ok
                                ;;
                            * ) # error
                                print_debug -error "parameter $idx - ACTION not allowed: $1"
                                check_inittab_line_rtc=$idx
                                ;;
                        esac
                        ;;
                    4 ) # inittab process
                        if ! check_inittab_process $1
                        then
                            print_debug -error "parameter $idx - FILE not found or not executable: $1"
                            check_inittab_line_rtc=$idx
                        fi
                        ;;
                    * ) # ignore rest
                        ;;
                esac

                # next parameter
                shift

                idx=`expr $idx + 1`
            done
        fi
    fi

    return $check_inittab_line_rtc
}

#----------------------------------------------------------------------------
# check services file
# input :  $1 - file name
# output:   0 - ok
#         <>0 - error (line number)
#----------------------------------------------------------------------------
function check_services_file ()
{
    check_services_file_rtc=0
    f_name="$1"

    idx=1
    while read LINE
    do
        if ! check_services_line "$LINE"
        then
            check_services_file_rtc=$idx
            # no break, check whole file
            #break
        fi

        idx=`expr $idx + 1`
    done < $f_name

    return $check_services_file_rtc
}

#----------------------------------------------------------------------------
# check inittab file
# input :  $1 - file name
# output:   0 - ok
#         <>0 - error (line number)
#----------------------------------------------------------------------------
function check_inittab_file ()
{
    check_inittab_file_rtc=0
    f_name="$1"

    idx=1
    while read LINE
    do
        if ! check_inittab_line "$LINE"
        then
            check_inittab_file_rtc=$idx
            # no break, check whole file
            #break
        fi

        idx=`expr $idx + 1`
    done < $f_name

    return $check_inittab_file_rtc
}

#----------------------------------------------------------------------------
# print file header
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
print_file_header ()
{
    f_name="$1"
    i_str="$2"

    echo '#----------------------------------------------------------------------'
    echo "# ${f_name} file generated by '${i_str}'"
    echo '#'
    echo "# Creation date:  ${EISDATE}  `whoami`"
    echo '#'
    echo "# Do not edit this file directly, create a '${f_name}.package-name'"
    echo "# file and re-run the '${pgmname}' command to update."
    echo '#----------------------------------------------------------------------'
}

#----------------------------------------------------------------------------
# print help
#----------------------------------------------------------------------------
print_help ()
{
    # show help
    mecho "Usage:"
    mecho "  $pgmname cannot be called directly. Please use one of the following names:"
    mecho
    mecho "  update-at.allow    [-debug] package-name  -> update /etc/at.allow file."
    mecho "  update-at.deny     [-debug] package-name  -> update /etc/at.deny file."
    mecho "  update-hosts.allow [-debug] package-name  -> update /etc/hosts.allow file."
    mecho "  update-hosts.deny  [-debug] package-name  -> update /etc/hosts.deny file."
    mecho "  update-services    [-debug] package-name  -> update /etc/services file."
    mecho "  update-inittab     [-debug] package-name  -> update /etc/inittab file."
    mecho "  update-sudoers     [-debug] package-name  -> update /etc/sudoers file."
    mecho "  update-nsswitch    [-debug] package-name  -> update /etc/nsswitch.conf file."
}

#----------------------------------------------------------------------------
# print debug
# input: $1 - output string
#----------------------------------------------------------------------------
print_debug ()
{
    if [ "$debug" = "-debug" ]
    then
        mecho $@
    fi
}

#----------------------------------------------------------------------------
# update at.allow or at.deny file
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
update_at_allow_deny_file ()
{
    fname="$1"
    fname_tmp=$fname.$$
    istr="$2"

    ls ${fname}.*|egrep -v ".backup|~" >/dev/null 2>/dev/null

    if [ $? -eq 0 ]
    then
        # backup file
        /var/install/bin/backup-file -quiet $fname backup

        # create fname_tmp with same file access permission like fname
        if [ -f $fname ]
        then
            cp -p $fname $fname_tmp
        else
            # create new file and set initial file permissions
            > $fname_tmp
            chmod 640    $fname_tmp
            chown root   $fname_tmp
            chgrp daemon $fname_tmp
        fi

        # create new file (temporary one)
        {
            # print header
            print_file_header "$fname" "$istr"

            for FN in `ls ${fname}.*|egrep -v ".backup|~|.$$"`
            do
                cat $FN
            done
        } > ${fname_tmp}

        # copy file, preserve access permissions
        cp -p ${fname_tmp} ${fname}
        rm -f ${fname_tmp}
    else
        # remove file
        if [ -f ${fname} ]
        then
            # backup file
            /var/install/bin/backup-file -quiet $fname backup

            rm -f ${fname}
        fi
    fi
}

#----------------------------------------------------------------------------
# update hosts.allow or hosts.deny file
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
update_hosts_allow_deny_file ()
{
    fname="$1"
    fname_tmp=$fname.$$
    istr="$2"

    ls ${fname}.*|egrep -v ".backup|~" >/dev/null 2>/dev/null

    if [ $? -eq 0 ]
    then
        # backup file
        /var/install/bin/backup-file -quiet $fname backup

        # create fname_tmp with same file access permission like fname
        if [ -f $fname ]
        then
            cp -p $fname $fname_tmp
        else
            # create new file and set initial file permissions
            > $fname_tmp
            chmod 644 $fname_tmp
        fi

        # create new file (temporary one)
        {
            # print header
            print_file_header "$fname" "$istr"

            for FN in `ls ${fname}.*|egrep -v ".backup|~|.$$"`
            do
                cat $FN
            done
        } > ${fname_tmp}

        # copy file, preserve access permissions
        cp -p ${fname_tmp} ${fname}
        rm -f ${fname_tmp}
    else
        # remove file
        if [ -f ${fname} ]
        then
            # backup file
            /var/install/bin/backup-file -quiet $fname backup

            rm -f ${fname}
        fi
    fi
}

#----------------------------------------------------------------------------
# update services file
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
#
# format of services file:
#
#      service-name   port/protocol   [aliases ...]
#
#   separator spaces or tabs
#   ignore blank line
#   comments start by #
#
#   old style format (port,protocol) is not supported
#
update_services_file ()
{
    fname="$1"
    fname_tmp=$fname.$$
    istr="$2"

    src_work=$fname-work-src.$$
    dest_work=$fname-work-dest.$$

    # check if files exist
    ls ${fname}.*|egrep -v ".backup|~" >/dev/null 2>/dev/null

    if [ $? -eq 0 ]
    then
        # backup file
        /var/install/bin/backup-file -quiet $fname backup

        # create working copy
        cp $fname.std $src_work

        # process all files
        filelist=`ls ${fname}.*|egrep -v ".backup|.std|~"`

        for FN in $filelist
        do
            if check_services_file $FN
            then
                # read files line by line
                while read line
                do
                    # ignore blank or empty lines
                    echo "$line" | grep -q '^[[:space:]]*$' && continue
                    # ignore comment only lines
                    echo "$line" | grep "^#" > /dev/null

                    if [ $? -ne 0 ]
                    then
                        # get service name and service protocol
                        sname=`echo $line|cut -d" " -f1|sed 's/ //g'`
                        sprotocol=`echo $line|cut -d" " -f2|sed 's/ //g'|sed 's|[0-9]*/||'`

                        if [ "$sname" != "" ]
                        then
                            # remove old entry if exist
                            grep -v "^$sname[[:space:]]*[0-9]*/$sprotocol" $src_work > $dest_work

                            # take result as base for next compare
                            mv $dest_work $src_work
                        fi
                    fi
                done < $FN
            else
                # error in file - skip it
                filelist=`echo $filelist|sed "s#$FN##g"`
                mecho -warn "warning: skipping file $FN because of syntax errors."
            fi
        done

        # create fname_tmp with same file access permission like fname
        cp -p $fname $fname_tmp

        # create new file (temporary one)
        {
            # print header
            print_file_header "$fname" "$istr"

            # use standard file
            cat $src_work

            for FN in $filelist
            do
                cat $FN
            done
        } > ${fname_tmp}
        # copy file, preserve access permissions
        cp -p ${fname_tmp} ${fname}
        rm -f ${fname_tmp}

        # remove working copy
        rm -f $src_work
    fi
}

#----------------------------------------------------------------------------
# update inittab file
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
#
# format of inittab file:
#
#      id:runlevels:action:process
#
#   separator :
#   ignore blank line
#   comments start by #
#
update_inittab_file ()
{
    fname="$1"
    fname_tmp=$fname.$$
    istr="$2"

    src_work=$fname-work-src.$$
    dest_work=$fname-work-dest.$$

    # check if files exist
    ls ${fname}.*|egrep -v ".backup|.std|~" >/dev/null 2>/dev/null

    if [ $? -eq 0 ]
    then
        # backup file
        /var/install/bin/backup-file -quiet $fname backup

        # create working copy
        cp $fname.std $src_work

        # process all files
        filelist=`ls ${fname}.*|egrep -v ".backup|.std|~"`

        for FN in $filelist
        do
            if check_inittab_file $FN
            then
                # read files line by line
                while read line
                do
                    # ignore blank or empty lines
                    echo "$line" | grep -q '^[[:space:]]*$' && continue
                    # ignore comments
                    echo $line|grep "^#" > /dev/null

                    if [ $? -ne 0 ]
                    then
                        # get id
                        id=`echo $line|cut -d":" -f1|sed 's/ //g'`

                        if [ "$id" != "" ]
                        then
                            # remove old entry if exist
                            grep -v "^$id:" $src_work > $dest_work

                            # take result as base for next compare
                            mv $dest_work $src_work
                        fi
                    fi
                done < $FN
            else
                # error in file - skip it
                filelist=`echo $filelist|sed "s#$FN##g"`
                mecho -warn "warning: skipping file $FN because of syntax errors."
            fi
        done

        # create fname_tmp with same file access permission like fname
        cp -p $fname $fname_tmp

        # create new file (temporary one)
        {
            # print header
            print_file_header "$fname" "$istr"

            # use standard file
            cat $src_work

            for FN in $filelist
            do
                cat $FN
            done
        } > ${fname_tmp}
        # copy file, preserve access permissions
        cp -p ${fname_tmp} ${fname}
        rm -f ${fname_tmp}

        # remove working copy
        rm -f $src_work
    fi
}

#----------------------------------------------------------------------------
# update nsswitch.conf
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
#
# format of nsswitch.conf:
#
#      hosts:          files wins dns
#
#   separator :
#
update_nsswitch_file ()
{
    fname="$1"
    fname_tmp=$fname.$$
    istr="$2"
    src_work=$fname-work-src.$$

    # check if files exist
    ls ${fname}.*|egrep -v ".backup|.std|~" >/dev/null 2>/dev/null

    if [ $? -eq 0 ]
    then
        # backup file
        /var/install/bin/backup-file -quiet $fname backup

        # create working copy
        cp $fname.std $src_work

        # process all files
        filelist=`ls ${fname}.*|egrep -v ".backup|.std|~"`

        # create fname_tmp with same file access permission like fname
        cp -p $fname $fname_tmp

        # create new file (temporary one)
        {
            # print header
            print_file_header "$fname" "$istr"

            while read a
            do
                id_a=''
                id_b=''
                id_a=`echo $a|cut -d":" -f1|sed 's/ //g'`
                id_b=`grep "^$id_a:" $filelist`
                wcl=`grep "^$id_a:" $filelist | wc -l`

                if [ "$id_a" != "" -a "$id_b" != "" ]
                then
                    if [ "$wcl" -gt "1" ]
                    then
                        mecho -error "More than one entry for $id_a:!" >&2
                        #echo "id_a is $id_a, id_b is $id_b, wcl is $wcl"
                        echo "$a"
                    else
                        echo "$id_b"
                    fi
                else
                    echo "$a"
                fi
            done < $src_work
        } 1> ${fname_tmp}

        # copy file, preserve access permissions
        cp -p ${fname_tmp} ${fname}
        rm -f ${fname_tmp}

        # remove working copy
        rm -f $src_work
    fi
}

#----------------------------------------------------------------------------
# update sudoers file
# $1 - file name
# $2 - package name
#----------------------------------------------------------------------------
#
# format of sudoers file:
#
# example
#
#cat >${SUDOERS_FILE} <<EOF
##
## ${SUDOERS_FILE} list file generated by ${PACKAGE_NAME} Version ${VERSION}
##
#Cmnd_Alias EISFAXTIFF2PDF = /usr/bin/tiff2pdf
#Cmnd_Alias EISFAXCHOWN = /bin/chown
#Cmnd_Alias EISFAXCHMOD = /bin/chmod
#fax ALL = NOPASSWD: EISFAXTIFF2PDF, EISFAXCHOWN, EISFAXCHMOD
#
#EOF
#
#   ignore blank line
#   comments start by #
#
update_sudoers_file ()
{

    . /var/install/include/check-eisfair-version

    case ${EISFAIR_SYSTEM} in
        eisfair-1)
            # with update 1.7.2 comes sudo 1.7.4p4
            # sudo use now a separate folder for include '/etc/sudoers.d'
            # all files in this folder musst have filemode 0440
            # update-sudoers is now deprecated
            if tty -s
            then
                mecho --warn " Use of 'update-sudoers' is deprecated" >`tty`
            fi
        ;;
        *)
            :
        ;;
    esac
    fname="$1"
    fname_tmp=${fname}.$$
    istr="$2"

    # check if files exist
    ls ${fname}.*|egrep -v ".backup|~" >/dev/null 2>/dev/null

    if [ $? -eq 0 ]
    then
        # backup file
        /var/install/bin/backup-file -quiet $fname backup

        # create fname_tmp with same file access permission like fname
        if [ -f ${fname} ]
        then
            cp -p ${fname} ${fname_tmp}
        else
            # create new file and set initial file permissions
            > ${fname_tmp}
            chmod 440 ${fname_tmp}
        fi

        # create new file (temporary one)
        {
            # print header
            print_file_header "${fname}" "${istr}"

            for FN in `find ${fname}.* | egrep -v ".backup|~|.$$|sudoers.d"`
            do
                cat ${FN}
            done
        } > ${fname_tmp}

        # copy file, preserve access permissions
        cp -p ${fname_tmp} ${fname}
        rm -f ${fname_tmp}
    else
        # remove file
        if [ -f ${fname} ]
        then
            # backup file
            /var/install/bin/backup-file -quiet $fname backup

            rm -f ${fname}
        fi
    fi
}

#============================================================================
# main
#============================================================================

if [ $# -eq 0 ]
then
    # show help
    print_help
else
    # update files
    while [ 1 ]
    do
        case "$1" in
            -debug)
                debug='-debug'
                shift
                ;;
            *)
                infostr="$1"
                break
                ;;
        esac
    done

    case $pgmname
    in
        update-hosts.allow|hosts.allow)
            update_hosts_allow_deny_file $testroot/etc/hosts.allow "$infostr"
            ;;
        update-hosts.deny|hosts.deny)
            update_hosts_allow_deny_file $testroot/etc/hosts.deny "$infostr"
            ;;
        update-services|services)
            update_services_file $testroot/etc/services "$infostr"
            ;;
        update-inittab|inittab)
            update_inittab_file $testroot/etc/inittab "$infostr"

            if [ "$debug" != "-debug" ]
            then
                /sbin/init q >/dev/null 2>/dev/null
            else
                mecho -warn "warning: debug mode enabled, inittab configuration will not be reloaded."
            fi
            ;;
        update-sudoers|sudoers)
            update_sudoers_file $testroot/etc/sudoers "$infostr"
            ;;
        update-nsswitch|nsswitch)
            update_nsswitch_file $testroot/etc/nsswitch.conf "$infostr"
            ;;
        update-at.allow|at.allow)
            update_at_allow_deny_file $testroot/etc/at.allow "$infostr"
            ;;
        update-at.deny|at.deny)
            update_at_allow_deny_file $testroot/etc/at.deny "$infostr"
            ;;
        *)
            mecho -info 'program name not allowed, use one of the following names:'
            print_help
            ;;
    esac
fi


#============================================================================
# end
#============================================================================