【項目一】一、知識儲備

一、Linux相關函數知識儲備

(一)獲取用戶信息

1. 獲取用戶UID、登錄名

在Linux內部機制上用戶擁有唯一的用戶標識符UID,Linux運行的每個程序實際上都是以某個用戶的名義在運行,所以有的程序必須管理員身份纔可以運行,我們需要su將UID改爲超級用戶UID(0)。所以UID時用戶身份的關鍵。UID的分類如下:

UID範圍 含義
0 系統管理員,所以當你要讓普通用戶具有管理員的權限時,將該賬號的UID改爲0即可
1~999 系統賬號,又分爲:1~200:由Linux發行版本自行建立的系統賬號,一般用來運行網絡服務和後臺服務;201~999:若用戶由系統賬號需求時,進行分配
1000~6000 給一般用戶使用,目前Linux3.10可以支持到2^32-1

那我們如何獲取用戶的UID和登錄名呢,系統給我們提供了兩個方法getuid,getlogin

# include<sys/types.h>
# include<unistd.h>

uid_t getuid(void) //返回程序關聯的UID即啓動程序的用戶UID
char* getlogin(void)//返回當前用戶的登錄名

uid_t是UID自己的數據類型,定義在頭文件sys/types.h中,通常是一個小整數。

2. 根據UID或登錄名獲取用戶信息

我們之前在用戶管理命令這一部分說過:系統文件/etc/passwd包含一個用戶賬號數據庫,它由行組成,每行對應一個用戶,包括用戶名、加密密碼佔位符,用戶標識符UID,組標識符GID,用戶全名,家目錄和默認shell。如何獲取到這些信息呢?系統給我們提供了兩個方法getpwuid,getpwnam

# include<sys/types.h>
# include<pwd.h>

struct passwd* getpwuid(uid_t uid);//根據UID獲取信息
struct passwd* getpwnam(const char* name);//根據用戶名獲取信息
                          成功返回一個指向passwd結構的指針,失敗返回空指針並設置errno

passwd結構我們稱它爲密碼數據庫,存儲用戶的相信信息,在頭文件pwd.h中,主要包含以下信息:

passwd成員 說明
char* pw_name 用戶登錄名
uid_t pw_uid UID號
gid_t pw_gid GID號
char* pw_dir 用戶家目錄
char* pw_gecos 用戶全名
char* pw_shell 用戶默認shell

我們可以根據:

struct passwd *pw;
pw=getpwuid(getuid());
pw=getpwnam("xxx")

獲取到指向xxx用戶的數據庫的pw指針,根據結構體指針的指向我們可以獲取passwd中的成員,如指針pw->pw_dir獲取家目錄信息。

(二)獲取主機信息

可以根據函數獲取到用戶詳細信息,那麼也可以通過函數uname獲取到主機的信息,我們可以根據系統調用函數獲取主機的詳細信息:

# include<sys/utsname.h>
int uname(struct utsname* name);
            成功返回一個非負整數,失敗返回-1,並設置errno指出錯誤

uname函數把主機信息寫入name參數指向的結構,這個結構類型是utsname,它定義在頭文件sys/utsname.h中,包含以下信息:

utsname成員 說明
char sysyname[] 操作系統名
char nodename[] 主機名
char release[] 系統發行級別
char version[] 系統版本號
char machine[] 硬件類型

我們可以根據:

struct utsname uts;
uname(&uts)

將主機信息保存在uts結構體中,根據結構體的指向可以獲取到主機信息,如uts.nodename獲取到主機名。

獲取用戶信息返回的是一個指向passwd結構體的指針,得到其成員變量需要用【->】符號,獲取主機信息得到一個被填充的結構體,得到其成員變量需要用【.】符號。因爲它們的本質不同,一個是指針結構體,一個是結構體。

(二)切換目錄 && 獲取當前工作目錄

cd命令可以在shell中切換路徑目錄,在程序中使用系統調用chdir也可以進行目錄切換:

# include <unistd.h>
int chdir(const char* path); 
             成功返回0,失敗返回-1

程序可以通過調用函數獲取自己當前的絕對工作目錄,函數getcwd功能和命令pwd一樣:

# include <unistd.h>
char* getcwd(char* buf,size_t size);
                成功返回buf指針,失敗返回NULL

getcwd函數把當前絕對目錄的名字寫到給定的緩衝區buf中,如果目錄名的長度超過給出的size即緩衝區長度,那麼就失敗,返回NULL。

(三)掃描目錄獲取目錄下的文件

