[pan2: 122/268] first version for openssl



commit de95be3f303180dcd61595f75d0db62e30dff040
Author: Heinrich MÃller <sphemuel stud informatik uni-erlangen de>
Date:   Fri Jul 8 21:45:16 2011 +0200

    first version for openssl

 pan.cbp                          |    2 +
 pan/gui/Makefile.am              |    2 +-
 pan/gui/pan.cc                   |    8 +-
 pan/tasks/Makefile.am            |    2 +
 pan/tasks/socket-impl-openssl.cc |  906 ++++++++++++++++++++++++++++++++++++++
 pan/tasks/socket-impl-openssl.h  |   91 ++++
 6 files changed, 1009 insertions(+), 2 deletions(-)
---
diff --git a/pan.cbp b/pan.cbp
index e964b7b..3c1517b 100644
--- a/pan.cbp
+++ b/pan.cbp
@@ -217,6 +217,8 @@
 		<Unit filename="pan/tasks/queue.h" />
 		<Unit filename="pan/tasks/socket-impl-gio.cc" />
 		<Unit filename="pan/tasks/socket-impl-gio.h" />
+		<Unit filename="pan/tasks/socket-impl-openssl.cc" />
+		<Unit filename="pan/tasks/socket-impl-openssl.h" />
 		<Unit filename="pan/tasks/socket-impl-scripted.cc" />
 		<Unit filename="pan/tasks/socket-impl-scripted.h" />
 		<Unit filename="pan/tasks/socket.cc" />
diff --git a/pan/gui/Makefile.am b/pan/gui/Makefile.am
index b50a5ff..522948f 100644
--- a/pan/gui/Makefile.am
+++ b/pan/gui/Makefile.am
@@ -93,7 +93,7 @@ WINRCOBJ =
 endif
 
 pan_SOURCES = gui.cc pan.cc $(WINRC)
-pan_LDADD = ./libpangui.a $(WINRCOBJ) ../data-impl/libpandata.a ../tasks/libtasks.a ../data/libdata.a ../usenet-utils/libusenetutils.a ../general/libgeneralutils.a ../../uulib/libuu.a @GTKSPELL_LIBS@ @GTK_LIBS@ @GMIME_LIBS@ @GLIB_LIBS@
+pan_LDADD = -lssl ./libpangui.a $(WINRCOBJ) ../data-impl/libpandata.a ../tasks/libtasks.a ../data/libdata.a ../usenet-utils/libusenetutils.a ../general/libgeneralutils.a ../../uulib/libuu.a @GTKSPELL_LIBS@ @GTK_LIBS@ @GMIME_LIBS@ @GLIB_LIBS@
 
 if HAVE_WIN32
 pan_LDFLAGS = -mwindows
