#! /bin/bash

VERSION='0.8'

usage_message() {
    echo 'Usage: tz-brasil [ --help | --force | --verbose | --test | --allowed | --version | --refresh | --info ]'
    echo '  --help    : print this screen and exit'
    echo '  --force   : run in verbose mode, and force a timezone fetch'
    echo '  --verbose : run in verbose mode'
    echo '  --test    : same as --verbose'
    echo '  --allowed : list allowed timezones'
    echo '  --version : print program version and exit'
    echo '  --refresh : sync data in file /etc/localtime'
    echo '  --info    : show last successful timezone retrieve age'
}

version_message() {
    echo "tz-brasil version $VERSION"
    echo '(c) 2002-2006 Pedro Zorzenon Neto <pzn@debian.org>'
    echo 'For licence details read copyright file.'
    echo 'On Debian systems, it can be found at /usr/share/doc/tz-brasil/copyright.'
}

allowed_timezones_message() {
    echo "Allowed timezones are: $ALLOWED_TIME_ZONES"
    echo ""
    echo "This timezones are obsolete:"
    echo " obsolete: America/Araguaina   please use America/Palmas"
    echo " obsolete: America/Bahia       please use America/Salvador"
    echo " obsolete: America/Porto_Acre  please use America/Rio_Branco"
    echo " obsolete: Brazil/Acre         please use America/Rio_Branco"
    echo " obsolete: Brazil/DeNoronha    please use America/Noronha"
    echo ""
    echo "Incompatible timezones: Brazil/East and Brazil/West, please use another"
    echo ""
    echo "More information at /usr/share/doc/tz-brasil/timezone.txt"
}

info_message() {
    echo -n "last successfull database retrieve: "
    if [ ! -e "$VARDIR/$STAMPFILE" ]; then
	echo "unknown"
    else
	cat "$VARDIR/$STAMPFILE"
    fi
    if [ -e "$VARDIR/$RETRYFILE" ]; then
	echo -n "last fetch try: "
	cat "$VARDIR/$RETRYFILE"
    fi
}

ping_failed_message() {
    (
	echo "Internet connection failed."
	echo "*** WARNING *** tz-brasil last successful retrieve is too old..."
	echo "Please connect to internet and run \"tz-brasil\" as root"
	echo "also try \"tz-brasil --verbose\" or \"tz-brasil --force\""
	echo ""
	echo "if you have a dial-up or pppoe connection, just connect and it will"
	echo "automatically retrieve timezone information"
	echo ""
	echo "if your connection does not allow 'ping', chech /etc/tz-brasil.conf and"
	echo "try setting the option ASSUME_PING_OK"
    ) >&2
}

wget_failed_message() {
    (
	echo "Command: wget $WGETOPTS $SERVER$FILE -O $TMP"
	echo "failed to get file from server."
	echo "*** WARNING *** tz-brasil last successful retrieve is too old..."
	echo "Please connect to internet and run \"tz-brasil\" as root"
	echo "also try \"tz-brasil --verbose\" or \"tz-brasil --force\""
	echo ""
	echo "if you have a dial-up or pppoe connection, just connect and it will automatically retrieve timezone information"
    ) >&2
}

wrong_timezone_message() {
    # send warning to STDERR and also mail it to 'root'
    (
	echo "*** tz-brasil *** WRONG_TIMEZONE_WARNING ***"
	echo ""
	echo "--- Want to supress this warning?"
	echo "--- Check /etc/tz-brasil.conf option SUPRESS_WRONG_TIMEZONE_WARNING"
	echo ""
	echo "Current timezone is ==> $1 <=="
	echo "Please change it using the command 'tzconfig'"
	echo ""
	echo "Allowed timezones are: $ALLOWED_TIME_ZONES"
	echo ""
	echo "This ones are obsolete, please try another: $OBSOLETE_TIME_ZONES"
	echo ""
	echo "Why timezones Brazil/East and Brazil/West are not allowed?"
	echo "  check answer at /usr/share/doc/tz-brasil/timezone.txt"
    ) | tee /dev/stderr | mail root -s 'tz-brasil: wrong timezone'
}

