diff --git a/manpages/dropbear.8 b/manpages/dropbear.8 index 72be0aef..10a0aba4 100644 --- a/manpages/dropbear.8 +++ b/manpages/dropbear.8 @@ -167,6 +167,14 @@ can be set in authorized_keys. Wildcard character ('*') may be used in port specification for matching any port. Hosts must be literal domain names or IP addresses. +.TP +.B permitlisten=\fR"\fIhost:port\fR" +Restrict remote port forwarding so that connection is allowed only from the +specified host and port. Multiple permitlisten options separated by commas +can be set in authorized_keys. Hosts must be literal domain names or +IP addresses or special addresses like 0.0.0.0. Port can be 0 to allow the +listen port to be dynamically allocated on the server. + .TP .B command=\fR"\fIforced_command\fR" Disregard the command provided by the user and always run \fIforced_command\fR. diff --git a/src/auth.h b/src/auth.h index 0e854fbb..16e75be2 100644 --- a/src/auth.h +++ b/src/auth.h @@ -47,6 +47,7 @@ int svr_pubkey_allows_tcpfwd(void); int svr_pubkey_allows_x11fwd(void); int svr_pubkey_allows_pty(void); int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port); +int svr_pubkey_allows_remote_tcpfwd(const char *host, unsigned int port); void svr_pubkey_set_forced_command(struct ChanSess *chansess); void svr_pubkey_options_cleanup(void); int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename); @@ -58,6 +59,8 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena #define svr_pubkey_allows_pty() 1 static inline int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { (void)host; (void)port; return 1; } +static inline int svr_pubkey_allows_remote_tcpfwd(const char *host, unsigned int port) + { (void)host; (void)port; return 1; } static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) { } static inline void svr_pubkey_options_cleanup(void) { } @@ -147,7 +150,9 @@ struct PubKeyOptions { char * forced_command; /* "permitopen=" option */ m_list *permit_open_destinations; - + /* "permitlisten=" option */ + m_list *permit_listen_sources; + #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 int no_touch_required_flag; int verify_required_flag; diff --git a/src/svr-authpubkeyoptions.c b/src/svr-authpubkeyoptions.c index df9a7dfc..f4af6f28 100644 --- a/src/svr-authpubkeyoptions.c +++ b/src/svr-authpubkeyoptions.c @@ -89,7 +89,7 @@ int svr_pubkey_allows_pty() { return 1; } -/* Returns 1 if pubkey allows local tcp fowarding to the provided destination, +/* Returns 1 if pubkey allows local tcp forwarding to the provided destination, * 0 otherwise */ int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { if (ses.authstate.pubkey_options @@ -112,7 +112,28 @@ int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { return 1; } -/* Set chansession command to the one forced +/* Returns 1 if pubkey allows remote tcp forwarding from the provided source, + * 0 otherwise */ +int svr_pubkey_allows_remote_tcpfwd(const char *host, unsigned int port) { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->permit_listen_sources) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_listen_sources->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; + if (strcmp(entry->host, host) == 0 && entry->port == port) { + return 1; + } + + iter = iter->next; + } + + return 0; + } + + return 1; +} + +/* Set chansession command to the one forced * by any 'command' public key option. */ void svr_pubkey_set_forced_command(struct ChanSess *chansess) { if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->forced_command) { @@ -147,6 +168,16 @@ void svr_pubkey_options_cleanup() { } m_free(ses.authstate.pubkey_options->permit_open_destinations); } + if (ses.authstate.pubkey_options->permit_listen_sources) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_listen_sources->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); + m_free(entry->host); + m_free(entry); + iter = ses.authstate.pubkey_options->permit_listen_sources->first; + } + m_free(ses.authstate.pubkey_options->permit_listen_sources); + } m_free(ses.authstate.pubkey_options); } if (ses.authstate.pubkey_info) { @@ -288,6 +319,50 @@ int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filena } } + if (match_option(options_buf, "permitlisten=\"") == DROPBEAR_SUCCESS) { + int valid_option = 0; + const unsigned char* permitlisten_start = buf_getptr(options_buf, 0); + + if (!ses.authstate.pubkey_options->permit_listen_sources) { + ses.authstate.pubkey_options->permit_listen_sources = list_new(); + } + + while (options_buf->pos < options_buf->len) { + const char c = buf_getbyte(options_buf); + if (c == '"') { + char *spec = NULL; + char *portstring = NULL; + const int permitlisten_len = buf_getptr(options_buf, 0) - permitlisten_start; + struct PermitTCPFwdEntry *entry = + (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); + + list_append(ses.authstate.pubkey_options->permit_listen_sources, entry); + spec = m_malloc(permitlisten_len); + memcpy(spec, permitlisten_start, permitlisten_len - 1); + spec[permitlisten_len - 1] = '\0'; + if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS) + && entry->host && portstring) { + if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS) { + valid_option = 1; + TRACE(("remote port forwarding allowed from host '%s' and port '%u'", + entry->host, entry->port)); + } + } + + m_free(spec); + m_free(portstring); + break; + } + } + + if (valid_option) { + goto next_option; + } else { + dropbear_log(LOG_WARNING, "Badly formatted permitlisten= authorized_keys option"); + goto bad_option; + } + } + if (match_option(options_buf, "no-touch-required") == DROPBEAR_SUCCESS) { #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 dropbear_log(LOG_WARNING, "No user presence check required for U2F/FIDO key."); diff --git a/src/svr-tcpfwd.c b/src/svr-tcpfwd.c index e6902ea9..d6a263f4 100644 --- a/src/svr-tcpfwd.c +++ b/src/svr-tcpfwd.c @@ -200,6 +200,11 @@ static int svr_remotetcpreq(int *allocated_listen_port) { } } + if (!svr_pubkey_allows_remote_tcpfwd(request_addr, port)) { + TRACE(("remote tcp forwarding not permitted from requested source")); + goto out; + } + tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener)); tcpinfo->sendaddr = NULL; tcpinfo->sendport = 0;