diff --git a/pan/gui/pan.cc b/pan/gui/pan.cc
index fe48e47..03f6612 100644
--- a/pan/gui/pan.cc
+++ b/pan/gui/pan.cc
@@ -34,6 +34,7 @@ extern "C" {
 #include <pan/general/file-util.h>
 #include <pan/general/worker-pool.h>
 #include <pan/tasks/socket-impl-gio.h>
+#include <pan/tasks/socket-impl-openssl.h>
 #include <pan/tasks/task-groups.h>
 #include <pan/tasks/task-xover.h>
 #include <pan/tasks/nzb.h>
@@ -324,7 +325,12 @@ main (int argc, char *argv[])
 
     // instantiate the queue...
     WorkerPool worker_pool (4, true);
-    GIOChannelSocket::Creator socket_creator;
+
+    //////////////////////////
+    /// DBG!!!!
+    GIOChannelSocketSSL::Creator socket_creator;
+    //////////////////////////
+
     Queue queue (data, data, &socket_creator, worker_pool,
                  prefs.get_flag ("work-online", true),
                  prefs.get_int ("task-save-delay-secs", 10));
diff --git a/pan/tasks/Makefile.am b/pan/tasks/Makefile.am
index 6a9a870..9e3bbdd 100644
--- a/pan/tasks/Makefile.am
+++ b/pan/tasks/Makefile.am
@@ -19,6 +19,7 @@ libtasks_a_SOURCES = \
   socket.cc \
   socket-impl-gio.cc \
   socket-impl-scripted.cc \
+  socket-impl-openssl.cc \
   nntp-pool.cc
 
 noinst_HEADERS = \
@@ -42,6 +43,7 @@ noinst_HEADERS = \
   socket.h \
   socket-impl-gio.h \
   socket-impl-scripted.h \
+  socket-impl-openssl.h \
   nntp-pool.h
 
 #noinst_PROGRAMS = \
diff --git a/pan/tasks/socket-impl-openssl.cc b/pan/tasks/socket-impl-openssl.cc
new file mode 100644
index 0000000..c2e651e
--- /dev/null
+++ b/pan/tasks/socket-impl-openssl.cc
@@ -0,0 +1,906 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Pan - A Newsreader for Gtk+
+ * Copyright (C) 2002-2006  Charles Kerr <charles rebelbase com>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* #define DEBUG_SOCKET_IO */
+
+/******
+*******
+******/
+
+#include <config.h>
+#include <iostream>
+#include <string>
+#include <cerrno>
+#include <cstring>
+
+extern "C" {
+  #include <unistd.h>
+  #include <glib/gi18n.h>
+}
+
+#include <pan/general/file-util.h>
+#include <pan/general/log.h>
+#include <pan/general/macros.h>
+#include <pan/general/worker-pool.h>
+
+#ifdef G_OS_WIN32
+  // this #define is necessary for mingw
+  #define _WIN32_WINNT 0x0501
+  #include <ws2tcpip.h>
+  #undef gai_strerror
+  /*
+  #define gai_strerror(i) gai_strerror_does_not_link (i)
+  const char*
+  gai_strerror_does_not_link (int errval)
+  {
+    char buf[32];
+    g_snprintf (buf, sizeof(buf), "Winsock error %d", errval);
+    return buf;
+  }
+  */
+  const char*
+  get_last_error (int err)
+  {
+    const char * msg = 0;
+    switch(err) {
+      case WSANOTINITIALISED: msg = "No successful WSAStartup call yet."; break;
+      case WSAENETDOWN: msg = "The network subsystem has failed."; break;
+      case WSAEADDRINUSE: msg = "Fully qualified address already bound"; break;
+      case WSAEADDRNOTAVAIL: msg = "The specified address is not a valid address for this computer."; break;
+      case WSAEFAULT: msg = "Error in socket address"; break;
+      case WSAEINPROGRESS: msg = "A call is already in progress"; break;
+      case WSAEINVAL: msg = "The socket is already bound to an address."; break;
+      case WSAENOBUFS: msg = "Not enough buffers available, too many connections."; break;
+      case WSAENOTSOCK: msg = "The descriptor is not a socket."; break;
+      case 11001: msg = "Host not found"; break;
+      default: msg = "Connect failed";
+    }
+    return msg;
+  }
+
+#else
+  #include <signal.h>
+  #include <sys/types.h>
+  #include <sys/socket.h>
+  #include <netinet/in.h>
+  #include <netdb.h>
+  #include <arpa/inet.h>
+  #define closesocket(fd) close(fd)
+#endif
+
+#include <pan/general/debug.h>
+#include <pan/general/log.h>
+#include <pan/general/string-view.h>
+#include <pan/usenet-utils/gnksa.h>
+#include "socket-impl-gio.h"
+#include "socket-impl-openssl.h"
+
+using namespace pan;
+
+namespace
+{
+  typedef int (*t_getaddrinfo)(const char *,const char *, const struct addrinfo*, struct addrinfo **);
+  t_getaddrinfo p_getaddrinfo (0);
+
+  typedef void (*t_freeaddrinfo)(struct addrinfo*);
+  t_freeaddrinfo p_freeaddrinfo (0);
+
+  void ensure_module_inited (void)
+  {
+    bool inited (false);
+
+    if (!inited)
+    {
+      p_freeaddrinfo=NULL;
+      p_getaddrinfo=NULL;
+
+#ifdef G_OS_WIN32
+      WSADATA wsaData;
+      WSAStartup(MAKEWORD(2,2), &wsaData);
+
+      char sysdir[MAX_PATH], path[MAX_PATH+8];
+
+      if(GetSystemDirectory(sysdir,MAX_PATH)!=0)
+      {
+        HMODULE lib=NULL;
+        FARPROC pfunc=NULL;
+        const char *libs[]={"ws2_32","wship6",NULL};
+
+        for(const char **p=libs;*p!=NULL;++p)
+        {
+          g_snprintf(path,MAX_PATH+8,"%s\\%s",sysdir,*p);
+          lib=LoadLibrary(path);
+          if(!lib)
+            continue;
+          pfunc=GetProcAddress(lib,"getaddrinfo");
+          if(!pfunc)
+          {
+            FreeLibrary(lib);
+            lib=NULL;
+            continue;
+          }
+          p_getaddrinfo=reinterpret_cast<t_getaddrinfo>(pfunc);
+          pfunc=GetProcAddress(lib,"freeaddrinfo");
+          if(!pfunc)
+          {
+            FreeLibrary(lib);
+            lib=NULL;
+            p_getaddrinfo=NULL;
+            continue;
+          }
+          p_freeaddrinfo=reinterpret_cast<t_freeaddrinfo>(pfunc);
+          break;
+        }
+      }
+#else
+      p_freeaddrinfo=::freeaddrinfo;
+      p_getaddrinfo=::getaddrinfo;
+#endif
+      inited = true;
+    }
+  }
+
+}
+
+/****
+*****
+*****
+*****
+****/
+
+GIOChannelSocketSSL :: GIOChannelSocketSSL ():
+   _channel (0),
+   _tag_watch (0),
+   _tag_timeout (0),
+   _listener (0),
+   _out_buf (g_string_new (NULL)),
+   _in_buf (g_string_new (NULL)),
+   _io_performed (false)
+{
+   debug ("GIOChannelSocketSSL ctor " << (void*)this);
+}
+
+GIOChannel *
+GIOChannelSocketSSL :: create_channel (const StringView& host_in, int port, std::string& setme_err)
+{
+  int err;
+  int sockfd;
+
+#ifndef G_OS_WIN32
+  signal (SIGPIPE, SIG_IGN);
+#endif
+
+  // get an addrinfo for the host
+  const std::string host (host_in.str, host_in.len);
+  char portbuf[32], hpbuf[255];
+  g_snprintf (portbuf, sizeof(portbuf), "%d", port);
+  g_snprintf (hpbuf,sizeof(hpbuf),"%s:%s",host_in.str,portbuf);
+
+#ifdef G_OS_WIN32 // windows might not have getaddrinfo...
+  if (!p_getaddrinfo)
+  {
+    struct hostent * ans = isalpha (host[0])
+      ? gethostbyname (host.c_str())
+      : gethostbyaddr (host.c_str(), host.size(), AF_INET);
+
+    err = WSAGetLastError();
+    if (err || !ans) {
+      setme_err = get_last_error (err);
+      return 0;
+    }
+
+    // try opening the socket
+    sockfd = socket (AF_INET, SOCK_STREAM, 0 /*IPPROTO_TCP*/);
+    if (sockfd < 0)
+      return 0;
+
+    // Try connecting
+    int i = 0;
+    err = -1;
+    struct sockaddr_in server;
+    memset (&server, 0, sizeof(struct sockaddr_in));
+    while (err && ans->h_addr_list[i])
+    {
+      char *addr = ans->h_addr_list[i];
+      memcpy (&server.sin_addr, addr, ans->h_length);
+      server.sin_family = AF_INET;
+      server.sin_port = htons(port);
+      ++i;
+      err = connect (sockfd,(struct sockaddr*)&server, sizeof(server));
+    }
+
+    if (err) {
+      closesocket (sockfd);
+      setme_err = get_last_error (err);
+      return 0;
+    }
+  }
+  else
+#endif // #ifdef G_OS_WIN32 ...
+  {
+    errno = 0;
+    struct addrinfo hints;
+    memset (&hints, 0, sizeof(struct addrinfo));
+    hints.ai_flags = 0;
+    hints.ai_family = 0;
+    hints.ai_socktype = SOCK_STREAM;
+    struct addrinfo * ans;
+    err = p_getaddrinfo (host.c_str(), portbuf, &hints, &ans);
+    if (err != 0) {
+      char buf[512];
+      snprintf (buf, sizeof(buf), _("Error connecting to \"%s\""), hpbuf);
+      setme_err = buf;
+      if (errno) {
+        setme_err += " (";
+        setme_err += file :: pan_strerror (errno);
+        setme_err += ")";
+      }
+      return 0;
+    }
+
+    // try to open a socket on any ipv4 or ipv6 addresses we found
+    errno = 0;
+    sockfd = -1;
+    for (struct addrinfo * walk(ans); walk && sockfd<0; walk=walk->ai_next)
+    {
+      // only use ipv4 or ipv6 addresses
+      if ((walk->ai_family!=PF_INET) && (walk->ai_family!=PF_INET6))
+        continue;
+
+      // try to create a socket...
+      sockfd = ::socket (walk->ai_family, walk->ai_socktype, walk->ai_protocol);
+      if (sockfd < 0)
+        continue;
+
+      // and make a connection
+      if (::connect (sockfd, walk->ai_addr, walk->ai_addrlen) < 0) {
+        closesocket (sockfd);
+        sockfd = -1;
+      }
+    }
+
+    // cleanup
+    p_freeaddrinfo (ans);
+  }
+
+  // create the giochannel...
+  if (sockfd < 0) {
+    char buf[512];
+    snprintf (buf, sizeof(buf), _("Error connecting to \"%s\""), hpbuf);
+    setme_err = buf;
+    if (errno) {
+      setme_err += " (";
+      setme_err += file :: pan_strerror (errno);
+      setme_err += ")";
+    }
+    return 0;
+  }
+
+  GIOChannel * channel (0);
+#ifndef G_OS_WIN32
+  channel = g_io_channel_unix_new (sockfd);
+  g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
+#else
+  channel = g_io_channel_win32_new_socket (sockfd);
+#endif
+  if (g_io_channel_get_encoding (channel) != NULL)
+    g_io_channel_set_encoding (channel, NULL, NULL);
+  g_io_channel_set_buffered (channel, true);
+  g_io_channel_set_line_term (channel, "\n", 1);
+  return ssl_get_iochannel(channel);
+}
+
+namespace
+{
+  void remove_source (guint& tag) {
+    if (tag) {
+      g_source_remove (tag);
+      tag = 0;
+    }
+  }
+}
+
+GIOChannelSocketSSL :: ~GIOChannelSocketSSL ()
+{
+//std::cerr << LINE_ID << " destroying socket " << this << std::endl;
+
+  remove_source (_tag_watch);
+  remove_source (_tag_timeout);
+
+
+  if (_channel)
+  {
+    g_io_channel_shutdown (_channel, true, NULL);
+    g_io_channel_unref (_channel);
+    _channel = 0;
+  }
+
+  g_string_free (_out_buf, true);
+  _out_buf = 0;
+
+  g_string_free (_in_buf, true);
+  _in_buf = 0;
+}
+
+bool
+GIOChannelSocketSSL :: open (const StringView& address, int port, std::string& setme_err)
+{
+  _host.assign (address.str, address.len);
+  _channel = create_channel (address, port, setme_err);
+  return _channel != 0;
+}
+
+void
+GIOChannelSocketSSL :: get_host (std::string& setme) const
+{
+  setme = _host;
+}
+
+void
+GIOChannelSocketSSL :: write_command (const StringView& command, Listener * l)
+{
+  _partial_read.clear ();
+  _listener = l;
+
+  g_string_truncate (_out_buf, 0);
+  if (!command.empty())
+    g_string_append_len (_out_buf, command.str, command.len);
+
+  set_watch_mode (WRITE_NOW);
+}
+
+/***
+**** SSL Functions
+***/
+
+namespace
+{
+  typedef struct
+  {
+    GIOChannel pad;
+    gint fd;
+    GIOChannel *giochan;
+    SSL *ssl;
+    SSL_CTX *ctx;
+    unsigned int verify:1;
+  } GIOSSLChannel;
+
+  SSL_CTX* ssl_init(void)
+  {
+    SSL_library_init();
+    SSL_load_error_strings();
+
+    SSL_CTX* ctx (SSL_CTX_new(SSLv3_client_method()));
+    return ctx;
+  }
+
+  static GIOStatus ssl_errno(gint e)
+  {
+    switch(e)
+    {
+      case EINVAL:
+        return G_IO_STATUS_ERROR;
+      case EINTR:
+      case EAGAIN:
+        return G_IO_STATUS_AGAIN;
+      default:
+        return G_IO_STATUS_ERROR;
+    }
+    return G_IO_STATUS_ERROR;
+  }
+
+  void ssl_free(GIOChannel *handle)
+  {
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+    g_io_channel_unref(chan->giochan);
+    SSL_free(chan->ssl);
+    SSL_CTX_free(chan->ctx);
+    g_free(chan);
+  }
+
+  int ssl_handshake(GIOChannel *handle)
+  {
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+    int ret, err;
+    X509 *cert;
+    const char *errstr;
+
+    ret = SSL_connect(chan->ssl);
+    if (ret <= 0) {
+      err = SSL_get_error(chan->ssl, ret);
+      if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
+        errstr = ERR_reason_error_string(ERR_get_error());
+        g_warning("SSL handshake failed: %s", errstr != NULL ? errstr : "server closed connection");
+        return -1;
+      }
+      return err == SSL_ERROR_WANT_READ ? 1 : 3;
+    }
+
+    cert = SSL_get_peer_certificate(chan->ssl);
+    if (cert == NULL) {
+      g_warning("SSL server supplied no certificate");
+      return -1;
+    }
+//    ret = !chan->verify || ssl_verify(chan->ssl, chan->ctx, cert);
+    X509_free(cert);
+    return ret ? 0 : -1;
+  }
+
+  GIOStatus ssl_read(GIOChannel *handle, gchar *buf, gsize len, gsize *ret, GError **gerr)
+  {
+    return G_IO_STATUS_AGAIN;
+  }
+
+  GIOStatus ssl_read_line_string(GIOChannel *handle, GString* g, gsize *ret, GError **gerr)
+  {
+
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+    gint err;
+    char tmp[4096];
+    g_string_set_size(g,0);
+
+    /// TODO perhaps own thread for this??
+    if (handle->read_buf->len == 0)
+    {
+      err = SSL_read(chan->ssl, tmp, sizeof(tmp));
+      if(err < 0)
+      {
+        if (ret) *ret = 0;
+        if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ) {
+          std::cerr<<"again\n";
+          return G_IO_STATUS_AGAIN;
+        }
+        return ssl_errno(errno);
+      }
+      else
+      {
+        std::cerr<<"append len "<<err<<std::endl;
+        g_string_append_len (handle->read_buf,tmp,err);
+        if (ret) *ret = err;
+      }
+    }
+
+    //fill in from read_buf
+    char * buf = handle->read_buf->str;
+    int pos(0);
+    bool found(false);
+    while (*buf) { if (*buf == '\n') { found = true; break; } ++pos; ++buf; }
+    if (found) {
+      int _pos(std::min(pos+1,(int)handle->read_buf->len));
+      g_string_append_len(g, handle->read_buf->str, _pos);
+      g_string_erase (handle->read_buf, 0, _pos);
+      return G_IO_STATUS_NORMAL;
+    }
+    // no linebreak, partial line. retry later...
+    return G_IO_STATUS_AGAIN;
+  }
+
+  GIOStatus ssl_write(GIOChannel *handle, const gchar *buf, gsize len, gsize *ret, GError **gerr)
+  {
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+    gint err;
+
+    err = SSL_write(chan->ssl, (const char *)buf, len);
+    if(err < 0)
+    {
+      *ret = 0;
+      if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ)
+        return G_IO_STATUS_AGAIN;
+      return ssl_errno(errno);
+    }
+    else
+    {
+      *ret = err;
+      return G_IO_STATUS_NORMAL;
+    }
+    return G_IO_STATUS_ERROR;
+  }
+
+  GIOStatus ssl_seek(GIOChannel *handle, gint64 offset, GSeekType type, GError **gerr)
+  {
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+    GIOError e;
+    e = g_io_channel_seek(chan->giochan, offset, type);
+    return (e == G_IO_ERROR_NONE) ? G_IO_STATUS_NORMAL : G_IO_STATUS_ERROR;
+  }
+
+  GIOStatus ssl_close(GIOChannel *handle, GError **gerr)
+  {
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+    g_io_channel_close(chan->giochan);
+
+    return G_IO_STATUS_NORMAL;
+  }
+
+  GSource *ssl_create_watch(GIOChannel *handle, GIOCondition cond)
+  {
+    GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+    return chan->giochan->funcs->io_create_watch(handle, cond);
+  }
+
+  GIOStatus ssl_set_flags(GIOChannel *handle, GIOFlags flags, GError **gerr)
+  {
+      GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+      return chan->giochan->funcs->io_set_flags(handle, flags, gerr);
+  }
+
+  GIOFlags ssl_get_flags(GIOChannel *handle)
+  {
+      GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+      return chan->giochan->funcs->io_get_flags(handle);
+  }
+
+  GIOFuncs ssl_channel_funcs = {
+    ssl_read,
+    ssl_write,
+    ssl_seek,
+    ssl_close,
+    ssl_create_watch,
+    ssl_free,
+    ssl_set_flags,
+    ssl_get_flags
+  };
+}
+
+/***
+****
+***/
+
+GIOChannelSocketSSL :: DoResult
+GIOChannelSocketSSL :: do_read ()
+{
+  g_assert (!_out_buf->len);
+
+  GError * err (0);
+  GString * g (_in_buf);
+
+  bool more (true);
+
+  GIOSSLChannel * chan = (GIOSSLChannel*)_channel;
+
+  while (more && !_abort_flag)
+  {
+    _io_performed = true;
+    const GIOStatus status (ssl_read_line_string(_channel, g, NULL, &err));
+                                     //g_io_channel_read_line_string (_channel, g, NULL, &err));
+
+    if (status == G_IO_STATUS_NORMAL)
+    {
+      g_string_prepend_len (g, _partial_read.c_str(), _partial_read.size());
+      _partial_read.clear ();
+
+      debug_v ("read [" << g->str << "]"); // verbose debug, if --debug --debug was on the command-line
+      increment_xfer_byte_count (g->len);
+      if (g_str_has_suffix (g->str, "\r\n"))
+        g_string_truncate (g, g->len-2);
+      more = _listener->on_socket_response (this, StringView (g->str, g->len));
+    }
+    else if (status == G_IO_STATUS_AGAIN)
+    {
+      // see if we've got a partial line buffered up
+      if (_channel->read_buf) {
+        _partial_read.append (_channel->read_buf->str, _channel->read_buf->len);
+        std::cerr<<"partial read : "<<_partial_read<<std::endl;
+        g_string_set_size (_channel->read_buf, 0);
+      }
+      return IO_READ;
+    }
+    else
+    {
+      const char * msg (err ? err->message : _("Unknown Error"));
+      Log::add_err_va (_("Error reading from %s: %s"), _host.c_str(), msg);
+      if (err != NULL)
+        g_clear_error (&err);
+      return IO_ERR;
+    }
+  }
+
+  return IO_DONE;
+}
+
+
+GIOChannelSocketSSL :: DoResult
+GIOChannelSocketSSL :: do_write ()
+{
+  g_assert (_partial_read.empty());
+
+  GString * g = _out_buf;
+
+#if 0
+  // #ifdef DEBUG_SOCKET_IO
+  // -2 to trim out trailing \r\n
+  std::cerr << LINE_ID << " channel " << _channel
+            << " writing ["<<StringView(g->str,g->len>=2?g->len-2:g->len)<< "]\n";
+#endif
+
+  _io_performed = true;
+  GError * err = 0;
+  gsize out = 0;
+  GIOStatus status = g->len
+    ? ssl_write(_channel, g->str, g->len, &out, &err)
+    : G_IO_STATUS_NORMAL;
+  debug ("socket " << this << " channel " << _channel
+                   << " maybe wrote [" << g->str << "]; status was " << status);
+
+  if (status == G_IO_STATUS_NORMAL)
+    status = g_io_channel_flush (_channel, &err);
+
+  if (err) {
+    Log::add_err (err->message);
+    g_clear_error (&err);
+    return IO_ERR;
+  }
+
+  if (out > 0) {
+    increment_xfer_byte_count (out);
+    g_string_erase (g, 0, out);
+  }
+
+  const bool finished = (!g->len) && (status==G_IO_STATUS_NORMAL);
+  if (!finished) return IO_WRITE; // not done writing.
+  if (_listener) return IO_READ; // listener wants to read the server's response
+  return IO_DONE; // done writing and not listening to response.
+}
+
+gboolean
+GIOChannelSocketSSL :: timeout_func (gpointer sock_gp)
+{
+  GIOChannelSocketSSL * self (static_cast<GIOChannelSocketSSL*>(sock_gp));
+
+  if (!self->_io_performed)
+  {
+    debug ("error: channel " << self->_channel << " not responding.");
+    gio_func (self->_channel, G_IO_ERR, sock_gp);
+    return false;
+  }
+
+  // wait another TIMEOUT_SECS and check again.
+  self->_io_performed = false;
+  return true;
+}
+
+gboolean
+GIOChannelSocketSSL :: gio_func (GIOChannel   * channel,
+                              GIOCondition   cond,
+                              gpointer       sock_gp)
+{
+  return static_cast<GIOChannelSocketSSL*>(sock_gp)->gio_func (channel, cond);
+}
+
+gboolean
+GIOChannelSocketSSL :: gio_func (GIOChannel   * channel,
+                              GIOCondition   cond)
+{
+  debug ("gio_func: sock " << this << ", channel " << channel << ", cond " << cond);
+
+  set_watch_mode (IGNORE_NOW);
+
+  if (_abort_flag)
+  {
+    _listener->on_socket_abort (this);
+  }
+  else if (!(cond & (G_IO_IN | G_IO_OUT)))
+  {
+    _listener->on_socket_error (this);
+  }
+  else // G_IO_IN or G_IO_OUT
+  {
+    const DoResult result = (cond & G_IO_IN) ? do_read () : do_write ();
+    /* I keep reading about crashes due to this check on OSX.
+     * _abort_flag is never set so this won't cause a problem.
+     * could be a bug in gcc 4.2.1.
+     */
+    /*if (_abort_flag)        _listener->on_socket_abort (this);
+    else*/ if (result == IO_ERR)   _listener->on_socket_error (this);
+    else if (result == IO_READ)  set_watch_mode (READ_NOW);
+    else if (result == IO_WRITE) set_watch_mode (WRITE_NOW);
+  }
+
+  return false; // set_watch_now(IGNORE) cleared the tag that called this func
+}
+
+namespace
+{
+  const unsigned int TIMEOUT_SECS (30);
+}
+
+void
+GIOChannelSocketSSL :: set_watch_mode (WatchMode mode)
+{
+  debug ("socket " << this << " calling set_watch_mode " << mode << "; _channel is " << _channel);
+  remove_source (_tag_watch);
+  remove_source (_tag_timeout);
+
+  guint cond;
+  switch (mode)
+  {
+    case IGNORE_NOW:
+      // don't add any watches
+      debug("channel " << _channel << " setting mode **IGNORE**");
+      break;
+
+    case READ_NOW:
+      debug("channel " << _channel << " setting mode read");
+      cond = (int)G_IO_IN | (int)G_IO_ERR | (int)G_IO_HUP | (int)G_IO_NVAL;
+      _tag_watch = g_io_add_watch (_channel, (GIOCondition)cond, gio_func, this);
+      _tag_timeout = g_timeout_add (TIMEOUT_SECS*1000, timeout_func, this);
+      _io_performed = false;
+      break;
+
+    case WRITE_NOW:
+      debug("channel " << _channel << " setting mode write");
+      cond = (int)G_IO_OUT | (int)G_IO_ERR | (int)G_IO_HUP | (int)G_IO_NVAL;
+      _tag_watch = g_io_add_watch (_channel, (GIOCondition)cond, gio_func, this);
+      _tag_timeout = g_timeout_add (TIMEOUT_SECS*1000, timeout_func, this);
+      _io_performed = false;
+      break;
+  }
+
+  debug ("set_watch_mode " << mode << ": _tag_watch is now " << _tag_watch);
+}
+
+
+GIOChannel *
+GIOChannelSocketSSL :: ssl_get_iochannel(GIOChannel *handle, const char *mycert, const char *mypkey, const char *cafile, const char *capath, gboolean verify)
+{
+	GIOSSLChannel *chan;
+	GIOChannel *gchan;
+	int err, fd;
+	SSL *ssl;
+	SSL_CTX *ctx;
+
+	g_return_val_if_fail(handle != NULL, NULL);
+
+	if(!(ctx = ssl_init()))
+		return NULL;
+
+	if(!(fd = g_io_channel_unix_get_fd(handle)))
+		return NULL;
+
+    ///TODO cert validation !
+//	if (mycert && *mycert) {
+//		char *scert = NULL, *spkey = NULL;
+//		if ((ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+//			g_error("Could not allocate memory for SSL context");
+//			return NULL;
+//		}
+//		scert = convert_home(mycert);
+//		if (mypkey && *mypkey)
+//			spkey = convert_home(mypkey);
+//		if (! SSL_CTX_use_certificate_file(ctx, scert, SSL_FILETYPE_PEM))
+//			g_warning("Loading of client certificate '%s' failed", mycert);
+//		else if (! SSL_CTX_use_PrivateKey_file(ctx, spkey ? spkey : scert, SSL_FILETYPE_PEM))
+//			g_warning("Loading of private key '%s' failed", mypkey ? mypkey : mycert);
+//		else if (! SSL_CTX_check_private_key(ctx))
+//			g_warning("Private key does not match the certificate");
+//		g_free(scert);
+//		g_free(spkey);
+//	}
+
+//	if ((cafile && *cafile) || (capath && *capath)) {
+//		char *scafile = NULL;
+//		char *scapath = NULL;
+//		if (! ctx && (ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+//			g_error("Could not allocate memory for SSL context");
+//			return NULL;
+//		}
+//		if (cafile && *cafile)
+//			scafile = convert_home(cafile);
+//		if (capath && *capath)
+//			scapath = convert_home(capath);
+//		if (! SSL_CTX_load_verify_locations(ctx, scafile, scapath)) {
+//			g_warning("Could not load CA list for verifying SSL server certificate");
+//			g_free(scafile);
+//			g_free(scapath);
+//			SSL_CTX_free(ctx);
+//			return NULL;
+//		}
+//		g_free(scafile);
+//		g_free(scapath);
+//		verify = TRUE;
+//	}
+
+//	if (ctx == NULL)
+//		ctx = ssl_ctx;
+
+	if(!(ssl = SSL_new(ctx)))
+	{
+	  SSL_CTX_free(ctx);
+		g_warning("Failed to allocate SSL structure");
+		return NULL;
+	}
+
+	if(!(err = SSL_set_fd(ssl, fd)))
+	{
+		g_warning("Failed to associate socket to SSL stream");
+		SSL_free(ssl);
+  	SSL_CTX_free(ctx);
+		return NULL;
+	}
+
+	chan = g_new0(GIOSSLChannel, 1);
+	chan->fd = fd;
+	chan->giochan = handle;
+	chan->ssl = ssl;
+	chan->ctx = ctx;
+	chan->verify = verify;
+
+	gchan = (GIOChannel *)chan;
+	gchan->funcs = &ssl_channel_funcs;
+	g_io_channel_init(gchan);
+  gchan->read_buf = g_string_sized_new(4096*128);
+
+  while (ssl_handshake(gchan) != 0) ;
+
+  std::cerr<<"handshake success!\n";
+
+	return gchan;
+}
+
+/***
+****  GIOChannel::SocketCreator -- create a socket in a worker thread
+***/
+
+namespace
+{
+  struct ThreadWorker : public WorkerPool::Worker,
+                        public WorkerPool::Worker::Listener
+  {
+    std::string host;
+    int port;
+    Socket::Creator::Listener * listener;
+
+    bool ok;
+    Socket * socket;
+    std::string err;
+
+    ThreadWorker (const StringView& h, int p, Socket::Creator::Listener *l):
+      host(h), port(p), listener(l), ok(false), socket(0) {}
+
+    void do_work ()
+    {
+      socket = new GIOChannelSocketSSL ();
+      ok = socket->open (host, port, err);
+    }
+
+    /** called in main thread after do_work() is done */
+    void on_worker_done (bool cancelled UNUSED)
+    {
+      // pass results to main thread...
+      if (!err.empty())   Log :: add_err (err.c_str());
+      listener->on_socket_created (host, port, ok, socket);
+    }
+  };
+}
+
+void
+GIOChannelSocketSSL :: Creator :: create_socket (const StringView & host,
+                                              int                port,
+                                              WorkerPool       & threadpool,
+                                              Listener         * listener)
+{
+  ensure_module_inited ();
+
+  ThreadWorker * w = new ThreadWorker (host, port, listener);
+  threadpool.push_work (w, w, true);
+}
diff --git a/pan/tasks/socket-impl-openssl.h b/pan/tasks/socket-impl-openssl.h
new file mode 100644
index 0000000..6bec184
--- /dev/null
+++ b/pan/tasks/socket-impl-openssl.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Pan - A Newsreader for Gtk+
+ * Copyright (C) 2002-2006  Charles Kerr <charles rebelbase com>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __SocketSSL_h__
+#define __SocketSSL_h__
+
+#include <string>
+#include <glib/giochannel.h>
+#include <glib/gstring.h>
+#include <pan/tasks/socket.h>
+
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+namespace pan
+{
+  /**
+   * glib implementation of Socket
+   *
+   * @ingroup tasks
+   */
+  class GIOChannelSocketSSL: public Socket
+  {
+    public:
+      GIOChannelSocketSSL ();
+      virtual ~GIOChannelSocketSSL ();
+      virtual bool open (const StringView& address, int port, std::string& setme_err);
+      virtual void write_command (const StringView& chars, Listener *);
+      virtual void get_host (std::string& setme) const;
+
+    private:
+      GIOChannel * _channel;
+      unsigned int _tag_watch;
+      unsigned int _tag_timeout;
+      Listener * _listener;
+      GString * _out_buf;
+      GString * _in_buf;
+      std::string _partial_read;
+      std::string _host;
+      bool _io_performed;
+
+    private:
+      enum WatchMode { READ_NOW, WRITE_NOW, IGNORE_NOW };
+      void set_watch_mode (WatchMode mode);
+      static gboolean gio_func (GIOChannel*, GIOCondition, gpointer);
+      gboolean gio_func (GIOChannel*, GIOCondition);
+      static gboolean timeout_func (gpointer);
+      enum DoResult { IO_ERR, IO_READ, IO_WRITE, IO_DONE };
+      DoResult do_read ();
+      DoResult do_write ();
+
+      GIOChannel * create_channel (const StringView& host_in, int port, std::string& setme_err);
+
+    //SSL functions
+    private:
+      GIOChannel* ssl_get_iochannel(GIOChannel *handle, const char *mycert=NULL, const char *mypkey=NULL,
+                                    const char *cafile=NULL, const char *capath=NULL, gboolean verify=false);
+
+    public:
+
+      /**
+       * Socket::Creator that instantiates GIOSocket objects.
+       */
+      class Creator: public Socket::Creator {
+        public:
+          virtual ~Creator () { }
+          virtual void create_socket (const StringView& host, int port, WorkerPool&, Listener *l);
+      };
+  };
+}
+
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]