Linux程式設計之一(轉)
Linux程式設計之一(轉)[@more@]Linux程式設計 - 1.fork (2001-05-24 14:08:00)在UNIX程式設計中,學會fork()的運用,算是相當基本的功夫。fork()及signal經常運用在daemon守護神這一類常駐程式,另外像a4c.tty/yact/chdrv這些中文終端機程式也有用到,一般如Mozilla/Apache/Squid等大程式幾乎都一定會用到。--------------------------------------------------------------------------------程式分歧fork()fork()會產生一個與父程式相同的子程式,唯一不同之處在於其process id(pid)。如果我們要撰寫守護神程式,或是例如網路伺服器,需要多個行程來同時提供多個連線,可以利用fork()來產生多個相同的行程。函式宣告pid_t fork(void);pid_t vfork(void);返回值:-1 : 失敗。0 : 子程式。>0 : 將子程式的process id傳回給父程式。在Linux下fork()及vfork()是相同的東西。Linux程式設計- 2.fork, pthread, and signals (2001-05-24 15:00:00)雖然在UNIX下的程式寫作,對thread的功能需求並非很大,但thread在現代的作業系統中,幾乎都已經存在了。pthread是Linux上的thread函式庫,如果您要在Linux下撰寫多執行緒式,例如MP3播放程式,熟悉pthread的用法是必要的。有關thread寫作,有兩本很好的書:Programming with POSIX ThreadsMultithreading Programming Techniques另外有一份初學者的參考檔案Getting Started With POSIX Threadspthread及signal都可以用一大章來討論。在這裡,我只談及最簡單及常用的技巧,當您熟悉這些基本技巧的運用後,再找一些專門深入探討pthread及signal程式寫作的書籍來研究。這些進階的寫法,用到的機會較少,將層次分明,學習速度應該會比較快。--------------------------------------------------------------------------------thread我假設您對thread已經有一些基本的概念,因此,在此我將著重於如何實作。函式宣告int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);int pthread_join(pthread_t th, void **thread_return);int pthread_detach(pthread_t th);void pthread_exit(void *retval);int pthread_attr_init(pthread_attr_t *attr);資料結構typedef struct{int detachstate;int schedpolicy;struct sched_param schedparam;int inheritsched;int scope;} pthread_attr_t;例一:#include#include#include#includevoid * mythread(void *arg){for (;;) {printf("thread ");sleep(1);}return NULL;}void main(void){pthread_t th;if (pthread_create(&th,NULL,mythread,NULL)!=0) exit(0);for (;;) {printf("main process ");sleep(3);}}執行結果:./ex1main processthreadthreadthreadmain processthreadthreadthreadmain processthreadthreadthreadmain processLinux程式設計- 3.signals (2001-05-24 16:47:48)訊號處理--------------------------------------------------------------------------------訊號處理概說送出訊號接收訊號訊號的處理任務控制--------------------------------------------------------------------------------POSIX IPCreliable/unreliablereentrantpendingsending signalscatching signalsmanipulatingsignal definitions--------------------------------------------------------------------------------訊號singals訊號的處理可以用一大章來寫,涉及的層面也會深入整個作業系統中,我並不打算這樣做,因為您可能會越搞越迷糊。這裡我只告訴您如何接上訊號,在實用的層面上,這樣便很夠用了。您可以先利用這些基本的技巧來撰寫程式,等到有進一步高等應用的需要時,找一本較深入的UNIX Programming教材,專門研究signal的寫法。一般簡單的signal寫法如下:void mysignal(int signo){/* my signal handler */}void initsignal(void){struct sigaction act;act.sa_handler = mysignal;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(SIGHUP,&act,NULL);sigaction(SIGINT,&act,NULL);sigaction(SIGQUIT,&act,NULL);sigaction(SIGILL,&act,NULL);sigaction(SIGTERM,&act,NULL);}例一: lock.c在fork的例三中提到,在daemon被殺掉時,需要在離開前,將/var/run/lock.pid刪除。這裡我們可以利用signal來處理這件事。#include#include#include#include#define LOCK_FILE "/var/run/lock.pid"void quit(int signo){printf("Receive signal %d ",signo);unlink(LOCK_FILE);exit(1);}void main(void){FILE *fp;pid_t pid;struct sigaction act;if (access(LOCK_FILE,R_OK)==0) {printf("Existing a copy of this daemon! ");exit(1);}pid = fork();if (pid>0) {printf("daemon on duty! ");fp = fopen(LOCK_FILE,"wt");fprintf(fp,"%d",pid);fclose(fp);} elseexit(0); if (pid<0) {printf("Can't fork! ");exit(-1);}act.sa_handler = quit;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(SIGTERM,&act,NULL);sigaction(SIGHUP,&act,NULL);sigaction(SIGINT,&act,NULL);sigaction(SIGQUIT,&act,NULL);sigaction(SIGUSR1,&act,NULL);sigaction(SIGUSR2,&act,NULL);for (;;) {sleep(3);}}編譯:gcc -o ex1 lock.c執行./ex1daemon on duty!送訊號我們先找出該守護神程式的pidPID=`cat /var/run/lock.pid`接下來利用kill來送訊號kill $PIDReceive signal 15程式將會結束,並且/var/run/lock.pid將會被刪除掉,以便下一次daemon再啟動。注意到如果quit函式內,沒有放exit(),程式將永遠殺不掉。接下來送一些其它的訊號試試看。./ex1PID=`cat /var/run/lock.pid`kill -HUP $PIDReceive signal 1您可以自行試試kill -INT $PIDkill -QUIT $PIDkill -ILL $PID...等等這些訊號,看看他們的結果如何。訊號的定義在/usr/include/signum.h中有各種訊號的定義#define SIGHUP 1 &nbLinux程式設計- 4.socket (2001-05-24 17:24:26)UNIX Socket Programming基本上是一本書名。Socket programming其實需要相當程度的基礎,我不想在這包山包海地,如果您需要徹底研究,可以買這本書來看。在此我想提供一些簡單的 Server/Client兩端的簡單寫法,讓你有個起點,做為進一步研究的基礎。很多涉及較複雜的內容的,我在這便不詳細說明,您可以照本宣科,照抄著用,稍微熟悉時,再細細研究。--------------------------------------------------------------------------------Clientint sock_connect(char *domain,int port){int white_sock;struct hostent * site;struct sockaddr_in me;site = gethostbyname(domain);if (site==NULL) return -2;white_sock = socket(AF_INET,SOCK_STREAM,0);if (white_sock<0) return -1;memset(&me,0,sizeof(struct sockaddr_in));memcpy(&me.sin_addr,site->h_addr_list[0],site->h_length);me.sin_family = AF_INET;me.sin_port = htons(port);return (connect(white_sock,(struct sockaddr *)&me,sizeof(struct sockaddr))<0) ? -1 : white_sock;}要由Client向伺服器端要求連線的步驟,首先您必須要找出對方的位址,可利用:gethostbyname()接下來要建立起一個socket,然後用這個socket來建立連線。接下來我們利用這個簡單的socket程式來寫一個讀取WWW網頁的簡單瀏覽器(看html source)。#include#include#include#include#include#include#includeint htconnect(char *domain,int port){int white_sock;struct hostent * site;struct sockaddr_in me;site = gethostbyname(domain);if (site==NULL) return -2;white_sock = socket(AF_INET,SOCK_STREAM,0);if (white_sock<0) return -1;memset(&me,0,sizeof(struct sockaddr_in));memcpy(&me.sin_addr,site->h_addr_list[0],site->h_length);me.sin_family = AF_INET;me.sin_port = htons(port);return (connect(white_sock,(struct sockaddr *)&me,sizeof(struct sockaddr))<0) ? -1 : white_sock;}int htsend(int sock,char *fmt,...){char BUF[1024];va_list argptr;va_start(argptr,fmt);vsprintf(BUF,fmt,argptr);va_end(argptr);return send(sock,BUF,strlen(BUF),0);}void main(int argc,char **argv){int black_sock;char bugs_bunny[3];if (argc<2) return;black_sock = htconnect(argv[1],80);if (black_sock<0) return;htsend(black_sock,"GET / HTTP/1.0%c",10);htsend(black_sock,"Host: %s%c",argv[1],10);htsend(black_sock,"%c",10);while (read(black_sock,bugs_bunny,1)>0) { printf("%c",bugs_bunny[0]); }close(black_sock);}編譯:gcc -o ex1 client.c執行./ex1 --------------------------------------------------------------------------------ServerListen to a port要建立起一個網路伺服器,第一步就是要"傾遠方",也就是要Listen。以下是一般建立服務的方法:int DaemonSocket;struct sockaddr_in DaemonAddr;int BindSocket(void){DaemonSocket = socket(AF_INET,SOCK_STREAM,0);if (DaemonSocket==-1) return 0;DaemonAddr.sin_family = AF_INET;DaemonAddr.sin_port = htons(DAEMON_PORT);if (bind(DaemonSocket,&DaemonAddr,sizeof(DaemonAddr))<0) {printf("Can not bind! ");return 0;}if (listen(DaemonSocket,1024)!=0) {printf("Can not listen! ");return 0;}return 1;}Incoming call要檢視是否有連線進來,可用以下方式:int incoming_call(void){fd_set sock;struct timeval tv;int t;FD_ZERO(&sock);FD_SET(DaemonSocket,&sock);tv.tv_sec = 60; tv.tv_usec = 0;t = select(DaemonSocket + 1,&sock,NULL,NULL,&tv);if (t<=0||!FD_ISSET(DaemonSocket,&sock)) return 0;printf("incoming... ");return 1;}Connect Client當我們確認有人進來要求服務時,會需要accept connection,可用以下方式int ConnectClient(void){int socksize=sizeof(HostAddr);unsigned char * addr;ClientSocket = accept(DaemonSocket,(struct sockaddr*)&HostAddr,&socksize);if (ClientSocket<0) return 0;addr = (unsigned char *)&HostAddr.sin_addr.s_addr;printf("incoming address:%d.%d.%d.%d ",addr[0],addr[1],addr[2],addr[3]);return 1;}注意到當您accept connection之後,連線已建立起,此時要用的socket是ClientSocket,而非DaemonSocket,ClientSocket才是真正用來連線用的socket。這是個我才剛開始動手寫的象棋伺服器。#include#include#include#include#include#include#include#include#include#include#include#include#define DAEMON_LOCK "/var/chess/daemon.lock"#define DAEMON_LOG "/var/chess/daemon.log"#define DAEMON_PORT 9901int DaemonSocket;struct sockaddr_in DaemonAddr;int ClientSocket=0;struct sockaddr_in HostAddr;void dlog(char *fmt,...){va_list argptr;FILE *fp;fp = fopen(DAEMON_LOG,"a+t");va_start(argptr,fmt);vfprintf(fp,fmt,argptr);va_end(argptr);fclose(fp);}pid_t CheckLock(void){pid_t me;FILE * fp;fp = fopen(DAEMON_LOCK,"rt");if (fp==NULL) return 0;fscanf(fp,"%d",&me);fclose(fp);return me;}pid_t WriteLock(void){pid_t me;FILE *fp;me = getpid();fp = fopen(DAEMON_LOCK,"w");fprintf(fp,"%d",me);fclose(fp);return me;}int CleanLock(void){return (unlink(DAEMON_LOCK)==0);}void report_time(void){time_t now;now = time(NULL);dlog("%s",asctime((const struct tm*)localtime(&now)));}static void signal_catch(int signo){time_t now;close(DaemonSocket);if (ClientSocket>0) close(ClientSocket);CleanLock();now = time(NULL);dlog("Catch signal %d, leave at %s ",signo,asctime((const struct tm*)localtiexit(-1);}void SetupSignal(void){struct sigaction act;act.sa_handler = signal_catch;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(SIGHUP,&act,NULL);sigaction(SIGINT,&act,NULL);sigaction(SIGQUIT,&act,NULL);sigaction(SIGILL,&act,NULL);sigaction(SIGABRT,&act,NULL);sigaction(SIGIOT,&act,NULL);sigaction(SIGBUS,&act,NULL);sigaction(SIGFPE,&act,NULL);sigaction(SIGTERM,&act,NULL);}int BindSocket(void){DaemonSocket = socket(AF_INET,SOCK_STREAM,0);if (DaemonSocket==-1) return 0;DaemonAddr.sin_family = AF_INET;DaemonAddr.sin_port = htons(DAEMON_PORT);if (bind(DaemonSocket,&DaemonAddr,sizeof(DaemonAddr))<0) {printf("Can not bind! ");return 0;}if (listen(DaemonSocket,1024)!=0) {printf("Can not listen! ");return 0;}return 1;}int incoming_call(void){fd_set sock;struct timeval tv;int t;FD_ZERO(&sock);FD_SET(DaemonSocket,&sock);tv.tv_sec = 60; tv.tv_usec = 0;t = select(DaemonSocket + 1,&sock,NULL,NULL,&tv);if (t<=0||!FD_ISSET(DaemonSocket,&sock)) return 0;dlog("incoming... ");return 1;}int ConnectClient(void){int socksize=sizeof(HostAddr);unsigned char * addr;ClientSocket = accept(DaemonSocket,(struct sockaddr*)&HostAddr,&socksize);if (ClientSocket<0) return 0;addr = (unsigned char *)&HostAddr.sin_addr.s_addr;dlog("incoming address:%d.%d.%d.%d ",addr[0],addr[1],addr[2],addr[3]);return 1;}int daemon_printf(char *fmt,...){char BUF[4096];va_list argptr;va_start(argptr,fmt);vsprintf(BUF,fmt,argptr);va_end(argptr);return write(ClientSocket,BUF,strlen(BUF));}void Log(void){char BUF[4096];read(DaemonSocket,BUF,16);daemon_printf("%s",BUF[0]);}int main(int argc,char **argv){pid_t myself;time_t now;/* find myself */myself = CheckLock();if (myself!=0) {printf("Existing a copy of chess daemon[pid=%d], leave now. ",myself);exit(1);}/* fork */myself = fork();if (myself>0) {exit(1);} elseif (myself<0) {printf("Strange world! I don't like it. Quit because of pid=%d ",myself);exit(1);} else {SetupSignal();if (!BindSocket()) {printf("Can not bind socket! ");exit(1);}WriteLock();}printf("Chess Daemon is up, have fun! ");now = time(NULL);dlog("---------------------------------------------- ");dlog("I am back! %s""Chess Daemon comes to alive again. ",asctime((const struct tm*)localtime(&now)));do {if (incoming_call()) {if (ConnectClient()) {fd_set sock;struct timeval tv;int t;char BUF[128];char CC[2];int n;daemon_printf("Welcome to Chinese Chess Game Center! ");FD_ZERO(&sock);FD_SET(ClientSocket,&sock);n = 0;do {tv.tv_sec = 60; tv.tv_usec = 0;t = select(ClientSocket+1,&sock,NULL,NULL,&tv);if (t<=0||!FD_ISSET(ClientSocket,&sock)) ;read(ClientSocket,CC,1);if (CC[0]==13||CC[0]==10||CC[0]==0) {BUF[n] = 0;dlog("%s ",BUF);if (strncasecmp(BUF,"exit",4)==0) {close(ClientSocket);break;}n = 0;} else {BUF[n]=CC[0]; n++;}} while (1);}}} while (1);return 1;}檢驗telnet localhost 9901在處理Connect Client時,事實上可以運用fork或thread來處理多個連線。Linux程式設計- 5.inetd (2001-05-24 18:08:00)inetd 提供被動式的伺服器服務,也就是伺服器是被使用端所啟動,平時則無須存在。例如,ftp, telnetd, pop3,imap, auth等等,這些服務沒有人使用時,無須啟動。此外,inetd將socket轉換成stdin/stdout,因而使得網路服務程式設計大大簡化,您可以只用printf及fgets便可完成處理很複雜的網路協定。--------------------------------------------------------------------------------inetd programming利用inetd來做網路程式設計是個既簡單又穩定的設計方法,您不需要考慮到複雜的socket programming。您的設計工作幾乎在設計好通訊協定後就完成了,所需要的技巧,僅為簡單的文字分析技巧。goodie inet service首先,我們先來撰寫一個稱為goodie的服務程式。goodie.c#include#include#includevoid main(void){printf("Welcome to goodie service! ");}這個程式很簡單,不是嗎?編譯gcc -o goodie goodie.c設定/etc/services及/etc/inetd.conf在/etc/services中加入以下這一行goodie 20001/tcp其意義為goodie這項服務是在port 20001、TCP協定。接下來在/etc/inetd.conf中加入以下這一行goodie stream tcp nowait root /full_goodie_path_name/goodie各項引數的意義為service_name需要為在services中存在的名稱。sock_type有很多種,大多用的是stream/dgram。proto一般用tcp/udp。flags有wait/nowait。user是您指定該程式要以那一個使用者來啟動,這個例子中用的是root,如果有安全性的考量,應該要改用nobody。一般來說,建議您用低許可權的使用者,除非必要,不開放root使用權。server_path及args,這是您的服務程式的位置及您所想加入的引數。接下來重新啟動inetdkillall inetdinetd這樣我們便建立起一個port 20001的goodie service。現在我們來檢驗一下goodie是否可以執行:telnet localhost 20001或telnet your_host_name 20001執行結果Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.Welcome to goodie service!Connection closed by foreign host.很簡單不是嗎? 信不信由您,telnet/pop3/imap/ftp都是靠這種方式建立起來的服務。我們現在來建立一點小小的"網路協定",這個協定使我們可以輸入"exit"時,離開程式,而其他的指令都是輸出與輸入相同的字串。#include#include#includevoid main(void){char buf[1024];int ok;printf("Welcome to goodie service! ");fflush(stdout);ok=0;do {while (fgets(buf,1023,stdin)==NULL);if (strncasecmp(buf,"exit",4)==0) ok=1;printf(buf);fflush(stdout);} while (!ok);}執行結果telnet localhost 20001或telnet your_host_name 20001Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.Welcome to goodie service!輸入"help"helphelp輸入"exit"exitexitConnection closed by foreign host.接下來,我們將設計一個稍微複雜一點點的通訊協定,比較通用於一般用途。#include#include#includechar *cmds[]={"help","say","hello","bye","exit",NULL};int getcmd(char *cmd){int n=0;while (cmds[n]!=NULL) {if (strncasecmp(cmd,cmds[n],strlen(cmds[n]))==0) return n;n++;}return -1;}void main(void){char buf[1024];int ok;printf("Welcome to goodie service! ");fflush(stdout);ok=0;do {while (fgets(buf,1023,stdin)==NULL);switch (getcmd(buf)) {case -1: printf("Unknown command! "); break;case 0: printf("How may I help you, sir? "); break;case 1: printf("I will say %s",&buf[3]); break;case 2: printf("How're you doing today? "); break;case 3: printf("Si ya, mate! "); ok=1; break;case 4: printf("Go ahead! "); ok=1; break;}fflush(stdout);} while (!ok);}telnet localhost 20001或telnet your_host_name 20001試試看輸入"help"、"say"、"hello"、"bye"、"exit"等等指令,及其它一些不在命令列中的指令。在設計inetd服務程式時,要特別注意buffer overflow的問題,也就是以下這種狀況:char buffer_overflow[64];fscanf(stdin,"%s",buffer_overflow);歷來幾乎所有的安全漏洞都是由此而來的。你一定不可這樣用,不論任何理由,類同的用法也不可以。Cracker可以透過將您的buffer塞爆,然後塞進他自己的程式進來執行。Linux程式設計- 6.syslog (2001-05-24 19:00:00)在Linux下有個syslogd的Daemon程式,syslog是個系統管理員必看的檔案。因此,如果您的程式有除錯或安全訊息要顯示,寫到syslog是個很好的選擇。syslog有三個函式,使用上,一般您只需要用syslog(...)這個函式即可,一般使用狀況下,openlog/closelog是可有可無的。syslog()中的priority是facility及level的組合,其後引數的用法與printf無異。例:#include#include#include#includevoid main(void){if (fork()==0) {for (;;) {syslog(LOG_USER|LOG_INFO,"syslog programming test ");sleep(10);}}}檢驗:tail -f /var/log/messagesMar 22 01:42:51 foxman log: syslog programming testMar 22 01:43:31 foxman last message repeated 4 timesMar 22 01:44:31 foxman last message repeated 6 timesMar 22 01:45:31 foxman last message repeated 6 timesMar 22 01:46:21 foxman last message repeated 5 times--------------------------------------------------------------------------------void openlog( char *ident, int option, int facility)void syslog( int priority, char *format, ...)void closelog( void )option用於openlog()的option引數可以是以下幾個的組合:LOG_CONS : 如果送到system logger時發生問題,直接寫入系統console。LOG_NDELAY : 立即開啟連線(通常,連線是在第一次寫入訊息時才開啟的)。LOG_PERROR : 將訊息也同時送到stderrLOG_PID : 將PID含入所有訊息中facilityfacility引數用來指定何種程式在記錄訊息,這可讓設定檔來設定何種訊息如何處理。LOG_AUTH : 安全/授權訊息(別用這個,請改用LOG_AUTHPRIV)LOG_AUTHPRIV : 安全/授權訊息LOG_CRON : 時間守護神專用(cron及at)LOG_DAEMON : 其它系統守護神LOG_KERN : 核心訊息LOG_LOCAL0到LOG_LOCAL7 : 保留LOG_LPR : line printer次系統LOG_MAIL : mail次系統LOG_NEWS : USENET news次系統LOG_SYSLOG : syslogd內部所產生的訊息LOG_USER(default) : 一般使用者等級訊息LOG_UUCP : UUCP次系統level決定訊息的重要性. 以下的等級重要性逐次遞減:LOG_EMERG : 系統無法使用LOG_ALERT : 必須要立即採取反應行動LOG_CRIT : 重要狀況發生LOG_ERR : 錯誤狀況發生LOG_WARNING : 警告狀況發生LOG_NOTICE : 一般狀況,但也是重要狀況LOG_INFO : 資訊訊息LOG_DEBUG : 除錯訊息Linux程式設計- 7.zlib的運用 (2001-05-24 20:10:00)gzip (*.gz)檔案格式幾乎是Linux下的標準格式了,有人認為bzip2的壓縮率比gzip來得高。一般來說,這個說法大致正確,不過根據我個人的經驗,有一半以上的檔案,bzip2沒有比gzip的壓縮率來得高,有少數狀況下,gzip壓縮率反而比bzip2來的高。zlib是個支援gzip檔案格式的函式庫,它使得gz檔的存取就猶如開檔關檔一樣地容易,您可以很容易地為您的程式加入gz檔的支援。--------------------------------------------------------------------------------使用例 : showgz.c#include#include#includevoid main(int argc,char **argv){gzFile zip;int c;if (argc<2) return;zip = gzopen(argv[1],"rb");while ((c=gzgetc(zip))!=EOF) putchar(c);gzclose(zip);}編譯gcc -o showgz showgz.c -lz檢驗gzip -9 < showgz.c > showgz.c.gz./showgz showgz.c.gz將會把這個程式內容顯示出來,showgz的作用可說等於gzip -dc。--------------------------------------------------------------------------------函式宣告gzFile gzopen (const char *path, const char *mode);開啟一個gzip(*.gz)檔。mode引數可為"rb"或"wb"。另外也可包含壓縮程度如"wb9"。用'f'作為過濾資料,如"wb6f"。用'h'可指定Huffman only壓縮,如"wb1h"gzopen亦可用於讀取非壓縮的gzip檔案格式,在這種狀況下,gzread會直接讀取,而不進行解壓縮。int gzread (gzFile file, voidp buf, unsigned len);與read的用法相同。int gzwrite (gzFile file, const voidp buf, unsigned len);與write用法相同。int gzprintf (gzFile file, const char *format, ...);與fprintf用法相同。char * gzgets (gzFile file, char *buf, int len);與fgets用法相同。int gzputc (gzFile file, int c);與fputc用法相同。int gzgetc (gzFile file);與fgetc用法相同。int gzflush (gzFile file, int flush);與fflush作用相同。z_off_t gzseek (gzFile file, z_off_t offset, int whence);whence不支援SEEK_END如果檔案是開啟為"讀取",則SEEK_SET及SEEK_CUR,向前及向後均支援,不過很慢就是了。如果檔案是開啟為"寫入",僅支援向前SEEK。int gzrewind (gzFile file);與gzseek(file, 0L, SEEK_SET)相同作用,僅在讀取時有效。z_off_t gztell (gzFile file);返回值 : 目前檔案位置(解壓縮後的位置)int gzeof (gzFile file);返回值 : 1 - EOF, 0 - not EOFint gzclose (gzFile file);關閉檔案返回值 : zlib error numberLinux程式設計- 8.crypt (2001-05-24 21:04:00)crypt是個密碼加密函式,它是基於Data Encryption Standard(DES)演演算法。crypt基本上是One way encryption,因此它只適用於密碼的使用,不適合於資料加密。char *crypt(const char *key, const char *salt);key 是使用者的密碼。salt是兩個字,每個字可從[a-zA-Z0-9./]中選出來,因此同一密碼增加了4096種可能性。透過使用key中每個字的低七位元,取得56-bit關鍵字,這56-bit關鍵字被用來加密成一組字,這組字有13個可顯示的ASCII字,包含開頭兩個salt。crypt在您有自行管理使用者的場合時使用,例如會員網站、BBS等等。--------------------------------------------------------------------------------例一 : crypt_word.c#include#include#includevoid main(int argc,char **argv){if (argc!=3) exit(0);printf("%s ",crypt(argv[1],argv[2]));}編譯gcc -o crypt_word crypt.c -lcrypt檢驗請先看您的/etc/passwd,找一個您自己的帳號,看前面兩個字,那是您自己的salt。接下來輸入:./crypt_word your_password salt看看它們是否相同(應該要相同,除非您加了crypt plugin或使用不同的crypt function,例如shadow、pam,這種狀況下,加密字是不同的),另外檢驗看看他們是否為13個字。您也可以利用Apache上所附的htpasswd來產生加密字做為驗。例二: verify_passwd.c注意,這個例讀取/etc/passwd的資料,不適用於使用shadow或已經使用pam的系統(例如slackware,RedHat及Debian在不外加crypt plugin的狀況下,應當相同)。此例僅供參考,做為解crypt函式運作的情形,真正撰寫程式時,應該避免類似的寫法。#include#include#includetypedef struct {char username[64];char passwd[16];int uid;int gid;char name[256];char root[256];char shell[256];} account;/* 注意! 以下的寫法,真實世界的軟體開發狀況下,千萬不要用! */int acc_info(char *info,account *user){char * start = info;char * now = info;/* username */while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */if (!*now) return 0;*now = 0; now++;strcpy(user->username,start); /* 這會導致buffer overflow */start = now;/* passwd */while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */if (!*now) return 0;*now = 0; now++;strcpy(user->passwd,start); /* 這會導致buffer overflow */start = now;/* uid */while (*now&&*now!=':') now++;if (!*now) return 0;*now = 0; now++;user->uid = atoi(start);start = now;/* gid */while (*now&&*now!=':') now++;if (!*now) return 0;*now = 0; now++;user->gid = atoi(start);start = now;/* name */while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */if (!*now) return 0;*now = 0; now++;strcpy(user->name,start); /* 這會導致buffer overflow */start = now;/* root */while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */if (!*now) return 0;*now = 0; now++;strcpy(user->root,start); /* 這會導致buffer overflow */start = now;/* shell */while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */*now = 0; now++;strcpy(user->shell,start); /* 這會導致buffer overflow */start = now;return 1;}int read_password(char *filename,account *users){FILE *fp;char buf[1024];int n;n = 0;fp = fopen(filename,"rt");while (fgets(buf,1024,fp)!=NULL) {if (acc_info(buf,&users[n])) n++;}fclose(fp);return n;}void main(int argc,char **argv){int n,i,done;account ACC[128];char username[256];char password[256];char * passwd;char salt[4];if (argc<2) {printf("username:");scanf("%s",username); /* 這是超級大安全漏洞 */} else strcpy(username,argv[1]); /* 這是超級大安全漏洞 */if (argc<3) {printf("password:");scanf("%s",password); /* 這是超級大安全漏洞 */} else strcpy(password,argv[2]); /* 這是超級大安全漏洞 */n = read_password("/etc/passwd",ACC);for (i=0,done=0;i if (strcmp(username,ACC[i].username)==0) {salt[0] = ACC[i].passwd[0];salt[1] = ACC[i].passwd[1];salt[2] = 0;passwd = crypt(password,salt);printf("%s %s %s ",ACC[i].username,ACC[i].passwd,passwd);if (strcmp(passwd,ACC[i].passwd)==0) {printf("login successfully! ");} else {printf("incorrect password! ");}done = 1;}if (!done) printf("invalid username! ");}編譯gcc -o verify_passwd verify_passwd.c -lcrypt檢驗./verify_passwd your_username your_password避免安全漏洞buffer overflow是個很嚴重的安全漏洞,通常您不可使用像char buf[xxxx]的宣告。在這一類與安全有相關的任何程式寫作中(不是隻有密碼,例如像www/ftp/telnet的這一類對外視窗都要算在內),您應該要先檢查字串長度。例如以下例子:len = strlen(incoming_username);if (len>xxx) invalid;new_string = (char*)malloc(len+1);strcpy(new_string,incoming_username);your_own_operations...如此才能避免buffer overflow,萬萬不可濫做假設,切記切記,連許多數十年經驗豐富的老手都會犯這個錯誤。--------------------------------------------------------------------------------與crypt函式相關者尚有以下三個:void setkey (const char *key);void encrypt (char *block, int edflag);void swab (const char *from, char *to, ssize_t n);一般來說,除非您有特殊需求,你不會用到這三個。Linux程式設計- 9.PAM (2001-05-24 22:08:00)Linux-PAM stands for Pluggable Authentication Modules for Linux。PAM是個可外掛式的認模組。其詳細檔案一般在/usr/doc/pam-XX中,您也可以在metalab.unc.edu/LDP或Red Hat站中找到PAM-HOWTO。我不準備介紹PAM的使用,在此我將精力放在如何運用PAM的函式庫上。您在進一步看下去之前,應當先閱讀有關PAM的相關資料,並且先解其運作機制,對它先有個初步解,然後再回來繼續。Linux程式設計- 10.termios/keymap/terminal programming (2001-05-25 07:00:00)termiosint tcgetattr (int fd, struct termios *termios_p);int tcsetattr (int fd, int optional_actions,const struct termios *termios_p);--------------------------------------------------------------------------------keymap我寫了一個小程式來專門處理Linux上的keymap,keymap.h及keymap.c。在Linux Terminal上,如果您想要設定某些按鍵返回特定值,您會需要用到以下這些技巧。設定keymap#include#includevoid setkeymap(void){struct kbentry KEYMAP;KEYMAP.kb_table=STATE;KEYMAP.kb_index=SCANCODE;KEYMAP.kb_value=VALUE;ioctl(console,KDSKBENT,&KEYMAP);}STATE為狀態鍵組合/usr/include/linux/keyboard.h中#define KG_SHIFT 0#define KG_CTRL 2#define KG_ALT 3#define KG_ALTGR 1#define KG_SHIFTL 4#define KG_SHIFTR 5#define KG_CTRLL 6#define KG_CTRLR 7#define KG_CAPSSHIFT 8使用方式如:#define KST_CTRL (1#define KST_ALT_SHIFT (KST_ALT|KST_SHIFT)SCANCODE為鍵盤掃描碼#define SCAN_ESC 0x01#define SCAN_1 0x02#define SCAN_2 0x03#define SCAN_3 0x04#define SCAN_4 0x05#define SCAN_5 0x06#define SCAN_6 0x07#define SCAN_7 0x08#define SCAN_8 0x09#define SCAN_9 0x0A#define SCAN_0 0x0B#define SCAN_MINUS 0x0C#define SCAN_PLUS 0x0D#define SCAN_BACK 0x0E#define SCAN_TAB 0x0F#define SCAN_Q 0x10#define SCAN_W 0x11#define SCAN_E 0x12#define SCAN_R 0x13#define SCAN_T 0x14#define SCAN_Y 0x15#define SCAN_U 0x16#define SCAN_I 0x17#define SCAN_O 0x18#define SCAN_P 0x19#define SCAN_LTQUOTE 0x1A#define SCAN_RTQUOTE 0x1B#define SCAN_ENTER 0x1C#define SCAN_CTRL 0x1D#define SCAN_A 0x1E#define SCAN_S 0x1F#define SCAN_D 0x20#define SCAN_F 0x21#define SCAN_G 0x22#define SCAN_H 0x23#define SCAN_J 0x24#define SCAN_K 0x25#define SCAN_L 0x26#define SCAN_SPLIT 0x27#define SCAN_QUOTE 0x28#define SCAN_MARK 0x29#define SCAN_LSHIFT 0x2A#define SCAN_STAND 0x2B#define SCAN_Z 0x2C#define SCAN_X 0x2D#define SCAN_C 0x2E#define SCAN_V 0x2F#define SCAN_B 0x30#define SCAN_N 0x31#define SCAN_M 0x32#define SCAN_LSQUOTE 0x33#define SCAN_RSQUOTE 0x34#define SCAN_QUESTION 0x35#define SCAN_RSHIFT 0x36#define SCAN_PRTSCR 0x37#define SCAN_ALT 0x38#define SCAN_SPACE 0x39#define SCAN_CAPSLOCK 0x3A#define SCAN_F1 0x3B#define SCAN_F2 0x3C#define SCAN_F3 0x3D#define SCAN_F4 0x3E#define SCAN_F5 0x3F#define SCAN_F6 0x40#define SCAN_F7 0x41#define SCAN_F8 0x42#define SCAN_F9 0x43#define SCAN_F10 0x44#define SCAN_NUMLOCK 0x45#define SCAN_HOME 0x47#define SCAN_UP 0x48#define SCAN_PGUP 0x49#define SCAN_LEFT 0x4B#define SCAN_RIGHT 0x4D#define SCAN_END 0x4F#define SCAN_DOWN 0x50#define SCAN_PGDN 0x51#define SCAN_INSERT 0x52#define SCAN_DELETE 0x53#define SCAN_F11 0x85#define SCAN_F12 0x86/usr/include/linux/kd.h中struct kbentry {unsigned char kb_table;unsigned char kb_index;unsigned short kb_value;};#define KDGKBENT 0x4B46 /* gets one entry in translation table */#define KDSKBENT 0x4B47 /* sets one entry in translation table */而console為console = open("/dev/console",O_RDWR);讀取按鍵read(console,&c,sizeof(char));--------------------------------------------------------------------------------terminal programmingterm.h/term.c是我寫來專門處理一些小型的互動介面程式。Terminal指令集設定顏色 :
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-944730/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- linux程式設計之一:入門Linux程式設計
- C++程式設計思想筆記之一 (轉)C++程式設計筆記
- Linux 程式設計之Shell程式設計(轉)Linux程式設計
- Windows SDK程式設計之一 視窗示例程式 (轉)Windows程式設計
- Linux核心程式設計(阻塞程式)(轉)Linux程式設計
- linux驅動程式設計(轉)Linux程式設計
- Linux程式設計之三(轉)Linux程式設計
- Linux程式設計之四(轉)Linux程式設計
- Linux防火牆程式設計(轉)Linux防火牆程式設計
- Linux Unicode 程式設計(轉)LinuxUnicode程式設計
- Linux核心模組程式設計--阻塞程式(轉)Linux程式設計
- 程式設計師的出路之一程式設計師
- linux 之一些有用的程式(轉)Linux
- Linux核心模組程式設計指南(轉)Linux程式設計
- Linux程式設計入門-crypt(轉)Linux程式設計
- Linux的shell程式設計(一)(轉)Linux程式設計
- Linux的shell程式設計(二)(轉)Linux程式設計
- Linux的shell程式設計(三)(轉)Linux程式設計
- Linux的shell程式設計(四)(轉)Linux程式設計
- Linux下的OpenGL程式設計(轉)Linux程式設計
- Linux核心程式設計(結論)(轉)Linux程式設計
- Linux程式設計入門-gpm(轉)Linux程式設計
- Linux程式設計入門 - crypt(轉)Linux程式設計
- 玩轉Linux – shell 程式設計基礎Linux程式設計
- Linux下面程式設計工具介紹(轉)Linux程式設計
- Linux下C語言程式設計(轉)LinuxC語言程式設計
- Linux核心模組程式設計指南(一)(轉)Linux程式設計
- Linux核心模組的程式設計方法(轉)Linux程式設計
- Linux程式設計入門 - Dialog(轉)Linux程式設計
- Linux程式設計入門 - SVGALIB(轉)Linux程式設計SVG
- Linux_FAQ:程式設計問題(轉)Linux程式設計
- Linux系統共享庫程式設計(轉)Linux程式設計
- linux系統程式設計影片教程(轉)Linux程式設計
- Linux守護程式的程式設計實現(轉)Linux程式設計
- C#程式設計利器之一:類(Class)C#程式設計
- Linux核心模組程式設計--中斷處理程式(轉)Linux程式設計
- Linux網路程式設計入門 (轉載)Linux程式設計
- Linux核心模組程式設計--替代printk系列(轉)Linux程式設計