Linux下確定一個特定目錄下存放的文件,在shell終端我們可以ls獲取,但是在程序中如何通過函數獲取呢,Linux提供了一整套的庫函數,可以實現對目錄進行掃描,獲取等操作。
先了解一下DIR結構,它作爲目錄操作的基礎,被稱爲目錄流的指向,這個結構的指針(DIR*)被用來完成各種目錄操作,其使用方法與用來操作普通文件的文件流(FILE*)非常相似。目錄數據項本身在dirent結構中返回,該結構也是dirent.h頭文件裏聲明的,這是因爲用戶不應該直接改動DIR結構中的數據字段。
操作目錄流用這三個方法:opendir,readdir,closedir就可以實現,我們逐一來看一下:
1.opendir:作用是打開一個目錄並建立一個目錄流:

# include<sys/types.h>
# include<dirent.h>
DIR* opendir(const char* name);//傳入路徑
      成功返回一個指向DIR結構的指針,該指針用於讀取目錄數據項,失敗返回一個空指針

當目錄流使用一個底層文件描述符來訪問文件本身,那麼如果打開文件過多,opendir可能會失敗。

2.readdir:用於讀取已打開目錄中的目錄項相關信息 :

# include <sys/types.h>
# include<dirent.h>
struct dirent* readdir(DIR *dirp);
   成功返回一個指向存儲目錄項結構體dirent的指針,有錯誤發生或讀取到目錄文件尾則返回NULL

結構體dirent中包含的目錄項內容包含以下部分:

成員 含義
ino_t d_ino 文件的inode節點號
char d_name[] 文件的名字

我們可以通過得到的指針,進行結構體成員的訪問,如dir->d_name可以獲取目錄下的文件名。
注意,如果在readdir函數掃描目錄的同時還有其他進程在該目錄裏創建或刪除文件,readdir將不保證能夠列出該目錄裏的所有文件和子目錄。
3.closedir:用於關閉一個目錄流並釋放與之關聯的資源:

# include<sys/types.h>
# include<dirent.h>
int closedir(DIR* dirp);
        執行成功時返回0,發生錯誤時返回-1

根據這幾個函數我們就可以實現一個類似ls的功能,即打開一個路徑下的目錄流,讀取目錄項,輸出文件名,最後關閉目錄流:下面是該目錄下只有一個文件的情況,如果有多個文件,那麼需要循環readdir讀取目錄流,獲取信息

DIR* dp;//先定義DIR結構指針,指向打開的目錄流
struct dirent *entry;//定義保存目錄項的結構體指針,指向讀取到的目錄項結構體
dp=opendir(path);//path爲需要打開目錄的路徑,結果是dp指針指向一個目錄流
entry=readdir(dp);//打開dp指針指向的目錄流,entry指針指向存儲目錄項的結構體
printf("%d",entry->d_node);//輸出文件節點
printf("%s",entry->d_name);//輸出文件名

(四)獲取文件屬性,類型,權限

  1. 文件屬性的獲取可以利用函數stat,lstat,fstat獲取;
  2. 文件類型可以用<sts/stat.h>頭文件提供的函數,根據文件類型宏判斷:
文件類型宏(st_mode) //判斷是否爲這個類型的文件,是返回1,否則返回0
  1. 文件權限可以利用:
if(st.st_mode & 文件權限宏) //判斷該文件用戶是否對應宏描述的權限

具體講解見文件屬性獲取這篇博客,就不在此重複講述了。

(五)重新設置有效用戶UID

在Linux中每個進程都有三個用戶標識符,分別爲:

UID類型 用處
real uid 真實用戶ID 是登錄時的用戶ID
saved uid 已保存的用戶ID 由exec函數保存
effective uid 有效用戶ID 在進程運行時的用戶ID,可用於文件訪問權限檢查

一般來說,三者相等,但是在某些情況下,我們需要讓程序運行時需要特殊的權限,如訪問Linux系統的/etc/passwd文件,或讓A用戶可以以B用戶的權限去做某些事,這就需要改變有效用戶ID,使用setuid可以進行改變,讓它們暫時獲得相關操作權限。

# include<unistd.h>
int setuid(uid_t uid);
     執行成功返回0,失敗返回-1

setuid函數的執行分爲以下幾個方面:

  • 如果由普通用戶調用,將當前進程的有效ID設置爲uid。
  • 如果是由管理員(uid爲0)調用,將真實,有效,保存的uid都設置新的uid。那麼root用戶就完全替換爲該用戶,風險很大。
  • 如果進程沒有超級用戶權限,那麼只將有效用戶id設置爲uid,即以uid的權限運行該進程。
  • 如果進程有超級用戶權限,那麼將真實,有效,保存用戶id都設置爲uid,那麼運行該進程的用戶完全改變。

