Subversion Repositories vgwhois

Rev

Rev 5 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

#!/bin/bash

#
#  VGWhoIs (ViaThinkSoft Global WhoIs, a fork of generic Whois / gwhois)
#  Maintenance / Developer utilities
#
#  (c) 2012-2019 by Daniel Marschall, ViaThinkSoft <info@daniel-marschall.de>
#
#  License: https://www.gnu.org/licenses/gpl-2.0.html (GPL version 2)
#

# TODO: use >&2

# TODO: anzeigen von wann der whois output ist... "last checked" -> "last activity" (da ein user-mode-batch-approval ja kein recheck beinhaltet)

DIR=$( dirname "$0" )

ANNOTATION_FILE="$DIR/../../config/testcases.annot"
TESTCASE_CACHE_FILE="$DIR/../../.cache/testcases"

# ---

function resetconsole {
        # http://superuser.com/questions/122911/bash-reset-and-clear-commands

        # clear

        # clears the console screen, but not the scrollback buffer
        # this is actually the escape code to "reset" the terminal
        echo -en "\ec"

        # clears the scrollback buffer, but not the console screen
        # screen content remains, and cursor position remains at its last position
        echo -en "\e[3J"
}

function showPatternHighlighted {
        A=()

        patterns=$( "$DIR"/allpatterns )
        for p in ${patterns[@]}; do
                A+=("$p");
        done

        out=""
        while IFS= read data; do
                out="$out$data"$'\n'
        done

        # Attention: "less" or "nano" does not work with Unicode for Asian languages :-(
        # Maybe this helps? http://serverfault.com/questions/414760/how-to-make-the-less-command-handle-utf-8 .
        # But then it still can't handle colors...
        echo "$out" | "$DIR"/highlighter "${A[@]}"
}

function question {
        if [ -f "$ANNOTATION_FILE" ]; then
                ANNOTS=$( cat "$ANNOTATION_FILE" | grep -E "^$query:" )
                if [ "$ANNOTS" != "" ]; then
                        echo ""
                        echo "********************"
                        echo "Notes in $ANNOTATION_FILE :"
                        OLDIFS="$IFS"
                        IFS=$'\n'
                        for p in ${ANNOTS[@]}; do
                                pos="${#query}"
                                (( pos += 2 ))
                                echo "   ${p:$pos}";
                        done
                        IFS="$OLDIFS"
                        echo "********************"
                fi
        fi
        echo ""
        echo "Keep in mind to only set this result as expected state, when:"
        echo "   (1) The output is well formatted OR when you add an entry to the ToDo"
        echo "       list when the output is 'OK, but not optimal'."
        echo "   (2) For web-requests you should also have a testcase which shows"
        echo "       non-existant domains"
        echo "   (3) All handles should have testcases too, or it should be noted that"
        echo "       specific handles cannot be queried by the NIC."
        echo "   (4) There should be no volative stuff like dates, serving whois server"
        echo "       names or your IP address in the output."
        echo "       Otherwise you should grep them away in $0 before you approve this testcase."
        echo "   (5) In notices, URLs/information must be correct"
        echo "   (6) Highlighting shall be working, otherwise the patterns or $DIR/highlighter needs to be changed"
        while true; do
                echo ""
                if [ -f "$E" ]; then
                        echo "Do you want to override the expected result? ([y]es, [n]o, [s]kip, [r]etry, [a]dd note, reset[t], e[x]it)"
                else
                        # TODO: braucht man hier reset?
                        echo "Do you want to define this as the expected result? ([y]es, [n]o, [s]kip, [r]etry, [a]dd note, rese[t], e[x]it)"
                fi
                read yn
                case $yn in
                        [Yy]* )
                                cat "$T" > "$E"
                                rm "$T"
                                if [ -f "$Q" ] && [ "$Q" != "$T" ]; then
                                        rm "$Q"
                                fi
                                touch "$tsfile"
                                return 0
                                ;;
                        [Nn]* )
                                rm "$T"
                                return 1
                                ;;
                        [Ss]* )
                                return 1
                                ;;
                        [Rr]* )
                                bakmode="$mode"
                                mode="i"
                                bakforce="$force"
                                force=1
                                process "$query"
                                RES=$?
                                mode="$bakmode"
                                force="$bakforce"
                                return $RES
                                ;;
                        [Aa]* )
                                echo "Please enter a note you want to add or empty string if you want to cancel"
                                read note

                                if [ "$note" == "" ]; then
                                        echo "Cancelled"
                                else
                                        echo "Note added to testcase of $query"
                                        echo "$query: $note" >> "$ANNOTATION_FILE"
                                fi
                                ;;
                        [Xx]* )
                                return 2
                                ;;
                        [Tt]* )
                                # Clears the expected state
                                #rm $TESTCASE_CACHE_FILE/*/$query
                                rm "$E"
                                rm "$Q"
                                rm "$tsfile"
                                # Now retry
                                # TODO: codeduplikat vermeiden?
                                bakmode="$mode"
                                mode="i"
                                bakforce="$force"
                                force=1
                                process "$query"
                                RES=$?
                                mode="$bakmode"
                                force="$bakforce"
                                return $RES
                                ;;
                        * )
                                echo "Please answer with the letter written in square brackets."
                                ;;
                esac
        done
}

