1 /*
2  * virnetsaslcontext.c: SASL encryption/auth handling
3  *
4  * Copyright (C) 2010-2012 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "virnetsaslcontext.h"
24 #include "virnetmessage.h"
25 
26 #include "virerror.h"
27 #include "viralloc.h"
28 #include "virthread.h"
29 #include "virlog.h"
30 #include "virstring.h"
31 
32 #define VIR_FROM_THIS VIR_FROM_RPC
33 
34 VIR_LOG_INIT("rpc.netsaslcontext");
35 
36 struct _virNetSASLContext {
37     virObjectLockable parent;
38 
39     const char *const *usernameACL;
40     unsigned int tcpMinSSF;
41 };
42 
43 struct _virNetSASLSession {
44     virObjectLockable parent;
45 
46     sasl_conn_t *conn;
47     size_t maxbufsize;
48     sasl_callback_t *callbacks;
49 };
50 
51 
52 static virClass *virNetSASLContextClass;
53 static virClass *virNetSASLSessionClass;
54 static void virNetSASLContextDispose(void *obj);
55 static void virNetSASLSessionDispose(void *obj);
56 
virNetSASLContextOnceInit(void)57 static int virNetSASLContextOnceInit(void)
58 {
59     if (!VIR_CLASS_NEW(virNetSASLContext, virClassForObjectLockable()))
60         return -1;
61 
62     if (!VIR_CLASS_NEW(virNetSASLSession, virClassForObjectLockable()))
63         return -1;
64 
65     return 0;
66 }
67 
68 VIR_ONCE_GLOBAL_INIT(virNetSASLContext);
69 
70 /* Apple have annotated all SASL functions as deprecated for
71  * unknown reasons. Since they still work, lets just ignore
72  * the warnings. If Apple finally delete the SASL functions
73  * our configure check should already catch that
74  */
75 #ifdef __APPLE__
76 VIR_WARNINGS_NO_DEPRECATED
77 #endif
78 
virNetSASLContextClientOnceInit(void)79 static int virNetSASLContextClientOnceInit(void)
80 {
81     int err = sasl_client_init(NULL);
82     if (err != SASL_OK) {
83         virReportError(VIR_ERR_AUTH_FAILED,
84                        _("failed to initialize SASL library: %d (%s)"),
85                        err, sasl_errstring(err, NULL, NULL));
86         return -1;
87     }
88 
89     return 0;
90 }
91 
92 VIR_ONCE_GLOBAL_INIT(virNetSASLContextClient);
93 
94 
virNetSASLContextServerOnceInit(void)95 static int virNetSASLContextServerOnceInit(void)
96 {
97     int err = sasl_server_init(NULL, "libvirt");
98     if (err != SASL_OK) {
99         virReportError(VIR_ERR_AUTH_FAILED,
100                        _("failed to initialize SASL library: %d (%s)"),
101                        err, sasl_errstring(err, NULL, NULL));
102         return -1;
103     }
104 
105     return 0;
106 }
107 
108 VIR_ONCE_GLOBAL_INIT(virNetSASLContextServer);
109 
110 
virNetSASLContextNewClient(void)111 virNetSASLContext *virNetSASLContextNewClient(void)
112 {
113     virNetSASLContext *ctxt;
114 
115     if (virNetSASLContextInitialize() < 0 ||
116         virNetSASLContextClientInitialize() < 0)
117         return NULL;
118 
119     if (!(ctxt = virObjectLockableNew(virNetSASLContextClass)))
120         return NULL;
121 
122     return ctxt;
123 }
124 
virNetSASLContextNewServer(const char * const * usernameACL,unsigned int tcpMinSSF)125 virNetSASLContext *virNetSASLContextNewServer(const char *const *usernameACL,
126                                               unsigned int tcpMinSSF)
127 {
128     virNetSASLContext *ctxt;
129 
130     if (virNetSASLContextInitialize() < 0 ||
131         virNetSASLContextServerInitialize() < 0)
132         return NULL;
133 
134     if (!(ctxt = virObjectLockableNew(virNetSASLContextClass)))
135         return NULL;
136 
137     ctxt->usernameACL = usernameACL;
138     ctxt->tcpMinSSF = tcpMinSSF;
139 
140     return ctxt;
141 }
142 
virNetSASLContextCheckIdentity(virNetSASLContext * ctxt,const char * identity)143 int virNetSASLContextCheckIdentity(virNetSASLContext *ctxt,
144                                    const char *identity)
145 {
146     const char *const*wildcards;
147     int ret = -1;
148 
149     virObjectLock(ctxt);
150 
151     /* If the list is not set, allow any DN. */
152     wildcards = ctxt->usernameACL;
153     if (!wildcards) {
154         ret = 1; /* No ACL, allow all */
155         goto cleanup;
156     }
157 
158     while (*wildcards) {
159         if (g_pattern_match_simple(*wildcards, identity)) {
160             ret = 1;
161             goto cleanup; /* Successful match */
162         }
163 
164         wildcards++;
165     }
166 
167     /* Denied */
168     VIR_ERROR(_("SASL client identity '%s' not allowed by ACL"), identity);
169 
170     /* This is the most common error: make it informative. */
171     virReportError(VIR_ERR_SYSTEM_ERROR, "%s",
172                    _("Client's username is not on the list of allowed clients"));
173     ret = 0;
174 
175  cleanup:
176     virObjectUnlock(ctxt);
177     return ret;
178 }
179 
180 
virNetSASLContextGetTCPMinSSF(virNetSASLContext * ctxt)181 unsigned int virNetSASLContextGetTCPMinSSF(virNetSASLContext *ctxt)
182 {
183     return ctxt->tcpMinSSF;
184 }
185 
186 
virNetSASLSessionNewClient(virNetSASLContext * ctxt G_GNUC_UNUSED,const char * service,const char * hostname,const char * localAddr,const char * remoteAddr,sasl_callback_t * cbs)187 virNetSASLSession *virNetSASLSessionNewClient(virNetSASLContext *ctxt G_GNUC_UNUSED,
188                                                 const char *service,
189                                                 const char *hostname,
190                                                 const char *localAddr,
191                                                 const char *remoteAddr,
192                                                 sasl_callback_t *cbs)
193 {
194     virNetSASLSession *sasl = NULL;
195     int err;
196 
197     if (!(sasl = virObjectLockableNew(virNetSASLSessionClass)))
198         return NULL;
199 
200     /* Arbitrary size for amount of data we can encode in a single block */
201     sasl->maxbufsize = 1 << 16;
202 
203     err = sasl_client_new(service,
204                           hostname,
205                           localAddr,
206                           remoteAddr,
207                           cbs,
208                           SASL_SUCCESS_DATA,
209                           &sasl->conn);
210     if (err != SASL_OK) {
211         virReportError(VIR_ERR_AUTH_FAILED,
212                        _("Failed to create SASL client context: %d (%s)"),
213                        err, sasl_errstring(err, NULL, NULL));
214         goto cleanup;
215     }
216     sasl->callbacks = cbs;
217 
218     return sasl;
219 
220  cleanup:
221     virObjectUnref(sasl);
222     return NULL;
223 }
224 
virNetSASLSessionNewServer(virNetSASLContext * ctxt G_GNUC_UNUSED,const char * service,const char * localAddr,const char * remoteAddr)225 virNetSASLSession *virNetSASLSessionNewServer(virNetSASLContext *ctxt G_GNUC_UNUSED,
226                                                 const char *service,
227                                                 const char *localAddr,
228                                                 const char *remoteAddr)
229 {
230     virNetSASLSession *sasl = NULL;
231     int err;
232 
233     if (!(sasl = virObjectLockableNew(virNetSASLSessionClass)))
234         return NULL;
235 
236     /* Arbitrary size for amount of data we can encode in a single block */
237     sasl->maxbufsize = 1 << 16;
238 
239     err = sasl_server_new(service,
240                           NULL,
241                           NULL,
242                           localAddr,
243                           remoteAddr,
244                           NULL,
245                           SASL_SUCCESS_DATA,
246                           &sasl->conn);
247     if (err != SASL_OK) {
248         virReportError(VIR_ERR_AUTH_FAILED,
249                        _("Failed to create SASL client context: %d (%s)"),
250                        err, sasl_errstring(err, NULL, NULL));
251         goto cleanup;
252     }
253 
254     return sasl;
255 
256  cleanup:
257     virObjectUnref(sasl);
258     return NULL;
259 }
260 
virNetSASLSessionExtKeySize(virNetSASLSession * sasl,int ssf)261 int virNetSASLSessionExtKeySize(virNetSASLSession *sasl,
262                                 int ssf)
263 {
264     int err;
265     int ret = -1;
266     virObjectLock(sasl);
267 
268     err = sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf);
269     if (err != SASL_OK) {
270         virReportError(VIR_ERR_INTERNAL_ERROR,
271                        _("cannot set external SSF %d (%s)"),
272                        err, sasl_errstring(err, NULL, NULL));
273         goto cleanup;
274     }
275 
276     ret = 0;
277 
278  cleanup:
279     virObjectUnlock(sasl);
280     return ret;
281 }
282 
virNetSASLSessionGetIdentity(virNetSASLSession * sasl)283 const char *virNetSASLSessionGetIdentity(virNetSASLSession *sasl)
284 {
285     const void *val = NULL;
286     int err;
287     virObjectLock(sasl);
288 
289     err = sasl_getprop(sasl->conn, SASL_USERNAME, &val);
290     if (err != SASL_OK) {
291         virReportError(VIR_ERR_AUTH_FAILED,
292                        _("cannot query SASL username on connection %d (%s)"),
293                        err, sasl_errstring(err, NULL, NULL));
294         val = NULL;
295         goto cleanup;
296     }
297     if (val == NULL) {
298         virReportError(VIR_ERR_AUTH_FAILED, "%s",
299                        _("no client username was found"));
300         goto cleanup;
301     }
302     VIR_DEBUG("SASL client username %s", (const char *)val);
303 
304  cleanup:
305     virObjectUnlock(sasl);
306     return (const char*)val;
307 }
308 
309 
virNetSASLSessionGetKeySize(virNetSASLSession * sasl)310 int virNetSASLSessionGetKeySize(virNetSASLSession *sasl)
311 {
312     int err;
313     int ssf;
314     const void *val;
315 
316     virObjectLock(sasl);
317     err = sasl_getprop(sasl->conn, SASL_SSF, &val);
318     if (err != SASL_OK) {
319         virReportError(VIR_ERR_AUTH_FAILED,
320                        _("cannot query SASL ssf on connection %d (%s)"),
321                        err, sasl_errstring(err, NULL, NULL));
322         ssf = -1;
323         goto cleanup;
324     }
325     ssf = *(const int *)val;
326 
327  cleanup:
328     virObjectUnlock(sasl);
329     return ssf;
330 }
331 
virNetSASLSessionSecProps(virNetSASLSession * sasl,int minSSF,int maxSSF,bool allowAnonymous)332 int virNetSASLSessionSecProps(virNetSASLSession *sasl,
333                               int minSSF,
334                               int maxSSF,
335                               bool allowAnonymous)
336 {
337     sasl_security_properties_t secprops;
338     int err;
339     int ret = -1;
340 
341     VIR_DEBUG("minSSF=%d maxSSF=%d allowAnonymous=%d maxbufsize=%zu",
342               minSSF, maxSSF, allowAnonymous, sasl->maxbufsize);
343 
344     virObjectLock(sasl);
345     memset(&secprops, 0, sizeof(secprops));
346 
347     secprops.min_ssf = minSSF;
348     secprops.max_ssf = maxSSF;
349     secprops.maxbufsize = sasl->maxbufsize;
350     secprops.security_flags = allowAnonymous ? 0 :
351         SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
352 
353     err = sasl_setprop(sasl->conn, SASL_SEC_PROPS, &secprops);
354     if (err != SASL_OK) {
355         virReportError(VIR_ERR_INTERNAL_ERROR,
356                        _("cannot set security props %d (%s)"),
357                        err, sasl_errstring(err, NULL, NULL));
358         goto cleanup;
359     }
360 
361     ret = 0;
362 
363  cleanup:
364     virObjectUnlock(sasl);
365     return ret;
366 }
367 
368 
virNetSASLSessionUpdateBufSize(virNetSASLSession * sasl)369 static int virNetSASLSessionUpdateBufSize(virNetSASLSession *sasl)
370 {
371     union {
372         unsigned *maxbufsize;
373         const void *ptr;
374     } u;
375     int err;
376 
377     err = sasl_getprop(sasl->conn, SASL_MAXOUTBUF, &u.ptr);
378     if (err != SASL_OK) {
379         virReportError(VIR_ERR_INTERNAL_ERROR,
380                        _("cannot get security props %d (%s)"),
381                        err, sasl_errstring(err, NULL, NULL));
382         return -1;
383     }
384 
385     VIR_DEBUG("Negotiated bufsize is %u vs requested size %zu",
386               *u.maxbufsize, sasl->maxbufsize);
387     sasl->maxbufsize = *u.maxbufsize;
388     return 0;
389 }
390 
virNetSASLSessionListMechanisms(virNetSASLSession * sasl)391 char *virNetSASLSessionListMechanisms(virNetSASLSession *sasl)
392 {
393     const char *mechlist;
394     char *ret = NULL;
395     int err;
396 
397     virObjectLock(sasl);
398     err = sasl_listmech(sasl->conn,
399                         NULL, /* Don't need to set user */
400                         "", /* Prefix */
401                         ",", /* Separator */
402                         "", /* Suffix */
403                         &mechlist,
404                         NULL,
405                         NULL);
406     if (err != SASL_OK) {
407         virReportError(VIR_ERR_INTERNAL_ERROR,
408                        _("cannot list SASL mechanisms %d (%s)"),
409                        err, sasl_errdetail(sasl->conn));
410         goto cleanup;
411     }
412     VIR_DEBUG("SASL mechanism list is '%s'", mechlist);
413     if (STREQ(mechlist, "")) {
414         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
415                        _("no SASL mechanisms are available"));
416         goto cleanup;
417     }
418     ret = g_strdup(mechlist);
419 
420  cleanup:
421     virObjectUnlock(sasl);
422     return ret;
423 }
424 
425 
virNetSASLSessionClientStart(virNetSASLSession * sasl,const char * mechlist,sasl_interact_t ** prompt_need,const char ** clientout,size_t * clientoutlen,const char ** mech)426 int virNetSASLSessionClientStart(virNetSASLSession *sasl,
427                                  const char *mechlist,
428                                  sasl_interact_t **prompt_need,
429                                  const char **clientout,
430                                  size_t *clientoutlen,
431                                  const char **mech)
432 {
433     unsigned outlen = 0;
434     int err;
435     int ret = -1;
436 
437     VIR_DEBUG("sasl=%p mechlist=%s prompt_need=%p clientout=%p clientoutlen=%p mech=%p",
438               sasl, mechlist, prompt_need, clientout, clientoutlen, mech);
439 
440     virObjectLock(sasl);
441     err = sasl_client_start(sasl->conn,
442                             mechlist,
443                             prompt_need,
444                             clientout,
445                             &outlen,
446                             mech);
447 
448     *clientoutlen = outlen;
449 
450     switch (err) {
451     case SASL_OK:
452         if (virNetSASLSessionUpdateBufSize(sasl) < 0)
453             goto cleanup;
454         ret = VIR_NET_SASL_COMPLETE;
455         break;
456     case SASL_CONTINUE:
457         ret = VIR_NET_SASL_CONTINUE;
458         break;
459     case SASL_INTERACT:
460         ret = VIR_NET_SASL_INTERACT;
461         break;
462     default:
463         virReportError(VIR_ERR_AUTH_FAILED,
464                        _("Failed to start SASL negotiation: %d (%s)"),
465                        err, sasl_errdetail(sasl->conn));
466         break;
467     }
468 
469  cleanup:
470     virObjectUnlock(sasl);
471     return ret;
472 }
473 
474 
virNetSASLSessionClientStep(virNetSASLSession * sasl,const char * serverin,size_t serverinlen,sasl_interact_t ** prompt_need,const char ** clientout,size_t * clientoutlen)475 int virNetSASLSessionClientStep(virNetSASLSession *sasl,
476                                 const char *serverin,
477                                 size_t serverinlen,
478                                 sasl_interact_t **prompt_need,
479                                 const char **clientout,
480                                 size_t *clientoutlen)
481 {
482     unsigned inlen = serverinlen;
483     unsigned outlen = 0;
484     int err;
485     int ret = -1;
486 
487     VIR_DEBUG("sasl=%p serverin=%p serverinlen=%zu prompt_need=%p clientout=%p clientoutlen=%p",
488               sasl, serverin, serverinlen, prompt_need, clientout, clientoutlen);
489 
490     virObjectLock(sasl);
491     err = sasl_client_step(sasl->conn,
492                            serverin,
493                            inlen,
494                            prompt_need,
495                            clientout,
496                            &outlen);
497     *clientoutlen = outlen;
498 
499     switch (err) {
500     case SASL_OK:
501         if (virNetSASLSessionUpdateBufSize(sasl) < 0)
502             goto cleanup;
503         ret = VIR_NET_SASL_COMPLETE;
504         break;
505     case SASL_CONTINUE:
506         ret = VIR_NET_SASL_CONTINUE;
507         break;
508     case SASL_INTERACT:
509         ret = VIR_NET_SASL_INTERACT;
510         break;
511     default:
512         virReportError(VIR_ERR_AUTH_FAILED,
513                        _("Failed to step SASL negotiation: %d (%s)"),
514                        err, sasl_errdetail(sasl->conn));
515         break;
516     }
517 
518  cleanup:
519     virObjectUnlock(sasl);
520     return ret;
521 }
522 
virNetSASLSessionServerStart(virNetSASLSession * sasl,const char * mechname,const char * clientin,size_t clientinlen,const char ** serverout,size_t * serveroutlen)523 int virNetSASLSessionServerStart(virNetSASLSession *sasl,
524                                  const char *mechname,
525                                  const char *clientin,
526                                  size_t clientinlen,
527                                  const char **serverout,
528                                  size_t *serveroutlen)
529 {
530     unsigned inlen = clientinlen;
531     unsigned outlen = 0;
532     int err;
533     int ret = -1;
534 
535     virObjectLock(sasl);
536     err = sasl_server_start(sasl->conn,
537                             mechname,
538                             clientin,
539                             inlen,
540                             serverout,
541                             &outlen);
542 
543     *serveroutlen = outlen;
544 
545     switch (err) {
546     case SASL_OK:
547         if (virNetSASLSessionUpdateBufSize(sasl) < 0)
548             goto cleanup;
549         ret = VIR_NET_SASL_COMPLETE;
550         break;
551     case SASL_CONTINUE:
552         ret = VIR_NET_SASL_CONTINUE;
553         break;
554     case SASL_INTERACT:
555         ret = VIR_NET_SASL_INTERACT;
556         break;
557     default:
558         virReportError(VIR_ERR_AUTH_FAILED,
559                        _("Failed to start SASL negotiation: %d (%s)"),
560                        err, sasl_errdetail(sasl->conn));
561         break;
562     }
563 
564  cleanup:
565     virObjectUnlock(sasl);
566     return ret;
567 }
568 
569 
virNetSASLSessionServerStep(virNetSASLSession * sasl,const char * clientin,size_t clientinlen,const char ** serverout,size_t * serveroutlen)570 int virNetSASLSessionServerStep(virNetSASLSession *sasl,
571                                 const char *clientin,
572                                 size_t clientinlen,
573                                 const char **serverout,
574                                 size_t *serveroutlen)
575 {
576     unsigned inlen = clientinlen;
577     unsigned outlen = 0;
578     int err;
579     int ret = -1;
580 
581     virObjectLock(sasl);
582     err = sasl_server_step(sasl->conn,
583                            clientin,
584                            inlen,
585                            serverout,
586                            &outlen);
587 
588     *serveroutlen = outlen;
589 
590     switch (err) {
591     case SASL_OK:
592         if (virNetSASLSessionUpdateBufSize(sasl) < 0)
593             goto cleanup;
594         ret = VIR_NET_SASL_COMPLETE;
595         break;
596     case SASL_CONTINUE:
597         ret = VIR_NET_SASL_CONTINUE;
598         break;
599     case SASL_INTERACT:
600         ret = VIR_NET_SASL_INTERACT;
601         break;
602     default:
603         virReportError(VIR_ERR_AUTH_FAILED,
604                        _("Failed to start SASL negotiation: %d (%s)"),
605                        err, sasl_errdetail(sasl->conn));
606         break;
607     }
608 
609  cleanup:
610     virObjectUnlock(sasl);
611     return ret;
612 }
613 
virNetSASLSessionGetMaxBufSize(virNetSASLSession * sasl)614 size_t virNetSASLSessionGetMaxBufSize(virNetSASLSession *sasl)
615 {
616     size_t ret;
617     virObjectLock(sasl);
618     ret = sasl->maxbufsize;
619     virObjectUnlock(sasl);
620     return ret;
621 }
622 
virNetSASLSessionEncode(virNetSASLSession * sasl,const char * input,size_t inputLen,const char ** output,size_t * outputlen)623 ssize_t virNetSASLSessionEncode(virNetSASLSession *sasl,
624                                 const char *input,
625                                 size_t inputLen,
626                                 const char **output,
627                                 size_t *outputlen)
628 {
629     unsigned inlen = inputLen;
630     unsigned outlen = 0;
631     int err;
632     ssize_t ret = -1;
633 
634     virObjectLock(sasl);
635     if (inputLen > sasl->maxbufsize) {
636         virReportSystemError(EINVAL,
637                              _("SASL data length %zu too long, max %zu"),
638                              inputLen, sasl->maxbufsize);
639         goto cleanup;
640     }
641 
642     err = sasl_encode(sasl->conn,
643                       input,
644                       inlen,
645                       output,
646                       &outlen);
647     *outputlen = outlen;
648 
649     if (err != SASL_OK) {
650         virReportError(VIR_ERR_INTERNAL_ERROR,
651                        _("failed to encode SASL data: %d (%s)"),
652                        err, sasl_errstring(err, NULL, NULL));
653         goto cleanup;
654     }
655     ret = 0;
656 
657  cleanup:
658     virObjectUnlock(sasl);
659     return ret;
660 }
661 
virNetSASLSessionDecode(virNetSASLSession * sasl,const char * input,size_t inputLen,const char ** output,size_t * outputlen)662 ssize_t virNetSASLSessionDecode(virNetSASLSession *sasl,
663                                 const char *input,
664                                 size_t inputLen,
665                                 const char **output,
666                                 size_t *outputlen)
667 {
668     unsigned inlen = inputLen;
669     unsigned outlen = 0;
670     int err;
671     ssize_t ret = -1;
672 
673     virObjectLock(sasl);
674     if (inputLen > sasl->maxbufsize) {
675         virReportSystemError(EINVAL,
676                              _("SASL data length %zu too long, max %zu"),
677                              inputLen, sasl->maxbufsize);
678         goto cleanup;
679     }
680 
681     err = sasl_decode(sasl->conn,
682                       input,
683                       inlen,
684                       output,
685                       &outlen);
686     *outputlen = outlen;
687     if (err != SASL_OK) {
688         virReportError(VIR_ERR_INTERNAL_ERROR,
689                        _("failed to decode SASL data: %d (%s)"),
690                        err, sasl_errstring(err, NULL, NULL));
691         goto cleanup;
692     }
693     ret = 0;
694 
695  cleanup:
696     virObjectUnlock(sasl);
697     return ret;
698 }
699 
virNetSASLContextDispose(void * obj G_GNUC_UNUSED)700 void virNetSASLContextDispose(void *obj G_GNUC_UNUSED)
701 {
702     return;
703 }
704 
virNetSASLSessionDispose(void * obj)705 void virNetSASLSessionDispose(void *obj)
706 {
707     virNetSASLSession *sasl = obj;
708 
709     if (sasl->conn)
710         sasl_dispose(&sasl->conn);
711     g_free(sasl->callbacks);
712 }
713 
714 #ifdef __APPLE__
715 VIR_WARNINGS_RESET
716 #endif
717