├── .github └── workflows │ └── cmake.yml ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── bin └── tinyftpd ├── bulid └── build.sh ├── src ├── common.h ├── commsocket.c ├── commsocket.h ├── ftpcodes.h ├── ftpproto.c ├── ftpproto.h ├── hash.c ├── hash.h ├── main.c ├── makefile ├── makefile.bak ├── parseconf.c ├── parseconf.h ├── privparent.c ├── privparent.h ├── privsock.c ├── privsock.h ├── sckutil.c ├── sckutil.h ├── session.c ├── session.h ├── str.c ├── str.h ├── tunable.c └── tunable.h └── tinyftpd.conf /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Configure CMake 24 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 25 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 26 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 27 | 28 | - name: Build 29 | # Build your program with the given configuration 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | 32 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | 3 | project(ftp) 4 | 5 | set(TARGET tinyftp) 6 | set(CMAKE_C_COMPILER "gcc") 7 | 8 | aux_source_directory(./src DIRSRCS) 9 | 10 | find_library(LCRYPT NAMES crypt) 11 | 12 | add_executable(${TARGET} ${DIRSRCS}) 13 | target_link_libraries(${TARGET} ${LCRYPT}) 14 | 15 | install(TARGETS ${TARGET} DESTINATION ./bin) 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbirds/Tinyftp/3759f7c4a79199d9ba969c198919ef9777b069a7/LICENSE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Tinyftpd 2 | ---------- 3 | [![CMake](https://github.com/vbirds/Tinyftp/actions/workflows/cmake.yml/badge.svg)](https://github.com/vbirds/Tinyftp/actions/workflows/cmake.yml) 4 | 5 | ### 概述 6 | Tinyftpd是用c语言实现的简单、快速、高效的Linux FTP服务器,只需简单的配置,就可快速的将主机变成高效的FTP服务器。 7 | 8 | ### 模块简介 9 | TinyFTP分为 字符串工具模块、参数配置模块、socket模块、内部进程间通讯模块、系统调用工具模块。 10 | 11 | 1. 字符串工具模块:字符串模块主要用来处理开发过程中,各种对字符串的处理。模块在`string.h` 与 `string.c` 12 | 2. 参数配置模块:参数配置模块提供参数配置的功能。具体在`parseconf.h` 和 `parseconf.c` 13 | 3. socket模块:用于socket通讯建立与数据传输。可见 `commonsock.h` `commonsock.c` 14 | 4. 内部进程间通讯模块:用于子进程 与 父进程间的通讯 与数据传输。`privsock.h` 和 `privsock.c` 15 | 5. 系统调用工具模块:主要是一些用到的系统调用的函数封装。可见`sckutil.h` `sckutil.c` 16 | 17 | ### 安装 18 | #### 编译 19 | ```bash?linenums=NULL 20 | cd build/ 21 | chmod +x bulid.sh 22 | sudo ./build.sh 23 | ``` 24 | 运行 25 | ```bash?linenums=NULL 26 | cd /bin 27 | sudo ./tinyftpd 28 | ``` 29 | 30 | ### 配置 31 | 配置文件在当前目录的`tinyftpd.conf` 32 | 33 | | 配置参数 | 说明 | 34 | | ------------------------------| ---------------| 35 | |tunable_pasv_enable |是否开启被动模式| 36 | |tunable_port_enable |是否开启主动模式| 37 | |tunable_max_clients |最大连接数 | 38 | |tunable_max_per_ip |每IP最大连接数 | 39 | |tunable_listen_port |FTP服务器端口 | 40 | |tunable_accept_timeout |accept超时间 | 41 | |tunable_connect_timeout |connect超时间 | 42 | |tunable_idle_session_timeout |控制时间连接超时| 43 | |tunable_data_connection_timeout|数据连接时间超时| 44 | |tunable_local_umask |掩码 | 45 | |tunable_upload_max_rate |最大上传速度(byte/s)| 46 | |tunable_download_max_rate |最大下载速度(byte/s)| 47 | |tunable_listen_address |FTP服务器IP地址 | 48 | 49 | ### LICENSE 50 | 软件遵循MIT开源协议 51 | ### 致谢 52 | 在开发过程中参考了vsftpd的源码,从中学到了许多知识,特此声明,表示感谢 53 | -------------------------------------------------------------------------------- /bin/tinyftpd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbirds/Tinyftp/3759f7c4a79199d9ba969c198919ef9777b069a7/bin/tinyftpd -------------------------------------------------------------------------------- /bulid/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SRCDIR="../src/" 4 | BINDIR="../bin/" 5 | 6 | cd $SRCDIR && 7 | make -f makefile && 8 | mv ./*.o $BINDIR && mv ./tinyftpd $BINDIR 9 | 10 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef _COMMON_H_ 2 | #define _COMMON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | 32 | #define MINIFTP_CONF "miniftpd.conf" 33 | 34 | 35 | 36 | #endif /*_COMMON_H_*/ -------------------------------------------------------------------------------- /src/commsocket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "commsocket.h" 14 | #include "sckutil.h" 15 | 16 | typedef struct _SckHandle 17 | { 18 | //int sockArray[100]; //定义socket池数组 19 | int arrayNum; //数组大小 20 | int sockfd; //socket句柄 21 | int contime; //链接超时时间 22 | int sendtime; //发送超时时间 23 | int revtime; //接受超时时间 24 | } SckHandle; 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | //函数声明 33 | //客户端环境初始化 34 | /* 35 | * handle 在函数内部分配内存,socket结构体 36 | * contime 链接超时时间 37 | * sendtime 发送超时时间 38 | * revtime 接受超时时间 39 | * nConNum 链接池的数目 40 | * */ 41 | /* 42 | * 函数名:sckCliet_init 43 | * 描述:客服端接受数据 44 | * 参数: 45 | * 46 | * 返回: 47 | * */ 48 | int sckCliet_init(void **handle, int contime, int sendtime, int revtime, 49 | int nConNum) 50 | { 51 | int ret = 0; 52 | //判断传入的参数 53 | if (handle == NULL || contime < 0 || sendtime < 0 || revtime < 0) 54 | { 55 | ret = Sck_ErrParam; //赋值预先定义的错误。 56 | printf( 57 | "func sckCliet_init() err: %d, check (handle == NULL ||contime<0 || sendtime<0 || revtime<0)\n", 58 | ret); 59 | return ret; 60 | } 61 | //定义结构体 62 | SckHandle *tmp = (SckHandle *) malloc(sizeof(SckHandle)); 63 | if (tmp == NULL) 64 | { 65 | ret = Sck_ErrMalloc; 66 | printf("func sckCliet_init() err: malloc %d\n", ret); 67 | return ret; 68 | } 69 | 70 | tmp->contime = contime; 71 | tmp->sendtime = sendtime; 72 | tmp->revtime = revtime; 73 | tmp->arrayNum = nConNum; 74 | tmp->sockfd = -1; 75 | *handle = tmp; 76 | return ret; 77 | } 78 | 79 | 80 | 81 | 82 | 83 | /* 84 | * 函数名:sckCliet_getconn 85 | * 描述:客服端接受数据 86 | * 参数: 87 | * 88 | * 返回: 89 | * */ 90 | int sckCliet_getconn(void *handle, char *ip, int port, int *connfd) 91 | { 92 | 93 | int ret = 0; 94 | SckHandle *tmp = NULL; 95 | if (handle == NULL || ip == NULL || connfd == NULL || port < 0 96 | || port > 65537) 97 | { 98 | ret = Sck_ErrParam; 99 | printf( 100 | "func sckCliet_getconn() err: %d, check (handle == NULL || ip==NULL || connfd==NULL || port<0 || port>65537) \n", 101 | ret); 102 | return ret; 103 | } 104 | 105 | // 106 | int sockfd; 107 | sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 108 | if (sockfd < 0) 109 | { 110 | ret = errno; 111 | printf("func socket() err: %d\n", ret); 112 | return ret; 113 | } 114 | 115 | struct sockaddr_in servaddr; 116 | memset(&servaddr, 0, sizeof(servaddr)); 117 | servaddr.sin_family = AF_INET; 118 | servaddr.sin_port = htons(port); 119 | servaddr.sin_addr.s_addr = inet_addr(ip); 120 | 121 | tmp = (SckHandle*) handle; 122 | 123 | /* 124 | ret = connect(sockfd, (struct sockaddr*) (&servaddr), sizeof(servaddr)); 125 | if (ret < 0) 126 | { 127 | ret = errno; 128 | printf("func connect() err: %d\n", ret); 129 | return ret; 130 | } 131 | */ 132 | 133 | ret = connect_timeout(sockfd, (struct sockaddr_in*) (&servaddr), 134 | (unsigned int) tmp->contime); 135 | if (ret < 0) 136 | { 137 | if (ret == -1 && errno == ETIMEDOUT) 138 | { 139 | ret = Sck_ErrTimeOut; 140 | return ret; 141 | } else 142 | { 143 | printf("func connect_timeout() err: %d\n", ret); 144 | } 145 | } 146 | 147 | *connfd = sockfd; 148 | 149 | return ret; 150 | 151 | } 152 | 153 | 154 | 155 | //客户端发送报文 156 | /* 157 | * 函数名:sckClient_send 158 | * 描述:客服端接受数据 159 | * 参数: 160 | * 161 | * 返回: 162 | * */ 163 | int sckClient_send(void *handle, int connfd, unsigned char *data, int datalen) 164 | { 165 | int ret = 0; 166 | 167 | SckHandle *tmp = NULL; 168 | tmp = (SckHandle *) handle; 169 | ret = write_timeout(connfd, tmp->sendtime); 170 | if (ret == 0) 171 | { 172 | int writed = 0; 173 | unsigned char *netdata = (unsigned char *)malloc(sizeof(unsigned char)*(datalen +4)); 174 | if (netdata == NULL) 175 | { 176 | ret = Sck_ErrMalloc; 177 | printf("func sckClient_send() mlloc Err:%d\n ", ret); 178 | return ret; 179 | } 180 | 181 | int netlen = htonl(datalen); 182 | memcpy(netdata, &netlen, 4); 183 | memcpy(netdata + 4, data, datalen); 184 | 185 | writed = writen(connfd, netdata, datalen + 4); 186 | if (writed < (datalen + 4)) 187 | { 188 | if (netdata != NULL) 189 | { 190 | free(netdata); 191 | netdata = NULL; 192 | } 193 | 194 | return writed; 195 | } 196 | 197 | } 198 | 199 | if (ret < 0) 200 | { 201 | //失败返回-1,超时返回-1并且errno = ETIMEDOUT 202 | if (ret == -1 && errno == ETIMEDOUT) 203 | { 204 | ret = Sck_ErrTimeOut; 205 | printf("func sckClient_send() mlloc Err:%d\n ", ret); 206 | return ret; 207 | } 208 | return ret; 209 | } 210 | 211 | return ret; 212 | } 213 | 214 | 215 | 216 | //客户端端接受报文 217 | /* 218 | * 函数名:sckClient_rev 219 | * 描述:客服端接受数据 220 | * 参数: 221 | * 222 | * 返回: 223 | * */ 224 | int sckClient_rev(void *handle, int connfd, unsigned char *out, int *outlen) 225 | { 226 | 227 | int ret = 0; 228 | SckHandle *tmpHandle = (SckHandle *) handle; 229 | 230 | if (handle == NULL || out == NULL) 231 | { 232 | ret = Sck_ErrParam; 233 | printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut); 234 | return ret; 235 | } 236 | 237 | ret = read_timeout(connfd, tmpHandle->revtime); //bugs modify bombing 238 | if (ret != 0) 239 | { 240 | if (ret == -1 || errno == ETIMEDOUT) 241 | { 242 | ret = Sck_ErrTimeOut; 243 | printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut); 244 | return ret; 245 | } else 246 | { 247 | printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut); 248 | return ret; 249 | } 250 | } 251 | 252 | int netdatalen = 0; 253 | ret = readn(connfd, &netdatalen, 4); //读包头 4个字节 254 | if (ret == -1) 255 | { 256 | printf("func readn() err:%d \n", ret); 257 | return ret; 258 | } else if (ret < 4) 259 | { 260 | ret = Sck_ErrPeerClosed; 261 | printf("func readn() err peer closed:%d \n", ret); 262 | return ret; 263 | } 264 | 265 | int n; 266 | n = ntohl(netdatalen); 267 | ret = readn(connfd, out, n); //根据长度读数据 268 | if (ret == -1) 269 | { 270 | printf("func readn() err:%d \n", ret); 271 | return ret; 272 | } else if (ret < n) 273 | { 274 | ret = Sck_ErrPeerClosed; 275 | printf("func readn() err peer closed:%d \n", ret); 276 | return ret; 277 | } 278 | 279 | *outlen = n; 280 | 281 | return 0; 282 | } 283 | 284 | // 客户端环境释放 285 | int sckClient_destroy(void *handle) 286 | { 287 | if (handle != NULL) 288 | { 289 | SckHandle *tmp = (SckHandle*)handle; 290 | if (tmp->sockfd != -1) 291 | { 292 | close(tmp->sockfd); 293 | tmp = NULL; 294 | } 295 | free(handle); 296 | } 297 | return 0; 298 | } 299 | 300 | int sckCliet_closeconn(int *connfd) 301 | { 302 | if (*connfd >= 0) 303 | { 304 | close(*connfd); 305 | } 306 | return 0; 307 | } 308 | 309 | ///////////////////////////////////////////////////////////////////////////////////// 310 | //函数声明 311 | //服务器端初始化 312 | /* 313 | * 函数名:sckServer_init 314 | * 描述:服务器端的socket初始化 315 | * 参数:address ip地址 316 | port 绑定的端口 317 | * listenfd 监听的socket文件 318 | * 返回:如果成功返回0 ,失败返回<0 或者 成功发送的数据的字节大小。 319 | * */ 320 | int sckServer_init(const char *address, int port, int *listenfd) 321 | { 322 | int ret = 0; 323 | int mylistenfd; 324 | struct sockaddr_in servaddr; 325 | memset(&servaddr, 0, sizeof(servaddr)); 326 | servaddr.sin_family = AF_INET; 327 | servaddr.sin_port = htons(port); 328 | 329 | if (address == NULL) 330 | { 331 | servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 332 | } 333 | inet_pton(AF_INET, address, &servaddr.sin_addr); 334 | //servaddr.sin_addr.s_addr = htonl(address); 335 | 336 | //返回一个新的socket描述符 337 | mylistenfd = socket(PF_INET, SOCK_STREAM, 0); 338 | if (mylistenfd < 0) 339 | { 340 | ret = errno; 341 | printf("func socket() err:%d \n", ret); 342 | return ret; 343 | } 344 | 345 | int on = 1; 346 | ret = setsockopt(mylistenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 347 | if (ret < 0) 348 | { 349 | ret = errno; 350 | printf("func setsockopt() err:%d \n", ret); 351 | return ret; 352 | } 353 | 354 | ret = bind(mylistenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)); 355 | if (ret < 0) 356 | { 357 | ret = errno; 358 | printf("func bind() err:%d \n", ret); 359 | return ret; 360 | } 361 | 362 | ret = listen(mylistenfd, SOMAXCONN); 363 | if (ret < 0) 364 | { 365 | ret = errno; 366 | printf("func listen() err:%d \n", ret); 367 | return ret; 368 | } 369 | 370 | *listenfd = mylistenfd; 371 | 372 | return 0; 373 | } 374 | 375 | 376 | /* 377 | * 函数名:sckServer_accept 378 | * 描述:服务器端等待数据 379 | * 参数: listenfd 监听的sock 380 | * timeout 定义的超时时间 381 | * 返回:如果成功返回0 ,失败返回<0 或者 成功发送的数据的字节大小。 382 | * */ 383 | 384 | int sckServer_accept(int listenfd, int *connfd, void *addr, int timeout) 385 | { 386 | int ret = 0; 387 | struct sockaddr_in *p_addr = NULL; 388 | if (addr != NULL) 389 | { 390 | p_addr = (struct sockaddr_in*)addr; 391 | } 392 | 393 | // 394 | ret = accept_timeout(listenfd, p_addr, (unsigned int) timeout); 395 | if (ret < 0) 396 | { 397 | if (ret == -1 && errno == ETIMEDOUT) 398 | { 399 | ret = Sck_ErrTimeOut; 400 | printf("func accept_timeout() timeout err:%d \n", ret); 401 | return ret; 402 | } else 403 | { 404 | ret = errno; 405 | printf("func accept_timeout() err:%d \n", ret); 406 | return ret; 407 | } 408 | } 409 | 410 | *connfd = ret; 411 | return 0; 412 | } 413 | //服务器端发送报文 414 | /* 415 | * 函数名:sckServer_send 416 | * 描述:发送报文,并进行了粘包处理。 417 | * 参数: connfd 链接的socket描述符 418 | * data 发送的数据 ,传入数据,在内部重新打包封装。 419 | * datalen 要发送的数据的长度 420 | * timeout 定义的超时时间 421 | * 返回:如果成功返回0 ,失败返回<0 或者 成功发送的数据的字节大小。 422 | * */ 423 | int sckServer_send(int connfd, unsigned char *data, int datalen, int timeout) 424 | { 425 | int ret = 0; 426 | //写时超时检测 427 | ret = write_timeout(connfd, timeout); 428 | if (ret == 0) 429 | { 430 | int writed = 0; 431 | //分配内存空间 432 | unsigned char *netdata = (unsigned char *)malloc(sizeof(unsigned char)*(datalen +4)); 433 | if (netdata == NULL) 434 | { 435 | ret = Sck_ErrMalloc; 436 | printf("func sckServer_send() mlloc Err:%d\n ", ret); 437 | return ret; 438 | } 439 | //将本地数据转换为网络数据 ;小端===》大端 440 | int netlen = htonl(datalen); 441 | //将数据的长度加到数据包的头4字节处 442 | memcpy(netdata, &netlen, 4); 443 | //将数据打包到新的数据包中。 444 | memcpy(netdata + 4, data, datalen); 445 | //发送数据 446 | //writed为成功发送的数据的字节长度。 447 | writed = writen(connfd, netdata, datalen + 4); 448 | //直到数据分包 封装 发送完成之后,返回 449 | if (writed < (datalen + 4)) 450 | { 451 | //释放内存 452 | if (netdata != NULL) 453 | { 454 | free(netdata); 455 | netdata = NULL; 456 | } 457 | return writed; 458 | } 459 | 460 | } 461 | //检测超时 462 | if (ret < 0) 463 | { 464 | //失败返回-1,超时返回-1并且errno = ETIMEDOUT 465 | //链接超时 466 | if (ret == -1 && errno == ETIMEDOUT) 467 | { 468 | ret = Sck_ErrTimeOut; 469 | printf("func sckServer_send() mlloc Err:%d\n ", ret); 470 | return ret; 471 | } 472 | return ret; 473 | } 474 | 475 | return ret; 476 | } 477 | //服务器端端接受报文 478 | /* 479 | * 函数名:sckServer_rev 480 | * 描述:接受报文,并进行了粘包处理。 481 | * 参数: connfd 链接的socket描述符 482 | * out 读取的内容,在外部分配内存 483 | * outlen 读取到内容的长度。 484 | * timeout 定义的超时时间 485 | * 返回:如果成功返回0 ,失败返回<0 或者错误码。 486 | * */ 487 | int sckServer_rev(int connfd, unsigned char *out, int *outlen, int timeout) 488 | { 489 | 490 | int ret = 0; 491 | //检测传入的参数是否是有效的参数。 492 | if (out == NULL || outlen == NULL) 493 | { 494 | ret = Sck_ErrParam; 495 | printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut); 496 | return ret; 497 | } 498 | //检测是否可读,防止阻塞假死,一个链接的等待时间是1.5倍的RTT 一个RTT 75秒 499 | ret = read_timeout(connfd, timeout); //bugs modify bombing 500 | if (ret != 0) 501 | { 502 | if (ret == -1 || errno == ETIMEDOUT) 503 | { 504 | ret = Sck_ErrTimeOut; 505 | printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut); 506 | return ret; 507 | } else 508 | { 509 | printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut); 510 | return ret; 511 | } 512 | } 513 | /* 514 | * 防止粘包 515 | * */ 516 | 517 | //定义收取的数据的长度,以用来获取收取数据的长度,初始化为0; 518 | //通过调用readn返回数据的长度 519 | int netdatalen = 0; 520 | ret = readn(connfd, &netdatalen, 4); //读包头 4个字节 521 | if (ret == -1) 522 | { 523 | printf("func readn() err:%d \n", ret); 524 | return ret; 525 | } else if (ret < 4) 526 | { 527 | ret = Sck_ErrPeerClosed; 528 | printf("func readn() err peer closed:%d \n", ret); 529 | return ret; 530 | } 531 | int n; 532 | //将网络数据转换为本地数据,大端===>小端 533 | n = ntohl(netdatalen); 534 | ret = readn(connfd, out, n); //根据长度读数据 535 | if (ret == -1) 536 | { 537 | printf("func readn() err:%d \n", ret); 538 | return ret; 539 | } else if (ret < n) 540 | { 541 | ret = Sck_ErrPeerClosed; 542 | printf("func readn() err peer closed:%d \n", ret); 543 | return ret; 544 | } 545 | //抛出需要读取的字节长度。 546 | *outlen = n; 547 | return 0; 548 | } 549 | 550 | //服务器端环境释放 551 | int sckServer_destroy(void *handle) 552 | { 553 | if(handle!=NULL) 554 | { 555 | free(handle); 556 | handle=NULL;//没有起作用。 557 | } 558 | return 0; 559 | } 560 | -------------------------------------------------------------------------------- /src/commsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef _SCK_CLINT_H_ 2 | #define _SCK_CLINT_H_ 3 | 4 | 5 | #ifdef __cplusplus 6 | extern 'C' 7 | { 8 | #endif 9 | 10 | 11 | 12 | //错误码定义 13 | 14 | #define Sck_Ok 0 15 | #define Sck_BaseErr 3000 16 | 17 | #define Sck_ErrParam (Sck_BaseErr+1) 18 | #define Sck_ErrTimeOut (Sck_BaseErr+2) 19 | #define Sck_ErrPeerClosed (Sck_BaseErr+3) 20 | #define Sck_ErrMalloc (Sck_BaseErr+4) 21 | 22 | //客户端Socket接口 23 | 24 | //客户端环境初始化 25 | int sckCliet_init(void **handle, int contime, int sendtime, int revtime, int nConNum); 26 | int sckCliet_getconn(void *handle, char *ip, int port, int *connfd); 27 | //int sckCliet_putconn(int *connfd); 28 | int sckCliet_closeconn(int *connfd); 29 | //客户端发送报文 30 | int sckClient_send(void *handle, int connfd, unsigned char *data, int datalen); 31 | //客户端端接受报文 32 | int sckClient_rev(void *handle, int connfd, unsigned char *out, int *outlen); //1 33 | // 客户端环境释放 34 | int sckClient_destroy(void *handle); 35 | 36 | 37 | //服务器端初始化 38 | int sckServer_init(const char *address, int port, int *listenfd); 39 | int sckServer_accept(int listenfd, int *connfd, void *addr, int timeout); 40 | //服务器端发送报文 41 | int sckServer_send(int connfd, unsigned char *data, int datalen, int timeout); 42 | //服务器端端接受报文 43 | int sckServer_rev(int connfd, unsigned char *out, int *outlen, int timeout); //1 44 | //服务器端环境释放 45 | int sckServer_destroy(void *handle); 46 | 47 | #ifdef __cpluspluse 48 | } 49 | #endif 50 | 51 | 52 | #endif -------------------------------------------------------------------------------- /src/ftpcodes.h: -------------------------------------------------------------------------------- 1 | #ifndef _FTP_CODES_H_ 2 | #define _FTP_CODES_H_ 3 | 4 | 5 | #define FTP_DATACONN 150 6 | 7 | #define FTP_NOOPOK 200 8 | #define FTP_TYPEOK 200 9 | #define FTP_PORTOK 200 10 | #define FTP_EPRTOK 200 11 | #define FTP_UMASKOK 200 12 | #define FTP_CHMODOK 200 13 | #define FTP_EPSVALLOK 200 14 | #define FTP_STRUOK 200 15 | #define FTP_MODEOK 200 16 | #define FTP_PBSZOK 200 17 | #define FTP_PROTOK 200 18 | #define FTP_OPTSOK 200 19 | #define FTP_ALLOOK 202 20 | #define FTP_FEAT 211 21 | #define FTP_STATOK 211 22 | #define FTP_SIZEOK 213 23 | #define FTP_MDTMOK 213 24 | #define FTP_STATFILE_OK 213 25 | #define FTP_SITEHELP 214 26 | #define FTP_HELP 214 27 | #define FTP_SYSTOK 215 28 | #define FTP_GREET 220 29 | #define FTP_GOODBYE 221 30 | #define FTP_ABOR_NOCONN 225 31 | #define FTP_TRANSFEROK 226 32 | #define FTP_ABOROK 226 33 | #define FTP_PASVOK 227 34 | #define FTP_EPSVOK 229 35 | #define FTP_LOGINOK 230 36 | #define FTP_AUTHOK 234 37 | #define FTP_CWDOK 250 38 | #define FTP_RMDIROK 250 39 | #define FTP_DELEOK 250 40 | #define FTP_RENAMEOK 250 41 | #define FTP_PWDOK 257 42 | #define FTP_MKDIROK 257 43 | 44 | #define FTP_GIVEPWORD 331 45 | #define FTP_RESTOK 350 46 | #define FTP_RNFROK 350 47 | 48 | #define FTP_IDLE_TIMEOUT 421 49 | #define FTP_DATA_TIMEOUT 421 50 | #define FTP_TOO_MANY_USERS 421 51 | #define FTP_IP_LIMIT 421 52 | #define FTP_IP_DENY 421 53 | #define FTP_TLS_FAIL 421 54 | #define FTP_BADSENDCONN 425 55 | #define FTP_BADSENDNET 426 56 | #define FTP_BADSENDFILE 451 57 | 58 | #define FTP_BADCMD 500 59 | #define FTP_BADOPTS 501 60 | #define FTP_COMMANDNOTIMPL 502 61 | #define FTP_NEEDUSER 503 62 | #define FTP_NEEDRNFR 503 63 | #define FTP_BADPBSZ 503 64 | #define FTP_BADPROT 503 65 | #define FTP_BADSTRU 504 66 | #define FTP_BADMODE 504 67 | #define FTP_BADAUTH 504 68 | #define FTP_NOSUCHPROT 504 69 | #define FTP_NEEDENCRYPT 522 70 | #define FTP_EPSUBAD 522 71 | #define FTP_DATATLSBAD 522 72 | #define FTP_LOGINERR 530 73 | #define FTP_NOHANDLEPROT 536 74 | #define FTP_FILEFAIL 550 75 | #define FTP_NOPERM 550 76 | #define FTP_UPLOADFAIL 553 77 | 78 | 79 | #endif /*_FTP_CODES_H_*/ -------------------------------------------------------------------------------- /src/ftpproto.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "common.h" 8 | #include "ftpproto.h" 9 | #include "sckutil.h" 10 | #include "commsocket.h" 11 | #include "str.h" 12 | #include "ftpcodes.h" 13 | #include "tunable.h" 14 | #include "privsock.h" 15 | 16 | void handle_alarm_timeout(int sig); 17 | void handle_sigurg(int sig); 18 | void handle_sigalrm(int sig); 19 | void start_cmdio_alarm(void); 20 | void start_data_alarm(void); 21 | 22 | void check_abor(session_t *sess); 23 | 24 | void ftp_relply(session_t *sess, int status, const char *text); 25 | void ftp_lrelply(session_t *sess, int status, const char *text); 26 | void ftp_frelply(session_t *sess, const char *text); /*feature relply*/ 27 | 28 | int list_common(session_t *sess, int detail); /*列出目录*/ 29 | void upload_common(session_t *sess, int is_apped); 30 | void limit_rate(session_t *sess, int bytes, int is_upload); 31 | void file_stat(const char *path, char *buf, struct stat *sbuf);/*文件的信息 权限位 uid gid size等等*/ 32 | 33 | int get_transfer_fd(session_t *sess); 34 | int port_active(session_t *sess); 35 | int pasv_active(session_t *sess); 36 | 37 | int get_port_fd(session_t *sess); 38 | int get_pasv_fd(session_t *sess); 39 | 40 | static void do_user(session_t *sess); 41 | static void do_pass(session_t *sess); 42 | static void do_cwd(session_t *sess); 43 | static void do_cdup(session_t *sess); 44 | static void do_quit(session_t *sess); 45 | static void do_port(session_t *sess); 46 | static void do_pasv(session_t *sess); 47 | static void do_type(session_t *sess); 48 | static void do_stru(session_t *sess); 49 | static void do_mode(session_t *sess); 50 | static void do_retr(session_t *sess); 51 | static void do_stor(session_t *sess); 52 | static void do_appe(session_t *sess); 53 | static void do_list(session_t *sess); 54 | static void do_nlst(session_t *sess); 55 | static void do_rest(session_t *sess); 56 | static void do_abor(session_t *sess); 57 | static void do_pwd(session_t *sess); 58 | static void do_mkd(session_t *sess); 59 | static void do_rmd(session_t *sess); 60 | static void do_dele(session_t *sess); 61 | static void do_rnfr(session_t *sess); 62 | static void do_rnto(session_t *sess); 63 | static void do_site(session_t *sess); 64 | static void do_syst(session_t *sess); 65 | static void do_feat(session_t *sess); 66 | static void do_size(session_t *sess); 67 | static void do_stat(session_t *sess); 68 | static void do_noop(session_t *sess); 69 | static void do_help(session_t *sess); 70 | 71 | typedef struct ftpcmd 72 | { 73 | const char *cmd; 74 | void (*cmd_handler)(session_t *sess); 75 | } ftpcmd_t; 76 | 77 | /*命令映射*/ 78 | static ftpcmd_t ctrl_cmds[] = { 79 | /*访问控制命令*/ 80 | {"USER", do_user }, 81 | {"PASS", do_pass }, 82 | {"CWD", do_cwd }, 83 | {"XCWD", do_cwd }, 84 | {"CDUP", do_cdup }, 85 | {"XCUP", do_cdup }, 86 | {"QUIT", do_quit }, 87 | {"ACCT", NULL }, 88 | {"SMNT", NULL }, 89 | {"REIN", NULL }, 90 | /*传输参数命令*/ 91 | {"PORT", do_port }, 92 | {"PASV", do_pasv }, 93 | {"TYPE", do_type }, 94 | {"STRU", do_stru }, 95 | {"MODE", do_mode }, 96 | /*服务命令*/ 97 | {"RETR", do_retr }, 98 | {"STOR", do_stor }, 99 | {"APPE", do_appe }, 100 | {"LIST", do_list }, 101 | {"NLST", do_nlst }, 102 | {"REST", do_rest }, 103 | {"ABOR", do_abor }, 104 | {"\377\364\377\362ABOR", do_abor }, 105 | {"PWD", do_pwd }, 106 | {"XPWD", do_pwd }, 107 | {"MKD", do_mkd }, 108 | {"RMD", do_rmd }, 109 | {"XRMD", do_rmd }, 110 | {"DELE", do_dele }, 111 | {"RNFR", do_rnfr }, 112 | {"RNTO", do_rnto }, 113 | {"SITE", do_site }, 114 | {"SYST", do_syst }, 115 | {"FEAT", do_feat }, 116 | {"SIZE", do_size }, 117 | {"STAT", do_stat }, 118 | {"NOOP", do_noop }, 119 | {"HELP", do_help }, 120 | {"STOU", NULL }, 121 | {"ALLO", NULL } 122 | }; 123 | 124 | session_t *p_sess; 125 | void handle_alarm_timeout(int sig) 126 | { 127 | /*关闭读端*/ 128 | shutdown(p_sess->ctrl_fd, SHUT_RD); 129 | ftp_relply(p_sess, FTP_IDLE_TIMEOUT, "Timeout.");//421 130 | /*关闭写端*/ 131 | shutdown(p_sess->ctrl_fd, SHUT_WR); 132 | exit(EXIT_FAILURE); 133 | } 134 | 135 | void handle_sigalrm(int sig) 136 | { 137 | if (p_sess->data_process == 0) 138 | { 139 | ftp_relply(p_sess, FTP_DATA_TIMEOUT, "Dta timeout. Reconnect. Sorry");//421 140 | exit(EXIT_FAILURE); 141 | } 142 | //否则,当前处于数据传输的状态收到了超时信号 143 | p_sess->data_process = 0; 144 | start_data_alarm(); 145 | } 146 | 147 | void handle_sigurg(int sig) 148 | { 149 | if (p_sess->data_fd == -1) 150 | { 151 | return; 152 | } 153 | 154 | //处于数据传输状态 155 | char cmdline[MAX_COMMAND_LINE] = {0}; 156 | int ret = readline(p_sess->ctrl_fd, cmdline, MAX_COMMAND_LINE); 157 | if (ret <= 0) 158 | { 159 | ERR_EXIT("readline"); 160 | } 161 | str_trim_crlf(cmdline); 162 | /*判断是否ABOR命令*/ 163 | if (strcmp(cmdline, "ABOR") == 0 164 | || strcmp(cmdline,"\377\364\377\362ABOR") == 0) 165 | { 166 | p_sess->abor_received = 1; 167 | //断开数据连接通道 168 | shutdown(p_sess->data_fd, SHUT_RDWR); 169 | } 170 | else //错误命令 171 | { 172 | //500 173 | ftp_relply(p_sess, FTP_BADCMD, "Unknown command"); 174 | } 175 | 176 | } 177 | /*检查ABOR是否接收*/ 178 | void check_abor(session_t *sess) 179 | { 180 | if (sess->abor_received == 1) 181 | { 182 | sess->abor_received = 0; 183 | //226 184 | ftp_relply(sess, FTP_ABOROK, "ABOR successful"); 185 | } 186 | } 187 | 188 | void start_cmdio_alarm(void) 189 | { 190 | if (tunable_idle_session_timeout > 0) 191 | { 192 | //安装信号 193 | signal(SIGALRM, handle_alarm_timeout); 194 | //启动闹钟 195 | alarm(tunable_idle_session_timeout); 196 | } 197 | } 198 | 199 | void start_data_alarm(void) 200 | { 201 | if (tunable_data_connection_timeout > 0) 202 | { 203 | //安装信号 204 | signal(SIGALRM, handle_alarm_timeout); 205 | //启动闹钟 206 | alarm(tunable_data_connection_timeout); 207 | } 208 | else if (tunable_idle_session_timeout > 0) 209 | { 210 | //关闭先前安装的闹钟 211 | alarm(0); 212 | } 213 | } 214 | 215 | void handle_child(session_t *sess) 216 | { 217 | int ret = 0; 218 | ftp_relply(sess, FTP_GREET, "(tinyftpd 1.0)"); 219 | 220 | while (1) 221 | { 222 | memset(sess->cmdline, 0, sizeof(sess->cmdline)); 223 | memset(sess->cmd, 0, sizeof(sess->cmd)); 224 | memset(sess->arg, 0, sizeof(sess->arg)); 225 | 226 | /*启动闹钟*/ 227 | start_cmdio_alarm(); 228 | 229 | ret = readline(sess->ctrl_fd, (void*)(sess->cmdline), MAX_COMMAND_LINE); 230 | if (ret < 0) 231 | { 232 | ERR_EXIT("readline"); 233 | } 234 | else if (ret == 0) 235 | { 236 | exit(EXIT_SUCCESS); 237 | } 238 | 239 | //printf("cmdline=[%s]\n", sess->cmdline); 240 | //去除\r\n 241 | str_trim_crlf(sess->cmdline); 242 | //printf("cmdline=[%s]\n", sess->cmdline); 243 | //解析TFP命令和参数 244 | str_split(sess->cmdline, sess->cmd, sess->arg, ' '); 245 | //printf("cmd=[%s] arg=[%s]\n", sess->cmd, sess->arg); 246 | //将命令转换为大写 247 | str_upper(sess->cmd); 248 | //处理FTP命令 249 | int i = 0; 250 | int size = sizeof(ctrl_cmds) / sizeof(ctrl_cmds[0]); 251 | for (i = 0; i < size; ++i) 252 | { 253 | if (strcmp(ctrl_cmds[i].cmd, sess->cmd) == 0) 254 | { 255 | if (ctrl_cmds[i].cmd_handler != NULL) 256 | { 257 | ctrl_cmds[i].cmd_handler(sess); 258 | } 259 | else 260 | { 261 | /*未实现命令*/ 262 | ftp_relply(sess, FTP_COMMANDNOTIMPL, "Uncompletement command"); 263 | } 264 | break; 265 | } 266 | } /*end for*/ 267 | if (i == size) 268 | { 269 | /*不认识命令*/ 270 | ftp_relply(sess, FTP_BADCMD, "Bad command"); 271 | } 272 | } 273 | } 274 | 275 | void ftp_relply(session_t *sess, int status, const char *text) 276 | { 277 | char buf[1024] = {0}; 278 | sprintf(buf, "%d %s\r\n", status, text); 279 | writen(sess->ctrl_fd, buf, strlen(buf)); 280 | } 281 | 282 | void ftp_lrelply(session_t *sess, int status, const char *text) 283 | { 284 | char buf[1024] = {0}; 285 | sprintf(buf, "%d-%s\r\n", status, text); 286 | writen(sess->ctrl_fd, buf, strlen(buf)); 287 | } 288 | 289 | void ftp_frelply(session_t *sess, const char *text) 290 | { 291 | char buf[1024] = {0}; 292 | sprintf(buf, " %s\r\n", text); 293 | writen(sess->ctrl_fd, buf, strlen(buf)); 294 | } 295 | 296 | int list_common(session_t *sess, int detail) 297 | { 298 | DIR *dir = opendir("."); 299 | if (dir == NULL) 300 | { 301 | return 0; 302 | 303 | } 304 | struct dirent *dt = NULL; 305 | struct stat sbuf; 306 | 307 | 308 | while ((dt = readdir(dir)) != NULL) 309 | { 310 | /*lstat(const char *path, struct stat *buf);*/ 311 | 312 | /*权限获取*/ 313 | if (lstat(dt->d_name, &sbuf) < 0) 314 | { 315 | continue; 316 | } 317 | /*过滤 '.'和'..' 目录 和文件*/ 318 | if (dt->d_name[0] == '.') 319 | { 320 | continue; 321 | } 322 | char buf[1024] = {0}; 323 | if (detail) 324 | { 325 | /*获取权限位信息*/ 326 | const char *perms =statbuf_get_perms(&sbuf); 327 | 328 | int off = 0; 329 | /*权限位*/ 330 | off += sprintf(buf, "%s ", perms); 331 | /*硬连接数 uid gid*/ 332 | off += sprintf(buf + off,"%3d %-8d %-8d ", sbuf.st_nlink, sbuf.st_uid, sbuf.st_gid); 333 | /*文件大小*/ 334 | off += sprintf(buf + off, "%-8lu ", (unsigned long)sbuf.st_size); 335 | 336 | /*时间格式化*/ 337 | const char *datebuf = statbuf_get_date(&sbuf); 338 | 339 | off += sprintf(buf + off, "%s ", datebuf); 340 | 341 | /*格式化添加文件名*/ 342 | 343 | /*判读是否连接文件,如果是连接文件添加指向的文件名*/ 344 | if (S_ISLNK(sbuf.st_mode)) 345 | { 346 | char real_file_buf[64] = {0}; 347 | readlink(dt->d_name, real_file_buf, sizeof(real_file_buf)); 348 | off += sprintf(buf + off, "%s -> %s\r\n", dt->d_name, real_file_buf); 349 | } 350 | else 351 | { 352 | off += sprintf(buf + off, "%s\r\n", dt->d_name); 353 | } 354 | 355 | }/*end if*/ 356 | else 357 | { 358 | sprintf(buf, "%s\r\n", dt->d_name); 359 | } 360 | //printf("%s", buf); 361 | writen(sess->data_fd, buf, strlen(buf)); 362 | 363 | }/*end while*/ 364 | 365 | /*关闭目录*/ 366 | closedir(dir); 367 | 368 | return 0; 369 | } 370 | 371 | /*限速*/ 372 | void limit_rate(session_t *sess, int bytes, int is_upload) 373 | { 374 | sess->data_process = 1; 375 | //睡眠时间 = (当前传输速度/最大速度 -1)* 当前传输时间 376 | long curr_sec = get_time_sec(); 377 | long curr_usec = get_time_usec(); 378 | 379 | double elapsed; 380 | elapsed = (double)(curr_sec - sess->bw_transfer_start_sec); 381 | elapsed += (double)(curr_usec - sess->bw_transfer_start_usec) / (double)1000000; 382 | if (elapsed < (double)0) 383 | { 384 | elapsed = (double)0.01; 385 | } 386 | 387 | 388 | /*计算当前传输速度*/ 389 | unsigned int bw_rate = (unsigned int)((double)bytes / elapsed); 390 | double rate_ratio; 391 | if (is_upload) 392 | { 393 | if (bw_rate < sess->bw_upload_rate_max) 394 | { 395 | //不需要限速 396 | /*重新更新时间*/ 397 | sess->bw_transfer_start_sec = get_time_sec(); 398 | sess->bw_transfer_start_usec = get_time_usec(); 399 | return; 400 | } 401 | rate_ratio = bw_rate / sess->bw_upload_rate_max; 402 | } 403 | else 404 | { 405 | if (bw_rate < sess->bw_download_rate_max) 406 | { 407 | //不需要限速 408 | /*重新更新时间*/ 409 | sess->bw_transfer_start_sec = get_time_sec(); 410 | sess->bw_transfer_start_usec = get_time_usec(); 411 | return; 412 | } 413 | rate_ratio = bw_rate / sess->bw_download_rate_max; 414 | } 415 | double pause_time; 416 | pause_time = (rate_ratio - (double)(1)) * elapsed; 417 | 418 | nano_sleep(pause_time); 419 | 420 | /*重新更新时间*/ 421 | sess->bw_transfer_start_sec = get_time_sec(); 422 | sess->bw_transfer_start_usec = get_time_usec(); 423 | } 424 | 425 | /*上传文件*/ 426 | void upload_common(session_t *sess, int is_apped) 427 | { 428 | // 创建数据连接 429 | if (get_transfer_fd(sess) == 0) 430 | { 431 | return; 432 | } 433 | /*保存断点*/ 434 | long long offset = sess->restart_pos; 435 | sess->restart_pos = 0; 436 | 437 | //打开文件 438 | int fd = open(sess->arg, O_CREAT | O_WRONLY | O_APPEND, 0666); 439 | if (fd == -1) 440 | { 441 | ftp_relply(sess, FTP_UPLOADFAIL, "Could not creat file."); 442 | return; 443 | } 444 | //加锁 445 | int ret = lock_file_write(fd); 446 | if (ret == -1) 447 | { 448 | ftp_relply(sess, FTP_FILEFAIL, "Could not creat file."); 449 | return; 450 | } 451 | 452 | //STOR 453 | //REST + STOR 454 | //APPE 455 | if (!is_apped && offset == 0) //STOR 456 | { 457 | //ftruncate 清零 458 | ftruncate(fd, 0); 459 | //定位到文件头 460 | if (lseek(fd, 0, SEEK_SET) < 0) 461 | { 462 | ftp_relply(sess, FTP_FILEFAIL, "Could not creat file."); 463 | return; 464 | } 465 | } 466 | else if (!is_apped && offset != 0) //REST + STOR 467 | { 468 | if (lseek(fd, offset, SEEK_SET) < 0) 469 | { 470 | ftp_relply(sess, FTP_FILEFAIL, "Could not creat file."); 471 | return; 472 | } 473 | } 474 | else if (is_apped)//APPE模式 475 | { 476 | if (lseek(fd, 0, SEEK_END) < 0) 477 | { 478 | ftp_relply(sess, FTP_FILEFAIL, "Could not creat file."); 479 | return; 480 | } 481 | } 482 | 483 | //获取文件状态 484 | struct stat sbuf; 485 | ret = fstat(fd, &sbuf); 486 | if (!S_ISREG(sbuf.st_mode)) 487 | { 488 | ftp_relply(sess, FTP_FILEFAIL, "Could not creat file."); 489 | return; 490 | } 491 | 492 | //向客户端响应 150 493 | //Opening BINARY mode data connection for /home/jhz/dump.rdb (18 bytes). 494 | 495 | ftp_relply(sess, FTP_DATACONN, "Ok to send data."); 496 | 497 | 498 | //上传文件 499 | int flag = 0; 500 | char buf[65536] = {0}; 501 | /*记录当前时间*/ 502 | sess->bw_transfer_start_sec = get_time_sec(); 503 | sess->bw_transfer_start_usec = get_time_usec(); 504 | while (1) 505 | { 506 | ret = read(sess->data_fd, buf, sizeof(buf)); 507 | if (ret == -1) 508 | { 509 | if (errno == EINTR) 510 | { 511 | continue; 512 | } 513 | else 514 | { 515 | flag = 1; 516 | break; 517 | } 518 | } 519 | else if (ret == 0) //传输完成 520 | { 521 | flag = 0; 522 | break; 523 | } 524 | 525 | //限速 526 | limit_rate(sess, ret, 1); 527 | if (sess->abor_received == 1) 528 | { 529 | //426 530 | flag = 1; 531 | break; 532 | } 533 | 534 | if (writen(fd, buf, ret) != ret) 535 | { 536 | flag = 2; 537 | break; 538 | } 539 | } 540 | 541 | 542 | //关闭数据连接套接字 543 | close(sess->data_fd); 544 | sess->data_fd = -1; 545 | close(fd); 546 | 547 | //传输完毕 发送226 548 | if (flag == 0 && !sess->abor_received) 549 | { 550 | //226 551 | ftp_relply(sess, FTP_TRANSFEROK, "Transfer complete."); 552 | 553 | } 554 | else if (flag == 1) 555 | { 556 | //426 FTP_BADSENDNET 557 | ftp_relply(sess, FTP_BADSENDNET, "Failure reading from local file."); 558 | } 559 | else if (flag == 2) 560 | { 561 | //451 562 | ftp_relply(sess, FTP_BADSENDFILE, "Failure recving to network stream."); 563 | } 564 | 565 | /*检查是否接收ABOR 426*/ 566 | check_abor(sess); 567 | 568 | /*重新开启控制连接闹钟*/ 569 | start_cmdio_alarm(); 570 | } 571 | 572 | void file_stat(const char *path, char *buf, struct stat *sbuf) 573 | { 574 | if (path == NULL || buf == NULL || sbuf == NULL) 575 | { 576 | fprintf(stderr, "file_stat buf == NULL || sbuf == NULL"); 577 | return; 578 | } 579 | 580 | if (lstat(path, sbuf) < 0) 581 | { 582 | return; 583 | } 584 | 585 | /*获取权限位信息*/ 586 | const char *perms =statbuf_get_perms(sbuf); 587 | 588 | int off = 0; 589 | /*权限位*/ 590 | off += sprintf(buf, "%s ", perms); 591 | /*硬连接数 uid gid*/ 592 | off += sprintf(buf + off,"%3d %-8d %-8d ", sbuf->st_nlink, sbuf->st_uid, sbuf->st_gid); 593 | /*文件大小*/ 594 | off += sprintf(buf + off, "%-8lu ", (unsigned long)sbuf->st_size); 595 | 596 | /*时间格式化*/ 597 | const char *datebuf = statbuf_get_date(sbuf); 598 | 599 | off += sprintf(buf + off, "%s ", datebuf); 600 | 601 | /*格式化添加文件名*/ 602 | 603 | /*判读是否连接文件,如果是连接文件添加指向的文件名*/ 604 | if (S_ISLNK(sbuf->st_mode)) 605 | { 606 | char real_file_buf[64] = {0}; 607 | readlink(path, real_file_buf, sizeof(real_file_buf)); 608 | off += sprintf(buf + off, "%s -> %s\r\n", path, real_file_buf); 609 | } 610 | else 611 | { 612 | off += sprintf(buf + off, "%s\r\n", path); 613 | } 614 | } 615 | 616 | static void do_user(session_t *sess) 617 | { 618 | //USER XXX 619 | struct passwd *pw = getpwnam(sess->arg); 620 | if (pw == NULL) 621 | { 622 | //用户不存在 623 | ftp_relply(sess, FTP_LOGINERR, "Login incorrect"); 624 | return; 625 | } 626 | sess->uid = pw->pw_uid; 627 | ftp_relply(sess, FTP_GIVEPWORD, "Please specify the password"); 628 | } 629 | 630 | static void do_pass(session_t *sess) 631 | { 632 | //PASS 123456 633 | struct passwd *pw = getpwuid(sess->uid); 634 | if (pw == NULL) 635 | { 636 | //用户不存在 637 | ftp_relply(sess, FTP_LOGINERR, "Login incorrect"); 638 | return; 639 | } 640 | 641 | struct spwd *sp = getspnam(pw->pw_name); 642 | if (sp == NULL) 643 | { 644 | ftp_relply(sess, FTP_LOGINERR, "Login incorrect"); 645 | return; 646 | } 647 | 648 | // 加密明文 649 | char *encrypted_pass = crypt(sess->arg, sp->sp_pwdp); 650 | //验证密码 651 | if (strcmp(encrypted_pass, sp->sp_pwdp) != 0) 652 | { 653 | ftp_relply(sess, FTP_LOGINERR, "Login incorrect"); 654 | return; 655 | } 656 | /*更改掩码*/ 657 | umask(tunable_local_umask); 658 | 659 | // 登入成功 660 | ftp_relply(sess, FTP_LOGINOK, "Login successful"); 661 | 662 | //接收SIGURG 663 | signal(SIGURG, handle_sigurg); 664 | /*开启接收SIGURG*/ 665 | activate_sigurg(sess->ctrl_fd); 666 | 667 | //将当前进程用户更改为 登入用户 668 | if (setegid(pw->pw_gid) < 0) 669 | { 670 | ERR_EXIT("setegid"); 671 | } 672 | if (seteuid(pw->pw_uid) < 0) 673 | { 674 | ERR_EXIT("seteuid"); 675 | } 676 | //更改用户目录 677 | if ( chdir(pw->pw_dir) < 0 ) 678 | { 679 | ERR_EXIT("chdir"); 680 | } 681 | } 682 | int port_active(session_t *sess) 683 | { 684 | if (sess->port_addr) 685 | { 686 | if (pasv_active(sess)) 687 | { 688 | fprintf(stderr, "both port and pasv are active"); 689 | exit(EXIT_FAILURE); 690 | } 691 | return 1; 692 | } 693 | return 0; 694 | } 695 | 696 | int pasv_active(session_t *sess) 697 | { 698 | /* 699 | if (sess->pasv_listen_fd != -1) 700 | { 701 | if (port_active(sess)) 702 | { 703 | fprintf(stderr, "both port and pasv are active"); 704 | exit(EXIT_FAILURE); 705 | } 706 | return 1; 707 | } 708 | */ 709 | /*向nobody进程请求是否处于被动模式*/ 710 | priv_sock_send_cmd(sess->child_fd, PRIV_SOCK_PASV_ACTIVE); 711 | int active = priv_sock_get_int(sess->child_fd); 712 | if (active) 713 | { 714 | if (port_active(sess)) 715 | { 716 | fprintf(stderr, "both port and pasv are active"); 717 | exit(EXIT_FAILURE); 718 | } 719 | return 1; 720 | } 721 | 722 | return 0; 723 | } 724 | 725 | 726 | int get_port_fd(session_t *sess) 727 | { 728 | priv_sock_send_cmd(sess->child_fd, PRIV_SOCK_GET_DATA_SOCK); 729 | unsigned short port = ntohs(sess->port_addr->sin_port); 730 | char *ip = inet_ntoa(sess->port_addr->sin_addr); 731 | /*发送端口*/ 732 | priv_sock_send_int(sess->child_fd, (int)port); 733 | /*发送ip*/ 734 | priv_sock_send_buf(sess->child_fd, ip, strlen(ip)); 735 | /*接受应答*/ 736 | char res = priv_sock_get_result(sess->child_fd); 737 | if (res == PRIV_SOCK_RESULT_BAD) 738 | { 739 | return 0; 740 | } 741 | else if (res == PRIV_SOCK_RESULT_OK) 742 | { 743 | /*接收文件描述符*/ 744 | sess->data_fd = priv_sock_recv_fd(sess->child_fd); 745 | } 746 | 747 | return 1; 748 | } 749 | 750 | int get_pasv_fd(session_t *sess) 751 | { 752 | priv_sock_send_cmd(sess->child_fd, PRIV_SOCK_PASV_ACCETP); 753 | char res = priv_sock_get_result(sess->child_fd); 754 | if (res == PRIV_SOCK_RESULT_BAD) 755 | { 756 | return 0; 757 | } 758 | else if (res == PRIV_SOCK_RESULT_OK) 759 | { 760 | sess->data_fd = priv_sock_recv_fd(sess->child_fd); 761 | } 762 | 763 | return 1; 764 | } 765 | 766 | int get_transfer_fd(session_t *sess) 767 | { 768 | int ret = 1; 769 | //判断先前是否接收过PORT或PASV 770 | if (!port_active(sess) && !pasv_active(sess)) 771 | { 772 | ftp_relply(sess, FTP_BADSENDCONN, "Use PORT or PASV first"); 773 | return 0; 774 | } 775 | 776 | //创建数据套接字 777 | 778 | 779 | /* 780 | void *handle = NULL; 781 | int data_sockfd = 0; 782 | if (port_active(sess)) 783 | { 784 | sckCliet_init(&handle, tunable_connect_timeout, 0, 0, 1); 785 | int ret = sckCliet_getconn(handle, inet_ntoa(sess->port_addr->sin_addr), ntohs(sess->port_addr->sin_port), &data_sockfd); 786 | if (ret != Sck_Ok) 787 | { 788 | sckClient_destroy(handle); 789 | } 790 | sess->data_fd = data_sockfd; 791 | } 792 | */ 793 | /*如果是主动模式*/ 794 | if (port_active(sess)) 795 | { 796 | /*创建数据套接字失败*/ 797 | if (get_port_fd(sess) == 0) 798 | { 799 | ret = 0; 800 | } 801 | } 802 | 803 | 804 | //被动模式 805 | if (pasv_active(sess)) 806 | { 807 | /* 808 | int connfd = -1; 809 | int ret = sckServer_accept(sess->pasv_listen_fd, &connfd, tunable_accept_timeout); 810 | { 811 | //关闭监听套接字 812 | close(sess->pasv_listen_fd); 813 | if (ret != Sck_Ok) 814 | { 815 | return 0; 816 | } 817 | } 818 | sess->data_fd = connfd; 819 | */ 820 | if (get_pasv_fd(sess) == 0) 821 | { 822 | return 0; 823 | } 824 | } 825 | 826 | if (ret == 0) 827 | { 828 | if (sess->port_addr) 829 | { 830 | free(sess->port_addr); 831 | sess->port_addr = NULL; 832 | } 833 | } 834 | 835 | if (ret) 836 | { 837 | /*开启数据连接闹钟*/ 838 | start_data_alarm(); 839 | } 840 | return 1; 841 | } 842 | 843 | static void do_cwd(session_t *sess) 844 | { 845 | char *cwdir = sess->arg; 846 | //更改用户目录 847 | if ( chdir(cwdir) < 0 ) 848 | { 849 | ftp_relply(sess, FTP_NOPERM,"Failured to change directory."); 850 | return; 851 | } 852 | 853 | ftp_relply(sess, FTP_CWDOK,"Directory successfully changed."); 854 | } 855 | static void do_cdup(session_t *sess) 856 | { 857 | //更改用户目录 858 | if ( chdir("..") < 0 ) 859 | { 860 | ftp_relply(sess, FTP_NOPERM,"Failured to change directory."); 861 | return; 862 | } 863 | 864 | ftp_relply(sess, FTP_CWDOK,"Directory successfully changed."); 865 | } 866 | static void do_quit(session_t *sess) 867 | { 868 | //221应答 869 | ftp_relply(sess, FTP_GOODBYE, "Goodbye."); 870 | exit(EXIT_SUCCESS); 871 | } 872 | static void do_port(session_t *sess) 873 | { 874 | //PORT 192,168,150,13,123,233 //最后两个 一个是高八位 一个是低八位 875 | unsigned int v[6]; 876 | sscanf(sess->arg, "%u,%u,%u,%u,%u,%u", &v[2], &v[3], &v[4], &v[5], &v[0], &v[1]); 877 | sess->port_addr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in)); 878 | memset(sess->port_addr, 0, sizeof(struct sockaddr_in)); 879 | 880 | sess->port_addr->sin_family = AF_INET; 881 | 882 | unsigned char *p = (unsigned char *)&(sess->port_addr->sin_port); 883 | p[0] = v[0]; 884 | p[1] = v[1]; 885 | 886 | p = (unsigned char *)&(sess->port_addr->sin_addr); 887 | p[0] = v[2]; 888 | p[1] = v[3]; 889 | p[2] = v[4]; 890 | p[3] = v[5]; 891 | 892 | ftp_relply(sess, FTP_PORTOK, "PORT command successful. Consider using PASV"); 893 | 894 | } 895 | static void do_pasv(session_t *sess) 896 | { 897 | char ip[16] = {0}; 898 | strcpy(ip, tunable_listen_address); 899 | 900 | /*监听套接字*/ 901 | /* 902 | int fd = -1; 903 | sckServer_init(ip, 0, &fd); 904 | sess->pasv_listen_fd = fd; 905 | 906 | struct sockaddr_in addr; 907 | socklen_t addrlen = sizeof(addr); 908 | if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0) 909 | { 910 | ERR_EXIT("getsockname"); 911 | } 912 | */ 913 | priv_sock_send_cmd(sess->child_fd, PRIV_SOCK_PASV_LISTEN); 914 | unsigned short port = (int)priv_sock_get_int(sess->child_fd); 915 | 916 | //unsigned short port = ntohs(addr.sin_port); 917 | 918 | //格式化ip和 端口 919 | unsigned int v[4]; 920 | sscanf(ip, "%u.%u.%u.%u", &v[0],&v[1],&v[2],&v[3]); 921 | 922 | char text[1024] = {0}; 923 | sprintf(text, "Entering Passive Mode (%u,%u,%u,%u,%u,%u).", v[0],v[1],v[2],v[3], port>>8, port&0xFF); 924 | 925 | ftp_relply(sess, FTP_PASVOK, text); 926 | } 927 | static void do_type(session_t *sess) 928 | { 929 | if (strcmp(sess->arg, "A") == 0) 930 | { 931 | ftp_relply(sess, FTP_TYPEOK, "Swiching to ASCII mode"); 932 | } 933 | else if (strcmp(sess->arg, "I") == 0) 934 | { 935 | sess->is_ascii = 1; 936 | ftp_relply(sess, FTP_TYPEOK, "Swiching to Binarry mode"); 937 | } 938 | else 939 | { 940 | ftp_relply(sess, FTP_BADCMD, "Unrecognised Type command"); 941 | } 942 | } 943 | static void do_stru(session_t *sess) 944 | { 945 | 946 | } 947 | static void do_mode(session_t *sess) 948 | { 949 | 950 | } 951 | /*下载文件 支持断点续载*/ 952 | static void do_retr(session_t *sess) 953 | { 954 | // 创建数据连接 955 | if (get_transfer_fd(sess) == 0) 956 | { 957 | return; 958 | } 959 | 960 | long long offset = sess->restart_pos; 961 | sess->restart_pos = 0; 962 | 963 | //打开文件 964 | int fd = open(sess->arg, O_RDONLY); 965 | if (fd == -1) 966 | { 967 | ftp_relply(sess, FTP_FILEFAIL, "Failured to open file."); 968 | return; 969 | } 970 | //加锁 971 | int ret = lock_file_read(fd); 972 | if (ret == -1) 973 | { 974 | ftp_relply(sess, FTP_FILEFAIL, "Failured to open file."); 975 | return; 976 | } 977 | //判断是否为普通文件 978 | struct stat sbuf; 979 | ret = fstat(fd, &sbuf); 980 | if (!S_ISREG(sbuf.st_mode)) 981 | { 982 | ftp_relply(sess, FTP_FILEFAIL, "Failured to open file."); 983 | return; 984 | } 985 | 986 | long long bytes_to_send = sbuf.st_size; 987 | if (offset > bytes_to_send) 988 | { 989 | bytes_to_send = 0; 990 | } 991 | else 992 | { 993 | bytes_to_send -= offset; 994 | } 995 | 996 | //向客户端响应 150 997 | //Opening BINARY mode data connection for /home/jhz/dump.rdb (18 bytes). 998 | char text[1024] = {0}; 999 | if (sess->is_ascii) 1000 | { 1001 | sprintf(text, "Opening ASCII mode data connection for %s (%lu bytes).", sess->arg, sbuf.st_size); 1002 | } 1003 | sprintf(text, "Opening BINARY mode data connection for %s (%lu bytes).", sess->arg, sbuf.st_size); 1004 | 1005 | ftp_relply(sess, FTP_DATACONN, text); 1006 | 1007 | /*为了方便,默认在此不对二进制传输进行转换*/ 1008 | 1009 | //零拷贝传输数据 下载文件 1010 | /*防止传输被中断*/ 1011 | int flag = 0; 1012 | sess->bw_transfer_start_sec = get_time_sec(); 1013 | sess->bw_transfer_start_usec = get_time_usec(); 1014 | while (bytes_to_send) 1015 | { 1016 | int num_this_time = bytes_to_send > 4096 ? 4096 : bytes_to_send; 1017 | ssize_t bytes = sendfile(sess->data_fd, fd, (off_t*)&offset, (size_t)num_this_time); 1018 | if (bytes == -1) 1019 | { 1020 | flag = 2; 1021 | break; 1022 | }/*end if*/ 1023 | /*限速*/ 1024 | limit_rate(sess, bytes, 0); 1025 | if (sess->abor_received == 1) 1026 | { 1027 | flag = 1; 1028 | break; 1029 | } 1030 | bytes_to_send -= bytes; 1031 | }/*end while*/ 1032 | if (bytes_to_send == 0) 1033 | { 1034 | flag = 0; 1035 | } 1036 | 1037 | //关闭数据连接套接字 1038 | close(sess->data_fd); 1039 | sess->data_fd = -1; 1040 | close(fd); 1041 | 1042 | //传输完毕 发送226 1043 | if (flag == 0 && !sess->abor_received) 1044 | { 1045 | //226 1046 | ftp_relply(sess, FTP_TRANSFEROK, "Transfer complete."); 1047 | } 1048 | if (flag == 1) 1049 | { 1050 | //426 1051 | ftp_relply(sess, FTP_TRANSFEROK, "Failure reading from local file"); 1052 | } 1053 | else if (flag == 2) 1054 | { 1055 | //451 1056 | ftp_relply(sess, FTP_BADSENDFILE, "Failure writting to network stream."); 1057 | } 1058 | 1059 | check_abor(sess); 1060 | 1061 | /*重新开启控制连接闹钟*/ 1062 | start_cmdio_alarm(); 1063 | } 1064 | /*上传文件 STOR 支持断点续传*/ 1065 | static void do_stor(session_t *sess) 1066 | { 1067 | /*0 为非appe模式*/ 1068 | upload_common(sess, 0); 1069 | } 1070 | static void do_appe(session_t *sess) 1071 | { 1072 | /*1 为appe模式*/ 1073 | upload_common(sess, 1); 1074 | } 1075 | static void do_list(session_t *sess) 1076 | { 1077 | // 创建数据连接 1078 | if (get_transfer_fd(sess) == 0) 1079 | { 1080 | return; 1081 | } 1082 | //向客户端响应 150 1083 | ftp_relply(sess, FTP_DATACONN, "Here comes the directory listing"); 1084 | //传输列表 1085 | list_common(sess, 1); 1086 | //关闭数据连接套接字 1087 | close(sess->data_fd); 1088 | //226 1089 | ftp_relply(sess, FTP_TRANSFEROK, "Directory send OK"); 1090 | } 1091 | static void do_nlst(session_t *sess) 1092 | { 1093 | // 创建数据连接 1094 | if (get_transfer_fd(sess) == 0) 1095 | { 1096 | return; 1097 | } 1098 | //向客户端响应 150 1099 | ftp_relply(sess, FTP_DATACONN, "Here comes the directory listing"); 1100 | //传输列表 1101 | list_common(sess, 0); 1102 | //关闭数据连接套接字 1103 | close(sess->data_fd); 1104 | //226 1105 | ftp_relply(sess, FTP_TRANSFEROK, "Directory send OK"); 1106 | } 1107 | 1108 | static void do_rest(session_t *sess) 1109 | { 1110 | sess->restart_pos = str_tolonglong(sess->arg); 1111 | char text[1024] = {0}; 1112 | 1113 | sprintf(text, "Restart position accepted (%lld).", sess->restart_pos); 1114 | ftp_relply(sess, FTP_RESTOK, text);//350 1115 | } 1116 | 1117 | static void do_abor(session_t *sess) 1118 | { 1119 | //225 1120 | ftp_relply(sess, FTP_ABOR_NOCONN, "No transfer to ABOR."); 1121 | } 1122 | static void do_pwd(session_t *sess) 1123 | { 1124 | /*实现1: 1125 | struct passwd *pw = getpwuid(sess->uid); 1126 | if (pw == NULL) 1127 | { 1128 | ftp_relply(sess, FTP_LOGINERR, "Login incorrect"); 1129 | return; 1130 | } 1131 | ftp_relply(sess, FTP_PWDOK, pw->pw_dir); 1132 | */ 1133 | /*实现2*/ 1134 | char text[1024] ={0}; 1135 | char dir[1024] = {0}; 1136 | 1137 | getcwd(dir, 1024); 1138 | sprintf(text, "\"%s\"", dir); //需要转义 1139 | 1140 | ftp_relply(sess, FTP_PWDOK, text); 1141 | } 1142 | /*新建文件夹*/ 1143 | static void do_mkd(session_t *sess) 1144 | { 1145 | //0777 & umask 1146 | int ret = mkdir(sess->arg, 0777); 1147 | if (ret < 0) 1148 | { 1149 | ftp_relply(sess, FTP_FILEFAIL, "Create directory operation failed");//550 1150 | return; 1151 | } 1152 | char text[200] = {0}; 1153 | /*判断是否绝对路径*/ 1154 | if (sess->arg[0] == '/') 1155 | { 1156 | sprintf(text, "\"%s\" created", sess->arg); 1157 | } 1158 | else 1159 | { 1160 | char dir[200] = {0}; 1161 | getcwd(dir, sizeof(dir)); 1162 | 1163 | if (dir[strlen(dir)-1] == '/') 1164 | { 1165 | sprintf(text, "\"%s%s\" created", dir, sess->arg); 1166 | } 1167 | else 1168 | { 1169 | sprintf(text, "\"%s/%s\" created", dir, sess->arg); 1170 | } 1171 | }/*end else*/ 1172 | ftp_relply(sess, FTP_MKDIROK, text); 1173 | } 1174 | 1175 | /*删除空目录*/ 1176 | static void do_rmd(session_t *sess) 1177 | { 1178 | const char *path = sess->arg; 1179 | if (path == NULL) 1180 | { 1181 | ftp_relply(sess, FTP_FILEFAIL, "Remove directory operation failed"); //550 1182 | return; 1183 | } 1184 | int ret = rmdir(path); 1185 | if (ret < 0) 1186 | { 1187 | ftp_relply(sess, FTP_FILEFAIL, "Remove directory operation failed"); //550 1188 | return; 1189 | } 1190 | ftp_relply(sess, FTP_RMDIROK, "Remove directory operation successful.");//250 1191 | } 1192 | 1193 | /*删除文件*/ 1194 | static void do_dele(session_t *sess) 1195 | { 1196 | const char *path = sess->arg; 1197 | if (path == NULL) 1198 | { 1199 | ftp_relply(sess, FTP_FILEFAIL, "Delete operation failed."); //550 1200 | return; 1201 | } 1202 | int ret = unlink(path); 1203 | if (ret < 0) 1204 | { 1205 | ftp_relply(sess, FTP_FILEFAIL, "Delete operation failed."); //550 1206 | return; 1207 | } 1208 | 1209 | ftp_relply(sess, FTP_DELEOK, "Delete operation successful."); //250 1210 | } 1211 | 1212 | static void do_rnfr(session_t *sess) 1213 | { 1214 | sess->rnfr_name = (char *)malloc(strlen(sess->arg) + 1); 1215 | memset(sess->rnfr_name, 0, strlen(sess->arg) + 1); 1216 | strcpy(sess->rnfr_name, sess->arg); 1217 | 1218 | ftp_relply(sess, FTP_RNFROK, "Ready for RNTO"); 1219 | 1220 | } 1221 | static void do_rnto(session_t *sess) 1222 | { 1223 | if (sess->rnfr_name == NULL) 1224 | { 1225 | ftp_relply(sess, FTP_NEEDRNFR, "RNFR required first."); 1226 | return; 1227 | } 1228 | 1229 | int ret = rename(sess->rnfr_name, sess->arg); 1230 | if (ret < 0) 1231 | { 1232 | ftp_relply(sess, FTP_BADPROT, "Rename directory operation failed"); 1233 | return; 1234 | } 1235 | ftp_relply(sess, FTP_RENAMEOK, "Rename successful."); 1236 | 1237 | if (sess->rnfr_name) 1238 | { 1239 | free(sess->rnfr_name); 1240 | sess->rnfr_name = NULL; 1241 | } 1242 | } 1243 | static void do_site(session_t *sess) 1244 | { 1245 | 1246 | } 1247 | static void do_syst(session_t *sess) 1248 | { 1249 | ftp_relply(sess, FTP_SYSTOK, "Unix Type: L8"); 1250 | } 1251 | static void do_feat(session_t *sess) 1252 | { 1253 | ftp_lrelply(sess, FTP_FEAT, "Features:"); 1254 | 1255 | ftp_frelply(sess, "EPRT"); 1256 | ftp_frelply(sess, "EPSV"); 1257 | ftp_frelply(sess, "MDTM"); 1258 | ftp_frelply(sess, "PASV"); 1259 | ftp_frelply(sess, "REST STREAM"); 1260 | ftp_frelply(sess, "SIZE"); 1261 | ftp_frelply(sess, "TVFS"); 1262 | ftp_frelply(sess, "UTF8"); 1263 | 1264 | ftp_relply(sess, FTP_FEAT, "End"); 1265 | /* 1266 | 211-Features: 1267 | EPRT 1268 | EPSV 1269 | MDTM 1270 | PASV 1271 | REST STREAM 1272 | SIZE 1273 | TVFS 1274 | UTF8 1275 | 211 End 1276 | */ 1277 | 1278 | } 1279 | /*获取文件大小*/ 1280 | static void do_size(session_t *sess) 1281 | { 1282 | const char *path = sess->arg; 1283 | struct stat buf; 1284 | memset(&buf, 0, sizeof(buf)); 1285 | 1286 | int ret = stat(path, &buf); 1287 | if (ret < 0) 1288 | { 1289 | printf("hello\n"); 1290 | ftp_relply(sess, FTP_FILEFAIL, "Could not get file size."); 1291 | return; 1292 | } 1293 | 1294 | /*如果不是普通文件则返回错误*/ 1295 | if (!S_ISREG(buf.st_mode)) 1296 | { 1297 | printf("hello world\n"); 1298 | ftp_relply(sess, FTP_FILEFAIL, "Could not get file size."); 1299 | return; 1300 | } 1301 | 1302 | char text[1024] = {0}; 1303 | sprintf(text, "%ld", buf.st_size); 1304 | ftp_relply(sess, FTP_STATFILE_OK, text); 1305 | } 1306 | 1307 | static void do_stat(session_t *sess) 1308 | { 1309 | if (sess->arg) 1310 | { 1311 | ftp_lrelply(sess, FTP_STATFILE_OK, "Status follows:"); 1312 | 1313 | //发送信息 1314 | char buf[1024] = {0}; 1315 | struct stat sbuf; 1316 | memset(&sbuf, 0, sizeof(sbuf)); 1317 | 1318 | file_stat(sess->arg, buf, &sbuf); 1319 | writen(sess->ctrl_fd, buf, strlen(buf)); 1320 | 1321 | 1322 | ftp_relply(sess, FTP_STATFILE_OK, "End of status"); 1323 | 1324 | } 1325 | char text[1024] = {0}; 1326 | 1327 | ftp_lrelply(sess, FTP_STATOK, "FTP server status:"); 1328 | 1329 | //连接ip 1330 | char *ip = get_sock_addr(sess->ctrl_fd); 1331 | sprintf(text, 1332 | "Connected to %s", 1333 | ip 1334 | ); 1335 | ftp_frelply(sess, text); 1336 | 1337 | //登入用户 1338 | struct passwd *pw = getpwuid(sess->uid); 1339 | memset(text, 0, sizeof(text)); 1340 | sprintf(text, 1341 | "Logged in as %s", 1342 | pw->pw_name 1343 | ); 1344 | ftp_frelply(sess, text); 1345 | 1346 | //ascii ? 1347 | if (sess->is_ascii) 1348 | { 1349 | ftp_frelply(sess, "TYPE: ASCII"); 1350 | } 1351 | else 1352 | { 1353 | ftp_frelply(sess, "TYPE: BINARY"); 1354 | } 1355 | //上传下载速率 1356 | if (sess->bw_download_rate_max >0) 1357 | { 1358 | memset(text, 0, sizeof(text)); 1359 | sprintf(text, 1360 | "session download limit in byte/s is %u", 1361 | sess->bw_download_rate_max 1362 | ); 1363 | ftp_frelply(sess, text); 1364 | } 1365 | if (sess->bw_upload_rate_max > 0) 1366 | { 1367 | memset(text, 0, sizeof(text)); 1368 | sprintf(text, 1369 | "session upload limit in byte/s is %u", 1370 | sess->bw_upload_rate_max 1371 | ); 1372 | ftp_frelply(sess, text); 1373 | } 1374 | // session_timeout 1375 | if (tunable_idle_session_timeout > 0) 1376 | { 1377 | memset(text, 0, sizeof(text)); 1378 | sprintf(text, 1379 | "Session timeout in seconds is %u", 1380 | tunable_idle_session_timeout 1381 | ); 1382 | ftp_frelply(sess, text); 1383 | } 1384 | 1385 | ftp_frelply(sess, "Control connection is plain text"); 1386 | ftp_frelply(sess, "Data connections will be plain text"); 1387 | 1388 | memset(text, 0, sizeof(text)); 1389 | sprintf(text, 1390 | "At session startup, client count was %d", 1391 | sess->num_clients 1392 | ); 1393 | ftp_frelply(sess, text); 1394 | 1395 | ftp_frelply(sess, "Tinyftpd 1.0 - secure, fast, stable"); 1396 | 1397 | ftp_relply(sess, FTP_STATOK, "End of status"); 1398 | 1399 | } 1400 | static void do_noop(session_t *sess) 1401 | { 1402 | //FTP_NOOPOK 200 1403 | ftp_relply(sess, FTP_NOOPOK, "NOOP ok"); 1404 | } 1405 | 1406 | static void do_help(session_t *sess) 1407 | { 1408 | ftp_lrelply(sess, FTP_HELP, "The following commands are recognized."); 1409 | 1410 | ftp_frelply(sess, "ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD"); 1411 | ftp_frelply(sess, "MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR"); 1412 | ftp_frelply(sess, "RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD"); 1413 | ftp_frelply(sess, "XPWD XRMD"); 1414 | 1415 | ftp_relply(sess, FTP_HELP, "Help OK."); 1416 | } 1417 | -------------------------------------------------------------------------------- /src/ftpproto.h: -------------------------------------------------------------------------------- 1 | #ifndef _FTP_PROTO_H_ 2 | #define _FTP_PROTO_H_ 3 | 4 | #include "session.h" 5 | 6 | void handle_child(session_t *sess); 7 | 8 | void ftp_relply(session_t *sess, int status, const char *text); 9 | 10 | #endif /*_FTP_PROTO_H_*/ -------------------------------------------------------------------------------- /src/hash.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbirds/Tinyftp/3759f7c4a79199d9ba969c198919ef9777b069a7/src/hash.c -------------------------------------------------------------------------------- /src/hash.h: -------------------------------------------------------------------------------- 1 | #ifndef _HASH_H_ 2 | #define _HASH_H_ 3 | 4 | typedef struct hash hash_t; 5 | typedef unsigned int (*hashfunc_t)(unsigned int, void*); 6 | 7 | 8 | hash_t* hash_alloc(unsigned int buckets, hashfunc_t hash_func); 9 | 10 | void* hash_lookup_entry(hash_t *hash, void* key, unsigned int key_size); 11 | 12 | void hash_add_entry(hash_t *hash, void* key, unsigned int key_size, 13 | void *value, unsigned int value_size); 14 | 15 | void hash_free_entry(hash_t *hash, void *key, unsigned int key_size); 16 | 17 | #endif /*_HASH_H_*/ -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "commsocket.h" 2 | #include "sckutil.h" 3 | #include "session.h" 4 | #include "parseconf.h" 5 | #include "tunable.h" 6 | #include "ftpproto.h" 7 | #include "ftpcodes.h" 8 | #include "common.h" 9 | #include "hash.h" 10 | 11 | void paint_conf() 12 | { 13 | printf("tunable_pasv_enable: %d\n", tunable_pasv_enable); 14 | printf("tunable_port_enable: %d\n", tunable_port_enable); 15 | printf("tunable_listen_port: %u\n", tunable_listen_port); 16 | printf("tunable_max_clients: %u\n", tunable_max_clients); 17 | printf("tunable_max_per_ip: %u\n", tunable_max_per_ip); 18 | printf("tunable_accept_timeout: %u\n", tunable_accept_timeout); 19 | printf("tunable_connect_timeout: %u\n", tunable_connect_timeout); 20 | printf("tunable_idle_session_timeout: %u\n", tunable_idle_session_timeout); 21 | printf("tunable_data_connection_timeout: %u\n",tunable_data_connection_timeout); 22 | printf("tunable_local_umask: %u\n", tunable_local_umask); 23 | printf("tunable_upload_max_rate: %u\n", tunable_upload_max_rate); 24 | printf("tunable_download_max_rate: %u\n", tunable_download_max_rate); 25 | //printf("tunable_listen_address: %s\n", tunable_listen_address); 26 | 27 | if (tunable_listen_address == NULL) 28 | { 29 | printf("tunable_listen_address: NULL\n"); 30 | } 31 | else 32 | { 33 | printf("tunable_listen_address: %s\n",tunable_listen_address); 34 | } 35 | } 36 | 37 | extern session_t *p_sess; 38 | static unsigned int s_children; 39 | 40 | static hash_t *s_ip_count_hash; 41 | static hash_t *s_pid_ip_hash; //pid与ip对应关系哈希表 42 | 43 | 44 | void check_limits(session_t *sess); 45 | void handle_sigchld(int sig); 46 | unsigned int hash_func(unsigned int buckets, void *key); 47 | 48 | unsigned int handle_ip_count(void *ip); 49 | void drop_ip_count(void *ip); 50 | 51 | int main(void) 52 | { 53 | 54 | parseconf_load_file("../tinyftpd.conf"); 55 | 56 | paint_conf(); 57 | 58 | //list_common(); 59 | 60 | if (getuid() !=0) 61 | { 62 | fprintf(stderr, "miniftpd: must be started as root\n"); 63 | exit(EXIT_FAILURE); 64 | } 65 | 66 | session_t sess = { 67 | /*控制连接*/ 68 | 0,-1,"","","", 69 | /*数据连接*/ 70 | NULL,-1,-1,0, 71 | /*限速*/ 72 | 0,0,0,0, 73 | /*父子进程通道*/ 74 | -1,-1, 75 | /*FTP协议状态*/ 76 | 0,0,NULL,0, 77 | /*连接数限制*/ 78 | 0,0 79 | }; 80 | p_sess = &sess; 81 | 82 | sess.bw_upload_rate_max = tunable_upload_max_rate; 83 | sess.bw_download_rate_max = tunable_download_max_rate; 84 | s_ip_count_hash = hash_alloc(256, hash_func); 85 | s_pid_ip_hash = hash_alloc(256, hash_func); 86 | 87 | signal(SIGCHLD, handle_sigchld); 88 | int listenfd = 0; 89 | int ret = sckServer_init(tunable_listen_address, tunable_listen_port, &listenfd); 90 | int connfd = 0; 91 | pid_t pid; 92 | struct sockaddr_in addr; 93 | 94 | while (1) 95 | { 96 | 97 | //connfd = accept_timeout(listenfd, &addr, 0); 98 | ret = sckServer_accept(listenfd, &connfd, (void*)&addr, 0); 99 | if (ret == Sck_ErrTimeOut) 100 | { 101 | ERR_EXIT("accept timeout"); 102 | } 103 | 104 | unsigned int ip = addr.sin_addr.s_addr; 105 | //printf("get_sock_addr ip: %u\n", ip);//++++ 106 | sess.num_this_ip = handle_ip_count(&ip); 107 | 108 | 109 | ++s_children; 110 | sess.num_clients = s_children; 111 | 112 | pid = fork(); 113 | if (pid == -1) 114 | { 115 | --s_children; 116 | ERR_EXIT("fork"); 117 | } 118 | if (pid == 0) 119 | { 120 | close(listenfd); 121 | sess.ctrl_fd = connfd; 122 | 123 | check_limits(&sess); 124 | 125 | //让session中的进程忽略handle_sigchld处理函数 126 | signal(SIGCHLD, SIG_IGN); 127 | //开启一个新会话 128 | begin_session(&sess); 129 | } 130 | else 131 | { 132 | hash_add_entry(s_pid_ip_hash, &pid, sizeof(pid), 133 | &ip, sizeof(unsigned int)); 134 | close(connfd); 135 | } 136 | } 137 | 138 | return 0; 139 | } 140 | 141 | void check_limits(session_t *sess) 142 | { 143 | if (tunable_max_clients > 0 && sess->num_clients > tunable_max_clients) 144 | { 145 | //421响应 FTP_TOO_MANY_USERS 146 | ftp_relply(sess, FTP_TOO_MANY_USERS, "There are too many connected users please try later"); 147 | exit(EXIT_FAILURE); 148 | } 149 | 150 | if (tunable_max_per_ip >0 && sess->num_this_ip > tunable_max_per_ip) 151 | { 152 | //421响应 FTP_IP_LIMIT 153 | ftp_relply(sess, FTP_IP_LIMIT, "There are too many connecter from your internal addr"); 154 | exit(EXIT_FAILURE); 155 | } 156 | 157 | } 158 | 159 | void handle_sigchld(int sig) 160 | { 161 | //避免僵尸进程 162 | pid_t pid; 163 | while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) 164 | { 165 | --s_children; 166 | unsigned int *ip = hash_lookup_entry(s_pid_ip_hash, &pid, sizeof(pid)); 167 | if (ip == NULL) 168 | { 169 | continue; 170 | } 171 | 172 | drop_ip_count(ip); 173 | hash_free_entry(s_pid_ip_hash, &pid, sizeof(pid)); 174 | } 175 | 176 | } 177 | 178 | unsigned int hash_func(unsigned int buckets, void *key) 179 | { 180 | unsigned int *number = (unsigned int*)key; 181 | 182 | return (*number) % buckets; 183 | } 184 | 185 | unsigned int handle_ip_count(void *ip) 186 | { 187 | unsigned int count; 188 | unsigned int *p_count = (unsigned int *)hash_lookup_entry(s_ip_count_hash, 189 | ip, sizeof(unsigned int)); 190 | 191 | if (p_count == NULL) 192 | { 193 | //该ip第一次连接 194 | count = 1; 195 | hash_add_entry(s_ip_count_hash, ip, sizeof(unsigned int), 196 | &count, sizeof(unsigned int)); 197 | } 198 | else 199 | { 200 | count = *p_count; 201 | ++count; 202 | *p_count = count; 203 | } 204 | 205 | //printf("handle_ip_count :%u\n", count);//+++++ 206 | 207 | return count; 208 | } 209 | 210 | void drop_ip_count(void *ip) 211 | { 212 | unsigned int count; 213 | unsigned int *p_count = (unsigned int *)hash_lookup_entry(s_ip_count_hash, 214 | ip, sizeof(unsigned int)); 215 | 216 | if (p_count == NULL) 217 | { 218 | //该ip第一次连接 219 | return; 220 | } 221 | 222 | count = *p_count; 223 | if (count <= 0) 224 | { 225 | return; 226 | } 227 | --count; 228 | *p_count = count; 229 | 230 | if (count == 0) 231 | { 232 | //删除表项 233 | hash_free_entry(s_ip_count_hash, ip, sizeof(unsigned int)); 234 | } 235 | 236 | //printf("drop_ip_count :%u\n", count);//++++++ 237 | 238 | } 239 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | .PHONY:clean all 2 | CC=gcc 3 | CFLAGS=-Wall -g 4 | BIN=tinyftpd 5 | OBJS= main.o commsocket.o sckutil.o session.o ftpproto.o \ 6 | privparent.o str.o tunable.o parseconf.o privsock.o hash.o 7 | LIBS=-lcrypt 8 | $(BIN):$(OBJS) 9 | $(CC) $(CFLAGS) $^ -o $@ $(LIBS) 10 | 11 | %.o:%.c 12 | $(CC) $(CFLAGS) -c $< -o $@ 13 | 14 | clean: 15 | rm -f *.o $(BIN) 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/makefile.bak: -------------------------------------------------------------------------------- 1 | .PHONY:clean all 2 | CC=gcc 3 | CFLAGS=-Wall -g 4 | BIN=tinyftpd 5 | OBJS= main.o commsocket.o sckutil.o session.o ftpproto.o \ 6 | privparent.o str.o tunable.o parseconf.o privsock.o hash.o 7 | LIBS=-lcrypt 8 | $(BIN):$(OBJS) 9 | $(CC) $(CFLAGS) $^ -o $@ $(LIBS) 10 | 11 | %.o:%.c 12 | $(CC) $(CFLAGS) -c $< -o $@ 13 | 14 | clean: 15 | rm -f *.o $(BIN) 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/parseconf.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbirds/Tinyftp/3759f7c4a79199d9ba969c198919ef9777b069a7/src/parseconf.c -------------------------------------------------------------------------------- /src/parseconf.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARSE_CONF_H_ 2 | #define _PARSE_CONF_H_ 3 | 4 | /*加载配置文件*/ 5 | void parseconf_load_file(const char *path); 6 | /*将配置项加载到相应变量*/ 7 | void parseconf_load_setting(const char *setting); 8 | 9 | 10 | #endif /*_PARSE_CONF_H_*/ -------------------------------------------------------------------------------- /src/privparent.c: -------------------------------------------------------------------------------- 1 | #include "privparent.h" 2 | #include "sckutil.h" 3 | #include "commsocket.h" 4 | #include "common.h" 5 | #include "privsock.h" 6 | #include "tunable.h" 7 | 8 | static void privop_pasv_get_data_sock(session_t *sess); 9 | static void privop_pasv_active(session_t *sess); 10 | static void privop_pasv_listen(session_t *sess); 11 | static void privop_pasv_accept(session_t *sess); 12 | 13 | int capset(cap_user_header_t hdrp, const cap_user_data_t datap); 14 | /*设置最小化特权*/ 15 | int minimize_privilege(); 16 | 17 | int capset(cap_user_header_t hdrp, const cap_user_data_t datap) 18 | { 19 | /*系统调用capset*/ 20 | syscall(__NR_capset, hdrp, datap); 21 | return 0; 22 | } 23 | 24 | int minimize_privilege() 25 | { 26 | /*设置权限 让nobody进程可以绑定20端口*/ 27 | struct __user_cap_header_struct cap_header; 28 | struct __user_cap_data_struct cap_data; 29 | 30 | memset(&cap_header, 0, sizeof(cap_header)); 31 | memset(&cap_data, 0, sizeof(cap_data)); 32 | 33 | cap_header.version = _LINUX_CAPABILITY_VERSION_1; 34 | cap_header.pid = 0; 35 | 36 | __u32 cap_mask = 0; 37 | cap_mask |= (1 << CAP_NET_BIND_SERVICE); //绑定系统端口 38 | cap_data.effective = cap_data.permitted = cap_mask; 39 | cap_data.inheritable = 0; 40 | 41 | capset(&cap_header, &cap_data); 42 | 43 | return 0; 44 | } 45 | 46 | void handle_parent(session_t *sess) 47 | { 48 | /*将父进程变成nobody进程*/ 49 | struct passwd *pw = getpwnam("nobody"); 50 | if (pw == NULL) 51 | { 52 | return; 53 | } 54 | if (setegid(pw->pw_gid) < 0) 55 | { 56 | ERR_EXIT("setegid"); 57 | } 58 | if (seteuid(pw->pw_uid) < 0) 59 | { 60 | ERR_EXIT("seteuid"); 61 | } 62 | 63 | minimize_privilege(); 64 | 65 | char cmd; 66 | while (1) 67 | { 68 | //readn(sess->parent_fd, buf, sizeof(buf)); 69 | cmd = priv_sock_get_cmd(sess->parent_fd); 70 | //解析命令 71 | //处理命令 72 | switch (cmd) 73 | { 74 | case PRIV_SOCK_GET_DATA_SOCK: 75 | privop_pasv_get_data_sock(sess); 76 | break; 77 | case PRIV_SOCK_PASV_ACTIVE: 78 | privop_pasv_active(sess); 79 | break; 80 | case PRIV_SOCK_PASV_LISTEN: 81 | privop_pasv_listen(sess); 82 | break; 83 | case PRIV_SOCK_PASV_ACCETP: 84 | privop_pasv_accept(sess); 85 | break; 86 | } 87 | } 88 | } 89 | 90 | static void privop_pasv_get_data_sock(session_t *sess) 91 | { 92 | /*接收port*/ 93 | unsigned short port = (unsigned short)priv_sock_get_int(sess->parent_fd); 94 | /*接收ip*/ 95 | char ip[16] = {0}; 96 | priv_sock_recv_buf(sess->parent_fd, ip, sizeof(ip)); 97 | 98 | /*创建数据连接*/ 99 | int data_sockfd = -1; 100 | 101 | struct sockaddr_in addr; 102 | memset(&addr, 0, sizeof(addr)); 103 | addr.sin_family = AF_INET; 104 | addr.sin_port = htons(port); 105 | //addr.sin_addr = inet_addr(ip) 106 | inet_pton(AF_INET, ip, &addr.sin_addr); 107 | 108 | 109 | /*绑定20端口*/ 110 | //sckServer_init(tunable_listen_address, 20, &data_sockfd); 111 | data_sockfd = tcp_client(tunable_listen_address, 20); 112 | 113 | if (data_sockfd == -1) 114 | { 115 | priv_sock_send_result(sess->parent_fd, PRIV_SOCK_RESULT_BAD); 116 | return; 117 | } 118 | 119 | printf("helloo\n"); 120 | if (connect_timeout(data_sockfd, &addr, tunable_connect_timeout) < 0) 121 | { 122 | close(data_sockfd); 123 | priv_sock_send_result(sess->parent_fd, PRIV_SOCK_RESULT_BAD); 124 | return; 125 | } 126 | /*发送成功信息*/ 127 | priv_sock_send_result(sess->parent_fd, PRIV_SOCK_RESULT_OK); 128 | /*传输文件描述符*/ 129 | priv_sock_send_fd(sess->parent_fd, data_sockfd); 130 | 131 | close(data_sockfd); 132 | } 133 | static void privop_pasv_active(session_t *sess) 134 | { 135 | int active; 136 | if (sess->pasv_listen_fd != -1) 137 | { 138 | active = 1; 139 | } 140 | else 141 | { 142 | active = 0; 143 | } 144 | 145 | priv_sock_send_int(sess->parent_fd, active); 146 | } 147 | static void privop_pasv_listen(session_t *sess) 148 | { 149 | /*监听套接字*/ 150 | char ip[16] = {0}; 151 | strcpy(ip, tunable_listen_address); 152 | 153 | int fd = -1; 154 | sckServer_init(ip, 0, &fd); 155 | sess->pasv_listen_fd = fd; 156 | 157 | struct sockaddr_in addr; 158 | socklen_t addrlen = sizeof(addr); 159 | if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0) 160 | { 161 | ERR_EXIT("getsockname"); 162 | } 163 | unsigned short port = ntohs(addr.sin_port); 164 | priv_sock_send_int(sess->parent_fd, port); 165 | } 166 | static void privop_pasv_accept(session_t *sess) 167 | { 168 | int connfd = -1; 169 | int ret = sckServer_accept(sess->pasv_listen_fd, &connfd, NULL, tunable_accept_timeout); 170 | 171 | //关闭监听套接字 172 | close(sess->pasv_listen_fd); 173 | if (ret != Sck_Ok) 174 | { 175 | priv_sock_send_result(sess->parent_fd, PRIV_SOCK_RESULT_BAD); 176 | return; 177 | } 178 | 179 | priv_sock_send_result(sess->parent_fd, PRIV_SOCK_RESULT_OK); 180 | priv_sock_send_fd(sess->parent_fd, connfd); 181 | 182 | close(connfd); 183 | } 184 | -------------------------------------------------------------------------------- /src/privparent.h: -------------------------------------------------------------------------------- 1 | #ifndef _PRIV_PARENT_H_ 2 | #define _PRIV_PARENT_H_ 3 | 4 | #include "session.h" 5 | 6 | void handle_parent(session_t *sess); 7 | 8 | #endif /*_PRIV_PARENT_H_*/ -------------------------------------------------------------------------------- /src/privsock.c: -------------------------------------------------------------------------------- 1 | #include "privsock.h" 2 | #include "common.h" 3 | #include "sckutil.h" 4 | 5 | /*初始化内部进程间通讯通道*/ 6 | void priv_sock_init(session_t *sess) 7 | { 8 | int sockfds[2]; 9 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) < 0) 10 | { 11 | ERR_EXIT("socketpair"); 12 | } 13 | 14 | sess->parent_fd = sockfds[0]; 15 | sess->child_fd = sockfds[1]; 16 | } 17 | /*关闭内部进程间通讯通道*/ 18 | void priv_sock_close(session_t *sess) 19 | { 20 | if (sess->parent_fd != -1) 21 | { 22 | close(sess->parent_fd); 23 | sess->parent_fd = -1; 24 | } 25 | if (sess->child_fd != -1) 26 | { 27 | close(sess->child_fd); 28 | sess->child_fd = -1; 29 | } 30 | } 31 | 32 | /*设置父进程环境*/ 33 | void priv_sock_set_parent_context(session_t *sess) 34 | { 35 | if (sess->child_fd != -1) 36 | { 37 | close(sess->child_fd); 38 | sess->child_fd = -1; 39 | } 40 | } 41 | /*设置子进程环境*/ 42 | void priv_sock_set_child_context(session_t *sess) 43 | { 44 | if (sess->parent_fd != -1) 45 | { 46 | close(sess->parent_fd); 47 | sess->parent_fd = -1; 48 | } 49 | } 50 | 51 | /*发送命令(子->父)*/ 52 | void priv_sock_send_cmd(int fd, char cmd) 53 | { 54 | int ret = 0; 55 | ret = writen(fd, &cmd, sizeof(cmd)); 56 | if (ret != sizeof(cmd)) 57 | { 58 | fprintf(stderr, "priv_sock_send_cmd error"); 59 | exit(EXIT_FAILURE); 60 | } 61 | } 62 | /*接受命令(父<-子)*/ 63 | char priv_sock_get_cmd(int fd) 64 | { 65 | char res; 66 | int ret = 0; 67 | ret = readn(fd, &res, sizeof(res)); 68 | 69 | if (ret == 0) 70 | { 71 | printf("ftp process exit\n"); 72 | exit(EXIT_SUCCESS); 73 | } 74 | 75 | if (ret != sizeof(res)) 76 | { 77 | fprintf(stderr, "priv_sock_get_cmd error"); 78 | exit(EXIT_FAILURE); 79 | } 80 | return res; 81 | } 82 | 83 | /*发送结果(父->子)*/ 84 | void priv_sock_send_result(int fd, char res) 85 | { 86 | int ret = 0; 87 | ret = writen(fd, &res, sizeof(res)); 88 | if (ret != sizeof(res)) 89 | { 90 | fprintf(stderr, "priv_sock_send_result error"); 91 | exit(EXIT_FAILURE); 92 | } 93 | } 94 | /*接收结果(子<-父)*/ 95 | char priv_sock_get_result(int fd) 96 | { 97 | char res; 98 | int ret = 0; 99 | ret = readn(fd, &res, sizeof(res)); 100 | if (ret != sizeof(res)) 101 | { 102 | fprintf(stderr, "priv_sock_get_result error"); 103 | exit(EXIT_FAILURE); 104 | } 105 | return res; 106 | } 107 | 108 | /*发送一个整数*/ 109 | void priv_sock_send_int(int fd, int the_int) 110 | { 111 | int ret = 0; 112 | ret = writen(fd, &the_int, sizeof(the_int)); 113 | if (ret != sizeof(the_int)) 114 | { 115 | fprintf(stderr, "priv_sock_send_result error"); 116 | exit(EXIT_FAILURE); 117 | } 118 | } 119 | /*接收一个整数*/ 120 | int priv_sock_get_int(int fd) 121 | { 122 | int the_int = 0; 123 | 124 | int ret = 0; 125 | ret = readn(fd, &the_int, sizeof(the_int)); 126 | if (ret != sizeof(the_int)) 127 | { 128 | fprintf(stderr, "priv_sock_get_int error"); 129 | exit(EXIT_FAILURE); 130 | } 131 | 132 | return the_int; 133 | } 134 | 135 | /*发送一个字符串*/ 136 | void priv_sock_send_buf(int fd, const char *buf, unsigned int len) 137 | { 138 | /*先发送字符串的长度*/ 139 | priv_sock_send_int(fd, len); 140 | /*发送字符串*/ 141 | int ret = writen(fd, buf, len); 142 | if (ret != (int)len) 143 | { 144 | fprintf(stderr, "priv_sock_send_buf error"); 145 | exit(EXIT_FAILURE); 146 | } 147 | } 148 | /*接收一个字符串*/ 149 | void priv_sock_recv_buf(int fd, char *buf, unsigned int len) 150 | { 151 | unsigned int recv_len = (unsigned int)priv_sock_get_int(fd); 152 | if (recv_len > len) 153 | { 154 | fprintf(stderr, "priv_sock_recv_buf error\n"); 155 | exit(EXIT_FAILURE); 156 | } 157 | 158 | int ret = readn(fd, buf, recv_len); 159 | if (ret != recv_len) 160 | { 161 | fprintf(stderr, "priv_sock_recv_buf error\n"); 162 | exit(EXIT_FAILURE); 163 | } 164 | 165 | } 166 | /*发送文件描述符*/ 167 | void priv_sock_send_fd(int sock_fd, int fd) 168 | { 169 | send_fd(sock_fd, fd); 170 | } 171 | /*接受文件描述符*/ 172 | int priv_sock_recv_fd(int sock_fd) 173 | { 174 | return recv_fd(sock_fd); 175 | } -------------------------------------------------------------------------------- /src/privsock.h: -------------------------------------------------------------------------------- 1 | #ifndef _PRIV_SOCK_H_ 2 | #define _PRIV_SOCK_H_ 3 | 4 | #include "session.h" 5 | 6 | /*FTP服务进程向nobody进程请求的命令*/ 7 | #define PRIV_SOCK_GET_DATA_SOCK 1 8 | #define PRIV_SOCK_PASV_ACTIVE 2 9 | #define PRIV_SOCK_PASV_LISTEN 3 10 | #define PRIV_SOCK_PASV_ACCETP 4 11 | 12 | /*nobody进程对FTP服务进程的应答*/ 13 | #define PRIV_SOCK_RESULT_OK 1 14 | #define PRIV_SOCK_RESULT_BAD 2 15 | 16 | 17 | /*初始化内部进程间通讯通道*/ 18 | void priv_sock_init(session_t *sess); 19 | /*关闭内部进程间通讯通道*/ 20 | void priv_sock_close(session_t *sess); 21 | 22 | /*设置父进程环境*/ 23 | void priv_sock_set_parent_context(session_t *sess); 24 | /*设置子进程环境*/ 25 | void priv_sock_set_child_context(session_t *sess); 26 | 27 | /*发送命令(子->父)*/ 28 | void priv_sock_send_cmd(int fd, char cmd); 29 | /*接受命令(父<-子)*/ 30 | char priv_sock_get_cmd(int fd); 31 | 32 | /*发送结果(父->子)*/ 33 | void priv_sock_send_result(int fd, char res); 34 | /*接收结果(子<-父)*/ 35 | char priv_sock_get_result(int fd); 36 | 37 | /*发送一个整数*/ 38 | void priv_sock_send_int(int fd, int the_int); 39 | /*接收一个整数*/ 40 | int priv_sock_get_int(int fd); 41 | 42 | /*发送一个字符串*/ 43 | void priv_sock_send_buf(int fd, const char *buf, unsigned int len); 44 | /*接收一个字符串*/ 45 | void priv_sock_recv_buf(int fd, char *buf, unsigned int len); 46 | 47 | /*发送文件描述符*/ 48 | void priv_sock_send_fd(int sock_fd, int fd); 49 | /*接受文件描述符*/ 50 | int priv_sock_recv_fd(int sock_fd); 51 | 52 | #endif /*_PRIV_SOCK_H_*/ -------------------------------------------------------------------------------- /src/sckutil.c: -------------------------------------------------------------------------------- 1 | #include "sckutil.h" 2 | 3 | 4 | int getlocalip(char *ip) 5 | { 6 | char host[100] = {0}; 7 | if (gethostname(host, sizeof(host)) < 0) 8 | { 9 | return -1; 10 | } 11 | struct hostent *hp; 12 | if ((hp = gethostbyname(host)) == NULL) 13 | { 14 | return -1; 15 | } 16 | strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr)); 17 | 18 | return 0; 19 | } 20 | 21 | /*获取socket_ip地址 */ 22 | char* get_sock_addr(int sockfd) 23 | { 24 | struct sockaddr_in addr; 25 | socklen_t len = sizeof(addr); 26 | char *ip = NULL; 27 | 28 | int ret = getpeername(sockfd, (struct sockaddr*)&addr, &len); 29 | if (ret == -1) 30 | { 31 | ERR_EXIT("get_sock_addr"); 32 | } 33 | ip = (char*)malloc(32); 34 | 35 | inet_ntop(AF_INET, (void*)&(addr.sin_addr), ip, INET_ADDRSTRLEN); 36 | 37 | return ip; 38 | } 39 | 40 | int tcp_client(const char *address, unsigned short port) 41 | { 42 | int sock; 43 | sock = socket(PF_INET, SOCK_STREAM, 0); 44 | if (sock < 0) 45 | { 46 | ERR_EXIT("tcp_client"); 47 | } 48 | 49 | if (port > 0) 50 | { 51 | struct sockaddr_in localaddr; 52 | memset(&localaddr, 0, sizeof(localaddr)); 53 | localaddr.sin_family = AF_INET; 54 | localaddr.sin_port = htons(port); 55 | inet_pton(AF_INET, address, &localaddr.sin_addr); 56 | 57 | int on = 1; 58 | int ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 59 | if (ret < 0) 60 | { 61 | ret = errno; 62 | ERR_EXIT("setsockopt"); 63 | } 64 | ret = bind(sock, (struct sockaddr*) &localaddr, sizeof(localaddr)); 65 | if (ret < 0) 66 | { 67 | ret = errno; 68 | ERR_EXIT("bind"); 69 | } 70 | } 71 | 72 | return sock; 73 | } 74 | 75 | /* read函数的调用方法 76 | int ret; 77 | ret = read_timeout(fd, 5); 78 | if (ret == 0) 79 | { 80 | read(fd, ...); 81 | } 82 | else if (ret == -1 && errno == ETIMEDOUT) 83 | { 84 | timeout.... 85 | } 86 | else 87 | { 88 | ERR_EXIT("read_timeout"); 89 | } 90 | */ 91 | 92 | /** 93 | * read_timeout - 读超时检测函数,不含读操作 94 | * @fd: 文件描述符 95 | * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 96 | * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT 97 | */ 98 | //客户端端接受报文 99 | /* 100 | * 函数名:read_timeout 101 | * 描述:客服端接受数据 102 | * 参数: 103 | * 104 | * 返回: 105 | * */ 106 | int read_timeout(int fd, unsigned int wait_seconds) 107 | { 108 | int ret = 0; 109 | if (wait_seconds > 0) 110 | { 111 | fd_set read_fdset; 112 | struct timeval timeout; 113 | 114 | FD_ZERO(&read_fdset); 115 | FD_SET(fd, &read_fdset); 116 | 117 | timeout.tv_sec = wait_seconds; 118 | timeout.tv_usec = 0; 119 | 120 | //select返回值三态 121 | //1 若timeout时间到(超时),没有检测到读事件 ret返回=0 122 | //2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理) 123 | //2-1 若返回-1,select出错 124 | //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数 125 | 126 | do 127 | { 128 | ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout); 129 | } while (ret < 0 && errno == EINTR); 130 | 131 | if (ret == 0) 132 | { 133 | ret = -1; 134 | errno = ETIMEDOUT; 135 | } else if (ret == 1) 136 | ret = 0; 137 | } 138 | 139 | return ret; 140 | } 141 | 142 | /** 143 | * write_timeout - 写超时检测函数,不含写操作 144 | * @fd: 文件描述符 145 | * @wait_seconds: 等待超时秒数,如果为0表示不检测超时 146 | * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT 147 | */ 148 | /* 149 | * 函数名:write_timeout 150 | * 描述:客服端接受数据 151 | * 参数: 152 | * 153 | * 返回: 154 | * */ 155 | int write_timeout(int fd, unsigned int wait_seconds) 156 | { 157 | int ret = 0; 158 | if (wait_seconds > 0) 159 | { 160 | fd_set write_fdset; 161 | struct timeval timeout; 162 | 163 | FD_ZERO(&write_fdset); 164 | FD_SET(fd, &write_fdset); 165 | 166 | timeout.tv_sec = wait_seconds; 167 | timeout.tv_usec = 0; 168 | do 169 | { 170 | ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout); 171 | } while (ret < 0 && errno == EINTR); 172 | 173 | if (ret == 0) 174 | { 175 | ret = -1; 176 | errno = ETIMEDOUT; 177 | } else if (ret == 1) 178 | ret = 0; 179 | } 180 | 181 | return ret; 182 | } 183 | 184 | /** 185 | * accept_timeout - 带超时的accept 186 | * @fd: 套接字 187 | * @addr: 输出参数,返回对方地址 188 | * @wait_seconds: 等待超时秒数,如果为0表示正常模式 189 | * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT 190 | */ 191 | int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds) 192 | { 193 | int ret; 194 | socklen_t addrlen = sizeof(struct sockaddr_in); 195 | 196 | if (wait_seconds > 0) 197 | { 198 | fd_set accept_fdset; 199 | struct timeval timeout; 200 | FD_ZERO(&accept_fdset); 201 | FD_SET(fd, &accept_fdset); 202 | timeout.tv_sec = wait_seconds; 203 | timeout.tv_usec = 0; 204 | do 205 | { 206 | ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); 207 | } while (ret < 0 && errno == EINTR); 208 | if (ret == -1) 209 | return -1; 210 | else if (ret == 0) 211 | { 212 | errno = ETIMEDOUT; 213 | return -1; 214 | } 215 | } 216 | 217 | //一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立 218 | //此时再调用accept将不会堵塞 219 | if (addr != NULL) 220 | ret = accept(fd, (struct sockaddr*) addr, &addrlen); //返回已连接套接字 221 | else 222 | ret = accept(fd, NULL, NULL); 223 | if (ret == -1) 224 | { 225 | ret = errno; 226 | printf("func accept() err:%d \n", ret); 227 | return ret; 228 | } 229 | 230 | return ret; 231 | } 232 | 233 | /** 234 | * activate_noblock - 设置I/O为非阻塞模式 235 | * @fd: 文件描符符 236 | */ 237 | /* 238 | * 函数名:activate_nonblock 239 | * 描述:客服端接受数据 240 | * 参数: 241 | * 242 | * 返回: 243 | * */ 244 | int activate_nonblock(int fd) 245 | { 246 | int ret = 0; 247 | /* 248 | * int fcntl(int fd, int cmd, ... \* arg \*) 249 | 获取文件或者修改文件状态 250 | F_GETLK 取得文件锁定的状态。 251 | 返回值 成功则返回0,若有错误则返回-1,错误原因存于errno. 252 | */ 253 | int flags = fcntl(fd, F_GETFL); 254 | if (flags == -1) 255 | { 256 | ret = flags; 257 | printf("func activate_nonblock() err:%d", ret); 258 | return ret; 259 | } 260 | /* 261 | * 按位或,加上没有锁的状态 262 | * */ 263 | flags |= O_NONBLOCK; 264 | /* 265 | * . F_SETFL :设置文件状态标志。 266 | 其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响, 267 | 可以更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。 268 | * */ 269 | ret = fcntl(fd, F_SETFL, flags); 270 | if (ret == -1) 271 | { 272 | printf("func activate_nonblock() err:%d", ret); 273 | return ret; 274 | } 275 | return ret; 276 | } 277 | 278 | 279 | /** 280 | * deactivate_nonblock - 设置I/O为阻塞模式 281 | * @fd: 文件描符符 282 | */ 283 | 284 | int deactivate_nonblock(int fd) 285 | { 286 | int ret = 0; 287 | /* 288 | * int fcntl(int fd, int cmd, ... \* arg \*) 289 | 获取文件或者修改文件状态 290 | F_GETLK 取得文件锁定的状态。 291 | 返回值 成功则返回0,若有错误则返回-1,错误原因存于errno. 292 | */ 293 | int flags = fcntl(fd, F_GETFL); 294 | if (flags == -1) 295 | { 296 | ret = flags; 297 | printf("func deactivate_nonblock() err:%d", ret); 298 | return ret; 299 | } 300 | 301 | /* 302 | * 按位与, NONBLOCK的按位反 并上状态 303 | * */ 304 | flags &= ~O_NONBLOCK; 305 | /* 306 | * . F_SETFL :设置文件状态标志。 307 | 其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响, 308 | 可以更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。 309 | * */ 310 | ret = fcntl(fd, F_SETFL, flags); 311 | if (ret == -1) 312 | { 313 | printf("func deactivate_nonblock() err:%d", ret); 314 | return ret; 315 | } 316 | return ret; 317 | } 318 | 319 | 320 | /** 321 | * connect_timeout - connect 322 | * @fd: 套接字 323 | * @addr: 要连接的对方地址 324 | * @wait_seconds: 等待超时秒数,如果为0表示正常模式 325 | * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT 326 | */ 327 | /* 328 | * 329 | *struct sockaddr_in { 330 | * sa_family_t sin_family; // address family: AF_INET 331 | * in_port_t sin_port; // port in network byte order 332 | * struct in_addr sin_addr; // internet address 333 | *}; 334 | * */ 335 | 336 | /* 337 | * 函数名:connect_timeout 338 | * 描述:客服端接受数据 339 | * 参数: 340 | * 341 | * 返回: 342 | * */ 343 | int connect_timeout(int fd, struct sockaddr_in *addr, 344 | unsigned int wait_seconds) 345 | { 346 | int ret; 347 | //获取socket结构体的大小。 348 | socklen_t addrlen = sizeof(struct sockaddr_in); 349 | //如果传入的等待时间大于0就取消socket的阻塞状态,0则不执行。 350 | if (wait_seconds > 0) 351 | activate_nonblock(fd); 352 | //链接 353 | /* 354 | * int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); 355 | * 356 | * */ 357 | ret = connect(fd, (struct sockaddr*) addr, addrlen); 358 | //EINPROGRESS 正在处理 359 | if (ret < 0 && errno == EINPROGRESS) 360 | { 361 | /* 362 | * void FD_CLR(int fd, fd_set *set); 363 | * int FD_ISSET(int fd, fd_set *set); 364 | * void FD_SET(int fd, fd_set *set); 365 | * void FD_ZERO(fd_set *set); 366 | * */ 367 | //设置监听集合 368 | fd_set connect_fdset; 369 | struct timeval timeout; 370 | //初始化集合 371 | FD_ZERO(&connect_fdset); 372 | //把fd 文件描述符的socket加入监听集合 373 | FD_SET(fd, &connect_fdset); 374 | /* 375 | * struct timeval { 376 | * long tv_sec; // seconds 秒 377 | * long tv_usec; // microseconds 微妙 378 | * }; 379 | * */ 380 | timeout.tv_sec = wait_seconds; 381 | timeout.tv_usec = 0; 382 | do 383 | { 384 | // 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中 385 | ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout); 386 | } while (ret < 0 && errno == EINTR); 387 | if (ret == 0) 388 | { 389 | ret = -1; 390 | /* 391 | * #define ETIMEDOUT 110 // Connection timed out 392 | * Tcp是面向连接的。在程序中表现为,当tcp检测到对端socket不再可 393 | * 用时(不能发出探测包,或探测包没有收到ACK的响应包),select会 394 | * 返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT。 395 | * */ 396 | errno = ETIMEDOUT; 397 | } else if (ret < 0) 398 | return -1; 399 | else if (ret == 1) 400 | { 401 | //printf("22222222222222222\n"); 402 | /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/ 403 | /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */ 404 | int err; 405 | socklen_t socklen = sizeof(err); 406 | //获取socket的状态 407 | int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, 408 | &socklen); 409 | if (sockoptret == -1) 410 | { 411 | return -1; 412 | } 413 | if (err == 0) 414 | { 415 | ret = 0; 416 | } else 417 | { 418 | errno = err; 419 | ret = -1; 420 | } 421 | } 422 | } 423 | if (wait_seconds > 0) 424 | { 425 | deactivate_nonblock(fd); 426 | } 427 | return ret; 428 | } 429 | 430 | /** 431 | * readn - 读取固定字节数 432 | * @fd: 文件描述符 433 | * @buf: 接收缓冲区 434 | * @count: 要读取的字节数 435 | * 成功返回count,失败返回-1,读到EOF返回 0) 453 | { 454 | /*ssize_t read(int fd, void *buf, size_t count); 455 | * 从文件描述符fd中读取count字节存到buf中 456 | * 返回读取字节数的个数。 457 | * */ 458 | if ((nread = read(fd, bufp, nleft)) < 0) 459 | { 460 | /* 461 | * 如果是被信号中断的继续读 462 | * */ 463 | if (errno == EINTR) 464 | continue; 465 | return -1; 466 | } 467 | /* 468 | * 如果输入的读取个数为0,那么返回的读取个数为0 469 | * 不执行任何操作。 470 | * nleft为剩余的需要读取的字节个数。 471 | * 如果为0,说明读到文件尾, 472 | * 473 | * */ 474 | else if (nread == 0) 475 | return count - nleft; 476 | bufp += nread; //将字符指针向前推进已成功读取字符数的大小单位。 477 | nleft -= nread; //剩余的个数减去已经成功读取的字节数。 478 | } 479 | return count; 480 | } 481 | 482 | /** 483 | * writen - 发送固定字节数 484 | * @fd: 文件描述符 485 | * @buf: 发送缓冲区 486 | * @count: 要读取的字节数 487 | * 成功返回count,失败返回-1 488 | */ 489 | /* 490 | * 函数名:writen 491 | * 描述:客服端接受数据 492 | * 参数: 493 | * 494 | * 返回: 495 | * */ 496 | ssize_t writen(int fd, const void *buf, size_t count) 497 | { 498 | size_t nleft = count; //剩余的需要写入的字节数。 499 | ssize_t nwritten; //成功写入的字节数。 500 | char *bufp = (char*) buf; //将缓冲的指针强制转换为字符类型的指针。 501 | /* 502 | * 如果剩余需要写入的字节数大于0则继续 503 | * */ 504 | while (nleft > 0) 505 | { 506 | /* 507 | * ssize_t write(int fd, const void *buf, size_t count); 508 | * fd为需要写入的文件描述符,buf为字符缓存区,count为需要写入的字节数。 509 | * 510 | * */ 511 | if ((nwritten = write(fd, bufp, nleft)) < 0) 512 | { 513 | /* 514 | * 如果是被信号中断的继续读 515 | * */ 516 | if (errno == EINTR) 517 | continue; 518 | return -1; 519 | } else if (nwritten == 0) 520 | continue; 521 | //字符指针推移已经写入成功大小的字节数。 522 | bufp += nwritten; 523 | //剩余的字节数。 524 | nleft -= nwritten; 525 | } 526 | return count; 527 | } 528 | 529 | /** 530 | * recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据 531 | * @sockfd: 套接字 532 | * @buf: 接收缓冲区 533 | * @len: 长度 534 | * 成功返回>=0,失败返回-1 535 | */ 536 | /* 537 | * 函数名:recv_peek 538 | * 描述:客服端接受数据 539 | * 参数: 540 | * 541 | * 返回: 542 | * */ 543 | ssize_t recv_peek(int sockfd, void *buf, size_t len) 544 | { 545 | while (1) 546 | { 547 | /* 548 | * ssize_t recv(int sockfd, void *buf, size_t len, int flags); 549 | * sockfd 套接字 550 | * len 需要读取的长度 551 | * MSG_PEEK只从队列中查看,但不取出。 552 | * 返回接受到的字节的长度,失败返回-1,接受到关闭信号返回0; 553 | * */ 554 | int ret = recv(sockfd, buf, len, MSG_PEEK); 555 | /* 556 | * 如果被信号中断了,继续 557 | * */ 558 | if (ret == -1 && errno == EINTR) 559 | continue; 560 | return ret; 561 | } 562 | } 563 | 564 | /** 565 | * readline - 按行读取数据 566 | * @sockfd: 套接字 567 | * @buf: 接收缓冲区 568 | * @maxline: 每行最大长度 569 | * 成功返回>=0,失败返回-1 570 | */ 571 | ssize_t readline(int sockfd, void *buf, size_t maxline) 572 | { 573 | int ret; 574 | int nread; 575 | char *bufp = buf; 576 | int nleft = maxline; 577 | while (1) 578 | { 579 | ret = recv_peek(sockfd, bufp, nleft); 580 | if (ret < 0) 581 | return ret; 582 | else if (ret == 0) 583 | return ret; 584 | 585 | nread = ret; 586 | int i; 587 | for (i=0; i nleft) 600 | exit(EXIT_FAILURE); 601 | 602 | nleft -= nread; 603 | ret = readn(sockfd, bufp, nread); 604 | if (ret != nread) 605 | exit(EXIT_FAILURE); 606 | 607 | bufp += nread; 608 | } 609 | 610 | return -1; 611 | } 612 | 613 | void send_fd(int sock_fd, int fd_to_send) 614 | { 615 | int ret = 0; 616 | struct msghdr msg; 617 | struct cmsghdr *p_cmsg; 618 | struct iovec vec; 619 | char cmsgbuf[CMSG_SPACE(sizeof(fd_to_send))]; 620 | int *p_fds; 621 | char sendchar =0; 622 | msg.msg_control = cmsgbuf; 623 | msg.msg_controllen = sizeof(cmsgbuf); 624 | p_cmsg = CMSG_FIRSTHDR(&msg); 625 | p_cmsg->cmsg_level = SOL_SOCKET; 626 | p_cmsg->cmsg_type = SCM_RIGHTS; 627 | p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd_to_send)); 628 | p_fds = (int*)CMSG_DATA(p_cmsg); 629 | *p_fds = fd_to_send; 630 | 631 | msg.msg_name = NULL; 632 | msg.msg_namelen = 0; 633 | msg.msg_iov = &vec; 634 | msg.msg_iovlen = 1; 635 | msg.msg_flags = 0; 636 | 637 | vec.iov_base = &sendchar; 638 | vec.iov_len = sizeof(sendchar); 639 | ret = sendmsg(sock_fd, &msg, 0); 640 | if (ret != 1) 641 | { 642 | ERR_EXIT("sendmsg"); 643 | } 644 | } 645 | 646 | int recv_fd(const int sock_fd) 647 | { 648 | int ret = 0; 649 | struct msghdr msg; 650 | char recvchar; 651 | struct iovec vec; 652 | int recv_fd; 653 | char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))]; 654 | struct cmsghdr *p_cmsg; 655 | int *p_fd; 656 | 657 | vec.iov_base = &recvchar; 658 | vec.iov_len = sizeof(recvchar); 659 | 660 | msg.msg_name = NULL; 661 | msg.msg_namelen = 0; 662 | msg.msg_iov = &vec; 663 | msg.msg_iovlen = 1; 664 | msg.msg_control = cmsgbuf; 665 | msg.msg_controllen = sizeof(cmsgbuf); 666 | msg.msg_flags = 0; 667 | 668 | p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg)); 669 | *p_fd = -1; 670 | ret = recvmsg(sock_fd, &msg, 0); 671 | if (ret != 1) 672 | { 673 | ERR_EXIT("recvmsg"); 674 | } 675 | 676 | p_cmsg= CMSG_FIRSTHDR(&msg); 677 | if (p_cmsg == NULL) 678 | { 679 | ERR_EXIT("no passed fd"); 680 | } 681 | p_fd = (int*)CMSG_DATA(p_cmsg); 682 | recv_fd = *p_fd; 683 | if (recv_fd == -1) 684 | { 685 | ERR_EXIT("no passed fd"); 686 | } 687 | 688 | return recv_fd; 689 | } 690 | 691 | /*获取权限位信息*/ 692 | const char* statbuf_get_perms(struct stat *sbuf) 693 | { 694 | static char perms[] = "----------"; /*10位权限位*/ 695 | perms[0] = '?'; 696 | 697 | /*获取文件类型*/ 698 | mode_t mode = sbuf->st_mode; 699 | switch (mode & S_IFMT) 700 | { 701 | case S_IFREG: 702 | perms[0] = '-'; 703 | break; 704 | case S_IFDIR: 705 | perms[0] = 'd'; 706 | break; 707 | case S_IFLNK: 708 | perms[0] = 'l'; 709 | break; 710 | case S_IFIFO: 711 | perms[0] = 'p'; 712 | break; 713 | case S_IFSOCK: 714 | perms[0] = 's'; 715 | break; 716 | case S_IFCHR: 717 | perms[0] = 'c'; 718 | break; 719 | case S_IFBLK: 720 | perms[0] = 'b'; 721 | break; 722 | 723 | }/*end switch*/ 724 | /*文件所有者权限 owner*/ 725 | if (mode & S_IRUSR) 726 | { 727 | perms[1] = 'r'; 728 | } 729 | if (mode & S_IWUSR) 730 | { 731 | perms[2] = 'w'; 732 | } 733 | if (mode & S_IXUSR) 734 | { 735 | perms[3] = 'x'; 736 | } 737 | /*所在组成员权限 group*/ 738 | if (mode & S_IRGRP) 739 | { 740 | perms[4] = 'r'; 741 | } 742 | if (mode & S_IWGRP) 743 | { 744 | perms[5] = 'w'; 745 | } 746 | if (mode & S_IXGRP) 747 | { 748 | perms[6] = 'x'; 749 | } 750 | /*其他访问者权限 other*/ 751 | if (mode & S_IROTH) 752 | { 753 | perms[7] = 'r'; 754 | } 755 | if (mode & S_IWOTH) 756 | { 757 | perms[8] = 'w'; 758 | } 759 | if (mode & S_IXOTH) 760 | { 761 | perms[9] = 'x'; 762 | } 763 | /*特殊权限*/ 764 | if (mode & S_ISUID) 765 | { 766 | perms[3] = (perms[3] == 'x') ? 's' : 'S'; 767 | } 768 | if (mode & S_ISGID) 769 | { 770 | perms[6] = (perms[6] == 'x') ? 's' : 'S'; 771 | } 772 | if (mode & S_ISVTX) 773 | { 774 | perms[9] = (perms[9] == 'x') ? 't' : 'T'; 775 | } 776 | 777 | return perms; 778 | } 779 | 780 | const char* statbuf_get_date(struct stat *sbuf) 781 | { 782 | static char datebuf[64] = {0}; 783 | /*时间格式化*/ 784 | const char *p_data_fomat = "%b %e %H:%M"; 785 | struct timeval tv; 786 | int ret = gettimeofday(&tv, NULL); 787 | if (ret == -1) 788 | { 789 | ERR_EXIT("gettimeofday"); 790 | } 791 | 792 | time_t local_time = tv.tv_sec; 793 | if (sbuf->st_mtime > local_time || (local_time - sbuf->st_mtime) > 182*24*60*60) 794 | { 795 | p_data_fomat = "%b %e %Y"; 796 | } 797 | 798 | 799 | /*将秒转换为结构体*/ 800 | struct tm *p_tm = localtime(&local_time); 801 | /*将tm转为指定格式的字符串*/ 802 | strftime(datebuf, sizeof(datebuf), p_data_fomat, p_tm); 803 | 804 | return datebuf; 805 | } 806 | 807 | int lock_internal(int fd, int lock_type) 808 | { 809 | int ret = 0; 810 | struct flock the_lock; 811 | memset(&the_lock, 0, sizeof(the_lock)); 812 | 813 | the_lock.l_type = lock_type;/*锁类型*/ 814 | the_lock.l_whence = SEEK_SET; /*加锁位置*/ 815 | the_lock.l_start = 0;/*加锁偏移位置*/ 816 | the_lock.l_len = 0; 817 | 818 | /*防止信号中断*/ 819 | do 820 | { 821 | ret = fcntl(fd, F_SETLKW, &the_lock); 822 | } 823 | while (ret < 0 && errno == EINTR); 824 | 825 | return ret; 826 | } 827 | 828 | //读锁 829 | int lock_file_read(int fd) 830 | { 831 | return lock_internal(fd, F_RDLCK); 832 | } 833 | //写锁 834 | int lock_file_write(int fd) 835 | { 836 | return lock_internal(fd, F_WRLCK); 837 | } 838 | //解锁 839 | int unlock_file(int fd) 840 | { 841 | int ret = 0; 842 | struct flock the_lock; 843 | memset(&the_lock, 0, sizeof(the_lock)); 844 | 845 | the_lock.l_type = F_UNLCK;/*锁类型 解锁*/ 846 | the_lock.l_whence = SEEK_SET; /*加锁位置*/ 847 | the_lock.l_start = 0;/*加锁偏移位置*/ 848 | the_lock.l_len = 0; 849 | 850 | /*非阻塞模式*/ 851 | ret = fcntl(fd, F_SETLK, &the_lock); 852 | 853 | return ret; 854 | } 855 | 856 | static struct timeval s_curr_time; 857 | long get_time_sec() 858 | { 859 | int ret = gettimeofday(&s_curr_time, NULL); 860 | if (ret < 0) 861 | { 862 | ERR_EXIT("gettimeofday"); 863 | } 864 | return s_curr_time.tv_sec; 865 | 866 | } 867 | 868 | long get_time_usec() 869 | { 870 | return s_curr_time.tv_usec; 871 | } 872 | 873 | /*睡眠规定时间*/ 874 | void nano_sleep(double seconds) 875 | { 876 | time_t secs = (time_t)seconds;//整数部分 877 | double fractional = seconds - (double)secs;//小数部分 878 | 879 | struct timespec ts; 880 | ts.tv_sec = secs; 881 | ts.tv_nsec = (long)(fractional*(double)1000000000); //纳秒 9个0 882 | 883 | int ret; 884 | do 885 | { 886 | ret = nanosleep(&ts, &ts); 887 | } 888 | while (ret == -1 && ret == EINTR); 889 | } 890 | 891 | //开启fd接收带外数据 892 | void activate_oobinline(int fd) 893 | { 894 | int oob_inline = 1; 895 | int ret; 896 | ret = setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &oob_inline, sizeof(oob_inline)); 897 | if (ret == -1) 898 | { 899 | ERR_EXIT("setsockopt"); 900 | } 901 | } 902 | 903 | /*开启接收SIGURG(当fd有带外数据是,将产生SIGURG信号*/ 904 | /*该函数设定当前进程能够接收fd所产生的SIGURG信号*/ 905 | void activate_sigurg(int fd) 906 | { 907 | int ret = 0; 908 | ret = fcntl(fd, F_SETOWN, getpid()); 909 | if (ret == -1) 910 | { 911 | ERR_EXIT("fcntl"); 912 | } 913 | } -------------------------------------------------------------------------------- /src/sckutil.h: -------------------------------------------------------------------------------- 1 | #ifndef _SCK_UTIL_H_ 2 | #define _SCK_UTIL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define ERR_EXIT(m) \ 23 | do \ 24 | { \ 25 | perror(m); \ 26 | exit(EXIT_FAILURE); \ 27 | } \ 28 | while (0) 29 | 30 | 31 | #define MAX_COMMAND_LINE 1024 32 | #define MAX_COMMAND 32 33 | #define MAX_ARG 1024 34 | 35 | /*获取本机IP*/ 36 | int getlocalip(char *ip); 37 | /*获取socket_ip地址*/ 38 | char* get_sock_addr(int sockfd); 39 | 40 | /*创建客户端监听套接字*/ 41 | int tcp_client(const char *address, unsigned short port); 42 | 43 | /*设置套接字为非阻塞*/ 44 | int activate_nonblock(int fd); 45 | /*设置套接字为阻塞*/ 46 | int deactivate_nonblock(int fd); 47 | 48 | /*带超时的数据接收*/ 49 | int read_timeout(int fd, unsigned int wait_seconds); 50 | /*带超时的数据发送*/ 51 | int write_timeout(int fd, unsigned int wait_seconds); 52 | /*带超时的accept*/ 53 | int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds); 54 | /*带超时的connect*/ 55 | int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds); 56 | 57 | 58 | ssize_t readn(int fd, void *buf, size_t count); 59 | ssize_t writen(int fd, const void *buf, size_t count); 60 | ssize_t recv_peek(int sockfd, void *buf, size_t len); 61 | ssize_t readline(int sockfd, void *buf, size_t maxline); 62 | 63 | /*发送文件描述符*/ 64 | void send_fd(int sock_fd, int fd_to_send); 65 | /*发送接收描述符*/ 66 | int recv_fd(const int sock_fd); 67 | 68 | /*获取权限位信息*/ 69 | const char* statbuf_get_perms(struct stat *sbuf); 70 | const char* statbuf_get_date(struct stat *sbuf); 71 | 72 | //锁 73 | /*加锁*/ 74 | int lock_internal(int fd, int lock_type); 75 | /*加读锁*/ 76 | int lock_file_read(int fd); 77 | /*加写锁*/ 78 | int lock_file_write(int fd); 79 | /*解锁*/ 80 | int unlock_file(int fd); 81 | 82 | //时间 83 | long get_time_sec(); 84 | long get_time_usec(); 85 | void nano_sleep(double seconds); 86 | 87 | //紧急模式 88 | void activate_oobinline(int fd); 89 | /*开启接收SIGURG*/ 90 | void activate_sigurg(int fd); 91 | 92 | 93 | #endif /* _SYS_UTIL_H_ */ 94 | -------------------------------------------------------------------------------- /src/session.c: -------------------------------------------------------------------------------- 1 | #include "commsocket.h" 2 | #include "sckutil.h" 3 | #include "session.h" 4 | #include "pwd.h" 5 | #include "privsock.h" 6 | 7 | 8 | void begin_session(session_t *sess) 9 | { 10 | /*开启接收带外数据*/ 11 | activate_oobinline(sess->ctrl_fd); 12 | 13 | /* 14 | int sockfds[2]; 15 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) < 0) 16 | { 17 | ERR_EXIT("socketpair"); 18 | } 19 | */ 20 | /*初始化内部进程间通讯通道*/ 21 | priv_sock_init(sess); 22 | 23 | pid_t pid; 24 | pid = fork(); 25 | if (pid < 0) 26 | { 27 | ERR_EXIT("fork"); 28 | } 29 | 30 | if (pid == 0) 31 | { 32 | //ftp服务进程 33 | /* 34 | close(sockfds[0]); 35 | sess->child_fd = sockfds[1]; 36 | */ 37 | /*设置子进程环境*/ 38 | priv_sock_set_child_context(sess); 39 | handle_child(sess); 40 | } 41 | else 42 | { 43 | //nobody进程 44 | /* 45 | close(sockfds[1]); 46 | sess->parent_fd = sockfds[0]; 47 | */ 48 | /*设置父进程环境*/ 49 | priv_sock_set_parent_context(sess); 50 | handle_parent(sess); 51 | } 52 | } -------------------------------------------------------------------------------- /src/session.h: -------------------------------------------------------------------------------- 1 | #ifndef _SESSION_H_ 2 | #define _SESSION_H_ 3 | 4 | #include "sckutil.h" 5 | 6 | typedef struct session 7 | { 8 | //控制连接 9 | uid_t uid; 10 | int ctrl_fd; 11 | char cmdline[MAX_COMMAND_LINE]; 12 | char cmd[MAX_COMMAND]; 13 | char arg[MAX_ARG]; 14 | 15 | //数据连接 16 | struct sockaddr_in *port_addr; 17 | int pasv_listen_fd; 18 | int data_fd; 19 | int data_process; /*当前有没处于传输数据的状态*/ 20 | 21 | //限速 22 | unsigned int bw_upload_rate_max; 23 | unsigned int bw_download_rate_max; 24 | long bw_transfer_start_sec; 25 | long bw_transfer_start_usec; 26 | 27 | //父子进程通道 28 | int parent_fd; 29 | int child_fd; 30 | 31 | //FTP协议状态 32 | int is_ascii; 33 | long long restart_pos; //设置断点续传 34 | char *rnfr_name; 35 | int abor_received; 36 | 37 | //连接数限制 38 | unsigned int num_clients; 39 | unsigned int num_this_ip; 40 | 41 | } session_t; 42 | 43 | void begin_session(session_t *sess); 44 | 45 | #endif /*_SESSION_H_*/ -------------------------------------------------------------------------------- /src/str.c: -------------------------------------------------------------------------------- 1 | #include "str.h" 2 | #include "string.h" 3 | #include "stdio.h" 4 | #include "ctype.h" 5 | #include "stdlib.h" 6 | 7 | void str_trim_crlf(char *str) 8 | { 9 | char *p = &(str[strlen(str)-1]); 10 | while (*p == '\r' || *p == '\n') 11 | { 12 | *p = '\0'; 13 | p--; 14 | } 15 | } 16 | 17 | void str_split(const char *str, char *left, char *right, char limit) 18 | { 19 | char *p = strrchr(str, limit); 20 | 21 | if (p == NULL) 22 | { 23 | strcpy(left, str); 24 | } 25 | else 26 | { 27 | strncpy(left, str, p-str); 28 | strcpy(right, p+1); 29 | } 30 | } 31 | 32 | int str_all_space(char *str) 33 | { 34 | if (NULL == str) 35 | { 36 | return 1; 37 | } 38 | int count = 0; 39 | for (size_t i = 0; i < strlen(str); ++i) 40 | { 41 | if (!isspace(str)) 42 | { 43 | str[count++] = str[i]; 44 | } 45 | } 46 | 47 | str[count] = '\0'; 48 | 49 | return (count == 0) ? 1 : 0; 50 | 51 | /* 52 | while (*str) 53 | { 54 | if (!isspace(*str)) 55 | { 56 | return 0; 57 | } 58 | str++; 59 | } 60 | */ 61 | 62 | return 1; 63 | } 64 | 65 | void str_upper(char *str) 66 | { 67 | char *p = str; 68 | while (*p) 69 | { 70 | *p = toupper(*p); 71 | p++; 72 | } 73 | 74 | } 75 | 76 | long long str_tolonglong(const char *str) 77 | { 78 | long long num = 0; 79 | const char *p = str; 80 | int strlength = 0; 81 | 82 | strlength = strlen(str); 83 | if (strlength > 15) 84 | { 85 | return 0; 86 | } 87 | 88 | while (*p) 89 | { 90 | num = num*10 + (*p-48); //ascii '0' = 48 91 | p++; 92 | } 93 | 94 | return num; 95 | //return atoll(str); 96 | } 97 | 98 | unsigned int str_octal_to_uint(const char *str) 99 | { 100 | unsigned int num = 0; 101 | const char *p = str; 102 | 103 | while (*p) 104 | { 105 | char digit = *p; //ascii '0' = 48 106 | if (!isdigit(digit) || digit > 55) //ascii '7' = 55 107 | { 108 | break; 109 | } 110 | 111 | // 判断数字非 '0' 应该只对第一位字符有效 112 | if ((p - str) > 0) 113 | { 114 | num <<= 3; 115 | num += (digit - '0'); 116 | } 117 | p++; 118 | } 119 | 120 | return num; 121 | } 122 | 123 | int yesno_to_int(char *str) 124 | { 125 | if (strcasecmp(str,"YES") == 0 126 | || strcasecmp(str,"TRUE") == 0 127 | || strcasecmp(str,"1") == 0) 128 | { 129 | return 1; 130 | } 131 | if (strcasecmp(str,"NO") == 0 132 | || strcasecmp(str,"FALSE") == 0 133 | || strcasecmp(str,"0") == 0) 134 | { 135 | return 0; 136 | } 137 | 138 | return -1; 139 | } 140 | -------------------------------------------------------------------------------- /src/str.h: -------------------------------------------------------------------------------- 1 | #ifndef _STR_H_ 2 | #define _STR_H_ 3 | 4 | void str_trim_crlf(char *str); 5 | 6 | /*提取键值对*/ 7 | void str_split(const char *str, char *left, char *right, char limit); 8 | 9 | int str_all_space(char *str); 10 | 11 | void str_upper(char *str); 12 | 13 | long long str_tolonglong(const char *str); 14 | 15 | unsigned int str_octal_to_uint(const char *str); 16 | 17 | /* 把字符串yes或no转换成整形, 错误返回-1 */ 18 | int yesno_to_int(char *str); 19 | 20 | #endif /*_STR_H_*/ -------------------------------------------------------------------------------- /src/tunable.c: -------------------------------------------------------------------------------- 1 | #include "tunable.h" 2 | 3 | int tunable_pasv_enable = 1; 4 | int tunable_port_enable = 1; 5 | unsigned int tunable_listen_port = 21; 6 | unsigned int tunable_max_clients = 2000; 7 | unsigned int tunable_max_per_ip = 50; 8 | unsigned int tunable_accept_timeout = 60; 9 | unsigned int tunable_connect_timeout = 60; 10 | unsigned int tunable_idle_session_timeout = 300; 11 | unsigned int tunable_data_connection_timeout = 300; 12 | unsigned int tunable_local_umask = 077; 13 | unsigned int tunable_upload_max_rate = 0; 14 | unsigned int tunable_download_max_rate = 0; 15 | const char *tunable_listen_address; 16 | -------------------------------------------------------------------------------- /src/tunable.h: -------------------------------------------------------------------------------- 1 | #ifndef _TUNABLE_H_ 2 | #define _TUNABLE_H_ 3 | 4 | extern int tunable_pasv_enable; 5 | extern int tunable_port_enable; 6 | extern unsigned int tunable_listen_port; 7 | extern unsigned int tunable_max_clients; 8 | extern unsigned int tunable_max_per_ip; 9 | extern unsigned int tunable_accept_timeout; 10 | extern unsigned int tunable_connect_timeout; 11 | extern unsigned int tunable_idle_session_timeout; 12 | extern unsigned int tunable_data_connection_timeout; 13 | extern unsigned int tunable_local_umask; 14 | extern unsigned int tunable_upload_max_rate; 15 | extern unsigned int tunable_download_max_rate; 16 | extern const char *tunable_listen_address; 17 | 18 | 19 | #endif /*_TUNABLE_H_*/ -------------------------------------------------------------------------------- /tinyftpd.conf: -------------------------------------------------------------------------------- 1 | # alow PASV model 2 | pasv_enable=YES 3 | # 4 | # alow PORT model 5 | port_enable=YES 6 | # 7 | # server listening port 8 | listen_port=5188 9 | # the max number of connecters 10 | max_clients=3 11 | # 12 | # the max number of ip 13 | max_per_ip=2 14 | # 15 | # You may change the default value for timing out a accept timeout. 16 | accept_timeout=60 17 | # 18 | # You may change the default value for timing out a data connection. 19 | connect_timeout=60 20 | # 21 | # You may change the default value for timing out a session connection. 22 | idle_session_timeout=300 23 | # 24 | # You may change the default value for timing out a data_connection connection. 25 | data_connection_timeout=300 26 | # 27 | # Default umask for local users is 077. You may wish to change this to 022, 28 | # if your users expect that (022 is used by most other ftpd's) 29 | local_umask=077 30 | # 31 | # You may change the default value for the max rate of upload 32 | # 33 | upload_max_rate=10240000000 34 | # 35 | # You may change the default value for the max rate of download 36 | # 37 | download_max_rate=2048000000 38 | # 39 | # You may change the ip_addr of server ,if you ignore this option, soft will choose the local addr 40 | # 41 | #listen_address=192.168.150.132 42 | --------------------------------------------------------------------------------