(六)獲取當前用戶密碼

用戶密碼爲了安全起見,都在一個稱爲陰影口令的文件/etcshadow中存放着,裏面的密碼都是加密過的密碼,那麼我們如何獲取到shadow文件中加密後的密碼呢?需要用到getspnam函數:

# include<shadow.h>
struct spwd* getspnam(const char* name);//參數傳入當前用戶名
    成功返回指向spwd結構體的指針,失敗返回NULL

spwd結構包含以下信息:

struct spwd {   
 char *sp_namp; //用戶登錄名   
 char *sp_pwdp; //加密密碼
 long int sp_lstchg; //上次更改密碼以來的時間   
 long int sp_min; // 進過多少天后可以更改 
 long int sp_max; //要求更改的剩餘天數 
 long int sp_warn; // 到期警告的天數  
 long int sp_inact; // 賬戶不活動之前尚餘天數    
 long int sp_expire; //賬戶到期天數 
 unsigned long int sp_flag; //保留 

那麼我們根據:

struct spwd* sp=getspnam("root");//得到spwd結構體
char* p=sp->sp_pwdp;//獲取加密密碼

加密密碼,由3部分組成:

$加密算法ID$加密密鑰$密文

獲取到它可以爲我們密碼驗證做鋪墊,如果我們將我們輸入的密碼,按照相同的加密算法,密鑰進行加密,如果和系統的一樣,就證明我們密碼輸入正確。

(七)加密函數

當我們輸入密碼後,系統會將我們的密碼進行加密存儲,我們學習一種常用的加密函數,即crypt函數,是一種單向加密技術,不可還原,同時不在C默認庫裏面,在crypt庫裏面,運行時需要手動連接。函數原型爲:

char* crypt(const char* key,const char* salt);//key是我們需要傳入的明文,即自己輸入的密碼,salt是我們指定的加密算法和密鑰。
       成功返回加密後的密文,失敗返回NULL

salt一般爲:

$id$密鑰;//id爲加密算法類型,密鑰不超過8字符

常用的加密算法,即id的取值爲:

id 算法類型
1 MD5
2a Blowfish
5 SHA-256
6 SHA-512(當前Linux採用的加密算法)

最後形成的加密信息爲:

$id$密鑰$明文

(八)添加所有者權限

Linux系統中我們經常使用的權限有r,w,x,其實除了這3個權限外還支持s,t權限,我們今天主要了解s權限。
s權限:設置使文件在執行階段具有文件所有者二等權限,相當於臨時擁有文件所有者的身份。如訪問shadow中的信息,需要管理員權限,那麼我們就可以給它添加s權限。我們採用字符模式設置s權限:

chmod a+s filename;//給這個文件的所有者都添加s權限

在設置s權限前,必須要先設置x權限,否則chmod雖然不報錯但無法s權限無法生效

(九)取消/添加系統回顯功能

回顯功能是顯示屏幕上輸入的信息,系統默認是存在回顯功能的,但是當我們輸入密碼時,我們不希望別人看到我們的密碼,那麼就需要取消系統的回顯功能。
回顯功能的設置需要設置終端的本地模式,那麼就需要了解一個結構體,termios結構體中保存了終端不同模式的成員變量,通過改變成員變量的值就可以改變終端的相關模式。termios結構體包含:

# include<termios.h>
struct termios{
      tcflag_t c_iflag;//輸入模式
      tcflag_t c_oflag;//輸出模式
      tcflag_t c_cflag;//控制模式
      tcflag_t c_lflag;//本地模式
      cc_t     c_cc[NCCS];//特殊控制字符
      }  

我們可以調用函數tcgetattr來初始化和當前終端對應的termios結構:

# include<termios.h>
int tcgetattr(int fd,struct termios*termios_p);//參數fd爲終端的文件描述符,再傳入temios結構體

這樣會把當前終端接口變量的值寫入termios_p參數指向的結構。
我們可以對termios結構體中的變量進行修改,修改過後需要再調用tcsetattr重新配置終端接口:

# include<termios.h>
int tcsetattr(int fd,int actions,const struct termios* termios_p);//文件描述符,修改方式,需要修改的結構體

常見的修改方式有三種:

變量 含義
TCSANON 立刻對值進行修改
TCSADRAIN 等當前的輸出完成後再對值進行修改
TCSAFLUSH 等當前輸出完成後再對值進行修改,但丟棄還未從read調用返回的當前可用的任何輸入

一定要注意,程序有責任將終端設置恢復到程序開始運行之前的狀態,這一點是非常重要的,所以我們需要先保存termios結構體的值,最後再程序結束時恢復它們。

我們現在對本地模式進行不回顯修改:不回顯宏爲

