talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit d28d62772b7b621b8dfea91c743404e3deef7daa
parent 6767709b0d603e9013ab10c749b77b9d69cd1500
Author: Andrej Kacian <ticho@claws-mail.org>
Date:   Fri, 18 May 2018 20:27:52 +0200

Added SOCKS proxy support.

Based on UI and network code from LibSylph/Sylpheed, rewritten
to use getaddrinfo(), fixed some small bugs in handling errors.

Added connect wrappers for etpan IMAP and NNTP, as well as for
anything Session-based (POP3, SMTP, Managesieve).

Adds support for global, as well as per-account proxy servers.

Original patch by Charles Lehner <cel at celehner com>, from
bug #2244.

Diffstat:
Mpo/POTFILES.in | 1+
Msrc/Makefile.am | 2++
Msrc/common/Makefile.am | 2++
Asrc/common/proxy.c | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/common/proxy.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/common/session.c | 19+++++++++++++++++++
Msrc/common/session.h | 9+++++++++
Msrc/common/socket.c | 8+++++++-
Msrc/etpan/imap-thread.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/etpan/imap-thread.h | 4++--
Msrc/etpan/nntp-thread.c | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/etpan/nntp-thread.h | 5+++--
Msrc/imap.c | 27++++++++++++++++++++++-----
Msrc/inc.c | 37++++++++++++++++++++++++++++---------
Msrc/main.c | 3+++
Msrc/news.c | 58+++++++++++++++++++++++++++++++++++-----------------------
Msrc/passwordstore.h | 4++++
Msrc/plugins/managesieve/managesieve.c | 20++++++++++++++++++++
Msrc/prefs_account.c | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/prefs_account.h | 6++++++
Msrc/prefs_common.c | 9+++++++++
Msrc/prefs_common.h | 4++++
Asrc/prefs_proxy.c | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/prefs_proxy.h | 26++++++++++++++++++++++++++
Msrc/send_message.c | 20+++++++++++++++++++-
25 files changed, 1246 insertions(+), 60 deletions(-)

