ms-nfs41-client/daemon/nfs41_rpc.c
Olga Kornievskaia d6967ea9ef reestablishing gss context on rpc_reconnect
if we receive an rpc_autherr, just recreate a new rpc client as well
as the rpc auth structure. as it ties into the recovery and handles
that only one thread recovers and reestablishes the rpc auth context.

in theory, reestablishing rpc client does not necessitate new rpc auth
context. we really need to restablish one only if we get rpc autherr. however,
it simplifies the code not to introduce a different synchronization
mechanism for rpc auth in addition to the rpc one.
2011-05-16 18:32:56 -04:00

393 lines
12 KiB
C

/* Copyright (c) 2010
* The Regents of the University of Michigan
* All Rights Reserved
*
* Permission is granted to use, copy and redistribute this software
* for noncommercial education and research purposes, so long as no
* fee is charged, and so long as the name of the University of Michigan
* is not used in any advertising or publicity pertaining to the use
* or distribution of this software without specific, written prior
* authorization. Permission to modify or otherwise create derivative
* works of this software is not granted.
*
* This software is provided as is, without representation or warranty
* of any kind either express or implied, including without limitation
* the implied warranties of merchantability, fitness for a particular
* purpose, or noninfringement. The Regents of the University of
* Michigan shall not be liable for any damages, including special,
* indirect, incidental, or consequential damages, with respect to any
* claim arising out of or in connection with the use of the software,
* even if it has been or is hereafter advised of the possibility of
* such damages.
*/
#include "nfs41_ops.h"
#include "daemon_debug.h"
#include "nfs41_xdr.h"
#include "nfs41_callback.h"
#include "nfs41_driver.h" /* for AUTH_SYS, AUTHGSS_KRB5s defines */
#include "rpc/rpc.h"
#define SECURITY_WIN32
#include <security.h>
#include "rpc/auth_sspi.h"
static enum clnt_stat send_null(CLIENT *client)
{
struct timeval timeout = {10, 0};
return clnt_call(client, 0,
(xdrproc_t)xdr_void, NULL,
(xdrproc_t)xdr_void, NULL, timeout);
}
static int get_client_for_netaddr(
IN const netaddr4 *netaddr,
IN uint32_t wsize,
IN uint32_t rsize,
IN nfs41_rpc_clnt *rpc,
OUT OPTIONAL char *server_name,
OUT CLIENT **client_out)
{
int status = ERROR_NETWORK_UNREACHABLE;
struct netconfig *nconf;
struct netbuf *addr;
CLIENT *client;
nconf = getnetconfigent(netaddr->netid);
if (nconf == NULL)
goto out;
addr = uaddr2taddr(nconf, netaddr->uaddr);
if (addr == NULL)
goto out_free_conf;
if (server_name) {
getnameinfo(addr->buf, addr->len, server_name, NI_MAXHOST, NULL, 0, 0);
dprintf(1, "servername is %s\n", server_name);
}
dprintf(1, "callback function %p args %p\n", nfs41_handle_callback, rpc);
client = clnt_tli_create(RPC_ANYFD, nconf, addr,
NFS41_RPC_PROGRAM, NFS41_RPC_VERSION, wsize, rsize,
rpc?proc_cb_compound_res:NULL, rpc?nfs41_handle_callback:NULL, rpc?rpc:NULL);
if (client) {
*client_out = client;
status = NO_ERROR;
goto out_free_addr;
}
out_free_addr:
freenetbuf(addr);
out_free_conf:
freenetconfigent(nconf);
out:
return status;
}
static int get_client_for_multi_addr(
IN const multi_addr4 *addrs,
IN uint32_t wsize,
IN uint32_t rsize,
IN nfs41_rpc_clnt *rpc,
OUT OPTIONAL char *server_name,
OUT CLIENT **client_out,
OUT uint32_t *addr_index)
{
int status = ERROR_NETWORK_UNREACHABLE;
uint32_t i;
for (i = 0; i < addrs->count; i++) {
status = get_client_for_netaddr(&addrs->arr[i],
wsize, rsize, rpc, server_name, client_out);
if (status == NO_ERROR) {
*addr_index = i;
break;
}
}
return status;
}
int create_rpcsec_auth_client(
IN uint32_t sec_flavor,
IN char *server_name,
CLIENT *client
)
{
int status = ERROR_NETWORK_UNREACHABLE;
switch (sec_flavor) {
case RPCSEC_AUTHGSS_KRB5:
client->cl_auth = authsspi_create_default(client, server_name,
RPCSEC_SSPI_SVC_NONE);
break;
case RPCSEC_AUTHGSS_KRB5I:
client->cl_auth = authsspi_create_default(client, server_name,
RPCSEC_SSPI_SVC_INTEGRITY);
break;
case RPCSEC_AUTHGSS_KRB5P:
client->cl_auth = authsspi_create_default(client, server_name,
RPCSEC_SSPI_SVC_PRIVACY);
break;
default:
eprintf("create_rpc_auth_client: unknown rpcsec flavor %d\n",
sec_flavor);
client->cl_auth = NULL;
}
if (client->cl_auth == NULL) {
eprintf("nfs41_rpc_clnt_create: failed to create %s\n",
secflavorop2name(sec_flavor));
goto out;
} else
dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n",
secflavorop2name(sec_flavor));
status = 0;
out:
return status;
}
/* Returns a client structure and an associated lock */
int nfs41_rpc_clnt_create(
IN const multi_addr4 *addrs,
IN uint32_t wsize,
IN uint32_t rsize,
bool_t needcb,
IN uint32_t uid,
IN uint32_t gid,
IN uint32_t sec_flavor,
OUT nfs41_rpc_clnt **rpc_out)
{
CLIENT *client;
nfs41_rpc_clnt *rpc;
uint32_t addr_index;
int status;
char machname[MAXHOSTNAMELEN + 1];
gid_t gids[1];
rpc = calloc(1, sizeof(nfs41_rpc_clnt));
if (rpc == NULL) {
status = GetLastError();
goto out;
}
#ifdef NO_CB_4_KRB5P
if (sec_flavor == RPCSEC_AUTHGSS_KRB5P)
needcb = 0;
#endif
rpc->needcb = needcb;
rpc->cond = CreateEvent(NULL, TRUE, FALSE, "rpc_recovery_cond");
if (rpc->cond == NULL) {
status = GetLastError();
eprintf("CreateEvent failed %d\n", status);
goto out_free_rpc_clnt;
}
status = get_client_for_multi_addr(addrs, wsize, rsize, needcb?rpc:NULL,
rpc->server_name, &client, &addr_index);
if (status) {
clnt_pcreateerror("connecting failed");
goto out_free_rpc_cond;
}
if (send_null(client) != RPC_SUCCESS) {
// XXX Do what here?
eprintf("nfs41_rpc_clnt_create: send_null failed\n");
status = ERROR_NETWORK_UNREACHABLE;
goto out_err_client;
}
rpc->sec_flavor = sec_flavor;
if (sec_flavor == RPCSEC_AUTH_SYS) {
if (gethostname(machname, sizeof(machname)) == -1) {
eprintf("nfs41_rpc_clnt_create: gethostname failed\n");
goto out_err_client;
}
machname[sizeof(machname) - 1] = '\0';
client->cl_auth = authsys_create(machname, uid, gid, 0, gids);
} else {
status = create_rpcsec_auth_client(sec_flavor, rpc->server_name, client);
if (status) {
eprintf("nfs41_rpc_clnt_create: failed to establish security "
"context with %s\n", rpc->server_name);
status = ERROR_NETWORK_UNREACHABLE;
goto out_err_client;
} else
dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n",
secflavorop2name(sec_flavor));
}
rpc->rpc = client;
/* keep a copy of the address and buffer sizes for reconnect */
memcpy(&rpc->addrs, addrs, sizeof(multi_addr4));
/* save the index of the address we connected to */
rpc->addr_index = addr_index;
rpc->wsize = wsize;
rpc->rsize = rsize;
rpc->is_valid_session = TRUE;
//initialize rpc client lock
InitializeSRWLock(&rpc->lock);
*rpc_out = rpc;
out:
return status;
out_err_client:
clnt_destroy(client);
out_free_rpc_cond:
CloseHandle(rpc->cond);
out_free_rpc_clnt:
free(rpc);
goto out;
}
/* Frees resources allocated in clnt_create */
void nfs41_rpc_clnt_free(
IN nfs41_rpc_clnt *rpc)
{
auth_destroy(rpc->rpc->cl_auth);
clnt_destroy(rpc->rpc);
CloseHandle(rpc->cond);
free(rpc);
}
static bool_t rpc_renew_in_progress(nfs41_rpc_clnt *rpc, int *value)
{
bool_t status = FALSE;
AcquireSRWLockExclusive(&rpc->lock);
if (value) {
dprintf(1, "nfs41_rpc_renew_in_progress: setting value %d\n", *value);
rpc->in_recovery = *value;
if (!rpc->in_recovery)
SetEvent(rpc->cond);
} else {
status = rpc->in_recovery;
dprintf(1, "nfs41_rpc_renew_in_progress: returning value %d\n", status);
}
ReleaseSRWLockExclusive(&rpc->lock);
return status;
}
static bool_t rpc_should_retry(nfs41_rpc_clnt *rpc, uint32_t version)
{
bool_t status = 0;
AcquireSRWLockExclusive(&rpc->lock);
if (rpc->version > version)
status = 1;
ReleaseSRWLockExclusive(&rpc->lock);
return status;
}
static int rpc_reconnect(
IN nfs41_rpc_clnt *rpc)
{
CLIENT *client = NULL;
uint32_t addr_index;
int status;
AcquireSRWLockExclusive(&rpc->lock);
status = get_client_for_multi_addr(&rpc->addrs, rpc->wsize, rpc->rsize,
rpc, NULL, &client, &addr_index);
if (status)
goto out_unlock;
if(rpc->sec_flavor == RPCSEC_AUTH_SYS)
client->cl_auth = rpc->rpc->cl_auth;
else {
auth_destroy(rpc->rpc->cl_auth);
status = create_rpcsec_auth_client(rpc->sec_flavor, rpc->server_name, client);
if (status) {
eprintf("Failed to reestablish security context\n");
status = ERROR_NETWORK_UNREACHABLE;
goto out_err_client;
}
}
if (send_null(client) != RPC_SUCCESS) {
eprintf("rpc_reconnect: send_null failed\n");
status = ERROR_NETWORK_UNREACHABLE;
goto out_err_client;
}
clnt_destroy(rpc->rpc);
rpc->rpc = client;
rpc->addr_index = addr_index;
rpc->version++;
dprintf(1, "nfs41_send_compound: reestablished RPC connection\n");
out_unlock:
ReleaseSRWLockExclusive(&rpc->lock);
/* after releasing the rpc lock, send a BIND_CONN_TO_SESSION if
* we need to associate the connection with the backchannel */
if (status == NO_ERROR && rpc->needcb &&
rpc->client && rpc->client->session) {
status = nfs41_bind_conn_to_session(rpc,
rpc->client->session->session_id, CDFC4_BACK_OR_BOTH);
if (status)
eprintf("nfs41_bind_conn_to_session() failed with %s\n",
nfs_error_string(status));
status = NFS4_OK;
}
return status;
out_err_client:
clnt_destroy(client);
goto out_unlock;
}
int nfs41_send_compound(
IN nfs41_rpc_clnt *rpc,
IN char *inbuf,
OUT char *outbuf)
{
struct timeval timeout = {90, 0};
enum clnt_stat rpc_status;
int status, count = 0, one = 1, zero = 0;
uint32_t version;
try_again:
AcquireSRWLockShared(&rpc->lock);
version = rpc->version;
rpc_status = clnt_call(rpc->rpc, 1,
(xdrproc_t)nfs_encode_compound, inbuf,
(xdrproc_t)nfs_decode_compound, outbuf,
timeout);
ReleaseSRWLockShared(&rpc->lock);
if (rpc_status != RPC_SUCCESS) {
eprintf("clnt_call returned rpc_status = %s\n",
rpc_error_string(rpc_status));
switch(rpc_status) {
case RPC_CANTRECV:
case RPC_CANTSEND:
case RPC_TIMEDOUT:
case RPC_AUTHERROR:
if (!rpc->is_valid_session && ++count > 3) {
status = ERROR_NETWORK_UNREACHABLE;
break;
}
if (rpc_should_retry(rpc, version))
goto try_again;
while (rpc_renew_in_progress(rpc, NULL)) {
status = WaitForSingleObject(rpc->cond, INFINITE);
if (status != WAIT_OBJECT_0) {
dprintf(1, "rpc_renew_in_progress: WaitForSingleObject failed\n");
print_condwait_status(1, status);
status = ERROR_LOCK_VIOLATION;
goto out;
}
rpc_renew_in_progress(rpc, &zero);
goto try_again;
}
rpc_renew_in_progress(rpc, &one);
if (rpc_reconnect(rpc))
eprintf("rpc_reconnect: Failed to reconnect!\n");
rpc_renew_in_progress(rpc, &zero);
goto try_again;
default:
eprintf("UNHANDLED RPC_ERROR: %d\n", rpc_status);
status = ERROR_NETWORK_UNREACHABLE;
goto out;
}
goto out;
}
status = 0;
out:
return status;
}