/* * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "picotls.h" #include "picotls/openssl.h" #include "util.h" static void shift_buffer(ptls_buffer_t *buf, size_t delta) { if (delta != 0) { assert(delta <= buf->off); if (delta != buf->off) memmove(buf->base, buf->base + delta, buf->off - delta); buf->off -= delta; } } static int handle_connection(int sockfd, ptls_context_t *ctx, const char *server_name, const char *input_file, ptls_handshake_properties_t *hsprop) { ptls_t *tls = ptls_new(ctx, server_name == NULL); ptls_buffer_t rbuf, encbuf, ptbuf; char bytebuf[16384]; enum { IN_HANDSHAKE, IN_1RTT, IN_SHUTDOWN } state = IN_HANDSHAKE; int inputfd = 0, ret = 0; size_t early_bytes_sent = 0; ssize_t ioret; ptls_buffer_init(&rbuf, "", 0); ptls_buffer_init(&encbuf, "", 0); ptls_buffer_init(&ptbuf, "", 0); fcntl(sockfd, F_SETFL, O_NONBLOCK); if (input_file != NULL) { if ((inputfd = open(input_file, O_RDONLY)) == -1) { fprintf(stderr, "failed to open file:%s:%s\n", input_file, strerror(errno)); ret = 1; goto Exit; } } if (server_name != NULL) { ptls_set_server_name(tls, server_name, 0); if ((ret = ptls_handshake(tls, &encbuf, NULL, NULL, hsprop)) != PTLS_ERROR_IN_PROGRESS) { fprintf(stderr, "ptls_handshake:%d\n", ret); ret = 1; goto Exit; } } while (1) { /* check if data is available */ fd_set readfds, writefds, exceptfds; int maxfd = 0; struct timeval timeout; do { FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_SET(sockfd, &readfds); if (encbuf.off != 0) FD_SET(sockfd, &writefds); FD_SET(sockfd, &exceptfds); maxfd = sockfd + 1; if (inputfd != -1) { FD_SET(inputfd, &readfds); FD_SET(inputfd, &exceptfds); if (maxfd <= inputfd) maxfd = inputfd + 1; } timeout.tv_sec = encbuf.off != 0 ? 0 : 3600; timeout.tv_usec = 0; } while (select(maxfd, &readfds, &writefds, &exceptfds, &timeout) == -1); /* consume incoming messages */ if (FD_ISSET(sockfd, &readfds) || FD_ISSET(sockfd, &exceptfds)) { size_t off = 0, leftlen; while ((ioret = read(sockfd, bytebuf, sizeof(bytebuf))) == -1 && errno == EINTR) ; if (ioret == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) { /* no data */ ioret = 0; } else if (ioret <= 0) { goto Exit; } while ((leftlen = ioret - off) != 0) { if (state == IN_HANDSHAKE) { if ((ret = ptls_handshake(tls, &encbuf, bytebuf + off, &leftlen, hsprop)) == 0) { state = IN_1RTT; /* release data sent as early-data, if server accepted it */ if (hsprop->client.early_data_accepted_by_peer) shift_buffer(&ptbuf, early_bytes_sent); if (ptbuf.off != 0) { if ((ret = ptls_send(tls, &encbuf, ptbuf.base, ptbuf.off)) != 0) { fprintf(stderr, "ptls_send(1rtt):%d\n", ret); goto Exit; } ptbuf.off = 0; } } else if (ret == PTLS_ERROR_IN_PROGRESS) { /* ok */ } else { fprintf(stderr, "ptls_handshake:%d\n", ret); goto Exit; } } else { if ((ret = ptls_receive(tls, &rbuf, bytebuf + off, &leftlen)) == 0) { if (rbuf.off != 0) { write(1, rbuf.base, rbuf.off); rbuf.off = 0; } } else if (ret == PTLS_ERROR_IN_PROGRESS) { /* ok */ } else { fprintf(stderr, "ptls_receive:%d\n", ret); goto Exit; } } off += leftlen; } } /* read input (and send if possible) */ if (inputfd != -1 && (FD_ISSET(inputfd, &readfds) || FD_ISSET(inputfd, &exceptfds))) { while ((ioret = read(inputfd, bytebuf, sizeof(bytebuf))) == -1 && errno == EINTR) ; if (ioret > 0) { ptls_buffer_pushv(&ptbuf, bytebuf, ioret); if (state == IN_HANDSHAKE) { size_t send_amount = 0; if (hsprop->client.max_early_data_size != NULL) { size_t max_can_be_sent = *hsprop->client.max_early_data_size; if (max_can_be_sent > ptbuf.off) max_can_be_sent = ptbuf.off; send_amount = max_can_be_sent - early_bytes_sent; } if (send_amount != 0) { if ((ret = ptls_send(tls, &encbuf, ptbuf.base, send_amount)) != 0) { fprintf(stderr, "ptls_send(early_data):%d\n", ret); goto Exit; } early_bytes_sent += send_amount; } } else { if ((ret = ptls_send(tls, &encbuf, bytebuf, ioret)) != 0) { fprintf(stderr, "ptls_send(1rtt):%d\n", ret); goto Exit; } ptbuf.off = 0; } } else { /* closed */ if (input_file != NULL) close(inputfd); inputfd = -1; } } /* send any data */ if (encbuf.off != 0) { while ((ioret = write(sockfd, encbuf.base, encbuf.off)) == -1 && errno == EINTR) ; if (ioret == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) { /* no data */ } else if (ioret <= 0) { goto Exit; } else { shift_buffer(&encbuf, ioret); } } /* close the sender side when necessary */ if (state == IN_1RTT && inputfd == -1) { /* FIXME send close_alert */ shutdown(sockfd, SHUT_WR); state = IN_SHUTDOWN; } } Exit: if (sockfd != -1) close(sockfd); if (input_file != NULL && inputfd != -1) close(inputfd); ptls_buffer_dispose(&rbuf); ptls_buffer_dispose(&encbuf); ptls_buffer_dispose(&ptbuf); ptls_free(tls); return ret != 0; } static int run_server(struct sockaddr *sa, socklen_t salen, ptls_context_t *ctx, const char *input_file, ptls_handshake_properties_t *hsprop) { int listen_fd, conn_fd, on = 1; if ((listen_fd = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) { perror("socket(2) failed"); return 1; } if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { perror("setsockopt(SO_REUSEADDR) failed"); return 1; } if (bind(listen_fd, sa, salen) != 0) { perror("bind(2) failed"); return 1; } if (listen(listen_fd, SOMAXCONN) != 0) { perror("listen(2) failed"); return 1; } while (1) { if ((conn_fd = accept(listen_fd, NULL, 0)) != -1) handle_connection(conn_fd, ctx, NULL, input_file, hsprop); } return 0; } static int run_client(struct sockaddr *sa, socklen_t salen, ptls_context_t *ctx, const char *server_name, const char *input_file, ptls_handshake_properties_t *hsprop) { int fd; if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == 1) { perror("socket(2) failed"); return 1; } if (connect(fd, sa, salen) != 0) { perror("connect(2) failed"); return 1; } return handle_connection(fd, ctx, server_name, input_file, hsprop); } static void usage(const char *cmd) { printf("Usage: %s [options] host port\n" "\n" "Options:\n" " -4 force IPv4\n" " -6 force IPv6\n" " -c certificate-file\n" " -i file a file to read from and send to the peer (default: stdin)\n" " -k key-file specifies the credentials to be used for running the\n" " server. If omitted, the command runs as a client.\n" " -l log-file file to log traffic secrets\n" " -n negotiates the key exchange method (i.e. wait for HRR)\n" " -s session-file file to read/write the session ticket\n" " -S require public key exchange when resuming a session\n" " -e when resuming a session, send first 8,192 bytes of input\n" " as early data\n" " -v verify peer using the default certificates\n" " -h print this help\n" "\n", cmd); } int main(int argc, char **argv) { ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); #if !defined(OPENSSL_NO_ENGINE) /* Load all compiled-in ENGINEs */ ENGINE_load_builtin_engines(); ENGINE_register_all_ciphers(); ENGINE_register_all_digests(); #endif ptls_context_t ctx = {ptls_openssl_random_bytes, &ptls_get_time, ptls_openssl_key_exchanges, ptls_openssl_cipher_suites}; ptls_handshake_properties_t hsprop = {{{{NULL}}}}; const char *host, *port, *file = NULL; int use_early_data = 0, ch; struct sockaddr_storage sa; socklen_t salen; int family = 0; while ((ch = getopt(argc, argv, "46c:i:k:nes:Sl:vh")) != -1) { switch (ch) { case '4': family = AF_INET; break; case '6': family = AF_INET6; break; case 'c': load_certificate_chain(&ctx, optarg); break; case 'i': file = optarg; break; case 'k': load_private_key(&ctx, optarg); break; case 'n': hsprop.client.negotiate_before_key_exchange = 1; break; case 'e': use_early_data = 1; break; case 's': setup_session_file(&ctx, &hsprop, optarg); break; case 'S': ctx.require_dhe_on_psk = 1; break; case 'l': setup_log_secret(&ctx, optarg); break; case 'v': setup_verify_certificate(&ctx); break; default: usage(argv[0]); exit(1); } } argc -= optind; argv += optind; if (ctx.certificates.count != 0 || ctx.sign_certificate != NULL) { /* server */ if (ctx.certificates.count == 0 || ctx.sign_certificate == NULL) { fprintf(stderr, "-c and -k options must be used together\n"); return 1; } setup_session_cache(&ctx); } else { /* client */ if (use_early_data) { static size_t max_early_data_size; hsprop.client.max_early_data_size = &max_early_data_size; } } if (argc != 2) { fprintf(stderr, "missing host and port\n"); return 1; } host = (--argc, *argv++); port = (--argc, *argv++); if (resolve_address((struct sockaddr *)&sa, &salen, host, port, family, SOCK_STREAM, IPPROTO_TCP) != 0) exit(1); if (ctx.certificates.count != 0) { return run_server((struct sockaddr *)&sa, salen, &ctx, file, &hsprop); } else { return run_client((struct sockaddr *)&sa, salen, &ctx, host, file, &hsprop); } }