diff --git a/po/POTFILES.in b/po/POTFILES.in @@ -17,6 +17,7 @@ src/common/plugin.c src/common/session.c src/common/smtp.c src/common/socket.c +src/common/socks.c src/common/ssl.c src/common/ssl_certificate.c src/common/string_match.c diff --git a/src/Makefile.am b/src/Makefile.am @@ -202,6 +202,7 @@ claws_mail_SOURCES = \ prefs_migration.c \ prefs_msg_colors.c \ prefs_other.c \ + prefs_proxy.c \ prefs_quote.c \ prefs_receive.c \ prefs_send.c \ @@ -322,6 +323,7 @@ claws_mailinclude_HEADERS = \ prefs_migration.h \ prefs_msg_colors.h \ prefs_other.h \ + prefs_proxy.h \ prefs_quote.h \ prefs_receive.h \ prefs_send.h \ diff --git a/src/common/Makefile.am b/src/common/Makefile.am @@ -27,6 +27,7 @@ libclawscommon_la_SOURCES = $(arch_sources) \ plugin.c \ prefs.c \ progressindicator.c \ + proxy.c \ quoted-printable.c \ session.c \ smtp.c \ @@ -55,6 +56,7 @@ clawscommoninclude_HEADERS = $(arch_headers) \ plugin.h \ prefs.h \ progressindicator.h \ + proxy.h \ quoted-printable.h \ session.h \ smtp.h \ diff --git a/src/common/proxy.c b/src/common/proxy.c @@ -0,0 +1,300 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2014 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> + +#ifdef G_OS_WIN32 +# include <winsock2.h> +# include <ws2tcpip.h> +#endif + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include "proxy.h" +#include "socket.h" +#include "utils.h" + +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); + +gint proxy_connect(SockInfo *sock, const gchar *hostname, gushort port, + ProxyInfo *proxy_info) +{ + gint ret; + + g_return_val_if_fail(sock != NULL, -1); + g_return_val_if_fail(hostname != NULL, -1); + g_return_val_if_fail(proxy_info != NULL, -1); + + debug_print("proxy_connect: connect to %s:%u via %s:%u\n", + hostname, port, + proxy_info->proxy_host, proxy_info->proxy_port); + + if (proxy_info->proxy_type == PROXY_SOCKS5) { + ret = socks5_connect(sock, hostname, port, + proxy_info->use_proxy_auth ? proxy_info->proxy_name : NULL, + proxy_info->use_proxy_auth ? proxy_info->proxy_pass : NULL); + /* Scrub the password before returning */ + if (proxy_info->proxy_pass != NULL) { + memset(proxy_info->proxy_pass, 0, strlen(proxy_info->proxy_pass)); + g_free(proxy_info->proxy_pass); + } + return ret; + } else if (proxy_info->proxy_type == PROXY_SOCKS4) { + return socks4_connect(sock, hostname, port); + } else { + g_warning("proxy_connect: unknown SOCKS type: %d\n", + proxy_info->proxy_type); + } + + return -1; +} + +gint socks4_connect(SockInfo *sock, const gchar *hostname, gushort port) +{ + guchar socks_req[1024]; + struct addrinfo hints, *res, *ai; + gboolean got_address = FALSE; + int s; + + 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 */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; /* SOCKS4 only supports IPv4 addresses */ + + s = getaddrinfo(hostname, NULL, &hints, &res); + if (s != 0) { + fprintf(stderr, "getaddrinfo for '%s' failed: %s\n", + hostname, gai_strerror(s)); + return -1; + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + uint32_t addr; + + if (ai->ai_family != AF_INET) + continue; + + addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr; + memcpy(socks_req + 4, &addr, 4); + got_address = TRUE; + break; + } + + if (res != NULL) + freeaddrinfo(res); + + if (!got_address) { + g_warning("socks4_connect: could not get valid IPv4 address for '%s'", hostname); + return -1; + } + + debug_print("got a valid IPv4 address, continuing\n"); + + /* 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; + } + + /* replace sock->hostname with endpoint */ + if (sock->hostname != hostname) { + g_free(sock->hostname); + sock->hostname = g_strdup(hostname); + sock->port = port; + } + + 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) { + debug_print("socks5_connect: auth using username '%s'\n", proxy_name); + 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) { + memset(socks_req, 0, reqlen); + g_warning("socks5_connect: SOCKS5 auth write failed"); + return -1; + } + memset(socks_req, 0, reqlen); + 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) { + if (socks_req[3] == 1) { /* IPv4 address */ + 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]); + } else if (socks_req[3] == 3) { /* Domain name */ + gint hnlen = socks_req[4]; + gchar *hn = malloc(hnlen + 1); + hn[hnlen + 1] = '\0'; + memcpy(hn, &socks_req[5], hnlen); + g_warning("socks5_connect: SOCKS5 connection to %s:%u failed. (%u)", + hn, ntohs(*(gushort *)(socks_req + 5 + hnlen)), socks_req[1]); + g_free(hn); + } else if (socks_req[3] == 4) { /* IPv6 address */ + gint hnlen = 16; + gchar *hn = malloc(hnlen + 1); + hn[hnlen + 1] = '\0'; + memcpy(hn, &socks_req[4], hnlen); + g_warning("socks5_connect: SOCKS5 connection to IPv6 %s:%u failed. (%u)", + hn, ntohs(*(gushort *)(socks_req + 5 + hnlen)), socks_req[1]); + g_free(hn); + } + 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; + } + } + + /* replace sock->hostname with endpoint */ + if (sock->hostname != hostname) { + g_free(sock->hostname); + sock->hostname = g_strdup(hostname); + sock->port = port; + } + + debug_print("socks5_connect: SOCKS5 connection to %s:%u successful.\n", hostname, port); + + return 0; +} diff --git a/src/common/proxy.h b/src/common/proxy.h @@ -0,0 +1,54 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2010 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __PROXY_H__ +#define __PROXY_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glib.h> + +#include "socket.h" + +typedef struct _ProxyInfo ProxyInfo; + +typedef enum { + PROXY_SOCKS4, + PROXY_SOCKS5 +} ProxyType; + +struct _ProxyInfo +{ + ProxyType proxy_type; + gchar *proxy_host; + gushort proxy_port; + + gboolean use_proxy_auth; + gchar *proxy_name; + gchar *proxy_pass; +}; + +/* As a side effect, this function will zero out and free + * string pointed to by proxy_info->proxy_pass after the password + * is no longer needed. */ +gint proxy_connect(SockInfo *sock, const gchar *hostname, gushort port, + ProxyInfo *proxy_info); + +#endif /* __PROXY_H__ */ diff --git a/src/common/session.c b/src/common/session.c @@ -102,6 +102,8 @@ void session_init(Session *session, const void *prefs_account, gboolean is_smtp) session->is_smtp = is_smtp; session->ping_tag = -1; + + session->proxy_info = NULL; } /*! @@ -123,6 +125,11 @@ gint session_connect(Session *session, const gchar *server, gushort port) session->server = g_strdup(server); session->port = port; + if (session->proxy_info) { + server = session->proxy_info->proxy_host; + port = session->proxy_info->proxy_port; + } + session->conn_id = sock_connect_async(server, port, session_connect_cb, session); if (session->conn_id < 0) { @@ -173,6 +180,18 @@ static gint session_connect_cb(SockInfo *sock, gpointer data) sock->is_smtp = session->is_smtp; sock->ssl_cert_auto_accept = session->ssl_cert_auto_accept; + if (session->proxy_info) { + debug_print("connecting through socks\n"); + sock_set_nonblocking_mode(sock, FALSE); + if (proxy_connect(sock, session->server, session->port, + session->proxy_info) < 0) { + g_warning("can't establish SOCKS connection."); + session->state = SESSION_ERROR; + return -1; + } + } + + #ifdef USE_GNUTLS sock->gnutls_priority = session->gnutls_priority; diff --git a/src/common/session.h b/src/common/session.h @@ -30,6 +30,7 @@ #include <unistd.h> #include "socket.h" +#include "proxy.h" #define SESSION_BUFFSIZE 4096 @@ -148,6 +149,14 @@ struct _Session gboolean ssl_cert_auto_accept; gint ping_tag; + /* Pointer to ProxyInfo struct holding the info about proxy + * to be used. Set to NULL if no proxy is used. + * If non-NULL, the memory this pointer is pointing at does + * not belong to this Session, and shouldn't be modified + * or freed by Session. It is usually a pointer to the + * SockInfo in common prefs, or in account prefs. */ + ProxyInfo *proxy_info; + #ifdef USE_GNUTLS SSLType ssl_type; gchar *gnutls_priority; diff --git a/src/common/socket.c b/src/common/socket.c @@ -589,7 +589,7 @@ static gint sock_connect_with_timeout(gint sock, gint addrlen, guint timeout_secs) { - gint ret; + gint ret, saved_errno; #ifdef G_OS_UNIX void (*prev_handler)(gint); @@ -606,6 +606,12 @@ static gint sock_connect_with_timeout(gint sock, #endif ret = connect(sock, serv_addr, addrlen); + saved_errno = errno; + + if (ret == -1) { + debug_print("connect() failed: %d (%s)\n", + saved_errno, g_strerror(saved_errno)); + } #ifdef G_OS_UNIX alarm(0); diff --git a/src/etpan/imap-thread.c b/src/etpan/imap-thread.c @@ -43,6 +43,7 @@ #include "etpan-ssl.h" #include "utils.h" #include "mainwindow.h" +#include "proxy.h" #include "ssl.h" #include "ssl_certificate.h" #include "socket.h" @@ -58,6 +59,79 @@ static chash * session_hash = NULL; static guint thread_manager_signal = 0; static GIOChannel * io_channel = NULL; +static int do_mailimap_socket_connect(mailimap * imap, const char * server, + gushort port, ProxyInfo * proxy_info) +{ + SockInfo * sock; + mailstream * stream; + + if (!proxy_info) + return mailimap_socket_connect(imap, server, port); + + if (port == 0) + port = 143; + + sock = sock_connect(proxy_info->proxy_host, proxy_info->proxy_port); + + if (sock == NULL) + return MAILIMAP_ERROR_CONNECTION_REFUSED; + + if (proxy_connect(sock, server, port, proxy_info) < 0) { + sock_close(sock); + return MAILIMAP_ERROR_CONNECTION_REFUSED; + } + + stream = mailstream_socket_open_timeout(sock->sock, + imap->imap_timeout); + if (stream == NULL) { + sock_close(sock); + return MAILIMAP_ERROR_MEMORY; + } + + return mailimap_connect(imap, stream); +} + +static int do_mailimap_ssl_connect_with_callback(mailimap * imap, const char * server, + gushort port, + void (* callback)(struct mailstream_ssl_context * ssl_context, void * data), + void * data, + ProxyInfo *proxy_info) +{ + SockInfo * sock; + mailstream * stream; + + if (!proxy_info) + return mailimap_ssl_connect_with_callback(imap, server, + port, callback, data); + + if (port == 0) + port = 993; + + sock = sock_connect(proxy_info->proxy_host, proxy_info->proxy_port); + + if (sock == NULL) { + debug_print("Can not connect to proxy %s:%d\n", + proxy_info->proxy_host, proxy_info->proxy_port); + return MAILIMAP_ERROR_CONNECTION_REFUSED; + } + + if (proxy_connect(sock, server, port, proxy_info) < 0) { + debug_print("Can not make proxy connection via %s:%d\n", + proxy_info->proxy_host, proxy_info->proxy_port); + sock_close(sock); + return MAILIMAP_ERROR_CONNECTION_REFUSED; + } + + stream = mailstream_ssl_open_with_callback_timeout(sock->sock, + imap->imap_timeout, callback, data); + if (stream == NULL) { + sock_close(sock); + return MAILIMAP_ERROR_SSL; + } + + return mailimap_connect(imap, stream); +} + static gboolean thread_manager_event(GIOChannel * source, GIOCondition condition, gpointer data) @@ -446,6 +520,7 @@ struct connect_param { PrefsAccount *account; const char * server; int port; + ProxyInfo * proxy_info; }; struct connect_result { @@ -519,14 +594,14 @@ static void connect_run(struct etpan_thread_op * op) CHECK_IMAP(); - r = mailimap_socket_connect(param->imap, - param->server, param->port); + r = do_mailimap_socket_connect(param->imap, + param->server, param->port, param->proxy_info); result->error = r; } -int imap_threaded_connect(Folder * folder, const char * server, int port) +int imap_threaded_connect(Folder * folder, const char * server, int port, ProxyInfo *proxy_info) { struct connect_param param; struct connect_result result; @@ -552,6 +627,7 @@ int imap_threaded_connect(Folder * folder, const char * server, int port) param.imap = imap; param.server = server; param.port = port; + param.proxy_info = proxy_info; refresh_resolvers(); threaded_run(folder, &param, &result, connect_run); @@ -572,13 +648,14 @@ static void connect_ssl_run(struct etpan_thread_op * op) CHECK_IMAP(); - r = mailimap_ssl_connect_with_callback(param->imap, + r = do_mailimap_ssl_connect_with_callback(param->imap, param->server, param->port, - etpan_connect_ssl_context_cb, param->account); + etpan_connect_ssl_context_cb, param->account, + param->proxy_info); result->error = r; } -int imap_threaded_connect_ssl(Folder * folder, const char * server, int port) +int imap_threaded_connect_ssl(Folder * folder, const char * server, int port, ProxyInfo *proxy_info) { struct connect_param param; struct connect_result result; @@ -606,6 +683,7 @@ int imap_threaded_connect_ssl(Folder * folder, const char * server, int port) param.server = server; param.port = port; param.account = folder->account; + param.proxy_info = proxy_info; if (folder->account) accept_if_valid = folder->account->ssl_certs_auto_accept; diff --git a/src/etpan/imap-thread.h b/src/etpan/imap-thread.h @@ -45,8 +45,8 @@ void imap_main_done(gboolean have_connectivity); void imap_init(Folder * folder); void imap_done(Folder * folder); -int imap_threaded_connect(Folder * folder, const char * server, int port); -int imap_threaded_connect_ssl(Folder * folder, const char * server, int port); +int imap_threaded_connect(Folder * folder, const char * server, int port, ProxyInfo *proxy_info); +int imap_threaded_connect_ssl(Folder * folder, const char * server, int port, ProxyInfo *proxy_info); int imap_threaded_capability(Folder *folder, struct mailimap_capability_data ** caps); #ifndef G_OS_WIN32 diff --git a/src/etpan/nntp-thread.c b/src/etpan/nntp-thread.c @@ -60,6 +60,75 @@ static chash * session_hash = NULL; static guint thread_manager_signal = 0; static GIOChannel * io_channel = NULL; +static int do_newsnntp_socket_connect(newsnntp * imap, const char * server, + gushort port, ProxyInfo * proxy_info) +{ + SockInfo * sock; + mailstream * stream; + + if (!proxy_info) + return newsnntp_socket_connect(imap, server, port); + + if (port == 0) + port = 119; + + sock = sock_connect(proxy_info->proxy_host, proxy_info->proxy_port); + + if (sock == NULL) + return NEWSNNTP_ERROR_CONNECTION_REFUSED; + + if (proxy_connect(sock, server, port, proxy_info) < 0) { + sock_close(sock); + return NEWSNNTP_ERROR_CONNECTION_REFUSED; + } + + stream = mailstream_socket_open_timeout(sock->sock, + imap->nntp_timeout); + if (stream == NULL) { + sock_close(sock); + return NEWSNNTP_ERROR_MEMORY; + } + + return newsnntp_connect(imap, stream); +} + +static int do_newsnntp_ssl_connect_with_callback(newsnntp * imap, const char * server, + gushort port, + void (* callback)(struct mailstream_ssl_context * ssl_context, void * data), + void * data, + ProxyInfo *proxy_info) +{ + SockInfo * sock; + mailstream * stream; + + if (!proxy_info) + return newsnntp_ssl_connect_with_callback(imap, server, + port, callback, data); + + if (port == 0) + port = 563; + + sock = sock_connect(proxy_info->proxy_host, proxy_info->proxy_port); + + if (sock == NULL) + return NEWSNNTP_ERROR_CONNECTION_REFUSED; + + if (proxy_connect(sock, server, port, proxy_info) < 0) { + sock_close(sock); + return NEWSNNTP_ERROR_CONNECTION_REFUSED; + } + + stream = mailstream_ssl_open_with_callback_timeout(sock->sock, + imap->nntp_timeout, callback, data); + if (stream == NULL) { + sock_close(sock); + return NEWSNNTP_ERROR_SSL; + } + + return newsnntp_connect(imap, stream); +} + + static void nntp_logger(int direction, const char * str, size_t size) { gchar *buf; @@ -309,6 +378,7 @@ struct connect_param { PrefsAccount *account; const char * server; int port; + ProxyInfo * proxy_info; }; struct connect_result { @@ -333,14 +403,15 @@ static void connect_run(struct etpan_thread_op * op) CHECK_NNTP(); - r = newsnntp_socket_connect(param->nntp, - param->server, param->port); + r = do_newsnntp_socket_connect(param->nntp, + param->server, param->port, + param->proxy_info); result->error = r; } -int nntp_threaded_connect(Folder * folder, const char * server, int port) +int nntp_threaded_connect(Folder * folder, const char * server, int port, ProxyInfo *proxy_info) { struct connect_param param; struct connect_result result; @@ -366,7 +437,8 @@ int nntp_threaded_connect(Folder * folder, const char * server, int port) param.nntp = nntp; param.server = server; param.port = port; - + param.proxy_info = proxy_info; + refresh_resolvers(); threaded_run(folder, &param, &result, connect_run); @@ -386,13 +458,14 @@ static void connect_ssl_run(struct etpan_thread_op * op) CHECK_NNTP(); - r = newsnntp_ssl_connect_with_callback(param->nntp, + r = do_newsnntp_ssl_connect_with_callback(param->nntp, param->server, param->port, - etpan_connect_ssl_context_cb, param->account); + etpan_connect_ssl_context_cb, param->account, + param->proxy_info); result->error = r; } -int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port) +int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port, ProxyInfo *proxy_info) { struct connect_param param; struct connect_result result; @@ -420,6 +493,7 @@ int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port) param.server = server; param.port = port; param.account = folder->account; + param.proxy_info = proxy_info; if (folder->account) accept_if_valid = folder->account->ssl_certs_auto_accept; diff --git a/src/etpan/nntp-thread.h b/src/etpan/nntp-thread.h @@ -23,6 +23,7 @@ #include <libetpan/libetpan.h> #include "folder.h" +#include "proxy.h" void nntp_main_set_timeout(int sec); void nntp_main_init(gboolean skip_ssl_cert_check); @@ -31,8 +32,8 @@ void nntp_main_done(gboolean have_connectivity); void nntp_init(Folder * folder); void nntp_done(Folder * folder); -int nntp_threaded_connect(Folder * folder, const char * server, int port); -int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port); +int nntp_threaded_connect(Folder * folder, const char * server, int port, ProxyInfo *proxy_info); +int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port, ProxyInfo *proxy_info); void nntp_threaded_disconnect(Folder * folder); diff --git a/src/imap.c b/src/imap.c @@ -1131,6 +1131,7 @@ static IMAPSession *imap_session_new(Folder * folder, const PrefsAccount *account) { IMAPSession *session; + ProxyInfo *proxy_info = NULL; gushort port; int r; int authenticated = FALSE; @@ -1169,6 +1170,20 @@ static IMAPSession *imap_session_new(Folder * folder, log_message(LOG_PROTOCOL, "%s\n", buf); g_free(buf); + if (account->use_proxy) { + if (account->use_default_proxy) { + proxy_info = (ProxyInfo *)&(prefs_common.proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY, + PWS_CORE_PROXY_PASS); + } else { + proxy_info = (ProxyInfo *)&(account->proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get_account(account->account_id, + PWS_ACCOUNT_PROXY_PASS); + } + } + #ifndef G_OS_WIN32 if (account->set_tunnelcmd) { r = imap_threaded_connect_cmd(folder, @@ -1180,17 +1195,20 @@ static IMAPSession *imap_session_new(Folder * folder, #endif { #ifdef USE_GNUTLS + if (ssl_type == SSL_TUNNEL) { r = imap_threaded_connect_ssl(folder, account->recv_server, - port); + port, + proxy_info); } else #endif { r = imap_threaded_connect(folder, account->recv_server, - port); + port, + proxy_info); } } @@ -1224,13 +1242,12 @@ static IMAPSession *imap_session_new(Folder * folder, session_init(SESSION(session), account, FALSE); SESSION(session)->type = SESSION_IMAP; SESSION(session)->server = g_strdup(account->recv_server); - SESSION(session)->port = port; + SESSION(session)->port = port; SESSION(session)->sock = NULL; - + SESSION(session)->proxy_info = proxy_info; SESSION(session)->destroy = imap_session_destroy; session->capability = NULL; - session->authenticated = authenticated; session->mbox = NULL; session->exists = 0; diff --git a/src/inc.c b/src/inc.c @@ -39,6 +39,7 @@ #include "prefs_account.h" #include "account.h" #include "procmsg.h" +#include "proxy.h" #include "socket.h" #include "ssl.h" #include "pop.h" @@ -783,32 +784,34 @@ static IncState inc_pop3_session_do(IncSession *session) { Pop3Session *pop3_session = POP3_SESSION(session->session); IncProgressDialog *inc_dialog = (IncProgressDialog *)session->data; + PrefsAccount *ac = pop3_session->ac_prefs; gchar *server; gchar *account_name; gushort port; gchar *buf; + ProxyInfo *proxy_info = NULL; debug_print("getting new messages of account %s...\n", - pop3_session->ac_prefs->account_name); + ac->account_name); - pop3_session->ac_prefs->last_pop_login_time = time(NULL); + ac->last_pop_login_time = time(NULL); buf = g_strdup_printf(_("%s: Retrieving new messages"), - pop3_session->ac_prefs->recv_server); + ac->recv_server); gtk_window_set_title(GTK_WINDOW(inc_dialog->dialog->window), buf); g_free(buf); - server = pop3_session->ac_prefs->recv_server; - account_name = pop3_session->ac_prefs->account_name; + server = ac->recv_server; + account_name = ac->account_name; port = pop3_get_port(pop3_session); #ifdef USE_GNUTLS - SESSION(pop3_session)->ssl_type = pop3_session->ac_prefs->ssl_pop; - if (pop3_session->ac_prefs->ssl_pop != SSL_NONE) + SESSION(pop3_session)->ssl_type = ac->ssl_pop; + if (ac->ssl_pop != SSL_NONE) SESSION(pop3_session)->nonblocking = - pop3_session->ac_prefs->use_nonblocking_ssl; + ac->use_nonblocking_ssl; #else - if (pop3_session->ac_prefs->ssl_pop != SSL_NONE) { + if (ac->ssl_pop != SSL_NONE) { if (alertpanel_full(_("Insecure connection"), _("This connection is configured to be secured " "using SSL/TLS, but SSL/TLS is not available " @@ -829,6 +832,22 @@ static IncState inc_pop3_session_do(IncSession *session) log_message(LOG_PROTOCOL, "%s\n", buf); progress_dialog_set_label(inc_dialog->dialog, buf); + + if (ac->use_proxy) { + if (ac->use_default_proxy) { + proxy_info = (ProxyInfo *)&(prefs_common.proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY, + PWS_CORE_PROXY_PASS); + } else { + proxy_info = (ProxyInfo *)&(ac->proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get_account(ac->account_id, + PWS_ACCOUNT_PROXY_PASS); + } + } + SESSION(session)->proxy_info = proxy_info; + GTK_EVENTS_FLUSH(); g_free(buf); diff --git a/src/main.c b/src/main.c @@ -90,6 +90,7 @@ #include "prefs_summaries.h" #include "prefs_themes.h" #include "prefs_other.h" +#include "prefs_proxy.h" #include "prefs_logging.h" #include "prefs_send.h" #include "prefs_wrapping.h" @@ -1261,6 +1262,7 @@ int main(int argc, char *argv[]) prefs_summaries_init(); prefs_message_init(); prefs_other_init(); + prefs_proxy_init(); prefs_logging_init(); prefs_receive_init(); prefs_send_init(); @@ -1744,6 +1746,7 @@ static void exit_claws(MainWindow *mainwin) prefs_summaries_done(); prefs_message_done(); prefs_other_done(); + prefs_proxy_done(); prefs_receive_done(); prefs_logging_done(); prefs_send_done(); diff --git a/src/news.c b/src/news.c @@ -104,18 +104,14 @@ static void news_remove_cached_msg (Folder *folder, FolderItem *item, MsgInfo *msginfo); #ifdef USE_GNUTLS -static Session *news_session_new (Folder *folder, - const gchar *server, - gushort port, - const gchar *userid, - const gchar *passwd, - SSLType ssl_type); +static Session *news_session_new (Folder *folder, + const PrefsAccount *account, + gushort port, + SSLType ssl_type); #else -static Session *news_session_new (Folder *folder, - const gchar *server, - gushort port, - const gchar *userid, - const gchar *passwd); +static Session *news_session_new (Folder *folder, + const PrefsAccount *account, + gushort port); #endif static gint news_get_article (Folder *folder, @@ -321,20 +317,22 @@ static gboolean nntp_ping(gpointer data) #ifdef USE_GNUTLS -static Session *news_session_new(Folder *folder, const gchar *server, gushort port, - const gchar *userid, const gchar *passwd, +static Session *news_session_new(Folder *folder, const PrefsAccount *account, gushort port, SSLType ssl_type) #else -static Session *news_session_new(Folder *folder, const gchar *server, gushort port, - const gchar *userid, const gchar *passwd) +static Session *news_session_new(Folder *folder, const PrefsAccount *account, gushort port) #endif { NewsSession *session; + const char *server = account->nntp_server; int r = 0; + ProxyInfo *proxy_info = NULL; + cm_return_val_if_fail(server != NULL, NULL); - log_message(LOG_PROTOCOL, _("Account '%s': Connecting to NNTP server: %s:%d...\n"), - folder->account->account_name, server, port); + log_message(LOG_PROTOCOL, + _("Account '%s': Connecting to NNTP server: %s:%d...\n"), + folder->account->account_name, server, port); session = g_new0(NewsSession, 1); session_init(SESSION(session), folder->account, FALSE); @@ -343,15 +341,30 @@ static Session *news_session_new(Folder *folder, const gchar *server, gushort po SESSION(session)->port = port; SESSION(session)->sock = NULL; SESSION(session)->destroy = news_session_destroy; - + + if (account->use_proxy) { + if (account->use_default_proxy) { + proxy_info = (ProxyInfo *)&(prefs_common.proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY, + PWS_CORE_PROXY_PASS); + } else { + proxy_info = (ProxyInfo *)&(account->proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get_account(account->account_id, + PWS_ACCOUNT_PROXY_PASS); + } + } + SESSION(session)->proxy_info = proxy_info; + nntp_init(folder); #ifdef USE_GNUTLS if (ssl_type != SSL_NONE) - r = nntp_threaded_connect_ssl(folder, server, port); + r = nntp_threaded_connect_ssl(folder, server, port, proxy_info); else #endif - r = nntp_threaded_connect(folder, server, port); + r = nntp_threaded_connect(folder, server, port, proxy_info); if (r != NEWSNNTP_NO_ERROR) { log_error(LOG_PROTOCOL, _("Error logging in to %s:%d...\n"), server, port); @@ -381,8 +394,7 @@ static Session *news_session_new_for_folder(Folder *folder) #ifdef USE_GNUTLS port = ac->set_nntpport ? ac->nntpport : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT; - session = news_session_new(folder, ac->nntp_server, port, userid, passwd, - ac->ssl_nntp); + session = news_session_new(folder, ac, port, ac->ssl_nntp); #else if (ac->ssl_nntp != SSL_NONE) { if (alertpanel_full(_("Insecure connection"), @@ -398,7 +410,7 @@ static Session *news_session_new_for_folder(Folder *folder) return NULL; } port = ac->set_nntpport ? ac->nntpport : NNTP_PORT; - session = news_session_new(folder, ac->nntp_server, port, userid, passwd); + session = news_session_new(folder, ac, port); #endif if (ac->use_nntp_auth && ac->userid && ac->userid[0]) { diff --git a/src/passwordstore.h b/src/passwordstore.h @@ -83,5 +83,9 @@ gchar *passwd_store_get_account(gint account_id, const gchar *block_name); #define PWS_ACCOUNT_SEND "send" #define PWS_ACCOUNT_RECV_CERT "recv_cert" #define PWS_ACCOUNT_SEND_CERT "send_cert" +#define PWS_ACCOUNT_PROXY_PASS "proxy_pass" + +#define PWS_CORE_PROXY "proxy" +#define PWS_CORE_PROXY_PASS "proxy_pass" #endif /* __PASSWORDSTORE_H */ diff --git a/src/plugins/managesieve/managesieve.c b/src/plugins/managesieve/managesieve.c @@ -31,6 +31,7 @@ #include "utils.h" #include "log.h" #include "session.h" +#include "prefs_common.h" #include "managesieve.h" #include "sieve_editor.h" @@ -994,11 +995,30 @@ static void sieve_connect_finished(Session *session, gboolean success) static gint sieve_session_connect(SieveSession *session) { + PrefsAccount *ac = session->account; + ProxyInfo *proxy_info = NULL; + session->state = SIEVE_CAPABILITIES; session->authenticated = FALSE; #ifdef USE_GNUTLS session->tls_init_done = FALSE; #endif + + if (ac->use_proxy) { + if (ac->use_default_proxy) { + proxy_info = (ProxyInfo *)&(prefs_common.proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY, + PWS_CORE_PROXY_PASS); + } else { + proxy_info = (ProxyInfo *)&(ac->proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get_account(ac->account_id, + PWS_ACCOUNT_PROXY_PASS); + } + } + SESSION(session)->proxy_info = proxy_info; + return session_connect(SESSION(session), session->host, session->port); } diff --git a/src/prefs_account.c b/src/prefs_account.c @@ -273,6 +273,24 @@ typedef struct SSLPage GtkWidget *use_nonblocking_ssl_checkbtn; } SSLPage; +typedef struct ProxyPage +{ + PrefsPage page; + + GtkWidget *vbox; + + GtkWidget *proxy_checkbtn; + GtkWidget *default_proxy_checkbtn; + GtkWidget *socks4_radiobtn; + GtkWidget *socks5_radiobtn; + GtkWidget *proxy_host_entry; + GtkWidget *proxy_port_spinbtn; + GtkWidget *proxy_auth_checkbtn; + GtkWidget *proxy_name_entry; + GtkWidget *proxy_pass_entry; + GtkWidget *proxy_send_checkbtn; +} ProxyPage; + typedef struct AdvancedPage { PrefsPage page; @@ -321,6 +339,7 @@ static PrivacyPage privacy_page; #ifdef USE_GNUTLS static SSLPage ssl_page; #endif +static ProxyPage proxy_page; static AdvancedPage advanced_page; struct BasicProtocol { @@ -788,6 +807,49 @@ static PrefParam ssl_param[] = { {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} }; +static PrefParam proxy_param[] = { + /* SOCKS proxy */ + {"use_proxy", "FALSE", &tmp_ac_prefs.use_proxy, P_BOOL, + &proxy_page.proxy_checkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"use_default_proxy", "TRUE", &tmp_ac_prefs.use_default_proxy, P_BOOL, + &proxy_page.default_proxy_checkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"use_proxy_for_send", "TRUE", &tmp_ac_prefs.use_proxy_for_send, P_BOOL, + &proxy_page.proxy_send_checkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"proxy_type", "1", &tmp_ac_prefs.proxy_info.proxy_type, P_ENUM, + &proxy_page.socks4_radiobtn, + prefs_account_enum_set_data_from_radiobtn, + prefs_account_enum_set_radiobtn}, + + {"proxy_host", "localhost", &tmp_ac_prefs.proxy_info.proxy_host, P_STRING, + &proxy_page.proxy_host_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"proxy_port", "1080", &tmp_ac_prefs.proxy_info.proxy_port, P_USHORT, + &proxy_page.proxy_port_spinbtn, + prefs_set_data_from_spinbtn, prefs_set_spinbtn}, + + {"use_proxy_auth", "FALSE", &tmp_ac_prefs.proxy_info.use_proxy_auth, P_BOOL, + &proxy_page.proxy_auth_checkbtn, + prefs_set_data_from_toggle, prefs_set_toggle}, + + {"proxy_name", "", &tmp_ac_prefs.proxy_info.proxy_name, P_STRING, + &proxy_page.proxy_name_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + {"proxy_pass", "", &tmp_ac_prefs.proxy_info.proxy_pass, P_PASSWORD, + &proxy_page.proxy_pass_entry, + prefs_set_data_from_entry, prefs_set_entry}, + + + {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} +}; + static PrefParam advanced_param[] = { {"set_smtpport", "FALSE", &tmp_ac_prefs.set_smtpport, P_BOOL, &advanced_page.smtpport_checkbtn, @@ -2369,8 +2431,6 @@ static void privacy_create_widget_func(PrefsPage * _page, page->page.widget = vbox1; } - -#ifdef USE_GNUTLS #define CREATE_RADIO_BUTTON(box, btn, btn_p, label, data) \ { \ @@ -2383,6 +2443,8 @@ static void privacy_create_widget_func(PrefsPage * _page, GINT_TO_POINTER (data)); \ } +#ifdef USE_GNUTLS + #define CREATE_RADIO_BUTTONS(box, \ btn1, btn1_label, btn1_data, \ btn2, btn2_label, btn2_data, \ @@ -2712,9 +2774,150 @@ static void ssl_create_widget_func(PrefsPage * _page, } #undef CREATE_RADIO_BUTTONS -#undef CREATE_RADIO_BUTTON #endif /* USE_GNUTLS */ - + +static void proxy_create_widget_func(PrefsPage * _page, + GtkWindow * window, + gpointer data) +{ + ProxyPage *page = (ProxyPage *) _page; + PrefsAccount *ac_prefs = (PrefsAccount *) data; + GtkWidget *vbox1, *vbox2, *vbox3, *vbox4; + GtkWidget *proxy_frame; + GtkWidget *proxy_checkbtn; + GtkWidget *default_proxy_checkbtn; + GtkWidget *hbox2; + GtkWidget *label; + GtkWidget *socks4_radiobtn; + GtkWidget *socks5_radiobtn; + GtkWidget *proxy_host_entry; + GtkWidget *proxy_port_spinbtn; + GtkWidget *proxy_auth_checkbtn; + GtkWidget *proxy_name_entry; + GtkWidget *proxy_pass_entry; + GtkWidget *proxy_send_checkbtn; + gchar *buf; + + vbox1 = gtk_vbox_new (FALSE, VSPACING); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), VBOX_BORDER); + + proxy_checkbtn = gtk_check_button_new_with_label (_("Use proxy server for this account")); + PACK_FRAME (vbox1, proxy_frame, NULL); + gtk_frame_set_label_widget (GTK_FRAME(proxy_frame), proxy_checkbtn); + + vbox2 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_container_add (GTK_CONTAINER (proxy_frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 8); + + default_proxy_checkbtn = + gtk_check_button_new_with_label (C_("In account preferences, referring to whether or not use proxy settings from common preferences", "Use default settings")); + CLAWS_SET_TIP(default_proxy_checkbtn, + _("Use proxy server settings from common preferences.")); + gtk_box_pack_start (GTK_BOX (vbox2), default_proxy_checkbtn, FALSE, FALSE, 0); + + vbox3 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_box_pack_start (GTK_BOX (vbox2), vbox3, FALSE, FALSE, 0); + + hbox2 = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox3), hbox2, FALSE, FALSE, 0); + + socks4_radiobtn = gtk_radio_button_new_with_label(NULL, "SOCKS4"); + gtk_box_pack_start (GTK_BOX (hbox2), socks4_radiobtn, FALSE, FALSE, 0); + g_object_set_data(G_OBJECT(socks4_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER(PROXY_SOCKS4)); + + CREATE_RADIO_BUTTON(hbox2, socks5_radiobtn, socks4_radiobtn, "SOCKS5", + PROXY_SOCKS5); + + hbox2 = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox3), hbox2, FALSE, FALSE, 0); + + label = gtk_label_new(_("Hostname")); + gtk_box_pack_start(GTK_BOX(hbox2), label, FALSE, FALSE, 0); + + proxy_host_entry = gtk_entry_new(); + gtk_widget_set_size_request(proxy_host_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start(GTK_BOX(hbox2), proxy_host_entry, TRUE, TRUE, 0); + + label = gtk_label_new(_("Port")); + gtk_box_pack_start(GTK_BOX(hbox2), label, FALSE, FALSE, 0); + + proxy_port_spinbtn = gtk_spin_button_new_with_range(0, 65535, 1080); + gtk_widget_set_size_request(proxy_port_spinbtn, 64, -1); + gtk_box_pack_start(GTK_BOX(hbox2), proxy_port_spinbtn, FALSE, FALSE, 0); + + vbox4 = gtk_vbox_new (FALSE, VSPACING_NARROW); + gtk_box_pack_start(GTK_BOX(vbox3), vbox4, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON (vbox4, proxy_auth_checkbtn, _("Use authentication")); + + hbox2 = gtk_hbox_new (FALSE, 8); + gtk_box_pack_start (GTK_BOX (vbox4), hbox2, FALSE, FALSE, 0); + + label = gtk_label_new(_("Username")); + gtk_box_pack_start(GTK_BOX(hbox2), label, FALSE, FALSE, 0); + + proxy_name_entry = gtk_entry_new(); + gtk_widget_set_size_request(proxy_name_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start(GTK_BOX(hbox2), proxy_name_entry, TRUE, TRUE, 0); + + label = gtk_label_new(_("Password")); + gtk_box_pack_start(GTK_BOX(hbox2), label, FALSE, FALSE, 0); + + proxy_pass_entry = gtk_entry_new(); + gtk_widget_set_size_request(proxy_pass_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_entry_set_visibility(GTK_ENTRY(proxy_pass_entry), FALSE); + gtk_box_pack_start(GTK_BOX(hbox2), proxy_pass_entry, TRUE, TRUE, 0); + + gtk_box_pack_start(GTK_BOX(vbox2), gtk_hseparator_new(), FALSE, FALSE, 0); + + PACK_CHECK_BUTTON(vbox2, proxy_send_checkbtn, + _("Use proxy server for sending")); + CLAWS_SET_TIP(proxy_send_checkbtn, + _("If disabled, messages will be sent using direct connection to configured outgoing server, bypassing any configured proxy server.")); + + SET_TOGGLE_SENSITIVITY(proxy_auth_checkbtn, hbox2); + SET_TOGGLE_SENSITIVITY(socks5_radiobtn, vbox4); + SET_TOGGLE_SENSITIVITY(proxy_checkbtn, vbox2); + SET_TOGGLE_SENSITIVITY_REVERSE(default_proxy_checkbtn, vbox3); + + gtk_widget_show_all(vbox1); + + page->proxy_checkbtn = proxy_checkbtn; + page->default_proxy_checkbtn = default_proxy_checkbtn; + page->socks4_radiobtn = socks4_radiobtn; + page->socks5_radiobtn = socks5_radiobtn; + page->proxy_host_entry = proxy_host_entry; + page->proxy_port_spinbtn = proxy_port_spinbtn; + page->proxy_auth_checkbtn = proxy_auth_checkbtn; + page->proxy_name_entry = proxy_name_entry; + page->proxy_pass_entry = proxy_pass_entry; + page->proxy_send_checkbtn = proxy_send_checkbtn; + page->vbox = vbox1; + page->page.widget = vbox1; + + tmp_ac_prefs = *ac_prefs; + + if (new_account) { + prefs_set_dialog_to_default(proxy_param); + } else + prefs_set_dialog(proxy_param); + + /* Passwords are handled outside of PrefParams. */ + buf = passwd_store_get_account(ac_prefs->account_id, + PWS_ACCOUNT_PROXY_PASS); + gtk_entry_set_text(GTK_ENTRY(page->proxy_pass_entry), + buf != NULL ? buf : ""); + if (buf != NULL) { + memset(buf, 0, strlen(buf)); + g_free(buf); + } + + page->vbox = vbox1; + + page->page.widget = vbox1; +} + static void advanced_create_widget_func(PrefsPage * _page, GtkWindow * window, gpointer data) @@ -3129,6 +3332,19 @@ static gint prefs_ssl_apply(void) } #endif +static gint prefs_proxy_apply(void) +{ + prefs_set_data_from_dialog(proxy_param); + + /* Passwords are stored outside of PrefParams. */ + passwd_store_set_account(tmp_ac_prefs.account_id, + PWS_ACCOUNT_PROXY_PASS, + gtk_entry_get_text(GTK_ENTRY(proxy_page.proxy_pass_entry)), + FALSE); + + return 0; +} + static gint prefs_advanced_apply(void) { prefs_set_data_from_dialog(advanced_param); @@ -3172,6 +3388,11 @@ static void ssl_destroy_widget_func(PrefsPage *_page) } #endif +static void proxy_destroy_widget_func(PrefsPage *_page) +{ + /* ProxyPage *page = (ProxyPage *) _page; */ +} + static void advanced_destroy_widget_func(PrefsPage *_page) { /* AdvancedPage *page = (AdvancedPage *) _page; */ @@ -3249,6 +3470,16 @@ static gboolean ssl_can_close_func(PrefsPage *_page) } #endif +static gboolean proxy_can_close_func(PrefsPage *_page) +{ + ProxyPage *page = (ProxyPage *) _page; + + if (!page->page.page_open) + return TRUE; + + return prefs_proxy_apply() >= 0; +} + static gboolean advanced_can_close_func(PrefsPage *_page) { AdvancedPage *page = (AdvancedPage *) _page; @@ -3350,6 +3581,17 @@ static void ssl_save_func(PrefsPage *_page) } #endif +static void proxy_save_func(PrefsPage *_page) +{ + ProxyPage *page = (ProxyPage *) _page; + + if (!page->page.page_open) + return; + + if (prefs_proxy_apply() >= 0) + cancelled = FALSE; +} + static void advanced_save_func(PrefsPage *_page) { AdvancedPage *page = (AdvancedPage *) _page; @@ -3563,6 +3805,24 @@ static gboolean sslcert_get_password(gpointer source, gpointer data) } #endif +static void register_proxy_page(void) +{ + static gchar *path[3]; + + path[0] = _("Account"); + path[1] = _("Proxy"); + path[2] = NULL; + + proxy_page.page.path = path; + proxy_page.page.weight = 1000.0; + proxy_page.page.create_widget = proxy_create_widget_func; + proxy_page.page.destroy_widget = proxy_destroy_widget_func; + proxy_page.page.save_page = proxy_save_func; + proxy_page.page.can_close = proxy_can_close_func; + + prefs_account_register_page((PrefsPage *) &proxy_page); +} + static void register_advanced_page(void) { static gchar *path[3]; @@ -3594,6 +3854,7 @@ void prefs_account_init() hooks_register_hook(SSLCERT_GET_CLIENT_CERT_HOOKLIST, sslcert_get_client_cert_hook, NULL); hooks_register_hook(SSL_CERT_GET_PASSWORD, sslcert_get_password, NULL); #endif + register_proxy_page(); register_advanced_page(); } @@ -3610,6 +3871,7 @@ PrefsAccount *prefs_account_new(void) prefs_set_default(templates_param); prefs_set_default(privacy_param); prefs_set_default(ssl_param); + prefs_set_default(proxy_param); prefs_set_default(advanced_param); *ac_prefs = tmp_ac_prefs; ac_prefs->account_id = prefs_account_get_new_id(); @@ -3652,6 +3914,7 @@ PrefsAccount *prefs_account_new_from_config(const gchar *label) prefs_read_config(templates_param, label, rcpath, NULL); prefs_read_config(privacy_param, label, rcpath, NULL); prefs_read_config(ssl_param, label, rcpath, NULL); + prefs_read_config(proxy_param, label, rcpath, NULL); prefs_read_config(advanced_param, label, rcpath, NULL); g_free(rcpath); @@ -3781,6 +4044,7 @@ void prefs_account_write_config_all(GList *account_list) WRITE_PARAM(templates_param) WRITE_PARAM(privacy_param) WRITE_PARAM(ssl_param) + WRITE_PARAM(proxy_param) WRITE_PARAM(advanced_param) g_free(privacy_prefs); @@ -3824,6 +4088,7 @@ void prefs_account_free(PrefsAccount *ac_prefs) prefs_free(templates_param); prefs_free(privacy_param); prefs_free(ssl_param); + prefs_free(proxy_param); prefs_free(advanced_param); } diff --git a/src/prefs_account.h b/src/prefs_account.h @@ -204,6 +204,12 @@ struct _PrefsAccount /* Unique account ID */ gint account_id; + /* SOCKS proxy */ + gboolean use_proxy; + gboolean use_default_proxy; + gboolean use_proxy_for_send; + ProxyInfo proxy_info; + struct _Folder *folder; GHashTable *privacy_prefs; SMTPSession *session; diff --git a/src/prefs_common.c b/src/prefs_common.c @@ -60,6 +60,7 @@ #include "stock_pixmap.h" #include "prefswindow.h" #include "colorlabel.h" +#include "passwordstore.h" #ifndef USE_ALT_ADDRBOOK #include "addrcustomattr.h" #endif @@ -1255,6 +1256,14 @@ static PrefParam param[] = { {"master_passphrase_pbkdf2_rounds", "50000", &prefs_common.master_passphrase_pbkdf2_rounds, P_INT, NULL, NULL, NULL}, #endif + {"use_proxy", "FALSE", &prefs_common.use_proxy, P_BOOL, NULL, NULL, NULL}, + {"proxy_type", "1", &prefs_common.proxy_info.proxy_type, P_ENUM, NULL, NULL, NULL}, + {"proxy_host", "localhost", &prefs_common.proxy_info.proxy_host, P_STRING, NULL, NULL, NULL}, + {"proxy_port", "1080", &prefs_common.proxy_info.proxy_port, P_USHORT, NULL, NULL, NULL}, + {"use_proxy_auth", "FALSE", &prefs_common.proxy_info.use_proxy_auth, P_BOOL, NULL, NULL, NULL}, + {"proxy_name", "", &prefs_common.proxy_info.proxy_name, P_STRING, NULL, NULL, NULL}, + {"proxy_pass", "", &prefs_common.proxy_info.proxy_pass, P_STRING, NULL, NULL, NULL}, + {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL} }; diff --git a/src/prefs_common.h b/src/prefs_common.h @@ -568,6 +568,10 @@ struct _PrefsCommon gchar *master_passphrase_salt; guint master_passphrase_pbkdf2_rounds; #endif + + /* Proxy */ + gboolean use_proxy; + ProxyInfo proxy_info; }; extern PrefsCommon prefs_common; diff --git a/src/prefs_proxy.c b/src/prefs_proxy.c @@ -0,0 +1,237 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2018 the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "common/defs.h" +#include "common/proxy.h" + +#include "gtk/menu.h" + +#include "prefs_common.h" +#include "prefs_gtk.h" +#include "passwordstore.h" + +typedef struct _ProxyPage +{ + PrefsPage page; + + GtkWidget *proxy_checkbtn; + GtkWidget *socks4_radiobtn; + GtkWidget *socks5_radiobtn; + GtkWidget *proxy_host_entry; + GtkWidget *proxy_port_spinbtn; + GtkWidget *proxy_auth_checkbtn; + GtkWidget *proxy_name_entry; + GtkWidget *proxy_pass_entry; +} ProxyPage; + + +static void prefs_proxy_create_widget(PrefsPage *_page, GtkWindow *window, + gpointer data) +{ + ProxyPage *page = (ProxyPage *)_page; + + GtkWidget *vbox0, *vbox1, *vbox2; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *proxy_checkbtn; + GtkWidget *socks4_radiobtn, *socks5_radiobtn; + GtkWidget *proxy_auth_checkbtn; + GtkWidget *proxy_frame; + GtkWidget *proxy_host_entry; + GtkWidget *proxy_port_spinbtn; + GtkWidget *proxy_name_entry; + GtkWidget *proxy_pass_entry; + GtkWidget *button; + gchar *buf; + + vbox0 = gtk_vbox_new(FALSE, VSPACING); + gtk_container_set_border_width(GTK_CONTAINER(vbox0), VBOX_BORDER); + + proxy_checkbtn = gtk_check_button_new_with_label(_("Use proxy server")); + PACK_FRAME(vbox0, proxy_frame, NULL); + gtk_frame_set_label_widget(GTK_FRAME(proxy_frame), proxy_checkbtn); + + vbox1 = gtk_vbox_new(FALSE, VSPACING_NARROW); + gtk_container_set_border_width(GTK_CONTAINER(vbox1), 8); + gtk_container_add(GTK_CONTAINER(proxy_frame), vbox1); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox1), hbox, FALSE, FALSE, 0); + + socks4_radiobtn = gtk_radio_button_new_with_label(NULL, "SOCKS4"); + gtk_box_pack_start(GTK_BOX(hbox), socks4_radiobtn, FALSE, FALSE, 0); + g_object_set_data(G_OBJECT(socks4_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER(PROXY_SOCKS4)); + + socks5_radiobtn = gtk_radio_button_new_with_label_from_widget( + GTK_RADIO_BUTTON(socks4_radiobtn), "SOCKS5"); + gtk_box_pack_start(GTK_BOX(hbox), socks5_radiobtn, FALSE, FALSE, 0); + g_object_set_data(G_OBJECT(socks5_radiobtn), MENU_VAL_ID, + GINT_TO_POINTER(PROXY_SOCKS5)); + + hbox = gtk_hbox_new(FALSE, 8); + gtk_box_pack_start(GTK_BOX(vbox1), hbox, FALSE, FALSE, 0); + + label = gtk_label_new(_("Hostname")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + proxy_host_entry = gtk_entry_new(); + gtk_widget_set_size_request(proxy_host_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start(GTK_BOX(hbox), proxy_host_entry, TRUE, TRUE, 0); + + label = gtk_label_new(_("Port")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + proxy_port_spinbtn = gtk_spin_button_new_with_range(0, 65535, 1080); + gtk_widget_set_size_request(proxy_port_spinbtn, 64, -1); + gtk_box_pack_start(GTK_BOX(hbox), proxy_port_spinbtn, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new(FALSE, VSPACING_NARROW); + gtk_box_pack_start(GTK_BOX(vbox1), vbox2, FALSE, FALSE, 0); + + PACK_CHECK_BUTTON(vbox2, proxy_auth_checkbtn, _("Use authentication")); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0); + + label = gtk_label_new(_("Username")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + proxy_name_entry = gtk_entry_new(); + gtk_widget_set_size_request(proxy_name_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_box_pack_start(GTK_BOX(hbox), proxy_name_entry, TRUE, TRUE, 0); + + label = gtk_label_new(_("Password")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + proxy_pass_entry = gtk_entry_new(); + gtk_widget_set_size_request(proxy_pass_entry, DEFAULT_ENTRY_WIDTH, -1); + gtk_entry_set_visibility(GTK_ENTRY(proxy_pass_entry), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), proxy_pass_entry, TRUE, TRUE, 0); + + gtk_widget_show_all(vbox0); + + SET_TOGGLE_SENSITIVITY(proxy_checkbtn, vbox1); + SET_TOGGLE_SENSITIVITY(proxy_auth_checkbtn, hbox); + SET_TOGGLE_SENSITIVITY(socks5_radiobtn, vbox2); + + /* Set widgets to their correct states, based on prefs. */ + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(proxy_checkbtn), + prefs_common.use_proxy); + if (prefs_common.proxy_info.proxy_type == PROXY_SOCKS4) + button = socks4_radiobtn; + else + button = socks5_radiobtn; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); + gtk_entry_set_text(GTK_ENTRY(proxy_host_entry), + prefs_common.proxy_info.proxy_host); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(proxy_port_spinbtn), + (gdouble)prefs_common.proxy_info.proxy_port); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(proxy_auth_checkbtn), + prefs_common.proxy_info.use_proxy_auth); + gtk_entry_set_text(GTK_ENTRY(proxy_name_entry), + prefs_common.proxy_info.proxy_name); + + buf = + passwd_store_get(PWS_CORE, PWS_CORE_PROXY, PWS_CORE_PROXY_PASS); + gtk_entry_set_text(GTK_ENTRY(proxy_pass_entry), buf != NULL ? buf : ""); + if (buf != NULL) { + memset(buf, 0, strlen(buf)); + g_free(buf); + } + + page->proxy_checkbtn = proxy_checkbtn; + page->socks4_radiobtn = socks4_radiobtn; + page->socks5_radiobtn = socks5_radiobtn; + page->proxy_host_entry = proxy_host_entry; + page->proxy_port_spinbtn = proxy_port_spinbtn; + page->proxy_auth_checkbtn = proxy_auth_checkbtn; + page->proxy_name_entry = proxy_name_entry; + page->proxy_pass_entry = proxy_pass_entry; + page->page.widget = vbox0; +} + + +static void prefs_proxy_destroy_widget(PrefsPage *_page) +{ +} + + +static void prefs_proxy_save(PrefsPage *_page) +{ + ProxyPage *page = (ProxyPage *)_page; + + prefs_common.use_proxy = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(page->proxy_checkbtn)); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->socks4_radiobtn))) + prefs_common.proxy_info.proxy_type = PROXY_SOCKS4; + else + prefs_common.proxy_info.proxy_type = PROXY_SOCKS5; + + g_free(prefs_common.proxy_info.proxy_host); + prefs_common.proxy_info.proxy_host = g_strdup(gtk_entry_get_text( + GTK_ENTRY(page->proxy_host_entry))); + + prefs_common.proxy_info.proxy_port = gtk_spin_button_get_value_as_int( + GTK_SPIN_BUTTON(page->proxy_port_spinbtn)); + + prefs_common.proxy_info.use_proxy_auth = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(page->proxy_auth_checkbtn)); + + g_free(prefs_common.proxy_info.proxy_name); + prefs_common.proxy_info.proxy_name = g_strdup(gtk_entry_get_text( + GTK_ENTRY(page->proxy_name_entry))); + + passwd_store_set(PWS_CORE, PWS_CORE_PROXY, PWS_CORE_PROXY_PASS, + gtk_entry_get_text(GTK_ENTRY(page->proxy_pass_entry)), FALSE); +} + + +ProxyPage *prefs_proxy; + +void prefs_proxy_init(void) +{ + ProxyPage *page; + static gchar *path[3]; + + path[0] = _("Other"); + path[1] = _("Proxy"); + path[2] = NULL; + + page = g_new0(ProxyPage, 1); + page->page.path = path; + page->page.create_widget = prefs_proxy_create_widget; + page->page.destroy_widget = prefs_proxy_destroy_widget; + page->page.save_page = prefs_proxy_save; + page->page.weight = 5.0; + + prefs_gtk_register_page((PrefsPage *)page); + prefs_proxy = page; +} + +void prefs_proxy_done(void) +{ + prefs_gtk_unregister_page((PrefsPage *)prefs_proxy); + g_free(prefs_proxy); +} diff --git a/src/prefs_proxy.h b/src/prefs_proxy.h @@ -0,0 +1,26 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2018 the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef PREFS_PROXY_H +#define PREFS_PROXY_H + +void prefs_proxy_init (void); +void prefs_proxy_done (void); + +#endif /* PREFS_PROXY_H */ diff --git a/src/send_message.c b/src/send_message.c @@ -49,6 +49,7 @@ #include "alertpanel.h" #include "manage_window.h" #include "logwindow.h" +#include "proxy.h" #include "socket.h" #include "utils.h" #include "gtkutils.h" @@ -221,6 +222,7 @@ gint send_message_smtp_full(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp, g MsgFlags flags = {0, 0}; long fp_pos = 0; gchar spec_from[BUFFSIZE]; + ProxyInfo *proxy_info = NULL; cm_return_val_if_fail(ac_prefs != NULL, -1); cm_return_val_if_fail(ac_prefs->address != NULL, -1); @@ -389,10 +391,26 @@ gint send_message_smtp_full(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp, g smtp_session->send_data = (guchar *)get_outgoing_rfc2822_str(fp); smtp_session->send_data_len = strlen((gchar *)smtp_session->send_data); + if (ac_prefs->use_proxy && ac_prefs->use_proxy_for_send) { + if (ac_prefs->use_default_proxy) { + proxy_info = (ProxyInfo *)&(prefs_common.proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY, + PWS_CORE_PROXY_PASS); + } else { + proxy_info = (ProxyInfo *)&(ac_prefs->proxy_info); + if (proxy_info->use_proxy_auth) + proxy_info->proxy_pass = passwd_store_get_account(ac_prefs->account_id, + PWS_ACCOUNT_PROXY_PASS); + } + } + SESSION(smtp_session)->proxy_info = proxy_info; + session_set_timeout(session, prefs_common.io_timeout_secs * 1000); /* connect if necessary */ - if (!was_inited && session_connect(session, ac_prefs->smtp_server, port) < 0) { + if (!was_inited && session_connect(session, ac_prefs->smtp_server, + port) < 0) { session_destroy(session); send_progress_dialog_destroy(send_dialog); ac_prefs->session = NULL;