#!/bin/bash

# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

##################################################
# Common functions                               #
##################################################
VERSION="2.0"

common_current_date=`date +%Y-%m-%d`

function getLogFile(){
    #1 - common path
    if [ ! -e "$1/logs" ];then
        mkdir -p "$1/logs"
    fi
    current_date_time=`date +"%Y-%m-%d %k:%M:%S"`
    echo "$1/logs/$common_current_date.log"
}

function writeToLog(){
    #1 - message
    #2 - common path
    if [ ! -e "$2/logs" ];then
        mkdir -p "$2/logs"
    fi
    current_date_time=`date +"%Y-%m-%d %k:%M:%S"`
    prg=`basename "$0"`
    local logfile="$2/logs/$common_current_date.log"
    # Refuse to append through a symlinked log path: a writable logs/ dir lets an
    # attacker plant a symlink so the root-run >> redirects into an arbitrary
    # file. Skip the write silently (callers must stay robust), don't error out.
    if [ -L "$logfile" ]; then
        return 0
    fi
    echo "[$current_date_time from $prg] $1" >> "$logfile"
}

function writeFileToLog(){
    #1 - file path
    #2 - common path
    if [ ! -e "$2/logs" ];then
        mkdir -p "$2/logs"
    fi
    current_date_time=`date +"%Y-%m-%d %k:%M:%S"`
    prg=`basename "$0"`
    local logfile="$2/logs/$common_current_date.log"
    # Refuse to append through a symlinked log path: a writable logs/ dir lets
    # an attacker plant a symlink so the root-run >> redirects into an arbitrary
    # file. Skip the write silently (callers must stay robust), don't error out.
    if [ -L "$logfile" ]; then
        return 0
    fi
    echo "[$current_date_time from $prg] ----------------File Content $1 BEG---------------" >> "$logfile"
    if [ -e "$1" ];then
        cat "$1" >> "$logfile"
    fi
    echo "[$current_date_time from $prg] ----------------File Content $1 End---------------" >> "$logfile"
}

function removeEmptyStringsFromFile(){
    filename="$1"
    # A privileged caller may hand $1 a path under an attacker-influenced
    # directory; bash > redirection and sed both follow symlinks with no
    # O_NOFOLLOW. Refuse to operate on a symlink so the truncating write cannot
    # be redirected to an arbitrary target. Quote $filename against word-splitting.
    if [ -L "$filename" ]; then
        return 1
    fi
    res=`sed -e '/^$/d' "$filename"`
    echo "$res" > "$filename"
}

# Guard for deleteAllExcept/deleteAllInclude: $3 is fed verbatim to sed as the
# PROGRAM, so restrict it to a single-address delete/print (/regex/d or /regex/p).
# Closes the GNU sed e/w/r/W/s///e arbitrary-write/exec surface. All shipped
# callers pass /#TOKEN/d (e.g. /#CageFS/d, /#l.v.e-manager/d).
function _isSafeSedAddressScript(){
    #1 - candidate sed program
    local _re='^/[^/]*/[dp]$'
    [[ "$1" != *$'\n'* && "$1" =~ $_re ]]
}

function deleteAllExcept(){
    #1 - hook
    #2 - tmp name
    #3 - pattern
    #4 - common path
    if [ ! -e "$4/tmp" ]; then
        mkdir -p "$4/tmp"
    fi
    if [ -e "$1" ];then
        # The cat read, the truncating `echo > "$1"` and the `cat >> "$1"`
        # below follow a symlink at $1 with no O_NOFOLLOW; refuse so the
        # root-run rewrite cannot be redirected to (and clobber) an arbitrary
        # target via a planted link.
        if [ -L "$1" ]; then
            writeToLog "deleteAllExcept: refusing symlink hook path $1" "$4"
            return 1
        fi
        if ! _isSafeSedAddressScript "$3"; then
            writeToLog "deleteAllExcept: refusing unsafe sed program for $1: $3" "$4"
            return 1
        fi
        cat "$1" | sed -n "$3" > "$4/tmp/$2.tmp.$$"
        echo "#!/bin/bash" > "$1"
        cat "$4/tmp/$2.tmp.$$" >> "$1"
        rm -f "$4/tmp/$2.tmp.$$"
    fi
}

