1 /* $Id: ncbi_gnutls.c,v 1.3 2016/10/13 20:49:14 fukanchi Exp $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author:  Anton Lavrentiev
27  *
28  * File Description:
29  *   GNUTLS support for SSL in connection library
30  *
31  */
32 
33 #include "ncbi_ansi_ext.h"
34 #include "ncbi_connssl.h"
35 #include "ncbi_priv.h"
36 #include <connect/ncbi_connutil.h>
37 #include <connect/ncbi_gnutls.h>
38 #include <stdlib.h>
39 
40 #ifdef HAVE_LIBGNUTLS
41 
42 #  include <gnutls/gnutls.h>
43 
44 #  if   defined(ENOTSUP)
45 #    define NCBI_NOTSUPPORTED  ENOTSUP
46 #  elif defined(ENOSYS)
47 #    define NCBI_NOTSUPPORTED  ENOSYS
48 #  else
49 #    define NCBI_NOTSUPPORTED  EINVAL
50 #  endif /*not implemented*/
51 
52 #  ifdef HAVE_LIBGCRYPT
53 
54 #    include <gcrypt.h>
55 
56 #    if   defined(NCBI_POSIX_THREADS)
57 
58 #      include <pthread.h>
59 #      ifdef __cplusplus
60 extern "C" {
61 #      endif /*__cplusplus*/
62     GCRY_THREAD_OPTION_PTHREAD_IMPL;
63 #      ifdef __cplusplus
64 } /* extern "C" */
65 #      endif /*__cplusplus*/
66 
67 #    elif defined(NCBI_THREADS)
68 
69 #      ifdef __cplusplus
70 extern "C" {
71 #      endif /*__cplusplus*/
gcry_user_mutex_init(void ** lock)72 static int gcry_user_mutex_init(void** lock)
73 {
74     return !(*lock = CORE_GetLOCK()) ? NCBI_NOTSUPPORTED : 0;
75 }
gcry_user_mutex_destroy(void ** lock)76 static int gcry_user_mutex_destroy(void** lock)
77 {
78     *lock = 0;
79     return 0;
80 }
gcry_user_mutex_lock(void ** lock)81 static int gcry_user_mutex_lock(void** lock)
82 {
83     return MT_LOCK_Do((MT_LOCK)(*lock), eMT_Lock) > 0 ? 0 : NCBI_NOTSUPPORTED;
84 }
gcry_user_mutex_unlock(void ** lock)85 static int gcry_user_mutex_unlock(void** lock)
86 {
87     return MT_LOCK_Do((MT_LOCK)(*lock), eMT_Unlock) ? 0 : NCBI_NOTSUPPORTED;
88 }
89 static struct gcry_thread_cbs gcry_threads_user = {
90     GCRY_THREAD_OPTION_USER, NULL/*gcry_user_init*/,
91     gcry_user_mutex_init, gcry_user_mutex_destroy,
92     gcry_user_mutex_lock, gcry_user_mutex_unlock,
93     NULL/*all other fields NULL-inited*/
94 };
95 #      ifdef __cplusplus
96 } /* extern "C" */
97 #      endif /*__cplusplus*/
98 
99 #    endif /*NCBI_POSIX_THREADS*/
100 
101 #  endif /*HAVE_LIBGCRYPT*/
102 
103 #  ifdef __cplusplus
104 extern "C" {
105 #  endif /*__cplusplus*/
106 
107 static EIO_Status  s_GnuTlsInit  (FSSLPull pull, FSSLPush push);
108 static void*       s_GnuTlsCreate(ESOCK_Side side, SOCK sock,
109                                   NCBI_CRED cred, int* error);
110 static EIO_Status  s_GnuTlsOpen  (void* session, int* error, char** desc);
111 static EIO_Status  s_GnuTlsRead  (void* session,       void* buf,  size_t size,
112                                   size_t* done, int* error);
113 static EIO_Status  s_GnuTlsWrite (void* session, const void* data, size_t size,
114                                   size_t* done, int* error);
115 static EIO_Status  s_GnuTlsClose (void* session, int how, int* error);
116 static void        s_GnuTlsDelete(void* session);
117 static void        s_GnuTlsExit  (void);
118 static const char* s_GnuTlsError (void* session, int error);
119 
120 static void        x_GnuTlsLogger(int level, const char* message);
121 static ssize_t     x_GnuTlsPull  (gnutls_transport_ptr_t,       void*, size_t);
122 static ssize_t     x_GnuTlsPush  (gnutls_transport_ptr_t, const void*, size_t);
123 
124 #  ifdef __cplusplus
125 }
126 #  endif /*__cplusplus*/
127 
128 
129 #  if LIBGNUTLS_VERSION_NUMBER < 0x030306
130 static const int kGnuTlsCertPrio[] = {
131     GNUTLS_CRT_X509,
132     /*GNUTLS_CRT_OPENPGP,*/
133     0
134 };
135 static const int kGnuTlsCompPrio[] = {
136     GNUTLS_COMP_ZLIB,
137     GNUTLS_COMP_NULL,
138     0
139 };
140 #  endif /*LIBGNUTLS_VERSION_NUMBER<3.3.6*/
141 
142 
143 static int                              s_GnuTlsLogLevel;
144 static gnutls_anon_client_credentials_t s_GnuTlsCredAnon;
145 static gnutls_certificate_credentials_t s_GnuTlsCredCert;
146 static FSSLPull                         s_Pull;
147 static FSSLPush                         s_Push;
148 
149 
x_GnuTlsLogger(int level,const char * message)150 static void x_GnuTlsLogger(int level, const char* message)
151 {
152     /* do some basic filtering and EOL cut-offs */
153     int len = message ? strlen(message) : 0;
154     if (!len  ||  *message == '\n')
155         return;
156     if (strncasecmp(message, "ASSERT: ", 8) == 0)
157         return;
158     if (message[len - 1] == '\n')
159         len--;
160     CORE_LOGF(eLOG_Note, ("GNUTLS%d: %.*s", level, len, message));
161 }
162 
163 
164 #  ifdef __GNUC__
165 inline
166 #  endif /*__GNUC__*/
x_RetryStatus(gnutls_session_t session,EIO_Event direction)167 static EIO_Status x_RetryStatus(gnutls_session_t session, EIO_Event direction)
168 {
169     SOCK sock = (SOCK) gnutls_session_get_ptr(session);
170     EIO_Status status;
171     if (direction == eIO_Open) {
172         EIO_Status r_status = SOCK_Status(sock, eIO_Read);
173         EIO_Status w_status = SOCK_Status(sock, eIO_Write);
174         status = r_status > w_status ? r_status : w_status;
175     } else
176         status = SOCK_Status(sock, direction);
177     return status == eIO_Success ? eIO_Timeout : status;
178 }
179 
180 
181 #  ifdef __GNUC__
182 inline
183 #  endif /*__GNUC__*/
x_ErrorToStatus(int * error,gnutls_session_t session,EIO_Event direction)184 static EIO_Status x_ErrorToStatus(int* error,
185                                   gnutls_session_t session,EIO_Event direction)
186 {
187     EIO_Status status;
188     SOCK       sock = (SOCK) gnutls_transport_get_ptr(session);
189 
190     assert(error  &&  *error <= 0);
191 
192     if (!*error)
193         return eIO_Success;
194     else if (*error == GNUTLS_E_AGAIN)
195         status = x_RetryStatus(session, direction);
196     else if (*error == GNUTLS_E_INTERRUPTED)
197         status = eIO_Interrupt;
198     else if (*error == GNUTLS_E_WARNING_ALERT_RECEIVED) {
199         status = eIO_Unknown;
200         *error = GNUTLS_E_APPLICATION_ERROR_MAX - gnutls_alert_get(session);
201     }
202     else if (*error == GNUTLS_E_FATAL_ALERT_RECEIVED) {
203         status = eIO_Closed;
204         *error = GNUTLS_E_APPLICATION_ERROR_MAX - gnutls_alert_get(session);
205     }
206     else if (*error == GNUTLS_E_PULL_ERROR
207              &&  sock->r_status != eIO_Success
208              &&  sock->r_status != eIO_Unknown) {
209         status = sock->r_status;
210     }
211     else if (*error == GNUTLS_E_PUSH_ERROR
212              &&  sock->w_status != eIO_Success
213              &&  sock->w_status != eIO_Unknown) {
214         status = sock->w_status;
215     }
216     else if (gnutls_error_is_fatal(*error))
217         status = eIO_Closed;
218     else
219         status = eIO_Unknown;
220 #if 0
221     CORE_TRACEF(("GNUTLS error %d -> CONNECT status %s",
222                  *error, IO_StatusStr(status)));
223 #endif
224     return status;
225 }
226 
227 
228 #  ifdef __GNUC__
229 inline
230 #  endif /*__GNUC__*/
x_IsTimeout(SOCK sock,EIO_Event direction)231 static int/*bool*/ x_IsTimeout(SOCK sock, EIO_Event direction)
232 {
233     int retval;
234     switch (direction) {
235     case eIO_Read:
236         retval = !sock->r_tv_set  ||  (sock->r_tv.tv_sec | sock->r_tv.tv_usec);
237         break;
238     case eIO_Write:
239         retval = !sock->w_tv_set  ||  (sock->w_tv.tv_sec | sock->w_tv.tv_usec);
240         break;
241     default:
242         retval = 0;
243         assert(0);
244         break;
245     }
246     return retval;
247 }
248 
249 
250 #  ifdef __GNUC__
251 inline
252 #  endif /*__GNUC__*/
x_StatusToError(EIO_Status status,SOCK sock,EIO_Event direction)253 static int x_StatusToError(EIO_Status status, SOCK sock, EIO_Event direction)
254 {
255     int error;
256 
257     assert(status != eIO_Success);
258 
259     switch (status) {
260     case eIO_Timeout:
261         error = x_IsTimeout(sock, direction) ? SOCK_ETIMEDOUT : EAGAIN;
262         break;
263     case eIO_Closed:
264         error = SOCK_ENOTCONN;
265         break;
266     case eIO_Interrupt:
267         error = SOCK_EINTR;
268         break;
269     case eIO_NotSupported:
270         error = NCBI_NOTSUPPORTED;
271         break;
272     case eIO_Unknown:
273         error = 0/*keep*/;
274         break;
275     default:
276         /*NB:eIO_InvalidArg*/
277         error = EINVAL;
278         break;
279     }
280 #if 0
281     CORE_TRACEF(("CONNECT status %s -> %s %d", IO_StatusStr(status),
282                  error ? "error" : "errno",
283                  error ?  error  :  errno));
284 #endif
285     return error;
286 }
287 
288 
s_GnuTlsCreate(ESOCK_Side side,SOCK sock,NCBI_CRED cred,int * error)289 static void* s_GnuTlsCreate(ESOCK_Side side, SOCK sock,
290                             NCBI_CRED cred, int* error)
291 {
292     gnutls_transport_ptr_t  ptr = (gnutls_transport_ptr_t) sock;
293     gnutls_connection_end_t end = (side == eSOCK_Client
294                                    ? GNUTLS_CLIENT
295                                    : GNUTLS_SERVER);
296     gnutls_certificate_credentials_t xcred;
297     gnutls_anon_client_credentials_t acred;
298     gnutls_session_t session;
299     char val[128];
300     int err;
301 
302     if (end == GNUTLS_SERVER) {
303         /*FIXME: not yet supported*/
304         *error = 0;
305         return 0;
306     }
307 
308     CORE_LOCK_READ;
309     xcred = s_GnuTlsCredCert;
310     acred = s_GnuTlsCredAnon;
311     CORE_UNLOCK;
312 
313     if (!acred
314         ||  (cred  &&  (cred->type != eNcbiCred_GnuTls  ||  !cred->data))) {
315         /*FIXME: there's a NULL(data)-terminated array of credentials */
316         *error = 0;
317         return 0;
318     }
319 
320     if ((*error = gnutls_init(&session, end)) != GNUTLS_E_SUCCESS/*0*/)
321         return 0;
322 
323     ConnNetInfo_GetValue(0, "GNUTLS_PRIORITY", val, sizeof(val), 0);
324 
325     if ((err = gnutls_set_default_priority(session))                   != 0  ||
326 #  if LIBGNUTLS_VERSION_NUMBER >= 0x020200
327         ( *val  &&
328          (err = gnutls_priority_set_direct(session, val, 0))           != 0) ||
329 #  endif /*LIBGNUTLS_VERSION_NUMBER>=2.2.0*/
330 #  if LIBGNUTLS_VERSION_NUMBER < 0x030306
331         (!*val  &&
332          (err = gnutls_compression_set_priority(session,
333                                                 kGnuTlsCompPrio))      != 0) ||
334         (!*val  &&
335          (err = gnutls_certificate_type_set_priority(session,
336                                                      kGnuTlsCertPrio)) != 0) ||
337 #  endif /*LIBGNUTLS_VERSION_NUMBER<3.3.6*/
338         (err = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
339                                       cred ? cred->data : xcred))      != 0  ||
340         (err = gnutls_credentials_set(session, GNUTLS_CRD_ANON, acred))!= 0) {
341         gnutls_deinit(session);
342         *error = err;
343         return 0;
344     }
345 
346     gnutls_transport_set_pull_function(session, x_GnuTlsPull);
347     gnutls_transport_set_push_function(session, x_GnuTlsPush);
348     gnutls_transport_set_ptr(session, ptr);
349     gnutls_session_set_ptr(session, sock);
350 
351 #  if LIBGNUTLS_VERSION_NUMBER >= 0x030000
352     gnutls_handshake_set_timeout(session, 0);
353 #  endif /*LIBGNUTLS_VERSION_NUMBER>=3.0.0*/
354 
355     return session;
356 }
357 
358 
s_GnuTlsOpen(void * session,int * error,char ** desc)359 static EIO_Status s_GnuTlsOpen(void* session, int* error, char** desc)
360 {
361     EIO_Status status;
362     int x_error;
363 
364     *desc = 0;
365 
366     do {
367         x_error = gnutls_handshake((gnutls_session_t) session);
368     } while (x_error  &&  x_error == GNUTLS_E_REHANDSHAKE);
369 
370     if (x_error < 0) {
371         status = x_ErrorToStatus(&x_error,
372                                  (gnutls_session_t) session, eIO_Open);
373         *error = x_error;
374     } else {
375 #  if LIBGNUTLS_VERSION_NUMBER >= 0x030110
376         char* temp = gnutls_session_get_desc(session);
377         if (temp) {
378             *desc = strdup(temp);
379             gnutls_free(temp);
380         }
381 #  endif /*LIBGNUTLS_VERSION_NUMBER<3.1.10*/
382         status = eIO_Success;
383     }
384     return status;
385 }
386 
387 
388 #ifdef __GNUC__
389 inline
390 #endif /*__GNUC__*/
x_IfToLog(void)391 static int x_IfToLog(void)
392 {
393     return 7 < s_GnuTlsLogLevel  &&  s_GnuTlsLogLevel <= 10 ? 1/*T*/ : 0/*F*/;
394 }
395 
396 
397 /*ARGSUSED*/
x_set_errno(gnutls_session_t session,int error)398 static void x_set_errno(gnutls_session_t session, int error)
399 {
400 #  if LIBGNUTLS_VERSION_NUMBER >= 0x010504
401     gnutls_transport_set_errno(session, error);
402 #  else
403     if (error)
404         errno = error;
405 #  endif /*LIBGNUTLS_VERSION>=1.5.4*/
406 }
407 
408 
x_GnuTlsPull(gnutls_transport_ptr_t ptr,void * buf,size_t size)409 static ssize_t x_GnuTlsPull(gnutls_transport_ptr_t ptr,
410                             void* buf, size_t size)
411 {
412     int x_error;
413     EIO_Status status;
414     SOCK sock = (SOCK) ptr;
415     FSSLPull pull = s_Pull;
416 
417     if (pull) {
418         size_t x_read = 0;
419         status = pull(sock, buf, size, &x_read, x_IfToLog());
420         if (x_read > 0  ||  status == eIO_Success/*&& x_read==0*/) {
421             assert(status == eIO_Success);
422             assert(x_read <= size);
423             x_set_errno((gnutls_session_t) sock->session, 0);
424             return x_read;
425         }
426     } else
427         status = eIO_NotSupported;
428 
429     x_error = x_StatusToError(status, sock, eIO_Read);
430     if (x_error)
431         x_set_errno((gnutls_session_t) sock->session, x_error);
432     return -1;
433 }
434 
435 
x_GnuTlsPush(gnutls_transport_ptr_t ptr,const void * data,size_t size)436 static ssize_t x_GnuTlsPush(gnutls_transport_ptr_t ptr,
437                             const void* data, size_t size)
438 {
439     int x_error;
440     EIO_Status status;
441     SOCK sock = (SOCK) ptr;
442     FSSLPush push = s_Push;
443 
444     if (push) {
445         ssize_t n_written = 0;
446         do {
447             size_t x_written = 0;
448             status = push(sock, data, size, &x_written, x_IfToLog());
449             if (!x_written) {
450                 assert(!size  ||  status != eIO_Success);
451                 if (size  ||  status != eIO_Success)
452                     goto out;
453             } else {
454                 assert(status == eIO_Success);
455                 assert(x_written <= size);
456                 n_written += x_written;
457                 size      -= x_written;
458                 data       = (const char*) data + x_written;
459             }
460         } while (size);
461         x_set_errno((gnutls_session_t) sock->session, 0);
462         return n_written;
463     } else
464         status = eIO_NotSupported;
465 
466  out:
467     x_error = x_StatusToError(status, sock, eIO_Write);
468     if (x_error)
469         x_set_errno((gnutls_session_t) sock->session, x_error);
470     return -1;
471 }
472 
473 
s_GnuTlsRead(void * session,void * buf,size_t n_todo,size_t * n_done,int * error)474 static EIO_Status s_GnuTlsRead(void* session, void* buf, size_t n_todo,
475                                size_t* n_done, int* error)
476 {
477     EIO_Status status;
478     int        x_read;
479 
480     assert(session);
481 
482     x_read = gnutls_record_recv((gnutls_session_t) session, buf, n_todo);
483     assert(x_read < 0  ||  x_read <= n_todo);
484 
485     if (x_read <= 0) {
486         status = x_ErrorToStatus(&x_read,
487                                  (gnutls_session_t) session, eIO_Read);
488         *error = x_read;
489         x_read = 0;
490     } else
491         status = eIO_Success;
492 
493     *n_done = x_read;
494     return status;
495 }
496 
497 
x_GnuTlsWrite(void * session,const void * data,size_t n_todo,size_t * n_done,int * error)498 static EIO_Status x_GnuTlsWrite(void* session, const void* data, size_t n_todo,
499                                 size_t* n_done, int* error)
500 {
501     EIO_Status status;
502     int        x_written;
503 
504     assert(session);
505 
506     x_written = gnutls_record_send((gnutls_session_t) session, data, n_todo);
507     assert(x_written < 0  ||  x_written <= n_todo);
508 
509     if (x_written <= 0) {
510         status = x_ErrorToStatus(&x_written,
511                                  (gnutls_session_t) session, eIO_Write);
512         *error = x_written;
513         x_written = 0;
514     } else
515         status = eIO_Success;
516 
517     *n_done = x_written;
518     return status;
519 }
520 
521 
s_GnuTlsWrite(void * session,const void * data,size_t n_todo,size_t * n_done,int * error)522 static EIO_Status s_GnuTlsWrite(void* session, const void* data, size_t n_todo,
523                                 size_t* n_done, int* error)
524 {
525     size_t max_size = gnutls_record_get_max_size((gnutls_session_t) session);
526     EIO_Status status;
527 
528     *n_done = 0;
529 
530     do {
531         size_t x_todo = n_todo > max_size ? max_size : n_todo;
532         size_t x_done;
533         status = x_GnuTlsWrite(session, data, x_todo, &x_done, error);
534         assert((status == eIO_Success) == (x_done > 0));
535         assert(status == eIO_Success  ||  *error);
536         assert(x_done <= x_todo);
537         if (status != eIO_Success)
538             break;
539         *n_done += x_done;
540         if (x_todo != x_done)
541             break;
542         n_todo  -= x_done;
543         data     = (const char*) data + x_done;
544     } while (n_todo);
545 
546     return *n_done ? eIO_Success : status;
547 }
548 
549 
s_GnuTlsClose(void * session,int how,int * error)550 static EIO_Status s_GnuTlsClose(void* session, int how, int* error)
551 {
552     int x_error;
553 
554     assert(session);
555 
556     x_error = gnutls_bye((gnutls_session_t) session,
557                          how == SOCK_SHUTDOWN_RDWR
558                          ? GNUTLS_SHUT_RDWR
559                          : GNUTLS_SHUT_WR);
560     if (x_error != GNUTLS_E_SUCCESS) {
561         *error = x_error;
562         return eIO_Unknown;
563     }
564 
565     return eIO_Success;
566 }
567 
568 
s_GnuTlsDelete(void * session)569 static void s_GnuTlsDelete(void* session)
570 {
571     assert(session);
572 
573     gnutls_deinit((gnutls_session_t) session);
574 }
575 
576 
577 /* NB: Called under a lock */
s_GnuTlsInit(FSSLPull pull,FSSLPush push)578 static EIO_Status s_GnuTlsInit(FSSLPull pull, FSSLPush push)
579 {
580     gnutls_anon_client_credentials_t acred;
581     gnutls_certificate_credentials_t xcred;
582     const char* version;
583     const char* val;
584     char buf[32];
585 
586     assert(!s_GnuTlsCredAnon);
587 
588     version = gnutls_check_version(0);
589     if (strcasecmp(GNUTLS_VERSION, version) != 0) {
590         CORE_LOGF(eLOG_Critical,
591                   ("GNUTLS version mismatch: %s headers vs. %s runtime",
592                    GNUTLS_VERSION, version));
593         assert(0);
594     }
595 
596     val = ConnNetInfo_GetValue(0, "GNUTLS_LOGLEVEL", buf, sizeof(buf), 0);
597     CORE_LOCK_READ;
598     if (!val  ||  !*val)
599         val = getenv("GNUTLS_DEBUG_LEVEL");
600     if (val  &&  *val) {
601         s_GnuTlsLogLevel = atoi(val);
602         CORE_UNLOCK;
603         if (s_GnuTlsLogLevel) {
604             gnutls_global_set_log_function(x_GnuTlsLogger);
605             if (val == buf)
606                 gnutls_global_set_log_level(s_GnuTlsLogLevel);
607             CORE_LOGF(eLOG_Note, ("GNUTLS V%s (Loglevel=%d)",
608                                   version, s_GnuTlsLogLevel));
609         }
610     } else
611         CORE_UNLOCK;
612 
613 #  ifdef HAVE_LIBGCRYPT
614 #    if   defined(NCBI_POSIX_THREADS)
615     if (gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread) != 0)
616         goto out;
617 #    elif defined(NCBI_THREADS)
618     if (gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_user) != 0)
619         goto out;
620 #    elif defined(_MT)
621     CORE_LOG(eLOG_Critical,"LIBGCRYPT uninitialized: Unknown threading model");
622 #    endif /*NCBI_POSIX_THREADS*/
623 #  endif /*HAVE_LIBGCRYPT*/
624 
625     if (!pull  ||  !push  ||  !gnutls_check_version(LIBGNUTLS_VERSION)
626         ||  gnutls_global_init() != GNUTLS_E_SUCCESS/*0*/) {
627         goto out;
628     }
629     if (gnutls_anon_allocate_client_credentials(&acred) != 0) {
630         gnutls_global_deinit();
631         goto out;
632     }
633     if (gnutls_certificate_allocate_credentials(&xcred) != 0) {
634         gnutls_anon_free_client_credentials(acred);
635         gnutls_global_deinit();
636         goto out;
637     }
638 
639     s_GnuTlsCredAnon = acred;
640     s_GnuTlsCredCert = xcred;
641     s_Pull           = pull;
642     s_Push           = push;
643 
644     return eIO_Success;
645 
646  out:
647     gnutls_global_set_log_level(s_GnuTlsLogLevel = 0);
648     gnutls_global_set_log_function(0);
649     return eIO_NotSupported;
650 }
651 
652 
653 /* NB: Called under a lock */
s_GnuTlsExit(void)654 static void s_GnuTlsExit(void)
655 {
656     gnutls_anon_client_credentials_t acred = s_GnuTlsCredAnon;
657     gnutls_certificate_credentials_t xcred = s_GnuTlsCredCert;
658 
659     assert(acred);
660 
661     s_Push           = 0;
662     s_Pull           = 0;
663     s_GnuTlsCredCert = 0;
664     s_GnuTlsCredAnon = 0;
665 
666     gnutls_certificate_free_credentials(xcred);
667     gnutls_anon_free_client_credentials(acred);
668     gnutls_global_deinit();
669 
670     gnutls_global_set_log_level(s_GnuTlsLogLevel = 0);
671     gnutls_global_set_log_function(0);
672 }
673 
674 
s_GnuTlsError(void * session,int error)675 static const char* s_GnuTlsError(void* session/*unused*/, int error)
676 {
677     /* GNUTLS defines only negative error codes */
678     return error >= 0 ? 0 : error < GNUTLS_E_APPLICATION_ERROR_MAX
679         ? gnutls_alert_get_name(GNUTLS_E_APPLICATION_ERROR_MAX - error)
680         : gnutls_strerror(error);
681 }
682 
683 
684 #else
685 
686 
687 /*ARGSUSED*/
s_GnuTlsInit(FSSLPull unused_pull,FSSLPush unused_push)688 static EIO_Status s_GnuTlsInit(FSSLPull unused_pull, FSSLPush unused_push)
689 {
690     CORE_LOG(eLOG_Critical, "Unavailable feature GNUTLS");
691     return eIO_NotSupported;
692 }
693 
694 
695 #endif /*HAVE_LIBGNUTLS*/
696 
697 
NcbiSetupGnuTls(void)698 extern SOCKSSL NcbiSetupGnuTls(void)
699 {
700     static const struct SOCKSSL_struct kGnuTlsOps = {
701         s_GnuTlsInit
702 #ifdef HAVE_LIBGNUTLS
703         , s_GnuTlsCreate
704         , s_GnuTlsOpen
705         , s_GnuTlsRead
706         , s_GnuTlsWrite
707         , s_GnuTlsClose
708         , s_GnuTlsDelete
709         , s_GnuTlsExit
710         , s_GnuTlsError
711 #endif /*HAVE_LIBGNUTLS*/
712     };
713 #ifndef HAVE_LIBGNUTLS
714     CORE_LOG(eLOG_Warning, "Unavailable feature GNUTLS");
715 #endif /*!HAVE_LIBGNUTLS*/
716     return &kGnuTlsOps;
717 }
718 
719 
NcbiCredGnuTls(void * xcred)720 extern NCBI_CRED NcbiCredGnuTls(void* xcred)
721 {
722     struct SNcbiCred* cred = (NCBI_CRED) calloc(xcred ? 2 : 1, sizeof(*cred));
723     if (cred  &&  xcred) {
724         cred->type = eNcbiCred_GnuTls;
725         cred->data = xcred;
726     }
727     return cred;
728 }
729