sync with 0.8.1cvs17
[claws.git] / src / socket.c
index 0468b2ce085f32e1110a1ea8764e068855a24034..c0430a22e7c46616adf57ae80a03844ecdf82c93 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * 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,
@@ -57,6 +68,9 @@ static gint sock_connect_by_getaddrinfo       (const gchar    *hostname,
                                         gushort         port);
 #endif
 
+static SockInfo *sockinfo_from_fd(const gchar *hostname,
+                                 gushort port,
+                                 gint sock);
 
 gint fd_connect_unix(const gchar *path)
 {
@@ -168,51 +182,145 @@ gboolean sock_is_nonblocking_mode(SockInfo *sock)
        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 */
@@ -220,6 +328,7 @@ static gint sock_connect_by_getaddrinfo(const gchar *hostname, gushort      port)
 {
        gint sock = -1, gai_error;
        struct addrinfo hints, *res, *ai;
+       guint timeout_secs = IO_TIMEOUT;
        gchar port_str[6];
 
        memset(&hints, 0, sizeof(hints));
@@ -242,7 +351,8 @@ static gint sock_connect_by_getaddrinfo(const gchar *hostname, gushort      port)
                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);
@@ -258,52 +368,38 @@ static gint sock_connect_by_getaddrinfo(const gchar *hostname, gushort    port)
 }
 #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)
@@ -321,65 +417,26 @@ SockInfo *sock_connect(const gchar *hostname, gushort port)
        }
 #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, ...)
 {
@@ -398,19 +455,21 @@ gint sock_read(SockInfo *sock, gchar *buf, gint len)
        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);
@@ -422,9 +481,8 @@ gint sock_write(SockInfo *sock, const gchar *buf, gint 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);
 }
@@ -434,9 +492,14 @@ gint fd_write(gint fd, const gchar *buf, gint 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;
@@ -445,7 +508,7 @@ gint fd_write(gint fd, const gchar *buf, gint len)
        return wrlen;
 }
 
-#ifdef USE_SSL
+#if USE_SSL
 gint ssl_write(SSL *ssl, const gchar *buf, gint len)
 {
        gint n, wrlen = 0;
@@ -463,6 +526,14 @@ gint ssl_write(SSL *ssl, const gchar *buf, gint len)
 }
 #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;
@@ -471,11 +542,11 @@ gint fd_gets(gint fd, gchar *buf, gint len)
        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;
@@ -488,24 +559,24 @@ gint fd_gets(gint fd, gchar *buf, gint len)
 #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
 
@@ -514,9 +585,8 @@ gint sock_gets(SockInfo *sock, gchar *buf, gint len)
        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);
 }
@@ -539,6 +609,13 @@ gchar *fd_getline(gint fd)
                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;
 }
@@ -572,9 +649,8 @@ gchar *sock_getline(SockInfo *sock)
        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);
 }
@@ -596,6 +672,14 @@ gint sock_peek(SockInfo *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
@@ -609,6 +693,10 @@ gint sock_close(SockInfo *sock)
        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);