function deleteAllInclude(){
    #1 - hook
    #2 - tmp name
    #3 - pattern
    #4 - common path
    if [ ! -e "$4/tmp" ]; then
        mkdir -p "$4/tmp"
    fi
    if [ -e "$1" ];then
        # The cat read and the truncating `cat tmp > "$1"` below follow a
        # symlink at $1 with no O_NOFOLLOW; refuse so the root-run rewrite
        # cannot be redirected to (and clobber) an arbitrary target via a
        # planted link.
        if [ -L "$1" ]; then
            writeToLog "deleteAllInclude: refusing symlink hook path $1" "$4"
            return 1
        fi
        if ! _isSafeSedAddressScript "$3"; then
            writeToLog "deleteAllInclude: refusing unsafe sed program for $1: $3" "$4"
            return 1
        fi
        cat "$1" | sed "$3" > "$4/tmp/$2.tmp.$$"
        cat "$4/tmp/$2.tmp.$$" > "$1"
        rm -f "$4/tmp/$2.tmp.$$"
    fi
}

function showBar {
 # showBar feeds $1 into `let` arithmetic below; bash evaluates array
 # subscripts during arithmetic, so require a plain integer first.
 [[ "$1" =~ ^[0-9]+$ ]] || return 1
 nmb=$(cat $0 | grep showBar | wc -l)
 let "nmb = $nmb"
 let "prct = $1 * 30 / $nmb"
 let "prct_n = $1 * 100 / $nmb"
 prg=`basename "$0"`
 echo -n "$prg: [" >&2
 for bar in {1..30}
 do
  if [ $bar -le $prct ];then
    echo -n "#" >&2
  else
    echo -n " " >&2
  fi
 done
 echo -ne "] ($prct_n%)\r" >&2
}

function get_command(){
    command=$(which $1)
    if [ $? != 0 ]; then
        writeToLog "Can't execute command $1..."
        exit 1;
    fi
    echo $command
}