 ECHO:啓動輸入字符的本地回顯功能
~ECHO:取消輸入字符的本地回顯功能

把termios結構體中的本地模式設置爲不回顯,然後進行重新配置,獲得密碼後,再將終端配置恢復,這個很重要,否則以後的輸入都是不回顯的了。
所以我們可以:

struct termios oldter,newter;//定義兩個保存終端信息的兩個結構體,oldter用來恢復,newter用來設置
tcgetatrr(0,&oldter);//oldter結構體獲取到當前終端的模式信息
newter=oldter;//把信息給newter結構體
newter.c_lflag&=~ECHO;//進行本地模式的修改,修改爲不回顯
tcsetattr(0,TCSANON,&newter);//進行修改後的配置,立即修改
fgets(password,127,stdin);//獲取不回顯的信息
tcsetattr(0,TCSANON,&oldter);//恢復系統原來的回顯功能

二、處理字符串函數知識儲備

(一)分割字符函數strtok()

分割函數可以按照一定的標識將字符串切割,函數原型爲:

# include<string.h>
char* strtok(char s[],const char* delim);//s爲要分解的字符串,delim爲分割字符

返回值:從s開頭開始一個個分割,當s中的字符查找到末尾是,返回NULL;如果找不到分割字符,則返回當前strtok的字符串指針。
首次調用時,s指向要分割的字符串,之後再次調用把s設爲NULL,表示接着上次分割的位置繼續分割。
如,將字符串“hello$world!"進行分割,分割字符保存在buff數組中:

char* s="hello$world!"
char* p=strtok(s,"$");
cout<<p;
p=strtok(NULL,"$");
cout<<p:

(二)初始化數組函數memset()

初始化數組,我們可以用memset,函數原型:

# include<memory.h>
void* memset(void*s,int ch,size_t n);//s爲指定數組,ch爲初始化數組的數值,n個字節
            返回替換好的指向數組的指針

如將buff數組初始化爲0:

char buff[4];
memset(buff,0,sizeof(char*)4);

(三)判斷字符存在函數strstr()

判斷一個字符串是否存在於另一個字符串,用strstr,函數原型爲:

string  strstr(string s1,string s2);

如果存在返回一個指針,指向s2在s1首次出現的位置。
如:s1=”hello",s2=“el”,判斷s2是否是s1的子串:

char *s1=”hello”;
char *s2=”el”;
printf(“%sn\n.”strstr(s1, s2);

輸出ello

(四)printf()函數的特殊控制

1. 輸出帶顏色的信息

要想輸出帶顏色的信息,printf函數需要遵循一定的格式:

printf("\033[字背景顏色;字體顏色m字符串\033[0m");
函數說明:
\033   表示後面的字符是轉義字符,通知終端切換到轉義模式,進行特殊處理;
[      標識命令序列的開始
m      制定將要執行的動作
33[0m  ANSI控制碼,對屏幕進行控制,此處表示爲關閉所有屬性,只顯示顏色

常見的字體背景顏色和字體顏色如下:

顏色 字背景顏色數值(40~49) 字顏色數值(30~39)
黑色 40 30
紅色 41 31
綠色 42 32
黃色 43 33
藍色 44 34
紫色 45 35
深綠 46 36
白色 47 37

輸出紅色的字體,白色背景的字符hello:

printf("\033[47;31mhello\033[0m");

2. 其餘特殊控制

可以對ANSI控制碼進行改變來達到不同的效果 ,常見的ANSI控制碼如下:

控制碼 含義
33[0m 關閉所有屬性
33[1m 設置高亮度
33[4m 下劃線
33[5m 閃爍
33[7m 反顯
33[8m 消隱
33[30m – 33[37m 設置前景色
33[40m – 33[47m 設置背景色
33[nA 光標上移n行
33[nB 光標下移n行
33[nC 光標右移n行
33[nD 光標左移n行
33[y;xH 設置光標位置
33[2J 清屏
33[K 清除從光標到行尾的內容
33[s 保存光標位置
33[u 恢復光標位置
33[?251 隱藏光標
33[?25h 顯示光標

加油哦!💪。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章