#include "Ecore_Con.h"
#include "ecore_con_private.h"
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0 /* noop */
+#endif
+
static Eina_Bool _ecore_con_client_timer(Ecore_Con_Client *cl);
static void _ecore_con_cl_timer_update(Ecore_Con_Client *cl);
static Eina_Bool _ecore_con_server_timer(Ecore_Con_Server *svr);
EAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_CONNECT = 0;
EAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY = 0;
EAPI Eina_Error EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST = 0;
+EAPI Eina_Error EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = 0;
static Eina_List *servers = NULL;
static int _ecore_con_init_count = 0;
EFL_NET_DIALER_ERROR_COULDNT_CONNECT = eina_error_msg_static_register("Couldn't connect to server");
EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY = eina_error_msg_static_register("Couldn't resolve proxy name");
EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST = eina_error_msg_static_register("Couldn't resolve host name");
+ EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED = eina_error_msg_static_register("Proxy authentication failed");
eina_magic_string_set(ECORE_MAGIC_CON_SERVER, "Ecore_Con_Server");
eina_magic_string_set(ECORE_MAGIC_CON_CLIENT, "Ecore_Con_Client");
{
*port = '\0';
port++;
+ if (*port == '\0') port = NULL;
}
}
return EINA_TRUE;
}
+static void
+_cleanup_close(void *data)
+{
+ int *p_fd = data;
+ int fd = *p_fd;
+ *p_fd = -1;
+ if (fd >= 0) close(fd);
+}
int
efl_net_socket4(int domain, int type, int protocol, Eina_Bool close_on_exec)
{
- int fd;
+ int fd = -1;
#ifdef SOCK_CLOEXEC
if (close_on_exec) type |= SOCK_CLOEXEC;
#endif
fd = socket(domain, type, protocol);
- if (fd < 0) return fd;
-
#ifndef SOCK_CLOEXEC
- if (close_on_exec)
+ EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+ if (fd > 0)
{
- if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
+ if (close_on_exec)
{
- int errno_bkp = errno;
- ERR("fcntl(%d, F_SETFD, FD_CLOEXEC): %s", fd, strerror(errno));
- close(fd);
- errno = errno_bkp;
- return -1;
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
+ {
+ int errno_bkp = errno;
+ ERR("fcntl(%d, F_SETFD, FD_CLOEXEC): %s", fd, strerror(errno));
+ close(fd);
+ fd = -1;
+ errno = errno_bkp;
+ }
}
}
+ EINA_THREAD_CLEANUP_POP(EINA_FALSE); /* we need fd on success */
#endif
return fd;
}
-typedef struct _Efl_Net_Resolve_Async_Data
+typedef struct _Efl_Net_Connect_Async_Data
{
- Efl_Net_Resolve_Async_Cb cb;
+ Efl_Net_Connect_Async_Cb cb;
const void *data;
- char *host;
- char *port;
- struct addrinfo *result;
- struct addrinfo *hints;
- int gai_error;
-} Efl_Net_Resolve_Async_Data;
+ socklen_t addrlen;
+ Eina_Bool close_on_exec;
+ int type;
+ int protocol;
+ int sockfd;
+ Eina_Error error;
+ struct sockaddr addr[];
+} Efl_Net_Connect_Async_Data;
static void
-_efl_net_resolve_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
{
- Efl_Net_Resolve_Async_Data *d = data;
+ Efl_Net_Connect_Async_Data *d = data;
+ char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+ int r;
/* allows ecore_thread_cancel() to cancel at some points, see
* man:pthreads(7).
*
* no need to set cleanup functions since the main thread will
- * handle that with _efl_net_resolve_async_cancel().
+ * handle that with _efl_net_connect_async_cancel().
*/
eina_thread_cancellable_set(EINA_TRUE, NULL);
- while (EINA_TRUE)
- {
- DBG("resolving host='%s' port='%s'", d->host, d->port);
- d->gai_error = getaddrinfo(d->host, d->port, d->hints, &d->result);
- if (d->gai_error == 0) break;
- if (d->gai_error == EAI_AGAIN) continue;
+ d->error = 0;
- DBG("getaddrinfo(\"%s\", \"%s\") failed: %s", d->host, d->port, gai_strerror(d->gai_error));
- break;
+ /* always close-on-exec since it's not a point to pass an
+ * under construction socket to a child process.
+ */
+ d->sockfd = efl_net_socket4(d->addr->sa_family, d->type, d->protocol, EINA_TRUE);
+ if (d->sockfd < 0)
+ {
+ d->error = errno;
+ DBG("socket(%d, %d, %d) failed: %s", d->addr->sa_family, d->type, d->protocol, strerror(errno));
+ return;
}
- eina_thread_cancellable_set(EINA_FALSE, NULL);
-
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+ efl_net_ip_port_fmt(buf, sizeof(buf), d->addr);
+
+ DBG("connecting fd=%d to %s", d->sockfd, buf);
+
+ r = connect(d->sockfd, d->addr, d->addrlen);
+ if (r < 0)
{
- char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
- const struct addrinfo *addrinfo;
- for (addrinfo = d->result; addrinfo != NULL; addrinfo = addrinfo->ai_next)
- {
- if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
- DBG("resolved host='%s' port='%s': %s", d->host, d->port, buf);
- }
+ int fd = d->sockfd;
+ d->error = errno;
+ d->sockfd = -1;
+ /* close() is a cancellation point, thus unset sockfd before
+ * closing, so the main thread _efl_net_connect_async_cancel()
+ * won't close it again.
+ */
+ close(fd);
+ DBG("connect(%d, %s) failed: %s", fd, buf, strerror(errno));
+ return;
}
+
+ DBG("connected fd=%d to %s", d->sockfd, buf);
}
static void
-_efl_net_resolve_async_data_free(Efl_Net_Resolve_Async_Data *d)
+_efl_net_connect_async_data_free(Efl_Net_Connect_Async_Data *d)
{
- free(d->hints);
- free(d->host);
- free(d->port);
free(d);
}
static void
-_efl_net_resolve_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
{
- Efl_Net_Resolve_Async_Data *d = data;
- d->cb((void *)d->data, d->host, d->port, d->hints, d->result, d->gai_error);
- _efl_net_resolve_async_data_free(d);
+ Efl_Net_Connect_Async_Data *d = data;
+
+#ifdef FD_CLOEXEC
+ /* if it wasn't a close on exec, release the socket to be passed to child */
+ if ((!d->close_on_exec) && (d->sockfd >= 0))
+ {
+ int flags = fcntl(d->sockfd, F_GETFD);
+ if (flags < 0)
+ {
+ d->error = errno;
+ ERR("fcntl(%d, F_GETFD): %s", d->sockfd, strerror(errno));
+ close(d->sockfd);
+ d->sockfd = -1;
+ }
+ else
+ {
+ flags &= (~FD_CLOEXEC);
+ if (fcntl(d->sockfd, F_SETFD, flags) < 0)
+ {
+ d->error = errno;
+ ERR("fcntl(%d, F_SETFD, %#x): %s", d->sockfd, flags, strerror(errno));
+ close(d->sockfd);
+ d->sockfd = -1;
+ }
+ }
+ }
+#endif
+ d->cb((void *)d->data, d->addr, d->addrlen, d->sockfd, d->error);
+ _efl_net_connect_async_data_free(d);
}
static void
-_efl_net_resolve_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
{
- Efl_Net_Resolve_Async_Data *d = data;
- if (d->result) freeaddrinfo(d->result);
- _efl_net_resolve_async_data_free(d);
+ Efl_Net_Connect_Async_Data *d = data;
+ if (d->sockfd >= 0) close(d->sockfd);
+ _efl_net_connect_async_data_free(d);
}
Ecore_Thread *
-efl_net_resolve_async_new(const char *host, const char *port, const struct addrinfo *hints, Efl_Net_Resolve_Async_Cb cb, const void *data)
+efl_net_connect_async_new(const struct sockaddr *addr, socklen_t addrlen, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data)
{
- Efl_Net_Resolve_Async_Data *d;
+ Efl_Net_Connect_Async_Data *d;
- EINA_SAFETY_ON_NULL_RETURN_VAL(host, NULL);
- EINA_SAFETY_ON_NULL_RETURN_VAL(port, NULL);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(addr, NULL);
+ EINA_SAFETY_ON_TRUE_RETURN_VAL(addrlen < 1, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
- d = malloc(sizeof(Efl_Net_Resolve_Async_Data));
+ d = malloc(sizeof(Efl_Net_Connect_Async_Data) + addrlen);
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
d->cb = cb;
d->data = data;
- d->host = strdup(host);
- EINA_SAFETY_ON_NULL_GOTO(d->host, failed_host);
- d->port = strdup(port);
- EINA_SAFETY_ON_NULL_GOTO(d->port, failed_port);
+ d->addrlen = addrlen;
+ d->close_on_exec = close_on_exec;
+ d->type = type;
+ d->protocol = protocol;
+ memcpy(d->addr, addr, addrlen);
- if (!hints) d->hints = NULL;
- else
+ d->sockfd = -1;
+ d->error = 0;
+
+ return ecore_thread_run(_efl_net_connect_async_run,
+ _efl_net_connect_async_end,
+ _efl_net_connect_async_cancel,
+ d);
+}
+
+static Eina_Bool
+_efl_net_ip_no_proxy(const char *host, char * const *no_proxy_strv)
+{
+ char * const *itr;
+ size_t host_len;
+
+ if (!no_proxy_strv)
+ return EINA_FALSE;
+
+ host_len = strlen(host);
+ for (itr = no_proxy_strv; *itr != NULL; itr++)
{
- d->hints = malloc(sizeof(struct addrinfo));
- EINA_SAFETY_ON_NULL_GOTO(d->hints, failed_hints);
- memcpy(d->hints, hints, sizeof(struct addrinfo));
- }
+ const char *s = *itr;
+ size_t slen;
- d->result = NULL;
+ /* '*' is not a glob/pattern, it matches all */
+ if (*s == '*') return EINA_TRUE;
- return ecore_thread_run(_efl_net_resolve_async_run,
- _efl_net_resolve_async_end,
- _efl_net_resolve_async_cancel,
- d);
+ /* old timers use leading dot to avoid matching partial names
+ * due implementation bugs not required anymore
+ */
+ if (*s == '.') s++;
- failed_hints:
- free(d->port);
- failed_port:
- free(d->host);
- failed_host:
- free(d);
- return NULL;
+ slen = strlen(s);
+ if (slen == 0) continue;
+
+ if (host_len < slen) continue;
+ if (memcmp(host + host_len - slen, s, slen) == 0)
+ {
+ if (slen == host_len)
+ return EINA_TRUE;
+ if (host[host_len - slen - 1] == '.')
+ return EINA_TRUE;
+ }
+ }
+
+ return EINA_FALSE;
}
-typedef struct _Efl_Net_Connect_Async_Data
+typedef struct _Efl_Net_Ip_Connect_Async_Data
{
Efl_Net_Connect_Async_Cb cb;
const void *data;
+ char *address;
+ char *proxy;
+ char *proxy_env;
+ char **no_proxy_strv;
socklen_t addrlen;
Eina_Bool close_on_exec;
int type;
int protocol;
int sockfd;
Eina_Error error;
- struct sockaddr addr[];
-} Efl_Net_Connect_Async_Data;
+ union {
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+ struct sockaddr addr;
+ };
+} Efl_Net_Ip_Connect_Async_Data;
+
+static Eina_Error
+_efl_net_ip_connect(const struct addrinfo *addr, int *sockfd)
+{
+ int fd = -1;
+ Eina_Error ret = 0;
+
+ /* always close-on-exec since it's not a point to pass an
+ * under construction socket to a child process.
+ */
+ fd = efl_net_socket4(addr->ai_family, addr->ai_socktype, addr->ai_protocol, EINA_TRUE);
+ if (fd < 0) ret = errno;
+ else
+ {
+ char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+ int r;
+
+ EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+ if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+ {
+ if (efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr))
+ DBG("connect fd=%d to %s", fd, buf);
+ }
+
+ r = connect(fd, addr->ai_addr, addr->ai_addrlen);
+ if (r == 0)
+ {
+ DBG("connected fd=%d to %s", fd, buf);
+ *sockfd = fd;
+ }
+ else
+ {
+ ret = errno;
+ DBG("couldn't connect fd=%d to %s: %s", fd, buf, strerror(errno));
+ close(fd);
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_FALSE); /* we need sockfd on success */
+ }
+ return ret;
+}
+
+static Eina_Error
+_efl_net_ip_resolve_and_connect(const char *host, const char *port, int type, int protocol, int *sockfd, struct sockaddr *addr, socklen_t *p_addrlen)
+{
+ struct addrinfo *results = NULL;
+ struct addrinfo hints = {
+ .ai_socktype = type,
+ .ai_protocol = protocol,
+ .ai_family = AF_UNSPEC,
+ };
+ Eina_Error ret = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+ int r;
+
+ if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+ do
+ r = getaddrinfo(host, port, &hints, &results);
+ while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+
+ if (r != 0)
+ {
+ DBG("couldn't resolve host='%s', port='%s': %s",
+ host, port, gai_strerror(r));
+ ret = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+ *sockfd = -1;
+ }
+ else
+ {
+ const struct addrinfo *addrinfo;
+
+ EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+ for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+ {
+ if (addrinfo->ai_socktype != type) continue;
+ if (addrinfo->ai_protocol != protocol) continue;
+ ret = _efl_net_ip_connect(addrinfo, sockfd);
+ if (ret == 0)
+ {
+ memcpy(addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+ *p_addrlen = addrinfo->ai_addrlen;
+ break;
+ }
+ }
+ if (ret != 0)
+ ret = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+ }
+ return ret;
+}
static void
-_efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_ip_connect_async_run_direct(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port)
{
- Efl_Net_Connect_Async_Data *d = data;
- char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
+ DBG("direct connection to %s:%s", host, port);
+ d->error = _efl_net_ip_resolve_and_connect(host, port, d->type, d->protocol, &d->sockfd, &d->addr, &d->addrlen);
+}
+
+static Eina_Bool
+_efl_net_ip_port_user_pass_split(char *buf, const char **p_host, const char **p_port, const char **p_user, const char **p_pass)
+{
+ char *p;
+
+ p = strchr(buf, '@');
+ if (!p)
+ {
+ p = buf;
+ *p_user = NULL;
+ *p_pass = NULL;
+ }
+ else
+ {
+ char *s;
+ *p_user = buf;
+ *p = '\0';
+ p++;
+
+ s = strchr(*p_user, ':');
+ if (!s)
+ *p_pass = NULL;
+ else
+ {
+ *s = '\0';
+ s++;
+ *p_pass = s;
+ }
+ }
+
+ return efl_net_ip_port_split(p, p_host, p_port);
+}
+
+typedef enum _Efl_Net_Socks4_Request_Command {
+ EFL_NET_SOCKS4_REQUEST_COMMAND_CONNECT = 0x01,
+ EFL_NET_SOCKS4_REQUEST_COMMAND_BIND = 0x02
+} Efl_Net_Socks4_Request_Command;
+
+typedef struct _Efl_Net_Socks4_Request {
+ uint8_t version; /* = 0x4 */
+ uint8_t command; /* Efl_Net_Socks4_Request_Command */
+ uint16_t port;
+ uint8_t ipv4[4];
+ char indent[];
+} Efl_Net_Socks4_Request;
+
+typedef enum _Efl_Net_Socks4_Reply_Status {
+ EFL_NET_SOCKS4_REPLY_STATUS_GRANTED = 0x5a,
+ EFL_NET_SOCKS4_REPLY_STATUS_REJECTED = 0x5b,
+ EFL_NET_SOCKS4_REPLY_STATUS_FAILED_INDENT = 0x5c,
+ EFL_NET_SOCKS4_REPLY_STATUS_FAILED_USER = 0x5d
+} Efl_Net_Socks4_Reply_Status;
+
+typedef struct _Efl_Net_Socks4_Reply {
+ uint8_t null;
+ uint8_t status;
+ uint16_t port;
+ uint8_t ipv4[4];
+} Efl_Net_Socks4_Reply;
+
+static Eina_Bool
+_efl_net_ip_connect_async_run_socks4_try(Efl_Net_Ip_Connect_Async_Data *d, const char *proxy_host, const char *proxy_port, const struct addrinfo *addrinfo, Efl_Net_Socks4_Request *request, size_t request_len)
+{
+ char buf[INET_ADDRSTRLEN + sizeof(":65536")];
+ struct sockaddr_in *a = (struct sockaddr_in *)addrinfo->ai_addr;
+ struct sockaddr_storage proxy_addr;
+ socklen_t proxy_addrlen;
+ int fd;
+ Eina_Error err;
+ Eina_Bool ret = EINA_FALSE;
+ ssize_t s;
+
+ err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+ if (err)
+ {
+ DBG("couldn't connect to socks4://%s:%s: %s", proxy_host, proxy_port, eina_error_msg_get(err));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ return EINA_TRUE; /* no point in continuing on this error */
+ }
+
+ EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+ if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
+ {
+ if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
+ DBG("resolved address='%s' to %s. Connect using fd=%d socks4://%s:%s", d->address, buf, fd, proxy_host, proxy_port);
+ }
+
+ request->port = a->sin_port;
+ memcpy(request->ipv4, &a->sin_addr, 4);
+
+ s = send(fd, request, request_len, MSG_NOSIGNAL);
+ if (s != (ssize_t)request_len)
+ {
+ if (s < 0)
+ DBG("couldn't request connection to host=%s fd=%d socks4://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+ }
+ else
+ {
+ Efl_Net_Socks4_Reply reply;
+ s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+ if (s != sizeof(reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv reply of connection to host=%s fd=%d socks4://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+ }
+ else
+ {
+ if (reply.status != EFL_NET_SOCKS4_REPLY_STATUS_GRANTED)
+ DBG("rejected connection to host=%s fd=%d socks4://%s:%s: reason=%#x", buf, fd, proxy_host, proxy_port, reply.status);
+ else
+ {
+ memcpy(&d->addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+ d->addrlen = addrinfo->ai_addrlen;
+ d->sockfd = fd;
+ d->error = 0;
+ ret = EINA_TRUE;
+ DBG("connected to host=%s fd=%d socks4://%s:%s", buf, fd, proxy_host, proxy_port);
+ }
+ }
+ }
+ EINA_THREAD_CLEANUP_POP(!ret); /* we need fd on success, on failure just close it */
+ return ret;
+}
+
+static void
+_efl_net_ip_connect_async_run_socks4(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+ char *str;
+ const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+ struct addrinfo *results = NULL;
+ struct addrinfo hints = {
+ .ai_socktype = d->type,
+ .ai_protocol = d->protocol,
+ .ai_family = AF_INET,
+ };
int r;
- /* allows ecore_thread_cancel() to cancel at some points, see
- * man:pthreads(7).
- *
- * no need to set cleanup functions since the main thread will
- * handle that with _efl_net_connect_async_cancel().
- */
- eina_thread_cancellable_set(EINA_TRUE, NULL);
+ if (strchr(host, ':'))
+ {
+ DBG("SOCKSv4 only handles IPv4. Wanted host=%s", host);
+ d->error = EAFNOSUPPORT;
+ return;
+ }
- d->error = 0;
+ if ((d->type != SOCK_STREAM) || (d->protocol != IPPROTO_TCP))
+ {
+ DBG("SOCKSv4 only accepts TCP requests. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+ d->error = EPROTONOSUPPORT;
+ return;
+ }
- d->sockfd = efl_net_socket4(d->addr->sa_family, d->type, d->protocol, d->close_on_exec);
- if (d->sockfd < 0)
+ DBG("proxy connection to %s:%s using socks4://%s", host, port, proxy);
+
+ str = strdup(proxy);
+ EINA_THREAD_CLEANUP_PUSH(free, str);
+
+ if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
{
- d->error = errno;
- DBG("socket(%d, %d, %d) failed: %s", d->addr->sa_family, d->type, d->protocol, strerror(errno));
+ ERR("Invalid proxy string: socks4://%s", proxy);
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ goto end;
+ }
+ if (!proxy_user) proxy_user = "";
+ if (!proxy_port) proxy_port = "1080";
+
+ do
+ r = getaddrinfo(host, port, &hints, &results);
+ while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+ if (r != 0)
+ {
+ DBG("couldn't resolve host='%s', port='%s': %s",
+ host, port, gai_strerror(r));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+ }
+ else
+ {
+ const struct addrinfo *addrinfo;
+ Efl_Net_Socks4_Request *request;
+ size_t request_len;
+
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+ EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+
+ request_len = sizeof(Efl_Net_Socks4_Request) + strlen(proxy_user) + 1;
+ request = malloc(request_len);
+ if (request)
+ {
+ request->version = 0x04;
+ request->command = EFL_NET_SOCKS4_REQUEST_COMMAND_CONNECT;
+ memcpy(request->indent, proxy_user, strlen(proxy_user) + 1);
+ EINA_THREAD_CLEANUP_PUSH(free, request);
+ for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+ {
+ if (addrinfo->ai_socktype != d->type) continue;
+ if (addrinfo->ai_protocol != d->protocol) continue;
+ if (addrinfo->ai_family != AF_INET) continue;
+ if (_efl_net_ip_connect_async_run_socks4_try(d, proxy_host, proxy_port, addrinfo, request, request_len))
+ break;
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(request) */
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+ }
+ end:
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+static void
+_efl_net_ip_connect_async_run_socks4a(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+ int fd = -1;
+ char *str;
+ const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+ struct sockaddr_storage proxy_addr;
+ socklen_t proxy_addrlen;
+ Eina_Error err;
+ struct addrinfo *results = NULL;
+ struct addrinfo hints = {
+ .ai_socktype = d->type,
+ .ai_protocol = d->protocol,
+ .ai_family = AF_INET,
+ };
+ int r;
+
+ if (strchr(host, ':'))
+ {
+ DBG("SOCKSv4 only handles IPv4. Wanted host=%s", host);
+ d->error = EAFNOSUPPORT;
return;
}
+ if ((d->type != SOCK_STREAM) || (d->protocol != IPPROTO_TCP))
+ {
+ DBG("SOCKSv4 only accepts TCP requests. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+ d->error = EPROTONOSUPPORT;
+ return;
+ }
+
+ DBG("proxy connection to %s:%s using socks4a://%s", host, port, proxy);
+
+ str = strdup(proxy);
+ EINA_THREAD_CLEANUP_PUSH(free, str);
+
+ if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
+ {
+ ERR("Invalid proxy string: socks4a://%s", proxy);
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ goto end;
+ }
+ if (!proxy_user) proxy_user = "";
+ if (!proxy_port) proxy_port = "1080";
+
+ err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+ if (err)
+ {
+ DBG("couldn't connect to socks4a://%s: %s", proxy, eina_error_msg_get(err));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ goto end;
+ }
+
+ DBG("connected fd=%d to socks4a://%s", fd, proxy);
+ EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+
+ /* we just resolve the port number here */
+ do
+ r = getaddrinfo(NULL, port, &hints, &results);
+ while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+ if (r != 0)
+ {
+ DBG("couldn't resolve port='%s': %s", port, gai_strerror(r));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+ }
+ else
+ {
+ const struct addrinfo *addrinfo;
+ Efl_Net_Socks4_Request *request;
+ size_t request_len;
+
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+ EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+
+ request_len = sizeof(Efl_Net_Socks4_Request) + strlen(proxy_user) + 1 + strlen(host) + 1;
+ request = malloc(request_len);
+ if (request)
+ {
+ request->version = 0x04;
+ request->command = EFL_NET_SOCKS4_REQUEST_COMMAND_CONNECT;
+ memcpy(request->indent, proxy_user, strlen(proxy_user) + 1);
+ memcpy(request->indent + strlen(proxy_user) + 1, host, strlen(host) + 1);
+ EINA_THREAD_CLEANUP_PUSH(free, request);
+ for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+ {
+ struct sockaddr_in *a = (struct sockaddr_in *)addrinfo->ai_addr;
+ ssize_t s;
+
+ if (addrinfo->ai_socktype != d->type) continue;
+ if (addrinfo->ai_protocol != d->protocol) continue;
+ if (addrinfo->ai_family != AF_INET) continue;
+
+ request->port = a->sin_port;
+ request->ipv4[0] = 0;
+ request->ipv4[1] = 0;
+ request->ipv4[2] = 0;
+ request->ipv4[3] = 255;
+
+ s = send(fd, request, request_len, MSG_NOSIGNAL);
+ if (s != (ssize_t)request_len)
+ {
+ if (s < 0)
+ DBG("couldn't send proxy request: %s", strerror(errno));
+ else
+ DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+ }
+ else
+ {
+ Efl_Net_Socks4_Reply reply;
+ s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+ if (s != sizeof(reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv proxy reply: %s", strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+ }
+ else
+ {
+ if (reply.status != EFL_NET_SOCKS4_REPLY_STATUS_GRANTED)
+ DBG("proxy rejected request status=%#x", reply.status);
+ else
+ {
+ d->addr4.sin_family = AF_INET;
+ d->addr4.sin_port = a->sin_port;
+ memcpy(&d->addr4.sin_addr, reply.ipv4, 4);
+ d->addrlen = sizeof(struct sockaddr_in);
+ d->sockfd = fd;
+ d->error = 0;
+ }
+ }
+ }
+ break;
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(request) */
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+ }
+ EINA_THREAD_CLEANUP_POP(d->sockfd == -1); /* we need fd only on success */
+ end:
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+typedef enum _Efl_Net_Socks5_Auth {
+ EFL_NET_SOCKS5_AUTH_NONE = 0x00,
+ EFL_NET_SOCKS5_AUTH_GSSAPI = 0x01,
+ EFL_NET_SOCKS5_AUTH_USER_PASS = 0x02,
+ EFL_NET_SOCKS5_AUTH_FAILED = 0xff
+} Efl_Net_Socks5_Auth;
+
+typedef struct _Efl_Net_Socks5_Greeting {
+ uint8_t version; /* = 0x5 */
+ uint8_t auths_count;
+ uint8_t auths[1]; /* series of Efl_Net_Socks5_Auth */
+} Efl_Net_Socks5_Greeting;
+
+typedef struct _Efl_Net_Socks5_Greeting_Reply {
+ uint8_t version; /* = 0x5 */
+ uint8_t auth; /* Efl_Net_Socks5_Auth */
+} Efl_Net_Socks5_Greeting_Reply;
+
+typedef enum _Efl_Net_Socks5_Request_Command {
+ EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_CONNECT = 0x01,
+ EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_BIND = 0x02,
+ EFL_NET_SOCKS5_REQUEST_COMMAND_UDP_ASSOCIATE = 0x03
+} Efl_Net_Socks5_Request_Command;
+
+typedef enum _Efl_Net_Socks5_Address_Type {
+ EFL_NET_SOCKS5_ADDRESS_TYPE_IPV4 = 0x01,
+ EFL_NET_SOCKS5_ADDRESS_TYPE_NAME = 0x03,
+ EFL_NET_SOCKS5_ADDRESS_TYPE_IPV6 = 0x04
+} Efl_Net_Socks5_Address_Type;
+
+typedef struct _Efl_Net_Socks5_Request {
+ uint8_t version; /* = 0x5 */
+ uint8_t command; /* Efl_Net_Socks5_Command */
+ uint8_t reserved;
+ uint8_t address_type; /* Efl_Net_Socks5_Address_Type */
+ /* follows:
+ * - one of:
+ * - 4 bytes for IPv4
+ * - 16 bytes for IPv6
+ * - 1 byte (size) + N bytes of name
+ * - uint16_t port
+ */
+} Efl_Net_Socks5_Request;
+
+typedef struct _Efl_Net_Socks5_Address_Ipv4 {
+ uint8_t address[4];
+ uint16_t port;
+} Efl_Net_Socks5_Address_Ipv4;
+
+typedef struct _Efl_Net_Socks5_Address_Ipv6 {
+ uint8_t address[16];
+ uint16_t port;
+} Efl_Net_Socks5_Address_Ipv6;
+
+typedef struct _Efl_Net_Socks5_Request_Ipv4 {
+ Efl_Net_Socks5_Request base;
+ Efl_Net_Socks5_Address_Ipv4 ipv4;
+} Efl_Net_Socks5_Request_Ipv4;
+
+ typedef struct _Efl_Net_Socks5_Request_Ipv6 {
+ Efl_Net_Socks5_Request base;
+ Efl_Net_Socks5_Address_Ipv6 ipv6;
+} Efl_Net_Socks5_Request_Ipv6;
+
+static Efl_Net_Socks5_Request *
+efl_net_socks5_request_addr_new(Efl_Net_Socks5_Request_Command command, const struct sockaddr *addr, size_t *p_request_len)
+{
+ if (addr->sa_family == AF_INET)
+ {
+ const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
+ Efl_Net_Socks5_Request_Ipv4 *request;
+
+ *p_request_len = sizeof(Efl_Net_Socks5_Request_Ipv4);
+ request = malloc(*p_request_len);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(request, NULL);
+ request->base.version = 0x05;
+ request->base.command = command;
+ request->base.reserved = 0;
+ request->base.address_type = EFL_NET_SOCKS5_ADDRESS_TYPE_IPV4;
+ memcpy(request->ipv4.address, &a->sin_addr, 4);
+ request->ipv4.port = a->sin_port;
+ return &request->base;
+ }
+ else
+ {
+ const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
+ Efl_Net_Socks5_Request_Ipv6 *request;
+
+ *p_request_len = sizeof(Efl_Net_Socks5_Request_Ipv6);
+ request = malloc(*p_request_len);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(request, NULL);
+ request->base.version = 0x05;
+ request->base.command = command;
+ request->base.reserved = 0;
+ request->base.address_type = EFL_NET_SOCKS5_ADDRESS_TYPE_IPV6;
+ memcpy(request->ipv6.address, &a->sin6_addr, 16);
+ request->ipv6.port = a->sin6_port;
+ return &request->base;
+ }
+}
+
+/* port must be network endianess */
+static Efl_Net_Socks5_Request *
+efl_net_socks5_request_name_new(Efl_Net_Socks5_Request_Command command, const char *name, uint16_t port, size_t *p_request_len)
+{
+ Efl_Net_Socks5_Request *request;
+ uint8_t namelen = strlen(name);
+ uint8_t *p;
+
+ *p_request_len = sizeof(Efl_Net_Socks5_Request) + 1 + namelen + 2;
+ request = malloc(*p_request_len);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(request, NULL);
+ request->version = 0x05;
+ request->command = command;
+ request->reserved = 0;
+ request->address_type = EFL_NET_SOCKS5_ADDRESS_TYPE_NAME;
+
+ p = (uint8_t *)request + sizeof(Efl_Net_Socks5_Request);
+ *p = namelen;
+ p++;
+ memcpy(p, name, namelen);
+ p += namelen;
+
+ memcpy(p, &port, sizeof(port));
+ return request;
+}
+
+typedef enum _Efl_Net_Socks5_Reply_Status {
+ EFL_NET_SOCKS5_REPLY_STATUS_GRANTED = 0x00,
+ EFL_NET_SOCKS5_REPLY_STATUS_GENERAL_FAILURE = 0x01,
+ EFL_NET_SOCKS5_REPLY_STATUS_REJECTED_BY_RULESET = 0x02,
+ EFL_NET_SOCKS5_REPLY_STATUS_NETWORK_UNREACHABLE = 0x03,
+ EFL_NET_SOCKS5_REPLY_STATUS_HOST_UNREACHABLE = 0x04,
+ EFL_NET_SOCKS5_REPLY_STATUS_CONNECTION_REFUSED = 0x05,
+ EFL_NET_SOCKS5_REPLY_STATUS_TTL_EXPIRED = 0x06,
+ EFL_NET_SOCKS5_REPLY_STATUS_PROTOCOL_ERROR = 0x07,
+ EFL_NET_SOCKS5_REPLY_STATUS_ADDRESS_TYPE_UNSUPORTED = 0x08,
+} Efl_Net_Socks5_Reply_Status;
+
+typedef struct _Efl_Net_Socks5_Reply {
+ uint8_t version; /* = 0x5 */
+ uint8_t status;
+ uint8_t null; /* = 0 */
+ uint8_t address_type; /* Efl_Net_Socks5_Address_Type */
+ /* follows:
+ * - one of:
+ * - 4 bytes for IPv4
+ * - 16 bytes for IPv6
+ * - 1 byte (size) + N bytes name
+ * - uint16_t port
+ */
+} Efl_Net_Socks5_Reply;
+
+typedef struct _Efl_Net_Socks5_Reply_Ipv4 {
+ Efl_Net_Socks5_Reply base;
+ Efl_Net_Socks5_Address_Ipv4 ipv4;
+} Efl_Net_Socks5_Reply_Ipv4;
+
+ typedef struct _Efl_Net_Socks5_Reply_Ipv6 {
+ Efl_Net_Socks5_Reply base;
+ Efl_Net_Socks5_Address_Ipv6 ipv6;
+} Efl_Net_Socks5_Reply_Ipv6;
+
+static Eina_Bool
+_efl_net_ip_connect_async_run_socks5_auth_user_pass(int fd, const char *user, const char *pass, const char *proxy_protocol, const char *proxy_host, const char *proxy_port)
+{
+ uint8_t user_len = user ? strlen(user) : 0;
+ uint8_t pass_len = pass ? strlen(pass) : 0;
+ size_t len = 1 + 1 + user_len + 1 + pass_len;
+ char *msg;
+ Eina_Bool ret = EINA_FALSE;
+ ssize_t s;
+
+ msg = malloc(len);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(msg, EINA_FALSE);
+ EINA_THREAD_CLEANUP_PUSH(free, msg);
+
+ msg[0] = 0x01; /* version */
+ msg[1] = user_len;
+ if (user) memcpy(msg + 1 + 1, user, user_len);
+ msg[1 + 1 + user_len] = pass_len;
+ if (pass) memcpy(msg + 1 + 1 + user_len + 1, pass, pass_len);
+
+ DBG("authenticate user='%s' pass=%hhu (bytes) to proxy %s://%s:%s", user, pass_len, proxy_protocol, proxy_host, proxy_port);
+
+ s = send(fd, msg, len, MSG_NOSIGNAL);
+ if (s != (ssize_t)len)
+ {
+ if (s < 0)
+ DBG("couldn't send user-password authentication to fd=%d %s://%s:%s: %s", fd, proxy_protocol, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't send user-password authentication: need %zu, did %zd", len, s);
+ }
+ else
+ {
+ uint8_t reply[2];
+
+ s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+ if (s != (ssize_t)sizeof(reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv user-password authentication reply from fd=%d %s://%s:%s: %s", fd, proxy_protocol, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv user-password authentication reply: need %zu, did %zd", len, s);
+ }
+ else
+ {
+ if (reply[1] != 0)
+ DBG("proxy authentication failed user='%s' pass=%hhu (bytes) to proxy %s://%s:%s: reason=%#x", user, pass_len, proxy_protocol, proxy_host, proxy_port, reply[1]);
+ else
+ {
+ DBG("successfully authenticated user=%s with proxy fd=%d %s://%s:%s", user, fd, proxy_protocol, proxy_host, proxy_port);
+ ret = EINA_TRUE;
+ }
+ }
+ }
+
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+
+ return ret;
+}
+
+static Eina_Bool
+_efl_net_ip_connect_async_run_socks5_try(Efl_Net_Ip_Connect_Async_Data *d, const char *proxy_host, const char *proxy_port, const char *proxy_user, const char *proxy_pass, Efl_Net_Socks5_Request_Command cmd, const struct addrinfo *addrinfo)
+{
+ char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
+ Efl_Net_Socks5_Greeting greeting = {
+ .version = 0x05,
+ .auths_count = 1,
+ .auths = { proxy_user ? EFL_NET_SOCKS5_AUTH_USER_PASS : EFL_NET_SOCKS5_AUTH_NONE },
+ };
+ struct sockaddr_storage proxy_addr;
+ socklen_t proxy_addrlen;
+ int fd;
+ Eina_Error err;
+ Eina_Bool ret = EINA_FALSE;
+ ssize_t s;
+
+ err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+ if (err)
+ {
+ DBG("couldn't connect to socks5://%s:%s: %s", proxy_host, proxy_port, eina_error_msg_get(err));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ return EINA_TRUE; /* no point in continuing on this error */
+ }
+
+ EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))
- efl_net_ip_port_fmt(buf, sizeof(buf), d->addr);
+ {
+ if (efl_net_ip_port_fmt(buf, sizeof(buf), addrinfo->ai_addr))
+ DBG("resolved address='%s' to %s. Connect using fd=%d socks5://%s:%s", d->address, buf, fd, proxy_host, proxy_port);
+ }
- DBG("connecting fd=%d to %s", d->sockfd, buf);
+ s = send(fd, &greeting, sizeof(greeting), MSG_NOSIGNAL);
+ if (s != (ssize_t)sizeof(greeting))
+ {
+ if (s < 0)
+ DBG("couldn't request connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't send proxy request: need %zu, did %zd", sizeof(greeting), s);
+ }
+ else
+ {
+ Efl_Net_Socks5_Greeting_Reply greeting_reply;
+ s = recv(fd, &greeting_reply, sizeof(greeting_reply), MSG_NOSIGNAL);
+ if (s != sizeof(greeting_reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv greeting reply of connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(greeting_reply), s);
+ }
+ else
+ {
+ if (greeting_reply.auth != greeting.auths[0])
+ DBG("proxy server rejected authentication %#x trying connection to host=%s fd=%d socks5://%s:%s", greeting.auths[0], buf, fd, proxy_host, proxy_port);
+ else
+ {
+ if ((greeting_reply.auth == EFL_NET_SOCKS5_AUTH_USER_PASS) &&
+ (!_efl_net_ip_connect_async_run_socks5_auth_user_pass(fd, proxy_user, proxy_pass, "socks5", proxy_host, proxy_port)))
+ {
+ d->error = EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED;
+ }
+ else
+ {
+ Efl_Net_Socks5_Request *request;
+ size_t request_len;
+
+ request = efl_net_socks5_request_addr_new(cmd, addrinfo->ai_addr, &request_len);
+ if (request)
+ {
+ EINA_THREAD_CLEANUP_PUSH(free, request);
+
+ s = send(fd, request, request_len, MSG_NOSIGNAL);
+ if (s != (ssize_t)request_len)
+ {
+ if (s < 0)
+ DBG("couldn't request connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+ }
+ else if (addrinfo->ai_family == AF_INET)
+ {
+ Efl_Net_Socks5_Reply_Ipv4 reply;
+ s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+ if (s != sizeof(reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv reply of connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+ }
+ else
+ {
+ if (reply.base.status != EFL_NET_SOCKS5_REPLY_STATUS_GRANTED)
+ DBG("rejected IPv4 connection to host=%s fd=%d socks5://%s:%s: reason=%#x", buf, fd, proxy_host, proxy_port, reply.base.status);
+ else
+ {
+ memcpy(&d->addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+ d->addrlen = addrinfo->ai_addrlen;
+ d->sockfd = fd;
+ d->error = 0;
+ ret = EINA_TRUE;
+ DBG("connected IPv4 to host=%s fd=%d socks5://%s:%s", buf, fd, proxy_host, proxy_port);
+ }
+ }
+ }
+ else if (addrinfo->ai_family == AF_INET6)
+ {
+ Efl_Net_Socks5_Reply_Ipv6 reply;
+ s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+ if (s != sizeof(reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv reply of connection to host=%s fd=%d socks5://%s:%s: %s", buf, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+ }
+ else
+ {
+ if (reply.base.status != EFL_NET_SOCKS5_REPLY_STATUS_GRANTED)
+ DBG("rejected IPv6 connection to host=%s fd=%d socks5://%s:%s: reason=%#x", buf, fd, proxy_host, proxy_port, reply.base.status);
+ else
+ {
+ memcpy(&d->addr, addrinfo->ai_addr, addrinfo->ai_addrlen);
+ d->addrlen = addrinfo->ai_addrlen;
+ d->sockfd = fd;
+ d->error = 0;
+ ret = EINA_TRUE;
+ DBG("connected IPv6 to host=%s fd=%d socks5://%s:%s", buf, fd, proxy_host, proxy_port);
+ }
+ }
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
+ }
+ }
+ }
+ }
+ }
+ EINA_THREAD_CLEANUP_POP(!ret); /* we need fd on success, on failure just close it */
+ return ret;
+}
- r = connect(d->sockfd, d->addr, d->addrlen);
- if (r < 0)
+static void
+_efl_net_ip_connect_async_run_socks5(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+ char *str;
+ const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+ struct addrinfo *results = NULL;
+ struct addrinfo hints = {
+ .ai_socktype = d->type,
+ .ai_protocol = d->protocol,
+ .ai_family = AF_UNSPEC,
+ };
+ Efl_Net_Socks5_Request_Command cmd;
+ int r;
+
+ if ((d->type == SOCK_STREAM) && (d->protocol == IPPROTO_TCP))
+ cmd = EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_CONNECT;
+ else
+ {
+ DBG("EFL SOCKSv5 only accepts TCP requests at this moment. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+ d->error = EPROTONOSUPPORT;
+ return;
+ }
+
+ if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+ DBG("proxy connection to %s:%s using socks5://%s", host, port, proxy);
+
+ str = strdup(proxy);
+ EINA_THREAD_CLEANUP_PUSH(free, str);
+
+ if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
+ {
+ ERR("Invalid proxy string: socks5://%s", proxy);
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ goto end;
+ }
+ if (!proxy_port) proxy_port = "1080";
+
+ do
+ r = getaddrinfo(host, port, &hints, &results);
+ while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+ if (r != 0)
+ {
+ DBG("couldn't resolve host='%s', port='%s': %s",
+ host, port, gai_strerror(r));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+ }
+ else
+ {
+ const struct addrinfo *addrinfo;
+
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+ EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+ for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+ {
+ if (addrinfo->ai_socktype != d->type) continue;
+ if (addrinfo->ai_protocol != d->protocol) continue;
+ if (_efl_net_ip_connect_async_run_socks5_try(d, proxy_host, proxy_port, proxy_user, proxy_pass, cmd, addrinfo))
+ break;
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+ }
+ end:
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+static void
+_efl_net_ip_connect_async_run_socks5h(Efl_Net_Ip_Connect_Async_Data *d, const char *host, const char *port, const char *proxy)
+{
+ int fd = -1;
+ char *str;
+ const char *proxy_user, *proxy_pass, *proxy_host, *proxy_port;
+ struct sockaddr_storage proxy_addr;
+ socklen_t proxy_addrlen;
+ Eina_Error err;
+ Efl_Net_Socks5_Greeting greeting = {
+ .version = 0x05,
+ .auths_count = 1,
+ };
+ Efl_Net_Socks5_Request_Command cmd;
+ ssize_t s;
+ int r;
+
+ if ((d->type == SOCK_STREAM) && (d->protocol == IPPROTO_TCP))
+ cmd = EFL_NET_SOCKS5_REQUEST_COMMAND_TCP_CONNECT;
+ else
+ {
+ DBG("EFL SOCKSv5 only accepts TCP requests at this moment. Wanted type=%#x, protocol=%#x", d->type, d->protocol);
+ d->error = EPROTONOSUPPORT;
+ return;
+ }
+
+ DBG("proxy connection to %s:%s using socks5h://%s", host, port, proxy);
+
+ str = strdup(proxy);
+ EINA_THREAD_CLEANUP_PUSH(free, str);
+
+ if (!_efl_net_ip_port_user_pass_split(str, &proxy_host, &proxy_port, &proxy_user, &proxy_pass))
+ {
+ ERR("Invalid proxy string: socks5h://%s", proxy);
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ goto end;
+ }
+ if (!proxy_port) proxy_port = "1080";
+
+ greeting.auths[0] = proxy_user ? EFL_NET_SOCKS5_AUTH_USER_PASS : EFL_NET_SOCKS5_AUTH_NONE;
+
+ err = _efl_net_ip_resolve_and_connect(proxy_host, proxy_port, SOCK_STREAM, IPPROTO_TCP, &fd, (struct sockaddr *)&proxy_addr, &proxy_addrlen);
+ if (err)
+ {
+ DBG("couldn't connect to socks5h://%s: %s", proxy, eina_error_msg_get(err));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_PROXY;
+ goto end;
+ }
+
+ DBG("connected fd=%d to socks5h://%s", fd, proxy);
+ EINA_THREAD_CLEANUP_PUSH(_cleanup_close, &fd);
+
+ s = send(fd, &greeting, sizeof(greeting), MSG_NOSIGNAL);
+ if (s != (ssize_t)sizeof(greeting))
+ {
+ if (s < 0)
+ DBG("couldn't request connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't send proxy request: need %zu, did %zd", sizeof(greeting), s);
+ }
+ else
+ {
+ Efl_Net_Socks5_Greeting_Reply greeting_reply;
+ s = recv(fd, &greeting_reply, sizeof(greeting_reply), MSG_NOSIGNAL);
+ if (s != sizeof(greeting_reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv greeting reply of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(greeting_reply), s);
+ }
+ else
+ {
+ if (greeting_reply.auth != greeting.auths[0])
+ DBG("proxy server rejected authentication %#x trying connection to host=%s:%s fd=%d socks5h://%s:%s", greeting.auths[0], host, port, fd, proxy_host, proxy_port);
+ else
+ {
+ if ((greeting_reply.auth == EFL_NET_SOCKS5_AUTH_USER_PASS) &&
+ (!_efl_net_ip_connect_async_run_socks5_auth_user_pass(fd, proxy_user, proxy_pass, "socks5h", proxy_host, proxy_port)))
+ {
+ d->error = EFL_NET_DIALER_ERROR_PROXY_AUTHENTICATION_FAILED;
+ }
+ else
+ {
+ struct addrinfo *results = NULL;
+ struct addrinfo hints = {
+ .ai_socktype = d->type,
+ .ai_protocol = d->protocol,
+ .ai_family = AF_UNSPEC,
+ };
+
+ if (strchr(host, ':')) hints.ai_family = AF_INET6;
+
+ /* we just resolve the port number here */
+ do
+ r = getaddrinfo(NULL, port, &hints, &results);
+ while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
+ if (r != 0)
+ {
+ DBG("couldn't resolve port='%s': %s", port, gai_strerror(r));
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST;
+ }
+ else
+ {
+ const struct addrinfo *addrinfo;
+
+ d->error = EFL_NET_DIALER_ERROR_COULDNT_CONNECT;
+ EINA_THREAD_CLEANUP_PUSH((Eina_Free_Cb)freeaddrinfo, results);
+ for (addrinfo = results; addrinfo != NULL; addrinfo = addrinfo->ai_next)
+ {
+ Efl_Net_Socks5_Request *request;
+ size_t request_len;
+ uint16_t port_num;
+
+ if (addrinfo->ai_socktype != d->type) continue;
+ if (addrinfo->ai_protocol != d->protocol) continue;
+
+ if (addrinfo->ai_family == AF_INET)
+ port_num = ((const struct sockaddr_in *)addrinfo->ai_addr)->sin_port;
+ else
+ port_num = ((const struct sockaddr_in6 *)addrinfo->ai_addr)->sin6_port;
+
+ request = efl_net_socks5_request_name_new(cmd, host, port_num, &request_len);
+ if (request)
+ {
+ EINA_THREAD_CLEANUP_PUSH(free, request);
+
+ s = send(fd, request, request_len, MSG_NOSIGNAL);
+ if (s != (ssize_t)request_len)
+ {
+ if (s < 0)
+ DBG("couldn't request connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't send proxy request: need %zu, did %zd", request_len, s);
+ }
+ else
+ {
+ Efl_Net_Socks5_Reply reply;
+
+ s = recv(fd, &reply, sizeof(reply), MSG_NOSIGNAL);
+ if (s != sizeof(reply))
+ {
+ if (s < 0)
+ DBG("couldn't recv reply of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy reply: need %zu, did %zd", sizeof(reply), s);
+ }
+ else
+ {
+ if (reply.status != EFL_NET_SOCKS5_REPLY_STATUS_GRANTED)
+ DBG("rejected connection to host=%s:%s fd=%d socks5h://%s:%s: reason=%#x", host, port, fd, proxy_host, proxy_port, reply.status);
+ else if (reply.address_type == EFL_NET_SOCKS5_ADDRESS_TYPE_IPV4)
+ {
+ Efl_Net_Socks5_Address_Ipv4 ipv4;
+
+ s = recv(fd, &ipv4, sizeof(ipv4), MSG_NOSIGNAL);
+ if (s != sizeof(ipv4))
+ {
+ if (s < 0)
+ DBG("couldn't recv ipv4 of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy ipv4: need %zu, did %zd", sizeof(ipv4), s);
+ }
+ else
+ {
+ d->addr4.sin_family = AF_INET;
+ d->addr4.sin_port = ipv4.port;
+ memcpy(&d->addr4.sin_addr, ipv4.address, 4);
+ d->addrlen = sizeof(struct sockaddr_in);
+ d->sockfd = fd;
+ d->error = 0;
+ DBG("connected IPv4 to host=%s:%s fd=%d socks5h://%s:%s", host, port, fd, proxy_host, proxy_port);
+ }
+ }
+ else if (reply.address_type == EFL_NET_SOCKS5_ADDRESS_TYPE_IPV6)
+ {
+ Efl_Net_Socks5_Address_Ipv6 ipv6;
+
+ s = recv(fd, &ipv6, sizeof(ipv6), MSG_NOSIGNAL);
+ if (s != sizeof(ipv6))
+ {
+ if (s < 0)
+ DBG("couldn't recv ipv6 of connection to host=%s:%s fd=%d socks5h://%s:%s: %s", host, port, fd, proxy_host, proxy_port, strerror(errno));
+ else
+ DBG("couldn't recv proxy ipv6: need %zu, did %zd", sizeof(ipv6), s);
+ }
+ else
+ {
+ d->addr6.sin6_family = AF_INET;
+ d->addr6.sin6_port = ipv6.port;
+ memcpy(&d->addr6.sin6_addr, ipv6.address, 16);
+ d->addrlen = sizeof(struct sockaddr_in);
+ d->sockfd = fd;
+ d->error = 0;
+ DBG("connected IPv6 to host=%s:%s fd=%d socks5h://%s:%s", host, port, fd, proxy_host, proxy_port);
+ }
+ }
+ else
+ {
+ /* most proxy servers will return a failure instead of this, but let's guard and log */
+ DBG("couldn't resolve host %s:%s fd=%d socks5h://%s:%s", host, port, fd, proxy_host, proxy_port);
+ }
+ }
+ }
+
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(request) */
+ break;
+ }
+ }
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* freeaddrinfo(results) */
+ }
+ }
+ }
+ }
+ }
+ EINA_THREAD_CLEANUP_POP(d->sockfd == -1); /* we need fd only on success */
+ end:
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(str) */
+}
+
+static void
+_efl_net_ip_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+ Efl_Net_Ip_Connect_Async_Data *d = data;
+ const char *host, *port, *proxy;
+ char *addrcopy;
+
+ addrcopy = strdup(d->address);
+ if (!addrcopy)
{
- int fd = d->sockfd;
d->error = errno;
- d->sockfd = -1;
- /* close() is a cancellation point, thus unset sockfd before
- * closing, so the main thread _efl_net_connect_async_cancel()
- * won't close it again.
- */
- close(fd);
- DBG("connect(%d, %s) failed: %s", fd, buf, strerror(errno));
return;
}
- DBG("connected fd=%d to %s", d->sockfd, buf);
+ if (!efl_net_ip_port_split(addrcopy, &host, &port))
+ {
+ d->error = EINVAL;
+ free(addrcopy);
+ return;
+ }
+ if (!port) port = "0";
+
+ proxy = d->proxy;
+ if (!proxy)
+ {
+ proxy = d->proxy_env;
+ if (!proxy)
+ proxy = "";
+ else
+ {
+ if (_efl_net_ip_no_proxy(host, d->no_proxy_strv))
+ proxy = "";
+ }
+ }
+
+ EINA_THREAD_CLEANUP_PUSH(free, addrcopy);
+ /* allows ecore_thread_cancel() to cancel at some points, see
+ * man:pthreads(7).
+ */
+ eina_thread_cancellable_set(EINA_TRUE, NULL);
+
+ if (!proxy[0])
+ _efl_net_ip_connect_async_run_direct(d, host, port);
+ else if (eina_str_has_prefix(proxy, "socks4://"))
+ _efl_net_ip_connect_async_run_socks4(d, host, port, proxy + strlen("socks4://"));
+ else if (eina_str_has_prefix(proxy, "socks5://"))
+ _efl_net_ip_connect_async_run_socks5(d, host, port, proxy + strlen("socks5://"));
+ else if (eina_str_has_prefix(proxy, "socks4a://"))
+ _efl_net_ip_connect_async_run_socks4a(d, host, port, proxy + strlen("socks4a://"));
+ else if (eina_str_has_prefix(proxy, "socks5h://"))
+ _efl_net_ip_connect_async_run_socks5h(d, host, port, proxy + strlen("socks5h://"));
+ else if (!strstr(proxy, "://"))
+ {
+ _efl_net_ip_connect_async_run_socks5(d, host, port, proxy);
+ if (d->error)
+ _efl_net_ip_connect_async_run_socks4(d, host, port, proxy);
+ }
+ else
+ {
+ if (d->proxy)
+ {
+ d->error = ENOTSUP;
+ ERR("proxy protocol not supported '%s'", proxy);
+ }
+ else
+ {
+ /* maybe bogus envvar, ignore it */
+ WRN("proxy protocol not supported '%s', connect directly", proxy);
+ _efl_net_ip_connect_async_run_direct(d, host, port);
+ }
+ }
+
+ if ((d->error) && (!d->proxy) && (proxy[0] != '\0'))
+ {
+ WRN("error using proxy '%s' from environment, try direct connect", proxy);
+ _efl_net_ip_connect_async_run_direct(d, host, port);
+ }
+
+ eina_thread_cancellable_set(EINA_FALSE, NULL);
+ EINA_THREAD_CLEANUP_POP(EINA_TRUE);
}
static void
-_efl_net_connect_async_data_free(Efl_Net_Connect_Async_Data *d)
+_efl_net_ip_connect_async_data_free(Efl_Net_Ip_Connect_Async_Data *d)
{
+ free(d->address);
+ free(d->proxy);
+ free(d->proxy_env);
+ if (d->no_proxy_strv)
+ {
+ free(d->no_proxy_strv[0]);
+ free(d->no_proxy_strv);
+ }
free(d);
}
static void
-_efl_net_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_ip_connect_async_end(void *data, Ecore_Thread *thread EINA_UNUSED)
{
- Efl_Net_Connect_Async_Data *d = data;
- d->cb((void *)d->data, d->addr, d->addrlen, d->sockfd, d->error);
- _efl_net_connect_async_data_free(d);
+ Efl_Net_Ip_Connect_Async_Data *d = data;
+
+#ifdef FD_CLOEXEC
+ /* if it wasn't a close on exec, release the socket to be passed to child */
+ if ((!d->close_on_exec) && (d->sockfd >= 0))
+ {
+ int flags = fcntl(d->sockfd, F_GETFD);
+ if (flags < 0)
+ {
+ d->error = errno;
+ ERR("fcntl(%d, F_GETFD): %s", d->sockfd, strerror(errno));
+ close(d->sockfd);
+ d->sockfd = -1;
+ }
+ else
+ {
+ flags &= (~FD_CLOEXEC);
+ if (fcntl(d->sockfd, F_SETFD, flags) < 0)
+ {
+ d->error = errno;
+ ERR("fcntl(%d, F_SETFD, %#x): %s", d->sockfd, flags, strerror(errno));
+ close(d->sockfd);
+ d->sockfd = -1;
+ }
+ }
+ }
+#endif
+
+ d->cb((void *)d->data, &d->addr, d->addrlen, d->sockfd, d->error);
+ _efl_net_ip_connect_async_data_free(d);
}
static void
-_efl_net_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
+_efl_net_ip_connect_async_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
{
- Efl_Net_Connect_Async_Data *d = data;
+ Efl_Net_Ip_Connect_Async_Data *d = data;
if (d->sockfd >= 0) close(d->sockfd);
- _efl_net_connect_async_data_free(d);
+ _efl_net_ip_connect_async_data_free(d);
}
Ecore_Thread *
-efl_net_connect_async_new(const struct sockaddr *addr, socklen_t addrlen, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data)
+efl_net_ip_connect_async_new(const char *address, const char *proxy, const char *proxy_env, const char *no_proxy_env, int type, int protocol, Eina_Bool close_on_exec, Efl_Net_Connect_Async_Cb cb, const void *data)
{
- Efl_Net_Connect_Async_Data *d;
+ Efl_Net_Ip_Connect_Async_Data *d;
- EINA_SAFETY_ON_NULL_RETURN_VAL(addr, NULL);
- EINA_SAFETY_ON_TRUE_RETURN_VAL(addrlen < 1, NULL);
+ EINA_SAFETY_ON_NULL_RETURN_VAL(address, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL);
- d = malloc(sizeof(Efl_Net_Connect_Async_Data) + addrlen);
+ d = calloc(1, sizeof(Efl_Net_Ip_Connect_Async_Data));
EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL);
+ d->address = strdup(address);
+ EINA_SAFETY_ON_NULL_GOTO(d->address, error_address);
+
+ if (proxy)
+ {
+ d->proxy = strdup(proxy);
+ EINA_SAFETY_ON_NULL_GOTO(d->proxy, error_proxy);
+ }
+ if (proxy_env)
+ {
+ d->proxy_env = strdup(proxy_env);
+ EINA_SAFETY_ON_NULL_GOTO(d->proxy_env, error_proxy_env);
+ }
+ if (no_proxy_env)
+ {
+ d->no_proxy_strv = eina_str_split(no_proxy_env, ",", 0);
+ EINA_SAFETY_ON_NULL_GOTO(d->no_proxy_strv, error_no_proxy_strv);
+ }
+
d->cb = cb;
d->data = data;
- d->addrlen = addrlen;
+ d->addrlen = 0;
d->close_on_exec = close_on_exec;
d->type = type;
d->protocol = protocol;
- memcpy(d->addr, addr, addrlen);
d->sockfd = -1;
d->error = 0;
- return ecore_thread_run(_efl_net_connect_async_run,
- _efl_net_connect_async_end,
- _efl_net_connect_async_cancel,
+ return ecore_thread_run(_efl_net_ip_connect_async_run,
+ _efl_net_ip_connect_async_end,
+ _efl_net_ip_connect_async_cancel,
d);
+
+ error_no_proxy_strv:
+ free(d->proxy_env);
+ error_proxy_env:
+ free(d->proxy);
+ error_proxy:
+ free(d->address);
+ error_address:
+ free(d);
+ return NULL;
}