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