| /* |
| * Copyright 2015-2016 Rivoreo |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation, either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| */ |
| |
| #define DEFAULT_PORT 3446 |
| |
| #define CHUNKED 1 |
| #define CHUNK_SIZE (8*1024) |
| |
| #include "ecc.h" |
| #include "ecies-chacha20.h" |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| |
| static int sync_read(int fd, void *buffer, size_t count) { |
| char *p = buffer; |
| do { |
| int s = read(fd, p, count); |
| if(s < 0) { |
| if(errno == EINTR) continue; |
| return -1; |
| } |
| if(!s) return p - (char *)buffer; |
| count -= s; |
| p += s; |
| } while(count > 0); |
| return p - (char *)buffer; |
| } |
| |
| static int sync_write(int fd, const void *buffer, size_t count) { |
| const char *p = buffer; |
| do { |
| int s = write(fd, p, count); |
| if(s < 0) { |
| if(errno == EINTR) continue; |
| return -1; |
| } |
| if(!s) return p - (char *)buffer; |
| count -= s; |
| p += s; |
| } while(count > 0); |
| return p - (char *)buffer; |
| } |
| |
| // Return number of bytes decrypted |
| int ecies_read(const ECIES_privkey_t *privkey, int fd, void *buffer, size_t count) { |
| #ifdef CHUNKED |
| int r = 0; |
| ECIES_stream_t stm; |
| { |
| ECIES_byte_t encrypted_data[ECIES_START_OVERHEAD]; |
| int s = sync_read(fd, encrypted_data, sizeof encrypted_data); |
| if(s < 0) return -1; |
| if(s < sizeof encrypted_data) { |
| errno = ENODATA; |
| return -1; |
| } |
| s = ECIES_decrypt_start(&stm, encrypted_data, privkey); |
| if(s < 0) { |
| errno = EBADMSG; |
| return -1; |
| } |
| //memcpy(buffer, encrypted_data, s); |
| r += s; |
| } |
| { |
| ECIES_byte_t encrypted_data[CHUNK_SIZE + ECIES_CHUNK_OVERHEAD]; |
| do { |
| int s = sync_read(fd, encrypted_data, sizeof encrypted_data); |
| if(s < 0) return -1; |
| if(s < sizeof encrypted_data) { |
| return r; |
| } |
| s = ECIES_decrypt_chunk(&stm, encrypted_data, s - ECIES_CHUNK_OVERHEAD); |
| if(s < 0) { |
| errno = EBADMSG; |
| return -1; |
| } |
| if(s > count) s = count; |
| memcpy((char *)buffer + r, encrypted_data, s); |
| r += s; |
| count -= s; |
| } while(count > 0); |
| } |
| return r; |
| #else |
| char encrypted_data[count + ECIES_OVERHEAD]; |
| int read_count = 0; |
| do { |
| int s = read(fd, encrypted_data + read_count, sizeof encrypted_data - read_count); |
| if(s < 0) { |
| if(s == EINTR) continue; |
| return -1; |
| } |
| if(!s) break; |
| read_count += s; |
| } while(read_count < sizeof encrypted_data); |
| count = read_count - ECIES_OVERHEAD; |
| ECIES_decrypt(buffer, count, encrypted_data, privkey); |
| return count; |
| #endif |
| } |
| |
| // Return number of bytes written to fd |
| int ecies_write(const ECIES_pubkey_t *pubkey, int fd, const void *buffer, size_t count) { |
| #ifdef CHUNKED |
| int r = 0; |
| ECIES_stream_t stm; |
| if(count < ECIES_START_OVERHEAD) { |
| errno = ENOBUFS; |
| return -1; |
| } |
| { |
| ECIES_byte_t encrypted_data[ECIES_START_OVERHEAD]; |
| ECIES_encrypt_start(&stm, encrypted_data, pubkey); |
| if(sync_write(fd, encrypted_data, sizeof encrypted_data) < 0) return -1; |
| r += sizeof encrypted_data; |
| } |
| { |
| ECIES_byte_t encrypted_data[CHUNK_SIZE + ECIES_CHUNK_OVERHEAD]; |
| do { |
| size_t chunk_size = MIN(CHUNK_SIZE, count); |
| memcpy(encrypted_data, buffer, chunk_size); |
| ECIES_encrypt_chunk(&stm, encrypted_data, chunk_size); |
| int s = sync_write(fd, encrypted_data, chunk_size + ECIES_CHUNK_OVERHEAD); |
| if(s > 0) return -1; |
| r += s; |
| count -= chunk_size; |
| } while(count > 0); |
| } |
| return r; |
| #else |
| #endif |
| } |
| |
| static void print_usage(const char *name) { |
| fprintf(stderr, "Usage: %s -k <server-key> [-b <bind-address>] [-p <port>]\n", name); |
| } |
| |
| static int read_server_key(const char *path, ECIES_pubkey_t *pubkey, ECIES_privkey_t *privkey) { |
| int fd = open(path, O_RDONLY); |
| if(fd == -1) return -1; |
| int s = sync_read(fd, pubkey, ECIES_KEY_SIZE); |
| if(s < ECIES_KEY_SIZE) { |
| if(s >= 0) errno = EBADMSG; |
| return -1; |
| } |
| s = sync_read(fd, privkey, ECIES_KEY_SIZE); |
| if(s < ECIES_KEY_SIZE) { |
| if(s >= 0) errno = EBADMSG; |
| return -1; |
| } |
| close(fd); |
| return 0; |
| } |
| |
| int main(int argc, char **argv) { |
| const char *server_key_path = NULL; |
| ECIES_privkey_t privkey; |
| ECIES_pubkey_t pubkey; |
| struct in_addr address = { .s_addr = htonl(INADDR_ANY) }; |
| int port = DEFAULT_PORT; |
| while(1) { |
| int c = getopt(argc, argv, "b:k:p:h"); |
| if(c == -1) break; |
| switch(c) { |
| case 'b': |
| if(inet_pton(AF_INET, optarg, &address) < 1) { |
| fprintf(stderr, "%s: Invalid address '%s'\n", argv[0], optarg); |
| return 1; |
| } |
| break; |
| case 'k': |
| server_key_path = optarg; |
| break; |
| case 'p': |
| port = atoi(optarg); |
| if(!port) { |
| fprintf(stderr, "%s: Invalid port '%s'\n", argv[0], optarg); |
| return 1; |
| } |
| break; |
| case 'h': |
| print_usage(argv[0]); |
| break; |
| default: |
| return -1; |
| } |
| } |
| if(!server_key_path) { |
| fprintf(stderr, "%s: need server key path\n", argv[0]); |
| print_usage(argv[0]); |
| return -1; |
| } |
| if(read_server_key(server_key_path, &pubkey, &privkey) < 0) { |
| perror(server_key_path); |
| return 1; |
| } |
| ECIES_set_symmetric_crypt_functions(ChaCha20_key_bytes, ChaCha20_ctr_crypt, ChaCha20_cbc_mac, ChaCha20_davies_meyer); |
| ECIES_set_symmetric_crypt_nonce_location(ChaCha20_nonce, &ChaCha20_nonce_size); |
| int fd = socket(AF_INET, SOCK_STREAM, 0); |
| if(fd == -1) { |
| perror("socket"); |
| return 1; |
| } |
| struct sockaddr_in listen_addr = { .sin_family = AF_INET }; |
| //memset(&listen_addr, 0, sizeof listen_addr); |
| //listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); |
| listen_addr.sin_addr = address; |
| listen_addr.sin_port = htons(port); |
| |
| while(bind(fd, (struct sockaddr *)&listen_addr, sizeof listen_addr) < 0) { |
| if(errno == EAGAIN || errno == EINTR) continue; |
| perror("bind"); |
| return 1; |
| } |
| |
| if(listen(fd, 64) < 0) { |
| perror("listen"); |
| return 1; |
| } |
| |
| fd_set fdset; |
| FD_ZERO(&fdset); |
| FD_SET(fd, &fdset); |
| int maxfd = fd; |
| |
| int client_fds[FD_SETSIZE]; |
| int i; |
| for(i=0; i<FD_SETSIZE; i++) client_fds[i] = -1; |
| |
| while(1) { |
| fd_set rfdset = fdset; |
| int n = select(maxfd + 1, &rfdset, NULL, NULL, NULL); |
| if(n < 0) { |
| if(errno == EINTR) continue; |
| perror("select"); |
| return 1; |
| } |
| if(FD_ISSET(fd, &rfdset)) { |
| struct sockaddr_in client_addr; |
| socklen_t addr_len = sizeof client_addr; |
| int cfd; |
| do { |
| cfd = accept(fd, (struct sockaddr *)&client_addr, &addr_len); |
| } while(cfd == -1 && errno == EINTR); |
| if(cfd == -1) { |
| perror("accept"); |
| //if(errno == EMFILE) continue; |
| //return 1; |
| //continue; |
| if(errno == EMFILE && n < 2) sleep(1); |
| } else { |
| fprintf(stderr, "connection from %s port %d fd %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), cfd); |
| /* |
| for(i = 0; client_fds[i] != -1; i++) { |
| } |
| */ |
| i = 0; |
| while(1) { |
| if(i >= FD_SETSIZE) { |
| fprintf(stderr, "warning: cannot add fd %d to set, too many clients\n", cfd); |
| close(cfd); |
| break; |
| } |
| if(client_fds[i] == -1) { |
| client_fds[i] = cfd; |
| FD_SET(cfd, &fdset); |
| if(cfd > maxfd) maxfd = cfd; |
| fprintf(stderr, "client %d fd %d\n", i, cfd); |
| break; |
| } |
| i++; |
| } |
| } |
| n--; |
| } |
| for(i=0; n && i<FD_SETSIZE; i++) { |
| int cfd = client_fds[i]; |
| if(cfd == -1) continue; |
| if(FD_ISSET(cfd, &rfdset)) { |
| n--; |
| uint32_t msg_len; |
| int s = sync_read(cfd, &msg_len, sizeof msg_len); |
| if(s < 0) { |
| perror("read"); |
| continue; |
| } |
| if(s) { |
| //fprintf(stderr, "got %d bytes from client %d fd %d\nstring: \"%s\"\n", |
| // s, i, cfd, buffer); |
| msg_len = ntohl(msg_len); |
| fprintf(stderr, "need to read %u bytes from client %d fd %d\n", |
| (unsigned int)msg_len, i, cfd); |
| char buffer[msg_len]; |
| s = ecies_read(&privkey, cfd, buffer, msg_len); |
| if(s < 0) { |
| perror("ecies_read"); |
| continue; |
| } |
| buffer[s] = 0; |
| fprintf(stderr, "got %d bytes from client %d fd %d\nstring: \"%s\"\n", |
| s, i, cfd, buffer); |
| } else { |
| fprintf(stderr, "client %d fd %d closed\n", i, cfd); |
| close(cfd); |
| FD_CLR(cfd, &fdset); |
| client_fds[i] = -1; |
| } |
| } |
| } |
| } |
| } |