整理自:http://blog.chinaunix.net/uid-22101889-id-3167454.html
注意:請謹慎使用,到現在為止,使用了,我還沒找到改回去的方法。
1. 問題
第一、很多大公司的服務器都不允許直接登錄,而是通過一個跳板機才能登錄過去。在跳板機中,通常只能執行幾個少數命令(如SSH),而其他命令是不允許執行的,那么怎樣才能實現這個功能呢?
第二、一些小公司,由於服務器比較少,不需要什么跳板機之類的說法,公司的開發運維人員加起來也就那么十幾二十人,通常大家都知道root密碼,所有人都是直接root登錄上去,但是有時有人由於失誤,把什么服務弄掛了,這時是肯定抓不到人的,因為所有人都知道密碼,那么怎樣才能知道誰都執行過什么命令呢?或者干脆只讓他們執行少數允許的命令呢?
2. 解答
這兩個需求,看似不同,其實原理都相同,只要在用戶與shell之間加入個檢測機制就可以了,您可以去網上找些開源軟件,但是如果您嫌那些軟件太復雜,又不能完全滿足您的需求,那么就完全可以像我這樣,自己用shell寫一個簡易的模擬終端。
在模擬終端里,你需要做的,就是向用戶提供一個看起來像是真的但實際不是真的、的虛擬終端。而你需要做的,就是讀取用戶輸入的命令,並在替用戶執行之前進行記錄,這樣就實現了上面第二個需求中的記錄功能。如果你不想讓用戶執行哪些命令,那么僅僅echo一句“Permission denied”就可以將用戶阻擋在真正的shell大門之外了。
如果用戶要執行的命令是你允許的,那么在執行之前,記錄個log就行了。如果將這個虛擬終端設置成只能執行ssh這個命令,那么不就實現了第一個問題中的跳板機的功能了嗎。
所以以上兩個需求,都可以總結為一個需求,那就是:攔截用戶的輸入,在用戶與shell之間加一道門,而腳本的功能,就是根據用戶不同的輸入采取不同的動作,腳本的長短也就跟需要的命令的多少有關,需要的命令越多,腳本需要判斷的就越多,腳本也就越長,但實際並不需要為每個命令都寫一個action,只要為每個種類的寫一個action就行了,比如:最簡單的情形,沒有任何參數的命令,如:pwd等,稍復雜些的,如touch、ls等命令,這時需要處理參數,再復雜些的,如rm、mv等命令,不只需要處理參數,還要避免用戶誤刪數據,而以上所有命令,都需要判斷用戶是否有權限等等,腳本的復雜程度,完全取決於你的需求了,下面的虛擬終端內,實現了用戶可以執行cd ls rm mv download upload pwd passwd等等幾個命令,如果您只實現ssh命令,那就是跳板機了。
3. 實現腳本
腳本主要技術:
1.echo 顯色
2.trap處理信號
3.stty,dd關閉回顯並讀取一個字符
4.讀取密碼
5.<<EOF行內輸入
6.命令使用日志記錄
7.將用戶固定在某目錄下
8.執行固定的命令
# ~/virtual terminal
# 將此文件放在/etc/profile.d/目錄下,並添加可執行權限。 # 如: -rwxr-xr-x 1 root root 7340 Oct 23 18:12 /etc/profile.d/vt.sh
############################################################################ # 本行直至文件末尾的代碼,用於實現用戶登錄使用的虛擬終端 # 請勿刪除或更改 # 如有疑問、請聯系維護負責人: xiaoxi227(QQ451914397) ############################################################################
# 該文件存放用戶名和密碼 文件格式為==> 用戶名:密碼 (每行一個用戶) passwd_file=/etc/user.password
function red() { # 以紅色顯示 echo -e "\033[31;40m$*\033[0m\n" }
function green() { # 以綠色顯示 echo -e "\033[32;40m$*\033[0m\n" }
# 忽略所有信號,以免用戶使用Ctrl+C或者Ctrl+Z等操作退出Virtual Terminal(VT) for signal in `seq 1 64` do trap : $signal &> /dev/null done
clear
function getchar() # 關閉回顯,用於輸入密碼 { stty cbreak -echo dd if=/dev/tty bs=1 count=1 2>/dev/null stty -cbreak echo }
############################################################################### green "登錄成功。" # 用戶通過unix pam認證以后顯示“登陸成功”,隨后進行身份驗證 while : do read -p "請輸入用戶名:" username echo -n "請輸入密碼: "
while : do ret=$(getchar) if [ x"$ret" = x"" ];then echo break fi passwd="$passwd$ret" echo -n '*' done correct_passwd=$(gawk -F: "/$username/{ print \$2 }" $passwd_file)
if [ -z "$username" -o -z "$passwd" ];then clear red "用戶名或密碼不能為空" continue fi input_password=$(echo $passwd | md5sum | gawk '{ print $1 }') if [ x"$input_password" != x"$correct_passwd" ];then clear red "用戶名或密碼錯誤,請重新輸入" else break fi done
title=" ################################################################################## ############################## Virtual Terminal ################################## ################################################################################## " cat | more <<EOF $title
使用說明:
歡迎使用虛擬終端(Virtual Terminal,以下簡稱VT). 在此模式下,用戶只能執行少量被允許執行的命令,其他命令將會被拒絕執行。 本模式支持的命令及功能如下:
cd <DIR> 進入<DIR>目錄,前提是用戶有進入該目錄的權限。 ls 功能和系統中的ls命令相同,但是只能顯示有讀取權限的目錄下的文件。 本命令支持參數,系統中ls命令可以使用的選項都可以在VT模式下使用。 mv <SRC> <DST> 將文件<SRC>移至<DST>,如果路徑相同則含義為改名。 用戶必須同時對<SRC>和<DST>有讀寫權限。 rm <FILE> 將<FILE>從系統中刪除。 當<FILE>是目錄時,會遞歸刪除所有子目錄及子目錄下的文件。 pwd 顯示當前的工作路徑。 download <FILELIST> 將<FILELIST>下載至本機 運行此命令時,用戶使用的終端必須支持ZModem協議,如SecureCRT等。 下載多個文件需將各文件用空格隔開。 如果下載的文件是目錄,系統會自動將其打包后再下載。 upload 向系統中上傳文件,運行此命令后,終端會彈出一個對話框,按提示選擇文件上傳即可。 注意:運行此命令時,用戶使用的終端必須支持ZModem協議,如SecureCRT等。 passwd 修改自己的登錄口令
如需其他功能,請與管理員聯系,VT維護負責人:xiaoxi227(QQ:451914397) $title
EOF
echo -e "當前登錄用戶是:\033[32;40m$username\033[0m"
logfile=/var/log/vt.log
function run() # 本函數用於替用戶執行命令,並記錄log { local command="$@" { echo -n -e "\033[32;40m$username\033[0m\033[7G -- " echo -n -e "\033[31;40m`date '+%Y-%m-%d %H:%M:%S'` -- \033[0m$command" echo } | sed 's/sz/download/g;s/rz/upload/g' >> $logfile $command }
# 通常開發人員需要部署的程序都在同一目錄下,所以為其指定根目錄,限定僅在該目錄有權限。 ROOT_DIR=/usr # 本例中用/usr目錄測試
cd $ROOT_DIR # 進入用戶的“根目錄”
while : # 死循環讀取用戶輸入 do # 這里的主機名寫成“localhost”寫死了,您也可以寫成$HOSTNAME,這樣就會顯示主機名了。 read -e -p "[${username}localhost ${PWD##*/}]$ " REPLY # 如上語句模擬產生shell提示符(其實是假的)
# 分隔命令和參數 COMMAND=$(echo $REPLY | gawk '{ print $1 }') OPTION=$(echo $REPLY | sed -r "s/^$COMMAND//g;s/^[[:space:]]+//g")
# 判斷要執行的命令是什么,根據不同命令設置不同的動作。如下是本例中實現的幾個命令: case $COMMAND in cd|ls) # 當執行cd命令且參數為空時,默認cd到根目錄,行為要與系統中的cd命令保持一致。 if [ x"$OPTION" = x"" -a x"$COMMAND" = x"cd" ];then cd $ROOT_DIR continue fi # 判斷參數的第一個字符是不是"/",如果不是,說明想要cd或ls的是相對路徑。 # 也就是當前目錄下的文件活目錄,此時用戶是有權限的。 first_char=${OPTION:0:1} if [ x"$first_char" != x"/" ];then run $REPLY else # 如果是絕對路徑,那么判斷路徑是否以$ROOT_DIR開頭,若否則提示用戶沒權限。 echo $OPTION | grep -q ^$ROOT_DIR if [ $? -eq 0 ];then run $REPLY else echo "$COMMAND: Permission denied." >&2 fi fi ;; # 其他支持的參數:rm、mv、pwd、passwd等等,就不寫注釋了,自己理解下吧。 rm|mv) if [ x"$COMMAND" = x"rm" ];then RM='-rf';else RM='';fi run='ok' for _file in $OPTION do echo $_file | grep -q '-' [ $? -eq 0 ] && { red "本命令在VT模式下不支持參數"; continue; }
echo $_file | grep -q ^$ROOT_DIR ret=$? first_char=${_file:0:1} if [ x"$first_char" = x"/" ];then if [ $ret -ne 0 ];then run='not ok' fi fi done
if [ x"$run" = x"ok" ];then run $COMMAND $RM $OPTION else echo "$COMMAND: Permission denied." >&2 fi ;; pwd) run $REPLY ;; upload) run rz ;; download) for _file in $OPTION do if [ ! -d $_file ];then echo "開始下載文件:$_file" run sz $_file [ $? -eq 0 ] && green "下載完成" || red "下載失敗" else echo "${_file}是目錄,正在將其打包成${_file}.tar" tar -cf ${_file}.tar $_file [ $? -eq 0 ] && green "打包完成,開始下載${_file}.tar" \ || { red "打包失敗,無法下載";continue; } run sz ${_file}.tar [ $? -eq 0 ] && { green "下載完成" echo "正在刪除打包文件" rm -rf ${_file}.tar [ $? -eq 0 ] && green "刪除成功" \ || red "刪除失敗" } || red "下載失敗" fi done ;; passwd) read -e -s -p "請輸入舊密碼:" old_passwd echo '******' old_passwd=$(echo $old_passwd | md5sum | gawk '{ print $1 }') if [ x"$old_passwd" != x"$correct_passwd" ];then red "密碼輸入錯誤" continue else read -e -s -p "請輸入新密碼:" new_passwd1 echo '******' read -e -s -p "請重復新密碼:" new_passwd2 echo '******' if [ x"$new_passwd1" != x"$new_passwd2" ];then red "兩次輸入的密碼不一致" continue else new_passwd=$(echo $new_passwd1 | md5sum | gawk '{ print $1 }') sed -i "/$username/s/.*/$username:$new_passwd/g" $passwd_file fi fi ;; "") continue ;; *) echo "-bash: $COMMAND: command not found" >&2 ;; esac
done |