function question2 {
        while true; do
                echo ""
                echo "vgwhois might already have been fixed. Do you want to enforce a vgwhois recheck now? ([y]es, [n]o, e[x]it)"
                read yn
                case $yn in
                        [YyRr]* )
                                if [ -f "$Q" ]; then
                                        rm "$Q"
                                fi
                                bakmode="$mode"
                                mode="i"
                                bakforce="$force"
                                force=1
                                process "$query"
                                RES=$?
                                mode="$bakmode"
                                force="$bakforce"
                                return $RES
                                ;;
                        [NnSs]* )
                                return 1
                                ;;
                        [Xx]* )
                                return 2
                                ;;
                        * )
                                echo "Please answer with the letter written in square brackets."
                                ;;
                esac
        done
}

function unquote {
        echo "$1" | sed "s/^'\(.*\)'$/\1/";
}

function process {
        if [ "$mode" != "b" ]; then
                # TODO: diese meldung soll nicht kommen, wenn einfach nur da steht "does not need a recheck" now...
                resetconsole
        fi

        query="$1"
        echo "Query: $query"

        E="$TESTCASE_CACHE_FILE/expected/$query"
        Q="$TESTCASE_CACHE_FILE/problems/$query"
        tsfile="$TESTCASE_CACHE_FILE/checktimestamps/$query"

        if [ "$mode" == "u" ]; then
                T="$Q"
                if [ ! -f "$T" ]; then
                        echo "There is no action needed by the user"
                        return 0
                fi
        else
                T=$( mktemp --suffix='.vgwhoisTC' )

                # In interactive/background mode, we will always do a webrequest when there is no problem and no expectation file without respect of the last checktime, to avoid that the status monitor will show entries with "no expectation file" when the user pressed "no", and then he would have to wait 7 days until "batch u" works again.
                # TODO: how to do that in 1 line?
                CALL_GWI=0
                if [ $force -eq 1 ]; then
                        CALL_GWI=1
                else
                        test \( ! -f "$Q" \) -a \( ! -f "$E" \)
                        if [ $? -eq 0 ]; then
                                CALL_GWI=1
                        elif [ ! -f "$tsfile" ] || [ $( stat --format=%Y "$tsfile" ) -le $(( $( date +%s ) - $recheck_time )) ]; then
                                if [ -f "$Q" ] && [ $( stat --format=%Y "$Q" ) -gt $(( $( date +%s ) - $recheck_time )) ]; then
                                        CALL_GWI=0
                                else
                                        CALL_GWI=1
                                fi
                        fi
                fi
                if [ $CALL_GWI -eq 1 ]; then
                        echo "... calling vgwhois ..."
                        # We have to use loc_vgwhois to allow $trytor to work correctly.
                        # The torifiers "torify" and "usewithtor" always outputting bogus "libtorsocks" warning messages which would be saved in the output
                        # "vtor" - if applied to this script - can only filter them from STDOUT and STDERR, but not intercept this "&>" pipe
                        # So we have to use this loc_vgwhois script, where we torify manually
                        # Also, it is important that we do the warning message filtering in this step, because in the final output the lines will be colored/highlighted, and therefore "vtor" cannot grep them correctly anymore.
                        # Note: regex only valid in the years 1000-1099, 1900-2099, 2900-2999
                        # grep away volative stuff like server names or times
                        (
                                "$DIR"/loc_vgwhois "$query" \
                                        | grep -v "This query was served by " \
                                        | grep -v "(c)[12][90][0-9]\{2\}" \
                                        | grep -v "[12][90][0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}" \
                                        | grep -v "\[[0-9]\{1,3\}.[0-9]\{1,3\}.[0-9]\{1,3\}.[0-9]\{1,3\} REQUEST\]" \
                                        | grep -v "RL Net \[.*\] - RL IP \[.*\]" \
                                        | grep -v "% Query time:" \
                                        | grep -v "% request from" \
                                        | grep -v "% Last update of whois database:" \
                                        | grep -v "%AM TLD whois server #" \
                                        | grep -v "Last updated on " \
                                        | grep -v "This is the Ukrainian Whois query server #" \
                                        | grep -v "Last update of whois database" \
                                        | grep -v "Query time:" \
                                        | grep -v "nsstat:" \
                                        | grep -v "nslastaa:"
                        ) &> "$T"
                        # auskommentiert wegen problem: wenn man einen recheck wegen perl errors macht und dann abbricht, ist dann der PROBLEM state gespeichert? nein, er wurde im i-mode gelöscht!
                        # -> lösung ?rm "$Q" nur im erfolgsfall
                        #                       if [ -f "$Q" ]; then
                        #                               rm "$Q"
                        #                       fi

                        # behoben? in mode=i and mode=u, should the output of a new vgwhois request be saved into the problem file, if the user cancels? (to avoid a second web request?)
                        #       also, when doing a recheck after a perl error, and then cancel, the problem-file will not be updated!
                        # TODO: was ist besser?
                        #if [ ! -f "$Q" ] && [ "$T" != "$Q" ]; then
                        if [ "$T" != "$Q" ]; then
                                cat "$T" > "$Q"
                        fi


                else
                        rm "$T"
                        if [ "$mode" == "b" ]; then
                                echo "The query does not need a recheck now. Use --force to enforce it."
#                               if [ -f "$Q" ] && [ "$Q" != "$T" ]; then
#                                       rm "$Q"
#                               fi
                                return 0
                        else
                                T="$Q"
                                if [ ! -f "$T" ]; then
                                        echo "There is no action needed by the user"
                                        return 0
                                fi
                        fi
                fi
        fi

        WARNINGS=()
        cat "$T" | grep -E "at /(bin|usr|etc|var)/\S+ line" > /dev/null
        if [ $? -eq 0 ]; then
                WARNINGS+=("Perl errors found in vgwhois output!")
        fi
        cat "$T" | head -n 1 | grep -E "^("$'\xEF\xBB\xBF'"){0,1}Process query: '$query'" > /dev/null
        if [ $? -ne 0 ]; then
                WARNINGS+=("The vgwhois output does not begin with 'Process query'!")
        fi
        cat "$T" | grep "vgwhois remarks: If this is a valid domainname or handle, please file a bug report." > /dev/null
        if [ $? -eq 0 ]; then
                WARNINGS+=("No pattern match!")
        fi
        if [ ${#WARNINGS[@]} -gt 0 ]; then
                if [ "$T" != "$Q" ]; then
                        cat "$T" > "$Q"
                        rm "$T"
                fi
#               if [ "$mode" != "b" ]; then
#                       resetconsole
#               fi
                OLDIFS="$IFS"
                IFS=$'\n'
                for p in ${WARNINGS[@]}; do
                        echo "WARNING: $p"
                done
                IFS="$OLDIFS"
                if [ "$mode" == "b" ]; then
                        echo "Saved for later analysis."
                        # TODO: problem: this will prevent another background runner to check it again, but it will prevent the user-batch to investigate this, too?!!
#                       touch "$tsfile"
                        return 1
                else
                        # Hinweis: niemals vor "question" oder "question2" ein ts aktualisieren. denn wenn man in question* einen recheck beantragt und dann abbricht, dann würde dieser aktualisierte ts bewirken, dass der testcase erst wieder in 7 tagen angezeigt wird
                        echo ""
                        cat "$Q" | showPatternHighlighted
                        question2
                        return $?
                fi
        fi

        if [ -f "$E" ]; then
                D=$( date -r "$E" )
                echo "Compare with results of $D"
                diff -U 0 "$E" "$T" | showPatternHighlighted
                if [ ${PIPESTATUS[0]} -eq 0 ]; then
                        echo "OK! No differences found!"
                        rm "$T"
                        touch "$tsfile"
                        if [ -f "$Q" ] && [ "$Q" != "$T" ]; then
                                rm "$Q"
                        fi
                        return 0
                else
                        echo "Problem! Differences found!"

                        if [ "$mode" == "b" ]; then
                                echo "Saved for later analysis."
                                if [ "$T" != "$Q" ]; then
                                        cat "$T" > "$Q"
                                        rm "$T"
                                fi

                                # TODO: problem: this will prevent another background runner to check it again, but it will prevent the user-batch to investigate this, too?!!
#                               touch "$tsfile"
                                return 1
                        else
                                if [ "$T" != "$Q" ]; then
                                        rm "$T"
                                fi

                                question
                                return $?
                        fi
                fi
        else
                if [ "$mode" == "b" ]; then
                        echo "This query has no expected state. Please define one."
                        echo "Saved for later analysis."
                        if [ "$T" != "$Q" ]; then
                                cat "$T" > "$Q"
                                rm "$T"
                        fi
                        # TODO: problem: this will prevent another background runner to check it again, but it will prevent the user-batch to investigate this, too?!!
#                       touch "$tsfile"
                        return 1
                else
#                       resetconsole
                        echo "This query has no expected state. Please define one."
                        echo "This is the current output of vgwhois:"

                        echo ""
                        cat "$T" | showPatternHighlighted
                        echo ""

                        if [ "$T" != "$Q" ]; then
                                rm "$T"
                        fi

                        question
                        return $?
                fi
        fi
}

function HumanReadableTime {
        local seconds=$1
        local days=$(($seconds/86400))
        seconds=$(($seconds-($days*86400) ))
        local hours=$(($seconds/3600))
        seconds=$((seconds-($hours*3600) ))
        local minutes=$(($seconds/60))
        seconds=$(( $seconds-($minutes*60) ))

        # echo -n "${days}D ${hours}H ${minutes}M ${seconds}S"
        if [ $days -gt 0 ]; then
                if [ $hours -gt 12 ]; then
                        ((days++));
                fi
                echo -n "${days} days"
        elif [ $hours -gt 0 ]; then
                if [ $minutes -gt 30 ]; then
                        ((hours++));
                fi
                echo -n "${hours} hours"
        elif [ $minutes -gt 0 ]; then
                if [ $seconds -gt 30 ]; then
                        ((minutes++));
                fi
                echo -n "${minutes} minutes"
        else
                echo -n "a few seconds"
        fi
}

function usage {
        . "$DIR/../../config/testcases.conf"

        hf_recheck_time=$( HumanReadableTime $recheck_time )

        echo "Syntax: $0 options query"
        echo "  -h|--help"
        echo "  -m|--mode mode"
        echo "          Default: $mode"
        echo "          Mode: i = interactive (download and then show results/ask the developer if the result is OK)"
        echo "                b = background (download only and save the results for later query)"
        echo "                u = user-dialog only (only ask the developer if there are questions, e.g. which were generated by mode b)."
        echo "  -r|--rechecktime seconds"
        echo "          Default: $recheck_time = approx. $hf_recheck_time)"
        echo "  -f|--force"
        echo "          Ignores --rechecktime and forces a new vgwhois request"
        echo "          Default: $force"
}

# ---

if [ $# -eq 0 ]; then
        usage;
        exit;
fi

if [ ! -d "$TESTCASE_CACHE_FILE/checktimestamps" ]; then
        mkdir -p "$TESTCASE_CACHE_FILE/checktimestamps"
fi

if [ ! -d "$TESTCASE_CACHE_FILE/problems" ]; then
        mkdir -p "$TESTCASE_CACHE_FILE/problems"
fi

if [ ! -d "$TESTCASE_CACHE_FILE/expected" ]; then
        mkdir -p "$TESTCASE_CACHE_FILE/expected"
fi

# defaults
. "$DIR/../../config/testcases.conf"

PARAMS=( "$@" );
optarr=( $( getopt --name "$0" --options 'hfr:m:' --long 'help,force,rechecktime:,mode:' -- "${PARAMS[@]}" 2> /dev/null ) );

# Now process the arguments
i=0;
while true; do
        case "${optarr[$i]}" in
                -h|--help)
                        usage;
                        exit 0;
                        ;;
                -f|--force)
                        force=1;
                        ((i++));
                        ;;
                -r|--rechecktime)
                        recheck_time=$( unquote "${optarr[$((i+1))]}" );
                        ((i=i+2));
                        ;;
                -m|--mode)
                        mode=$( unquote "${optarr[$((i+1))]}" );

                        if [ "$mode" != "i" ] && [ "$mode" != "b" ] && [ "$mode" != "u" ]; then
                                echo "Invalid mode '$mode'"
                                usage
                                exit 2
                        fi

                        ((i=i+2));
                        ;;
                --)
                        ((i++));
                        break;
                        ;;
                *)
                        # Should never happen
                        echo "$0: Internal error while command-line-processing! Please report this error as bug." >&2;
                        exit 2;
                        ;;
        esac
done

RESTARGS=${optarr[@]:i}; # i..end
CMD="${optarr[i]}";
ARG="${optarr[@]:((i+1))}"; # (i+1)..end


EXITSTATUS=0
for X in ${RESTARGS[@]}
do
        X=$( unquote "$X" )
        process "$X"
        EC=$?

        if [ $EC -eq 2 ]; then
                # user pressed x for exit
                exit $EXITSTATUS;
        elif [ $EC -gt $EXITSTATUS ]; then
                # exitcode = max(exitcodes from processes)
                EXITSTATUS=$EC
        fi

done

exit $EXITSTATUS