check_allowed_timezone() {
    # check if timezone is one of the allowed time zones
    case "$SUPRESS_WRONG_TIMEZONE_WARNING" in
	true|TRUE|True|1|Yes|YES|yes)
	    # does nothing! user does not want this check!
	    ;;
	*)
	    FOUND_TZ='0'
	    CURR_TZ=`cat /etc/timezone`
	    for i in $ALLOWED_TIME_ZONES; do
		if [ "$CURR_TZ" == "$i" ]; then
		    FOUND_TZ='1'
		fi
	    done
	    if [ "$FOUND_TZ" == '0' ]; then
		wrong_timezone_message $CURR_TZ
		return 1
	    fi
	    ;;
    esac
    return 0
}

refresh_localtime() {
    # refresh 
    REF_TZ="/usr/share/zoneinfo/`cat /etc/timezone`"
    if [ -r "$REF_TZ" -a -w "/etc/localtime" ]; then
      REF_MD1=`md5sum < $REF_TZ`
      REF_MD2=`md5sum < /etc/localtime`
      if [ "$REF_MD1" != "$REF_MD2" ]; then
	  echo "tz-brasil: refresh: refreshing /etc/localtime"
	  rm -f /etc/localtime && cp $REF_TZ /etc/localtime
      else
	  echo "tz-brasil: refresh: /etc/localtime is already updated"
      fi
    else
	echo "Please check..."
	echo "  $REF_TZ must be readable"
	echo "  /etc/localtime must be writable"
	echo "after correcting this, run: tz-brasil --refresh"
	return 1
    fi
    return 0
}

should_i_try_to_fetch() {
    # test if it is needed to fetch the timezone file
    # return 1 if not needed, 0 if needed
    find $VARDIR -mmin -$FETCH_INTERVAL | grep "$STAMPFILE" >/dev/null
    if [ "$?" == "0" ]; then
	echo "there was a successfull update not older than $FETCH_INTERVAL minutes. will not try to update again."
	echo 'run with argument "--force" if you want to update now'
	return 1
    fi
    
    find $VARDIR -mmin -$RETRY_INTERVAL | grep "$RETRYFILE" >/dev/null
    if [ "$?" == "0" ]; then
	echo "there was a retry not older than $RETRY_INTERVAL minutes. will not try to update again."
	echo 'run with argument "--force" if you want to update now'
	return 1
    fi
    return 0
}

touch_retry_timestamp() {
    echo "touching retry-timestamp"
    /bin/date --utc --rfc-822 > $VARDIR/$RETRYFILE
    if [ "$?" != "0" ]; then
	echo "Could not touch retry-timestamp" >&2
	return 1
    fi
    return 0
}

touch_success_timestamp() {
    echo "touching success-timestamp"
    /bin/date --utc --rfc-822 > $VARDIR/$STAMPFILE
    if [ "$?" != "0" ]; then
	echo "Could not touch success-timestamp" >&2
	return 1
    fi
    return 0
}

test_internet_connection() {
    echo -n "testing Internet connection: "
    case "$ASSUME_PING_OK" in
	true|TRUE|True|1|YES|Yes|yes)
	    echo "skipped. config ASSUME_PING_OK is set."
	    ;;
	*)
	    fping -q www.debian.org
	    if [ "$?" != "0" ]; then
		echo "failed. will try again later."
		find $VARDIR -mmin -$WARN_FETCH_INTERVAL | grep "$STAMPFILE" >/dev/null
		if [ "$?" != "0" ]; then
		    ping_failed_message
		fi
		return 1
	    fi
	    echo "ok"
	    ;;
    esac
    return 0
}

fetch_zic_from_server() {
    echo "generating a new tempfile"
    TMP=`tempfile`
    echo "tempfile is $TMP"
    echo "Command: wget $WGETOPTS $SERVER$FILE -O $TMP"
    wget $WGETOPTS "$SERVER$FILE" -O "$TMP" 2>&1
    if [ "$?" != "0" ]; then
    # failed to get file
	rm -f $TMP
	echo "Command: wget $WGETOPTS $SERVER$FILE -O $TMP"
	echo "Failed to get file from server"
	find $VARDIR -mmin -$WARN_FETCH_INTERVAL | grep "$STAMPFILE" >/dev/null
	if [ "$?" != "0" ]; then
	    wget_failed_message
	fi
	return 1
    fi
    return 0
}

