ftp的隐式加密、显式加密、不加密这三种方式有一些区别。
ftp协议可以直接抓包查看,没啥好说的。
ftps抓包无法查看加密的那一部分,因为这个原因,调试显式加密时坑惨了我。

FTP隐式加密流程:

  1. 与服务器信令端口建立TCP连接
  2. TCP连接成功后,直接进行ssl握手
  3. 握手成功后,使用USER/PASS指令进行登录。

涉及数据端口的指令流程,如LIST指令:
使用被动模式:

  1. 发送PASV指令后,得到服务器的数据端口,
  2. 进行数据端口的TCP连接,(注意: 这时还不能进行数据端口的SSL握手)
  3. 发送LIST指令,收到 150 accept data connection
  4. 进行数据端口的SSL握手
  5. 接收数据

FTP显式加密流程:

  1. 与服务器信令端口建立TCP连接
  2. TCP连接成功后,使用AUTH 指令进行加密协商。
  3. 收到 234 的响应码,说明服务器支持显式加密
  4. 进行信令端口的SSL握手
  5. 握手成功后,使用USER/PASS指令进行登录。
  6. 使用PBSZ /PROT指令进行数据端口的加密协商(不进行协商,无法进行数据传输)

涉及数据端口的指令流程,与隐式加密相同,区别在于:在发送LIST等涉及数据端口的指令前,一定要先发送PBSZ/PROT指令进行数据端口的加密等级协商,否则服务器可能断开连接。

下面两个例子都是使用MBEDTLS库的例子改的

隐式加密示例:

