From 42420cae469ca3552993f9f05000f0e5053e547e Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Tue, 30 Sep 2014 00:56:57 +0100 Subject: [PATCH] Add experimental SOCKS5 support for the clients. --- CMakeLists.txt | 5 + ChangeLog.txt | 3 + client/client_shared.c | 195 +++++++++++++++++++++++++++++++++++++++ client/client_shared.h | 6 ++ client/pub_client.c | 10 +- client/sub_client.c | 8 ++ config.mk | 8 ++ lib/CMakeLists.txt | 1 + lib/Makefile | 4 + lib/linker.version | 1 + lib/mosquitto.c | 28 +++++- lib/mosquitto.h | 26 +++++- lib/mosquitto_internal.h | 18 +++- man/mosquitto_pub.1.xml | 21 +++++ man/mosquitto_sub.1.xml | 20 ++++ 15 files changed, 347 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48665fd6fe..fca8bb2b84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,11 @@ else (${WITH_TLS} STREQUAL ON) set (OPENSSL_INCLUDE_DIR "") endif (${WITH_TLS} STREQUAL ON) +option(WITH_SOCKS "Include SOCKS5 support?" ON) +if (${WITH_SOCKS} STREQUAL ON) + add_definitions("-DWITH_SOCKS") +endif (${WITH_SOCKS} STREQUAL ON) + option(WITH_SRV "Include SRV lookup support?" ON) # ======================================== diff --git a/ChangeLog.txt b/ChangeLog.txt index 9ec9e07cff..b16d859596 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -15,6 +15,7 @@ Important changes: connected. - New use_username_as_clientid option on the broker, for preventing hijacking of a client id. +- The client library and clients now have experimental SOCKS5 support. Broker: @@ -46,8 +47,10 @@ Broker: Clients: - Both clients can now load default configuration options from a file. - Add -1 (oneshot) option to mosquitto_sub. +- Add --proxy SOCKS5 support for both clients. Client library: +- Add experimental SOCKS5 support. - mosquitto_loop_forever now quits after a fatal error, rather than blindly retrying. diff --git a/client/client_shared.c b/client/client_shared.c index be22e3ca87..e8912c08d9 100644 --- a/client/client_shared.c +++ b/client/client_shared.c @@ -31,6 +31,7 @@ and the Eclipse Distribution License is available at #include #include "client_shared.h" +static int mosquitto__parse_socks_url(struct mosq_config *cfg, char *url); static int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, char *argv[]); void init_config(struct mosq_config *cfg) @@ -359,6 +360,18 @@ int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, c }else{ cfg->pub_mode = MSGMODE_NULL; } +#ifdef WITH_SOCKS + }else if(!strcmp(argv[i], "--proxy")){ + if(i==argc-1){ + fprintf(stderr, "Error: --proxy argument given but no proxy url specified.\n\n"); + return 1; + }else{ + if(mosquitto__parse_socks_url(cfg, argv[i+1])){ + return 1; + } + i++; + } +#endif #ifdef WITH_TLS_PSK }else if(!strcmp(argv[i], "--psk")){ if(i==argc-1){ @@ -544,6 +557,8 @@ int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, c int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) { + int rc; + if(cfg->will_topic && mosquitto_will_set(mosq, cfg->will_topic, cfg->will_payloadlen, cfg->will_payload, cfg->will_qos, cfg->will_retain)){ @@ -584,6 +599,15 @@ int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) } #endif mosquitto_max_inflight_messages_set(mosq, cfg->max_inflight); +#ifdef WITH_SOCKS + if(cfg->socks5_host){ + rc = mosquitto_socks5_set(mosq, cfg->socks5_host, cfg->socks5_port, cfg->socks5_username, cfg->socks5_password); + if(rc){ + mosquitto_lib_cleanup(); + return rc; + } + } +#endif return MOSQ_ERR_SUCCESS; } @@ -648,3 +672,174 @@ int client_connect(struct mosquitto *mosq, struct mosq_config *cfg) } return MOSQ_ERR_SUCCESS; } + +#ifdef WITH_SOCKS +/* Convert %25 -> %, %3a, %3A -> :, %40 -> @ */ +static int mosquitto__urldecode(char *str) +{ + int i, j; + int len; + if(!str) return 0; + + if(!strchr(str, '%')) return 0; + + len = strlen(str); + for(i=0; i= len){ + return 1; + } + if(str[i+1] == '2' && str[i+2] == '5'){ + str[i] = '%'; + len -= 2; + for(j=i+1; j start){ + len = i-start; + if(host){ + port = malloc(len + 1); + memcpy(port, &(str[start]), len); + port[len] = '\0'; + }else if(username_or_host){ + if(have_auth){ + host = malloc(len + 1); + memcpy(host, &(str[start]), len); + host[len] = '\0'; + }else{ + host = username_or_host; + username_or_host = NULL; + port = malloc(len + 1); + memcpy(port, &(str[start]), len); + port[len] = '\0'; + } + }else{ + host = malloc(len + 1); + memcpy(host, &(str[start]), len); + host[len] = '\0'; + } + } + + if(!host){ + fprintf(stderr, "Error: Invalid proxy.\n"); + goto cleanup; + } + + if(mosquitto__urldecode(username)){ + goto cleanup; + } + if(mosquitto__urldecode(password)){ + goto cleanup; + } + if(port){ + port_int = atoi(port); + if(port_int < 1 || port_int > 65535){ + fprintf(stderr, "Error: Invalid proxy port %d\n", port_int); + goto cleanup; + } + free(port); + }else{ + port_int = 1080; + } + + cfg->socks5_username = username; + cfg->socks5_password = password; + cfg->socks5_host = host; + cfg->socks5_port = port_int; + + return 0; +cleanup: + if(username_or_host) free(username_or_host); + if(username) free(username); + if(password) free(password); + if(host) free(host); + if(port) free(port); + return 1; +} + +#endif diff --git a/client/client_shared.h b/client/client_shared.h index 1c6beabece..5fd994aa5e 100644 --- a/client/client_shared.h +++ b/client/client_shared.h @@ -78,6 +78,12 @@ struct mosq_config { bool verbose; /* sub */ bool eol; /* sub */ bool oneshot; /* sub */ +#ifdef WITH_SOCKS + char *socks5_host; + int socks5_port; + char *socks5_username; + char *socks5_password; +#endif }; int client_config_load(struct mosq_config *config, int pub_or_sub, int argc, char *argv[]); diff --git a/client/pub_client.c b/client/pub_client.c index 0a6183c35e..b20cb780ff 100644 --- a/client/pub_client.c +++ b/client/pub_client.c @@ -217,6 +217,9 @@ void print_usage(void) #ifdef WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif +#endif +#ifdef WITH_SOCKS + printf(" [--proxy socks-url]\n"); #endif printf(" mosquitto_pub --help\n\n"); printf(" -A : bind the outgoing socket to this host/ip address. Use to control which interface\n"); @@ -263,10 +266,15 @@ void print_usage(void) printf(" hostname. Using this option means that you cannot be sure that the\n"); printf(" remote host is the server you wish to connect to and so is insecure.\n"); printf(" Do not use this option in a production environment.\n"); -#ifdef WITH_TLS_PSK +# ifdef WITH_TLS_PSK printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); +# endif #endif +#ifdef WITH_SOCKS + printf(" --proxy : SOCKS5 proxy URL of the form:\n"); + printf(" socks5h://[username[:password]@]hostname[:port]\n"); + printf(" Only \"none\" and \"username\" authentication is supported.\n"); #endif printf("\nSee http://mosquitto.org/ for more information.\n\n"); } diff --git a/client/sub_client.c b/client/sub_client.c index 0ecd251d4c..1f3fc1a639 100644 --- a/client/sub_client.c +++ b/client/sub_client.c @@ -142,6 +142,9 @@ void print_usage(void) #ifdef WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif +#endif +#ifdef WITH_SOCKS + printf(" [--proxy socks-url]\n"); #endif printf(" mosquitto_sub --help\n\n"); printf(" -1 : disconnect and exit after receiving the first message.\n"); @@ -192,6 +195,11 @@ void print_usage(void) printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); #endif +#endif +#ifdef WITH_SOCKS + printf(" --proxy : SOCKS5 proxy URL of the form:\n"); + printf(" socks5h://[username[:password]@]hostname[:port]\n"); + printf(" Only \"none\" and \"username\" authentication is supported.\n"); #endif printf("\nSee http://mosquitto.org/ for more information.\n\n"); } diff --git a/config.mk b/config.mk index 10fa4215f4..890511b07b 100644 --- a/config.mk +++ b/config.mk @@ -70,6 +70,9 @@ WITH_EC:=yes # Build man page documentation by default. WITH_DOCS:=yes +# Build with client support for SOCK5 proxy. +WITH_SOCKS:=yes + # ============================================================================= # End of user configuration # ============================================================================= @@ -174,6 +177,11 @@ ifeq ($(WITH_THREADING),yes) LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_THREADING endif +ifeq ($(WITH_SOCKS),yes) + LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_SOCKS + CLIENT_CFLAGS:=$(CLIENT_CFLAGS) -DWITH_SOCKS +endif + ifeq ($(WITH_UUID),yes) ifeq ($(UNAME),Linux) BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_UUID diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a907183be7..32a04b4d03 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(libmosquitto SHARED read_handle_shared.c send_client_mosq.c send_mosq.c send_mosq.h + socks_mosq.c srv_mosq.c thread_mosq.c time_mosq.c diff --git a/lib/Makefile b/lib/Makefile index c923b2e07d..825fceadf7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -12,6 +12,7 @@ MOSQ_OBJS=mosquitto.o \ read_handle_shared.o \ send_mosq.o \ send_client_mosq.o \ + socks_mosq.o \ srv_mosq.o \ thread_mosq.o \ time_mosq.o \ @@ -76,6 +77,9 @@ send_mosq.o : send_mosq.c send_mosq.h send_client_mosq.o : send_client_mosq.c send_mosq.h ${CROSS_COMPILE}$(CC) $(LIB_CFLAGS) -c $< -o $@ +socks_mosq.o : socks_mosq.c + ${CROSS_COMPILE}$(CC) $(LIB_CFLAGS) -c $< -o $@ + srv_mosq.o : srv_mosq.c ${CROSS_COMPILE}$(CC) $(LIB_CFLAGS) -c $< -o $@ diff --git a/lib/linker.version b/lib/linker.version index 74030e957f..9f88d9582c 100644 --- a/lib/linker.version +++ b/lib/linker.version @@ -75,4 +75,5 @@ MOSQ_1.4 { mosquitto_threaded_set; mosquitto_pub_topic_check; mosquitto_sub_topic_check; + mosquitto_socks5_set; } MOSQ_1.3; diff --git a/lib/mosquitto.c b/lib/mosquitto.c index 1cd5a9e6c6..d7b51e40df 100644 --- a/lib/mosquitto.c +++ b/lib/mosquitto.c @@ -38,6 +38,7 @@ typedef int ssize_t; #include #include #include +#include #include #include #include @@ -461,7 +462,11 @@ static int _mosquitto_reconnect(struct mosquitto *mosq, bool blocking) if(!mosq->host || mosq->port <= 0) return MOSQ_ERR_INVAL; pthread_mutex_lock(&mosq->state_mutex); - mosq->state = mosq_cs_new; + if(mosq->socks5_host){ + mosq->state = mosq_cs_socks5_new; + }else{ + mosq->state = mosq_cs_new; + } pthread_mutex_unlock(&mosq->state_mutex); pthread_mutex_lock(&mosq->msgtime_mutex); @@ -497,12 +502,20 @@ static int _mosquitto_reconnect(struct mosquitto *mosq, bool blocking) _mosquitto_messages_reconnect_reset(mosq); - rc = _mosquitto_socket_connect(mosq, mosq->host, mosq->port, mosq->bind_address, blocking); + if(mosq->socks5_host){ + rc = _mosquitto_socket_connect(mosq, mosq->socks5_host, mosq->socks5_port, mosq->bind_address, blocking); + }else{ + rc = _mosquitto_socket_connect(mosq, mosq->host, mosq->port, mosq->bind_address, blocking); + } if(rc){ return rc; } - return _mosquitto_send_connect(mosq, mosq->keepalive, mosq->clean_session); + if(mosq->socks5_host){ + return mosquitto__socks5_send(mosq); + }else{ + return _mosquitto_send_connect(mosq, mosq->keepalive, mosq->clean_session); + } } int mosquitto_disconnect(struct mosquitto *mosq) @@ -951,6 +964,7 @@ int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets) case MOSQ_ERR_UNKNOWN: case MOSQ_ERR_ERRNO: case MOSQ_ERR_EAI: + case MOSQ_ERR_PROXY: return rc; } if(errno == EPROTO){ @@ -1075,7 +1089,11 @@ int mosquitto_loop_read(struct mosquitto *mosq, int max_packets) * have QoS > 0. We should try to deal with that many in this loop in order * to keep up. */ for(i=0; isocks5_host){ + rc = mosquitto__socks5_read(mosq); + }else{ + rc = _mosquitto_packet_read(mosq); + } if(rc || errno == EAGAIN || errno == COMPAT_EWOULDBLOCK){ return _mosquitto_loop_rc_handle(mosq, rc); } @@ -1212,6 +1230,8 @@ const char *mosquitto_strerror(int mosq_errno) return "Unknown error."; case MOSQ_ERR_ERRNO: return strerror(errno); + case MOSQ_ERR_PROXY: + return "Proxy error."; default: return "Unknown error."; } diff --git a/lib/mosquitto.h b/lib/mosquitto.h index 2819e65faf..91ffaae98a 100644 --- a/lib/mosquitto.h +++ b/lib/mosquitto.h @@ -78,7 +78,8 @@ enum mosq_err_t { MOSQ_ERR_ACL_DENIED = 12, MOSQ_ERR_UNKNOWN = 13, MOSQ_ERR_ERRNO = 14, - MOSQ_ERR_EAI = 15 + MOSQ_ERR_EAI = 15, + MOSQ_ERR_PROXY = 16 }; /* MQTT specification restricts client ids to a maximum of 23 characters */ @@ -1294,6 +1295,29 @@ libmosq_EXPORT void mosquitto_message_retry_set(struct mosquitto *mosq, unsigned */ libmosq_EXPORT void mosquitto_user_data_set(struct mosquitto *mosq, void *obj); +/* ============================================================================= + * + * Section: SOCKS5 proxy functions + * + * ============================================================================= + */ + +/* + * Function: mosquitto_socks5_set + * + * Configure the client to use a SOCKS5 proxy when connecting. Must be called + * before connecting. "None" and "username/password" authentication is + * supported. + * + * Parameters: + * mosq - a valid mosquitto instance. + * host - the SOCKS5 proxy host to connect to. + * port - the SOCKS5 proxy port to use. + * username - if not NULL, use this username when authenticating with the proxy. + * password - if not NULL and username is not NULL, use this password when + * authenticating with the proxy. + */ +libmosq_EXPORT int mosquitto_socks5_set(struct mosquitto *mosq, const char *host, int port, const char *username, const char *password); /* ============================================================================= * diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h index 531c9a8151..5b01bfc0de 100644 --- a/lib/mosquitto_internal.h +++ b/lib/mosquitto_internal.h @@ -86,7 +86,14 @@ enum mosquitto_client_state { mosq_cs_connect_pending = 4, mosq_cs_connect_srv = 5, mosq_cs_disconnect_ws = 6, - mosq_cs_disconnected = 7 + mosq_cs_disconnected = 7, + mosq_cs_socks5_new = 8, + mosq_cs_socks5_start = 9, + mosq_cs_socks5_request = 10, + mosq_cs_socks5_reply = 11, + mosq_cs_socks5_auth_ok = 12, + mosq_cs_socks5_userpass_reply = 13, + mosq_cs_socks5_send_userpass = 14, }; enum _mosquitto_protocol { @@ -199,6 +206,12 @@ struct mosquitto { struct libwebsocket *wsi; # endif #else +# ifdef WITH_SOCKS + char *socks5_host; + int socks5_port; + char *socks5_username; + char *socks5_password; +# endif void *userdata; bool in_callback; unsigned int message_retry; @@ -239,6 +252,9 @@ struct mosquitto { # ifdef WITH_BRIDGE UT_hash_handle hh_bridge; # endif +# ifdef WITH_WEBSOCKETS + UT_hash_handle hh_websockets; +# endif #endif }; diff --git a/man/mosquitto_pub.1.xml b/man/mosquitto_pub.1.xml index f7305472e3..090f717c7b 100644 --- a/man/mosquitto_pub.1.xml +++ b/man/mosquitto_pub.1.xml @@ -63,6 +63,8 @@ version + socks-url + message-topic @@ -252,6 +254,25 @@ MQTT v3.1. See also the option. + + + + Specify a SOCKS5 proxy to connect through. "None" and + "username" authentication types are supported. The + must be of the form + . + The protocol prefix means that + hostnames are resolved by the proxy. The symbols %25, + %3A and %40 are URL decoded into %, : and @ + respectively, if present in the username or + password. + More SOCKS versions may be available in the future, + depending on demand, and will use different protocol + prefixes as described in + curl + 1 . + + diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml index 4fdeebd457..c23be4ec2e 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -61,6 +61,7 @@ filter-out message-topic + socks-url mosquitto_sub @@ -269,6 +270,25 @@ MQTT v3.1. See also the option. + + + + Specify a SOCKS5 proxy to connect through. "None" and + "username" authentication types are supported. The + must be of the form + . + The protocol prefix means that + hostnames are resolved by the proxy. The symbols %25, + %3A and %40 are URL decoded into %, : and @ + respectively, if present in the username or + password. + More SOCKS versions may be available in the future, + depending on demand, and will use different protocol + prefixes as described in + curl + 1 . + +