/*
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2001 Hiroyuki Yamamoto
+ * Copyright (C) 1999-2002 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
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
-
-#if USE_THREADS
-# include <pthread.h>
+#include <signal.h>
+#include <setjmp.h>
+#if HAVE_SYS_SELECT_H
+# include <sys/select.h>
#endif
#include "socket.h"
+#include "utils.h"
+#if USE_SSL
+# include "ssl.h"
+#endif
#if USE_GIO
#error USE_GIO is currently not supported
#endif
#define BUFFSIZE 8192
+#define IO_TIMEOUT 60
+
+static gint sock_connect_with_timeout (gint sock,
+ const struct sockaddr *serv_addr,
+ gint addrlen,
+ guint timeout_secs);
#ifndef INET6
static gint sock_connect_by_hostname (gint sock,
gushort port);
#endif
+static SockInfo *sockinfo_from_fd(const gchar *hostname,
+ gushort port,
+ gint sock);
gint fd_connect_unix(const gchar *path)
{
return is_nonblocking_mode(sock->sock);
}
+static gint fd_check_io(gint fd, GIOCondition cond)
+{
+ struct timeval timeout;
+ fd_set fds;
+
+ timeout.tv_sec = IO_TIMEOUT;
+ timeout.tv_usec = 0;
-#ifndef INET6
-static gint sock_connect_by_hostname(gint sock, const gchar *hostname,
- gushort port)
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ if (cond == G_IO_IN) {
+ select(fd + 1, &fds, NULL, NULL, &timeout);
+ } else {
+ select(fd + 1, NULL, &fds, NULL, &timeout);
+ }
+
+ if (FD_ISSET(fd, &fds)) {
+ return 0;
+ } else {
+ g_warning("Socket IO timeout\n");
+ return -1;
+ }
+}
+
+static sigjmp_buf jmpenv;
+
+static void timeout_handler(gint sig)
+{
+ siglongjmp(jmpenv, 1);
+}
+
+static gint sock_connect_with_timeout(gint sock,
+ const struct sockaddr *serv_addr,
+ gint addrlen,
+ guint timeout_secs)
+{
+ gint ret;
+ void (*prev_handler)(gint);
+
+ alarm(0);
+ prev_handler = signal(SIGALRM, timeout_handler);
+ if (sigsetjmp(jmpenv, 1)) {
+ alarm(0);
+ signal(SIGALRM, prev_handler);
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ alarm(timeout_secs);
+
+ ret = connect(sock, serv_addr, addrlen);
+
+ alarm(0);
+ signal(SIGALRM, prev_handler);
+
+ return ret;
+}
+
+struct hostent *my_gethostbyname(const gchar *hostname)
{
struct hostent *hp;
- struct sockaddr_in ad;
-#ifndef HAVE_INET_ATON
-#if HAVE_INET_ADDR
- guint32 inaddr;
-#endif
-#endif /* HAVE_INET_ATON */
+ void (*prev_handler)(gint);
+ guint timeout_secs = IO_TIMEOUT;
+
+ alarm(0);
+ prev_handler = signal(SIGALRM, timeout_handler);
+ if (sigsetjmp(jmpenv, 1)) {
+ alarm(0);
+ signal(SIGALRM, prev_handler);
+ fprintf(stderr, "%s: host lookup timed out.\n", hostname);
+ errno = 0;
+ return NULL;
+ }
+ alarm(timeout_secs);
- memset(&ad, 0, sizeof(ad));
- ad.sin_family = AF_INET;
- ad.sin_port = htons(port);
+ if ((hp = gethostbyname(hostname)) == NULL) {
+ alarm(0);
+ signal(SIGALRM, prev_handler);
+ fprintf(stderr, "%s: unknown host.\n", hostname);
+ errno = 0;
+ return NULL;
+ }
+
+ alarm(0);
+ signal(SIGALRM, prev_handler);
+
+ return hp;
+}
+#ifndef INET6
+static gint my_inet_aton(const gchar *hostname, struct in_addr *inp)
+{
#if HAVE_INET_ATON
- if (!inet_aton(hostname, &ad.sin_addr)) {
+ return inet_aton(hostname, inp);
#else
#if HAVE_INET_ADDR
+ guint32 inaddr;
+
inaddr = inet_addr(hostname);
- if (inaddr != -1)
- memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
- else {
+ if (inaddr != -1) {
+ memcpy(inp, &inaddr, sizeof(inaddr));
+ return 1;
+ } else
+ return 0;
#else
- {
+ return 0;
#endif
#endif /* HAVE_INET_ATON */
- if ((hp = gethostbyname(hostname)) == NULL) {
+}
+
+static gint sock_connect_by_hostname(gint sock, const gchar *hostname,
+ gushort port)
+{
+ struct hostent *hp;
+ struct sockaddr_in ad;
+ guint timeout_secs = IO_TIMEOUT;
+
+ memset(&ad, 0, sizeof(ad));
+ ad.sin_family = AF_INET;
+ ad.sin_port = htons(port);
+
+ if (!my_inet_aton(hostname, &ad.sin_addr)) {
+ if ((hp = my_gethostbyname(hostname)) == NULL) {
fprintf(stderr, "%s: unknown host.\n", hostname);
errno = 0;
return -1;
}
if (hp->h_length != 4 && hp->h_length != 8) {
- h_errno = errno = 0;
fprintf(stderr, "illegal address length received for host %s\n", hostname);
+ errno = 0;
return -1;
}
memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
}
- return connect(sock, (struct sockaddr *)&ad, sizeof(ad));
+ return sock_connect_with_timeout(sock, (struct sockaddr *)&ad,
+ sizeof(ad), timeout_secs);
}
#else /* INET6 */
{
gint sock = -1, gai_error;
struct addrinfo hints, *res, *ai;
+ guint timeout_secs = IO_TIMEOUT;
gchar port_str[6];
memset(&hints, 0, sizeof(hints));
if (sock < 0)
continue;
- if (connect(sock, ai->ai_addr, ai->ai_addrlen) == 0)
+ if (sock_connect_with_timeout
+ (sock, ai->ai_addr, ai->ai_addrlen, timeout_secs) == 0)
break;
close(sock);
}
#endif /* !INET6 */
-#if 0
-SockInfo *sock_connect_nb(const gchar *hostname, gushort port)
-{
- gint sock;
- gint ret;
- SockInfo *sockinfo;
-#ifdef INET6
- if ((sock = sock_connect_by_getaddrinfo(hostname, port)) < 0)
- return NULL;
- if (set_nonblocking_mode(sock, TRUE) < 0) return NULL;
- ret = sock;
-#else
- if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- perror("socket");
+/* Open a connection using an external program. May be useful when
+ * you need to tunnel through a SOCKS or other firewall, or to
+ * establish an IMAP-over-SSH connection. */
+/* TODO: Recreate this for sock_connect_thread() */
+SockInfo *sock_connect_cmd(const gchar *hostname, const gchar *tunnelcmd)
+{
+ gint fd[2];
+ int r;
+
+ if ((r = socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) == -1) {
+ perror("socketpair");
return NULL;
}
-
- if (set_nonblocking_mode(sock, TRUE) < 0) return NULL;
-
- ret = sock_connect_by_hostname(sock, hostname, port);
-
- if (ret < 0 && errno != EINPROGRESS) {
- if (errno != 0) perror("connect");
- close(sock);
- return NULL;
+ log_message("launching tunnel command \"%s\"\n", tunnelcmd);
+ if (fork() == 0) {
+ close(fd[0]);
+ close(0);
+ close(1);
+ dup(fd[1]); /* set onto stdin */
+ dup(fd[1]);
+ execlp("/bin/sh", "/bin/sh", "-c", tunnelcmd, NULL);
}
-#endif /* INET6 */
-
- sockinfo = g_new0(SockInfo, 1);
- sockinfo->sock = sock;
- sockinfo->hostname = g_strdup(hostname);
- sockinfo->port = port;
- sockinfo->state = CONN_LOOKUPSUCCESS;
-
- if (ret < 0 && errno == EINPROGRESS) return sockinfo;
- sockinfo->state = CONN_ESTABLISHED;
- return sockinfo;
+ close(fd[1]);
+ return sockinfo_from_fd(hostname, 0, fd[0]);
}
-#endif
+
SockInfo *sock_connect(const gchar *hostname, gushort port)
{
gint sock;
- SockInfo *sockinfo;
#ifdef INET6
if ((sock = sock_connect_by_getaddrinfo(hostname, port)) < 0)
}
#endif /* INET6 */
- sockinfo = g_new0(SockInfo, 1);
- sockinfo->sock = sock;
- sockinfo->hostname = g_strdup(hostname);
- sockinfo->port = port;
- sockinfo->state = CONN_ESTABLISHED;
-
- usleep(100000);
-
- return sockinfo;
+ return sockinfo_from_fd(hostname, port, sock);
}
-#if USE_THREADS
-static void sock_connect_thread(SockInfo *sockinfo)
-{
-#ifdef INET6
- if ((sockinfo->sock = sock_connect_by_getaddrinfo
- (sockinfo->hostname, sockinfo->port)) < 0)
- pthread_exit((void *)1);
-#else
- if (sock_connect_by_hostname(sockinfo->sock, sockinfo->hostname,
- sockinfo->port) < 0) {
- if (errno != 0) perror("connect");
- sockinfo->state = CONN_FAILED;
- pthread_exit((void *)1);
- }
-#endif /* INET6 */
- sockinfo->state = CONN_ESTABLISHED;
-
- pthread_exit(0);
-}
-SockInfo *sock_connect_with_thread(const gchar *hostname, gushort port)
+static SockInfo *sockinfo_from_fd(const gchar *hostname,
+ gushort port,
+ gint sock)
{
- gint sock = 0;
SockInfo *sockinfo;
-
-#ifndef INET6
- if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- perror("socket");
- return NULL;
- }
-#endif /* !INET6 */
-
+
sockinfo = g_new0(SockInfo, 1);
sockinfo->sock = sock;
sockinfo->hostname = g_strdup(hostname);
sockinfo->port = port;
- sockinfo->state = CONN_READY;
+ sockinfo->state = CONN_ESTABLISHED;
- pthread_create(&sockinfo->connect_thr, NULL,
- (void *)sock_connect_thread,
- sockinfo);
- pthread_mutex_init(&sockinfo->mutex, NULL);
- pthread_detach(sockinfo->connect_thr);
+ usleep(100000);
return sockinfo;
}
-#endif
-
gint sock_printf(SockInfo *sock, const gchar *format, ...)
{
g_return_val_if_fail(sock != NULL, -1);
#if USE_SSL
- if(sock->ssl) {
+ if (sock->ssl)
return ssl_read(sock->ssl, buf, len);
- }
#endif
return fd_read(sock->sock, buf, len);
}
gint fd_read(gint fd, gchar *buf, gint len)
{
+ if (fd_check_io(fd, G_IO_IN) < 0)
+ return -1;
+
return read(fd, buf, len);
}
-#ifdef USE_SSL
+#if USE_SSL
gint ssl_read(SSL *ssl, gchar *buf, gint len)
{
return SSL_read(ssl, buf, len);
g_return_val_if_fail(sock != NULL, -1);
#if USE_SSL
- if(sock->ssl) {
+ if (sock->ssl)
return ssl_write(sock->ssl, buf, len);
- }
#endif
return fd_write(sock->sock, buf, len);
}
gint n, wrlen = 0;
while (len) {
+ if (fd_check_io(fd, G_IO_OUT) < 0)
+ return -1;
+ signal(SIGPIPE, SIG_IGN);
n = write(fd, buf, len);
- if (n <= 0)
+ if (n <= 0) {
+ log_error("write on fd%d: %s\n", fd, strerror(errno));
return -1;
+ }
len -= n;
wrlen += n;
buf += n;
return wrlen;
}
-#ifdef USE_SSL
+#if USE_SSL
gint ssl_write(SSL *ssl, const gchar *buf, gint len)
{
gint n, wrlen = 0;
}
#endif
+gint fd_recv(gint fd, gchar *buf, gint len, gint flags)
+{
+ if (fd_check_io(fd, G_IO_IN) < 0)
+ return -1;
+
+ return recv(fd, buf, len, flags);
+}
+
gint fd_gets(gint fd, gchar *buf, gint len)
{
gchar *newline, *bp = buf;
if (--len < 1)
return -1;
do {
- if ((n = recv(fd, bp, len, MSG_PEEK)) <= 0)
+ if ((n = fd_recv(fd, bp, len, MSG_PEEK)) <= 0)
return -1;
if ((newline = memchr(bp, '\n', n)) != NULL)
n = newline - bp + 1;
- if ((n = read(fd, bp, n)) < 0)
+ if ((n = fd_read(fd, bp, n)) < 0)
return -1;
bp += n;
len -= n;
#if USE_SSL
gint ssl_gets(SSL *ssl, gchar *buf, gint len)
{
- gchar *buf2 = buf;
- gboolean newline = FALSE;
- gint n, count = 0;
+ gchar *newline, *bp = buf;
+ gint n;
if (--len < 1)
return -1;
- while(len > 0 && !newline) {
- *buf2 = '\0';
- if((n = SSL_read(ssl, buf2, 1)) < 0)
+ do {
+ if ((n = SSL_peek(ssl, bp, len)) <= 0)
return -1;
- if(*buf2 == '\n')
- newline = TRUE;
- buf2 += n;
- count += n;
- }
+ if ((newline = memchr(bp, '\n', n)) != NULL)
+ n = newline - bp + 1;
+ if ((n = SSL_read(ssl, bp, n)) < 0)
+ return -1;
+ bp += n;
+ len -= n;
+ } while (!newline && len);
- *buf2 = '\0';
- return count;
+ *bp = '\0';
+ return bp - buf;
}
#endif
g_return_val_if_fail(sock != NULL, -1);
#if USE_SSL
- if(sock->ssl) {
+ if (sock->ssl)
return ssl_gets(sock->ssl, buf, len);
- }
#endif
return fd_gets(sock->sock, buf, len);
}
if (buf[len - 1] == '\n')
break;
}
+ if (len == -1) {
+ log_error("Read from socket fd%d failed: %s\n",
+ fd, strerror(errno));
+ if (str)
+ g_free(str);
+ return NULL;
+ }
return str;
}
g_return_val_if_fail(sock != NULL, NULL);
#if USE_SSL
- if(sock->ssl) {
+ if (sock->ssl)
return ssl_getline(sock->ssl);
- }
#endif
return fd_getline(sock->sock);
}
g_return_val_if_fail(sock != NULL, -1);
+#if USE_SSL
+ if (sock->ssl) {
+ if ((n = SSL_peek(sock->ssl, &ch, 1)) < 0)
+ return -1;
+ else
+ return ch;
+ }
+#endif
if ((n = recv(sock->sock, &ch, 1, MSG_PEEK)) < 0)
return -1;
else
if (!sock)
return 0;
+#if USE_SSL
+ if (sock->ssl)
+ ssl_done_socket(sock);
+#endif
ret = fd_close(sock->sock);
g_free(sock->hostname);
g_free(sock);