/**  SSL client demonstration program**  Copyright The Mbed TLS Contributors*  SPDX-License-Identifier: Apache-2.0**  Licensed under the Apache License, Version 2.0 (the "License"); you may*  not use this file except in compliance with the License.*  You may obtain a copy of the License at**  http://www.apache.org/licenses/LICENSE-2.0**  Unless required by applicable law or agreed to in writing, software*  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT*  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*  See the License for the specific language governing permissions and*  limitations under the License.*/#include "mbedtls/build_info.h"
#include <string.h>#if defined(MBEDTLS_PLATFORM_C)
#include "mbedtls/platform.h"
#else
#include <stdio.h>
#include <stdlib.h>
#define mbedtls_time time
#define mbedtls_time_t time_t
#define mbedtls_fprintf fprintf
#define mbedtls_printf printf
#define mbedtls_exit exit
#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS
#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE
#endif /* MBEDTLS_PLATFORM_C */#if !defined(MBEDTLS_BIGNUM_C) || !defined(MBEDTLS_ENTROPY_C) ||     \!defined(MBEDTLS_SSL_TLS_C) || !defined(MBEDTLS_SSL_CLI_C) ||    \!defined(MBEDTLS_NET_C) || !defined(MBEDTLS_RSA_C) ||            \!defined(MBEDTLS_PEM_PARSE_C) || !defined(MBEDTLS_CTR_DRBG_C) || \!defined(MBEDTLS_X509_CRT_PARSE_C)
int main(void)
{mbedtls_printf("MBEDTLS_BIGNUM_C and/or MBEDTLS_ENTROPY_C and/or ""MBEDTLS_SSL_TLS_C and/or MBEDTLS_SSL_CLI_C and/or ""MBEDTLS_NET_C and/or MBEDTLS_RSA_C and/or ""MBEDTLS_CTR_DRBG_C and/or MBEDTLS_X509_CRT_PARSE_C ""not defined.\n");mbedtls_exit(0);
}
#else#include "mbedtls/net_sockets.h"
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "test/certs.h"#include <string.h>#define SERVER_PORT "990"
#define SERVER_NAME "110.78.86.97"
#define GET_REQUEST "GET / HTTP/1.0\r\n\r\n"#define DEBUG_LEVEL 1static void my_debug(void *ctx, int level,const char *file, int line,const char *str)
{((void)level);mbedtls_fprintf((FILE *)ctx, "%s:%04d: %s", file, line, str);fflush((FILE *)ctx);
}typedef enum
{USER = 1,PASS,LIST,TYPE,PASV,WAIT_DATA_CONNECT,WAIT_HANDSHAKE,IDLE} state;state ftp_state = IDLE;char data_port[10] = {0};int nb_fds = 100;
int do_connect(mbedtls_net_context *server_fd)
{int ret = 0;// uint32_t flags;/** 1. Start the connection*/mbedtls_printf("  . Connecting to tcp/%s/%s...", SERVER_NAME, data_port);fflush(stdout);if ((ret = mbedtls_net_connect(server_fd, SERVER_NAME,data_port, MBEDTLS_NET_PROTO_TCP)) != 0){mbedtls_printf(" failed\n  ! mbedtls_net_connect returned %d\n\n", ret);goto exit;}if (ret == 0){if (nb_fds < server_fd->fd)nb_fds = server_fd->fd;++nb_fds;}mbedtls_printf(" ok\n");exit:return ret;
}int do_handshake(mbedtls_net_context *server_fd, mbedtls_ssl_context *ssl, mbedtls_ssl_config *conf, mbedtls_x509_crt *cacert, mbedtls_ctr_drbg_context *ctr_drbg)
{int ret = 0;uint32_t flags;/** 2. Setup stuff*/mbedtls_printf("  . Setting up the SSL/TLS structure...");fflush(stdout);if ((ret = mbedtls_ssl_config_defaults(conf,MBEDTLS_SSL_IS_CLIENT,MBEDTLS_SSL_TRANSPORT_STREAM,MBEDTLS_SSL_PRESET_DEFAULT)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_config_defaults returned %d\n\n", ret);goto exit;}mbedtls_printf(" ok\n");/* OPTIONAL is not optimal for security,* but makes interop easier in this simplified example */mbedtls_ssl_conf_authmode(conf, MBEDTLS_SSL_VERIFY_OPTIONAL);mbedtls_ssl_conf_ca_chain(conf, cacert, NULL);mbedtls_ssl_conf_rng(conf, mbedtls_ctr_drbg_random, ctr_drbg);mbedtls_ssl_conf_dbg(conf, my_debug, stdout);if ((ret = mbedtls_ssl_setup(ssl, conf)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_setup returned %d\n\n", ret);goto exit;}if ((ret = mbedtls_ssl_set_hostname(ssl, SERVER_NAME)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);goto exit;}mbedtls_ssl_set_bio(ssl, server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);/** 4. Handshake*/mbedtls_printf("  . Performing the SSL/TLS handshake...");fflush(stdout);while ((ret = mbedtls_ssl_handshake(ssl)) != 0){if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){mbedtls_printf(" failed\n  ! mbedtls_ssl_handshake returned -0x%x\n\n", (unsigned int)-ret);goto exit;}}mbedtls_printf(" ok\n");/** 5. Verify the server certificate*/mbedtls_printf("  . Verifying peer X.509 certificate...");/* In real life, we probably want to bail out when ret != 0 */if ((flags = mbedtls_ssl_get_verify_result(ssl)) != 0){#if !defined(MBEDTLS_X509_REMOVE_INFO)char vrfy_buf[512];
#endifmbedtls_printf(" failed\n");#if !defined(MBEDTLS_X509_REMOVE_INFO)mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags);mbedtls_printf("%s\n", vrfy_buf);
#endif}elsembedtls_printf(" ok\n");return 0;
exit:return -1;
}state parse_buf(char *buf, int len)
{// mbedtls_printf( " parse:%d\n",ftp_state);if (strstr(buf, "331 ")){ftp_state = PASS;goto exit;}if (strstr(buf, "226 ") && ftp_state == IDLE){ftp_state = TYPE;mbedtls_printf(" change state to pasv \n");goto exit;}if (strstr(buf, "230 ") && ftp_state == PASS){ftp_state = TYPE;goto exit;}if (strstr(buf, "200 ") && ftp_state == TYPE){ftp_state = PASV;mbedtls_printf(" change state to pasv \n");goto exit;}if (ftp_state == LIST){ftp_state = WAIT_HANDSHAKE;goto exit;}if (strstr(buf, "227 ")){char *p = NULL;if (p = strstr(buf, "110.78.86.97")){char *q = p + 13;printf("%s", q);int first = 0, second = 0;sscanf(q, "%d,%d)", &first, &second);printf("%d %d\n", first, second);int port = 0;port = first * 256 + second;sprintf(data_port, "%d", port);printf("%d %s\n", port, data_port);}ftp_state = LIST;goto exit;}// ftp_state = IDLE;exit:// mbedtls_printf( " %s %d end\n",buf,ftp_state);return ftp_state;
}static void _free(mbedtls_net_context *server_fd, mbedtls_ssl_context *ssl, mbedtls_ssl_config *conf, mbedtls_x509_crt *cacert, mbedtls_ctr_drbg_context *ctr_drbg, mbedtls_entropy_context *entropy)
{mbedtls_ssl_close_notify(ssl);printf("  . Closing the connection...");mbedtls_net_free(server_fd);mbedtls_x509_crt_free(cacert);mbedtls_ssl_free(ssl);mbedtls_ssl_config_free(conf);mbedtls_ctr_drbg_free(ctr_drbg);mbedtls_entropy_free(entropy);
}static void _init(mbedtls_net_context *server_fd, mbedtls_ssl_context *ssl, mbedtls_ssl_config *conf, mbedtls_x509_crt *cacert, mbedtls_ctr_drbg_context *ctr_drbg, mbedtls_entropy_context *entropy)
{const char *pers = "ssl_client1";mbedtls_net_init(server_fd);mbedtls_ssl_init(ssl);mbedtls_ssl_config_init(conf);mbedtls_x509_crt_init(cacert);mbedtls_ctr_drbg_init(ctr_drbg);mbedtls_entropy_init(entropy);int ret = 0;if ((ret = mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, entropy,(const unsigned char *)pers,strlen(pers))) != 0){mbedtls_printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);goto exit;}
exit:return;
}static int write_ssl_data(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
{int ret;mbedtls_printf("\n%s", buf);while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0){if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){mbedtls_printf(" failed\n  ! mbedtls_ssl_write returned %d\n\n", ret);return -1;}}return (0);
}int main(void)
{int ret = 1, len;int exit_code = MBEDTLS_EXIT_FAILURE;mbedtls_net_context server_fd;mbedtls_net_context data_fd;uint32_t flags;unsigned char buf[1024];const char *pers = "ssl_client1";mbedtls_entropy_context entropy;mbedtls_ctr_drbg_context ctr_drbg;mbedtls_ssl_context ssl;mbedtls_ssl_config conf;mbedtls_x509_crt cacert;mbedtls_entropy_context entropy_1;mbedtls_ctr_drbg_context ctr_drbg_1;mbedtls_ssl_context ssl_1;mbedtls_ssl_config conf_1;mbedtls_x509_crt cacert_1;#if defined(MBEDTLS_DEBUG_C)mbedtls_debug_set_threshold(DEBUG_LEVEL);
#endif/** 0. Initialize the RNG and the session data*/mbedtls_net_init(&server_fd);mbedtls_ssl_init(&ssl);mbedtls_ssl_config_init(&conf);mbedtls_x509_crt_init(&cacert);mbedtls_ctr_drbg_init(&ctr_drbg);mbedtls_printf("\n  . Seeding the random number generator...");fflush(stdout);mbedtls_entropy_init(&entropy);if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,(const unsigned char *)pers,strlen(pers))) != 0){mbedtls_printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);goto exit;}mbedtls_printf(" ok\n");/** 0. Initialize certificates*/mbedtls_printf("  . Loading the CA root certificate ...");fflush(stdout);ret = mbedtls_x509_crt_parse(&cacert, (const unsigned char *)mbedtls_test_cas_pem,mbedtls_test_cas_pem_len);if (ret < 0){mbedtls_printf(" failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", (unsigned int)-ret);goto exit;}ret = mbedtls_x509_crt_parse(&cacert_1, (const unsigned char *)mbedtls_test_cas_pem,mbedtls_test_cas_pem_len);if (ret < 0){mbedtls_printf(" failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", (unsigned int)-ret);goto exit;}mbedtls_printf(" ok (%d skipped)\n", ret);/** 1. Start the connection*/mbedtls_printf("  . Connecting to tcp/%s/%s...", SERVER_NAME, SERVER_PORT);fflush(stdout);if ((ret = mbedtls_net_connect(&server_fd, SERVER_NAME,SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0){mbedtls_printf(" failed\n  ! mbedtls_net_connect returned %d\n\n", ret);goto exit;}mbedtls_printf(" ok\n");/** 2. Setup stuff*/mbedtls_printf("  . Setting up the SSL/TLS structure...");fflush(stdout);if ((ret = mbedtls_ssl_config_defaults(&conf,MBEDTLS_SSL_IS_CLIENT,MBEDTLS_SSL_TRANSPORT_STREAM,MBEDTLS_SSL_PRESET_DEFAULT)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_config_defaults returned %d\n\n", ret);goto exit;}mbedtls_printf(" ok\n");/* OPTIONAL is not optimal for security,* but makes interop easier in this simplified example */mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);mbedtls_ssl_conf_dbg(&conf, my_debug, stdout);if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_setup returned %d\n\n", ret);goto exit;}if ((ret = mbedtls_ssl_set_hostname(&ssl, SERVER_NAME)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);goto exit;}mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);/** 4. Handshake*/mbedtls_printf("  . Performing the SSL/TLS handshake...");fflush(stdout);while ((ret = mbedtls_ssl_handshake(&ssl)) != 0){if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){mbedtls_printf(" failed\n  ! mbedtls_ssl_handshake returned -0x%x\n\n", (unsigned int)-ret);goto exit;}}mbedtls_printf(" ok\n");/** 5. Verify the server certificate*/mbedtls_printf("  . Verifying peer X.509 certificate...");/* In real life, we probably want to bail out when ret != 0 */if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0){#if !defined(MBEDTLS_X509_REMOVE_INFO)char vrfy_buf[512];
#endifmbedtls_printf(" failed\n");#if !defined(MBEDTLS_X509_REMOVE_INFO)mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags);mbedtls_printf("%s\n", vrfy_buf);
#endif}elsembedtls_printf(" ok\n");/** 3. Write the GET request*/mbedtls_printf("  > Write to server:");fflush(stdout);/** 7. Read the HTTP response*/mbedtls_printf("  < Read from server:");fflush(stdout);int first = 0;mbedtls_ssl_write(&ssl, "USER admin\r\n", 12);struct timeval tm;struct timeval *tm_ptr = NULL;fd_set read_fds;// fd_set write_fds;int max_wait = 2000;tm.tv_sec = max_wait / 1000;tm.tv_usec = (max_wait % 1000) * 1000;tm_ptr = &tm;if (nb_fds < server_fd.fd)nb_fds = server_fd.fd;if (nb_fds < data_fd.fd)nb_fds = data_fd.fd;++nb_fds;int ok = 0;while (1){FD_ZERO(&read_fds);if (server_fd.fd > 0){FD_SET(server_fd.fd, &read_fds);}if (data_fd.fd > 0){FD_SET(data_fd.fd, &read_fds);}if ((ret = select(nb_fds, &read_fds, NULL, NULL, tm_ptr)) < 0){perror("select");goto exit;}if (FD_ISSET(server_fd.fd, &read_fds)){printf("=====%d ", nb_fds);len = sizeof(buf) - 1;memset(buf, 0, sizeof(buf));mbedtls_net_set_nonblock(&ssl);ret = mbedtls_ssl_read(&ssl, buf, len);if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)continue;if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)break;if (ret < 0){mbedtls_printf("failed\n  ! mbedtls_ssl_read returned %d\n\n", ret);break;}if (ret == 0){mbedtls_printf("\n\nEOF\n\n");break;}len = ret;mbedtls_printf(" %d bytes read\n\n%s", len, (char *)buf);switch (parse_buf(buf, len)){case PASS:mbedtls_ssl_write(&ssl, "PASS admin\r\n", 12);break;case TYPE:mbedtls_ssl_write(&ssl, "TYPE A\r\n", 8);break;case PASV:mbedtls_printf(" in pasv\n");mbedtls_ssl_write(&ssl, "PASV\r\n", 6);break;case LIST:_init(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1, &entropy_1);do_connect(&data_fd);mbedtls_ssl_write(&ssl, "NLST .\r\n", 8);if (do_handshake(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1) == 0){mbedtls_printf("success data handshake\n ");mbedtls_printf("data_fd.fd is : %d\n ", data_fd.fd);}break;case WAIT_HANDSHAKE:break;default:break;}}if (data_fd.fd!=-1 && FD_ISSET(data_fd.fd, &read_fds)){char buf1[1024] = {0};mbedtls_net_set_nonblock(&ssl_1);int ret1 = mbedtls_ssl_read(&ssl_1, buf1, 1023);if (ret1 > 0){printf("recv:\n%s\n", buf1);}else if (ret1 == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY){printf("ret1 < 0 :%d\n", ret1);_free(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1, &entropy_1);mbedtls_net_set_nonblock(&ssl);ftp_state = IDLE;}else if (ret1 == 0){printf("ret1 = 0 :%d\n", ret1);_free(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1, &entropy_1);mbedtls_net_set_nonblock(&ssl);ftp_state = IDLE;}}}mbedtls_ssl_close_notify(&ssl);exit_code = MBEDTLS_EXIT_SUCCESS;exit:#ifdef MBEDTLS_ERROR_Cif (exit_code != MBEDTLS_EXIT_SUCCESS){char error_buf[100];mbedtls_strerror(ret, error_buf, 100);mbedtls_printf("Last error was: %d - %s\n\n", ret, error_buf);}
#endifmbedtls_net_free(&server_fd);mbedtls_x509_crt_free(&cacert);mbedtls_ssl_free(&ssl);mbedtls_ssl_config_free(&conf);mbedtls_ctr_drbg_free(&ctr_drbg);mbedtls_entropy_free(&entropy);#if defined(_WIN32)mbedtls_printf("  + Press Enter to exit this program.\n");fflush(stdout);getchar();
#endifmbedtls_exit(exit_code);
}
#endif /* MBEDTLS_BIGNUM_C && MBEDTLS_ENTROPY_C && MBEDTLS_SSL_TLS_C && \MBEDTLS_SSL_CLI_C && MBEDTLS_NET_C && MBEDTLS_RSA_C &&        \MBEDTLS_PEM_PARSE_C && MBEDTLS_CTR_DRBG_C && MBEDTLS_X509_CRT_PARSE_C */