check_if_zic_has_changed() {
    if [ ! -e $ZIC ]; then
        # will create an empty ZIC file the first time it runs
	touch $ZIC
	if [ "$?" != "0" ]; then
	    echo "Could not create $ZIC" >&2
	    rm -f $TMP
	    return 3
	fi
    fi
    diff $ZIC $TMP >/dev/null
    if [ "$?" == "0" ]; then
	echo "The retrieved file is the same"
	rm -f $TMP
	rm -f $VARDIR/$RETRYFILE
	/bin/date --utc --rfc-822 > $VARDIR/$STAMPFILE
	if [ "$?" != "0" ]; then
	    echo "Could not touch timestamp" >&2
	    return 5
	fi
	echo "Success"
	return 0
    fi
    return 255
}

show_diff_to_user() {
    # show the diferences to the user
    echo "The following lines were changed in the timezone information:" >&2
    diff -uw $ZIC $TMP | egrep '^[+-][^+#-]' >&2
    echo "" >&2
}

security_parse_zic() {
    # remove strange lines from ZIC file
    # check line syntax, remove lines that are not correct
    SAFE_TMP=`tempfile`
    cat $TMP \
	| perl -e 'while (not eof STDIN) { $_=<STDIN>; s/\s+/ /g; s![^a-zA-Z0-9 #:/_%+-]!!g; s/ $//; print $_."\n"; }' \
	| grep -E -i -e '^ ?(rule [a-z0-9]+ (minimum|maximum|[0-9]+) (minimum|maximum|only|[0-9]+) - [a-z]+ [a-z0-9<>=]+ [0-9:]+[wsugz]? [0-9:]+ [a-z0-9+-]+|zone [a-z0-9/_]+ -?[0-9:]+ [a-z0-9]+ [a-z0-9/%]+|leap [0-9]+ [a-z]+ [0-9]+ [0-9:]+ [+-] [rs]) ?$' \
	> $SAFE_TMP
    echo "#  this file has only valid commands. full file contents may be fetched" > $TMP
    echo "#  from the server (see /etc/tz-brasil.conf for source URL)" >> $TMP
    cat $SAFE_TMP >> $TMP
    rm -f $SAFE_TMP
}

apply_zic() {
    /usr/sbin/zic $TMP
    if [ "$?" != "0" ]; then
        # failed to compile file
	echo "Failed to compile the file" >&2
	rm -fv $TMP
	return 1
    fi

    cat $TMP > $ZIC

    refresh_localtime
    if [ "$1" != "0" ]; then
	return 1
    fi
    
    rm -fv $TMP
    rm -f $VARDIR/$RETRYFILE
    return 0
}

# set default path
if [ -z "$PATH" ]; then
    PATH='/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin'
else
    PATH="$PATH:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
fi

