Shell/Python中的用戶名獲取

一、幾個基本概念

登錄用戶(login user):通過登錄方式進入系統的用戶,強調登錄身份。
當前用戶(current user):執行一個進程或者命令時所使用的用戶身份,強調執行身份。
 
       舉個例子,比如通過用戶名/密碼的方式,以用戶A的身份ssh到服務器上,那麼此時登陸用戶是A,當前用戶也是A;然後以su的方式從A切換到用戶B,此時登陸用戶是A,當前用戶是B。
 
       那麼“登錄用戶”和“當前用戶”有哪些本質意義上的不同呢?我認爲,大多數情況下(有例外)你可以認爲,其本質不同在於是否掌握用戶密碼,也就是是否真正的可驗證爲用戶本人。一般來說,“登錄用戶”需要通過賬號密碼驗證,是真正的“我”,而“當前用戶”可以通過su等手段切換過來,可能是僞造的“我”。
 

二、獲取用戶名

假設場景
       以root身份登錄ic-monitor01這臺機器(登錄用戶爲root),然後用su的方式切換爲liyanqing.1987這個賬號(當前用戶爲liyanqing.1987)。
[root@ic-monitor01 ~]# su - liyanqing.1987 
Last login: Sat Apr 27 09:34:21 CST 2024 on pts/2 
Welcome liyanqing.1987 ~ 
[liyanqing.1987@ic-monitor01 ~]$
 
       Linux shell中有幾種方法獲取當前terminal的登錄用戶名和當前用戶名。
 

2.1 whoami:獲取當前用戶名

[liyanqing.1987@ic-monitor01 ~]$ whoami 
liyanqing.1987
 

2.2 id -un:獲取當前用戶名

[liyanqing.1987@ic-monitor01 ~]$ id -un 
liyanqing.1987
 

2.3 who am i:獲取登錄用戶名

[liyanqing.1987@ic-monitor01 ~]$ who am i 
root pts/0 2024-05-18 09:35 (10.232.135.12)
 
       同樣,通過python也可以獲取當前termianl中的登錄用戶名和當前用戶名。
 

2.4 getpass.getuser():python獲取當前用戶名

[liyanqing.1987@ic-monitor01 ~]$ python3 
Python 3.8.8 (default, Dec 7 2022, 15:48:27) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import getpass 
>>> print(getpass.getuser()) 
liyanqing.1987
 

2.5 os.getlogin():python獲取登錄用戶名

[liyanqing.1987@ic-monitor01 ~]$ python3 
Python 3.8.8 (default, Dec 7 2022, 15:48:27) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import os 
>>> print(os.getlogin()) 
root
 
       由上可見,通過shell command和python function抓到的當前用戶名及登陸用戶名是一致的,因爲它們的信息來源及信息檢索方式是一致的。
 

三、用戶名信息來源

       當前用戶名信息來源於系統記錄,比如系統環境變量USER就記錄了當前用戶名信息。
[liyanqing.1987@ic-monitor01 ~]$ echo $USER 
liyanqing.1987
 
       而登錄用戶名的信息來源,則來自於系統登錄記錄。
       首先來了解一下Linux系統的用戶登錄/會話記錄三劍客 utmp/wtmp/btmp,這三個文件記錄和監控了用戶登錄、註銷以及失敗登陸嘗試等信息。
 
/var/run/utmp:記錄了當前正在登陸系統的用戶。
/var/log/wtmp:記錄了系統上所有的登陸和註銷事件。
/var/log/btmp:記錄了系統上的失敗登錄嘗試。
 
       這三個文件都是二進制的,並不易讀,但是可以通過utmpdump這個工具將其轉換爲更加易讀的文本文件。
[liyanqing.1987@ic-monitor01 ~]$ utmpdump /var/run/utmp > utmp 
Utmp dump of /var/run/utmp 
[liyanqing.1987@ic-monitor01 ~]$ head -n 3 utmp 
[2] [00000] [~~ ] [reboot ] [~ ] [3.10.0-1160.49.1.el7.x86_64] [0.0.0.0 ] [Sun Dec 24 10:41:13 2023 CST] 
[6] [03200] [tty1] [LOGIN ] [tty1 ] [ ] [0.0.0.0 ] [Sun Dec 24 10:41:31 2023 CST] 
[7] [62407] [ts/0] [root ] [pts/0 ] [10.232.135.12 ] [10.232.135.12 ] [Sat May 18 09:35:05 2024 CST]
 
       /var/run/utemp是最常被用到的一個文件,用於獲取登錄狀態的登錄用戶信息,可以通過w/who/users等系統命令獲取。
 
  • w:打印系統登錄用戶機器進程信息
