bincimap

Log | Files | Refs | LICENSE

io-ssl.cc (8646B)


      1 /* -*- Mode: c++; -*- */
      2 /*  --------------------------------------------------------------------
      3  *  Filename:
      4  *    io-ssl.cc
      5  *  
      6  *  Description:
      7  *    Implementation of the IO class.
      8  *
      9  *  Authors:
     10  *    Andreas Aardal Hanssen <andreas-binc curly bincimap spot org>
     11  *
     12  *  Bugs:
     13  *
     14  *  ChangeLog:
     15  *
     16  *  --------------------------------------------------------------------
     17  *  Copyright 2002-2005 Andreas Aardal Hanssen
     18  *
     19  *  This program is free software; you can redistribute it and/or modify
     20  *  it under the terms of the GNU General Public License as published by
     21  *  the Free Software Foundation; either version 2 of the License, or
     22  *  (at your option) any later version.
     23  *
     24  *  This program is distributed in the hope that it will be useful,
     25  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     26  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     27  *  GNU General Public License for more details.
     28  *
     29  *  You should have received a copy of the GNU General Public License
     30  *  along with this program; if not, write to the Free Software
     31  *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
     32  *  --------------------------------------------------------------------
     33  */
     34 #ifdef HAVE_CONFIG_H
     35 #include <config.h>
     36 #endif
     37 
     38 #ifdef WITH_SSL
     39 
     40 #include <string>
     41 
     42 #include <openssl/ssl.h>
     43 
     44 #include "session.h"
     45 #include "io-ssl.h"
     46 
     47 using namespace ::std;
     48 using namespace Binc;
     49 
     50 //------------------------------------------------------------------------
     51 SSLEnabledIO::SSLEnabledIO(void)
     52 {
     53 }
     54 
     55 //------------------------------------------------------------------------
     56 SSLEnabledIO::SSLEnabledIO(FILE *fp) : IO(fp)
     57 {
     58 }
     59 
     60 //------------------------------------------------------------------------
     61 SSLEnabledIO::~SSLEnabledIO(void)
     62 {
     63   switch (mode) {
     64   case MODE_SSL:
     65     SSL_shutdown(ssl);
     66     SSL_shutdown(ssl);
     67 
     68     SSL_free(ssl);
     69     SSL_CTX_free(ctx);
     70 
     71     break;
     72   case MODE_SYSLOG:
     73     closelog();
     74     break;
     75   default:
     76     break;
     77   }
     78 }
     79 
     80 
     81 //------------------------------------------------------------------------
     82 bool SSLEnabledIO::setModeSSL(void)
     83 {
     84   SSL_library_init();
     85   SSL_load_error_strings();
     86 
     87   OpenSSL_add_ssl_algorithms();
     88   
     89   if ((ctx = SSL_CTX_new(SSLv23_server_method())) == 0) {
     90     setLastError("SSL error: internal error when creating CTX: " 
     91 		 + string(ERR_error_string(ERR_get_error(), 0)));
     92     return false;
     93   }
     94   
     95   SSL_CTX_set_options(ctx, SSL_OP_ALL);
     96 
     97   Session &session = Session::getInstance();
     98 
     99   string clist = session.globalconfig["SSL"]["cipher list"];
    100   if (clist != "" && !SSL_CTX_set_cipher_list(ctx, clist.c_str())) {
    101     setLastError("SSL error: cannot load cipher list " + clist);
    102     return false;
    103   }
    104 
    105   string CAfile = session.globalconfig["SSL"]["ca file"];
    106   if (CAfile == "") CAfile == "/usr/share/ssl/certs/.crt";
    107 
    108   string CApath = session.globalconfig["SSL"]["ca path"];
    109   if (CApath == "") CApath == "/usr/share/ssl/certs/";
    110 
    111   SSL_CTX_set_default_verify_paths(ctx);
    112 
    113   string pemname = session.globalconfig["SSL"]["pem file"];
    114   if (pemname == "") pemname = "/usr/share/ssl/certs/stunnel.pem";
    115 
    116   if (!SSL_CTX_use_certificate_chain_file(ctx, pemname.c_str())) {
    117     setLastError("SSL error: unable to use certificate in PEM file: "
    118 		 + pemname + ": "
    119 		 + string(ERR_error_string(ERR_get_error(), 0)));
    120     return false;
    121   }
    122 
    123   if (!SSL_CTX_use_PrivateKey_file(ctx, pemname.c_str(), SSL_FILETYPE_PEM)) {
    124     setLastError("SSL error: unable to use private key in PEM file: "
    125 		 + pemname + ": "
    126 		 + string(ERR_error_string(ERR_get_error(), 0)));
    127     return false;
    128   }
    129 
    130   if (!SSL_CTX_check_private_key(ctx)) {
    131     setLastError("SSL error: public and private key in PEM file"
    132 		 " don't match: " 
    133 		 + pemname + ": "
    134 		 + string(ERR_error_string(ERR_get_error(), 0)));
    135     return false;
    136   }
    137 
    138   const char *CAfileptr = CAfile == "" ? 0 : CAfile.c_str();
    139   const char *CApathptr = CApath == "" ? 0 : CApath.c_str();
    140   if (CAfileptr || CApathptr) {
    141     if (!SSL_CTX_load_verify_locations(ctx, CAfileptr, CApathptr)) {
    142       setLastError("SSL error: unable to load CA file or path: "
    143 		   + string(ERR_error_string(ERR_get_error(), 0)));
    144       return false;
    145     }
    146   }
    147 
    148   if (session.globalconfig["SSL"]["verify peer"] == "yes")
    149     SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
    150   else
    151     SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0);
    152 
    153   SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CAfile.c_str()));
    154 
    155   SSL *ssl = SSL_new(ctx);
    156   if (ssl == 0) {
    157     setLastError("SSL error: when creating SSL object: "
    158 		 + string(ERR_error_string(ERR_get_error(), 0)));
    159     return false;
    160   }
    161 
    162   SSL_clear(ssl);
    163 
    164   SSL_set_rfd(ssl, 0);
    165   SSL_set_wfd(ssl, 1);
    166   SSL_set_accept_state(ssl);
    167    
    168   fflush(stdout);
    169 
    170   int result = SSL_accept(ssl);
    171   if (result <= 0) {
    172     string errstr;
    173     switch (SSL_get_error(ssl, 0)) {
    174     case SSL_ERROR_NONE: errstr = "Unknown error"; break;
    175     case SSL_ERROR_ZERO_RETURN: errstr = "Connection closed"; break;
    176     case SSL_ERROR_WANT_READ:
    177     case SSL_ERROR_WANT_WRITE:
    178     case SSL_ERROR_WANT_CONNECT: errstr = "Operation did not complete"; break;
    179     case SSL_ERROR_WANT_X509_LOOKUP: errstr = "X509 lookup requested"; break;
    180     case SSL_ERROR_SYSCALL: errstr = "Unexpected EOF"; break;
    181     case SSL_ERROR_SSL: errstr = "Internal SSL error: ";
    182       errstr += string(ERR_error_string(ERR_get_error(), 0)); break;
    183     }
    184 
    185     setLastError(errstr);
    186     return false;
    187   }
    188 
    189   this->ssl = ssl;
    190   mode = MODE_SSL;
    191   return true;
    192 }
    193 
    194 //------------------------------------------------------------------------
    195 bool SSLEnabledIO::isModeSSL(void)
    196 {
    197   return mode == MODE_SSL;
    198 }
    199 
    200 //------------------------------------------------------------------------
    201 void SSLEnabledIO::writeStr(const string s)
    202 {
    203   if (s == "")
    204     return;
    205   
    206   if (mode == MODE_PLAIN) {
    207     // Set transfer timeout
    208     alarm(transfertimeout);
    209     int n = fwrite(s.c_str(), 1, s.length(), fpout);
    210     fflush(fpout);
    211     alarm(0);
    212     
    213     Session::getInstance().addWriteBytes(n);
    214 
    215     if (n != (int) s.length()) {
    216       setLastError("Output error: fwrite wrote too few bytes");
    217       return;
    218     }
    219   } else if (mode == MODE_SYSLOG) {
    220     // Set transfer timeout
    221     alarm(transfertimeout);
    222     syslog(LOG_INFO, "%s", s.c_str());
    223     alarm(0);
    224   } else if (mode == MODE_SSL) {
    225     do {
    226       alarm(transfertimeout);
    227       int retval = SSL_write(ssl, s.c_str(), s.length());
    228       alarm(0);
    229       
    230       if (retval > 0) {
    231 	Session::getInstance().addWriteBytes(retval);
    232 	break;
    233       } else if (retval == 0) {
    234 	/* call get_error */
    235 	setLastError("SSL_write returned 0");
    236 	return;
    237       } else if (retval < 0) {
    238 	int err = SSL_get_error(ssl, retval);
    239 	
    240 	if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
    241 	  setLastError("SSL_write returned < 0");
    242 	  return;
    243 	} else {
    244 	  // retry when we get this error.
    245 	} 
    246       }
    247     } while (1);
    248   }
    249 }
    250 
    251 //------------------------------------------------------------------------
    252 int SSLEnabledIO::fillBuffer(int timeout, bool retry)
    253 {
    254   if (mode == MODE_PLAIN)
    255     return IO::fillBuffer(timeout, retry);
    256 
    257   fd_set rfds;
    258   FD_ZERO(&rfds);
    259   FD_SET(0, &rfds);
    260   
    261   for (;;) {
    262     fd_set rfds;
    263     FD_ZERO(&rfds);
    264     FD_SET(0, &rfds);
    265 
    266     struct timeval tv;
    267     tv.tv_sec = timeout;
    268     tv.tv_usec = 0;
    269 
    270     if (pending() == 0) {
    271       int r = ::select(fileno(stdin) + 1, &rfds, 0, 0, timeout ? &tv : 0);
    272       if (r == 0) {
    273         setLastError("Reading from client timed out.");
    274         return -2;
    275       }
    276 
    277       if (r < 0) {
    278         setLastError("Error when reading from client");
    279         return -1;
    280       }
    281     }
    282     
    283     char buf[1024];
    284     unsigned int readBytes = SSL_read(ssl, buf, sizeof(buf));      
    285     if (readBytes > 0) {
    286 
    287       Session::getInstance().addReadBytes(readBytes);
    288 
    289       for (unsigned int i = 0; i < readBytes; ++i)
    290 	inputBuffer.push_front(buf[i]);
    291 
    292       return readBytes;
    293     }
    294     
    295     if (readBytes == 0) {
    296       setLastError("Client disconnected");
    297       return -1;
    298     }
    299     
    300     if (readBytes < 0) {
    301       int err = SSL_get_error(ssl, readBytes);
    302       if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
    303 	setLastError("SSL error: read returned " 
    304 		     + toString(readBytes) + ": " 
    305 		     + string(ERR_error_string(ERR_get_error(), 0)));
    306 	return -1;
    307       }
    308       
    309       if (timeout == 0 || !retry) {
    310 	setLastError("Reading from client timed out.");
    311 	return -2;
    312       }
    313     }
    314   }
    315 }
    316 
    317 //------------------------------------------------------------------------
    318 int SSLEnabledIO::pending(void) const
    319 {
    320   int tmp = (mode == MODE_PLAIN ? 0 : SSL_pending(ssl));
    321   if (tmp)
    322     return tmp;
    323   else
    324     return inputBuffer.size();
    325 }
    326 
    327 #endif