function createHookHeader(){
    #1 - hook name
    #2 - modificator
    #3 - common path
    if [ ! -e "$3/tmp" ]; then
        mkdir -p "$3/tmp"
    fi
    # Refuse a symlinked hook path: the > create, chmod and mv -f below all
    # follow symlinks with no O_NOFOLLOW, so a planted link would redirect the
    # root-run truncating write / mode change to an arbitrary target.
    if [ -L "$1" ]; then
        writeToLog "createHookHeader: refusing symlink hook path $1" "$3"
        return 1
    fi
    # $1 (hook path) and $2 (modificator) are interpolated UNESCAPED into a
    # root-run wrapper line and into the .bak path of mv. Restrict $2 to a safe
    # token and reject shell metacharacters / control chars in $1 so neither can
    # break out of the generated `<path>.<mod>.bak "$@"` command line.
    if [[ ! "$2" =~ ^[A-Za-z0-9._-]+$ ]]; then
        writeToLog "createHookHeader: refusing unsafe modificator for $1: $2" "$3"
        return 1
    fi
    if [[ "$1" == *[[:cntrl:]]* || "$1" == *[\'\"\\\`\$\;\&\|\<\>\(\)]* ]]; then
        writeToLog "createHookHeader: refusing unsafe hook path $1" "$3"
        return 1
    fi
    if [ ! -e "$1" ]; then
        echo "#!/bin/bash" > "$1"
        chmod 755 "$1"
    else
        chmod 755 "$1"
        tmp=`/bin/sed -n '/#\!\/bin\/\(ba\)\?sh/p' "$1"`
        if [ -z "$tmp" ];then
            /bin/grep -q "$1.$2.bak" "$1"
            if [ $? = 1 ];then
                mv -f "$1" "$1.$2.bak"
                echo "#!/bin/bash" > "$1"
                echo "'$1.$2.bak'"' "$@"' >> "$1"
                chmod 755 "$1"
                chmod 755 "$1.$2.bak"
            fi
        fi
    fi
}

function checkHookString(){
    #1 - hook name
    #2 - pattern string
    #3 - comment srting
    if [ -e "$1" ];then
        # $2/$3 are appended verbatim to a root-run hook script; a newline or
        # other control char would land an uncommented command. A backslash is
        # rejected too: GNU sed's `1a` (used in addHookStringFirst) expands
        # backslash escapes (\n, \t) into real chars, so a control-char-free
        # `foo\ntouch X` would still split into a new line. Require a single
        # printable, escape-free line for each (no control chars, no backslash).
        if [[ "$2" == *[[:cntrl:]]* || "$2" == *\\* || "$3" == *[[:cntrl:]]* || "$3" == *\\* ]];then
            writeToLog "checkHookString: refusing control chars or backslash in hook line for $1" "`dirname "$1"`"
            return 1
        fi
        # The cat read and the >> append below follow a symlink at $1 with no
        # O_NOFOLLOW; refuse so the root-run append cannot be redirected to an
        # arbitrary target via a planted link.
        if [ -L "$1" ]; then
            writeToLog "checkHookString: refusing symlink hook path $1" "`dirname "$1"`"
            return 1
        fi
        tmp=`cat "$1" | grep "$2"`
        if [ -z "$tmp" ];then
            echo "$2   #$3" >> "$1"
        fi
    fi
}

function addHookStringFirst(){
    #1 - hook name
    #2 - pattern string
    #3 - comment srting
    #4 - short hook name
    #5 - common path
    if [ -e "$1" ];then
    # The cat read and the truncating `cat tmp > "$1"` below follow a symlink
    # at $1 with no O_NOFOLLOW; refuse so the root-run rewrite cannot be
    # redirected to (and clobber) an arbitrary target via a planted link.
    if [ -L "$1" ]; then
        writeToLog "addHookStringFirst: refusing symlink hook path $1" "$5"
        return 1
    fi
    # $4 is interpolated into a sed PROGRAM (/$4/d); restrict it to a bare
    # token so it cannot reach GNU sed e/w/r/W or close the address.
    if [[ ! "$4" =~ ^[A-Za-z0-9._-]+$ ]];then
        writeToLog "addHookStringFirst: refusing unsafe sed address token for $1: $4" "$5"
        return 1
    fi
    # $2/$3 are appended via GNU sed `1a`, which expands backslash escapes
    # (\n, \t) in its text. A control-char-free `foo\ntouch X` would therefore
    # be split into a new, uncommented line in a root-run hook. Reject control
    # chars AND backslash so $2/$3 stay a single printable, escape-free line.
    if [[ "$2" == *[[:cntrl:]]* || "$2" == *\\* || "$3" == *[[:cntrl:]]* || "$3" == *\\* ]];then
        writeToLog "addHookStringFirst: refusing control chars or backslash in hook line for $1" "$5"
        return 1
    fi
    cat "$1" | sed /$4/d | sed 1a"$2   #$3" > "$5/tmp/future_hook.tmp.$$"
    cat "$5/tmp/future_hook.tmp.$$" > "$1"
    rm -f "$5/tmp/future_hook.tmp.$$"
    fi
}

function getEasyApacheDir(){
    if [ -e /usr/home/cpeasyapache ]; then
        echo "/usr/home/cpeasyapache"
    elif [ -e /home/cpeasyapache ]; then
        echo "/home/cpeasyapache"
    else
        echo "/var/cpanel/cpeasyapache"
    fi
}

function is_ea4(){
    if [ -e '/etc/cpanel/ea4/is_ea4' ]; then
      return 0
    else
      return 1
    fi
}