显式加密示例:

/**  SSL client demonstration program**  Copyright The Mbed TLS Contributors*  SPDX-License-Identifier: Apache-2.0**  Licensed under the Apache License, Version 2.0 (the "License"); you may*  not use this file except in compliance with the License.*  You may obtain a copy of the License at**  http://www.apache.org/licenses/LICENSE-2.0**  Unless required by applicable law or agreed to in writing, software*  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT*  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*  See the License for the specific language governing permissions and*  limitations under the License.*/#include "mbedtls/build_info.h"
#include <string.h>#if defined(MBEDTLS_PLATFORM_C)
#include "mbedtls/platform.h"
#else
#include <stdio.h>
#include <stdlib.h>
#define mbedtls_time time
#define mbedtls_time_t time_t
#define mbedtls_fprintf fprintf
#define mbedtls_printf printf
#define mbedtls_exit exit
#define MBEDTLS_EXIT_SUCCESS EXIT_SUCCESS
#define MBEDTLS_EXIT_FAILURE EXIT_FAILURE
#endif /* MBEDTLS_PLATFORM_C */#if !defined(MBEDTLS_BIGNUM_C) || !defined(MBEDTLS_ENTROPY_C) ||     \!defined(MBEDTLS_SSL_TLS_C) || !defined(MBEDTLS_SSL_CLI_C) ||    \!defined(MBEDTLS_NET_C) || !defined(MBEDTLS_RSA_C) ||            \!defined(MBEDTLS_PEM_PARSE_C) || !defined(MBEDTLS_CTR_DRBG_C) || \!defined(MBEDTLS_X509_CRT_PARSE_C)
int main(void)
{mbedtls_printf("MBEDTLS_BIGNUM_C and/or MBEDTLS_ENTROPY_C and/or ""MBEDTLS_SSL_TLS_C and/or MBEDTLS_SSL_CLI_C and/or ""MBEDTLS_NET_C and/or MBEDTLS_RSA_C and/or ""MBEDTLS_CTR_DRBG_C and/or MBEDTLS_X509_CRT_PARSE_C ""not defined.\n");mbedtls_exit(0);
}
#else#include "mbedtls/net_sockets.h"
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "test/certs.h"#include <string.h>#define SERVER_PORT "21"
#define SERVER_NAME "121.75.13.180"
#define GET_REQUEST "GET / HTTP/1.0\r\n\r\n"#define DEBUG_LEVEL 1static void my_debug(void *ctx, int level,const char *file, int line,const char *str)
{((void)level);mbedtls_fprintf((FILE *)ctx, "%s:%04d: %s", file, line, str);fflush((FILE *)ctx);
}typedef enum
{USER = 1,PASS,LIST,TYPE,PASV,WAIT_DATA_CONNECT,WAIT_HANDSHAKE,CTRL_HANDSHAKE,PBSZ,PROT,IDLE} state;state ftp_state = IDLE;char data_port[10] = {0};int nb_fds = 100;
int do_connect(mbedtls_net_context *server_fd)
{int ret = 0;// uint32_t flags;/** 1. Start the connection*/mbedtls_printf("  . Connecting to tcp/%s/%s...", SERVER_NAME, data_port);fflush(stdout);if ((ret = mbedtls_net_connect(server_fd, SERVER_NAME,data_port, MBEDTLS_NET_PROTO_TCP)) != 0){mbedtls_printf(" failed\n  ! mbedtls_net_connect returned %d\n\n", ret);goto exit;}if (ret == 0){if (nb_fds < server_fd->fd)nb_fds = server_fd->fd;++nb_fds;}mbedtls_printf(" ok\n");exit:return ret;
}int do_handshake(mbedtls_net_context *server_fd, mbedtls_ssl_context *ssl, mbedtls_ssl_config *conf, mbedtls_x509_crt *cacert, mbedtls_ctr_drbg_context *ctr_drbg)
{int ret = 0;uint32_t flags;/** 2. Setup stuff*/mbedtls_printf("  . Setting up the SSL/TLS structure...");fflush(stdout);if ((ret = mbedtls_ssl_config_defaults(conf,MBEDTLS_SSL_IS_CLIENT,MBEDTLS_SSL_TRANSPORT_STREAM,MBEDTLS_SSL_PRESET_DEFAULT)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_config_defaults returned %d\n\n", ret);goto exit;}mbedtls_printf(" ok\n");/* OPTIONAL is not optimal for security,* but makes interop easier in this simplified example */mbedtls_ssl_conf_authmode(conf, MBEDTLS_SSL_VERIFY_OPTIONAL);mbedtls_ssl_conf_ca_chain(conf, cacert, NULL);mbedtls_ssl_conf_rng(conf, mbedtls_ctr_drbg_random, ctr_drbg);mbedtls_ssl_conf_dbg(conf, my_debug, stdout);if ((ret = mbedtls_ssl_setup(ssl, conf)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_setup returned %d\n\n", ret);goto exit;}if ((ret = mbedtls_ssl_set_hostname(ssl, SERVER_NAME)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);goto exit;}mbedtls_ssl_set_bio(ssl, server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);/** 4. Handshake*/mbedtls_printf("  . Performing the SSL/TLS handshake...");fflush(stdout);while ((ret = mbedtls_ssl_handshake(ssl)) != 0){if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){mbedtls_printf(" failed\n  ! mbedtls_ssl_handshake returned -0x%x\n\n", (unsigned int)-ret);goto exit;}}mbedtls_printf(" ok\n");/** 5. Verify the server certificate*/mbedtls_printf("  . Verifying peer X.509 certificate...");/* In real life, we probably want to bail out when ret != 0 */if ((flags = mbedtls_ssl_get_verify_result(ssl)) != 0){#if !defined(MBEDTLS_X509_REMOVE_INFO)char vrfy_buf[512];
#endifmbedtls_printf(" failed\n");#if !defined(MBEDTLS_X509_REMOVE_INFO)mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags);mbedtls_printf("%s\n", vrfy_buf);
#endif}elsembedtls_printf(" ok\n");return 0;
exit:return -1;
}state parse_buf(char *buf, int len)
{// mbedtls_printf( " parse:%d\n",ftp_state);if (strstr(buf, "331 ")){ftp_state = PASS;goto exit;}if (strstr(buf, "234 ")){ftp_state = CTRL_HANDSHAKE;mbedtls_printf(" change state to CTRL_HANDSHAKE; \n");goto exit;}if (strstr(buf, "226 ") && ftp_state == IDLE){ftp_state = TYPE;mbedtls_printf(" change state to pasv \n");goto exit;}if (strstr(buf, "230 ") && ftp_state == PASS){// ftp_state=TYPE;// ftp_state=PBSZ;ftp_state = PROT; //没有这个会报7200的错误goto exit;}if (strstr(buf, "200 ") && ftp_state == TYPE){ftp_state = PASV;mbedtls_printf(" change state to pasv \n");goto exit;}if (strstr(buf, "200 ") && ftp_state == PBSZ){ftp_state = PROT;mbedtls_printf(" change state to pbsz \n");goto exit;}if (strstr(buf, "200 ") && ftp_state == PROT){ftp_state = TYPE;mbedtls_printf(" change state to prot \n");goto exit;}if (ftp_state == LIST){ftp_state = WAIT_HANDSHAKE;goto exit;}if (strstr(buf, "227 ")){char *p = NULL;if (p = strstr(buf, "121.75.13.180")){char *q = p + 14;printf("%s", q);int first = 0, second = 0;sscanf(q, "%d,%d)", &first, &second);printf("%d %d\n", first, second);int port = 0;port = first * 256 + second;sprintf(data_port, "%d", port);printf("%d %s\n", port, data_port);}ftp_state = LIST;goto exit;}// ftp_state = IDLE;exit:// mbedtls_printf( " %s %d end\n",buf,ftp_state);return ftp_state;
}static void _free(mbedtls_net_context *server_fd, mbedtls_ssl_context *ssl, mbedtls_ssl_config *conf, mbedtls_x509_crt *cacert, mbedtls_ctr_drbg_context *ctr_drbg, mbedtls_entropy_context *entropy)
{mbedtls_ssl_close_notify(ssl);mbedtls_net_free(server_fd);mbedtls_x509_crt_free(cacert);mbedtls_ssl_free(ssl);mbedtls_ssl_config_free(conf);mbedtls_ctr_drbg_free(ctr_drbg);mbedtls_entropy_free(entropy);
}static void _init(mbedtls_net_context *server_fd, mbedtls_ssl_context *ssl, mbedtls_ssl_config *conf, mbedtls_x509_crt *cacert, mbedtls_ctr_drbg_context *ctr_drbg, mbedtls_entropy_context *entropy)
{const char *pers = "ssl_client1";mbedtls_net_init(server_fd);mbedtls_ssl_init(ssl);mbedtls_ssl_config_init(conf);mbedtls_x509_crt_init(cacert);mbedtls_ctr_drbg_init(ctr_drbg);mbedtls_entropy_init(entropy);int ret = 0;if ((ret = mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, entropy,(const unsigned char *)pers,strlen(pers))) != 0){mbedtls_printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);goto exit;}
exit:return;
}static int write_ssl_data(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
{int ret;mbedtls_printf("\n%s", buf);while (len && (ret = mbedtls_ssl_write(ssl, buf, len)) <= 0){if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){mbedtls_printf(" failed\n  ! mbedtls_ssl_write returned %d\n\n", ret);return -1;}}return (0);
}static int do_handshake_(mbedtls_ssl_context *ssl)
{int ret;uint32_t flags;unsigned char buf[1024];memset(buf, 0, 1024);/** 4. Handshake*/mbedtls_printf("  . Performing the SSL/TLS handshake...");fflush(stdout);while ((ret = mbedtls_ssl_handshake(ssl)) != 0){if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){#if defined(MBEDTLS_ERROR_C)mbedtls_strerror(ret, (char *)buf, 1024);
#endifmbedtls_printf(" failed\n  ! mbedtls_ssl_handshake returned %d: %s\n\n", ret, buf);return (-1);}}mbedtls_printf(" ok\n    [ Ciphersuite is %s ]\n",mbedtls_ssl_get_ciphersuite(ssl));/** 5. Verify the server certificate*/mbedtls_printf("  . Verifying peer X.509 certificate...");/* In real life, we probably want to bail out when ret != 0 */if ((flags = mbedtls_ssl_get_verify_result(ssl)) != 0){#if !defined(MBEDTLS_X509_REMOVE_INFO)char vrfy_buf[512];
#endifmbedtls_printf(" failed\n");#if !defined(MBEDTLS_X509_REMOVE_INFO)mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags);mbedtls_printf("%s\n", vrfy_buf);
#endif}elsembedtls_printf(" ok\n");#if !defined(MBEDTLS_X509_REMOVE_INFO)mbedtls_printf("  . Peer certificate information    ...\n");mbedtls_x509_crt_info((char *)buf, sizeof(buf) - 1, "      ",mbedtls_ssl_get_peer_cert(ssl));mbedtls_printf("%s\n", buf);
#endifreturn (0);
}int main(void)
{int ret = 1, len;int exit_code = MBEDTLS_EXIT_FAILURE;mbedtls_net_context server_fd;mbedtls_net_context data_fd;uint32_t flags;unsigned char buf[1024];const char *pers = "ssl_client1";mbedtls_entropy_context entropy;mbedtls_ctr_drbg_context ctr_drbg;mbedtls_ssl_context ssl;mbedtls_ssl_config conf;mbedtls_x509_crt cacert;mbedtls_entropy_context entropy_1;mbedtls_ctr_drbg_context ctr_drbg_1;mbedtls_ssl_context ssl_1;mbedtls_ssl_config conf_1;mbedtls_x509_crt cacert_1;#if defined(MBEDTLS_DEBUG_C)mbedtls_debug_set_threshold(DEBUG_LEVEL);
#endif/** 0. Initialize the RNG and the session data*/mbedtls_net_init(&server_fd);mbedtls_ssl_init(&ssl);mbedtls_ssl_config_init(&conf);mbedtls_x509_crt_init(&cacert);mbedtls_ctr_drbg_init(&ctr_drbg);mbedtls_printf("\n  . Seeding the random number generator...");fflush(stdout);mbedtls_entropy_init(&entropy);if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,(const unsigned char *)pers,strlen(pers))) != 0){mbedtls_printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);goto exit;}mbedtls_printf(" ok\n");/** 0. Initialize certificates*/mbedtls_printf("  . Loading the CA root certificate ...");fflush(stdout);ret = mbedtls_x509_crt_parse(&cacert, (const unsigned char *)mbedtls_test_cas_pem,mbedtls_test_cas_pem_len);if (ret < 0){mbedtls_printf(" failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", (unsigned int)-ret);goto exit;}ret = mbedtls_x509_crt_parse(&cacert_1, (const unsigned char *)mbedtls_test_cas_pem,mbedtls_test_cas_pem_len);if (ret < 0){mbedtls_printf(" failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", (unsigned int)-ret);goto exit;}mbedtls_printf(" ok (%d skipped)\n", ret);/** 1. Start the connection*/mbedtls_printf("  . Connecting to tcp/%s/%s...", SERVER_NAME, SERVER_PORT);fflush(stdout);if ((ret = mbedtls_net_connect(&server_fd, SERVER_NAME,SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0){mbedtls_printf(" failed\n  ! mbedtls_net_connect returned %d\n\n", ret);goto exit;}mbedtls_printf(" ok\n");/** 2. Setup stuff*/mbedtls_printf("  . Setting up the SSL/TLS structure...");fflush(stdout);if ((ret = mbedtls_ssl_config_defaults(&conf,MBEDTLS_SSL_IS_CLIENT,MBEDTLS_SSL_TRANSPORT_STREAM,MBEDTLS_SSL_PRESET_DEFAULT)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_config_defaults returned %d\n\n", ret);goto exit;}mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);mbedtls_ssl_conf_max_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);mbedtls_printf(" ok\n");/* OPTIONAL is not optimal for security,* but makes interop easier in this simplified example */mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);mbedtls_ssl_conf_dbg(&conf, my_debug, stdout);if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_setup returned %d\n\n", ret);goto exit;}if ((ret = mbedtls_ssl_set_hostname(&ssl, SERVER_NAME)) != 0){mbedtls_printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);goto exit;}mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);/** 4. Handshake*/mbedtls_printf("  . Performing the SSL/TLS handshake...");fflush(stdout);mbedtls_printf(" ok\n");/** 5. Verify the server certificate*/mbedtls_printf("  . Verifying peer X.509 certificate...");/* In real life, we probably want to bail out when ret != 0 */if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0){#if !defined(MBEDTLS_X509_REMOVE_INFO)char vrfy_buf[512];
#endifmbedtls_printf(" failed\n");#if !defined(MBEDTLS_X509_REMOVE_INFO)mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags);mbedtls_printf("%s\n", vrfy_buf);
#endif}elsembedtls_printf(" ok\n");/** 3. Write the GET request*/mbedtls_printf("  > Write to server:");fflush(stdout);/** 7. Read the HTTP response*/mbedtls_printf("  < Read from server:");fflush(stdout);int first = 0;// mbedtls_ssl_write( &ssl, "USER admin\r\n", 12);// mbedtls_net_send( &server_fd, "AUTH TLS\r\n",10 );mbedtls_net_send(&server_fd, "AUTH TLS\r\n", 10);struct timeval tm;struct timeval *tm_ptr = NULL;fd_set read_fds;// fd_set write_fds;int max_wait = 2000;tm.tv_sec = max_wait / 1000;tm.tv_usec = (max_wait % 1000) * 1000;tm_ptr = &tm;int encrypt = 0;if (nb_fds < server_fd.fd)nb_fds = server_fd.fd;if (nb_fds < data_fd.fd)nb_fds = data_fd.fd;++nb_fds;int ok = 0;while (1){FD_ZERO(&read_fds);if (server_fd.fd > 0){FD_SET(server_fd.fd, &read_fds);}if(data_fd.fd > 0){FD_SET( data_fd.fd, &read_fds );}if ((ret = select(nb_fds, &read_fds, NULL, NULL, tm_ptr)) < 0){perror("select");goto exit;}if (data_fd.fd != -1 && FD_ISSET(data_fd.fd, &read_fds)){char buf1[1024] = {0};mbedtls_net_set_nonblock(&ssl_1);int ret1 = mbedtls_ssl_read(&ssl_1, buf1, 1023);if (ret1 > 0){printf("recv:\n%s\n", buf1);}else if (ret1 == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY){printf("ret1 < 0 :%d\n", ret1);_free(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1, &entropy_1);mbedtls_net_set_nonblock(&ssl);ftp_state = IDLE;}else if (ret1 == 0){printf("ret1 = 0 :%d\n", ret1);_free(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1, &entropy_1);mbedtls_net_set_nonblock(&ssl);ftp_state = IDLE;}}if (FD_ISSET(server_fd.fd, &read_fds)){printf("=====%d ", nb_fds);len = sizeof(buf) - 1;memset(buf, 0, sizeof(buf));mbedtls_net_set_nonblock(&ssl);if (encrypt == 0){ret = mbedtls_net_recv(&server_fd, buf, len);}else{ret = mbedtls_ssl_read(&ssl, buf, len);}if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)continue;if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)break;if (ret < 0){mbedtls_printf("failed\n  ! mbedtls_ssl_read returned %d\n\n", ret);break;}if (ret == 0){mbedtls_printf("\n\nEOF\n\n");break;}len = ret;mbedtls_printf(" %d bytes read\n\n%s", len, (char *)buf);switch (parse_buf(buf, len)){case CTRL_HANDSHAKE:do_handshake_(&ssl);if (write_ssl_data(&ssl, "USER root\r\n", 11) == 0)mbedtls_printf("write success\n");ftp_state = USER;encrypt = 1;break;case PASS:mbedtls_ssl_write(&ssl, "PASS admin12345.\r\n", 18);break;case TYPE:mbedtls_ssl_write(&ssl, "TYPE A\r\n", 8);break;case PBSZ:mbedtls_ssl_write(&ssl, "PBSZ 0\r\n", 8);break;case PROT:mbedtls_ssl_write(&ssl, "PROT P\r\n", 8);break;case PASV:mbedtls_printf(" in pasv\n");mbedtls_ssl_write(&ssl, "PASV\r\n", 6);break;case LIST:mbedtls_net_set_nonblock(&ssl_1);_init(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1, &entropy_1);do_connect(&data_fd);mbedtls_printf(" in list\n");if (write_ssl_data(&ssl, "LIST .\r\n", 8) != 0){mbedtls_printf("write fail\n ");}break;case WAIT_HANDSHAKE:if (do_handshake(&data_fd, &ssl_1, &conf_1, &cacert_1, &ctr_drbg_1) == 0){mbedtls_printf("success data handshake\n ");}break;default:break;}}}mbedtls_ssl_close_notify(&ssl);exit_code = MBEDTLS_EXIT_SUCCESS;exit:#ifdef MBEDTLS_ERROR_Cif (exit_code != MBEDTLS_EXIT_SUCCESS){char error_buf[100];mbedtls_strerror(ret, error_buf, 100);mbedtls_printf("Last error was: %d - %s\n\n", ret, error_buf);}
#endifmbedtls_net_free(&server_fd);mbedtls_x509_crt_free(&cacert);mbedtls_ssl_free(&ssl);mbedtls_ssl_config_free(&conf);mbedtls_ctr_drbg_free(&ctr_drbg);mbedtls_entropy_free(&entropy);#if defined(_WIN32)mbedtls_printf("  + Press Enter to exit this program.\n");fflush(stdout);getchar();
#endifmbedtls_exit(exit_code);
}
#endif /* MBEDTLS_BIGNUM_C && MBEDTLS_ENTROPY_C && MBEDTLS_SSL_TLS_C && \MBEDTLS_SSL_CLI_C && MBEDTLS_NET_C && MBEDTLS_RSA_C &&        \MBEDTLS_PEM_PARSE_C && MBEDTLS_CTR_DRBG_C && MBEDTLS_X509_CRT_PARSE_C */

使用mbedtls库实现ftps协议相关推荐

  1. stm32 lwip 如何发送不出_mbedtls | 移植mbedtls库到STM32裸机的两种方法

    一.mbedtls 开源库 1. mbedtls是什么 Mbed TLS是一个开源.可移植.易于使用.代码可读性高的SSL库.可实现加密原语,X.509证书操作以及SSL / TLS和 DTLS 协议 ...

  2. python调用库实现返回ping的时延_python网络作业:使用python的socket库实现ICMP协议的ping...

    ICMP ping是您遇到过的最常见的网络扫描类型. 打开命令行提示符或终端并输入ping www.google.com非常容易. 为什么要在python中实现? 很多名牌大学喜欢考试用python的 ...

  3. 从入门到入土:基于Python|ACK|FIN|Null|Xmas|windows|扫描|端口扫描|scapy库编写|icmp协议探测主机|对开放端口和非开放端口完成半连接扫描|全连接扫描|

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  4. Windows下使用DCMTK开源库对DICOM协议的医学图像进行解析与显示

    DICOM(Digital Imaging and Communications in Medicine),是用于医学影像处理.储存.打印.传输的一组通用标准协定.目前,被广泛应用于放射医疗,心血管成 ...

  5. mbedtls 库基础及其应用

    文章目录 1.引言 1.1 为什么要加密 1.2 SSL/TLS协议的历史 2.SSL/TLS演化 2.1 明文时代 2.2 对称加密时代 2.3 非对称加密时代 2.4 公证时代 2.5 TLS协议 ...

  6. Linux基础之Libcurl库之HTTPS协议实现人脸识别

    目录 一.前言 二.编译openssl支持libcurl的https访问 三.base64编码 四.封装base64编码函数并人脸识别成功 五.车牌识别 六.总结 一.前言 上一篇文章我们调用libc ...

  7. 基于libusb库、uac协议,获取Audio声音数据

    android_usbaudio 基于libusb,实现无驱动获取USBAudio 期望实现的功能: 通过libusb获取USBAudio数据,无需SELinux声卡权限 部分摄像头无法获取音频问题解 ...

  8. stm32f103使用hal库读取sbus协议(乐迪at9s航模遥控器)

    在51黑论坛上找到了标准库的,这里用hal库移植了一下,原理是一样的,亲测能用,sbus转换接线用的8050,搜一下原理图就可.

  9. Java基于Snmp4j库实现SNMP协议的调用

    在进行SNMP协议调用之前,要先保证主机和目的机器可以通信,可以用ping工具来尝试连通性,如果可以通信,可直接跳转到第二章节和第三章节查看协议的调用:如果不能通信,即不在同一个网段,那么需要先按照第 ...

  10. 接口测试自动化之坑二:如何使用requests库请求https协议,解决提示报错和Unverified HTTPS request警告warning问题

    1.首先在请求的requests中增加一条verify=False设置次参数的目的是跳过SSL证书问题 2.设置步骤一后,控制台会有Unverified HTTPS request报错信息 解决办法: ...

最新文章

  1. Red Hat Linux 5.2 14T大文件系统 分区过程
  2. 物理光学10 相干光与相干性
  3. php连接plc,PLC 几种常见的连接口和通讯协议
  4. linux环境变量显示、添加、删除
  5. 企业选择数据中心的建议
  6. 通过cookie保存并读取用户登录信息
  7. android EditText光标位置,光标样式,EditText限制输入内容,软键盘遮挡的EditText,搜索框,限制输入表情
  8. 加加减减的奥秘——从数学到魔术的思考(二)
  9. 转:神经网络编程入门
  10. Firebug无法添加到最新版firefox55.0.*中解决办法
  11. JavaScript小技巧总结
  12. Android打开相机进行人脸识别,使用虹软人脸识别引擎
  13. 一个小小的签到功能,到底用MySQL还是Redis?
  14. 传奇修改map地图教程_传奇服务端内NPC添加MAP地图说明
  15. 零基础入门渗透测试教程
  16. Ubuntu18.04安装PX4并与ROS联合实验
  17. 巴斯大学计算机科学研究生,巴斯大学计算机科学.pdf
  18. Dijkstra算法(迪杰斯特拉算法)
  19. table td 调整margin无效
  20. android进入微信加好友页面,Xposed-微信自动加好友功能实现2--自动跳转验证申请页面...

热门文章

  1. chapter 1 JS简介,核心语法,变量
  2. 实用的网站、工具(科研学术、wps、作图、教程和文档、在线开发工具、在线编程学习、文档笔记工具、办公工具、写作、设计制作类、素材库)
  3. Windows 10 低版本驱动数字签名更新的必要性
  4. TFIDF算法Java实现
  5. 修改vscode图标
  6. python调用鼠标驱动_python+selenium 鼠标事件操作方法
  7. 考研408(操作系统、计算机组成原理、数据结构、计算机网络)
  8. 最全面的Kano模型详解,及Kano模型为何是5种需求?
  9. MySQL over函数的用法
  10. Twaver-HTML5基础学习(2)基本数据元素(Data)