diff options
| author | Ron Yorston <rmy@pobox.com> | 2026-03-11 10:14:49 +0000 |
|---|---|---|
| committer | Ron Yorston <rmy@pobox.com> | 2026-03-11 10:14:49 +0000 |
| commit | ede205bd07573813337b5706acb0cae3b127a36f (patch) | |
| tree | 1f79073ae9b7fa6fc8bd6b5f090380fd972f77dc | |
| parent | b8b19b18ad8f189e1a6f52043dabe8d38d19b883 (diff) | |
| parent | e527bd22dbda697d09d4dff8786c6290d79d577a (diff) | |
| download | busybox-w32-ede205bd07573813337b5706acb0cae3b127a36f.tar.gz busybox-w32-ede205bd07573813337b5706acb0cae3b127a36f.tar.bz2 busybox-w32-ede205bd07573813337b5706acb0cae3b127a36f.zip | |
Merge branch 'busybox' into merge
52 files changed, 6426 insertions, 1740 deletions
diff --git a/archival/libarchive/get_header_tar.c b/archival/libarchive/get_header_tar.c index 9beed93cf..b83523fb2 100644 --- a/archival/libarchive/get_header_tar.c +++ b/archival/libarchive/get_header_tar.c | |||
| @@ -460,7 +460,7 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle) | |||
| 460 | 460 | ||
| 461 | /* Everything up to and including last ".." component is stripped */ | 461 | /* Everything up to and including last ".." component is stripped */ |
| 462 | strip_unsafe_prefix(file_header->name); | 462 | strip_unsafe_prefix(file_header->name); |
| 463 | if (file_header->link_target) { | 463 | if (file_header->link_target && !S_ISLNK(file_header->mode)) { |
| 464 | /* GNU tar 1.34 examples: | 464 | /* GNU tar 1.34 examples: |
| 465 | * tar: Removing leading '/' from hard link targets | 465 | * tar: Removing leading '/' from hard link targets |
| 466 | * tar: Removing leading '../' from hard link targets | 466 | * tar: Removing leading '../' from hard link targets |
diff --git a/coreutils/od_bloaty.c b/coreutils/od_bloaty.c index 641d93503..b268368bf 100644 --- a/coreutils/od_bloaty.c +++ b/coreutils/od_bloaty.c | |||
| @@ -1162,7 +1162,7 @@ dump_strings(off_t address, off_t end_offset) | |||
| 1162 | leading '+' return nonzero and set *OFFSET to the offset it denotes. */ | 1162 | leading '+' return nonzero and set *OFFSET to the offset it denotes. */ |
| 1163 | 1163 | ||
| 1164 | static int | 1164 | static int |
| 1165 | parse_old_offset(const char *s, off_t *offset) | 1165 | parse_old_offset(char *s, off_t *offset) |
| 1166 | { | 1166 | { |
| 1167 | static const struct suffix_mult Bb[] ALIGN_SUFFIX = { | 1167 | static const struct suffix_mult Bb[] ALIGN_SUFFIX = { |
| 1168 | { "B", 1024 }, | 1168 | { "B", 1024 }, |
| @@ -1188,7 +1188,8 @@ parse_old_offset(const char *s, off_t *offset) | |||
| 1188 | radix = 16; | 1188 | radix = 16; |
| 1189 | 1189 | ||
| 1190 | *offset = xstrtooff_sfx(s, radix, Bb); | 1190 | *offset = xstrtooff_sfx(s, radix, Bb); |
| 1191 | if (p) p[0] = '.'; | 1191 | if (p) |
| 1192 | p[0] = '.'; /* undo cheating */ | ||
| 1192 | 1193 | ||
| 1193 | return (*offset >= 0); | 1194 | return (*offset >= 0); |
| 1194 | } | 1195 | } |
| @@ -1241,7 +1242,7 @@ int od_main(int argc UNUSED_PARAM, char **argv) | |||
| 1241 | static const uint8_t doxn_address_pad_len_char[] ALIGN1 = { | 1242 | static const uint8_t doxn_address_pad_len_char[] ALIGN1 = { |
| 1242 | '7', '7', '6', /* '?' */ | 1243 | '7', '7', '6', /* '?' */ |
| 1243 | }; | 1244 | }; |
| 1244 | char *p; | 1245 | const char *p; |
| 1245 | int pos; | 1246 | int pos; |
| 1246 | p = strchr(doxn, str_A[0]); | 1247 | p = strchr(doxn, str_A[0]); |
| 1247 | if (!p) | 1248 | if (!p) |
diff --git a/coreutils/paste.c b/coreutils/paste.c index 78a5c2a14..363340e3d 100644 --- a/coreutils/paste.c +++ b/coreutils/paste.c | |||
| @@ -34,23 +34,16 @@ | |||
| 34 | 34 | ||
| 35 | static void paste_files(FILE** files, int file_cnt, char* delims, int del_cnt) | 35 | static void paste_files(FILE** files, int file_cnt, char* delims, int del_cnt) |
| 36 | { | 36 | { |
| 37 | #if ENABLE_PLATFORM_MINGW32 | ||
| 38 | char **line = xmalloc(file_cnt * sizeof(char *)); | 37 | char **line = xmalloc(file_cnt * sizeof(char *)); |
| 39 | #else | ||
| 40 | char *line; | ||
| 41 | #endif | ||
| 42 | char delim; | 38 | char delim; |
| 43 | int active_files = file_cnt; | 39 | int active_files = file_cnt; |
| 44 | int i; | 40 | int i; |
| 45 | 41 | ||
| 46 | while (active_files > 0) { | 42 | while (active_files > 0) { |
| 47 | int del_idx = 0; | 43 | int del_idx = 0; |
| 48 | #if ENABLE_PLATFORM_MINGW32 | ||
| 49 | int got_line = FALSE; | 44 | int got_line = FALSE; |
| 50 | #endif | ||
| 51 | 45 | ||
| 52 | for (i = 0; i < file_cnt; ++i) { | 46 | for (i = 0; i < file_cnt; ++i) { |
| 53 | #if ENABLE_PLATFORM_MINGW32 | ||
| 54 | if (files[i]) { | 47 | if (files[i]) { |
| 55 | line[i] = xmalloc_fgetline(files[i]); | 48 | line[i] = xmalloc_fgetline(files[i]); |
| 56 | if (!line[i]) { | 49 | if (!line[i]) { |
| @@ -73,20 +66,6 @@ static void paste_files(FILE** files, int file_cnt, char* delims, int del_cnt) | |||
| 73 | fputs_stdout(line[i]); | 66 | fputs_stdout(line[i]); |
| 74 | free(line[i]); | 67 | free(line[i]); |
| 75 | } | 68 | } |
| 76 | #else | ||
| 77 | if (files[i] == NULL) | ||
| 78 | continue; | ||
| 79 | |||
| 80 | line = xmalloc_fgetline(files[i]); | ||
| 81 | if (!line) { | ||
| 82 | fclose_if_not_stdin(files[i]); | ||
| 83 | files[i] = NULL; | ||
| 84 | --active_files; | ||
| 85 | continue; | ||
| 86 | } | ||
| 87 | fputs_stdout(line); | ||
| 88 | free(line); | ||
| 89 | #endif | ||
| 90 | delim = '\n'; | 69 | delim = '\n'; |
| 91 | if (i != file_cnt - 1) { | 70 | if (i != file_cnt - 1) { |
| 92 | delim = delims[del_idx++]; | 71 | delim = delims[del_idx++]; |
| @@ -97,9 +76,7 @@ static void paste_files(FILE** files, int file_cnt, char* delims, int del_cnt) | |||
| 97 | fputc(delim, stdout); | 76 | fputc(delim, stdout); |
| 98 | } | 77 | } |
| 99 | } | 78 | } |
| 100 | #if ENABLE_PLATFORM_MINGW32 | ||
| 101 | free(line); | 79 | free(line); |
| 102 | #endif | ||
| 103 | } | 80 | } |
| 104 | 81 | ||
| 105 | static void paste_files_separate(FILE** files, char* delims, int del_cnt) | 82 | static void paste_files_separate(FILE** files, char* delims, int del_cnt) |
diff --git a/coreutils/printf.c b/coreutils/printf.c index 379c00cc6..0c2502f1b 100644 --- a/coreutils/printf.c +++ b/coreutils/printf.c | |||
| @@ -419,7 +419,7 @@ static char **print_formatted(char *f, char **argv, int *conv_err) | |||
| 419 | /* Add "ll" if integer modifier, then print */ | 419 | /* Add "ll" if integer modifier, then print */ |
| 420 | { | 420 | { |
| 421 | static const char format_chars[] ALIGN1 = "diouxXfeEgGcs"; | 421 | static const char format_chars[] ALIGN1 = "diouxXfeEgGcs"; |
| 422 | char *p = strchr(format_chars, *f); | 422 | char *p = (char*)strchr(format_chars, *f); |
| 423 | /* needed - try "printf %" without it */ | 423 | /* needed - try "printf %" without it */ |
| 424 | if (p == NULL || *f == '\0') { | 424 | if (p == NULL || *f == '\0') { |
| 425 | bb_error_msg("%s: invalid format", direc_start); | 425 | bb_error_msg("%s: invalid format", direc_start); |
diff --git a/editors/awk.c b/editors/awk.c index b3ec99d3f..81d7005b3 100644 --- a/editors/awk.c +++ b/editors/awk.c | |||
| @@ -2841,7 +2841,7 @@ static NOINLINE var *exec_builtin(node *op, var *res) | |||
| 2841 | l = strlen(as[0]) - ll; | 2841 | l = strlen(as[0]) - ll; |
| 2842 | if (ll > 0 && l >= 0) { | 2842 | if (ll > 0 && l >= 0) { |
| 2843 | if (!icase) { | 2843 | if (!icase) { |
| 2844 | char *s = strstr(as[0], as[1]); | 2844 | const char *s = strstr(as[0], as[1]); |
| 2845 | if (s) | 2845 | if (s) |
| 2846 | n = (s - as[0]) + 1; | 2846 | n = (s - as[0]) + 1; |
| 2847 | } else { | 2847 | } else { |
diff --git a/include/libbb.h b/include/libbb.h index 6ce99abda..ee3a5b56a 100644 --- a/include/libbb.h +++ b/include/libbb.h | |||
| @@ -501,6 +501,11 @@ void *mmap_anon(size_t size) FAST_FUNC; | |||
| 501 | void *xmmap_anon(size_t size) FAST_FUNC; | 501 | void *xmmap_anon(size_t size) FAST_FUNC; |
| 502 | 502 | ||
| 503 | #if defined(__x86_64__) || defined(i386) | 503 | #if defined(__x86_64__) || defined(i386) |
| 504 | /* 0x7f would be better, but it causes alignment problems */ | ||
| 505 | # define ARCH_GLOBAL_PTR_OFF 0x80 | ||
| 506 | #endif | ||
| 507 | |||
| 508 | #if defined(__x86_64__) || defined(i386) | ||
| 504 | # define BB_ARCH_FIXED_PAGESIZE 4096 | 509 | # define BB_ARCH_FIXED_PAGESIZE 4096 |
| 505 | #elif defined(__arm__) /* only 32bit, 64bit ARM has variable page size */ | 510 | #elif defined(__arm__) /* only 32bit, 64bit ARM has variable page size */ |
| 506 | # define BB_ARCH_FIXED_PAGESIZE 4096 | 511 | # define BB_ARCH_FIXED_PAGESIZE 4096 |
| @@ -511,11 +516,6 @@ void *xmmap_anon(size_t size) FAST_FUNC; | |||
| 511 | //sparc64,alpha,openrisc: fixed 8k pages | 516 | //sparc64,alpha,openrisc: fixed 8k pages |
| 512 | #endif | 517 | #endif |
| 513 | 518 | ||
| 514 | #if defined(__x86_64__) || defined(i386) | ||
| 515 | /* 0x7f would be better, but it causes alignment problems */ | ||
| 516 | # define ARCH_GLOBAL_PTR_OFF 0x80 | ||
| 517 | #endif | ||
| 518 | |||
| 519 | #if defined BB_ARCH_FIXED_PAGESIZE | 519 | #if defined BB_ARCH_FIXED_PAGESIZE |
| 520 | # define IF_VARIABLE_ARCH_PAGESIZE(...) /*nothing*/ | 520 | # define IF_VARIABLE_ARCH_PAGESIZE(...) /*nothing*/ |
| 521 | # define bb_getpagesize() BB_ARCH_FIXED_PAGESIZE | 521 | # define bb_getpagesize() BB_ARCH_FIXED_PAGESIZE |
| @@ -780,6 +780,76 @@ struct fd_pair { int rd; int wr; }; | |||
| 780 | #define piped_pair(pair) pipe(&((pair).rd)) | 780 | #define piped_pair(pair) pipe(&((pair).rd)) |
| 781 | #define xpiped_pair(pair) xpipe(&((pair).rd)) | 781 | #define xpiped_pair(pair) xpipe(&((pair).rd)) |
| 782 | 782 | ||
| 783 | |||
| 784 | int parse_datestr(const char *date_str, struct tm *ptm) FAST_FUNC; | ||
| 785 | time_t validate_tm_time(const char *date_str, struct tm *ptm) FAST_FUNC; | ||
| 786 | char *strftime_HHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC; | ||
| 787 | char *strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC; | ||
| 788 | void xgettimeofday(struct timeval *tv) FAST_FUNC; | ||
| 789 | void xsettimeofday(const struct timeval *tv) FAST_FUNC; | ||
| 790 | |||
| 791 | |||
| 792 | /* Generic I/O loop */ | ||
| 793 | struct connection; | ||
| 794 | typedef struct ioloop_state { | ||
| 795 | struct connection *conns; | ||
| 796 | unsigned flags; | ||
| 797 | unsigned max_timeout; | ||
| 798 | unsigned current_iteration_timeout; | ||
| 799 | unsigned last_timeout; | ||
| 800 | int (*pre_poll_callback)(struct ioloop_state*); | ||
| 801 | } ioloop_state_t; | ||
| 802 | |||
| 803 | #define STRUCT_CONNECTION \ | ||
| 804 | struct connection *next; \ | ||
| 805 | ioloop_state_t *io; \ | ||
| 806 | int read_fd; \ | ||
| 807 | int write_fd; \ | ||
| 808 | int (*have_buffer_to_read_into)(void *this); \ | ||
| 809 | int (*have_data_to_write)(void *this); \ | ||
| 810 | int (*read)(void *this); \ | ||
| 811 | int (*write)(void *this); \ | ||
| 812 | |||
| 813 | typedef struct connection { | ||
| 814 | STRUCT_CONNECTION | ||
| 815 | } connection_t; | ||
| 816 | |||
| 817 | static ALWAYS_INLINE void free_connection(connection_t *conn) | ||
| 818 | { | ||
| 819 | free(conn); | ||
| 820 | } | ||
| 821 | |||
| 822 | void FAST_FUNC conn_close_fds(connection_t *conn); | ||
| 823 | void FAST_FUNC conn_close_fds_remove_and_free(connection_t *conn); | ||
| 824 | static ALWAYS_INLINE ioloop_state_t *new_ioloop_state(void) | ||
| 825 | { | ||
| 826 | ioloop_state_t *io = xzalloc(sizeof(*io)); | ||
| 827 | return io; | ||
| 828 | } | ||
| 829 | static ALWAYS_INLINE void free_ioloop_state(ioloop_state_t *io) | ||
| 830 | { | ||
| 831 | free(io); | ||
| 832 | } | ||
| 833 | static ALWAYS_INLINE void ioloop_decrease_current_timeout(ioloop_state_t *io, unsigned n) | ||
| 834 | { | ||
| 835 | if (io->current_iteration_timeout > n) | ||
| 836 | io->current_iteration_timeout = n; | ||
| 837 | } | ||
| 838 | |||
| 839 | void FAST_FUNC ioloop_insert_conn(ioloop_state_t *io, connection_t *conn); | ||
| 840 | void FAST_FUNC ioloop_remove_conn(ioloop_state_t *io, connection_t *conn); | ||
| 841 | enum { | ||
| 842 | IOLOOP_FLAG_EXIT_IF_TIMEOUT = (1 << 0), | ||
| 843 | IOLOOP_FLAG_EXIT_IF_EINTR = (1 << 1), | ||
| 844 | //IOLOOP_FLAG_EXIT_IF_ALL_NOT_READY = (1 << 1), | ||
| 845 | /* ioloop_run return values */ | ||
| 846 | IOLOOP_NO_CONNS = 0, | ||
| 847 | IOLOOP_TIMEOUT = 1, | ||
| 848 | IOLOOP_EINTR = 2, | ||
| 849 | }; | ||
| 850 | int FAST_FUNC ioloop_run(ioloop_state_t *io); | ||
| 851 | |||
| 852 | |||
| 783 | /* Useful for having small structure members/global variables */ | 853 | /* Useful for having small structure members/global variables */ |
| 784 | typedef int8_t socktype_t; | 854 | typedef int8_t socktype_t; |
| 785 | typedef int8_t family_t; | 855 | typedef int8_t family_t; |
| @@ -808,14 +878,6 @@ struct BUG_too_small { | |||
| 808 | }; | 878 | }; |
| 809 | 879 | ||
| 810 | 880 | ||
| 811 | int parse_datestr(const char *date_str, struct tm *ptm) FAST_FUNC; | ||
| 812 | time_t validate_tm_time(const char *date_str, struct tm *ptm) FAST_FUNC; | ||
| 813 | char *strftime_HHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC; | ||
| 814 | char *strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC; | ||
| 815 | void xgettimeofday(struct timeval *tv) FAST_FUNC; | ||
| 816 | void xsettimeofday(const struct timeval *tv) FAST_FUNC; | ||
| 817 | |||
| 818 | |||
| 819 | int xsocket(int domain, int type, int protocol) FAST_FUNC; | 881 | int xsocket(int domain, int type, int protocol) FAST_FUNC; |
| 820 | void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) FAST_FUNC; | 882 | void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) FAST_FUNC; |
| 821 | void xlisten(int s, int backlog) FAST_FUNC; | 883 | void xlisten(int s, int backlog) FAST_FUNC; |
| @@ -926,6 +988,11 @@ char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa) FAST_FUNC RETU | |||
| 926 | /* inet_[ap]ton on steroids */ | 988 | /* inet_[ap]ton on steroids */ |
| 927 | char* xmalloc_sockaddr2dotted(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC; | 989 | char* xmalloc_sockaddr2dotted(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC; |
| 928 | char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC; | 990 | char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC; |
| 991 | // NB: unlike getservbyport, port parameter/retval are in host, NOT network order! | ||
| 992 | #define getservbyport dont_use_getservbyport_uses_global_buffer | ||
| 993 | char* bb_get_servname_by_port(char **p_etc_services, int port, const char *type) FAST_FUNC RETURNS_MALLOC; | ||
| 994 | #define getservbyname dont_use_getservbyname_uses_global_buffer | ||
| 995 | int bb_get_servport_by_name(char **p_etc_services, const char *name, const char *type) FAST_FUNC; | ||
| 929 | // "old" (ipv4 only) API | 996 | // "old" (ipv4 only) API |
| 930 | // users: traceroute.c hostname.c - use _list_ of all IPs | 997 | // users: traceroute.c hostname.c - use _list_ of all IPs |
| 931 | struct hostent *xgethostbyname(const char *name) FAST_FUNC; | 998 | struct hostent *xgethostbyname(const char *name) FAST_FUNC; |
| @@ -976,8 +1043,11 @@ typedef struct tls_state { | |||
| 976 | int ofd; | 1043 | int ofd; |
| 977 | int ifd; | 1044 | int ifd; |
| 978 | 1045 | ||
| 979 | unsigned min_encrypted_len_on_read; | 1046 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL |
| 1047 | smallint expecting_first_packet; | ||
| 1048 | #endif | ||
| 980 | uint16_t cipher_id; | 1049 | uint16_t cipher_id; |
| 1050 | unsigned min_encrypted_len_on_read; | ||
| 981 | unsigned MAC_size; | 1051 | unsigned MAC_size; |
| 982 | unsigned key_size; | 1052 | unsigned key_size; |
| 983 | unsigned IV_size; | 1053 | unsigned IV_size; |
| @@ -1002,21 +1072,27 @@ typedef struct tls_state { | |||
| 1002 | /*uint64_t read_seq64_be;*/ | 1072 | /*uint64_t read_seq64_be;*/ |
| 1003 | uint64_t write_seq64_be; | 1073 | uint64_t write_seq64_be; |
| 1004 | 1074 | ||
| 1005 | /*uint8_t *server_write_MAC_key;*/ | 1075 | uint8_t *our_write_MAC_key; |
| 1006 | uint8_t *client_write_key; | 1076 | uint8_t *peer_write_MAC_key; |
| 1007 | uint8_t *server_write_key; | 1077 | uint8_t *our_write_key; |
| 1008 | uint8_t *client_write_IV; | 1078 | uint8_t *peer_write_key; |
| 1009 | uint8_t *server_write_IV; | 1079 | uint8_t *our_write_IV; |
| 1010 | uint8_t client_write_MAC_key[TLS_MAX_MAC_SIZE]; | 1080 | uint8_t *peer_write_IV; |
| 1011 | uint8_t server_write_MAC_k__[TLS_MAX_MAC_SIZE]; | 1081 | uint8_t key_block[TLS_MAX_MAC_SIZE]; |
| 1012 | uint8_t client_write_k__[TLS_MAX_KEY_SIZE]; | 1082 | uint8_t key_block2[TLS_MAX_MAC_SIZE]; |
| 1013 | uint8_t server_write_k__[TLS_MAX_KEY_SIZE]; | 1083 | uint8_t key_block3[TLS_MAX_KEY_SIZE]; |
| 1014 | uint8_t client_write_I_[TLS_MAX_IV_SIZE]; | 1084 | uint8_t key_block4[TLS_MAX_KEY_SIZE]; |
| 1015 | uint8_t server_write_I_[TLS_MAX_IV_SIZE]; | 1085 | uint8_t key_block5[TLS_MAX_IV_SIZE]; |
| 1086 | uint8_t key_block6[TLS_MAX_IV_SIZE]; | ||
| 1016 | 1087 | ||
| 1017 | struct tls_aes aes_encrypt; | 1088 | struct tls_aes aes_encrypt; |
| 1018 | struct tls_aes aes_decrypt; | 1089 | struct tls_aes aes_decrypt; |
| 1019 | uint8_t H[16]; //used by AES_GCM | 1090 | uint8_t H[16]; //used by AES_GCM |
| 1091 | |||
| 1092 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL | ||
| 1093 | /* For ECDHE: server's ephemeral EC private key */ | ||
| 1094 | //uint8_t ecc_priv_key32[32]; | ||
| 1095 | #endif | ||
| 1020 | } tls_state_t; | 1096 | } tls_state_t; |
| 1021 | #endif | 1097 | #endif |
| 1022 | 1098 | ||
| @@ -1025,8 +1101,9 @@ static inline tls_state_t *new_tls_state(void) | |||
| 1025 | tls_state_t *tls = xzalloc(sizeof(*tls)); | 1101 | tls_state_t *tls = xzalloc(sizeof(*tls)); |
| 1026 | return tls; | 1102 | return tls; |
| 1027 | } | 1103 | } |
| 1028 | 1104 | void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni); | |
| 1029 | void tls_handshake(tls_state_t *tls, const char *sni) FAST_FUNC; | 1105 | void FAST_FUNC tls_handshake_as_server(tls_state_t *tls, |
| 1106 | const char *pem_filename); | ||
| 1030 | #define TLSLOOP_EXIT_ON_LOCAL_EOF (1 << 0) | 1107 | #define TLSLOOP_EXIT_ON_LOCAL_EOF (1 << 0) |
| 1031 | void tls_run_copy_loop(tls_state_t *tls, unsigned flags) FAST_FUNC; | 1108 | void tls_run_copy_loop(tls_state_t *tls, unsigned flags) FAST_FUNC; |
| 1032 | 1109 | ||
diff --git a/klibc-utils/resume.c b/klibc-utils/resume.c index 179413627..cb8314c75 100644 --- a/klibc-utils/resume.c +++ b/klibc-utils/resume.c | |||
| @@ -31,7 +31,7 @@ | |||
| 31 | * - /dev/ram (alias to /dev/ram0) | 31 | * - /dev/ram (alias to /dev/ram0) |
| 32 | * - /dev/mtd | 32 | * - /dev/mtd |
| 33 | */ | 33 | */ |
| 34 | static dev_t name_to_dev_t(const char *devname) | 34 | static dev_t name_to_dev_t(char *devname) |
| 35 | { | 35 | { |
| 36 | char devfile[sizeof(int)*3 * 2 + 4]; | 36 | char devfile[sizeof(int)*3 * 2 + 4]; |
| 37 | char *sysname; | 37 | char *sysname; |
diff --git a/libbb/bb_get_servname_by_port.c b/libbb/bb_get_servname_by_port.c new file mode 100644 index 000000000..970a718fe --- /dev/null +++ b/libbb/bb_get_servname_by_port.c | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | /* | ||
| 2 | * Utility routines. | ||
| 3 | * | ||
| 4 | * Copyright (C) 2026 Denys Vlasenko | ||
| 5 | * | ||
| 6 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
| 7 | */ | ||
| 8 | //kbuild:lib-$(CONFIG_NETSTAT) += bb_get_servname_by_port.o | ||
| 9 | //kbuild:lib-$(CONFIG_PSCAN) += bb_get_servname_by_port.o | ||
| 10 | #include "libbb.h" | ||
| 11 | |||
| 12 | //Rationale for existing: | ||
| 13 | //#define getservbyport dont_use_getservbyport_uses_global_buffer | ||
| 14 | //(for example: -280 bytes on musl, x86-32) | ||
| 15 | |||
| 16 | char* FAST_FUNC bb_get_servname_by_port(char **p_etc_services, int port, const char *type) | ||
| 17 | { | ||
| 18 | char *sp; | ||
| 19 | |||
| 20 | sp = *p_etc_services; | ||
| 21 | if (!sp) { | ||
| 22 | //TODO: we need mmap_entire_file() for this use case! | ||
| 23 | sp = xmalloc_open_read_close("/etc/services", NULL); | ||
| 24 | if (!sp) | ||
| 25 | return NULL; | ||
| 26 | *p_etc_services = sp; | ||
| 27 | } | ||
| 28 | while (*sp) { | ||
| 29 | const char *pnstr, *sp_end; | ||
| 30 | char *end; | ||
| 31 | unsigned n; | ||
| 32 | |||
| 33 | sp = skip_whitespace(sp); | ||
| 34 | if (*sp == '#') | ||
| 35 | goto next; | ||
| 36 | sp_end = skip_non_whitespace(sp); | ||
| 37 | pnstr = skip_whitespace(sp_end); | ||
| 38 | n = bb_strtou(pnstr, &end, 10); | ||
| 39 | if (n != port || *end != '/') | ||
| 40 | goto next; | ||
| 41 | if (type) { | ||
| 42 | end++; | ||
| 43 | end = is_prefixed_with(end, type); | ||
| 44 | if (!end | ||
| 45 | || !(isspace(*end) || *end == '\0' | ||
| 46 | || *end == '#') // glibc treats "http 80/tcp#COMMENT" (no space!) as valid | ||
| 47 | ) { | ||
| 48 | goto next; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | return auto_string(xstrndup(sp, sp_end - sp)); | ||
| 52 | next: | ||
| 53 | sp = strchrnul(sp, '\n'); | ||
| 54 | } | ||
| 55 | return NULL; | ||
| 56 | } | ||
diff --git a/libbb/bb_get_servport_by_name.c b/libbb/bb_get_servport_by_name.c new file mode 100644 index 000000000..96580c900 --- /dev/null +++ b/libbb/bb_get_servport_by_name.c | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | /* | ||
| 2 | * Utility routines. | ||
| 3 | * | ||
| 4 | * Copyright (C) 2026 Denys Vlasenko | ||
| 5 | * | ||
| 6 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
| 7 | */ | ||
| 8 | //kbuild:lib-y += bb_get_servport_by_name.o | ||
| 9 | //kbuild:lib-y += bb_get_servport_by_name.o | ||
| 10 | #include "libbb.h" | ||
| 11 | |||
| 12 | //Rationale for existing: | ||
| 13 | //#define getservbyname dont_use_getservbyname_uses_global_buffer | ||
| 14 | //(for example: -32 BSS bytes on musl, x86-32) | ||
| 15 | |||
| 16 | int FAST_FUNC bb_get_servport_by_name(char **p_etc_services, const char *name, const char *proto) | ||
| 17 | { | ||
| 18 | unsigned namelen; | ||
| 19 | char *sp; | ||
| 20 | |||
| 21 | if (!name[0]) // This can hang the search (strstr advances by 0 bytes) | ||
| 22 | return -1; // bad service name form: "" | ||
| 23 | // Any other bogosity to reject? | ||
| 24 | // Or just enforce isalnum_or_dash_or_slash(name[i])? | ||
| 25 | // Yes, service name like "cl/1" is allowed and _exists_. | ||
| 26 | // | ||
| 27 | // Protect against finding service "www#c" in "http 10/tcp www#c" line | ||
| 28 | //if (strchr(name, '#')) | ||
| 29 | // return -1; // bad service name form | ||
| 30 | //^^^^ the main code already catches this possibility. | ||
| 31 | // | ||
| 32 | // Current code allows e.g. service names like "http 80/tcp" | ||
| 33 | // to map to port 80. Probably harmless? | ||
| 34 | //if (skip_non_whitespace(name)[0]) | ||
| 35 | // return -1; // bad service name form (has whitespace) | ||
| 36 | |||
| 37 | sp = *p_etc_services; | ||
| 38 | if (!sp) { | ||
| 39 | //TODO: we need mmap_entire_file() for this use case! | ||
| 40 | sp = xmalloc_open_read_close("/etc/services", NULL); | ||
| 41 | if (!sp) | ||
| 42 | return -1; | ||
| 43 | *p_etc_services = sp; | ||
| 44 | } | ||
| 45 | namelen = strlen(name); | ||
| 46 | while (*sp) { | ||
| 47 | const char *pnstr; | ||
| 48 | char *start, *end; | ||
| 49 | unsigned n; | ||
| 50 | |||
| 51 | // First, find a possible service name without regard for line separators (!) | ||
| 52 | start = strstr(sp, name); | ||
| 53 | if (!start) | ||
| 54 | return -1; | ||
| 55 | sp = start + namelen; | ||
| 56 | if (start != *p_etc_services && !isspace(start[-1])) { | ||
| 57 | // There is a char before it, and it's not whitespace | ||
| 58 | continue; | ||
| 59 | } | ||
| 60 | if (!(isspace(*sp) || *sp == '\0' || *sp == '#')) | ||
| 61 | // After it: not whitespace/EOF/comment | ||
| 62 | continue; | ||
| 63 | // The found substring _is_ correctly delimited before and after | ||
| 64 | |||
| 65 | // Find beginning of the line we are on | ||
| 66 | while (start != *p_etc_services && start[-1] != '\n') | ||
| 67 | start--; | ||
| 68 | start = skip_whitespace(start); | ||
| 69 | // Is there a comment char between start of line and "service name"? | ||
| 70 | if (memchr(start, '#', sp - start)) { | ||
| 71 | // The "service name" we found is actually in comment / has comment in it. | ||
| 72 | // Also rejects names with #: | ||
| 73 | // service "www#c" won't be found even on "http 80/tcp www#c" line | ||
| 74 | continue; | ||
| 75 | } | ||
| 76 | // Is the [start...sp) of the form "SERVNAME NUM/PROTO[ ALIAS[ ALIAS...][ ]]"? | ||
| 77 | pnstr = skip_whitespace(skip_non_whitespace(start)); // go to NUM... | ||
| 78 | |||
| 79 | // I've seen this line in /etc/services of a real-world machine: | ||
| 80 | //914c/g 211/tcp 914c-g | ||
| 81 | // Now consider just a bit more pathological example: | ||
| 82 | //914c/tcp 914/tcp 914/tcp | ||
| 83 | // If we search for service name "914/tcp", | ||
| 84 | // we'll find it at second word first, yet we must not reject this line | ||
| 85 | // because the THIRD word also matches, and it's a valid match (tested on glibc!). | ||
| 86 | // But in this case we must not match: | ||
| 87 | //914c/tcp 914/tcp something-else | ||
| 88 | if (sp - namelen == pnstr) | ||
| 89 | continue; // the match is at NUM..., try matching further | ||
| 90 | |||
| 91 | n = bb_strtou(pnstr, &end, 10); | ||
| 92 | if (n > 0xffff || *end != '/') | ||
| 93 | continue; // NUM... part is not a valid port#, or has no slash | ||
| 94 | |||
| 95 | if (proto) { | ||
| 96 | end++; // points to PROTO | ||
| 97 | end = is_prefixed_with(end, proto); | ||
| 98 | if (!end | ||
| 99 | || !(isspace(*end) || *end == '\0' | ||
| 100 | || *end == '#') // glibc treats "http 80/tcp#COMMENT" (no space!) as valid | ||
| 101 | ) { | ||
| 102 | continue; // PROTO does not match | ||
| 103 | } | ||
| 104 | } | ||
| 105 | // By now, either WORD or one of the ALIASes has to be the part | ||
| 106 | // which was found by strstr()! | ||
| 107 | return n; | ||
| 108 | } | ||
| 109 | return -1; | ||
| 110 | } | ||
| 111 | |||
| 112 | // Return port number for a service. | ||
| 113 | // If "port" is a number use it as the port. | ||
| 114 | // If "port" is a name it is looked up in /etc/services. | ||
| 115 | // if NULL, return default_port | ||
| 116 | unsigned FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned port_nr) | ||
| 117 | { | ||
| 118 | if (port) { | ||
| 119 | port_nr = bb_strtou(port, NULL, 10); | ||
| 120 | if (errno || port_nr > 65535) { | ||
| 121 | char *p_etc_services = NULL; | ||
| 122 | port_nr = bb_get_servport_by_name(&p_etc_services, port, protocol); | ||
| 123 | if (port_nr > 0xffff) // -1? | ||
| 124 | bb_error_msg_and_die("bad port '%s'", port); | ||
| 125 | free(p_etc_services); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | return (uint16_t)port_nr; | ||
| 129 | } | ||
diff --git a/libbb/get_last_path_component.c b/libbb/get_last_path_component.c index 46a87d7fc..9be813f80 100644 --- a/libbb/get_last_path_component.c +++ b/libbb/get_last_path_component.c | |||
| @@ -48,20 +48,20 @@ char* FAST_FUNC bb_get_last_path_component_nostrip(const char *path) | |||
| 48 | { | 48 | { |
| 49 | #if ENABLE_PLATFORM_MINGW32 | 49 | #if ENABLE_PLATFORM_MINGW32 |
| 50 | const char *start = path + root_len(path); | 50 | const char *start = path + root_len(path); |
| 51 | char *slash = get_last_slash(path); | 51 | const char *slash = get_last_slash(path); |
| 52 | 52 | ||
| 53 | if (!slash && has_dos_drive_prefix(path) && path[2] != '\0') | 53 | if (!slash && has_dos_drive_prefix(path) && path[2] != '\0') |
| 54 | return (char *)path + 2; | 54 | return (char *)path + 2; |
| 55 | if (!slash || (slash == start && !slash[1])) | 55 | if (!slash || (slash == start && !slash[1])) |
| 56 | return (char*)path; | 56 | return (char*)path; |
| 57 | #else | 57 | #else |
| 58 | char *slash = strrchr(path, '/'); | 58 | const char *slash = strrchr(path, '/'); |
| 59 | 59 | ||
| 60 | if (!slash || (slash == path && !slash[1])) | 60 | if (!slash || (slash == path && !slash[1])) |
| 61 | return (char*)path; | 61 | return (char*)path; |
| 62 | #endif | 62 | #endif |
| 63 | 63 | ||
| 64 | return slash + 1; | 64 | return (char*)slash + 1; |
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | /* | 67 | /* |
diff --git a/libbb/getpty.c b/libbb/getpty.c index 9ec6265ad..2471812c6 100644 --- a/libbb/getpty.c +++ b/libbb/getpty.c | |||
| @@ -9,35 +9,69 @@ | |||
| 9 | 9 | ||
| 10 | #define DEBUG 0 | 10 | #define DEBUG 0 |
| 11 | 11 | ||
| 12 | // On modern systems (old ways were more kludgy with setuid "pt_chown" binary): | ||
| 13 | // ptyfd = open("/dev/ptmx"): | ||
| 14 | // Kernel creates slave /dev/pts/N with owner = real UID of opener, | ||
| 15 | // group and mode as configured by /dev/pts mount options. | ||
| 16 | // The mode is safe with standard mount options (gid=5,mode=620). | ||
| 17 | // | ||
| 18 | // grantpt(ptyfd) is now only a verification step. In glibc: | ||
| 19 | // Calls ioctl(fd, TIOCGPTN, &minor) to confirm ptyfd is valid master pty. | ||
| 20 | // If ioctl fails with ENOTTY, remaps to EINVAL (POSIX). | ||
| 21 | // Otherwise returns 0. (No chown/chmod occurs.) | ||
| 22 | // | ||
| 23 | // unlockpt(ptyfd) is ioctl(TIOCSPTLCK, 0): clears optional protection flag | ||
| 24 | // on slave. If flag was set, open(slave) fails with EIO for everyone (even root) | ||
| 25 | // until unlocked. [Do modern kernels still set the lock in open("/dev/ptmx")?] | ||
| 26 | // The lock historically prevented opening slave before grantpt() fixes perms | ||
| 27 | // in misconfigured/old setups where kernel didn't set 0620 atomically. | ||
| 28 | // Today it's mostly belt-and-suspenders + POSIX compatibility. | ||
| 29 | // | ||
| 30 | // The attack thwarted by the lock (assuming unprivileged adversary): | ||
| 31 | // you open /dev/ptmx, kernel allocates /dev/pts/N with overly permissive mode. | ||
| 32 | // Adversary manages to open /dev/pts/N before your grantpt() call | ||
| 33 | // locks down permissions. | ||
| 34 | // Typically, /dev/pts mount options are gid=5,mode=620, 5 = "tty" group, | ||
| 35 | // system is set up with only trusted binaries and no users in "tty" group. | ||
| 36 | // In this case, the locking mechanism is overkill (would be safe without it). | ||
| 37 | // To fortify more, you can mount with gid=0,mode=600. Tools like "wall", | ||
| 38 | // "mesg" would stop working. | ||
| 39 | // Processes with my own UID can still race against me, but I probably | ||
| 40 | // trust myself... | ||
| 41 | // | ||
| 42 | // ptsname(ptyfd), internally ioctl(TIOCGPTN): get index, build | ||
| 43 | // "/dev/pts/NN" string which refers to the slave pty. | ||
| 12 | int FAST_FUNC xgetpty(char *line) | 44 | int FAST_FUNC xgetpty(char *line) |
| 13 | { | 45 | { |
| 14 | int p; | ||
| 15 | |||
| 16 | #if ENABLE_FEATURE_DEVPTS | 46 | #if ENABLE_FEATURE_DEVPTS |
| 17 | p = open("/dev/ptmx", O_RDWR); | 47 | int ptyfd; |
| 18 | if (p >= 0) { | 48 | |
| 19 | grantpt(p); /* chmod+chown corresponding slave pty */ | 49 | ptyfd = open("/dev/ptmx", O_RDWR); |
| 20 | unlockpt(p); /* (what does this do?) */ | 50 | if (ptyfd < 0) |
| 51 | bb_simple_perror_msg_and_die("can't find free pty"); | ||
| 52 | grantpt(ptyfd); /* chmod+chown corresponding slave pty */ | ||
| 53 | unlockpt(ptyfd); /* allow open() on slave /dev node */ | ||
| 21 | # ifndef HAVE_PTSNAME_R | 54 | # ifndef HAVE_PTSNAME_R |
| 22 | { | 55 | { |
| 23 | const char *name; | 56 | const char *name; |
| 24 | name = ptsname(p); /* find out the name of slave pty */ | 57 | name = ptsname(ptyfd); /* find out the name of slave pty */ |
| 25 | if (!name) { | 58 | if (!name) { |
| 26 | bb_simple_perror_msg_and_die("ptsname error (is /dev/pts mounted?)"); | ||
| 27 | } | ||
| 28 | safe_strncpy(line, name, GETPTY_BUFSIZE); | ||
| 29 | } | ||
| 30 | # else | ||
| 31 | /* find out the name of slave pty */ | ||
| 32 | if (ptsname_r(p, line, GETPTY_BUFSIZE-1) != 0) { | ||
| 33 | bb_simple_perror_msg_and_die("ptsname error (is /dev/pts mounted?)"); | 59 | bb_simple_perror_msg_and_die("ptsname error (is /dev/pts mounted?)"); |
| 34 | } | 60 | } |
| 35 | line[GETPTY_BUFSIZE-1] = '\0'; | 61 | safe_strncpy(line, name, GETPTY_BUFSIZE); |
| 36 | # endif | ||
| 37 | return p; | ||
| 38 | } | 62 | } |
| 63 | # else | ||
| 64 | /* find out the name of slave pty */ | ||
| 65 | if (ptsname_r(ptyfd, line, GETPTY_BUFSIZE-1) != 0) { | ||
| 66 | bb_simple_perror_msg_and_die("ptsname error (is /dev/pts mounted?)"); | ||
| 67 | } | ||
| 68 | line[GETPTY_BUFSIZE-1] = '\0'; | ||
| 69 | # endif | ||
| 70 | return ptyfd; | ||
| 71 | |||
| 39 | #else | 72 | #else |
| 40 | struct stat stb; | 73 | struct stat stb; |
| 74 | int ptyfd; | ||
| 41 | int i; | 75 | int i; |
| 42 | int j; | 76 | int j; |
| 43 | 77 | ||
| @@ -53,13 +87,13 @@ int FAST_FUNC xgetpty(char *line) | |||
| 53 | line[9] = j < 10 ? j + '0' : j - 10 + 'a'; | 87 | line[9] = j < 10 ? j + '0' : j - 10 + 'a'; |
| 54 | if (DEBUG) | 88 | if (DEBUG) |
| 55 | fprintf(stderr, "Trying to open device: %s\n", line); | 89 | fprintf(stderr, "Trying to open device: %s\n", line); |
| 56 | p = open(line, O_RDWR | O_NOCTTY); | 90 | ptyfd = open(line, O_RDWR | O_NOCTTY); |
| 57 | if (p >= 0) { | 91 | if (ptyfd >= 0) { |
| 58 | line[5] = 't'; | 92 | line[5] = 't'; |
| 59 | return p; | 93 | return ptyfd; |
| 60 | } | 94 | } |
| 61 | } | 95 | } |
| 62 | } | 96 | } |
| 63 | #endif /* FEATURE_DEVPTS */ | ||
| 64 | bb_simple_error_msg_and_die("can't find free pty"); | 97 | bb_simple_error_msg_and_die("can't find free pty"); |
| 98 | #endif /* FEATURE_DEVPTS */ | ||
| 65 | } | 99 | } |
diff --git a/libbb/ioloop.c b/libbb/ioloop.c new file mode 100644 index 000000000..cc9876487 --- /dev/null +++ b/libbb/ioloop.c | |||
| @@ -0,0 +1,264 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 2 | /* | ||
| 3 | * Copyright (C) 2026 Denys Vlasenko <vda.linux@googlemail.com> | ||
| 4 | * | ||
| 5 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
| 6 | */ | ||
| 7 | //kbuild:lib-$(CONFIG_TELNETD) += ioloop.o | ||
| 8 | |||
| 9 | #include "libbb.h" | ||
| 10 | |||
| 11 | #define DEBUG 0 | ||
| 12 | |||
| 13 | #if DEBUG | ||
| 14 | # define dbg(...) bb_error_msg(__VA_ARGS__) | ||
| 15 | static char *bin_to_hex(const void *hash_value, unsigned hash_length) | ||
| 16 | { | ||
| 17 | /* xzalloc zero-terminates */ | ||
| 18 | char *hex_value = xzalloc((hash_length * 2) + 1); | ||
| 19 | bin2hex(hex_value, (char*)hash_value, hash_length); | ||
| 20 | return auto_string(hex_value); | ||
| 21 | } | ||
| 22 | #else | ||
| 23 | # define dbg(...) ((void)0) | ||
| 24 | #endif | ||
| 25 | |||
| 26 | void FAST_FUNC ioloop_insert_conn(ioloop_state_t *io, connection_t *conn) | ||
| 27 | { | ||
| 28 | conn->io = io; | ||
| 29 | conn->next = io->conns; | ||
| 30 | io->conns = conn; | ||
| 31 | } | ||
| 32 | |||
| 33 | void FAST_FUNC ioloop_remove_conn(ioloop_state_t *io, connection_t *conn) | ||
| 34 | { | ||
| 35 | connection_t **pp = &io->conns; | ||
| 36 | while (*pp) { | ||
| 37 | if (*pp == conn) { | ||
| 38 | *pp = conn->next; | ||
| 39 | return; | ||
| 40 | } | ||
| 41 | pp = &(*pp)->next; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | void FAST_FUNC conn_close_fds(connection_t *conn) | ||
| 46 | { | ||
| 47 | if (conn->read_fd >= 0) | ||
| 48 | close(conn->read_fd); | ||
| 49 | if (conn->write_fd >= 0 && conn->write_fd != conn->read_fd) | ||
| 50 | close(conn->write_fd); | ||
| 51 | conn->write_fd = -1; | ||
| 52 | conn->read_fd = -1; | ||
| 53 | } | ||
| 54 | |||
| 55 | void FAST_FUNC conn_close_fds_remove_and_free(connection_t *conn) | ||
| 56 | { | ||
| 57 | conn_close_fds(conn); | ||
| 58 | ioloop_remove_conn(conn->io, conn); | ||
| 59 | free_connection(conn); | ||
| 60 | } | ||
| 61 | |||
| 62 | #ifdef UNUSED | ||
| 63 | void FAST_FUNC ioloop_close_fd_in_all_conns(ioloop_state_t *io, int fd) | ||
| 64 | { | ||
| 65 | connection_t *conn; | ||
| 66 | |||
| 67 | close(fd); | ||
| 68 | conn = io->conns; | ||
| 69 | while (conn) { | ||
| 70 | if (conn->read_fd == fd) | ||
| 71 | conn->read_fd = -1; | ||
| 72 | if (conn->write_fd == fd) | ||
| 73 | conn->write_fd = -1; | ||
| 74 | conn = conn->next; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | #endif | ||
| 78 | |||
| 79 | // have_data_to_write() - Do we have data to write? | ||
| 80 | // May return error if knows that write side is closed, even if it has free buffer space. | ||
| 81 | // > 0: Has data to write (or can generate such data) | ||
| 82 | // In this case, write_fd must be valid! (it's a bug if it is < 0, can crash) | ||
| 83 | // 0: No data to write currently | ||
| 84 | // < 0: error, I probably freed myself (do not use my structure in this iteration, | ||
| 85 | // on next iteration, if I indeed freed myself, you won't find me in the list). | ||
| 86 | // | ||
| 87 | // have_buffer_to_read_into() - Is there a buffer to read into? | ||
| 88 | // May return error if knows that write side is closed, even if it has free buffer space. | ||
| 89 | // > 0: Healthy, has buffer space, please poll read_fd | ||
| 90 | // In this case, read_fd must be valid! (it's a bug if it is < 0) | ||
| 91 | // 0: One of: | ||
| 92 | // Buffer is full (hopefully write() will free some) | ||
| 93 | // Got error/EOF, want to drain write buffer first | ||
| 94 | // < 0: Error/EOF, I probably freed myself (do not use my structure) | ||
| 95 | // | ||
| 96 | // Essentially: add your "this connection is dead, close+ioloop_remove_conn(this)+free(this)" code | ||
| 97 | // to either of the above two functions. | ||
| 98 | // Either would work equally well from ioloop POV. | ||
| 99 | // | ||
| 100 | // write() - Perform write | ||
| 101 | // Even if poll shows that write_fd become ready, the method will not be called if conn->write_fd become < 0. | ||
| 102 | // However, changing it to another fd >= 0 is a bug. | ||
| 103 | // >= 0: No error (ioloop() does not handle 0 specially) | ||
| 104 | // < 0: Error occurred, I probably freed myself (do not use my structure) | ||
| 105 | // | ||
| 106 | // Error returns from read() and write() will skip the opposite op. | ||
| 107 | // (Which one happens first depends on ioloop_run() code, as I write this, write() | ||
| 108 | // is done first - therefore read() can't skip write()). | ||
| 109 | // IOW: if you "remove(this)" in either of them you MUST return < 0. | ||
| 110 | // | ||
| 111 | // Write function, if it performs some processing, needs to account for the possibility | ||
| 112 | // that any "incomplete data, can't write, wait for more" logic should also check whether | ||
| 113 | // "more" is even possible: your other code may know that the read side is at EOF, | ||
| 114 | // and arrange for it to not be polled. Thus a hang: read not reading, write waits for more date. | ||
| 115 | // | ||
| 116 | // read() - Perform read | ||
| 117 | // Will not be called if read_fd become < 0. | ||
| 118 | // >= 0: No error (0 is EOF, ioloop() does not handle 0 specially) | ||
| 119 | // < 0: Error occurred, I probably freed myself (do not use my structure) | ||
| 120 | // | ||
| 121 | // Putting "this connection is dead, close+remove+free" code into read function | ||
| 122 | // is often inconvenient: if you got EOF/error on read, you still want to poll write side | ||
| 123 | // and try to write out the buffered data to it. Which means read() can't "remove+free". | ||
| 124 | // Instead, you can remember EOF/error and make future have_buffer_to_read_into() respond 0. | ||
| 125 | // One way is to close (if possible) read_fd, set it to -1 and use as a flag. | ||
| 126 | // | ||
| 127 | // If need to support one-sided close, such as when HTTP/1.x client sends us | ||
| 128 | // "GET / HTTP/1.1\r\n\r\n" and closes its writing side with shutdown(SHUT_WR), | ||
| 129 | // the idiom is that when read() sees EOF, it sets conn->read_fd to -1 | ||
| 130 | // and subsequently have_buffer_to_read_into() always return 0 (no more attempts to read); | ||
| 131 | // write() flushes all remaining data to conn->write_fd and then signals EOF | ||
| 132 | // to write_fd: shutdown(SHUT_WR) for sockets, close() for pipes | ||
| 133 | // (how to do this for ptys!?). | ||
| 134 | // After this, have_data_to_write() can return 0 if fd has to stay open (socket) | ||
| 135 | // or can return -1 and free itself if fd is closed. | ||
| 136 | |||
| 137 | static ALWAYS_INLINE int have_data_to_write(connection_t *conn) | ||
| 138 | { | ||
| 139 | return conn->have_data_to_write(conn); | ||
| 140 | } | ||
| 141 | static ALWAYS_INLINE int have_buffer_to_read_into(connection_t *conn) | ||
| 142 | { | ||
| 143 | return conn->have_buffer_to_read_into(conn); | ||
| 144 | } | ||
| 145 | static ALWAYS_INLINE int write_from_buf(connection_t *conn) | ||
| 146 | { | ||
| 147 | return conn->write(conn); | ||
| 148 | } | ||
| 149 | static ALWAYS_INLINE int read_to_buf(connection_t *conn) | ||
| 150 | { | ||
| 151 | return conn->read(conn); | ||
| 152 | } | ||
| 153 | |||
| 154 | int FAST_FUNC ioloop_run(ioloop_state_t *io) | ||
| 155 | { | ||
| 156 | connection_t *conn; | ||
| 157 | unsigned poll_timeout_us; | ||
| 158 | int maxfd; | ||
| 159 | int count; | ||
| 160 | struct timeval tv; | ||
| 161 | struct timeval *tv_ptr; | ||
| 162 | fd_set rdfdset, wrfdset; | ||
| 163 | |||
| 164 | io->current_iteration_timeout = io->max_timeout ? io->max_timeout : UINT_MAX; | ||
| 165 | //bb_error_msg("io->current_iteration_timeout:%d", io->current_iteration_timeout); | ||
| 166 | again: | ||
| 167 | FD_ZERO(&rdfdset); | ||
| 168 | FD_ZERO(&wrfdset); | ||
| 169 | |||
| 170 | conn = io->conns; | ||
| 171 | if (!conn) | ||
| 172 | return IOLOOP_NO_CONNS; | ||
| 173 | |||
| 174 | maxfd = -1; | ||
| 175 | while (conn) { | ||
| 176 | connection_t *next; | ||
| 177 | int rcw, rcr; | ||
| 178 | |||
| 179 | next = conn->next; /* in case conn is freed */ | ||
| 180 | rcw = have_data_to_write(conn); | ||
| 181 | if (rcw < 0) { | ||
| 182 | /* often indicates that conn is gone (freed), do not use it anymore */ | ||
| 183 | goto next; | ||
| 184 | } | ||
| 185 | /* Do not add conn->write_fd to waiting set yet, | ||
| 186 | * the *reader* may decide to abort (return rcr < 0)! | ||
| 187 | * Check it first: | ||
| 188 | */ | ||
| 189 | rcr = have_buffer_to_read_into(conn); | ||
| 190 | if (rcr < 0) | ||
| 191 | goto next; | ||
| 192 | if (rcw > 0) { | ||
| 193 | FD_SET(conn->write_fd, &wrfdset); | ||
| 194 | //FIXME: what if fd > 1023 | ||
| 195 | if (conn->write_fd > maxfd) | ||
| 196 | maxfd = conn->write_fd; | ||
| 197 | } | ||
| 198 | if (rcr > 0) { | ||
| 199 | FD_SET(conn->read_fd, &rdfdset); | ||
| 200 | if (conn->read_fd > maxfd) | ||
| 201 | maxfd = conn->read_fd; | ||
| 202 | } | ||
| 203 | next: | ||
| 204 | conn = next; | ||
| 205 | } | ||
| 206 | if (!io->conns) | ||
| 207 | return IOLOOP_NO_CONNS; /* all conns removed themselves before we reached polling */ | ||
| 208 | |||
| 209 | poll_timeout_us = io->current_iteration_timeout; | ||
| 210 | /* May be useful to know after loop exit: */ | ||
| 211 | io->last_timeout = poll_timeout_us; | ||
| 212 | dbg("poll_timeout_us:%d", poll_timeout_us); | ||
| 213 | |||
| 214 | tv_ptr = NULL; /* infinite timeout */ | ||
| 215 | if (poll_timeout_us != UINT_MAX) { | ||
| 216 | tv.tv_sec = poll_timeout_us / 1000000; | ||
| 217 | tv.tv_usec = poll_timeout_us % 1000000; | ||
| 218 | tv_ptr = &tv; | ||
| 219 | } | ||
| 220 | maxfd++; | ||
| 221 | count = select(maxfd, maxfd ? &rdfdset : NULL, maxfd ? &wrfdset : NULL, NULL, tv_ptr); | ||
| 222 | dbg("select:%d", count); | ||
| 223 | |||
| 224 | /* Any function in the loop can change it, modifying next select() timeout */ | ||
| 225 | io->current_iteration_timeout = io->max_timeout ? io->max_timeout : UINT_MAX; | ||
| 226 | dbg("io->current_iteration_timeout:%d", io->current_iteration_timeout); | ||
| 227 | |||
| 228 | if (count <= 0) { | ||
| 229 | /* 0: timeout */ | ||
| 230 | if (count == 0 && (io->flags & IOLOOP_FLAG_EXIT_IF_TIMEOUT)) | ||
| 231 | return IOLOOP_TIMEOUT; | ||
| 232 | /* < 0: EINTR or ENOMEM */ | ||
| 233 | if (count < 0 && (io->flags & IOLOOP_FLAG_EXIT_IF_EINTR) && errno == EINTR) | ||
| 234 | return IOLOOP_EINTR; | ||
| 235 | goto again; | ||
| 236 | } | ||
| 237 | |||
| 238 | conn = io->conns; | ||
| 239 | while (conn) { | ||
| 240 | connection_t *next = conn->next; /* in case conn freed */ | ||
| 241 | int rcw; | ||
| 242 | |||
| 243 | /* We do check for valid fd >=0: this means that conns | ||
| 244 | * may prevent I/O of other conns by setting fds to -1. | ||
| 245 | */ | ||
| 246 | dbg("conn->write_fd:%d ?", conn->write_fd); | ||
| 247 | if (conn->write_fd >= 0 && FD_ISSET(conn->write_fd, &wrfdset)) { | ||
| 248 | dbg("conn->write_fd:%d ready", conn->write_fd); | ||
| 249 | rcw = write_from_buf(conn); | ||
| 250 | if (rcw < 0) { | ||
| 251 | /* often indicates that conn is gone (freed), do not use it anymore */ | ||
| 252 | goto next1; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | dbg("conn->read_fd:%d ?", conn->read_fd); | ||
| 256 | if (conn->read_fd >= 0 && FD_ISSET(conn->read_fd, &rdfdset)) { | ||
| 257 | dbg("conn->read_fd:%d ready", conn->read_fd); | ||
| 258 | read_to_buf(conn); | ||
| 259 | } | ||
| 260 | next1: | ||
| 261 | conn = next; | ||
| 262 | } | ||
| 263 | goto again; | ||
| 264 | } | ||
diff --git a/libbb/login.c b/libbb/login.c index af860c277..ca0da70b4 100644 --- a/libbb/login.c +++ b/libbb/login.c | |||
| @@ -28,8 +28,6 @@ void FAST_FUNC print_login_issue(const char *issue_file, const char *tty) | |||
| 28 | time(&t); | 28 | time(&t); |
| 29 | uname(&uts); | 29 | uname(&uts); |
| 30 | 30 | ||
| 31 | puts("\r"); /* start a new line */ | ||
| 32 | |||
| 33 | fp = fopen_for_read(issue_file); | 31 | fp = fopen_for_read(issue_file); |
| 34 | if (!fp) | 32 | if (!fp) |
| 35 | return; | 33 | return; |
diff --git a/libbb/read_key.c b/libbb/read_key.c index 2414105ee..07cf1af5a 100644 --- a/libbb/read_key.c +++ b/libbb/read_key.c | |||
| @@ -26,7 +26,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 26 | 'd' |0x80,KEYCODE_ALT_D , | 26 | 'd' |0x80,KEYCODE_ALT_D , |
| 27 | /* lineedit mimics bash: Alt-f and Alt-b are forward/backward | 27 | /* lineedit mimics bash: Alt-f and Alt-b are forward/backward |
| 28 | * word jumps. We cheat here and make them return ALT_LEFT/RIGHT | 28 | * word jumps. We cheat here and make them return ALT_LEFT/RIGHT |
| 29 | * keycodes. This way, lineedit need no special code to handle them. | 29 | * keycodes. This way, lineedit needs no special code to handle them. |
| 30 | * If we'll need to distinguish them, introduce new ALT_F/B keycodes, | 30 | * If we'll need to distinguish them, introduce new ALT_F/B keycodes, |
| 31 | * and update lineedit to react to them. | 31 | * and update lineedit to react to them. |
| 32 | */ | 32 | */ |
| @@ -40,12 +40,12 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 40 | 'O','F' |0x80,KEYCODE_END , | 40 | 'O','F' |0x80,KEYCODE_END , |
| 41 | #if 0 | 41 | #if 0 |
| 42 | 'O','P' |0x80,KEYCODE_FUN1 , | 42 | 'O','P' |0x80,KEYCODE_FUN1 , |
| 43 | /* [ESC] ESC O [2] P - [Alt-][Shift-]F1 */ | 43 | // [ESC] ESC O [2] P - [Alt-][Shift-]F1 |
| 44 | /* ESC [ O 1 ; 2 P - Shift-F1 */ | 44 | // ESC [ O 1 ; 2 P - Shift-F1 |
| 45 | /* ESC [ O 1 ; 3 P - Alt-F1 */ | 45 | // ESC [ O 1 ; 3 P - Alt-F1 |
| 46 | /* ESC [ O 1 ; 4 P - Alt-Shift-F1 */ | 46 | // ESC [ O 1 ; 4 P - Alt-Shift-F1 |
| 47 | /* ESC [ O 1 ; 5 P - Ctrl-F1 */ | 47 | // ESC [ O 1 ; 5 P - Ctrl-F1 |
| 48 | /* ESC [ O 1 ; 6 P - Ctrl-Shift-F1 */ | 48 | // ESC [ O 1 ; 6 P - Ctrl-Shift-F1 |
| 49 | 'O','Q' |0x80,KEYCODE_FUN2 , | 49 | 'O','Q' |0x80,KEYCODE_FUN2 , |
| 50 | 'O','R' |0x80,KEYCODE_FUN3 , | 50 | 'O','R' |0x80,KEYCODE_FUN3 , |
| 51 | 'O','S' |0x80,KEYCODE_FUN4 , | 51 | 'O','S' |0x80,KEYCODE_FUN4 , |
| @@ -54,29 +54,29 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 54 | '[','B' |0x80,KEYCODE_DOWN , | 54 | '[','B' |0x80,KEYCODE_DOWN , |
| 55 | '[','C' |0x80,KEYCODE_RIGHT , | 55 | '[','C' |0x80,KEYCODE_RIGHT , |
| 56 | '[','D' |0x80,KEYCODE_LEFT , | 56 | '[','D' |0x80,KEYCODE_LEFT , |
| 57 | /* ESC [ 1 ; 2 x, where x = A/B/C/D: Shift-<arrow> */ | 57 | // ESC [ 1 ; 2 x, where x = A/B/C/D: Shift-<arrow> |
| 58 | /* ESC [ 1 ; 3 x, where x = A/B/C/D: Alt-<arrow> - implemented below */ | 58 | // ESC [ 1 ; 3 x, where x = A/B/C/D: Alt-<arrow> - implemented below |
| 59 | /* ESC [ 1 ; 4 x, where x = A/B/C/D: Alt-Shift-<arrow> */ | 59 | // ESC [ 1 ; 4 x, where x = A/B/C/D: Alt-Shift-<arrow> |
| 60 | /* ESC [ 1 ; 5 x, where x = A/B/C/D: Ctrl-<arrow> - implemented below */ | 60 | // ESC [ 1 ; 5 x, where x = A/B/C/D: Ctrl-<arrow> - implemented below |
| 61 | /* ESC [ 1 ; 6 x, where x = A/B/C/D: Ctrl-Shift-<arrow> */ | 61 | // ESC [ 1 ; 6 x, where x = A/B/C/D: Ctrl-Shift-<arrow> |
| 62 | /* ESC [ 1 ; 7 x, where x = A/B/C/D: Ctrl-Alt-<arrow> */ | 62 | // ESC [ 1 ; 7 x, where x = A/B/C/D: Ctrl-Alt-<arrow> |
| 63 | /* ESC [ 1 ; 8 x, where x = A/B/C/D: Ctrl-Alt-Shift-<arrow> */ | 63 | // ESC [ 1 ; 8 x, where x = A/B/C/D: Ctrl-Alt-Shift-<arrow> |
| 64 | '[','H' |0x80,KEYCODE_HOME , /* xterm */ | 64 | '[','H' |0x80,KEYCODE_HOME , /* xterm */ |
| 65 | '[','F' |0x80,KEYCODE_END , /* xterm */ | 65 | '[','F' |0x80,KEYCODE_END , /* xterm */ |
| 66 | /* [ESC] ESC [ [2] H - [Alt-][Shift-]Home (End similarly?) */ | 66 | // [ESC] ESC [ [2] H - [Alt-][Shift-]Home (End similarly?) |
| 67 | /* '[','Z' |0x80,KEYCODE_SHIFT_TAB, */ | 67 | // '[','Z' |0x80,KEYCODE_SHIFT_TAB, |
| 68 | '[','1','~' |0x80,KEYCODE_HOME , /* vt100? linux vt? or what? */ | 68 | '[','1','~' |0x80,KEYCODE_HOME , /* vt100? linux vt? or what? */ |
| 69 | '[','2','~' |0x80,KEYCODE_INSERT , | 69 | '[','2','~' |0x80,KEYCODE_INSERT , |
| 70 | /* ESC [ 2 ; 3 ~ - Alt-Insert */ | 70 | // ESC [ 2 ; 3 ~ - Alt-Insert |
| 71 | '[','3','~' |0x80,KEYCODE_DELETE , | 71 | '[','3','~' |0x80,KEYCODE_DELETE , |
| 72 | /* [ESC] ESC [ 3 [;2] ~ - [Alt-][Shift-]Delete */ | 72 | // [ESC] ESC [ 3 [;2] ~ - [Alt-][Shift-]Delete |
| 73 | /* ESC [ 3 ; 3 ~ - Alt-Delete */ | 73 | // ESC [ 3 ; 3 ~ - Alt-Delete |
| 74 | /* ESC [ 3 ; 5 ~ - Ctrl-Delete */ | 74 | // ESC [ 3 ; 5 ~ - Ctrl-Delete |
| 75 | '[','4','~' |0x80,KEYCODE_END , /* vt100? linux vt? or what? */ | 75 | '[','4','~' |0x80,KEYCODE_END , /* vt100? linux vt? or what? */ |
| 76 | '[','5','~' |0x80,KEYCODE_PAGEUP , | 76 | '[','5','~' |0x80,KEYCODE_PAGEUP , |
| 77 | /* ESC [ 5 ; 3 ~ - Alt-PgUp */ | 77 | // ESC [ 5 ; 3 ~ - Alt-PgUp |
| 78 | /* ESC [ 5 ; 5 ~ - Ctrl-PgUp */ | 78 | // ESC [ 5 ; 5 ~ - Ctrl-PgUp |
| 79 | /* ESC [ 5 ; 7 ~ - Ctrl-Alt-PgUp */ | 79 | // ESC [ 5 ; 7 ~ - Ctrl-Alt-PgUp |
| 80 | '[','6','~' |0x80,KEYCODE_PAGEDOWN, | 80 | '[','6','~' |0x80,KEYCODE_PAGEDOWN, |
| 81 | '[','7','~' |0x80,KEYCODE_HOME , /* vt100? linux vt? or what? */ | 81 | '[','7','~' |0x80,KEYCODE_HOME , /* vt100? linux vt? or what? */ |
| 82 | '[','8','~' |0x80,KEYCODE_END , /* vt100? linux vt? or what? */ | 82 | '[','8','~' |0x80,KEYCODE_END , /* vt100? linux vt? or what? */ |
| @@ -86,7 +86,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 86 | '[','1','3','~'|0x80,KEYCODE_FUN3 , /* old xterm... */ | 86 | '[','1','3','~'|0x80,KEYCODE_FUN3 , /* old xterm... */ |
| 87 | '[','1','4','~'|0x80,KEYCODE_FUN4 , /* old xterm... */ | 87 | '[','1','4','~'|0x80,KEYCODE_FUN4 , /* old xterm... */ |
| 88 | '[','1','5','~'|0x80,KEYCODE_FUN5 , | 88 | '[','1','5','~'|0x80,KEYCODE_FUN5 , |
| 89 | /* [ESC] ESC [ 1 5 [;2] ~ - [Alt-][Shift-]F5 */ | 89 | // [ESC] ESC [ 1 5 [;2] ~ - [Alt-][Shift-]F5 |
| 90 | '[','1','7','~'|0x80,KEYCODE_FUN6 , | 90 | '[','1','7','~'|0x80,KEYCODE_FUN6 , |
| 91 | '[','1','8','~'|0x80,KEYCODE_FUN7 , | 91 | '[','1','8','~'|0x80,KEYCODE_FUN7 , |
| 92 | '[','1','9','~'|0x80,KEYCODE_FUN8 , | 92 | '[','1','9','~'|0x80,KEYCODE_FUN8 , |
| @@ -94,21 +94,21 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 94 | '[','2','1','~'|0x80,KEYCODE_FUN10 , | 94 | '[','2','1','~'|0x80,KEYCODE_FUN10 , |
| 95 | '[','2','3','~'|0x80,KEYCODE_FUN11 , | 95 | '[','2','3','~'|0x80,KEYCODE_FUN11 , |
| 96 | '[','2','4','~'|0x80,KEYCODE_FUN12 , | 96 | '[','2','4','~'|0x80,KEYCODE_FUN12 , |
| 97 | /* ESC [ 2 4 ; 2 ~ - Shift-F12 */ | 97 | // ESC [ 2 4 ; 2 ~ - Shift-F12 |
| 98 | /* ESC [ 2 4 ; 3 ~ - Alt-F12 */ | 98 | // ESC [ 2 4 ; 3 ~ - Alt-F12 |
| 99 | /* ESC [ 2 4 ; 4 ~ - Alt-Shift-F12 */ | 99 | // ESC [ 2 4 ; 4 ~ - Alt-Shift-F12 |
| 100 | /* ESC [ 2 4 ; 5 ~ - Ctrl-F12 */ | 100 | // ESC [ 2 4 ; 5 ~ - Ctrl-F12 |
| 101 | /* ESC [ 2 4 ; 6 ~ - Ctrl-Shift-F12 */ | 101 | // ESC [ 2 4 ; 6 ~ - Ctrl-Shift-F12 |
| 102 | #endif | 102 | #endif |
| 103 | /* '[','1',';','5','A' |0x80,KEYCODE_CTRL_UP , - unused */ | 103 | // '[','1',';','3','A' |0x80,KEYCODE_ALT_UP , - unused |
| 104 | /* '[','1',';','5','B' |0x80,KEYCODE_CTRL_DOWN , - unused */ | 104 | // '[','1',';','3','B' |0x80,KEYCODE_ALT_DOWN , - unused |
| 105 | '[','1',';','5','C' |0x80,KEYCODE_CTRL_RIGHT, | ||
| 106 | '[','1',';','5','D' |0x80,KEYCODE_CTRL_LEFT , | ||
| 107 | /* '[','1',';','3','A' |0x80,KEYCODE_ALT_UP , - unused */ | ||
| 108 | /* '[','1',';','3','B' |0x80,KEYCODE_ALT_DOWN , - unused */ | ||
| 109 | '[','1',';','3','C' |0x80,KEYCODE_ALT_RIGHT, | 105 | '[','1',';','3','C' |0x80,KEYCODE_ALT_RIGHT, |
| 110 | '[','1',';','3','D' |0x80,KEYCODE_ALT_LEFT , | 106 | '[','1',';','3','D' |0x80,KEYCODE_ALT_LEFT , |
| 111 | /* '[','3',';','3','~' |0x80,KEYCODE_ALT_DELETE, - unused */ | 107 | // '[','1',';','5','A' |0x80,KEYCODE_CTRL_UP , - unused |
| 108 | // '[','1',';','5','B' |0x80,KEYCODE_CTRL_DOWN , - unused | ||
| 109 | '[','1',';','5','C' |0x80,KEYCODE_CTRL_RIGHT, | ||
| 110 | '[','1',';','5','D' |0x80,KEYCODE_CTRL_LEFT , | ||
| 111 | // '[','3',';','3','~' |0x80,KEYCODE_ALT_DELETE, - unused | ||
| 112 | 0 | 112 | 0 |
| 113 | }; | 113 | }; |
| 114 | 114 | ||
| @@ -210,7 +210,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 210 | /* Forward to last char */ | 210 | /* Forward to last char */ |
| 211 | while (!(*seq & 0x80)) | 211 | while (!(*seq & 0x80)) |
| 212 | seq++; | 212 | seq++; |
| 213 | /* Skip it and the keycode which follows */ | 213 | /* Skip seq terminating byte, and KEYCODE_xyz byte that follows */ |
| 214 | seq += 2; | 214 | seq += 2; |
| 215 | break; | 215 | break; |
| 216 | } | 216 | } |
| @@ -222,6 +222,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout) | |||
| 222 | * but we never read ahead that much, | 222 | * but we never read ahead that much, |
| 223 | * and n == i here. */ | 223 | * and n == i here. */ |
| 224 | buffer[-1] = 0; | 224 | buffer[-1] = 0; |
| 225 | /* Return KEYCODE_xyz (always negative) */ | ||
| 225 | return (signed char)seq[i+1]; | 226 | return (signed char)seq[i+1]; |
| 226 | } | 227 | } |
| 227 | i++; | 228 | i++; |
diff --git a/libbb/replace.c b/libbb/replace.c index bc26b04cc..273330f8a 100644 --- a/libbb/replace.c +++ b/libbb/replace.c | |||
| @@ -28,7 +28,8 @@ unsigned FAST_FUNC count_strstr(const char *str, const char *sub) | |||
| 28 | 28 | ||
| 29 | char* FAST_FUNC xmalloc_substitute_string(const char *src, int count, const char *sub, const char *repl) | 29 | char* FAST_FUNC xmalloc_substitute_string(const char *src, int count, const char *sub, const char *repl) |
| 30 | { | 30 | { |
| 31 | char *buf, *dst, *end; | 31 | char *buf, *dst; |
| 32 | const char *end; | ||
| 32 | size_t sub_len = strlen(sub); | 33 | size_t sub_len = strlen(sub); |
| 33 | size_t repl_len = strlen(repl); | 34 | size_t repl_len = strlen(repl); |
| 34 | 35 | ||
diff --git a/libbb/strrstr.c b/libbb/strrstr.c index a173b034f..bea5d1773 100644 --- a/libbb/strrstr.c +++ b/libbb/strrstr.c | |||
| @@ -19,10 +19,10 @@ char* FAST_FUNC strrstr(const char *haystack, const char *needle) | |||
| 19 | if (!needle[0]) | 19 | if (!needle[0]) |
| 20 | return (char*)haystack + strlen(haystack); | 20 | return (char*)haystack + strlen(haystack); |
| 21 | while (1) { | 21 | while (1) { |
| 22 | char *p = strstr(haystack, needle); | 22 | const char *p = strstr(haystack, needle); |
| 23 | if (!p) | 23 | if (!p) |
| 24 | return r; | 24 | return r; |
| 25 | r = p; | 25 | r = (char *)p; |
| 26 | haystack = p + 1; | 26 | haystack = p + 1; |
| 27 | } | 27 | } |
| 28 | } | 28 | } |
diff --git a/libbb/xconnect.c b/libbb/xconnect.c index 65b1cb8de..c0ed94336 100644 --- a/libbb/xconnect.c +++ b/libbb/xconnect.c | |||
| @@ -115,29 +115,8 @@ void FAST_FUNC xconnect(int s, const struct sockaddr *saddr, socklen_t addrlen) | |||
| 115 | } | 115 | } |
| 116 | } | 116 | } |
| 117 | 117 | ||
| 118 | /* Return port number for a service. | ||
| 119 | * If "port" is a number use it as the port. | ||
| 120 | * If "port" is a name it is looked up in /etc/services. | ||
| 121 | * if NULL, return default_port | ||
| 122 | */ | ||
| 123 | unsigned FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned port_nr) | ||
| 124 | { | ||
| 125 | if (port) { | ||
| 126 | port_nr = bb_strtou(port, NULL, 10); | ||
| 127 | if (errno || port_nr > 65535) { | ||
| 128 | struct servent *tserv = getservbyname(port, protocol); | ||
| 129 | if (!tserv) | ||
| 130 | bb_error_msg_and_die("bad port '%s'", port); | ||
| 131 | port_nr = ntohs(tserv->s_port); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | return (uint16_t)port_nr; | ||
| 135 | } | ||
| 136 | |||
| 137 | |||
| 138 | /* "New" networking API */ | 118 | /* "New" networking API */ |
| 139 | 119 | ||
| 140 | |||
| 141 | int FAST_FUNC get_nport(const struct sockaddr *sa) | 120 | int FAST_FUNC get_nport(const struct sockaddr *sa) |
| 142 | { | 121 | { |
| 143 | #if ENABLE_FEATURE_IPV6 | 122 | #if ENABLE_FEATURE_IPV6 |
diff --git a/libbb/xfuncs_printf.c b/libbb/xfuncs_printf.c index f4d1b913a..3eb30fd94 100644 --- a/libbb/xfuncs_printf.c +++ b/libbb/xfuncs_printf.c | |||
| @@ -381,7 +381,7 @@ void FAST_FUNC bb_unsetenv(const char *var) | |||
| 381 | char onstack[128 - 16]; /* smaller stack setup code on x86 */ | 381 | char onstack[128 - 16]; /* smaller stack setup code on x86 */ |
| 382 | char *tp; | 382 | char *tp; |
| 383 | 383 | ||
| 384 | tp = strchr(var, '='); | 384 | tp = (char*)strchr(var, '='); |
| 385 | if (tp) { | 385 | if (tp) { |
| 386 | /* In case var was putenv'ed, we can't replace '=' | 386 | /* In case var was putenv'ed, we can't replace '=' |
| 387 | * with NUL and unsetenv(var) - it won't work, | 387 | * with NUL and unsetenv(var) - it won't work, |
diff --git a/libpwdgrp/uidgid_get.c b/libpwdgrp/uidgid_get.c index d76eb8298..2aa444416 100644 --- a/libpwdgrp/uidgid_get.c +++ b/libpwdgrp/uidgid_get.c | |||
| @@ -32,7 +32,8 @@ int FAST_FUNC get_uidgid(struct bb_uidgid_t *u, const char *ug) | |||
| 32 | { | 32 | { |
| 33 | struct passwd *pwd; | 33 | struct passwd *pwd; |
| 34 | struct group *gr; | 34 | struct group *gr; |
| 35 | char *user, *group; | 35 | char *user; |
| 36 | const char *group; | ||
| 36 | unsigned n; | 37 | unsigned n; |
| 37 | 38 | ||
| 38 | user = (char*)ug; | 39 | user = (char*)ug; |
diff --git a/loginutils/getty.c b/loginutils/getty.c index 67a08f487..232fa2b84 100644 --- a/loginutils/getty.c +++ b/loginutils/getty.c | |||
| @@ -471,8 +471,10 @@ static char *get_logname(void) | |||
| 471 | do { | 471 | do { |
| 472 | /* Write issue file and prompt */ | 472 | /* Write issue file and prompt */ |
| 473 | #ifdef ISSUE | 473 | #ifdef ISSUE |
| 474 | if (!(option_mask32 & F_NOISSUE)) | 474 | if (!(option_mask32 & F_NOISSUE)) { |
| 475 | puts("\r"); /* start a new line */ | ||
| 475 | print_login_issue(G.issue, G.tty_name); | 476 | print_login_issue(G.issue, G.tty_name); |
| 477 | } | ||
| 476 | #endif | 478 | #endif |
| 477 | print_login_prompt(); | 479 | print_login_prompt(); |
| 478 | 480 | ||
diff --git a/miscutils/bc.c b/miscutils/bc.c index 31485ae9c..b855c111d 100644 --- a/miscutils/bc.c +++ b/miscutils/bc.c | |||
| @@ -5528,7 +5528,7 @@ static void xc_program_printString(const char *str) | |||
| 5528 | char c = *str++; | 5528 | char c = *str++; |
| 5529 | if (c == '\\') { | 5529 | if (c == '\\') { |
| 5530 | static const char esc[] ALIGN1 = "nabfrt""e\\"; | 5530 | static const char esc[] ALIGN1 = "nabfrt""e\\"; |
| 5531 | char *n; | 5531 | const char *n; |
| 5532 | 5532 | ||
| 5533 | c = *str++; | 5533 | c = *str++; |
| 5534 | n = strchr(esc, c); // note: if c is NUL, n = \0 at end of esc | 5534 | n = strchr(esc, c); // note: if c is NUL, n = \0 at end of esc |
diff --git a/miscutils/less.c b/miscutils/less.c index 91403a252..6da1f88a7 100644 --- a/miscutils/less.c +++ b/miscutils/less.c | |||
| @@ -205,9 +205,10 @@ struct globals { | |||
| 205 | #if ENABLE_FEATURE_LESS_WINCH | 205 | #if ENABLE_FEATURE_LESS_WINCH |
| 206 | unsigned winch_counter; | 206 | unsigned winch_counter; |
| 207 | #endif | 207 | #endif |
| 208 | ssize_t eof_error; /* eof if 0, error if < 0 */ | 208 | smallint ndelay_set; |
| 209 | ssize_t readpos; | 209 | ssize_t eof_error_ok; /* eof if 0, error if < 0, > 0 if last read did not indicate either */ |
| 210 | ssize_t readeof; /* must be signed */ | 210 | size_t readpos; |
| 211 | size_t read_size; | ||
| 211 | const char **buffer; | 212 | const char **buffer; |
| 212 | const char **flines; | 213 | const char **flines; |
| 213 | const char *empty_line_marker; | 214 | const char *empty_line_marker; |
| @@ -255,9 +256,8 @@ struct globals { | |||
| 255 | #define winch_counter (G.winch_counter ) | 256 | #define winch_counter (G.winch_counter ) |
| 256 | /* This one is 100% not cached by compiler on read access */ | 257 | /* This one is 100% not cached by compiler on read access */ |
| 257 | #define WINCH_COUNTER (*(volatile unsigned *)&winch_counter) | 258 | #define WINCH_COUNTER (*(volatile unsigned *)&winch_counter) |
| 258 | #define eof_error (G.eof_error ) | ||
| 259 | #define readpos (G.readpos ) | 259 | #define readpos (G.readpos ) |
| 260 | #define readeof (G.readeof ) | 260 | #define read_size (G.read_size ) |
| 261 | #define buffer (G.buffer ) | 261 | #define buffer (G.buffer ) |
| 262 | #define flines (G.flines ) | 262 | #define flines (G.flines ) |
| 263 | #define empty_line_marker (G.empty_line_marker ) | 263 | #define empty_line_marker (G.empty_line_marker ) |
| @@ -285,7 +285,7 @@ struct globals { | |||
| 285 | less_gets_pos = -1; \ | 285 | less_gets_pos = -1; \ |
| 286 | empty_line_marker = "~"; \ | 286 | empty_line_marker = "~"; \ |
| 287 | current_file = 1; \ | 287 | current_file = 1; \ |
| 288 | eof_error = 1; \ | 288 | G.eof_error_ok = 1; \ |
| 289 | terminated = 1; \ | 289 | terminated = 1; \ |
| 290 | IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \ | 290 | IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \ |
| 291 | } while (0) | 291 | } while (0) |
| @@ -466,22 +466,21 @@ static int at_end(void) | |||
| 466 | * that it was seen]) | 466 | * that it was seen]) |
| 467 | * max_lineno - last line's number, this one doesn't increment | 467 | * max_lineno - last line's number, this one doesn't increment |
| 468 | * on line wrap, only on "real" new lines. | 468 | * on line wrap, only on "real" new lines. |
| 469 | * readbuf[0..readeof-1] - small preliminary buffer. | 469 | * readbuf[0..read_size-1] - small preliminary buffer. |
| 470 | * readbuf[readpos] - next character to add to current line. | 470 | * readbuf[readpos] - next character to add to current line. |
| 471 | * last_line_pos - screen line position of next char to be read | 471 | * last_line_pos - screen line position of next char to be read |
| 472 | * (takes into account tabs and backspaces) | 472 | * (takes into account tabs and backspaces) |
| 473 | * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error | 473 | * G.eof_error_ok - < 0 error, == 0 EOF, > 0 not EOF/error |
| 474 | * | 474 | * |
| 475 | * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs, | 475 | * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs, |
| 476 | * "/search on very long input" and "reaching max line count" corner cases. | 476 | * "/search on very long input" and "reaching max line count" corner cases. |
| 477 | */ | 477 | */ |
| 478 | static void read_lines(void) | 478 | static void read_lines(void) |
| 479 | { | 479 | { |
| 480 | int ndelay_set, eagain, fdflags; | ||
| 480 | char *current_line, *p; | 481 | char *current_line, *p; |
| 481 | int w = width; | 482 | int w = width; |
| 482 | char last_terminated = terminated; | 483 | char last_terminated = terminated; |
| 483 | time_t last_time = 0; | ||
| 484 | int retry_EAGAIN = 2; | ||
| 485 | #if ENABLE_FEATURE_LESS_REGEXP | 484 | #if ENABLE_FEATURE_LESS_REGEXP |
| 486 | unsigned old_max_fline = max_fline; | 485 | unsigned old_max_fline = max_fline; |
| 487 | #endif | 486 | #endif |
| @@ -507,36 +506,43 @@ static void read_lines(void) | |||
| 507 | last_line_pos = 0; | 506 | last_line_pos = 0; |
| 508 | } | 507 | } |
| 509 | 508 | ||
| 509 | // Consider these cases: | ||
| 510 | // "less FILE": can set O_NONBLOCK on open. | ||
| 511 | // "true | less": can't. | ||
| 512 | // " { less; cat; } <FILE": can't. And must not confuse cat. | ||
| 513 | ndelay_set = -1; // "don't know whether stdin is nonblocking" | ||
| 514 | eagain = 0; | ||
| 515 | |||
| 510 | while (1) { /* read lines until we reach cur_fline or wanted_match */ | 516 | while (1) { /* read lines until we reach cur_fline or wanted_match */ |
| 511 | *p = '\0'; | 517 | *p = '\0'; |
| 512 | terminated = 0; | 518 | terminated = 0; |
| 513 | while (1) { /* read chars until we have a line */ | 519 | while (1) { /* read chars until we have a line */ |
| 514 | char c; | 520 | char c; |
| 515 | /* if no unprocessed chars left, eat more */ | 521 | /* if no unprocessed chars left, eat more */ |
| 516 | if (readpos >= readeof) { | 522 | if (readpos >= read_size) { |
| 517 | int flags = ndelay_on(0); | 523 | // Read stdin, temporarily make it nonblocking (if it's not already) |
| 518 | 524 | if (ndelay_set < 0) { | |
| 519 | while (1) { | 525 | ndelay_set = G.ndelay_set; // can be only 0 or 1 |
| 520 | time_t t; | 526 | if (ndelay_set == 0) { |
| 521 | 527 | fdflags = ndelay_on(STDIN_FILENO); | |
| 522 | errno = 0; | 528 | if (fdflags & O_NONBLOCK) |
| 523 | eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE); | 529 | ndelay_set = 2; // it _was_ nonblocking, do NOT restore later |
| 524 | if (errno != EAGAIN) | 530 | } //else: G.ndelay_set=1: set nonblocking at open(FILE) time |
| 525 | break; | 531 | } //else: we were here already, stdin is already nonblocking |
| 526 | t = time(NULL); | 532 | |
| 527 | if (t != last_time) { | 533 | // NB: we do NOT check last eof_error_ok before reading. |
| 528 | last_time = t; | 534 | // This has the effect that e.g. PageDown on a regular file |
| 529 | if (--retry_EAGAIN < 0) | 535 | // where we already reached EOF *will try reading anyway*, |
| 530 | break; | 536 | // if the file is a growing log file, less *will* show the new data. |
| 531 | } | ||
| 532 | sched_yield(); | ||
| 533 | } | ||
| 534 | fcntl(0, F_SETFL, flags); /* ndelay_off(0) */ | ||
| 535 | readpos = 0; | 537 | readpos = 0; |
| 536 | readeof = eof_error; | 538 | G.eof_error_ok = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE); |
| 537 | if (eof_error <= 0) | 539 | read_size = G.eof_error_ok; |
| 540 | if (G.eof_error_ok <= 0) { | ||
| 541 | if (G.eof_error_ok < 0 && errno == EAGAIN) | ||
| 542 | eagain = 1; | ||
| 543 | read_size = 0; // -1 would be seen as UINT_MAX, prevent | ||
| 538 | goto reached_eof; | 544 | goto reached_eof; |
| 539 | retry_EAGAIN = 1; | 545 | } |
| 540 | } | 546 | } |
| 541 | c = readbuf[readpos]; | 547 | c = readbuf[readpos]; |
| 542 | /* backspace? [needed for manpages] */ | 548 | /* backspace? [needed for manpages] */ |
| @@ -619,7 +625,7 @@ static void read_lines(void) | |||
| 619 | max_lineno++; | 625 | max_lineno++; |
| 620 | 626 | ||
| 621 | if (max_fline >= MAXLINES) { | 627 | if (max_fline >= MAXLINES) { |
| 622 | eof_error = 0; /* Pretend we saw EOF */ | 628 | G.eof_error_ok = 0; /* Pretend we saw EOF */ |
| 623 | break; | 629 | break; |
| 624 | } | 630 | } |
| 625 | if (!at_end()) { | 631 | if (!at_end()) { |
| @@ -634,7 +640,11 @@ static void read_lines(void) | |||
| 634 | break; | 640 | break; |
| 635 | #endif | 641 | #endif |
| 636 | } | 642 | } |
| 637 | if (eof_error <= 0) { | 643 | if (G.eof_error_ok <= 0) { /* EOF or error? */ |
| 644 | if (eagain) { | ||
| 645 | G.eof_error_ok = 1; // "neither EOF nor error: stdin not ready" | ||
| 646 | //^^^ needed to make main loop's poll() check stdin | ||
| 647 | } | ||
| 638 | break; | 648 | break; |
| 639 | } | 649 | } |
| 640 | max_fline++; | 650 | max_fline++; |
| @@ -643,15 +653,16 @@ static void read_lines(void) | |||
| 643 | last_line_pos = 0; | 653 | last_line_pos = 0; |
| 644 | } /* end of "read lines until we reach cur_fline" loop */ | 654 | } /* end of "read lines until we reach cur_fline" loop */ |
| 645 | 655 | ||
| 646 | if (eof_error < 0) { | 656 | if (ndelay_set == 0) // stdin was not nonblocking, restore that |
| 647 | if (errno == EAGAIN) { | 657 | fcntl(STDIN_FILENO, F_SETFL, fdflags); |
| 648 | eof_error = 1; | 658 | |
| 649 | } else { | 659 | // Will not be seen (overwritten immediately) |
| 650 | print_statusline(bb_msg_read_error); | 660 | //if (G.eof_error_ok < 0) { // error? |
| 651 | } | 661 | // print_statusline(bb_msg_read_error); |
| 652 | } | 662 | // bb_error_msg("G.eof_error_ok:%d", G.eof_error_ok); sleep(5); |
| 663 | //} else | ||
| 653 | #if ENABLE_FEATURE_LESS_FLAGS | 664 | #if ENABLE_FEATURE_LESS_FLAGS |
| 654 | else if (eof_error == 0) | 665 | if (G.eof_error_ok == 0) // EOF? |
| 655 | num_lines = max_lineno; | 666 | num_lines = max_lineno; |
| 656 | #endif | 667 | #endif |
| 657 | 668 | ||
| @@ -731,17 +742,17 @@ static void m_status_print(void) | |||
| 731 | clear_line(); | 742 | clear_line(); |
| 732 | printf(HIGHLIGHT"%s", filename); | 743 | printf(HIGHLIGHT"%s", filename); |
| 733 | if (num_files > 1) | 744 | if (num_files > 1) |
| 734 | printf(" (file %i of %i)", current_file, num_files); | 745 | printf(" (file %d of %d)", current_file, num_files); |
| 735 | 746 | ||
| 736 | first = safe_lineno(cur_fline); | 747 | first = safe_lineno(cur_fline); |
| 737 | last = (option_mask32 & FLAG_S) | 748 | last = (option_mask32 & FLAG_S) |
| 738 | ? MIN(first + max_displayed_line, max_lineno) | 749 | ? MIN(first + max_displayed_line, max_lineno) |
| 739 | : safe_lineno(cur_fline + max_displayed_line); | 750 | : safe_lineno(cur_fline + max_displayed_line); |
| 740 | printf(" lines %i-%i", first, last); | 751 | printf(" lines %d-%d", first, last); |
| 741 | 752 | ||
| 742 | update_num_lines(); | 753 | update_num_lines(); |
| 743 | if (num_lines >= 0) | 754 | if (num_lines >= 0) |
| 744 | printf("/%i", num_lines); | 755 | printf("/%d", num_lines); |
| 745 | 756 | ||
| 746 | if (at_end()) { | 757 | if (at_end()) { |
| 747 | printf(" (END)"); | 758 | printf(" (END)"); |
| @@ -749,8 +760,11 @@ static void m_status_print(void) | |||
| 749 | printf(" - next: %s", files[current_file]); | 760 | printf(" - next: %s", files[current_file]); |
| 750 | } else if (num_lines > 0) { | 761 | } else if (num_lines > 0) { |
| 751 | percent = (100 * last + num_lines/2) / num_lines; | 762 | percent = (100 * last + num_lines/2) / num_lines; |
| 752 | printf(" %i%%", percent <= 100 ? percent : 100); | 763 | printf(" %d%%", percent <= 100 ? percent : 100); |
| 753 | } | 764 | } |
| 765 | if (G.eof_error_ok < 0) | ||
| 766 | // Reproducer: strace -oLOG -e fault=read:error=EIO:when=2 less FILE | ||
| 767 | printf(" %s", bb_msg_read_error); | ||
| 754 | printf(NORMAL); | 768 | printf(NORMAL); |
| 755 | } | 769 | } |
| 756 | #endif | 770 | #endif |
| @@ -780,6 +794,9 @@ static void status_print(void) | |||
| 780 | p = "(END)"; | 794 | p = "(END)"; |
| 781 | if (!cur_fline) | 795 | if (!cur_fline) |
| 782 | p = filename; | 796 | p = filename; |
| 797 | if (G.eof_error_ok < 0) | ||
| 798 | // Reproducer: strace -oLOG -e fault=read:error=EIO:when=2 less FILE | ||
| 799 | p = bb_msg_read_error; | ||
| 783 | if (num_files > 1) { | 800 | if (num_files > 1) { |
| 784 | printf(HIGHLIGHT"%s (file %i of %i)"NORMAL, | 801 | printf(HIGHLIGHT"%s (file %i of %i)"NORMAL, |
| 785 | p, current_file, num_files); | 802 | p, current_file, num_files); |
| @@ -944,9 +961,9 @@ static void buffer_print(void) | |||
| 944 | print_ascii(buffer[i]); | 961 | print_ascii(buffer[i]); |
| 945 | } | 962 | } |
| 946 | if ((option_mask32 & (FLAG_E|FLAG_F)) | 963 | if ((option_mask32 & (FLAG_E|FLAG_F)) |
| 947 | && eof_error <= 0 | 964 | && G.eof_error_ok <= 0 |
| 948 | ) { | 965 | ) { |
| 949 | i = option_mask32 & FLAG_F ? 0 : cur_fline; | 966 | i = (option_mask32 & FLAG_F) ? 0 : cur_fline; |
| 950 | if (max_fline - i <= max_displayed_line) | 967 | if (max_fline - i <= max_displayed_line) |
| 951 | less_exit(); | 968 | less_exit(); |
| 952 | } | 969 | } |
| @@ -999,7 +1016,7 @@ static void goto_lineno(int target) | |||
| 999 | while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline) | 1016 | while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline) |
| 1000 | ++cur_fline; | 1017 | ++cur_fline; |
| 1001 | /* target not reached but more input is available */ | 1018 | /* target not reached but more input is available */ |
| 1002 | if (LINENO(flines[cur_fline]) != target && eof_error > 0) { | 1019 | if (LINENO(flines[cur_fline]) != target && G.eof_error_ok > 0) { |
| 1003 | read_lines(); | 1020 | read_lines(); |
| 1004 | goto retry; | 1021 | goto retry; |
| 1005 | } | 1022 | } |
| @@ -1082,8 +1099,11 @@ static void buffer_lineno(int lineno) | |||
| 1082 | 1099 | ||
| 1083 | static void open_file_and_read_lines(void) | 1100 | static void open_file_and_read_lines(void) |
| 1084 | { | 1101 | { |
| 1102 | G.ndelay_set = 0; | ||
| 1085 | if (filename) { | 1103 | if (filename) { |
| 1086 | xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO); | 1104 | xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO); |
| 1105 | ndelay_on(STDIN_FILENO); | ||
| 1106 | G.ndelay_set = 1; | ||
| 1087 | #if ENABLE_FEATURE_LESS_FLAGS | 1107 | #if ENABLE_FEATURE_LESS_FLAGS |
| 1088 | num_lines = REOPEN_AND_COUNT; | 1108 | num_lines = REOPEN_AND_COUNT; |
| 1089 | #endif | 1109 | #endif |
| @@ -1096,7 +1116,7 @@ static void open_file_and_read_lines(void) | |||
| 1096 | #endif | 1116 | #endif |
| 1097 | } | 1117 | } |
| 1098 | readpos = 0; | 1118 | readpos = 0; |
| 1099 | readeof = 0; | 1119 | read_size = 0; |
| 1100 | last_line_pos = 0; | 1120 | last_line_pos = 0; |
| 1101 | terminated = 1; | 1121 | terminated = 1; |
| 1102 | read_lines(); | 1122 | read_lines(); |
| @@ -1128,13 +1148,17 @@ static void reinitialize(void) | |||
| 1128 | buffer_fill_and_print(); | 1148 | buffer_fill_and_print(); |
| 1129 | } | 1149 | } |
| 1130 | 1150 | ||
| 1151 | /* Poll stdin and keyboard. | ||
| 1152 | * If stdin has more data, redraw and repeat. | ||
| 1153 | * Return keycode when a key is pressed. | ||
| 1154 | */ | ||
| 1131 | #if ENABLE_PLATFORM_MINGW32 | 1155 | #if ENABLE_PLATFORM_MINGW32 |
| 1132 | static int64_t unix_getch_nowait(void) | 1156 | static int64_t unix_getch_nowait(void) |
| 1133 | #else | 1157 | #else |
| 1134 | static int64_t getch_nowait(void) | 1158 | static int64_t getch_nowait(void) |
| 1135 | #endif | 1159 | #endif |
| 1136 | { | 1160 | { |
| 1137 | int rd; | 1161 | int dont_poll_stdin; |
| 1138 | int64_t key64; | 1162 | int64_t key64; |
| 1139 | struct pollfd pfd[2]; | 1163 | struct pollfd pfd[2]; |
| 1140 | 1164 | ||
| @@ -1150,11 +1174,11 @@ static int64_t getch_nowait(void) | |||
| 1150 | * Even if select/poll says that input is available, read CAN block | 1174 | * Even if select/poll says that input is available, read CAN block |
| 1151 | * (switch fd into O_NONBLOCK'ed mode to avoid it) | 1175 | * (switch fd into O_NONBLOCK'ed mode to avoid it) |
| 1152 | */ | 1176 | */ |
| 1153 | rd = 1; | 1177 | dont_poll_stdin = 1; |
| 1154 | /* Are we interested in stdin? */ | 1178 | /* Are we interested in stdin? */ |
| 1155 | if (at_end()) { | 1179 | if (at_end()) { |
| 1156 | if (eof_error > 0) /* did NOT reach eof yet */ | 1180 | if (G.eof_error_ok > 0) /* did NOT reach EOF/error yet */ |
| 1157 | rd = 0; /* yes, we are interested in stdin */ | 1181 | dont_poll_stdin = 0; /* yes, we are interested in stdin */ |
| 1158 | } | 1182 | } |
| 1159 | /* Position cursor if line input is done */ | 1183 | /* Position cursor if line input is done */ |
| 1160 | if (less_gets_pos >= 0) | 1184 | if (less_gets_pos >= 0) |
| @@ -1166,21 +1190,24 @@ static int64_t getch_nowait(void) | |||
| 1166 | while (1) { | 1190 | while (1) { |
| 1167 | int r; | 1191 | int r; |
| 1168 | /* NB: SIGWINCH interrupts poll() */ | 1192 | /* NB: SIGWINCH interrupts poll() */ |
| 1169 | r = poll(pfd + rd, 2 - rd, -1); | 1193 | r = poll(pfd + dont_poll_stdin, 2 - dont_poll_stdin, -1); |
| 1170 | if (/*r < 0 && errno == EINTR &&*/ winch_counter) | 1194 | if (/*r < 0 && errno == EINTR &&*/ winch_counter != 0) |
| 1171 | return '\\'; /* anything which has no defined function */ | 1195 | return '\\'; /* anything which has no defined function */ |
| 1172 | if (r) break; | 1196 | if (r) break; |
| 1173 | } | 1197 | } |
| 1174 | #else | 1198 | #else |
| 1175 | safe_poll(pfd + rd, 2 - rd, -1); | 1199 | safe_poll(pfd + dont_poll_stdin, 2 - dont_poll_stdin, -1); |
| 1176 | #endif | 1200 | #endif |
| 1177 | } | 1201 | } |
| 1178 | 1202 | ||
| 1203 | if (pfd[1].revents == 0) | ||
| 1204 | goto no_kbd_input; | ||
| 1179 | /* We have kbd_fd in O_NONBLOCK mode, read inside safe_read_key() | 1205 | /* We have kbd_fd in O_NONBLOCK mode, read inside safe_read_key() |
| 1180 | * would not block even if there is no input available */ | 1206 | * would not block even if there is no input available */ |
| 1181 | key64 = safe_read_key(kbd_fd, kbd_input, /*do not poll:*/ -2); | 1207 | key64 = safe_read_key(kbd_fd, kbd_input, /*do not poll:*/ -2); |
| 1182 | if ((int)key64 == -1) { | 1208 | if ((int)key64 == -1) { |
| 1183 | if (errno == EAGAIN) { | 1209 | if (errno == EAGAIN) { |
| 1210 | no_kbd_input: | ||
| 1184 | /* No keyboard input available. Since poll() did return, | 1211 | /* No keyboard input available. Since poll() did return, |
| 1185 | * we should have input on stdin */ | 1212 | * we should have input on stdin */ |
| 1186 | read_lines(); | 1213 | read_lines(); |
| @@ -1408,7 +1435,7 @@ static void goto_match(int match) | |||
| 1408 | if (match < 0) | 1435 | if (match < 0) |
| 1409 | match = 0; | 1436 | match = 0; |
| 1410 | /* Try to find next match if eof isn't reached yet */ | 1437 | /* Try to find next match if eof isn't reached yet */ |
| 1411 | if (match >= num_matches && eof_error > 0) { | 1438 | if (match >= num_matches && G.eof_error_ok > 0) { |
| 1412 | wanted_match = match; /* "I want to read until I see N'th match" */ | 1439 | wanted_match = match; /* "I want to read until I see N'th match" */ |
| 1413 | read_lines(); | 1440 | read_lines(); |
| 1414 | } | 1441 | } |
| @@ -2014,7 +2041,7 @@ int less_main(int argc, char **argv) | |||
| 2014 | int64_t keypress; | 2041 | int64_t keypress; |
| 2015 | 2042 | ||
| 2016 | #if ENABLE_FEATURE_LESS_WINCH | 2043 | #if ENABLE_FEATURE_LESS_WINCH |
| 2017 | while (WINCH_COUNTER) { | 2044 | while (WINCH_COUNTER != 0) { |
| 2018 | again: | 2045 | again: |
| 2019 | winch_counter--; | 2046 | winch_counter--; |
| 2020 | IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line); | 2047 | IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line); |
diff --git a/networking/httpd.c b/networking/httpd.c index 02507b8f0..ddb346f18 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
| @@ -2006,7 +2006,7 @@ static void send_cgi_and_exit( | |||
| 2006 | */ | 2006 | */ |
| 2007 | static NOINLINE void send_file_and_exit(const char *url, int what) | 2007 | static NOINLINE void send_file_and_exit(const char *url, int what) |
| 2008 | { | 2008 | { |
| 2009 | char *suffix; | 2009 | const char *suffix; |
| 2010 | int fd; | 2010 | int fd; |
| 2011 | ssize_t count; | 2011 | ssize_t count; |
| 2012 | 2012 | ||
diff --git a/networking/ifconfig.c b/networking/ifconfig.c index 9ee232a66..32638e2a3 100644 --- a/networking/ifconfig.c +++ b/networking/ifconfig.c | |||
| @@ -336,7 +336,7 @@ int ifconfig_main(int argc UNUSED_PARAM, char **argv) | |||
| 336 | #endif | 336 | #endif |
| 337 | char *p; | 337 | char *p; |
| 338 | /*char host[128];*/ | 338 | /*char host[128];*/ |
| 339 | const char *host = NULL; /* make gcc happy */ | 339 | char *host = NULL; /* make gcc happy */ |
| 340 | IF_FEATURE_IFCONFIG_STATUS(char *show_all_param;) | 340 | IF_FEATURE_IFCONFIG_STATUS(char *show_all_param;) |
| 341 | 341 | ||
| 342 | did_flags = 0; | 342 | did_flags = 0; |
diff --git a/networking/ifupdown.c b/networking/ifupdown.c index bc2dca506..6832ee0d4 100644 --- a/networking/ifupdown.c +++ b/networking/ifupdown.c | |||
| @@ -363,7 +363,7 @@ static char *parse(const char *command, struct interface_defn_t *ifd) | |||
| 363 | break; | 363 | break; |
| 364 | case '%': | 364 | case '%': |
| 365 | { | 365 | { |
| 366 | char *nextpercent; | 366 | const char *nextpercent; |
| 367 | char *varvalue; | 367 | char *varvalue; |
| 368 | 368 | ||
| 369 | command++; | 369 | command++; |
diff --git a/networking/inetd.c b/networking/inetd.c index e63edcd9d..6220a08e3 100644 --- a/networking/inetd.c +++ b/networking/inetd.c | |||
| @@ -969,9 +969,8 @@ static void reread_config_file(int sig UNUSED_PARAM) | |||
| 969 | servtab_t *sep, *cp, **sepp; | 969 | servtab_t *sep, *cp, **sepp; |
| 970 | len_and_sockaddr *lsa; | 970 | len_and_sockaddr *lsa; |
| 971 | sigset_t omask; | 971 | sigset_t omask; |
| 972 | unsigned n; | ||
| 973 | uint16_t port; | ||
| 974 | int save_errno = errno; | 972 | int save_errno = errno; |
| 973 | char *p_etc_services = NULL; | ||
| 975 | 974 | ||
| 976 | if (!reopen_config_file()) | 975 | if (!reopen_config_file()) |
| 977 | goto ret; | 976 | goto ret; |
| @@ -1039,7 +1038,9 @@ static void reread_config_file(int sig UNUSED_PARAM) | |||
| 1039 | break; | 1038 | break; |
| 1040 | 1039 | ||
| 1041 | default: /* case AF_INET, case AF_INET6 */ | 1040 | default: /* case AF_INET, case AF_INET6 */ |
| 1042 | n = bb_strtou(sep->se_service, NULL, 10); | 1041 | { |
| 1042 | unsigned portno; | ||
| 1043 | portno = bb_strtou(sep->se_service, NULL, 10); | ||
| 1043 | #if ENABLE_FEATURE_INETD_RPC | 1044 | #if ENABLE_FEATURE_INETD_RPC |
| 1044 | if (is_rpc_service(sep)) { | 1045 | if (is_rpc_service(sep)) { |
| 1045 | sep->se_rpcprog = n; | 1046 | sep->se_rpcprog = n; |
| @@ -1059,26 +1060,23 @@ static void reread_config_file(int sig UNUSED_PARAM) | |||
| 1059 | } | 1060 | } |
| 1060 | #endif | 1061 | #endif |
| 1061 | /* what port to listen on? */ | 1062 | /* what port to listen on? */ |
| 1062 | port = htons(n); | 1063 | if (errno || portno > 0xffff) { /* se_service is not numeric */ |
| 1063 | if (errno || n > 0xffff) { /* se_service is not numeric */ | ||
| 1064 | char protoname[4]; | 1064 | char protoname[4]; |
| 1065 | struct servent *sp; | ||
| 1066 | /* can result only in "tcp" or "udp": */ | 1065 | /* can result only in "tcp" or "udp": */ |
| 1067 | safe_strncpy(protoname, sep->se_proto, 4); | 1066 | safe_strncpy(protoname, sep->se_proto, 4); |
| 1068 | sp = getservbyname(sep->se_service, protoname); | 1067 | portno = bb_get_servport_by_name(&p_etc_services, sep->se_service, protoname); |
| 1069 | if (sp == NULL) { | 1068 | if (portno > 0xffff) { |
| 1070 | bb_error_msg("%s/%s: unknown service", | 1069 | bb_error_msg("%s/%s: unknown service", |
| 1071 | sep->se_service, sep->se_proto); | 1070 | sep->se_service, sep->se_proto); |
| 1072 | goto next_cp; | 1071 | goto next_cp; |
| 1073 | } | 1072 | } |
| 1074 | port = sp->s_port; | ||
| 1075 | } | 1073 | } |
| 1076 | if (LONE_CHAR(sep->se_local_hostname, '*')) { | 1074 | if (LONE_CHAR(sep->se_local_hostname, '*')) { |
| 1077 | lsa = xzalloc_lsa(sep->se_family); | 1075 | lsa = xzalloc_lsa(sep->se_family); |
| 1078 | set_nport(&lsa->u.sa, port); | 1076 | set_nport(&lsa->u.sa, htons(portno)); |
| 1079 | } else { | 1077 | } else { |
| 1080 | lsa = host_and_af2sockaddr(sep->se_local_hostname, | 1078 | lsa = host_and_af2sockaddr(sep->se_local_hostname, |
| 1081 | ntohs(port), sep->se_family); | 1079 | portno, sep->se_family); |
| 1082 | if (!lsa) { | 1080 | if (!lsa) { |
| 1083 | bb_error_msg("%s/%s: unknown host '%s'", | 1081 | bb_error_msg("%s/%s: unknown host '%s'", |
| 1084 | sep->se_service, sep->se_proto, | 1082 | sep->se_service, sep->se_proto, |
| @@ -1087,6 +1085,7 @@ static void reread_config_file(int sig UNUSED_PARAM) | |||
| 1087 | } | 1085 | } |
| 1088 | } | 1086 | } |
| 1089 | break; | 1087 | break; |
| 1088 | }//default: | ||
| 1090 | } /* end of "switch (sep->se_family)" */ | 1089 | } /* end of "switch (sep->se_family)" */ |
| 1091 | 1090 | ||
| 1092 | /* did lsa change? Then close/open */ | 1091 | /* did lsa change? Then close/open */ |
| @@ -1134,6 +1133,7 @@ static void reread_config_file(int sig UNUSED_PARAM) | |||
| 1134 | } | 1133 | } |
| 1135 | restore_sigmask(&omask); | 1134 | restore_sigmask(&omask); |
| 1136 | ret: | 1135 | ret: |
| 1136 | free(p_etc_services); | ||
| 1137 | errno = save_errno; | 1137 | errno = save_errno; |
| 1138 | } | 1138 | } |
| 1139 | 1139 | ||
diff --git a/networking/nbd-client.c b/networking/nbd-client.c index 556fa8c97..4fda66125 100644 --- a/networking/nbd-client.c +++ b/networking/nbd-client.c | |||
| @@ -260,7 +260,7 @@ int nbdclient_main(int argc, char **argv) | |||
| 260 | // needs some other process to sit in ioctl(nbd, NBD_DO_IT). | 260 | // needs some other process to sit in ioctl(nbd, NBD_DO_IT). |
| 261 | if (fork() == 0) { | 261 | if (fork() == 0) { |
| 262 | /* child */ | 262 | /* child */ |
| 263 | char *s = strrchr(device, '/'); | 263 | const char *s = strrchr(device, '/'); |
| 264 | sprintf(data, "/sys/block/%.32s/pid", s ? s + 1 : device); | 264 | sprintf(data, "/sys/block/%.32s/pid", s ? s + 1 : device); |
| 265 | // Is it up yet? | 265 | // Is it up yet? |
| 266 | for (;;) { | 266 | for (;;) { |
diff --git a/networking/netstat.c b/networking/netstat.c index d7afa8fdd..d31928957 100644 --- a/networking/netstat.c +++ b/networking/netstat.c | |||
| @@ -176,6 +176,7 @@ struct globals { | |||
| 176 | smallint prg_cache_loaded; | 176 | smallint prg_cache_loaded; |
| 177 | struct prg_node *prg_hash[PRG_HASH_SIZE]; | 177 | struct prg_node *prg_hash[PRG_HASH_SIZE]; |
| 178 | #endif | 178 | #endif |
| 179 | char *p_etc_services; | ||
| 179 | #if ENABLE_FEATURE_NETSTAT_PRG | 180 | #if ENABLE_FEATURE_NETSTAT_PRG |
| 180 | const char *progname_banner; | 181 | const char *progname_banner; |
| 181 | #endif | 182 | #endif |
| @@ -378,15 +379,15 @@ static void build_ipv4_addr(char* local_addr, struct sockaddr_in* localaddr) | |||
| 378 | 379 | ||
| 379 | static const char *get_sname(int port, const char *proto, int numeric) | 380 | static const char *get_sname(int port, const char *proto, int numeric) |
| 380 | { | 381 | { |
| 381 | if (!port) | 382 | if (port == 0) |
| 382 | return "*"; | 383 | return "*"; |
| 383 | if (!numeric) { | 384 | if (!numeric) { |
| 384 | struct servent *se = getservbyport(port, proto); | 385 | const char *se = bb_get_servname_by_port(&G.p_etc_services, port, proto); |
| 385 | if (se) | 386 | if (se) |
| 386 | return se->s_name; | 387 | return se; |
| 387 | } | 388 | } |
| 388 | /* hummm, we may return static buffer here!! */ | 389 | /* hummm, we may return static buffer here!! */ |
| 389 | return itoa(ntohs(port)); | 390 | return itoa(port); |
| 390 | } | 391 | } |
| 391 | 392 | ||
| 392 | static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int numeric) | 393 | static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int numeric) |
| @@ -402,7 +403,7 @@ static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int | |||
| 402 | if (!host) | 403 | if (!host) |
| 403 | host = xmalloc_sockaddr2dotted_noport(addr); | 404 | host = xmalloc_sockaddr2dotted_noport(addr); |
| 404 | 405 | ||
| 405 | xasprintf_inplace(host, "%s:%s", host, get_sname(htons(port), proto, numeric)); | 406 | xasprintf_inplace(host, "%s:%s", host, get_sname(port, proto, numeric)); |
| 406 | return host; | 407 | return host; |
| 407 | } | 408 | } |
| 408 | 409 | ||
diff --git a/networking/pscan.c b/networking/pscan.c index 13785deca..454dba3fb 100644 --- a/networking/pscan.c +++ b/networking/pscan.c | |||
| @@ -37,13 +37,13 @@ | |||
| 37 | #define DERR(...) ((void)0) | 37 | #define DERR(...) ((void)0) |
| 38 | #endif | 38 | #endif |
| 39 | 39 | ||
| 40 | static const char *port_name(unsigned port) | 40 | static const char *port_name(char **p_etc_services, unsigned port) |
| 41 | { | 41 | { |
| 42 | struct servent *server; | 42 | char *server; |
| 43 | 43 | ||
| 44 | server = getservbyport(htons(port), NULL); | 44 | server = bb_get_servname_by_port(p_etc_services, port, NULL); |
| 45 | if (server) | 45 | if (server) |
| 46 | return server->s_name; | 46 | return server; |
| 47 | return "unknown"; | 47 | return "unknown"; |
| 48 | } | 48 | } |
| 49 | 49 | ||
| @@ -62,6 +62,7 @@ int pscan_main(int argc UNUSED_PARAM, char **argv) | |||
| 62 | * Rule of thumb: with min_rtt of N msec, scanning 1000 ports | 62 | * Rule of thumb: with min_rtt of N msec, scanning 1000 ports |
| 63 | * will take N seconds at absolute minimum */ | 63 | * will take N seconds at absolute minimum */ |
| 64 | const char *opt_min_rtt = "5"; /* -T: default min rtt in msec */ | 64 | const char *opt_min_rtt = "5"; /* -T: default min rtt in msec */ |
| 65 | char *p_etc_services = NULL; | ||
| 65 | const char *result_str; | 66 | const char *result_str; |
| 66 | len_and_sockaddr *lsap; | 67 | len_and_sockaddr *lsap; |
| 67 | int s; | 68 | int s; |
| @@ -152,7 +153,8 @@ int pscan_main(int argc UNUSED_PARAM, char **argv) | |||
| 152 | DMSG("out of loop @%u", diff); | 153 | DMSG("out of loop @%u", diff); |
| 153 | if (result_str) | 154 | if (result_str) |
| 154 | printf("%5u" "\t" "tcp" "\t" "%s" "\t" "%s" "\n", | 155 | printf("%5u" "\t" "tcp" "\t" "%s" "\t" "%s" "\n", |
| 155 | port, result_str, port_name(port)); | 156 | port, result_str, port_name(&p_etc_services, port) |
| 157 | ); | ||
| 156 | 158 | ||
| 157 | /* Estimate new rtt - we don't want to wait entire timeout | 159 | /* Estimate new rtt - we don't want to wait entire timeout |
| 158 | * for each port. *4 allows for rise in net delay. | 160 | * for each port. *4 allows for rise in net delay. |
diff --git a/networking/route.c b/networking/route.c index 6e2d30cfd..9d9b72416 100644 --- a/networking/route.c +++ b/networking/route.c | |||
| @@ -179,7 +179,7 @@ static NOINLINE void INET_setroute(int action, char **args) | |||
| 179 | memset(rt, 0, sizeof(*rt)); | 179 | memset(rt, 0, sizeof(*rt)); |
| 180 | 180 | ||
| 181 | { | 181 | { |
| 182 | const char *target = *args++; | 182 | char *target = *args++; |
| 183 | char *prefix; | 183 | char *prefix; |
| 184 | 184 | ||
| 185 | /* recognize x.x.x.x/mask format. */ | 185 | /* recognize x.x.x.x/mask format. */ |
| @@ -353,25 +353,25 @@ static NOINLINE void INET6_setroute(int action, char **args) | |||
| 353 | int prefix_len, skfd; | 353 | int prefix_len, skfd; |
| 354 | const char *devname; | 354 | const char *devname; |
| 355 | 355 | ||
| 356 | /* We know args isn't NULL from the check in route_main. */ | 356 | /* We know args isn't NULL from the check in route_main. */ |
| 357 | const char *target = *args++; | 357 | char *target = *args++; |
| 358 | 358 | ||
| 359 | if (strcmp(target, "default") == 0) { | 359 | if (strcmp(target, "default") == 0) { |
| 360 | prefix_len = 0; | 360 | prefix_len = 0; |
| 361 | memset(&sa6, 0, sizeof(sa6)); | 361 | memset(&sa6, 0, sizeof(sa6)); |
| 362 | } else { | ||
| 363 | char *cp; | ||
| 364 | cp = strchr(target, '/'); /* Yes... const to non is ok. */ | ||
| 365 | if (cp) { | ||
| 366 | *cp = '\0'; | ||
| 367 | prefix_len = xatoul_range(cp + 1, 0, 128); | ||
| 362 | } else { | 368 | } else { |
| 363 | char *cp; | 369 | prefix_len = 128; |
| 364 | cp = strchr(target, '/'); /* Yes... const to non is ok. */ | ||
| 365 | if (cp) { | ||
| 366 | *cp = '\0'; | ||
| 367 | prefix_len = xatoul_range(cp + 1, 0, 128); | ||
| 368 | } else { | ||
| 369 | prefix_len = 128; | ||
| 370 | } | ||
| 371 | if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) { | ||
| 372 | bb_error_msg_and_die("resolving %s", target); | ||
| 373 | } | ||
| 374 | } | 370 | } |
| 371 | if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) { | ||
| 372 | bb_error_msg_and_die("resolving %s", target); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | 375 | ||
| 376 | /* Clean out the RTREQ structure. */ | 376 | /* Clean out the RTREQ structure. */ |
| 377 | memset(&rt, 0, sizeof(rt)); | 377 | memset(&rt, 0, sizeof(rt)); |
diff --git a/networking/ssl_client.c b/networking/ssl_client.c index 757745896..4d021a4ec 100644 --- a/networking/ssl_client.c +++ b/networking/ssl_client.c | |||
| @@ -16,7 +16,7 @@ | |||
| 16 | 16 | ||
| 17 | //usage:#define ssl_client_trivial_usage | 17 | //usage:#define ssl_client_trivial_usage |
| 18 | //usage: IF_NOT_PLATFORM_MINGW32( | 18 | //usage: IF_NOT_PLATFORM_MINGW32( |
| 19 | //usage: "[-e] -s FD [-r FD] [-n SNI]" | 19 | //usage: "[-n SNI] { -s FD [-r FD] | HOST | -e PROG ARGS }" |
| 20 | //usage: ) | 20 | //usage: ) |
| 21 | //usage: IF_PLATFORM_MINGW32( | 21 | //usage: IF_PLATFORM_MINGW32( |
| 22 | //usage: "[-e] -h handle [-n SNI]" | 22 | //usage: "[-e] -h handle [-n SNI]" |
| @@ -28,51 +28,104 @@ | |||
| 28 | int ssl_client_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 28 | int ssl_client_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| 29 | int ssl_client_main(int argc UNUSED_PARAM, char **argv) | 29 | int ssl_client_main(int argc UNUSED_PARAM, char **argv) |
| 30 | { | 30 | { |
| 31 | int exit_if_stdin_closed; | ||
| 31 | tls_state_t *tls; | 32 | tls_state_t *tls; |
| 32 | const char *sni = NULL; | 33 | const char *sni = NULL; |
| 33 | int opt; | 34 | int opt; |
| 34 | #if ENABLE_PLATFORM_MINGW32 | 35 | #if ENABLE_PLATFORM_MINGW32 |
| 35 | char *hstr = NULL; | 36 | char *hstr = NULL; |
| 36 | HANDLE h; | 37 | HANDLE h; |
| 38 | enum { | ||
| 39 | /* wrong name so exit_if_stdin_closed is set later */ | ||
| 40 | OPT_s = (1 << 0), | ||
| 41 | OPT_h = (1 << 1), | ||
| 42 | OPT_n = (1 << 2), | ||
| 43 | }; | ||
| 44 | #else | ||
| 45 | enum { | ||
| 46 | OPT_s = (1 << 0), | ||
| 47 | OPT_r = (1 << 1), | ||
| 48 | OPT_n = (1 << 2), | ||
| 49 | OPT_e = (1 << 3), | ||
| 50 | }; | ||
| 37 | #endif | 51 | #endif |
| 38 | 52 | ||
| 39 | // INIT_G(); | 53 | // INIT_G(); |
| 40 | |||
| 41 | tls = new_tls_state(); | 54 | tls = new_tls_state(); |
| 42 | #if !ENABLE_PLATFORM_MINGW32 | 55 | #if ENABLE_PLATFORM_MINGW32 |
| 43 | opt = getopt32(argv, "es:+r:+n:", &tls->ofd, &tls->ifd, &sni); | ||
| 44 | if (!(opt & (1<<2))) { | ||
| 45 | /* -r N defaults to -s N */ | ||
| 46 | tls->ifd = tls->ofd; | ||
| 47 | } | ||
| 48 | #else | ||
| 49 | opt = getopt32(argv, "eh:n:", &hstr, &sni); | 56 | opt = getopt32(argv, "eh:n:", &hstr, &sni); |
| 50 | #endif | ||
| 51 | 57 | ||
| 52 | if (!(opt & (3<<1))) { | 58 | if (!hstr || sscanf(hstr, "%p", &h) != 1) |
| 53 | if (!argv[1]) | 59 | bb_error_msg_and_die("invalid handle"); |
| 60 | init_winsock(); | ||
| 61 | tls->ifd = tls->ofd = _open_osfhandle((intptr_t)h, _O_RDWR|_O_BINARY); | ||
| 62 | #else | ||
| 63 | /* "+": stop on first non-option */ | ||
| 64 | opt = getopt32(argv, "^+" "s:+r:+n:e" "\0" | ||
| 65 | "e--s:e--r:s--e:r--e", &tls->ofd, &tls->ifd, &sni | ||
| 66 | ); | ||
| 67 | argv += optind; | ||
| 68 | |||
| 69 | if (opt & OPT_e) { | ||
| 70 | /* -e PROG: run PROG and talk TLS to its stdin/stdout */ | ||
| 71 | // Talk to local HTTP server behind local TLS server: | ||
| 72 | // printf "GET / HTTP/1.1\r\n\r\n" | ssl_client -e ssl_server -d PRIVKEY.der -e httpd -i | ||
| 73 | struct fd_pair to_prog; | ||
| 74 | struct fd_pair from_prog; | ||
| 75 | |||
| 76 | pid_t pid; | ||
| 77 | |||
| 78 | if (!argv[0]) | ||
| 54 | bb_show_usage(); | 79 | bb_show_usage(); |
| 55 | /* Undocumented debug feature: without -s and -r, takes HOST arg and connects to it */ | 80 | |
| 56 | // | 81 | xpiped_pair(to_prog); |
| 82 | xpiped_pair(from_prog); | ||
| 83 | |||
| 84 | pid = xvfork(); | ||
| 85 | if (pid == 0) { | ||
| 86 | /* Child: run the program */ | ||
| 87 | |||
| 88 | /* NB: close _first_, then move fds! */ | ||
| 89 | close(to_prog.wr); | ||
| 90 | close(from_prog.rd); | ||
| 91 | xmove_fd(to_prog.rd, STDIN_FILENO); | ||
| 92 | xmove_fd(from_prog.wr, STDOUT_FILENO); | ||
| 93 | |||
| 94 | BB_EXECVP_or_die(argv); | ||
| 95 | } | ||
| 96 | |||
| 97 | /* Parent: close child ends of pipes */ | ||
| 98 | close(to_prog.rd); | ||
| 99 | close(from_prog.wr); | ||
| 100 | |||
| 101 | tls->ofd = to_prog.wr; /* write to program's stdin */ | ||
| 102 | tls->ifd = from_prog.rd; /* read from program's stdout */ | ||
| 103 | |||
| 104 | } else if (!(opt & (OPT_s|OPT_r))) { | ||
| 105 | /* Not -e/-s/-r: connect to HOST */ | ||
| 57 | // Talk to kernel.org: | 106 | // Talk to kernel.org: |
| 58 | // printf "GET / HTTP/1.1\r\nHost: kernel.org\r\n\r\n" | busybox ssl_client kernel.org | 107 | // printf "GET / HTTP/1.1\r\nHost: kernel.org\r\n\r\n" | ssl_client kernel.org |
| 108 | if (!argv[0] || argv[1]) | ||
| 109 | bb_show_usage(); | ||
| 59 | if (!sni) | 110 | if (!sni) |
| 60 | sni = argv[1]; | 111 | sni = argv[0]; |
| 61 | tls->ifd = tls->ofd = create_and_connect_stream_or_die(argv[1], 443); | 112 | tls->ifd = tls->ofd = create_and_connect_stream_or_die(argv[0], 443); |
| 62 | } | 113 | |
| 63 | #if ENABLE_PLATFORM_MINGW32 | 114 | } else { |
| 64 | else { | 115 | /* -s FD [-r FD] */ |
| 65 | if (!hstr || sscanf(hstr, "%p", &h) != 1) | 116 | if (!(opt & OPT_s) || argv[0]) |
| 66 | bb_error_msg_and_die("invalid handle"); | 117 | bb_show_usage(); |
| 67 | init_winsock(); | 118 | if (!(opt & OPT_r)) { |
| 68 | tls->ifd = tls->ofd = _open_osfhandle((intptr_t)h, _O_RDWR|_O_BINARY); | 119 | /* -r FD defaults to -s FD */ |
| 120 | tls->ifd = tls->ofd; | ||
| 121 | } | ||
| 69 | } | 122 | } |
| 70 | #endif | 123 | #endif |
| 71 | 124 | ||
| 72 | tls_handshake(tls, sni); | 125 | tls_handshake(tls, sni); |
| 73 | 126 | ||
| 74 | BUILD_BUG_ON(TLSLOOP_EXIT_ON_LOCAL_EOF != 1); | 127 | exit_if_stdin_closed = (opt & OPT_s) ? TLSLOOP_EXIT_ON_LOCAL_EOF : 0; |
| 75 | tls_run_copy_loop(tls, /*flags*/ opt & 1); | 128 | tls_run_copy_loop(tls, /*flags*/ exit_if_stdin_closed); |
| 76 | 129 | ||
| 77 | return EXIT_SUCCESS; | 130 | return EXIT_SUCCESS; |
| 78 | } | 131 | } |
diff --git a/networking/ssl_server.c b/networking/ssl_server.c new file mode 100644 index 000000000..2a89dae6c --- /dev/null +++ b/networking/ssl_server.c | |||
| @@ -0,0 +1,121 @@ | |||
| 1 | /* | ||
| 2 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
| 3 | */ | ||
| 4 | //config:config SSL_SERVER | ||
| 5 | //config: bool "ssl_server (test TLS server)" | ||
| 6 | //config: default y | ||
| 7 | //config: select TLS | ||
| 8 | //config: help | ||
| 9 | //config: inetd-style TLS server. Stdin/stdout are already connected | ||
| 10 | //config: to an accepted TCP socket. | ||
| 11 | |||
| 12 | //applet:IF_SSL_SERVER(APPLET(ssl_server, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
| 13 | |||
| 14 | //kbuild:lib-$(CONFIG_SSL_SERVER) += ssl_server.o | ||
| 15 | |||
| 16 | //usage:#define ssl_server_trivial_usage | ||
| 17 | //usage: "-f PRIVKEY_CERT.pem PROG ARGS" | ||
| 18 | //usage:#define ssl_server_full_usage "" | ||
| 19 | //usage: "Inetd-style TLS server\n" | ||
| 20 | //usage: "\n -f PEMFILE HAProxy-style CRT file" | ||
| 21 | /* | ||
| 22 | # Generate RSA key and certificate | ||
| 23 | openssl req -x509 -newkey rsa:4096 \ | ||
| 24 | -keyout $HOSTNAME-rsa.key \ | ||
| 25 | -out $HOSTNAME-rsa.crt \ | ||
| 26 | -sha256 -days 9999 -nodes \ | ||
| 27 | -subj /CN=$HOSTNAME \ | ||
| 28 | -addext "subjectAltName=DNS:$HOSTNAME" | ||
| 29 | # Generate ECDSA key and certificate | ||
| 30 | openssl genpkey -algorithm EC \ | ||
| 31 | -pkeyopt ec_paramgen_curve:prime256v1 \ | ||
| 32 | -out $HOSTNAME-ecdsa.key | ||
| 33 | fopenssl req -new -x509 \ | ||
| 34 | -key $HOSTNAME-ecdsa.key \ | ||
| 35 | -out $HOSTNAME-ecdsa.crt \ | ||
| 36 | -sha256 -days 9999 \ | ||
| 37 | -subj "/CN=$HOSTNAME" \ | ||
| 38 | -addext "subjectAltName=DNS:$HOSTNAME" | ||
| 39 | # Concatenate all these files into PRIVKEY_CERT.pem | ||
| 40 | { cat $HOSTNAME-rsa.key | ||
| 41 | cat $HOSTNAME-rsa.crt | ||
| 42 | cat $HOSTNAME-ecdsa.key | ||
| 43 | cat $HOSTNAME-ecdsa.crt | ||
| 44 | } >PRIVKEY_CERT.pem | ||
| 45 | */ | ||
| 46 | #include "libbb.h" | ||
| 47 | |||
| 48 | /* TLS server applet. | ||
| 49 | * | ||
| 50 | * To generate a test RSA certificate and key: | ||
| 51 | * openssl req -x509 -newkey rsa:2048 -days 9999 -nodes \ | ||
| 52 | * -subj '/CN=localhost' \ | ||
| 53 | * -out cert.pem -keyout privkey.pem | ||
| 54 | * Convert to DER format: | ||
| 55 | * openssl x509 -in cert.pem -outform DER -out cert.der | ||
| 56 | * openssl rsa -in privkey.pem -outform DER -out privkey.der | ||
| 57 | * | ||
| 58 | * Run the server: | ||
| 59 | * tcpsvd 127.0.0.1 4433 ssl_server -p privkey.der -c cert.der -e echo 'Hello world' | ||
| 60 | * | ||
| 61 | * Test with: | ||
| 62 | * openssl s_client -connect localhost:4433 | ||
| 63 | */ | ||
| 64 | int ssl_server_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
| 65 | int ssl_server_main(int argc UNUSED_PARAM, char **argv) | ||
| 66 | { | ||
| 67 | struct fd_pair to_prog; | ||
| 68 | struct fd_pair from_prog; | ||
| 69 | pid_t pid; | ||
| 70 | tls_state_t *tls; | ||
| 71 | const char *pem_file; | ||
| 72 | unsigned opt; | ||
| 73 | |||
| 74 | tls = new_tls_state(); | ||
| 75 | |||
| 76 | /* "+": stop on first non-option */ | ||
| 77 | opt = getopt32(argv, "+""vf:", | ||
| 78 | &pem_file | ||
| 79 | ); | ||
| 80 | argv += optind; | ||
| 81 | if (!argv[0] || !(opt & 2)) | ||
| 82 | bb_show_usage(); | ||
| 83 | |||
| 84 | /* In inetd mode, stdin/stdout are the socket. | ||
| 85 | * But tls_run_copy_loop() needs *non-TLS* fds on STDIN and STDOUT. | ||
| 86 | * Shuffle them. | ||
| 87 | */ | ||
| 88 | xdup2(STDIN_FILENO, 3); | ||
| 89 | xdup2(STDOUT_FILENO, 4); | ||
| 90 | tls->ifd = 3; | ||
| 91 | tls->ofd = 4; | ||
| 92 | |||
| 93 | /* This can abort on errors */ | ||
| 94 | tls_handshake_as_server(tls, pem_file); | ||
| 95 | |||
| 96 | /* Run PROG, wrap its data in TLS and I/O to socket */ | ||
| 97 | xpiped_pair(to_prog); | ||
| 98 | xpiped_pair(from_prog); | ||
| 99 | pid = xvfork(); | ||
| 100 | if (pid == 0) { | ||
| 101 | /* Child: run the program */ | ||
| 102 | |||
| 103 | /* NB: close _first_, then move fds! */ | ||
| 104 | close(to_prog.wr); | ||
| 105 | close(from_prog.rd); | ||
| 106 | xmove_fd(to_prog.rd, STDIN_FILENO); | ||
| 107 | xmove_fd(from_prog.wr, STDOUT_FILENO); | ||
| 108 | |||
| 109 | BB_EXECVP_or_die(argv); | ||
| 110 | } | ||
| 111 | /* Parent: close child ends of pipes */ | ||
| 112 | close(to_prog.rd); | ||
| 113 | close(from_prog.wr); | ||
| 114 | |||
| 115 | /* tls_run_copy_loop() needs non-TLS fds on STDIN and STDOUT */ | ||
| 116 | xmove_fd(from_prog.rd, STDIN_FILENO); | ||
| 117 | xmove_fd(to_prog.wr, STDOUT_FILENO); | ||
| 118 | tls_run_copy_loop(tls, /*flags*/ TLSLOOP_EXIT_ON_LOCAL_EOF); | ||
| 119 | |||
| 120 | return EXIT_SUCCESS; | ||
| 121 | } | ||
diff --git a/networking/telnet.c b/networking/telnet.c index 7a0253525..a0eadc91b 100644 --- a/networking/telnet.c +++ b/networking/telnet.c | |||
| @@ -11,13 +11,11 @@ | |||
| 11 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | 11 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
| 12 | * | 12 | * |
| 13 | * HISTORY | 13 | * HISTORY |
| 14 | * Revision 3.1 1994/04/17 11:31:54 too | 14 | * 1994/04/17 Initial revision |
| 15 | * initial revision | 15 | * 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org> |
| 16 | * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org> | 16 | * 2001/05/07 add ability to pass TTYPE to remote host by Jim McQuillan <jam@ltsp.org> |
| 17 | * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan | 17 | * 2004/02/11 add ability to pass the USER variable to remote host by Fernando Silveira <swrh@gmx.net> |
| 18 | * <jam@ltsp.org> | 18 | * 2026/02/20 Almost total rewrite to use ioloop() |
| 19 | * Modified 2004/02/11 to add ability to pass the USER variable to remote host | ||
| 20 | * by Fernando Silveira <swrh@gmx.net> | ||
| 21 | */ | 19 | */ |
| 22 | //config:config TELNET | 20 | //config:config TELNET |
| 23 | //config: bool "telnet (8.8 kb)" | 21 | //config: bool "telnet (8.8 kb)" |
| @@ -54,20 +52,14 @@ | |||
| 54 | 52 | ||
| 55 | //kbuild:lib-$(CONFIG_TELNET) += telnet.o | 53 | //kbuild:lib-$(CONFIG_TELNET) += telnet.o |
| 56 | 54 | ||
| 57 | //usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 58 | //usage:#define telnet_trivial_usage | 55 | //usage:#define telnet_trivial_usage |
| 59 | //usage: "[-a] [-l USER] HOST [PORT]" | 56 | //usage: IF_FEATURE_TELNET_AUTOLOGIN("[-a] [-l USER] ")"HOST [PORT]" |
| 60 | //usage:#define telnet_full_usage "\n\n" | ||
| 61 | //usage: "Connect to telnet server\n" | ||
| 62 | //usage: "\n -a Automatic login with $USER variable" | ||
| 63 | //usage: "\n -l USER Automatic login as USER" | ||
| 64 | //usage: | ||
| 65 | //usage:#else | ||
| 66 | //usage:#define telnet_trivial_usage | ||
| 67 | //usage: "HOST [PORT]" | ||
| 68 | //usage:#define telnet_full_usage "\n\n" | 57 | //usage:#define telnet_full_usage "\n\n" |
| 69 | //usage: "Connect to telnet server" | 58 | //usage: "Connect to telnet server" |
| 70 | //usage:#endif | 59 | //usage: IF_FEATURE_TELNET_AUTOLOGIN("\n" |
| 60 | //usage: "\n -a Automatic login with $USER envvar" | ||
| 61 | //usage: "\n -l USER Automatic login as USER" | ||
| 62 | //usage: ) | ||
| 71 | 63 | ||
| 72 | #include <arpa/telnet.h> | 64 | #include <arpa/telnet.h> |
| 73 | #include <netinet/in.h> | 65 | #include <netinet/in.h> |
| @@ -80,285 +72,183 @@ | |||
| 80 | # define DONT 254 /* you are not to use option */ | 72 | # define DONT 254 /* you are not to use option */ |
| 81 | # define DO 253 /* please, you use option */ | 73 | # define DO 253 /* please, you use option */ |
| 82 | # define WONT 252 /* I won't use option */ | 74 | # define WONT 252 /* I won't use option */ |
| 83 | # define WILL 251 /* I will use option */ | 75 | # define WILL 251 /* I will use option (if I see your "IAC DO <opt>" confirmation */ |
| 84 | # define SB 250 /* interpret as subnegotiation */ | 76 | # define SB 250 /* begin subnegotiation */ |
| 85 | # define SE 240 /* end sub negotiation */ | 77 | # define SE 240 /* end subnegotiation */ |
| 86 | # define TELOPT_ECHO 1 /* echo */ | 78 | # define TELOPT_ECHO 1 /* echo */ |
| 87 | # define TELOPT_SGA 3 /* suppress go ahead */ | 79 | # define TELOPT_SGA 3 /* suppress go ahead */ |
| 88 | # define TELOPT_TTYPE 24 /* terminal type */ | 80 | # define TELOPT_TTYPE 24 /* terminal type */ |
| 89 | # define TELOPT_NAWS 31 /* window size */ | 81 | # define TELOPT_NAWS 31 /* window size */ |
| 90 | #endif | 82 | #endif |
| 91 | 83 | ||
| 92 | enum { | 84 | #define SUPPORT_FOR_LOCAL_OPTS (0 \ |
| 93 | DATABUFSIZE = 128, | 85 | || ENABLE_FEATURE_TELNET_WIDTH \ |
| 94 | IACBUFSIZE = 128, | 86 | || ENABLE_FEATURE_TELNET_TTYPE \ |
| 87 | || ENABLE_FEATURE_TELNET_AUTOLOGIN \ | ||
| 88 | ) | ||
| 89 | |||
| 90 | // -v option | ||
| 91 | #define VERBOSE 0 | ||
| 92 | // lots of unconditional debug | ||
| 93 | #define DEBUG 0 | ||
| 95 | 94 | ||
| 96 | CHM_TRY = 0, | 95 | #if DEBUG |
| 97 | CHM_ON = 1, | 96 | # define dbg(...) bb_error_msg(__VA_ARGS__) |
| 98 | CHM_OFF = 2, | 97 | static char *bin_to_hex(const void *hash_value, unsigned hash_length) |
| 98 | { | ||
| 99 | /* xzalloc zero-terminates */ | ||
| 100 | char *hex_value = xzalloc((hash_length * 2) + 1); | ||
| 101 | bin2hex(hex_value, (char*)hash_value, hash_length); | ||
| 102 | return auto_string(hex_value); | ||
| 103 | } | ||
| 104 | #else | ||
| 105 | # define dbg(...) ((void)0) | ||
| 106 | #endif | ||
| 99 | 107 | ||
| 100 | UF_ECHO = 0x01, | 108 | #if VERBOSE |
| 101 | UF_SGA = 0x02, | 109 | # define log1(...) do { if (G.verbose) bb_error_msg(__VA_ARGS__); } while (0) |
| 110 | # define IF_VERBOSE(...) __VA_ARGS__ | ||
| 111 | #else | ||
| 112 | # define log1(...) ((void)0) | ||
| 113 | # define IF_VERBOSE(...) /* nothing */ | ||
| 114 | #endif | ||
| 102 | 115 | ||
| 116 | enum { | ||
| 103 | TS_NORMAL = 0, | 117 | TS_NORMAL = 0, |
| 104 | TS_COPY = 1, | 118 | TS_IAC = 1, |
| 105 | TS_IAC = 2, | 119 | TS_OPT = 2, |
| 106 | TS_OPT = 3, | 120 | TS_SUB1 = 3, |
| 107 | TS_SUB1 = 4, | 121 | TS_SUB2 = 4, |
| 108 | TS_SUB2 = 5, | 122 | TS_CR = 5, |
| 109 | TS_CR = 6, | 123 | |
| 124 | MAX_NAWS_SIZE = 13, // pathological 65535x65535 case needs full escaping | ||
| 125 | |||
| 126 | netfd = 3, | ||
| 110 | }; | 127 | }; |
| 111 | 128 | ||
| 112 | typedef unsigned char byte; | 129 | typedef unsigned char byte; |
| 113 | 130 | ||
| 114 | enum { netfd = 3 }; | 131 | typedef struct stdin_to_net { |
| 132 | STRUCT_CONNECTION | ||
| 133 | int rdidx, wridx, size; | ||
| 134 | //byte buf[BUFSIZE]; | ||
| 135 | } stdin_to_net_t; | ||
| 136 | typedef struct net_to_stdout { | ||
| 137 | STRUCT_CONNECTION | ||
| 138 | int rdidx, wridx, size; | ||
| 139 | byte input_state; | ||
| 140 | byte negotiation_verb; | ||
| 141 | //byte buf[BUFSIZE]; | ||
| 142 | } net_to_stdout_t; | ||
| 143 | #if DEBUG | ||
| 144 | static void set_input_state(net_to_stdout_t *conn, int new_state, int c) | ||
| 145 | { | ||
| 146 | if (conn->input_state != new_state) { | ||
| 147 | conn->input_state = new_state; | ||
| 148 | dbg("input_state=%d at char:0x%02x '%c'", new_state, c, | ||
| 149 | c >= 32 && c < 127 ? c : '.'); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | # define SET_INPUT_STATE(conn, state, c) set_input_state(conn, state, c) | ||
| 153 | #else | ||
| 154 | # define SET_INPUT_STATE(conn, state, c) ((conn)->input_state = (state)) | ||
| 155 | #endif | ||
| 115 | 156 | ||
| 116 | struct globals { | 157 | struct globals { |
| 117 | int iaclen; /* could even use byte, but it's a loss on x86 */ | 158 | unsigned flags; |
| 118 | byte telstate; /* telnet negotiation state from network input */ | 159 | // Set when server agreed to use NAWS: |
| 119 | byte telwish; /* DO, DONT, WILL, WONT */ | 160 | #define FLAGS_NAWS_ON (1 << 0) |
| 120 | byte charmode; | 161 | // SGA option seen and responded to, no longer look for it: |
| 121 | byte telflags; | 162 | #define FLAGS_SGA_SEEN (1 << 1) |
| 122 | byte do_termios; | 163 | // Seen telnet protocol from server and sent our WILLs: |
| 123 | #if ENABLE_FEATURE_TELNET_TTYPE | 164 | #define INITIAL_SENT (1 << 2) |
| 124 | char *ttype; | 165 | #define DO_TERMIOS (1 << 3) |
| 125 | #endif | 166 | |
| 126 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | 167 | byte word_aligned_bytes[2]; |
| 127 | const char *autologin; | 168 | #define changes_seen word_aligned_bytes[0] |
| 128 | #endif | 169 | #define CHANGED_ECHO (1 << 0) |
| 129 | #if ENABLE_FEATURE_TELNET_WIDTH | 170 | #define CHANGED_NAWS (1 << 1) |
| 130 | unsigned win_width, win_height; | 171 | // These happen only once: |
| 131 | #endif | 172 | #define CHANGED_SGA (1 << 2) |
| 132 | /* same buffer used both for network and console read/write */ | 173 | #define CHANGED_TTYPE (1 << 3) |
| 133 | char buf[DATABUFSIZE]; | 174 | #define CHANGED_NEW_ENVIRON (1 << 4) |
| 134 | /* buffer to handle telnet negotiations */ | 175 | // The second byte is changed async, by signal handler: |
| 135 | char iacbuf[IACBUFSIZE]; | 176 | #define got_SIGWINCH word_aligned_bytes[1] |
| 136 | struct termios termios_def; | 177 | #define G_changes_or_WINCH (*(uint16_t*)G.word_aligned_bytes) |
| 137 | struct termios termios_raw; | 178 | // The "shared word" trick is unsafe on only-word-store arches such as DEC Alpha or SHARC, |
| 179 | // but Alpha retired in 2004 and SHARC does not run linux (it's a DSP). | ||
| 180 | |||
| 181 | byte optstate_ECHO; | ||
| 182 | // On program start: | ||
| 183 | #define OPT_ECHO_UNKNOWN 0xff | ||
| 184 | #define OPT_ECHO_OFF 0 | ||
| 185 | // We operate tty in rawmode only when echo ON (IOW: we saw server say that) | ||
| 186 | #define OPT_ECHO_ON 1 | ||
| 187 | |||
| 188 | byte echo_sga_response_size; | ||
| 189 | |||
| 190 | IF_VERBOSE( unsigned verbose;) | ||
| 191 | IF_FEATURE_TELNET_TTYPE( const char *ttype;) | ||
| 192 | IF_FEATURE_TELNET_AUTOLOGIN(const char *autologin;) | ||
| 193 | ioloop_state_t io; | ||
| 194 | stdin_to_net_t conn_stdin2net; | ||
| 195 | net_to_stdout_t conn_net2stdout; | ||
| 196 | struct termios termios_def; | ||
| 197 | struct termios termios_raw; | ||
| 198 | // buf[] arrays in conn structs are conceptually cleaner, but they | ||
| 199 | // make G.member offsets larger -> larger code | ||
| 200 | #define BUF_TTY2NET ((byte*)bb_common_bufsiz1) | ||
| 201 | #define BUF_NET2TTY G.buf2 | ||
| 202 | #define BUFSIZE 1024 | ||
| 203 | // Note: can't just increase BUFSIZE arbitrarily: common bufsiz1 is not guaranteed to be >1k! | ||
| 204 | #define BUFMASK (BUFSIZE - 1) | ||
| 205 | byte buf2[BUFSIZE]; | ||
| 138 | } FIX_ALIASING; | 206 | } FIX_ALIASING; |
| 139 | #define G (*(struct globals*)bb_common_bufsiz1) | 207 | #define G (*ptr_to_globals) |
| 140 | #define INIT_G() do { \ | 208 | #define INIT_G() do { \ |
| 141 | setup_common_bufsiz(); \ | 209 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
| 142 | BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ | ||
| 143 | } while (0) | 210 | } while (0) |
| 144 | 211 | ||
| 145 | 212 | static int remaining_free_bytes(int n) | |
| 146 | static void rawmode(void); | ||
| 147 | static void cookmode(void); | ||
| 148 | static void do_linemode(void); | ||
| 149 | static void will_charmode(void); | ||
| 150 | static void telopt(byte c); | ||
| 151 | static void subneg(byte c); | ||
| 152 | |||
| 153 | static void iac_flush(void) | ||
| 154 | { | 213 | { |
| 155 | if (G.iaclen != 0) { | 214 | return BUFSIZE - n; |
| 156 | full_write(netfd, G.iacbuf, G.iaclen); | ||
| 157 | G.iaclen = 0; | ||
| 158 | } | ||
| 159 | } | 215 | } |
| 160 | 216 | ||
| 161 | static void doexit(int ev) NORETURN; | 217 | #if ENABLE_FEATURE_TELNET_WIDTH |
| 162 | static void doexit(int ev) | 218 | static void handle_SIGWINCH(int sig UNUSED_PARAM) |
| 163 | { | 219 | { |
| 164 | cookmode(); | 220 | // Only if server said DO NAWS and seen our WILL NAWS: |
| 165 | exit(ev); | 221 | if ((G.flags & (FLAGS_NAWS_ON|INITIAL_SENT)) == (FLAGS_NAWS_ON|INITIAL_SENT)) |
| 222 | G.got_SIGWINCH = 1; | ||
| 166 | } | 223 | } |
| 224 | #endif | ||
| 167 | 225 | ||
| 168 | static void con_escape(void) | 226 | static void rawmode(void) |
| 169 | { | 227 | { |
| 170 | char b; | 228 | if (G.flags & DO_TERMIOS) |
| 171 | 229 | tcsetattr(0, TCSADRAIN, &G.termios_raw); | |
| 172 | if (bb_got_signal) /* came from line mode... go raw */ | ||
| 173 | rawmode(); | ||
| 174 | |||
| 175 | full_write1_str("\r\nConsole escape. Commands are:\r\n\n" | ||
| 176 | " l go to line mode\r\n" | ||
| 177 | " c go to character mode\r\n" | ||
| 178 | " z suspend telnet\r\n" | ||
| 179 | " e exit telnet\r\n"); | ||
| 180 | |||
| 181 | if (read(STDIN_FILENO, &b, 1) <= 0) | ||
| 182 | doexit(EXIT_FAILURE); | ||
| 183 | |||
| 184 | switch (b) { | ||
| 185 | case 'l': | ||
| 186 | if (!bb_got_signal) { | ||
| 187 | do_linemode(); | ||
| 188 | goto ret; | ||
| 189 | } | ||
| 190 | break; | ||
| 191 | case 'c': | ||
| 192 | if (bb_got_signal) { | ||
| 193 | will_charmode(); | ||
| 194 | goto ret; | ||
| 195 | } | ||
| 196 | break; | ||
| 197 | case 'z': | ||
| 198 | cookmode(); | ||
| 199 | kill(0, SIGTSTP); | ||
| 200 | rawmode(); | ||
| 201 | break; | ||
| 202 | case 'e': | ||
| 203 | doexit(EXIT_SUCCESS); | ||
| 204 | } | ||
| 205 | |||
| 206 | full_write1_str("continuing...\r\n"); | ||
| 207 | |||
| 208 | if (bb_got_signal) | ||
| 209 | cookmode(); | ||
| 210 | ret: | ||
| 211 | bb_got_signal = 0; | ||
| 212 | } | 230 | } |
| 213 | 231 | ||
| 214 | static void handle_net_output(int len) | 232 | static void cookmode(void) |
| 215 | { | 233 | { |
| 216 | byte outbuf[2 * DATABUFSIZE]; | 234 | if (G.flags & DO_TERMIOS) |
| 217 | byte *dst = outbuf; | 235 | tcsetattr(0, TCSADRAIN, &G.termios_def); |
| 218 | byte *src = (byte*)G.buf; | ||
| 219 | byte *end = src + len; | ||
| 220 | |||
| 221 | while (src < end) { | ||
| 222 | byte c = *src++; | ||
| 223 | if (c == 0x1d) { | ||
| 224 | con_escape(); | ||
| 225 | return; | ||
| 226 | } | ||
| 227 | *dst = c; | ||
| 228 | if (c == IAC) | ||
| 229 | *++dst = c; /* IAC -> IAC IAC */ | ||
| 230 | else | ||
| 231 | if (c == '\r' || c == '\n') { | ||
| 232 | /* Enter key sends '\r' in raw mode and '\n' in cooked one. | ||
| 233 | * | ||
| 234 | * See RFC 1123 3.3.1 Telnet End-of-Line Convention. | ||
| 235 | * Using CR LF instead of other allowed possibilities | ||
| 236 | * like CR NUL - easier to talk to HTTP/SMTP servers. | ||
| 237 | */ | ||
| 238 | *dst = '\r'; /* Enter -> CR LF */ | ||
| 239 | *++dst = '\n'; | ||
| 240 | } | ||
| 241 | #if 0 | ||
| 242 | /* putty's "special commands" mode does this: */ | ||
| 243 | /* Korenix 3005 switch needs at least the backspace tweak */ | ||
| 244 | if (c == 0x08 || c == 0x7f) { /* ctrl+h || backspace */ | ||
| 245 | *dst = IAC; | ||
| 246 | *++dst = EC; | ||
| 247 | } | ||
| 248 | if (c == 0x03) { /* ctrl+c */ | ||
| 249 | *dst = IAC; | ||
| 250 | *++dst = IP; | ||
| 251 | } | ||
| 252 | #endif | ||
| 253 | dst++; | ||
| 254 | } | ||
| 255 | if (dst - outbuf != 0) | ||
| 256 | full_write(netfd, outbuf, dst - outbuf); | ||
| 257 | } | 236 | } |
| 258 | 237 | ||
| 259 | static void handle_net_input(int len) | 238 | static void doexit(int ev) NORETURN; |
| 239 | static void doexit(int ev) | ||
| 260 | { | 240 | { |
| 261 | byte c; | 241 | cookmode(); |
| 262 | int i; | 242 | exit(ev); |
| 263 | int cstart = 0; | ||
| 264 | |||
| 265 | i = 0; | ||
| 266 | //bb_error_msg("[%u,'%.*s']", G.telstate, len, G.buf); | ||
| 267 | if (G.telstate == TS_NORMAL) { /* most typical state */ | ||
| 268 | while (i < len) { | ||
| 269 | c = G.buf[i]; | ||
| 270 | i++; | ||
| 271 | if (c == IAC) /* unlikely */ | ||
| 272 | goto got_IAC; | ||
| 273 | if (c != '\r') /* likely */ | ||
| 274 | continue; | ||
| 275 | G.telstate = TS_CR; | ||
| 276 | cstart = i; | ||
| 277 | goto got_special; | ||
| 278 | } | ||
| 279 | full_write(STDOUT_FILENO, G.buf, len); | ||
| 280 | return; | ||
| 281 | got_IAC: | ||
| 282 | G.telstate = TS_IAC; | ||
| 283 | cstart = i - 1; | ||
| 284 | got_special: ; | ||
| 285 | } | ||
| 286 | |||
| 287 | for (; i < len; i++) { | ||
| 288 | c = G.buf[i]; | ||
| 289 | |||
| 290 | switch (G.telstate) { | ||
| 291 | case TS_CR: | ||
| 292 | /* Prev char was CR. If cur one is NUL, ignore it. | ||
| 293 | * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling. | ||
| 294 | */ | ||
| 295 | G.telstate = TS_COPY; | ||
| 296 | if (c == '\0') | ||
| 297 | break; | ||
| 298 | /* else: fall through - need to handle CR IAC ... properly */ | ||
| 299 | |||
| 300 | case TS_COPY: /* Prev char was ordinary */ | ||
| 301 | /* Similar to NORMAL, but in TS_COPY we need to copy bytes */ | ||
| 302 | if (c == IAC) | ||
| 303 | G.telstate = TS_IAC; | ||
| 304 | else { | ||
| 305 | G.buf[cstart++] = c; | ||
| 306 | if (c == '\r') | ||
| 307 | G.telstate = TS_CR; | ||
| 308 | } | ||
| 309 | break; | ||
| 310 | |||
| 311 | case TS_IAC: /* Prev char was IAC */ | ||
| 312 | switch (c) { | ||
| 313 | case IAC: /* IAC IAC -> one IAC */ | ||
| 314 | G.buf[cstart++] = c; | ||
| 315 | G.telstate = TS_COPY; | ||
| 316 | break; | ||
| 317 | case SB: | ||
| 318 | G.telstate = TS_SUB1; | ||
| 319 | break; | ||
| 320 | case DO: | ||
| 321 | case DONT: | ||
| 322 | case WILL: | ||
| 323 | case WONT: | ||
| 324 | G.telwish = c; | ||
| 325 | G.telstate = TS_OPT; | ||
| 326 | break; | ||
| 327 | /* DATA MARK must be added later */ | ||
| 328 | default: | ||
| 329 | G.telstate = TS_COPY; | ||
| 330 | } | ||
| 331 | break; | ||
| 332 | |||
| 333 | case TS_OPT: /* Prev chars were IAC WILL/WONT/DO/DONT */ | ||
| 334 | telopt(c); | ||
| 335 | G.telstate = TS_COPY; | ||
| 336 | break; | ||
| 337 | |||
| 338 | case TS_SUB1: /* Subnegotiation */ | ||
| 339 | case TS_SUB2: /* Subnegotiation */ | ||
| 340 | subneg(c); /* can change G.telstate */ | ||
| 341 | break; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | /* We had some IACs, or CR */ | ||
| 346 | iac_flush(); | ||
| 347 | if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */ | ||
| 348 | G.telstate = TS_NORMAL; | ||
| 349 | if (cstart != 0) | ||
| 350 | full_write(STDOUT_FILENO, G.buf, cstart); | ||
| 351 | } | 243 | } |
| 352 | 244 | ||
| 353 | static void put_iac(int c) | 245 | static void put_iac(int c) |
| 354 | { | 246 | { |
| 355 | int iaclen = G.iaclen; | 247 | stdin_to_net_t *conn = &G.conn_stdin2net; |
| 356 | if (iaclen >= IACBUFSIZE) { | 248 | /* Write directly to stdin2net buffer */ |
| 357 | iac_flush(); | 249 | BUF_TTY2NET[conn->rdidx] = c; /* "... & 0xff" is implicit */ |
| 358 | iaclen = 0; | 250 | conn->rdidx = (conn->rdidx + 1) & BUFMASK; |
| 359 | } | 251 | conn->size++; |
| 360 | G.iacbuf[iaclen] = c; /* "... & 0xff" is implicit */ | ||
| 361 | G.iaclen = iaclen + 1; | ||
| 362 | } | 252 | } |
| 363 | 253 | ||
| 364 | static void put_iac2_msb_lsb(unsigned x_y) | 254 | static void put_iac2_msb_lsb(unsigned x_y) |
| @@ -368,15 +258,23 @@ static void put_iac2_msb_lsb(unsigned x_y) | |||
| 368 | } | 258 | } |
| 369 | #define put_iac2_x_y(x,y) put_iac2_msb_lsb(((x)<<8) + (y)) | 259 | #define put_iac2_x_y(x,y) put_iac2_msb_lsb(((x)<<8) + (y)) |
| 370 | 260 | ||
| 371 | #if ENABLE_FEATURE_TELNET_WIDTH \ | 261 | #if SUPPORT_FOR_LOCAL_OPTS |
| 372 | || ENABLE_FEATURE_TELNET_TTYPE \ | ||
| 373 | || ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 374 | static void put_iac4_msb_lsb(unsigned x_y_z_t) | 262 | static void put_iac4_msb_lsb(unsigned x_y_z_t) |
| 375 | { | 263 | { |
| 376 | put_iac2_msb_lsb(x_y_z_t >> 16); | 264 | put_iac2_msb_lsb(x_y_z_t >> 16); |
| 377 | put_iac2_msb_lsb(x_y_z_t); /* "... & 0xffff" is implicit */ | 265 | put_iac2_msb_lsb(x_y_z_t); /* "... & 0xffff" is implicit */ |
| 378 | } | 266 | } |
| 379 | #define put_iac4_x_y_z_t(x,y,z,t) put_iac4_msb_lsb(((x)<<24) + ((y)<<16) + ((z)<<8) + (t)) | 267 | #define put_iac4_x_y_z_t(x,y,z,t) \ |
| 268 | put_iac4_msb_lsb(((x)<<24) + ((y)<<16) + ((z)<<8) + (t)) | ||
| 269 | |||
| 270 | /* Send a byte in subnegotiation, escaping IAC (0xFF) as IAC IAC */ | ||
| 271 | static void put_iac_byte_escaped(int c) | ||
| 272 | { | ||
| 273 | c = (byte)c; | ||
| 274 | put_iac(c); | ||
| 275 | if (c == IAC) | ||
| 276 | put_iac(IAC); | ||
| 277 | } | ||
| 380 | #endif | 278 | #endif |
| 381 | 279 | ||
| 382 | static void put_iac3_IAC_x_y_merged(unsigned wwdd_and_c) | 280 | static void put_iac3_IAC_x_y_merged(unsigned wwdd_and_c) |
| @@ -384,237 +282,656 @@ static void put_iac3_IAC_x_y_merged(unsigned wwdd_and_c) | |||
| 384 | put_iac(IAC); | 282 | put_iac(IAC); |
| 385 | put_iac2_msb_lsb(wwdd_and_c); | 283 | put_iac2_msb_lsb(wwdd_and_c); |
| 386 | } | 284 | } |
| 387 | #define put_iac3_IAC_x_y(wwdd,c) put_iac3_IAC_x_y_merged(((wwdd)<<8) + (c)) | 285 | #define put_iac3_IAC_x_y(wwdd,c) \ |
| 286 | put_iac3_IAC_x_y_merged(((wwdd)<<8) + (c)) | ||
| 388 | 287 | ||
| 389 | #if ENABLE_FEATURE_TELNET_TTYPE | 288 | #if ENABLE_FEATURE_TELNET_TTYPE |
| 390 | static void put_iac_subopt(byte c, char *str) | 289 | static void put_iac_subopt(byte c, const char *str) |
| 391 | { | 290 | { |
| 392 | put_iac4_x_y_z_t(IAC, SB, c, 0); | 291 | put_iac4_x_y_z_t(IAC, SB, c, 0); |
| 393 | 292 | ||
| 394 | while (*str) | 293 | while (*str) { |
| 395 | put_iac(*str++); | 294 | put_iac_byte_escaped(*str); |
| 295 | str++; | ||
| 296 | } | ||
| 396 | 297 | ||
| 397 | put_iac2_x_y(IAC, SE); | 298 | put_iac2_x_y(IAC, SE); |
| 398 | } | 299 | } |
| 399 | #endif | 300 | #endif |
| 400 | 301 | ||
| 401 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | 302 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
| 402 | static void put_iac_subopt_autologin(void) | 303 | static void put_iac_subopt_autologin(const char *p) |
| 403 | { | 304 | { |
| 404 | const char *p; | ||
| 405 | |||
| 406 | put_iac4_x_y_z_t(IAC, SB, TELOPT_NEW_ENVIRON, TELQUAL_IS); | 305 | put_iac4_x_y_z_t(IAC, SB, TELOPT_NEW_ENVIRON, TELQUAL_IS); |
| 407 | put_iac4_x_y_z_t(NEW_ENV_VAR, 'U', 'S', 'E'); /* "USER" */ | 306 | put_iac4_x_y_z_t(NEW_ENV_VAR, 'U', 'S', 'E'); /* "USER" */ |
| 408 | put_iac2_x_y('R', NEW_ENV_VALUE); | 307 | put_iac2_x_y('R', NEW_ENV_VALUE); |
| 409 | 308 | ||
| 410 | p = G.autologin; | ||
| 411 | while (*p) | 309 | while (*p) |
| 412 | put_iac(*p++); | 310 | put_iac_byte_escaped(*p++); |
| 413 | 311 | ||
| 414 | put_iac2_x_y(IAC, SE); | 312 | put_iac2_x_y(IAC, SE); |
| 415 | } | 313 | } |
| 416 | #endif | 314 | #endif |
| 417 | 315 | ||
| 418 | #if ENABLE_FEATURE_TELNET_WIDTH | 316 | #if ENABLE_FEATURE_TELNET_WIDTH |
| 419 | static void put_iac_naws(byte c, int x, int y) | 317 | static void put_iac_naws(void) |
| 420 | { | 318 | { |
| 421 | put_iac3_IAC_x_y(SB, c); | 319 | unsigned width, height; |
| 422 | 320 | ||
| 423 | put_iac4_msb_lsb((x << 16) + y); | 321 | put_iac3_IAC_x_y(SB, TELOPT_NAWS); |
| 322 | |||
| 323 | /* Send width and height as 16-bit big-endian, escaping IAC bytes */ | ||
| 324 | get_terminal_width_height(0, &width, &height); | ||
| 325 | log1("C:SB NAWS %dx%d", width, height); | ||
| 326 | put_iac_byte_escaped(width >> 8); | ||
| 327 | put_iac_byte_escaped(width); | ||
| 328 | put_iac_byte_escaped(height >> 8); | ||
| 329 | put_iac_byte_escaped(height); | ||
| 424 | 330 | ||
| 425 | put_iac2_x_y(IAC, SE); | 331 | put_iac2_x_y(IAC, SE); |
| 426 | } | 332 | } |
| 427 | #endif | 333 | #endif |
| 428 | 334 | ||
| 429 | static void setConMode(void) | 335 | // Telnet option handling strategy: |
| 336 | // | ||
| 337 | // We send nothing on startup on our own (think "telnet www.kernel.org 80"). | ||
| 338 | // As soon as we see any DO X or WILL X message known to us, we respond | ||
| 339 | // to them: send DO/DONT ECHO, DO SGA if server talked about them, then send | ||
| 340 | // WILL NAWS, WILL TTYPE, WILL NEW_ENVIRON | ||
| 341 | // without waiting for server to advertise those options. | ||
| 342 | // (Why? Some servers may decide to not advertise all 123 options they know). | ||
| 343 | // | ||
| 344 | // TELOPT_ECHO (1) - remote: server echoes our keystrokes back to us | ||
| 345 | // - Server says: WILL ECHO or WONT ECHO | ||
| 346 | // - If we don't see it, or see WONT: line mode (local terminal echoes), aka cooked mode | ||
| 347 | // - When active: We're in character mode (server echoes), aka raw mode | ||
| 348 | // - Response logic: | ||
| 349 | // - Server says WILL ECHO: we send DO ECHO, enter raw mode | ||
| 350 | // (this is the most usual case for non-ancient servers) | ||
| 351 | // - Server says WONT ECHO: we send DONT ECHO, enter cooked mode | ||
| 352 | // - We mirror server's changes, we do allow it to change during session | ||
| 353 | // unlike one-shot options which are negotiated once during startup. | ||
| 354 | // - If user changes mode from ^] menu, we send corresponding DO/DONT ECHO. | ||
| 355 | // | ||
| 356 | // TELOPT_SGA (3) - remote: "Suppress Go Ahead", full-duplex | ||
| 357 | // - Server says: WILL SGA or WONT SGA | ||
| 358 | // - Response logic: one-shot. | ||
| 359 | // - We expect all sane servers to choose WILL SGA at startup. | ||
| 360 | // - If that happens, we send DO SGA. After that, we never react to any | ||
| 361 | // further SGA messages (we expect them to not repeat in practice) | ||
| 362 | // | ||
| 363 | // TELOPT_NAWS (31) - Window Size - local: we send our window size | ||
| 364 | // - If server says: DO NAWS, it understands NAWS. We require this before | ||
| 365 | // - sending SB NAWS ... | ||
| 366 | // - We send window size during initial handshake and on window resize | ||
| 367 | // | ||
| 368 | // TELOPT_TTYPE (24) - Terminal Type - local: we send our terminal type | ||
| 369 | // - Response logic: one-shot. | ||
| 370 | // - If server says DO TTYPE: send "IAC SB TTYPE 0 '$TERM' IAC SE" | ||
| 371 | // | ||
| 372 | // TELOPT_NEW_ENVIRON (39) - Environment (Autologin) - local: we send our username | ||
| 373 | // - Response logic: one-shot. | ||
| 374 | // - If server says DO NEW_ENVIRON: send "IAC SB NEW_ENVIRON IS VAR 'USER' VALUE 'name' IAC SE" | ||
| 375 | // | ||
| 376 | // Unknown options: | ||
| 377 | // Response: NONE - just ignore them | ||
| 378 | // By protocol definition, an option is not in effect until BOTH sides agree. | ||
| 379 | // We don't respond to unknown options - they simply won't be in effect. | ||
| 380 | // | ||
| 381 | // Testing at discworld.atuin.net:23 reveal servers often do NOT negotiate ECHO/SGA | ||
| 382 | // in initial handshake while supporting NAWS and TTYPE: | ||
| 383 | //telnet: S:DO 24 <--TTYPE | ||
| 384 | //telnet: TTYPE:'xterm-256color' setting CHANGED_TTYPE | ||
| 385 | //LPmud version : DW OS v1.02 on port 4242. | ||
| 386 | //telnet: changed:8 flags:8 | ||
| 387 | //telnet: C:SB TTYPE 'xterm-256color' | ||
| 388 | //telnet: S:DO 31 <--NAWS | ||
| 389 | //telnet: S:WILL 86 | ||
| 390 | //telnet: S:DO 91 | ||
| 391 | //telnet: S:WILL 70 | ||
| 392 | //telnet: S:WILL 93 | ||
| 393 | //telnet: S:DO 39 | ||
| 394 | //telnet: S:WILL 201 | ||
| 395 | //......text..... | ||
| 396 | //telnet: changed:2 flags:8 | ||
| 397 | //telnet: C:SB NAWS 226x53 | ||
| 398 | //...text.... | ||
| 399 | //telnet: S:SB 24 <--TTYPE | ||
| 400 | //...text.... | ||
| 401 | //Setting your network terminal type to "xterm-256color". | ||
| 402 | //> cols | ||
| 403 | //Columns currently set to 79. | ||
| 404 | //> rows | ||
| 405 | //Rows currently set to 24. | ||
| 406 | //^^^ server understood TTYPE but not NAWS. ?! | ||
| 407 | static void handle_changes_in_options(stdin_to_net_t *conn) | ||
| 430 | { | 408 | { |
| 431 | if (G.telflags & UF_ECHO) { | 409 | int count; |
| 432 | if (G.charmode == CHM_TRY) { | 410 | |
| 433 | G.charmode = CHM_ON; | 411 | log1("changed:%x flags:%x", G.changes_seen, G.flags); |
| 434 | printf("\r\nEntering %s mode" | 412 | |
| 435 | "\r\nEscape character is '^%c'.\r\n", "character", ']'); | 413 | count = remaining_free_bytes(conn->size); |
| 436 | rawmode(); | 414 | // As soon as we see any DO/DONT/WILL/WONT known to us, |
| 415 | // we assume it *is* a telnet server | ||
| 416 | // (we are not in "telnet www.kernel.org 80" scenario), | ||
| 417 | // and we respond to them, also expressing our own | ||
| 418 | // wishes: WILL NAWS etc. We send our WILLs once, all of them. | ||
| 419 | // We send actual SB with option contents after WILLs | ||
| 420 | // only if that option was requested. | ||
| 421 | // We repeatedly send only NAWS (when our window changes). | ||
| 422 | // Repeated DO requests are ignored. | ||
| 423 | if (G.changes_seen | ||
| 424 | && (count >= G.echo_sga_response_size) | ||
| 425 | ) { | ||
| 426 | if (G.changes_seen & CHANGED_ECHO) { | ||
| 427 | // Server said WILL/WONT ECHO - confirm every time | ||
| 428 | log1("C:%s ECHO", G.optstate_ECHO ? "DO" : "DONT"); | ||
| 429 | put_iac3_IAC_x_y(G.optstate_ECHO ? DO : DONT, TELOPT_ECHO); | ||
| 437 | } | 430 | } |
| 438 | } else { | 431 | if (G.changes_seen & CHANGED_SGA) { |
| 439 | if (G.charmode != CHM_OFF) { | 432 | // Server said WILL SGA - confirm once |
| 440 | G.charmode = CHM_OFF; | 433 | log1("C:DO SGA"); |
| 441 | printf("\r\nEntering %s mode" | 434 | put_iac3_IAC_x_y(DO, TELOPT_SGA); |
| 442 | "\r\nEscape character is '^%c'.\r\n", "line", 'C'); | 435 | G.flags |= FLAGS_SGA_SEEN; // remember we did it |
| 443 | cookmode(); | 436 | G.changes_seen -= CHANGED_SGA; |
| 444 | } | 437 | } |
| 438 | G.changes_seen &= ~(CHANGED_ECHO|CHANGED_SGA); | ||
| 439 | |||
| 440 | if (!(G.flags & INITIAL_SENT)) { | ||
| 441 | // From now on, we'll only do DO/DONT ECHO and maybe DO SGA | ||
| 442 | // in the "if (G.changes_seen)" block. | ||
| 443 | G.flags |= INITIAL_SENT; | ||
| 444 | G.echo_sga_response_size = (G.flags & FLAGS_SGA_SEEN) ? 3 : 6; | ||
| 445 | |||
| 446 | // Send initial IAC sequences for local options we want to advertise | ||
| 447 | // (there may be servers which send DO X only after we say WILL X) | ||
| 448 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 449 | log1("C:WILL %s", "NAWS"); | ||
| 450 | put_iac3_IAC_x_y(WILL, TELOPT_NAWS); | ||
| 451 | #endif | ||
| 452 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 453 | if (G.ttype) { | ||
| 454 | log1("C:WILL %s", "TTYPE"); | ||
| 455 | put_iac3_IAC_x_y(WILL, TELOPT_TTYPE); | ||
| 456 | } | ||
| 457 | #endif | ||
| 458 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 459 | if (G.autologin) { | ||
| 460 | log1("C:WILL %s", "NEW_ENVIRON"); | ||
| 461 | put_iac3_IAC_x_y(WILL, TELOPT_NEW_ENVIRON); | ||
| 462 | } | ||
| 463 | #endif | ||
| 464 | } | ||
| 465 | } | ||
| 466 | |||
| 467 | // If any of the checks below are true, then we know we | ||
| 468 | // went through INITIAL_SENT code path above. | ||
| 469 | // Therefore we always send WILL X before SB X... | ||
| 470 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 471 | if (remaining_free_bytes(conn->size) > MAX_NAWS_SIZE) { | ||
| 472 | if (G.changes_seen & CHANGED_NAWS) { | ||
| 473 | G.flags |= FLAGS_NAWS_ON; // remember we did it | ||
| 474 | G.changes_seen -= CHANGED_NAWS; | ||
| 475 | goto generate_naws; | ||
| 476 | } | ||
| 477 | // Handle window resize: send updated NAWS | ||
| 478 | if (G.got_SIGWINCH) { | ||
| 479 | // we know that INITIAL_SENT is set, signal handler checks that | ||
| 480 | generate_naws: | ||
| 481 | // Clear the flag before put_iac_naws() to avoid race | ||
| 482 | G.got_SIGWINCH = 0; | ||
| 483 | //log1("C:WILL %s", "NAWS, the second time, I'm telling you it's fine!"); | ||
| 484 | //put_iac3_IAC_x_y(WILL, TELOPT_NAWS); | ||
| 485 | put_iac_naws(); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | #endif | ||
| 489 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 490 | if ((G.changes_seen & CHANGED_TTYPE) | ||
| 491 | && remaining_free_bytes(conn->size) > 6 + 2 * strlen(G.ttype) | ||
| 492 | ) { | ||
| 493 | log1("C:SB %s '%s'", "TTYPE", G.ttype); | ||
| 494 | put_iac_subopt(TELOPT_TTYPE, G.ttype); | ||
| 495 | G.ttype = NULL; // remember we did it | ||
| 496 | G.changes_seen -= CHANGED_TTYPE; | ||
| 497 | } | ||
| 498 | #endif | ||
| 499 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 500 | if ((G.changes_seen & CHANGED_NEW_ENVIRON) | ||
| 501 | && remaining_free_bytes(conn->size) > 12 + 2 * strlen(G.autologin) | ||
| 502 | ) { | ||
| 503 | log1("C:SB %s '%s'", "NEW_ENVIRON", G.autologin); | ||
| 504 | put_iac_subopt_autologin(G.autologin); | ||
| 505 | G.autologin = NULL; // remember we did it | ||
| 506 | G.changes_seen -= CHANGED_NEW_ENVIRON; | ||
| 445 | } | 507 | } |
| 508 | #endif | ||
| 446 | } | 509 | } |
| 447 | 510 | ||
| 448 | static void will_charmode(void) | 511 | static void announce_rawmode(void) |
| 449 | { | 512 | { |
| 450 | G.charmode = CHM_TRY; | 513 | printf("\r\nEntering %s mode" |
| 451 | G.telflags |= (UF_ECHO | UF_SGA); | 514 | "\r\nEscape character is '^%c'.\r\n", "character", ']' |
| 452 | setConMode(); | 515 | ); |
| 453 | 516 | rawmode(); | |
| 454 | put_iac3_IAC_x_y(DO, TELOPT_ECHO); | ||
| 455 | put_iac3_IAC_x_y(DO, TELOPT_SGA); | ||
| 456 | iac_flush(); | ||
| 457 | } | 517 | } |
| 458 | 518 | static void announce_and_switch_to_rawmode(void) | |
| 459 | static void do_linemode(void) | ||
| 460 | { | 519 | { |
| 461 | G.charmode = CHM_TRY; | 520 | announce_rawmode(); |
| 462 | G.telflags &= ~(UF_ECHO | UF_SGA); | 521 | rawmode(); |
| 463 | setConMode(); | ||
| 464 | |||
| 465 | put_iac3_IAC_x_y(DONT, TELOPT_ECHO); | ||
| 466 | put_iac3_IAC_x_y(DONT, TELOPT_SGA); | ||
| 467 | iac_flush(); | ||
| 468 | } | 522 | } |
| 469 | 523 | static void announce_and_switch_to_cookmode(void) | |
| 470 | static void to_notsup(char c) | ||
| 471 | { | 524 | { |
| 472 | if (G.telwish == WILL) | 525 | printf("\r\nEntering %s mode" |
| 473 | put_iac3_IAC_x_y(DONT, c); | 526 | "\r\nEscape character is '^%c'.\r\n", "line", 'C' |
| 474 | else if (G.telwish == DO) | 527 | ); |
| 475 | put_iac3_IAC_x_y(WONT, c); | 528 | cookmode(); |
| 476 | } | 529 | } |
| 477 | 530 | ||
| 478 | static void to_echo(void) | 531 | static void show_menu(void) |
| 479 | { | 532 | { |
| 480 | /* if server requests ECHO, don't agree */ | 533 | char b; |
| 481 | if (G.telwish == DO) { | 534 | |
| 482 | put_iac3_IAC_x_y(WONT, TELOPT_ECHO); | 535 | rawmode(); |
| 483 | return; | 536 | |
| 537 | full_write1_str("\r\nConsole escape. Commands are:\r\n" | ||
| 538 | "l go to line mode\r\n" | ||
| 539 | "c go to character mode\r\n" | ||
| 540 | "z suspend telnet\r\n" | ||
| 541 | "e exit\r\n"); | ||
| 542 | if (read(STDIN_FILENO, &b, 1) <= 0 | ||
| 543 | || b == 'e' | ||
| 544 | ) { | ||
| 545 | doexit(EXIT_FAILURE); | ||
| 484 | } | 546 | } |
| 485 | if (G.telwish == DONT) | ||
| 486 | return; | ||
| 487 | 547 | ||
| 488 | if (G.telflags & UF_ECHO) { | 548 | switch (b) { |
| 489 | if (G.telwish == WILL) | 549 | case 'l': |
| 550 | if (G.optstate_ECHO == OPT_ECHO_ON) { /* are we in rawmode? */ | ||
| 551 | G.optstate_ECHO = OPT_ECHO_OFF; | ||
| 552 | announce_and_switch_to_cookmode(); | ||
| 553 | goto echo_changed; | ||
| 554 | } | ||
| 555 | break; | ||
| 556 | case 'c': | ||
| 557 | if (G.optstate_ECHO != OPT_ECHO_ON) { /* OFF or UNKNOWN? (both operate as linemode) */ | ||
| 558 | G.optstate_ECHO = OPT_ECHO_ON; | ||
| 559 | announce_rawmode(); /* no "_and_switch_": we are already in rawmode */ | ||
| 560 | echo_changed: | ||
| 561 | if (G.flags & INITIAL_SENT) | ||
| 562 | G.changes_seen |= CHANGED_ECHO; /* inform the server at next send */ | ||
| 490 | return; | 563 | return; |
| 491 | } else if (G.telwish == WONT) | 564 | } |
| 492 | return; | 565 | break; |
| 566 | case 'z': | ||
| 567 | cookmode(); | ||
| 568 | kill(0, SIGTSTP); | ||
| 569 | rawmode(); | ||
| 570 | break; | ||
| 571 | } | ||
| 493 | 572 | ||
| 494 | if (G.charmode != CHM_OFF) | 573 | full_write1_str("continuing...\r\n"); |
| 495 | G.telflags ^= UF_ECHO; | ||
| 496 | 574 | ||
| 497 | if (G.telflags & UF_ECHO) | 575 | if (G.optstate_ECHO != OPT_ECHO_ON) |
| 498 | put_iac3_IAC_x_y(DO, TELOPT_ECHO); | 576 | cookmode(); |
| 499 | else | 577 | } |
| 500 | put_iac3_IAC_x_y(DONT, TELOPT_ECHO); | ||
| 501 | 578 | ||
| 502 | setConMode(); | 579 | static int have_buffer_to_read_from_stdin(void *this) |
| 503 | full_write1_str("\r\n"); /* sudden modec */ | 580 | { |
| 581 | stdin_to_net_t *conn = this; | ||
| 582 | if (conn->read_fd < 0) | ||
| 583 | return 0; | ||
| 584 | return conn->size < BUFSIZE; | ||
| 504 | } | 585 | } |
| 505 | 586 | ||
| 506 | static void to_sga(void) | 587 | static int read_from_stdin(void *this) |
| 507 | { | 588 | { |
| 508 | /* daemon always sends will/wont, client do/dont */ | 589 | stdin_to_net_t *conn = this; |
| 590 | int count, rem, expand_count; | ||
| 591 | byte *start, *src, *end; | ||
| 592 | byte c; | ||
| 509 | 593 | ||
| 510 | if (G.telflags & UF_SGA) { | 594 | //if (conn->read_fd < 0) |
| 511 | if (G.telwish == WILL) | 595 | // return 0; /* Already stopped reading stdin */ |
| 512 | return; | 596 | //ioloop_run() guarantees it won't call us with fd < 0 |
| 513 | } else if (G.telwish == WONT) | 597 | |
| 514 | return; | 598 | count = BUFSIZE - conn->size; |
| 515 | 599 | count = MIN(BUFSIZE - conn->rdidx, count); | |
| 516 | G.telflags ^= UF_SGA; /* toggle */ | 600 | count /= 2; /* Reserve room for worst-case expansion */ |
| 517 | if (G.telflags & UF_SGA) | 601 | if (count == 0) |
| 518 | put_iac3_IAC_x_y(DO, TELOPT_SGA); | 602 | return 0; |
| 519 | else | 603 | |
| 520 | put_iac3_IAC_x_y(DONT, TELOPT_SGA); | 604 | start = BUF_TTY2NET + conn->rdidx; |
| 605 | count = safe_read(conn->read_fd, start, count); | ||
| 606 | if (count <= 0) { | ||
| 607 | conn->read_fd = -1; | ||
| 608 | return 0; /* Error or EOF - didn't read anything */ | ||
| 609 | } | ||
| 610 | |||
| 611 | /* First pass: scan forward counting characters that need expansion */ | ||
| 612 | src = start; | ||
| 613 | expand_count = 0; | ||
| 614 | rem = count; /* Remaining bytes to scan */ | ||
| 615 | do { | ||
| 616 | c = *src++; | ||
| 617 | if (c == 0x1d) { | ||
| 618 | /* Escape character - process bytes before it, then handle escape */ | ||
| 619 | count -= rem; /* Drop the remaining tail */ | ||
| 620 | #define found_escape (rem != 0) | ||
| 621 | break; | ||
| 622 | } | ||
| 623 | if (c == IAC) { | ||
| 624 | expand_count++; /* IAC -> IAC IAC (one extra byte) */ | ||
| 625 | } else if (c == '\r' || c == '\n') { | ||
| 626 | expand_count++; /* \r or \n -> \r\n (one extra byte) */ | ||
| 627 | } | ||
| 628 | } while (--rem != 0); | ||
| 629 | |||
| 630 | if (expand_count != 0) { | ||
| 631 | /* Slow path: expand in place working backwards */ | ||
| 632 | src = start + count - 1; /* Last read byte */ | ||
| 633 | end = src + expand_count; /* Last byte after expansion */ | ||
| 634 | |||
| 635 | /* As soon as src == end, the remaining bytes do not need processing (think about it!) */ | ||
| 636 | while (src < end) { | ||
| 637 | c = *src--; | ||
| 638 | if (c == IAC) { | ||
| 639 | *end-- = IAC; | ||
| 640 | } else if (c == '\r' || c == '\n') { | ||
| 641 | *end-- = '\n'; | ||
| 642 | c = '\r'; | ||
| 643 | } | ||
| 644 | *end-- = c; | ||
| 645 | } | ||
| 646 | count += expand_count; | ||
| 647 | } | ||
| 648 | |||
| 649 | conn->size += count; | ||
| 650 | conn->rdidx = (conn->rdidx + count) & BUFMASK; | ||
| 651 | |||
| 652 | if (found_escape) | ||
| 653 | show_menu(); | ||
| 654 | #undef found_escape | ||
| 655 | |||
| 656 | return count; | ||
| 521 | } | 657 | } |
| 522 | 658 | ||
| 523 | #if ENABLE_FEATURE_TELNET_TTYPE | 659 | static int have_data_to_write_to_net(void *this) |
| 524 | static void to_ttype(void) | ||
| 525 | { | 660 | { |
| 526 | /* Tell server we will (or won't) do TTYPE */ | 661 | stdin_to_net_t *conn = this; |
| 527 | if (G.ttype) | 662 | if (conn->size == 0 && conn->read_fd < 0) { |
| 528 | put_iac3_IAC_x_y(WILL, TELOPT_TTYPE); | 663 | /* buffer drained and stdin EOF - signal EOF to server */ |
| 529 | else | 664 | shutdown(conn->write_fd, SHUT_WR); |
| 530 | put_iac3_IAC_x_y(WONT, TELOPT_TTYPE); | 665 | /* Remove this pipe */ |
| 666 | ioloop_remove_conn(conn->io, (connection_t*)conn); | ||
| 667 | return -1; | ||
| 668 | } | ||
| 669 | return conn->size > 0 || G_changes_or_WINCH != 0; | ||
| 531 | } | 670 | } |
| 532 | #endif | ||
| 533 | 671 | ||
| 534 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | 672 | static int write_to_net(void *this) |
| 535 | static void to_new_environ(void) | ||
| 536 | { | 673 | { |
| 537 | /* Tell server we will (or will not) do AUTOLOGIN */ | 674 | stdin_to_net_t *conn = this; |
| 538 | if (G.autologin) | 675 | int count; |
| 539 | put_iac3_IAC_x_y(WILL, TELOPT_NEW_ENVIRON); | 676 | |
| 540 | else | 677 | /* Do we have option or NAWS changes to handle? */ |
| 541 | put_iac3_IAC_x_y(WONT, TELOPT_NEW_ENVIRON); | 678 | if (G_changes_or_WINCH) |
| 679 | handle_changes_in_options(conn); /* yes */ | ||
| 680 | |||
| 681 | count = MIN(BUFSIZE - conn->wridx, conn->size); | ||
| 682 | // can be zero due to handle_changes_in_options() | ||
| 683 | if (count == 0) | ||
| 684 | return 0; | ||
| 685 | count = safe_write(conn->write_fd, BUF_TTY2NET + conn->wridx, count); | ||
| 686 | if (count <= 0) { | ||
| 687 | if (count < 0 && errno == EAGAIN) | ||
| 688 | return 0; | ||
| 689 | full_write1_str("Error writing to foreign host\r\n"); | ||
| 690 | ioloop_remove_conn(conn->io, (connection_t*)conn); | ||
| 691 | return -1; /* "I'm gone" */ | ||
| 692 | } | ||
| 693 | |||
| 694 | conn->wridx = (conn->wridx + count) & BUFMASK; | ||
| 695 | conn->size -= count; | ||
| 696 | if (conn->size == 0) { | ||
| 697 | conn->rdidx = 0; | ||
| 698 | conn->wridx = 0; | ||
| 699 | } | ||
| 700 | return count; | ||
| 542 | } | 701 | } |
| 543 | #endif | ||
| 544 | 702 | ||
| 545 | #if ENABLE_FEATURE_TELNET_WIDTH | 703 | static int have_buffer_to_read_from_net(void *this) |
| 546 | static void to_naws(void) | ||
| 547 | { | 704 | { |
| 548 | /* Tell server we will do NAWS */ | 705 | net_to_stdout_t *conn = this; |
| 549 | put_iac3_IAC_x_y(WILL, TELOPT_NAWS); | 706 | if (conn->read_fd < 0) |
| 707 | return 0; | ||
| 708 | return conn->size < BUFSIZE; | ||
| 550 | } | 709 | } |
| 551 | #endif | ||
| 552 | 710 | ||
| 553 | static void telopt(byte c) | 711 | static int read_from_net(void *this) |
| 554 | { | 712 | { |
| 555 | switch (c) { | 713 | net_to_stdout_t *conn = this; |
| 556 | case TELOPT_ECHO: | 714 | int count; |
| 557 | to_echo(); break; | 715 | byte *src, *dst; |
| 558 | case TELOPT_SGA: | 716 | byte c; |
| 559 | to_sga(); break; | 717 | byte oldstate_ECHO; |
| 718 | |||
| 719 | count = BUFSIZE - conn->size; | ||
| 720 | //if (count == 0) | ||
| 721 | // return 0; /* buffer full */ | ||
| 722 | //ioloop_run() ensures this does not happen | ||
| 723 | count = MIN(BUFSIZE - conn->rdidx, count); /* can't be zero */ | ||
| 724 | |||
| 725 | /* Read directly into circular buffer's linear fragment */ | ||
| 726 | dst = BUF_NET2TTY + conn->rdidx; | ||
| 727 | count = safe_read(conn->read_fd, dst, count); | ||
| 728 | dbg("read_from_net bytes:%d input_state:%d %s", | ||
| 729 | count, conn->input_state, bin_to_hex(dst, count > 0 ? count : 0)); | ||
| 730 | if (count <= 0) { | ||
| 731 | if (count < 0 && errno == EAGAIN) | ||
| 732 | return 0; | ||
| 733 | full_write1_str("EOF on read from foreign host\r\n"); | ||
| 734 | conn->read_fd = -1; | ||
| 735 | /* Imagine scenario: "cat" is running in the server. | ||
| 736 | * We connect and press ^D. Byte 0x04 is transmitted to "cat", | ||
| 737 | * it sees that as EOF and exits. | ||
| 738 | * We see EOF here (netfd read side is closed). | ||
| 739 | * Our stdin is still open, but we have no buffered data to write to net. | ||
| 740 | * ioloop_run() will not _poll_ the netfd for writing. | ||
| 741 | * We will not realize that netfd write side is also closed (!!!) | ||
| 742 | * until something is typed in our stdin and we poll netfd to write that data. | ||
| 743 | */ | ||
| 744 | //FIXME: send a few IAC NOPs to see whether the peer can still receive? | ||
| 745 | /* This is a workaround: pretend that our stdin is also closed: */ | ||
| 746 | G.conn_stdin2net.read_fd = -1; | ||
| 747 | /* Unlike just exiting, this will try to send any buffered data */ | ||
| 748 | |||
| 749 | return 0; | ||
| 750 | } | ||
| 751 | |||
| 752 | /* Copy option states - will be updated during processing, then compared after */ | ||
| 753 | oldstate_ECHO = G.optstate_ECHO; | ||
| 754 | |||
| 755 | /* Optimization: do not load/store-in-place if unnecessary */ | ||
| 756 | if (conn->input_state == TS_NORMAL) { | ||
| 757 | while (count != 0 && *dst != IAC && *dst != '\r') | ||
| 758 | count--, dst++; | ||
| 759 | } | ||
| 760 | /* Process IAC sequences in place: | ||
| 761 | * - Update option states (G.oldstate_XYZ) | ||
| 762 | * - Decode/remove IAC sequences, compacting the data | ||
| 763 | * - src = read pointer, dst = write pointer (for compaction) | ||
| 764 | */ | ||
| 765 | src = dst; | ||
| 766 | while (--count >= 0) { | ||
| 767 | c = *src++; | ||
| 768 | |||
| 769 | switch (conn->input_state) { | ||
| 770 | int will; | ||
| 771 | case TS_NORMAL: | ||
| 772 | normal: | ||
| 773 | if (c == IAC) { | ||
| 774 | SET_INPUT_STATE(conn, TS_IAC, c); | ||
| 775 | continue; | ||
| 776 | } | ||
| 777 | if (c == '\r') { | ||
| 778 | SET_INPUT_STATE(conn, TS_CR, c); | ||
| 779 | } | ||
| 780 | *dst++ = c; | ||
| 781 | continue; | ||
| 782 | |||
| 783 | case TS_CR: | ||
| 784 | SET_INPUT_STATE(conn, TS_NORMAL, c); | ||
| 785 | if (c == '\0') /* Skip NUL after CR (telnet EOL: CR NUL) */ | ||
| 786 | continue; | ||
| 787 | goto normal; | ||
| 788 | |||
| 789 | case TS_IAC: | ||
| 790 | if (c == IAC) { | ||
| 791 | /* IAC IAC -> single IAC */ | ||
| 792 | *dst++ = c; | ||
| 793 | SET_INPUT_STATE(conn, TS_NORMAL, c); | ||
| 794 | } else if (c == SB) { | ||
| 795 | conn->negotiation_verb = 0xff; /* reuse as counter */ | ||
| 796 | SET_INPUT_STATE(conn, TS_SUB1, c); | ||
| 797 | log1("S:SB %d", count ? *src : 0); | ||
| 798 | } else if (c == WILL || c == WONT || c == DO || c == DONT) { /* 251-254 */ | ||
| 799 | conn->negotiation_verb = c; | ||
| 800 | SET_INPUT_STATE(conn, TS_OPT, c); | ||
| 801 | } else { | ||
| 802 | /* Unknown IAC command, ignore */ | ||
| 803 | SET_INPUT_STATE(conn, TS_NORMAL, c); | ||
| 804 | } | ||
| 805 | break; | ||
| 806 | |||
| 807 | case TS_OPT: | ||
| 808 | #if VERBOSE | ||
| 809 | { | ||
| 810 | static const char verbs[] ALIGN1 = "WILL\0""WONT\0""DO\0""DONT"; | ||
| 811 | log1("S:%s %d", nth_string(verbs, conn->negotiation_verb - WILL), c); | ||
| 812 | } | ||
| 813 | #endif | ||
| 814 | /* Process option negotiation */ | ||
| 815 | will = (conn->negotiation_verb == WILL); | ||
| 816 | if (will || conn->negotiation_verb == WONT) { | ||
| 817 | switch (c) { | ||
| 818 | case TELOPT_ECHO: /* Remote option: server echoes our typing */ | ||
| 819 | G.optstate_ECHO = will; | ||
| 820 | break; | ||
| 821 | case TELOPT_SGA: /* Remote option: "suppress go ahead" */ | ||
| 822 | if (will && !(G.flags & FLAGS_SGA_SEEN)) | ||
| 823 | G.changes_seen |= CHANGED_SGA; | ||
| 824 | break; | ||
| 825 | } | ||
| 826 | } else if (conn->negotiation_verb == DO) { | ||
| 827 | switch (c) { | ||
| 560 | #if ENABLE_FEATURE_TELNET_TTYPE | 828 | #if ENABLE_FEATURE_TELNET_TTYPE |
| 561 | case TELOPT_TTYPE: | 829 | case TELOPT_TTYPE: /* Local option: we send terminal type */ |
| 562 | to_ttype(); break; | 830 | log1("TTYPE:'%s' %ssetting CHANGED_TTYPE", G.ttype, G.ttype ? "" : "not "); |
| 831 | if (G.ttype) | ||
| 832 | G.changes_seen |= CHANGED_TTYPE; | ||
| 833 | break; | ||
| 563 | #endif | 834 | #endif |
| 564 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | 835 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
| 565 | case TELOPT_NEW_ENVIRON: | 836 | case TELOPT_NEW_ENVIRON: /* Local option: we send username */ |
| 566 | to_new_environ(); break; | 837 | if (G.autologin) |
| 838 | G.changes_seen |= CHANGED_NEW_ENVIRON; | ||
| 839 | break; | ||
| 567 | #endif | 840 | #endif |
| 568 | #if ENABLE_FEATURE_TELNET_WIDTH | 841 | #if ENABLE_FEATURE_TELNET_WIDTH |
| 569 | case TELOPT_NAWS: | 842 | case TELOPT_NAWS: /* Local option: we send window size */ |
| 570 | to_naws(); | 843 | if (!(G.flags & FLAGS_NAWS_ON)) |
| 571 | put_iac_naws(c, G.win_width, G.win_height); | 844 | G.changes_seen |= CHANGED_NAWS; |
| 572 | break; | 845 | break; |
| 573 | #endif | 846 | #endif |
| 574 | default: | 847 | } |
| 575 | to_notsup(c); | 848 | } |
| 576 | break; | 849 | /* else: "DONT <something>": ignore */ |
| 850 | SET_INPUT_STATE(conn, TS_NORMAL, c); | ||
| 851 | break; | ||
| 852 | |||
| 853 | case TS_SUB1: | ||
| 854 | /* Avoid being stuck in TS_SUB1 forever (with detours into TS_SUB2) | ||
| 855 | * if IAC SE is never seen (buggy server response?). | ||
| 856 | */ | ||
| 857 | if (--conn->negotiation_verb == 0) { | ||
| 858 | log1("unterminated SB (server bug?)"); | ||
| 859 | SET_INPUT_STATE(conn, TS_NORMAL, c); | ||
| 860 | } else | ||
| 861 | /* Skip over subnegotiation bytes until we see IAC */ | ||
| 862 | if (c == IAC) { | ||
| 863 | SET_INPUT_STATE(conn, TS_SUB2, c); | ||
| 864 | } | ||
| 865 | break; | ||
| 866 | |||
| 867 | case TS_SUB2: | ||
| 868 | /* After IAC in subnegotiation, check for SE */ | ||
| 869 | if (c == SE) { | ||
| 870 | /* End of subnegotiation */ | ||
| 871 | SET_INPUT_STATE(conn, TS_NORMAL, c); | ||
| 872 | } else { | ||
| 873 | /* IAC followed by something other than SE, back to SUB1 */ | ||
| 874 | SET_INPUT_STATE(conn, TS_SUB1, c); | ||
| 875 | } | ||
| 876 | break; | ||
| 877 | } | ||
| 577 | } | 878 | } |
| 578 | } | ||
| 579 | 879 | ||
| 580 | /* subnegotiation -- ignore all (except TTYPE,NAWS) */ | 880 | if (oldstate_ECHO != G.optstate_ECHO) { |
| 581 | static void subneg(byte c) | 881 | /* Tell net writer to generate a confirmation */ |
| 582 | { | 882 | G.changes_seen |= CHANGED_ECHO; |
| 583 | switch (G.telstate) { | 883 | /* Print the banner and set termios */ |
| 584 | case TS_SUB1: | 884 | if (G.optstate_ECHO == OPT_ECHO_ON) |
| 585 | if (c == IAC) | 885 | announce_and_switch_to_rawmode(); |
| 586 | G.telstate = TS_SUB2; | ||
| 587 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 588 | else | 886 | else |
| 589 | if (c == TELOPT_TTYPE && G.ttype) | 887 | announce_and_switch_to_cookmode(); |
| 590 | put_iac_subopt(TELOPT_TTYPE, G.ttype); | ||
| 591 | #endif | ||
| 592 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 593 | else | ||
| 594 | if (c == TELOPT_NEW_ENVIRON && G.autologin) | ||
| 595 | put_iac_subopt_autologin(); | ||
| 596 | #endif | ||
| 597 | break; | ||
| 598 | case TS_SUB2: | ||
| 599 | if (c == SE) { | ||
| 600 | G.telstate = TS_COPY; | ||
| 601 | return; | ||
| 602 | } | ||
| 603 | G.telstate = TS_SUB1; | ||
| 604 | break; | ||
| 605 | } | 888 | } |
| 889 | |||
| 890 | /* Update circular buffer: only the compacted data */ | ||
| 891 | count = dst - (BUF_NET2TTY + conn->rdidx); | ||
| 892 | conn->size += count; | ||
| 893 | conn->rdidx = (conn->rdidx + count) & BUFMASK; | ||
| 894 | |||
| 895 | dbg("read_from_net: user bytes:%d input_state:%d", count, conn->input_state); | ||
| 896 | |||
| 897 | return count; | ||
| 606 | } | 898 | } |
| 607 | 899 | ||
| 608 | static void rawmode(void) | 900 | static int have_data_to_write_to_stdout(void *this) |
| 609 | { | 901 | { |
| 610 | if (G.do_termios) | 902 | net_to_stdout_t *conn = this; |
| 611 | tcsetattr(0, TCSADRAIN, &G.termios_raw); | 903 | if (conn->size == 0 && conn->read_fd < 0) { |
| 904 | /* buffer drained and network read EOF */ | ||
| 905 | /* Remove this pipe */ | ||
| 906 | ioloop_remove_conn(conn->io, (connection_t*)conn); | ||
| 907 | return -1; | ||
| 908 | } | ||
| 909 | return conn->size > 0; | ||
| 612 | } | 910 | } |
| 613 | 911 | ||
| 614 | static void cookmode(void) | 912 | static int write_to_stdout(void *this) |
| 615 | { | 913 | { |
| 616 | if (G.do_termios) | 914 | net_to_stdout_t *conn = this; |
| 617 | tcsetattr(0, TCSADRAIN, &G.termios_def); | 915 | int wr = MIN(BUFSIZE - conn->wridx, conn->size); |
| 916 | ssize_t count; | ||
| 917 | |||
| 918 | dbg("write_to_stdout: wr:%d %s", wr, bin_to_hex(BUF_NET2TTY + conn->wridx, wr)); | ||
| 919 | count = safe_write(conn->write_fd, BUF_NET2TTY + conn->wridx, wr); | ||
| 920 | if (count <= 0) { | ||
| 921 | if (count < 0 && errno == EAGAIN) | ||
| 922 | return 0; | ||
| 923 | //full_write1_str("Error writing to stdout\r\n"); | ||
| 924 | ioloop_remove_conn(conn->io, (connection_t*)conn); | ||
| 925 | return -1; /* "I'm gone" */ | ||
| 926 | } | ||
| 927 | |||
| 928 | conn->wridx = (conn->wridx + count) & BUFMASK; | ||
| 929 | conn->size -= count; | ||
| 930 | if (conn->size == 0) { | ||
| 931 | conn->rdidx = 0; | ||
| 932 | conn->wridx = 0; | ||
| 933 | } | ||
| 934 | return count; | ||
| 618 | } | 935 | } |
| 619 | 936 | ||
| 620 | int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 937 | int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| @@ -622,81 +939,102 @@ int telnet_main(int argc UNUSED_PARAM, char **argv) | |||
| 622 | { | 939 | { |
| 623 | char *host; | 940 | char *host; |
| 624 | int port; | 941 | int port; |
| 625 | int len; | 942 | int opts; |
| 626 | struct pollfd ufds[2]; | ||
| 627 | 943 | ||
| 628 | INIT_G(); | 944 | INIT_G(); |
| 629 | 945 | ||
| 630 | #if ENABLE_FEATURE_TELNET_TTYPE | 946 | #if ENABLE_FEATURE_TELNET_TTYPE |
| 631 | G.ttype = getenv("TERM"); | 947 | G.ttype = getenv("TERM"); |
| 632 | #endif | 948 | #endif |
| 633 | 949 | opts = getopt32(argv, ""IF_VERBOSE("^") | |
| 634 | if (tcgetattr(0, &G.termios_def) >= 0) { | 950 | IF_FEATURE_TELNET_AUTOLOGIN("al:")IF_VERBOSE("v") |
| 635 | G.do_termios = 1; | 951 | IF_VERBOSE("\0" "vv") |
| 636 | G.termios_raw = G.termios_def; | 952 | IF_FEATURE_TELNET_AUTOLOGIN(, &G.autologin) |
| 637 | cfmakeraw(&G.termios_raw); | 953 | IF_VERBOSE(, &G.verbose) |
| 638 | } | 954 | ); |
| 639 | |||
| 640 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | 955 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN |
| 641 | if (1 == getopt32(argv, "al:", &G.autologin)) { | 956 | if ((opts & 3) == 1) { |
| 642 | /* Only -a without -l USER picks $USER from envvar */ | 957 | // -a without -l USER picks USER from envvar: |
| 643 | G.autologin = getenv("USER"); | 958 | G.autologin = getenv("USER"); |
| 644 | } | 959 | } |
| 645 | argv += optind; | ||
| 646 | #else | ||
| 647 | argv++; | ||
| 648 | #endif | 960 | #endif |
| 961 | argv += optind; | ||
| 649 | if (!*argv) | 962 | if (!*argv) |
| 650 | bb_show_usage(); | 963 | bb_show_usage(); |
| 651 | host = *argv++; | 964 | host = *argv++; |
| 652 | port = *argv ? bb_lookup_port(*argv++, "tcp", 23) | 965 | port = *argv ? bb_lookup_port(*argv++, "tcp", 23) |
| 653 | : bb_lookup_std_port("telnet", "tcp", 23); | 966 | : bb_lookup_std_port("telnet", "tcp", 23); |
| 654 | if (*argv) /* extra params?? */ | 967 | if (*argv) // extra params?? |
| 655 | bb_show_usage(); | 968 | bb_show_usage(); |
| 656 | 969 | ||
| 970 | // Save our termios | ||
| 971 | if (tcgetattr(0, &G.termios_def) >= 0) { | ||
| 972 | G.flags |= DO_TERMIOS; | ||
| 973 | G.termios_raw = G.termios_def; | ||
| 974 | cfmakeraw(&G.termios_raw); | ||
| 975 | } | ||
| 976 | |||
| 657 | xmove_fd(create_and_connect_stream_or_die(host, port), netfd); | 977 | xmove_fd(create_and_connect_stream_or_die(host, port), netfd); |
| 658 | printf("Connected to %s\n", host); | 978 | printf("Connected to %s\n", host); |
| 659 | 979 | ||
| 660 | setsockopt_keepalive(netfd); | 980 | setsockopt_keepalive(netfd); |
| 981 | ndelay_on(netfd); | ||
| 661 | 982 | ||
| 662 | #if ENABLE_FEATURE_TELNET_WIDTH | 983 | #if ENABLE_FEATURE_TELNET_WIDTH |
| 663 | get_terminal_width_height(0, &G.win_width, &G.win_height); | 984 | signal(SIGWINCH, handle_SIGWINCH); |
| 664 | //TODO: support dynamic resize? | ||
| 665 | #endif | 985 | #endif |
| 666 | |||
| 667 | signal(SIGINT, record_signo); | 986 | signal(SIGINT, record_signo); |
| 668 | 987 | // Without this, SIGPIPE was seen on loopback connections: | |
| 669 | ufds[0].fd = STDIN_FILENO; | 988 | signal(SIGPIPE, SIG_IGN); |
| 670 | ufds[0].events = POLLIN; | 989 | |
| 671 | ufds[1].fd = netfd; | 990 | // Initialize connections |
| 672 | ufds[1].events = POLLIN; | 991 | G.conn_stdin2net.have_buffer_to_read_into = have_buffer_to_read_from_stdin; |
| 673 | 992 | G.conn_stdin2net.have_data_to_write = have_data_to_write_to_net; | |
| 674 | while (1) { | 993 | G.conn_stdin2net.read = read_from_stdin; |
| 675 | if (poll(ufds, 2, -1) < 0) { | 994 | G.conn_stdin2net.write = write_to_net; |
| 676 | /* error, ignore and/or log something, bay go to loop */ | 995 | if (STDIN_FILENO != 0) |
| 677 | if (bb_got_signal) | 996 | G.conn_stdin2net.read_fd = STDIN_FILENO; |
| 678 | con_escape(); | 997 | G.conn_stdin2net.write_fd = netfd; |
| 679 | else | 998 | |
| 680 | sleep1(); | 999 | G.conn_net2stdout.have_buffer_to_read_into = have_buffer_to_read_from_net; |
| 681 | continue; | 1000 | G.conn_net2stdout.have_data_to_write = have_data_to_write_to_stdout; |
| 1001 | G.conn_net2stdout.read = read_from_net; | ||
| 1002 | G.conn_net2stdout.write = write_to_stdout; | ||
| 1003 | G.conn_net2stdout.read_fd = netfd; | ||
| 1004 | G.conn_net2stdout.write_fd = STDOUT_FILENO; | ||
| 1005 | if (TS_NORMAL != 0) | ||
| 1006 | G.conn_net2stdout.input_state = TS_NORMAL; | ||
| 1007 | |||
| 1008 | ioloop_insert_conn(&G.io, (connection_t*)&G.conn_stdin2net); | ||
| 1009 | ioloop_insert_conn(&G.io, (connection_t*)&G.conn_net2stdout); | ||
| 1010 | |||
| 1011 | G.echo_sga_response_size = 3 * (2 | ||
| 1012 | IF_FEATURE_TELNET_WIDTH( + 1) | ||
| 1013 | IF_FEATURE_TELNET_TTYPE(+ !!G.ttype) | ||
| 1014 | IF_FEATURE_TELNET_AUTOLOGIN(+ !!G.autologin) | ||
| 1015 | ); | ||
| 1016 | // Make *any* ECHO negotiation from server, positive or negative, | ||
| 1017 | // trigger "we have a change" logic: | ||
| 1018 | G.optstate_ECHO = OPT_ECHO_UNKNOWN; | ||
| 1019 | #if DEBUG || VERBOSE | ||
| 1020 | // Terminal can change to raw mode, fix line printing | ||
| 1021 | msg_eol = "\r\n"; | ||
| 1022 | #endif | ||
| 1023 | // EINTR flag and looping is only needed to handle ^C | ||
| 1024 | // in line mode, otherwise just a call to ioloop_run() would do. | ||
| 1025 | // TODO: replace primitive line mode with read_line_input()!!! | ||
| 1026 | G.io.flags |= IOLOOP_FLAG_EXIT_IF_EINTR; | ||
| 1027 | for (;;) { | ||
| 1028 | int rc = ioloop_run(&G.io); | ||
| 1029 | if (rc == IOLOOP_NO_CONNS) { | ||
| 1030 | dbg("connection is closed"); | ||
| 1031 | break; | ||
| 682 | } | 1032 | } |
| 683 | 1033 | if (bb_got_signal /*&& rc == IOLOOP_EINTR*/) { | |
| 684 | // FIXME: reads can block. Need full bidirectional buffering. | 1034 | bb_got_signal = 0; |
| 685 | 1035 | show_menu(); | |
| 686 | if (ufds[0].revents) { | ||
| 687 | len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); | ||
| 688 | if (len <= 0) | ||
| 689 | doexit(EXIT_SUCCESS); | ||
| 690 | handle_net_output(len); | ||
| 691 | } | 1036 | } |
| 1037 | } | ||
| 692 | 1038 | ||
| 693 | if (ufds[1].revents) { | 1039 | doexit(EXIT_SUCCESS); |
| 694 | len = safe_read(netfd, G.buf, DATABUFSIZE); | ||
| 695 | if (len <= 0) { | ||
| 696 | full_write1_str("Connection closed by foreign host\r\n"); | ||
| 697 | doexit(EXIT_FAILURE); | ||
| 698 | } | ||
| 699 | handle_net_input(len); | ||
| 700 | } | ||
| 701 | } /* while (1) */ | ||
| 702 | } | 1040 | } |
diff --git a/networking/telnet.c.OLD b/networking/telnet.c.OLD new file mode 100644 index 000000000..7a0253525 --- /dev/null +++ b/networking/telnet.c.OLD | |||
| @@ -0,0 +1,702 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 2 | /* | ||
| 3 | * telnet implementation for busybox | ||
| 4 | * | ||
| 5 | * Author: Tomi Ollila <too@iki.fi> | ||
| 6 | * Copyright (C) 1994-2000 by Tomi Ollila | ||
| 7 | * | ||
| 8 | * Created: Thu Apr 7 13:29:41 1994 too | ||
| 9 | * Last modified: Fri Jun 9 14:34:24 2000 too | ||
| 10 | * | ||
| 11 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
| 12 | * | ||
| 13 | * HISTORY | ||
| 14 | * Revision 3.1 1994/04/17 11:31:54 too | ||
| 15 | * initial revision | ||
| 16 | * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org> | ||
| 17 | * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan | ||
| 18 | * <jam@ltsp.org> | ||
| 19 | * Modified 2004/02/11 to add ability to pass the USER variable to remote host | ||
| 20 | * by Fernando Silveira <swrh@gmx.net> | ||
| 21 | */ | ||
| 22 | //config:config TELNET | ||
| 23 | //config: bool "telnet (8.8 kb)" | ||
| 24 | //config: default y | ||
| 25 | //config: help | ||
| 26 | //config: Telnet is an interface to the TELNET protocol, but is also commonly | ||
| 27 | //config: used to test other simple protocols. | ||
| 28 | //config: | ||
| 29 | //config:config FEATURE_TELNET_TTYPE | ||
| 30 | //config: bool "Pass TERM type to remote host" | ||
| 31 | //config: default y | ||
| 32 | //config: depends on TELNET | ||
| 33 | //config: help | ||
| 34 | //config: Setting this option will forward the TERM environment variable to the | ||
| 35 | //config: remote host you are connecting to. This is useful to make sure that | ||
| 36 | //config: things like ANSI colors and other control sequences behave. | ||
| 37 | //config: | ||
| 38 | //config:config FEATURE_TELNET_AUTOLOGIN | ||
| 39 | //config: bool "Pass USER type to remote host" | ||
| 40 | //config: default y | ||
| 41 | //config: depends on TELNET | ||
| 42 | //config: help | ||
| 43 | //config: Setting this option will forward the USER environment variable to the | ||
| 44 | //config: remote host you are connecting to. This is useful when you need to | ||
| 45 | //config: log into a machine without telling the username (autologin). This | ||
| 46 | //config: option enables '-a' and '-l USER' options. | ||
| 47 | //config: | ||
| 48 | //config:config FEATURE_TELNET_WIDTH | ||
| 49 | //config: bool "Enable window size autodetection" | ||
| 50 | //config: default y | ||
| 51 | //config: depends on TELNET | ||
| 52 | |||
| 53 | //applet:IF_TELNET(APPLET(telnet, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
| 54 | |||
| 55 | //kbuild:lib-$(CONFIG_TELNET) += telnet.o | ||
| 56 | |||
| 57 | //usage:#if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 58 | //usage:#define telnet_trivial_usage | ||
| 59 | //usage: "[-a] [-l USER] HOST [PORT]" | ||
| 60 | //usage:#define telnet_full_usage "\n\n" | ||
| 61 | //usage: "Connect to telnet server\n" | ||
| 62 | //usage: "\n -a Automatic login with $USER variable" | ||
| 63 | //usage: "\n -l USER Automatic login as USER" | ||
| 64 | //usage: | ||
| 65 | //usage:#else | ||
| 66 | //usage:#define telnet_trivial_usage | ||
| 67 | //usage: "HOST [PORT]" | ||
| 68 | //usage:#define telnet_full_usage "\n\n" | ||
| 69 | //usage: "Connect to telnet server" | ||
| 70 | //usage:#endif | ||
| 71 | |||
| 72 | #include <arpa/telnet.h> | ||
| 73 | #include <netinet/in.h> | ||
| 74 | #include "libbb.h" | ||
| 75 | #include "common_bufsiz.h" | ||
| 76 | |||
| 77 | #ifdef __BIONIC__ | ||
| 78 | /* should be in arpa/telnet.h */ | ||
| 79 | # define IAC 255 /* interpret as command: */ | ||
| 80 | # define DONT 254 /* you are not to use option */ | ||
| 81 | # define DO 253 /* please, you use option */ | ||
| 82 | # define WONT 252 /* I won't use option */ | ||
| 83 | # define WILL 251 /* I will use option */ | ||
| 84 | # define SB 250 /* interpret as subnegotiation */ | ||
| 85 | # define SE 240 /* end sub negotiation */ | ||
| 86 | # define TELOPT_ECHO 1 /* echo */ | ||
| 87 | # define TELOPT_SGA 3 /* suppress go ahead */ | ||
| 88 | # define TELOPT_TTYPE 24 /* terminal type */ | ||
| 89 | # define TELOPT_NAWS 31 /* window size */ | ||
| 90 | #endif | ||
| 91 | |||
| 92 | enum { | ||
| 93 | DATABUFSIZE = 128, | ||
| 94 | IACBUFSIZE = 128, | ||
| 95 | |||
| 96 | CHM_TRY = 0, | ||
| 97 | CHM_ON = 1, | ||
| 98 | CHM_OFF = 2, | ||
| 99 | |||
| 100 | UF_ECHO = 0x01, | ||
| 101 | UF_SGA = 0x02, | ||
| 102 | |||
| 103 | TS_NORMAL = 0, | ||
| 104 | TS_COPY = 1, | ||
| 105 | TS_IAC = 2, | ||
| 106 | TS_OPT = 3, | ||
| 107 | TS_SUB1 = 4, | ||
| 108 | TS_SUB2 = 5, | ||
| 109 | TS_CR = 6, | ||
| 110 | }; | ||
| 111 | |||
| 112 | typedef unsigned char byte; | ||
| 113 | |||
| 114 | enum { netfd = 3 }; | ||
| 115 | |||
| 116 | struct globals { | ||
| 117 | int iaclen; /* could even use byte, but it's a loss on x86 */ | ||
| 118 | byte telstate; /* telnet negotiation state from network input */ | ||
| 119 | byte telwish; /* DO, DONT, WILL, WONT */ | ||
| 120 | byte charmode; | ||
| 121 | byte telflags; | ||
| 122 | byte do_termios; | ||
| 123 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 124 | char *ttype; | ||
| 125 | #endif | ||
| 126 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 127 | const char *autologin; | ||
| 128 | #endif | ||
| 129 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 130 | unsigned win_width, win_height; | ||
| 131 | #endif | ||
| 132 | /* same buffer used both for network and console read/write */ | ||
| 133 | char buf[DATABUFSIZE]; | ||
| 134 | /* buffer to handle telnet negotiations */ | ||
| 135 | char iacbuf[IACBUFSIZE]; | ||
| 136 | struct termios termios_def; | ||
| 137 | struct termios termios_raw; | ||
| 138 | } FIX_ALIASING; | ||
| 139 | #define G (*(struct globals*)bb_common_bufsiz1) | ||
| 140 | #define INIT_G() do { \ | ||
| 141 | setup_common_bufsiz(); \ | ||
| 142 | BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ | ||
| 143 | } while (0) | ||
| 144 | |||
| 145 | |||
| 146 | static void rawmode(void); | ||
| 147 | static void cookmode(void); | ||
| 148 | static void do_linemode(void); | ||
| 149 | static void will_charmode(void); | ||
| 150 | static void telopt(byte c); | ||
| 151 | static void subneg(byte c); | ||
| 152 | |||
| 153 | static void iac_flush(void) | ||
| 154 | { | ||
| 155 | if (G.iaclen != 0) { | ||
| 156 | full_write(netfd, G.iacbuf, G.iaclen); | ||
| 157 | G.iaclen = 0; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | static void doexit(int ev) NORETURN; | ||
| 162 | static void doexit(int ev) | ||
| 163 | { | ||
| 164 | cookmode(); | ||
| 165 | exit(ev); | ||
| 166 | } | ||
| 167 | |||
| 168 | static void con_escape(void) | ||
| 169 | { | ||
| 170 | char b; | ||
| 171 | |||
| 172 | if (bb_got_signal) /* came from line mode... go raw */ | ||
| 173 | rawmode(); | ||
| 174 | |||
| 175 | full_write1_str("\r\nConsole escape. Commands are:\r\n\n" | ||
| 176 | " l go to line mode\r\n" | ||
| 177 | " c go to character mode\r\n" | ||
| 178 | " z suspend telnet\r\n" | ||
| 179 | " e exit telnet\r\n"); | ||
| 180 | |||
| 181 | if (read(STDIN_FILENO, &b, 1) <= 0) | ||
| 182 | doexit(EXIT_FAILURE); | ||
| 183 | |||
| 184 | switch (b) { | ||
| 185 | case 'l': | ||
| 186 | if (!bb_got_signal) { | ||
| 187 | do_linemode(); | ||
| 188 | goto ret; | ||
| 189 | } | ||
| 190 | break; | ||
| 191 | case 'c': | ||
| 192 | if (bb_got_signal) { | ||
| 193 | will_charmode(); | ||
| 194 | goto ret; | ||
| 195 | } | ||
| 196 | break; | ||
| 197 | case 'z': | ||
| 198 | cookmode(); | ||
| 199 | kill(0, SIGTSTP); | ||
| 200 | rawmode(); | ||
| 201 | break; | ||
| 202 | case 'e': | ||
| 203 | doexit(EXIT_SUCCESS); | ||
| 204 | } | ||
| 205 | |||
| 206 | full_write1_str("continuing...\r\n"); | ||
| 207 | |||
| 208 | if (bb_got_signal) | ||
| 209 | cookmode(); | ||
| 210 | ret: | ||
| 211 | bb_got_signal = 0; | ||
| 212 | } | ||
| 213 | |||
| 214 | static void handle_net_output(int len) | ||
| 215 | { | ||
| 216 | byte outbuf[2 * DATABUFSIZE]; | ||
| 217 | byte *dst = outbuf; | ||
| 218 | byte *src = (byte*)G.buf; | ||
| 219 | byte *end = src + len; | ||
| 220 | |||
| 221 | while (src < end) { | ||
| 222 | byte c = *src++; | ||
| 223 | if (c == 0x1d) { | ||
| 224 | con_escape(); | ||
| 225 | return; | ||
| 226 | } | ||
| 227 | *dst = c; | ||
| 228 | if (c == IAC) | ||
| 229 | *++dst = c; /* IAC -> IAC IAC */ | ||
| 230 | else | ||
| 231 | if (c == '\r' || c == '\n') { | ||
| 232 | /* Enter key sends '\r' in raw mode and '\n' in cooked one. | ||
| 233 | * | ||
| 234 | * See RFC 1123 3.3.1 Telnet End-of-Line Convention. | ||
| 235 | * Using CR LF instead of other allowed possibilities | ||
| 236 | * like CR NUL - easier to talk to HTTP/SMTP servers. | ||
| 237 | */ | ||
| 238 | *dst = '\r'; /* Enter -> CR LF */ | ||
| 239 | *++dst = '\n'; | ||
| 240 | } | ||
| 241 | #if 0 | ||
| 242 | /* putty's "special commands" mode does this: */ | ||
| 243 | /* Korenix 3005 switch needs at least the backspace tweak */ | ||
| 244 | if (c == 0x08 || c == 0x7f) { /* ctrl+h || backspace */ | ||
| 245 | *dst = IAC; | ||
| 246 | *++dst = EC; | ||
| 247 | } | ||
| 248 | if (c == 0x03) { /* ctrl+c */ | ||
| 249 | *dst = IAC; | ||
| 250 | *++dst = IP; | ||
| 251 | } | ||
| 252 | #endif | ||
| 253 | dst++; | ||
| 254 | } | ||
| 255 | if (dst - outbuf != 0) | ||
| 256 | full_write(netfd, outbuf, dst - outbuf); | ||
| 257 | } | ||
| 258 | |||
| 259 | static void handle_net_input(int len) | ||
| 260 | { | ||
| 261 | byte c; | ||
| 262 | int i; | ||
| 263 | int cstart = 0; | ||
| 264 | |||
| 265 | i = 0; | ||
| 266 | //bb_error_msg("[%u,'%.*s']", G.telstate, len, G.buf); | ||
| 267 | if (G.telstate == TS_NORMAL) { /* most typical state */ | ||
| 268 | while (i < len) { | ||
| 269 | c = G.buf[i]; | ||
| 270 | i++; | ||
| 271 | if (c == IAC) /* unlikely */ | ||
| 272 | goto got_IAC; | ||
| 273 | if (c != '\r') /* likely */ | ||
| 274 | continue; | ||
| 275 | G.telstate = TS_CR; | ||
| 276 | cstart = i; | ||
| 277 | goto got_special; | ||
| 278 | } | ||
| 279 | full_write(STDOUT_FILENO, G.buf, len); | ||
| 280 | return; | ||
| 281 | got_IAC: | ||
| 282 | G.telstate = TS_IAC; | ||
| 283 | cstart = i - 1; | ||
| 284 | got_special: ; | ||
| 285 | } | ||
| 286 | |||
| 287 | for (; i < len; i++) { | ||
| 288 | c = G.buf[i]; | ||
| 289 | |||
| 290 | switch (G.telstate) { | ||
| 291 | case TS_CR: | ||
| 292 | /* Prev char was CR. If cur one is NUL, ignore it. | ||
| 293 | * See RFC 1123 section 3.3.1 for discussion of telnet EOL handling. | ||
| 294 | */ | ||
| 295 | G.telstate = TS_COPY; | ||
| 296 | if (c == '\0') | ||
| 297 | break; | ||
| 298 | /* else: fall through - need to handle CR IAC ... properly */ | ||
| 299 | |||
| 300 | case TS_COPY: /* Prev char was ordinary */ | ||
| 301 | /* Similar to NORMAL, but in TS_COPY we need to copy bytes */ | ||
| 302 | if (c == IAC) | ||
| 303 | G.telstate = TS_IAC; | ||
| 304 | else { | ||
| 305 | G.buf[cstart++] = c; | ||
| 306 | if (c == '\r') | ||
| 307 | G.telstate = TS_CR; | ||
| 308 | } | ||
| 309 | break; | ||
| 310 | |||
| 311 | case TS_IAC: /* Prev char was IAC */ | ||
| 312 | switch (c) { | ||
| 313 | case IAC: /* IAC IAC -> one IAC */ | ||
| 314 | G.buf[cstart++] = c; | ||
| 315 | G.telstate = TS_COPY; | ||
| 316 | break; | ||
| 317 | case SB: | ||
| 318 | G.telstate = TS_SUB1; | ||
| 319 | break; | ||
| 320 | case DO: | ||
| 321 | case DONT: | ||
| 322 | case WILL: | ||
| 323 | case WONT: | ||
| 324 | G.telwish = c; | ||
| 325 | G.telstate = TS_OPT; | ||
| 326 | break; | ||
| 327 | /* DATA MARK must be added later */ | ||
| 328 | default: | ||
| 329 | G.telstate = TS_COPY; | ||
| 330 | } | ||
| 331 | break; | ||
| 332 | |||
| 333 | case TS_OPT: /* Prev chars were IAC WILL/WONT/DO/DONT */ | ||
| 334 | telopt(c); | ||
| 335 | G.telstate = TS_COPY; | ||
| 336 | break; | ||
| 337 | |||
| 338 | case TS_SUB1: /* Subnegotiation */ | ||
| 339 | case TS_SUB2: /* Subnegotiation */ | ||
| 340 | subneg(c); /* can change G.telstate */ | ||
| 341 | break; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | /* We had some IACs, or CR */ | ||
| 346 | iac_flush(); | ||
| 347 | if (G.telstate == TS_COPY) /* we aren't in the middle of IAC */ | ||
| 348 | G.telstate = TS_NORMAL; | ||
| 349 | if (cstart != 0) | ||
| 350 | full_write(STDOUT_FILENO, G.buf, cstart); | ||
| 351 | } | ||
| 352 | |||
| 353 | static void put_iac(int c) | ||
| 354 | { | ||
| 355 | int iaclen = G.iaclen; | ||
| 356 | if (iaclen >= IACBUFSIZE) { | ||
| 357 | iac_flush(); | ||
| 358 | iaclen = 0; | ||
| 359 | } | ||
| 360 | G.iacbuf[iaclen] = c; /* "... & 0xff" is implicit */ | ||
| 361 | G.iaclen = iaclen + 1; | ||
| 362 | } | ||
| 363 | |||
| 364 | static void put_iac2_msb_lsb(unsigned x_y) | ||
| 365 | { | ||
| 366 | put_iac(x_y >> 8); /* "... & 0xff" is implicit */ | ||
| 367 | put_iac(x_y); /* "... & 0xff" is implicit */ | ||
| 368 | } | ||
| 369 | #define put_iac2_x_y(x,y) put_iac2_msb_lsb(((x)<<8) + (y)) | ||
| 370 | |||
| 371 | #if ENABLE_FEATURE_TELNET_WIDTH \ | ||
| 372 | || ENABLE_FEATURE_TELNET_TTYPE \ | ||
| 373 | || ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 374 | static void put_iac4_msb_lsb(unsigned x_y_z_t) | ||
| 375 | { | ||
| 376 | put_iac2_msb_lsb(x_y_z_t >> 16); | ||
| 377 | put_iac2_msb_lsb(x_y_z_t); /* "... & 0xffff" is implicit */ | ||
| 378 | } | ||
| 379 | #define put_iac4_x_y_z_t(x,y,z,t) put_iac4_msb_lsb(((x)<<24) + ((y)<<16) + ((z)<<8) + (t)) | ||
| 380 | #endif | ||
| 381 | |||
| 382 | static void put_iac3_IAC_x_y_merged(unsigned wwdd_and_c) | ||
| 383 | { | ||
| 384 | put_iac(IAC); | ||
| 385 | put_iac2_msb_lsb(wwdd_and_c); | ||
| 386 | } | ||
| 387 | #define put_iac3_IAC_x_y(wwdd,c) put_iac3_IAC_x_y_merged(((wwdd)<<8) + (c)) | ||
| 388 | |||
| 389 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 390 | static void put_iac_subopt(byte c, char *str) | ||
| 391 | { | ||
| 392 | put_iac4_x_y_z_t(IAC, SB, c, 0); | ||
| 393 | |||
| 394 | while (*str) | ||
| 395 | put_iac(*str++); | ||
| 396 | |||
| 397 | put_iac2_x_y(IAC, SE); | ||
| 398 | } | ||
| 399 | #endif | ||
| 400 | |||
| 401 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 402 | static void put_iac_subopt_autologin(void) | ||
| 403 | { | ||
| 404 | const char *p; | ||
| 405 | |||
| 406 | put_iac4_x_y_z_t(IAC, SB, TELOPT_NEW_ENVIRON, TELQUAL_IS); | ||
| 407 | put_iac4_x_y_z_t(NEW_ENV_VAR, 'U', 'S', 'E'); /* "USER" */ | ||
| 408 | put_iac2_x_y('R', NEW_ENV_VALUE); | ||
| 409 | |||
| 410 | p = G.autologin; | ||
| 411 | while (*p) | ||
| 412 | put_iac(*p++); | ||
| 413 | |||
| 414 | put_iac2_x_y(IAC, SE); | ||
| 415 | } | ||
| 416 | #endif | ||
| 417 | |||
| 418 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 419 | static void put_iac_naws(byte c, int x, int y) | ||
| 420 | { | ||
| 421 | put_iac3_IAC_x_y(SB, c); | ||
| 422 | |||
| 423 | put_iac4_msb_lsb((x << 16) + y); | ||
| 424 | |||
| 425 | put_iac2_x_y(IAC, SE); | ||
| 426 | } | ||
| 427 | #endif | ||
| 428 | |||
| 429 | static void setConMode(void) | ||
| 430 | { | ||
| 431 | if (G.telflags & UF_ECHO) { | ||
| 432 | if (G.charmode == CHM_TRY) { | ||
| 433 | G.charmode = CHM_ON; | ||
| 434 | printf("\r\nEntering %s mode" | ||
| 435 | "\r\nEscape character is '^%c'.\r\n", "character", ']'); | ||
| 436 | rawmode(); | ||
| 437 | } | ||
| 438 | } else { | ||
| 439 | if (G.charmode != CHM_OFF) { | ||
| 440 | G.charmode = CHM_OFF; | ||
| 441 | printf("\r\nEntering %s mode" | ||
| 442 | "\r\nEscape character is '^%c'.\r\n", "line", 'C'); | ||
| 443 | cookmode(); | ||
| 444 | } | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | static void will_charmode(void) | ||
| 449 | { | ||
| 450 | G.charmode = CHM_TRY; | ||
| 451 | G.telflags |= (UF_ECHO | UF_SGA); | ||
| 452 | setConMode(); | ||
| 453 | |||
| 454 | put_iac3_IAC_x_y(DO, TELOPT_ECHO); | ||
| 455 | put_iac3_IAC_x_y(DO, TELOPT_SGA); | ||
| 456 | iac_flush(); | ||
| 457 | } | ||
| 458 | |||
| 459 | static void do_linemode(void) | ||
| 460 | { | ||
| 461 | G.charmode = CHM_TRY; | ||
| 462 | G.telflags &= ~(UF_ECHO | UF_SGA); | ||
| 463 | setConMode(); | ||
| 464 | |||
| 465 | put_iac3_IAC_x_y(DONT, TELOPT_ECHO); | ||
| 466 | put_iac3_IAC_x_y(DONT, TELOPT_SGA); | ||
| 467 | iac_flush(); | ||
| 468 | } | ||
| 469 | |||
| 470 | static void to_notsup(char c) | ||
| 471 | { | ||
| 472 | if (G.telwish == WILL) | ||
| 473 | put_iac3_IAC_x_y(DONT, c); | ||
| 474 | else if (G.telwish == DO) | ||
| 475 | put_iac3_IAC_x_y(WONT, c); | ||
| 476 | } | ||
| 477 | |||
| 478 | static void to_echo(void) | ||
| 479 | { | ||
| 480 | /* if server requests ECHO, don't agree */ | ||
| 481 | if (G.telwish == DO) { | ||
| 482 | put_iac3_IAC_x_y(WONT, TELOPT_ECHO); | ||
| 483 | return; | ||
| 484 | } | ||
| 485 | if (G.telwish == DONT) | ||
| 486 | return; | ||
| 487 | |||
| 488 | if (G.telflags & UF_ECHO) { | ||
| 489 | if (G.telwish == WILL) | ||
| 490 | return; | ||
| 491 | } else if (G.telwish == WONT) | ||
| 492 | return; | ||
| 493 | |||
| 494 | if (G.charmode != CHM_OFF) | ||
| 495 | G.telflags ^= UF_ECHO; | ||
| 496 | |||
| 497 | if (G.telflags & UF_ECHO) | ||
| 498 | put_iac3_IAC_x_y(DO, TELOPT_ECHO); | ||
| 499 | else | ||
| 500 | put_iac3_IAC_x_y(DONT, TELOPT_ECHO); | ||
| 501 | |||
| 502 | setConMode(); | ||
| 503 | full_write1_str("\r\n"); /* sudden modec */ | ||
| 504 | } | ||
| 505 | |||
| 506 | static void to_sga(void) | ||
| 507 | { | ||
| 508 | /* daemon always sends will/wont, client do/dont */ | ||
| 509 | |||
| 510 | if (G.telflags & UF_SGA) { | ||
| 511 | if (G.telwish == WILL) | ||
| 512 | return; | ||
| 513 | } else if (G.telwish == WONT) | ||
| 514 | return; | ||
| 515 | |||
| 516 | G.telflags ^= UF_SGA; /* toggle */ | ||
| 517 | if (G.telflags & UF_SGA) | ||
| 518 | put_iac3_IAC_x_y(DO, TELOPT_SGA); | ||
| 519 | else | ||
| 520 | put_iac3_IAC_x_y(DONT, TELOPT_SGA); | ||
| 521 | } | ||
| 522 | |||
| 523 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 524 | static void to_ttype(void) | ||
| 525 | { | ||
| 526 | /* Tell server we will (or won't) do TTYPE */ | ||
| 527 | if (G.ttype) | ||
| 528 | put_iac3_IAC_x_y(WILL, TELOPT_TTYPE); | ||
| 529 | else | ||
| 530 | put_iac3_IAC_x_y(WONT, TELOPT_TTYPE); | ||
| 531 | } | ||
| 532 | #endif | ||
| 533 | |||
| 534 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 535 | static void to_new_environ(void) | ||
| 536 | { | ||
| 537 | /* Tell server we will (or will not) do AUTOLOGIN */ | ||
| 538 | if (G.autologin) | ||
| 539 | put_iac3_IAC_x_y(WILL, TELOPT_NEW_ENVIRON); | ||
| 540 | else | ||
| 541 | put_iac3_IAC_x_y(WONT, TELOPT_NEW_ENVIRON); | ||
| 542 | } | ||
| 543 | #endif | ||
| 544 | |||
| 545 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 546 | static void to_naws(void) | ||
| 547 | { | ||
| 548 | /* Tell server we will do NAWS */ | ||
| 549 | put_iac3_IAC_x_y(WILL, TELOPT_NAWS); | ||
| 550 | } | ||
| 551 | #endif | ||
| 552 | |||
| 553 | static void telopt(byte c) | ||
| 554 | { | ||
| 555 | switch (c) { | ||
| 556 | case TELOPT_ECHO: | ||
| 557 | to_echo(); break; | ||
| 558 | case TELOPT_SGA: | ||
| 559 | to_sga(); break; | ||
| 560 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 561 | case TELOPT_TTYPE: | ||
| 562 | to_ttype(); break; | ||
| 563 | #endif | ||
| 564 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 565 | case TELOPT_NEW_ENVIRON: | ||
| 566 | to_new_environ(); break; | ||
| 567 | #endif | ||
| 568 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 569 | case TELOPT_NAWS: | ||
| 570 | to_naws(); | ||
| 571 | put_iac_naws(c, G.win_width, G.win_height); | ||
| 572 | break; | ||
| 573 | #endif | ||
| 574 | default: | ||
| 575 | to_notsup(c); | ||
| 576 | break; | ||
| 577 | } | ||
| 578 | } | ||
| 579 | |||
| 580 | /* subnegotiation -- ignore all (except TTYPE,NAWS) */ | ||
| 581 | static void subneg(byte c) | ||
| 582 | { | ||
| 583 | switch (G.telstate) { | ||
| 584 | case TS_SUB1: | ||
| 585 | if (c == IAC) | ||
| 586 | G.telstate = TS_SUB2; | ||
| 587 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 588 | else | ||
| 589 | if (c == TELOPT_TTYPE && G.ttype) | ||
| 590 | put_iac_subopt(TELOPT_TTYPE, G.ttype); | ||
| 591 | #endif | ||
| 592 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 593 | else | ||
| 594 | if (c == TELOPT_NEW_ENVIRON && G.autologin) | ||
| 595 | put_iac_subopt_autologin(); | ||
| 596 | #endif | ||
| 597 | break; | ||
| 598 | case TS_SUB2: | ||
| 599 | if (c == SE) { | ||
| 600 | G.telstate = TS_COPY; | ||
| 601 | return; | ||
| 602 | } | ||
| 603 | G.telstate = TS_SUB1; | ||
| 604 | break; | ||
| 605 | } | ||
| 606 | } | ||
| 607 | |||
| 608 | static void rawmode(void) | ||
| 609 | { | ||
| 610 | if (G.do_termios) | ||
| 611 | tcsetattr(0, TCSADRAIN, &G.termios_raw); | ||
| 612 | } | ||
| 613 | |||
| 614 | static void cookmode(void) | ||
| 615 | { | ||
| 616 | if (G.do_termios) | ||
| 617 | tcsetattr(0, TCSADRAIN, &G.termios_def); | ||
| 618 | } | ||
| 619 | |||
| 620 | int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
| 621 | int telnet_main(int argc UNUSED_PARAM, char **argv) | ||
| 622 | { | ||
| 623 | char *host; | ||
| 624 | int port; | ||
| 625 | int len; | ||
| 626 | struct pollfd ufds[2]; | ||
| 627 | |||
| 628 | INIT_G(); | ||
| 629 | |||
| 630 | #if ENABLE_FEATURE_TELNET_TTYPE | ||
| 631 | G.ttype = getenv("TERM"); | ||
| 632 | #endif | ||
| 633 | |||
| 634 | if (tcgetattr(0, &G.termios_def) >= 0) { | ||
| 635 | G.do_termios = 1; | ||
| 636 | G.termios_raw = G.termios_def; | ||
| 637 | cfmakeraw(&G.termios_raw); | ||
| 638 | } | ||
| 639 | |||
| 640 | #if ENABLE_FEATURE_TELNET_AUTOLOGIN | ||
| 641 | if (1 == getopt32(argv, "al:", &G.autologin)) { | ||
| 642 | /* Only -a without -l USER picks $USER from envvar */ | ||
| 643 | G.autologin = getenv("USER"); | ||
| 644 | } | ||
| 645 | argv += optind; | ||
| 646 | #else | ||
| 647 | argv++; | ||
| 648 | #endif | ||
| 649 | if (!*argv) | ||
| 650 | bb_show_usage(); | ||
| 651 | host = *argv++; | ||
| 652 | port = *argv ? bb_lookup_port(*argv++, "tcp", 23) | ||
| 653 | : bb_lookup_std_port("telnet", "tcp", 23); | ||
| 654 | if (*argv) /* extra params?? */ | ||
| 655 | bb_show_usage(); | ||
| 656 | |||
| 657 | xmove_fd(create_and_connect_stream_or_die(host, port), netfd); | ||
| 658 | printf("Connected to %s\n", host); | ||
| 659 | |||
| 660 | setsockopt_keepalive(netfd); | ||
| 661 | |||
| 662 | #if ENABLE_FEATURE_TELNET_WIDTH | ||
| 663 | get_terminal_width_height(0, &G.win_width, &G.win_height); | ||
| 664 | //TODO: support dynamic resize? | ||
| 665 | #endif | ||
| 666 | |||
| 667 | signal(SIGINT, record_signo); | ||
| 668 | |||
| 669 | ufds[0].fd = STDIN_FILENO; | ||
| 670 | ufds[0].events = POLLIN; | ||
| 671 | ufds[1].fd = netfd; | ||
| 672 | ufds[1].events = POLLIN; | ||
| 673 | |||
| 674 | while (1) { | ||
| 675 | if (poll(ufds, 2, -1) < 0) { | ||
| 676 | /* error, ignore and/or log something, bay go to loop */ | ||
| 677 | if (bb_got_signal) | ||
| 678 | con_escape(); | ||
| 679 | else | ||
| 680 | sleep1(); | ||
| 681 | continue; | ||
| 682 | } | ||
| 683 | |||
| 684 | // FIXME: reads can block. Need full bidirectional buffering. | ||
| 685 | |||
| 686 | if (ufds[0].revents) { | ||
| 687 | len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); | ||
| 688 | if (len <= 0) | ||
| 689 | doexit(EXIT_SUCCESS); | ||
| 690 | handle_net_output(len); | ||
| 691 | } | ||
| 692 | |||
| 693 | if (ufds[1].revents) { | ||
| 694 | len = safe_read(netfd, G.buf, DATABUFSIZE); | ||
| 695 | if (len <= 0) { | ||
| 696 | full_write1_str("Connection closed by foreign host\r\n"); | ||
| 697 | doexit(EXIT_FAILURE); | ||
| 698 | } | ||
| 699 | handle_net_input(len); | ||
| 700 | } | ||
| 701 | } /* while (1) */ | ||
| 702 | } | ||
diff --git a/networking/telnet.txt b/networking/telnet.txt new file mode 100644 index 000000000..87b1d1aa6 --- /dev/null +++ b/networking/telnet.txt | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // 2-byte sequences: | ||
| 2 | // IAC SE (240) End of subnegotiation parameters | ||
| 3 | // IAC NOP (241) No operation | ||
| 4 | // IAC DM (242) Data Mark (used with TCP urgent pointer) | ||
| 5 | // IAC BRK (243) Break (simulate serial line break) - perhaps tcsendbreak(master_ptyfd)? [untested] | ||
| 6 | // IAC IP (244) Interrupt Process ("send SIGINT to foreground process group a-la ^C") | ||
| 7 | // IAC AO (245) Abort Output | ||
| 8 | // IAC AYT (246) Are You There | ||
| 9 | // IAC EC (247) Erase Character | ||
| 10 | // IAC EL (248) Erase Line | ||
| 11 | // IAC GA (249) Go Ahead | ||
| 12 | // IAC IAC (255) I'm sending literal byte 255 | ||
| 13 | // 3-byte sequences: | ||
| 14 | // IAC WILL <option> (251) sender declares willingness to enable option | ||
| 15 | // IAC WONT <option> (252) sender refuses / disables option | ||
| 16 | // IAC DO <option> (253) sender requests peer to send option, or enable option on peer's side | ||
| 17 | // IAC DONT <option> (254) sender requests peer to NOT send option, or disable option on peer's side | ||
| 18 | // Example: | ||
| 19 | // IAC DO 31 (request NAWS) | ||
| 20 | // IAC WILL 24 (offer TERMINAL-TYPE) | ||
| 21 | // | ||
| 22 | // Variable length: subnegotiation (250) | ||
| 23 | // IAC SB <option> <data...> IAC SE | ||
| 24 | // option = single byte | ||
| 25 | // data = zero or more bytes | ||
| 26 | // Subnegotiation can not be nested, and can not have 2- and 3-byte commands inside. | ||
| 27 | // Inside subnegotiation data, IAC IAC represents literal 255. | ||
| 28 | // An IAC byte inside SB: | ||
| 29 | // IAC IAC: literal byte 255 | ||
| 30 | // IAC SE: end of subnegotiation | ||
| 31 | // IAC <anything else>: protocol error | ||
| 32 | // Common options that use subnegotiation: | ||
| 33 | // TERMINAL-TYPE (24) | ||
| 34 | // IAC SB 24 SEND(1) IAC SE | ||
| 35 | // IAC SB 24 IS(0) "xterm-256color" IAC SE | ||
| 36 | // NAWS - Negotiate About Window Size (31) | ||
| 37 | // IAC SB 31 <width_hi> <width_lo> <height_hi> <height_lo> IAC SE | ||
| 38 | // width and height are 16-bit big endian. HOWTO: | ||
| 39 | // Server: IAC DO NAWS | ||
| 40 | // Client: IAC WILL NAWS | ||
| 41 | // Client: IAC SB NAWS <w> <h> IAC SE | ||
| 42 | // ...sometime later: client's window is resized by user... | ||
| 43 | // Client: IAC SB NAWS <w> <h> IAC SE | ||
| 44 | // TERMINAL-SPEED (32) | ||
| 45 | // IAC SB 32 IS(0) "rx,tx" IAC SE | ||
| 46 | // REMOTE-FLOW-CONTROL (33) | ||
| 47 | // LINEMODE (34) | ||
| 48 | // X-DISPLAY-LOCATION (35) | ||
| 49 | // ENVIRON (36) | ||
| 50 | // AUTHENTICATION (37) | ||
| 51 | // ENCRYPT (38) | ||
| 52 | // NEW-ENVIRON (39) | ||
| 53 | // COM-PORT-OPTION (44) | ||
diff --git a/networking/telnetd.c b/networking/telnetd.c index a5a783047..3eb9446a2 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c | |||
| @@ -61,6 +61,14 @@ | |||
| 61 | //config: | 61 | //config: |
| 62 | //config: with all that done, telnetd _should_ work.... | 62 | //config: with all that done, telnetd _should_ work.... |
| 63 | //config: | 63 | //config: |
| 64 | //config:config FEATURE_TELNETD_SELFTEST_DEBUG | ||
| 65 | //config: bool "Include self-test (telnetd -@)" | ||
| 66 | //config: default n | ||
| 67 | //config: depends on TELNETD | ||
| 68 | //config: help | ||
| 69 | //config: Include self-test code for pty/vhangup() behavior. | ||
| 70 | //config: Useful for development and validation on new platforms. | ||
| 71 | //config: | ||
| 64 | //config:config FEATURE_TELNETD_STANDALONE | 72 | //config:config FEATURE_TELNETD_STANDALONE |
| 65 | //config: bool "Support standalone telnetd (not inetd only)" | 73 | //config: bool "Support standalone telnetd (not inetd only)" |
| 66 | //config: default y | 74 | //config: default y |
| @@ -119,49 +127,54 @@ | |||
| 119 | //usage: "\n -S Log to syslog (implied by -i or without -F and -w)" | 127 | //usage: "\n -S Log to syslog (implied by -i or without -F and -w)" |
| 120 | //usage: ) | 128 | //usage: ) |
| 121 | //usage: ) | 129 | //usage: ) |
| 122 | 130 | //usage: "\n -v Verbose" | |
| 123 | #define DEBUG 0 | ||
| 124 | 131 | ||
| 125 | #include "libbb.h" | 132 | #include "libbb.h" |
| 126 | #include "common_bufsiz.h" | 133 | #include "common_bufsiz.h" |
| 127 | #include <syslog.h> | 134 | #include <syslog.h> |
| 135 | #include <arpa/telnet.h> | ||
| 136 | |||
| 137 | #define DEBUG 0 | ||
| 138 | #define DEBUG_CLOSING 0 | ||
| 128 | 139 | ||
| 129 | #if DEBUG | 140 | #if DEBUG |
| 130 | # define TELCMDS | 141 | # define dbg(...) bb_error_msg(__VA_ARGS__) |
| 131 | # define TELOPTS | 142 | #else |
| 143 | # define dbg(...) ((void)0) | ||
| 144 | #endif | ||
| 145 | #if DEBUG_CLOSING | ||
| 146 | # define dbg_close(...) bb_error_msg(__VA_ARGS__) | ||
| 147 | #else | ||
| 148 | # define dbg_close(...) ((void)0) | ||
| 132 | #endif | 149 | #endif |
| 133 | #include <arpa/telnet.h> | ||
| 134 | 150 | ||
| 151 | #define SELFTEST_VHANGUP 1 | ||
| 152 | #define SELFTEST_NET2PTY 1 | ||
| 153 | #define SELFTEST_PTY2NET 1 | ||
| 135 | 154 | ||
| 136 | struct tsession { | 155 | #define SAVE_NET2PTY_EXPECTED 0 |
| 137 | struct tsession *next; | 156 | #define SAVE_NET2PTY_ACTUAL 0 |
| 138 | pid_t shell_pid; | ||
| 139 | int sockfd_read; | ||
| 140 | int sockfd_write; | ||
| 141 | int ptyfd; | ||
| 142 | smallint buffered_IAC_for_pty; | ||
| 143 | |||
| 144 | /* two circular buffers */ | ||
| 145 | /*char *buf1, *buf2;*/ | ||
| 146 | /*#define TS_BUF1(ts) ts->buf1*/ | ||
| 147 | /*#define TS_BUF2(ts) TS_BUF2(ts)*/ | ||
| 148 | #define TS_BUF1(ts) ((unsigned char*)(ts + 1)) | ||
| 149 | #define TS_BUF2(ts) (((unsigned char*)(ts + 1)) + BUFSIZE) | ||
| 150 | int rdidx1, wridx1, size1; | ||
| 151 | int rdidx2, wridx2, size2; | ||
| 152 | }; | ||
| 153 | 157 | ||
| 154 | /* Two buffers are directly after tsession in malloced memory. | 158 | #define SAVE_PTY2NET_EXPECTED 0 |
| 155 | * Make whole thing fit in 4k */ | 159 | #define SAVE_PTY2NET_ACTUAL 0 |
| 156 | enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 }; | ||
| 157 | 160 | ||
| 161 | /* Must match getopt32 string */ | ||
| 162 | enum { | ||
| 163 | OPT_WATCHCHILD = (1 << 2), /* -K */ | ||
| 164 | OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */ | ||
| 165 | OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p PORT */ | ||
| 166 | OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */ | ||
| 167 | OPT_SYSLOG = (1 << 7) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -S */ | ||
| 168 | OPT_WAIT = (1 << 8) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -w SEC */ | ||
| 169 | }; | ||
| 158 | 170 | ||
| 159 | /* Globals */ | 171 | /* Globals */ |
| 160 | struct globals { | 172 | struct globals { |
| 161 | struct tsession *sessions; | 173 | ioloop_state_t io; |
| 162 | const char *loginpath; | 174 | const char *loginpath; |
| 163 | const char *issuefile; | 175 | const char *issuefile; |
| 164 | int maxfd; | 176 | unsigned verbose; |
| 177 | IF_FEATURE_TELNETD_INETD_WAIT(unsigned sec_linger;) | ||
| 165 | } FIX_ALIASING; | 178 | } FIX_ALIASING; |
| 166 | #define G (*(struct globals*)bb_common_bufsiz1) | 179 | #define G (*(struct globals*)bb_common_bufsiz1) |
| 167 | #define INIT_G() do { \ | 180 | #define INIT_G() do { \ |
| @@ -170,303 +183,688 @@ struct globals { | |||
| 170 | G.issuefile = "/etc/issue.net"; \ | 183 | G.issuefile = "/etc/issue.net"; \ |
| 171 | } while (0) | 184 | } while (0) |
| 172 | 185 | ||
| 186 | #define log1(...) do { if (G.verbose) bb_error_msg(__VA_ARGS__); } while (0) | ||
| 187 | |||
| 188 | static NOINLINE void fabricate_ctrl_D_on_pty(int fd) | ||
| 189 | { | ||
| 190 | struct termios tio; | ||
| 191 | // Master pty can see slave's termios - abuse that | ||
| 192 | if (tcgetattr(fd, &tio) != 0) { | ||
| 193 | dbg_close("%s: no termios(%d)", __func__, fd); | ||
| 194 | return; | ||
| 195 | } | ||
| 196 | dbg_close("%s: termios(%d) ICANON:%d,VEOF:%02x", __func__, | ||
| 197 | fd, !!(tio.c_lflag & ICANON), (uint8_t)tio.c_cc[VEOF]); | ||
| 198 | if ((tio.c_lflag & ICANON) && tio.c_cc[VEOF] != 0) { | ||
| 199 | // _Should_ look like EOF on input on the slave side... | ||
| 200 | write(fd, &tio.c_cc[VEOF], 1); // usually ^D | ||
| 201 | // One ^D often won't do. If a program was blocked on read already, | ||
| 202 | // ^D makes _that_ read return: | ||
| 203 | // read(0, "some data", 64) = 9 | ||
| 204 | // but the program will likely read more | ||
| 205 | // (it did not interpret above as EOF), block, and die: | ||
| 206 | // read(0, 0x123456, 64) = ...<blocked>... -1 EIO | ||
| 207 | // +++ killed by SIGHUP +++ | ||
| 208 | // Well, I'm not greedy... | ||
| 209 | write(fd, &tio.c_cc[VEOF], 1); // here is another EOF for you | ||
| 210 | // If this doesn't work, they can see 0x04 bytes. Tough cookies | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | #define TELNET_CONNECTION \ | ||
| 215 | STRUCT_CONNECTION \ | ||
| 216 | pid_t shell_pid; | ||
| 173 | 217 | ||
| 174 | /* Write some buf1 data to pty, processing IACs. | 218 | typedef struct telnet_conn { |
| 175 | * Update wridx1 and size1. Return < 0 on error. | 219 | TELNET_CONNECTION |
| 176 | * Buggy if IAC is present but incomplete: skips them. | 220 | } telnet_conn_t; |
| 221 | |||
| 222 | struct net_to_pty; | ||
| 223 | |||
| 224 | typedef struct pty_to_net { | ||
| 225 | TELNET_CONNECTION | ||
| 226 | struct net_to_pty *sibling; /* must be directly after TELNET_CONNECTION */ | ||
| 227 | int rdidx, wridx, size; | ||
| 228 | unsigned deadline_us; | ||
| 229 | unsigned eio_count; | ||
| 230 | //TODO: these two can be unified into one: | ||
| 231 | smallint respond_to_AYT; | ||
| 232 | char extra_byte_to_write; | ||
| 233 | /* circular buffer follows */ | ||
| 234 | #define BUF2NET(ts) ((unsigned char*)(ts + 1)) | ||
| 235 | } pty_to_net_t ALIGN8; | ||
| 236 | /* The buffer is directly after malloced memory, aligned to 64 bits */ | ||
| 237 | enum { TO_NET_BUFSIZE = 2 * 1024 }; | ||
| 238 | enum { TO_NET_BUFMASK = TO_NET_BUFSIZE - 1 }; | ||
| 239 | #define BUF2NET_INC(n, inc) ((n) = ((n) + (inc)) & TO_NET_BUFMASK) | ||
| 240 | |||
| 241 | typedef struct net_to_pty { | ||
| 242 | TELNET_CONNECTION | ||
| 243 | pty_to_net_t *sibling; /* must be directly after TELNET_CONNECTION */ | ||
| 244 | int rdidx, wridx, size; | ||
| 245 | unsigned deadline_us; | ||
| 246 | smallint skip_LF_or_NUL; | ||
| 247 | /* circular buffer follows */ | ||
| 248 | #define BUF2PTY(ts) ((unsigned char*)(ts + 1)) | ||
| 249 | } net_to_pty_t ALIGN8; | ||
| 250 | /* The buffer is directly after malloced memory, aligned to 64 bits */ | ||
| 251 | enum { TO_PTY_BUFSIZE = 2 * 1024 }; | ||
| 252 | enum { TO_PTY_BUFMASK = TO_PTY_BUFSIZE - 1 }; | ||
| 253 | #define BUF2PTY_INC(n, inc) ((n) = ((n) + (inc)) & TO_PTY_BUFMASK) | ||
| 254 | #define BUF2PTY_DEC(n, dec) ((n) = ((n) - (dec)) & TO_PTY_BUFMASK) | ||
| 255 | |||
| 256 | /* The mutual pipes are linked by to_pty, to_net. | ||
| 257 | * The rules: | ||
| 258 | * = always test for !NULL before use. | ||
| 259 | * = if you are freeing one of the structs, set | ||
| 260 | * "me->sibling->sibling = NULL". | ||
| 261 | * Use one of these helpers: | ||
| 177 | */ | 262 | */ |
| 178 | static ssize_t | 263 | static void remove_and_free_to_pty(net_to_pty_t *ts) |
| 179 | safe_write_to_pty_decode_iac(struct tsession *ts) | 264 | { |
| 265 | dbg_close("remove_and_free:%p rd:%d wr:%d sibling:%p", | ||
| 266 | ts, ts->read_fd, ts->write_fd, ts->sibling); | ||
| 267 | if (ts->sibling) { | ||
| 268 | /* "you have no sibling now" */ | ||
| 269 | ts->sibling->sibling = NULL; | ||
| 270 | } | ||
| 271 | conn_close_fds_remove_and_free((void*)ts); | ||
| 272 | } | ||
| 273 | static void ALWAYS_INLINE remove_and_free_to_net(pty_to_net_t *ts) | ||
| 180 | { | 274 | { |
| 275 | /* Works because ->sibling have the same offset */ | ||
| 276 | remove_and_free_to_pty((void*)ts); | ||
| 277 | } | ||
| 278 | |||
| 279 | // Theory of operation | ||
| 280 | // (AKA "when should I close fds? when should I detach from ioloop?"). | ||
| 281 | // The fds are named read_fd and write_fd, but for clarity let's call them netfd and ptyfd. | ||
| 282 | // net_to_pty::have_data_to_write | ||
| 283 | // net_to_pty::have_buffer_to_read_into | ||
| 284 | // if ptyfd < 0: //sibling told us ptyfd is down? | ||
| 285 | // if sibling && sibling->netfd >= 0: netfd = -1; //do not close netfd, sibling uses it (if sibling exists)! | ||
| 286 | // close_and_detach; | ||
| 287 | // if netfd < 0: return 0; | ||
| 288 | // net_to_pty::write | ||
| 289 | // if ptyfd write detects error (not EAGAIN): | ||
| 290 | // //if (sibling): | ||
| 291 | // // sibling->ptyfd = -1; // let sibling know ptyfd is closed | ||
| 292 | // // if sibling->netfd >= 0: netfd = -1; // do not close netfd, sibling uses it! | ||
| 293 | // //close_and_detach; | ||
| 294 | // let pty_to_net detect this (ignore the error) | ||
| 295 | // net_to_pty::read | ||
| 296 | // if netfd read detects EOF or error (there is no way to tell pty the difference): | ||
| 297 | // if no sibling: close(netfd); | ||
| 298 | // netfd = -1; //no longer try to read | ||
| 299 | // return 0; //but do not detach yet | ||
| 300 | |||
| 301 | static unsigned char read_byte_unescaping_IAC(int *iac_cnt, unsigned char **pp) | ||
| 302 | { | ||
| 303 | unsigned char c = *(*pp)++; | ||
| 304 | if (c == IAC && **pp == IAC) { | ||
| 305 | (*iac_cnt)++; | ||
| 306 | (*pp)++; /* Skip the second IAC in IAC IAC sequence */ | ||
| 307 | } | ||
| 308 | return c; | ||
| 309 | } | ||
| 310 | |||
| 311 | static int net_to_pty__have_data_to_write(void *this) | ||
| 312 | { | ||
| 313 | //connection_t *conn = this; | ||
| 314 | net_to_pty_t *ts = this; | ||
| 315 | unsigned char *buf; | ||
| 316 | int size, increment; | ||
| 317 | |||
| 318 | if (ts->write_fd < 0) /* did slave close its pty? */ | ||
| 319 | return 0; | ||
| 320 | again: | ||
| 321 | size = ts->size; | ||
| 322 | if (size == 0) | ||
| 323 | return 0; | ||
| 324 | |||
| 325 | buf = BUF2PTY(ts) + ts->wridx; | ||
| 326 | if (ts->skip_LF_or_NUL) { | ||
| 327 | /* last char we wrote was '\r' */ | ||
| 328 | ts->skip_LF_or_NUL = 0; | ||
| 329 | /* we have the next one: examine */ | ||
| 330 | if (buf[0] == '\n' || buf[0] == '\0') { | ||
| 331 | increment = 1; /* drop it now */ | ||
| 332 | inc: | ||
| 333 | BUF2PTY_INC(ts->wridx, increment); | ||
| 334 | ts->size -= increment; | ||
| 335 | goto again; | ||
| 336 | } | ||
| 337 | /* else: not '\r\n' or '\rNUL' */ | ||
| 338 | } | ||
| 339 | if (buf[0] != IAC) /* very likely */ | ||
| 340 | return size; | ||
| 341 | if (size == 1) { /* we only have one char: IAC */ | ||
| 342 | cant_write: | ||
| 343 | if (ts->read_fd < 0) | ||
| 344 | ts->size = 0; /* EOF was seen: ignore incomplete IAC, prevent infinite loop */ | ||
| 345 | //bb_error_msg("cant_write"); | ||
| 346 | return 0; /* "can't write" */ | ||
| 347 | } | ||
| 348 | |||
| 349 | /* size is >= 2 and buf[0] is IAC */ | ||
| 350 | //dbg_iac("size:%d %02x %02x", size, buf[0], buf[1]); | ||
| 351 | |||
| 352 | /* Cannot process IACs which wrap around buffer's end. | ||
| 353 | * Worst case: IAC SB NAWS with all bytes IAC-escaped = 13 bytes */ | ||
| 354 | while (ts->wridx > TO_PTY_BUFSIZE - 14) { | ||
| 355 | uint64_t v64; | ||
| 356 | if (ts->wridx < ts->rdidx) { | ||
| 357 | /* |.......WRxRD.| */ | ||
| 358 | /* Buffer is not wrapped */ | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | /* Possible situations: */ | ||
| 362 | /* |xRD.......WRx| wrapped */ | ||
| 363 | /* |xxxxxxxxRDWRx| wrapped and full! rdidx = wridx */ | ||
| 364 | /* Rotate entire buffer 8 bytes back */ | ||
| 365 | v64 = *(uint64_t*)BUF2PTY(ts); | ||
| 366 | memmove(BUF2PTY(ts), BUF2PTY(ts) + 8, TO_PTY_BUFSIZE - 8); | ||
| 367 | *(uint64_t*)(BUF2PTY(ts) + TO_PTY_BUFSIZE - 8) = v64; | ||
| 368 | ts->wridx -= 8; /* can't underflow */ | ||
| 369 | buf -= 8; | ||
| 370 | BUF2PTY_DEC(ts->rdidx, 8); /* can underflow, use DEC() */ | ||
| 371 | } | ||
| 372 | |||
| 373 | if (buf[1] == IAC) /* IAC-IAC: we have something to write */ | ||
| 374 | return size; | ||
| 375 | |||
| 376 | if (buf[1] >= 240 && buf[1] <= 249) { | ||
| 377 | /* 2-byte commands (240..250 and 255): | ||
| 378 | * IAC IAC (255) Literal 255. | ||
| 379 | * IAC SE (240) End of subnegotiation. Treated as NOP. | ||
| 380 | * IAC NOP (241) NOP. | ||
| 381 | * IAC BRK (243) Break. Like serial line break. TODO via tcsendbreak()? | ||
| 382 | * IAC AYT (246) Are you there. | ||
| 383 | * These don't look useful: | ||
| 384 | * IAC DM (242) Data mark. What is this? | ||
| 385 | * IAC IP (244) Suspend, interrupt or abort the process. (Ancient cousin of ^C). | ||
| 386 | * IAC AO (245) Abort output. "You can continue running, but do not send me the output". | ||
| 387 | * IAC EC (247) Erase character. The receiver should delete the last received char. | ||
| 388 | * IAC EL (248) Erase line. The receiver should delete everything up to last newline. | ||
| 389 | * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". | ||
| 390 | */ | ||
| 391 | if (buf[1] == AYT && ts->sibling) /* notify other pipe that AYT was seen */ | ||
| 392 | ts->sibling->respond_to_AYT = 1; | ||
| 393 | /* NOP (241). Ignore (putty keepalive, etc) */ | ||
| 394 | /* All other 2-byte commands also treated as NOPs here */ | ||
| 395 | increment = 2; | ||
| 396 | goto inc; | ||
| 397 | } | ||
| 398 | if (size <= 2) { | ||
| 399 | /* only 2 bytes of the longer IAC is in the buffer */ | ||
| 400 | goto cant_write; /* incomplete, can't process */ | ||
| 401 | } | ||
| 402 | if (buf[1] == SB) { | ||
| 403 | if (buf[2] == TELOPT_NAWS) { | ||
| 404 | // IAC,SB,TELOPT_NAWS,<4 bytes possibly IAC-escaped>,IAC,SE | ||
| 405 | struct { | ||
| 406 | struct winsize ws; | ||
| 407 | int iac_cnt; | ||
| 408 | } s; | ||
| 409 | unsigned char *p; | ||
| 410 | unsigned char byte45, byte67, byte89; | ||
| 411 | |||
| 412 | // The usual: IAC,SB,TELOPT_NAWS,w,w,h,h (IAC,SE later): 7 + 2 = 9 bytes | ||
| 413 | // 255*HH: IAC,SB,TELOPT_NAWS,0,255,255,h,h: 8 bytes (10 with IAC,SE) | ||
| 414 | // The worst: IAC,SB,TELOPT_NAWS,w,w,w,w,h,h,h,h (four IACs): 11 bytes (not counting IAC,SE) | ||
| 415 | // We can't simply check for size < 11: | ||
| 416 | // will mishandle IAC,SB,TELOPT_NAWS,w,w,h,h,IAC,SE,'A' (10 bytes) | ||
| 417 | // the write of 'A' (ordinary visible char) can be delayed! | ||
| 418 | // Also mishandles 255*HH case (10 bytes) by delaying its processing | ||
| 419 | // until at least one more byte after it is received and size becomes >= 11. | ||
| 420 | if (size < 9) // postpone parsing until have 9+ bytes | ||
| 421 | goto cant_write; | ||
| 422 | memset(&s, 0, sizeof(s)); // pixel sizes are set to 0 | ||
| 423 | //s.iac_cnt = 0; //done | ||
| 424 | p = buf + 3; | ||
| 425 | byte45 = read_byte_unescaping_IAC(&s.iac_cnt, &p); | ||
| 426 | byte67 = read_byte_unescaping_IAC(&s.iac_cnt, &p); | ||
| 427 | byte89 = read_byte_unescaping_IAC(&s.iac_cnt, &p); // fetches at most byte#9 - allowed by size | ||
| 428 | // If any one of these is IAC, the NAWS seq must be at least 10 bytes. | ||
| 429 | // IOW: it can't be case 'A' above. *Can* postpone if size == 10! | ||
| 430 | if (size < 9 + s.iac_cnt) // if IAC was seen: postpone parsing until have 10+ bytes | ||
| 431 | goto cant_write; // if 2+ IACs seen: postpone parsing until have 11+ bytes | ||
| 432 | s.ws.ws_col = (byte45 << 8) | byte67; | ||
| 433 | s.ws.ws_row = (byte89 << 8) | read_byte_unescaping_IAC(&s.iac_cnt, &p); // fetches _at most_ byte#11 (if four IACs) | ||
| 434 | |||
| 435 | if (s.ws.ws_col != 0 && s.ws.ws_row != 0) // don't provoke bugs elsewhere with "zero-sized screen" | ||
| 436 | ioctl(ts->write_fd, TIOCSWINSZ, (char *)&s.ws); | ||
| 437 | log1("pfd:%d window size:%dx%d", ts->write_fd, s.ws.ws_row, s.ws.ws_col); | ||
| 438 | increment = p - buf; | ||
| 439 | goto inc; | ||
| 440 | // trailing IAC,SE will be eaten separately, as 2-byte NOP | ||
| 441 | } | ||
| 442 | //fixme: skip them correctly | ||
| 443 | /* else: other subnegs not supported yet */ | ||
| 444 | } | ||
| 445 | |||
| 446 | /* Assume it is a 3-byte WILL/WONT/DO/DONT 251..254 command and skip it */ | ||
| 447 | dbg("ignoring IAC,%02x,%02x", buf[1], buf[2]); | ||
| 448 | increment = 3; | ||
| 449 | goto inc; | ||
| 450 | } | ||
| 451 | |||
| 452 | /* Write some buf data to pty, processing IACs. | ||
| 453 | * Update wridx and size. Return < 0 on error. | ||
| 454 | */ | ||
| 455 | static int net_to_pty__write(void *this) | ||
| 456 | { | ||
| 457 | //connection_t *conn = this; | ||
| 458 | net_to_pty_t *ts = this; | ||
| 181 | unsigned wr; | 459 | unsigned wr; |
| 182 | ssize_t rc; | 460 | ssize_t rc; |
| 183 | unsigned char *buf; | 461 | unsigned char *buf; |
| 184 | unsigned char *found; | 462 | unsigned char *found; |
| 185 | 463 | ||
| 186 | buf = TS_BUF1(ts) + ts->wridx1; | 464 | if (ts->write_fd < 0) /* did slave close its pty? */ |
| 187 | wr = MIN(BUFSIZE - ts->wridx1, ts->size1); | 465 | return 0; |
| 188 | /* wr is at least 1 here */ | ||
| 189 | 466 | ||
| 190 | if (ts->buffered_IAC_for_pty) { | 467 | buf = BUF2PTY(ts) + ts->wridx; |
| 191 | /* Last time we stopped on a "dangling" IAC byte. | 468 | wr = MIN(TO_PTY_BUFSIZE - ts->wridx, ts->size); |
| 192 | * We removed it from the buffer back then. | 469 | /* wr is at least 1 here */ |
| 193 | * Now pretend it's still there, and jump to IAC processing. | ||
| 194 | */ | ||
| 195 | ts->buffered_IAC_for_pty = 0; | ||
| 196 | wr++; | ||
| 197 | ts->size1++; | ||
| 198 | buf--; /* Yes, this can point before the buffer. It's ok */ | ||
| 199 | ts->wridx1--; | ||
| 200 | goto handle_iac; | ||
| 201 | } | ||
| 202 | 470 | ||
| 203 | found = memchr(buf, IAC, wr); | 471 | found = memchr(buf, IAC, wr); |
| 204 | if (found != buf) { | 472 | if (found == buf) { |
| 205 | /* There is a "prefix" of non-IAC chars. | 473 | /* The first char is IAC. |
| 206 | * Write only them, and return. | 474 | * have_data_to_write() ensures we are only called this way |
| 475 | * if there are two IACs. | ||
| 476 | * It also ensures the buffer is not wrapping within 7 chars. | ||
| 477 | * Write one IAC. If that works, skip both. | ||
| 207 | */ | 478 | */ |
| 208 | if (found) | 479 | //TODO: advance ptr by one, and memchr up to _next_ IAC (if any): |
| 209 | wr = found - buf; | 480 | //this would write more data |
| 210 | 481 | rc = safe_write(ts->write_fd, buf, 1); | |
| 211 | /* We map \r\n ==> \r for pragmatic reasons: | 482 | if (rc > 0) { |
| 212 | * many client implementations send \r\n when | 483 | //dbg_iac("wrote: IAC"); |
| 213 | * the user hits the CarriageReturn key. | 484 | ts->wridx += 2; /* this can't overflow */ |
| 214 | * See RFC 1123 3.3.1 Telnet End-of-Line Convention. | 485 | ts->size -= 2; |
| 215 | */ | ||
| 216 | rc = wr; | ||
| 217 | found = memchr(buf, '\r', wr); | ||
| 218 | if (found) | ||
| 219 | rc = found - buf + 1; | ||
| 220 | rc = safe_write(ts->ptyfd, buf, rc); | ||
| 221 | if (rc <= 0) | ||
| 222 | return rc; | ||
| 223 | if (rc < wr /* don't look past available data */ | ||
| 224 | && buf[rc-1] == '\r' /* need this: imagine that write was _short_ */ | ||
| 225 | && (buf[rc] == '\n' || buf[rc] == '\0') | ||
| 226 | ) { | ||
| 227 | rc++; | ||
| 228 | } | 486 | } |
| 229 | goto update_and_return; | 487 | /* Leave it to pty2net side to deal with closing pty on errors |
| 488 | * (it'll see them when it tries to read pty). | ||
| 489 | */ | ||
| 490 | return 0; /* "didn't write anything" */ | ||
| 230 | } | 491 | } |
| 231 | 492 | /* No leading IACs: found != buf. | |
| 232 | /* buf starts with IAC char. Process that sequence. | 493 | * IOW: there is a "prefix" of non-IAC chars. |
| 233 | * Example: we get this from our own (bbox) telnet client: | 494 | * Write only them, and return. |
| 234 | * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3"): | ||
| 235 | * IAC WONT ECHO, IAC WILL NAWS, IAC SB NAWS <cols> <rows> IAC SE, IAC DO SGA | ||
| 236 | * Another example (telnet-0.17 from old-netkit): | ||
| 237 | * read(4, "\377\375\3""\377\373\30""\377\373\37""\377\373 ""\377\373!""\377\373\"""\377\373'" | ||
| 238 | * "\377\375\5""\377\373#""\377\374\1""\377\372\37\0\257\0I\377\360""\377\375\1"): | ||
| 239 | * IAC DO SGA, IAC WILL TTYPE, IAC WILL NAWS, IAC WILL TSPEED, IAC WILL LFLOW, IAC WILL LINEMODE, IAC WILL NEW_ENVIRON, | ||
| 240 | * IAC DO STATUS, IAC WILL XDISPLOC, IAC WONT ECHO, IAC SB NAWS <cols> <rows> IAC SE, IAC DO ECHO | ||
| 241 | */ | 495 | */ |
| 242 | if (wr <= 1) { | 496 | if (found) |
| 243 | /* Only the single IAC byte is in the buffer, eat it | 497 | wr = found - buf; |
| 244 | * and set a flag "process the rest of the sequence | 498 | |
| 245 | * next time we are here". | 499 | /* We map \r\n ==> \r for pragmatic reasons: |
| 246 | */ | 500 | * many client implementations send \r\n when |
| 247 | //bb_error_msg("dangling IAC!"); | 501 | * the user hits the CarriageReturn key. |
| 248 | ts->buffered_IAC_for_pty = 1; | 502 | * See RFC 1123 3.3.1 Telnet End-of-Line Convention. |
| 249 | rc = 1; | ||
| 250 | goto update_and_return; | ||
| 251 | } | ||
| 252 | |||
| 253 | handle_iac: | ||
| 254 | /* 2-byte commands (240..250 and 255): | ||
| 255 | * IAC IAC (255) Literal 255. Supported. | ||
| 256 | * IAC SE (240) End of subnegotiation. Treated as NOP. | ||
| 257 | * IAC NOP (241) NOP. Supported. | ||
| 258 | * IAC BRK (243) Break. Like serial line break. TODO via tcsendbreak()? | ||
| 259 | * IAC AYT (246) Are you there. | ||
| 260 | * These don't look useful: | ||
| 261 | * IAC DM (242) Data mark. What is this? | ||
| 262 | * IAC IP (244) Suspend, interrupt or abort the process. (Ancient cousin of ^C). | ||
| 263 | * IAC AO (245) Abort output. "You can continue running, but do not send me the output". | ||
| 264 | * IAC EC (247) Erase character. The receiver should delete the last received char. | ||
| 265 | * IAC EL (248) Erase line. The receiver should delete everything up tp last newline. | ||
| 266 | * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". | ||
| 267 | * Implemented only as part of NAWS: | ||
| 268 | * IAC SB (250) Subnegotiation of an option follows. | ||
| 269 | */ | 503 | */ |
| 270 | if (buf[1] == IAC) { | 504 | found = memchr(buf, '\r', wr); |
| 271 | /* Literal 255 (emacs M-DEL) */ | 505 | if (found) |
| 272 | //bb_error_msg("255!"); | 506 | wr = found - buf + 1; /* write up to and including \r */ |
| 273 | rc = safe_write(ts->ptyfd, &buf[1], 1); | 507 | rc = safe_write(ts->write_fd, buf, wr); |
| 274 | /* | 508 | if (rc <= 0) |
| 275 | * If we went through buffered_IAC_for_pty==1 path, | 509 | return 0; /* "didn't write anything" */ |
| 276 | * bailing out on error like below messes up the buffer. | 510 | //dbg_iac("wrote: %s", bin_to_hex(buf, rc)); |
| 277 | * EAGAIN is highly unlikely here, other errors will be | 511 | /* check is needed: imagine that write was _short_ */ |
| 278 | * repeated on next write, let's just skip error check. | 512 | if (buf[rc - 1] == '\r') /* last written byte was CR? */ |
| 279 | */ | 513 | ts->skip_LF_or_NUL = 1; |
| 280 | #if 0 | 514 | |
| 281 | if (rc <= 0) | 515 | BUF2PTY_INC(ts->wridx, rc); |
| 282 | return rc; | 516 | ts->size -= rc; |
| 283 | #endif | 517 | if (ts->size == 0) { /* very typical */ |
| 284 | rc = 2; | 518 | /* Avoid buffer wrapping most of the time (-> have bigger writes) */ |
| 285 | goto update_and_return; | 519 | //dbg_buffer("zero size"); |
| 520 | ts->rdidx = 0; | ||
| 521 | ts->wridx = 0; | ||
| 286 | } | 522 | } |
| 287 | if (buf[1] == AYT) { | 523 | return rc; |
| 288 | if (ts->size2 == 0) { /* if nothing buffered yet... */ | 524 | } |
| 289 | /* Send back evidence that AYT was seen */ | 525 | |
| 290 | unsigned char *buf2 = TS_BUF2(ts); | 526 | /* Check if buffer has space to read into */ |
| 291 | buf2[0] = IAC; | 527 | static int net_to_pty__have_buffer_to_read_into(void *this) |
| 292 | buf2[1] = NOP; | 528 | { |
| 293 | ts->wridx2 = 0; | 529 | //connection_t *conn = this; |
| 294 | ts->rdidx2 = ts->size2 = 2; | 530 | net_to_pty_t *ts = this; |
| 531 | |||
| 532 | /* the sibling may have closed our ptyfd after getting 10 EIO's */ | ||
| 533 | if (ts->write_fd < 0 | ||
| 534 | || ts->shell_pid < 0 /* -K option, and our pty's pid has exited */ | ||
| 535 | ) { | ||
| 536 | if (ts->sibling) { | ||
| 537 | /* do not close our netfd, it's in use by sibling */ | ||
| 538 | ts->read_fd = -1; | ||
| 295 | } | 539 | } |
| 296 | rc = 2; | 540 | dbg_close("%s:%d: remove_and_free_to_pty:%p wr:%d pid:%d", |
| 297 | goto update_and_return; | 541 | __func__, __LINE__, ts, ts->write_fd, ts->shell_pid); |
| 542 | remove_and_free_to_pty(ts); | ||
| 543 | return -1; /* "don't use me anymore, I'm gone" */ | ||
| 298 | } | 544 | } |
| 299 | if (buf[1] >= 240 && buf[1] <= 249) { | 545 | |
| 300 | /* NOP (241). Ignore (putty keepalive, etc) */ | 546 | if (ts->read_fd < 0) { /* we've seen EOF/error on netfd */ |
| 301 | /* All other 2-byte commands also treated as NOPs here */ | 547 | dbg_close("EOF on netfd was seen, ts->size:%d", ts->size); |
| 302 | rc = 2; | 548 | if (ts->size == 0) { /* we wrote everything */ |
| 303 | goto update_and_return; | 549 | unsigned rem; |
| 550 | /* close ptyfd, but after a 20 ms pause */ | ||
| 551 | |||
| 552 | /* pty has a design problem: close(master_ptyfd) | ||
| 553 | * is defined as causing hangup on slave pty. | ||
| 554 | * Which discards all currently buffered unread data | ||
| 555 | * (in our case, all data we just wrote). | ||
| 556 | * usleep(20000) is a crude solution. | ||
| 557 | * A better one (does not block other conns): | ||
| 558 | */ | ||
| 559 | dbg_close("going to close ptyfd:%d deadline_us:%u", | ||
| 560 | ts->write_fd, ts->deadline_us); | ||
| 561 | if (!ts->deadline_us) { | ||
| 562 | fabricate_ctrl_D_on_pty(ts->write_fd); | ||
| 563 | ts->deadline_us = (monotonic_us() + 20000) | 1; | ||
| 564 | rem = 20000; | ||
| 565 | } else { | ||
| 566 | rem = ts->deadline_us - monotonic_us(); | ||
| 567 | } | ||
| 568 | dbg_close("until ptyfd_close:%d", rem); | ||
| 569 | if (rem <= 20000) { | ||
| 570 | ioloop_decrease_current_timeout(ts->io, rem); | ||
| 571 | return 0; /* "do not read" */ | ||
| 572 | } | ||
| 573 | /* rem undeflowed (is "negative"): we waited at least 1000us */ | ||
| 574 | dbg_close("EOF on netfd, closing ptyfd:%d", ts->write_fd); | ||
| 575 | if (ts->sibling) | ||
| 576 | ts->sibling->read_fd = -1; | ||
| 577 | remove_and_free_to_pty(ts); /* closes ts->write_fd (ptyfd) */ | ||
| 578 | return -1; /* "don't use me anymore, I'm gone" */ | ||
| 579 | } | ||
| 580 | return 0; /* "don't want to read" */ | ||
| 304 | } | 581 | } |
| 305 | 582 | ||
| 306 | if (wr <= 2) { | 583 | return ts->size < TO_PTY_BUFSIZE; |
| 307 | /* BUG: only 2 bytes of the IAC is in the buffer, we just eat them. | 584 | } |
| 308 | * This is not a practical problem since >2 byte IACs are seen only | 585 | |
| 309 | * in initial negotiation, when buffer is empty | 586 | /* Read from socket to buffer, stripping trailing NUL if present */ |
| 310 | */ | 587 | static int net_to_pty__read(void *this) |
| 311 | rc = 2; | 588 | { |
| 312 | goto update_and_return; | 589 | net_to_pty_t *ts = this; |
| 590 | ssize_t count; | ||
| 591 | int avail; | ||
| 592 | |||
| 593 | /* Calculate available space and contiguous segment */ | ||
| 594 | avail = TO_PTY_BUFSIZE - ts->size; | ||
| 595 | //if (avail <= 0) | ||
| 596 | // return 0; /* buffer full */ | ||
| 597 | // ioloop logic ensures this is false | ||
| 598 | |||
| 599 | /* Read into contiguous segment from rdidx */ | ||
| 600 | count = MIN(TO_PTY_BUFSIZE - ts->rdidx, avail); | ||
| 601 | count = safe_read(ts->read_fd, BUF2PTY(ts) + ts->rdidx, count); | ||
| 602 | if (count <= 0) { | ||
| 603 | dbg_close("EOF/error on netfd:%d sibling:%p", ts->read_fd, ts->sibling); | ||
| 604 | /* There is no way to signal to pty that input is in "error state", | ||
| 605 | * therefore treat both EOF and error the same. | ||
| 606 | */ | ||
| 607 | if (!ts->sibling) | ||
| 608 | close(ts->read_fd); /* close netfd if not in use by sibling */ | ||
| 609 | ts->read_fd = -1; | ||
| 610 | return 0; /* "didn't read anything" */ | ||
| 313 | } | 611 | } |
| 314 | 612 | ||
| 315 | if (buf[1] == SB) { | 613 | ts->size += count; |
| 316 | if (buf[2] == TELOPT_NAWS) { | 614 | BUF2PTY_INC(ts->rdidx, count); |
| 317 | /* IAC SB, TELOPT_NAWS, 4-byte, IAC SE */ | 615 | |
| 318 | struct winsize ws; | 616 | return count; |
| 319 | if (wr <= 6) { | 617 | } |
| 320 | /* BUG: incomplete, can't process */ | 618 | |
| 321 | rc = wr; | 619 | static net_to_pty_t *new_net_to_pty(int from, int to) |
| 322 | goto update_and_return; | 620 | { |
| 621 | net_to_pty_t *this = xzalloc(sizeof(*this) + TO_PTY_BUFSIZE); | ||
| 622 | this->have_buffer_to_read_into = net_to_pty__have_buffer_to_read_into; | ||
| 623 | this->have_data_to_write = net_to_pty__have_data_to_write; | ||
| 624 | this->read = net_to_pty__read; | ||
| 625 | this->write = net_to_pty__write; | ||
| 626 | this->read_fd = from; | ||
| 627 | this->write_fd = to; | ||
| 628 | /* indexes and size are all 0 */ | ||
| 629 | return this; | ||
| 630 | } | ||
| 631 | |||
| 632 | static int pty_to_net__have_buffer_to_read_into(void *this) | ||
| 633 | { | ||
| 634 | //connection_t *conn = this; | ||
| 635 | pty_to_net_t *ts = this; | ||
| 636 | |||
| 637 | if (ts->read_fd < 0 /* we saw fatal read error before */ | ||
| 638 | || ts->shell_pid < 0 /* -K option, and our pty's pid has exited */ | ||
| 639 | ) { | ||
| 640 | if (ts->size == 0) { /* output flushed? */ | ||
| 641 | remove_and_free_to_net(ts); | ||
| 642 | return -1; /* "don't use me anymore, I'm gone" */ | ||
| 643 | } | ||
| 644 | return 0; /* "don't read" */ | ||
| 645 | } | ||
| 646 | if (ts->eio_count != 0) { | ||
| 647 | unsigned rem; | ||
| 648 | |||
| 649 | //if (ts->eio_count >= 10) | ||
| 650 | // return 0; /* "do not read" */ | ||
| 651 | //^^^ can't reach, caught by "ts->read_fd < 0" above. | ||
| 652 | |||
| 653 | /* last read was EIO. Still waiting before retry? */ | ||
| 654 | rem = ts->deadline_us - monotonic_us(); | ||
| 655 | if (rem <= 1000) { | ||
| 656 | /* tell io loop that poll timeout should now be "rem" us, not infinity */ | ||
| 657 | ioloop_decrease_current_timeout(ts->io, rem); | ||
| 658 | return 0; /* "do not read me (yet)" */ | ||
| 659 | } | ||
| 660 | /* else: rem underflowed: 1000us passed, can try reading again */ | ||
| 661 | } | ||
| 662 | return ts->size < TO_NET_BUFSIZE; | ||
| 663 | } | ||
| 664 | |||
| 665 | /* Read from pty to buffer, handling vhangup() EIO retries */ | ||
| 666 | static int pty_to_net__read(void *this) | ||
| 667 | { | ||
| 668 | //connection_t *conn = this; | ||
| 669 | pty_to_net_t *ts = this; | ||
| 670 | ssize_t count; | ||
| 671 | int avail; | ||
| 672 | |||
| 673 | /* Calculate available space and contiguous segment */ | ||
| 674 | avail = TO_NET_BUFSIZE - ts->size; | ||
| 675 | //if (avail <= 0) | ||
| 676 | // return 0; /* buffer full */ | ||
| 677 | // io loop logic ensures this is false | ||
| 678 | |||
| 679 | /* Read into contiguous segment from rdidx */ | ||
| 680 | count = MIN(TO_NET_BUFSIZE - ts->rdidx, avail); | ||
| 681 | count = safe_read(ts->read_fd, BUF2NET(ts) + ts->rdidx, count); | ||
| 682 | if (count <= 0) { | ||
| 683 | if (count < 0) { | ||
| 684 | if (errno == EAGAIN) | ||
| 685 | return 0; | ||
| 686 | /* login process might call vhangup(), | ||
| 687 | * which causes intermittent EIOs on read above | ||
| 688 | * (observed on kernel 4.12.0, not seen on 5.18.0). | ||
| 689 | * Try up to 10*1000 us. | ||
| 690 | */ | ||
| 691 | if (errno == EIO) { | ||
| 692 | ts->eio_count++; | ||
| 693 | dbg_close("EIO ptyfd:%d ts->eio_count:%d %06u", | ||
| 694 | ts->read_fd, ts->eio_count, | ||
| 695 | (unsigned)(monotonic_us() % 1000000) | ||
| 696 | ); | ||
| 697 | if (ts->eio_count >= 10) | ||
| 698 | goto close_ptyfd; | ||
| 699 | ts->deadline_us = monotonic_us() + 1000; | ||
| 700 | return 0; /* "read nothing" */ | ||
| 323 | } | 701 | } |
| 324 | memset(&ws, 0, sizeof(ws)); /* pixel sizes are set to 0 */ | 702 | dbg_close("error on ptyfd:%d %m", ts->read_fd); |
| 325 | ws.ws_col = (buf[3] << 8) | buf[4]; | 703 | /* (what other read errors from pty are possible, apart from EIO?) */ |
| 326 | ws.ws_row = (buf[5] << 8) | buf[6]; | 704 | ts->eio_count = 10; /* "fatally bad error" */ |
| 327 | ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); | 705 | goto close_ptyfd; |
| 328 | rc = 7; | ||
| 329 | /* trailing IAC SE will be eaten separately, as 2-byte NOP */ | ||
| 330 | goto update_and_return; | ||
| 331 | } | 706 | } |
| 332 | /* else: other subnegs not supported yet */ | 707 | /* EOF. (Doesn't happen with master ptys on Linux when slave closes - always EIO instead?) */ |
| 708 | dbg_close("EOF on ptyfd:%d", ts->read_fd); | ||
| 709 | ts->eio_count = 0; | ||
| 710 | close_ptyfd: | ||
| 711 | dbg_close("closing ptyfd:%d", ts->read_fd); | ||
| 712 | close(ts->read_fd); | ||
| 713 | if (ts->sibling) | ||
| 714 | /* net2pty pipe needs to close (nowhere to write its data) */ | ||
| 715 | ts->sibling->write_fd = -1; | ||
| 716 | ts->read_fd = -1; | ||
| 717 | /* Returning -1 here would mean "do not try writing to net", which is wrong */ | ||
| 718 | return 0; /* EOF or error, but we do not prevent draining to net */ | ||
| 333 | } | 719 | } |
| 720 | ts->eio_count = 0; | ||
| 334 | 721 | ||
| 335 | /* Assume it is a 3-byte WILL/WONT/DO/DONT 251..254 command and skip it */ | 722 | ts->size += count; |
| 336 | #if DEBUG | 723 | BUF2NET_INC(ts->rdidx, count); |
| 337 | fprintf(stderr, "Ignoring IAC %s,%s\n", | 724 | |
| 338 | TELCMD(buf[1]), TELOPT(buf[2])); | 725 | return count; |
| 339 | #endif | 726 | } |
| 340 | rc = 3; | 727 | |
| 341 | 728 | static int pty_to_net__have_data_to_write(void *this) | |
| 342 | update_and_return: | 729 | { |
| 343 | ts->wridx1 += rc; | 730 | pty_to_net_t *ts = this; |
| 344 | if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */ | 731 | if (ts->size == 0) { |
| 345 | ts->wridx1 = 0; | 732 | /* Wrote everything and pty is dead? */ |
| 346 | ts->size1 -= rc; | 733 | if (ts->read_fd < 0) { |
| 347 | /* | 734 | dbg_close("%s:%d: remove_and_free_to_net:%p rd:%d eio_count:%d", |
| 348 | * Hack. We cannot process IACs which wrap around buffer's end. | 735 | __func__, __LINE__, ts, ts->read_fd, ts->eio_count); |
| 349 | * Since properly fixing it requires writing bigger code, | 736 | if (ts->sibling) |
| 350 | * we rely instead on this code making it virtually impossible | 737 | ts->write_fd = -1; |
| 351 | * to have wrapped IAC (people don't type at 2k/second). | 738 | remove_and_free_to_net(ts); |
| 352 | * It also allows for bigger reads in common case. | 739 | return -1; /* "I'm gone" */ |
| 353 | */ | 740 | } |
| 354 | if (ts->size1 == 0) { /* very typical */ | ||
| 355 | //bb_error_msg("zero size1"); | ||
| 356 | ts->rdidx1 = 0; | ||
| 357 | ts->wridx1 = 0; | ||
| 358 | return rc; | ||
| 359 | } | ||
| 360 | wr = ts->wridx1; | ||
| 361 | if (wr != 0 && wr < ts->rdidx1) { | ||
| 362 | /* Buffer is not wrapped yet. | ||
| 363 | * We can easily move it to the beginning. | ||
| 364 | */ | ||
| 365 | //bb_error_msg("moved %d", wr); | ||
| 366 | memmove(TS_BUF1(ts), TS_BUF1(ts) + wr, ts->size1); | ||
| 367 | ts->rdidx1 -= wr; | ||
| 368 | ts->wridx1 = 0; | ||
| 369 | } | 741 | } |
| 370 | return rc; | 742 | return ts->size > 0 || ts->respond_to_AYT || ts->extra_byte_to_write; |
| 371 | } | 743 | } |
| 372 | 744 | ||
| 373 | /* | 745 | /* Write to socket from buffer, escaping IAC bytes */ |
| 374 | * Converting single IAC into double on output | 746 | static int pty_to_net__write(void *this) |
| 375 | */ | ||
| 376 | static size_t safe_write_double_iac(int fd, const char *buf, size_t count) | ||
| 377 | { | 747 | { |
| 378 | const char *IACptr; | 748 | pty_to_net_t *ts = this; |
| 379 | size_t wr, rc, total; | 749 | ssize_t count; |
| 750 | unsigned char *buf; | ||
| 751 | unsigned char *iac_ptr; | ||
| 752 | size_t wr; | ||
| 753 | |||
| 754 | /* Did we fail to send a byte last time? */ | ||
| 755 | if (ts->extra_byte_to_write) { | ||
| 756 | count = safe_write(ts->write_fd, &ts->extra_byte_to_write, 1); | ||
| 757 | if (count == 1) | ||
| 758 | ts->extra_byte_to_write = 0; | ||
| 759 | goto ret; | ||
| 760 | } | ||
| 380 | 761 | ||
| 381 | total = 0; | 762 | if (ts->size == 0) { |
| 382 | while (1) { | 763 | if (ts->respond_to_AYT) { /* other side pinged us? */ |
| 383 | if (count == 0) | 764 | /* Send back evidence that AYT was seen: IAC NOP */ |
| 384 | return total; | 765 | static const char ayt_response[2] = { IAC, NOP }; |
| 385 | if (*buf == (char)IAC) { | 766 | count = safe_write(ts->write_fd, ayt_response, 2); |
| 386 | static const char IACIAC[] ALIGN1 = { IAC, IAC }; | 767 | if (count >= 1) { |
| 387 | rc = safe_write(fd, IACIAC, 2); | 768 | if (count == 1) /* goddamit */ |
| 388 | /* BUG: if partial write was only 1 byte long, we end up emitting just one IAC */ | 769 | ts->extra_byte_to_write = NOP; |
| 389 | if (rc != 2) | 770 | ts->respond_to_AYT = 0; |
| 390 | break; | 771 | } |
| 391 | buf++; | 772 | goto ret; |
| 392 | total++; | ||
| 393 | count--; | ||
| 394 | continue; | ||
| 395 | } | 773 | } |
| 396 | /* count != 0, *buf != IAC */ | 774 | return 0; |
| 397 | IACptr = memchr(buf, IAC, count); | 775 | } |
| 398 | wr = count; | 776 | |
| 399 | if (IACptr) | 777 | buf = BUF2NET(ts) + ts->wridx; |
| 400 | wr = IACptr - buf; | 778 | wr = MIN(TO_NET_BUFSIZE - ts->wridx, ts->size); |
| 401 | rc = safe_write(fd, buf, wr); | 779 | dbg("rem:%d sz:%d ch:0x%02x", TO_NET_BUFSIZE - ts->wridx, ts->size, *buf); |
| 402 | if (rc != wr) | 780 | |
| 403 | break; | 781 | if (*buf == IAC) { |
| 404 | buf += rc; | 782 | static const char IACIAC[2] ALIGN1 = { IAC, IAC }; |
| 405 | total += rc; | 783 | count = safe_write(ts->write_fd, IACIAC, 2); |
| 406 | count -= rc; | 784 | if (count <= 1) { |
| 785 | if (count < 1) /* wrote nothing? */ | ||
| 786 | goto ret; | ||
| 787 | /* Wrote only one byte of two. Amazing... */ | ||
| 788 | ts->extra_byte_to_write = IAC; | ||
| 789 | } | ||
| 790 | /* Consume one byte from buffer */ | ||
| 791 | count = 1; | ||
| 792 | } else { | ||
| 793 | /* Find next IAC or write up to end of segment */ | ||
| 794 | iac_ptr = memchr(buf, IAC, wr); | ||
| 795 | if (iac_ptr) | ||
| 796 | wr = iac_ptr - buf; | ||
| 797 | count = safe_write(ts->write_fd, buf, wr); | ||
| 798 | if (count <= 0) | ||
| 799 | goto ret; | ||
| 800 | } | ||
| 801 | |||
| 802 | BUF2NET_INC(ts->wridx, count); | ||
| 803 | ts->size -= count; | ||
| 804 | if (ts->size == 0) { /* very typical */ | ||
| 805 | /* Avoid buffer wrapping most of the time (-> have bigger writes) */ | ||
| 806 | //dbg_buffer("zero size"); | ||
| 807 | ts->rdidx = 0; | ||
| 808 | ts->wridx = 0; | ||
| 407 | } | 809 | } |
| 408 | /* here: rc - result of last short write */ | 810 | ret: |
| 409 | if ((ssize_t)rc < 0) { /* error? */ | 811 | if (count >= 0) |
| 410 | if (total == 0) | 812 | return count; /* "not error" */ |
| 411 | return rc; | 813 | if (errno == EAGAIN) |
| 412 | rc = 0; | 814 | return 0; /* "not error" */ |
| 815 | |||
| 816 | /* Assuming fatal write error */ | ||
| 817 | /* If peer closes its read side with | ||
| 818 | * shutdown(SHUT_RD), we'll see EPIPE | ||
| 819 | * or ECONNRESET. | ||
| 820 | * (both were seen to occur in practice) | ||
| 821 | */ | ||
| 822 | if (ts->sibling) { | ||
| 823 | /* Sibling exists, don't close yet */ | ||
| 824 | ts->read_fd = -1; | ||
| 825 | ts->write_fd = -1; | ||
| 413 | } | 826 | } |
| 414 | return total + rc; | 827 | dbg_close("%s:%d: remove_and_free_to_net:%p", __func__, __LINE__, ts); |
| 828 | remove_and_free_to_net(ts); | ||
| 829 | return -1; /* "I'm gone" */ | ||
| 415 | } | 830 | } |
| 416 | 831 | ||
| 417 | /* Must match getopt32 string */ | 832 | static pty_to_net_t *new_pty_to_net(int from, int to) |
| 418 | enum { | 833 | { |
| 419 | OPT_WATCHCHILD = (1 << 2), /* -K */ | 834 | pty_to_net_t *this = xzalloc(sizeof(*this) + TO_NET_BUFSIZE); |
| 420 | OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */ | 835 | this->have_buffer_to_read_into = pty_to_net__have_buffer_to_read_into; |
| 421 | OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p PORT */ | 836 | this->have_data_to_write = pty_to_net__have_data_to_write; |
| 422 | OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */ | 837 | this->read = pty_to_net__read; |
| 423 | OPT_SYSLOG = (1 << 7) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -S */ | 838 | this->write = pty_to_net__write; |
| 424 | OPT_WAIT = (1 << 8) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -w SEC */ | 839 | this->read_fd = from; |
| 425 | }; | 840 | this->write_fd = to; |
| 841 | /* indexes and size are all 0 */ | ||
| 842 | return this; | ||
| 843 | } | ||
| 426 | 844 | ||
| 427 | static struct tsession * | ||
| 428 | make_new_session( | ||
| 429 | IF_FEATURE_TELNETD_STANDALONE(int sock) | ||
| 430 | IF_NOT_FEATURE_TELNETD_STANDALONE(void) | ||
| 431 | ) { | ||
| 432 | #if !ENABLE_FEATURE_TELNETD_STANDALONE | 845 | #if !ENABLE_FEATURE_TELNETD_STANDALONE |
| 433 | enum { sock = 0 }; | 846 | #define make_new_session(io, sockrd) \ |
| 847 | make_new_session(io) | ||
| 434 | #endif | 848 | #endif |
| 849 | static void make_new_session(ioloop_state_t *io, int sockrd) | ||
| 850 | { | ||
| 851 | IF_NOT_FEATURE_TELNETD_STANDALONE(enum { sockrd = 0 };) | ||
| 852 | const int sockwr = sockrd != 0 ? sockrd : 1; | ||
| 435 | const char *login_argv[2]; | 853 | const char *login_argv[2]; |
| 436 | struct termios termbuf; | 854 | struct termios termbuf; |
| 437 | int fd, pid; | 855 | int ptyfd, pid; |
| 438 | char tty_name[GETPTY_BUFSIZE]; | 856 | char tty_name[GETPTY_BUFSIZE]; |
| 439 | struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2); | ||
| 440 | 857 | ||
| 441 | /*ts->buf1 = (char *)(ts + 1);*/ | 858 | /* Got a new connection, set up a pty */ |
| 442 | /*ts->buf2 = ts->buf1 + BUFSIZE;*/ | 859 | ptyfd = xgetpty(tty_name); |
| 443 | 860 | ndelay_on(ptyfd); | |
| 444 | /* Got a new connection, set up a tty */ | 861 | close_on_exec_on(ptyfd); |
| 445 | fd = xgetpty(tty_name); | ||
| 446 | if (fd > G.maxfd) | ||
| 447 | G.maxfd = fd; | ||
| 448 | ts->ptyfd = fd; | ||
| 449 | ndelay_on(fd); | ||
| 450 | close_on_exec_on(fd); | ||
| 451 | 862 | ||
| 452 | /* SO_KEEPALIVE by popular demand */ | 863 | /* SO_KEEPALIVE by popular demand */ |
| 453 | setsockopt_keepalive(sock); | 864 | setsockopt_keepalive(sockrd); |
| 454 | #if ENABLE_FEATURE_TELNETD_STANDALONE | 865 | ndelay_on(sockrd); |
| 455 | ts->sockfd_read = sock; | 866 | if (sockrd == 0) /* called with fd 0 (inetd mode?) */ |
| 456 | ndelay_on(sock); | 867 | ndelay_on(sockwr); |
| 457 | if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */ | ||
| 458 | sock++; /* so use fd 1 for output */ | ||
| 459 | ndelay_on(sock); | ||
| 460 | } | ||
| 461 | ts->sockfd_write = sock; | ||
| 462 | if (sock > G.maxfd) | ||
| 463 | G.maxfd = sock; | ||
| 464 | #else | ||
| 465 | /* ts->sockfd_read = 0; - done by xzalloc */ | ||
| 466 | ts->sockfd_write = 1; | ||
| 467 | ndelay_on(0); | ||
| 468 | ndelay_on(1); | ||
| 469 | #endif | ||
| 470 | 868 | ||
| 471 | /* Make the telnet client understand we will echo characters so it | 869 | /* Make the telnet client understand we will echo characters so it |
| 472 | * should not do it locally. We don't tell the client to run linemode, | 870 | * should not do it locally. We don't tell the client to run linemode, |
| @@ -474,41 +872,48 @@ make_new_session( | |||
| 474 | * stuff that requires char-by-char support. */ | 872 | * stuff that requires char-by-char support. */ |
| 475 | { | 873 | { |
| 476 | static const char iacs_to_send[] ALIGN1 = { | 874 | static const char iacs_to_send[] ALIGN1 = { |
| 477 | IAC, DO, TELOPT_ECHO, | 875 | IAC, WILL, TELOPT_ECHO, // "I will echo your chars" |
| 478 | IAC, DO, TELOPT_NAWS, | 876 | // (Not really, _we_ won't - our programs in pty usually |
| 479 | /* This requires telnetd.ctrlSQ.patch (incomplete) */ | 877 | // handle that. In practice, this says: "do not echo |
| 480 | /*IAC, DO, TELOPT_LFLOW,*/ | 878 | // your user's input chars back to him _on your side_".) |
| 481 | IAC, WILL, TELOPT_ECHO, | 879 | IAC, WILL, TELOPT_SGA, // "I assume full-duplex, won't send GA's" |
| 482 | IAC, WILL, TELOPT_SGA | 880 | //IAC, DO, TELOPT_ECHO, //WRONG: "can you echo my chars to me"?? |
| 881 | IAC, DO, TELOPT_NAWS, // "can you send me terminal size data?" | ||
| 882 | // This requires telnetd.ctrlSQ.patch (incomplete): | ||
| 883 | //IAC, DO, TELOPT_LFLOW, | ||
| 483 | }; | 884 | }; |
| 484 | /* This confuses safe_write_double_iac(), it will try to duplicate | 885 | //Theoretically, our "WILL X" are requests and should only activate when client responds with "DO X". |
| 485 | * each IAC... */ | 886 | //However, we do not wait/check for "DO"s. Why? |
| 486 | //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send)); | 887 | //There is nothing to "activate" on our side for TELOPT_ECHO. |
| 487 | //ts->rdidx2 = sizeof(iacs_to_send); | 888 | //For TELOPT_SGA, we don't even have code to support sending/understanding GAs, |
| 488 | //ts->size2 = sizeof(iacs_to_send); | 889 | //so SGA is "always activated". |
| 489 | /* So just stuff it into TCP stream! (no error check...) */ | 890 | /* Just stuff it into TCP stream! (no error check...) */ |
| 490 | #if ENABLE_FEATURE_TELNETD_STANDALONE | 891 | safe_write(sockwr, iacs_to_send, sizeof(iacs_to_send)); |
| 491 | safe_write(sock, iacs_to_send, sizeof(iacs_to_send)); | ||
| 492 | #else | ||
| 493 | safe_write(1, iacs_to_send, sizeof(iacs_to_send)); | ||
| 494 | #endif | ||
| 495 | /*ts->rdidx2 = 0; - xzalloc did it */ | ||
| 496 | /*ts->size2 = 0;*/ | ||
| 497 | } | 892 | } |
| 498 | 893 | ||
| 499 | fflush_all(); | 894 | fflush_all(); |
| 500 | pid = vfork(); /* NOMMU-friendly */ | 895 | pid = vfork(); /* NOMMU-friendly */ |
| 501 | if (pid < 0) { | 896 | if (pid < 0) { |
| 502 | free(ts); | 897 | close(ptyfd); |
| 503 | close(fd); | 898 | close(sockrd); |
| 504 | /* sock will be closed by caller */ | ||
| 505 | bb_simple_perror_msg("vfork"); | 899 | bb_simple_perror_msg("vfork"); |
| 506 | return NULL; | 900 | return; |
| 507 | } | 901 | } |
| 508 | if (pid > 0) { | 902 | if (pid > 0) { |
| 509 | /* Parent */ | 903 | /* Parent */ |
| 510 | ts->shell_pid = pid; | 904 | pty_to_net_t *to_net; |
| 511 | return ts; | 905 | net_to_pty_t *to_pty; |
| 906 | |||
| 907 | to_net = new_pty_to_net(ptyfd, sockwr); | ||
| 908 | to_pty = new_net_to_pty(sockrd, ptyfd); | ||
| 909 | dbg("to_net:%p to_pty:%p", to_net, to_pty); | ||
| 910 | to_pty->sibling = to_net; | ||
| 911 | to_net->sibling = to_pty; | ||
| 912 | to_net->shell_pid = pid; | ||
| 913 | to_pty->shell_pid = pid; | ||
| 914 | ioloop_insert_conn(io, (connection_t *)to_net); | ||
| 915 | ioloop_insert_conn(io, (connection_t *)to_pty); | ||
| 916 | return; | ||
| 512 | } | 917 | } |
| 513 | 918 | ||
| 514 | /* Child */ | 919 | /* Child */ |
| @@ -517,31 +922,30 @@ make_new_session( | |||
| 517 | /* Restore default signal handling ASAP */ | 922 | /* Restore default signal handling ASAP */ |
| 518 | bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL); | 923 | bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL); |
| 519 | 924 | ||
| 520 | pid = getpid(); | 925 | /* Make new session and process group */ |
| 926 | //pid = getpid(); // redundant, setsid gives us our pid | ||
| 927 | pid = setsid(); | ||
| 928 | |||
| 929 | /* Open the child's side of the pty */ | ||
| 930 | /* NB: setsid() disconnects from any previous ctty's. Therefore | ||
| 931 | * we must open child's side of the tty AFTER setsid! */ | ||
| 932 | close(0); | ||
| 933 | xopen(tty_name, O_RDWR); /* fd 0: becomes our ctty */ | ||
| 934 | xdup2(0, 1); | ||
| 935 | xdup2(0, 2); | ||
| 936 | tcsetpgrp(0, pid); /* switch this tty's process group to us */ | ||
| 521 | 937 | ||
| 522 | if (ENABLE_FEATURE_UTMP) { | 938 | if (ENABLE_FEATURE_UTMP) { |
| 523 | len_and_sockaddr *lsa = get_peer_lsa(sock); | 939 | len_and_sockaddr *lsa = get_peer_lsa(sockrd); |
| 524 | char *hostname = NULL; | 940 | char *hostname = NULL; |
| 525 | if (lsa) { | 941 | if (lsa) { |
| 526 | hostname = xmalloc_sockaddr2dotted(&lsa->u.sa); | 942 | hostname = xmalloc_sockaddr2dotted(&lsa->u.sa); |
| 527 | free(lsa); | 943 | free(lsa); |
| 528 | } | 944 | } |
| 529 | write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname); | 945 | write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname); |
| 530 | free(hostname); | 946 | IF_FEATURE_CLEAN_UP(free(hostname);) |
| 531 | } | 947 | } |
| 532 | 948 | ||
| 533 | /* Make new session and process group */ | ||
| 534 | setsid(); | ||
| 535 | |||
| 536 | /* Open the child's side of the tty */ | ||
| 537 | /* NB: setsid() disconnects from any previous ctty's. Therefore | ||
| 538 | * we must open child's side of the tty AFTER setsid! */ | ||
| 539 | close(0); | ||
| 540 | xopen(tty_name, O_RDWR); /* becomes our ctty */ | ||
| 541 | xdup2(0, 1); | ||
| 542 | xdup2(0, 2); | ||
| 543 | tcsetpgrp(0, pid); /* switch this tty's process group to us */ | ||
| 544 | |||
| 545 | /* The pseudo-terminal allocated to the client is configured to operate | 949 | /* The pseudo-terminal allocated to the client is configured to operate |
| 546 | * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */ | 950 | * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */ |
| 547 | tcgetattr(0, &termbuf); | 951 | tcgetattr(0, &termbuf); |
| @@ -565,8 +969,8 @@ make_new_session( | |||
| 565 | login_argv[1] = NULL; | 969 | login_argv[1] = NULL; |
| 566 | /* exec busybox applet (if PREFER_APPLETS=y), if that fails, | 970 | /* exec busybox applet (if PREFER_APPLETS=y), if that fails, |
| 567 | * exec external program. | 971 | * exec external program. |
| 568 | * NB: sock is either 0 or has CLOEXEC set on it. | 972 | * NB: sockrd is either 0 or has CLOEXEC set on it. |
| 569 | * fd has CLOEXEC set on it too. These two fds will be closed here. | 973 | * ptyfd has CLOEXEC set on it too. These two fds will be closed here. |
| 570 | */ | 974 | */ |
| 571 | BB_EXECVP(G.loginpath, (char **)login_argv); | 975 | BB_EXECVP(G.loginpath, (char **)login_argv); |
| 572 | /* _exit is safer with vfork, and we shouldn't send message | 976 | /* _exit is safer with vfork, and we shouldn't send message |
| @@ -574,123 +978,708 @@ make_new_session( | |||
| 574 | _exit_FAILURE(); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/ | 978 | _exit_FAILURE(); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/ |
| 575 | } | 979 | } |
| 576 | 980 | ||
| 981 | |||
| 577 | #if ENABLE_FEATURE_TELNETD_STANDALONE | 982 | #if ENABLE_FEATURE_TELNETD_STANDALONE |
| 983 | struct accept_conn { | ||
| 984 | TELNET_CONNECTION | ||
| 985 | }; | ||
| 578 | 986 | ||
| 579 | static void | 987 | static int accept_conn__can_accept(void *this UNUSED_PARAM) |
| 580 | free_session(struct tsession *ts) | ||
| 581 | { | 988 | { |
| 582 | struct tsession *t; | 989 | return 1; |
| 583 | 990 | } | |
| 584 | if (option_mask32 & OPT_INETD) | ||
| 585 | exit_SUCCESS(); | ||
| 586 | 991 | ||
| 587 | /* Unlink this telnet session from the session list */ | 992 | static int accept_conn__accept(void *this) |
| 588 | t = G.sessions; | 993 | { |
| 589 | if (t == ts) | 994 | struct accept_conn *ts = this; |
| 590 | G.sessions = ts->next; | 995 | int fd; |
| 591 | else { | ||
| 592 | while (t->next != ts) | ||
| 593 | t = t->next; | ||
| 594 | t->next = ts->next; | ||
| 595 | } | ||
| 596 | 996 | ||
| 597 | #if 0 | 997 | dbg("%s:%d", __func__, __LINE__); |
| 598 | /* It was said that "normal" telnetd just closes ptyfd, | 998 | fd = accept(ts->read_fd, NULL, NULL); |
| 599 | * doesn't send SIGKILL. When we close ptyfd, | 999 | //TODO: accept4(SOCK_NONBLOCK) - can remove ndelay_on() in make_new_session |
| 600 | * kernel sends SIGHUP to processes having slave side opened. */ | 1000 | if (fd >= 0) { |
| 601 | kill(ts->shell_pid, SIGKILL); | 1001 | close_on_exec_on(fd); |
| 602 | waitpid(ts->shell_pid, NULL, 0); | 1002 | /* Create two new pipes and insert them into ioloop */ |
| 603 | #endif | 1003 | make_new_session(ts->io, fd); |
| 604 | close(ts->ptyfd); | ||
| 605 | close(ts->sockfd_read); | ||
| 606 | /* We do not need to close(ts->sockfd_write), it's the same | ||
| 607 | * as sockfd_read unless we are in inetd mode. But in inetd mode | ||
| 608 | * we do not reach this */ | ||
| 609 | free(ts); | ||
| 610 | |||
| 611 | /* Scan all sessions and find new maxfd */ | ||
| 612 | G.maxfd = 0; | ||
| 613 | ts = G.sessions; | ||
| 614 | while (ts) { | ||
| 615 | if (G.maxfd < ts->ptyfd) | ||
| 616 | G.maxfd = ts->ptyfd; | ||
| 617 | if (G.maxfd < ts->sockfd_read) | ||
| 618 | G.maxfd = ts->sockfd_read; | ||
| 619 | #if 0 | ||
| 620 | /* Again, sockfd_write == sockfd_read here */ | ||
| 621 | if (G.maxfd < ts->sockfd_write) | ||
| 622 | G.maxfd = ts->sockfd_write; | ||
| 623 | #endif | ||
| 624 | ts = ts->next; | ||
| 625 | } | 1004 | } |
| 1005 | return 0; | ||
| 626 | } | 1006 | } |
| 627 | 1007 | ||
| 628 | #else /* !FEATURE_TELNETD_STANDALONE */ | 1008 | static int accept_conn__return_zero(void *this UNUSED_PARAM) |
| 629 | 1009 | { | |
| 630 | /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */ | 1010 | return 0; |
| 631 | #define free_session(ts) return 0 | 1011 | } |
| 632 | 1012 | ||
| 1013 | static struct accept_conn *new_accept_conn(int fd) | ||
| 1014 | { | ||
| 1015 | struct accept_conn *this = xzalloc(sizeof(*this)); | ||
| 1016 | this->have_buffer_to_read_into = accept_conn__can_accept; | ||
| 1017 | this->have_data_to_write = accept_conn__return_zero; | ||
| 1018 | this->read = accept_conn__accept; | ||
| 1019 | //this->write = accept_conn__return_zero; //never called | ||
| 1020 | this->read_fd = fd; | ||
| 1021 | this->write_fd = -1; | ||
| 1022 | return this; | ||
| 1023 | } | ||
| 633 | #endif | 1024 | #endif |
| 634 | 1025 | ||
| 635 | static void handle_sigchld(int sig UNUSED_PARAM) | 1026 | static void handle_sigchld(int sig UNUSED_PARAM) |
| 636 | { | 1027 | { |
| 637 | pid_t pid; | ||
| 638 | struct tsession *ts; | ||
| 639 | int save_errno = errno; | 1028 | int save_errno = errno; |
| 640 | 1029 | ||
| 641 | /* Looping: more than one child may have exited */ | 1030 | /* Looping: more than one child may have exited */ |
| 642 | while (1) { | 1031 | while (1) { |
| 1032 | pid_t pid; | ||
| 1033 | struct telnet_conn *conn; | ||
| 1034 | |||
| 643 | pid = wait_any_nohang(NULL); | 1035 | pid = wait_any_nohang(NULL); |
| 644 | if (pid <= 0) | 1036 | if (pid <= 0) |
| 645 | break; | 1037 | break; |
| 646 | ts = G.sessions; | 1038 | update_utmp_DEAD_PROCESS(pid); |
| 647 | while (ts) { | 1039 | conn = (void*)G.io.conns; |
| 648 | if (ts->shell_pid == pid) { | 1040 | while (conn) { |
| 649 | ts->shell_pid = -1; | 1041 | if (conn->shell_pid == pid) { |
| 650 | update_utmp_DEAD_PROCESS(pid); | 1042 | /* mark the conn to close soon */ |
| 651 | break; | 1043 | conn->shell_pid = -1; |
| 652 | } | 1044 | } |
| 653 | ts = ts->next; | 1045 | conn = (void*)conn->next; |
| 654 | } | 1046 | } |
| 655 | } | 1047 | } |
| 656 | 1048 | ||
| 657 | errno = save_errno; | 1049 | errno = save_errno; |
| 658 | } | 1050 | } |
| 659 | 1051 | ||
| 1052 | #if ENABLE_FEATURE_TELNETD_SELFTEST_DEBUG | ||
| 1053 | |||
| 1054 | static char *bin_to_hex(const void *hash_value, unsigned hash_length) | ||
| 1055 | { | ||
| 1056 | /* xzalloc zero-terminates */ | ||
| 1057 | char *hex_value = xzalloc((hash_length * 2) + 1); | ||
| 1058 | bin2hex(hex_value, (char*)hash_value, hash_length); | ||
| 1059 | return auto_string(hex_value); | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | static int test_vhangup(void) | ||
| 1063 | { | ||
| 1064 | char tty_name[GETPTY_BUFSIZE]; | ||
| 1065 | int master_fd; | ||
| 1066 | int last_rc = 999; | ||
| 1067 | int last_errno = 0; | ||
| 1068 | unsigned long long last_change_ns, base_ns; | ||
| 1069 | |||
| 1070 | bb_simple_info_msg("selftest: pty vhangup() behavior"); | ||
| 1071 | |||
| 1072 | master_fd = xgetpty(tty_name); | ||
| 1073 | ndelay_on(master_fd); | ||
| 1074 | |||
| 1075 | fflush_all(); | ||
| 1076 | if (xfork() == 0) { | ||
| 1077 | /* Child */ | ||
| 1078 | //int slave_fd; | ||
| 1079 | int fd; | ||
| 1080 | |||
| 1081 | /* vhangup() sends SIGHUP to the session, ignore it */ | ||
| 1082 | signal(SIGHUP, SIG_IGN); | ||
| 1083 | |||
| 1084 | applet_name = "vhangup child"; | ||
| 1085 | |||
| 1086 | close(master_fd); | ||
| 1087 | setsid(); | ||
| 1088 | /*slave_fd =*/ xopen(tty_name, O_RDWR); | ||
| 1089 | |||
| 1090 | fd = open("/dev/tty", O_RDWR); | ||
| 1091 | if (fd >= 0) { | ||
| 1092 | bb_simple_info_msg("/dev/tty opened OK - we have a ctty"); | ||
| 1093 | close(fd); | ||
| 1094 | } else { | ||
| 1095 | bb_simple_perror_msg("/dev/tty"); | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | bb_simple_info_msg("sleeping 0.2 sec before vhangup()"); | ||
| 1099 | usleep(200*1000); | ||
| 1100 | |||
| 1101 | bb_simple_info_msg("calling vhangup()"); | ||
| 1102 | vhangup(); | ||
| 1103 | |||
| 1104 | fd = open("/dev/tty", O_RDWR); | ||
| 1105 | if (fd >= 0) { | ||
| 1106 | bb_simple_info_msg("/dev/tty opened OK - we still have a ctty"); | ||
| 1107 | close(fd); | ||
| 1108 | } else { | ||
| 1109 | bb_simple_perror_msg("/dev/tty"); | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | bb_simple_info_msg("sleeping 0.2 sec after vhangup()"); | ||
| 1113 | usleep(200*1000); | ||
| 1114 | |||
| 1115 | bb_simple_error_msg_and_die("exiting"); | ||
| 1116 | } | ||
| 1117 | |||
| 1118 | /* Parent */ | ||
| 1119 | bb_simple_info_msg("nonblocking read() from pty master in a loop..."); | ||
| 1120 | last_change_ns = monotonic_ns(); | ||
| 1121 | |||
| 1122 | base_ns = monotonic_ns(); | ||
| 1123 | for (;;) { | ||
| 1124 | int rc; | ||
| 1125 | char buf[1]; | ||
| 1126 | unsigned long long now_ns; | ||
| 1127 | |||
| 1128 | errno = 0; | ||
| 1129 | rc = read(master_fd, buf, 1); | ||
| 1130 | now_ns = monotonic_ns() - base_ns; | ||
| 1131 | |||
| 1132 | if (rc != last_rc || errno != last_errno) { | ||
| 1133 | bb_error_msg("t=%lluns: read()=%d errno=%d(%s)", | ||
| 1134 | now_ns, | ||
| 1135 | rc, errno, strerror(errno)); | ||
| 1136 | last_rc = rc; | ||
| 1137 | last_errno = errno; | ||
| 1138 | last_change_ns = now_ns; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | /* Exit if no change for a few seconds */ | ||
| 1142 | if ((now_ns - last_change_ns) > 1000*1000*1000) { | ||
| 1143 | bb_simple_info_msg("no change for 1 second, loop stopped"); | ||
| 1144 | break; | ||
| 1145 | } | ||
| 1146 | |||
| 1147 | usleep(100); /* 100us polling - fast enough to catch transitions */ | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | close(master_fd); | ||
| 1151 | waitpid(-1, NULL, 0); | ||
| 1152 | |||
| 1153 | bb_simple_error_msg("vhangup test completed"); | ||
| 1154 | return 0; | ||
| 1155 | } | ||
| 1156 | static int test_net_to_pty_data_integrity(void) | ||
| 1157 | { | ||
| 1158 | enum { TESTDATA_SIZE = 16 * 1024 * 1024 }; | ||
| 1159 | unsigned char *input_data; | ||
| 1160 | unsigned char *expected_data; | ||
| 1161 | int input_size; | ||
| 1162 | int expected_size; | ||
| 1163 | uint32_t randbits; | ||
| 1164 | uint8_t expected_hash32[32]; | ||
| 1165 | uint8_t hash_in_pty32[32]; | ||
| 1166 | uint8_t dummy_byte; | ||
| 1167 | int sock_pair[2]; | ||
| 1168 | char tty_name[GETPTY_BUFSIZE]; | ||
| 1169 | int ptyfd; | ||
| 1170 | int i; | ||
| 1171 | int waitstatus, exitcode; | ||
| 1172 | |||
| 1173 | bb_simple_info_msg("selftest: net_to_pty"); | ||
| 1174 | |||
| 1175 | /* Allocate buffer with paranoia space */ | ||
| 1176 | input_data = xzalloc(TESTDATA_SIZE + 64); | ||
| 1177 | |||
| 1178 | /* Generate nightmare input data: | ||
| 1179 | * - Mostly random bytes | ||
| 1180 | * - IAC bytes sprinkled in (1 in 16 probability) | ||
| 1181 | * - IAC IAC sequences (1 in 32) | ||
| 1182 | * - IAC SB TELOPT_NAWS sequences(1 in 32) | ||
| 1183 | * - All sorts of IAC commands with random parameters | ||
| 1184 | * - Malformed sequences | ||
| 1185 | */ | ||
| 1186 | randbits = 0; | ||
| 1187 | for (i = 0; i < TESTDATA_SIZE;) { | ||
| 1188 | unsigned _32possibilities; | ||
| 1189 | if (randbits == 0) | ||
| 1190 | randbits = random(); | ||
| 1191 | _32possibilities = (randbits & 0x1f); | ||
| 1192 | randbits >>= 5; | ||
| 1193 | if (_32possibilities == 3 && (randbits & 0x7f) == 0) { /* 1 in 32*127=4*1k prob: long run of printables */ | ||
| 1194 | if (i < TESTDATA_SIZE - 4*1024) { | ||
| 1195 | int end = i + ((4*1024 - 1) & random()); | ||
| 1196 | while (i < end) | ||
| 1197 | input_data[i++] = (random() & 0x3f) + 0x20; | ||
| 1198 | continue; | ||
| 1199 | } | ||
| 1200 | } | ||
| 1201 | if (_32possibilities == 3) { /* CR,LF/NUL: 1 in 32 prob */ | ||
| 1202 | input_data[i++] = '\r'; | ||
| 1203 | input_data[i++] = randbits & 1 ? '\n' : '\0'; | ||
| 1204 | } else | ||
| 1205 | if (_32possibilities == 2) { /* NAWS: 1 in 32 prob */ | ||
| 1206 | input_data[i++] = IAC; | ||
| 1207 | input_data[i++] = SB; | ||
| 1208 | input_data[i++] = TELOPT_NAWS; | ||
| 1209 | } else | ||
| 1210 | if (_32possibilities == 1) { /* IAC: 1 in 32 prob */ | ||
| 1211 | input_data[i++] = IAC; | ||
| 1212 | if (random() & 1) /* IAC IAC: 1 in 64 prob */ | ||
| 1213 | input_data[i++] = IAC; | ||
| 1214 | } else { | ||
| 1215 | input_data[i++] = random(); | ||
| 1216 | } | ||
| 1217 | } | ||
| 1218 | input_size = TESTDATA_SIZE; | ||
| 1219 | bb_error_msg("generated %d bytes of test data", input_size); | ||
| 1220 | |||
| 1221 | /* Allocate expected output buffer */ | ||
| 1222 | expected_data = xmalloc(TESTDATA_SIZE); | ||
| 1223 | |||
| 1224 | /* Process input_data simulating net_to_pty transformations: | ||
| 1225 | * - IAC IAC → single IAC | ||
| 1226 | * - IAC SB TELOPT_NAWS [4 bytes] IAC SE → stripped | ||
| 1227 | * - Other IAC sequences → stripped | ||
| 1228 | * - \r\n → \r (skip the \n) | ||
| 1229 | * - \r\0 → \r (skip the \0) | ||
| 1230 | */ | ||
| 1231 | memset(input_data + input_size, 0, 64); | ||
| 1232 | expected_size = 0; | ||
| 1233 | i = 0; | ||
| 1234 | while (i < input_size) { | ||
| 1235 | /* Tail of 64 extra zero bytes allows to safely read beyond the end */ | ||
| 1236 | if (input_data[i] == IAC) { | ||
| 1237 | if (input_data[i + 1] == IAC) { | ||
| 1238 | /* IAC IAC → single IAC */ | ||
| 1239 | expected_data[expected_size++] = IAC; | ||
| 1240 | i += 2; | ||
| 1241 | continue; | ||
| 1242 | } | ||
| 1243 | if (input_data[i + 1] >= 240 && input_data[i + 1] <= 249) { | ||
| 1244 | /* 2-byte IAC command (NOP, etc.) - skip */ | ||
| 1245 | i += 2; | ||
| 1246 | continue; | ||
| 1247 | } | ||
| 1248 | if (input_data[i + 1] == SB) { | ||
| 1249 | /* IAC SB ... skip subnegotiation */ | ||
| 1250 | if (input_data[i + 2] == TELOPT_NAWS) { | ||
| 1251 | //TODO: simulate unusual NAWS | ||
| 1252 | /* IAC SB TELOPT_NAWS [4 bytes] */ | ||
| 1253 | i += 3; /* skip IAC SB TELOPT_NAWS */ | ||
| 1254 | /* Skip 4 bytes of window size */ | ||
| 1255 | i += 4; | ||
| 1256 | continue; | ||
| 1257 | // } else { | ||
| 1258 | //should do: /* Other subneg, skip to IAC SE */ but that's not what telnetd code is doing yet... | ||
| 1259 | } | ||
| 1260 | } | ||
| 1261 | /* Assumed 3-byte IAC WILL/WONT/DO/DONT - skip */ | ||
| 1262 | i += 3; | ||
| 1263 | continue; | ||
| 1264 | } | ||
| 1265 | if (input_data[i] == '\r') { | ||
| 1266 | /* Write \r, check if followed by \n or \0 */ | ||
| 1267 | expected_data[expected_size++] = '\r'; | ||
| 1268 | i++; | ||
| 1269 | if (input_data[i] == '\n' || input_data[i] == '\0') | ||
| 1270 | i++; /* skip \n or \0 after \r */ | ||
| 1271 | continue; | ||
| 1272 | } | ||
| 1273 | /* Regular byte */ | ||
| 1274 | expected_data[expected_size++] = input_data[i++]; | ||
| 1275 | } | ||
| 1276 | sha256_block(expected_data, expected_size, expected_hash32); | ||
| 1277 | bb_error_msg("expected %d bytes after net_to_pty processing. hash:%s", expected_size, bin_to_hex(expected_hash32, 32)); | ||
| 1278 | #if SAVE_NET2PTY_EXPECTED | ||
| 1279 | { | ||
| 1280 | int fd = xopen("expected.bin", O_WRONLY | O_CREAT | O_TRUNC); | ||
| 1281 | xwrite(fd, expected_data, expected_size); | ||
| 1282 | close(fd); | ||
| 1283 | bb_error_msg("wrote expected.bin"); | ||
| 1284 | } | ||
| 1285 | #endif | ||
| 1286 | free(expected_data); | ||
| 1287 | expected_data = NULL; /* to catch use-after-free */ | ||
| 1288 | |||
| 1289 | /* Create socketpair for network side */ | ||
| 1290 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) < 0) | ||
| 1291 | bb_simple_perror_msg_and_die("socketpair"); | ||
| 1292 | |||
| 1293 | fflush_all(); | ||
| 1294 | if (xfork() == 0) { | ||
| 1295 | /* Fork writer child: writes telnet data to socket in random chunks */ | ||
| 1296 | int written = 0; | ||
| 1297 | close(sock_pair[0]); /* close read end */ | ||
| 1298 | |||
| 1299 | applet_name = "tty input"; | ||
| 1300 | |||
| 1301 | while (written < input_size) { | ||
| 1302 | int chunk = (random() & 4095) + 1; /* 1 to 4096 bytes */ | ||
| 1303 | if (written + chunk > input_size) | ||
| 1304 | chunk = input_size - written; | ||
| 1305 | |||
| 1306 | chunk = safe_write(sock_pair[1], input_data + written, chunk); | ||
| 1307 | if (chunk > 0) | ||
| 1308 | written += chunk; | ||
| 1309 | |||
| 1310 | /* Small random delay to vary timing */ | ||
| 1311 | if ((random() & 0xf) == 0) | ||
| 1312 | usleep(random() & 0x3ff); | ||
| 1313 | } | ||
| 1314 | _exit(0); | ||
| 1315 | } | ||
| 1316 | close(sock_pair[1]); /* close write end */ | ||
| 1317 | |||
| 1318 | free(input_data); | ||
| 1319 | input_data = NULL; /* to catch use-after-free */ | ||
| 1320 | |||
| 1321 | /* Create pty pair */ | ||
| 1322 | ptyfd = xgetpty(tty_name); | ||
| 1323 | |||
| 1324 | if (xfork() == 0) { | ||
| 1325 | /* Fork reader child: reads from PTY slave */ | ||
| 1326 | int slave_fd; | ||
| 1327 | int retry; | ||
| 1328 | int n; | ||
| 1329 | unsigned char read_buf[8192]; | ||
| 1330 | int total_read = 0; | ||
| 1331 | struct termios termbuf; | ||
| 1332 | sha256_ctx_t ctx; | ||
| 1333 | #if SAVE_NET2PTY_ACTUAL | ||
| 1334 | int out_fd = xopen("actual.bin", O_WRONLY | O_CREAT | O_TRUNC); | ||
| 1335 | #endif | ||
| 1336 | applet_name = "pty reader"; | ||
| 1337 | |||
| 1338 | close(sock_pair[0]); | ||
| 1339 | close(ptyfd); | ||
| 1340 | |||
| 1341 | slave_fd = xopen(tty_name, O_RDWR); | ||
| 1342 | setsid(); /* this loses ctty (no tty signals ^C, ^Z, ^\; no SIGHUP on master close (probably?)) */ | ||
| 1343 | |||
| 1344 | tcgetattr(slave_fd, &termbuf); | ||
| 1345 | cfmakeraw(&termbuf); | ||
| 1346 | tcsetattr(slave_fd, TCSANOW, &termbuf); | ||
| 1347 | //bb_error_msg("tty_name:'%s'", tty_name); | ||
| 1348 | |||
| 1349 | /* Synchronize with master: "raw set, you can start writing" */ | ||
| 1350 | dummy_byte = '!'; | ||
| 1351 | safe_write(slave_fd, &dummy_byte, 1); | ||
| 1352 | |||
| 1353 | sha256_begin(&ctx); | ||
| 1354 | /* Retry logic shows how master pty close looks on slave side: | ||
| 1355 | * on kernel 5.18.0, I see EIO once, then (if I retry reads), | ||
| 1356 | * I see EOFs (zero reads). | ||
| 1357 | * What is not visible (but does happen) is that any | ||
| 1358 | * unread-by-me buffered data is lost - something that | ||
| 1359 | * wouldn't happen on pipes/socketpairs! | ||
| 1360 | */ | ||
| 1361 | retry = 0; | ||
| 1362 | for (;;) { | ||
| 1363 | n = safe_read(slave_fd, read_buf, sizeof(read_buf)); | ||
| 1364 | if (n == 0) { | ||
| 1365 | retry++; | ||
| 1366 | bb_error_msg("EOF on read(%d)", retry); | ||
| 1367 | if (retry < 3) continue; | ||
| 1368 | break; /* EOF (parent closed master) - expected */ | ||
| 1369 | } | ||
| 1370 | if (n < 0) { | ||
| 1371 | retry++; | ||
| 1372 | bb_perror_msg("read(%d)", retry); | ||
| 1373 | if (retry < 3) continue; | ||
| 1374 | break; /* error (parent closed master) - expected */ | ||
| 1375 | } | ||
| 1376 | /* If happens, probably kernel bug! */ | ||
| 1377 | if (retry != 0) bb_error_msg_and_die("READ DATA AFTER EOF/ERROR RETRY!!!"); | ||
| 1378 | #if SAVE_NET2PTY_ACTUAL | ||
| 1379 | xwrite(out_fd, read_buf, n); | ||
| 1380 | #endif | ||
| 1381 | sha256_hash(&ctx, read_buf, n); | ||
| 1382 | total_read += n; | ||
| 1383 | // Test premature close on pty read side: | ||
| 1384 | //if (total_read >= 128*1024) _exit(0); | ||
| 1385 | } | ||
| 1386 | |||
| 1387 | sha256_end(&ctx, hash_in_pty32); | ||
| 1388 | n = memcmp(hash_in_pty32, expected_hash32, 32); | ||
| 1389 | bb_error_msg("received %d bytes, hash:%s%s", total_read, bin_to_hex(hash_in_pty32, 32), n ? " MISMATCH" : ""); | ||
| 1390 | _exit(!!n); | ||
| 1391 | } | ||
| 1392 | |||
| 1393 | /* Parent: run the net_to_pty logic */ | ||
| 1394 | { | ||
| 1395 | net_to_pty_t *conn; | ||
| 1396 | ioloop_state_t *io; | ||
| 1397 | int rc; | ||
| 1398 | |||
| 1399 | io = new_ioloop_state(); | ||
| 1400 | conn = new_net_to_pty(sock_pair[0], ptyfd); | ||
| 1401 | ioloop_insert_conn(io, (void*)conn); | ||
| 1402 | |||
| 1403 | /* We must allow the pty child to change its tty to RAW | ||
| 1404 | * before we start writing. Otherwise (it was observed), | ||
| 1405 | * it can see a few \r -> \n remapped. | ||
| 1406 | */ | ||
| 1407 | safe_read(ptyfd, &dummy_byte, 1); | ||
| 1408 | |||
| 1409 | rc = ioloop_run(io); | ||
| 1410 | bb_error_msg("ioloop_run:%d", rc); | ||
| 1411 | |||
| 1412 | /* This should be done inside ioloop. Check where possible: */ | ||
| 1413 | //free_connection(conn); | ||
| 1414 | if (close(sock_pair[0]) == 0) /* should be EBADF */ | ||
| 1415 | bb_error_msg_and_die("BUG: net read side wasn't closed"); | ||
| 1416 | if (close(ptyfd) == 0) | ||
| 1417 | bb_error_msg_and_die("BUG: pty write side wasn't closed"); | ||
| 1418 | |||
| 1419 | free_ioloop_state(io); | ||
| 1420 | } | ||
| 1421 | |||
| 1422 | /* Wait for all children */ | ||
| 1423 | exitcode = 0; | ||
| 1424 | while (safe_waitpid(-1, &waitstatus, 0) > 0) | ||
| 1425 | exitcode |= (waitstatus != 0); | ||
| 1426 | |||
| 1427 | bb_error_msg("pty_to_net test completed: %d", exitcode); | ||
| 1428 | return exitcode; | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | static int test_pty_to_net_data_integrity(void) | ||
| 1432 | { | ||
| 1433 | enum { TESTDATA_SIZE = 16 * 1024 * 1024 }; | ||
| 1434 | unsigned char *input_data; | ||
| 1435 | unsigned char *expected_data; | ||
| 1436 | int input_size; | ||
| 1437 | int expected_size; | ||
| 1438 | uint32_t randbits; | ||
| 1439 | uint8_t expected_hash32[32]; | ||
| 1440 | uint8_t hash_from_net32[32]; | ||
| 1441 | int sock_pair[2]; | ||
| 1442 | char tty_name[GETPTY_BUFSIZE]; | ||
| 1443 | int ptyfd; | ||
| 1444 | int i; | ||
| 1445 | int waitstatus, exitcode; | ||
| 1446 | |||
| 1447 | bb_simple_info_msg("selftest: pty_to_net"); | ||
| 1448 | |||
| 1449 | /* Allocate buffer */ | ||
| 1450 | input_data = xzalloc(TESTDATA_SIZE); | ||
| 1451 | |||
| 1452 | /* Generate random data - this time we want lots of 0xFF (IAC) bytes | ||
| 1453 | * to test IAC escaping (IAC → IAC IAC) | ||
| 1454 | */ | ||
| 1455 | randbits = 0; | ||
| 1456 | for (i = 0; i < TESTDATA_SIZE;) { | ||
| 1457 | unsigned _32possibilities; | ||
| 1458 | if (randbits == 0) | ||
| 1459 | randbits = random(); | ||
| 1460 | _32possibilities = (randbits & 0x1f); | ||
| 1461 | randbits >>= 5; | ||
| 1462 | |||
| 1463 | if (_32possibilities == 1 && (randbits & 0x7f) == 0) { | ||
| 1464 | /* Long run of printables (1 in ~4k prob) */ | ||
| 1465 | if (i < TESTDATA_SIZE - 4*1024) { | ||
| 1466 | int end = i + ((4*1024 - 1) & random()); | ||
| 1467 | while (i < end) | ||
| 1468 | input_data[i++] = (random() & 0x3f) + 0x20; | ||
| 1469 | continue; | ||
| 1470 | } | ||
| 1471 | } | ||
| 1472 | if (_32possibilities == 1) { | ||
| 1473 | /* IAC byte: 1 in 32 prob */ | ||
| 1474 | input_data[i++] = IAC; | ||
| 1475 | } else { | ||
| 1476 | input_data[i++] = random(); | ||
| 1477 | } | ||
| 1478 | } | ||
| 1479 | input_size = TESTDATA_SIZE; | ||
| 1480 | bb_error_msg("generated %d bytes of test data", input_size); | ||
| 1481 | |||
| 1482 | /* Allocate expected output buffer (worst case: all IACs doubled) */ | ||
| 1483 | expected_data = xmalloc(TESTDATA_SIZE * 2); | ||
| 1484 | |||
| 1485 | /* Process input_data simulating pty_to_net transformations: | ||
| 1486 | * - IAC (0xFF) → IAC IAC (doubled) | ||
| 1487 | * - All other bytes pass through unchanged | ||
| 1488 | */ | ||
| 1489 | expected_size = 0; | ||
| 1490 | for (i = 0; i < input_size; i++) { | ||
| 1491 | if (input_data[i] == IAC) { | ||
| 1492 | expected_data[expected_size++] = IAC; | ||
| 1493 | expected_data[expected_size++] = IAC; | ||
| 1494 | } else { | ||
| 1495 | expected_data[expected_size++] = input_data[i]; | ||
| 1496 | } | ||
| 1497 | } | ||
| 1498 | |||
| 1499 | sha256_block(expected_data, expected_size, expected_hash32); | ||
| 1500 | bb_error_msg("expected %d bytes after pty_to_net processing. hash:%s", | ||
| 1501 | expected_size, bin_to_hex(expected_hash32, 32)); | ||
| 1502 | |||
| 1503 | #if SAVE_PTY2NET_EXPECTED | ||
| 1504 | { | ||
| 1505 | int fd = xopen("expected.bin", O_WRONLY | O_CREAT | O_TRUNC); | ||
| 1506 | xwrite(fd, expected_data, expected_size); | ||
| 1507 | close(fd); | ||
| 1508 | bb_error_msg("wrote expected.bin"); | ||
| 1509 | } | ||
| 1510 | #endif | ||
| 1511 | |||
| 1512 | free(expected_data); | ||
| 1513 | expected_data = NULL; | ||
| 1514 | |||
| 1515 | /* Create socketpair for network side */ | ||
| 1516 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) < 0) | ||
| 1517 | bb_simple_perror_msg_and_die("socketpair"); | ||
| 1518 | |||
| 1519 | /* Create pty pair */ | ||
| 1520 | ptyfd = xgetpty(tty_name); | ||
| 1521 | |||
| 1522 | fflush_all(); | ||
| 1523 | if (xfork() == 0) { | ||
| 1524 | /* Fork writer child: writes data to PTY slave */ | ||
| 1525 | int slave_fd; | ||
| 1526 | int written = 0; | ||
| 1527 | struct termios termbuf; | ||
| 1528 | |||
| 1529 | applet_name = "pty writer"; | ||
| 1530 | |||
| 1531 | close(sock_pair[0]); | ||
| 1532 | close(sock_pair[1]); | ||
| 1533 | close(ptyfd); | ||
| 1534 | |||
| 1535 | slave_fd = xopen(tty_name, O_RDWR); | ||
| 1536 | //setsid(); /* lose ctty */ | ||
| 1537 | |||
| 1538 | /* Put PTY in raw mode */ | ||
| 1539 | tcgetattr(slave_fd, &termbuf); | ||
| 1540 | cfmakeraw(&termbuf); | ||
| 1541 | tcsetattr(slave_fd, TCSANOW, &termbuf); | ||
| 1542 | |||
| 1543 | while (written < input_size) { | ||
| 1544 | int n = (random() & 4095) + 1; /* 1 to 4096 bytes */ | ||
| 1545 | if (n > input_size - written) | ||
| 1546 | n = input_size - written; | ||
| 1547 | n = safe_write(slave_fd, input_data + written, n); | ||
| 1548 | if (n < 0) | ||
| 1549 | bb_perror_msg_and_die("%s:%d: write error", __func__, __LINE__); | ||
| 1550 | written += n; | ||
| 1551 | /* Small random delay */ | ||
| 1552 | if ((random() & 0xf) == 0) | ||
| 1553 | usleep(random() & 0x3ff); | ||
| 1554 | } | ||
| 1555 | _exit(0); | ||
| 1556 | } | ||
| 1557 | |||
| 1558 | free(input_data); | ||
| 1559 | input_data = NULL; | ||
| 1560 | |||
| 1561 | if (xfork() == 0) { | ||
| 1562 | /* Fork reader child: reads from network socket */ | ||
| 1563 | int n; | ||
| 1564 | unsigned char read_buf[8192]; | ||
| 1565 | int total_read = 0; | ||
| 1566 | sha256_ctx_t ctx; | ||
| 1567 | #if SAVE_PTY2NET_ACTUAL | ||
| 1568 | int out_fd = xopen("actual.bin", O_WRONLY | O_CREAT | O_TRUNC); | ||
| 1569 | #endif | ||
| 1570 | applet_name = "net reader"; | ||
| 1571 | |||
| 1572 | close(sock_pair[1]); /* close write end */ | ||
| 1573 | close(ptyfd); | ||
| 1574 | |||
| 1575 | sha256_begin(&ctx); | ||
| 1576 | for (;;) { | ||
| 1577 | n = safe_read(sock_pair[0], read_buf, sizeof(read_buf)); | ||
| 1578 | if (n <= 0) { | ||
| 1579 | if (n < 0) | ||
| 1580 | bb_simple_perror_msg("read"); | ||
| 1581 | break; /* EOF or error */ | ||
| 1582 | } | ||
| 1583 | // To test what writer sees if we close read end: | ||
| 1584 | //shutdown(sock_pair[0], SHUT_RD); | ||
| 1585 | //sleep(2); | ||
| 1586 | //_exit(1); | ||
| 1587 | #if SAVE_PTY2NET_ACTUAL | ||
| 1588 | xwrite(out_fd, read_buf, n); | ||
| 1589 | #endif | ||
| 1590 | sha256_hash(&ctx, read_buf, n); | ||
| 1591 | total_read += n; | ||
| 1592 | } | ||
| 1593 | |||
| 1594 | sha256_end(&ctx, hash_from_net32); | ||
| 1595 | n = memcmp(hash_from_net32, expected_hash32, 32); | ||
| 1596 | bb_error_msg("received %d bytes, hash:%s%s", | ||
| 1597 | total_read, bin_to_hex(hash_from_net32, 32), n ? " MISMATCH" : ""); | ||
| 1598 | _exit(!!n); | ||
| 1599 | } | ||
| 1600 | |||
| 1601 | close(sock_pair[0]); /* close read end */ | ||
| 1602 | |||
| 1603 | /* Parent: run the pty_to_net logic */ | ||
| 1604 | { | ||
| 1605 | pty_to_net_t *conn; | ||
| 1606 | ioloop_state_t *io; | ||
| 1607 | int rc; | ||
| 1608 | |||
| 1609 | io = new_ioloop_state(); | ||
| 1610 | |||
| 1611 | conn = new_pty_to_net(ptyfd, sock_pair[1]); | ||
| 1612 | ioloop_insert_conn(io, (void*)conn); | ||
| 1613 | |||
| 1614 | rc = ioloop_run(io); | ||
| 1615 | bb_error_msg("ioloop_run:%d", rc); | ||
| 1616 | |||
| 1617 | /* This should be done inside ioloop. Check where possible: */ | ||
| 1618 | //free_connection(conn); | ||
| 1619 | if (close(sock_pair[1]) == 0) /* should be EBADF */ | ||
| 1620 | bb_error_msg_and_die("BUG: net write side wasn't closed"); | ||
| 1621 | if (close(ptyfd) == 0) | ||
| 1622 | bb_error_msg_and_die("BUG: pty read side wasn't closed"); | ||
| 1623 | |||
| 1624 | free_ioloop_state(io); | ||
| 1625 | } | ||
| 1626 | |||
| 1627 | /* Wait for all children */ | ||
| 1628 | exitcode = 0; | ||
| 1629 | while (safe_waitpid(-1, &waitstatus, 0) > 0) | ||
| 1630 | exitcode |= (waitstatus != 0); | ||
| 1631 | |||
| 1632 | bb_error_msg("pty_to_net test completed: %d", exitcode); | ||
| 1633 | return exitcode; | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | static void selftest(void) | ||
| 1637 | { | ||
| 1638 | int exitcode = 0; | ||
| 1639 | srandom(12345); | ||
| 1640 | if (SELFTEST_VHANGUP) exitcode |= test_vhangup(); | ||
| 1641 | if (SELFTEST_NET2PTY) exitcode |= test_net_to_pty_data_integrity(); | ||
| 1642 | if (SELFTEST_PTY2NET) exitcode |= test_pty_to_net_data_integrity(); | ||
| 1643 | bb_error_msg("selftest completed: %d", exitcode); | ||
| 1644 | _exit(exitcode); | ||
| 1645 | } | ||
| 1646 | #endif | ||
| 1647 | |||
| 660 | int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 1648 | int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| 661 | int telnetd_main(int argc UNUSED_PARAM, char **argv) | 1649 | int telnetd_main(int argc UNUSED_PARAM, char **argv) |
| 662 | { | 1650 | { |
| 663 | fd_set rdfdset, wrfdset; | ||
| 664 | unsigned opt; | 1651 | unsigned opt; |
| 665 | int count; | ||
| 666 | struct tsession *ts; | ||
| 667 | #if ENABLE_FEATURE_TELNETD_STANDALONE | 1652 | #if ENABLE_FEATURE_TELNETD_STANDALONE |
| 668 | #define IS_INETD (opt & OPT_INETD) | 1653 | #define IS_INETD (opt & OPT_INETD) |
| 669 | int master_fd = master_fd; /* for compiler */ | ||
| 670 | int sec_linger = sec_linger; | ||
| 671 | char *opt_bindaddr = NULL; | 1654 | char *opt_bindaddr = NULL; |
| 672 | char *opt_portnbr; | 1655 | char *opt_portnbr; |
| 673 | #else | 1656 | #else |
| 674 | enum { | 1657 | enum { IS_INETD = 1 }; |
| 675 | IS_INETD = 1, | ||
| 676 | master_fd = -1, | ||
| 677 | }; | ||
| 678 | #endif | 1658 | #endif |
| 679 | INIT_G(); | 1659 | INIT_G(); |
| 680 | 1660 | ||
| 681 | /* Even if !STANDALONE, we accept (and ignore) -i, thus people | 1661 | /* Even if !STANDALONE, we accept (and ignore) -i, thus people |
| 682 | * don't need to guess whether it's ok to pass -i to us */ | 1662 | * don't need to guess whether it's ok to pass -i to us */ |
| 683 | opt = getopt32(argv, "^" | 1663 | opt = getopt32(argv, "^" |
| 1664 | IF_FEATURE_TELNETD_SELFTEST_DEBUG("@") | ||
| 684 | "f:l:Ki" | 1665 | "f:l:Ki" |
| 685 | IF_FEATURE_TELNETD_STANDALONE("p:b:F") | 1666 | IF_FEATURE_TELNETD_STANDALONE("p:b:F") |
| 686 | IF_FEATURE_TELNETD_INETD_WAIT("Sw:+") /* -w NUM */ | 1667 | IF_FEATURE_TELNETD_INETD_WAIT("Sw:+v") /* -w NUM */ |
| 687 | "\0" | 1668 | "\0" |
| 688 | /* -w implies -F. -w and -i don't mix */ | 1669 | /* -w implies -F. -w and -i don't mix. -v counter */ |
| 689 | IF_FEATURE_TELNETD_INETD_WAIT("wF:i--w:w--i"), | 1670 | IF_FEATURE_TELNETD_INETD_WAIT("wF:i--w:w--i:vv") |
| 690 | &G.issuefile, &G.loginpath | 1671 | , &G.issuefile, &G.loginpath |
| 691 | IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr) | 1672 | IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr) |
| 692 | IF_FEATURE_TELNETD_INETD_WAIT(, &sec_linger) | 1673 | IF_FEATURE_TELNETD_INETD_WAIT(, &G.io.max_timeout) |
| 1674 | , &G.verbose | ||
| 693 | ); | 1675 | ); |
| 1676 | |||
| 1677 | #if ENABLE_FEATURE_TELNETD_SELFTEST_DEBUG | ||
| 1678 | if (opt & 1) { /* -@ is first option */ | ||
| 1679 | selftest(); /* does not return */ | ||
| 1680 | } | ||
| 1681 | opt >>= 1; /* Shift away the selftest bit */ | ||
| 1682 | #endif | ||
| 694 | if (!IS_INETD /*&& !re_execed*/) { | 1683 | if (!IS_INETD /*&& !re_execed*/) { |
| 695 | /* inform that we start in standalone mode? | 1684 | /* inform that we start in standalone mode? |
| 696 | * May be useful when people forget to give -i */ | 1685 | * May be useful when people forget to give -i */ |
| @@ -706,28 +1695,28 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) | |||
| 706 | openlog(applet_name, LOG_PID, LOG_DAEMON); | 1695 | openlog(applet_name, LOG_PID, LOG_DAEMON); |
| 707 | logmode = LOGMODE_SYSLOG; | 1696 | logmode = LOGMODE_SYSLOG; |
| 708 | } | 1697 | } |
| 709 | #if ENABLE_FEATURE_TELNETD_STANDALONE | 1698 | |
| 710 | if (IS_INETD) { | 1699 | if (IS_INETD) { |
| 711 | G.sessions = make_new_session(0); | 1700 | make_new_session(&G.io, 0); |
| 712 | if (!G.sessions) /* pty opening or vfork problem, exit */ | 1701 | } |
| 713 | return 1; /* make_new_session printed error message */ | 1702 | #if ENABLE_FEATURE_TELNETD_STANDALONE |
| 714 | } else { | 1703 | else { |
| 715 | master_fd = 0; | 1704 | int master_fd = 0; |
| 1705 | /* For -w SEC, listening socket is 0. Otherwise: */ | ||
| 716 | if (!(opt & OPT_WAIT)) { | 1706 | if (!(opt & OPT_WAIT)) { |
| 717 | unsigned portnbr = CONFIG_FEATURE_TELNETD_PORT_DEFAULT; | 1707 | unsigned portnbr = CONFIG_FEATURE_TELNETD_PORT_DEFAULT; |
| 718 | if (opt & OPT_PORT) | 1708 | if (opt & OPT_PORT) |
| 719 | portnbr = xatou16(opt_portnbr); | 1709 | portnbr = xatou16(opt_portnbr); |
| 1710 | /* If someone would run "telnetd 0>&-", we can get terribly confused. */ | ||
| 1711 | /* Nake sure master_fd, or accept() result fd, etc, cant become 0 or 1: */ | ||
| 1712 | bb_sanitize_stdio(); | ||
| 720 | master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr); | 1713 | master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr); |
| 721 | xlisten(master_fd, 1); | 1714 | xlisten(master_fd, 1); |
| 722 | } | 1715 | } |
| 723 | close_on_exec_on(master_fd); | 1716 | close_on_exec_on(master_fd); |
| 1717 | ioloop_insert_conn(&G.io, (void *)new_accept_conn(master_fd)); | ||
| 724 | } | 1718 | } |
| 725 | #else | ||
| 726 | G.sessions = make_new_session(); | ||
| 727 | if (!G.sessions) /* pty opening or vfork problem, exit */ | ||
| 728 | return 1; /* make_new_session printed error message */ | ||
| 729 | #endif | 1719 | #endif |
| 730 | |||
| 731 | /* We don't want to die if just one session is broken */ | 1720 | /* We don't want to die if just one session is broken */ |
| 732 | signal(SIGPIPE, SIG_IGN); | 1721 | signal(SIGPIPE, SIG_IGN); |
| 733 | 1722 | ||
| @@ -736,189 +1725,30 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) | |||
| 736 | else /* prevent dead children from becoming zombies */ | 1725 | else /* prevent dead children from becoming zombies */ |
| 737 | signal(SIGCHLD, SIG_IGN); | 1726 | signal(SIGCHLD, SIG_IGN); |
| 738 | 1727 | ||
| 739 | /* | ||
| 740 | This is how the buffers are used. The arrows indicate data flow. | ||
| 741 | |||
| 742 | +-------+ wridx1++ +------+ rdidx1++ +----------+ | ||
| 743 | | | <-------------- | buf1 | <-------------- | | | ||
| 744 | | | size1-- +------+ size1++ | | | ||
| 745 | | pty | | socket | | ||
| 746 | | | rdidx2++ +------+ wridx2++ | | | ||
| 747 | | | --------------> | buf2 | --------------> | | | ||
| 748 | +-------+ size2++ +------+ size2-- +----------+ | ||
| 749 | |||
| 750 | size1: "how many bytes are buffered for pty between rdidx1 and wridx1?" | ||
| 751 | size2: "how many bytes are buffered for socket between rdidx2 and wridx2?" | ||
| 752 | |||
| 753 | Each session has got two buffers. Buffers are circular. If sizeN == 0, | ||
| 754 | buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases | ||
| 755 | rdidxN == wridxN. | ||
| 756 | */ | ||
| 757 | again: | ||
| 758 | FD_ZERO(&rdfdset); | ||
| 759 | FD_ZERO(&wrfdset); | ||
| 760 | |||
| 761 | /* Select on the master socket, all telnet sockets and their | ||
| 762 | * ptys if there is room in their session buffers. | ||
| 763 | * NB: scalability problem: we recalculate entire bitmap | ||
| 764 | * before each select. Can be a problem with 500+ connections. */ | ||
| 765 | ts = G.sessions; | ||
| 766 | while (ts) { | ||
| 767 | struct tsession *next = ts->next; /* in case we free ts */ | ||
| 768 | if (ts->shell_pid == -1) { | ||
| 769 | /* Child died and we detected that */ | ||
| 770 | free_session(ts); | ||
| 771 | } else { | ||
| 772 | if (ts->size1 > 0) /* can write to pty */ | ||
| 773 | FD_SET(ts->ptyfd, &wrfdset); | ||
| 774 | if (ts->size1 < BUFSIZE) /* can read from socket */ | ||
| 775 | FD_SET(ts->sockfd_read, &rdfdset); | ||
| 776 | if (ts->size2 > 0) /* can write to socket */ | ||
| 777 | FD_SET(ts->sockfd_write, &wrfdset); | ||
| 778 | if (ts->size2 < BUFSIZE) /* can read from pty */ | ||
| 779 | FD_SET(ts->ptyfd, &rdfdset); | ||
| 780 | } | ||
| 781 | ts = next; | ||
| 782 | } | ||
| 783 | if (!IS_INETD) { | ||
| 784 | FD_SET(master_fd, &rdfdset); | ||
| 785 | /* This is needed because free_session() does not | ||
| 786 | * take master_fd into account when it finds new | ||
| 787 | * maxfd among remaining fd's */ | ||
| 788 | if (master_fd > G.maxfd) | ||
| 789 | G.maxfd = master_fd; | ||
| 790 | } | ||
| 791 | |||
| 792 | { | ||
| 793 | struct timeval *tv_ptr = NULL; | ||
| 794 | #if ENABLE_FEATURE_TELNETD_INETD_WAIT | 1728 | #if ENABLE_FEATURE_TELNETD_INETD_WAIT |
| 795 | struct timeval tv; | 1729 | if (G.io.max_timeout > UINT_MAX / 1000000) /* -w TMOUT capped at 4294 sec */ |
| 796 | if ((opt & OPT_WAIT) && !G.sessions) { | 1730 | G.io.max_timeout = (UINT_MAX / 1000000); |
| 797 | tv.tv_sec = sec_linger; | 1731 | G.io.max_timeout *= 1000000; |
| 798 | tv.tv_usec = 0; | ||
| 799 | tv_ptr = &tv; | ||
| 800 | } | ||
| 801 | #endif | 1732 | #endif |
| 802 | count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr); | 1733 | G.io.flags |= IOLOOP_FLAG_EXIT_IF_TIMEOUT; |
| 803 | } | 1734 | for (;;) { |
| 804 | if (count == 0) /* "telnetd -w SEC" timed out */ | 1735 | int rc = ioloop_run(&G.io); |
| 805 | return 0; | 1736 | if (rc == IOLOOP_NO_CONNS) { |
| 806 | if (count < 0) | 1737 | /* inetd mode (not -w), and it's closed now */ |
| 807 | goto again; /* EINTR or ENOMEM */ | 1738 | dbg_close("connection is closed"); |
| 808 | 1739 | break; | |
| 809 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 810 | /* Check for and accept new sessions */ | ||
| 811 | if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { | ||
| 812 | int fd; | ||
| 813 | struct tsession *new_ts; | ||
| 814 | |||
| 815 | fd = accept(master_fd, NULL, NULL); | ||
| 816 | if (fd < 0) | ||
| 817 | goto again; | ||
| 818 | close_on_exec_on(fd); | ||
| 819 | |||
| 820 | /* Create a new session and link it into active list */ | ||
| 821 | new_ts = make_new_session(fd); | ||
| 822 | if (new_ts) { | ||
| 823 | new_ts->next = G.sessions; | ||
| 824 | G.sessions = new_ts; | ||
| 825 | } else { | ||
| 826 | close(fd); | ||
| 827 | } | ||
| 828 | } | ||
| 829 | #endif | ||
| 830 | |||
| 831 | /* Then check for data tunneling */ | ||
| 832 | ts = G.sessions; | ||
| 833 | while (ts) { /* For all sessions... */ | ||
| 834 | struct tsession *next = ts->next; /* in case we free ts */ | ||
| 835 | |||
| 836 | if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) { | ||
| 837 | /* Write to pty from buffer 1 */ | ||
| 838 | count = safe_write_to_pty_decode_iac(ts); | ||
| 839 | if (count < 0) { | ||
| 840 | if (errno == EAGAIN) | ||
| 841 | goto skip1; | ||
| 842 | goto kill_session; | ||
| 843 | } | ||
| 844 | } | 1740 | } |
| 845 | skip1: | 1741 | /* else: timeout */ |
| 846 | if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) { | 1742 | #if ENABLE_FEATURE_TELNETD_INETD_WAIT |
| 847 | /* Write to socket from buffer 2 */ | 1743 | if (1 /* rc == IOLOOP_TIMEOUT */ |
| 848 | count = MIN(BUFSIZE - ts->wridx2, ts->size2); | 1744 | && (!G.io.conns || !G.io.conns->next) /* only accept conn exists */ |
| 849 | count = safe_write_double_iac(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count); | 1745 | ) { |
| 850 | if (count < 0) { | 1746 | dbg_close("-w TIMEOUT:%dus", G.io.max_timeout); |
| 851 | if (errno == EAGAIN) | 1747 | break; |
| 852 | goto skip2; | ||
| 853 | goto kill_session; | ||
| 854 | } | ||
| 855 | ts->wridx2 += count; | ||
| 856 | if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 857 | ts->wridx2 = 0; | ||
| 858 | ts->size2 -= count; | ||
| 859 | if (ts->size2 == 0) { | ||
| 860 | ts->rdidx2 = 0; | ||
| 861 | ts->wridx2 = 0; | ||
| 862 | } | ||
| 863 | } | 1748 | } |
| 864 | skip2: | 1749 | #endif |
| 865 | 1750 | dbg("timeout was due to temporary EIO: continue looping"); | |
| 866 | if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) { | 1751 | } |
| 867 | /* Read from socket to buffer 1 */ | 1752 | dbg_close("terminating"); |
| 868 | count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); | 1753 | return EXIT_SUCCESS; |
| 869 | count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count); | ||
| 870 | if (count <= 0) { | ||
| 871 | if (count < 0 && errno == EAGAIN) | ||
| 872 | goto skip3; | ||
| 873 | goto kill_session; | ||
| 874 | } | ||
| 875 | /* Ignore trailing NUL if it is there */ | ||
| 876 | if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) { | ||
| 877 | --count; | ||
| 878 | } | ||
| 879 | ts->size1 += count; | ||
| 880 | ts->rdidx1 += count; | ||
| 881 | if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 882 | ts->rdidx1 = 0; | ||
| 883 | } | ||
| 884 | skip3: | ||
| 885 | if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) { | ||
| 886 | /* Read from pty to buffer 2 */ | ||
| 887 | int eio = 0; | ||
| 888 | read_pty: | ||
| 889 | count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); | ||
| 890 | count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count); | ||
| 891 | if (count <= 0) { | ||
| 892 | if (count < 0) { | ||
| 893 | if (errno == EAGAIN) | ||
| 894 | goto skip4; | ||
| 895 | /* login process might call vhangup(), | ||
| 896 | * which causes intermittent EIOs on read above | ||
| 897 | * (observed on kernel 4.12.0). Try up to 10 ms. | ||
| 898 | */ | ||
| 899 | if (errno == EIO && eio < 10) { | ||
| 900 | eio++; | ||
| 901 | //bb_error_msg("EIO pty %u", eio); | ||
| 902 | usleep(1000); | ||
| 903 | goto read_pty; | ||
| 904 | } | ||
| 905 | } | ||
| 906 | goto kill_session; | ||
| 907 | } | ||
| 908 | ts->size2 += count; | ||
| 909 | ts->rdidx2 += count; | ||
| 910 | if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 911 | ts->rdidx2 = 0; | ||
| 912 | } | ||
| 913 | skip4: | ||
| 914 | ts = next; | ||
| 915 | continue; | ||
| 916 | kill_session: | ||
| 917 | if (ts->shell_pid > 0) | ||
| 918 | update_utmp_DEAD_PROCESS(ts->shell_pid); | ||
| 919 | free_session(ts); | ||
| 920 | ts = next; | ||
| 921 | } | ||
| 922 | |||
| 923 | goto again; | ||
| 924 | } | 1754 | } |
diff --git a/networking/telnetd.c.OLD b/networking/telnetd.c.OLD new file mode 100644 index 000000000..a5a783047 --- /dev/null +++ b/networking/telnetd.c.OLD | |||
| @@ -0,0 +1,924 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 2 | /* | ||
| 3 | * Simple telnet server | ||
| 4 | * Bjorn Wesen, Axis Communications AB (bjornw@axis.com) | ||
| 5 | * | ||
| 6 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
| 7 | * | ||
| 8 | * --------------------------------------------------------------------------- | ||
| 9 | * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN | ||
| 10 | **************************************************************************** | ||
| 11 | * | ||
| 12 | * The telnetd manpage says it all: | ||
| 13 | * | ||
| 14 | * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for | ||
| 15 | * a client, then creating a login process which has the slave side of the | ||
| 16 | * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the | ||
| 17 | * master side of the pseudo-terminal, implementing the telnet protocol and | ||
| 18 | * passing characters between the remote client and the login process. | ||
| 19 | * | ||
| 20 | * Vladimir Oleynik <dzo@simtreas.ru> 2001 | ||
| 21 | * Set process group corrections, initial busybox port | ||
| 22 | */ | ||
| 23 | //config:config TELNETD | ||
| 24 | //config: bool "telnetd (13 kb)" | ||
| 25 | //config: default y | ||
| 26 | //config: select FEATURE_SYSLOG | ||
| 27 | //config: help | ||
| 28 | //config: A daemon for the TELNET protocol, allowing you to log onto the host | ||
| 29 | //config: running the daemon. Please keep in mind that the TELNET protocol | ||
| 30 | //config: sends passwords in plain text. If you can't afford the space for an | ||
| 31 | //config: SSH daemon and you trust your network, you may say 'y' here. As a | ||
| 32 | //config: more secure alternative, you should seriously consider installing the | ||
| 33 | //config: very small Dropbear SSH daemon instead: | ||
| 34 | //config: http://matt.ucc.asn.au/dropbear/dropbear.html | ||
| 35 | //config: | ||
| 36 | //config: Note that for busybox telnetd to work you need several things: | ||
| 37 | //config: First of all, your kernel needs: | ||
| 38 | //config: CONFIG_UNIX98_PTYS=y | ||
| 39 | //config: | ||
| 40 | //config: Next, you need a /dev/pts directory on your root filesystem: | ||
| 41 | //config: | ||
| 42 | //config: $ ls -ld /dev/pts | ||
| 43 | //config: drwxr-xr-x 2 root root 0 Sep 23 13:21 /dev/pts/ | ||
| 44 | //config: | ||
| 45 | //config: Next you need the pseudo terminal master multiplexer /dev/ptmx: | ||
| 46 | //config: | ||
| 47 | //config: $ ls -la /dev/ptmx | ||
| 48 | //config: crw-rw-rw- 1 root tty 5, 2 Sep 23 13:55 /dev/ptmx | ||
| 49 | //config: | ||
| 50 | //config: Any /dev/ttyp[0-9]* files you may have can be removed. | ||
| 51 | //config: Next, you need to mount the devpts filesystem on /dev/pts using: | ||
| 52 | //config: | ||
| 53 | //config: mount -t devpts devpts /dev/pts | ||
| 54 | //config: | ||
| 55 | //config: You need to be sure that busybox has LOGIN and | ||
| 56 | //config: FEATURE_SUID enabled. And finally, you should make | ||
| 57 | //config: certain that busybox has been installed setuid root: | ||
| 58 | //config: | ||
| 59 | //config: chown root.root /bin/busybox | ||
| 60 | //config: chmod 4755 /bin/busybox | ||
| 61 | //config: | ||
| 62 | //config: with all that done, telnetd _should_ work.... | ||
| 63 | //config: | ||
| 64 | //config:config FEATURE_TELNETD_STANDALONE | ||
| 65 | //config: bool "Support standalone telnetd (not inetd only)" | ||
| 66 | //config: default y | ||
| 67 | //config: depends on TELNETD | ||
| 68 | //config: help | ||
| 69 | //config: Selecting this will make telnetd able to run standalone. | ||
| 70 | //config: | ||
| 71 | //config:config FEATURE_TELNETD_PORT_DEFAULT | ||
| 72 | //config: int "Default port" | ||
| 73 | //config: default 23 | ||
| 74 | //config: range 1 65535 | ||
| 75 | //config: depends on FEATURE_TELNETD_STANDALONE | ||
| 76 | //config: | ||
| 77 | //config:config FEATURE_TELNETD_INETD_WAIT | ||
| 78 | //config: bool "Support -w SEC option (inetd wait mode)" | ||
| 79 | //config: default y | ||
| 80 | //config: depends on FEATURE_TELNETD_STANDALONE | ||
| 81 | //config: help | ||
| 82 | //config: This option allows you to run telnetd in "inet wait" mode. | ||
| 83 | //config: Example inetd.conf line (note "wait", not usual "nowait"): | ||
| 84 | //config: | ||
| 85 | //config: telnet stream tcp wait root /bin/telnetd telnetd -w10 | ||
| 86 | //config: | ||
| 87 | //config: In this example, inetd passes _listening_ socket_ as fd 0 | ||
| 88 | //config: to telnetd when connection appears. | ||
| 89 | //config: telnetd will wait for connections until all existing | ||
| 90 | //config: connections are closed, and no new connections | ||
| 91 | //config: appear during 10 seconds. Then it exits, and inetd continues | ||
| 92 | //config: to listen for new connections. | ||
| 93 | //config: | ||
| 94 | //config: This option is rarely used. "tcp nowait" is much more usual | ||
| 95 | //config: way of running tcp services, including telnetd. | ||
| 96 | //config: You most probably want to say N here. | ||
| 97 | |||
| 98 | //applet:IF_TELNETD(APPLET(telnetd, BB_DIR_USR_SBIN, BB_SUID_DROP)) | ||
| 99 | |||
| 100 | //kbuild:lib-$(CONFIG_TELNETD) += telnetd.o | ||
| 101 | |||
| 102 | //usage:#define telnetd_trivial_usage | ||
| 103 | //usage: "[OPTIONS]" | ||
| 104 | //usage:#define telnetd_full_usage "\n\n" | ||
| 105 | //usage: "Handle incoming telnet connections" | ||
| 106 | //usage: IF_NOT_FEATURE_TELNETD_STANDALONE(" via inetd") "\n" | ||
| 107 | //usage: "\n -l LOGIN Exec LOGIN on connect (default /bin/login)" | ||
| 108 | //usage: "\n -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue.net" | ||
| 109 | //usage: "\n -K Close connection as soon as login exits" | ||
| 110 | //usage: "\n (normally wait until all programs close slave pty)" | ||
| 111 | //usage: IF_FEATURE_TELNETD_STANDALONE( | ||
| 112 | //usage: "\n -p PORT Port to listen on. Default "STR(CONFIG_FEATURE_TELNETD_PORT_DEFAULT) | ||
| 113 | //usage: "\n -b ADDR[:PORT] Address to bind to" | ||
| 114 | //usage: "\n -F Run in foreground" | ||
| 115 | //usage: "\n -i Inetd mode" | ||
| 116 | //usage: IF_FEATURE_TELNETD_INETD_WAIT( | ||
| 117 | //usage: "\n -w SEC Inetd 'wait' mode, linger time SEC" | ||
| 118 | //usage: "\n inetd.conf line: 23 stream tcp wait root telnetd telnetd -w10" | ||
| 119 | //usage: "\n -S Log to syslog (implied by -i or without -F and -w)" | ||
| 120 | //usage: ) | ||
| 121 | //usage: ) | ||
| 122 | |||
| 123 | #define DEBUG 0 | ||
| 124 | |||
| 125 | #include "libbb.h" | ||
| 126 | #include "common_bufsiz.h" | ||
| 127 | #include <syslog.h> | ||
| 128 | |||
| 129 | #if DEBUG | ||
| 130 | # define TELCMDS | ||
| 131 | # define TELOPTS | ||
| 132 | #endif | ||
| 133 | #include <arpa/telnet.h> | ||
| 134 | |||
| 135 | |||
| 136 | struct tsession { | ||
| 137 | struct tsession *next; | ||
| 138 | pid_t shell_pid; | ||
| 139 | int sockfd_read; | ||
| 140 | int sockfd_write; | ||
| 141 | int ptyfd; | ||
| 142 | smallint buffered_IAC_for_pty; | ||
| 143 | |||
| 144 | /* two circular buffers */ | ||
| 145 | /*char *buf1, *buf2;*/ | ||
| 146 | /*#define TS_BUF1(ts) ts->buf1*/ | ||
| 147 | /*#define TS_BUF2(ts) TS_BUF2(ts)*/ | ||
| 148 | #define TS_BUF1(ts) ((unsigned char*)(ts + 1)) | ||
| 149 | #define TS_BUF2(ts) (((unsigned char*)(ts + 1)) + BUFSIZE) | ||
| 150 | int rdidx1, wridx1, size1; | ||
| 151 | int rdidx2, wridx2, size2; | ||
| 152 | }; | ||
| 153 | |||
| 154 | /* Two buffers are directly after tsession in malloced memory. | ||
| 155 | * Make whole thing fit in 4k */ | ||
| 156 | enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 }; | ||
| 157 | |||
| 158 | |||
| 159 | /* Globals */ | ||
| 160 | struct globals { | ||
| 161 | struct tsession *sessions; | ||
| 162 | const char *loginpath; | ||
| 163 | const char *issuefile; | ||
| 164 | int maxfd; | ||
| 165 | } FIX_ALIASING; | ||
| 166 | #define G (*(struct globals*)bb_common_bufsiz1) | ||
| 167 | #define INIT_G() do { \ | ||
| 168 | setup_common_bufsiz(); \ | ||
| 169 | G.loginpath = "/bin/login"; \ | ||
| 170 | G.issuefile = "/etc/issue.net"; \ | ||
| 171 | } while (0) | ||
| 172 | |||
| 173 | |||
| 174 | /* Write some buf1 data to pty, processing IACs. | ||
| 175 | * Update wridx1 and size1. Return < 0 on error. | ||
| 176 | * Buggy if IAC is present but incomplete: skips them. | ||
| 177 | */ | ||
| 178 | static ssize_t | ||
| 179 | safe_write_to_pty_decode_iac(struct tsession *ts) | ||
| 180 | { | ||
| 181 | unsigned wr; | ||
| 182 | ssize_t rc; | ||
| 183 | unsigned char *buf; | ||
| 184 | unsigned char *found; | ||
| 185 | |||
| 186 | buf = TS_BUF1(ts) + ts->wridx1; | ||
| 187 | wr = MIN(BUFSIZE - ts->wridx1, ts->size1); | ||
| 188 | /* wr is at least 1 here */ | ||
| 189 | |||
| 190 | if (ts->buffered_IAC_for_pty) { | ||
| 191 | /* Last time we stopped on a "dangling" IAC byte. | ||
| 192 | * We removed it from the buffer back then. | ||
| 193 | * Now pretend it's still there, and jump to IAC processing. | ||
| 194 | */ | ||
| 195 | ts->buffered_IAC_for_pty = 0; | ||
| 196 | wr++; | ||
| 197 | ts->size1++; | ||
| 198 | buf--; /* Yes, this can point before the buffer. It's ok */ | ||
| 199 | ts->wridx1--; | ||
| 200 | goto handle_iac; | ||
| 201 | } | ||
| 202 | |||
| 203 | found = memchr(buf, IAC, wr); | ||
| 204 | if (found != buf) { | ||
| 205 | /* There is a "prefix" of non-IAC chars. | ||
| 206 | * Write only them, and return. | ||
| 207 | */ | ||
| 208 | if (found) | ||
| 209 | wr = found - buf; | ||
| 210 | |||
| 211 | /* We map \r\n ==> \r for pragmatic reasons: | ||
| 212 | * many client implementations send \r\n when | ||
| 213 | * the user hits the CarriageReturn key. | ||
| 214 | * See RFC 1123 3.3.1 Telnet End-of-Line Convention. | ||
| 215 | */ | ||
| 216 | rc = wr; | ||
| 217 | found = memchr(buf, '\r', wr); | ||
| 218 | if (found) | ||
| 219 | rc = found - buf + 1; | ||
| 220 | rc = safe_write(ts->ptyfd, buf, rc); | ||
| 221 | if (rc <= 0) | ||
| 222 | return rc; | ||
| 223 | if (rc < wr /* don't look past available data */ | ||
| 224 | && buf[rc-1] == '\r' /* need this: imagine that write was _short_ */ | ||
| 225 | && (buf[rc] == '\n' || buf[rc] == '\0') | ||
| 226 | ) { | ||
| 227 | rc++; | ||
| 228 | } | ||
| 229 | goto update_and_return; | ||
| 230 | } | ||
| 231 | |||
| 232 | /* buf starts with IAC char. Process that sequence. | ||
| 233 | * Example: we get this from our own (bbox) telnet client: | ||
| 234 | * read(5, "\377\374\1""\377\373\37""\377\372\37\0\262\0@\377\360""\377\375\1""\377\375\3"): | ||
| 235 | * IAC WONT ECHO, IAC WILL NAWS, IAC SB NAWS <cols> <rows> IAC SE, IAC DO SGA | ||
| 236 | * Another example (telnet-0.17 from old-netkit): | ||
| 237 | * read(4, "\377\375\3""\377\373\30""\377\373\37""\377\373 ""\377\373!""\377\373\"""\377\373'" | ||
| 238 | * "\377\375\5""\377\373#""\377\374\1""\377\372\37\0\257\0I\377\360""\377\375\1"): | ||
| 239 | * IAC DO SGA, IAC WILL TTYPE, IAC WILL NAWS, IAC WILL TSPEED, IAC WILL LFLOW, IAC WILL LINEMODE, IAC WILL NEW_ENVIRON, | ||
| 240 | * IAC DO STATUS, IAC WILL XDISPLOC, IAC WONT ECHO, IAC SB NAWS <cols> <rows> IAC SE, IAC DO ECHO | ||
| 241 | */ | ||
| 242 | if (wr <= 1) { | ||
| 243 | /* Only the single IAC byte is in the buffer, eat it | ||
| 244 | * and set a flag "process the rest of the sequence | ||
| 245 | * next time we are here". | ||
| 246 | */ | ||
| 247 | //bb_error_msg("dangling IAC!"); | ||
| 248 | ts->buffered_IAC_for_pty = 1; | ||
| 249 | rc = 1; | ||
| 250 | goto update_and_return; | ||
| 251 | } | ||
| 252 | |||
| 253 | handle_iac: | ||
| 254 | /* 2-byte commands (240..250 and 255): | ||
| 255 | * IAC IAC (255) Literal 255. Supported. | ||
| 256 | * IAC SE (240) End of subnegotiation. Treated as NOP. | ||
| 257 | * IAC NOP (241) NOP. Supported. | ||
| 258 | * IAC BRK (243) Break. Like serial line break. TODO via tcsendbreak()? | ||
| 259 | * IAC AYT (246) Are you there. | ||
| 260 | * These don't look useful: | ||
| 261 | * IAC DM (242) Data mark. What is this? | ||
| 262 | * IAC IP (244) Suspend, interrupt or abort the process. (Ancient cousin of ^C). | ||
| 263 | * IAC AO (245) Abort output. "You can continue running, but do not send me the output". | ||
| 264 | * IAC EC (247) Erase character. The receiver should delete the last received char. | ||
| 265 | * IAC EL (248) Erase line. The receiver should delete everything up tp last newline. | ||
| 266 | * IAC GA (249) Go ahead. For half-duplex lines: "now you talk". | ||
| 267 | * Implemented only as part of NAWS: | ||
| 268 | * IAC SB (250) Subnegotiation of an option follows. | ||
| 269 | */ | ||
| 270 | if (buf[1] == IAC) { | ||
| 271 | /* Literal 255 (emacs M-DEL) */ | ||
| 272 | //bb_error_msg("255!"); | ||
| 273 | rc = safe_write(ts->ptyfd, &buf[1], 1); | ||
| 274 | /* | ||
| 275 | * If we went through buffered_IAC_for_pty==1 path, | ||
| 276 | * bailing out on error like below messes up the buffer. | ||
| 277 | * EAGAIN is highly unlikely here, other errors will be | ||
| 278 | * repeated on next write, let's just skip error check. | ||
| 279 | */ | ||
| 280 | #if 0 | ||
| 281 | if (rc <= 0) | ||
| 282 | return rc; | ||
| 283 | #endif | ||
| 284 | rc = 2; | ||
| 285 | goto update_and_return; | ||
| 286 | } | ||
| 287 | if (buf[1] == AYT) { | ||
| 288 | if (ts->size2 == 0) { /* if nothing buffered yet... */ | ||
| 289 | /* Send back evidence that AYT was seen */ | ||
| 290 | unsigned char *buf2 = TS_BUF2(ts); | ||
| 291 | buf2[0] = IAC; | ||
| 292 | buf2[1] = NOP; | ||
| 293 | ts->wridx2 = 0; | ||
| 294 | ts->rdidx2 = ts->size2 = 2; | ||
| 295 | } | ||
| 296 | rc = 2; | ||
| 297 | goto update_and_return; | ||
| 298 | } | ||
| 299 | if (buf[1] >= 240 && buf[1] <= 249) { | ||
| 300 | /* NOP (241). Ignore (putty keepalive, etc) */ | ||
| 301 | /* All other 2-byte commands also treated as NOPs here */ | ||
| 302 | rc = 2; | ||
| 303 | goto update_and_return; | ||
| 304 | } | ||
| 305 | |||
| 306 | if (wr <= 2) { | ||
| 307 | /* BUG: only 2 bytes of the IAC is in the buffer, we just eat them. | ||
| 308 | * This is not a practical problem since >2 byte IACs are seen only | ||
| 309 | * in initial negotiation, when buffer is empty | ||
| 310 | */ | ||
| 311 | rc = 2; | ||
| 312 | goto update_and_return; | ||
| 313 | } | ||
| 314 | |||
| 315 | if (buf[1] == SB) { | ||
| 316 | if (buf[2] == TELOPT_NAWS) { | ||
| 317 | /* IAC SB, TELOPT_NAWS, 4-byte, IAC SE */ | ||
| 318 | struct winsize ws; | ||
| 319 | if (wr <= 6) { | ||
| 320 | /* BUG: incomplete, can't process */ | ||
| 321 | rc = wr; | ||
| 322 | goto update_and_return; | ||
| 323 | } | ||
| 324 | memset(&ws, 0, sizeof(ws)); /* pixel sizes are set to 0 */ | ||
| 325 | ws.ws_col = (buf[3] << 8) | buf[4]; | ||
| 326 | ws.ws_row = (buf[5] << 8) | buf[6]; | ||
| 327 | ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); | ||
| 328 | rc = 7; | ||
| 329 | /* trailing IAC SE will be eaten separately, as 2-byte NOP */ | ||
| 330 | goto update_and_return; | ||
| 331 | } | ||
| 332 | /* else: other subnegs not supported yet */ | ||
| 333 | } | ||
| 334 | |||
| 335 | /* Assume it is a 3-byte WILL/WONT/DO/DONT 251..254 command and skip it */ | ||
| 336 | #if DEBUG | ||
| 337 | fprintf(stderr, "Ignoring IAC %s,%s\n", | ||
| 338 | TELCMD(buf[1]), TELOPT(buf[2])); | ||
| 339 | #endif | ||
| 340 | rc = 3; | ||
| 341 | |||
| 342 | update_and_return: | ||
| 343 | ts->wridx1 += rc; | ||
| 344 | if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 345 | ts->wridx1 = 0; | ||
| 346 | ts->size1 -= rc; | ||
| 347 | /* | ||
| 348 | * Hack. We cannot process IACs which wrap around buffer's end. | ||
| 349 | * Since properly fixing it requires writing bigger code, | ||
| 350 | * we rely instead on this code making it virtually impossible | ||
| 351 | * to have wrapped IAC (people don't type at 2k/second). | ||
| 352 | * It also allows for bigger reads in common case. | ||
| 353 | */ | ||
| 354 | if (ts->size1 == 0) { /* very typical */ | ||
| 355 | //bb_error_msg("zero size1"); | ||
| 356 | ts->rdidx1 = 0; | ||
| 357 | ts->wridx1 = 0; | ||
| 358 | return rc; | ||
| 359 | } | ||
| 360 | wr = ts->wridx1; | ||
| 361 | if (wr != 0 && wr < ts->rdidx1) { | ||
| 362 | /* Buffer is not wrapped yet. | ||
| 363 | * We can easily move it to the beginning. | ||
| 364 | */ | ||
| 365 | //bb_error_msg("moved %d", wr); | ||
| 366 | memmove(TS_BUF1(ts), TS_BUF1(ts) + wr, ts->size1); | ||
| 367 | ts->rdidx1 -= wr; | ||
| 368 | ts->wridx1 = 0; | ||
| 369 | } | ||
| 370 | return rc; | ||
| 371 | } | ||
| 372 | |||
| 373 | /* | ||
| 374 | * Converting single IAC into double on output | ||
| 375 | */ | ||
| 376 | static size_t safe_write_double_iac(int fd, const char *buf, size_t count) | ||
| 377 | { | ||
| 378 | const char *IACptr; | ||
| 379 | size_t wr, rc, total; | ||
| 380 | |||
| 381 | total = 0; | ||
| 382 | while (1) { | ||
| 383 | if (count == 0) | ||
| 384 | return total; | ||
| 385 | if (*buf == (char)IAC) { | ||
| 386 | static const char IACIAC[] ALIGN1 = { IAC, IAC }; | ||
| 387 | rc = safe_write(fd, IACIAC, 2); | ||
| 388 | /* BUG: if partial write was only 1 byte long, we end up emitting just one IAC */ | ||
| 389 | if (rc != 2) | ||
| 390 | break; | ||
| 391 | buf++; | ||
| 392 | total++; | ||
| 393 | count--; | ||
| 394 | continue; | ||
| 395 | } | ||
| 396 | /* count != 0, *buf != IAC */ | ||
| 397 | IACptr = memchr(buf, IAC, count); | ||
| 398 | wr = count; | ||
| 399 | if (IACptr) | ||
| 400 | wr = IACptr - buf; | ||
| 401 | rc = safe_write(fd, buf, wr); | ||
| 402 | if (rc != wr) | ||
| 403 | break; | ||
| 404 | buf += rc; | ||
| 405 | total += rc; | ||
| 406 | count -= rc; | ||
| 407 | } | ||
| 408 | /* here: rc - result of last short write */ | ||
| 409 | if ((ssize_t)rc < 0) { /* error? */ | ||
| 410 | if (total == 0) | ||
| 411 | return rc; | ||
| 412 | rc = 0; | ||
| 413 | } | ||
| 414 | return total + rc; | ||
| 415 | } | ||
| 416 | |||
| 417 | /* Must match getopt32 string */ | ||
| 418 | enum { | ||
| 419 | OPT_WATCHCHILD = (1 << 2), /* -K */ | ||
| 420 | OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */ | ||
| 421 | OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p PORT */ | ||
| 422 | OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */ | ||
| 423 | OPT_SYSLOG = (1 << 7) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -S */ | ||
| 424 | OPT_WAIT = (1 << 8) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -w SEC */ | ||
| 425 | }; | ||
| 426 | |||
| 427 | static struct tsession * | ||
| 428 | make_new_session( | ||
| 429 | IF_FEATURE_TELNETD_STANDALONE(int sock) | ||
| 430 | IF_NOT_FEATURE_TELNETD_STANDALONE(void) | ||
| 431 | ) { | ||
| 432 | #if !ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 433 | enum { sock = 0 }; | ||
| 434 | #endif | ||
| 435 | const char *login_argv[2]; | ||
| 436 | struct termios termbuf; | ||
| 437 | int fd, pid; | ||
| 438 | char tty_name[GETPTY_BUFSIZE]; | ||
| 439 | struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2); | ||
| 440 | |||
| 441 | /*ts->buf1 = (char *)(ts + 1);*/ | ||
| 442 | /*ts->buf2 = ts->buf1 + BUFSIZE;*/ | ||
| 443 | |||
| 444 | /* Got a new connection, set up a tty */ | ||
| 445 | fd = xgetpty(tty_name); | ||
| 446 | if (fd > G.maxfd) | ||
| 447 | G.maxfd = fd; | ||
| 448 | ts->ptyfd = fd; | ||
| 449 | ndelay_on(fd); | ||
| 450 | close_on_exec_on(fd); | ||
| 451 | |||
| 452 | /* SO_KEEPALIVE by popular demand */ | ||
| 453 | setsockopt_keepalive(sock); | ||
| 454 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 455 | ts->sockfd_read = sock; | ||
| 456 | ndelay_on(sock); | ||
| 457 | if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */ | ||
| 458 | sock++; /* so use fd 1 for output */ | ||
| 459 | ndelay_on(sock); | ||
| 460 | } | ||
| 461 | ts->sockfd_write = sock; | ||
| 462 | if (sock > G.maxfd) | ||
| 463 | G.maxfd = sock; | ||
| 464 | #else | ||
| 465 | /* ts->sockfd_read = 0; - done by xzalloc */ | ||
| 466 | ts->sockfd_write = 1; | ||
| 467 | ndelay_on(0); | ||
| 468 | ndelay_on(1); | ||
| 469 | #endif | ||
| 470 | |||
| 471 | /* Make the telnet client understand we will echo characters so it | ||
| 472 | * should not do it locally. We don't tell the client to run linemode, | ||
| 473 | * because we want to handle line editing and tab completion and other | ||
| 474 | * stuff that requires char-by-char support. */ | ||
| 475 | { | ||
| 476 | static const char iacs_to_send[] ALIGN1 = { | ||
| 477 | IAC, DO, TELOPT_ECHO, | ||
| 478 | IAC, DO, TELOPT_NAWS, | ||
| 479 | /* This requires telnetd.ctrlSQ.patch (incomplete) */ | ||
| 480 | /*IAC, DO, TELOPT_LFLOW,*/ | ||
| 481 | IAC, WILL, TELOPT_ECHO, | ||
| 482 | IAC, WILL, TELOPT_SGA | ||
| 483 | }; | ||
| 484 | /* This confuses safe_write_double_iac(), it will try to duplicate | ||
| 485 | * each IAC... */ | ||
| 486 | //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send)); | ||
| 487 | //ts->rdidx2 = sizeof(iacs_to_send); | ||
| 488 | //ts->size2 = sizeof(iacs_to_send); | ||
| 489 | /* So just stuff it into TCP stream! (no error check...) */ | ||
| 490 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 491 | safe_write(sock, iacs_to_send, sizeof(iacs_to_send)); | ||
| 492 | #else | ||
| 493 | safe_write(1, iacs_to_send, sizeof(iacs_to_send)); | ||
| 494 | #endif | ||
| 495 | /*ts->rdidx2 = 0; - xzalloc did it */ | ||
| 496 | /*ts->size2 = 0;*/ | ||
| 497 | } | ||
| 498 | |||
| 499 | fflush_all(); | ||
| 500 | pid = vfork(); /* NOMMU-friendly */ | ||
| 501 | if (pid < 0) { | ||
| 502 | free(ts); | ||
| 503 | close(fd); | ||
| 504 | /* sock will be closed by caller */ | ||
| 505 | bb_simple_perror_msg("vfork"); | ||
| 506 | return NULL; | ||
| 507 | } | ||
| 508 | if (pid > 0) { | ||
| 509 | /* Parent */ | ||
| 510 | ts->shell_pid = pid; | ||
| 511 | return ts; | ||
| 512 | } | ||
| 513 | |||
| 514 | /* Child */ | ||
| 515 | /* Careful - we are after vfork! */ | ||
| 516 | |||
| 517 | /* Restore default signal handling ASAP */ | ||
| 518 | bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL); | ||
| 519 | |||
| 520 | pid = getpid(); | ||
| 521 | |||
| 522 | if (ENABLE_FEATURE_UTMP) { | ||
| 523 | len_and_sockaddr *lsa = get_peer_lsa(sock); | ||
| 524 | char *hostname = NULL; | ||
| 525 | if (lsa) { | ||
| 526 | hostname = xmalloc_sockaddr2dotted(&lsa->u.sa); | ||
| 527 | free(lsa); | ||
| 528 | } | ||
| 529 | write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname); | ||
| 530 | free(hostname); | ||
| 531 | } | ||
| 532 | |||
| 533 | /* Make new session and process group */ | ||
| 534 | setsid(); | ||
| 535 | |||
| 536 | /* Open the child's side of the tty */ | ||
| 537 | /* NB: setsid() disconnects from any previous ctty's. Therefore | ||
| 538 | * we must open child's side of the tty AFTER setsid! */ | ||
| 539 | close(0); | ||
| 540 | xopen(tty_name, O_RDWR); /* becomes our ctty */ | ||
| 541 | xdup2(0, 1); | ||
| 542 | xdup2(0, 2); | ||
| 543 | tcsetpgrp(0, pid); /* switch this tty's process group to us */ | ||
| 544 | |||
| 545 | /* The pseudo-terminal allocated to the client is configured to operate | ||
| 546 | * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */ | ||
| 547 | tcgetattr(0, &termbuf); | ||
| 548 | termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */ | ||
| 549 | termbuf.c_oflag |= ONLCR | XTABS; | ||
| 550 | termbuf.c_iflag |= ICRNL; | ||
| 551 | termbuf.c_iflag &= ~IXOFF; | ||
| 552 | /*termbuf.c_lflag &= ~ICANON;*/ | ||
| 553 | tcsetattr_stdin_TCSANOW(&termbuf); | ||
| 554 | |||
| 555 | /* Uses FILE-based I/O to stdout, but does fflush_all(), | ||
| 556 | * so should be safe with vfork. | ||
| 557 | * I fear, though, that some users will have ridiculously big | ||
| 558 | * issue files, and they may block writing to fd 1, | ||
| 559 | * (parent is supposed to read it, but parent waits | ||
| 560 | * for vforked child to exec!) */ | ||
| 561 | print_login_issue(G.issuefile, tty_name); | ||
| 562 | |||
| 563 | /* Exec shell / login / whatever */ | ||
| 564 | login_argv[0] = G.loginpath; | ||
| 565 | login_argv[1] = NULL; | ||
| 566 | /* exec busybox applet (if PREFER_APPLETS=y), if that fails, | ||
| 567 | * exec external program. | ||
| 568 | * NB: sock is either 0 or has CLOEXEC set on it. | ||
| 569 | * fd has CLOEXEC set on it too. These two fds will be closed here. | ||
| 570 | */ | ||
| 571 | BB_EXECVP(G.loginpath, (char **)login_argv); | ||
| 572 | /* _exit is safer with vfork, and we shouldn't send message | ||
| 573 | * to remote clients anyway */ | ||
| 574 | _exit_FAILURE(); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/ | ||
| 575 | } | ||
| 576 | |||
| 577 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 578 | |||
| 579 | static void | ||
| 580 | free_session(struct tsession *ts) | ||
| 581 | { | ||
| 582 | struct tsession *t; | ||
| 583 | |||
| 584 | if (option_mask32 & OPT_INETD) | ||
| 585 | exit_SUCCESS(); | ||
| 586 | |||
| 587 | /* Unlink this telnet session from the session list */ | ||
| 588 | t = G.sessions; | ||
| 589 | if (t == ts) | ||
| 590 | G.sessions = ts->next; | ||
| 591 | else { | ||
| 592 | while (t->next != ts) | ||
| 593 | t = t->next; | ||
| 594 | t->next = ts->next; | ||
| 595 | } | ||
| 596 | |||
| 597 | #if 0 | ||
| 598 | /* It was said that "normal" telnetd just closes ptyfd, | ||
| 599 | * doesn't send SIGKILL. When we close ptyfd, | ||
| 600 | * kernel sends SIGHUP to processes having slave side opened. */ | ||
| 601 | kill(ts->shell_pid, SIGKILL); | ||
| 602 | waitpid(ts->shell_pid, NULL, 0); | ||
| 603 | #endif | ||
| 604 | close(ts->ptyfd); | ||
| 605 | close(ts->sockfd_read); | ||
| 606 | /* We do not need to close(ts->sockfd_write), it's the same | ||
| 607 | * as sockfd_read unless we are in inetd mode. But in inetd mode | ||
| 608 | * we do not reach this */ | ||
| 609 | free(ts); | ||
| 610 | |||
| 611 | /* Scan all sessions and find new maxfd */ | ||
| 612 | G.maxfd = 0; | ||
| 613 | ts = G.sessions; | ||
| 614 | while (ts) { | ||
| 615 | if (G.maxfd < ts->ptyfd) | ||
| 616 | G.maxfd = ts->ptyfd; | ||
| 617 | if (G.maxfd < ts->sockfd_read) | ||
| 618 | G.maxfd = ts->sockfd_read; | ||
| 619 | #if 0 | ||
| 620 | /* Again, sockfd_write == sockfd_read here */ | ||
| 621 | if (G.maxfd < ts->sockfd_write) | ||
| 622 | G.maxfd = ts->sockfd_write; | ||
| 623 | #endif | ||
| 624 | ts = ts->next; | ||
| 625 | } | ||
| 626 | } | ||
| 627 | |||
| 628 | #else /* !FEATURE_TELNETD_STANDALONE */ | ||
| 629 | |||
| 630 | /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */ | ||
| 631 | #define free_session(ts) return 0 | ||
| 632 | |||
| 633 | #endif | ||
| 634 | |||
| 635 | static void handle_sigchld(int sig UNUSED_PARAM) | ||
| 636 | { | ||
| 637 | pid_t pid; | ||
| 638 | struct tsession *ts; | ||
| 639 | int save_errno = errno; | ||
| 640 | |||
| 641 | /* Looping: more than one child may have exited */ | ||
| 642 | while (1) { | ||
| 643 | pid = wait_any_nohang(NULL); | ||
| 644 | if (pid <= 0) | ||
| 645 | break; | ||
| 646 | ts = G.sessions; | ||
| 647 | while (ts) { | ||
| 648 | if (ts->shell_pid == pid) { | ||
| 649 | ts->shell_pid = -1; | ||
| 650 | update_utmp_DEAD_PROCESS(pid); | ||
| 651 | break; | ||
| 652 | } | ||
| 653 | ts = ts->next; | ||
| 654 | } | ||
| 655 | } | ||
| 656 | |||
| 657 | errno = save_errno; | ||
| 658 | } | ||
| 659 | |||
| 660 | int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
| 661 | int telnetd_main(int argc UNUSED_PARAM, char **argv) | ||
| 662 | { | ||
| 663 | fd_set rdfdset, wrfdset; | ||
| 664 | unsigned opt; | ||
| 665 | int count; | ||
| 666 | struct tsession *ts; | ||
| 667 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 668 | #define IS_INETD (opt & OPT_INETD) | ||
| 669 | int master_fd = master_fd; /* for compiler */ | ||
| 670 | int sec_linger = sec_linger; | ||
| 671 | char *opt_bindaddr = NULL; | ||
| 672 | char *opt_portnbr; | ||
| 673 | #else | ||
| 674 | enum { | ||
| 675 | IS_INETD = 1, | ||
| 676 | master_fd = -1, | ||
| 677 | }; | ||
| 678 | #endif | ||
| 679 | INIT_G(); | ||
| 680 | |||
| 681 | /* Even if !STANDALONE, we accept (and ignore) -i, thus people | ||
| 682 | * don't need to guess whether it's ok to pass -i to us */ | ||
| 683 | opt = getopt32(argv, "^" | ||
| 684 | "f:l:Ki" | ||
| 685 | IF_FEATURE_TELNETD_STANDALONE("p:b:F") | ||
| 686 | IF_FEATURE_TELNETD_INETD_WAIT("Sw:+") /* -w NUM */ | ||
| 687 | "\0" | ||
| 688 | /* -w implies -F. -w and -i don't mix */ | ||
| 689 | IF_FEATURE_TELNETD_INETD_WAIT("wF:i--w:w--i"), | ||
| 690 | &G.issuefile, &G.loginpath | ||
| 691 | IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr) | ||
| 692 | IF_FEATURE_TELNETD_INETD_WAIT(, &sec_linger) | ||
| 693 | ); | ||
| 694 | if (!IS_INETD /*&& !re_execed*/) { | ||
| 695 | /* inform that we start in standalone mode? | ||
| 696 | * May be useful when people forget to give -i */ | ||
| 697 | /*bb_error_msg("listening for connections");*/ | ||
| 698 | if (!(opt & OPT_FOREGROUND)) { | ||
| 699 | /* DAEMON_CHDIR_ROOT was giving inconsistent | ||
| 700 | * behavior with/without -F, -i */ | ||
| 701 | bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv); | ||
| 702 | } | ||
| 703 | } | ||
| 704 | /* Redirect log to syslog early, if needed */ | ||
| 705 | if (IS_INETD || (opt & OPT_SYSLOG) || !(opt & OPT_FOREGROUND)) { | ||
| 706 | openlog(applet_name, LOG_PID, LOG_DAEMON); | ||
| 707 | logmode = LOGMODE_SYSLOG; | ||
| 708 | } | ||
| 709 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 710 | if (IS_INETD) { | ||
| 711 | G.sessions = make_new_session(0); | ||
| 712 | if (!G.sessions) /* pty opening or vfork problem, exit */ | ||
| 713 | return 1; /* make_new_session printed error message */ | ||
| 714 | } else { | ||
| 715 | master_fd = 0; | ||
| 716 | if (!(opt & OPT_WAIT)) { | ||
| 717 | unsigned portnbr = CONFIG_FEATURE_TELNETD_PORT_DEFAULT; | ||
| 718 | if (opt & OPT_PORT) | ||
| 719 | portnbr = xatou16(opt_portnbr); | ||
| 720 | master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr); | ||
| 721 | xlisten(master_fd, 1); | ||
| 722 | } | ||
| 723 | close_on_exec_on(master_fd); | ||
| 724 | } | ||
| 725 | #else | ||
| 726 | G.sessions = make_new_session(); | ||
| 727 | if (!G.sessions) /* pty opening or vfork problem, exit */ | ||
| 728 | return 1; /* make_new_session printed error message */ | ||
| 729 | #endif | ||
| 730 | |||
| 731 | /* We don't want to die if just one session is broken */ | ||
| 732 | signal(SIGPIPE, SIG_IGN); | ||
| 733 | |||
| 734 | if (opt & OPT_WATCHCHILD) | ||
| 735 | signal(SIGCHLD, handle_sigchld); | ||
| 736 | else /* prevent dead children from becoming zombies */ | ||
| 737 | signal(SIGCHLD, SIG_IGN); | ||
| 738 | |||
| 739 | /* | ||
| 740 | This is how the buffers are used. The arrows indicate data flow. | ||
| 741 | |||
| 742 | +-------+ wridx1++ +------+ rdidx1++ +----------+ | ||
| 743 | | | <-------------- | buf1 | <-------------- | | | ||
| 744 | | | size1-- +------+ size1++ | | | ||
| 745 | | pty | | socket | | ||
| 746 | | | rdidx2++ +------+ wridx2++ | | | ||
| 747 | | | --------------> | buf2 | --------------> | | | ||
| 748 | +-------+ size2++ +------+ size2-- +----------+ | ||
| 749 | |||
| 750 | size1: "how many bytes are buffered for pty between rdidx1 and wridx1?" | ||
| 751 | size2: "how many bytes are buffered for socket between rdidx2 and wridx2?" | ||
| 752 | |||
| 753 | Each session has got two buffers. Buffers are circular. If sizeN == 0, | ||
| 754 | buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases | ||
| 755 | rdidxN == wridxN. | ||
| 756 | */ | ||
| 757 | again: | ||
| 758 | FD_ZERO(&rdfdset); | ||
| 759 | FD_ZERO(&wrfdset); | ||
| 760 | |||
| 761 | /* Select on the master socket, all telnet sockets and their | ||
| 762 | * ptys if there is room in their session buffers. | ||
| 763 | * NB: scalability problem: we recalculate entire bitmap | ||
| 764 | * before each select. Can be a problem with 500+ connections. */ | ||
| 765 | ts = G.sessions; | ||
| 766 | while (ts) { | ||
| 767 | struct tsession *next = ts->next; /* in case we free ts */ | ||
| 768 | if (ts->shell_pid == -1) { | ||
| 769 | /* Child died and we detected that */ | ||
| 770 | free_session(ts); | ||
| 771 | } else { | ||
| 772 | if (ts->size1 > 0) /* can write to pty */ | ||
| 773 | FD_SET(ts->ptyfd, &wrfdset); | ||
| 774 | if (ts->size1 < BUFSIZE) /* can read from socket */ | ||
| 775 | FD_SET(ts->sockfd_read, &rdfdset); | ||
| 776 | if (ts->size2 > 0) /* can write to socket */ | ||
| 777 | FD_SET(ts->sockfd_write, &wrfdset); | ||
| 778 | if (ts->size2 < BUFSIZE) /* can read from pty */ | ||
| 779 | FD_SET(ts->ptyfd, &rdfdset); | ||
| 780 | } | ||
| 781 | ts = next; | ||
| 782 | } | ||
| 783 | if (!IS_INETD) { | ||
| 784 | FD_SET(master_fd, &rdfdset); | ||
| 785 | /* This is needed because free_session() does not | ||
| 786 | * take master_fd into account when it finds new | ||
| 787 | * maxfd among remaining fd's */ | ||
| 788 | if (master_fd > G.maxfd) | ||
| 789 | G.maxfd = master_fd; | ||
| 790 | } | ||
| 791 | |||
| 792 | { | ||
| 793 | struct timeval *tv_ptr = NULL; | ||
| 794 | #if ENABLE_FEATURE_TELNETD_INETD_WAIT | ||
| 795 | struct timeval tv; | ||
| 796 | if ((opt & OPT_WAIT) && !G.sessions) { | ||
| 797 | tv.tv_sec = sec_linger; | ||
| 798 | tv.tv_usec = 0; | ||
| 799 | tv_ptr = &tv; | ||
| 800 | } | ||
| 801 | #endif | ||
| 802 | count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr); | ||
| 803 | } | ||
| 804 | if (count == 0) /* "telnetd -w SEC" timed out */ | ||
| 805 | return 0; | ||
| 806 | if (count < 0) | ||
| 807 | goto again; /* EINTR or ENOMEM */ | ||
| 808 | |||
| 809 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
| 810 | /* Check for and accept new sessions */ | ||
| 811 | if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { | ||
| 812 | int fd; | ||
| 813 | struct tsession *new_ts; | ||
| 814 | |||
| 815 | fd = accept(master_fd, NULL, NULL); | ||
| 816 | if (fd < 0) | ||
| 817 | goto again; | ||
| 818 | close_on_exec_on(fd); | ||
| 819 | |||
| 820 | /* Create a new session and link it into active list */ | ||
| 821 | new_ts = make_new_session(fd); | ||
| 822 | if (new_ts) { | ||
| 823 | new_ts->next = G.sessions; | ||
| 824 | G.sessions = new_ts; | ||
| 825 | } else { | ||
| 826 | close(fd); | ||
| 827 | } | ||
| 828 | } | ||
| 829 | #endif | ||
| 830 | |||
| 831 | /* Then check for data tunneling */ | ||
| 832 | ts = G.sessions; | ||
| 833 | while (ts) { /* For all sessions... */ | ||
| 834 | struct tsession *next = ts->next; /* in case we free ts */ | ||
| 835 | |||
| 836 | if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) { | ||
| 837 | /* Write to pty from buffer 1 */ | ||
| 838 | count = safe_write_to_pty_decode_iac(ts); | ||
| 839 | if (count < 0) { | ||
| 840 | if (errno == EAGAIN) | ||
| 841 | goto skip1; | ||
| 842 | goto kill_session; | ||
| 843 | } | ||
| 844 | } | ||
| 845 | skip1: | ||
| 846 | if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) { | ||
| 847 | /* Write to socket from buffer 2 */ | ||
| 848 | count = MIN(BUFSIZE - ts->wridx2, ts->size2); | ||
| 849 | count = safe_write_double_iac(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count); | ||
| 850 | if (count < 0) { | ||
| 851 | if (errno == EAGAIN) | ||
| 852 | goto skip2; | ||
| 853 | goto kill_session; | ||
| 854 | } | ||
| 855 | ts->wridx2 += count; | ||
| 856 | if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 857 | ts->wridx2 = 0; | ||
| 858 | ts->size2 -= count; | ||
| 859 | if (ts->size2 == 0) { | ||
| 860 | ts->rdidx2 = 0; | ||
| 861 | ts->wridx2 = 0; | ||
| 862 | } | ||
| 863 | } | ||
| 864 | skip2: | ||
| 865 | |||
| 866 | if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) { | ||
| 867 | /* Read from socket to buffer 1 */ | ||
| 868 | count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); | ||
| 869 | count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count); | ||
| 870 | if (count <= 0) { | ||
| 871 | if (count < 0 && errno == EAGAIN) | ||
| 872 | goto skip3; | ||
| 873 | goto kill_session; | ||
| 874 | } | ||
| 875 | /* Ignore trailing NUL if it is there */ | ||
| 876 | if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) { | ||
| 877 | --count; | ||
| 878 | } | ||
| 879 | ts->size1 += count; | ||
| 880 | ts->rdidx1 += count; | ||
| 881 | if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 882 | ts->rdidx1 = 0; | ||
| 883 | } | ||
| 884 | skip3: | ||
| 885 | if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) { | ||
| 886 | /* Read from pty to buffer 2 */ | ||
| 887 | int eio = 0; | ||
| 888 | read_pty: | ||
| 889 | count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); | ||
| 890 | count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count); | ||
| 891 | if (count <= 0) { | ||
| 892 | if (count < 0) { | ||
| 893 | if (errno == EAGAIN) | ||
| 894 | goto skip4; | ||
| 895 | /* login process might call vhangup(), | ||
| 896 | * which causes intermittent EIOs on read above | ||
| 897 | * (observed on kernel 4.12.0). Try up to 10 ms. | ||
| 898 | */ | ||
| 899 | if (errno == EIO && eio < 10) { | ||
| 900 | eio++; | ||
| 901 | //bb_error_msg("EIO pty %u", eio); | ||
| 902 | usleep(1000); | ||
| 903 | goto read_pty; | ||
| 904 | } | ||
| 905 | } | ||
| 906 | goto kill_session; | ||
| 907 | } | ||
| 908 | ts->size2 += count; | ||
| 909 | ts->rdidx2 += count; | ||
| 910 | if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */ | ||
| 911 | ts->rdidx2 = 0; | ||
| 912 | } | ||
| 913 | skip4: | ||
| 914 | ts = next; | ||
| 915 | continue; | ||
| 916 | kill_session: | ||
| 917 | if (ts->shell_pid > 0) | ||
| 918 | update_utmp_DEAD_PROCESS(ts->shell_pid); | ||
| 919 | free_session(ts); | ||
| 920 | ts = next; | ||
| 921 | } | ||
| 922 | |||
| 923 | goto again; | ||
| 924 | } | ||
diff --git a/networking/tls.c b/networking/tls.c index 46bfd9b24..c7015e044 100644 --- a/networking/tls.c +++ b/networking/tls.c | |||
| @@ -57,13 +57,15 @@ | |||
| 57 | #endif | 57 | #endif |
| 58 | 58 | ||
| 59 | #if TLS_DEBUG | 59 | #if TLS_DEBUG |
| 60 | # define dbg(...) fprintf(stderr, __VA_ARGS__) | 60 | # define dbg(...) bb_error_msg(__VA_ARGS__) |
| 61 | # define dbgcont(...) fprintf(stderr, __VA_ARGS__) | ||
| 61 | #else | 62 | #else |
| 62 | # define dbg(...) ((void)0) | 63 | # define dbg(...) ((void)0) |
| 64 | # define dbgcont(...) ((void)0) | ||
| 63 | #endif | 65 | #endif |
| 64 | 66 | ||
| 65 | #if TLS_DEBUG_DER | 67 | #if TLS_DEBUG_DER |
| 66 | # define dbg_der(...) fprintf(stderr, __VA_ARGS__) | 68 | # define dbg_der(...) bb_error_msg(__VA_ARGS__) |
| 67 | #else | 69 | #else |
| 68 | # define dbg_der(...) ((void)0) | 70 | # define dbg_der(...) ((void)0) |
| 69 | #endif | 71 | #endif |
| @@ -188,6 +190,76 @@ | |||
| 188 | #define TLS_MAX_CRYPTBLOCK_SIZE 16 | 190 | #define TLS_MAX_CRYPTBLOCK_SIZE 16 |
| 189 | #define TLS_MAX_OUTBUF (1 << 14) | 191 | #define TLS_MAX_OUTBUF (1 << 14) |
| 190 | 192 | ||
| 193 | /* Cipher suites we support, in preference order (best first) */ | ||
| 194 | #define NUM_CIPHERS (0 \ | ||
| 195 | + 4 * ENABLE_FEATURE_TLS_SHA1 \ | ||
| 196 | + ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 \ | ||
| 197 | + ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \ | ||
| 198 | + ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 \ | ||
| 199 | + ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \ | ||
| 200 | + 2 * ENABLE_FEATURE_TLS_SHA1 \ | ||
| 201 | + ALLOW_RSA_WITH_AES_128_CBC_SHA256 \ | ||
| 202 | + ALLOW_RSA_WITH_AES_256_CBC_SHA256 \ | ||
| 203 | + ALLOW_RSA_WITH_AES_128_GCM_SHA256 \ | ||
| 204 | + ALLOW_RSA_NULL_SHA256 \ | ||
| 205 | ) | ||
| 206 | static const uint8_t client_hello_ciphers[] = { | ||
| 207 | 0x00,2 * (1 + NUM_CIPHERS), //len16_be | ||
| 208 | 0x00,0xFF, //not a cipher - TLS_EMPTY_RENEGOTIATION_INFO_SCSV | ||
| 209 | /* ^^^^^^ RFC 5746 Renegotiation Indication Extension - some servers will refuse to work with us otherwise */ | ||
| 210 | #if ENABLE_FEATURE_TLS_SHA1 | ||
| 211 | 0xC0,0x09, // 1 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ok: wget https://is.gd/ | ||
| 212 | 0xC0,0x0A, // 2 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - ok: wget https://is.gd/ | ||
| 213 | 0xC0,0x13, // 3 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA | ||
| 214 | 0xC0,0x14, // 4 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES256-SHA (might fail with older openssl) | ||
| 215 | // 0xC0,0x18, // TLS_ECDH_anon_WITH_AES_128_CBC_SHA | ||
| 216 | // 0xC0,0x19, // TLS_ECDH_anon_WITH_AES_256_CBC_SHA | ||
| 217 | #endif | ||
| 218 | #if ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 | ||
| 219 | 0xC0,0x23, // 5 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - ok: wget https://is.gd/ | ||
| 220 | #endif | ||
| 221 | // 0xC0,0x24, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 222 | #if ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256 | ||
| 223 | 0xC0,0x27, // 6 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA256 | ||
| 224 | #endif | ||
| 225 | // 0xC0,0x28, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 226 | #if ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | ||
| 227 | 0xC0,0x2B, // 7 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - ok: wget https://is.gd/ | ||
| 228 | #endif | ||
| 229 | // 0xC0,0x2C, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - wget https://is.gd/: "TLS error from peer (alert code 20): bad MAC" | ||
| 230 | //TODO: GCM_SHA384 ciphers can be supported, only need sha384-based PRF? | ||
| 231 | #if ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | ||
| 232 | 0xC0,0x2F, // 8 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-GCM-SHA256 | ||
| 233 | #endif | ||
| 234 | // 0xC0,0x30, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher ECDHE-RSA-AES256-GCM-SHA384: "decryption failed or bad record mac" | ||
| 235 | //possibly these too: | ||
| 236 | #if ENABLE_FEATURE_TLS_SHA1 | ||
| 237 | // 0xC0,0x35, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA | ||
| 238 | // 0xC0,0x36, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA | ||
| 239 | #endif | ||
| 240 | // 0xC0,0x37, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 | ||
| 241 | // 0xC0,0x38, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 242 | #if ENABLE_FEATURE_TLS_SHA1 | ||
| 243 | 0x00,0x2F, // 9 TLS_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher AES128-SHA | ||
| 244 | 0x00,0x35, //10 TLS_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher AES256-SHA | ||
| 245 | #endif | ||
| 246 | #if ALLOW_RSA_WITH_AES_128_CBC_SHA256 | ||
| 247 | 0x00,0x3C, //11 TLS_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher AES128-SHA256 | ||
| 248 | #endif | ||
| 249 | #if ALLOW_RSA_WITH_AES_256_CBC_SHA256 | ||
| 250 | 0x00,0x3D, //12 TLS_RSA_WITH_AES_256_CBC_SHA256 - ok: openssl s_server ... -cipher AES256-SHA256 | ||
| 251 | #endif | ||
| 252 | #if ALLOW_RSA_WITH_AES_128_GCM_SHA256 | ||
| 253 | 0x00,0x9C, //13 TLS_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher AES128-GCM-SHA256 | ||
| 254 | #endif | ||
| 255 | // 0x00,0x9D, // TLS_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher AES256-GCM-SHA384: "decryption failed or bad record mac" | ||
| 256 | #if ALLOW_RSA_NULL_SHA256 | ||
| 257 | 0x00,0x3B, // TLS_RSA_WITH_NULL_SHA256 | ||
| 258 | #endif | ||
| 259 | 0x01,0x00, //not a cipher - comprtypes_len, comprtype | ||
| 260 | }; | ||
| 261 | #define supported_ciphers (client_hello_ciphers + 4) | ||
| 262 | |||
| 191 | enum { | 263 | enum { |
| 192 | AES128_KEYSIZE = 16, | 264 | AES128_KEYSIZE = 16, |
| 193 | AES256_KEYSIZE = 32, | 265 | AES256_KEYSIZE = 32, |
| @@ -241,11 +313,75 @@ enum { | |||
| 241 | GOT_CERT_RSA_KEY_ALG = 1 << 1, | 313 | GOT_CERT_RSA_KEY_ALG = 1 << 1, |
| 242 | GOT_CERT_ECDSA_KEY_ALG = 1 << 2, // so far unused | 314 | GOT_CERT_ECDSA_KEY_ALG = 1 << 2, // so far unused |
| 243 | GOT_EC_KEY = 1 << 3, | 315 | GOT_EC_KEY = 1 << 3, |
| 244 | GOT_EC_CURVE_X25519 = 1 << 4, // else P256 | 316 | /* Client: server sent x25519 key in SERVER_KEY_EXCHANGE (else P256) |
| 317 | * Server: we chose x25519 based on client's supported_groups (else P256) */ | ||
| 318 | USE_EC_CURVE_X25519 = 1 << 4, | ||
| 245 | ENCRYPTION_AESGCM = 1 << 5, // else AES-SHA (or NULL-SHA if ALLOW_RSA_NULL_SHA256=1) | 319 | ENCRYPTION_AESGCM = 1 << 5, // else AES-SHA (or NULL-SHA if ALLOW_RSA_NULL_SHA256=1) |
| 246 | ENCRYPT_ON_WRITE = 1 << 6, | ||
| 247 | }; | 320 | }; |
| 248 | 321 | ||
| 322 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL | ||
| 323 | /* Note: return value matches KEY_RSA (0) / KEY_ECDSA (1) enum values */ | ||
| 324 | static int is_cipher_ECDSA(const uint8_t *cipherid) | ||
| 325 | { | ||
| 326 | uint8_t cipher_lo; | ||
| 327 | if (cipherid[0] != 0xC0) | ||
| 328 | return 0; | ||
| 329 | /* ECDHE cipher - check if ECDSA or RSA */ | ||
| 330 | cipher_lo = cipherid[1]; | ||
| 331 | return (cipher_lo == 0x09 || cipher_lo == 0x0A | ||
| 332 | || cipher_lo == 0x23 || cipher_lo == 0x2B | ||
| 333 | ); | ||
| 334 | } | ||
| 335 | #endif | ||
| 336 | |||
| 337 | /* Set cipher parameters based on selected cipher_id */ | ||
| 338 | static void set_cipher_parameters(tls_state_t *tls, const uint8_t *cipherid) | ||
| 339 | { | ||
| 340 | uint8_t cipherid1 = cipherid[1]; | ||
| 341 | |||
| 342 | tls->cipher_id = 0x100 * cipherid[0] + cipherid1; | ||
| 343 | |||
| 344 | /* Set defaults for RSA with AES-256 */ | ||
| 345 | tls->key_size = AES256_KEYSIZE; | ||
| 346 | tls->MAC_size = SHA256_OUTSIZE; | ||
| 347 | tls->IV_size = 0; | ||
| 348 | |||
| 349 | if (cipherid[0] == 0xC0) { | ||
| 350 | /* All C0xx are ECDHE */ | ||
| 351 | tls->flags |= NEED_EC_KEY; | ||
| 352 | if (cipherid1 & 1) { | ||
| 353 | /* Odd numbered C0xx use AES128 (even ones use AES256) */ | ||
| 354 | tls->key_size = AES128_KEYSIZE; | ||
| 355 | } | ||
| 356 | if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x19) { | ||
| 357 | tls->MAC_size = SHA1_OUTSIZE; | ||
| 358 | } else | ||
| 359 | if (cipherid1 >= 0x2B && cipherid1 <= 0x30) { | ||
| 360 | /* C02B,2C,2F,30 are AES-GCM */ | ||
| 361 | tls->flags |= ENCRYPTION_AESGCM; | ||
| 362 | tls->MAC_size = 0; | ||
| 363 | tls->IV_size = 4; | ||
| 364 | } | ||
| 365 | } else { | ||
| 366 | /* All 00xx are RSA */ | ||
| 367 | if ((ENABLE_FEATURE_TLS_SHA1 && cipherid1 == 0x2F) | ||
| 368 | || cipherid1 == 0x3C | ||
| 369 | || cipherid1 == 0x9C | ||
| 370 | ) { | ||
| 371 | tls->key_size = AES128_KEYSIZE; | ||
| 372 | } | ||
| 373 | if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x35) { | ||
| 374 | tls->MAC_size = SHA1_OUTSIZE; | ||
| 375 | } else | ||
| 376 | if (cipherid1 == 0x9C /*|| cipherid1 == 0x9D*/) { | ||
| 377 | /* 009C,9D are AES-GCM */ | ||
| 378 | tls->flags |= ENCRYPTION_AESGCM; | ||
| 379 | tls->MAC_size = 0; | ||
| 380 | tls->IV_size = 4; | ||
| 381 | } | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 249 | struct record_hdr { | 385 | struct record_hdr { |
| 250 | uint8_t type; | 386 | uint8_t type; |
| 251 | uint8_t proto_maj, proto_min; | 387 | uint8_t proto_maj, proto_min; |
| @@ -271,14 +407,46 @@ struct tls_handshake_data { | |||
| 271 | /* HANDSHAKE HASH: */ | 407 | /* HANDSHAKE HASH: */ |
| 272 | //unsigned saved_client_hello_size; | 408 | //unsigned saved_client_hello_size; |
| 273 | //uint8_t saved_client_hello[1]; | 409 | //uint8_t saved_client_hello[1]; |
| 274 | }; | ||
| 275 | 410 | ||
| 411 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL | ||
| 412 | smallint reneg_info_requested; | ||
| 413 | /* Server certificate and key data */ | ||
| 414 | char *keys[2]; | ||
| 415 | char *certs[2]; | ||
| 416 | unsigned keysize[2]; | ||
| 417 | unsigned certsize[2]; | ||
| 418 | int key_type_chosen; | ||
| 419 | psRsaKey_t rsa_priv_key; | ||
| 420 | |||
| 421 | /* For ECDHE: server's ephemeral EC private key */ | ||
| 422 | uint8_t ecc_priv_key32[32]; | ||
| 423 | #endif | ||
| 424 | }; | ||
| 425 | enum { | ||
| 426 | KEY_RSA, | ||
| 427 | KEY_ECDSA, | ||
| 428 | }; | ||
| 276 | 429 | ||
| 277 | static unsigned get24be(const uint8_t *p) | 430 | static unsigned get24be(const uint8_t *p) |
| 278 | { | 431 | { |
| 279 | return 0x100*(0x100*p[0] + p[1]) + p[2]; | 432 | return 0x100*(0x100*p[0] + p[1]) + p[2]; |
| 280 | } | 433 | } |
| 281 | 434 | ||
| 435 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL | ||
| 436 | static int is_minor_version_valid(tls_state_t *tls, uint8_t minor_ver) | ||
| 437 | { | ||
| 438 | if (tls->expecting_first_packet == 1) { | ||
| 439 | /* First packet: accept TLS 1.0 (3.1) through TLS 1.3 (3.4) | ||
| 440 | * for compatibility with clients using other record versions */ | ||
| 441 | return (minor_ver > 0 && minor_ver <= 4); | ||
| 442 | } | ||
| 443 | /* Subsequent packets: must match negotiated version exactly */ | ||
| 444 | return minor_ver == TLS_MIN; | ||
| 445 | } | ||
| 446 | #else | ||
| 447 | #define is_minor_version_valid(tls, minor_ver) ((minor_ver) == TLS_MIN) | ||
| 448 | #endif | ||
| 449 | |||
| 282 | #if TLS_DEBUG | 450 | #if TLS_DEBUG |
| 283 | /* Nondestructively see the current hash value */ | 451 | /* Nondestructively see the current hash value */ |
| 284 | # if TLS_DEBUG_HASH | 452 | # if TLS_DEBUG_HASH |
| @@ -295,7 +463,7 @@ static void dump_hex(const char *fmt, const void *vp, int len) | |||
| 295 | const uint8_t *p = vp; | 463 | const uint8_t *p = vp; |
| 296 | 464 | ||
| 297 | bin2hex(hexbuf, (void*)p, len)[0] = '\0'; | 465 | bin2hex(hexbuf, (void*)p, len)[0] = '\0'; |
| 298 | dbg(fmt, hexbuf); | 466 | bb_error_msg(fmt, hexbuf); |
| 299 | } | 467 | } |
| 300 | 468 | ||
| 301 | static void dump_tls_record(const void *vp, int len) | 469 | static void dump_tls_record(const void *vp, int len) |
| @@ -305,20 +473,20 @@ static void dump_tls_record(const void *vp, int len) | |||
| 305 | while (len > 0) { | 473 | while (len > 0) { |
| 306 | unsigned xhdr_len; | 474 | unsigned xhdr_len; |
| 307 | if (len < RECHDR_LEN) { | 475 | if (len < RECHDR_LEN) { |
| 308 | dump_hex("< |%s|\n", p, len); | 476 | dump_hex("< |%s|", p, len); |
| 309 | return; | 477 | return; |
| 310 | } | 478 | } |
| 311 | xhdr_len = 0x100*p[3] + p[4]; | 479 | xhdr_len = 0x100*p[3] + p[4]; |
| 312 | dbg("< hdr_type:%u ver:%u.%u len:%u", p[0], p[1], p[2], xhdr_len); | 480 | dbgcont("%s: < hdr_type:%u ver:%u.%u len:%u", applet_name, p[0], p[1], p[2], xhdr_len); |
| 313 | p += RECHDR_LEN; | 481 | p += RECHDR_LEN; |
| 314 | len -= RECHDR_LEN; | 482 | len -= RECHDR_LEN; |
| 315 | if (len >= 4 && p[-RECHDR_LEN] == RECORD_TYPE_HANDSHAKE) { | 483 | if (len >= 4 && p[-RECHDR_LEN] == RECORD_TYPE_HANDSHAKE) { |
| 316 | unsigned len24 = get24be(p + 1); | 484 | unsigned len24 = get24be(p + 1); |
| 317 | dbg(" type:%u len24:%u", p[0], len24); | 485 | dbgcont(" type:%u len24:%u", p[0], len24); |
| 318 | } | 486 | } |
| 319 | if (xhdr_len > len) | 487 | if (xhdr_len > len) |
| 320 | xhdr_len = len; | 488 | xhdr_len = len; |
| 321 | dump_hex(" |%s|\n", p, xhdr_len); | 489 | dump_hex(" |%s|", p, xhdr_len); |
| 322 | p += xhdr_len; | 490 | p += xhdr_len; |
| 323 | len -= xhdr_len; | 491 | len -= xhdr_len; |
| 324 | } | 492 | } |
| @@ -348,12 +516,12 @@ static void hash_handshake(tls_state_t *tls, const char *fmt, const void *buffer | |||
| 348 | dbg(" (%u bytes) ", (int)len); | 516 | dbg(" (%u bytes) ", (int)len); |
| 349 | len = sha_peek(&tls->hsd->handshake_hash_ctx, h); | 517 | len = sha_peek(&tls->hsd->handshake_hash_ctx, h); |
| 350 | if (ENABLE_FEATURE_TLS_SHA1 && len == SHA1_OUTSIZE) | 518 | if (ENABLE_FEATURE_TLS_SHA1 && len == SHA1_OUTSIZE) |
| 351 | dump_hex("sha1:%s\n", h, len); | 519 | dump_hex("sha1:%s", h, len); |
| 352 | else | 520 | else |
| 353 | if (len == SHA256_OUTSIZE) | 521 | if (len == SHA256_OUTSIZE) |
| 354 | dump_hex("sha256:%s\n", h, len); | 522 | dump_hex("sha256:%s", h, len); |
| 355 | else | 523 | else |
| 356 | dump_hex("sha???:%s\n", h, len); | 524 | dump_hex("sha???:%s", h, len); |
| 357 | } | 525 | } |
| 358 | #endif | 526 | #endif |
| 359 | } | 527 | } |
| @@ -550,11 +718,11 @@ static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, un | |||
| 550 | xhdr->proto_min = TLS_MIN; | 718 | xhdr->proto_min = TLS_MIN; |
| 551 | /* fake unencrypted record len for MAC calculation */ | 719 | /* fake unencrypted record len for MAC calculation */ |
| 552 | xhdr->len16_hi = size >> 8; | 720 | xhdr->len16_hi = size >> 8; |
| 553 | xhdr->len16_lo = size & 0xff; | 721 | xhdr->len16_lo = size; // & 0xff implicit |
| 554 | 722 | ||
| 555 | /* Calculate MAC signature */ | 723 | /* Calculate MAC signature */ |
| 556 | hmac_blocks(tls, buf + size, /* result */ | 724 | hmac_blocks(tls, buf + size, /* result */ |
| 557 | tls->client_write_MAC_key, TLS_MAC_SIZE(tls), | 725 | tls->our_write_MAC_key, TLS_MAC_SIZE(tls), |
| 558 | &tls->write_seq64_be, sizeof(tls->write_seq64_be), | 726 | &tls->write_seq64_be, sizeof(tls->write_seq64_be), |
| 559 | xhdr, RECHDR_LEN, | 727 | xhdr, RECHDR_LEN, |
| 560 | buf, size, | 728 | buf, size, |
| @@ -602,10 +770,10 @@ static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, un | |||
| 602 | ) { | 770 | ) { |
| 603 | /* No encryption, only signing */ | 771 | /* No encryption, only signing */ |
| 604 | xhdr->len16_hi = size >> 8; | 772 | xhdr->len16_hi = size >> 8; |
| 605 | xhdr->len16_lo = size & 0xff; | 773 | xhdr->len16_lo = size; // & 0xff implicit |
| 606 | dump_raw_out(">> %s\n", xhdr, RECHDR_LEN + size); | 774 | dump_raw_out(">> %s", xhdr, RECHDR_LEN + size); |
| 607 | xwrite(tls->ofd, xhdr, RECHDR_LEN + size); | 775 | xwrite(tls->ofd, xhdr, RECHDR_LEN + size); |
| 608 | dbg("wrote %u bytes (NULL crypt, SHA256 hash)\n", size); | 776 | dbg("wrote %u bytes (NULL crypt, SHA256 hash)", size); |
| 609 | return; | 777 | return; |
| 610 | } | 778 | } |
| 611 | 779 | ||
| @@ -646,7 +814,7 @@ static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, un | |||
| 646 | // AES_256_CBC Block 32 16 16 | 814 | // AES_256_CBC Block 32 16 16 |
| 647 | 815 | ||
| 648 | tls_get_random(buf - AES_BLOCK_SIZE, AES_BLOCK_SIZE); /* IV */ | 816 | tls_get_random(buf - AES_BLOCK_SIZE, AES_BLOCK_SIZE); /* IV */ |
| 649 | dbg("before crypt: 5 hdr + %u data + %u hash bytes\n", | 817 | dbg("before crypt: 5 hdr + %u data + %u hash bytes", |
| 650 | size - TLS_MAC_SIZE(tls), TLS_MAC_SIZE(tls)); | 818 | size - TLS_MAC_SIZE(tls), TLS_MAC_SIZE(tls)); |
| 651 | 819 | ||
| 652 | /* Fill IV and padding in outbuf */ | 820 | /* Fill IV and padding in outbuf */ |
| @@ -679,14 +847,14 @@ static void xwrite_encrypted_and_hmac_signed(tls_state_t *tls, unsigned size, un | |||
| 679 | ); | 847 | ); |
| 680 | 848 | ||
| 681 | /* Write out */ | 849 | /* Write out */ |
| 682 | dbg("writing 5 + %u IV + %u encrypted bytes, padding_length:0x%02x\n", | 850 | dbg("writing 5 + %u IV + %u encrypted bytes, padding_length:0x%02x", |
| 683 | AES_BLOCK_SIZE, size, padding_length); | 851 | AES_BLOCK_SIZE, size, padding_length); |
| 684 | size += AES_BLOCK_SIZE; /* + IV */ | 852 | size += AES_BLOCK_SIZE; /* + IV */ |
| 685 | xhdr->len16_hi = size >> 8; | 853 | xhdr->len16_hi = size >> 8; |
| 686 | xhdr->len16_lo = size & 0xff; | 854 | xhdr->len16_lo = size; // & 0xff implicit |
| 687 | dump_raw_out(">> %s\n", xhdr, RECHDR_LEN + size); | 855 | dump_raw_out(">> %s", xhdr, RECHDR_LEN + size); |
| 688 | xwrite(tls->ofd, xhdr, RECHDR_LEN + size); | 856 | xwrite(tls->ofd, xhdr, RECHDR_LEN + size); |
| 689 | dbg("wrote %u bytes\n", (int)RECHDR_LEN + size); | 857 | dbg("wrote %u bytes", (int)RECHDR_LEN + size); |
| 690 | } | 858 | } |
| 691 | 859 | ||
| 692 | /* Example how GCM encryption combines nonce, aad, input and generates | 860 | /* Example how GCM encryption combines nonce, aad, input and generates |
| @@ -714,7 +882,7 @@ static void xwrite_encrypted_aesgcm(tls_state_t *tls, unsigned size, unsigned ty | |||
| 714 | uint64_t t64; | 882 | uint64_t t64; |
| 715 | 883 | ||
| 716 | buf = tls->outbuf + OUTBUF_PFX; /* see above for the byte it points to */ | 884 | buf = tls->outbuf + OUTBUF_PFX; /* see above for the byte it points to */ |
| 717 | dump_hex("xwrite_encrypted_aesgcm plaintext:%s\n", buf, size); | 885 | dump_hex("xwrite_encrypted_aesgcm plaintext:%s", buf, size); |
| 718 | 886 | ||
| 719 | xhdr = (void*)(buf - 8 - RECHDR_LEN); | 887 | xhdr = (void*)(buf - 8 - RECHDR_LEN); |
| 720 | xhdr->type = type; /* do it here so that "type" param no longer used */ | 888 | xhdr->type = type; /* do it here so that "type" param no longer used */ |
| @@ -726,7 +894,7 @@ static void xwrite_encrypted_aesgcm(tls_state_t *tls, unsigned size, unsigned ty | |||
| 726 | /* set aad[12], and clear aad[13..15] */ | 894 | /* set aad[12], and clear aad[13..15] */ |
| 727 | COUNTER(aad) = SWAP_LE32(size & 0xff); | 895 | COUNTER(aad) = SWAP_LE32(size & 0xff); |
| 728 | 896 | ||
| 729 | memcpy(nonce, tls->client_write_IV, 4); | 897 | memcpy(nonce, tls->our_write_IV, 4); |
| 730 | t64 = tls->write_seq64_be; | 898 | t64 = tls->write_seq64_be; |
| 731 | move_to_unaligned64(nonce + 4, t64); | 899 | move_to_unaligned64(nonce + 4, t64); |
| 732 | move_to_unaligned64(aad, t64); | 900 | move_to_unaligned64(aad, t64); |
| @@ -767,11 +935,11 @@ static void xwrite_encrypted_aesgcm(tls_state_t *tls, unsigned size, unsigned ty | |||
| 767 | xhdr->proto_maj = TLS_MAJ; | 935 | xhdr->proto_maj = TLS_MAJ; |
| 768 | xhdr->proto_min = TLS_MIN; | 936 | xhdr->proto_min = TLS_MIN; |
| 769 | xhdr->len16_hi = size >> 8; | 937 | xhdr->len16_hi = size >> 8; |
| 770 | xhdr->len16_lo = size & 0xff; | 938 | xhdr->len16_lo = size; // & 0xff implicit |
| 771 | size += RECHDR_LEN; | 939 | size += RECHDR_LEN; |
| 772 | dump_raw_out(">> %s\n", xhdr, size); | 940 | dump_raw_out(">> %s", xhdr, size); |
| 773 | xwrite(tls->ofd, xhdr, size); | 941 | xwrite(tls->ofd, xhdr, size); |
| 774 | dbg("wrote %u bytes\n", size); | 942 | dbg("wrote %u bytes", size); |
| 775 | #undef COUNTER | 943 | #undef COUNTER |
| 776 | } | 944 | } |
| 777 | 945 | ||
| @@ -793,24 +961,19 @@ static void xwrite_handshake_record(tls_state_t *tls, unsigned size) | |||
| 793 | xhdr->proto_maj = TLS_MAJ; | 961 | xhdr->proto_maj = TLS_MAJ; |
| 794 | xhdr->proto_min = TLS_MIN; | 962 | xhdr->proto_min = TLS_MIN; |
| 795 | xhdr->len16_hi = size >> 8; | 963 | xhdr->len16_hi = size >> 8; |
| 796 | xhdr->len16_lo = size & 0xff; | 964 | xhdr->len16_lo = size; // & 0xff implicit |
| 797 | dump_raw_out(">> %s\n", xhdr, RECHDR_LEN + size); | 965 | dump_raw_out(">> %s", xhdr, RECHDR_LEN + size); |
| 798 | xwrite(tls->ofd, xhdr, RECHDR_LEN + size); | 966 | xwrite(tls->ofd, xhdr, RECHDR_LEN + size); |
| 799 | dbg("wrote %u bytes\n", (int)RECHDR_LEN + size); | 967 | dbg("wrote %u bytes", (int)RECHDR_LEN + size); |
| 800 | } | 968 | } |
| 801 | 969 | ||
| 802 | static void xwrite_and_update_handshake_hash(tls_state_t *tls, unsigned size) | 970 | static void xwrite_and_update_handshake_hash(tls_state_t *tls, unsigned size) |
| 803 | { | 971 | { |
| 804 | if (!(tls->flags & ENCRYPT_ON_WRITE)) { | 972 | uint8_t *buf; |
| 805 | uint8_t *buf; | 973 | xwrite_handshake_record(tls, size); |
| 806 | 974 | /* Handshake hash does not include record headers */ | |
| 807 | xwrite_handshake_record(tls, size); | 975 | buf = tls->outbuf + OUTBUF_PFX; |
| 808 | /* Handshake hash does not include record headers */ | 976 | hash_handshake(tls, ">> hash:%s", buf, size); |
| 809 | buf = tls->outbuf + OUTBUF_PFX; | ||
| 810 | hash_handshake(tls, ">> hash:%s", buf, size); | ||
| 811 | return; | ||
| 812 | } | ||
| 813 | xwrite_encrypted(tls, size, RECORD_TYPE_HANDSHAKE); | ||
| 814 | } | 977 | } |
| 815 | 978 | ||
| 816 | static int tls_has_buffered_record(tls_state_t *tls) | 979 | static int tls_has_buffered_record(tls_state_t *tls) |
| @@ -894,7 +1057,7 @@ static void tls_aesgcm_decrypt(tls_state_t *tls, uint8_t *buf, int size) | |||
| 894 | ///* set aad[12], and clear aad[13..15] */ | 1057 | ///* set aad[12], and clear aad[13..15] */ |
| 895 | //COUNTER(aad) = SWAP_LE32(size & 0xff); | 1058 | //COUNTER(aad) = SWAP_LE32(size & 0xff); |
| 896 | 1059 | ||
| 897 | memcpy(nonce, tls->server_write_IV, 4); | 1060 | memcpy(nonce, tls->peer_write_IV, 4); |
| 898 | memcpy(nonce + 4, buf, 8); | 1061 | memcpy(nonce + 4, buf, 8); |
| 899 | 1062 | ||
| 900 | cnt = 1; | 1063 | cnt = 1; |
| @@ -928,12 +1091,12 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 928 | int target; | 1091 | int target; |
| 929 | 1092 | ||
| 930 | again: | 1093 | again: |
| 931 | dbg("ofs_to_buffered:%u buffered_size:%u\n", tls->ofs_to_buffered, tls->buffered_size); | 1094 | dbg("ofs_to_buffered:%u buffered_size:%u", tls->ofs_to_buffered, tls->buffered_size); |
| 932 | total = tls->buffered_size; | 1095 | total = tls->buffered_size; |
| 933 | if (total != 0) { | 1096 | if (total != 0) { |
| 934 | memmove(tls->inbuf, tls->inbuf + tls->ofs_to_buffered, total); | 1097 | memmove(tls->inbuf, tls->inbuf + tls->ofs_to_buffered, total); |
| 935 | //dbg("<< remaining at %d [%d] ", tls->ofs_to_buffered, total); | 1098 | //dbg("<< remaining at %d [%d] ", tls->ofs_to_buffered, total); |
| 936 | //dump_raw_in("<< %s\n", tls->inbuf, total); | 1099 | //dump_raw_in("<< %s", tls->inbuf, total); |
| 937 | } | 1100 | } |
| 938 | errno = 0; | 1101 | errno = 0; |
| 939 | target = MAX_INBUF; | 1102 | target = MAX_INBUF; |
| @@ -946,12 +1109,12 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 946 | 1109 | ||
| 947 | if (target > MAX_INBUF /* malformed input (too long) */ | 1110 | if (target > MAX_INBUF /* malformed input (too long) */ |
| 948 | || xhdr->proto_maj != TLS_MAJ | 1111 | || xhdr->proto_maj != TLS_MAJ |
| 949 | || xhdr->proto_min != TLS_MIN | 1112 | || !is_minor_version_valid(tls, xhdr->proto_min) |
| 950 | ) { | 1113 | ) { |
| 951 | sz = total < target ? total : target; | 1114 | sz = total < target ? total : target; |
| 952 | bad_record_die(tls, expected, sz); | 1115 | bad_record_die(tls, expected, sz); |
| 953 | } | 1116 | } |
| 954 | dbg("xhdr type:%d ver:%d.%d len:%d\n", | 1117 | dbg("xhdr type:%d ver:%d.%d len:%d", |
| 955 | xhdr->type, xhdr->proto_maj, xhdr->proto_min, | 1118 | xhdr->type, xhdr->proto_maj, xhdr->proto_min, |
| 956 | 0x100 * xhdr->len16_hi + xhdr->len16_lo | 1119 | 0x100 * xhdr->len16_hi + xhdr->len16_lo |
| 957 | ); | 1120 | ); |
| @@ -965,7 +1128,7 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 965 | tls->inbuf_size += MAX_INBUF / 8; | 1128 | tls->inbuf_size += MAX_INBUF / 8; |
| 966 | if (tls->inbuf_size > MAX_INBUF) | 1129 | if (tls->inbuf_size > MAX_INBUF) |
| 967 | tls->inbuf_size = MAX_INBUF; | 1130 | tls->inbuf_size = MAX_INBUF; |
| 968 | dbg("inbuf_size:%d\n", tls->inbuf_size); | 1131 | dbg("inbuf_size:%d", tls->inbuf_size); |
| 969 | rem = tls->inbuf_size - total; | 1132 | rem = tls->inbuf_size - total; |
| 970 | tls->inbuf = xrealloc(tls->inbuf, tls->inbuf_size); | 1133 | tls->inbuf = xrealloc(tls->inbuf, tls->inbuf_size); |
| 971 | } | 1134 | } |
| @@ -973,7 +1136,7 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 973 | if (sz <= 0) { | 1136 | if (sz <= 0) { |
| 974 | if (sz == 0 && total == 0) { | 1137 | if (sz == 0 && total == 0) { |
| 975 | /* "Abrupt" EOF, no TLS shutdown (seen from kernel.org) */ | 1138 | /* "Abrupt" EOF, no TLS shutdown (seen from kernel.org) */ |
| 976 | dbg("EOF (without TLS shutdown) from peer\n"); | 1139 | dbg("EOF (without TLS shutdown) from peer"); |
| 977 | tls->buffered_size = 0; | 1140 | tls->buffered_size = 0; |
| 978 | goto end; | 1141 | goto end; |
| 979 | } | 1142 | } |
| @@ -982,13 +1145,13 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 982 | bb_perror_msg_and_die("%s header: got %d bytes", "truncated TLS record", total); | 1145 | bb_perror_msg_and_die("%s header: got %d bytes", "truncated TLS record", total); |
| 983 | bb_perror_msg_and_die("%s: expected %d, got %d bytes", "truncated TLS record", target, total); | 1146 | bb_perror_msg_and_die("%s: expected %d, got %d bytes", "truncated TLS record", target, total); |
| 984 | } | 1147 | } |
| 985 | dump_raw_in("<< %s\n", tls->inbuf + total, sz); | 1148 | dump_raw_in("<< %s", tls->inbuf + total, sz); |
| 986 | total += sz; | 1149 | total += sz; |
| 987 | } | 1150 | } |
| 988 | tls->buffered_size = total - target; | 1151 | tls->buffered_size = total - target; |
| 989 | tls->ofs_to_buffered = target; | 1152 | tls->ofs_to_buffered = target; |
| 990 | //dbg("<< stashing at %d [%d] ", tls->ofs_to_buffered, tls->buffered_size); | 1153 | //dbg("<< stashing at %d [%d] ", tls->ofs_to_buffered, tls->buffered_size); |
| 991 | //dump_hex("<< %s\n", tls->inbuf + tls->ofs_to_buffered, tls->buffered_size); | 1154 | //dump_hex("<< %s", tls->inbuf + tls->ofs_to_buffered, tls->buffered_size); |
| 992 | 1155 | ||
| 993 | sz = target - RECHDR_LEN; | 1156 | sz = target - RECHDR_LEN; |
| 994 | 1157 | ||
| @@ -1003,7 +1166,7 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 1003 | 1166 | ||
| 1004 | sz -= 8 + AES_BLOCK_SIZE; /* we will overwrite nonce, drop hash */ | 1167 | sz -= 8 + AES_BLOCK_SIZE; /* we will overwrite nonce, drop hash */ |
| 1005 | tls_aesgcm_decrypt(tls, p, sz); | 1168 | tls_aesgcm_decrypt(tls, p, sz); |
| 1006 | dbg("encrypted size:%u\n", sz); | 1169 | dbg("encrypted size:%u", sz); |
| 1007 | } else | 1170 | } else |
| 1008 | if (tls->min_encrypted_len_on_read > TLS_MAC_SIZE(tls)) { | 1171 | if (tls->min_encrypted_len_on_read > TLS_MAC_SIZE(tls)) { |
| 1009 | /* AES+SHA */ | 1172 | /* AES+SHA */ |
| @@ -1022,7 +1185,7 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 1022 | p /* plaintext */ | 1185 | p /* plaintext */ |
| 1023 | ); | 1186 | ); |
| 1024 | padding_len = p[sz - 1]; | 1187 | padding_len = p[sz - 1]; |
| 1025 | dbg("encrypted size:%u type:0x%02x padding_length:0x%02x\n", sz, p[0], padding_len); | 1188 | dbg("encrypted size:%u type:0x%02x padding_length:0x%02x", sz, p[0], padding_len); |
| 1026 | padding_len++; | 1189 | padding_len++; |
| 1027 | sz -= TLS_MAC_SIZE(tls) + padding_len; /* drop MAC and padding */ | 1190 | sz -= TLS_MAC_SIZE(tls) + padding_len; /* drop MAC and padding */ |
| 1028 | } else { | 1191 | } else { |
| @@ -1034,12 +1197,12 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 1034 | if (sz < 0) | 1197 | if (sz < 0) |
| 1035 | bb_simple_error_msg_and_die("encrypted data too short"); | 1198 | bb_simple_error_msg_and_die("encrypted data too short"); |
| 1036 | 1199 | ||
| 1037 | //dump_hex("<< %s\n", tls->inbuf, RECHDR_LEN + sz); | 1200 | //dump_hex("<< %s", tls->inbuf, RECHDR_LEN + sz); |
| 1038 | 1201 | ||
| 1039 | xhdr = (void*)tls->inbuf; | 1202 | xhdr = (void*)tls->inbuf; |
| 1040 | if (xhdr->type == RECORD_TYPE_ALERT && sz >= 2) { | 1203 | if (xhdr->type == RECORD_TYPE_ALERT && sz >= 2) { |
| 1041 | uint8_t *p = tls->inbuf + RECHDR_LEN; | 1204 | uint8_t *p = tls->inbuf + RECHDR_LEN; |
| 1042 | dbg("ALERT size:%d level:%d description:%d\n", sz, p[0], p[1]); | 1205 | dbg("ALERT size:%d level:%d description:%d", sz, p[0], p[1]); |
| 1043 | if (p[0] == 2) { /* fatal */ | 1206 | if (p[0] == 2) { /* fatal */ |
| 1044 | bb_error_msg_and_die("TLS %s from peer (alert code %d): %s", | 1207 | bb_error_msg_and_die("TLS %s from peer (alert code %d): %s", |
| 1045 | "error", | 1208 | "error", |
| @@ -1048,7 +1211,7 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 1048 | } | 1211 | } |
| 1049 | if (p[0] == 1) { /* warning */ | 1212 | if (p[0] == 1) { /* warning */ |
| 1050 | if (p[1] == 0) { /* "close_notify" warning: it's EOF */ | 1213 | if (p[1] == 0) { /* "close_notify" warning: it's EOF */ |
| 1051 | dbg("EOF (TLS encoded) from peer\n"); | 1214 | dbg("EOF (TLS encoded) from peer"); |
| 1052 | sz = 0; | 1215 | sz = 0; |
| 1053 | goto end; | 1216 | goto end; |
| 1054 | } | 1217 | } |
| @@ -1076,7 +1239,7 @@ static int tls_xread_record(tls_state_t *tls, const char *expected) | |||
| 1076 | hash_handshake(tls, "<< hash:%s", tls->inbuf + RECHDR_LEN, sz); | 1239 | hash_handshake(tls, "<< hash:%s", tls->inbuf + RECHDR_LEN, sz); |
| 1077 | } | 1240 | } |
| 1078 | end: | 1241 | end: |
| 1079 | dbg("got block len:%u\n", sz); | 1242 | dbg("got block len:%u", sz); |
| 1080 | return sz; | 1243 | return sz; |
| 1081 | } | 1244 | } |
| 1082 | 1245 | ||
| @@ -1136,7 +1299,7 @@ static uint8_t *enter_der_item(uint8_t *der, uint8_t **endp) | |||
| 1136 | { | 1299 | { |
| 1137 | uint8_t *new_der; | 1300 | uint8_t *new_der; |
| 1138 | unsigned len = get_der_len(&new_der, der, *endp); | 1301 | unsigned len = get_der_len(&new_der, der, *endp); |
| 1139 | dbg_der("entered der @%p:0x%02x len:%u inner_byte @%p:0x%02x\n", der, der[0], len, new_der, new_der[0]); | 1302 | dbg_der("entered der @%p:0x%02x len:%u inner_byte @%p:0x%02x", der, der[0], len, new_der, new_der[0]); |
| 1140 | /* Move "end" position to cover only this item */ | 1303 | /* Move "end" position to cover only this item */ |
| 1141 | *endp = new_der + len; | 1304 | *endp = new_der + len; |
| 1142 | return new_der; | 1305 | return new_der; |
| @@ -1148,7 +1311,7 @@ static uint8_t *skip_der_item(uint8_t *der, uint8_t *end) | |||
| 1148 | unsigned len = get_der_len(&new_der, der, end); | 1311 | unsigned len = get_der_len(&new_der, der, end); |
| 1149 | /* Skip body */ | 1312 | /* Skip body */ |
| 1150 | new_der += len; | 1313 | new_der += len; |
| 1151 | dbg_der("skipped der 0x%02x, next byte 0x%02x\n", der[0], new_der[0]); | 1314 | dbg_der("skipped der 0x%02x, next byte 0x%02x", der[0], new_der[0]); |
| 1152 | return new_der; | 1315 | return new_der; |
| 1153 | } | 1316 | } |
| 1154 | 1317 | ||
| @@ -1157,7 +1320,7 @@ static void der_binary_to_pstm(pstm_int *pstm_n, uint8_t *der, uint8_t *end) | |||
| 1157 | uint8_t *bin_ptr; | 1320 | uint8_t *bin_ptr; |
| 1158 | unsigned len = get_der_len(&bin_ptr, der, end); | 1321 | unsigned len = get_der_len(&bin_ptr, der, end); |
| 1159 | 1322 | ||
| 1160 | dbg_der("binary bytes:%u, first:0x%02x\n", len, bin_ptr[0]); | 1323 | dbg_der("binary bytes:%u, first:0x%02x", len, bin_ptr[0]); |
| 1161 | binary_to_pstm(pstm_n, bin_ptr, len); | 1324 | binary_to_pstm(pstm_n, bin_ptr, len); |
| 1162 | } | 1325 | } |
| 1163 | 1326 | ||
| @@ -1304,11 +1467,11 @@ static void find_key_in_der_cert(tls_state_t *tls, uint8_t *der, int len) | |||
| 1304 | //42.134.72.206.61.3.1.7 is curve_secp256r1 | 1467 | //42.134.72.206.61.3.1.7 is curve_secp256r1 |
| 1305 | }; | 1468 | }; |
| 1306 | if (memcmp(der, OID_RSA_KEY_ALG, sizeof(OID_RSA_KEY_ALG)) == 0) { | 1469 | if (memcmp(der, OID_RSA_KEY_ALG, sizeof(OID_RSA_KEY_ALG)) == 0) { |
| 1307 | dbg("RSA key\n"); | 1470 | dbg("RSA key"); |
| 1308 | tls->flags |= GOT_CERT_RSA_KEY_ALG; | 1471 | tls->flags |= GOT_CERT_RSA_KEY_ALG; |
| 1309 | } else | 1472 | } else |
| 1310 | if (memcmp(der, OID_ECDSA_KEY_ALG, sizeof(OID_ECDSA_KEY_ALG)) == 0) { | 1473 | if (memcmp(der, OID_ECDSA_KEY_ALG, sizeof(OID_ECDSA_KEY_ALG)) == 0) { |
| 1311 | dbg("ECDSA key\n"); | 1474 | dbg("ECDSA key"); |
| 1312 | //UNUSED: tls->flags |= GOT_CERT_ECDSA_KEY_ALG; | 1475 | //UNUSED: tls->flags |= GOT_CERT_ECDSA_KEY_ALG; |
| 1313 | } else | 1476 | } else |
| 1314 | bb_simple_error_msg_and_die("not RSA or ECDSA cert"); | 1477 | bb_simple_error_msg_and_die("not RSA or ECDSA cert"); |
| @@ -1323,7 +1486,7 @@ static void find_key_in_der_cert(tls_state_t *tls, uint8_t *der, int len) | |||
| 1323 | //die_if_not_this_der_type(der, end, 0x03); /* must be BITSTRING */ | 1486 | //die_if_not_this_der_type(der, end, 0x03); /* must be BITSTRING */ |
| 1324 | der = enter_der_item(der, &end); | 1487 | der = enter_der_item(der, &end); |
| 1325 | 1488 | ||
| 1326 | dbg("key bytes:%u, first:0x%02x\n", (int)(end - der), der[0]); | 1489 | dbg("key bytes:%u, first:0x%02x", (int)(end - der), der[0]); |
| 1327 | if (end - der < 14) | 1490 | if (end - der < 14) |
| 1328 | xfunc_die(); | 1491 | xfunc_die(); |
| 1329 | /* example format: | 1492 | /* example format: |
| @@ -1341,7 +1504,7 @@ static void find_key_in_der_cert(tls_state_t *tls, uint8_t *der, int len) | |||
| 1341 | der = skip_der_item(der, end); | 1504 | der = skip_der_item(der, end); |
| 1342 | der_binary_to_pstm(&tls->hsd->server_rsa_pub_key.e, der, end); /* exponent */ | 1505 | der_binary_to_pstm(&tls->hsd->server_rsa_pub_key.e, der, end); /* exponent */ |
| 1343 | tls->hsd->server_rsa_pub_key.size = pstm_unsigned_bin_size(&tls->hsd->server_rsa_pub_key.N); | 1506 | tls->hsd->server_rsa_pub_key.size = pstm_unsigned_bin_size(&tls->hsd->server_rsa_pub_key.N); |
| 1344 | dbg("server_rsa_pub_key.size:%d\n", tls->hsd->server_rsa_pub_key.size); | 1507 | dbg("server_rsa_pub_key.size:%d", tls->hsd->server_rsa_pub_key.size); |
| 1345 | } | 1508 | } |
| 1346 | /* else: ECDSA key. It is not used for generating encryption keys, | 1509 | /* else: ECDSA key. It is not used for generating encryption keys, |
| 1347 | * it is used only to sign the EC public key (which comes in ServerKey message). | 1510 | * it is used only to sign the EC public key (which comes in ServerKey message). |
| @@ -1364,7 +1527,7 @@ static int tls_xread_handshake_block(tls_state_t *tls, int min_len) | |||
| 1364 | ) { | 1527 | ) { |
| 1365 | bad_record_die(tls, "handshake record", len); | 1528 | bad_record_die(tls, "handshake record", len); |
| 1366 | } | 1529 | } |
| 1367 | dbg("got HANDSHAKE\n"); | 1530 | dbg("got HANDSHAKE"); |
| 1368 | return len; | 1531 | return len; |
| 1369 | } | 1532 | } |
| 1370 | 1533 | ||
| @@ -1379,78 +1542,18 @@ static ALWAYS_INLINE void fill_handshake_record_hdr(void *buf, unsigned type, un | |||
| 1379 | h->type = type; | 1542 | h->type = type; |
| 1380 | h->len24_hi = len >> 16; | 1543 | h->len24_hi = len >> 16; |
| 1381 | h->len24_mid = len >> 8; | 1544 | h->len24_mid = len >> 8; |
| 1382 | h->len24_lo = len & 0xff; | 1545 | h->len24_lo = len; // & 0xff implicit |
| 1546 | } | ||
| 1547 | |||
| 1548 | static void *get_outbuf_fill_handshake_record(tls_state_t *tls, unsigned type, unsigned len) | ||
| 1549 | { | ||
| 1550 | void *record = tls_get_zeroed_outbuf(tls, len); | ||
| 1551 | fill_handshake_record_hdr(record, type, len); | ||
| 1552 | return record; | ||
| 1383 | } | 1553 | } |
| 1384 | 1554 | ||
| 1385 | static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni) | 1555 | static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni) |
| 1386 | { | 1556 | { |
| 1387 | #define NUM_CIPHERS (0 \ | ||
| 1388 | + 4 * ENABLE_FEATURE_TLS_SHA1 \ | ||
| 1389 | + ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 \ | ||
| 1390 | + ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256 \ | ||
| 1391 | + ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 \ | ||
| 1392 | + ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \ | ||
| 1393 | + 2 * ENABLE_FEATURE_TLS_SHA1 \ | ||
| 1394 | + ALLOW_RSA_WITH_AES_128_CBC_SHA256 \ | ||
| 1395 | + ALLOW_RSA_WITH_AES_256_CBC_SHA256 \ | ||
| 1396 | + ALLOW_RSA_WITH_AES_128_GCM_SHA256 \ | ||
| 1397 | + ALLOW_RSA_NULL_SHA256 \ | ||
| 1398 | ) | ||
| 1399 | static const uint8_t ciphers[] = { | ||
| 1400 | 0x00,2 * (1 + NUM_CIPHERS), //len16_be | ||
| 1401 | 0x00,0xFF, //not a cipher - TLS_EMPTY_RENEGOTIATION_INFO_SCSV | ||
| 1402 | /* ^^^^^^ RFC 5746 Renegotiation Indication Extension - some servers will refuse to work with us otherwise */ | ||
| 1403 | #if ENABLE_FEATURE_TLS_SHA1 | ||
| 1404 | 0xC0,0x09, // 1 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ok: wget https://is.gd/ | ||
| 1405 | 0xC0,0x0A, // 2 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - ok: wget https://is.gd/ | ||
| 1406 | 0xC0,0x13, // 3 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA | ||
| 1407 | 0xC0,0x14, // 4 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES256-SHA (might fail with older openssl) | ||
| 1408 | // 0xC0,0x18, // TLS_ECDH_anon_WITH_AES_128_CBC_SHA | ||
| 1409 | // 0xC0,0x19, // TLS_ECDH_anon_WITH_AES_256_CBC_SHA | ||
| 1410 | #endif | ||
| 1411 | #if ALLOW_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 | ||
| 1412 | 0xC0,0x23, // 5 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - ok: wget https://is.gd/ | ||
| 1413 | #endif | ||
| 1414 | // 0xC0,0x24, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 1415 | #if ALLOW_ECDHE_RSA_WITH_AES_128_CBC_SHA256 | ||
| 1416 | 0xC0,0x27, // 6 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA256 | ||
| 1417 | #endif | ||
| 1418 | // 0xC0,0x28, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 1419 | #if ALLOW_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | ||
| 1420 | 0xC0,0x2B, // 7 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - ok: wget https://is.gd/ | ||
| 1421 | #endif | ||
| 1422 | // 0xC0,0x2C, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - wget https://is.gd/: "TLS error from peer (alert code 20): bad MAC" | ||
| 1423 | //TODO: GCM_SHA384 ciphers can be supported, only need sha384-based PRF? | ||
| 1424 | #if ALLOW_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | ||
| 1425 | 0xC0,0x2F, // 8 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-GCM-SHA256 | ||
| 1426 | #endif | ||
| 1427 | // 0xC0,0x30, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher ECDHE-RSA-AES256-GCM-SHA384: "decryption failed or bad record mac" | ||
| 1428 | //possibly these too: | ||
| 1429 | #if ENABLE_FEATURE_TLS_SHA1 | ||
| 1430 | // 0xC0,0x35, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA | ||
| 1431 | // 0xC0,0x36, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA | ||
| 1432 | #endif | ||
| 1433 | // 0xC0,0x37, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 | ||
| 1434 | // 0xC0,0x38, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 1435 | #if ENABLE_FEATURE_TLS_SHA1 | ||
| 1436 | 0x00,0x2F, // 9 TLS_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher AES128-SHA | ||
| 1437 | 0x00,0x35, //10 TLS_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher AES256-SHA | ||
| 1438 | #endif | ||
| 1439 | #if ALLOW_RSA_WITH_AES_128_CBC_SHA256 | ||
| 1440 | 0x00,0x3C, //11 TLS_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher AES128-SHA256 | ||
| 1441 | #endif | ||
| 1442 | #if ALLOW_RSA_WITH_AES_256_CBC_SHA256 | ||
| 1443 | 0x00,0x3D, //12 TLS_RSA_WITH_AES_256_CBC_SHA256 - ok: openssl s_server ... -cipher AES256-SHA256 | ||
| 1444 | #endif | ||
| 1445 | #if ALLOW_RSA_WITH_AES_128_GCM_SHA256 | ||
| 1446 | 0x00,0x9C, //13 TLS_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher AES128-GCM-SHA256 | ||
| 1447 | #endif | ||
| 1448 | // 0x00,0x9D, // TLS_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher AES256-GCM-SHA384: "decryption failed or bad record mac" | ||
| 1449 | #if ALLOW_RSA_NULL_SHA256 | ||
| 1450 | 0x00,0x3B, // TLS_RSA_WITH_NULL_SHA256 | ||
| 1451 | #endif | ||
| 1452 | 0x01,0x00, //not a cipher - comprtypes_len, comprtype | ||
| 1453 | }; | ||
| 1454 | struct client_hello { | 1557 | struct client_hello { |
| 1455 | uint8_t type; | 1558 | uint8_t type; |
| 1456 | uint8_t len24_hi, len24_mid, len24_lo; | 1559 | uint8_t len24_hi, len24_mid, len24_lo; |
| @@ -1528,9 +1631,8 @@ static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni) | |||
| 1528 | 1631 | ||
| 1529 | /* +2 is for "len of all extensions" 2-byte field */ | 1632 | /* +2 is for "len of all extensions" 2-byte field */ |
| 1530 | len = sizeof(*record) + 2 + ext_len; | 1633 | len = sizeof(*record) + 2 + ext_len; |
| 1531 | record = tls_get_zeroed_outbuf(tls, len); | 1634 | record = get_outbuf_fill_handshake_record(tls, HANDSHAKE_CLIENT_HELLO, len); |
| 1532 | 1635 | ||
| 1533 | fill_handshake_record_hdr(record, HANDSHAKE_CLIENT_HELLO, len); | ||
| 1534 | record->proto_maj = TLS_MAJ; /* the "requested" version of the protocol, */ | 1636 | record->proto_maj = TLS_MAJ; /* the "requested" version of the protocol, */ |
| 1535 | record->proto_min = TLS_MIN; /* can be higher than one in record headers */ | 1637 | record->proto_min = TLS_MIN; /* can be higher than one in record headers */ |
| 1536 | tls_get_random(record->rand32, sizeof(record->rand32)); | 1638 | tls_get_random(record->rand32, sizeof(record->rand32)); |
| @@ -1538,8 +1640,8 @@ static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni) | |||
| 1538 | memset(record->rand32, 0x11, sizeof(record->rand32)); | 1640 | memset(record->rand32, 0x11, sizeof(record->rand32)); |
| 1539 | /* record->session_id_len = 0; - already is */ | 1641 | /* record->session_id_len = 0; - already is */ |
| 1540 | 1642 | ||
| 1541 | BUILD_BUG_ON(sizeof(ciphers) != 2 * (1 + 1 + NUM_CIPHERS + 1)); | 1643 | BUILD_BUG_ON(sizeof(client_hello_ciphers) != 2 * (1 + 1 + NUM_CIPHERS + 1)); |
| 1542 | memcpy(&record->cipherid_len16_hi, ciphers, sizeof(ciphers)); | 1644 | memcpy(&record->cipherid_len16_hi, client_hello_ciphers, sizeof(client_hello_ciphers)); |
| 1543 | 1645 | ||
| 1544 | ptr = (void*)(record + 1); | 1646 | ptr = (void*)(record + 1); |
| 1545 | *ptr++ = ext_len >> 8; | 1647 | *ptr++ = ext_len >> 8; |
| @@ -1565,7 +1667,7 @@ static void send_client_hello_and_alloc_hsd(tls_state_t *tls, const char *sni) | |||
| 1565 | tls->hsd->saved_client_hello_size = len; | 1667 | tls->hsd->saved_client_hello_size = len; |
| 1566 | memcpy(tls->hsd->saved_client_hello, record, len); | 1668 | memcpy(tls->hsd->saved_client_hello, record, len); |
| 1567 | */ | 1669 | */ |
| 1568 | dbg(">> CLIENT_HELLO\n"); | 1670 | dbg(">> CLIENT_HELLO"); |
| 1569 | /* Can hash immediately only if we know which MAC hash to use. | 1671 | /* Can hash immediately only if we know which MAC hash to use. |
| 1570 | * So far we do know: it's sha256: | 1672 | * So far we do know: it's sha256: |
| 1571 | */ | 1673 | */ |
| @@ -1594,7 +1696,6 @@ static void get_server_hello(tls_state_t *tls) | |||
| 1594 | 1696 | ||
| 1595 | struct server_hello *hp; | 1697 | struct server_hello *hp; |
| 1596 | uint8_t *cipherid; | 1698 | uint8_t *cipherid; |
| 1597 | uint8_t cipherid1; | ||
| 1598 | int len, len24; | 1699 | int len, len24; |
| 1599 | 1700 | ||
| 1600 | len = tls_xread_handshake_block(tls, 74 - 32); | 1701 | len = tls_xread_handshake_block(tls, 74 - 32); |
| @@ -1629,80 +1730,13 @@ static void get_server_hello(tls_state_t *tls) | |||
| 1629 | 1730 | ||
| 1630 | if (len24 < 70) | 1731 | if (len24 < 70) |
| 1631 | bad_record_die(tls, "'server hello'", len); | 1732 | bad_record_die(tls, "'server hello'", len); |
| 1632 | dbg("<< SERVER_HELLO\n"); | 1733 | dbg("<< SERVER_HELLO"); |
| 1633 | 1734 | ||
| 1634 | memcpy(tls->hsd->client_and_server_rand32 + 32, hp->rand32, sizeof(hp->rand32)); | 1735 | memcpy(tls->hsd->client_and_server_rand32 + 32, hp->rand32, sizeof(hp->rand32)); |
| 1635 | 1736 | ||
| 1636 | /* Set up encryption params based on selected cipher */ | 1737 | set_cipher_parameters(tls, cipherid); |
| 1637 | #if 0 | 1738 | dbg("server chose cipher %04x", tls->cipher_id); |
| 1638 | 0xC0,0x09, // 1 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA - ok: wget https://is.gd/ | 1739 | dbg("key_size:%u MAC_size:%u IV_size:%u", tls->key_size, tls->MAC_size, tls->IV_size); |
| 1639 | 0xC0,0x0A, // 2 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - ok: wget https://is.gd/ | ||
| 1640 | 0xC0,0x13, // 3 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA | ||
| 1641 | 0xC0,0x14, // 4 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher ECDHE-RSA-AES256-SHA (might fail with older openssl) | ||
| 1642 | // 0xC0,0x18, // TLS_ECDH_anon_WITH_AES_128_CBC_SHA | ||
| 1643 | // 0xC0,0x19, // TLS_ECDH_anon_WITH_AES_256_CBC_SHA | ||
| 1644 | 0xC0,0x23, // 5 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - ok: wget https://is.gd/ | ||
| 1645 | // 0xC0,0x24, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 1646 | 0xC0,0x27, // 6 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-SHA256 | ||
| 1647 | // 0xC0,0x28, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 1648 | 0xC0,0x2B, // 7 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - ok: wget https://is.gd/ | ||
| 1649 | // 0xC0,0x2C, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - wget https://is.gd/: "TLS error from peer (alert code 20): bad MAC" | ||
| 1650 | 0xC0,0x2F, // 8 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher ECDHE-RSA-AES128-GCM-SHA256 | ||
| 1651 | // 0xC0,0x30, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher ECDHE-RSA-AES256-GCM-SHA384: "decryption failed or bad record mac" | ||
| 1652 | //possibly these too: | ||
| 1653 | // 0xC0,0x35, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA | ||
| 1654 | // 0xC0,0x36, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA | ||
| 1655 | // 0xC0,0x37, // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 | ||
| 1656 | // 0xC0,0x38, // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 - can't do SHA384 yet | ||
| 1657 | 0x00,0x2F, // 9 TLS_RSA_WITH_AES_128_CBC_SHA - ok: openssl s_server ... -cipher AES128-SHA | ||
| 1658 | 0x00,0x35, //10 TLS_RSA_WITH_AES_256_CBC_SHA - ok: openssl s_server ... -cipher AES256-SHA | ||
| 1659 | 0x00,0x3C, //11 TLS_RSA_WITH_AES_128_CBC_SHA256 - ok: openssl s_server ... -cipher AES128-SHA256 | ||
| 1660 | 0x00,0x3D, //12 TLS_RSA_WITH_AES_256_CBC_SHA256 - ok: openssl s_server ... -cipher AES256-SHA256 | ||
| 1661 | 0x00,0x9C, //13 TLS_RSA_WITH_AES_128_GCM_SHA256 - ok: openssl s_server ... -cipher AES128-GCM-SHA256 | ||
| 1662 | // 0x00,0x9D, // TLS_RSA_WITH_AES_256_GCM_SHA384 - openssl s_server ... -cipher AES256-GCM-SHA384: "decryption failed or bad record mac" | ||
| 1663 | 0x00,0x3B, // TLS_RSA_WITH_NULL_SHA256 | ||
| 1664 | #endif | ||
| 1665 | cipherid1 = cipherid[1]; | ||
| 1666 | tls->cipher_id = 0x100 * cipherid[0] + cipherid1; | ||
| 1667 | tls->key_size = AES256_KEYSIZE; | ||
| 1668 | tls->MAC_size = SHA256_OUTSIZE; | ||
| 1669 | /*tls->IV_size = 0; - already is */ | ||
| 1670 | if (cipherid[0] == 0xC0) { | ||
| 1671 | /* All C0xx are ECDHE */ | ||
| 1672 | tls->flags |= NEED_EC_KEY; | ||
| 1673 | if (cipherid1 & 1) { | ||
| 1674 | /* Odd numbered C0xx use AES128 (even ones use AES256) */ | ||
| 1675 | tls->key_size = AES128_KEYSIZE; | ||
| 1676 | } | ||
| 1677 | if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x19) { | ||
| 1678 | tls->MAC_size = SHA1_OUTSIZE; | ||
| 1679 | } else | ||
| 1680 | if (cipherid1 >= 0x2B && cipherid1 <= 0x30) { | ||
| 1681 | /* C02B,2C,2F,30 are AES-GCM */ | ||
| 1682 | tls->flags |= ENCRYPTION_AESGCM; | ||
| 1683 | tls->MAC_size = 0; | ||
| 1684 | tls->IV_size = 4; | ||
| 1685 | } | ||
| 1686 | } else { | ||
| 1687 | /* All 00xx are RSA */ | ||
| 1688 | if ((ENABLE_FEATURE_TLS_SHA1 && cipherid1 == 0x2F) | ||
| 1689 | || cipherid1 == 0x3C | ||
| 1690 | || cipherid1 == 0x9C | ||
| 1691 | ) { | ||
| 1692 | tls->key_size = AES128_KEYSIZE; | ||
| 1693 | } | ||
| 1694 | if (ENABLE_FEATURE_TLS_SHA1 && cipherid1 <= 0x35) { | ||
| 1695 | tls->MAC_size = SHA1_OUTSIZE; | ||
| 1696 | } else | ||
| 1697 | if (cipherid1 == 0x9C /*|| cipherid1 == 0x9D*/) { | ||
| 1698 | /* 009C,9D are AES-GCM */ | ||
| 1699 | tls->flags |= ENCRYPTION_AESGCM; | ||
| 1700 | tls->MAC_size = 0; | ||
| 1701 | tls->IV_size = 4; | ||
| 1702 | } | ||
| 1703 | } | ||
| 1704 | dbg("server chose cipher %04x\n", tls->cipher_id); | ||
| 1705 | dbg("key_size:%u MAC_size:%u IV_size:%u\n", tls->key_size, tls->MAC_size, tls->IV_size); | ||
| 1706 | 1740 | ||
| 1707 | /* Handshake hash eventually destined to FINISHED record | 1741 | /* Handshake hash eventually destined to FINISHED record |
| 1708 | * is sha256 regardless of cipher | 1742 | * is sha256 regardless of cipher |
| @@ -1732,7 +1766,7 @@ static void get_server_cert(tls_state_t *tls) | |||
| 1732 | certbuf = (void*)(xhdr + 1); | 1766 | certbuf = (void*)(xhdr + 1); |
| 1733 | if (certbuf[0] != HANDSHAKE_CERTIFICATE) | 1767 | if (certbuf[0] != HANDSHAKE_CERTIFICATE) |
| 1734 | bad_record_die(tls, "certificate", len); | 1768 | bad_record_die(tls, "certificate", len); |
| 1735 | dbg("<< CERTIFICATE\n"); | 1769 | dbg("<< CERTIFICATE"); |
| 1736 | // 4392 bytes: | 1770 | // 4392 bytes: |
| 1737 | // 0b 00|11|24 00|11|21 00|05|b0 30|82|05|ac|30|82|04|94|a0|03|02|01|02|02|11|00|9f|85|bf|66|4b|0c|dd|af|ca|50|86|79|50|1b|2b|e4|30|0d... | 1771 | // 0b 00|11|24 00|11|21 00|05|b0 30|82|05|ac|30|82|04|94|a0|03|02|01|02|02|11|00|9f|85|bf|66|4b|0c|dd|af|ca|50|86|79|50|1b|2b|e4|30|0d... |
| 1738 | //Cert len=4388 ChainLen CertLen^ DER encoded X509 starts here. openssl x509 -in FILE -inform DER -noout -text | 1772 | //Cert len=4388 ChainLen CertLen^ DER encoded X509 starts here. openssl x509 -in FILE -inform DER -noout -text |
| @@ -1813,12 +1847,12 @@ static void process_server_key(tls_state_t *tls, int len) | |||
| 1813 | keybuf += 4; | 1847 | keybuf += 4; |
| 1814 | switch (t32) { | 1848 | switch (t32) { |
| 1815 | case _0x03001d20: //curve_x25519 | 1849 | case _0x03001d20: //curve_x25519 |
| 1816 | dbg("got x25519 eccPubKey\n"); | 1850 | dbg("got x25519 eccPubKey"); |
| 1817 | tls->flags |= GOT_EC_CURVE_X25519; | 1851 | tls->flags |= USE_EC_CURVE_X25519; |
| 1818 | memcpy(tls->hsd->ecc_pub_key32, keybuf, 32); | 1852 | memcpy(tls->hsd->ecc_pub_key32, keybuf, 32); |
| 1819 | break; | 1853 | break; |
| 1820 | case _0x03001741: //curve_secp256r1 (aka P256) | 1854 | case _0x03001741: //curve_secp256r1 (aka P256) |
| 1821 | dbg("got P256 eccPubKey\n"); | 1855 | dbg("got P256 eccPubKey"); |
| 1822 | /* P256 point can be transmitted odd- or even-compressed | 1856 | /* P256 point can be transmitted odd- or even-compressed |
| 1823 | * (first byte is 3 or 2) or uncompressed (4). | 1857 | * (first byte is 3 or 2) or uncompressed (4). |
| 1824 | */ | 1858 | */ |
| @@ -1842,19 +1876,89 @@ static void send_empty_client_cert(tls_state_t *tls) | |||
| 1842 | }; | 1876 | }; |
| 1843 | struct client_empty_cert *record; | 1877 | struct client_empty_cert *record; |
| 1844 | 1878 | ||
| 1845 | record = tls_get_zeroed_outbuf(tls, sizeof(*record)); | 1879 | record = get_outbuf_fill_handshake_record(tls, HANDSHAKE_CERTIFICATE, sizeof(*record)); |
| 1846 | //fill_handshake_record_hdr(record, HANDSHAKE_CERTIFICATE, sizeof(*record)); | 1880 | dbg(">> CERTIFICATE"); |
| 1847 | //record->cert_chain_len24_hi = 0; | ||
| 1848 | //record->cert_chain_len24_mid = 0; | ||
| 1849 | //record->cert_chain_len24_lo = 0; | ||
| 1850 | // same as above: | ||
| 1851 | record->type = HANDSHAKE_CERTIFICATE; | ||
| 1852 | record->len24_lo = 3; | ||
| 1853 | |||
| 1854 | dbg(">> CERTIFICATE\n"); | ||
| 1855 | xwrite_and_update_handshake_hash(tls, sizeof(*record)); | 1881 | xwrite_and_update_handshake_hash(tls, sizeof(*record)); |
| 1856 | } | 1882 | } |
| 1857 | 1883 | ||
| 1884 | static void derive_master_secret_and_keys(tls_state_t *tls, uint8_t *premaster, int premaster_size) | ||
| 1885 | { | ||
| 1886 | uint8_t tmp64[64]; | ||
| 1887 | // RFC 5246 | ||
| 1888 | // For all key exchange methods, the same algorithm is used to convert | ||
| 1889 | // the pre_master_secret into the master_secret. The pre_master_secret | ||
| 1890 | // should be deleted from memory once the master_secret has been | ||
| 1891 | // computed. | ||
| 1892 | // master_secret = PRF(pre_master_secret, "master secret", | ||
| 1893 | // ClientHello.random + ServerHello.random) | ||
| 1894 | // [0..47]; | ||
| 1895 | // The master secret is always exactly 48 bytes in length. The length | ||
| 1896 | // of the premaster secret will vary depending on key exchange method. | ||
| 1897 | prf_hmac_sha256(/*tls,*/ | ||
| 1898 | tls->hsd->master_secret, sizeof(tls->hsd->master_secret), | ||
| 1899 | premaster, premaster_size, | ||
| 1900 | "master secret", | ||
| 1901 | tls->hsd->client_and_server_rand32, sizeof(tls->hsd->client_and_server_rand32) | ||
| 1902 | ); | ||
| 1903 | dump_hex("master secret:%s", tls->hsd->master_secret, sizeof(tls->hsd->master_secret)); | ||
| 1904 | |||
| 1905 | // RFC 5246 | ||
| 1906 | // 6.3. Key Calculation | ||
| 1907 | // | ||
| 1908 | // The Record Protocol requires an algorithm to generate keys required | ||
| 1909 | // by the current connection state (see Appendix A.6) from the security | ||
| 1910 | // parameters provided by the handshake protocol. | ||
| 1911 | // | ||
| 1912 | // The master secret is expanded into a sequence of secure bytes, which | ||
| 1913 | // is then split to a client write MAC key, a server write MAC key, a | ||
| 1914 | // client write encryption key, and a server write encryption key. Each | ||
| 1915 | // of these is generated from the byte sequence in that order. Unused | ||
| 1916 | // values are empty. Some AEAD ciphers may additionally require a | ||
| 1917 | // client write IV and a server write IV (see Section 6.2.3.3). | ||
| 1918 | // | ||
| 1919 | // When keys and MAC keys are generated, the master secret is used as an | ||
| 1920 | // entropy source. | ||
| 1921 | // | ||
| 1922 | // To generate the key material, compute | ||
| 1923 | // | ||
| 1924 | // key_block = PRF(SecurityParameters.master_secret, | ||
| 1925 | // "key expansion", | ||
| 1926 | // SecurityParameters.server_random + | ||
| 1927 | // SecurityParameters.client_random); | ||
| 1928 | // | ||
| 1929 | // until enough output has been generated. Then, the key_block is | ||
| 1930 | // partitioned as follows: | ||
| 1931 | // | ||
| 1932 | // client_write_MAC_key[SecurityParameters.mac_key_length] | ||
| 1933 | // server_write_MAC_key[SecurityParameters.mac_key_length] | ||
| 1934 | // client_write_key[SecurityParameters.enc_key_length] | ||
| 1935 | // server_write_key[SecurityParameters.enc_key_length] | ||
| 1936 | // client_write_IV[SecurityParameters.fixed_iv_length] | ||
| 1937 | // server_write_IV[SecurityParameters.fixed_iv_length] | ||
| 1938 | |||
| 1939 | /* make "server_rand32 + client_rand32" */ | ||
| 1940 | memcpy(&tmp64[0] , &tls->hsd->client_and_server_rand32[32], 32); | ||
| 1941 | memcpy(&tmp64[32], &tls->hsd->client_and_server_rand32[0] , 32); | ||
| 1942 | |||
| 1943 | prf_hmac_sha256(/*tls,*/ | ||
| 1944 | tls->key_block, 2 * (tls->MAC_size + tls->key_size + tls->IV_size), | ||
| 1945 | tls->hsd->master_secret, sizeof(tls->hsd->master_secret), | ||
| 1946 | "key expansion", | ||
| 1947 | tmp64, 64 | ||
| 1948 | ); | ||
| 1949 | } | ||
| 1950 | |||
| 1951 | static void initialize_aes_keys(tls_state_t *tls) | ||
| 1952 | { | ||
| 1953 | uint8_t iv[AES_BLOCK_SIZE]; | ||
| 1954 | aes_setkey(&tls->aes_decrypt, tls->peer_write_key, tls->key_size); | ||
| 1955 | aes_setkey(&tls->aes_encrypt, tls->our_write_key, tls->key_size); | ||
| 1956 | if (1) { //if AESGCM | ||
| 1957 | memset(iv, 0, AES_BLOCK_SIZE); | ||
| 1958 | aes_encrypt_one_block(&tls->aes_encrypt, iv, tls->H); | ||
| 1959 | } | ||
| 1960 | } | ||
| 1961 | |||
| 1858 | static void send_client_key_exchange(tls_state_t *tls) | 1962 | static void send_client_key_exchange(tls_state_t *tls) |
| 1859 | { | 1963 | { |
| 1860 | struct client_key_exchange { | 1964 | struct client_key_exchange { |
| @@ -1863,11 +1967,13 @@ static void send_client_key_exchange(tls_state_t *tls) | |||
| 1863 | uint8_t key[2 + 4 * 1024]; // size?? | 1967 | uint8_t key[2 + 4 * 1024]; // size?? |
| 1864 | }; | 1968 | }; |
| 1865 | //FIXME: better size estimate | 1969 | //FIXME: better size estimate |
| 1866 | struct client_key_exchange *record = tls_get_zeroed_outbuf(tls, sizeof(*record)); | 1970 | struct client_key_exchange *record; |
| 1867 | uint8_t premaster[RSA_PREMASTER_SIZE > EC_CURVE_KEYSIZE ? RSA_PREMASTER_SIZE : EC_CURVE_KEYSIZE]; | 1971 | uint8_t premaster[RSA_PREMASTER_SIZE > EC_CURVE_KEYSIZE ? RSA_PREMASTER_SIZE : EC_CURVE_KEYSIZE]; |
| 1868 | int premaster_size; | 1972 | int premaster_size; |
| 1869 | int len; | 1973 | int len; |
| 1870 | 1974 | ||
| 1975 | record = tls_get_zeroed_outbuf(tls, sizeof(*record)); | ||
| 1976 | |||
| 1871 | if (!(tls->flags & NEED_EC_KEY)) { | 1977 | if (!(tls->flags & NEED_EC_KEY)) { |
| 1872 | /* RSA */ | 1978 | /* RSA */ |
| 1873 | if (!(tls->flags & GOT_CERT_RSA_KEY_ALG)) | 1979 | if (!(tls->flags & GOT_CERT_RSA_KEY_ALG)) |
| @@ -1882,7 +1988,7 @@ static void send_client_key_exchange(tls_state_t *tls) | |||
| 1882 | // version negotiated for the connection." | 1988 | // version negotiated for the connection." |
| 1883 | premaster[0] = TLS_MAJ; | 1989 | premaster[0] = TLS_MAJ; |
| 1884 | premaster[1] = TLS_MIN; | 1990 | premaster[1] = TLS_MIN; |
| 1885 | dump_hex("premaster:%s\n", premaster, sizeof(premaster)); | 1991 | dump_hex("premaster:%s", premaster, sizeof(premaster)); |
| 1886 | len = psRsaEncryptPub(/*pool:*/ NULL, | 1992 | len = psRsaEncryptPub(/*pool:*/ NULL, |
| 1887 | /* psRsaKey_t* */ &tls->hsd->server_rsa_pub_key, | 1993 | /* psRsaKey_t* */ &tls->hsd->server_rsa_pub_key, |
| 1888 | premaster, /*inlen:*/ RSA_PREMASTER_SIZE, | 1994 | premaster, /*inlen:*/ RSA_PREMASTER_SIZE, |
| @@ -1891,7 +1997,7 @@ static void send_client_key_exchange(tls_state_t *tls) | |||
| 1891 | ); | 1997 | ); |
| 1892 | /* keylen16 exists for RSA (in TLS, not in SSL), but not for some other key types */ | 1998 | /* keylen16 exists for RSA (in TLS, not in SSL), but not for some other key types */ |
| 1893 | record->key[0] = len >> 8; | 1999 | record->key[0] = len >> 8; |
| 1894 | record->key[1] = len & 0xff; | 2000 | record->key[1] = len; // & 0xff implicit |
| 1895 | len += 2; | 2001 | len += 2; |
| 1896 | premaster_size = RSA_PREMASTER_SIZE; | 2002 | premaster_size = RSA_PREMASTER_SIZE; |
| 1897 | } else { | 2003 | } else { |
| @@ -1899,9 +2005,9 @@ static void send_client_key_exchange(tls_state_t *tls) | |||
| 1899 | if (!(tls->flags & GOT_EC_KEY)) | 2005 | if (!(tls->flags & GOT_EC_KEY)) |
| 1900 | bb_simple_error_msg_and_die("server did not provide EC key"); | 2006 | bb_simple_error_msg_and_die("server did not provide EC key"); |
| 1901 | 2007 | ||
| 1902 | if (tls->flags & GOT_EC_CURVE_X25519) { | 2008 | if (tls->flags & USE_EC_CURVE_X25519) { |
| 1903 | /* ECDHE, curve x25519 */ | 2009 | /* ECDHE, curve x25519 */ |
| 1904 | dbg("computing x25519_premaster\n"); | 2010 | dbg("computing x25519_premaster"); |
| 1905 | curve_x25519_compute_pubkey_and_premaster( | 2011 | curve_x25519_compute_pubkey_and_premaster( |
| 1906 | record->key + 1, premaster, | 2012 | record->key + 1, premaster, |
| 1907 | /*point:*/ tls->hsd->ecc_pub_key32 | 2013 | /*point:*/ tls->hsd->ecc_pub_key32 |
| @@ -1912,7 +2018,7 @@ static void send_client_key_exchange(tls_state_t *tls) | |||
| 1912 | //premaster_size = CURVE25519_KEYSIZE; | 2018 | //premaster_size = CURVE25519_KEYSIZE; |
| 1913 | } else { | 2019 | } else { |
| 1914 | /* ECDHE, curve P256 */ | 2020 | /* ECDHE, curve P256 */ |
| 1915 | dbg("computing P256_premaster\n"); | 2021 | dbg("computing P256_premaster"); |
| 1916 | curve_P256_compute_pubkey_and_premaster( | 2022 | curve_P256_compute_pubkey_and_premaster( |
| 1917 | record->key + 2, premaster, | 2023 | record->key + 2, premaster, |
| 1918 | /*point:*/ tls->hsd->ecc_pub_key32 | 2024 | /*point:*/ tls->hsd->ecc_pub_key32 |
| @@ -1928,104 +2034,28 @@ static void send_client_key_exchange(tls_state_t *tls) | |||
| 1928 | record->type = HANDSHAKE_CLIENT_KEY_EXCHANGE; | 2034 | record->type = HANDSHAKE_CLIENT_KEY_EXCHANGE; |
| 1929 | /* record->len24_hi = 0; - already is */ | 2035 | /* record->len24_hi = 0; - already is */ |
| 1930 | record->len24_mid = len >> 8; | 2036 | record->len24_mid = len >> 8; |
| 1931 | record->len24_lo = len & 0xff; | 2037 | record->len24_lo = len; |
| 1932 | len += 4; | 2038 | len += 4; |
| 1933 | 2039 | ||
| 1934 | dbg(">> CLIENT_KEY_EXCHANGE\n"); | 2040 | dbg(">> CLIENT_KEY_EXCHANGE"); |
| 1935 | xwrite_and_update_handshake_hash(tls, len); | 2041 | xwrite_and_update_handshake_hash(tls, len); |
| 1936 | 2042 | ||
| 1937 | // RFC 5246 | 2043 | derive_master_secret_and_keys(tls, premaster, premaster_size); |
| 1938 | // For all key exchange methods, the same algorithm is used to convert | 2044 | // The key_block is partitioned as follows: |
| 1939 | // the pre_master_secret into the master_secret. The pre_master_secret | 2045 | tls->our_write_MAC_key = tls->key_block; // client_write_MAC_key[] |
| 1940 | // should be deleted from memory once the master_secret has been | 2046 | tls->peer_write_MAC_key = tls->key_block + tls->MAC_size; // server_write_MAC_key[] |
| 1941 | // computed. | 2047 | tls->our_write_key = tls->peer_write_MAC_key + tls->MAC_size; // client_write_key[] |
| 1942 | // master_secret = PRF(pre_master_secret, "master secret", | 2048 | tls->peer_write_key = tls->our_write_key + tls->key_size; // server_write_key[] |
| 1943 | // ClientHello.random + ServerHello.random) | 2049 | tls->our_write_IV = tls->peer_write_key + tls->key_size; // client_write_IV[] |
| 1944 | // [0..47]; | 2050 | tls->peer_write_IV = tls->our_write_IV + tls->IV_size; // server_write_IV[] |
| 1945 | // The master secret is always exactly 48 bytes in length. The length | 2051 | dump_hex("client write_MAC_key:%s", tls->our_write_MAC_key, tls->MAC_size); |
| 1946 | // of the premaster secret will vary depending on key exchange method. | 2052 | dump_hex("client write_key:%s", tls->our_write_key, tls->key_size); |
| 1947 | prf_hmac_sha256(/*tls,*/ | 2053 | dump_hex("client write_IV:%s", tls->our_write_IV, tls->IV_size); |
| 1948 | tls->hsd->master_secret, sizeof(tls->hsd->master_secret), | 2054 | dump_hex("server write_MAC_key:%s", tls->peer_write_MAC_key, tls->MAC_size); |
| 1949 | premaster, premaster_size, | 2055 | dump_hex("server write_key:%s", tls->peer_write_key, tls->key_size); |
| 1950 | "master secret", | 2056 | dump_hex("server write_IV:%s", tls->peer_write_IV, tls->IV_size); |
| 1951 | tls->hsd->client_and_server_rand32, sizeof(tls->hsd->client_and_server_rand32) | 2057 | |
| 1952 | ); | 2058 | initialize_aes_keys(tls); |
| 1953 | dump_hex("master secret:%s\n", tls->hsd->master_secret, sizeof(tls->hsd->master_secret)); | ||
| 1954 | |||
| 1955 | // RFC 5246 | ||
| 1956 | // 6.3. Key Calculation | ||
| 1957 | // | ||
| 1958 | // The Record Protocol requires an algorithm to generate keys required | ||
| 1959 | // by the current connection state (see Appendix A.6) from the security | ||
| 1960 | // parameters provided by the handshake protocol. | ||
| 1961 | // | ||
| 1962 | // The master secret is expanded into a sequence of secure bytes, which | ||
| 1963 | // is then split to a client write MAC key, a server write MAC key, a | ||
| 1964 | // client write encryption key, and a server write encryption key. Each | ||
| 1965 | // of these is generated from the byte sequence in that order. Unused | ||
| 1966 | // values are empty. Some AEAD ciphers may additionally require a | ||
| 1967 | // client write IV and a server write IV (see Section 6.2.3.3). | ||
| 1968 | // | ||
| 1969 | // When keys and MAC keys are generated, the master secret is used as an | ||
| 1970 | // entropy source. | ||
| 1971 | // | ||
| 1972 | // To generate the key material, compute | ||
| 1973 | // | ||
| 1974 | // key_block = PRF(SecurityParameters.master_secret, | ||
| 1975 | // "key expansion", | ||
| 1976 | // SecurityParameters.server_random + | ||
| 1977 | // SecurityParameters.client_random); | ||
| 1978 | // | ||
| 1979 | // until enough output has been generated. Then, the key_block is | ||
| 1980 | // partitioned as follows: | ||
| 1981 | // | ||
| 1982 | // client_write_MAC_key[SecurityParameters.mac_key_length] | ||
| 1983 | // server_write_MAC_key[SecurityParameters.mac_key_length] | ||
| 1984 | // client_write_key[SecurityParameters.enc_key_length] | ||
| 1985 | // server_write_key[SecurityParameters.enc_key_length] | ||
| 1986 | // client_write_IV[SecurityParameters.fixed_iv_length] | ||
| 1987 | // server_write_IV[SecurityParameters.fixed_iv_length] | ||
| 1988 | { | ||
| 1989 | uint8_t tmp64[64]; | ||
| 1990 | |||
| 1991 | /* make "server_rand32 + client_rand32" */ | ||
| 1992 | memcpy(&tmp64[0] , &tls->hsd->client_and_server_rand32[32], 32); | ||
| 1993 | memcpy(&tmp64[32], &tls->hsd->client_and_server_rand32[0] , 32); | ||
| 1994 | |||
| 1995 | prf_hmac_sha256(/*tls,*/ | ||
| 1996 | tls->client_write_MAC_key, 2 * (tls->MAC_size + tls->key_size + tls->IV_size), | ||
| 1997 | // also fills: | ||
| 1998 | // server_write_MAC_key[] | ||
| 1999 | // client_write_key[] | ||
| 2000 | // server_write_key[] | ||
| 2001 | // client_write_IV[] | ||
| 2002 | // server_write_IV[] | ||
| 2003 | tls->hsd->master_secret, sizeof(tls->hsd->master_secret), | ||
| 2004 | "key expansion", | ||
| 2005 | tmp64, 64 | ||
| 2006 | ); | ||
| 2007 | tls->client_write_key = tls->client_write_MAC_key + (2 * tls->MAC_size); | ||
| 2008 | tls->server_write_key = tls->client_write_key + tls->key_size; | ||
| 2009 | tls->client_write_IV = tls->server_write_key + tls->key_size; | ||
| 2010 | tls->server_write_IV = tls->client_write_IV + tls->IV_size; | ||
| 2011 | dump_hex("client_write_MAC_key:%s\n", | ||
| 2012 | tls->client_write_MAC_key, tls->MAC_size | ||
| 2013 | ); | ||
| 2014 | dump_hex("client_write_key:%s\n", | ||
| 2015 | tls->client_write_key, tls->key_size | ||
| 2016 | ); | ||
| 2017 | dump_hex("client_write_IV:%s\n", | ||
| 2018 | tls->client_write_IV, tls->IV_size | ||
| 2019 | ); | ||
| 2020 | |||
| 2021 | aes_setkey(&tls->aes_decrypt, tls->server_write_key, tls->key_size); | ||
| 2022 | aes_setkey(&tls->aes_encrypt, tls->client_write_key, tls->key_size); | ||
| 2023 | { | ||
| 2024 | uint8_t iv[AES_BLOCK_SIZE]; | ||
| 2025 | memset(iv, 0, AES_BLOCK_SIZE); | ||
| 2026 | aes_encrypt_one_block(&tls->aes_encrypt, iv, tls->H); | ||
| 2027 | } | ||
| 2028 | } | ||
| 2029 | } | 2059 | } |
| 2030 | 2060 | ||
| 2031 | static const uint8_t rec_CHANGE_CIPHER_SPEC[] ALIGN1 = { | 2061 | static const uint8_t rec_CHANGE_CIPHER_SPEC[] ALIGN1 = { |
| @@ -2035,7 +2065,7 @@ static const uint8_t rec_CHANGE_CIPHER_SPEC[] ALIGN1 = { | |||
| 2035 | 2065 | ||
| 2036 | static void send_change_cipher_spec(tls_state_t *tls) | 2066 | static void send_change_cipher_spec(tls_state_t *tls) |
| 2037 | { | 2067 | { |
| 2038 | dbg(">> CHANGE_CIPHER_SPEC\n"); | 2068 | dbg(">> CHANGE_CIPHER_SPEC"); |
| 2039 | xwrite(tls->ofd, rec_CHANGE_CIPHER_SPEC, sizeof(rec_CHANGE_CIPHER_SPEC)); | 2069 | xwrite(tls->ofd, rec_CHANGE_CIPHER_SPEC, sizeof(rec_CHANGE_CIPHER_SPEC)); |
| 2040 | } | 2070 | } |
| 2041 | 2071 | ||
| @@ -2076,36 +2106,81 @@ static void send_change_cipher_spec(tls_state_t *tls) | |||
| 2076 | // suite. Any cipher suite which does not explicitly specify | 2106 | // suite. Any cipher suite which does not explicitly specify |
| 2077 | // verify_data_length has a verify_data_length equal to 12. This | 2107 | // verify_data_length has a verify_data_length equal to 12. This |
| 2078 | // includes all existing cipher suites. | 2108 | // includes all existing cipher suites. |
| 2079 | static void send_client_finished(tls_state_t *tls) | 2109 | static void send_finished(tls_state_t *tls, const char *msg_to_encrypt) |
| 2080 | { | 2110 | { |
| 2081 | struct finished { | 2111 | struct finished { |
| 2082 | uint8_t type; | 2112 | uint8_t type; |
| 2083 | uint8_t len24_hi, len24_mid, len24_lo; | 2113 | uint8_t len24_hi, len24_mid, len24_lo; |
| 2084 | uint8_t prf_result[12]; | 2114 | uint8_t prf_result[12]; |
| 2085 | }; | 2115 | }; |
| 2086 | struct finished *record = tls_get_outbuf(tls, sizeof(*record)); | 2116 | struct finished *record; |
| 2087 | uint8_t handshake_hash[TLS_MAX_MAC_SIZE]; | 2117 | uint8_t handshake_hash[TLS_MAX_MAC_SIZE]; |
| 2088 | unsigned len; | 2118 | unsigned len; |
| 2089 | 2119 | ||
| 2090 | fill_handshake_record_hdr(record, HANDSHAKE_FINISHED, sizeof(*record)); | 2120 | record = get_outbuf_fill_handshake_record(tls, HANDSHAKE_FINISHED, sizeof(*record)); |
| 2091 | 2121 | ||
| 2092 | len = sha_end(&tls->hsd->handshake_hash_ctx, handshake_hash); | 2122 | len = sha_end(&tls->hsd->handshake_hash_ctx, handshake_hash); |
| 2093 | 2123 | ||
| 2094 | prf_hmac_sha256(/*tls,*/ | 2124 | prf_hmac_sha256(/*tls,*/ |
| 2095 | record->prf_result, sizeof(record->prf_result), | 2125 | record->prf_result, sizeof(record->prf_result), |
| 2096 | tls->hsd->master_secret, sizeof(tls->hsd->master_secret), | 2126 | tls->hsd->master_secret, sizeof(tls->hsd->master_secret), |
| 2097 | "client finished", | 2127 | msg_to_encrypt, |
| 2098 | handshake_hash, len | 2128 | handshake_hash, len |
| 2099 | ); | 2129 | ); |
| 2100 | dump_hex("from secret: %s\n", tls->hsd->master_secret, sizeof(tls->hsd->master_secret)); | 2130 | dump_hex("from secret: %s", tls->hsd->master_secret, sizeof(tls->hsd->master_secret)); |
| 2101 | dump_hex("from labelSeed: %s", "client finished", sizeof("client finished")-1); | 2131 | dump_hex("from labelSeed: %s", msg_to_encrypt, strlen(msg_to_encrypt)); |
| 2102 | dump_hex("%s\n", handshake_hash, sizeof(handshake_hash)); | 2132 | dump_hex("handshake_hash: %s", handshake_hash, sizeof(handshake_hash)); |
| 2103 | dump_hex("=> digest: %s\n", record->prf_result, sizeof(record->prf_result)); | 2133 | dump_hex("=> digest: %s", record->prf_result, sizeof(record->prf_result)); |
| 2104 | 2134 | ||
| 2105 | dbg(">> FINISHED\n"); | 2135 | dbg(">> FINISHED"); |
| 2106 | xwrite_encrypted(tls, sizeof(*record), RECORD_TYPE_HANDSHAKE); | 2136 | xwrite_encrypted(tls, sizeof(*record), RECORD_TYPE_HANDSHAKE); |
| 2107 | } | 2137 | } |
| 2108 | 2138 | ||
| 2139 | /* Receive and process ChangeCipherSpec */ | ||
| 2140 | static void get_change_cipher_spec(tls_state_t *tls) | ||
| 2141 | { | ||
| 2142 | int len; | ||
| 2143 | |||
| 2144 | /* Get CHANGE_CIPHER_SPEC */ | ||
| 2145 | len = tls_xread_record(tls, "switch to encrypted traffic"); | ||
| 2146 | if (len != 1 || memcmp(tls->inbuf, rec_CHANGE_CIPHER_SPEC, 6) != 0) | ||
| 2147 | bad_record_die(tls, "switch to encrypted traffic", len); | ||
| 2148 | dbg("<< CHANGE_CIPHER_SPEC"); | ||
| 2149 | |||
| 2150 | /* Enable decryption for incoming packets */ | ||
| 2151 | if (ALLOW_RSA_NULL_SHA256 | ||
| 2152 | && tls->cipher_id == TLS_RSA_WITH_NULL_SHA256 | ||
| 2153 | ) { | ||
| 2154 | tls->min_encrypted_len_on_read = tls->MAC_size; | ||
| 2155 | } else | ||
| 2156 | if (!(tls->flags & ENCRYPTION_AESGCM)) { | ||
| 2157 | unsigned mac_blocks = (unsigned)(TLS_MAC_SIZE(tls) + AES_BLOCK_SIZE-1) / AES_BLOCK_SIZE; | ||
| 2158 | /* all incoming packets now should be encrypted and have | ||
| 2159 | * at least IV + (MAC padded to blocksize): | ||
| 2160 | */ | ||
| 2161 | tls->min_encrypted_len_on_read = AES_BLOCK_SIZE + (mac_blocks * AES_BLOCK_SIZE); | ||
| 2162 | } else { | ||
| 2163 | tls->min_encrypted_len_on_read = 8 + AES_BLOCK_SIZE; | ||
| 2164 | } | ||
| 2165 | dbg("min_encrypted_len_on_read: %u", tls->min_encrypted_len_on_read); | ||
| 2166 | } | ||
| 2167 | |||
| 2168 | /* Receive encrypted Finished message */ | ||
| 2169 | static void get_finished(tls_state_t *tls, const char *expected) | ||
| 2170 | { | ||
| 2171 | int len; | ||
| 2172 | |||
| 2173 | len = tls_xread_record(tls, expected); | ||
| 2174 | if (len < 4 || tls->inbuf[RECHDR_LEN] != HANDSHAKE_FINISHED) | ||
| 2175 | bad_record_die(tls, expected, len); | ||
| 2176 | dbg("<< FINISHED"); | ||
| 2177 | |||
| 2178 | /* TODO: Verify the Finished message contents */ | ||
| 2179 | /* The Finished message contains verify_data which is: | ||
| 2180 | * PRF(master_secret, "client finished"/"server finished", SHA256(handshake_messages)) | ||
| 2181 | */ | ||
| 2182 | } | ||
| 2183 | |||
| 2109 | void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) | 2184 | void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) |
| 2110 | { | 2185 | { |
| 2111 | // Client RFC 5246 Server | 2186 | // Client RFC 5246 Server |
| @@ -2156,8 +2231,8 @@ void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) | |||
| 2156 | // This message is used to convey the server's ephemeral ECDH public key | 2231 | // This message is used to convey the server's ephemeral ECDH public key |
| 2157 | // (and the corresponding elliptic curve domain parameters) to the | 2232 | // (and the corresponding elliptic curve domain parameters) to the |
| 2158 | // client. | 2233 | // client. |
| 2159 | dbg("<< SERVER_KEY_EXCHANGE len:%u\n", len); | 2234 | dbg("<< SERVER_KEY_EXCHANGE len:%u", len); |
| 2160 | dump_raw_in("<< %s\n", tls->inbuf, RECHDR_LEN + len); | 2235 | dump_raw_in("<< %s", tls->inbuf, RECHDR_LEN + len); |
| 2161 | if (tls->flags & NEED_EC_KEY) | 2236 | if (tls->flags & NEED_EC_KEY) |
| 2162 | process_server_key(tls, len); | 2237 | process_server_key(tls, len); |
| 2163 | 2238 | ||
| @@ -2167,7 +2242,7 @@ void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) | |||
| 2167 | 2242 | ||
| 2168 | got_cert_req = (tls->inbuf[RECHDR_LEN] == HANDSHAKE_CERTIFICATE_REQUEST); | 2243 | got_cert_req = (tls->inbuf[RECHDR_LEN] == HANDSHAKE_CERTIFICATE_REQUEST); |
| 2169 | if (got_cert_req) { | 2244 | if (got_cert_req) { |
| 2170 | dbg("<< CERTIFICATE_REQUEST\n"); | 2245 | dbg("<< CERTIFICATE_REQUEST"); |
| 2171 | // RFC 5246: "If no suitable certificate is available, | 2246 | // RFC 5246: "If no suitable certificate is available, |
| 2172 | // the client MUST send a certificate message containing no | 2247 | // the client MUST send a certificate message containing no |
| 2173 | // certificates. That is, the certificate_list structure has a | 2248 | // certificates. That is, the certificate_list structure has a |
| @@ -2185,7 +2260,7 @@ void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) | |||
| 2185 | bad_record_die(tls, "'server hello done'", len); | 2260 | bad_record_die(tls, "'server hello done'", len); |
| 2186 | } | 2261 | } |
| 2187 | // 0e 000000 (len:0) | 2262 | // 0e 000000 (len:0) |
| 2188 | dbg("<< SERVER_HELLO_DONE\n"); | 2263 | dbg("<< SERVER_HELLO_DONE"); |
| 2189 | 2264 | ||
| 2190 | if (got_cert_req) | 2265 | if (got_cert_req) |
| 2191 | send_empty_client_cert(tls); | 2266 | send_empty_client_cert(tls); |
| @@ -2193,39 +2268,16 @@ void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) | |||
| 2193 | send_client_key_exchange(tls); | 2268 | send_client_key_exchange(tls); |
| 2194 | 2269 | ||
| 2195 | send_change_cipher_spec(tls); | 2270 | send_change_cipher_spec(tls); |
| 2271 | |||
| 2196 | /* from now on we should send encrypted */ | 2272 | /* from now on we should send encrypted */ |
| 2197 | /* tls->write_seq64_be = 0; - already is */ | ||
| 2198 | tls->flags |= ENCRYPT_ON_WRITE; | ||
| 2199 | 2273 | ||
| 2200 | send_client_finished(tls); | 2274 | send_finished(tls, "client finished"); |
| 2201 | 2275 | ||
| 2202 | /* Get CHANGE_CIPHER_SPEC */ | 2276 | get_change_cipher_spec(tls); |
| 2203 | len = tls_xread_record(tls, "switch to encrypted traffic"); | ||
| 2204 | if (len != 1 || memcmp(tls->inbuf, rec_CHANGE_CIPHER_SPEC, 6) != 0) | ||
| 2205 | bad_record_die(tls, "switch to encrypted traffic", len); | ||
| 2206 | dbg("<< CHANGE_CIPHER_SPEC\n"); | ||
| 2207 | |||
| 2208 | if (ALLOW_RSA_NULL_SHA256 | ||
| 2209 | && tls->cipher_id == TLS_RSA_WITH_NULL_SHA256 | ||
| 2210 | ) { | ||
| 2211 | tls->min_encrypted_len_on_read = tls->MAC_size; | ||
| 2212 | } else | ||
| 2213 | if (!(tls->flags & ENCRYPTION_AESGCM)) { | ||
| 2214 | unsigned mac_blocks = (unsigned)(TLS_MAC_SIZE(tls) + AES_BLOCK_SIZE-1) / AES_BLOCK_SIZE; | ||
| 2215 | /* all incoming packets now should be encrypted and have | ||
| 2216 | * at least IV + (MAC padded to blocksize): | ||
| 2217 | */ | ||
| 2218 | tls->min_encrypted_len_on_read = AES_BLOCK_SIZE + (mac_blocks * AES_BLOCK_SIZE); | ||
| 2219 | } else { | ||
| 2220 | tls->min_encrypted_len_on_read = 8 + AES_BLOCK_SIZE; | ||
| 2221 | } | ||
| 2222 | dbg("min_encrypted_len_on_read: %u\n", tls->min_encrypted_len_on_read); | ||
| 2223 | 2277 | ||
| 2224 | /* Get (encrypted) FINISHED from the server */ | 2278 | /* Get (encrypted) FINISHED from the server */ |
| 2225 | len = tls_xread_record(tls, "'server finished'"); | 2279 | get_finished(tls, "'server finished'"); |
| 2226 | if (len < 4 || tls->inbuf[RECHDR_LEN] != HANDSHAKE_FINISHED) | 2280 | |
| 2227 | bad_record_die(tls, "'server finished'", len); | ||
| 2228 | dbg("<< FINISHED\n"); | ||
| 2229 | /* application data can be sent/received */ | 2281 | /* application data can be sent/received */ |
| 2230 | 2282 | ||
| 2231 | /* free handshake data */ | 2283 | /* free handshake data */ |
| @@ -2238,7 +2290,7 @@ void FAST_FUNC tls_handshake(tls_state_t *tls, const char *sni) | |||
| 2238 | 2290 | ||
| 2239 | static void tls_xwrite(tls_state_t *tls, int len) | 2291 | static void tls_xwrite(tls_state_t *tls, int len) |
| 2240 | { | 2292 | { |
| 2241 | dbg(">> DATA\n"); | 2293 | dbg(">> DATA"); |
| 2242 | xwrite_encrypted(tls, len, RECORD_TYPE_APPLICATION_DATA); | 2294 | xwrite_encrypted(tls, len, RECORD_TYPE_APPLICATION_DATA); |
| 2243 | } | 2295 | } |
| 2244 | 2296 | ||
| @@ -2313,7 +2365,7 @@ void FAST_FUNC tls_run_copy_loop(tls_state_t *tls, unsigned flags) | |||
| 2313 | if (pfds[0].revents) { | 2365 | if (pfds[0].revents) { |
| 2314 | void *buf; | 2366 | void *buf; |
| 2315 | 2367 | ||
| 2316 | dbg("STDIN HAS DATA\n"); | 2368 | dbg("STDIN HAS DATA"); |
| 2317 | buf = tls_get_outbuf(tls, inbuf_size); | 2369 | buf = tls_get_outbuf(tls, inbuf_size); |
| 2318 | nread = safe_read(STDIN_FILENO, buf, inbuf_size); | 2370 | nread = safe_read(STDIN_FILENO, buf, inbuf_size); |
| 2319 | if (nread < 1) { | 2371 | if (nread < 1) { |
| @@ -2329,6 +2381,7 @@ void FAST_FUNC tls_run_copy_loop(tls_state_t *tls, unsigned flags) | |||
| 2329 | tls_free_outbuf(tls); /* mem usage optimization */ | 2381 | tls_free_outbuf(tls); /* mem usage optimization */ |
| 2330 | if (flags & TLSLOOP_EXIT_ON_LOCAL_EOF) | 2382 | if (flags & TLSLOOP_EXIT_ON_LOCAL_EOF) |
| 2331 | break; | 2383 | break; |
| 2384 | //TODO: if (pfds[1].revents) network has data, do a last read from it before exiting. | ||
| 2332 | } else { | 2385 | } else { |
| 2333 | if (nread == inbuf_size) { | 2386 | if (nread == inbuf_size) { |
| 2334 | /* TLS has per record overhead, if input comes fast, | 2387 | /* TLS has per record overhead, if input comes fast, |
| @@ -2338,11 +2391,16 @@ void FAST_FUNC tls_run_copy_loop(tls_state_t *tls, unsigned flags) | |||
| 2338 | if (inbuf_size > TLS_MAX_OUTBUF) | 2391 | if (inbuf_size > TLS_MAX_OUTBUF) |
| 2339 | inbuf_size = TLS_MAX_OUTBUF; | 2392 | inbuf_size = TLS_MAX_OUTBUF; |
| 2340 | } | 2393 | } |
| 2394 | //BUG: in this example: printf '\n' | ssl_client -e ssl_server -OPTS echo 'Hi' | ||
| 2395 | //if ssl_client arrives here (if it sees stdin before it sees the server's "Hi" message), | ||
| 2396 | //it can be killed by SIGPIPE because server has already exited. | ||
| 2397 | //(If we disable SIGPIPE, it would die on write error). | ||
| 2398 | //Which means it won't get and won't print to stdout the server's response! | ||
| 2341 | tls_xwrite(tls, nread); | 2399 | tls_xwrite(tls, nread); |
| 2342 | } | 2400 | } |
| 2343 | } | 2401 | } |
| 2344 | if (pfds[1].revents) { | 2402 | if (pfds[1].revents) { |
| 2345 | dbg("NETWORK HAS DATA\n"); | 2403 | dbg("NETWORK HAS DATA"); |
| 2346 | read_record: | 2404 | read_record: |
| 2347 | nread = tls_xread_record(tls, "encrypted data"); | 2405 | nread = tls_xread_record(tls, "encrypted data"); |
| 2348 | if (nread < 1) { | 2406 | if (nread < 1) { |
| @@ -2366,6 +2424,784 @@ void FAST_FUNC tls_run_copy_loop(tls_state_t *tls, unsigned flags) | |||
| 2366 | } | 2424 | } |
| 2367 | } | 2425 | } |
| 2368 | } | 2426 | } |
| 2427 | |||
| 2428 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL | ||
| 2429 | |||
| 2430 | /* =============== SERVER-SIDE CODE =============== */ | ||
| 2431 | |||
| 2432 | static void get_client_hello(tls_state_t *tls) | ||
| 2433 | { | ||
| 2434 | struct client_hello { | ||
| 2435 | uint8_t type; | ||
| 2436 | uint8_t len24_hi, len24_mid, len24_lo; | ||
| 2437 | uint8_t proto_maj, proto_min; | ||
| 2438 | uint8_t rand32[32]; | ||
| 2439 | uint8_t session_id_len; | ||
| 2440 | /* followed by session_id, cipher suites, compression methods, extensions */ | ||
| 2441 | }; | ||
| 2442 | unsigned i, j; | ||
| 2443 | struct client_hello *hp; | ||
| 2444 | uint8_t *p; | ||
| 2445 | int cipher_list_len; | ||
| 2446 | int extensions_len; | ||
| 2447 | int len; | ||
| 2448 | |||
| 2449 | len = tls_xread_handshake_block(tls, sizeof(*hp)); | ||
| 2450 | /* NB: the recv'd block is already hashed by tls_xread_handshake_block() */ | ||
| 2451 | hp = (void*)(tls->inbuf + RECHDR_LEN); | ||
| 2452 | if (hp->type != HANDSHAKE_CLIENT_HELLO | ||
| 2453 | || len != get24be(&hp->len24_hi) + 4 | ||
| 2454 | || hp->proto_maj != TLS_MAJ | ||
| 2455 | || !is_minor_version_valid(tls, hp->proto_min) | ||
| 2456 | ) { | ||
| 2457 | bad_record_die(tls, "'client hello'", len); | ||
| 2458 | } | ||
| 2459 | dbg("<< CLIENT_HELLO len:%d len24:%d", len, get24be(&len24_hi)); | ||
| 2460 | |||
| 2461 | /* Save client random */ | ||
| 2462 | memcpy(tls->hsd->client_and_server_rand32, hp->rand32, 32); | ||
| 2463 | |||
| 2464 | /* Skip session ID and handshake header */ | ||
| 2465 | p = (uint8_t*)(hp + 1) + hp->session_id_len; | ||
| 2466 | len -= (sizeof(*hp) + hp->session_id_len); | ||
| 2467 | |||
| 2468 | /* Parse cipher suites to select one we support */ | ||
| 2469 | if (len < 2) { | ||
| 2470 | bb_simple_error_msg_and_die("malformed ClientHello"); | ||
| 2471 | } | ||
| 2472 | cipher_list_len = (p[0] << 8) | p[1]; | ||
| 2473 | p += 2; | ||
| 2474 | len -= 2; | ||
| 2475 | |||
| 2476 | if (len < cipher_list_len) { | ||
| 2477 | bb_simple_error_msg_and_die("malformed ClientHello"); | ||
| 2478 | } | ||
| 2479 | |||
| 2480 | /* Check whether we have TLS_EMPTY_RENEGOTIATION_INFO_SCSV */ | ||
| 2481 | for (j = 0; j < cipher_list_len; j += 2) { | ||
| 2482 | if (p[j] == TLS_EMPTY_RENEGOTIATION_INFO_SCSV >> 8 | ||
| 2483 | && p[j + 1] == (uint8_t)TLS_EMPTY_RENEGOTIATION_INFO_SCSV | ||
| 2484 | ) { | ||
| 2485 | dbg("got TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); | ||
| 2486 | tls->hsd->reneg_info_requested = 1; | ||
| 2487 | break; | ||
| 2488 | } | ||
| 2489 | } | ||
| 2490 | |||
| 2491 | /* Select cipher + cert pair from client's list, preferring our ciphers in order */ | ||
| 2492 | for (i = 0; i < NUM_CIPHERS*2; i += 2) { | ||
| 2493 | const uint8_t *our_cipher = &supported_ciphers[i]; | ||
| 2494 | int key_type; | ||
| 2495 | |||
| 2496 | /* Determine required key type for this cipher */ | ||
| 2497 | key_type = is_cipher_ECDSA(our_cipher); | ||
| 2498 | if (key_type == KEY_ECDSA) { | ||
| 2499 | if (!tls->hsd->keys[KEY_ECDSA]) | ||
| 2500 | /* No ECDSA cert configured, can't choose this */ | ||
| 2501 | continue; | ||
| 2502 | //TODO: ECDSA not supported yet at all | ||
| 2503 | continue; | ||
| 2504 | } else { | ||
| 2505 | if (!tls->hsd->keys[KEY_RSA]) | ||
| 2506 | /* No RSA cert configured, can't choose this */ | ||
| 2507 | continue; | ||
| 2508 | /* We _can_ choose this! */ | ||
| 2509 | } | ||
| 2510 | |||
| 2511 | /* Check if this cipher is in client's list */ | ||
| 2512 | for (j = 0; j < cipher_list_len; j += 2) { | ||
| 2513 | if (p[j] == our_cipher[0] && p[j + 1] == our_cipher[1]) { | ||
| 2514 | /* Found a match! */ | ||
| 2515 | set_cipher_parameters(tls, our_cipher); | ||
| 2516 | dbg("Selected cipher: %04x", tls->cipher_id); | ||
| 2517 | tls->hsd->key_type_chosen = key_type; | ||
| 2518 | goto cipher_selected; | ||
| 2519 | } | ||
| 2520 | } | ||
| 2521 | /* try our next cipherid */ | ||
| 2522 | } | ||
| 2523 | bb_simple_error_msg_and_die("no common cipher suites"); | ||
| 2524 | |||
| 2525 | cipher_selected: | ||
| 2526 | /* Skip past cipher list */ | ||
| 2527 | p += cipher_list_len; | ||
| 2528 | len -= cipher_list_len; | ||
| 2529 | |||
| 2530 | /* Skip compression methods */ | ||
| 2531 | len -= 1 + p[0]; | ||
| 2532 | p += 1 + p[0]; | ||
| 2533 | |||
| 2534 | /* Parse extensions if present */ | ||
| 2535 | if (len < 2) { | ||
| 2536 | dbg("No extensions"); | ||
| 2537 | return; /* no extensions */ | ||
| 2538 | } | ||
| 2539 | extensions_len = (p[0] << 8) | p[1]; | ||
| 2540 | p += 2; | ||
| 2541 | len -= 2; | ||
| 2542 | dbg("Extensions total length: %u, remaining len: %d", extensions_len, len); | ||
| 2543 | |||
| 2544 | if (len < extensions_len) { | ||
| 2545 | dbg("Malformed extensions length (len %d < extensions_len %u)", len, extensions_len); | ||
| 2546 | return; /* malformed extensions, ignore */ | ||
| 2547 | } | ||
| 2548 | |||
| 2549 | /* Process extensions */ | ||
| 2550 | while (extensions_len >= 4) { | ||
| 2551 | unsigned ext_type = (p[0] << 8) | p[1]; | ||
| 2552 | int ext_len = (p[2] << 8) | p[3]; | ||
| 2553 | dbg("Extension type: 0x%04x, len: %u", ext_type, ext_len); | ||
| 2554 | |||
| 2555 | p += 4; | ||
| 2556 | extensions_len -= 4 + ext_len; | ||
| 2557 | if (extensions_len < 0) { | ||
| 2558 | dbg("Extension length overflow"); | ||
| 2559 | return; /* malformed */ | ||
| 2560 | } | ||
| 2561 | |||
| 2562 | if (ext_type == 0x000a) { /* supported_groups */ | ||
| 2563 | /* Parse named curve list */ | ||
| 2564 | int curve_list_len = (p[0] << 8) | p[1]; | ||
| 2565 | dbg("Found supported_groups extension"); | ||
| 2566 | p += 2; | ||
| 2567 | ext_len -= 2; | ||
| 2568 | if (ext_len != curve_list_len || (ext_len & 1)) | ||
| 2569 | return; /* malformed */ | ||
| 2570 | while (1) { | ||
| 2571 | unsigned curve; | ||
| 2572 | ext_len -= 2; /* skip (presumably existing) curve id */ | ||
| 2573 | if (ext_len < 0) | ||
| 2574 | break; /* oops, it didn't */ | ||
| 2575 | curve = (p[0] << 8) | p[1]; | ||
| 2576 | p += 2; | ||
| 2577 | if (curve == 0x001d) /* x25519 */ | ||
| 2578 | tls->flags |= USE_EC_CURVE_X25519; | ||
| 2579 | // if (curve == 0x0017) /* secp256r1 (P256) */ | ||
| 2580 | // /* We'll try P256 as fallback without checking client support */ | ||
| 2581 | } | ||
| 2582 | dbg("Client supports:%s", | ||
| 2583 | (tls->flags & USE_EC_CURVE_X25519) ? " x25519" : " P256(assumed)"); | ||
| 2584 | continue; | ||
| 2585 | } | ||
| 2586 | |||
| 2587 | if (ext_type == 0xff01) { | ||
| 2588 | dbg("got reneg_info extension ff01"); | ||
| 2589 | tls->hsd->reneg_info_requested = 1; | ||
| 2590 | } | ||
| 2591 | p += ext_len; | ||
| 2592 | } | ||
| 2593 | } | ||
| 2594 | |||
| 2595 | static void send_server_hello(tls_state_t *tls) | ||
| 2596 | { | ||
| 2597 | struct server_hello { | ||
| 2598 | uint8_t type; | ||
| 2599 | uint8_t len24_hi, len24_mid, len24_lo; | ||
| 2600 | uint8_t proto_maj, proto_min; | ||
| 2601 | uint8_t rand32[32]; | ||
| 2602 | uint8_t session_id_len; | ||
| 2603 | uint8_t cipherid_hi, cipherid_lo; | ||
| 2604 | uint8_t comprtype; | ||
| 2605 | uint8_t extensions_len_hi, extensions_len_lo; | ||
| 2606 | /* extensions follow */ | ||
| 2607 | uint8_t ext_reneg_info[5]; /* ff 01 00 01 00 */ | ||
| 2608 | }; | ||
| 2609 | struct server_hello *record; | ||
| 2610 | unsigned len = sizeof(*record); | ||
| 2611 | |||
| 2612 | if (!tls->hsd->reneg_info_requested) | ||
| 2613 | len -= 7; | ||
| 2614 | |||
| 2615 | record = get_outbuf_fill_handshake_record(tls, HANDSHAKE_SERVER_HELLO, len); | ||
| 2616 | |||
| 2617 | record->proto_maj = TLS_MAJ; | ||
| 2618 | record->proto_min = TLS_MIN; | ||
| 2619 | |||
| 2620 | /* Generate server random */ | ||
| 2621 | tls_get_random(record->rand32, sizeof(record->rand32)); | ||
| 2622 | memcpy(tls->hsd->client_and_server_rand32 + 32, record->rand32, 32); | ||
| 2623 | |||
| 2624 | /* No session ID */ | ||
| 2625 | //record->session_id_len = 0; | ||
| 2626 | |||
| 2627 | /* Selected cipher suite */ | ||
| 2628 | record->cipherid_hi = tls->cipher_id >> 8; | ||
| 2629 | record->cipherid_lo = tls->cipher_id; // & 0xff implicit | ||
| 2630 | |||
| 2631 | /* No compression */ | ||
| 2632 | //record->comprtype = 0; | ||
| 2633 | |||
| 2634 | if (tls->hsd->reneg_info_requested) { | ||
| 2635 | /* Extensions */ | ||
| 2636 | //record->extensions_len_hi = 0; | ||
| 2637 | record->extensions_len_lo = 5; /* length of renegotiation_info extension */ | ||
| 2638 | |||
| 2639 | /* Renegotiation info extension (RFC 5746) */ | ||
| 2640 | record->ext_reneg_info[0] = 0xff; | ||
| 2641 | record->ext_reneg_info[1] = 0x01; /* extension type */ | ||
| 2642 | //record->ext_reneg_info[2] = 0x00; | ||
| 2643 | record->ext_reneg_info[3] = 0x01; /* extension data length: 1 byte */ | ||
| 2644 | //record->ext_reneg_info[4] = 0x00; /* renegotiation info length: 0 (no previous connection) */ | ||
| 2645 | } | ||
| 2646 | |||
| 2647 | dbg(">> SERVER_HELLO"); | ||
| 2648 | xwrite_and_update_handshake_hash(tls, len); | ||
| 2649 | } | ||
| 2650 | |||
| 2651 | static void send_server_certificate(tls_state_t *tls) | ||
| 2652 | { | ||
| 2653 | void *record; | ||
| 2654 | int n = tls->hsd->key_type_chosen; | ||
| 2655 | int sz = tls->hsd->certsize[n]; | ||
| 2656 | |||
| 2657 | record = tls_get_outbuf(tls, sz); | ||
| 2658 | memcpy(record, tls->hsd->certs[n], sz); | ||
| 2659 | dbg(">> CERTIFICATE"); | ||
| 2660 | xwrite_and_update_handshake_hash(tls, sz); | ||
| 2661 | } | ||
| 2662 | |||
| 2663 | static void send_server_key_exchange(tls_state_t *tls) | ||
| 2664 | { | ||
| 2665 | struct server_key_exchange { | ||
| 2666 | uint8_t type; | ||
| 2667 | uint8_t len24_hi, len24_mid, len24_lo; | ||
| 2668 | uint8_t curve_type; /* 3 = named curve */ | ||
| 2669 | uint8_t curve_id_hi, curve_id_lo; | ||
| 2670 | uint8_t pubkey_len; | ||
| 2671 | uint8_t pubkey[1 + 2 * 32]; /* for P256: 04 + x(32) + y(32) */ | ||
| 2672 | /* Followed by signature: hash_alg(1) + sign_alg(1) + sig_len(2) + signature */ | ||
| 2673 | }; | ||
| 2674 | struct server_key_exchange *record; | ||
| 2675 | uint8_t *p; | ||
| 2676 | int pubkey_len; | ||
| 2677 | int params_len; | ||
| 2678 | int total_len; | ||
| 2679 | uint8_t hash[32]; /* SHA256 hash */ | ||
| 2680 | sha256_ctx_t sha256_ctx; | ||
| 2681 | int32 sig_len; | ||
| 2682 | |||
| 2683 | record = tls_get_zeroed_outbuf(tls, sizeof(*record) + 4 + 512); /* extra space for signature */ | ||
| 2684 | |||
| 2685 | record->type = HANDSHAKE_SERVER_KEY_EXCHANGE; | ||
| 2686 | record->curve_type = 3; /* named curve */ | ||
| 2687 | |||
| 2688 | /* Determine which curve to use based on client's supported_groups extension. | ||
| 2689 | * Prefer x25519 (faster) if client supports it, otherwise use P256. | ||
| 2690 | * If client didn't send supported_groups, default to P256 (most widely supported). | ||
| 2691 | */ | ||
| 2692 | if (tls->flags & USE_EC_CURVE_X25519) { /* Use x25519 */ | ||
| 2693 | //record->curve_id_hi = 0x00; /* already zero from tls_get_zeroed_outbuf() */ | ||
| 2694 | record->curve_id_lo = 0x1d; /* x25519 */ | ||
| 2695 | |||
| 2696 | /* Generate ephemeral keypair directly into output buffer */ | ||
| 2697 | curve_x25519_generate_keypair(tls->hsd->ecc_priv_key32, record->pubkey); | ||
| 2698 | |||
| 2699 | pubkey_len = 32; | ||
| 2700 | dbg("Using x25519 for ECDHE"); | ||
| 2701 | } else { /* Use P256 (default or if client advertised it) */ | ||
| 2702 | //record->curve_id_hi = 0x00; /* already zero from tls_get_zeroed_outbuf() */ | ||
| 2703 | record->curve_id_lo = 0x17; /* secp256r1 (P256) */ | ||
| 2704 | |||
| 2705 | /* Generate ephemeral keypair directly into output buffer */ | ||
| 2706 | record->pubkey[0] = 0x04; /* uncompressed point */ | ||
| 2707 | curve_P256_generate_keypair(tls->hsd->ecc_priv_key32, record->pubkey + 1); | ||
| 2708 | |||
| 2709 | pubkey_len = 1 + 2 * 32; | ||
| 2710 | /* P256 is the default, no need to set USE_EC_CURVE_X25519 flag */ | ||
| 2711 | dbg("Using P256 for ECDHE"); | ||
| 2712 | } | ||
| 2713 | |||
| 2714 | record->pubkey_len = pubkey_len; | ||
| 2715 | |||
| 2716 | /* ServerECDHParams length: curve_type(1) + curve_id(2) + pubkey_len(1) + pubkey */ | ||
| 2717 | params_len = 4 + pubkey_len; | ||
| 2718 | |||
| 2719 | /* Sign the parameters with our RSA private key: | ||
| 2720 | * Signature is over SHA256(client_random + server_random + ServerECDHParams) | ||
| 2721 | */ | ||
| 2722 | sha256_begin(&sha256_ctx); | ||
| 2723 | sha256_hash(&sha256_ctx, tls->hsd->client_and_server_rand32, 2 * 32); | ||
| 2724 | sha256_hash(&sha256_ctx, &record->curve_type, params_len); | ||
| 2725 | sha256_end(&sha256_ctx, hash); | ||
| 2726 | |||
| 2727 | /* Pointer to where signature data goes (after ServerECDHParams) */ | ||
| 2728 | p = record->pubkey + pubkey_len; | ||
| 2729 | |||
| 2730 | /* SignatureAndHashAlgorithm: hash=SHA256(4), signature=RSA(1) */ | ||
| 2731 | *p++ = 4; /* SHA256 */ | ||
| 2732 | *p++ = 1; /* RSA */ | ||
| 2733 | |||
| 2734 | /* Sign the hash */ | ||
| 2735 | sig_len = privRsaEncryptSignedElement(NULL, &tls->hsd->rsa_priv_key, | ||
| 2736 | hash, 32, p + 2, 512, NULL); | ||
| 2737 | if (sig_len < 0) { | ||
| 2738 | bb_error_msg_and_die("RSA signature failed"); | ||
| 2739 | } | ||
| 2740 | |||
| 2741 | /* Signature length (2 bytes, big-endian) */ | ||
| 2742 | p[0] = sig_len >> 8; | ||
| 2743 | p[1] = sig_len; // & 0xff implicit | ||
| 2744 | |||
| 2745 | /* Total handshake message length: params + hash_alg(1) + sign_alg(1) + sig_len(2) + signature */ | ||
| 2746 | total_len = params_len + 2 + 2 + sig_len; | ||
| 2747 | |||
| 2748 | //record->len24_hi = 0; /* already zero from tls_get_zeroed_outbuf() */ | ||
| 2749 | record->len24_mid = total_len >> 8; | ||
| 2750 | record->len24_lo = total_len; // & 0xff implicit | ||
| 2751 | |||
| 2752 | /* Total wire length */ | ||
| 2753 | total_len += 4; /* type + len24 */ | ||
| 2754 | |||
| 2755 | dbg(">> SERVER_KEY_EXCHANGE"); | ||
| 2756 | xwrite_and_update_handshake_hash(tls, total_len); | ||
| 2757 | } | ||
| 2758 | |||
| 2759 | static void send_server_hello_done(tls_state_t *tls) | ||
| 2760 | { | ||
| 2761 | struct server_hello_done { | ||
| 2762 | uint8_t type; | ||
| 2763 | uint8_t len24_hi, len24_mid, len24_lo; | ||
| 2764 | }; | ||
| 2765 | struct server_hello_done *record; | ||
| 2766 | |||
| 2767 | record = get_outbuf_fill_handshake_record(tls, HANDSHAKE_SERVER_HELLO_DONE, sizeof(*record)); | ||
| 2768 | |||
| 2769 | dbg(">> SERVER_HELLO_DONE"); | ||
| 2770 | xwrite_and_update_handshake_hash(tls, sizeof(*record)); | ||
| 2771 | } | ||
| 2772 | |||
| 2773 | /* Receive and process ClientKeyExchange */ | ||
| 2774 | static void get_client_key_exchange(tls_state_t *tls) | ||
| 2775 | { | ||
| 2776 | struct client_key_exchange { | ||
| 2777 | uint8_t type; | ||
| 2778 | uint8_t len24_hi, len24_mid, len24_lo; | ||
| 2779 | uint8_t key[1]; /* Variable length: encrypted premaster (RSA) or EC point (ECDHE) */ | ||
| 2780 | }; | ||
| 2781 | struct client_key_exchange *record; | ||
| 2782 | uint8_t premaster[RSA_PREMASTER_SIZE > EC_CURVE_KEYSIZE ? RSA_PREMASTER_SIZE : EC_CURVE_KEYSIZE]; | ||
| 2783 | int len, premaster_size; | ||
| 2784 | uint8_t *key_data; | ||
| 2785 | |||
| 2786 | len = tls_xread_handshake_block(tls, sizeof(*record)); | ||
| 2787 | record = (void*)(tls->inbuf + RECHDR_LEN); | ||
| 2788 | |||
| 2789 | if (record->type != HANDSHAKE_CLIENT_KEY_EXCHANGE) { | ||
| 2790 | bad_record_die(tls, "'client key exchange'", len); | ||
| 2791 | } | ||
| 2792 | dbg("<< CLIENT_KEY_EXCHANGE"); | ||
| 2793 | |||
| 2794 | key_data = record->key; | ||
| 2795 | |||
| 2796 | if (!(tls->flags & NEED_EC_KEY)) { | ||
| 2797 | /* RSA key exchange */ | ||
| 2798 | int enckey_len; | ||
| 2799 | |||
| 2800 | /* Get the length of the encrypted premaster secret */ | ||
| 2801 | enckey_len = (key_data[0] << 8) | key_data[1]; | ||
| 2802 | key_data += 2; | ||
| 2803 | dbg("enckey_len:%d len:%d", enckey_len, len); | ||
| 2804 | |||
| 2805 | if (enckey_len < 128 || enckey_len > 512) { | ||
| 2806 | bb_simple_error_msg_and_die("bad encrypted premaster length"); | ||
| 2807 | } | ||
| 2808 | |||
| 2809 | /* Decrypt the premaster secret using server's private RSA key */ | ||
| 2810 | { | ||
| 2811 | int32 ret; | ||
| 2812 | uint32 plen; | ||
| 2813 | psRsaKey_t *key = &tls->hsd->rsa_priv_key; | ||
| 2814 | |||
| 2815 | plen = RSA_PREMASTER_SIZE; | ||
| 2816 | ret = psRsaDecryptPriv(NULL, key, | ||
| 2817 | key_data, enckey_len, | ||
| 2818 | premaster, plen, NULL); | ||
| 2819 | |||
| 2820 | if (ret != RSA_PREMASTER_SIZE) { | ||
| 2821 | bb_error_msg_and_die("RSA decrypt failed or wrong premaster size: %d", ret); | ||
| 2822 | } | ||
| 2823 | |||
| 2824 | dbg("Decrypted premaster secret (%d bytes)", ret); | ||
| 2825 | |||
| 2826 | /* Verify premaster format: should start with version 0x03 0x03 (TLS 1.2) */ | ||
| 2827 | if (premaster[0] != 0x03 || premaster[1] != 0x03) { | ||
| 2828 | bb_simple_error_msg_and_die("bad premaster secret version"); | ||
| 2829 | } | ||
| 2830 | } | ||
| 2831 | premaster_size = RSA_PREMASTER_SIZE; | ||
| 2832 | } else { | ||
| 2833 | /* ECDHE key exchange */ | ||
| 2834 | int pubkey_len; | ||
| 2835 | uint8_t *client_pubkey; | ||
| 2836 | |||
| 2837 | /* Get client's ephemeral public key length */ | ||
| 2838 | pubkey_len = *key_data++; | ||
| 2839 | client_pubkey = key_data; | ||
| 2840 | |||
| 2841 | dbg("ECDHE: client pubkey_len:%d", pubkey_len); | ||
| 2842 | |||
| 2843 | /* Compute shared secret using client's public key and our private key */ | ||
| 2844 | if (tls->flags & USE_EC_CURVE_X25519) { | ||
| 2845 | /* x25519 */ | ||
| 2846 | if (pubkey_len != CURVE25519_KEYSIZE) { | ||
| 2847 | bb_simple_error_msg_and_die("bad x25519 public key length"); | ||
| 2848 | } | ||
| 2849 | curve_x25519_compute_premaster( | ||
| 2850 | tls->hsd->ecc_priv_key32, client_pubkey, | ||
| 2851 | premaster | ||
| 2852 | ); | ||
| 2853 | premaster_size = CURVE25519_KEYSIZE; | ||
| 2854 | } else { | ||
| 2855 | /* P256 */ | ||
| 2856 | if (pubkey_len != 1 + 2 * P256_KEYSIZE) { | ||
| 2857 | bb_simple_error_msg_and_die("bad P256 public key length"); | ||
| 2858 | } | ||
| 2859 | if (*client_pubkey++ != 0x04) { | ||
| 2860 | bb_simple_error_msg_and_die("compressed EC points not supported"); | ||
| 2861 | } | ||
| 2862 | curve_P256_compute_premaster( | ||
| 2863 | tls->hsd->ecc_priv_key32, client_pubkey, | ||
| 2864 | premaster | ||
| 2865 | ); | ||
| 2866 | premaster_size = P256_KEYSIZE; | ||
| 2867 | } | ||
| 2868 | |||
| 2869 | dbg("Computed ECDHE premaster secret (%d bytes)", premaster_size); | ||
| 2870 | } | ||
| 2871 | |||
| 2872 | derive_master_secret_and_keys(tls, premaster, premaster_size); | ||
| 2873 | // The key_block[] is partitioned as follows: | ||
| 2874 | tls->peer_write_MAC_key = tls->key_block; // client_write_MAC_key[] | ||
| 2875 | tls->our_write_MAC_key = tls->key_block + tls->MAC_size; // server_write_MAC_key[] | ||
| 2876 | tls->peer_write_key = tls->our_write_MAC_key + tls->MAC_size; // client_write_key[] | ||
| 2877 | tls->our_write_key = tls->peer_write_key + tls->key_size; // server_write_key[] | ||
| 2878 | tls->peer_write_IV = tls->our_write_key + tls->key_size; // client_write_IV[] | ||
| 2879 | tls->our_write_IV = tls->peer_write_IV + tls->IV_size; // server_write_IV[] | ||
| 2880 | dump_hex("server write_MAC_key:%s", tls->our_write_MAC_key, tls->MAC_size); | ||
| 2881 | dump_hex("server write_key:%s", tls->our_write_key, tls->key_size); | ||
| 2882 | dump_hex("server write_IV:%s", tls->our_write_IV, tls->IV_size); | ||
| 2883 | dump_hex("client write_MAC_key:%s", tls->peer_write_MAC_key, tls->MAC_size); | ||
| 2884 | dump_hex("client write_key:%s", tls->peer_write_key, tls->key_size); | ||
| 2885 | dump_hex("client write_IV:%s", tls->peer_write_IV, tls->IV_size); | ||
| 2886 | |||
| 2887 | initialize_aes_keys(tls); | ||
| 2888 | } | ||
| 2889 | |||
| 2890 | /* Load RSA private key from DER file (supports PKCS#8 or PKCS#1) | ||
| 2891 | * PKCS#8 PrivateKeyInfo ::= SEQUENCE { | ||
| 2892 | * version INTEGER, | ||
| 2893 | * algorithm AlgorithmIdentifier, | ||
| 2894 | * privateKey OCTET STRING -- contains PKCS#1 RSAPrivateKey | ||
| 2895 | * } | ||
| 2896 | * PKCS#1 RSAPrivateKey ::= SEQUENCE { | ||
| 2897 | * version INTEGER, -- 0 | ||
| 2898 | * modulus INTEGER, -- N | ||
| 2899 | * publicExponent INTEGER, -- e | ||
| 2900 | * privateExponent INTEGER, -- d | ||
| 2901 | * prime1 INTEGER, -- p | ||
| 2902 | * prime2 INTEGER, -- q | ||
| 2903 | * exponent1 INTEGER, -- dP (d mod (p-1)) | ||
| 2904 | * exponent2 INTEGER, -- dQ (d mod (q-1)) | ||
| 2905 | * coefficient INTEGER -- qP ((inverse of q) mod p) | ||
| 2906 | * } | ||
| 2907 | */ | ||
| 2908 | static NOINLINE /* don't inline - large stack use */ | ||
| 2909 | void load_rsa_priv_key(psRsaKey_t *key, uint8_t *buf, ssize_t sz) | ||
| 2910 | { | ||
| 2911 | uint8_t *der, *end; | ||
| 2912 | |||
| 2913 | der = buf; | ||
| 2914 | end = der + sz; | ||
| 2915 | |||
| 2916 | /* Enter the outer SEQUENCE */ | ||
| 2917 | der = enter_der_item(der, &end); | ||
| 2918 | |||
| 2919 | /* Check if this is PKCS#8 or PKCS#1 format | ||
| 2920 | * PKCS#8 starts with version 0 (02 01 00) followed by AlgorithmIdentifier (30 0d...) | ||
| 2921 | * PKCS#1 starts with version 0 (02 01 00) followed by modulus (02 82...) | ||
| 2922 | * We can distinguish by checking if the second element is a SEQUENCE (0x30) or INTEGER (0x02) | ||
| 2923 | */ | ||
| 2924 | der = skip_der_item(der, end); /* Skip version */ | ||
| 2925 | |||
| 2926 | if (*der == 0x30) { | ||
| 2927 | /* PKCS#8 format - skip AlgorithmIdentifier and enter OCTET STRING */ | ||
| 2928 | der = skip_der_item(der, end); /* Skip AlgorithmIdentifier */ | ||
| 2929 | der = enter_der_item(der, &end); /* Enter OCTET STRING containing PKCS#1 key */ | ||
| 2930 | der = enter_der_item(der, &end); /* Enter the PKCS#1 SEQUENCE */ | ||
| 2931 | der = skip_der_item(der, end); /* Skip version again */ | ||
| 2932 | } | ||
| 2933 | /* else: PKCS#1 format - we already skipped the version */ | ||
| 2934 | |||
| 2935 | /* Read the key components */ | ||
| 2936 | der_binary_to_pstm(&key->N, der, end); /* modulus */ | ||
| 2937 | der = skip_der_item(der, end); | ||
| 2938 | |||
| 2939 | der_binary_to_pstm(&key->e, der, end); /* publicExponent */ | ||
| 2940 | der = skip_der_item(der, end); | ||
| 2941 | |||
| 2942 | der_binary_to_pstm(&key->d, der, end); /* privateExponent */ | ||
| 2943 | der = skip_der_item(der, end); | ||
| 2944 | |||
| 2945 | der_binary_to_pstm(&key->p, der, end); /* prime1 */ | ||
| 2946 | der = skip_der_item(der, end); | ||
| 2947 | |||
| 2948 | der_binary_to_pstm(&key->q, der, end); /* prime2 */ | ||
| 2949 | der = skip_der_item(der, end); | ||
| 2950 | |||
| 2951 | der_binary_to_pstm(&key->dP, der, end); /* exponent1 */ | ||
| 2952 | der = skip_der_item(der, end); | ||
| 2953 | |||
| 2954 | der_binary_to_pstm(&key->dQ, der, end); /* exponent2 */ | ||
| 2955 | der = skip_der_item(der, end); | ||
| 2956 | |||
| 2957 | der_binary_to_pstm(&key->qP, der, end); /* coefficient */ | ||
| 2958 | |||
| 2959 | key->size = pstm_unsigned_bin_size(&key->N); | ||
| 2960 | key->optimized = 1; /* We have CRT parameters */ | ||
| 2961 | } | ||
| 2962 | |||
| 2963 | static char *decode_base64_or_die(char *dst, const char *src) | ||
| 2964 | { | ||
| 2965 | char *dst_end = decode_base64(dst, &src); | ||
| 2966 | if (*src != '\0') | ||
| 2967 | bb_error_msg_and_die("base64 decode error"); | ||
| 2968 | return dst_end; | ||
| 2969 | } | ||
| 2970 | |||
| 2971 | /* Parse PEM file and extract key + cert chain pairs | ||
| 2972 | * Returns number of pairs loaded | ||
| 2973 | */ | ||
| 2974 | static void load_pem_key_cert_pairs(tls_state_t *tls, const char *pem_filename) | ||
| 2975 | { | ||
| 2976 | static const char BLOCK_NAMES[] ALIGN1 = | ||
| 2977 | "EC PARAMETERS" "\0" | ||
| 2978 | "CERTIFICATE" "\0" | ||
| 2979 | "EC PRIVATE KEY" "\0" | ||
| 2980 | "PRIVATE KEY" "\0" | ||
| 2981 | "RSA PRIVATE KEY""\0" | ||
| 2982 | ; | ||
| 2983 | enum { | ||
| 2984 | str_EC_PARAMETERS = 0, | ||
| 2985 | str_CERTIFICATE = 1, | ||
| 2986 | str_EC_KEY = 2, | ||
| 2987 | }; | ||
| 2988 | char *p; | ||
| 2989 | char *pem_data; | ||
| 2990 | size_t pem_size; | ||
| 2991 | char *der_data; | ||
| 2992 | unsigned der_size; | ||
| 2993 | int keyidx; | ||
| 2994 | |||
| 2995 | /* Read PEM file */ | ||
| 2996 | pem_size = 64 * 1024; /* sanity limit */ | ||
| 2997 | pem_data = xmalloc_xopen_read_close(pem_filename, &pem_size); | ||
| 2998 | |||
| 2999 | der_data = NULL; | ||
| 3000 | der_size = 0; | ||
| 3001 | keyidx = -1; /* "we did not see any KEY yet" */ | ||
| 3002 | |||
| 3003 | p = pem_data; | ||
| 3004 | while (1) { | ||
| 3005 | unsigned n; | ||
| 3006 | char *block_end; | ||
| 3007 | char *block_type_end; | ||
| 3008 | |||
| 3009 | /* Find next PEM block */ | ||
| 3010 | p = skip_whitespace(p); | ||
| 3011 | if (*p == '\0') | ||
| 3012 | break; /* end of file */ | ||
| 3013 | p = is_prefixed_with(p, "-----BEGIN "); | ||
| 3014 | if (!p) | ||
| 3015 | goto err; | ||
| 3016 | block_type_end = strstr(p, "-----\n"); | ||
| 3017 | if (!block_type_end) | ||
| 3018 | goto err; | ||
| 3019 | block_type_end += 5; | ||
| 3020 | block_end = strstr(block_type_end, "\n-----END "); | ||
| 3021 | if (!block_end) | ||
| 3022 | goto err; | ||
| 3023 | *block_end = '\0'; | ||
| 3024 | block_end += 10; | ||
| 3025 | //-----BEGIN PRIVATE KEY-----\n | ||
| 3026 | // ^p ^block_type_end | ||
| 3027 | //BASE64HERE-BASE64HERE | ||
| 3028 | //-----END PRIVATE KEY----- | ||
| 3029 | // ^block_end | ||
| 3030 | /* The headers must match */ | ||
| 3031 | *block_type_end = '\0'; | ||
| 3032 | if (!is_prefixed_with(block_end, p)) | ||
| 3033 | goto err; | ||
| 3034 | /* Truncate trailing dashes from block type name */ | ||
| 3035 | block_type_end[-5] = '\0'; | ||
| 3036 | |||
| 3037 | block_end += (block_type_end - p); | ||
| 3038 | block_type_end++; | ||
| 3039 | //BASE64HERE-BASE64HERE | ||
| 3040 | //^block_type_end | ||
| 3041 | //-----END PRIVATE KEY----- | ||
| 3042 | // ^block_end | ||
| 3043 | n = index_in_strings(BLOCK_NAMES, p); | ||
| 3044 | if ((int)n < 0) | ||
| 3045 | bb_error_msg_and_die("'%s': unknown PEM block '%s'", pem_filename, p); | ||
| 3046 | |||
| 3047 | /* Note: may point to "\n" or even NUL */ | ||
| 3048 | p = block_end; | ||
| 3049 | |||
| 3050 | /* What block do we see? */ | ||
| 3051 | |||
| 3052 | if (n == str_EC_PARAMETERS) { | ||
| 3053 | /* "openssl ecparam -genkey" generates these, skip silently */ | ||
| 3054 | continue; /* skip */ | ||
| 3055 | } | ||
| 3056 | |||
| 3057 | if (n == str_CERTIFICATE) { | ||
| 3058 | struct certificate_msg { | ||
| 3059 | uint8_t type; | ||
| 3060 | uint8_t len24_hi, len24_mid, len24_lo; | ||
| 3061 | uint8_t cert_chain_len24_hi, cert_chain_len24_mid, cert_chain_len24_lo; | ||
| 3062 | uint8_t cert1_len24_hi, cert1_len24_mid, cert1_len24_lo; | ||
| 3063 | /* followed by certificate DER data */ | ||
| 3064 | /* followed by cert2_len24, cert2 DER data, ... */ | ||
| 3065 | }; | ||
| 3066 | struct certificate_msg *cert_msg; | ||
| 3067 | unsigned start; | ||
| 3068 | |||
| 3069 | if (keyidx < 0) | ||
| 3070 | bb_error_msg_and_die("'%s': certificate must be after key", pem_filename); | ||
| 3071 | |||
| 3072 | /* We create or update a full HANDSHAKE_CERTIFICATE message */ | ||
| 3073 | if (der_size == 0) { | ||
| 3074 | der_size = sizeof(*cert_msg); | ||
| 3075 | der_data = xzalloc(der_size); | ||
| 3076 | cert_msg = (void*)der_data; | ||
| 3077 | cert_msg->type = HANDSHAKE_CERTIFICATE; | ||
| 3078 | } else { | ||
| 3079 | /* We are here when we decode second or later cert */ | ||
| 3080 | der_size += 3; /* for len24 */ | ||
| 3081 | } | ||
| 3082 | |||
| 3083 | /* Decode BASE64 */ | ||
| 3084 | start = der_size; | ||
| 3085 | der_size += block_end - block_type_end; /* worst case size */ | ||
| 3086 | der_data = xrealloc(der_data, der_size); | ||
| 3087 | der_size = decode_base64_or_die(der_data + start, block_type_end) - der_data; | ||
| 3088 | der_data = xrealloc(der_data, der_size); | ||
| 3089 | |||
| 3090 | /* Fill last cert's len24 */ | ||
| 3091 | n = der_size - start; | ||
| 3092 | der_data[start - 3] = n >> 16; | ||
| 3093 | der_data[start - 2] = n >> 8; | ||
| 3094 | der_data[start - 1] = n; | ||
| 3095 | /* Update sizes in header */ | ||
| 3096 | cert_msg = (void*)der_data; | ||
| 3097 | n = der_size - 4; | ||
| 3098 | cert_msg->len24_hi = n >> 16; | ||
| 3099 | cert_msg->len24_mid = n >> 8; | ||
| 3100 | cert_msg->len24_lo = n; | ||
| 3101 | n -= 3; | ||
| 3102 | cert_msg->cert_chain_len24_hi = n >> 16; | ||
| 3103 | cert_msg->cert_chain_len24_mid = n >> 8; | ||
| 3104 | cert_msg->cert_chain_len24_lo = n; | ||
| 3105 | |||
| 3106 | tls->hsd->certs[keyidx] = der_data; | ||
| 3107 | tls->hsd->certsize[keyidx] = der_size; | ||
| 3108 | continue; | ||
| 3109 | } | ||
| 3110 | |||
| 3111 | /* We see a key */ | ||
| 3112 | |||
| 3113 | /* Decode BASE64 */ | ||
| 3114 | der_size = block_end - block_type_end; /* worst case size */ | ||
| 3115 | der_data = xmalloc(der_size); | ||
| 3116 | der_size = decode_base64_or_die(der_data, block_type_end) - der_data; | ||
| 3117 | der_data = xrealloc(der_data, der_size); | ||
| 3118 | |||
| 3119 | keyidx = (n == str_EC_KEY) ? KEY_ECDSA : KEY_RSA; | ||
| 3120 | if (tls->hsd->keys[keyidx]) | ||
| 3121 | bb_error_msg_and_die("'%s': more than one key", pem_filename); | ||
| 3122 | tls->hsd->keys[keyidx] = der_data; | ||
| 3123 | tls->hsd->keysize[keyidx] = der_size; | ||
| 3124 | |||
| 3125 | der_data = NULL; | ||
| 3126 | der_size = 0; | ||
| 3127 | } /* while (parsing PEM) */ | ||
| 3128 | free(pem_data); | ||
| 3129 | |||
| 3130 | if (!tls->hsd->keys[KEY_RSA] && !tls->hsd->keys[KEY_ECDSA]) | ||
| 3131 | bb_error_msg_and_die("'%s': no private keys", pem_filename); | ||
| 3132 | |||
| 3133 | if (tls->hsd->keys[KEY_RSA]) { | ||
| 3134 | if (!tls->hsd->certs[KEY_RSA]) | ||
| 3135 | bb_error_msg_and_die("'%s': key with no cert", pem_filename); | ||
| 3136 | /* Parse RSA key from DER */ | ||
| 3137 | load_rsa_priv_key(&tls->hsd->rsa_priv_key, (uint8_t*)tls->hsd->keys[KEY_RSA], tls->hsd->keysize[KEY_RSA]); | ||
| 3138 | } | ||
| 3139 | if (tls->hsd->keys[KEY_ECDSA]) { | ||
| 3140 | if (!tls->hsd->certs[KEY_ECDSA]) | ||
| 3141 | bb_error_msg_and_die("'%s': key with no cert", pem_filename); | ||
| 3142 | bb_error_msg("'%s': ECDSA keys not supported", pem_filename); | ||
| 3143 | } | ||
| 3144 | |||
| 3145 | return; | ||
| 3146 | err: | ||
| 3147 | bb_error_msg_and_die("malformed PEM file at '%.*s'", (int)(skip_whitespace(p) - p), p); | ||
| 3148 | } | ||
| 3149 | |||
| 3150 | void FAST_FUNC tls_handshake_as_server(tls_state_t *tls, | ||
| 3151 | const char *pem_filename) | ||
| 3152 | { | ||
| 3153 | /* Allocate handshake data */ | ||
| 3154 | tls->hsd = xzalloc(sizeof(*tls->hsd)); | ||
| 3155 | |||
| 3156 | /* Load server key(s) and certificate(s) from PEM file */ | ||
| 3157 | load_pem_key_cert_pairs(tls, pem_filename); | ||
| 3158 | |||
| 3159 | sha256_begin(&tls->hsd->handshake_hash_ctx); | ||
| 3160 | |||
| 3161 | /* Server handshake sequence: | ||
| 3162 | * 1. Receive ClientHello | ||
| 3163 | * 2. Send ServerHello | ||
| 3164 | * 3. Send Certificate | ||
| 3165 | * 4. [Send ServerKeyExchange] - only for DHE/ECDHE | ||
| 3166 | * 5. [Send CertificateRequest] - optional, skip for now | ||
| 3167 | * 6. Send ServerHelloDone | ||
| 3168 | * 7. Receive ClientKeyExchange | ||
| 3169 | * 8. Receive ChangeCipherSpec | ||
| 3170 | * 9. Receive Finished (encrypted) | ||
| 3171 | * 10. Send ChangeCipherSpec | ||
| 3172 | * 11. Send Finished (encrypted) | ||
| 3173 | */ | ||
| 3174 | tls->expecting_first_packet = 1; | ||
| 3175 | get_client_hello(tls); | ||
| 3176 | tls->expecting_first_packet = 0; | ||
| 3177 | send_server_hello(tls); | ||
| 3178 | send_server_certificate(tls); | ||
| 3179 | if (tls->flags & NEED_EC_KEY) | ||
| 3180 | send_server_key_exchange(tls); | ||
| 3181 | send_server_hello_done(tls); | ||
| 3182 | |||
| 3183 | get_client_key_exchange(tls); | ||
| 3184 | get_change_cipher_spec(tls); | ||
| 3185 | /* Get (encrypted) FINISHED from the client */ | ||
| 3186 | get_finished(tls, "'cliend finished'"); | ||
| 3187 | |||
| 3188 | send_change_cipher_spec(tls); | ||
| 3189 | send_finished(tls, "server finished"); | ||
| 3190 | |||
| 3191 | /* application data can be sent/received */ | ||
| 3192 | |||
| 3193 | /* free handshake data */ | ||
| 3194 | psRsaKey_clear(&tls->hsd->rsa_priv_key); | ||
| 3195 | free(tls->hsd->keys[0]); | ||
| 3196 | free(tls->hsd->keys[1]); | ||
| 3197 | free(tls->hsd->certs[0]); | ||
| 3198 | free(tls->hsd->certs[1]); | ||
| 3199 | // if (PARANOIA) | ||
| 3200 | // memset(tls->hsd, 0, sizeof(*tls->hsd)); | ||
| 3201 | free(tls->hsd); | ||
| 3202 | tls->hsd = NULL; | ||
| 3203 | } | ||
| 3204 | #endif | ||
| 2369 | #else | 3205 | #else |
| 2370 | #include <stdbool.h> | 3206 | #include <stdbool.h> |
| 2371 | 3207 | ||
diff --git a/networking/tls.h b/networking/tls.h index fde2e4d3d..0252b161f 100644 --- a/networking/tls.h +++ b/networking/tls.h | |||
| @@ -49,6 +49,7 @@ | |||
| 49 | #define PS_PLATFORM_FAIL -7 /* Failure as a result of system call error */ | 49 | #define PS_PLATFORM_FAIL -7 /* Failure as a result of system call error */ |
| 50 | #define PS_MEM_FAIL -8 /* Failure to allocate requested memory */ | 50 | #define PS_MEM_FAIL -8 /* Failure to allocate requested memory */ |
| 51 | #define PS_LIMIT_FAIL -9 /* Failure on sanity/limit tests */ | 51 | #define PS_LIMIT_FAIL -9 /* Failure on sanity/limit tests */ |
| 52 | #define PS_UNSUPPORTED_FAIL -10 /* Unsupported algorithm or operation */ | ||
| 52 | 53 | ||
| 53 | #define PS_TRUE 1 | 54 | #define PS_TRUE 1 |
| 54 | #define PS_FALSE 0 | 55 | #define PS_FALSE 0 |
| @@ -90,6 +91,7 @@ void tls_get_random(void *buf, unsigned len) FAST_FUNC; | |||
| 90 | 91 | ||
| 91 | #define matrixCryptoGetPrngData(buf, len, userPtr) (tls_get_random(buf, len), PS_SUCCESS) | 92 | #define matrixCryptoGetPrngData(buf, len, userPtr) (tls_get_random(buf, len), PS_SUCCESS) |
| 92 | 93 | ||
| 94 | #define psMalloc(pool, size) xmalloc(size) | ||
| 93 | #define psFree(p, pool) free(p) | 95 | #define psFree(p, pool) free(p) |
| 94 | #define psTraceCrypto(msg) bb_simple_error_msg_and_die(msg) | 96 | #define psTraceCrypto(msg) bb_simple_error_msg_and_die(msg) |
| 95 | 97 | ||
| @@ -109,6 +111,22 @@ void tls_get_random(void *buf, unsigned len) FAST_FUNC; | |||
| 109 | #define P256_KEYSIZE 32 | 111 | #define P256_KEYSIZE 32 |
| 110 | #define CURVE25519_KEYSIZE 32 | 112 | #define CURVE25519_KEYSIZE 32 |
| 111 | 113 | ||
| 114 | /* Separate keypair generation and premaster computation functions */ | ||
| 115 | void curve_x25519_generate_keypair( | ||
| 116 | uint8_t *privkey32, uint8_t *pubkey32) FAST_FUNC; | ||
| 117 | void curve_x25519_compute_premaster( | ||
| 118 | const uint8_t *privkey32, const uint8_t *peerkey32, | ||
| 119 | uint8_t *premaster32) FAST_FUNC; | ||
| 120 | |||
| 121 | #if ENABLE_SSL_SERVER | ||
| 122 | void curve_P256_generate_keypair( | ||
| 123 | uint8_t *privkey32, uint8_t *pubkey2x32) FAST_FUNC; | ||
| 124 | void curve_P256_compute_premaster( | ||
| 125 | const uint8_t *privkey32, const uint8_t *peerkey2x32, | ||
| 126 | uint8_t *premaster32) FAST_FUNC; | ||
| 127 | #endif | ||
| 128 | |||
| 129 | /* Combined operations (for client-side use) */ | ||
| 112 | void curve_x25519_compute_pubkey_and_premaster( | 130 | void curve_x25519_compute_pubkey_and_premaster( |
| 113 | uint8_t *pubkey32, uint8_t *premaster32, | 131 | uint8_t *pubkey32, uint8_t *premaster32, |
| 114 | const uint8_t *peerkey32) FAST_FUNC; | 132 | const uint8_t *peerkey32) FAST_FUNC; |
diff --git a/networking/tls_fe.c b/networking/tls_fe.c index e5580fbcf..479d0aaee 100644 --- a/networking/tls_fe.c +++ b/networking/tls_fe.c | |||
| @@ -607,22 +607,47 @@ static void curve25519(byte *result, const byte *e, const byte *q) | |||
| 607 | fe_normalize(result); | 607 | fe_normalize(result); |
| 608 | } | 608 | } |
| 609 | 609 | ||
| 610 | /* interface to bbox's TLS code: */ | 610 | /* interface to bbox's TLS code: |
| 611 | * | ||
| 612 | * Wire format for elliptic curve points differs between curves: | ||
| 613 | * - P256: point is (x,y) where each coordinate is a 256-bit (32-byte) big-endian integer. | ||
| 614 | * Wire format: 64 bytes total (plus 0x04 prefix byte for "uncompressed point"). | ||
| 615 | * - x25519: point is a single 256-bit (32-byte) little-endian integer. | ||
| 616 | * Wire format: 32 bytes. | ||
| 617 | * | ||
| 618 | * The interface functions below accept and generate EC points in their respective | ||
| 619 | * wire formats. Internal calculations may use different representations, but all | ||
| 620 | * conversions are handled internally within these functions. | ||
| 621 | * (Note: x25519 implementation in this file uses wire format internally as well) | ||
| 622 | */ | ||
| 623 | |||
| 624 | /* Generate x25519 keypair: random private key + corresponding public key */ | ||
| 625 | void FAST_FUNC curve_x25519_generate_keypair(uint8_t *privkey32, uint8_t *pubkey32) | ||
| 626 | { | ||
| 627 | /* Generate random private key, see RFC 7748 */ | ||
| 628 | tls_get_random(privkey32, CURVE25519_KEYSIZE); | ||
| 629 | privkey32[0] &= 0xf8; | ||
| 630 | privkey32[CURVE25519_KEYSIZE-1] = ((privkey32[CURVE25519_KEYSIZE-1] & 0x7f) | 0x40); | ||
| 631 | |||
| 632 | /* Compute public key from private key */ | ||
| 633 | curve25519(pubkey32, privkey32, NULL /* "use base point of x25519" */); | ||
| 634 | } | ||
| 635 | |||
| 636 | /* Compute shared secret (premaster) from our private key and peer's public key */ | ||
| 637 | void FAST_FUNC curve_x25519_compute_premaster( | ||
| 638 | const uint8_t *privkey32, const uint8_t *peerkey32, | ||
| 639 | uint8_t *premaster32) | ||
| 640 | { | ||
| 641 | curve25519(premaster32, privkey32, peerkey32); | ||
| 642 | } | ||
| 611 | 643 | ||
| 644 | /* Combined operation: generate keypair and compute premaster in one call */ | ||
| 612 | void FAST_FUNC curve_x25519_compute_pubkey_and_premaster( | 645 | void FAST_FUNC curve_x25519_compute_pubkey_and_premaster( |
| 613 | uint8_t *pubkey, uint8_t *premaster, | 646 | uint8_t *pubkey, uint8_t *premaster, |
| 614 | const uint8_t *peerkey32) | 647 | const uint8_t *peerkey32) |
| 615 | { | 648 | { |
| 616 | uint8_t privkey[CURVE25519_KEYSIZE]; //[32] | 649 | uint8_t privkey[CURVE25519_KEYSIZE]; //[32] |
| 617 | 650 | ||
| 618 | /* Generate random private key, see RFC 7748 */ | 651 | curve_x25519_generate_keypair(privkey, pubkey); |
| 619 | tls_get_random(privkey, sizeof(privkey)); | 652 | curve_x25519_compute_premaster(privkey, peerkey32, premaster); |
| 620 | privkey[0] &= 0xf8; | ||
| 621 | privkey[CURVE25519_KEYSIZE-1] = ((privkey[CURVE25519_KEYSIZE-1] & 0x7f) | 0x40); | ||
| 622 | |||
| 623 | /* Compute public key */ | ||
| 624 | curve25519(pubkey, privkey, NULL /* "use base point of x25519" */); | ||
| 625 | |||
| 626 | /* Compute premaster using peer's public key */ | ||
| 627 | curve25519(premaster, privkey, peerkey32); | ||
| 628 | } | 653 | } |
diff --git a/networking/tls_rsa.c b/networking/tls_rsa.c index 2dd5a02f4..4dcbd3a4d 100644 --- a/networking/tls_rsa.c +++ b/networking/tls_rsa.c | |||
| @@ -211,3 +211,176 @@ int32 FAST_FUNC psRsaEncryptPub(psPool_t *pool, psRsaKey_t *key, | |||
| 211 | } | 211 | } |
| 212 | return size; | 212 | return size; |
| 213 | } | 213 | } |
| 214 | |||
| 215 | #if ENABLE_SSL_SERVER // || ENABLE_FEATURE_HTTPD_SSL | ||
| 216 | |||
| 217 | #define psRsaEncryptPriv(pool, key, in, inlen, out, outlen, data) \ | ||
| 218 | psRsaEncryptPriv( key, in, inlen, out, outlen) | ||
| 219 | static //bbox | ||
| 220 | int32 psRsaEncryptPriv(psPool_t *pool, psRsaKey_t *key, | ||
| 221 | unsigned char *in, uint32 inlen, | ||
| 222 | unsigned char *out, uint32 outlen, void *data) | ||
| 223 | { | ||
| 224 | int32 err; | ||
| 225 | uint32 size; | ||
| 226 | |||
| 227 | size = key->size; | ||
| 228 | if (outlen < size) { | ||
| 229 | psTraceCrypto("Error on bad outlen parameter to psRsaEncryptPriv\n"); | ||
| 230 | return PS_ARG_FAIL; | ||
| 231 | } | ||
| 232 | if ((err = pkcs1Pad(in, inlen, out, size, PUBKEY_TYPE, data)) < PS_SUCCESS){ | ||
| 233 | psTraceCrypto("Error padding psRsaEncryptPriv. Likely data too long\n"); | ||
| 234 | return err; | ||
| 235 | } | ||
| 236 | if ((err = psRsaCrypt(pool, out, size, out, (uint32*)&outlen, key, | ||
| 237 | PRIVKEY_TYPE, data)) < PS_SUCCESS) { | ||
| 238 | psTraceCrypto("Error performing psRsaEncryptPriv\n"); | ||
| 239 | return err; | ||
| 240 | } | ||
| 241 | if (outlen != size) { | ||
| 242 | psTraceCrypto("Encrypted size error in psRsaEncryptPriv\n"); | ||
| 243 | return PS_FAILURE; | ||
| 244 | } | ||
| 245 | return size; | ||
| 246 | } | ||
| 247 | |||
| 248 | #define ASN_OVERHEAD_LEN_RSA_SHA2 19 | ||
| 249 | //#define ASN_OVERHEAD_LEN_RSA_SHA1 15 | ||
| 250 | |||
| 251 | /* ASN.1 DigestInfo wrappers for hash algorithms */ | ||
| 252 | static const unsigned char asn256dsWrap[] = {0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, | ||
| 253 | 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; | ||
| 254 | //#ifdef USE_SHA384 | ||
| 255 | //static const unsigned char asn384dsWrap[] = {0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, | ||
| 256 | // 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}; | ||
| 257 | //#endif | ||
| 258 | //static const unsigned char asn1dsWrap[] = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, | ||
| 259 | // 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14}; | ||
| 260 | |||
| 261 | int32 FAST_FUNC privRsaEncryptSignedElement(psPool_t *pool, psRsaKey_t *key, //bbox: was psPubKey_t | ||
| 262 | unsigned char *in, uint32 inlen, unsigned char *out, uint32 outlen, | ||
| 263 | void *data) | ||
| 264 | { | ||
| 265 | unsigned char *c; | ||
| 266 | uint32 inlenWithAsn; | ||
| 267 | int32 rc; | ||
| 268 | |||
| 269 | if (inlen == 32) { //SHA256_HASH_SIZE | ||
| 270 | inlenWithAsn = inlen + ASN_OVERHEAD_LEN_RSA_SHA2; | ||
| 271 | c = psMalloc(pool, inlenWithAsn); | ||
| 272 | memcpy(c, asn256dsWrap, ASN_OVERHEAD_LEN_RSA_SHA2); | ||
| 273 | memcpy(c + ASN_OVERHEAD_LEN_RSA_SHA2, in, inlen); | ||
| 274 | // } else if (inlen == SHA1_HASH_SIZE) { | ||
| 275 | // inlenWithAsn = inlen + ASN_OVERHEAD_LEN_RSA_SHA1; | ||
| 276 | // c = psMalloc(pool, inlenWithAsn); | ||
| 277 | // memcpy(c, asn1dsWrap, ASN_OVERHEAD_LEN_RSA_SHA1); | ||
| 278 | // memcpy(c + ASN_OVERHEAD_LEN_RSA_SHA1, in, inlen); | ||
| 279 | //#ifdef USE_SHA384 | ||
| 280 | // } else if (inlen == SHA384_HASH_SIZE) { | ||
| 281 | // inlenWithAsn = inlen + ASN_OVERHEAD_LEN_RSA_SHA2; | ||
| 282 | // c = psMalloc(pool, inlenWithAsn); | ||
| 283 | // memcpy(c, asn384dsWrap, ASN_OVERHEAD_LEN_RSA_SHA2); | ||
| 284 | // memcpy(c + ASN_OVERHEAD_LEN_RSA_SHA2, in, inlen); | ||
| 285 | //#endif | ||
| 286 | } else { | ||
| 287 | return PS_UNSUPPORTED_FAIL; | ||
| 288 | } | ||
| 289 | |||
| 290 | rc = psRsaEncryptPriv(pool, key, c, inlenWithAsn, //bbox: was (psRsaKey_t*)key->key | ||
| 291 | out, outlen, data); | ||
| 292 | |||
| 293 | psFree(c, pool); | ||
| 294 | return rc; | ||
| 295 | } | ||
| 296 | |||
| 297 | /* Remove PKCS#1 padding (Type 2) from decrypted data | ||
| 298 | * Format: 00 || 02 || PS || 00 || M | ||
| 299 | * Returns length of unpadded message, or negative on error | ||
| 300 | */ | ||
| 301 | #define pkcs1Unpad(in, inlen, out, outlen) \ | ||
| 302 | pkcs1Unpad(in, inlen, out, outlen) | ||
| 303 | static //bbox | ||
| 304 | int32 pkcs1Unpad(unsigned char *in, uint32 inlen, unsigned char *out, | ||
| 305 | uint32 outlen) | ||
| 306 | { | ||
| 307 | unsigned char *c, *end; | ||
| 308 | uint32 msglen; | ||
| 309 | |||
| 310 | if (inlen < 11) { /* Minimum: 00 02 [8 bytes PS] 00 */ | ||
| 311 | psTraceCrypto("pkcs1Unpad: input too short\n"); | ||
| 312 | return PS_FAILURE; | ||
| 313 | } | ||
| 314 | |||
| 315 | c = in; | ||
| 316 | end = in + inlen; | ||
| 317 | |||
| 318 | /* Check padding type byte */ | ||
| 319 | if (*c++ != 0x00) { | ||
| 320 | psTraceCrypto("pkcs1Unpad: bad first byte\n"); | ||
| 321 | return PS_FAILURE; | ||
| 322 | } | ||
| 323 | if (*c++ != 0x02) { | ||
| 324 | psTraceCrypto("pkcs1Unpad: bad padding type\n"); | ||
| 325 | return PS_FAILURE; | ||
| 326 | } | ||
| 327 | |||
| 328 | /* Skip padding string (non-zero bytes) until we find 0x00 */ | ||
| 329 | while (c < end && *c != 0x00) { | ||
| 330 | c++; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (c >= end) { | ||
| 334 | psTraceCrypto("pkcs1Unpad: no 0x00 separator found\n"); | ||
| 335 | return PS_FAILURE; | ||
| 336 | } | ||
| 337 | |||
| 338 | /* Skip the 0x00 separator */ | ||
| 339 | c++; | ||
| 340 | |||
| 341 | /* Calculate message length */ | ||
| 342 | msglen = (uint32)(end - c); | ||
| 343 | |||
| 344 | if (msglen > outlen) { | ||
| 345 | psTraceCrypto("pkcs1Unpad: output buffer too small\n"); | ||
| 346 | return PS_FAILURE; | ||
| 347 | } | ||
| 348 | |||
| 349 | /* Copy message to output */ | ||
| 350 | memcpy(out, c, msglen); | ||
| 351 | |||
| 352 | return msglen; | ||
| 353 | } | ||
| 354 | |||
| 355 | /* RSA private key decryption (PKCS#1 v1.5) | ||
| 356 | * Decrypts with private key and removes PKCS#1 padding | ||
| 357 | */ | ||
| 358 | #define psRsaDecryptPriv(pool, key, in, inlen, out, outlen, data) \ | ||
| 359 | psRsaDecryptPriv( key, in, inlen, out, outlen) | ||
| 360 | int32 FAST_FUNC psRsaDecryptPriv(psPool_t *pool, psRsaKey_t *key, | ||
| 361 | unsigned char *in, uint32 inlen, | ||
| 362 | unsigned char *out, uint32 outlen, void *data) | ||
| 363 | { | ||
| 364 | int32 err; | ||
| 365 | uint32 ptLen; | ||
| 366 | |||
| 367 | if (inlen != key->size) { | ||
| 368 | psTraceCrypto("Error on bad inlen parameter to psRsaDecryptPriv\n"); | ||
| 369 | return PS_ARG_FAIL; | ||
| 370 | } | ||
| 371 | ptLen = inlen; | ||
| 372 | if ((err = psRsaCrypt(pool, in, inlen, in, &ptLen, key, | ||
| 373 | PRIVKEY_TYPE, data)) < PS_SUCCESS) { | ||
| 374 | psTraceCrypto("Error performing psRsaDecryptPriv\n"); | ||
| 375 | return err; | ||
| 376 | } | ||
| 377 | if (ptLen != inlen) { | ||
| 378 | psTraceCrypto("Decrypted size error in psRsaDecryptPriv\n"); | ||
| 379 | return PS_FAILURE; | ||
| 380 | } | ||
| 381 | err = pkcs1Unpad(in, inlen, out, outlen); | ||
| 382 | memset(in, 0x0, inlen); | ||
| 383 | return err; | ||
| 384 | } | ||
| 385 | |||
| 386 | #endif /* server */ | ||
diff --git a/networking/tls_rsa.h b/networking/tls_rsa.h index 82bea2a67..a8db73b9c 100644 --- a/networking/tls_rsa.h +++ b/networking/tls_rsa.h | |||
| @@ -30,3 +30,15 @@ static ALWAYS_INLINE void psRsaKey_clear(psRsaKey_t *key) | |||
| 30 | int32 psRsaEncryptPub(psPool_t *pool, psRsaKey_t *key, | 30 | int32 psRsaEncryptPub(psPool_t *pool, psRsaKey_t *key, |
| 31 | unsigned char *in, uint32 inlen, | 31 | unsigned char *in, uint32 inlen, |
| 32 | unsigned char *out, uint32 outlen, void *data) FAST_FUNC; | 32 | unsigned char *out, uint32 outlen, void *data) FAST_FUNC; |
| 33 | |||
| 34 | #define psRsaDecryptPriv(pool, key, in, inlen, out, outlen, data) \ | ||
| 35 | psRsaDecryptPriv( key, in, inlen, out, outlen) | ||
| 36 | int32 psRsaDecryptPriv(psPool_t *pool, psRsaKey_t *key, | ||
| 37 | unsigned char *in, uint32 inlen, | ||
| 38 | unsigned char *out, uint32 outlen, void *data) FAST_FUNC; | ||
| 39 | |||
| 40 | #define privRsaEncryptSignedElement(pool, key, in, inlen, out, outlen, data) \ | ||
| 41 | privRsaEncryptSignedElement( key, in, inlen, out, outlen) | ||
| 42 | int32 privRsaEncryptSignedElement(psPool_t *pool, psRsaKey_t *key, | ||
| 43 | unsigned char *in, uint32 inlen, | ||
| 44 | unsigned char *out, uint32 outlen, void *data) FAST_FUNC; | ||
diff --git a/networking/tls_sp_c32.c b/networking/tls_sp_c32.c index e493c436a..90ab61a56 100644 --- a/networking/tls_sp_c32.c +++ b/networking/tls_sp_c32.c | |||
| @@ -1514,6 +1514,52 @@ static void sp_ecc_make_key_256(sp_digit privkey[8], uint8_t *pubkey) | |||
| 1514 | memset(point, 0, sizeof(point)); //paranoia | 1514 | memset(point, 0, sizeof(point)); //paranoia |
| 1515 | } | 1515 | } |
| 1516 | 1516 | ||
| 1517 | /* interface to bbox's TLS code: | ||
| 1518 | * | ||
| 1519 | * Wire format for elliptic curve points differs between curves: | ||
| 1520 | * - P256: point is (x,y) where each coordinate is a 256-bit (32-byte) big-endian integer. | ||
| 1521 | * Wire format: 64 bytes total (plus 0x04 prefix byte for "uncompressed point"). | ||
| 1522 | * - x25519: point is a single 256-bit (32-byte) little-endian integer. | ||
| 1523 | * Wire format: 32 bytes. | ||
| 1524 | * | ||
| 1525 | * The interface functions below accept and generate EC points in their respective | ||
| 1526 | * wire formats. Internal calculations may use different representations, but all | ||
| 1527 | * conversions are handled internally within these functions. | ||
| 1528 | */ | ||
| 1529 | |||
| 1530 | #if ENABLE_SSL_SERVER | ||
| 1531 | /* Generate P256 keypair: random private key + corresponding public key */ | ||
| 1532 | void FAST_FUNC curve_P256_generate_keypair(uint8_t *privkey32, uint8_t *pubkey2x32) | ||
| 1533 | { | ||
| 1534 | sp_digit privkey_sp[8]; | ||
| 1535 | |||
| 1536 | /* Generate keypair using internal representation */ | ||
| 1537 | sp_ecc_make_key_256(privkey_sp, pubkey2x32); | ||
| 1538 | |||
| 1539 | /* Convert private key to binary format for storage */ | ||
| 1540 | sp_256_to_bin_8(privkey_sp, privkey32); | ||
| 1541 | |||
| 1542 | memset(privkey_sp, 0, sizeof(privkey_sp)); //paranoia | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | /* Compute shared secret (premaster) from our private key and peer's public key */ | ||
| 1546 | void FAST_FUNC curve_P256_compute_premaster( | ||
| 1547 | const uint8_t *privkey32, const uint8_t *peerkey2x32, | ||
| 1548 | uint8_t *premaster32) | ||
| 1549 | { | ||
| 1550 | sp_digit privkey_sp[8]; | ||
| 1551 | |||
| 1552 | /* Convert binary private key to internal representation */ | ||
| 1553 | sp_256_from_bin_8(privkey_sp, privkey32); | ||
| 1554 | |||
| 1555 | /* Compute shared secret */ | ||
| 1556 | sp_ecc_secret_gen_256(privkey_sp, peerkey2x32, premaster32); | ||
| 1557 | |||
| 1558 | memset(privkey_sp, 0, sizeof(privkey_sp)); //paranoia | ||
| 1559 | } | ||
| 1560 | #endif | ||
| 1561 | |||
| 1562 | /* Combined operation: generate keypair and compute premaster in one call */ | ||
| 1517 | void FAST_FUNC curve_P256_compute_pubkey_and_premaster( | 1563 | void FAST_FUNC curve_P256_compute_pubkey_and_premaster( |
| 1518 | uint8_t *pubkey2x32, uint8_t *premaster32, | 1564 | uint8_t *pubkey2x32, uint8_t *premaster32, |
| 1519 | const uint8_t *peerkey2x32) | 1565 | const uint8_t *peerkey2x32) |
diff --git a/networking/udhcp/d6_socket.c b/networking/udhcp/d6_socket.c index acf108367..83df4b396 100644 --- a/networking/udhcp/d6_socket.c +++ b/networking/udhcp/d6_socket.c | |||
| @@ -116,7 +116,8 @@ int FAST_FUNC d6_listen_socket(int port, const char *inf) | |||
| 116 | bb_simple_perror_msg_and_die("SO_BROADCAST"); | 116 | bb_simple_perror_msg_and_die("SO_BROADCAST"); |
| 117 | 117 | ||
| 118 | /* SO_BINDTODEVICE doesn't work on ethernet aliases (ethN:M) */ | 118 | /* SO_BINDTODEVICE doesn't work on ethernet aliases (ethN:M) */ |
| 119 | colon = strrchr(inf, ':'); | 119 | colon = (char*)strrchr(inf, ':'); |
| 120 | /* NB: inf can really be a *const* string if it's a default, but defaults have no ':' */ | ||
| 120 | if (colon) | 121 | if (colon) |
| 121 | *colon = '\0'; | 122 | *colon = '\0'; |
| 122 | 123 | ||
diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c index 35e10688b..5d2283eaf 100644 --- a/networking/udhcp/socket.c +++ b/networking/udhcp/socket.c | |||
| @@ -90,7 +90,8 @@ int FAST_FUNC udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) | |||
| 90 | bb_simple_perror_msg_and_die("SO_BROADCAST"); | 90 | bb_simple_perror_msg_and_die("SO_BROADCAST"); |
| 91 | 91 | ||
| 92 | /* SO_BINDTODEVICE doesn't work on ethernet aliases (ethN:M) */ | 92 | /* SO_BINDTODEVICE doesn't work on ethernet aliases (ethN:M) */ |
| 93 | colon = strrchr(inf, ':'); | 93 | colon = (char*)strrchr(inf, ':'); |
| 94 | /* NB: inf can really be a *const* string if it's a default, but defaults have no ':' */ | ||
| 94 | if (colon) | 95 | if (colon) |
| 95 | *colon = '\0'; | 96 | *colon = '\0'; |
| 96 | 97 | ||
diff --git a/printutils/lpr.c b/printutils/lpr.c index 25b0f7235..464208c65 100644 --- a/printutils/lpr.c +++ b/printutils/lpr.c | |||
| @@ -122,7 +122,7 @@ int lpqr_main(int argc UNUSED_PARAM, char **argv) | |||
| 122 | 122 | ||
| 123 | { | 123 | { |
| 124 | // queue name is to the left of '@' | 124 | // queue name is to the left of '@' |
| 125 | char *s = strchr(queue, '@'); | 125 | char *s = (char *)strchr(queue, '@'); |
| 126 | if (s) { | 126 | if (s) { |
| 127 | // server name is to the right of '@' | 127 | // server name is to the right of '@' |
| 128 | *s = '\0'; | 128 | *s = '\0'; |
diff --git a/procps/nmeter.c b/procps/nmeter.c index dca07eac6..fd8907aac 100644 --- a/procps/nmeter.c +++ b/procps/nmeter.c | |||
| @@ -913,7 +913,8 @@ int nmeter_main(int argc UNUSED_PARAM, char **argv) | |||
| 913 | // parameters as seen by e.g. ps. Making a copy... | 913 | // parameters as seen by e.g. ps. Making a copy... |
| 914 | cur = xstrdup(argv[0]); | 914 | cur = xstrdup(argv[0]); |
| 915 | while (1) { | 915 | while (1) { |
| 916 | char *param, *p; | 916 | char *param; |
| 917 | const char *p; | ||
| 917 | prev = cur; | 918 | prev = cur; |
| 918 | again: | 919 | again: |
| 919 | cur = strchr(cur, '%'); | 920 | cur = strchr(cur, '%'); |
| @@ -929,7 +930,7 @@ int nmeter_main(int argc UNUSED_PARAM, char **argv) | |||
| 929 | // format: %[foptstring] | 930 | // format: %[foptstring] |
| 930 | cur++; | 931 | cur++; |
| 931 | p = strchr(options, cur[0]); | 932 | p = strchr(options, cur[0]); |
| 932 | param = cur+1; | 933 | param = cur + 1; |
| 933 | while (cur[0] != ']') { | 934 | while (cur[0] != ']') { |
| 934 | if (!cur[0]) | 935 | if (!cur[0]) |
| 935 | bb_show_usage(); | 936 | bb_show_usage(); |
diff --git a/procps/powertop.c b/procps/powertop.c index 6fe892540..a24748efc 100644 --- a/procps/powertop.c +++ b/procps/powertop.c | |||
| @@ -238,7 +238,7 @@ static void save_line(const char *string, int count) | |||
| 238 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ | 238 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ |
| 239 | static int is_hpet_irq(const char *name) | 239 | static int is_hpet_irq(const char *name) |
| 240 | { | 240 | { |
| 241 | char *p; | 241 | const char *p; |
| 242 | # if BLOATY_HPET_IRQ_NUM_DETECTION | 242 | # if BLOATY_HPET_IRQ_NUM_DETECTION |
| 243 | long hpet_chan; | 243 | long hpet_chan; |
| 244 | 244 | ||
| @@ -423,7 +423,8 @@ static NOINLINE int process_timer_stats(void) | |||
| 423 | // 1, 2159 udisks-daemon hrtimer_start_range_ns (hrtimer_wakeup) | 423 | // 1, 2159 udisks-daemon hrtimer_start_range_ns (hrtimer_wakeup) |
| 424 | // 331 total events, 249.059 events/sec | 424 | // 331 total events, 249.059 events/sec |
| 425 | while (fgets(buf, sizeof(buf), fp)) { | 425 | while (fgets(buf, sizeof(buf), fp)) { |
| 426 | const char *count, *process, *func; | 426 | const char *process, *func; |
| 427 | char *count; | ||
| 427 | char *p; | 428 | char *p; |
| 428 | int idx; | 429 | int idx; |
| 429 | unsigned cnt; | 430 | unsigned cnt; |
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c index 2f7fa6618..777c27660 100644 --- a/scripts/kconfig/confdata.c +++ b/scripts/kconfig/confdata.c | |||
| @@ -348,7 +348,7 @@ int conf_write(const char *name) | |||
| 348 | dirname[0] = 0; | 348 | dirname[0] = 0; |
| 349 | if (name && name[0]) { | 349 | if (name && name[0]) { |
| 350 | struct stat st; | 350 | struct stat st; |
| 351 | char *slash; | 351 | const char *slash; |
| 352 | 352 | ||
| 353 | if (!stat(name, &st) && S_ISDIR(st.st_mode)) { | 353 | if (!stat(name, &st) && S_ISDIR(st.st_mode)) { |
| 354 | strcpy(dirname, name); | 354 | strcpy(dirname, name); |
diff --git a/shell/ash.c b/shell/ash.c index 7647a42cf..bf73d6bc2 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
| @@ -6939,7 +6939,7 @@ tryexec_applet(int applet_no, int noexec, const char *cmd, char **argv, char **e | |||
| 6939 | #endif | 6939 | #endif |
| 6940 | 6940 | ||
| 6941 | #if ENABLE_PLATFORM_MINGW32 | 6941 | #if ENABLE_PLATFORM_MINGW32 |
| 6942 | static struct builtincmd *find_builtin(const char *name); | 6942 | static const struct builtincmd *find_builtin(const char *name); |
| 6943 | static void | 6943 | static void |
| 6944 | tryexec(const char *cmd, const char *path, int noexec, char **argv, char **envp) | 6944 | tryexec(const char *cmd, const char *path, int noexec, char **argv, char **envp) |
| 6945 | { | 6945 | { |
| @@ -9663,7 +9663,7 @@ test_exec(/*const char *fullname,*/ struct stat *statb) | |||
| 9663 | } | 9663 | } |
| 9664 | 9664 | ||
| 9665 | /* Circular dep: find_command->find_builtin->builtintab[]->hashcmd->find_command */ | 9665 | /* Circular dep: find_command->find_builtin->builtintab[]->hashcmd->find_command */ |
| 9666 | static struct builtincmd *find_builtin(const char *name); | 9666 | static const struct builtincmd *find_builtin(const char *name); |
| 9667 | #if ENABLE_ASH_BASH_NOT_FOUND_HOOK | 9667 | #if ENABLE_ASH_BASH_NOT_FOUND_HOOK |
| 9668 | static int evalfun(struct funcnode *func, int argc, char **argv, int flags); | 9668 | static int evalfun(struct funcnode *func, int argc, char **argv, int flags); |
| 9669 | #endif | 9669 | #endif |
| @@ -9684,7 +9684,7 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) | |||
| 9684 | struct stat statb; | 9684 | struct stat statb; |
| 9685 | int e; | 9685 | int e; |
| 9686 | int updatetbl; | 9686 | int updatetbl; |
| 9687 | struct builtincmd *bcmd; | 9687 | const struct builtincmd *bcmd; |
| 9688 | int len; | 9688 | int len; |
| 9689 | 9689 | ||
| 9690 | #if !ENABLE_PLATFORM_MINGW32 | 9690 | #if !ENABLE_PLATFORM_MINGW32 |
| @@ -10303,9 +10303,7 @@ static const uint8_t nodesize[N_NUMBER] ALIGN1 = { | |||
| 10303 | #endif | 10303 | #endif |
| 10304 | [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)), | 10304 | [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)), |
| 10305 | [NFROM ] = SHELL_ALIGN(sizeof(struct nfile)), | 10305 | [NFROM ] = SHELL_ALIGN(sizeof(struct nfile)), |
| 10306 | #if ENABLE_PLATFORM_MINGW32 | ||
| 10307 | [NFROMSTR ] = SHELL_ALIGN(sizeof(struct nfile)), | 10306 | [NFROMSTR ] = SHELL_ALIGN(sizeof(struct nfile)), |
| 10308 | #endif | ||
| 10309 | [NFROMTO ] = SHELL_ALIGN(sizeof(struct nfile)), | 10307 | [NFROMTO ] = SHELL_ALIGN(sizeof(struct nfile)), |
| 10310 | [NAPPEND ] = SHELL_ALIGN(sizeof(struct nfile)), | 10308 | [NAPPEND ] = SHELL_ALIGN(sizeof(struct nfile)), |
| 10311 | [NTOFD ] = SHELL_ALIGN(sizeof(struct ndup)), | 10309 | [NTOFD ] = SHELL_ALIGN(sizeof(struct ndup)), |
| @@ -12074,10 +12072,10 @@ static const struct builtincmd builtintab[] = { | |||
| 12074 | /* | 12072 | /* |
| 12075 | * Search the table of builtin commands. | 12073 | * Search the table of builtin commands. |
| 12076 | */ | 12074 | */ |
| 12077 | static struct builtincmd * | 12075 | static const struct builtincmd * |
| 12078 | find_builtin(const char *name) | 12076 | find_builtin(const char *name) |
| 12079 | { | 12077 | { |
| 12080 | struct builtincmd *bp; | 12078 | const struct builtincmd *bp; |
| 12081 | 12079 | ||
| 12082 | bp = bsearch( | 12080 | bp = bsearch( |
| 12083 | name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]), | 12081 | name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]), |
| @@ -12093,11 +12091,7 @@ ash_command_name(int i) | |||
| 12093 | int n; | 12091 | int n; |
| 12094 | 12092 | ||
| 12095 | if (/*i >= 0 &&*/ i < ARRAY_SIZE(builtintab)) | 12093 | if (/*i >= 0 &&*/ i < ARRAY_SIZE(builtintab)) |
| 12096 | #if ENABLE_PLATFORM_MINGW32 | ||
| 12097 | return builtintab[i].name; | 12094 | return builtintab[i].name; |
| 12098 | #else | ||
| 12099 | return builtintab[i].name + 1; | ||
| 12100 | #endif | ||
| 12101 | i -= ARRAY_SIZE(builtintab); | 12095 | i -= ARRAY_SIZE(builtintab); |
| 12102 | 12096 | ||
| 12103 | for (n = 0; n < CMDTABLESIZE; n++) { | 12097 | for (n = 0; n < CMDTABLESIZE; n++) { |
| @@ -14316,12 +14310,12 @@ decode_dollar_squote(void) | |||
| 14316 | { | 14310 | { |
| 14317 | static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567"; | 14311 | static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567"; |
| 14318 | int c, cnt; | 14312 | int c, cnt; |
| 14319 | char *p; | ||
| 14320 | char buf[4]; | 14313 | char buf[4]; |
| 14321 | 14314 | ||
| 14322 | c = pgetc(); | 14315 | c = pgetc(); |
| 14323 | p = strchr(C_escapes, c); | 14316 | if (strchr(C_escapes, c)) { |
| 14324 | if (p) { | 14317 | char *p; |
| 14318 | |||
| 14325 | buf[0] = c; | 14319 | buf[0] = c; |
| 14326 | p = buf; | 14320 | p = buf; |
| 14327 | cnt = 3; | 14321 | cnt = 3; |
| @@ -15815,11 +15809,7 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
| 15815 | "------------------\n"); | 15809 | "------------------\n"); |
| 15816 | for (col = 0, i = 0; i < ARRAY_SIZE(builtintab); i++) { | 15810 | for (col = 0, i = 0; i < ARRAY_SIZE(builtintab); i++) { |
| 15817 | col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), | 15811 | col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), |
| 15818 | #if ENABLE_PLATFORM_MINGW32 | ||
| 15819 | builtintab[i].name); | 15812 | builtintab[i].name); |
| 15820 | #else | ||
| 15821 | builtintab[i].name + 1); | ||
| 15822 | #endif | ||
| 15823 | if (col > 60) { | 15813 | if (col > 60) { |
| 15824 | out1fmt("\n"); | 15814 | out1fmt("\n"); |
| 15825 | col = 0; | 15815 | col = 0; |
diff --git a/util-linux/fdisk.c b/util-linux/fdisk.c index 6611d3954..5ab350c74 100644 --- a/util-linux/fdisk.c +++ b/util-linux/fdisk.c | |||
| @@ -93,7 +93,7 @@ | |||
| 93 | 93 | ||
| 94 | //usage:#define fdisk_trivial_usage | 94 | //usage:#define fdisk_trivial_usage |
| 95 | //usage: "[-ul" IF_FEATURE_FDISK_BLKSIZE("s") "] " | 95 | //usage: "[-ul" IF_FEATURE_FDISK_BLKSIZE("s") "] " |
| 96 | //usage: "[-C CYLINDERS] [-H HEADS] [-S SECTORS] [-b SSZ] DISK" | 96 | //usage: "[-C CYLINDERS] [-H HEADS] [-S SECTORS] [-b SSZ] [-t PARTTYPE] DISK" |
| 97 | //usage:#define fdisk_full_usage "\n\n" | 97 | //usage:#define fdisk_full_usage "\n\n" |
| 98 | //usage: "Change partition table\n" | 98 | //usage: "Change partition table\n" |
| 99 | //usage: "\n -u Start and End are in sectors (instead of cylinders)" | 99 | //usage: "\n -u Start and End are in sectors (instead of cylinders)" |
| @@ -104,6 +104,7 @@ | |||
| 104 | //but in fact, util-linux 2.41.1 shows the size in KILOBYTES! | 104 | //but in fact, util-linux 2.41.1 shows the size in KILOBYTES! |
| 105 | //usage: ) | 105 | //usage: ) |
| 106 | //usage: "\n -b 2048 (for certain MO disks) use 2048-byte sectors" | 106 | //usage: "\n -b 2048 (for certain MO disks) use 2048-byte sectors" |
| 107 | //usage: "\n -T PARTTYPE Force 'dos' partition if 'gpt' also present" | ||
| 107 | //usage: "\n -C CYLINDERS Set number of cylinders/heads/sectors" | 108 | //usage: "\n -C CYLINDERS Set number of cylinders/heads/sectors" |
| 108 | //usage: "\n -H HEADS Typically 255" | 109 | //usage: "\n -H HEADS Typically 255" |
| 109 | //usage: "\n -S SECTORS Typically 63" | 110 | //usage: "\n -S SECTORS Typically 63" |
| @@ -186,7 +187,8 @@ enum { | |||
| 186 | OPT_l = 1 << 3, | 187 | OPT_l = 1 << 3, |
| 187 | OPT_S = 1 << 4, | 188 | OPT_S = 1 << 4, |
| 188 | OPT_u = 1 << 5, | 189 | OPT_u = 1 << 5, |
| 189 | OPT_s = (1 << 6) * ENABLE_FEATURE_FDISK_BLKSIZE, | 190 | OPT_t = 1 << 6, |
| 191 | OPT_s = (1 << 7) * ENABLE_FEATURE_FDISK_BLKSIZE, | ||
| 190 | }; | 192 | }; |
| 191 | #define USER_SET_SECTOR_SIZE (option_mask32 & OPT_b) | 193 | #define USER_SET_SECTOR_SIZE (option_mask32 & OPT_b) |
| 192 | #define NOWARN_OPT_ls (!ENABLE_FEATURE_FDISK_WRITABLE || (option_mask32 & (OPT_l|OPT_s))) | 194 | #define NOWARN_OPT_ls (!ENABLE_FEATURE_FDISK_WRITABLE || (option_mask32 & (OPT_l|OPT_s))) |
| @@ -464,6 +466,7 @@ struct globals { | |||
| 464 | sector_t extended_offset; /* offset of link pointers */ | 466 | sector_t extended_offset; /* offset of link pointers */ |
| 465 | sector_t total_number_of_sectors; | 467 | sector_t total_number_of_sectors; |
| 466 | 468 | ||
| 469 | const char *opt_t; | ||
| 467 | #if ENABLE_FEATURE_GPT_LABEL | 470 | #if ENABLE_FEATURE_GPT_LABEL |
| 468 | struct gpt_header *gpt_hdr; | 471 | struct gpt_header *gpt_hdr; |
| 469 | char *gpt_part_array; | 472 | char *gpt_part_array; |
| @@ -1209,7 +1212,7 @@ static void | |||
| 1209 | warn_cylinders(void) | 1212 | warn_cylinders(void) |
| 1210 | { | 1213 | { |
| 1211 | if (LABEL_IS_DOS && g_cylinders > 1024 && !NOWARN_OPT_ls) | 1214 | if (LABEL_IS_DOS && g_cylinders > 1024 && !NOWARN_OPT_ls) |
| 1212 | printf("\n" | 1215 | printf( |
| 1213 | "The number of cylinders for this disk is set to %u.\n" | 1216 | "The number of cylinders for this disk is set to %u.\n" |
| 1214 | "There is nothing wrong with that, but this is larger than 1024,\n" | 1217 | "There is nothing wrong with that, but this is larger than 1024,\n" |
| 1215 | "and could in certain setups cause problems with:\n" | 1218 | "and could in certain setups cause problems with:\n" |
| @@ -3064,10 +3067,14 @@ int fdisk_main(int argc UNUSED_PARAM, char **argv) | |||
| 3064 | 3067 | ||
| 3065 | close_dev_fd(); /* needed: fd 3 must not stay closed */ | 3068 | close_dev_fd(); /* needed: fd 3 must not stay closed */ |
| 3066 | 3069 | ||
| 3067 | opt = getopt32(argv, "^" "b:+C:+H:+lS:+u"IF_FEATURE_FDISK_BLKSIZE("s")"\0" | 3070 | //G.opt_t = NULL; |
| 3071 | opt = getopt32(argv, "^" "b:+C:+H:+lS:+ut:"IF_FEATURE_FDISK_BLKSIZE("s")"\0" | ||
| 3068 | /* among -s and -l, the last one takes precedence */ | 3072 | /* among -s and -l, the last one takes precedence */ |
| 3069 | IF_FEATURE_FDISK_BLKSIZE("s-l:l-s"), | 3073 | IF_FEATURE_FDISK_BLKSIZE("s-l:l-s"), |
| 3070 | §or_size, &user_cylinders, &user_heads, &user_sectors); | 3074 | §or_size, // -b |
| 3075 | &user_cylinders, &user_heads, &user_sectors, //-CHS | ||
| 3076 | &G.opt_t | ||
| 3077 | ); | ||
| 3071 | argv += optind; | 3078 | argv += optind; |
| 3072 | 3079 | ||
| 3073 | #if ENABLE_FEATURE_FDISK_BLKSIZE | 3080 | #if ENABLE_FEATURE_FDISK_BLKSIZE |
diff --git a/util-linux/fdisk_gpt.c b/util-linux/fdisk_gpt.c index c085af79c..696b4937c 100644 --- a/util-linux/fdisk_gpt.c +++ b/util-linux/fdisk_gpt.c | |||
| @@ -150,12 +150,13 @@ check_gpt_label(void) | |||
| 150 | struct pte pe; | 150 | struct pte pe; |
| 151 | uint32_t crc; | 151 | uint32_t crc; |
| 152 | 152 | ||
| 153 | current_label_type = LABEL_DOS; | ||
| 154 | |||
| 153 | /* LBA 0 contains the legacy MBR */ | 155 | /* LBA 0 contains the legacy MBR */ |
| 154 | 156 | ||
| 155 | if (!valid_part_table_flag(MBRbuffer) | 157 | if (!valid_part_table_flag(MBRbuffer) |
| 156 | || first->sys_ind != LEGACY_GPT_TYPE | 158 | || first->sys_ind != LEGACY_GPT_TYPE |
| 157 | ) { | 159 | ) { |
| 158 | current_label_type = LABEL_DOS; | ||
| 159 | return 0; | 160 | return 0; |
| 160 | } | 161 | } |
| 161 | 162 | ||
| @@ -165,7 +166,6 @@ check_gpt_label(void) | |||
| 165 | G.gpt_hdr = (void *)pe.sectorbuffer; | 166 | G.gpt_hdr = (void *)pe.sectorbuffer; |
| 166 | 167 | ||
| 167 | if (G.gpt_hdr->magic != SWAP_LE64(GPT_MAGIC)) { | 168 | if (G.gpt_hdr->magic != SWAP_LE64(GPT_MAGIC)) { |
| 168 | current_label_type = LABEL_DOS; | ||
| 169 | return 0; | 169 | return 0; |
| 170 | } | 170 | } |
| 171 | 171 | ||
| @@ -188,7 +188,6 @@ check_gpt_label(void) | |||
| 188 | || SWAP_LE32(G.gpt_hdr->hdr_size) > sector_size | 188 | || SWAP_LE32(G.gpt_hdr->hdr_size) > sector_size |
| 189 | ) { | 189 | ) { |
| 190 | puts("\nwarning: can't parse GPT disklabel"); | 190 | puts("\nwarning: can't parse GPT disklabel"); |
| 191 | current_label_type = LABEL_DOS; | ||
| 192 | return 0; | 191 | return 0; |
| 193 | } | 192 | } |
| 194 | 193 | ||
| @@ -204,10 +203,15 @@ check_gpt_label(void) | |||
| 204 | puts("\nwarning: GPT array CRC is invalid"); | 203 | puts("\nwarning: GPT array CRC is invalid"); |
| 205 | } | 204 | } |
| 206 | 205 | ||
| 207 | puts("Found valid GPT with protective MBR; using GPT"); | 206 | fputs_stdout("Found valid GPT with protective MBR; "); |
| 208 | 207 | ||
| 209 | current_label_type = LABEL_GPT; | 208 | if (!G.opt_t || strcasecmp(G.opt_t, "gpt") == 0) { |
| 210 | return 1; | 209 | puts("using GPT (-t dos to override)"); |
| 210 | current_label_type = LABEL_GPT; | ||
| 211 | return 1; | ||
| 212 | } | ||
| 213 | puts("NOT using it (-t specified)"); | ||
| 214 | return 0; | ||
| 211 | } | 215 | } |
| 212 | 216 | ||
| 213 | #endif /* GPT_LABEL */ | 217 | #endif /* GPT_LABEL */ |
