xref: /unit/src/nxt_gnutls.c (revision 0:a63ceefd6ab0)
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) NGINX, Inc.
5  */
6 
7 #include <nxt_main.h>
8 #include <gnutls/gnutls.h>
9 
10 
11 typedef struct {
12     gnutls_session_t                  session;
13 
14     uint8_t                           times;        /* 2 bits */
15     uint8_t                           no_shutdown;  /* 1 bit */
16 
17     nxt_buf_mem_t                     buffer;
18 } nxt_gnutls_conn_t;
19 
20 
21 typedef struct {
22     gnutls_priority_t                 ciphers;
23     gnutls_certificate_credentials_t  certificate;
24 } nxt_gnutls_ctx_t;
25 
26 
27 
28 #if (NXT_HAVE_GNUTLS_SET_TIME)
29 time_t nxt_gnutls_time(time_t *tp);
30 #endif
31 static nxt_int_t nxt_gnutls_server_init(nxt_ssltls_conf_t *conf);
32 static nxt_int_t nxt_gnutls_set_ciphers(nxt_ssltls_conf_t *conf);
33 
34 static void nxt_gnutls_conn_init(nxt_thread_t *thr, nxt_ssltls_conf_t *conf,
35     nxt_event_conn_t *c);
36 static void nxt_gnutls_session_cleanup(void *data);
37 static ssize_t nxt_gnutls_pull(gnutls_transport_ptr_t data, void *buf,
38     size_t size);
39 static ssize_t nxt_gnutls_push(gnutls_transport_ptr_t data, const void *buf,
40     size_t size);
41 #if (NXT_HAVE_GNUTLS_VEC_PUSH)
42 static ssize_t nxt_gnutls_vec_push(gnutls_transport_ptr_t data,
43     const giovec_t *iov, int iovcnt);
44 #endif
45 static void nxt_gnutls_conn_handshake(nxt_thread_t *thr, void *obj, void *data);
46 static void nxt_gnutls_conn_io_read(nxt_thread_t *thr, void *obj, void *data);
47 static ssize_t nxt_gnutls_conn_io_write_chunk(nxt_thread_t *thr,
48     nxt_event_conn_t *c, nxt_buf_t *b, size_t limit);
49 static ssize_t nxt_gnutls_conn_io_send(nxt_event_conn_t *c, void *buf,
50     size_t size);
51 static void nxt_gnutls_conn_io_shutdown(nxt_thread_t *thr, void *obj,
52     void *data);
53 static nxt_int_t nxt_gnutls_conn_test_error(nxt_thread_t *thr,
54     nxt_event_conn_t *c, ssize_t err, nxt_work_handler_t handler);
55 static void nxt_cdecl nxt_gnutls_conn_log_error(nxt_event_conn_t *c,
56     ssize_t err, const char *fmt, ...);
57 static nxt_uint_t nxt_gnutls_log_error_level(nxt_event_conn_t *c, ssize_t err);
58 static void nxt_cdecl nxt_gnutls_log_error(nxt_uint_t level, nxt_log_t *log,
59     int err, const char *fmt, ...);
60 
61 
62 const nxt_ssltls_lib_t  nxt_gnutls_lib = {
63     nxt_gnutls_server_init,
64     NULL,
65 };
66 
67 
68 static nxt_event_conn_io_t  nxt_gnutls_event_conn_io = {
69     NULL,
70     NULL,
71 
72     nxt_gnutls_conn_io_read,
73     NULL,
74     NULL,
75 
76     nxt_event_conn_io_write,
77     nxt_gnutls_conn_io_write_chunk,
78     NULL,
79     NULL,
80     nxt_gnutls_conn_io_send,
81 
82     nxt_gnutls_conn_io_shutdown,
83 };
84 
85 
86 static nxt_int_t
87 nxt_gnutls_start(void)
88 {
89     int                ret;
90     static nxt_bool_t  started;
91 
92     if (nxt_fast_path(started)) {
93         return NXT_OK;
94     }
95 
96     started = 1;
97 
98     /* TODO: gnutls_global_deinit */
99 
100     ret = gnutls_global_init();
101     if (ret != GNUTLS_E_SUCCESS) {
102         nxt_gnutls_log_error(NXT_LOG_CRIT, nxt_thread_log(), ret,
103                              "gnutls_global_init() failed");
104         return NXT_ERROR;
105     }
106 
107     nxt_thread_log_error(NXT_LOG_INFO, "GnuTLS version: %s",
108                          gnutls_check_version(NULL));
109 
110 #if (NXT_HAVE_GNUTLS_SET_TIME)
111     gnutls_global_set_time_function(nxt_gnutls_time);
112 #endif
113 
114     return NXT_OK;
115 }
116 
117 
118 #if (NXT_HAVE_GNUTLS_SET_TIME)
119 
120 /* GnuTLS 2.12.0 */
121 
122 time_t
123 nxt_gnutls_time(time_t *tp)
124 {
125     time_t        t;
126     nxt_thread_t  *thr;
127 
128     thr = nxt_thread();
129     nxt_log_debug(thr->log, "gnutls time");
130 
131     t = (time_t) nxt_thread_time(thr);
132 
133     if (tp != NULL) {
134         *tp = t;
135     }
136 
137     return t;
138 }
139 
140 #endif
141 
142 
143 static nxt_int_t
144 nxt_gnutls_server_init(nxt_ssltls_conf_t *conf)
145 {
146     int                ret;
147     char               *certificate, *key, *ca_certificate;
148     nxt_thread_t       *thr;
149     nxt_gnutls_ctx_t   *ctx;
150 
151     if (nxt_slow_path(nxt_gnutls_start() != NXT_OK)) {
152         return NXT_ERROR;
153     }
154 
155     /* TODO: mem_pool, cleanup: gnutls_certificate_free_credentials,
156              gnutls_priority_deinit */
157 
158     ctx = nxt_zalloc(sizeof(nxt_gnutls_ctx_t));
159     if (ctx == NULL) {
160         return NXT_ERROR;
161     }
162 
163     conf->ctx = ctx;
164     conf->conn_init = nxt_gnutls_conn_init;
165 
166     thr = nxt_thread();
167 
168     ret = gnutls_certificate_allocate_credentials(&ctx->certificate);
169     if (ret != GNUTLS_E_SUCCESS) {
170         nxt_gnutls_log_error(NXT_LOG_CRIT, thr->log, ret,
171                 "gnutls_certificate_allocate_credentials() failed");
172         return NXT_ERROR;
173     }
174 
175     certificate = conf->certificate;
176     key = conf->certificate_key;
177 
178     ret = gnutls_certificate_set_x509_key_file(ctx->certificate, certificate,
179                                                key, GNUTLS_X509_FMT_PEM);
180     if (ret != GNUTLS_E_SUCCESS) {
181         nxt_gnutls_log_error(NXT_LOG_CRIT, thr->log, ret,
182                 "gnutls_certificate_set_x509_key_file(\"%s\", \"%s\") failed",
183                 certificate, key);
184         goto certificate_fail;
185     }
186 
187     if (nxt_gnutls_set_ciphers(conf) != NXT_OK) {
188         goto ciphers_fail;
189     }
190 
191     if (conf->ca_certificate != NULL) {
192         ca_certificate = conf->ca_certificate;
193 
194         ret = gnutls_certificate_set_x509_trust_file(ctx->certificate,
195                                                      ca_certificate,
196                                                      GNUTLS_X509_FMT_PEM);
197         if (ret < 0) {
198             nxt_gnutls_log_error(NXT_LOG_CRIT, thr->log, ret,
199                 "gnutls_certificate_set_x509_trust_file(\"%s\") failed",
200                 ca_certificate);
201             goto ca_certificate_fail;
202         }
203     }
204 
205     return NXT_OK;
206 
207 ca_certificate_fail:
208 
209     gnutls_priority_deinit(ctx->ciphers);
210 
211 ciphers_fail:
212 
213 certificate_fail:
214 
215     gnutls_certificate_free_credentials(ctx->certificate);
216 
217     return NXT_ERROR;
218 }
219 
220 
221 static nxt_int_t
222 nxt_gnutls_set_ciphers(nxt_ssltls_conf_t *conf)
223 {
224     int                ret;
225     const char         *ciphers;
226     const char         *err;
227     nxt_gnutls_ctx_t   *ctx;
228 
229     ciphers = (conf->ciphers != NULL) ? conf->ciphers : "NORMAL:!COMP-DEFLATE";
230     ctx = conf->ctx;
231 
232     ret = gnutls_priority_init(&ctx->ciphers, ciphers, &err);
233 
234     switch (ret) {
235 
236     case GNUTLS_E_SUCCESS:
237         return NXT_OK;
238 
239     case GNUTLS_E_INVALID_REQUEST:
240         nxt_gnutls_log_error(NXT_LOG_CRIT, nxt_thread_log(), ret,
241                              "gnutls_priority_init(\"%s\") failed at \"%s\"",
242                              ciphers, err);
243         return NXT_ERROR;
244 
245     default:
246         nxt_gnutls_log_error(NXT_LOG_CRIT, nxt_thread_log(), ret,
247                              "gnutls_priority_init() failed");
248         return NXT_ERROR;
249     }
250 }
251 
252 
253 static void
254 nxt_gnutls_conn_init(nxt_thread_t *thr, nxt_ssltls_conf_t *conf,
255     nxt_event_conn_t *c)
256 {
257     int                     ret;
258     gnutls_session_t        sess;
259     nxt_gnutls_ctx_t        *ctx;
260     nxt_gnutls_conn_t       *ssltls;
261     nxt_mem_pool_cleanup_t  *mpcl;
262 
263     nxt_log_debug(c->socket.log, "gnutls conn init");
264 
265     ssltls = nxt_mem_zalloc(c->mem_pool, sizeof(nxt_gnutls_conn_t));
266     if (ssltls == NULL) {
267         goto fail;
268     }
269 
270     c->u.ssltls = ssltls;
271     nxt_buf_mem_set_size(&ssltls->buffer, conf->buffer_size);
272 
273     mpcl = nxt_mem_pool_cleanup(c->mem_pool, 0);
274     if (mpcl == NULL) {
275         goto fail;
276     }
277 
278     ret = gnutls_init(&ssltls->session, GNUTLS_SERVER);
279     if (ret != GNUTLS_E_SUCCESS) {
280         nxt_gnutls_log_error(NXT_LOG_CRIT, c->socket.log, ret,
281                              "gnutls_init() failed");
282         goto fail;
283     }
284 
285     sess = ssltls->session;
286     mpcl->handler = nxt_gnutls_session_cleanup;
287     mpcl->data = ssltls;
288 
289     ctx = conf->ctx;
290 
291     ret = gnutls_priority_set(sess, ctx->ciphers);
292     if (ret != GNUTLS_E_SUCCESS) {
293         nxt_gnutls_log_error(NXT_LOG_CRIT, c->socket.log, ret,
294                              "gnutls_priority_set() failed");
295         goto fail;
296     }
297 
298     /*
299      * Disable TLS random padding of records in CBC ciphers,
300      * which may be up to 255 bytes.
301      */
302     gnutls_record_disable_padding(sess);
303 
304     ret = gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE,
305                                  ctx->certificate);
306     if (ret != GNUTLS_E_SUCCESS) {
307         nxt_gnutls_log_error(NXT_LOG_CRIT, c->socket.log, ret,
308                              "gnutls_credentials_set() failed");
309         goto fail;
310     }
311 
312     if (conf->ca_certificate != NULL) {
313         gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
314     }
315 
316     gnutls_transport_set_ptr(sess, (gnutls_transport_ptr_t) c);
317     gnutls_transport_set_pull_function(sess, nxt_gnutls_pull);
318     gnutls_transport_set_push_function(sess, nxt_gnutls_push);
319 #if (NXT_HAVE_GNUTLS_VEC_PUSH)
320     gnutls_transport_set_vec_push_function(sess, nxt_gnutls_vec_push);
321 #endif
322 
323     c->io = &nxt_gnutls_event_conn_io;
324     c->sendfile = NXT_CONN_SENDFILE_OFF;
325 
326     nxt_gnutls_conn_handshake(thr, c, c->socket.data);
327     return;
328 
329 fail:
330 
331     nxt_event_conn_io_handle(thr, c->read_work_queue,
332                              c->read_state->error_handler, c, c->socket.data);
333 }
334 
335 
336 static void
337 nxt_gnutls_session_cleanup(void *data)
338 {
339     nxt_gnutls_conn_t  *ssltls;
340 
341     ssltls = data;
342 
343     nxt_thread_log_debug("gnutls session cleanup");
344 
345     nxt_free(ssltls->buffer.start);
346 
347     gnutls_deinit(ssltls->session);
348 }
349 
350 
351 static ssize_t
352 nxt_gnutls_pull(gnutls_transport_ptr_t data, void *buf, size_t size)
353 {
354     ssize_t           n;
355     nxt_thread_t      *thr;
356     nxt_event_conn_t  *c;
357 
358     c = data;
359     thr = nxt_thread();
360 
361     n = thr->engine->event->io->recv(c, buf, size, 0);
362 
363     if (n == NXT_AGAIN) {
364         nxt_set_errno(NXT_EAGAIN);
365         return -1;
366     }
367 
368     return n;
369 }
370 
371 
372 static ssize_t
373 nxt_gnutls_push(gnutls_transport_ptr_t data, const void *buf, size_t size)
374 {
375     ssize_t           n;
376     nxt_thread_t      *thr;
377     nxt_event_conn_t  *c;
378 
379     c = data;
380     thr = nxt_thread();
381 
382     n = thr->engine->event->io->send(c, (u_char *) buf, size);
383 
384     if (n == NXT_AGAIN) {
385         nxt_set_errno(NXT_EAGAIN);
386         return -1;
387     }
388 
389     return n;
390 }
391 
392 
393 #if (NXT_HAVE_GNUTLS_VEC_PUSH)
394 
395 /* GnuTLS 2.12.0 */
396 
397 static ssize_t
398 nxt_gnutls_vec_push(gnutls_transport_ptr_t data, const giovec_t *iov,
399     int iovcnt)
400 {
401     ssize_t           n;
402     nxt_thread_t      *thr;
403     nxt_event_conn_t  *c;
404 
405     c = data;
406     thr = nxt_thread();
407 
408     /*
409      * This code assumes that giovec_t is the same as "struct iovec"
410      * and nxt_iobuf_t.  It is not true for Windows.
411      */
412     n = thr->engine->event->io->writev(c, (nxt_iobuf_t *) iov, iovcnt);
413 
414     if (n == NXT_AGAIN) {
415         nxt_set_errno(NXT_EAGAIN);
416         return -1;
417     }
418 
419     return n;
420 }
421 
422 #endif
423 
424 
425 static void
426 nxt_gnutls_conn_handshake(nxt_thread_t *thr, void *obj, void *data)
427 {
428     int                err;
429     nxt_int_t          ret;
430     nxt_event_conn_t   *c;
431     nxt_gnutls_conn_t  *ssltls;
432 
433     c = obj;
434     ssltls = c->u.ssltls;
435 
436     nxt_log_debug(thr->log, "gnutls conn handshake: %d", ssltls->times);
437 
438     /* "ssltls->times == 1" is suitable to run gnutls_handshake() in job. */
439 
440     err = gnutls_handshake(ssltls->session);
441 
442     nxt_thread_time_debug_update(thr);
443 
444     nxt_log_debug(thr->log, "gnutls_handshake(): %d", err);
445 
446     if (err == GNUTLS_E_SUCCESS) {
447         nxt_gnutls_conn_io_read(thr, c, data);
448         return;
449     }
450 
451     ret = nxt_gnutls_conn_test_error(thr, c, err, nxt_gnutls_conn_handshake);
452 
453     if (ret == NXT_ERROR) {
454         nxt_gnutls_conn_log_error(c, err, "gnutls_handshake() failed");
455 
456         nxt_event_conn_io_handle(thr, c->read_work_queue,
457                                  c->read_state->error_handler, c, data);
458 
459     } else if (err == GNUTLS_E_AGAIN
460                && ssltls->times < 2
461                && gnutls_record_get_direction(ssltls->session) == 0)
462     {
463         ssltls->times++;
464     }
465 }
466 
467 
468 static void
469 nxt_gnutls_conn_io_read(nxt_thread_t *thr, void *obj, void *data)
470 {
471     ssize_t             n;
472     nxt_buf_t           *b;
473     nxt_int_t           ret;
474     nxt_event_conn_t    *c;
475     nxt_gnutls_conn_t   *ssltls;
476     nxt_work_handler_t  handler;
477 
478     c = obj;
479 
480     nxt_log_debug(thr->log, "gnutls conn read");
481 
482     handler = c->read_state->ready_handler;
483     b = c->read;
484 
485     /* b == NULL is used to test descriptor readiness. */
486 
487     if (b != NULL) {
488         ssltls = c->u.ssltls;
489 
490         n = gnutls_record_recv(ssltls->session, b->mem.free,
491                                b->mem.end - b->mem.free);
492 
493         nxt_log_debug(thr->log, "gnutls_record_recv(%d, %p, %uz): %z",
494                       c->socket.fd, b->mem.free, b->mem.end - b->mem.free, n);
495 
496         if (n > 0) {
497             /* c->socket.read_ready is kept. */
498             b->mem.free += n;
499             handler = c->read_state->ready_handler;
500 
501         } else if (n == 0) {
502             handler = c->read_state->close_handler;
503 
504         } else {
505             ret = nxt_gnutls_conn_test_error(thr, c, n,
506                                              nxt_gnutls_conn_io_read);
507 
508             if (nxt_fast_path(ret != NXT_ERROR)) {
509                 return;
510             }
511 
512             nxt_gnutls_conn_log_error(c, n,
513                                       "gnutls_record_recv(%d, %p, %uz): failed",
514                                       c->socket.fd, b->mem.free,
515                                       b->mem.end - b->mem.free);
516 
517             handler = c->read_state->error_handler;
518         }
519     }
520 
521     nxt_event_conn_io_handle(thr, c->read_work_queue, handler, c, data);
522 }
523 
524 
525 static ssize_t
526 nxt_gnutls_conn_io_write_chunk(nxt_thread_t *thr, nxt_event_conn_t *c,
527     nxt_buf_t *b, size_t limit)
528 {
529     nxt_gnutls_conn_t  *ssltls;
530 
531     nxt_log_debug(thr->log, "gnutls conn write chunk");
532 
533     ssltls = c->u.ssltls;
534 
535     return nxt_sendbuf_copy_coalesce(c, &ssltls->buffer, b, limit);
536 }
537 
538 
539 static ssize_t
540 nxt_gnutls_conn_io_send(nxt_event_conn_t *c, void *buf, size_t size)
541 {
542     ssize_t            n;
543     nxt_int_t          ret;
544     nxt_gnutls_conn_t  *ssltls;
545 
546     ssltls = c->u.ssltls;
547 
548     n = gnutls_record_send(ssltls->session, buf, size);
549 
550     nxt_log_debug(c->socket.log, "gnutls_record_send(%d, %p, %uz): %z",
551                   c->socket.fd, buf, size, n);
552 
553     if (n > 0) {
554         return n;
555     }
556 
557     ret = nxt_gnutls_conn_test_error(nxt_thread(), c, n,
558                                      nxt_event_conn_io_write);
559 
560     if (nxt_slow_path(ret == NXT_ERROR)) {
561         nxt_gnutls_conn_log_error(c, n,
562                                   "gnutls_record_send(%d, %p, %uz): failed",
563                                   c->socket.fd, buf, size);
564     }
565 
566     return ret;
567 }
568 
569 
570 static void
571 nxt_gnutls_conn_io_shutdown(nxt_thread_t *thr, void *obj, void *data)
572 {
573     int                     err;
574     nxt_int_t               ret;
575     nxt_event_conn_t        *c;
576     nxt_gnutls_conn_t       *ssltls;
577     nxt_work_handler_t      handler;
578     gnutls_close_request_t  how;
579 
580     c = obj;
581     ssltls = c->u.ssltls;
582 
583     if (ssltls->session == NULL || ssltls->no_shutdown) {
584         handler = c->write_state->close_handler;
585         goto done;
586     }
587 
588     nxt_log_debug(c->socket.log, "gnutls conn shutdown");
589 
590     if (c->socket.timedout || c->socket.error != 0) {
591         how = GNUTLS_SHUT_WR;
592 
593     } else if (c->socket.closed) {
594         how = GNUTLS_SHUT_RDWR;
595 
596     } else {
597         how = GNUTLS_SHUT_RDWR;
598     }
599 
600     err = gnutls_bye(ssltls->session, how);
601 
602     nxt_log_debug(c->socket.log, "gnutls_bye(%d, %d): %d",
603                   c->socket.fd, how, err);
604 
605     if (err == GNUTLS_E_SUCCESS) {
606         handler = c->write_state->close_handler;
607 
608     } else {
609         ret = nxt_gnutls_conn_test_error(thr, c, err,
610                                          nxt_gnutls_conn_io_shutdown);
611 
612         if (ret != NXT_ERROR) {  /* ret == NXT_AGAIN */
613             c->socket.error_handler = c->read_state->error_handler;
614             nxt_event_timer_add(thr->engine, &c->read_timer, 5000);
615             return;
616         }
617 
618         nxt_gnutls_conn_log_error(c, err, "gnutls_bye(%d) failed",
619                                   c->socket.fd);
620 
621         handler = c->write_state->error_handler;
622     }
623 
624 done:
625 
626     nxt_event_conn_io_handle(thr, c->write_work_queue, handler, c, data);
627 }
628 
629 
630 static nxt_int_t
631 nxt_gnutls_conn_test_error(nxt_thread_t *thr, nxt_event_conn_t *c, ssize_t err,
632     nxt_work_handler_t handler)
633 {
634     int                ret;
635     nxt_gnutls_conn_t  *ssltls;
636 
637     switch (err) {
638 
639     case GNUTLS_E_REHANDSHAKE:
640     case GNUTLS_E_AGAIN:
641         ssltls = c->u.ssltls;
642         ret = gnutls_record_get_direction(ssltls->session);
643 
644         nxt_log_debug(thr->log, "gnutls_record_get_direction(): %d", ret);
645 
646         if (ret == 0) {
647             /* A read direction. */
648 
649             nxt_event_fd_block_write(thr->engine, &c->socket);
650 
651             c->socket.read_ready = 0;
652             c->socket.read_handler = handler;
653 
654             if (nxt_event_fd_is_disabled(c->socket.read)) {
655                 nxt_event_fd_enable_read(thr->engine, &c->socket);
656             }
657 
658         } else {
659             /* A write direction. */
660 
661             nxt_event_fd_block_read(thr->engine, &c->socket);
662 
663             c->socket.write_ready = 0;
664             c->socket.write_handler = handler;
665 
666             if (nxt_event_fd_is_disabled(c->socket.write)) {
667                 nxt_event_fd_enable_write(thr->engine, &c->socket);
668             }
669         }
670 
671         return NXT_AGAIN;
672 
673     default:
674         c->socket.error = 1000;  /* Nonexistent errno code. */
675         return NXT_ERROR;
676     }
677 }
678 
679 
680 static void
681 nxt_gnutls_conn_log_error(nxt_event_conn_t *c, ssize_t err,
682     const char *fmt, ...)
683 {
684     va_list      args;
685     nxt_uint_t   level;
686     u_char       *p, msg[NXT_MAX_ERROR_STR];
687 
688     level = nxt_gnutls_log_error_level(c, err);
689 
690     if (nxt_log_level_enough(c->socket.log, level)) {
691 
692         va_start(args, fmt);
693         p = nxt_vsprintf(msg, msg + sizeof(msg), fmt, args);
694         va_end(args);
695 
696         nxt_log_error(level, c->socket.log, "%*s (%d: %s)",
697                       p - msg, msg, err, gnutls_strerror(err));
698     }
699 }
700 
701 
702 static nxt_uint_t
703 nxt_gnutls_log_error_level(nxt_event_conn_t *c, ssize_t err)
704 {
705     nxt_gnutls_conn_t  *ssltls;
706 
707     switch (err) {
708 
709     case GNUTLS_E_UNKNOWN_CIPHER_SUITE:                      /*  -21 */
710 
711          /* Disable gnutls_bye(), because it returns GNUTLS_E_INTERNAL_ERROR. */
712         ssltls = c->u.ssltls;
713         ssltls->no_shutdown = 1;
714 
715          /* Fall through. */
716 
717     case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:                  /*   -9 */
718         c->socket.error = 1000;  /* Nonexistent errno code. */
719         break;
720 
721     default:
722         return NXT_LOG_CRIT;
723     }
724 
725     return NXT_LOG_INFO;
726 }
727 
728 
729 static void
730 nxt_gnutls_log_error(nxt_uint_t level, nxt_log_t *log, int err,
731     const char *fmt, ...)
732 {
733     va_list  args;
734     u_char   *p, msg[NXT_MAX_ERROR_STR];
735 
736     va_start(args, fmt);
737     p = nxt_vsprintf(msg, msg + sizeof(msg), fmt, args);
738     va_end(args);
739 
740     nxt_log_error(level, log, "%*s (%d: %s)",
741                   p - msg, msg, err, gnutls_strerror(err));
742 }
743