# some parameters
CONFFILE='/etc/tz-brasil.conf'
VARDIR='/var/lib/tz-brasil'
STAMPFILE='success-stamp'
RETRYFILE='failure-stamp'
ZIC='/var/lib/tz-brasil/info'
ATZ="America/Aracaju"
ATZ="$ATZ America/Araguaina"  # back compatibility, use America/Palmas
ATZ="$ATZ America/Bahia"      # back compatibility, use America/Salvador
ATZ="$ATZ America/Belem"
ATZ="$ATZ America/Belo_Horizonte"
ATZ="$ATZ America/Boa_Vista"
ATZ="$ATZ America/Brasilia"
ATZ="$ATZ America/Campo_Grande"
ATZ="$ATZ America/Cuiaba"
ATZ="$ATZ America/Curitiba"
ATZ="$ATZ America/Eirunepe"
ATZ="$ATZ America/Florianopolis"
ATZ="$ATZ America/Fortaleza"
ATZ="$ATZ America/Goiania"
ATZ="$ATZ America/Joao_Pessoa"
ATZ="$ATZ America/Macapa"
ATZ="$ATZ America/Maceio"
ATZ="$ATZ America/Manaus"
ATZ="$ATZ America/Natal"
ATZ="$ATZ America/Noronha"
ATZ="$ATZ America/Palmas"
ATZ="$ATZ America/Porto_Acre" # back compatibility, use America/Rio_Branco
ATZ="$ATZ America/Porto_Alegre"
ATZ="$ATZ America/Porto_Velho"
ATZ="$ATZ America/Recife"
ATZ="$ATZ America/Rio_Branco"
ATZ="$ATZ America/Rio_de_Janeiro"
ATZ="$ATZ America/Salvador"
ATZ="$ATZ America/Santarem"
ATZ="$ATZ America/Sao_Luis"
ATZ="$ATZ America/Sao_Paulo"
ATZ="$ATZ America/Teresina"
ATZ="$ATZ America/Vitoria"
ATZ="$ATZ Brazil/Acre"        # back compatibility, use America/Rio_Branco
ATZ="$ATZ Brazil/DeNoronha"   # back compatibility, use America/Noronha
ALLOWED_TIME_ZONES="$ATZ"
ATZ="America/Araguaina"
ATZ="$ATZ America/Bahia"
ATZ="$ATZ America/Porto_Acre"
ATZ="$ATZ Brazil/Acre"
ATZ="$ATZ Brazil/DeNoronha"
OBSOLETE_TIME_ZONES="$ATZ"

# this is the default configuration, /etc/tz-brasil.conf will overwrite values
VERBOSE='1'
SERVER='http://people.debian.org/~pzn/tz-brasil/'
FILE='tz-brasil.zic'
WGETOPTS=''
FETCH_INTERVAL='15000'      # aprox 4 days
WARN_FETCH_INTERVAL='25000' # aprox 7 days
RETRY_INTERVAL='250'        # aprox 4 hours
ASSUME_PING_OK='false'
SUPRESS_WRONG_TIMEZONE_WARNING='false'

# read the config file
if [ ! -r "$CONFFILE" ]; then
    echo "could not read configuration file: $CONFFILE" >&2
    exit 32
fi
. $CONFFILE

umask 022

# sorry for my dumb command line argument parser :-)
case "$1" in
    --info)
	info_message
	exit 0
	;;
    --refresh)
	refresh_localtime
	exit 0
	;;
    --version)
	version_message
	exit 0
	;;
    --help)
	usage_message
	exit 0
	;;
    --allowed)
	allowed_timezones_message
	exit 0
	;;
    --test|--verbose)
	echo '--verbose: verbose mode forced'
	VERBOSE='2'
	;;
    --force)
	echo '--force: verbose mode forced'
	VERBOSE='2'
	echo '--force: removing old timestamp. forcing a new fetch'
	rm -fv $VARDIR/$STAMPFILE || exit 34
	rm -fv $VARDIR/$RETRYFILE || exit 34
	;;
    '')
        # no cmdline arguments
	;;
    *)
	echo "Unknown command line argument: '$1'" >&2
	echo 'Try: tz-brasil --help' >&2
	exit 35
	;;
esac

case $VERBOSE in
    0)
        # supress STDOUT and STDERR
	exec >/dev/null 2>/dev/null
	;;
    1)
        # supress STDOUT
	exec >/dev/null
	;;
    *)
	;;
esac

check_allowed_timezone
if [ "$?" != "0" ]; then
    exit 33
fi

should_i_try_to_fetch
if [ "$?" != "0" ]; then
    exit 0
fi

touch_retry_timestamp
if [ "$?" != "0" ]; then
    exit 5
fi

test_internet_connection
if [ "$?" != "0" ]; then
    exit 2
fi

fetch_zic_from_server
if [ "$?" != "0" ]; then
    exit 2
fi

echo "Got the file, now lets check it..."

security_parse_zic

check_if_zic_has_changed
RET="$?"
if [ "$RET" != "255" ]; then
    exit $RET
fi

show_diff_to_user

apply_zic
if [ "$?" != "0" ]; then
    exit 4
fi

touch_success_timestamp
if [ "$?" != "0" ]; then
    exit 5
fi

echo "Success"

exit 0
