Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -15,10 +15,14 @@ #### The suffix to add to executable files. ".exe" for windows. # Nothing for unix. # E = + +#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) +# +FOSSIL_ENABLE_SSL=1 #### C Compile and options for use in building executables that # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used @@ -25,10 +29,15 @@ # for building intermediate code-generator tools. # #TCC = gcc -O6 #TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage TCC = gcc -g -Os -Wall + +# With HTTPS support +ifdef FOSSIL_ENABLE_SSL +TCC += -DFOSSIL_ENABLE_SSL=1 +endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other dependencies. We sometimes add the -static option here # so that we can build a static executable that will run in a @@ -38,11 +47,15 @@ # If you're on OpenSolaris: # LIB += lsocket # Solaris 10 needs: # LIB += -lsocket -lnsl # My assumption is that the Sol10 flags will work for Sol8/9 and possibly 11. - +# +# OpenSSL: +ifdef FOSSIL_ENABLE_SSL +LIB += -lcrypto -lssl +endif #### Tcl shell for use in running the fossil testsuite. # TCLSH = tclsh Index: Makefile.w32 ================================================================== --- Makefile.w32 +++ Makefile.w32 @@ -15,10 +15,14 @@ #### The suffix to add to executable files. ".exe" for windows. # Nothing for unix. # E = .exe + +#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) +# +# FOSSIL_ENABLE_SSL=1 #### C Compile and options for use in building executables that # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used @@ -27,10 +31,15 @@ #TCC = gcc -O6 #TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage #TCC = gcc -g -Os -Wall #TCC = gcc -g -Os -Wall -DFOSSIL_I18N=0 -L/usr/local/lib -I/usr/local/include TCC = gcc -Os -Wall -DFOSSIL_I18N=0 -L/mingw/lib -I/mingw/include + +# With HTTPS support +ifdef FOSSIL_ENABLE_SSL +TCC += -DFOSSIL_ENABLE_SSL=1 +endif #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other dependencies. We sometimes add the -static option here # so that we can build a static executable that will run in a @@ -37,10 +46,14 @@ # chroot jail. # #LIB = -lz #LIB = -lz -lws2_32 LIB = -lmingwex -lz -lws2_32 +# OpenSSL: +ifdef FOSSIL_ENABLE_SSL +LIB += -lcrypto -lssl +endif #### Tcl shell for use in running the fossil testsuite. # TCLSH = tclsh ADDED src/http_ssl.c Index: src/http_ssl.c ================================================================== --- src/http_ssl.c +++ src/http_ssl.c @@ -0,0 +1,290 @@ +/* +** Copyright (c) 2009 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License version 2 as published by the Free Software Foundation. +** +** 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 library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file manages low-level SSL communications. +** +** This file implements a singleton. A single SSL connection may be active +** at a time. State information is stored in static variables. The identity +** of the server is held in global variables that are set by url_parse(). +** +** SSL support is abstracted out into this module because Fossil can +** be compiled without SSL support (which requires OpenSSL library) +*/ + +#include "config.h" + +#ifdef FOSSIL_ENABLE_SSL + +#include +#include +#include + +#include "http_ssl.h" +#include +#include + +/* +** There can only be a single OpenSSL IO connection open at a time. +** State information about that IO is stored in the following +** local variables: +*/ +static int sslIsInit = 0; /* True after global initialization */ +static BIO *iBio; /* OpenSSL I/O abstraction */ +static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ +static SSL_CTX *sslCtx; /* SSL context */ +static SSL *ssl; + + +/* +** Clear the SSL error message +*/ +static void ssl_clear_errmsg(void){ + free(sslErrMsg); + sslErrMsg = 0; +} + +/* +** Set the SSL error message. +*/ +void ssl_set_errmsg(char *zFormat, ...){ + va_list ap; + ssl_clear_errmsg(); + va_start(ap, zFormat); + sslErrMsg = vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** Return the current SSL error message +*/ +const char *ssl_errmsg(void){ + return sslErrMsg; +} + +/* +** Call this routine once before any other use of the SSL interface. +** This routine does initial configuration of the SSL module. +*/ +void ssl_global_init(void){ + if( sslIsInit==0 ){ + SSL_library_init(); + SSL_load_error_strings(); + ERR_load_BIO_strings(); + OpenSSL_add_all_algorithms(); + sslCtx = SSL_CTX_new(SSLv23_client_method()); + sslIsInit = 1; + } +} + +/* +** Call this routine to shutdown the SSL module prior to program exit. +*/ +void ssl_global_shutdown(void){ + if( sslIsInit ){ + SSL_CTX_free(sslCtx); + ssl_clear_errmsg(); + sslIsInit = 0; + } +} + +/* +** Close the currently open SSL connection. If no connection is open, +** this routine is a no-op. +*/ +void ssl_close(void){ + if( iBio!=NULL ){ + BIO_reset(iBio); + BIO_free_all(iBio); + } +} + +/* +** Open an SSL connection. The identify of the server is determined +** by global varibles that are set using url_parse(): +** +** g.urlName Name of the server. Ex: www.fossil-scm.org +** g.urlPort TCP/IP port to use. Ex: 80 +** +** Return the number of errors. +*/ +int ssl_open(void){ + X509 *cert; + int hasSavedCertificate = 0; + + ssl_global_init(); + + /* Get certificate for current server from global config and + * (if we have it in config) add it to certificate store. + */ + cert = ssl_get_certificate(); + if ( cert!=NULL ){ + X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert); + X509_free(cert); + hasSavedCertificate = 1; + } + + iBio = BIO_new_ssl_connect(sslCtx); + BIO_get_ssl(iBio, &ssl); + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + if( iBio==NULL ) { + ssl_set_errmsg("SSL: cannot open SSL (%s)", + ERR_reason_error_string(ERR_get_error())); + return 1; + } + + char *connStr = mprintf("%s:%d", g.urlName, g.urlPort); + BIO_set_conn_hostname(iBio, connStr); + free(connStr); + + if( BIO_do_connect(iBio)<=0 ){ + ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", + g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error())); + ssl_close(); + return 1; + } + + if( BIO_do_handshake(iBio)<=0 ) { + ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", + g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error())); + ssl_close(); + return 1; + } + /* Check if certificate is valid */ + cert = SSL_get_peer_certificate(ssl); + + if ( cert==NULL ){ + ssl_set_errmsg("No SSL certificate was presented by the peer"); + ssl_close(); + return 1; + } + + if( SSL_get_verify_result(ssl) != X509_V_OK ){ + char *desc, *prompt; + BIO *mem; + + mem = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE); + BIO_puts(mem, "\n\nIssued By:\n\n"); + X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE); + BIO_write(mem, "", 1); // null-terminate mem buffer + BIO_get_mem_data(mem, &desc); + + char *warning = ""; + if( hasSavedCertificate ){ + warning = "WARNING: Certificate doesn't match the " + "saved certificate for this host!"; + } + prompt = mprintf("\nUnknown SSL certificate:\n\n%s\n\n%s\n" + "Accept certificate [a=always/y/N]? ", desc, warning); + BIO_free(mem); + + Blob ans; + blob_zero(&ans); + prompt_user(prompt, &ans); + free(prompt); + if( blob_str(&ans)[0]!='y' && blob_str(&ans)[0]!='a' ) { + X509_free(cert); + ssl_set_errmsg("SSL certificate declined"); + ssl_close(); + return 1; + } + if( blob_str(&ans)[0]=='a' ) { + ssl_save_certificate(cert); + } + } + X509_free(cert); + return 0; +} + +/* +** Save certificate to global config. +*/ +void ssl_save_certificate(X509 *cert){ + BIO *mem; + char *zCert, *zHost; + + mem = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(mem, cert); + BIO_write(mem, "", 1); // null-terminate mem buffer + BIO_get_mem_data(mem, &zCert); + zHost = mprintf("cert:%s", g.urlName); + db_set(zHost, zCert, 1); + free(zHost); + BIO_free(mem); +} + +/* +** Get certificate for g.urlName from global config. +** Return NULL if no certificate found. +*/ +X509 *ssl_get_certificate(void){ + char *zHost, *zCert; + BIO *mem; + X509 *cert; + + zHost = mprintf("cert:%s", g.urlName); + zCert = db_get(zHost, NULL); + free(zHost); + if ( zCert==NULL ) + return NULL; + mem = BIO_new(BIO_s_mem()); + BIO_puts(mem, zCert); + cert = PEM_read_bio_X509(mem, NULL, 0, NULL); + free(zCert); + BIO_free(mem); + return cert; +} + +/* +** Send content out over the SSL connection. +*/ +size_t ssl_send(void *NotUsed, void *pContent, size_t N){ + size_t sent; + size_t total = 0; + while( N>0 ){ + sent = BIO_write(iBio, pContent, N); + if( sent<=0 ) break; + total += sent; + N -= sent; + pContent = (void*)&((char*)pContent)[sent]; + } + return total; +} + +/* +** Receive content back from the SSL connection. +*/ +size_t ssl_receive(void *NotUsed, void *pContent, size_t N){ + size_t got; + size_t total = 0; + while( N>0 ){ + got = BIO_read(iBio, pContent, N); + if( got<=0 ) break; + total += got; + N -= got; + pContent = (void*)&((char*)pContent)[got]; + } + return total; +} + +#endif /* FOSSIL_ENABLE_SSL */ Index: src/http_transport.c ================================================================== --- src/http_transport.c +++ src/http_transport.c @@ -49,10 +49,15 @@ /* ** Return the current transport error message. */ const char *transport_errmsg(void){ + #ifdef FOSSIL_ENABLE_SSL + if( g.urlIsHttps ){ + return ssl_errmsg(); + } + #endif return socket_errmsg(); } /* ** Retrieve send/receive counts from the transport layer. If "resetFlag" @@ -79,12 +84,17 @@ */ int transport_open(void){ int rc = 0; if( transport.isOpen==0 ){ if( g.urlIsHttps ){ - socket_set_errmsg("HTTPS: is not yet implemented"); + #ifdef FOSSIL_ENABLE_SSL + rc = ssl_open(); + if( rc==0 ) transport.isOpen = 1; + #else + socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support"); rc = 1; + #endif }else if( g.urlIsFile ){ sqlite3_uint64 iRandId; sqlite3_randomness(sizeof(iRandId), &iRandId); transport.zOutFile = mprintf("%s-%llu-out.http", g.zRepositoryName, iRandId); @@ -112,11 +122,13 @@ transport.pBuf = 0; transport.nAlloc = 0; transport.nUsed = 0; transport.iCursor = 0; if( g.urlIsHttps ){ - /* TBD */ + #ifdef FOSSIL_ENABLE_SSL + ssl_close(); + #endif }else if( g.urlIsFile ){ if( transport.pFile ){ fclose(transport.pFile); transport.pFile = 0; } @@ -137,11 +149,19 @@ void transport_send(Blob *toSend){ char *z = blob_buffer(toSend); int n = blob_size(toSend); transport.nSent += n; if( g.urlIsHttps ){ - /* TBD */ + #ifdef FOSSIL_ENABLE_SSL + int sent; + while( n>0 ){ + sent = ssl_send(0, z, n); + /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */ + if( sent<=0 ) break; + n -= sent; + } + #endif }else if( g.urlIsFile ){ fwrite(z, 1, n, transport.pFile); }else{ int sent; while( n>0 ){ @@ -204,12 +224,16 @@ nByte += toMove; } if( N>0 ){ int got; if( g.urlIsHttps ){ - /* TBD */ + #ifdef FOSSIL_ENABLE_SSL + got = ssl_receive(0, zBuf, N); + /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */ + #else got = 0; + #endif }else if( g.urlIsFile ){ got = fread(zBuf, 1, N, transport.pFile); }else{ got = socket_receive(0, zBuf, N); /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */ @@ -293,5 +317,15 @@ i++; } /* printf("Got line: [%s]\n", &transport.pBuf[iStart]); */ return &transport.pBuf[iStart]; } + +void transport_global_shutdown(void){ + if( g.urlIsHttps ){ + #ifdef FOSSIL_ENABLE_SSL + ssl_global_shutdown(); + #endif + }else{ + socket_global_shutdown(); + } +} Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -40,10 +40,11 @@ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ + $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ $(SRCDIR)/info.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ @@ -112,10 +113,11 @@ file_.c \ finfo_.c \ graph_.c \ http_.c \ http_socket_.c \ + http_ssl_.c \ http_transport_.c \ info_.c \ login_.c \ main_.c \ manifest_.c \ @@ -184,10 +186,11 @@ file.o \ finfo.o \ graph.o \ http.o \ http_socket.o \ + http_ssl.o \ http_transport.o \ info.o \ login.o \ main.o \ manifest.o \ @@ -267,16 +270,16 @@ # noop clean: rm -f *.o *_.c $(APPNAME) VERSION.h rm -f translate makeheaders mkindex page_index.h headers - rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h + rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h page_index.h: $(TRANS_SRC) mkindex ./mkindex $(TRANS_SRC) >$@ headers: page_index.h makeheaders VERSION.h - ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h + ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h touch headers headers: Makefile Makefile: add_.c: $(SRCDIR)/add.c translate ./translate $(SRCDIR)/add.c >add_.c @@ -479,10 +482,17 @@ http_socket.o: http_socket_.c http_socket.h $(SRCDIR)/config.h $(XTCC) -o http_socket.o -c http_socket_.c http_socket.h: headers +http_ssl_.c: $(SRCDIR)/http_ssl.c translate + ./translate $(SRCDIR)/http_ssl.c >http_ssl_.c + +http_ssl.o: http_ssl_.c http_ssl.h $(SRCDIR)/config.h + $(XTCC) -o http_ssl.o -c http_ssl_.c + +http_ssl.h: headers http_transport_.c: $(SRCDIR)/http_transport.c translate ./translate $(SRCDIR)/http_transport.c >http_transport_.c http_transport.o: http_transport_.c http_transport.h $(SRCDIR)/config.h $(XTCC) -o http_transport.o -c http_transport_.c Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -75,10 +75,11 @@ wiki wikiformat winhttp xfer zip + http_ssl } # Name of the final application # set name fossil Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -1315,10 +1315,10 @@ }; transport_stats(&nSent, &nRcvd, 1); printf("Total network traffic: %d bytes sent, %d bytes received\n", nSent, nRcvd); transport_close(); - socket_global_shutdown(); + transport_global_shutdown(); db_multi_exec("DROP TABLE onremote"); manifest_crosslink_end(); db_end_transaction(0); }