[liyanqing.1987@ic-monitor01 ~]$ w 
10:20:49 up 145 days, 23:39, 1 user, load average: 0.41, 0.32, 0.33 
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT 
root pts/0 10.232.135.12 09:35 1.00s 0.29s 0.03s w
 
       注意,此處打印的是“登錄用戶”而非“當前用戶”,所以可以看到登錄用戶root的信息,但是看不到當前用戶liyanqing.1987的信息。
 
  • who:打印系統登錄用戶機器登錄信息
[liyanqing.1987@ic-monitor01 ~]$ who 
root pts/0 2024-05-18 09:35 (10.232.135.12)
 
       who命令仍然是隻打印登陸用戶信息,而且是隻打印登陸信息,所以更簡潔。
       who am i命令是who命令的分支用法,其信息檢索方式詳見第四章。
 
  • users:打印當前登陸系統的所有用戶的用戶列表。
[liyanqing.1987@ic-monitor01 ~]$ users 
root
 
       users比who的命令輸出更簡潔。
 

四、who am i命令如何獲取登錄用戶信息

       已知who am i命令的信息來自於who命令,那麼它的信息檢索原理是什麼呢?(一些tty/pts相關的基礎知識可以參考https://my.oschina.net/liyanqing/blog/11154146)
       首先,每個登陸用戶都會有個虛擬終端號與其綁定,這個信息可以通過tty命令獲取。
[liyanqing.1987@n232-135-012 ~]$ tty 
/dev/pts/1172
 
       這個綁定信息會在用戶登錄時寫入/var/run/utmp中,也就可以被who命令所查詢到。
[liyanqing.1987@n232-135-012 ~]$ who 
user_a pts/0 2024-05-14 19:20 (:59.0) 
user_b pts/1 2023-12-27 10:07 (:55.0) 
user_c pts/3 2024-02-20 19:30 (:42.0) 
user_d pts/4 2024-03-07 16:36 (:42.0) 
... 
liyanqing.1987 pts/1172 2024-05-18 10:35 (:1.0) 
...
 
       執行who am i的時候,它會自動獲取tty信息,然後依據這個pts編號去/var/run/utmp中檢索到匹配的項,然後把相應的信息返回回來。
[liyanqing.1987@n232-135-012 ~]$ who am i 
liyanqing.1987 pts/1172 2024-05-18 10:35 (:1.0)
 
       其基本流程如下。
 

五、避坑:who am i失效分析

       那麼存在執行who am i命令沒有輸出的情況嗎?實際上這種情況是存在的。
       測試條件:遠程連接工具ETX;操作系統centos 7.9;終端gnome-terminal
 
[liyanqing.1987@n232-135-013 ~]$ whoami 
liyanqing.1987 
[liyanqing.1987@n232-135-013 ~]$ who am i 
[liyanqing.1987@n232-135-013 ~]$ id -un liyanqing.1987
 
       如上所示,whoami和id -un命令都可以正常獲取當前用戶名,但是who am i無法獲取登陸用戶名。(這是因爲信息來源不同)
       更可怕的是,這種情況下,如果用python的os.getlogin()來嘗試獲取登錄用戶信息,會直接導致python coredump。
[liyanqing.1987@n232-135-013 ~]$ python3 
Python 3.8.8 (default, Dec 7 2022, 15:48:27) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import os 
>>> print(os.getlogin()) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module> 
OSError: [Errno 6] No such device or address
 
       從上一章可以知道,who am i的信息可以從who命令輸出中檢索出來,來檢驗一下。
[liyanqing.1987@n232-135-013 ~]$ tty 
/dev/pts/1115 
[liyanqing.1987@n232-135-013 ~]$ who | grep pts/1115 
[liyanqing.1987@n232-135-013 ~]$ 
 
       果然,who的輸出就不能獲取pts/1115,who am i必然沒有輸出。那麼who的輸出來自於/var/run/utmp,直接人工檢索一下這個文件呢?
        /var/run/utmp中也沒有!
       但是我們在/var/log/wtmp這個記錄了所有的用戶登錄/註銷信息的文件中是能檢索到pts/1115的。
 
       同時也可以用last命令來二次驗證一下,證明pts/1115確實存活,且爲“still logged in”的狀態。
[liyanqing.1987@n232-135-013 ~]$ last | grep pts/1115 
liyanqin pts/1115 :65.0 Thu May 9 15:30 still logged in
 
       那麼爲什麼在當前終端pts/1115存在的情況下,/var/run/utmp中卻沒有記錄呢?
       實際上這個問題很早就有人反饋給了redhat官方,但是官方反饋中並沒有給出明確的說明。https://bugzilla.redhat.com/show_bug.cgi?id=91442
如下回答則是一個可信度較高的答案。https://sorami-chi.hateblo.jp/entry/2018/02/18/002530
 
The root cause behind this problem is that /var/run/utmp is not magically maintained by the OS and it has to be updated by each process, while recent versions of libvte does not do this. This is not a bug of libvte, but an intentional choice discussed like here. Although in the same thread a concern about who am i was raised, it was just removed to improve the code cleanness (it seems like the function to update /var/run/utmp was in a very nasty file that is almost never used). Because both GNOME-terminal and MATE-terminal use libvte, this problem exist in both of them. 

根源是因爲操作系統並沒有有效地維護var/run/utmp,每個進程必須獨自更新這個文件,而最新版本的libvte並不能做到這一點。這不是libvte的bug,而是個刻意的選擇。儘管存在影響who am i命令的擔心,但是它仍然被刪了以提升代碼清潔度(更新/var/run/utmp的函數似乎位於一個從未被使用過的討厭的文件中)。因爲gonme-terminal和mete-terminal都使用libvte,所以這兩個終端都存在這個問題。
 

六、如何健壯地獲取登陸用戶名

       Shell中的who am i命令,或者python中的os.getlogin()函數,都是非常常用的功能而很難被繞過,但是第五章提到的故障會在用戶程序(shell/python程序)引起不可預知或者致命的問題,如何繞過這個問題呢?
 

6.1 shell中獲取登陸用戶名

       可以採用下面的寫法,在shell中健壯地獲取登陸用戶信息。
[liyanqing.1987@n232-135-013 ~]$ cat get_login_user.sh 
#!/bin/bash 

login_user=`who am i | awk '{print $1}'` 

if [ "0${login_user}" = "0" ]; then 
    pts=`tty | cut -c 6-` 
    login_user=`last -w | grep -w ${pts} | awk '{print $1}'` 
fi 

echo "login user is : ${login_user}"
 
       在正常的shell中可以獲取login user信息。
[liyanqing.1987@n232-135-013 ~]$ who am i 
liyanqing.1987 pts/1183 2024-05-18 11:29 (:65.0) 
[liyanqing.1987@n232-135-013 ~]$ ./get_login_user.sh 
login user is : liyanqing.1987
 
       在不正常的shell中仍然可以獲取login user信息。
[liyanqing.1987@n232-135-013 ~]$ who am i 
[liyanqing.1987@n232-135-013 ~]$ ./get_login_user.sh 
login user is : liyanqing.1987
 

6.2 python中獲取登錄用戶名

       可以採用下面的寫法,在python中健壯地獲取登陸用戶信息。
[liyanqing.1987@n232-135-013 ~]$ cat get_login_user.py 
#!/bin/env python3 
import os 
import subprocess 


def get_login_user(): 
    try: 
        login_user = os.getlogin() 
    except: 
        pts = subprocess.check_output(['tty']).decode('utf-8')[5:] 
        login_user = subprocess.check_output('last -w | grep -w ' + str(pts), shell=True).decode('utf-8').split()[0] 

    return login_user 


login_user = get_login_user() 
print('login user is : ' + str(login_user))
 
       在正常的shell中可以獲取login user信息。
[liyanqing.1987@n232-135-013 ~]$ who am i 
liyanqing.1987 pts/1183 2024-05-18 11:29 (:65.0) 
[liyanqing.1987@n232-135-013 ~]$ ./get_login_user.py 
login user is : liyanqing.1987
 
       在不正常的shell中仍然可以獲取login user信息,且不會發生coredump。
[liyanqing.1987@n232-135-013 ~]$ who am i 
[liyanqing.1987@n232-135-013 ~]$ ./get_login_user.py 
login user is : liyanqing.1987
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章