Add experimental SOCKS5 support for the clients.

This commit is contained in:
Roger A. Light 2014-09-30 00:56:57 +01:00
parent e9c18f8347
commit 42420cae46
15 changed files with 347 additions and 7 deletions

View File

@ -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)
# ========================================

View File

@ -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.

View File

@ -31,6 +31,7 @@ Contributors:
#include <mosquitto.h>
#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 @@ unknown_option:
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; i++){
if(str[i] == '%'){
if(i+2 >= len){
return 1;
}
if(str[i+1] == '2' && str[i+2] == '5'){
str[i] = '%';
len -= 2;
for(j=i+1; j<len; j++){
str[j] = str[j+2];
}
str[j] = '\0';
}else if(str[i+1] == '3' && (str[i+2] == 'A' || str[i+2] == 'a')){
str[i] = ':';
len -= 2;
for(j=i+1; j<len; j++){
str[j] = str[j+2];
}
str[j] = '\0';
}else if(str[i+1] == '4' && str[i+2] == '0'){
str[i] = ':';
len -= 2;
for(j=i+1; j<len; j++){
str[j] = str[j+2];
}
str[j] = '\0';
}else{
return 1;
}
}
}
return 0;
}
static int mosquitto__parse_socks_url(struct mosq_config *cfg, char *url)
{
char *str;
int i;
char *username = NULL, *password = NULL, *host = NULL, *port = NULL;
char *username_or_host = NULL;
int start;
int len;
bool have_auth = false;
int port_int;
if(!strncmp(url, "socks5h://", strlen("socks5h://"))){
str = url + strlen("socks5h://");
}else{
fprintf(stderr, "Error: Unsupported proxy protocol: %s\n", url);
return 1;
}
start = 0;
for(i=0; i<strlen(str); i++){
if(str[i] == ':'){
if(i == start){
goto cleanup;
}
if(have_auth){
len = i-start;
host = malloc(len + 1);
memcpy(host, &(str[start]), len);
host[len] = '\0';
start = i+1;
}else if(!username_or_host){
len = i-start;
username_or_host = malloc(len + 1);
memcpy(username_or_host, &(str[start]), len);
username_or_host[len] = '\0';
start = i+1;
}
}else if(str[i] == '@'){
if(i == start){
goto cleanup;
}
have_auth = true;
if(username_or_host){
username = username_or_host;
username_or_host = NULL;
len = i-start;
password = malloc(len + 1);
memcpy(password, &(str[start]), len);
password[len] = '\0';
start = i+1;
}else{
len = i-start;
username = malloc(len + 1);
memcpy(username, &(str[start]), len);
username[len] = '\0';
start = i+1;
}
}
}
/* Deal with remainder */
if(i > 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

View File

@ -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[]);

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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

View File

@ -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

View File

@ -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 $@

View File

@ -75,4 +75,5 @@ MOSQ_1.4 {
mosquitto_threaded_set;
mosquitto_pub_topic_check;
mosquitto_sub_topic_check;
mosquitto_socks5_set;
} MOSQ_1.3;

View File

@ -38,6 +38,7 @@ typedef int ssize_t;
#include <net_mosq.h>
#include <read_handle.h>
#include <send_mosq.h>
#include <socks_mosq.h>
#include <time_mosq.h>
#include <tls_mosq.h>
#include <util_mosq.h>
@ -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; i<max_packets; i++){
rc = _mosquitto_packet_read(mosq);
if(mosq->socks5_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.";
}

View File

@ -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);
/* =============================================================================
*

View File

@ -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
};

View File

@ -63,6 +63,8 @@
<arg><option>--tls-version</option> <replaceable>version</replaceable></arg>
</arg>
</group>
<arg><option>--proxy</option> <replaceable>socks-url</replaceable></arg>
<arg choice='plain'><option>-t</option> <replaceable>message-topic</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
@ -252,6 +254,25 @@
MQTT v3.1. See also the <option>--username</option> option.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--proxy</option></term>
<listitem>
<para>Specify a SOCKS5 proxy to connect through. "None" and
"username" authentication types are supported. The
<option>socks-url</option> must be of the form
<option>socks5h://[username[:password]@]host[:port]</option>.
The protocol prefix <option>socks5h</option> 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.</para>
<para>More SOCKS versions may be available in the future,
depending on demand, and will use different protocol
prefixes as described in <citerefentry>
<refentrytitle>curl</refentrytitle>
<manvolnum>1</manvolnum> </citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--psk</option></term>
<listitem>

View File

@ -61,6 +61,7 @@
</group>
<arg choice='opt' rep='repeat'><option>-T</option> <replaceable>filter-out</replaceable></arg>
<arg choice='plain' rep='repeat'><option>-t</option> <replaceable>message-topic</replaceable></arg>
<arg><option>--proxy</option> <replaceable>socks-url</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>mosquitto_sub</command>
@ -269,6 +270,25 @@
MQTT v3.1. See also the <option>--username</option> option.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--proxy</option></term>
<listitem>
<para>Specify a SOCKS5 proxy to connect through. "None" and
"username" authentication types are supported. The
<option>socks-url</option> must be of the form
<option>socks5h://[username[:password]@]host[:port]</option>.
The protocol prefix <option>socks5h</option> 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.</para>
<para>More SOCKS versions may be available in the future,
depending on demand, and will use different protocol
prefixes as described in <citerefentry>
<refentrytitle>curl</refentrytitle>
<manvolnum>1</manvolnum> </citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--psk</option></term>
<listitem>