From 14c652150c9a6339c792eb1911da809327237123 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 7 Dec 2010 04:48:14 +0000 Subject: added SOCKS4/5 proxy support. git-svn-id: svn://sylpheed.sraoss.jp/sylpheed/trunk@2733 ee746299-78ed-0310-b773-934348b2243d --- libsylph/Makefile.am | 2 + libsylph/prefs_account.c | 11 +++ libsylph/prefs_account.h | 13 ++- libsylph/session.c | 79 ++++++++++++++- libsylph/session.h | 7 +- libsylph/socks.c | 251 +++++++++++++++++++++++++++++++++++++++++++++++ libsylph/socks.h | 59 +++++++++++ 7 files changed, 415 insertions(+), 7 deletions(-) create mode 100644 libsylph/socks.c create mode 100644 libsylph/socks.h (limited to 'libsylph') diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am index aef0e3cb..1aeb5e6b 100644 --- a/libsylph/Makefile.am +++ b/libsylph/Makefile.am @@ -37,6 +37,7 @@ libsylph_0_la_SOURCES = \ session.c \ smtp.c \ socket.c \ + socks.c \ ssl.c \ stringtable.c \ sylmain.c \ @@ -78,6 +79,7 @@ libsylph_0include_HEADERS = \ session.h \ smtp.h \ socket.h \ + socks.h \ ssl.h \ stringtable.h \ sylmain.h \ diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c index e60a3bec..1eaa7231 100644 --- a/libsylph/prefs_account.c +++ b/libsylph/prefs_account.c @@ -132,6 +132,17 @@ static PrefParam param[] = { {"set_trash_folder", "FALSE", &tmp_ac_prefs.set_trash_folder, P_BOOL}, {"trash_folder", NULL, &tmp_ac_prefs.trash_folder, P_STRING}, + /* SOCKS proxy */ + {"use_socks", "FALSE", &tmp_ac_prefs.use_socks, P_BOOL}, + {"use_socks_for_recv", "TRUE", &tmp_ac_prefs.use_socks_for_recv, P_BOOL}, + {"use_socks_for_send", "TRUE", &tmp_ac_prefs.use_socks_for_send, P_BOOL}, + {"socks_type", "1", &tmp_ac_prefs.socks_type, P_ENUM}, + {"proxy_host", NULL, &tmp_ac_prefs.proxy_host, P_STRING}, + {"proxy_port", "1080", &tmp_ac_prefs.proxy_port, P_USHORT}, + {"use_proxy_auth", "FALSE", &tmp_ac_prefs.use_proxy_auth, P_BOOL}, + {"proxy_name", NULL, &tmp_ac_prefs.proxy_name, P_STRING}, + {"proxy_pass", NULL, &tmp_ac_prefs.proxy_pass, P_STRING}, + {NULL, NULL, NULL, P_OTHER} }; diff --git a/libsylph/prefs_account.h b/libsylph/prefs_account.h index d666fd1b..53295801 100644 --- a/libsylph/prefs_account.h +++ b/libsylph/prefs_account.h @@ -1,6 +1,6 @@ /* * LibSylph -- E-Mail client library - * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * Copyright (C) 1999-2010 Hiroyuki Yamamoto * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -169,6 +169,17 @@ struct _PrefsAccount /* Compose */ gboolean sig_before_quote; + + /* Advanced - SOCKS proxy */ + gboolean use_socks; + gboolean use_socks_for_recv; + gboolean use_socks_for_send; + gint socks_type; + gchar *proxy_host; + gushort proxy_port; + gboolean use_proxy_auth; + gchar *proxy_name; + gchar *proxy_pass; }; PrefsAccount *prefs_account_new (void); diff --git a/libsylph/session.c b/libsylph/session.c index 0b561eda..f45138f0 100644 --- a/libsylph/session.c +++ b/libsylph/session.c @@ -1,6 +1,6 @@ /* * LibSylph -- E-Mail client library - * Copyright (C) 1999-2009 Hiroyuki Yamamoto + * Copyright (C) 1999-2010 Hiroyuki Yamamoto * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -35,6 +35,18 @@ #include "session.h" #include "utils.h" +typedef struct _SessionPrivData SessionPrivData; + +struct _SessionPrivData { + Session *session; + SocksInfo *socks_info; + gpointer data; +}; + +static GList *priv_list = NULL; + +static SessionPrivData *session_get_priv(Session *session); + static gint session_connect_cb (SockInfo *sock, gpointer data); static gint session_close (Session *session); @@ -117,7 +129,29 @@ void session_init(Session *session) session->data = NULL; } +static SessionPrivData *session_get_priv(Session *session) +{ + SessionPrivData *priv; + GList *cur; + + g_return_val_if_fail(session != NULL, NULL); + + for (cur = priv_list; cur != NULL; cur = cur->next) { + priv = (SessionPrivData *)cur->data; + if (priv->session == session) + return priv; + } + + return NULL; +} + gint session_connect(Session *session, const gchar *server, gushort port) +{ + return session_connect_full(session, server, port, NULL); +} + +gint session_connect_full(Session *session, const gchar *server, gushort port, + SocksInfo *socks_info) { #ifndef G_OS_UNIX SockInfo *sock = NULL; @@ -132,6 +166,11 @@ gint session_connect(Session *session, const gchar *server, gushort port) } session->port = port; + if (socks_info) { + server = socks_info->proxy_host; + port = socks_info->proxy_port; + } + #ifdef G_OS_UNIX session->conn_id = sock_connect_async(server, port, session_connect_cb, session); @@ -140,8 +179,6 @@ gint session_connect(Session *session, const gchar *server, gushort port) session->state = SESSION_ERROR; return -1; } - - return 0; #elif USE_THREADS session->conn_id = sock_connect_async_thread(server, port); if (session->conn_id < 0) { @@ -154,8 +191,6 @@ gint session_connect(Session *session, const gchar *server, gushort port) session->state = SESSION_ERROR; return -1; } - - return session_connect_cb(sock, session); #else /* !USE_THREADS */ sock = sock_connect(server, port); if (sock == NULL) { @@ -163,7 +198,20 @@ gint session_connect(Session *session, const gchar *server, gushort port) session->state = SESSION_ERROR; return -1; } +#endif + + if (socks_info) { + SessionPrivData *priv; + + priv = g_new0(SessionPrivData, 1); + priv->session = session; + priv->socks_info = socks_info; + priv_list = g_list_prepend(priv_list, priv); + } +#ifdef G_OS_UNIX + return 0; +#else return session_connect_cb(sock, session); #endif } @@ -171,6 +219,7 @@ gint session_connect(Session *session, const gchar *server, gushort port) static gint session_connect_cb(SockInfo *sock, gpointer data) { Session *session = SESSION(data); + SessionPrivData *priv; session->conn_id = 0; @@ -182,6 +231,17 @@ static gint session_connect_cb(SockInfo *sock, gpointer data) session->sock = sock; + priv = session_get_priv(session); + if (priv && priv->socks_info) { + sock_set_nonblocking_mode(sock, FALSE); + if (socks_connect(sock, session->server, session->port, + priv->socks_info) < 0) { + g_warning("can't establish SOCKS connection."); + session->state = SESSION_ERROR; + return -1; + } + } + #if USE_SSL if (session->ssl_type == SSL_TUNNEL) { sock_set_nonblocking_mode(sock, FALSE); @@ -217,6 +277,8 @@ gint session_disconnect(Session *session) void session_destroy(Session *session) { + SessionPrivData *priv; + g_return_if_fail(session != NULL); g_return_if_fail(session->destroy != NULL); @@ -230,6 +292,13 @@ void session_destroy(Session *session) fclose(session->read_data_fp); g_free(session->write_buf); + priv = session_get_priv(session); + if (priv) { + priv_list = g_list_remove(priv_list, priv); + socks_info_free(priv->socks_info); + g_free(priv); + } + debug_print("session (%p): destroyed\n", session); g_free(session); diff --git a/libsylph/session.h b/libsylph/session.h index b5127c0e..a807a4ae 100644 --- a/libsylph/session.h +++ b/libsylph/session.h @@ -1,6 +1,6 @@ /* * LibSylph -- E-Mail client library - * Copyright (C) 1999-2007 Hiroyuki Yamamoto + * Copyright (C) 1999-2010 Hiroyuki Yamamoto * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,6 +30,7 @@ #include #include "socket.h" +#include "socks.h" #define SESSION_BUFFSIZE 8192 @@ -181,6 +182,10 @@ void session_init (Session *session); gint session_connect (Session *session, const gchar *server, gushort port); +gint session_connect_full (Session *session, + const gchar *server, + gushort port, + SocksInfo *socks_info); gint session_disconnect (Session *session); void session_destroy (Session *session); gboolean session_is_connected (Session *session); diff --git a/libsylph/socks.c b/libsylph/socks.c new file mode 100644 index 00000000..7c799b5c --- /dev/null +++ b/libsylph/socks.c @@ -0,0 +1,251 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "socks.h" +#include "utils.h" + + +SocksInfo *socks_info_new(SocksType type, const gchar *proxy_host, + gushort proxy_port, const gchar *proxy_name, + const gchar *proxy_pass) +{ + SocksInfo *socks_info; + + socks_info = g_new0(SocksInfo, 1); + socks_info->type = type; + socks_info->proxy_host = g_strdup(proxy_host); + socks_info->proxy_port = proxy_port; + socks_info->proxy_name = g_strdup(proxy_name); + socks_info->proxy_pass = g_strdup(proxy_pass); + + return socks_info; +} + +void socks_info_free(SocksInfo *socks_info) +{ + if (!socks_info) + return; + g_free(socks_info->proxy_host); + g_free(socks_info->proxy_name); + g_free(socks_info->proxy_pass); + g_free(socks_info); +} + +gint socks_connect(SockInfo *sock, const gchar *hostname, gushort port, + SocksInfo *socks_info) +{ + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + g_return_val_if_fail(socks_info != NULL, -1); + + debug_print("socks_connect: connect to %s:%u via %s:%u\n", + hostname, port, + socks_info->proxy_host, socks_info->proxy_port); + + if (socks_info->type == SOCKS_SOCKS5) + return socks5_connect(sock, hostname, port, + socks_info->proxy_name, + socks_info->proxy_pass); + else if (socks_info->type == SOCKS_SOCKS4) + return socks4_connect(sock, hostname, port); + else + g_warning("socks_connect: unknown SOCKS type: %d\n", + socks_info->type); + + return -1; +} + +gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port) +{ + guchar socks_req[1024]; + struct hostent *hp; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + + debug_print("socks4_connect: connect to %s:%u\n", hostname, port); + + socks_req[0] = 4; + socks_req[1] = 1; + *((gushort *)(socks_req + 2)) = htons(port); + + /* lookup */ + if ((hp = my_gethostbyname(hostname)) == NULL) { + g_warning("socks4_connect: cannot lookup host: %s", hostname); + return -1; + } + if (hp->h_length != 4) { + g_warning("socks4_connect: invalid address length for host: %s", hostname); + return -1; + } + memcpy(socks_req + 4, (guchar *)hp->h_addr, 4); + g_print("addr = %u.", ((guchar *)(hp->h_addr))[0]); + g_print("%u.", ((guchar *)(hp->h_addr))[1]); + g_print("%u.", ((guchar *)(hp->h_addr))[2]); + g_print("%u\n", ((guchar *)(hp->h_addr))[3]); + + /* userid (empty) */ + socks_req[8] = 0; + + if (sock_write_all(sock, (gchar *)socks_req, 9) != 9) { + g_warning("socks4_connect: SOCKS4 initial request write failed"); + return -1; + } + + if (sock_read(sock, (gchar *)socks_req, 8) != 8) { + g_warning("socks4_connect: SOCKS4 response read failed"); + return -1; + } + if (socks_req[0] != 0) { + g_warning("socks4_connect: SOCKS4 response has invalid version"); + return -1; + } + if (socks_req[1] != 90) { + g_warning("socks4_connect: SOCKS4 connection to %u.%u.%u.%u:%u failed. (%u)", socks_req[4], socks_req[5], socks_req[6], socks_req[7], ntohs(*(gushort *)(socks_req + 2)), socks_req[1]); + return -1; + } + + debug_print("socks4_connect: SOCKS4 connection to %s:%u successful.\n", hostname, port); + + return 0; +} + +gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port, + const gchar *proxy_name, const gchar *proxy_pass) +{ + guchar socks_req[1024]; + size_t len; + size_t size; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + + debug_print("socks5_connect: connect to %s:%u\n", hostname, port); + + len = strlen(hostname); + if (len > 255) { + g_warning("socks5_connect: hostname too long"); + return -1; + } + + socks_req[0] = 5; + socks_req[1] = proxy_name ? 2 : 1; + socks_req[2] = 0; + socks_req[3] = 2; + + if (sock_write_all(sock, (gchar *)socks_req, 2 + socks_req[1]) != 2 + socks_req[1]) { + g_warning("socks5_connect: SOCKS5 initial request write failed"); + return -1; + } + + if (sock_read(sock, (gchar *)socks_req, 2) != 2) { + g_warning("socks5_connect: SOCKS5 response read failed"); + return -1; + } + if (socks_req[0] != 5) { + g_warning("socks5_connect: SOCKS5 response has invalid version"); + return -1; + } + if (socks_req[1] == 2) { + /* auth */ + size_t userlen, passlen; + gint reqlen; + + if (proxy_name && proxy_pass) { + userlen = strlen(proxy_name); + passlen = strlen(proxy_pass); + } else + userlen = passlen = 0; + + socks_req[0] = 1; + socks_req[1] = (guchar)userlen; + if (proxy_name && userlen > 0) + memcpy(socks_req + 2, proxy_name, userlen); + socks_req[2 + userlen] = (guchar)passlen; + if (proxy_pass && passlen > 0) + memcpy(socks_req + 2 + userlen + 1, proxy_pass, passlen); + + reqlen = 2 + userlen + 1 + passlen; + if (sock_write_all(sock, (gchar *)socks_req, reqlen) != reqlen) { + g_warning("socks5_connect: SOCKS5 auth write failed"); + return -1; + } + if (sock_read(sock, (gchar *)socks_req, 2) != 2) { + g_warning("socks5_connect: SOCKS5 auth response read failed"); + return -1; + } + if (socks_req[1] != 0) { + g_warning("socks5_connect: SOCKS5 authentication failed: user: %s (%u %u)", proxy_name ? proxy_name : "(none)", socks_req[0], socks_req[1]); + return -1; + } + } else if (socks_req[1] != 0) { + g_warning("socks5_connect: SOCKS5 reply (%u) error", socks_req[1]); + return -1; + } + + socks_req[0] = 5; + socks_req[1] = 1; + socks_req[2] = 0; + + socks_req[3] = 3; + socks_req[4] = (guchar)len; + memcpy(socks_req + 5, hostname, len); + *((gushort *)(socks_req + 5 + len)) = htons(port); + + if (sock_write_all(sock, (gchar *)socks_req, 5 + len + 2) != 5 + len + 2) { + g_warning("socks5_connect: SOCKS5 connect request write failed"); + return -1; + } + + if (sock_read(sock, (gchar *)socks_req, 10) != 10) { + g_warning("socks5_connect: SOCKS5 connect request response read failed"); + return -1; + } + if (socks_req[0] != 5) { + g_warning("socks5_connect: SOCKS5 response has invalid version"); + return -1; + } + if (socks_req[1] != 0) { + g_warning("socks5_connect: SOCKS5 connection to %u.%u.%u.%u:%u failed. (%u)", socks_req[4], socks_req[5], socks_req[6], socks_req[7], ntohs(*(gushort *)(socks_req + 8)), socks_req[1]); + return -1; + } + + size = 10; + if (socks_req[3] == 3) + size = 5 + socks_req[4] + 2; + else if (socks_req[3] == 4) + size = 4 + 16 + 2; + if (size > 10) { + size -= 10; + if (sock_read(sock, (gchar *)socks_req + 10, size) != size) { + g_warning("socks5_connect: SOCKS5 connect request response read failed"); + return -1; + } + } + + debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port); + + return 0; +} diff --git a/libsylph/socks.h b/libsylph/socks.h new file mode 100644 index 00000000..afa34c5b --- /dev/null +++ b/libsylph/socks.h @@ -0,0 +1,59 @@ +/* + * LibSylph -- E-Mail client library + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __SOCKS_H__ +#define __SOCKS_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "socket.h" + +typedef struct _SocksInfo SocksInfo; + +typedef enum { + SOCKS_SOCKS4, + SOCKS_SOCKS5 +} SocksType; + +struct _SocksInfo +{ + SocksType type; + gchar *proxy_host; + gushort proxy_port; + gchar *proxy_name; + gchar *proxy_pass; +}; + +SocksInfo *socks_info_new(SocksType type, const gchar *proxy_host, + gushort proxy_port, const gchar *proxy_name, + const gchar *proxy_pass); +void socks_info_free(SocksInfo *socks_info); + +gint socks_connect(SockInfo *sock, const gchar *hostname, gushort port, + SocksInfo *socks_info); + +gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port); +gint socks5_connect(SockInfo *sock, const gchar *hostname, gushort port, + const gchar *proxy_name, const gchar *proxy_pass); + +#endif /* __SOCKS_H__ */ -- cgit v1.2.3