I've found that OpenWrt 24.10.1 doesn't have last command. Thus, I had to write a simple last command. However, how do I keep track of luci user login ? Any suggestion ?
last.sh
#!/bin/ash
bname=$(basename $0)
VERSION="$bname from $(uname) $(uname -r) Ver.0.1a" #Jun 9, 2025
succ=""
succTstmp=""
succPid=""
succNm=""
succIP=""
escSymbol="&"
disp="" #Output result
par=""
isShowTS=0 #0 = false show readable date time, 1 = true show timestamp
isShowTitle=0 #0 = false not show header, 1 = true show header
columnname=""
#Coloring Result
#=====================================================================================
#Header
bc='\033[1;36m'
et='\033[m'
husr="$bc""User""$et";hremote="$bc""Remote IP""$et";hloginT="$bc""Login Time""$et";hdur="$bc""Duration""$et";hstat="$bc""Status""$et"
#Up and Down Status
ubc='\033[1;32m'
dbc='\033[1;31m'
uPstat="$ubc""Up""$et";dNstat="$dbc""Down""$et"
#=====================================================================================
#Append variable to 'par' variable
while [ -n "$1" ]; do
par="$par$1 "
shift
done
#generate column header
function showtitle () {
columnname=""
if [ $isShowTitle -eq 1 ]
then
if [ $isShowTS -eq 0 ]
then
# format for readable date time header
columnname="$husr"$'\t'"$hremote"$'\t'$'\t'"$hloginT"$'\t'$'\t'"$hdur"$'\t'"$hstat"$'\n'
else
# format for timestamp header
columnname="$husr"$'\t'"$hremote"$'\t'$'\t'"$hloginT"$'\t'"$hdur"$'\t'"$hstat"$'\n'
fi
columnname="$columnname=============================================================================="$'\n'
fi
}
function helpdoc () {
echo $'\n'"Usage: $bname [-t][-T][?][--help]"$'\n'
echo "Show a listing of last logged in users."$'\n'
echo "Options:"
echo " -t, --title"$'\t'$'\t'"show column title"
echo " -T, --timestamp"$'\t'"show timestamp instead of human readable date and time"
echo " -h, --help"$'\t'$'\t'"display this help"
echo ""
}
function parameterhandler () {
for itm in $par;do
case $itm in
#Inject Title to output
-t) isShowTitle=1
;;
#Show Timestamp, instead of human readable date
-T) isShowTS=1
;;
#Print Usage
\?|--help) helpdoc; exit
;;
#Print Usage
-V|--version) echo $VERSION; exit
;;
esac
done
}
#Connect record Structure: TimeStamp PID Usrname IP
conrec=""
function retriveSucc () {
submatch1="[[:alpha:]]{3}[[:blank:]]*[[:alpha:]]{3}[[:blank:]]*[[:digit:]]+[[:blank:]]*[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}[[:blank:]]*[[:digit:]]{4}[[:blank:]]*\[([[:digit:]]+\.[[[:digit:]]{3})\]"
#Match PID: dropbear[7966]
submatch2="dropbear\[([[:digit:]]+)\]"
#Match login name: Password auth succeeded for 'root'
submatch3="Password[[:blank:]]*auth[[:blank:]]*succeeded[[:blank:]]*for[[:blank:]]*'([^\']+)'"
#Match ip name: from 10.10.1.55:51128$:
submatch4="from[[:blank:]]*([^$]+)"
tmp1=""
tmp1=$(printf '%s' "$succ" | sed -nE "s/^$submatch1.*$submatch2.*$submatch3.*$submatch4$/\1 \2 \3 \4/p" | tr '\n' '\n')
conrec=$(printf "%s" "$tmp1" | tr '\n' "$escSymbol" | sed -E "s/[$escSymbol]*$//g" | tr "$escSymbol" '\n' | sort) #Replace the end \n symbol
}
#disconnect record Structure: TimeStamp PID Usrname IP
disrec=""
function retrieveDisc () {
#Match timestamp: Wed Jun 4 02:16:11 2025 [1749003371.761]
submatch1="[[:alpha:]]{3}[[:blank:]]*[[:alpha:]]{3}[[:blank:]]*[[:digit:]]+[[:blank:]]*[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}[[:blank:]]*[[:digit:]]{4}[[:blank:]]*\[([[:digit:]]+\.[[[:digit:]]{3})\]"
#Match PID: dropbear[7966]
submatch2="dropbear\[([[:digit:]]+)\]"
#Match login name: Exit (root) or (user 'root', 0 fails)
submatch3="(\(([[:alnum:]]+)\)|'([[:alnum:]]+)')"
#Match ip name: from <10.10.1.55:51128>:
submatch4="from[[:blank:]]*<([^>]+)>[[:blank:]]*:"
tmp1=$(printf '%s' "$disc" | sed -nE "s/^$submatch1.*$submatch2.*$submatch3.*$/\1 \2 \4\5/p")
tmp2=$(printf '%s' "$disc" | sed -nE "s/^.*$submatch4.*$/\1/p")
IFSold=$IFS
#line separator for variable
IFS=$'\n'""
k=0
disrec="" #Make use it is empty
for item1 in $tmp1
do
disrec="$disrec$item1"
j=0
for item2 in $tmp2
do
if [ $k -eq $j ]
then
disrec="$disrec $item2"$'\n'"" #Append IP
break
fi
j=$(($j+1))
done
k=$(($k+1))
done
disrec=$(printf "%s" "$disrec" | tr '\n' "$escSymbol" | sed -E "s/[$escSymbol]*$//g" | tr "$escSymbol" '\n' | sort)
IFS=$IFSold
}
#exit record Structure: TimeStamp PID Usrname IP
exitrec=""
function retrieveExit () {
# Match timestamp: Wed Jun 4 02:16:11 2025 [1749003371.761]
submatch1="[[:alpha:]]{3}[[:blank:]]*[[:alpha:]]{3}[[:blank:]]*[[:digit:]]+[[:blank:]]*[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}[[:blank:]]*[[:digit:]]{4}[[:blank:]]*\[([[:digit:]]+\.[[[:digit:]]{3})\]"
#Match PID: dropbear[7966]
submatch2="dropbear\[([[:digit:]]+)\]"
#Match login name: (user 'root',
submatch3="\(user[[:blank:]]*'([^']+)',"
#Match ip name: from <10.10.1.55:51128>:
submatch4="from[[:blank:]]*<([^>]+)>[[:blank:]]*:"
tmp1=""
tmp1=$(printf '%s' "$ext" | sed -nE "s/^$submatch1.*$submatch2.*$submatch4.*$submatch3.*$/\1 \2 \4 \3/p")
#Take away 0 fail results, take
#Tue Jun 3 05:32:29 2025 [1748928749.149] authpriv.info dropbear[3556]: Exit before auth from <10.10.1.55:33686>: (user 'root', 0 fails): Exited normally
unMatch=$(printf '%s' "$ext" | grep -vE "\(user[[:blank:]]*\'[^\']+\',[[:blank:]]*0[[:blank:]]*fails")
#printf "%s" "$unMatch"
#===========================================
#UnMatch type 1: Tue Jun 3 06:54:07 2025 [1748933647.855] authpriv.info dropbear[5226]: Exit (root) from <10.10.1.224:49940>: Exited normally
#===========================================
unmat1=""
unmat1=$(printf '%s' "$unMatch" | grep -E ":[[:blank:]]*Exit[[:blank:]]*\([^\)]*\)[[:blank:]]*from") #match line with
#Match login name: Exit (root) from
submatch3=":[[:blank:]]*Exit[[:blank:]]*\(([^\)]+)\)[[:blank:]]*"
#Match ip name: from <10.10.1.55:51128>:
submatch4="from[[:blank:]]*<([^>]+)>"
tmp2=""
tmp2=$(printf '%s' "$unmat1" | sed -nE "s/^$submatch1.*$submatch2.*$submatch3.*$submatch4.*$/\1 \2 \3 \4/p")
#===========================================
#UnMatch type 2: Tue Jun 3 05:34:04 2025 [1748928844.274] authpriv.info dropbear[3557]: Exit before auth from <10.10.1.55:58844>: Exited normally
#No User name specify
#===========================================
unmat2=""
unmat2=$(printf '%s' "$unMatch" | grep -vE ":[[:blank:]]*Exit[[:blank:]]*\([^\)]*\)[[:blank:]]*from") #match reverse
tmp3=""
tmp3=$(printf '%s' "$unmat2" | sed -nE "s/^$submatch1.*$submatch2.*$submatch4.*$/\1 \2 Anonymous \3/p")
exitrec="$tmp1"$'\n'"$tmp2"$'\n'"$tmp3"$'\n'""
exitrec=$(printf "%s" "$exitrec" | tr '\n' "$escSymbol" | sed -E "s/[$escSymbol]*$//g" | tr "$escSymbol" '\n' | sort) #Replace the end \n symbol )
return 0
}
onprocrec=""
function UserOnProc () {
#lgUsr=$(ps | grep dropbear | grep -v "`cat /var/run/dropbear.1.pid`\|grep" | awk '{print $1 }')
lgUsr=$(ps | grep dropbear | grep -v "`cat /var/run/dropbear.1.pid`\|grep")
currentTS="$(date +%s)"
}
#ps | grep dropbear | grep -v "`cat /var/run/dropbear.1.pid`\|grep" | awk '{print $1 }' > /tmp/usrlog
lgUsr=$(ps | grep dropbear | grep -v "`cat /var/run/dropbear.1.pid`\|grep" | awk '{print $1 }')
drobearLog=$(logread -t -e 'dropbear')
succ=$(printf '%s' "$drobearLog" | grep "Password auth succeeded for")
disc=$(printf '%s' "$drobearLog" | grep "Disconnect received")
ext="$(printf '%s' "$drobearLog" | grep 'Exited')"
function dispformat () {
item1=$2
test=$3
usrIP=""
lginTS=""
lgoutTS=""
duration=""
usrIP=$(printf "%s" "$item1" | awk '{print $4}')
usrIP='\033[1;33m'"$usrIP"'\033[m'
lginTS=$(printf "%s" "$item1" | awk '{print $1}')
lgoutTS=$(printf "%s" "$test" | awk '{print $1}')
duration=$(awk "BEGIN {print $lgoutTS - $lginTS}")
lginTS=$(printf "%.f" "$lginTS") #Remove decimal
lgoutTS=$(printf "%.f" "$lgoutTS") #Remove decimal
duration=$(printf "%.f" "$duration") #Remove decimal
if [ $isShowTS -eq 0 ] #not show timestamp
then
duration="$(printf '%dh:%dm:%ds\n' $((duration/3600)) $((duration%3600/60)) $((duration%60)))"
lginTS=$(date +'%y-%m-%d %H:%M:%S' -d @"$lginTS")
disp="$disp""$usrNm"$'\t'"$usrIP"$'\t'"$lginTS"$'\t'"$duration"$'\t'"$1"$'\n'
else
disp="$disp""$usrNm"$'\t'"$usrIP"$'\t'"$lginTS"$'\t'"$duration"$'\t'$'\t'"$1"$'\n' #header format for showing timestamp
fi
}
function checkStatus () {
IFSold=$IFS
#line separator for variable
IFS=$'\n'""
test1=""
test2=""
test3=""
k=0
showtitle #check if show header
ubColor="\033[1;32m" # color Up begin
dbColor="\033[1;31m" # color Down begin
deColor="\033[m" # color end
for item1 in $conrec
do
#echo "$k)$item1"
usrPID=$(printf "%s" "$item1" | awk '{print $2}')
usrNm=$(printf "%s" "$item1" | awk '{print $3}')
#echo $usrPID" "$usrNm
test1=""
test1=$(printf '%s' "$disrec" | grep -E ".*$usrPID.*$usrNm")
if [ -n "$test1" ]
then
dispformat "$dNstat" "$item1" "$test1"
else
test2=$(printf '%s' "$exitrec" | grep -E ".*$usrPID.*$usrNm") #If exited
if [ -n "$test2" ]
then
dispformat "$dNstat" "$item1" "$test2"
else
currentTS="$(date +%s)"
test3=$item1
test3=$(printf "%s" "$test3" | awk '$1='"$currentTS")
dispformat "$uPstat" "$item1" "$test3"
fi
fi
k=$(($k+1))
done
IFSold=$IFS
return 0
}
parameterhandler
retriveSucc #Extract login
retrieveDisc #Extract logout
retrieveExit #Extract Exit
checkStatus
printf "$columnname""$disp" #Print output