From e332ccdf710de8d2669e3fd2a491abff2eab37bf Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 5 Dec 2007 18:55:04 +0000 Subject: [PATCH] Wire up SASL interaction callbacks to libvirt callbacks. Provide default callback impl --- ChangeLog | 12 + include/libvirt/libvirt.h | 2 + include/libvirt/libvirt.h.in | 2 + src/internal.h | 2 + src/libvirt.c | 81 ++++++ src/libvirt_sym.version | 3 + src/remote_internal.c | 471 ++++++++++++++++++++++++++++------- src/virsh.c | 8 +- 8 files changed, 485 insertions(+), 96 deletions(-) diff --git a/ChangeLog b/ChangeLog index 594c967d8e..0efe90c97c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +Wed Dec 5 13:51:00 EST 2007 Daniel P. Berrange + + * include/libvirt/libvirt.h.in: Add virConnectAuthPtrDefault + as default CLI auth callback + * src/libvirt_sym.version: Export virConnectAuthPtrDefault + * src/libvirt.c: Default auth callback for command line based + apps + * src/virsh.c: Use default auth callback + * src/internal.h: Add STRCASEEQLEN, STRCASENEQLEN + * src/remote_internal.c: Wire up callback API to SASL interaction + types / callbacks. + Wed Dec 5 13:27:00 EST 2007 Daniel P. Berrange * Makefile.am: Put include/ before src/ in SUBDIRS diff --git a/include/libvirt/libvirt.h b/include/libvirt/libvirt.h index 74b8beea9a..8086e6d7ca 100644 --- a/include/libvirt/libvirt.h +++ b/include/libvirt/libvirt.h @@ -343,6 +343,8 @@ struct _virConnectAuth { typedef struct _virConnectAuth virConnectAuth; typedef virConnectAuth *virConnectAuthPtr; +extern virConnectAuthPtr virConnectAuthPtrDefault; + /** * VIR_UUID_BUFLEN: * diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 15f325fb23..5782d16c12 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -343,6 +343,8 @@ struct _virConnectAuth { typedef struct _virConnectAuth virConnectAuth; typedef virConnectAuth *virConnectAuthPtr; +extern virConnectAuthPtr virConnectAuthPtrDefault; + /** * VIR_UUID_BUFLEN: * diff --git a/src/internal.h b/src/internal.h index b50d626303..563831b250 100644 --- a/src/internal.h +++ b/src/internal.h @@ -46,7 +46,9 @@ extern "C" { #define STRNEQ(a,b) (strcmp((a),(b)) != 0) #define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0) #define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0) +#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0) #define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0) +#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0) /* C99 uses __func__. __FUNCTION__ is legacy. */ #ifndef __GNUC__ diff --git a/src/libvirt.c b/src/libvirt.c index 62ede212a3..5a7a2eb055 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -63,6 +63,87 @@ static int initialized = 0; #define DEBUG(fs,...) #endif /* !ENABLE_DEBUG */ +static int virConnectAuthCallbackDefault(virConnectCredentialPtr cred, + unsigned int ncred, + void *cbdata ATTRIBUTE_UNUSED) { + int i; + + for (i = 0 ; i < ncred ; i++) { + char buf[1024]; + char *bufptr = buf; + + if (printf("%s:", cred[i].prompt) < 0) + return -1; + if (fflush(stdout) != 0) + return -1; + + switch (cred[i].type) { + case VIR_CRED_USERNAME: + case VIR_CRED_AUTHNAME: + case VIR_CRED_ECHOPROMPT: + case VIR_CRED_REALM: + if (!fgets(buf, sizeof(buf), stdin)) { + if (feof(stdin)) { /* Treat EOF as "" */ + buf[0] = '\0'; + break; + } + return -1; + } + if (buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + break; + + case VIR_CRED_PASSPHRASE: + case VIR_CRED_NOECHOPROMPT: + bufptr = getpass(""); + if (!bufptr) + return -1; + break; + } + + if (STREQ(bufptr, "") && cred[i].defresult) + cred[i].result = strdup(cred[i].defresult); + else + cred[i].result = strdup(bufptr); + if (!cred[i].result) + return -1; + cred[i].resultlen = strlen(cred[i].result); + } + + return 0; +} + +/* Don't typically want VIR_CRED_USERNAME. It enables you to authenticate + * as one user, and act as another. It just results in annoying + * prompts for the username twice & is very rarely what you want + */ +static int virConnectCredTypeDefault[] = { + VIR_CRED_AUTHNAME, + VIR_CRED_ECHOPROMPT, + VIR_CRED_REALM, + VIR_CRED_PASSPHRASE, + VIR_CRED_NOECHOPROMPT, +}; + +static virConnectAuth virConnectAuthDefault = { + virConnectCredTypeDefault, + sizeof(virConnectCredTypeDefault)/sizeof(int), + virConnectAuthCallbackDefault, + NULL, +}; + +/* + * virConnectAuthPtrDefault + * + * A default implementation of the authentication callbacks. This + * implementation is suitable for command line based tools. It will + * prompt for username, passwords, realm and one time keys as needed. + * It will print on STDOUT, and read from STDIN. If this is not + * suitable for the application's needs an alternative implementation + * should be provided. + */ +virConnectAuthPtr virConnectAuthPtrDefault = &virConnectAuthDefault; + /** * virInitialize: * diff --git a/src/libvirt_sym.version b/src/libvirt_sym.version index 9b194e9edd..ba8e227262 100644 --- a/src/libvirt_sym.version +++ b/src/libvirt_sym.version @@ -3,6 +3,9 @@ virInitialize; virConnectOpen; virConnectOpenReadOnly; + virConnectOpenAuth; + virConnectAuthPtrDefault; + virConnectClose; virConnectGetType; virConnectGetVersion; diff --git a/src/remote_internal.c b/src/remote_internal.c index 78e4524b47..068f46674d 100644 --- a/src/remote_internal.c +++ b/src/remote_internal.c @@ -111,12 +111,15 @@ static int call (virConnectPtr conn, struct private_data *priv, int flags, int proc_nr, xdrproc_t args_filter, char *args, xdrproc_t ret_filter, char *ret); -static int remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open); +static int remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open, + virConnectAuthPtr auth, const char *authtype); #if HAVE_SASL -static int remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open); +static int remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, + virConnectAuthPtr auth, const char *mech); #endif #if HAVE_POLKIT -static int remoteAuthPolkit (virConnectPtr conn, struct private_data *priv, int in_open); +static int remoteAuthPolkit (virConnectPtr conn, struct private_data *priv, int in_open, + virConnectAuthPtr auth); #endif /* HAVE_POLKIT */ static void error (virConnectPtr conn, virErrorNumber code, const char *info); static void server_error (virConnectPtr conn, remote_error *err); @@ -342,7 +345,7 @@ doRemoteOpen (virConnectPtr conn, * get freed in the failed: path. */ char *name = 0, *command = 0, *sockname = 0, *netcat = 0, *username = 0; - char *port = 0; + char *port = 0, *authtype = 0; int no_verify = 0, no_tty = 0; char **cmd_argv = 0; @@ -402,6 +405,10 @@ doRemoteOpen (virConnectPtr conn, sockname = strdup (var->value); if (!sockname) goto out_of_memory; var->ignore = 1; + } else if (strcasecmp (var->name, "auth") == 0) { + authtype = strdup (var->value); + if (!authtype) goto out_of_memory; + var->ignore = 1; } else if (strcasecmp (var->name, "netcat") == 0) { netcat = strdup (var->value); if (!netcat) goto out_of_memory; @@ -718,7 +725,7 @@ doRemoteOpen (virConnectPtr conn, /* Try and authenticate with server */ - if (remoteAuthenticate(conn, priv, 1) == -1) + if (remoteAuthenticate(conn, priv, 1, auth, authtype) == -1) goto failed; /* Finally we can call the remote side's open function. */ @@ -737,6 +744,7 @@ doRemoteOpen (virConnectPtr conn, if (name) free (name); if (command) free (command); if (sockname) free (sockname); + if (authtype) free (authtype); if (netcat) free (netcat); if (username) free (username); if (port) free (port); @@ -2833,10 +2841,11 @@ remoteNetworkSetAutostart (virNetworkPtr network, int autostart) /*----------------------------------------------------------------------*/ static int -remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open) +remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open, + virConnectAuthPtr auth, const char *authtype) { struct remote_auth_list_ret ret; - int err; + int err, type = REMOTE_AUTH_NONE; memset(&ret, 0, sizeof ret); err = call (conn, priv, @@ -2853,19 +2862,52 @@ remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open) if (ret.types.types_len == 0) return 0; - switch (ret.types.types_val[0]) { + if (authtype) { + int want, i; + if (STRCASEEQ(authtype, "sasl") || + STRCASEEQLEN(authtype, "sasl.", 5)) { + want = REMOTE_AUTH_SASL; + } else if (STRCASEEQ(authtype, "polkit")) { + want = REMOTE_AUTH_POLKIT; + } else { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "unknown authentication type %s", authtype); + return -1; + } + for (i = 0 ; i < ret.types.types_len ; i++) { + if (ret.types.types_val[i] == want) + type = want; + } + if (type == REMOTE_AUTH_NONE) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "requested authentication type %s rejected", authtype); + return -1; + } + } else { + type = ret.types.types_val[0]; + } + + switch (type) { #if HAVE_SASL - case REMOTE_AUTH_SASL: - if (remoteAuthSASL(conn, priv, in_open) < 0) { + case REMOTE_AUTH_SASL: { + const char *mech = NULL; + if (authtype && + STRCASEEQLEN(authtype, "sasl.", 5)) + mech = authtype + 5; + + if (remoteAuthSASL(conn, priv, in_open, auth, mech) < 0) { free(ret.types.types_val); return -1; } break; + } #endif #if HAVE_POLKIT case REMOTE_AUTH_POLKIT: - if (remoteAuthPolkit(conn, priv, in_open) < 0) { + if (remoteAuthPolkit(conn, priv, in_open, auth) < 0) { free(ret.types.types_val); return -1; } @@ -2926,13 +2968,174 @@ static char *addrToString(struct sockaddr_storage *sa, socklen_t salen) } -/* Perform the SASL authentication process +static int remoteAuthCredVir2SASL(int vircred) +{ + switch (vircred) { + case VIR_CRED_USERNAME: + return SASL_CB_USER; + + case VIR_CRED_AUTHNAME: + return SASL_CB_AUTHNAME; + + case VIR_CRED_LANGUAGE: + return SASL_CB_LANGUAGE; + + case VIR_CRED_CNONCE: + return SASL_CB_CNONCE; + + case VIR_CRED_PASSPHRASE: + return SASL_CB_PASS; + + case VIR_CRED_ECHOPROMPT: + return SASL_CB_ECHOPROMPT; + + case VIR_CRED_NOECHOPROMPT: + return SASL_CB_NOECHOPROMPT; + + case VIR_CRED_REALM: + return SASL_CB_GETREALM; + } + + return 0; +} + +static int remoteAuthCredSASL2Vir(int vircred) +{ + switch (vircred) { + case SASL_CB_USER: + return VIR_CRED_USERNAME; + + case SASL_CB_AUTHNAME: + return VIR_CRED_AUTHNAME; + + case SASL_CB_LANGUAGE: + return VIR_CRED_LANGUAGE; + + case SASL_CB_CNONCE: + return VIR_CRED_CNONCE; + + case SASL_CB_PASS: + return VIR_CRED_PASSPHRASE; + + case SASL_CB_ECHOPROMPT: + return VIR_CRED_ECHOPROMPT; + + case SASL_CB_NOECHOPROMPT: + return VIR_CRED_NOECHOPROMPT; + + case SASL_CB_GETREALM: + return VIR_CRED_REALM; + } + + return 0; +} + +/* + * @param credtype array of credential types client supports + * @param ncredtype size of credtype array + * @return the SASL callback structure, or NULL on error * - * XXX fetch credentials from a libvirt client app callback - * XXX better mechanism negotiation ? Ask client app ? + * Build up the SASL callback structure. We register one callback for + * each credential type that the libvirt client indicated they support. + * We explicitly leav the callback function pointer at NULL though, + * because we don't actually want to get SASL callbacks triggered. + * Instead, we want the start/step functions to return SASL_INTERACT. + * This lets us give the libvirt client a list of all required + * credentials in one go, rather than triggering the callback one + * credential at a time, + */ +static sasl_callback_t *remoteAuthMakeCallbacks(int *credtype, int ncredtype) +{ + sasl_callback_t *cbs = calloc(ncredtype+1, sizeof (sasl_callback_t)); + int i, n; + if (!cbs) { + return NULL; + } + + for (i = 0, n = 0 ; i < ncredtype ; i++) { + int id = remoteAuthCredVir2SASL(credtype[i]); + if (id != 0) + cbs[n++].id = id; + /* Don't fill proc or context fields of sasl_callback_t + * because we want to use interactions instead */ + } + cbs[n].id = 0; + return cbs; +} + + +/* + * @param interact SASL interactions required + * @param cred populated with libvirt credential metadata + * @return the size of the cred array returned + * + * Builds up an array of libvirt credential structs, populating + * with data from the SASL interaction struct. These two structs + * are basically a 1-to-1 copy of each other. + */ +static int remoteAuthMakeCredentials(sasl_interact_t *interact, + virConnectCredentialPtr *cred) +{ + int ninteract; + if (!cred) + return -1; + + for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) + ; /* empty */ + + *cred = calloc(ninteract, sizeof(virConnectCredential)); + if (!*cred) + return -1; + + for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) { + (*cred)[ninteract].type = remoteAuthCredSASL2Vir(interact[ninteract].id); + if (!(*cred)[ninteract].type) { + free(*cred); + return -1; + } + if (interact[ninteract].challenge) + (*cred)[ninteract].challenge = interact[ninteract].challenge; + (*cred)[ninteract].prompt = interact[ninteract].prompt; + if (interact[ninteract].defresult) + (*cred)[ninteract].defresult = interact[ninteract].defresult; + (*cred)[ninteract].result = NULL; + } + + return ninteract; +} + +static void remoteAuthFreeCredentials(virConnectCredentialPtr cred, + int ncred) +{ + int i; + for (i = 0 ; i < ncred ; i++) + free(cred[i].result); + free(cred); +} + + +/* + * @param cred the populated libvirt credentials + * @param interact the SASL interactions to fill in results for + * + * Fills the SASL interactions with the result from the libvirt + * callbacks + */ +static void remoteAuthFillInteract(virConnectCredentialPtr cred, + sasl_interact_t *interact) +{ + int ninteract; + for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) { + interact[ninteract].result = cred[ninteract].result; + interact[ninteract].len = cred[ninteract].resultlen; + } +} + +/* Perform the SASL authentication process */ static int -remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) +remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open, + virConnectAuthPtr auth, const char *wantmech) { sasl_conn_t *saslconn = NULL; sasl_security_properties_t secprops; @@ -2942,15 +3145,21 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) remote_auth_sasl_step_args pargs; remote_auth_sasl_step_ret pret; const char *clientout; - char *serverin; + char *serverin = NULL; unsigned int clientoutlen, serverinlen; const char *mech; int err, complete; struct sockaddr_storage sa; socklen_t salen; - char *localAddr, *remoteAddr; + char *localAddr = NULL, *remoteAddr = NULL; const void *val; sasl_ssf_t ssf; + sasl_callback_t *saslcb = NULL; + sasl_interact_t *interact = NULL; + virConnectCredentialPtr cred = NULL; + int ncred = 0; + int ret = -1; + const char *mechlist; remoteDebug(priv, "Client initialize SASL authentication"); /* Sets up the SASL library as a whole */ @@ -2960,7 +3169,7 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "failed to initialize SASL library: %d (%s)", err, sasl_errstring(err, NULL, NULL)); - return -1; + goto cleanup; } /* Get local address in form IPADDR:PORT */ @@ -2970,11 +3179,10 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "failed to get sock address %d (%s)", errno, strerror(errno)); - return -1; - } - if ((localAddr = addrToString(&sa, salen)) == NULL) { - return -1; + goto cleanup; } + if ((localAddr = addrToString(&sa, salen)) == NULL) + goto cleanup; /* Get remote address in form IPADDR:PORT */ salen = sizeof(sa); @@ -2983,30 +3191,29 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "failed to get peer address %d (%s)", errno, strerror(errno)); - free(localAddr); - return -1; - } - if ((remoteAddr = addrToString(&sa, salen)) == NULL) { - free(localAddr); - return -1; + goto cleanup; } + if ((remoteAddr = addrToString(&sa, salen)) == NULL) + goto cleanup; + + if ((saslcb = remoteAuthMakeCallbacks(auth->credtype, auth->ncredtype)) == NULL) + goto cleanup; /* Setup a handle for being a client */ err = sasl_client_new("libvirt", priv->hostname, localAddr, remoteAddr, - NULL, /* XXX callbacks */ + saslcb, SASL_SUCCESS_DATA, &saslconn); - free(localAddr); - free(remoteAddr); + if (err != SASL_OK) { __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "Failed to create SASL client context: %d (%s)", err, sasl_errstring(err, NULL, NULL)); - return -1; + goto cleanup; } /* Initialize some connection props we care about */ @@ -3018,8 +3225,7 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_INTERNAL_ERROR, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "invalid cipher size for TLS session"); - sasl_dispose(&saslconn); - return -1; + goto cleanup; } ssf *= 8; /* key size is bytes, sasl wants bits */ @@ -3030,8 +3236,7 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) VIR_ERR_INTERNAL_ERROR, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "cannot set external SSF %d (%s)", err, sasl_errstring(err, NULL, NULL)); - sasl_dispose(&saslconn); - return -1; + goto cleanup; } } @@ -3050,36 +3255,70 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) VIR_ERR_INTERNAL_ERROR, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "cannot set security props %d (%s)", err, sasl_errstring(err, NULL, NULL)); - sasl_dispose(&saslconn); - return -1; + goto cleanup; } /* First call is to inquire about supported mechanisms in the server */ memset (&iret, 0, sizeof iret); if (call (conn, priv, in_open, REMOTE_PROC_AUTH_SASL_INIT, (xdrproc_t) xdr_void, (char *)NULL, - (xdrproc_t) xdr_remote_auth_sasl_init_ret, (char *) &iret) != 0) { - sasl_dispose(&saslconn); - return -1; /* virError already set by call */ + (xdrproc_t) xdr_remote_auth_sasl_init_ret, (char *) &iret) != 0) + goto cleanup; + + + mechlist = iret.mechlist; + if (wantmech) { + if (strstr(mechlist, wantmech) == NULL) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "SASL mechanism %s not supported by server", wantmech); + free(iret.mechlist); + goto cleanup; + } + mechlist = wantmech; } - - + restart: /* Start the auth negotiation on the client end first */ - remoteDebug(priv, "Client start negotiation mechlist '%s'", iret.mechlist); + remoteDebug(priv, "Client start negotiation mechlist '%s'", mechlist); err = sasl_client_start(saslconn, - iret.mechlist, - NULL, /* XXX interactions */ + mechlist, + &interact, &clientout, &clientoutlen, &mech); - if (err != SASL_OK && err != SASL_CONTINUE) { + if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) { __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "Failed to start SASL negotiation: %d (%s)", err, sasl_errdetail(saslconn)); free(iret.mechlist); - sasl_dispose(&saslconn); - return -1; + goto cleanup; + } + + /* Need to gather some credentials from the client */ + if (err == SASL_INTERACT) { + if (cred) { + remoteAuthFreeCredentials(cred, ncred); + cred = NULL; + } + if ((ncred = + remoteAuthMakeCredentials(interact, &cred)) < 0) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to make auth credentials"); + free(iret.mechlist); + goto cleanup; + } + /* Run the authentication callback */ + if ((*(auth->cb))(cred, ncred, auth->cbdata) < 0) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to collect auth credentials"); + goto cleanup; + return -1; + } + remoteAuthFillInteract(cred, interact); + goto restart; } free(iret.mechlist); @@ -3087,8 +3326,7 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "SASL negotiation data too long: %d bytes", clientoutlen); - sasl_dispose(&saslconn); - return -1; + goto cleanup; } /* NB, distinction of NULL vs "" is *critical* in SASL */ memset(&sargs, 0, sizeof sargs); @@ -3102,10 +3340,8 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) memset (&sret, 0, sizeof sret); if (call (conn, priv, in_open, REMOTE_PROC_AUTH_SASL_START, (xdrproc_t) xdr_remote_auth_sasl_start_args, (char *) &sargs, - (xdrproc_t) xdr_remote_auth_sasl_start_ret, (char *) &sret) != 0) { - sasl_dispose(&saslconn); - return -1; /* virError already set by call */ - } + (xdrproc_t) xdr_remote_auth_sasl_start_ret, (char *) &sret) != 0) + goto cleanup; complete = sret.complete; /* NB, distinction of NULL vs "" is *critical* in SASL */ @@ -3118,20 +3354,48 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) * Even if the server has completed, the client must *always* do at least one step * in this loop to verify the server isn't lieing about something. Mutual auth */ for (;;) { + restep: err = sasl_client_step(saslconn, serverin, serverinlen, - NULL, /* XXX interactions */ + &interact, &clientout, &clientoutlen); - if (serverin) free(serverin); - if (err != SASL_OK && err != SASL_CONTINUE) { + if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) { __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "Failed SASL step: %d (%s)", err, sasl_errdetail(saslconn)); - sasl_dispose(&saslconn); - return -1; + goto cleanup; + } + /* Need to gather some credentials from the client */ + if (err == SASL_INTERACT) { + if (cred) { + remoteAuthFreeCredentials(cred, ncred); + cred = NULL; + } + if ((ncred = remoteAuthMakeCredentials(interact, &cred)) < 0) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to make auth credentials"); + goto cleanup; + return -1; + } + /* Run the authentication callback */ + if ((*(auth->cb))(cred, ncred, auth->cbdata) < 0) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to collect auth credentials"); + goto cleanup; + return -1; + } + remoteAuthFillInteract(cred, interact); + goto restep; + } + + if (serverin) { + free(serverin); + serverin = NULL; } remoteDebug(priv, "Client step result %d. Data %d bytes %p", err, clientoutlen, clientout); @@ -3150,10 +3414,8 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) memset (&pret, 0, sizeof pret); if (call (conn, priv, in_open, REMOTE_PROC_AUTH_SASL_STEP, (xdrproc_t) xdr_remote_auth_sasl_step_args, (char *) &pargs, - (xdrproc_t) xdr_remote_auth_sasl_step_ret, (char *) &pret) != 0) { - sasl_dispose(&saslconn); - return -1; /* virError already set by call */ - } + (xdrproc_t) xdr_remote_auth_sasl_step_ret, (char *) &pret) != 0) + goto cleanup; complete = pret.complete; /* NB, distinction of NULL vs "" is *critical* in SASL */ @@ -3178,8 +3440,7 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "cannot query SASL ssf on connection %d (%s)", err, sasl_errstring(err, NULL, NULL)); - sasl_dispose(&saslconn); - return -1; + goto cleanup; } ssf = *(const int *)val; remoteDebug(priv, "SASL SSF value %d", ssf); @@ -3187,17 +3448,66 @@ remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "negotiation SSF %d was not strong enough", ssf); - sasl_dispose(&saslconn); - return -1; + goto cleanup; } } remoteDebug(priv, "SASL authentication complete"); priv->saslconn = saslconn; - return 0; + ret = 0; + + cleanup: + if (localAddr) free(localAddr); + if (remoteAddr) free(remoteAddr); + if (serverin) free(serverin); + + free(saslcb); + remoteAuthFreeCredentials(cred, ncred); + if (ret != 0 && saslconn) + sasl_dispose(&saslconn); + return ret; } #endif /* HAVE_SASL */ + +#if HAVE_POLKIT +/* Perform the PolicyKit authentication process + */ +static int +remoteAuthPolkit (virConnectPtr conn, struct private_data *priv, int in_open, + virConnectAuthPtr auth) +{ + remote_auth_polkit_ret ret; + virConnectCredential cred = { + VIR_CRED_EXTERNAL, + conn->flags & VIR_CONNECT_RO ? "org.libvirt.unix.monitor" : "org.libvirt.unix.manage", + "PolicyKit", + NULL, + NULL, + 0, + }; + remoteDebug(priv, "Client initialize PolicyKit authentication"); + + /* Run the authentication callback */ + if (auth && auth->cb && (*(auth->cb))(&cred, 1, auth->cbdata) < 0) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to collect auth credentials"); + return -1; + } + + memset (&ret, 0, sizeof ret); + if (call (conn, priv, in_open, REMOTE_PROC_AUTH_POLKIT, + (xdrproc_t) xdr_void, (char *)NULL, + (xdrproc_t) xdr_remote_auth_polkit_ret, (char *) &ret) != 0) { + return -1; /* virError already set by call */ + } + + remoteDebug(priv, "PolicyKit authentication complete"); + return 0; +} +#endif /* HAVE_POLKIT */ + /*----------------------------------------------------------------------*/ static int really_write (virConnectPtr conn, struct private_data *priv, @@ -3462,29 +3772,6 @@ really_write_sasl (virConnectPtr conn, struct private_data *priv, } #endif - -#if HAVE_POLKIT -/* Perform the PolicyKit authentication process - */ -static int -remoteAuthPolkit (virConnectPtr conn, struct private_data *priv, int in_open) -{ - remote_auth_polkit_ret ret; - - remoteDebug(priv, "Client initialize PolicyKit authentication"); - - memset (&ret, 0, sizeof ret); - if (call (conn, priv, in_open, REMOTE_PROC_AUTH_POLKIT, - (xdrproc_t) xdr_void, (char *)NULL, - (xdrproc_t) xdr_remote_auth_polkit_ret, (char *) &ret) != 0) { - return -1; /* virError already set by call */ - } - - remoteDebug(priv, "PolicyKit authentication complete"); - return 0; -} -#endif /* HAVE_POLKIT */ - static int really_write (virConnectPtr conn, struct private_data *priv, int in_open /* if we are in virConnectOpen */, diff --git a/src/virsh.c b/src/virsh.c index c6fc5deefd..d67ba7c068 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -4520,10 +4520,10 @@ vshInit(vshControl * ctl) !strcasecmp(ctl->name, "xen")) && ctl->uid != 0) ctl->readonly = 1; - if (!ctl->readonly) - ctl->conn = virConnectOpen(ctl->name); - else - ctl->conn = virConnectOpenReadOnly(ctl->name); + ctl->conn = virConnectOpenAuth(ctl->name, + virConnectAuthPtrDefault, + ctl->readonly ? VIR_CONNECT_RO : 0); + /* This is not necessarily fatal. All the individual commands check * vshConnectionUsability, except ones which don't need a connection