您現在的位置是:首頁 > 足球

FTP伺服器的實現(C語言)

  • 由 略略略GaGa 發表于 足球
  • 2021-10-15
簡介}*** MKD command* TODO: full path directory creation *void ftp_mkd(Command *cmd, State *state){if(state->logg

ftp是因特網中的什麼

我們在之前的文章中,曾經對FTP檔案傳輸協議做過詳細的介紹。本章,我們對如何用C語言實現FTP伺服器做一個簡單的介紹。

FTP伺服器的實現(C語言)

概述

FTP檔案傳輸協議

,是因特網上使用得最廣泛的檔案傳輸協議。FTP提供互動式的訪問,允許客戶指明檔案的格式與型別,並允許檔案具有儲存許可權。FTP遮蔽了不同作業系統之前的細節,因此適合在異構網路中任意計算機之間傳送檔案。

FTP的基本工作原理

FTP使用

C/S

方式,一個FTP伺服器可以為多個客戶程序提供服務,FTP伺服器程序由兩大部分組成:一個主程序,負責接收新的請求;另外有若干個從屬程序,負責處理單個請求。

主程序的工作步驟如下:

開啟埠號(一般為21),使客戶端能透過此埠號訪問;

等待客戶端發出連線請求;

啟動從屬程序來處理客戶程序發來的請求。從屬程序對客戶程序的請求處理完後即終止,從屬程序在執行期間可能會根據需要另外建立其他一些程序。

回到等待狀態,繼續等待其他客戶程序發來的連線請求。主程序和從屬程序是併發進行的。

在進行檔案傳輸時,FTP的客戶和伺服器之間要建立兩個並行的TCP連線:“

控制連線

”和“

資料連線

”。

控制連線

在整個會話期間一直保持開啟,FTP客戶所發出的傳送請求,透過控制連線傳送給伺服器端的控制程序,但是控制連線並不會用於傳輸資料。

實際傳輸檔案的是“資料連線”

。伺服器端的控制程序在接收到FTP客戶傳送來的檔案傳輸請求後,就會建立

“資料傳送程序”

“資料連線”

,用來連線客戶端和伺服器端的資料傳送程序。由於FTP使用了一個分離的控制連線,因此FTP的控制資訊是帶外控制的。

當客戶程序向伺服器程序發出建立連線請求時,透過伺服器埠號21請求連線,同時會告訴伺服器程序自己用於建立資料傳送連線的另一個埠號。伺服器一般使用埠號20同客戶程序建立資料連線,由於FTP使用兩個不同的埠號,所以資料連線和控制連線不會發生混亂。

FTP伺服器的實現(C語言)

綜上所述,我們可以畫出基本的演算法流程圖

FTP伺服器的實現(C語言)

程式碼實現:

首先是基本的定義

/* Commands enumeration */typedef enum cmdlist { ABOR, CWD, DELE, LIST, MDTM, MKD, NLST, PASS, PASV, PORT, PWD, QUIT, RETR, RMD, RNFR, RNTO, SITE, SIZE, STOR, TYPE, USER, NOOP} cmdlist;/* String mappings for cmdlist */static const char *cmdlist_str[] = { “ABOR”, “CWD”, “DELE”, “LIST”, “MDTM”, “MKD”, “NLST”, “PASS”, “PASV”, “PORT”, “PWD”, “QUIT”, “RETR”, “RMD”, “RNFR”, “RNTO”, “SITE”, “SIZE”, “STOR”, “TYPE”, “USER”, “NOOP” };

控制埠的定義

/* define FTP control port */#define CONTROLPORT 21

主函式

/** * Sets up server and handles incoming connections * @param port Server port */int main(){ int sock = create_socket(CONTROLPORT ); struct sockaddr_in client_address; int len = sizeof(client_address); int connection, pid, bytes_read; while(1){ connection = accept(sock, (struct sockaddr*) &client_address,&len); char buffer[BSIZE]; Command *cmd = malloc(sizeof(Command)); State *state = malloc(sizeof(State)); pid = fork(); memset(buffer,0,BSIZE); if(pid<0){ fprintf(stderr, “Cannot create child process。”); exit(EXIT_FAILURE); } if(pid==0){ close(sock); char welcome[BSIZE] = “220 ”; if(strlen(welcome_message)BSIZE)){ /* TODO: output this to log */ buffer[BSIZE-1] = ‘\0’; printf(“User %s sent command: %s\n”,(state->username==0)?“unknown”:state->username,buffer); parse_command(buffer,cmd); state->connection = connection; /* Ignore non-ascii char。 Ignores telnet command */ if(buffer[0]<=127 || buffer[0]>=0){ response(cmd,state); } memset(buffer,0,BSIZE); memset(cmd,0,sizeof(cmd)); } else{ /* Read error */ perror(“server:read”); } } printf(“Client disconnected。\n”); exit(0); }else{ printf(“closing。。。 :(\n”); close(connection); } }}

功能的實現

/** * Handle USER command * @param cmd Command with args * @param state Current client connection state */void ftp_user(Command *cmd, State *state){ const int total_usernames = sizeof(usernames)/sizeof(char *); if(lookup(cmd->arg,usernames,total_usernames)>=0){ state->username = malloc(32); memset(state->username,0,32); strcpy(state->username,cmd->arg); state->username_ok = 1; state->message = “331 User name okay, need password\n”; }else{ state->message = “530 Invalid username\n”; } write_state(state);}/** PASS command */void ftp_pass(Command *cmd, State *state){ if(state->username_ok==1){ state->logged_in = 1; state->message = “230 Login successful\n”; }else{ state->message = “500 Invalid username or password\n”; } write_state(state);}/** PASV command */void ftp_pasv(Command *cmd, State *state){ if(state->logged_in){ int ip[4]; char buff[255]; char *response = “227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\n”; Port *port = malloc(sizeof(Port)); gen_port(port); getip(state->connection,ip); /* Close previous passive socket? */ close(state->sock_pasv); /* Start listening here, but don‘t accept the connection */ state->sock_pasv = create_socket((256*port->p1)+port->p2); printf(“port: %d\n”,256*port->p1+port->p2); sprintf(buff,response,ip[0],ip[1],ip[2],ip[3],port->p1,port->p2); state->message = buff; state->mode = SERVER; puts(state->message); }else{ state->message = “530 Please login with USER and PASS。\n”; printf(“%s”,state->message); } write_state(state);}/** LIST command */void ftp_list(Command *cmd, State *state){ if(state->logged_in==1){ struct dirent *entry; struct stat statbuf; struct tm *time; char timebuff[80], current_dir[BSIZE]; int connection; time_t rawtime; /* TODO: dynamic buffering maybe? */ char cwd[BSIZE], cwd_orig[BSIZE]; memset(cwd,0,BSIZE); memset(cwd_orig,0,BSIZE); /* Later we want to go to the original path */ getcwd(cwd_orig,BSIZE); /* Just chdir to specified path */ if(strlen(cmd->arg)>0&&cmd->arg[0]!=’-‘){ chdir(cmd->arg); } getcwd(cwd,BSIZE); DIR *dp = opendir(cwd); if(!dp){ state->message = “550 Failed to open directory。\n”; }else{ if(state->mode == SERVER){ connection = accept_connection(state->sock_pasv); state->message = “150 Here comes the directory listing。\n”; puts(state->message); while(entry=readdir(dp)){ if(stat(entry->d_name,&statbuf)==-1){ fprintf(stderr, “FTP: Error reading file stats。。。\n”); }else{ char *perms = malloc(9); memset(perms,0,9); /* Convert time_t to tm struct */ rawtime = statbuf。st_mtime; time = localtime(&rawtime); strftime(timebuff,80,“%b %d %H:%M”,time); str_perm((statbuf。st_mode & ALLPERMS), perms); dprintf(connection, “%c%s %5d %4d %4d %8d %s %s\r\n”, (entry->d_type==DT_DIR)?’d‘:’-‘, perms,statbuf。st_nlink, statbuf。st_uid, statbuf。st_gid, statbuf。st_size, timebuff, entry->d_name); } } write_state(state); state->message = “226 Directory send OK。\n”; state->mode = NORMAL; close(connection); close(state->sock_pasv); }else if(state->mode == CLIENT){ state->message = “502 Command not implemented。\n”; }else{ state->message = “425 Use PASV or PORT first。\n”; } } closedir(dp); chdir(cwd_orig); }else{ state->message = “530 Please login with USER and PASS。\n”; } state->mode = NORMAL; write_state(state);}/** QUIT command */void ftp_quit(State *state){ state->message = “221 Goodbye, friend。 I never thought I’d die like this。\n”; write_state(state); close(state->connection); exit(0);}/** PWD command */void ftp_pwd(Command *cmd, State *state){ if(state->logged_in){ char cwd[BSIZE]; char result[BSIZE]; memset(result, 0, BSIZE); if(getcwd(cwd,BSIZE)!=NULL){ strcat(result,“257 \”“); strcat(result,cwd); strcat(result,”\“\n”); state->message = result; }else{ state->message = “550 Failed to get pwd。\n”; } write_state(state); }}/** CWD command */void ftp_cwd(Command *cmd, State *state){ if(state->logged_in){ if(chdir(cmd->arg)==0){ state->message = “250 Directory successfully changed。\n”; }else{ state->message = “550 Failed to change directory。\n”; } }else{ state->message = “500 Login with USER and PASS。\n”; } write_state(state);}/** * MKD command * TODO: full path directory creation */void ftp_mkd(Command *cmd, State *state){ if(state->logged_in){ char cwd[BSIZE]; char res[BSIZE]; memset(cwd,0,BSIZE); memset(res,0,BSIZE); getcwd(cwd,BSIZE); /* TODO: check if directory already exists with chdir? */ /* Absolute path */ if(cmd->arg[0]==‘/’){ if(mkdir(cmd->arg,S_IRWXU)==0){ strcat(res,“257 \”“); strcat(res,cmd->arg); strcat(res,”\“ new directory created。\n”); state->message = res; }else{ state->message = “550 Failed to create directory。 Check path or permissions。\n”; } } /* Relative path */ else{ if(mkdir(cmd->arg,S_IRWXU)==0){ sprintf(res,“257 \”%s/%s\“ new directory created。\n”,cwd,cmd->arg); state->message = res; }else{ state->message = “550 Failed to create directory。\n”; } } }else{ state->message = “500 Good news, everyone! There‘s a report on TV with some very bad news!\n”; } write_state(state);}/** RETR command */void ftp_retr(Command *cmd, State *state){ if(fork()==0){ int connection; int fd; struct stat stat_buf; off_t offset = 0; int sent_total = 0; if(state->logged_in){ /* Passive mode */ if(state->mode == SERVER){ if(access(cmd->arg,R_OK)==0 && (fd = open(cmd->arg,O_RDONLY))){ fstat(fd,&stat_buf); state->message = “150 Opening BINARY mode data connection。\n”; write_state(state); connection = accept_connection(state->sock_pasv); close(state->sock_pasv); if(sent_total = sendfile(connection, fd, &offset, stat_buf。st_size)){ if(sent_total != stat_buf。st_size){ perror(“ftp_retr:sendfile”); exit(EXIT_SUCCESS); } state->message = “226 File send OK。\n”; }else{ state->message = “550 Failed to read file。\n”; } }else{ state->message = “550 Failed to get file\n”; } }else{ state->message = “550 Please use PASV instead of PORT。\n”; } }else{ state->message = “530 Please login with USER and PASS。\n”; } close(fd); close(connection); write_state(state); exit(EXIT_SUCCESS); } state->mode = NORMAL; close(state->sock_pasv);}/** Handle STOR command。 TODO: check permissions。 */void ftp_stor(Command *cmd, State *state){ if(fork()==0){ int connection, fd; off_t offset = 0; int pipefd[2]; int res = 1; const int buff_size = 8192; FILE *fp = fopen(cmd->arg,“w”); if(fp==NULL){ /* TODO: write status message here! */ perror(“ftp_stor:fopen”); }else if(state->logged_in){ if(!(state->mode==SERVER)){ state->message = “550 Please use PASV instead of PORT。\n”; } /* Passive mode */ else{ fd = fileno(fp); connection = accept_connection(state->sock_pasv); close(state->sock_pasv); if(pipe(pipefd)==-1)perror(“ftp_stor: pipe”); state->message = “125 Data connection already open; transfer starting。\n”; write_state(state); /* Using splice function for file receiving。 * The splice() system call first appeared in Linux 2。6。17。 */ while ((res = splice(connection, 0, pipefd[1], NULL, buff_size, SPLICE_F_MORE | SPLICE_F_MOVE))>0){ splice(pipefd[0], NULL, fd, 0, buff_size, SPLICE_F_MORE | SPLICE_F_MOVE); } /* TODO: signal with ABOR command to exit */ /* Internal error */ if(res==-1){ perror(“ftp_stor: splice”); exit(EXIT_SUCCESS); }else{ state->message = “226 File send OK。\n”; } close(connection); close(fd); } }else{ state->message = “530 Please login with USER and PASS。\n”; } close(connection); write_state(state); exit(EXIT_SUCCESS); } state->mode = NORMAL; close(state->sock_pasv);}/** ABOR command */void ftp_abor(State *state){ if(state->logged_in){ state->message = “226 Closing data connection。\n”; state->message = “225 Data connection open; no transfer in progress。\n”; }else{ state->message = “530 Please login with USER and PASS。\n”; } write_state(state);}/** * Handle TYPE command。 * BINARY only at the moment。 */void ftp_type(Command *cmd,State *state){ if(state->logged_in){ if(cmd->arg[0]==’I‘){ state->message = “200 Switching to Binary mode。\n”; }else if(cmd->arg[0]==’A‘){ /* Type A must be always accepted according to RFC */ state->message = “200 Switching to ASCII mode。\n”; }else{ state->message = “504 Command not implemented for that parameter。\n”; } }else{ state->message = “530 Please login with USER and PASS。\n”; } write_state(state);}/** Handle DELE command */void ftp_dele(Command *cmd,State *state){ if(state->logged_in){ if(unlink(cmd->arg)==-1){ state->message = “550 File unavailable。\n”; }else{ state->message = “250 Requested file action okay, completed。\n”; } }else{ state->message = “530 Please login with USER and PASS。\n”; } write_state(state);}/** Handle RMD */void ftp_rmd(Command *cmd, State *state){ if(!state->logged_in){ state->message = “530 Please login first。\n”; }else{ if(rmdir(cmd->arg)==0){ state->message = “250 Requested file action okay, completed。\n”; }else{ state->message = “550 Cannot delete directory。\n”; } } write_state(state);}/** Handle SIZE (RFC 3659) */void ftp_size(Command *cmd, State *state){ if(state->logged_in){ struct stat statbuf; char filesize[128]; memset(filesize,0,128); /* Success */ if(stat(cmd->arg,&statbuf)==0){ sprintf(filesize, “213 %d\n”, statbuf。st_size); state->message = filesize; }else{ state->message = “550 Could not get file size。\n”; } }else{ state->message = “530 Please login with USER and PASS。\n”; } write_state(state);}

Top