summaryrefslogtreecommitdiffstats
path: root/sys/rpc
diff options
context:
space:
mode:
authordfr <dfr@FreeBSD.org>2008-11-03 10:38:00 +0000
committerdfr <dfr@FreeBSD.org>2008-11-03 10:38:00 +0000
commit2fb03513fc4b5d35a398f1ceb4b439fe4bb5fb74 (patch)
treec59f88924c0b3ead68523ce14806894836f8d9a7 /sys/rpc
parent8b86595849b35ac7c26977f1b8206c1678c9b5bb (diff)
downloadFreeBSD-src-2fb03513fc4b5d35a398f1ceb4b439fe4bb5fb74.zip
FreeBSD-src-2fb03513fc4b5d35a398f1ceb4b439fe4bb5fb74.tar.gz
Implement support for RPCSEC_GSS authentication to both the NFS client
and server. This replaces the RPC implementation of the NFS client and server with the newer RPC implementation originally developed (actually ported from the userland sunrpc code) to support the NFS Lock Manager. I have tested this code extensively and I believe it is stable and that performance is at least equal to the legacy RPC implementation. The NFS code currently contains support for both the new RPC implementation and the older legacy implementation inherited from the original NFS codebase. The default is to use the new implementation - add the NFS_LEGACYRPC option to fall back to the old code. When I merge this support back to RELENG_7, I will probably change this so that users have to 'opt in' to get the new code. To use RPCSEC_GSS on either client or server, you must build a kernel which includes the KGSSAPI option and the crypto device. On the userland side, you must build at least a new libc, mountd, mount_nfs and gssd. You must install new versions of /etc/rc.d/gssd and /etc/rc.d/nfsd and add 'gssd_enable=YES' to /etc/rc.conf. As long as gssd is running, you should be able to mount an NFS filesystem from a server that requires RPCSEC_GSS authentication. The mount itself can happen without any kerberos credentials but all access to the filesystem will be denied unless the accessing user has a valid ticket file in the standard place (/tmp/krb5cc_<uid>). There is currently no support for situations where the ticket file is in a different place, such as when the user logged in via SSH and has delegated credentials from that login. This restriction is also present in Solaris and Linux. In theory, we could improve this in future, possibly using Brooks Davis' implementation of variant symlinks. Supporting RPCSEC_GSS on a server is nearly as simple. You must create service creds for the server in the form 'nfs/<fqdn>@<REALM>' and install them in /etc/krb5.keytab. The standard heimdal utility ktutil makes this fairly easy. After the service creds have been created, you can add a '-sec=krb5' option to /etc/exports and restart both mountd and nfsd. The only other difference an administrator should notice is that nfsd doesn't fork to create service threads any more. In normal operation, there will be two nfsd processes, one in userland waiting for TCP connections and one in the kernel handling requests. The latter process will create as many kthreads as required - these should be visible via 'top -H'. The code has some support for varying the number of service threads according to load but initially at least, nfsd uses a fixed number of threads according to the value supplied to its '-n' option. Sponsored by: Isilon Systems MFC after: 1 month
Diffstat (limited to 'sys/rpc')
-rw-r--r--sys/rpc/auth.h45
-rw-r--r--sys/rpc/auth_none.c20
-rw-r--r--sys/rpc/auth_unix.c34
-rw-r--r--sys/rpc/clnt.h70
-rw-r--r--sys/rpc/clnt_dg.c310
-rw-r--r--sys/rpc/clnt_rc.c126
-rw-r--r--sys/rpc/clnt_vc.c239
-rw-r--r--sys/rpc/replay.c248
-rw-r--r--sys/rpc/replay.h85
-rw-r--r--sys/rpc/rpc_com.h1
-rw-r--r--sys/rpc/rpc_generic.c134
-rw-r--r--sys/rpc/rpc_msg.h2
-rw-r--r--sys/rpc/rpc_prot.c81
-rw-r--r--sys/rpc/rpcsec_gss.h189
-rw-r--r--sys/rpc/rpcsec_gss/rpcsec_gss.c1064
-rw-r--r--sys/rpc/rpcsec_gss/rpcsec_gss_conf.c163
-rw-r--r--sys/rpc/rpcsec_gss/rpcsec_gss_int.h94
-rw-r--r--sys/rpc/rpcsec_gss/rpcsec_gss_misc.c53
-rw-r--r--sys/rpc/rpcsec_gss/rpcsec_gss_prot.c359
-rw-r--r--sys/rpc/rpcsec_gss/svc_rpcsec_gss.c1485
-rw-r--r--sys/rpc/svc.c1048
-rw-r--r--sys/rpc/svc.h244
-rw-r--r--sys/rpc/svc_auth.c75
-rw-r--r--sys/rpc/svc_auth.h24
-rw-r--r--sys/rpc/svc_auth_unix.c3
-rw-r--r--sys/rpc/svc_dg.c157
-rw-r--r--sys/rpc/svc_generic.c93
-rw-r--r--sys/rpc/svc_vc.c247
-rw-r--r--sys/rpc/xdr.h2
29 files changed, 5957 insertions, 738 deletions
diff --git a/sys/rpc/auth.h b/sys/rpc/auth.h
index b919559..6be08b6 100644
--- a/sys/rpc/auth.h
+++ b/sys/rpc/auth.h
@@ -132,7 +132,7 @@ enum auth_stat {
* failed locally
*/
AUTH_INVALIDRESP=6, /* bogus response verifier */
- AUTH_FAILED=7 /* some unknown reason */
+ AUTH_FAILED=7, /* some unknown reason */
#ifdef KERBEROS
/*
* kerberos errors
@@ -142,8 +142,14 @@ enum auth_stat {
AUTH_TIMEEXPIRE = 9, /* time of credential expired */
AUTH_TKT_FILE = 10, /* something wrong with ticket file */
AUTH_DECODE = 11, /* can't decode authenticator */
- AUTH_NET_ADDR = 12 /* wrong net address in ticket */
+ AUTH_NET_ADDR = 12, /* wrong net address in ticket */
#endif /* KERBEROS */
+ /*
+ * RPCSEC_GSS errors
+ */
+ RPCSEC_GSS_CREDPROBLEM = 13,
+ RPCSEC_GSS_CTXPROBLEM = 14,
+ RPCSEC_GSS_NODISPATCH = 0x8000000
};
union des_block {
@@ -171,6 +177,7 @@ struct opaque_auth {
/*
* Auth handle, interface to client side authenticators.
*/
+struct rpc_err;
typedef struct __auth {
struct opaque_auth ah_cred;
struct opaque_auth ah_verf;
@@ -178,10 +185,11 @@ typedef struct __auth {
struct auth_ops {
void (*ah_nextverf) (struct __auth *);
/* nextverf & serialize */
- int (*ah_marshal) (struct __auth *, XDR *);
+ int (*ah_marshal) (struct __auth *, uint32_t, XDR *,
+ struct mbuf *);
/* validate verifier */
- int (*ah_validate) (struct __auth *,
- struct opaque_auth *);
+ int (*ah_validate) (struct __auth *, uint32_t,
+ struct opaque_auth *, struct mbuf **);
/* refresh credentials */
int (*ah_refresh) (struct __auth *, void *);
/* destroy this structure */
@@ -201,29 +209,18 @@ typedef struct __auth {
*/
#define AUTH_NEXTVERF(auth) \
((*((auth)->ah_ops->ah_nextverf))(auth))
-#define auth_nextverf(auth) \
- ((*((auth)->ah_ops->ah_nextverf))(auth))
-#define AUTH_MARSHALL(auth, xdrs) \
- ((*((auth)->ah_ops->ah_marshal))(auth, xdrs))
-#define auth_marshall(auth, xdrs) \
- ((*((auth)->ah_ops->ah_marshal))(auth, xdrs))
+#define AUTH_MARSHALL(auth, xid, xdrs, args) \
+ ((*((auth)->ah_ops->ah_marshal))(auth, xid, xdrs, args))
-#define AUTH_VALIDATE(auth, verfp) \
- ((*((auth)->ah_ops->ah_validate))((auth), verfp))
-#define auth_validate(auth, verfp) \
- ((*((auth)->ah_ops->ah_validate))((auth), verfp))
+#define AUTH_VALIDATE(auth, xid, verfp, resultsp) \
+ ((*((auth)->ah_ops->ah_validate))((auth), xid, verfp, resultsp))
#define AUTH_REFRESH(auth, msg) \
((*((auth)->ah_ops->ah_refresh))(auth, msg))
-#define auth_refresh(auth, msg) \
- ((*((auth)->ah_ops->ah_refresh))(auth, msg))
#define AUTH_DESTROY(auth) \
((*((auth)->ah_ops->ah_destroy))(auth))
-#define auth_destroy(auth) \
- ((*((auth)->ah_ops->ah_destroy))(auth))
-
__BEGIN_DECLS
extern struct opaque_auth _null_auth;
@@ -357,5 +354,13 @@ __END_DECLS
#define AUTH_DH 3 /* for Diffie-Hellman mechanism */
#define AUTH_DES AUTH_DH /* for backward compatibility */
#define AUTH_KERB 4 /* kerberos style */
+#define RPCSEC_GSS 6 /* RPCSEC_GSS */
+
+/*
+ * Pseudo auth flavors for RPCSEC_GSS.
+ */
+#define RPCSEC_GSS_KRB5 390003
+#define RPCSEC_GSS_KRB5I 390004
+#define RPCSEC_GSS_KRB5P 390005
#endif /* !_RPC_AUTH_H */
diff --git a/sys/rpc/auth_none.c b/sys/rpc/auth_none.c
index 8530437..a256b83 100644
--- a/sys/rpc/auth_none.c
+++ b/sys/rpc/auth_none.c
@@ -54,6 +54,7 @@ __FBSDID("$FreeBSD$");
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
+#include <rpc/clnt.h>
#define MAX_MARSHAL_SIZE 20
@@ -61,9 +62,10 @@ __FBSDID("$FreeBSD$");
* Authenticator operations routines
*/
-static bool_t authnone_marshal (AUTH *, XDR *);
+static bool_t authnone_marshal (AUTH *, uint32_t, XDR *, struct mbuf *);
static void authnone_verf (AUTH *);
-static bool_t authnone_validate (AUTH *, struct opaque_auth *);
+static bool_t authnone_validate (AUTH *, uint32_t, struct opaque_auth *,
+ struct mbuf **);
static bool_t authnone_refresh (AUTH *, void *);
static void authnone_destroy (AUTH *);
@@ -72,7 +74,7 @@ static struct auth_ops authnone_ops = {
.ah_marshal = authnone_marshal,
.ah_validate = authnone_validate,
.ah_refresh = authnone_refresh,
- .ah_destroy = authnone_destroy
+ .ah_destroy = authnone_destroy,
};
struct authnone_private {
@@ -109,13 +111,18 @@ authnone_create()
/*ARGSUSED*/
static bool_t
-authnone_marshal(AUTH *client, XDR *xdrs)
+authnone_marshal(AUTH *client, uint32_t xid, XDR *xdrs, struct mbuf *args)
{
struct authnone_private *ap = &authnone_private;
KASSERT(xdrs != NULL, ("authnone_marshal: xdrs is null"));
- return (xdrs->x_ops->x_putbytes(xdrs, ap->mclient, ap->mcnt));
+ if (!XDR_PUTBYTES(xdrs, ap->mclient, ap->mcnt))
+ return (FALSE);
+
+ xdrmbuf_append(xdrs, args);
+
+ return (TRUE);
}
/* All these unused parameters are required to keep ANSI-C from grumbling */
@@ -127,7 +134,8 @@ authnone_verf(AUTH *client)
/*ARGSUSED*/
static bool_t
-authnone_validate(AUTH *client, struct opaque_auth *opaque)
+authnone_validate(AUTH *client, uint32_t xid, struct opaque_auth *opaque,
+ struct mbuf **mrepp)
{
return (TRUE);
diff --git a/sys/rpc/auth_unix.c b/sys/rpc/auth_unix.c
index e30e59e..bd4be34 100644
--- a/sys/rpc/auth_unix.c
+++ b/sys/rpc/auth_unix.c
@@ -62,13 +62,15 @@ __FBSDID("$FreeBSD$");
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
+#include <rpc/clnt.h>
#include <rpc/rpc_com.h>
/* auth_unix.c */
static void authunix_nextverf (AUTH *);
-static bool_t authunix_marshal (AUTH *, XDR *);
-static bool_t authunix_validate (AUTH *, struct opaque_auth *);
+static bool_t authunix_marshal (AUTH *, uint32_t, XDR *, struct mbuf *);
+static bool_t authunix_validate (AUTH *, uint32_t, struct opaque_auth *,
+ struct mbuf **);
static bool_t authunix_refresh (AUTH *, void *);
static void authunix_destroy (AUTH *);
static void marshal_new_auth (AUTH *);
@@ -78,7 +80,7 @@ static struct auth_ops authunix_ops = {
.ah_marshal = authunix_marshal,
.ah_validate = authunix_validate,
.ah_refresh = authunix_refresh,
- .ah_destroy = authunix_destroy
+ .ah_destroy = authunix_destroy,
};
/*
@@ -246,23 +248,32 @@ authunix_nextverf(AUTH *auth)
}
static bool_t
-authunix_marshal(AUTH *auth, XDR *xdrs)
+authunix_marshal(AUTH *auth, uint32_t xid, XDR *xdrs, struct mbuf *args)
{
struct audata *au;
au = AUTH_PRIVATE(auth);
- return (XDR_PUTBYTES(xdrs, au->au_marshed, au->au_mpos));
+ if (!XDR_PUTBYTES(xdrs, au->au_marshed, au->au_mpos))
+ return (FALSE);
+
+ xdrmbuf_append(xdrs, args);
+
+ return (TRUE);
}
static bool_t
-authunix_validate(AUTH *auth, struct opaque_auth *verf)
+authunix_validate(AUTH *auth, uint32_t xid, struct opaque_auth *verf,
+ struct mbuf **mrepp)
{
struct audata *au;
- XDR xdrs;
+ XDR txdrs;
+
+ if (!verf)
+ return (TRUE);
if (verf->oa_flavor == AUTH_SHORT) {
au = AUTH_PRIVATE(auth);
- xdrmem_create(&xdrs, verf->oa_base, verf->oa_length,
+ xdrmem_create(&txdrs, verf->oa_base, verf->oa_length,
XDR_DECODE);
if (au->au_shcred.oa_base != NULL) {
@@ -270,16 +281,17 @@ authunix_validate(AUTH *auth, struct opaque_auth *verf)
au->au_shcred.oa_length);
au->au_shcred.oa_base = NULL;
}
- if (xdr_opaque_auth(&xdrs, &au->au_shcred)) {
+ if (xdr_opaque_auth(&txdrs, &au->au_shcred)) {
auth->ah_cred = au->au_shcred;
} else {
- xdrs.x_op = XDR_FREE;
- (void)xdr_opaque_auth(&xdrs, &au->au_shcred);
+ txdrs.x_op = XDR_FREE;
+ (void)xdr_opaque_auth(&txdrs, &au->au_shcred);
au->au_shcred.oa_base = NULL;
auth->ah_cred = au->au_origcred;
}
marshal_new_auth(auth);
}
+
return (TRUE);
}
diff --git a/sys/rpc/clnt.h b/sys/rpc/clnt.h
index 03e3112..74d5813 100644
--- a/sys/rpc/clnt.h
+++ b/sys/rpc/clnt.h
@@ -118,6 +118,15 @@ struct rpc_err {
typedef void rpc_feedback(int cmd, int procnum, void *);
/*
+ * Timers used for the pseudo-transport protocol when using datagrams
+ */
+struct rpc_timers {
+ u_short rt_srtt; /* smoothed round-trip time */
+ u_short rt_deviate; /* estimated deviation */
+ u_long rt_rtxcur; /* current (backed-off) rto */
+};
+
+/*
* A structure used with CLNT_CALL_EXT to pass extra information used
* while processing an RPC call.
*/
@@ -125,6 +134,8 @@ struct rpc_callextra {
AUTH *rc_auth; /* auth handle to use for this call */
rpc_feedback *rc_feedback; /* callback for retransmits etc. */
void *rc_feedback_arg; /* argument for callback */
+ struct rpc_timers *rc_timers; /* optional RTT timers */
+ struct rpc_err rc_err; /* detailed call status */
};
#endif
@@ -140,8 +151,8 @@ typedef struct __rpc_client {
struct clnt_ops {
/* call remote procedure */
enum clnt_stat (*cl_call)(struct __rpc_client *,
- struct rpc_callextra *, rpcproc_t, xdrproc_t, void *,
- xdrproc_t, void *, struct timeval);
+ struct rpc_callextra *, rpcproc_t,
+ struct mbuf *, struct mbuf **, struct timeval);
/* abort a call */
void (*cl_abort)(struct __rpc_client *);
/* get specific error code */
@@ -150,6 +161,8 @@ typedef struct __rpc_client {
/* frees results */
bool_t (*cl_freeres)(struct __rpc_client *,
xdrproc_t, void *);
+ /* close the connection and terminate pending RPCs */
+ void (*cl_close)(struct __rpc_client *);
/* destroy this structure */
void (*cl_destroy)(struct __rpc_client *);
/* the ioctl() of rpc */
@@ -183,15 +196,6 @@ typedef struct __rpc_client {
char *cl_tp; /* device name */
} CLIENT;
-/*
- * Timers used for the pseudo-transport protocol when using datagrams
- */
-struct rpc_timers {
- u_short rt_srtt; /* smoothed round-trip time */
- u_short rt_deviate; /* estimated deviation */
- u_long rt_rtxcur; /* current (backed-off) rto */
-};
-
/*
* Feedback values used for possible congestion and rate control
*/
@@ -222,6 +226,32 @@ struct rpc_timers {
CLNT_DESTROY(rh)
/*
+ * void
+ * CLNT_CLOSE(rh);
+ * CLIENT *rh;
+ */
+#define CLNT_CLOSE(rh) ((*(rh)->cl_ops->cl_close)(rh))
+
+enum clnt_stat clnt_call_private(CLIENT *, struct rpc_callextra *, rpcproc_t,
+ xdrproc_t, void *, xdrproc_t, void *, struct timeval);
+
+/*
+ * enum clnt_stat
+ * CLNT_CALL_MBUF(rh, ext, proc, mreq, mrepp, timeout)
+ * CLIENT *rh;
+ * struct rpc_callextra *ext;
+ * rpcproc_t proc;
+ * struct mbuf *mreq;
+ * struct mbuf **mrepp;
+ * struct timeval timeout;
+ *
+ * Call arguments in mreq which is consumed by the call (even if there
+ * is an error). Results returned in *mrepp.
+ */
+#define CLNT_CALL_MBUF(rh, ext, proc, mreq, mrepp, secs) \
+ ((*(rh)->cl_ops->cl_call)(rh, ext, proc, mreq, mrepp, secs))
+
+/*
* enum clnt_stat
* CLNT_CALL_EXT(rh, ext, proc, xargs, argsp, xres, resp, timeout)
* CLIENT *rh;
@@ -234,8 +264,8 @@ struct rpc_timers {
* struct timeval timeout;
*/
#define CLNT_CALL_EXT(rh, ext, proc, xargs, argsp, xres, resp, secs) \
- ((*(rh)->cl_ops->cl_call)(rh, ext, proc, xargs, \
- argsp, xres, resp, secs))
+ clnt_call_private(rh, ext, proc, xargs, \
+ argsp, xres, resp, secs)
#endif
/*
@@ -250,12 +280,12 @@ struct rpc_timers {
* struct timeval timeout;
*/
#ifdef _KERNEL
-#define CLNT_CALL(rh, proc, xargs, argsp, xres, resp, secs) \
- ((*(rh)->cl_ops->cl_call)(rh, NULL, proc, xargs, \
- argsp, xres, resp, secs))
-#define clnt_call(rh, proc, xargs, argsp, xres, resp, secs) \
- ((*(rh)->cl_ops->cl_call)(rh, NULL, proc, xargs, \
- argsp, xres, resp, secs))
+#define CLNT_CALL(rh, proc, xargs, argsp, xres, resp, secs) \
+ clnt_call_private(rh, NULL, proc, xargs, \
+ argsp, xres, resp, secs)
+#define clnt_call(rh, proc, xargs, argsp, xres, resp, secs) \
+ clnt_call_private(rh, NULL, proc, xargs, \
+ argsp, xres, resp, secs)
#else
#define CLNT_CALL(rh, proc, xargs, argsp, xres, resp, secs) \
((*(rh)->cl_ops->cl_call)(rh, proc, xargs, \
@@ -340,6 +370,8 @@ struct rpc_timers {
#define CLGET_INTERRUPTIBLE 24 /* set interruptible flag */
#define CLSET_RETRIES 25 /* set retry count for reconnect */
#define CLGET_RETRIES 26 /* get retry count for reconnect */
+#define CLSET_PRIVPORT 27 /* set privileged source port flag */
+#define CLGET_PRIVPORT 28 /* get privileged source port flag */
#endif
diff --git a/sys/rpc/clnt_dg.c b/sys/rpc/clnt_dg.c
index f14e1d6..e6d101d 100644
--- a/sys/rpc/clnt_dg.c
+++ b/sys/rpc/clnt_dg.c
@@ -72,11 +72,12 @@ __FBSDID("$FreeBSD$");
static bool_t time_not_ok(struct timeval *);
static enum clnt_stat clnt_dg_call(CLIENT *, struct rpc_callextra *,
- rpcproc_t, xdrproc_t, void *, xdrproc_t, void *, struct timeval);
+ rpcproc_t, struct mbuf *, struct mbuf **, struct timeval);
static void clnt_dg_geterr(CLIENT *, struct rpc_err *);
static bool_t clnt_dg_freeres(CLIENT *, xdrproc_t, void *);
static void clnt_dg_abort(CLIENT *);
static bool_t clnt_dg_control(CLIENT *, u_int, void *);
+static void clnt_dg_close(CLIENT *);
static void clnt_dg_destroy(CLIENT *);
static void clnt_dg_soupcall(struct socket *so, void *arg, int waitflag);
@@ -85,6 +86,7 @@ static struct clnt_ops clnt_dg_ops = {
.cl_abort = clnt_dg_abort,
.cl_geterr = clnt_dg_geterr,
.cl_freeres = clnt_dg_freeres,
+ .cl_close = clnt_dg_close,
.cl_destroy = clnt_dg_destroy,
.cl_control = clnt_dg_control
};
@@ -102,6 +104,7 @@ struct cu_request {
uint32_t cr_xid; /* XID of request */
struct mbuf *cr_mrep; /* reply received by upcall */
int cr_error; /* any error from upcall */
+ char cr_verf[MAX_AUTH_BYTES]; /* reply verf */
};
TAILQ_HEAD(cu_request_list, cu_request);
@@ -120,7 +123,6 @@ struct cu_socket {
struct mtx cs_lock;
int cs_refs; /* Count of clients */
struct cu_request_list cs_pending; /* Requests awaiting replies */
-
};
/*
@@ -128,7 +130,8 @@ struct cu_socket {
*/
struct cu_data {
int cu_threads; /* # threads in clnt_vc_call */
- bool_t cu_closing; /* TRUE if we are destroying */
+ bool_t cu_closing; /* TRUE if we are closing */
+ bool_t cu_closed; /* TRUE if we are closed */
struct socket *cu_socket; /* connection socket */
bool_t cu_closeit; /* opened by library */
struct sockaddr_storage cu_raddr; /* remote address */
@@ -146,8 +149,14 @@ struct cu_data {
int cu_connected; /* Have done connect(). */
const char *cu_waitchan;
int cu_waitflag;
+ int cu_cwnd; /* congestion window */
+ int cu_sent; /* number of in-flight RPCs */
+ bool_t cu_cwnd_wait;
};
+#define CWNDSCALE 256
+#define MAXCWND (32 * CWNDSCALE)
+
/*
* Connection less client creation returns with client handle parameters.
* Default options are set, which the user can change using clnt_control().
@@ -211,6 +220,7 @@ clnt_dg_create(
cu = mem_alloc(sizeof (*cu));
cu->cu_threads = 0;
cu->cu_closing = FALSE;
+ cu->cu_closed = FALSE;
(void) memcpy(&cu->cu_raddr, svcaddr, (size_t)svcaddr->sa_len);
cu->cu_rlen = svcaddr->sa_len;
/* Other values can also be set through clnt_control() */
@@ -225,6 +235,9 @@ clnt_dg_create(
cu->cu_connected = FALSE;
cu->cu_waitchan = "rpcrecv";
cu->cu_waitflag = 0;
+ cu->cu_cwnd = MAXCWND / 2;
+ cu->cu_sent = 0;
+ cu->cu_cwnd_wait = FALSE;
(void) getmicrotime(&now);
cu->cu_xid = __RPC_GETXID(&now);
call_msg.rm_xid = cu->cu_xid;
@@ -304,15 +317,16 @@ clnt_dg_call(
CLIENT *cl, /* client handle */
struct rpc_callextra *ext, /* call metadata */
rpcproc_t proc, /* procedure number */
- xdrproc_t xargs, /* xdr routine for args */
- void *argsp, /* pointer to args */
- xdrproc_t xresults, /* xdr routine for results */
- void *resultsp, /* pointer to results */
+ struct mbuf *args, /* pointer to args */
+ struct mbuf **resultsp, /* pointer to results */
struct timeval utimeout) /* seconds to wait before giving up */
{
struct cu_data *cu = (struct cu_data *)cl->cl_private;
struct cu_socket *cs = (struct cu_socket *) cu->cu_socket->so_upcallarg;
+ struct rpc_timers *rt;
AUTH *auth;
+ struct rpc_err *errp;
+ enum clnt_stat stat;
XDR xdrs;
struct rpc_msg reply_msg;
bool_t ok;
@@ -321,11 +335,11 @@ clnt_dg_call(
struct timeval *tvp;
int timeout;
int retransmit_time;
- int next_sendtime, starttime, time_waited, tv;
+ int next_sendtime, starttime, rtt, time_waited, tv = 0;
struct sockaddr *sa;
socklen_t salen;
- uint32_t xid;
- struct mbuf *mreq = NULL;
+ uint32_t xid = 0;
+ struct mbuf *mreq = NULL, *results;
struct cu_request *cr;
int error;
@@ -333,17 +347,20 @@ clnt_dg_call(
mtx_lock(&cs->cs_lock);
- if (cu->cu_closing) {
+ if (cu->cu_closing || cu->cu_closed) {
mtx_unlock(&cs->cs_lock);
free(cr, M_RPC);
return (RPC_CANTSEND);
}
cu->cu_threads++;
- if (ext)
+ if (ext) {
auth = ext->rc_auth;
- else
+ errp = &ext->rc_err;
+ } else {
auth = cl->cl_auth;
+ errp = &cu->cu_error;
+ }
cr->cr_client = cl;
cr->cr_mrep = NULL;
@@ -365,8 +382,8 @@ clnt_dg_call(
(struct sockaddr *)&cu->cu_raddr, curthread);
mtx_lock(&cs->cs_lock);
if (error) {
- cu->cu_error.re_errno = error;
- cu->cu_error.re_status = RPC_CANTSEND;
+ errp->re_errno = error;
+ errp->re_status = stat = RPC_CANTSEND;
goto out;
}
cu->cu_connected = 1;
@@ -380,7 +397,15 @@ clnt_dg_call(
}
time_waited = 0;
retrans = 0;
- retransmit_time = next_sendtime = tvtohz(&cu->cu_wait);
+ if (ext && ext->rc_timers) {
+ rt = ext->rc_timers;
+ if (!rt->rt_rtxcur)
+ rt->rt_rtxcur = tvtohz(&cu->cu_wait);
+ retransmit_time = next_sendtime = rt->rt_rtxcur;
+ } else {
+ rt = NULL;
+ retransmit_time = next_sendtime = tvtohz(&cu->cu_wait);
+ }
starttime = ticks;
@@ -394,9 +419,9 @@ send_again:
mtx_unlock(&cs->cs_lock);
MGETHDR(mreq, M_WAIT, MT_DATA);
- MCLGET(mreq, M_WAIT);
- mreq->m_len = 0;
- m_append(mreq, cu->cu_mcalllen, cu->cu_mcallc);
+ KASSERT(cu->cu_mcalllen <= MHLEN, ("RPC header too big"));
+ bcopy(cu->cu_mcallc, mreq->m_data, cu->cu_mcalllen);
+ mreq->m_len = cu->cu_mcalllen;
/*
* The XID is the first thing in the request.
@@ -405,20 +430,36 @@ send_again:
xdrmbuf_create(&xdrs, mreq, XDR_ENCODE);
- if (cu->cu_async == TRUE && xargs == NULL)
+ if (cu->cu_async == TRUE && args == NULL)
goto get_reply;
if ((! XDR_PUTINT32(&xdrs, &proc)) ||
- (! AUTH_MARSHALL(auth, &xdrs)) ||
- (! (*xargs)(&xdrs, argsp))) {
- cu->cu_error.re_status = RPC_CANTENCODEARGS;
+ (! AUTH_MARSHALL(auth, xid, &xdrs,
+ m_copym(args, 0, M_COPYALL, M_WAITOK)))) {
+ errp->re_status = stat = RPC_CANTENCODEARGS;
mtx_lock(&cs->cs_lock);
goto out;
}
- m_fixhdr(mreq);
+ mreq->m_pkthdr.len = m_length(mreq, NULL);
cr->cr_xid = xid;
mtx_lock(&cs->cs_lock);
+
+ /*
+ * Try to get a place in the congestion window.
+ */
+ while (cu->cu_sent >= cu->cu_cwnd) {
+ cu->cu_cwnd_wait = TRUE;
+ error = msleep(&cu->cu_cwnd_wait, &cs->cs_lock,
+ cu->cu_waitflag, "rpccwnd", 0);
+ if (error) {
+ errp->re_errno = error;
+ errp->re_status = stat = RPC_CANTSEND;
+ goto out;
+ }
+ }
+ cu->cu_sent += CWNDSCALE;
+
TAILQ_INSERT_TAIL(&cs->cs_pending, cr, cr_link);
mtx_unlock(&cs->cs_lock);
@@ -433,15 +474,22 @@ send_again:
* some clock time to spare while the packets are in flight.
* (We assume that this is actually only executed once.)
*/
- reply_msg.acpted_rply.ar_verf = _null_auth;
- reply_msg.acpted_rply.ar_results.where = resultsp;
- reply_msg.acpted_rply.ar_results.proc = xresults;
+ reply_msg.acpted_rply.ar_verf.oa_flavor = AUTH_NULL;
+ reply_msg.acpted_rply.ar_verf.oa_base = cr->cr_verf;
+ reply_msg.acpted_rply.ar_verf.oa_length = 0;
+ reply_msg.acpted_rply.ar_results.where = NULL;
+ reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void;
mtx_lock(&cs->cs_lock);
if (error) {
TAILQ_REMOVE(&cs->cs_pending, cr, cr_link);
- cu->cu_error.re_errno = error;
- cu->cu_error.re_status = RPC_CANTSEND;
+ errp->re_errno = error;
+ errp->re_status = stat = RPC_CANTSEND;
+ cu->cu_sent -= CWNDSCALE;
+ if (cu->cu_cwnd_wait) {
+ cu->cu_cwnd_wait = FALSE;
+ wakeup(&cu->cu_cwnd_wait);
+ }
goto out;
}
@@ -451,12 +499,22 @@ send_again:
*/
if (cr->cr_error) {
TAILQ_REMOVE(&cs->cs_pending, cr, cr_link);
- cu->cu_error.re_errno = cr->cr_error;
- cu->cu_error.re_status = RPC_CANTRECV;
+ errp->re_errno = cr->cr_error;
+ errp->re_status = stat = RPC_CANTRECV;
+ cu->cu_sent -= CWNDSCALE;
+ if (cu->cu_cwnd_wait) {
+ cu->cu_cwnd_wait = FALSE;
+ wakeup(&cu->cu_cwnd_wait);
+ }
goto out;
}
if (cr->cr_mrep) {
TAILQ_REMOVE(&cs->cs_pending, cr, cr_link);
+ cu->cu_sent -= CWNDSCALE;
+ if (cu->cu_cwnd_wait) {
+ cu->cu_cwnd_wait = FALSE;
+ wakeup(&cu->cu_cwnd_wait);
+ }
goto got_reply;
}
@@ -465,7 +523,12 @@ send_again:
*/
if (timeout == 0) {
TAILQ_REMOVE(&cs->cs_pending, cr, cr_link);
- cu->cu_error.re_status = RPC_TIMEDOUT;
+ errp->re_status = stat = RPC_TIMEDOUT;
+ cu->cu_sent -= CWNDSCALE;
+ if (cu->cu_cwnd_wait) {
+ cu->cu_cwnd_wait = FALSE;
+ wakeup(&cu->cu_cwnd_wait);
+ }
goto out;
}
@@ -479,7 +542,7 @@ get_reply:
tv -= time_waited;
if (tv > 0) {
- if (cu->cu_closing)
+ if (cu->cu_closing || cu->cu_closed)
error = 0;
else
error = msleep(cr, &cs->cs_lock,
@@ -489,6 +552,11 @@ get_reply:
}
TAILQ_REMOVE(&cs->cs_pending, cr, cr_link);
+ cu->cu_sent -= CWNDSCALE;
+ if (cu->cu_cwnd_wait) {
+ cu->cu_cwnd_wait = FALSE;
+ wakeup(&cu->cu_cwnd_wait);
+ }
if (!error) {
/*
@@ -497,10 +565,52 @@ get_reply:
* otherwise we have a reply.
*/
if (cr->cr_error) {
- cu->cu_error.re_errno = cr->cr_error;
- cu->cu_error.re_status = RPC_CANTRECV;
+ errp->re_errno = cr->cr_error;
+ errp->re_status = stat = RPC_CANTRECV;
goto out;
}
+
+ cu->cu_cwnd += (CWNDSCALE * CWNDSCALE
+ + cu->cu_cwnd / 2) / cu->cu_cwnd;
+ if (cu->cu_cwnd > MAXCWND)
+ cu->cu_cwnd = MAXCWND;
+
+ if (rt) {
+ /*
+ * Add one to the time since a tick
+ * count of N means that the actual
+ * time taken was somewhere between N
+ * and N+1.
+ */
+ rtt = ticks - starttime + 1;
+
+ /*
+ * Update our estimate of the round
+ * trip time using roughly the
+ * algorithm described in RFC
+ * 2988. Given an RTT sample R:
+ *
+ * RTTVAR = (1-beta) * RTTVAR + beta * |SRTT-R|
+ * SRTT = (1-alpha) * SRTT + alpha * R
+ *
+ * where alpha = 0.125 and beta = 0.25.
+ *
+ * The initial retransmit timeout is
+ * SRTT + 4*RTTVAR and doubles on each
+ * retransmision.
+ */
+ if (rt->rt_srtt == 0) {
+ rt->rt_srtt = rtt;
+ rt->rt_deviate = rtt / 2;
+ } else {
+ int32_t error = rtt - rt->rt_srtt;
+ rt->rt_srtt += error / 8;
+ error = abs(error) - rt->rt_deviate;
+ rt->rt_deviate += error / 4;
+ }
+ rt->rt_rtxcur = rt->rt_srtt + 4*rt->rt_deviate;
+ }
+
break;
}
@@ -510,11 +620,11 @@ get_reply:
* re-send the request.
*/
if (error != EWOULDBLOCK) {
- cu->cu_error.re_errno = error;
+ errp->re_errno = error;
if (error == EINTR)
- cu->cu_error.re_status = RPC_INTR;
+ errp->re_status = stat = RPC_INTR;
else
- cu->cu_error.re_status = RPC_CANTRECV;
+ errp->re_status = stat = RPC_CANTRECV;
goto out;
}
@@ -522,13 +632,16 @@ get_reply:
/* Check for timeout. */
if (time_waited > timeout) {
- cu->cu_error.re_errno = EWOULDBLOCK;
- cu->cu_error.re_status = RPC_TIMEDOUT;
+ errp->re_errno = EWOULDBLOCK;
+ errp->re_status = stat = RPC_TIMEDOUT;
goto out;
}
/* Retransmit if necessary. */
if (time_waited >= next_sendtime) {
+ cu->cu_cwnd /= 2;
+ if (cu->cu_cwnd < CWNDSCALE)
+ cu->cu_cwnd = CWNDSCALE;
if (ext && ext->rc_feedback) {
mtx_unlock(&cs->cs_lock);
if (retrans == 0)
@@ -539,9 +652,9 @@ get_reply:
proc, ext->rc_feedback_arg);
mtx_lock(&cs->cs_lock);
}
- if (cu->cu_closing) {
- cu->cu_error.re_errno = ESHUTDOWN;
- cu->cu_error.re_status = RPC_CANTRECV;
+ if (cu->cu_closing || cu->cu_closed) {
+ errp->re_errno = ESHUTDOWN;
+ errp->re_status = stat = RPC_CANTRECV;
goto out;
}
retrans++;
@@ -566,47 +679,72 @@ got_reply:
xdrmbuf_create(&xdrs, cr->cr_mrep, XDR_DECODE);
ok = xdr_replymsg(&xdrs, &reply_msg);
- XDR_DESTROY(&xdrs);
cr->cr_mrep = NULL;
- mtx_lock(&cs->cs_lock);
-
if (ok) {
if ((reply_msg.rm_reply.rp_stat == MSG_ACCEPTED) &&
- (reply_msg.acpted_rply.ar_stat == SUCCESS))
- cu->cu_error.re_status = RPC_SUCCESS;
+ (reply_msg.acpted_rply.ar_stat == SUCCESS))
+ errp->re_status = stat = RPC_SUCCESS;
else
- _seterr_reply(&reply_msg, &(cu->cu_error));
-
- if (cu->cu_error.re_status == RPC_SUCCESS) {
- if (! AUTH_VALIDATE(cl->cl_auth,
- &reply_msg.acpted_rply.ar_verf)) {
- cu->cu_error.re_status = RPC_AUTHERROR;
- cu->cu_error.re_why = AUTH_INVALIDRESP;
- }
- if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
- xdrs.x_op = XDR_FREE;
- (void) xdr_opaque_auth(&xdrs,
- &(reply_msg.acpted_rply.ar_verf));
+ stat = _seterr_reply(&reply_msg, &(cu->cu_error));
+
+ if (errp->re_status == RPC_SUCCESS) {
+ results = xdrmbuf_getall(&xdrs);
+ if (! AUTH_VALIDATE(auth, xid,
+ &reply_msg.acpted_rply.ar_verf,
+ &results)) {
+ errp->re_status = stat = RPC_AUTHERROR;
+ errp->re_why = AUTH_INVALIDRESP;
+ if (retrans &&
+ auth->ah_cred.oa_flavor == RPCSEC_GSS) {
+ /*
+ * If we retransmitted, its
+ * possible that we will
+ * receive a reply for one of
+ * the earlier transmissions
+ * (which will use an older
+ * RPCSEC_GSS sequence
+ * number). In this case, just
+ * go back and listen for a
+ * new reply. We could keep a
+ * record of all the seq
+ * numbers we have transmitted
+ * so far so that we could
+ * accept a reply for any of
+ * them here.
+ */
+ XDR_DESTROY(&xdrs);
+ mtx_lock(&cs->cs_lock);
+ TAILQ_INSERT_TAIL(&cs->cs_pending,
+ cr, cr_link);
+ cr->cr_mrep = NULL;
+ goto get_reply;
+ }
+ } else {
+ *resultsp = results;
}
} /* end successful completion */
/*
* If unsuccesful AND error is an authentication error
* then refresh credentials and try again, else break
*/
- else if (cu->cu_error.re_status == RPC_AUTHERROR)
+ else if (stat == RPC_AUTHERROR)
/* maybe our credentials need to be refreshed ... */
if (nrefreshes > 0 &&
- AUTH_REFRESH(cl->cl_auth, &reply_msg)) {
+ AUTH_REFRESH(auth, &reply_msg)) {
nrefreshes--;
+ XDR_DESTROY(&xdrs);
+ mtx_lock(&cs->cs_lock);
goto call_again;
}
/* end of unsuccessful completion */
} /* end of valid reply message */
else {
- cu->cu_error.re_status = RPC_CANTDECODERES;
+ errp->re_status = stat = RPC_CANTDECODERES;
}
+ XDR_DESTROY(&xdrs);
+ mtx_lock(&cs->cs_lock);
out:
mtx_assert(&cs->cs_lock, MA_OWNED);
@@ -621,9 +759,12 @@ out:
mtx_unlock(&cs->cs_lock);
+ if (auth && stat != RPC_SUCCESS)
+ AUTH_VALIDATE(auth, xid, NULL, NULL);
+
free(cr, M_RPC);
- return (cu->cu_error.re_status);
+ return (stat);
}
static void
@@ -759,7 +900,7 @@ clnt_dg_control(CLIENT *cl, u_int request, void *info)
cu->cu_connect = *(int *)info;
break;
case CLSET_WAITCHAN:
- cu->cu_waitchan = *(const char **)info;
+ cu->cu_waitchan = (const char *)info;
break;
case CLGET_WAITCHAN:
*(const char **) info = cu->cu_waitchan;
@@ -785,16 +926,27 @@ clnt_dg_control(CLIENT *cl, u_int request, void *info)
}
static void
-clnt_dg_destroy(CLIENT *cl)
+clnt_dg_close(CLIENT *cl)
{
struct cu_data *cu = (struct cu_data *)cl->cl_private;
struct cu_socket *cs = (struct cu_socket *) cu->cu_socket->so_upcallarg;
struct cu_request *cr;
- struct socket *so = NULL;
- bool_t lastsocketref;
mtx_lock(&cs->cs_lock);
+ if (cu->cu_closed) {
+ mtx_unlock(&cs->cs_lock);
+ return;
+ }
+
+ if (cu->cu_closing) {
+ while (cu->cu_closing)
+ msleep(cu, &cs->cs_lock, 0, "rpcclose", 0);
+ KASSERT(cu->cu_closed, ("client should be closed"));
+ mtx_unlock(&cs->cs_lock);
+ return;
+ }
+
/*
* Abort any pending requests and wait until everyone
* has finished with clnt_vc_call.
@@ -811,6 +963,25 @@ clnt_dg_destroy(CLIENT *cl)
while (cu->cu_threads)
msleep(cu, &cs->cs_lock, 0, "rpcclose", 0);
+ cu->cu_closing = FALSE;
+ cu->cu_closed = TRUE;
+
+ mtx_unlock(&cs->cs_lock);
+ wakeup(cu);
+}
+
+static void
+clnt_dg_destroy(CLIENT *cl)
+{
+ struct cu_data *cu = (struct cu_data *)cl->cl_private;
+ struct cu_socket *cs = (struct cu_socket *) cu->cu_socket->so_upcallarg;
+ struct socket *so = NULL;
+ bool_t lastsocketref;
+
+ clnt_dg_close(cl);
+
+ mtx_lock(&cs->cs_lock);
+
cs->cs_refs--;
if (cs->cs_refs == 0) {
mtx_destroy(&cs->cs_lock);
@@ -894,7 +1065,8 @@ clnt_dg_soupcall(struct socket *so, void *arg, int waitflag)
/*
* The XID is in the first uint32_t of the reply.
*/
- m = m_pullup(m, sizeof(xid));
+ if (m->m_len < sizeof(xid))
+ m = m_pullup(m, sizeof(xid));
if (!m)
/*
* Should never happen.
diff --git a/sys/rpc/clnt_rc.c b/sys/rpc/clnt_rc.c
index f0ad673..8d7bfd6 100644
--- a/sys/rpc/clnt_rc.c
+++ b/sys/rpc/clnt_rc.c
@@ -30,6 +30,7 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
@@ -46,11 +47,12 @@ __FBSDID("$FreeBSD$");
#include <rpc/rpc_com.h>
static enum clnt_stat clnt_reconnect_call(CLIENT *, struct rpc_callextra *,
- rpcproc_t, xdrproc_t, void *, xdrproc_t, void *, struct timeval);
+ rpcproc_t, struct mbuf *, struct mbuf **, struct timeval);
static void clnt_reconnect_geterr(CLIENT *, struct rpc_err *);
static bool_t clnt_reconnect_freeres(CLIENT *, xdrproc_t, void *);
static void clnt_reconnect_abort(CLIENT *);
static bool_t clnt_reconnect_control(CLIENT *, u_int, void *);
+static void clnt_reconnect_close(CLIENT *);
static void clnt_reconnect_destroy(CLIENT *);
static struct clnt_ops clnt_reconnect_ops = {
@@ -58,10 +60,13 @@ static struct clnt_ops clnt_reconnect_ops = {
.cl_abort = clnt_reconnect_abort,
.cl_geterr = clnt_reconnect_geterr,
.cl_freeres = clnt_reconnect_freeres,
+ .cl_close = clnt_reconnect_close,
.cl_destroy = clnt_reconnect_destroy,
.cl_control = clnt_reconnect_control
};
+static int fake_wchan;
+
struct rc_data {
struct mtx rc_lock;
struct sockaddr_storage rc_addr; /* server address */
@@ -73,10 +78,14 @@ struct rc_data {
struct timeval rc_timeout;
struct timeval rc_retry;
int rc_retries;
- const char *rc_waitchan;
+ int rc_privport;
+ char *rc_waitchan;
int rc_intr;
int rc_connecting;
+ int rc_closed;
+ struct ucred *rc_ucred;
CLIENT* rc_client; /* underlying RPC client */
+ struct rpc_err rc_err;
};
CLIENT *
@@ -110,9 +119,12 @@ clnt_reconnect_create(
rc->rc_retry.tv_sec = 3;
rc->rc_retry.tv_usec = 0;
rc->rc_retries = INT_MAX;
+ rc->rc_privport = FALSE;
rc->rc_waitchan = "rpcrecv";
rc->rc_intr = 0;
rc->rc_connecting = FALSE;
+ rc->rc_closed = FALSE;
+ rc->rc_ucred = crdup(curthread->td_ucred);
rc->rc_client = NULL;
cl->cl_refs = 1;
@@ -127,16 +139,22 @@ clnt_reconnect_create(
static enum clnt_stat
clnt_reconnect_connect(CLIENT *cl)
{
+ struct thread *td = curthread;
struct rc_data *rc = (struct rc_data *)cl->cl_private;
struct socket *so;
enum clnt_stat stat;
int error;
int one = 1;
+ struct ucred *oldcred;
mtx_lock(&rc->rc_lock);
again:
+ if (rc->rc_closed) {
+ mtx_unlock(&rc->rc_lock);
+ return (RPC_CANTSEND);
+ }
if (rc->rc_connecting) {
- while (!rc->rc_client) {
+ while (!rc->rc_closed && !rc->rc_client) {
error = msleep(rc, &rc->rc_lock,
rc->rc_intr ? PCATCH : 0, "rpcrecon", 0);
if (error) {
@@ -163,7 +181,11 @@ again:
rpc_createerr.cf_error.re_errno = 0;
goto out;
}
+ if (rc->rc_privport)
+ bindresvport(so, NULL);
+ oldcred = td->td_ucred;
+ td->td_ucred = rc->rc_ucred;
if (rc->rc_nconf->nc_semantics == NC_TPI_CLTS)
rc->rc_client = clnt_dg_create(so,
(struct sockaddr *) &rc->rc_addr, rc->rc_prog, rc->rc_vers,
@@ -172,8 +194,11 @@ again:
rc->rc_client = clnt_vc_create(so,
(struct sockaddr *) &rc->rc_addr, rc->rc_prog, rc->rc_vers,
rc->rc_sendsz, rc->rc_recvsz);
+ td->td_ucred = oldcred;
if (!rc->rc_client) {
+ soclose(so);
+ rc->rc_err = rpc_createerr.cf_error;
stat = rpc_createerr.cf_stat;
goto out;
}
@@ -182,12 +207,19 @@ again:
CLNT_CONTROL(rc->rc_client, CLSET_CONNECT, &one);
CLNT_CONTROL(rc->rc_client, CLSET_TIMEOUT, &rc->rc_timeout);
CLNT_CONTROL(rc->rc_client, CLSET_RETRY_TIMEOUT, &rc->rc_retry);
- CLNT_CONTROL(rc->rc_client, CLSET_WAITCHAN, &rc->rc_waitchan);
+ CLNT_CONTROL(rc->rc_client, CLSET_WAITCHAN, rc->rc_waitchan);
CLNT_CONTROL(rc->rc_client, CLSET_INTERRUPTIBLE, &rc->rc_intr);
stat = RPC_SUCCESS;
out:
mtx_lock(&rc->rc_lock);
+ if (rc->rc_closed) {
+ if (rc->rc_client) {
+ CLNT_CLOSE(rc->rc_client);
+ CLNT_RELEASE(rc->rc_client);
+ rc->rc_client = NULL;
+ }
+ }
rc->rc_connecting = FALSE;
wakeup(rc);
mtx_unlock(&rc->rc_lock);
@@ -200,11 +232,9 @@ clnt_reconnect_call(
CLIENT *cl, /* client handle */
struct rpc_callextra *ext, /* call metadata */
rpcproc_t proc, /* procedure number */
- xdrproc_t xargs, /* xdr routine for args */
- void *argsp, /* pointer to args */
- xdrproc_t xresults, /* xdr routine for results */
- void *resultsp, /* pointer to results */
- struct timeval utimeout) /* seconds to wait before giving up */
+ struct mbuf *args, /* pointer to args */
+ struct mbuf **resultsp, /* pointer to results */
+ struct timeval utimeout)
{
struct rc_data *rc = (struct rc_data *)cl->cl_private;
CLIENT *client;
@@ -213,18 +243,40 @@ clnt_reconnect_call(
tries = 0;
do {
+ if (rc->rc_closed) {
+ return (RPC_CANTSEND);
+ }
+
if (!rc->rc_client) {
stat = clnt_reconnect_connect(cl);
+ if (stat == RPC_SYSTEMERROR) {
+ (void) tsleep(&fake_wchan, 0,
+ "rpccon", hz);
+ tries++;
+ if (tries >= rc->rc_retries)
+ return (stat);
+ continue;
+ }
if (stat != RPC_SUCCESS)
return (stat);
}
mtx_lock(&rc->rc_lock);
+ if (!rc->rc_client) {
+ mtx_unlock(&rc->rc_lock);
+ stat = RPC_FAILED;
+ continue;
+ }
CLNT_ACQUIRE(rc->rc_client);
client = rc->rc_client;
mtx_unlock(&rc->rc_lock);
- stat = CLNT_CALL_EXT(client, ext, proc, xargs, argsp,
- xresults, resultsp, utimeout);
+ stat = CLNT_CALL_MBUF(client, ext, proc, args,
+ resultsp, utimeout);
+
+ if (stat != RPC_SUCCESS) {
+ if (!ext)
+ CLNT_GETERR(client, &rc->rc_err);
+ }
CLNT_RELEASE(client);
if (stat == RPC_TIMEDOUT) {
@@ -241,10 +293,8 @@ clnt_reconnect_call(
}
}
- if (stat == RPC_INTR)
- break;
-
- if (stat != RPC_SUCCESS) {
+ if (stat == RPC_TIMEDOUT || stat == RPC_CANTSEND
+ || stat == RPC_CANTRECV) {
tries++;
if (tries >= rc->rc_retries)
break;
@@ -263,9 +313,14 @@ clnt_reconnect_call(
rc->rc_client = NULL;
}
mtx_unlock(&rc->rc_lock);
+ } else {
+ break;
}
} while (stat != RPC_SUCCESS);
+ KASSERT(stat != RPC_SUCCESS || *resultsp,
+ ("RPC_SUCCESS without reply"));
+
return (stat);
}
@@ -274,10 +329,7 @@ clnt_reconnect_geterr(CLIENT *cl, struct rpc_err *errp)
{
struct rc_data *rc = (struct rc_data *)cl->cl_private;
- if (rc->rc_client)
- CLNT_GETERR(rc->rc_client, errp);
- else
- memset(errp, 0, sizeof(*errp));
+ *errp = rc->rc_err;
}
static bool_t
@@ -344,7 +396,7 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
break;
case CLSET_WAITCHAN:
- rc->rc_waitchan = *(const char **)info;
+ rc->rc_waitchan = (char *)info;
if (rc->rc_client)
CLNT_CONTROL(rc->rc_client, request, info);
break;
@@ -371,6 +423,14 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
*(int *) info = rc->rc_retries;
break;
+ case CLSET_PRIVPORT:
+ rc->rc_privport = *(int *) info;
+ break;
+
+ case CLGET_PRIVPORT:
+ *(int *) info = rc->rc_privport;
+ break;
+
default:
return (FALSE);
}
@@ -379,12 +439,38 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
}
static void
+clnt_reconnect_close(CLIENT *cl)
+{
+ struct rc_data *rc = (struct rc_data *)cl->cl_private;
+ CLIENT *client;
+
+ mtx_lock(&rc->rc_lock);
+
+ if (rc->rc_closed) {
+ mtx_unlock(&rc->rc_lock);
+ return;
+ }
+
+ rc->rc_closed = TRUE;
+ client = rc->rc_client;
+ rc->rc_client = NULL;
+
+ mtx_unlock(&rc->rc_lock);
+
+ if (client) {
+ CLNT_CLOSE(client);
+ CLNT_RELEASE(client);
+ }
+}
+
+static void
clnt_reconnect_destroy(CLIENT *cl)
{
struct rc_data *rc = (struct rc_data *)cl->cl_private;
if (rc->rc_client)
CLNT_DESTROY(rc->rc_client);
+ crfree(rc->rc_ucred);
mtx_destroy(&rc->rc_lock);
mem_free(rc, sizeof(*rc));
mem_free(cl, sizeof (CLIENT));
diff --git a/sys/rpc/clnt_vc.c b/sys/rpc/clnt_vc.c
index cb09352..11fc201 100644
--- a/sys/rpc/clnt_vc.c
+++ b/sys/rpc/clnt_vc.c
@@ -64,11 +64,13 @@ __FBSDID("$FreeBSD$");
#include <sys/mutex.h>
#include <sys/pcpu.h>
#include <sys/proc.h>
+#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.h>
#include <sys/time.h>
#include <sys/uio.h>
+#include <netinet/tcp.h>
#include <rpc/rpc.h>
#include <rpc/rpc_com.h>
@@ -81,11 +83,12 @@ struct cmessage {
};
static enum clnt_stat clnt_vc_call(CLIENT *, struct rpc_callextra *,
- rpcproc_t, xdrproc_t, void *, xdrproc_t, void *, struct timeval);
+ rpcproc_t, struct mbuf *, struct mbuf **, struct timeval);
static void clnt_vc_geterr(CLIENT *, struct rpc_err *);
static bool_t clnt_vc_freeres(CLIENT *, xdrproc_t, void *);
static void clnt_vc_abort(CLIENT *);
static bool_t clnt_vc_control(CLIENT *, u_int, void *);
+static void clnt_vc_close(CLIENT *);
static void clnt_vc_destroy(CLIENT *);
static bool_t time_not_ok(struct timeval *);
static void clnt_vc_soupcall(struct socket *so, void *arg, int waitflag);
@@ -95,6 +98,7 @@ static struct clnt_ops clnt_vc_ops = {
.cl_abort = clnt_vc_abort,
.cl_geterr = clnt_vc_geterr,
.cl_freeres = clnt_vc_freeres,
+ .cl_close = clnt_vc_close,
.cl_destroy = clnt_vc_destroy,
.cl_control = clnt_vc_control
};
@@ -109,6 +113,7 @@ struct ct_request {
uint32_t cr_xid; /* XID of request */
struct mbuf *cr_mrep; /* reply received by upcall */
int cr_error; /* any error from upcall */
+ char cr_verf[MAX_AUTH_BYTES]; /* reply verf */
};
TAILQ_HEAD(ct_request_list, ct_request);
@@ -116,7 +121,8 @@ TAILQ_HEAD(ct_request_list, ct_request);
struct ct_data {
struct mtx ct_lock;
int ct_threads; /* number of threads in clnt_vc_call */
- bool_t ct_closing; /* TRUE if we are destroying client */
+ bool_t ct_closing; /* TRUE if we are closing */
+ bool_t ct_closed; /* TRUE if we are closed */
struct socket *ct_socket; /* connection socket */
bool_t ct_closeit; /* close it on destroy */
struct timeval ct_wait; /* wait interval in milliseconds */
@@ -165,7 +171,8 @@ clnt_vc_create(
static uint32_t disrupt;
struct __rpc_sockinfo si;
XDR xdrs;
- int error, interrupted;
+ int error, interrupted, one = 1;
+ struct sockopt sopt;
if (disrupt == 0)
disrupt = (uint32_t)(long)raddr;
@@ -176,6 +183,7 @@ clnt_vc_create(
mtx_init(&ct->ct_lock, "ct->ct_lock", NULL, MTX_DEF);
ct->ct_threads = 0;
ct->ct_closing = FALSE;
+ ct->ct_closed = FALSE;
if ((so->so_state & (SS_ISCONNECTED|SS_ISCONFIRMING)) == 0) {
error = soconnect(so, raddr, curthread);
@@ -208,6 +216,26 @@ clnt_vc_create(
if (!__rpc_socket2sockinfo(so, &si))
goto err;
+ if (so->so_proto->pr_flags & PR_CONNREQUIRED) {
+ bzero(&sopt, sizeof(sopt));
+ sopt.sopt_dir = SOPT_SET;
+ sopt.sopt_level = SOL_SOCKET;
+ sopt.sopt_name = SO_KEEPALIVE;
+ sopt.sopt_val = &one;
+ sopt.sopt_valsize = sizeof(one);
+ sosetopt(so, &sopt);
+ }
+
+ if (so->so_proto->pr_protocol == IPPROTO_TCP) {
+ bzero(&sopt, sizeof(sopt));
+ sopt.sopt_dir = SOPT_SET;
+ sopt.sopt_level = IPPROTO_TCP;
+ sopt.sopt_name = TCP_NODELAY;
+ sopt.sopt_val = &one;
+ sopt.sopt_valsize = sizeof(one);
+ sosetopt(so, &sopt);
+ }
+
ct->ct_closeit = FALSE;
/*
@@ -255,6 +283,7 @@ clnt_vc_create(
cl->cl_auth = authnone_create();
sendsz = __rpc_get_t_size(si.si_af, si.si_proto, (int)sendsz);
recvsz = __rpc_get_t_size(si.si_af, si.si_proto, (int)recvsz);
+ soreserve(ct->ct_socket, sendsz, recvsz);
SOCKBUF_LOCK(&ct->ct_socket->so_rcv);
ct->ct_socket->so_upcallarg = ct;
@@ -280,24 +309,24 @@ err:
static enum clnt_stat
clnt_vc_call(
- CLIENT *cl,
- struct rpc_callextra *ext,
- rpcproc_t proc,
- xdrproc_t xdr_args,
- void *args_ptr,
- xdrproc_t xdr_results,
- void *results_ptr,
- struct timeval utimeout)
+ CLIENT *cl, /* client handle */
+ struct rpc_callextra *ext, /* call metadata */
+ rpcproc_t proc, /* procedure number */
+ struct mbuf *args, /* pointer to args */
+ struct mbuf **resultsp, /* pointer to results */
+ struct timeval utimeout)
{
struct ct_data *ct = (struct ct_data *) cl->cl_private;
AUTH *auth;
+ struct rpc_err *errp;
+ enum clnt_stat stat;
XDR xdrs;
struct rpc_msg reply_msg;
bool_t ok;
int nrefreshes = 2; /* number of times to refresh cred */
struct timeval timeout;
uint32_t xid;
- struct mbuf *mreq = NULL;
+ struct mbuf *mreq = NULL, *results;
struct ct_request *cr;
int error;
@@ -305,17 +334,20 @@ clnt_vc_call(
mtx_lock(&ct->ct_lock);
- if (ct->ct_closing) {
+ if (ct->ct_closing || ct->ct_closed) {
mtx_unlock(&ct->ct_lock);
free(cr, M_RPC);
return (RPC_CANTSEND);
}
ct->ct_threads++;
- if (ext)
+ if (ext) {
auth = ext->rc_auth;
- else
+ errp = &ext->rc_err;
+ } else {
auth = cl->cl_auth;
+ errp = &ct->ct_error;
+ }
cr->cr_mrep = NULL;
cr->cr_error = 0;
@@ -338,10 +370,11 @@ call_again:
* Leave space to pre-pend the record mark.
*/
MGETHDR(mreq, M_WAIT, MT_DATA);
- MCLGET(mreq, M_WAIT);
- mreq->m_len = 0;
mreq->m_data += sizeof(uint32_t);
- m_append(mreq, ct->ct_mpos, ct->ct_mcallc);
+ KASSERT(ct->ct_mpos + sizeof(uint32_t) <= MHLEN,
+ ("RPC header too big"));
+ bcopy(ct->ct_mcallc, mreq->m_data, ct->ct_mpos);
+ mreq->m_len = ct->ct_mpos;
/*
* The XID is the first thing in the request.
@@ -350,17 +383,16 @@ call_again:
xdrmbuf_create(&xdrs, mreq, XDR_ENCODE);
- ct->ct_error.re_status = RPC_SUCCESS;
+ errp->re_status = stat = RPC_SUCCESS;
if ((! XDR_PUTINT32(&xdrs, &proc)) ||
- (! AUTH_MARSHALL(auth, &xdrs)) ||
- (! (*xdr_args)(&xdrs, args_ptr))) {
- if (ct->ct_error.re_status == RPC_SUCCESS)
- ct->ct_error.re_status = RPC_CANTENCODEARGS;
+ (! AUTH_MARSHALL(auth, xid, &xdrs,
+ m_copym(args, 0, M_COPYALL, M_WAITOK)))) {
+ errp->re_status = stat = RPC_CANTENCODEARGS;
mtx_lock(&ct->ct_lock);
goto out;
}
- m_fixhdr(mreq);
+ mreq->m_pkthdr.len = m_length(mreq, NULL);
/*
* Prepend a record marker containing the packet length.
@@ -379,16 +411,27 @@ call_again:
*/
error = sosend(ct->ct_socket, NULL, NULL, mreq, NULL, 0, curthread);
mreq = NULL;
+ if (error == EMSGSIZE) {
+ SOCKBUF_LOCK(&ct->ct_socket->so_snd);
+ sbwait(&ct->ct_socket->so_snd);
+ SOCKBUF_UNLOCK(&ct->ct_socket->so_snd);
+ AUTH_VALIDATE(auth, xid, NULL, NULL);
+ mtx_lock(&ct->ct_lock);
+ TAILQ_REMOVE(&ct->ct_pending, cr, cr_link);
+ goto call_again;
+ }
- reply_msg.acpted_rply.ar_verf = _null_auth;
- reply_msg.acpted_rply.ar_results.where = results_ptr;
- reply_msg.acpted_rply.ar_results.proc = xdr_results;
+ reply_msg.acpted_rply.ar_verf.oa_flavor = AUTH_NULL;
+ reply_msg.acpted_rply.ar_verf.oa_base = cr->cr_verf;
+ reply_msg.acpted_rply.ar_verf.oa_length = 0;
+ reply_msg.acpted_rply.ar_results.where = NULL;
+ reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void;
mtx_lock(&ct->ct_lock);
if (error) {
TAILQ_REMOVE(&ct->ct_pending, cr, cr_link);
- ct->ct_error.re_errno = error;
- ct->ct_error.re_status = RPC_CANTSEND;
+ errp->re_errno = error;
+ errp->re_status = stat = RPC_CANTSEND;
goto out;
}
@@ -399,8 +442,8 @@ call_again:
*/
if (cr->cr_error) {
TAILQ_REMOVE(&ct->ct_pending, cr, cr_link);
- ct->ct_error.re_errno = cr->cr_error;
- ct->ct_error.re_status = RPC_CANTRECV;
+ errp->re_errno = cr->cr_error;
+ errp->re_status = stat = RPC_CANTRECV;
goto out;
}
if (cr->cr_mrep) {
@@ -413,7 +456,7 @@ call_again:
*/
if (timeout.tv_sec == 0 && timeout.tv_usec == 0) {
TAILQ_REMOVE(&ct->ct_pending, cr, cr_link);
- ct->ct_error.re_status = RPC_TIMEDOUT;
+ errp->re_status = stat = RPC_TIMEDOUT;
goto out;
}
@@ -428,17 +471,18 @@ call_again:
* on the list. Turn the error code into an
* appropriate client status.
*/
- ct->ct_error.re_errno = error;
+ errp->re_errno = error;
switch (error) {
case EINTR:
- ct->ct_error.re_status = RPC_INTR;
+ stat = RPC_INTR;
break;
case EWOULDBLOCK:
- ct->ct_error.re_status = RPC_TIMEDOUT;
+ stat = RPC_TIMEDOUT;
break;
default:
- ct->ct_error.re_status = RPC_CANTRECV;
+ stat = RPC_CANTRECV;
}
+ errp->re_status = stat;
goto out;
} else {
/*
@@ -447,8 +491,8 @@ call_again:
* otherwise we have a reply.
*/
if (cr->cr_error) {
- ct->ct_error.re_errno = cr->cr_error;
- ct->ct_error.re_status = RPC_CANTRECV;
+ errp->re_errno = cr->cr_error;
+ errp->re_status = stat = RPC_CANTRECV;
goto out;
}
}
@@ -460,51 +504,59 @@ got_reply:
*/
mtx_unlock(&ct->ct_lock);
+ if (ext && ext->rc_feedback)
+ ext->rc_feedback(FEEDBACK_OK, proc, ext->rc_feedback_arg);
+
xdrmbuf_create(&xdrs, cr->cr_mrep, XDR_DECODE);
ok = xdr_replymsg(&xdrs, &reply_msg);
- XDR_DESTROY(&xdrs);
cr->cr_mrep = NULL;
- mtx_lock(&ct->ct_lock);
-
if (ok) {
if ((reply_msg.rm_reply.rp_stat == MSG_ACCEPTED) &&
- (reply_msg.acpted_rply.ar_stat == SUCCESS))
- ct->ct_error.re_status = RPC_SUCCESS;
+ (reply_msg.acpted_rply.ar_stat == SUCCESS))
+ errp->re_status = stat = RPC_SUCCESS;
else
- _seterr_reply(&reply_msg, &(ct->ct_error));
-
- if (ct->ct_error.re_status == RPC_SUCCESS) {
- if (! AUTH_VALIDATE(cl->cl_auth,
- &reply_msg.acpted_rply.ar_verf)) {
- ct->ct_error.re_status = RPC_AUTHERROR;
- ct->ct_error.re_why = AUTH_INVALIDRESP;
- }
- if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
- xdrs.x_op = XDR_FREE;
- (void) xdr_opaque_auth(&xdrs,
- &(reply_msg.acpted_rply.ar_verf));
+ stat = _seterr_reply(&reply_msg, errp);
+
+ if (stat == RPC_SUCCESS) {
+ results = xdrmbuf_getall(&xdrs);
+ if (!AUTH_VALIDATE(auth, xid,
+ &reply_msg.acpted_rply.ar_verf,
+ &results)) {
+ errp->re_status = stat = RPC_AUTHERROR;
+ errp->re_why = AUTH_INVALIDRESP;
+ } else {
+ KASSERT(results,
+ ("auth validated but no result"));
+ *resultsp = results;
}
} /* end successful completion */
/*
* If unsuccesful AND error is an authentication error
* then refresh credentials and try again, else break
*/
- else if (ct->ct_error.re_status == RPC_AUTHERROR)
+ else if (stat == RPC_AUTHERROR)
/* maybe our credentials need to be refreshed ... */
if (nrefreshes > 0 &&
- AUTH_REFRESH(cl->cl_auth, &reply_msg)) {
+ AUTH_REFRESH(auth, &reply_msg)) {
nrefreshes--;
+ XDR_DESTROY(&xdrs);
+ mtx_lock(&ct->ct_lock);
goto call_again;
}
/* end of unsuccessful completion */
} /* end of valid reply message */
else {
- ct->ct_error.re_status = RPC_CANTDECODERES;
+ errp->re_status = stat = RPC_CANTDECODERES;
}
+ XDR_DESTROY(&xdrs);
+ mtx_lock(&ct->ct_lock);
out:
mtx_assert(&ct->ct_lock, MA_OWNED);
+ KASSERT(stat != RPC_SUCCESS || *resultsp,
+ ("RPC_SUCCESS without reply"));
+
if (mreq)
m_freem(mreq);
if (cr->cr_mrep)
@@ -516,9 +568,12 @@ out:
mtx_unlock(&ct->ct_lock);
+ if (auth && stat != RPC_SUCCESS)
+ AUTH_VALIDATE(auth, xid, NULL, NULL);
+
free(cr, M_RPC);
- return (ct->ct_error.re_status);
+ return (stat);
}
static void
@@ -642,7 +697,7 @@ clnt_vc_control(CLIENT *cl, u_int request, void *info)
break;
case CLSET_WAITCHAN:
- ct->ct_waitchan = *(const char **)info;
+ ct->ct_waitchan = (const char *)info;
break;
case CLGET_WAITCHAN:
@@ -673,14 +728,26 @@ clnt_vc_control(CLIENT *cl, u_int request, void *info)
}
static void
-clnt_vc_destroy(CLIENT *cl)
+clnt_vc_close(CLIENT *cl)
{
struct ct_data *ct = (struct ct_data *) cl->cl_private;
struct ct_request *cr;
- struct socket *so = NULL;
mtx_lock(&ct->ct_lock);
+ if (ct->ct_closed) {
+ mtx_unlock(&ct->ct_lock);
+ return;
+ }
+
+ if (ct->ct_closing) {
+ while (ct->ct_closing)
+ msleep(ct, &ct->ct_lock, 0, "rpcclose", 0);
+ KASSERT(ct->ct_closed, ("client should be closed"));
+ mtx_unlock(&ct->ct_lock);
+ return;
+ }
+
if (ct->ct_socket) {
SOCKBUF_LOCK(&ct->ct_socket->so_rcv);
ct->ct_socket->so_upcallarg = NULL;
@@ -701,7 +768,25 @@ clnt_vc_destroy(CLIENT *cl)
while (ct->ct_threads)
msleep(ct, &ct->ct_lock, 0, "rpcclose", 0);
+ }
+
+ ct->ct_closing = FALSE;
+ ct->ct_closed = TRUE;
+ mtx_unlock(&ct->ct_lock);
+ wakeup(ct);
+}
+static void
+clnt_vc_destroy(CLIENT *cl)
+{
+ struct ct_data *ct = (struct ct_data *) cl->cl_private;
+ struct socket *so = NULL;
+
+ clnt_vc_close(cl);
+
+ mtx_lock(&ct->ct_lock);
+
+ if (ct->ct_socket) {
if (ct->ct_closeit) {
so = ct->ct_socket;
}
@@ -738,6 +823,7 @@ clnt_vc_soupcall(struct socket *so, void *arg, int waitflag)
struct ct_request *cr;
int error, rcvflag, foundreq;
uint32_t xid, header;
+ bool_t do_read;
uio.uio_td = curthread;
do {
@@ -746,7 +832,6 @@ clnt_vc_soupcall(struct socket *so, void *arg, int waitflag)
* record mark.
*/
if (ct->ct_record_resid == 0) {
- bool_t do_read;
/*
* Make sure there is either a whole record
@@ -795,7 +880,7 @@ clnt_vc_soupcall(struct socket *so, void *arg, int waitflag)
mtx_unlock(&ct->ct_lock);
break;
}
- memcpy(&header, mtod(m, uint32_t *), sizeof(uint32_t));
+ bcopy(mtod(m, uint32_t *), &header, sizeof(uint32_t));
header = ntohl(header);
ct->ct_record = NULL;
ct->ct_record_resid = header & 0x7fffffff;
@@ -803,6 +888,21 @@ clnt_vc_soupcall(struct socket *so, void *arg, int waitflag)
m_freem(m);
} else {
/*
+ * Wait until the socket has the whole record
+ * buffered.
+ */
+ do_read = FALSE;
+ SOCKBUF_LOCK(&so->so_rcv);
+ if (so->so_rcv.sb_cc >= ct->ct_record_resid
+ || (so->so_rcv.sb_state & SBS_CANTRCVMORE)
+ || so->so_error)
+ do_read = TRUE;
+ SOCKBUF_UNLOCK(&so->so_rcv);
+
+ if (!do_read)
+ return;
+
+ /*
* We have the record mark. Read as much as
* the socket has buffered up to the end of
* this record.
@@ -839,13 +939,14 @@ clnt_vc_soupcall(struct socket *so, void *arg, int waitflag)
* The XID is in the first uint32_t of
* the reply.
*/
- ct->ct_record =
- m_pullup(ct->ct_record, sizeof(xid));
+ if (ct->ct_record->m_len < sizeof(xid))
+ ct->ct_record =
+ m_pullup(ct->ct_record,
+ sizeof(xid));
if (!ct->ct_record)
break;
- memcpy(&xid,
- mtod(ct->ct_record, uint32_t *),
- sizeof(uint32_t));
+ bcopy(mtod(ct->ct_record, uint32_t *),
+ &xid, sizeof(uint32_t));
xid = ntohl(xid);
mtx_lock(&ct->ct_lock);
diff --git a/sys/rpc/replay.c b/sys/rpc/replay.c
new file mode 100644
index 0000000..d82fc20
--- /dev/null
+++ b/sys/rpc/replay.c
@@ -0,0 +1,248 @@
+/*-
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/hash.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+
+#include <rpc/rpc.h>
+#include <rpc/replay.h>
+
+struct replay_cache_entry {
+ int rce_hash;
+ struct rpc_msg rce_msg;
+ struct sockaddr_storage rce_addr;
+ struct rpc_msg rce_repmsg;
+ struct mbuf *rce_repbody;
+
+ TAILQ_ENTRY(replay_cache_entry) rce_link;
+ TAILQ_ENTRY(replay_cache_entry) rce_alllink;
+};
+TAILQ_HEAD(replay_cache_list, replay_cache_entry);
+
+static struct replay_cache_entry *
+ replay_alloc(struct replay_cache *rc, struct rpc_msg *msg,
+ struct sockaddr *addr, int h);
+static void replay_free(struct replay_cache *rc,
+ struct replay_cache_entry *rce);
+static void replay_prune(struct replay_cache *rc);
+
+#define REPLAY_HASH_SIZE 256
+#define REPLAY_MAX 1024
+
+struct replay_cache {
+ struct replay_cache_list rc_cache[REPLAY_HASH_SIZE];
+ struct replay_cache_list rc_all;
+ struct mtx rc_lock;
+ int rc_count;
+ size_t rc_size;
+ size_t rc_maxsize;
+};
+
+struct replay_cache *
+replay_newcache(size_t maxsize)
+{
+ struct replay_cache *rc;
+ int i;
+
+ rc = malloc(sizeof(*rc), M_RPC, M_WAITOK|M_ZERO);
+ for (i = 0; i < REPLAY_HASH_SIZE; i++)
+ TAILQ_INIT(&rc->rc_cache[i]);
+ TAILQ_INIT(&rc->rc_all);
+ mtx_init(&rc->rc_lock, "rc_lock", NULL, MTX_DEF);
+ rc->rc_maxsize = maxsize;
+
+ return (rc);
+}
+
+void
+replay_setsize(struct replay_cache *rc, size_t newmaxsize)
+{
+
+ rc->rc_maxsize = newmaxsize;
+ replay_prune(rc);
+}
+
+void
+replay_freecache(struct replay_cache *rc)
+{
+
+ mtx_lock(&rc->rc_lock);
+ while (TAILQ_FIRST(&rc->rc_all))
+ replay_free(rc, TAILQ_FIRST(&rc->rc_all));
+ mtx_destroy(&rc->rc_lock);
+ free(rc, M_RPC);
+}
+
+static struct replay_cache_entry *
+replay_alloc(struct replay_cache *rc,
+ struct rpc_msg *msg, struct sockaddr *addr, int h)
+{
+ struct replay_cache_entry *rce;
+
+ rc->rc_count++;
+ rce = malloc(sizeof(*rce), M_RPC, M_NOWAIT|M_ZERO);
+ rce->rce_hash = h;
+ rce->rce_msg = *msg;
+ bcopy(addr, &rce->rce_addr, addr->sa_len);
+
+ TAILQ_INSERT_HEAD(&rc->rc_cache[h], rce, rce_link);
+ TAILQ_INSERT_HEAD(&rc->rc_all, rce, rce_alllink);
+
+ return (rce);
+}
+
+static void
+replay_free(struct replay_cache *rc, struct replay_cache_entry *rce)
+{
+
+ rc->rc_count--;
+ TAILQ_REMOVE(&rc->rc_cache[rce->rce_hash], rce, rce_link);
+ TAILQ_REMOVE(&rc->rc_all, rce, rce_alllink);
+ if (rce->rce_repbody) {
+ rc->rc_size -= m_length(rce->rce_repbody, NULL);
+ m_freem(rce->rce_repbody);
+ }
+ free(rce, M_RPC);
+}
+
+static void
+replay_prune(struct replay_cache *rc)
+{
+ struct replay_cache_entry *rce;
+ bool_t freed_one;
+
+ if (rc->rc_count >= REPLAY_MAX || rc->rc_size > rc->rc_maxsize) {
+ freed_one = FALSE;
+ do {
+ /*
+ * Try to free an entry. Don't free in-progress entries
+ */
+ TAILQ_FOREACH_REVERSE(rce, &rc->rc_all,
+ replay_cache_list, rce_alllink) {
+ if (rce->rce_repmsg.rm_xid) {
+ replay_free(rc, rce);
+ freed_one = TRUE;
+ break;
+ }
+ }
+ } while (freed_one
+ && (rc->rc_count >= REPLAY_MAX
+ || rc->rc_size > rc->rc_maxsize));
+ }
+}
+
+enum replay_state
+replay_find(struct replay_cache *rc, struct rpc_msg *msg,
+ struct sockaddr *addr, struct rpc_msg *repmsg, struct mbuf **mp)
+{
+ int h = HASHSTEP(HASHINIT, msg->rm_xid) % REPLAY_HASH_SIZE;
+ struct replay_cache_entry *rce;
+
+ mtx_lock(&rc->rc_lock);
+ TAILQ_FOREACH(rce, &rc->rc_cache[h], rce_link) {
+ if (rce->rce_msg.rm_xid == msg->rm_xid
+ && rce->rce_msg.rm_call.cb_prog == msg->rm_call.cb_prog
+ && rce->rce_msg.rm_call.cb_vers == msg->rm_call.cb_vers
+ && rce->rce_msg.rm_call.cb_proc == msg->rm_call.cb_proc
+ && rce->rce_addr.ss_len == addr->sa_len
+ && bcmp(&rce->rce_addr, addr, addr->sa_len) == 0) {
+ if (rce->rce_repmsg.rm_xid) {
+ /*
+ * We have a reply for this
+ * message. Copy it and return. Keep
+ * replay_all LRU sorted
+ */
+ TAILQ_REMOVE(&rc->rc_all, rce, rce_alllink);
+ TAILQ_INSERT_HEAD(&rc->rc_all, rce,
+ rce_alllink);
+ *repmsg = rce->rce_repmsg;
+ if (rce->rce_repbody) {
+ *mp = m_copym(rce->rce_repbody,
+ 0, M_COPYALL, M_NOWAIT);
+ mtx_unlock(&rc->rc_lock);
+ if (!*mp)
+ return (RS_ERROR);
+ } else {
+ mtx_unlock(&rc->rc_lock);
+ }
+ return (RS_DONE);
+ } else {
+ mtx_unlock(&rc->rc_lock);
+ return (RS_INPROGRESS);
+ }
+ }
+ }
+
+ replay_prune(rc);
+
+ rce = replay_alloc(rc, msg, addr, h);
+
+ mtx_unlock(&rc->rc_lock);
+
+ if (!rce)
+ return (RS_ERROR);
+ else
+ return (RS_NEW);
+}
+
+void
+replay_setreply(struct replay_cache *rc,
+ struct rpc_msg *repmsg, struct sockaddr *addr, struct mbuf *m)
+{
+ int h = HASHSTEP(HASHINIT, repmsg->rm_xid) % REPLAY_HASH_SIZE;
+ struct replay_cache_entry *rce;
+
+ /*
+ * Copy the reply before the lock so we can sleep.
+ */
+ if (m)
+ m = m_copym(m, 0, M_COPYALL, M_WAITOK);
+
+ mtx_lock(&rc->rc_lock);
+ TAILQ_FOREACH(rce, &rc->rc_cache[h], rce_link) {
+ if (rce->rce_msg.rm_xid == repmsg->rm_xid
+ && rce->rce_addr.ss_len == addr->sa_len
+ && bcmp(&rce->rce_addr, addr, addr->sa_len) == 0) {
+ break;
+ }
+ }
+ if (rce) {
+ rce->rce_repmsg = *repmsg;
+ rce->rce_repbody = m;
+ if (m)
+ rc->rc_size += m_length(m, NULL);
+ }
+ mtx_unlock(&rc->rc_lock);
+}
diff --git a/sys/rpc/replay.h b/sys/rpc/replay.h
new file mode 100644
index 0000000..0ef7bf3
--- /dev/null
+++ b/sys/rpc/replay.h
@@ -0,0 +1,85 @@
+/*-
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _RPC_REPLAY_H
+#define _RPC_REPLAY_H
+
+enum replay_state {
+ RS_NEW, /* new request - caller should execute */
+ RS_DONE, /* request was executed and reply sent */
+ RS_INPROGRESS, /* request is being executed now */
+ RS_ERROR /* allocation or other failure */
+};
+
+struct replay_cache;
+
+/*
+ * Create a new replay cache.
+ */
+struct replay_cache *replay_newcache(size_t);
+
+/*
+ * Set the replay cache size.
+ */
+void replay_setsize(struct replay_cache *, size_t);
+
+/*
+ * Free a replay cache. Caller must ensure that no cache entries are
+ * in-progress.
+ */
+void replay_freecache(struct replay_cache *rc);
+
+/*
+ * Check a replay cache for a message from a given address.
+ *
+ * If this is a new request, RS_NEW is returned. Caller should call
+ * replay_setreply with the results of the request.
+ *
+ * If this is a request which is currently executing
+ * (i.e. replay_setreply hasn't been called for it yet), RS_INPROGRESS
+ * is returned. The caller should silently drop the request.
+ *
+ * If a reply to this message already exists, *repmsg and *mp are set
+ * to point at the reply and, RS_DONE is returned. The caller should
+ * re-send this reply.
+ *
+ * If the attempt to update the replay cache or copy a replay failed
+ * for some reason (typically memory shortage), RS_ERROR is returned.
+ */
+enum replay_state replay_find(struct replay_cache *rc,
+ struct rpc_msg *msg, struct sockaddr *addr,
+ struct rpc_msg *repmsg, struct mbuf **mp);
+
+/*
+ * Call this after executing a request to record the reply.
+ */
+void replay_setreply(struct replay_cache *rc,
+ struct rpc_msg *repmsg, struct sockaddr *addr, struct mbuf *m);
+
+#endif /* !_RPC_REPLAY_H */
diff --git a/sys/rpc/rpc_com.h b/sys/rpc/rpc_com.h
index ad9cc68..e50e513 100644
--- a/sys/rpc/rpc_com.h
+++ b/sys/rpc/rpc_com.h
@@ -115,6 +115,7 @@ extern const char *__rpc_inet_ntop(int af, const void * __restrict src,
char * __restrict dst, socklen_t size);
extern int __rpc_inet_pton(int af, const char * __restrict src,
void * __restrict dst);
+extern int bindresvport(struct socket *so, struct sockaddr *sa);
struct xucred;
struct __rpc_xdr;
diff --git a/sys/rpc/rpc_generic.c b/sys/rpc/rpc_generic.c
index ee8ee8a..d9100b3 100644
--- a/sys/rpc/rpc_generic.c
+++ b/sys/rpc/rpc_generic.c
@@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
+#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/protosw.h>
@@ -722,6 +723,139 @@ __rpc_sockisbound(struct socket *so)
}
/*
+ * Implement XDR-style API for RPC call.
+ */
+enum clnt_stat
+clnt_call_private(
+ CLIENT *cl, /* client handle */
+ struct rpc_callextra *ext, /* call metadata */
+ rpcproc_t proc, /* procedure number */
+ xdrproc_t xargs, /* xdr routine for args */
+ void *argsp, /* pointer to args */
+ xdrproc_t xresults, /* xdr routine for results */
+ void *resultsp, /* pointer to results */
+ struct timeval utimeout) /* seconds to wait before giving up */
+{
+ XDR xdrs;
+ struct mbuf *mreq;
+ struct mbuf *mrep;
+ enum clnt_stat stat;
+
+ MGET(mreq, M_WAIT, MT_DATA);
+ MCLGET(mreq, M_WAIT);
+ mreq->m_len = 0;
+
+ xdrmbuf_create(&xdrs, mreq, XDR_ENCODE);
+ if (!xargs(&xdrs, argsp)) {
+ m_freem(mreq);
+ return (RPC_CANTENCODEARGS);
+ }
+ XDR_DESTROY(&xdrs);
+
+ stat = CLNT_CALL_MBUF(cl, ext, proc, mreq, &mrep, utimeout);
+ m_freem(mreq);
+
+ if (stat == RPC_SUCCESS) {
+ xdrmbuf_create(&xdrs, mrep, XDR_DECODE);
+ if (!xresults(&xdrs, resultsp)) {
+ XDR_DESTROY(&xdrs);
+ return (RPC_CANTDECODERES);
+ }
+ XDR_DESTROY(&xdrs);
+ }
+
+ return (stat);
+}
+
+/*
+ * Bind a socket to a privileged IP port
+ */
+int
+bindresvport(struct socket *so, struct sockaddr *sa)
+{
+ int old, error, af;
+ bool_t freesa = FALSE;
+ struct sockaddr_in *sin;
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+ struct sockopt opt;
+ int proto, portrange, portlow;
+ u_int16_t *portp;
+ socklen_t salen;
+
+ if (sa == NULL) {
+ error = so->so_proto->pr_usrreqs->pru_sockaddr(so, &sa);
+ if (error)
+ return (error);
+ freesa = TRUE;
+ af = sa->sa_family;
+ salen = sa->sa_len;
+ memset(sa, 0, sa->sa_len);
+ } else {
+ af = sa->sa_family;
+ salen = sa->sa_len;
+ }
+
+ switch (af) {
+ case AF_INET:
+ proto = IPPROTO_IP;
+ portrange = IP_PORTRANGE;
+ portlow = IP_PORTRANGE_LOW;
+ sin = (struct sockaddr_in *)sa;
+ portp = &sin->sin_port;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ proto = IPPROTO_IPV6;
+ portrange = IPV6_PORTRANGE;
+ portlow = IPV6_PORTRANGE_LOW;
+ sin6 = (struct sockaddr_in6 *)sa;
+ portp = &sin6->sin6_port;
+ break;
+#endif
+ default:
+ return (EPFNOSUPPORT);
+ }
+
+ sa->sa_family = af;
+ sa->sa_len = salen;
+
+ if (*portp == 0) {
+ bzero(&opt, sizeof(opt));
+ opt.sopt_dir = SOPT_GET;
+ opt.sopt_level = proto;
+ opt.sopt_name = portrange;
+ opt.sopt_val = &old;
+ opt.sopt_valsize = sizeof(old);
+ error = sogetopt(so, &opt);
+ if (error)
+ goto out;
+
+ opt.sopt_dir = SOPT_SET;
+ opt.sopt_val = &portlow;
+ error = sosetopt(so, &opt);
+ if (error)
+ goto out;
+ }
+
+ error = sobind(so, sa, curthread);
+
+ if (*portp == 0) {
+ if (error) {
+ opt.sopt_dir = SOPT_SET;
+ opt.sopt_val = &old;
+ sosetopt(so, &opt);
+ }
+ }
+out:
+ if (freesa)
+ free(sa, M_SONAME);
+
+ return (error);
+}
+
+/*
* Kernel module glue
*/
static int
diff --git a/sys/rpc/rpc_msg.h b/sys/rpc/rpc_msg.h
index 707250a..ff2a6d8 100644
--- a/sys/rpc/rpc_msg.h
+++ b/sys/rpc/rpc_msg.h
@@ -208,7 +208,7 @@ extern bool_t xdr_rejected_reply(XDR *, struct rejected_reply *);
* struct rpc_msg *msg;
* struct rpc_err *error;
*/
-extern void _seterr_reply(struct rpc_msg *, struct rpc_err *);
+extern enum clnt_stat _seterr_reply(struct rpc_msg *, struct rpc_err *);
__END_DECLS
#endif /* !_RPC_RPC_MSG_H */
diff --git a/sys/rpc/rpc_prot.c b/sys/rpc/rpc_prot.c
index 16f602f..294c4e3 100644
--- a/sys/rpc/rpc_prot.c
+++ b/sys/rpc/rpc_prot.c
@@ -64,8 +64,8 @@ MALLOC_DEFINE(M_RPC, "rpc", "Remote Procedure Call");
#define assert(exp) KASSERT(exp, ("bad arguments"))
-static void accepted(enum accept_stat, struct rpc_err *);
-static void rejected(enum reject_stat, struct rpc_err *);
+static enum clnt_stat accepted(enum accept_stat, struct rpc_err *);
+static enum clnt_stat rejected(enum reject_stat, struct rpc_err *);
/* * * * * * * * * * * * * * XDR Authentication * * * * * * * * * * * */
@@ -111,7 +111,11 @@ xdr_accepted_reply(XDR *xdrs, struct accepted_reply *ar)
switch (ar->ar_stat) {
case SUCCESS:
- return ((*(ar->ar_results.proc))(xdrs, ar->ar_results.where));
+ if (ar->ar_results.proc != (xdrproc_t) xdr_void)
+ return ((*(ar->ar_results.proc))(xdrs,
+ ar->ar_results.where));
+ else
+ return (TRUE);
case PROG_MISMATCH:
if (! xdr_uint32_t(xdrs, &(ar->ar_vers.low)))
@@ -171,12 +175,34 @@ static const struct xdr_discrim reply_dscrm[3] = {
bool_t
xdr_replymsg(XDR *xdrs, struct rpc_msg *rmsg)
{
+ int32_t *buf;
enum msg_type *prm_direction;
enum reply_stat *prp_stat;
assert(xdrs != NULL);
assert(rmsg != NULL);
+ if (xdrs->x_op == XDR_DECODE) {
+ buf = XDR_INLINE(xdrs, 3 * BYTES_PER_XDR_UNIT);
+ if (buf != NULL) {
+ rmsg->rm_xid = IXDR_GET_UINT32(buf);
+ rmsg->rm_direction = IXDR_GET_ENUM(buf, enum msg_type);
+ if (rmsg->rm_direction != REPLY) {
+ return (FALSE);
+ }
+ rmsg->rm_reply.rp_stat =
+ IXDR_GET_ENUM(buf, enum reply_stat);
+ if (rmsg->rm_reply.rp_stat == MSG_ACCEPTED)
+ return (xdr_accepted_reply(xdrs,
+ &rmsg->acpted_rply));
+ else if (rmsg->rm_reply.rp_stat == MSG_DENIED)
+ return (xdr_rejected_reply(xdrs,
+ &rmsg->rjcted_rply));
+ else
+ return (FALSE);
+ }
+ }
+
prm_direction = &rmsg->rm_direction;
prp_stat = &rmsg->rm_reply.rp_stat;
@@ -220,7 +246,7 @@ xdr_callhdr(XDR *xdrs, struct rpc_msg *cmsg)
/* ************************** Client utility routine ************* */
-static void
+static enum clnt_stat
accepted(enum accept_stat acpt_stat, struct rpc_err *error)
{
@@ -230,36 +256,32 @@ accepted(enum accept_stat acpt_stat, struct rpc_err *error)
case PROG_UNAVAIL:
error->re_status = RPC_PROGUNAVAIL;
- return;
+ return (RPC_PROGUNAVAIL);
case PROG_MISMATCH:
error->re_status = RPC_PROGVERSMISMATCH;
- return;
+ return (RPC_PROGVERSMISMATCH);
case PROC_UNAVAIL:
- error->re_status = RPC_PROCUNAVAIL;
- return;
+ return (RPC_PROCUNAVAIL);
case GARBAGE_ARGS:
- error->re_status = RPC_CANTDECODEARGS;
- return;
+ return (RPC_CANTDECODEARGS);
case SYSTEM_ERR:
- error->re_status = RPC_SYSTEMERROR;
- return;
+ return (RPC_SYSTEMERROR);
case SUCCESS:
- error->re_status = RPC_SUCCESS;
- return;
+ return (RPC_SUCCESS);
}
/* NOTREACHED */
/* something's wrong, but we don't know what ... */
- error->re_status = RPC_FAILED;
error->re_lb.s1 = (int32_t)MSG_ACCEPTED;
error->re_lb.s2 = (int32_t)acpt_stat;
+ return (RPC_FAILED);
}
-static void
+static enum clnt_stat
rejected(enum reject_stat rjct_stat, struct rpc_err *error)
{
@@ -267,26 +289,25 @@ rejected(enum reject_stat rjct_stat, struct rpc_err *error)
switch (rjct_stat) {
case RPC_MISMATCH:
- error->re_status = RPC_VERSMISMATCH;
- return;
+ return (RPC_VERSMISMATCH);
case AUTH_ERROR:
- error->re_status = RPC_AUTHERROR;
- return;
+ return (RPC_AUTHERROR);
}
/* something's wrong, but we don't know what ... */
/* NOTREACHED */
- error->re_status = RPC_FAILED;
error->re_lb.s1 = (int32_t)MSG_DENIED;
error->re_lb.s2 = (int32_t)rjct_stat;
+ return (RPC_FAILED);
}
/*
* given a reply message, fills in the error
*/
-void
+enum clnt_stat
_seterr_reply(struct rpc_msg *msg, struct rpc_err *error)
{
+ enum clnt_stat stat;
assert(msg != NULL);
assert(error != NULL);
@@ -296,22 +317,24 @@ _seterr_reply(struct rpc_msg *msg, struct rpc_err *error)
case MSG_ACCEPTED:
if (msg->acpted_rply.ar_stat == SUCCESS) {
- error->re_status = RPC_SUCCESS;
- return;
+ stat = RPC_SUCCESS;
+ return (stat);
}
- accepted(msg->acpted_rply.ar_stat, error);
+ stat = accepted(msg->acpted_rply.ar_stat, error);
break;
case MSG_DENIED:
- rejected(msg->rjcted_rply.rj_stat, error);
+ stat = rejected(msg->rjcted_rply.rj_stat, error);
break;
default:
- error->re_status = RPC_FAILED;
+ stat = RPC_FAILED;
error->re_lb.s1 = (int32_t)(msg->rm_reply.rp_stat);
break;
}
- switch (error->re_status) {
+ error->re_status = stat;
+
+ switch (stat) {
case RPC_VERSMISMATCH:
error->re_vers.low = msg->rjcted_rply.rj_vers.low;
@@ -345,4 +368,6 @@ _seterr_reply(struct rpc_msg *msg, struct rpc_err *error)
default:
break;
}
+
+ return (stat);
}
diff --git a/sys/rpc/rpcsec_gss.h b/sys/rpc/rpcsec_gss.h
new file mode 100644
index 0000000..563205c
--- /dev/null
+++ b/sys/rpc/rpcsec_gss.h
@@ -0,0 +1,189 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _RPCSEC_GSS_H
+#define _RPCSEC_GSS_H
+
+#include <kgssapi/gssapi.h>
+
+#ifndef MAX_GSS_MECH
+#define MAX_GSS_MECH 64
+#endif
+
+/*
+ * Define the types of security service required for rpc_gss_seccreate().
+ */
+typedef enum {
+ rpc_gss_svc_default = 0,
+ rpc_gss_svc_none = 1,
+ rpc_gss_svc_integrity = 2,
+ rpc_gss_svc_privacy = 3
+} rpc_gss_service_t;
+
+/*
+ * Structure containing options for rpc_gss_seccreate().
+ */
+typedef struct {
+ int req_flags; /* GSS request bits */
+ int time_req; /* requested credential lifetime */
+ gss_cred_id_t my_cred; /* GSS credential */
+ gss_channel_bindings_t input_channel_bindings;
+} rpc_gss_options_req_t;
+
+/*
+ * Structure containing options returned by rpc_gss_seccreate().
+ */
+typedef struct {
+ int major_status;
+ int minor_status;
+ u_int rpcsec_version;
+ int ret_flags;
+ int time_req;
+ gss_ctx_id_t gss_context;
+ char actual_mechanism[MAX_GSS_MECH];
+} rpc_gss_options_ret_t;
+
+/*
+ * Client principal type. Used as an argument to
+ * rpc_gss_get_principal_name(). Also referenced by the
+ * rpc_gss_rawcred_t structure.
+ */
+typedef struct {
+ int len;
+ char name[1];
+} *rpc_gss_principal_t;
+
+/*
+ * Structure for raw credentials used by rpc_gss_getcred() and
+ * rpc_gss_set_callback().
+ */
+typedef struct {
+ u_int version; /* RPC version number */
+ const char *mechanism; /* security mechanism */
+ const char *qop; /* quality of protection */
+ rpc_gss_principal_t client_principal; /* client name */
+ const char *svc_principal; /* server name */
+ rpc_gss_service_t service; /* service type */
+} rpc_gss_rawcred_t;
+
+/*
+ * Unix credentials derived from raw credentials. Returned by
+ * rpc_gss_getcred().
+ */
+typedef struct {
+ uid_t uid; /* user ID */
+ gid_t gid; /* group ID */
+ short gidlen;
+ gid_t *gidlist; /* list of groups */
+} rpc_gss_ucred_t;
+
+/*
+ * Structure used to enforce a particular QOP and service.
+ */
+typedef struct {
+ bool_t locked;
+ rpc_gss_rawcred_t *raw_cred;
+} rpc_gss_lock_t;
+
+/*
+ * Callback structure used by rpc_gss_set_callback().
+ */
+typedef struct {
+ u_int program; /* RPC program number */
+ u_int version; /* RPC version number */
+ /* user defined callback */
+ bool_t (*callback)(struct svc_req *req,
+ gss_cred_id_t deleg,
+ gss_ctx_id_t gss_context,
+ rpc_gss_lock_t *lock,
+ void **cookie);
+} rpc_gss_callback_t;
+
+/*
+ * Structure used to return error information by rpc_gss_get_error()
+ */
+typedef struct {
+ int rpc_gss_error;
+ int system_error; /* same as errno */
+} rpc_gss_error_t;
+
+/*
+ * Values for rpc_gss_error
+ */
+#define RPC_GSS_ER_SUCCESS 0 /* no error */
+#define RPC_GSS_ER_SYSTEMERROR 1 /* system error */
+
+__BEGIN_DECLS
+
+#ifdef _KERNEL
+AUTH *rpc_gss_secfind(CLIENT *clnt, struct ucred *cred,
+ const char *principal, gss_OID mech_oid, rpc_gss_service_t service);
+void rpc_gss_secpurge(CLIENT *clnt);
+#endif
+AUTH *rpc_gss_seccreate(CLIENT *clnt, struct ucred *cred,
+ const char *principal, const char *mechanism, rpc_gss_service_t service,
+ const char *qop, rpc_gss_options_req_t *options_req,
+ rpc_gss_options_ret_t *options_ret);
+bool_t rpc_gss_set_defaults(AUTH *auth, rpc_gss_service_t service,
+ const char *qop);
+int rpc_gss_max_data_length(AUTH *handle, int max_tp_unit_len);
+void rpc_gss_get_error(rpc_gss_error_t *error);
+
+bool_t rpc_gss_mech_to_oid(const char *mech, gss_OID *oid_ret);
+bool_t rpc_gss_oid_to_mech(gss_OID oid, const char **mech_ret);
+bool_t rpc_gss_qop_to_num(const char *qop, const char *mech, u_int *num_ret);
+const char **rpc_gss_get_mechanisms(void);
+const char **rpc_gss_get_mech_info(const char *mech, rpc_gss_service_t *service);
+bool_t rpc_gss_get_versions(u_int *vers_hi, u_int *vers_lo);
+bool_t rpc_gss_is_installed(const char *mech);
+
+bool_t rpc_gss_set_svc_name(const char *principal, const char *mechanism,
+ u_int req_time, u_int program, u_int version);
+void rpc_gss_clear_svc_name(u_int program, u_int version);
+bool_t rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
+ rpc_gss_ucred_t **ucred, void **cookie);
+bool_t rpc_gss_set_callback(rpc_gss_callback_t *cb);
+void rpc_gss_clear_callback(rpc_gss_callback_t *cb);
+bool_t rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
+ const char *mech, const char *name, const char *node, const char *domain);
+int rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len);
+
+/*
+ * Internal interface from the RPC implementation.
+ */
+#ifndef _KERNEL
+bool_t __rpc_gss_wrap(AUTH *auth, void *header, size_t headerlen,
+ XDR* xdrs, xdrproc_t xdr_args, void *args_ptr);
+bool_t __rpc_gss_unwrap(AUTH *auth, XDR* xdrs, xdrproc_t xdr_args,
+ void *args_ptr);
+#endif
+bool_t __rpc_gss_set_error(int rpc_gss_error, int system_error);
+
+__END_DECLS
+
+#endif /* !_RPCSEC_GSS_H */
diff --git a/sys/rpc/rpcsec_gss/rpcsec_gss.c b/sys/rpc/rpcsec_gss/rpcsec_gss.c
new file mode 100644
index 0000000..790804d
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/rpcsec_gss.c
@@ -0,0 +1,1064 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ auth_gss.c
+
+ RPCSEC_GSS client routines.
+
+ Copyright (c) 2000 The Regents of the University of Michigan.
+ All rights reserved.
+
+ Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+ All rights reserved, all wrongs reversed.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ $Id: auth_gss.c,v 1.32 2002/01/15 15:43:00 andros Exp $
+*/
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/hash.h>
+#include <sys/kernel.h>
+#include <sys/kobj.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/refcount.h>
+#include <sys/sx.h>
+#include <sys/ucred.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+#include "rpcsec_gss_int.h"
+
+static void rpc_gss_nextverf(AUTH*);
+static bool_t rpc_gss_marshal(AUTH *, uint32_t, XDR *, struct mbuf *);
+static bool_t rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret);
+static bool_t rpc_gss_refresh(AUTH *, void *);
+static bool_t rpc_gss_validate(AUTH *, uint32_t, struct opaque_auth *,
+ struct mbuf **);
+static void rpc_gss_destroy(AUTH *);
+static void rpc_gss_destroy_context(AUTH *, bool_t);
+
+static struct auth_ops rpc_gss_ops = {
+ rpc_gss_nextverf,
+ rpc_gss_marshal,
+ rpc_gss_validate,
+ rpc_gss_refresh,
+ rpc_gss_destroy,
+};
+
+enum rpcsec_gss_state {
+ RPCSEC_GSS_START,
+ RPCSEC_GSS_CONTEXT,
+ RPCSEC_GSS_ESTABLISHED,
+ RPCSEC_GSS_DESTROYING
+};
+
+struct rpc_pending_request {
+ uint32_t pr_xid; /* XID of rpc */
+ uint32_t pr_seq; /* matching GSS seq */
+ LIST_ENTRY(rpc_pending_request) pr_link;
+};
+LIST_HEAD(rpc_pending_request_list, rpc_pending_request);
+
+struct rpc_gss_data {
+ volatile u_int gd_refs; /* number of current users */
+ struct mtx gd_lock;
+ uint32_t gd_hash;
+ AUTH *gd_auth; /* link back to AUTH */
+ struct ucred *gd_ucred; /* matching local cred */
+ char *gd_principal; /* server principal name */
+ rpc_gss_options_req_t gd_options; /* GSS context options */
+ enum rpcsec_gss_state gd_state; /* connection state */
+ gss_buffer_desc gd_verf; /* save GSS_S_COMPLETE
+ * NULL RPC verfier to
+ * process at end of
+ * context negotiation */
+ CLIENT *gd_clnt; /* client handle */
+ gss_OID gd_mech; /* mechanism to use */
+ gss_qop_t gd_qop; /* quality of protection */
+ gss_ctx_id_t gd_ctx; /* context id */
+ struct rpc_gss_cred gd_cred; /* client credentials */
+ uint32_t gd_seq; /* next sequence number */
+ u_int gd_win; /* sequence window */
+ struct rpc_pending_request_list gd_reqs;
+ TAILQ_ENTRY(rpc_gss_data) gd_link;
+ TAILQ_ENTRY(rpc_gss_data) gd_alllink;
+};
+TAILQ_HEAD(rpc_gss_data_list, rpc_gss_data);
+
+#define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private)
+
+static struct timeval AUTH_TIMEOUT = { 25, 0 };
+
+#define RPC_GSS_HASH_SIZE 11
+#define RPC_GSS_MAX 256
+static struct rpc_gss_data_list rpc_gss_cache[RPC_GSS_HASH_SIZE];
+static struct rpc_gss_data_list rpc_gss_all;
+static struct sx rpc_gss_lock;
+static int rpc_gss_count;
+
+static AUTH *rpc_gss_seccreate_int(CLIENT *, struct ucred *, const char *,
+ gss_OID, rpc_gss_service_t, u_int, rpc_gss_options_req_t *,
+ rpc_gss_options_ret_t *);
+
+static void
+rpc_gss_hashinit(void *dummy)
+{
+ int i;
+
+ for (i = 0; i < RPC_GSS_HASH_SIZE; i++)
+ TAILQ_INIT(&rpc_gss_cache[i]);
+ TAILQ_INIT(&rpc_gss_all);
+ sx_init(&rpc_gss_lock, "rpc_gss_lock");
+}
+SYSINIT(rpc_gss_hashinit, SI_SUB_KMEM, SI_ORDER_ANY, rpc_gss_hashinit, NULL);
+
+static uint32_t
+rpc_gss_hash(const char *principal, gss_OID mech,
+ struct ucred *cred, rpc_gss_service_t service)
+{
+ uint32_t h;
+
+ h = HASHSTEP(HASHINIT, cred->cr_uid);
+ h = hash32_str(principal, h);
+ h = hash32_buf(mech->elements, mech->length, h);
+ h = HASHSTEP(h, (int) service);
+
+ return (h % RPC_GSS_HASH_SIZE);
+}
+
+/*
+ * Simplified interface to create a security association for the
+ * current thread's * ucred.
+ */
+AUTH *
+rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
+ gss_OID mech_oid, rpc_gss_service_t service)
+{
+ uint32_t h, th;
+ AUTH *auth;
+ struct rpc_gss_data *gd, *tgd;
+
+ if (rpc_gss_count > RPC_GSS_MAX) {
+ while (rpc_gss_count > RPC_GSS_MAX) {
+ sx_xlock(&rpc_gss_lock);
+ tgd = TAILQ_FIRST(&rpc_gss_all);
+ th = tgd->gd_hash;
+ TAILQ_REMOVE(&rpc_gss_cache[th], tgd, gd_link);
+ TAILQ_REMOVE(&rpc_gss_all, tgd, gd_alllink);
+ rpc_gss_count--;
+ sx_xunlock(&rpc_gss_lock);
+ AUTH_DESTROY(tgd->gd_auth);
+ }
+ }
+
+ /*
+ * See if we already have an AUTH which matches.
+ */
+ h = rpc_gss_hash(principal, mech_oid, cred, service);
+
+again:
+ sx_slock(&rpc_gss_lock);
+ TAILQ_FOREACH(gd, &rpc_gss_cache[h], gd_link) {
+ if (gd->gd_ucred->cr_uid == cred->cr_uid
+ && !strcmp(gd->gd_principal, principal)
+ && gd->gd_mech == mech_oid
+ && gd->gd_cred.gc_svc == service) {
+ refcount_acquire(&gd->gd_refs);
+ if (sx_try_upgrade(&rpc_gss_lock)) {
+ /*
+ * Keep rpc_gss_all LRU sorted.
+ */
+ TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
+ TAILQ_INSERT_TAIL(&rpc_gss_all, gd,
+ gd_alllink);
+ sx_xunlock(&rpc_gss_lock);
+ } else {
+ sx_sunlock(&rpc_gss_lock);
+ }
+ return (gd->gd_auth);
+ }
+ }
+ sx_sunlock(&rpc_gss_lock);
+
+ /*
+ * We missed in the cache - create a new association.
+ */
+ auth = rpc_gss_seccreate_int(clnt, cred, principal, mech_oid, service,
+ GSS_C_QOP_DEFAULT, NULL, NULL);
+ if (!auth)
+ return (NULL);
+
+ gd = AUTH_PRIVATE(auth);
+ gd->gd_hash = h;
+
+ sx_xlock(&rpc_gss_lock);
+ TAILQ_FOREACH(tgd, &rpc_gss_cache[h], gd_link) {
+ if (tgd->gd_ucred->cr_uid == cred->cr_uid
+ && !strcmp(tgd->gd_principal, principal)
+ && tgd->gd_mech == mech_oid
+ && tgd->gd_cred.gc_svc == service) {
+ /*
+ * We lost a race to create the AUTH that
+ * matches this cred.
+ */
+ sx_xunlock(&rpc_gss_lock);
+ AUTH_DESTROY(auth);
+ goto again;
+ }
+ }
+
+ rpc_gss_count++;
+ TAILQ_INSERT_TAIL(&rpc_gss_cache[h], gd, gd_link);
+ TAILQ_INSERT_TAIL(&rpc_gss_all, gd, gd_alllink);
+ refcount_acquire(&gd->gd_refs); /* one for the cache, one for user */
+ sx_xunlock(&rpc_gss_lock);
+
+ return (auth);
+}
+
+void
+rpc_gss_secpurge(CLIENT *clnt)
+{
+ uint32_t h;
+ struct rpc_gss_data *gd, *tgd;
+
+ TAILQ_FOREACH_SAFE(gd, &rpc_gss_all, gd_alllink, tgd) {
+ if (gd->gd_clnt == clnt) {
+ sx_xlock(&rpc_gss_lock);
+ h = gd->gd_hash;
+ TAILQ_REMOVE(&rpc_gss_cache[h], gd, gd_link);
+ TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
+ rpc_gss_count--;
+ sx_xunlock(&rpc_gss_lock);
+ AUTH_DESTROY(gd->gd_auth);
+ }
+ }
+}
+
+AUTH *
+rpc_gss_seccreate(CLIENT *clnt, struct ucred *cred, const char *principal,
+ const char *mechanism, rpc_gss_service_t service, const char *qop,
+ rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
+{
+ gss_OID oid;
+ u_int qop_num;
+
+ /*
+ * Bail out now if we don't know this mechanism.
+ */
+ if (!rpc_gss_mech_to_oid(mechanism, &oid))
+ return (NULL);
+
+ if (qop) {
+ if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num))
+ return (NULL);
+ } else {
+ qop_num = GSS_C_QOP_DEFAULT;
+ }
+
+ return (rpc_gss_seccreate_int(clnt, cred, principal, oid, service,
+ qop_num, options_req, options_ret));
+}
+
+static AUTH *
+rpc_gss_seccreate_int(CLIENT *clnt, struct ucred *cred, const char *principal,
+ gss_OID mech_oid, rpc_gss_service_t service, u_int qop_num,
+ rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
+{
+ AUTH *auth;
+ rpc_gss_options_ret_t options;
+ struct rpc_gss_data *gd;
+
+ /*
+ * If the caller doesn't want the options, point at local
+ * storage to simplify the code below.
+ */
+ if (!options_ret)
+ options_ret = &options;
+
+ /*
+ * Default service is integrity.
+ */
+ if (service == rpc_gss_svc_default)
+ service = rpc_gss_svc_integrity;
+
+ memset(options_ret, 0, sizeof(*options_ret));
+
+ rpc_gss_log_debug("in rpc_gss_seccreate()");
+
+ memset(&rpc_createerr, 0, sizeof(rpc_createerr));
+
+ auth = mem_alloc(sizeof(*auth));
+ if (auth == NULL) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ return (NULL);
+ }
+ gd = mem_alloc(sizeof(*gd));
+ if (gd == NULL) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ mem_free(auth, sizeof(*auth));
+ return (NULL);
+ }
+
+ auth->ah_ops = &rpc_gss_ops;
+ auth->ah_private = (caddr_t) gd;
+ auth->ah_cred.oa_flavor = RPCSEC_GSS;
+
+ refcount_init(&gd->gd_refs, 1);
+ mtx_init(&gd->gd_lock, "gd->gd_lock", NULL, MTX_DEF);
+ gd->gd_auth = auth;
+ gd->gd_ucred = crdup(cred);
+ gd->gd_principal = strdup(principal, M_RPC);
+
+
+ if (options_req) {
+ gd->gd_options = *options_req;
+ } else {
+ gd->gd_options.req_flags = GSS_C_MUTUAL_FLAG;
+ gd->gd_options.time_req = 0;
+ gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
+ gd->gd_options.input_channel_bindings = NULL;
+ }
+ CLNT_ACQUIRE(clnt);
+ gd->gd_clnt = clnt;
+ gd->gd_ctx = GSS_C_NO_CONTEXT;
+ gd->gd_mech = mech_oid;
+ gd->gd_qop = qop_num;
+
+ gd->gd_cred.gc_version = RPCSEC_GSS_VERSION;
+ gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
+ gd->gd_cred.gc_seq = 0;
+ gd->gd_cred.gc_svc = service;
+ LIST_INIT(&gd->gd_reqs);
+
+ if (!rpc_gss_init(auth, options_ret)) {
+ goto bad;
+ }
+
+ return (auth);
+
+ bad:
+ AUTH_DESTROY(auth);
+ return (NULL);
+}
+
+bool_t
+rpc_gss_set_defaults(AUTH *auth, rpc_gss_service_t service, const char *qop)
+{
+ struct rpc_gss_data *gd;
+ u_int qop_num;
+ const char *mechanism;
+
+ gd = AUTH_PRIVATE(auth);
+ if (!rpc_gss_oid_to_mech(gd->gd_mech, &mechanism)) {
+ return (FALSE);
+ }
+
+ if (qop) {
+ if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num)) {
+ return (FALSE);
+ }
+ } else {
+ qop_num = GSS_C_QOP_DEFAULT;
+ }
+
+ gd->gd_cred.gc_svc = service;
+ gd->gd_qop = qop_num;
+ return (TRUE);
+}
+
+static void
+rpc_gss_purge_xid(struct rpc_gss_data *gd, uint32_t xid)
+{
+ struct rpc_pending_request *pr, *npr;
+ struct rpc_pending_request_list reqs;
+
+ LIST_INIT(&reqs);
+ mtx_lock(&gd->gd_lock);
+ LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
+ if (pr->pr_xid == xid) {
+ LIST_REMOVE(pr, pr_link);
+ LIST_INSERT_HEAD(&reqs, pr, pr_link);
+ }
+ }
+
+ mtx_unlock(&gd->gd_lock);
+
+ LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
+ mem_free(pr, sizeof(*pr));
+ }
+}
+
+static uint32_t
+rpc_gss_alloc_seq(struct rpc_gss_data *gd)
+{
+ uint32_t seq;
+
+ mtx_lock(&gd->gd_lock);
+ seq = gd->gd_seq;
+ gd->gd_seq++;
+ mtx_unlock(&gd->gd_lock);
+
+ return (seq);
+}
+
+static void
+rpc_gss_nextverf(__unused AUTH *auth)
+{
+
+ /* not used */
+}
+
+static bool_t
+rpc_gss_marshal(AUTH *auth, uint32_t xid, XDR *xdrs, struct mbuf *args)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_pending_request *pr;
+ uint32_t seq;
+ XDR tmpxdrs;
+ struct rpc_gss_cred gsscred;
+ char credbuf[MAX_AUTH_BYTES];
+ struct opaque_auth creds, verf;
+ gss_buffer_desc rpcbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+ bool_t xdr_stat;
+
+ rpc_gss_log_debug("in rpc_gss_marshal()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ gsscred = gd->gd_cred;
+ seq = rpc_gss_alloc_seq(gd);
+ gsscred.gc_seq = seq;
+
+ xdrmem_create(&tmpxdrs, credbuf, sizeof(credbuf), XDR_ENCODE);
+ if (!xdr_rpc_gss_cred(&tmpxdrs, &gsscred)) {
+ XDR_DESTROY(&tmpxdrs);
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ creds.oa_flavor = RPCSEC_GSS;
+ creds.oa_base = credbuf;
+ creds.oa_length = XDR_GETPOS(&tmpxdrs);
+ XDR_DESTROY(&tmpxdrs);
+
+ xdr_opaque_auth(xdrs, &creds);
+
+ if (gd->gd_cred.gc_proc == RPCSEC_GSS_INIT ||
+ gd->gd_cred.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
+ if (!xdr_opaque_auth(xdrs, &_null_auth)) {
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ xdrmbuf_append(xdrs, args);
+ return (TRUE);
+ } else {
+ /*
+ * Keep track of this XID + seq pair so that we can do
+ * the matching gss_verify_mic in AUTH_VALIDATE.
+ */
+ pr = mem_alloc(sizeof(struct rpc_pending_request));
+ mtx_lock(&gd->gd_lock);
+ pr->pr_xid = xid;
+ pr->pr_seq = seq;
+ LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
+ mtx_unlock(&gd->gd_lock);
+
+ /*
+ * Checksum serialized RPC header, up to and including
+ * credential. For the in-kernel environment, we
+ * assume that our XDR stream is on a contiguous
+ * memory buffer (e.g. an mbuf).
+ */
+ rpcbuf.length = XDR_GETPOS(xdrs);
+ XDR_SETPOS(xdrs, 0);
+ rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length);
+
+ maj_stat = gss_get_mic(&min_stat, gd->gd_ctx, gd->gd_qop,
+ &rpcbuf, &checksum);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_get_mic", gd->gd_mech,
+ maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ rpc_gss_destroy_context(auth, TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
+ return (FALSE);
+ }
+
+ verf.oa_flavor = RPCSEC_GSS;
+ verf.oa_base = checksum.value;
+ verf.oa_length = checksum.length;
+
+ xdr_stat = xdr_opaque_auth(xdrs, &verf);
+ gss_release_buffer(&min_stat, &checksum);
+ if (!xdr_stat) {
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
+ gd->gd_cred.gc_svc == rpc_gss_svc_none) {
+ xdrmbuf_append(xdrs, args);
+ return (TRUE);
+ } else {
+ if (!xdr_rpc_gss_wrap_data(&args,
+ gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
+ seq))
+ return (FALSE);
+ xdrmbuf_append(xdrs, args);
+ return (TRUE);
+ }
+ }
+
+ return (TRUE);
+}
+
+static bool_t
+rpc_gss_validate(AUTH *auth, uint32_t xid, struct opaque_auth *verf,
+ struct mbuf **resultsp)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_pending_request *pr, *npr;
+ struct rpc_pending_request_list reqs;
+ gss_qop_t qop_state;
+ uint32_t num, seq;
+ gss_buffer_desc signbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+
+ rpc_gss_log_debug("in rpc_gss_validate()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ /*
+ * The client will call us with a NULL verf when it gives up
+ * on an XID.
+ */
+ if (!verf) {
+ rpc_gss_purge_xid(gd, xid);
+ return (TRUE);
+ }
+
+ if (gd->gd_state == RPCSEC_GSS_CONTEXT) {
+ /*
+ * Save the on the wire verifier to validate last INIT
+ * phase packet after decode if the major status is
+ * GSS_S_COMPLETE.
+ */
+ if (gd->gd_verf.value)
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_verf);
+ gd->gd_verf.value = mem_alloc(verf->oa_length);
+ if (gd->gd_verf.value == NULL) {
+ printf("gss_validate: out of memory\n");
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ m_freem(*resultsp);
+ *resultsp = NULL;
+ return (FALSE);
+ }
+ memcpy(gd->gd_verf.value, verf->oa_base, verf->oa_length);
+ gd->gd_verf.length = verf->oa_length;
+
+ return (TRUE);
+ }
+
+ /*
+ * We need to check the verifier against all the requests
+ * we've send for this XID - for unreliable protocols, we
+ * retransmit with the same XID but different sequence
+ * number. We temporarily take this set of requests out of the
+ * list so that we can work through the list without having to
+ * hold the lock.
+ */
+ mtx_lock(&gd->gd_lock);
+ LIST_INIT(&reqs);
+ LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
+ if (pr->pr_xid == xid) {
+ LIST_REMOVE(pr, pr_link);
+ LIST_INSERT_HEAD(&reqs, pr, pr_link);
+ }
+ }
+ mtx_unlock(&gd->gd_lock);
+ LIST_FOREACH(pr, &reqs, pr_link) {
+ if (pr->pr_xid == xid) {
+ seq = pr->pr_seq;
+ num = htonl(seq);
+ signbuf.value = &num;
+ signbuf.length = sizeof(num);
+
+ checksum.value = verf->oa_base;
+ checksum.length = verf->oa_length;
+
+ maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
+ &signbuf, &checksum, &qop_state);
+ if (maj_stat != GSS_S_COMPLETE
+ || qop_state != gd->gd_qop) {
+ continue;
+ }
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ rpc_gss_destroy_context(auth, TRUE);
+ break;
+ }
+ //rpc_gss_purge_reqs(gd, seq);
+ LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr)
+ mem_free(pr, sizeof(*pr));
+
+ if (gd->gd_cred.gc_svc == rpc_gss_svc_none) {
+ return (TRUE);
+ } else {
+ if (!xdr_rpc_gss_unwrap_data(resultsp,
+ gd->gd_ctx, gd->gd_qop,
+ gd->gd_cred.gc_svc, seq)) {
+ return (FALSE);
+ }
+ }
+ return (TRUE);
+ }
+ }
+
+ /*
+ * We didn't match - put back any entries for this XID so that
+ * a future call to validate can retry.
+ */
+ mtx_lock(&gd->gd_lock);
+ LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
+ LIST_REMOVE(pr, pr_link);
+ LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
+ }
+ mtx_unlock(&gd->gd_lock);
+
+ /*
+ * Nothing matches - give up.
+ */
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
+ m_freem(*resultsp);
+ *resultsp = NULL;
+ return (FALSE);
+}
+
+static bool_t
+rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
+{
+ struct thread *td = curthread;
+ struct ucred *crsave;
+ struct rpc_gss_data *gd;
+ struct rpc_gss_init_res gr;
+ gss_buffer_desc principal_desc;
+ gss_buffer_desc *recv_tokenp, recv_token, send_token;
+ gss_name_t name;
+ OM_uint32 maj_stat, min_stat, call_stat;
+ const char *mech;
+ struct rpc_callextra ext;
+
+ rpc_gss_log_debug("in rpc_gss_refresh()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ mtx_lock(&gd->gd_lock);
+ /*
+ * If the context isn't in START state, someone else is
+ * refreshing - we wait till they are done. If they fail, they
+ * will put the state back to START and we can try (most
+ * likely to also fail).
+ */
+ while (gd->gd_state != RPCSEC_GSS_START
+ && gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
+ msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
+ }
+ if (gd->gd_state == RPCSEC_GSS_ESTABLISHED) {
+ mtx_unlock(&gd->gd_lock);
+ return (TRUE);
+ }
+ gd->gd_state = RPCSEC_GSS_CONTEXT;
+ mtx_unlock(&gd->gd_lock);
+
+ principal_desc.value = (void *)gd->gd_principal;
+ principal_desc.length = strlen(gd->gd_principal);
+ maj_stat = gss_import_name(&min_stat, &principal_desc,
+ GSS_C_NT_HOSTBASED_SERVICE, &name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ options_ret->major_status = maj_stat;
+ options_ret->minor_status = min_stat;
+ goto out;
+ }
+
+ /* GSS context establishment loop. */
+ gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
+ gd->gd_cred.gc_seq = 0;
+
+ memset(&recv_token, 0, sizeof(recv_token));
+ memset(&gr, 0, sizeof(gr));
+ memset(options_ret, 0, sizeof(*options_ret));
+ options_ret->major_status = GSS_S_FAILURE;
+ recv_tokenp = GSS_C_NO_BUFFER;
+
+ for (;;) {
+ crsave = td->td_ucred;
+ td->td_ucred = gd->gd_ucred;
+ maj_stat = gss_init_sec_context(&min_stat,
+ gd->gd_options.my_cred,
+ &gd->gd_ctx,
+ name,
+ gd->gd_mech,
+ gd->gd_options.req_flags,
+ gd->gd_options.time_req,
+ gd->gd_options.input_channel_bindings,
+ recv_tokenp,
+ &gd->gd_mech, /* used mech */
+ &send_token,
+ &options_ret->ret_flags,
+ &options_ret->time_req);
+ td->td_ucred = crsave;
+
+ /*
+ * Free the token which we got from the server (if
+ * any). Remember that this was allocated by XDR, not
+ * GSS-API.
+ */
+ if (recv_tokenp != GSS_C_NO_BUFFER) {
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &recv_token);
+ recv_tokenp = GSS_C_NO_BUFFER;
+ }
+ if (gd->gd_mech && rpc_gss_oid_to_mech(gd->gd_mech, &mech)) {
+ strlcpy(options_ret->actual_mechanism,
+ mech,
+ sizeof(options_ret->actual_mechanism));
+ }
+ if (maj_stat != GSS_S_COMPLETE &&
+ maj_stat != GSS_S_CONTINUE_NEEDED) {
+ rpc_gss_log_status("gss_init_sec_context", gd->gd_mech,
+ maj_stat, min_stat);
+ options_ret->major_status = maj_stat;
+ options_ret->minor_status = min_stat;
+ break;
+ }
+ if (send_token.length != 0) {
+ memset(&gr, 0, sizeof(gr));
+
+ bzero(&ext, sizeof(ext));
+ ext.rc_auth = auth;
+ call_stat = CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
+ (xdrproc_t)xdr_gss_buffer_desc,
+ &send_token,
+ (xdrproc_t)xdr_rpc_gss_init_res,
+ (caddr_t)&gr, AUTH_TIMEOUT);
+
+ gss_release_buffer(&min_stat, &send_token);
+
+ if (call_stat != RPC_SUCCESS)
+ break;
+
+ if (gr.gr_major != GSS_S_COMPLETE &&
+ gr.gr_major != GSS_S_CONTINUE_NEEDED) {
+ rpc_gss_log_status("server reply", gd->gd_mech,
+ gr.gr_major, gr.gr_minor);
+ options_ret->major_status = gr.gr_major;
+ options_ret->minor_status = gr.gr_minor;
+ break;
+ }
+
+ /*
+ * Save the server's gr_handle value, freeing
+ * what we have already (remember that this
+ * was allocated by XDR, not GSS-API).
+ */
+ if (gr.gr_handle.length != 0) {
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_cred.gc_handle);
+ gd->gd_cred.gc_handle = gr.gr_handle;
+ }
+
+ /*
+ * Save the server's token as well.
+ */
+ if (gr.gr_token.length != 0) {
+ recv_token = gr.gr_token;
+ recv_tokenp = &recv_token;
+ }
+
+ /*
+ * Since we have copied out all the bits of gr
+ * which XDR allocated for us, we don't need
+ * to free it.
+ */
+ gd->gd_cred.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
+ }
+
+ if (maj_stat == GSS_S_COMPLETE) {
+ gss_buffer_desc bufin;
+ u_int seq, qop_state = 0;
+
+ /*
+ * gss header verifier,
+ * usually checked in gss_validate
+ */
+ seq = htonl(gr.gr_win);
+ bufin.value = (unsigned char *)&seq;
+ bufin.length = sizeof(seq);
+
+ maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
+ &bufin, &gd->gd_verf, &qop_state);
+
+ if (maj_stat != GSS_S_COMPLETE ||
+ qop_state != gd->gd_qop) {
+ rpc_gss_log_status("gss_verify_mic", gd->gd_mech,
+ maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ rpc_gss_destroy_context(auth, TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR,
+ EPERM);
+ options_ret->major_status = maj_stat;
+ options_ret->minor_status = min_stat;
+ break;
+ }
+
+ options_ret->major_status = GSS_S_COMPLETE;
+ options_ret->minor_status = 0;
+ options_ret->rpcsec_version = gd->gd_cred.gc_version;
+ options_ret->gss_context = gd->gd_ctx;
+
+ gd->gd_cred.gc_proc = RPCSEC_GSS_DATA;
+ gd->gd_seq = 1;
+ gd->gd_win = gr.gr_win;
+ break;
+ }
+ }
+
+ gss_release_name(&min_stat, &name);
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_verf);
+
+out:
+ /* End context negotiation loop. */
+ if (gd->gd_cred.gc_proc != RPCSEC_GSS_DATA) {
+ rpc_createerr.cf_stat = RPC_AUTHERROR;
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
+ if (gd->gd_ctx) {
+ gss_delete_sec_context(&min_stat, &gd->gd_ctx,
+ GSS_C_NO_BUFFER);
+ }
+ mtx_lock(&gd->gd_lock);
+ gd->gd_state = RPCSEC_GSS_START;
+ wakeup(gd);
+ mtx_unlock(&gd->gd_lock);
+ return (FALSE);
+ }
+
+ mtx_lock(&gd->gd_lock);
+ gd->gd_state = RPCSEC_GSS_ESTABLISHED;
+ wakeup(gd);
+ mtx_unlock(&gd->gd_lock);
+
+ return (TRUE);
+}
+
+static bool_t
+rpc_gss_refresh(AUTH *auth, void *msg)
+{
+ struct rpc_msg *reply = (struct rpc_msg *) msg;
+ rpc_gss_options_ret_t options;
+
+ /*
+ * If the error was RPCSEC_GSS_CREDPROBLEM of
+ * RPCSEC_GSS_CTXPROBLEM we start again from scratch. All
+ * other errors are fatal.
+ */
+ if (reply->rm_reply.rp_stat == MSG_DENIED
+ && reply->rm_reply.rp_rjct.rj_stat == AUTH_ERROR
+ && (reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CREDPROBLEM
+ || reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CTXPROBLEM)) {
+ rpc_gss_destroy_context(auth, FALSE);
+ memset(&options, 0, sizeof(options));
+ return (rpc_gss_init(auth, &options));
+ }
+
+ return (FALSE);
+}
+
+static void
+rpc_gss_destroy_context(AUTH *auth, bool_t send_destroy)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_pending_request *pr;
+ OM_uint32 min_stat;
+ struct rpc_callextra ext;
+
+ rpc_gss_log_debug("in rpc_gss_destroy_context()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ mtx_lock(&gd->gd_lock);
+ /*
+ * If the context isn't in ESTABISHED state, someone else is
+ * destroying/refreshing - we wait till they are done.
+ */
+ if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
+ while (gd->gd_state != RPCSEC_GSS_START
+ && gd->gd_state != RPCSEC_GSS_ESTABLISHED)
+ msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
+ mtx_unlock(&gd->gd_lock);
+ return;
+ }
+ gd->gd_state = RPCSEC_GSS_DESTROYING;
+ mtx_unlock(&gd->gd_lock);
+
+ if (send_destroy) {
+ gd->gd_cred.gc_proc = RPCSEC_GSS_DESTROY;
+ bzero(&ext, sizeof(ext));
+ ext.rc_auth = auth;
+ CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
+ (xdrproc_t)xdr_void, NULL,
+ (xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT);
+ }
+
+ while ((pr = LIST_FIRST(&gd->gd_reqs)) != NULL) {
+ LIST_REMOVE(pr, pr_link);
+ mem_free(pr, sizeof(*pr));
+ }
+
+ /*
+ * Free the context token. Remember that this was
+ * allocated by XDR, not GSS-API.
+ */
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_cred.gc_handle);
+ gd->gd_cred.gc_handle.length = 0;
+
+ if (gd->gd_ctx != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&min_stat, &gd->gd_ctx, NULL);
+
+ mtx_lock(&gd->gd_lock);
+ gd->gd_state = RPCSEC_GSS_START;
+ wakeup(gd);
+ mtx_unlock(&gd->gd_lock);
+}
+
+static void
+rpc_gss_destroy(AUTH *auth)
+{
+ struct rpc_gss_data *gd;
+
+ rpc_gss_log_debug("in rpc_gss_destroy()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (!refcount_release(&gd->gd_refs))
+ return;
+
+ rpc_gss_destroy_context(auth, TRUE);
+
+ CLNT_RELEASE(gd->gd_clnt);
+ crfree(gd->gd_ucred);
+ free(gd->gd_principal, M_RPC);
+ if (gd->gd_verf.value)
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_verf);
+ mtx_destroy(&gd->gd_lock);
+
+ mem_free(gd, sizeof(*gd));
+ mem_free(auth, sizeof(*auth));
+}
+
+int
+rpc_gss_max_data_length(AUTH *auth, int max_tp_unit_len)
+{
+ struct rpc_gss_data *gd;
+ int want_conf;
+ OM_uint32 max;
+ OM_uint32 maj_stat, min_stat;
+ int result;
+
+ gd = AUTH_PRIVATE(auth);
+
+ switch (gd->gd_cred.gc_svc) {
+ case rpc_gss_svc_none:
+ return (max_tp_unit_len);
+ break;
+
+ case rpc_gss_svc_default:
+ case rpc_gss_svc_integrity:
+ want_conf = FALSE;
+ break;
+
+ case rpc_gss_svc_privacy:
+ want_conf = TRUE;
+ break;
+
+ default:
+ return (0);
+ }
+
+ maj_stat = gss_wrap_size_limit(&min_stat, gd->gd_ctx, want_conf,
+ gd->gd_qop, max_tp_unit_len, &max);
+
+ if (maj_stat == GSS_S_COMPLETE) {
+ result = (int) max;
+ if (result < 0)
+ result = 0;
+ return (result);
+ } else {
+ rpc_gss_log_status("gss_wrap_size_limit", gd->gd_mech,
+ maj_stat, min_stat);
+ return (0);
+ }
+}
diff --git a/sys/rpc/rpcsec_gss/rpcsec_gss_conf.c b/sys/rpc/rpcsec_gss/rpcsec_gss_conf.c
new file mode 100644
index 0000000..b5e99d4
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/rpcsec_gss_conf.c
@@ -0,0 +1,163 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kobj.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+#include "rpcsec_gss_int.h"
+
+bool_t
+rpc_gss_mech_to_oid(const char *mech, gss_OID *oid_ret)
+{
+ gss_OID oid = kgss_find_mech_by_name(mech);
+
+ if (oid) {
+ *oid_ret = oid;
+ return (TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
+ return (FALSE);
+}
+
+bool_t
+rpc_gss_oid_to_mech(gss_OID oid, const char **mech_ret)
+{
+ const char *name = kgss_find_mech_by_oid(oid);
+
+ if (name) {
+ *mech_ret = name;
+ return (TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
+ return (FALSE);
+}
+
+bool_t
+rpc_gss_qop_to_num(const char *qop, const char *mech, u_int *num_ret)
+{
+
+ if (!strcmp(qop, "default")) {
+ *num_ret = GSS_C_QOP_DEFAULT;
+ return (TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
+ return (FALSE);
+}
+
+const char *
+_rpc_gss_num_to_qop(const char *mech, u_int num)
+{
+
+ if (num == GSS_C_QOP_DEFAULT)
+ return "default";
+
+ return (NULL);
+}
+
+const char **
+rpc_gss_get_mechanisms(void)
+{
+ static const char **mech_names = NULL;
+ struct kgss_mech *km;
+ int count;
+
+ if (mech_names)
+ return (mech_names);
+
+ count = 0;
+ LIST_FOREACH(km, &kgss_mechs, km_link) {
+ count++;
+ }
+ count++;
+
+ mech_names = malloc(count * sizeof(const char *), M_RPC, M_WAITOK);
+ count = 0;
+ LIST_FOREACH(km, &kgss_mechs, km_link) {
+ mech_names[count++] = km->km_mech_name;
+ }
+ mech_names[count++] = NULL;
+
+ return (mech_names);
+}
+
+#if 0
+const char **
+rpc_gss_get_mech_info(const char *mech, rpc_gss_service_t *service)
+{
+ struct mech_info *info;
+
+ _rpc_gss_load_mech();
+ _rpc_gss_load_qop();
+ SLIST_FOREACH(info, &mechs, link) {
+ if (!strcmp(mech, info->name)) {
+ /*
+ * I'm not sure what to do with service
+ * here. The Solaris manpages are not clear on
+ * the subject and the OpenSolaris code just
+ * sets it to rpc_gss_svc_privacy
+ * unconditionally with a comment noting that
+ * it is bogus.
+ */
+ *service = rpc_gss_svc_privacy;
+ return info->qops;
+ }
+ }
+
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
+ return (NULL);
+}
+#endif
+
+bool_t
+rpc_gss_get_versions(u_int *vers_hi, u_int *vers_lo)
+{
+
+ *vers_hi = 1;
+ *vers_lo = 1;
+ return (TRUE);
+}
+
+bool_t
+rpc_gss_is_installed(const char *mech)
+{
+ gss_OID oid = kgss_find_mech_by_name(mech);
+
+ if (oid)
+ return (TRUE);
+ else
+ return (FALSE);
+}
+
diff --git a/sys/rpc/rpcsec_gss/rpcsec_gss_int.h b/sys/rpc/rpcsec_gss/rpcsec_gss_int.h
new file mode 100644
index 0000000..4f38828
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/rpcsec_gss_int.h
@@ -0,0 +1,94 @@
+/*
+ rpcsec_gss.h
+
+ Copyright (c) 2000 The Regents of the University of Michigan.
+ All rights reserved.
+
+ Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+ All rights reserved, all wrongs reversed.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ $Id: auth_gss.h,v 1.12 2001/04/30 19:44:47 andros Exp $
+*/
+/* $FreeBSD$ */
+
+#ifndef _RPCSEC_GSS_INT_H
+#define _RPCSEC_GSS_INT_H
+
+#include <kgssapi/gssapi_impl.h>
+
+/* RPCSEC_GSS control procedures. */
+typedef enum {
+ RPCSEC_GSS_DATA = 0,
+ RPCSEC_GSS_INIT = 1,
+ RPCSEC_GSS_CONTINUE_INIT = 2,
+ RPCSEC_GSS_DESTROY = 3
+} rpc_gss_proc_t;
+
+#define RPCSEC_GSS_VERSION 1
+
+/* Credentials. */
+struct rpc_gss_cred {
+ u_int gc_version; /* version */
+ rpc_gss_proc_t gc_proc; /* control procedure */
+ u_int gc_seq; /* sequence number */
+ rpc_gss_service_t gc_svc; /* service */
+ gss_buffer_desc gc_handle; /* handle to server-side context */
+};
+
+/* Context creation response. */
+struct rpc_gss_init_res {
+ gss_buffer_desc gr_handle; /* handle to server-side context */
+ u_int gr_major; /* major status */
+ u_int gr_minor; /* minor status */
+ u_int gr_win; /* sequence window */
+ gss_buffer_desc gr_token; /* token */
+};
+
+/* Maximum sequence number value. */
+#define MAXSEQ 0x80000000
+
+/* Prototypes. */
+__BEGIN_DECLS
+
+bool_t xdr_rpc_gss_cred(XDR *xdrs, struct rpc_gss_cred *p);
+bool_t xdr_rpc_gss_init_res(XDR *xdrs, struct rpc_gss_init_res *p);
+bool_t xdr_rpc_gss_wrap_data(struct mbuf **argsp,
+ gss_ctx_id_t ctx, gss_qop_t qop, rpc_gss_service_t svc,
+ u_int seq);
+bool_t xdr_rpc_gss_unwrap_data(struct mbuf **resultsp,
+ gss_ctx_id_t ctx, gss_qop_t qop, rpc_gss_service_t svc, u_int seq);
+const char *_rpc_gss_num_to_qop(const char *mech, u_int num);
+void _rpc_gss_set_error(int rpc_gss_error, int system_error);
+
+void rpc_gss_log_debug(const char *fmt, ...);
+void rpc_gss_log_status(const char *m, gss_OID mech, OM_uint32 major,
+ OM_uint32 minor);
+
+__END_DECLS
+
+#endif /* !_RPCSEC_GSS_INT_H */
diff --git a/sys/rpc/rpcsec_gss/rpcsec_gss_misc.c b/sys/rpc/rpcsec_gss/rpcsec_gss_misc.c
new file mode 100644
index 0000000..5c8bf91
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/rpcsec_gss_misc.c
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kobj.h>
+#include <sys/malloc.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+#include "rpcsec_gss_int.h"
+
+static rpc_gss_error_t _rpc_gss_error;
+
+void
+_rpc_gss_set_error(int rpc_gss_error, int system_error)
+{
+
+ _rpc_gss_error.rpc_gss_error = rpc_gss_error;
+ _rpc_gss_error.system_error = system_error;
+}
+
+void
+rpc_gss_get_error(rpc_gss_error_t *error)
+{
+
+ *error = _rpc_gss_error;
+}
diff --git a/sys/rpc/rpcsec_gss/rpcsec_gss_prot.c b/sys/rpc/rpcsec_gss/rpcsec_gss_prot.c
new file mode 100644
index 0000000..0654a6e
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/rpcsec_gss_prot.c
@@ -0,0 +1,359 @@
+/*
+ rpcsec_gss_prot.c
+
+ Copyright (c) 2000 The Regents of the University of Michigan.
+ All rights reserved.
+
+ Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+ All rights reserved, all wrongs reversed.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ $Id: authgss_prot.c,v 1.18 2000/09/01 04:14:03 dugsong Exp $
+*/
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kobj.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+#include "rpcsec_gss_int.h"
+
+#define MAX_GSS_SIZE 10240 /* XXX */
+
+#if 0 /* use the one from kgssapi */
+bool_t
+xdr_gss_buffer_desc(XDR *xdrs, gss_buffer_desc *p)
+{
+ char *val;
+ u_int len;
+ bool_t ret;
+
+ val = p->value;
+ len = p->length;
+ ret = xdr_bytes(xdrs, &val, &len, MAX_GSS_SIZE);
+ p->value = val;
+ p->length = len;
+
+ return (ret);
+}
+#endif
+
+bool_t
+xdr_rpc_gss_cred(XDR *xdrs, struct rpc_gss_cred *p)
+{
+ enum_t proc, svc;
+ bool_t ret;
+
+ proc = p->gc_proc;
+ svc = p->gc_svc;
+ ret = (xdr_u_int(xdrs, &p->gc_version) &&
+ xdr_enum(xdrs, &proc) &&
+ xdr_u_int(xdrs, &p->gc_seq) &&
+ xdr_enum(xdrs, &svc) &&
+ xdr_gss_buffer_desc(xdrs, &p->gc_handle));
+ p->gc_proc = proc;
+ p->gc_svc = svc;
+
+ return (ret);
+}
+
+bool_t
+xdr_rpc_gss_init_res(XDR *xdrs, struct rpc_gss_init_res *p)
+{
+
+ return (xdr_gss_buffer_desc(xdrs, &p->gr_handle) &&
+ xdr_u_int(xdrs, &p->gr_major) &&
+ xdr_u_int(xdrs, &p->gr_minor) &&
+ xdr_u_int(xdrs, &p->gr_win) &&
+ xdr_gss_buffer_desc(xdrs, &p->gr_token));
+}
+
+static void
+put_uint32(struct mbuf **mp, uint32_t v)
+{
+ struct mbuf *m = *mp;
+ uint32_t n;
+
+ M_PREPEND(m, sizeof(uint32_t), M_WAIT);
+ n = htonl(v);
+ bcopy(&n, mtod(m, uint32_t *), sizeof(uint32_t));
+ *mp = m;
+}
+
+bool_t
+xdr_rpc_gss_wrap_data(struct mbuf **argsp,
+ gss_ctx_id_t ctx, gss_qop_t qop,
+ rpc_gss_service_t svc, u_int seq)
+{
+ struct mbuf *args, *mic;
+ OM_uint32 maj_stat, min_stat;
+ int conf_state;
+ u_int len;
+ static char zpad[4];
+
+ args = *argsp;
+
+ /*
+ * Prepend the sequence number before calling gss_get_mic or gss_wrap.
+ */
+ put_uint32(&args, seq);
+ len = m_length(args, NULL);
+
+ if (svc == rpc_gss_svc_integrity) {
+ /* Checksum rpc_gss_data_t. */
+ maj_stat = gss_get_mic_mbuf(&min_stat, ctx, qop, args, &mic);
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_debug("gss_get_mic failed");
+ m_freem(args);
+ return (FALSE);
+ }
+
+ /*
+ * Marshal databody_integ. Note that since args is
+ * already RPC encoded, there will be no padding.
+ */
+ put_uint32(&args, len);
+
+ /*
+ * Marshal checksum. This is likely to need padding.
+ */
+ len = m_length(mic, NULL);
+ put_uint32(&mic, len);
+ if (len != RNDUP(len)) {
+ m_append(mic, RNDUP(len) - len, zpad);
+ }
+
+ /*
+ * Concatenate databody_integ with checksum.
+ */
+ m_cat(args, mic);
+ } else if (svc == rpc_gss_svc_privacy) {
+ /* Encrypt rpc_gss_data_t. */
+ maj_stat = gss_wrap_mbuf(&min_stat, ctx, TRUE, qop,
+ &args, &conf_state);
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_wrap", NULL,
+ maj_stat, min_stat);
+ return (FALSE);
+ }
+
+ /*
+ * Marshal databody_priv and deal with RPC padding.
+ */
+ len = m_length(args, NULL);
+ put_uint32(&args, len);
+ if (len != RNDUP(len)) {
+ m_append(args, RNDUP(len) - len, zpad);
+ }
+ }
+ *argsp = args;
+ return (TRUE);
+}
+
+static uint32_t
+get_uint32(struct mbuf **mp)
+{
+ struct mbuf *m = *mp;
+ uint32_t n;
+
+ if (m->m_len < sizeof(uint32_t)) {
+ m = m_pullup(m, sizeof(uint32_t));
+ if (!m) {
+ *mp = NULL;
+ return (0);
+ }
+ }
+ bcopy(mtod(m, uint32_t *), &n, sizeof(uint32_t));
+ m_adj(m, sizeof(uint32_t));
+ *mp = m;
+ return (ntohl(n));
+}
+
+static void
+m_trim(struct mbuf *m, int len)
+{
+ struct mbuf *n;
+ int off;
+
+ n = m_getptr(m, len, &off);
+ if (n) {
+ n->m_len = off;
+ if (n->m_next) {
+ m_freem(n->m_next);
+ n->m_next = NULL;
+ }
+ }
+}
+
+bool_t
+xdr_rpc_gss_unwrap_data(struct mbuf **resultsp,
+ gss_ctx_id_t ctx, gss_qop_t qop,
+ rpc_gss_service_t svc, u_int seq)
+{
+ struct mbuf *results, *message, *mic;
+ uint32_t len, cklen;
+ OM_uint32 maj_stat, min_stat;
+ u_int seq_num, conf_state, qop_state;
+
+ results = *resultsp;
+ *resultsp = NULL;
+
+ message = NULL;
+ if (svc == rpc_gss_svc_integrity) {
+ /*
+ * Extract the seq+message part. Remember that there
+ * may be extra RPC padding in the checksum. The
+ * message part is RPC encoded already so no
+ * padding.
+ */
+ len = get_uint32(&results);
+ message = results;
+ results = m_split(results, len, M_WAIT);
+ if (!results) {
+ m_freem(message);
+ return (FALSE);
+ }
+
+ /*
+ * Extract the MIC and make it contiguous.
+ */
+ cklen = get_uint32(&results);
+ KASSERT(cklen <= MHLEN, ("unexpected large GSS-API checksum"));
+ mic = results;
+ if (cklen > mic->m_len)
+ mic = m_pullup(mic, cklen);
+ if (cklen != RNDUP(cklen))
+ m_trim(mic, cklen);
+
+ /* Verify checksum and QOP. */
+ maj_stat = gss_verify_mic_mbuf(&min_stat, ctx,
+ message, mic, &qop_state);
+ m_freem(mic);
+
+ if (maj_stat != GSS_S_COMPLETE || qop_state != qop) {
+ m_freem(message);
+ rpc_gss_log_status("gss_verify_mic", NULL,
+ maj_stat, min_stat);
+ return (FALSE);
+ }
+ } else if (svc == rpc_gss_svc_privacy) {
+ /* Decode databody_priv. */
+ len = get_uint32(&results);
+
+ /* Decrypt databody. */
+ message = results;
+ if (len != RNDUP(len))
+ m_trim(message, len);
+ maj_stat = gss_unwrap_mbuf(&min_stat, ctx, &message,
+ &conf_state, &qop_state);
+
+ /* Verify encryption and QOP. */
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_unwrap", NULL,
+ maj_stat, min_stat);
+ return (FALSE);
+ }
+ if (qop_state != qop || conf_state != TRUE) {
+ m_freem(results);
+ return (FALSE);
+ }
+ }
+
+ /* Decode rpc_gss_data_t (sequence number + arguments). */
+ seq_num = get_uint32(&message);
+
+ /* Verify sequence number. */
+ if (seq_num != seq) {
+ rpc_gss_log_debug("wrong sequence number in databody");
+ m_freem(message);
+ return (FALSE);
+ }
+
+ *resultsp = message;
+ return (TRUE);
+}
+
+#ifdef DEBUG
+#include <ctype.h>
+
+void
+rpc_gss_log_debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ fprintf(stderr, "rpcsec_gss: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+}
+
+void
+rpc_gss_log_status(const char *m, gss_OID mech, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+ OM_uint32 min;
+ gss_buffer_desc msg;
+ int msg_ctx = 0;
+
+ fprintf(stderr, "rpcsec_gss: %s: ", m);
+
+ gss_display_status(&min, maj_stat, GSS_C_GSS_CODE, GSS_C_NULL_OID,
+ &msg_ctx, &msg);
+ printf("%s - ", (char *)msg.value);
+ gss_release_buffer(&min, &msg);
+
+ gss_display_status(&min, min_stat, GSS_C_MECH_CODE, mech,
+ &msg_ctx, &msg);
+ printf("%s\n", (char *)msg.value);
+ gss_release_buffer(&min, &msg);
+}
+
+#else
+
+void
+rpc_gss_log_debug(__unused const char *fmt, ...)
+{
+}
+
+void
+rpc_gss_log_status(__unused const char *m, __unused gss_OID mech,
+ __unused OM_uint32 maj_stat, __unused OM_uint32 min_stat)
+{
+}
+
+#endif
+
+
diff --git a/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c b/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c
new file mode 100644
index 0000000..e2469fd
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c
@@ -0,0 +1,1485 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ svc_rpcsec_gss.c
+
+ Copyright (c) 2000 The Regents of the University of Michigan.
+ All rights reserved.
+
+ Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+ All rights reserved, all wrongs reversed.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/kobj.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/sx.h>
+#include <sys/ucred.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+#include "rpcsec_gss_int.h"
+
+static bool_t svc_rpc_gss_wrap(SVCAUTH *, struct mbuf **);
+static bool_t svc_rpc_gss_unwrap(SVCAUTH *, struct mbuf **);
+static void svc_rpc_gss_release(SVCAUTH *);
+static enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
+static int rpc_gss_svc_getcred(struct svc_req *, struct ucred **, int *);
+
+static struct svc_auth_ops svc_auth_gss_ops = {
+ svc_rpc_gss_wrap,
+ svc_rpc_gss_unwrap,
+ svc_rpc_gss_release,
+};
+
+struct sx svc_rpc_gss_lock;
+
+struct svc_rpc_gss_callback {
+ SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
+ rpc_gss_callback_t cb_callback;
+};
+static SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
+ svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(&svc_rpc_gss_callbacks);
+
+struct svc_rpc_gss_svc_name {
+ SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
+ char *sn_principal;
+ gss_OID sn_mech;
+ u_int sn_req_time;
+ gss_cred_id_t sn_cred;
+ u_int sn_program;
+ u_int sn_version;
+};
+static SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
+ svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(&svc_rpc_gss_svc_names);
+
+enum svc_rpc_gss_client_state {
+ CLIENT_NEW, /* still authenticating */
+ CLIENT_ESTABLISHED, /* context established */
+ CLIENT_STALE /* garbage to collect */
+};
+
+#define SVC_RPC_GSS_SEQWINDOW 128
+
+struct svc_rpc_gss_clientid {
+ uint32_t ci_hostid;
+ uint32_t ci_boottime;
+ uint32_t ci_id;
+};
+
+struct svc_rpc_gss_client {
+ TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
+ TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
+ volatile u_int cl_refs;
+ struct sx cl_lock;
+ struct svc_rpc_gss_clientid cl_id;
+ time_t cl_expiration; /* when to gc */
+ enum svc_rpc_gss_client_state cl_state; /* client state */
+ bool_t cl_locked; /* fixed service+qop */
+ gss_ctx_id_t cl_ctx; /* context id */
+ gss_cred_id_t cl_creds; /* delegated creds */
+ gss_name_t cl_cname; /* client name */
+ struct svc_rpc_gss_svc_name *cl_sname; /* server name used */
+ rpc_gss_rawcred_t cl_rawcred; /* raw credentials */
+ rpc_gss_ucred_t cl_ucred; /* unix-style credentials */
+ struct ucred *cl_cred; /* kernel-style credentials */
+ int cl_rpcflavor; /* RPC pseudo sec flavor */
+ bool_t cl_done_callback; /* TRUE after call */
+ void *cl_cookie; /* user cookie from callback */
+ gid_t cl_gid_storage[NGROUPS];
+ gss_OID cl_mech; /* mechanism */
+ gss_qop_t cl_qop; /* quality of protection */
+ uint32_t cl_seqlast; /* sequence window origin */
+ uint32_t cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
+};
+TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
+
+/*
+ * This structure holds enough information to unwrap arguments or wrap
+ * results for a given request. We use the rq_clntcred area for this
+ * (which is a per-request buffer).
+ */
+struct svc_rpc_gss_cookedcred {
+ struct svc_rpc_gss_client *cc_client;
+ rpc_gss_service_t cc_service;
+ uint32_t cc_seq;
+};
+
+#define CLIENT_HASH_SIZE 256
+#define CLIENT_MAX 128
+struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
+struct svc_rpc_gss_client_list svc_rpc_gss_clients;
+static size_t svc_rpc_gss_client_count;
+static uint32_t svc_rpc_gss_next_clientid = 1;
+
+static void
+svc_rpc_gss_init(void *arg)
+{
+ int i;
+
+ for (i = 0; i < CLIENT_HASH_SIZE; i++)
+ TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
+ TAILQ_INIT(&svc_rpc_gss_clients);
+ svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
+ sx_init(&svc_rpc_gss_lock, "gsslock");
+}
+SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
+
+bool_t
+rpc_gss_set_callback(rpc_gss_callback_t *cb)
+{
+ struct svc_rpc_gss_callback *scb;
+
+ scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
+ if (!scb) {
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ scb->cb_callback = *cb;
+ sx_xlock(&svc_rpc_gss_lock);
+ SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
+ sx_xunlock(&svc_rpc_gss_lock);
+
+ return (TRUE);
+}
+
+void
+rpc_gss_clear_callback(rpc_gss_callback_t *cb)
+{
+ struct svc_rpc_gss_callback *scb;
+
+ sx_xlock(&svc_rpc_gss_lock);
+ SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
+ if (scb->cb_callback.program == cb->program
+ && scb->cb_callback.version == cb->version
+ && scb->cb_callback.callback == cb->callback) {
+ SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
+ svc_rpc_gss_callback, cb_link);
+ sx_xunlock(&svc_rpc_gss_lock);
+ mem_free(scb, sizeof(*scb));
+ return;
+ }
+ }
+ sx_xunlock(&svc_rpc_gss_lock);
+}
+
+static bool_t
+rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
+{
+ OM_uint32 maj_stat, min_stat;
+ gss_buffer_desc namebuf;
+ gss_name_t name;
+ gss_OID_set_desc oid_set;
+
+ oid_set.count = 1;
+ oid_set.elements = sname->sn_mech;
+
+ namebuf.value = (void *) sname->sn_principal;
+ namebuf.length = strlen(sname->sn_principal);
+
+ maj_stat = gss_import_name(&min_stat, &namebuf,
+ GSS_C_NT_HOSTBASED_SERVICE, &name);
+ if (maj_stat != GSS_S_COMPLETE)
+ return (FALSE);
+
+ if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
+ gss_release_cred(&min_stat, &sname->sn_cred);
+
+ maj_stat = gss_acquire_cred(&min_stat, name,
+ sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
+ NULL, NULL);
+ if (maj_stat != GSS_S_COMPLETE) {
+ gss_release_name(&min_stat, &name);
+ return (FALSE);
+ }
+ gss_release_name(&min_stat, &name);
+
+ return (TRUE);
+}
+
+bool_t
+rpc_gss_set_svc_name(const char *principal, const char *mechanism,
+ u_int req_time, u_int program, u_int version)
+{
+ struct svc_rpc_gss_svc_name *sname;
+ gss_OID mech_oid;
+
+ if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
+ return (FALSE);
+
+ sname = mem_alloc(sizeof(*sname));
+ if (!sname)
+ return (FALSE);
+ sname->sn_principal = strdup(principal, M_RPC);
+ sname->sn_mech = mech_oid;
+ sname->sn_req_time = req_time;
+ sname->sn_cred = GSS_C_NO_CREDENTIAL;
+ sname->sn_program = program;
+ sname->sn_version = version;
+
+ if (!rpc_gss_acquire_svc_cred(sname)) {
+ free(sname->sn_principal, M_RPC);
+ mem_free(sname, sizeof(*sname));
+ return (FALSE);
+ }
+
+ sx_xlock(&svc_rpc_gss_lock);
+ SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
+ sx_xunlock(&svc_rpc_gss_lock);
+
+ return (TRUE);
+}
+
+void
+rpc_gss_clear_svc_name(u_int program, u_int version)
+{
+ OM_uint32 min_stat;
+ struct svc_rpc_gss_svc_name *sname;
+
+ sx_xlock(&svc_rpc_gss_lock);
+ SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
+ if (sname->sn_program == program
+ && sname->sn_version == version) {
+ SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
+ svc_rpc_gss_svc_name, sn_link);
+ sx_xunlock(&svc_rpc_gss_lock);
+ gss_release_cred(&min_stat, &sname->sn_cred);
+ free(sname->sn_principal, M_RPC);
+ mem_free(sname, sizeof(*sname));
+ return;
+ }
+ }
+ sx_xunlock(&svc_rpc_gss_lock);
+}
+
+bool_t
+rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
+ const char *mech, const char *name, const char *node, const char *domain)
+{
+ OM_uint32 maj_stat, min_stat;
+ gss_OID mech_oid;
+ size_t namelen;
+ gss_buffer_desc buf;
+ gss_name_t gss_name, gss_mech_name;
+ rpc_gss_principal_t result;
+
+ if (!rpc_gss_mech_to_oid(mech, &mech_oid))
+ return (FALSE);
+
+ /*
+ * Construct a gss_buffer containing the full name formatted
+ * as "name/node@domain" where node and domain are optional.
+ */
+ namelen = strlen(name);
+ if (node) {
+ namelen += strlen(node) + 1;
+ }
+ if (domain) {
+ namelen += strlen(domain) + 1;
+ }
+
+ buf.value = mem_alloc(namelen);
+ buf.length = namelen;
+ strcpy((char *) buf.value, name);
+ if (node) {
+ strcat((char *) buf.value, "/");
+ strcat((char *) buf.value, node);
+ }
+ if (domain) {
+ strcat((char *) buf.value, "@");
+ strcat((char *) buf.value, domain);
+ }
+
+ /*
+ * Convert that to a gss_name_t and then convert that to a
+ * mechanism name in the selected mechanism.
+ */
+ maj_stat = gss_import_name(&min_stat, &buf,
+ GSS_C_NT_USER_NAME, &gss_name);
+ mem_free(buf.value, buf.length);
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
+ return (FALSE);
+ }
+ maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
+ &gss_mech_name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
+ min_stat);
+ gss_release_name(&min_stat, &gss_name);
+ return (FALSE);
+ }
+ gss_release_name(&min_stat, &gss_name);
+
+ /*
+ * Export the mechanism name and use that to construct the
+ * rpc_gss_principal_t result.
+ */
+ maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
+ gss_release_name(&min_stat, &gss_mech_name);
+ return (FALSE);
+ }
+ gss_release_name(&min_stat, &gss_mech_name);
+
+ result = mem_alloc(sizeof(int) + buf.length);
+ if (!result) {
+ gss_release_buffer(&min_stat, &buf);
+ return (FALSE);
+ }
+ result->len = buf.length;
+ memcpy(result->name, buf.value, buf.length);
+ gss_release_buffer(&min_stat, &buf);
+
+ *principal = result;
+ return (TRUE);
+}
+
+bool_t
+rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
+ rpc_gss_ucred_t **ucred, void **cookie)
+{
+ struct svc_rpc_gss_cookedcred *cc;
+ struct svc_rpc_gss_client *client;
+
+ if (req->rq_cred.oa_flavor != RPCSEC_GSS)
+ return (FALSE);
+
+ cc = req->rq_clntcred;
+ client = cc->cc_client;
+ if (rcred)
+ *rcred = &client->cl_rawcred;
+ if (ucred)
+ *ucred = &client->cl_ucred;
+ if (cookie)
+ *cookie = client->cl_cookie;
+ return (TRUE);
+}
+
+/*
+ * This simpler interface is used by svc_getcred to copy the cred data
+ * into a kernel cred structure.
+ */
+static int
+rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
+{
+ struct ucred *cr;
+ struct svc_rpc_gss_cookedcred *cc;
+ struct svc_rpc_gss_client *client;
+ rpc_gss_ucred_t *uc;
+ int i;
+
+ if (req->rq_cred.oa_flavor != RPCSEC_GSS)
+ return (FALSE);
+
+ cc = req->rq_clntcred;
+ client = cc->cc_client;
+
+ if (flavorp)
+ *flavorp = client->cl_rpcflavor;
+
+ if (client->cl_cred) {
+ *crp = crhold(client->cl_cred);
+ return (TRUE);
+ }
+
+ uc = &client->cl_ucred;
+ cr = client->cl_cred = crget();
+ cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
+ cr->cr_rgid = cr->cr_svgid = uc->gid;
+ cr->cr_ngroups = uc->gidlen;
+ if (cr->cr_ngroups > NGROUPS)
+ cr->cr_ngroups = NGROUPS;
+ for (i = 0; i < cr->cr_ngroups; i++)
+ cr->cr_groups[i] = uc->gidlist[i];
+ *crp = crhold(cr);
+
+ return (TRUE);
+}
+
+int
+rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
+{
+ struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
+ struct svc_rpc_gss_client *client = cc->cc_client;
+ int want_conf;
+ OM_uint32 max;
+ OM_uint32 maj_stat, min_stat;
+ int result;
+
+ switch (client->cl_rawcred.service) {
+ case rpc_gss_svc_none:
+ return (max_tp_unit_len);
+ break;
+
+ case rpc_gss_svc_default:
+ case rpc_gss_svc_integrity:
+ want_conf = FALSE;
+ break;
+
+ case rpc_gss_svc_privacy:
+ want_conf = TRUE;
+ break;
+
+ default:
+ return (0);
+ }
+
+ maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
+ client->cl_qop, max_tp_unit_len, &max);
+
+ if (maj_stat == GSS_S_COMPLETE) {
+ result = (int) max;
+ if (result < 0)
+ result = 0;
+ return (result);
+ } else {
+ rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
+ maj_stat, min_stat);
+ return (0);
+ }
+}
+
+static struct svc_rpc_gss_client *
+svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
+{
+ struct svc_rpc_gss_client *client;
+ struct svc_rpc_gss_client_list *list;
+
+ rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
+
+ if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
+ return (NULL);
+
+ list = &svc_rpc_gss_client_hash[id->ci_id % CLIENT_HASH_SIZE];
+ sx_xlock(&svc_rpc_gss_lock);
+ TAILQ_FOREACH(client, list, cl_link) {
+ if (client->cl_id.ci_id == id->ci_id) {
+ /*
+ * Move this client to the front of the LRU
+ * list.
+ */
+ TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
+ TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
+ cl_alllink);
+ refcount_acquire(&client->cl_refs);
+ break;
+ }
+ }
+ sx_xunlock(&svc_rpc_gss_lock);
+
+ return (client);
+}
+
+static struct svc_rpc_gss_client *
+svc_rpc_gss_create_client(void)
+{
+ struct svc_rpc_gss_client *client;
+ struct svc_rpc_gss_client_list *list;
+
+ rpc_gss_log_debug("in svc_rpc_gss_create_client()");
+
+ client = mem_alloc(sizeof(struct svc_rpc_gss_client));
+ memset(client, 0, sizeof(struct svc_rpc_gss_client));
+ refcount_init(&client->cl_refs, 1);
+ sx_init(&client->cl_lock, "GSS-client");
+ client->cl_id.ci_hostid = hostid;
+ client->cl_id.ci_boottime = boottime.tv_sec;
+ client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
+ list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
+ sx_xlock(&svc_rpc_gss_lock);
+ TAILQ_INSERT_HEAD(list, client, cl_link);
+ TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
+ svc_rpc_gss_client_count++;
+ sx_xunlock(&svc_rpc_gss_lock);
+
+ /*
+ * Start the client off with a short expiration time. We will
+ * try to get a saner value from the client creds later.
+ */
+ client->cl_state = CLIENT_NEW;
+ client->cl_locked = FALSE;
+ client->cl_expiration = time_uptime + 5*60;
+
+ return (client);
+}
+
+static void
+svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
+{
+ OM_uint32 min_stat;
+
+ rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
+
+ if (client->cl_ctx)
+ gss_delete_sec_context(&min_stat,
+ &client->cl_ctx, GSS_C_NO_BUFFER);
+
+ if (client->cl_cname)
+ gss_release_name(&min_stat, &client->cl_cname);
+
+ if (client->cl_rawcred.client_principal)
+ mem_free(client->cl_rawcred.client_principal,
+ sizeof(*client->cl_rawcred.client_principal)
+ + client->cl_rawcred.client_principal->len);
+
+ if (client->cl_cred)
+ crfree(client->cl_cred);
+
+ sx_destroy(&client->cl_lock);
+ mem_free(client, sizeof(*client));
+}
+
+/*
+ * Drop a reference to a client and free it if that was the last reference.
+ */
+static void
+svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
+{
+
+ if (!refcount_release(&client->cl_refs))
+ return;
+ svc_rpc_gss_destroy_client(client);
+}
+
+/*
+ * Remove a client from our global lists and free it if we can.
+ */
+static void
+svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
+{
+ struct svc_rpc_gss_client_list *list;
+
+ list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
+ sx_xlock(&svc_rpc_gss_lock);
+ TAILQ_REMOVE(list, client, cl_link);
+ TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
+ svc_rpc_gss_client_count--;
+ sx_xunlock(&svc_rpc_gss_lock);
+ svc_rpc_gss_release_client(client);
+}
+
+static void
+svc_rpc_gss_timeout_clients(void)
+{
+ struct svc_rpc_gss_client *client;
+ struct svc_rpc_gss_client *nclient;
+ time_t now = time_uptime;
+
+ rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
+
+ /*
+ * First enforce the max client limit. We keep
+ * svc_rpc_gss_clients in LRU order.
+ */
+ while (svc_rpc_gss_client_count > CLIENT_MAX)
+ svc_rpc_gss_forget_client(TAILQ_LAST(&svc_rpc_gss_clients,
+ svc_rpc_gss_client_list));
+ TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
+ if (client->cl_state == CLIENT_STALE
+ || now > client->cl_expiration) {
+ rpc_gss_log_debug("expiring client %p", client);
+ svc_rpc_gss_forget_client(client);
+ }
+ }
+}
+
+#ifdef DEBUG
+/*
+ * OID<->string routines. These are uuuuugly.
+ */
+static OM_uint32
+gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
+{
+ char numstr[128];
+ unsigned long number;
+ int numshift;
+ size_t string_length;
+ size_t i;
+ unsigned char *cp;
+ char *bp;
+
+ /* Decoded according to krb5/gssapi_krb5.c */
+
+ /* First determine the size of the string */
+ string_length = 0;
+ number = 0;
+ numshift = 0;
+ cp = (unsigned char *) oid->elements;
+ number = (unsigned long) cp[0];
+ sprintf(numstr, "%ld ", number/40);
+ string_length += strlen(numstr);
+ sprintf(numstr, "%ld ", number%40);
+ string_length += strlen(numstr);
+ for (i=1; i<oid->length; i++) {
+ if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
+ number = (number << 7) | (cp[i] & 0x7f);
+ numshift += 7;
+ }
+ else {
+ *minor_status = 0;
+ return(GSS_S_FAILURE);
+ }
+ if ((cp[i] & 0x80) == 0) {
+ sprintf(numstr, "%ld ", number);
+ string_length += strlen(numstr);
+ number = 0;
+ numshift = 0;
+ }
+ }
+ /*
+ * If we get here, we've calculated the length of "n n n ... n ". Add 4
+ * here for "{ " and "}\0".
+ */
+ string_length += 4;
+ if ((bp = (char *) mem_alloc(string_length))) {
+ strcpy(bp, "{ ");
+ number = (unsigned long) cp[0];
+ sprintf(numstr, "%ld ", number/40);
+ strcat(bp, numstr);
+ sprintf(numstr, "%ld ", number%40);
+ strcat(bp, numstr);
+ number = 0;
+ cp = (unsigned char *) oid->elements;
+ for (i=1; i<oid->length; i++) {
+ number = (number << 7) | (cp[i] & 0x7f);
+ if ((cp[i] & 0x80) == 0) {
+ sprintf(numstr, "%ld ", number);
+ strcat(bp, numstr);
+ number = 0;
+ }
+ }
+ strcat(bp, "}");
+ oid_str->length = strlen(bp)+1;
+ oid_str->value = (void *) bp;
+ *minor_status = 0;
+ return(GSS_S_COMPLETE);
+ }
+ *minor_status = 0;
+ return(GSS_S_FAILURE);
+}
+#endif
+
+static void
+svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
+ const gss_name_t name)
+{
+ OM_uint32 maj_stat, min_stat;
+ rpc_gss_ucred_t *uc = &client->cl_ucred;
+ int numgroups;
+
+ uc->uid = 65534;
+ uc->gid = 65534;
+ uc->gidlist = client->cl_gid_storage;
+
+ numgroups = NGROUPS;
+ maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
+ &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
+ if (GSS_ERROR(maj_stat))
+ uc->gidlen = 0;
+ else
+ uc->gidlen = numgroups;
+}
+
+static void
+svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
+{
+ static gss_OID_desc krb5_mech_oid =
+ {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+ /*
+ * Attempt to translate mech type and service into a
+ * 'pseudo flavor'. Hardwire in krb5 support for now.
+ */
+ if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
+ switch (client->cl_rawcred.service) {
+ case rpc_gss_svc_default:
+ case rpc_gss_svc_none:
+ client->cl_rpcflavor = RPCSEC_GSS_KRB5;
+ break;
+ case rpc_gss_svc_integrity:
+ client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
+ break;
+ case rpc_gss_svc_privacy:
+ client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
+ break;
+ }
+ } else {
+ client->cl_rpcflavor = RPCSEC_GSS;
+ }
+}
+
+static bool_t
+svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
+ struct svc_req *rqst,
+ struct rpc_gss_init_res *gr,
+ struct rpc_gss_cred *gc)
+{
+ gss_buffer_desc recv_tok;
+ gss_OID mech;
+ OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
+ OM_uint32 cred_lifetime;
+ struct svc_rpc_gss_svc_name *sname;
+
+ rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
+
+ /* Deserialize arguments. */
+ memset(&recv_tok, 0, sizeof(recv_tok));
+
+ if (!svc_getargs(rqst,
+ (xdrproc_t) xdr_gss_buffer_desc,
+ (caddr_t) &recv_tok)) {
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+
+ /*
+ * First time round, try all the server names we have until
+ * one matches. Afterwards, stick with that one.
+ */
+ sx_xlock(&svc_rpc_gss_lock);
+ if (!client->cl_sname) {
+ SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
+ if (sname->sn_program == rqst->rq_prog
+ && sname->sn_version == rqst->rq_vers) {
+ retry:
+ gr->gr_major = gss_accept_sec_context(
+ &gr->gr_minor,
+ &client->cl_ctx,
+ sname->sn_cred,
+ &recv_tok,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &client->cl_cname,
+ &mech,
+ &gr->gr_token,
+ &ret_flags,
+ &cred_lifetime,
+ &client->cl_creds);
+ if (gr->gr_major ==
+ GSS_S_CREDENTIALS_EXPIRED) {
+ /*
+ * Either our creds really did
+ * expire or gssd was
+ * restarted.
+ */
+ if (rpc_gss_acquire_svc_cred(sname))
+ goto retry;
+ }
+ client->cl_sname = sname;
+ break;
+ }
+ }
+ if (!sname) {
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &recv_tok);
+ sx_xunlock(&svc_rpc_gss_lock);
+ return (FALSE);
+ }
+ } else {
+ gr->gr_major = gss_accept_sec_context(
+ &gr->gr_minor,
+ &client->cl_ctx,
+ client->cl_sname->sn_cred,
+ &recv_tok,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &client->cl_cname,
+ &mech,
+ &gr->gr_token,
+ &ret_flags,
+ &cred_lifetime,
+ NULL);
+ }
+ sx_xunlock(&svc_rpc_gss_lock);
+
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
+
+ /*
+ * If we get an error from gss_accept_sec_context, send the
+ * reply anyway so that the client gets a chance to see what
+ * is wrong.
+ */
+ if (gr->gr_major != GSS_S_COMPLETE &&
+ gr->gr_major != GSS_S_CONTINUE_NEEDED) {
+ rpc_gss_log_status("accept_sec_context", client->cl_mech,
+ gr->gr_major, gr->gr_minor);
+ client->cl_state = CLIENT_STALE;
+ return (TRUE);
+ }
+
+ gr->gr_handle.value = &client->cl_id;
+ gr->gr_handle.length = sizeof(client->cl_id);
+ gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
+
+ /* Save client info. */
+ client->cl_mech = mech;
+ client->cl_qop = GSS_C_QOP_DEFAULT;
+ client->cl_done_callback = FALSE;
+
+ if (gr->gr_major == GSS_S_COMPLETE) {
+ gss_buffer_desc export_name;
+
+ /*
+ * Change client expiration time to be near when the
+ * client creds expire (or 24 hours if we can't figure
+ * that out).
+ */
+ if (cred_lifetime == GSS_C_INDEFINITE)
+ cred_lifetime = time_uptime + 24*60*60;
+
+ client->cl_expiration = time_uptime + cred_lifetime;
+
+ /*
+ * Fill in cred details in the rawcred structure.
+ */
+ client->cl_rawcred.version = RPCSEC_GSS_VERSION;
+ rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
+ maj_stat = gss_export_name(&min_stat, client->cl_cname,
+ &export_name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_export_name", client->cl_mech,
+ maj_stat, min_stat);
+ return (FALSE);
+ }
+ client->cl_rawcred.client_principal =
+ mem_alloc(sizeof(*client->cl_rawcred.client_principal)
+ + export_name.length);
+ client->cl_rawcred.client_principal->len = export_name.length;
+ memcpy(client->cl_rawcred.client_principal->name,
+ export_name.value, export_name.length);
+ gss_release_buffer(&min_stat, &export_name);
+ client->cl_rawcred.svc_principal =
+ client->cl_sname->sn_principal;
+ client->cl_rawcred.service = gc->gc_svc;
+
+ /*
+ * Use gss_pname_to_uid to map to unix creds. For
+ * kerberos5, this uses krb5_aname_to_localname.
+ */
+ svc_rpc_gss_build_ucred(client, client->cl_cname);
+ svc_rpc_gss_set_flavor(client);
+ gss_release_name(&min_stat, &client->cl_cname);
+
+#ifdef DEBUG
+ {
+ gss_buffer_desc mechname;
+
+ gss_oid_to_str(&min_stat, mech, &mechname);
+
+ rpc_gss_log_debug("accepted context for %s with "
+ "<mech %.*s, qop %d, svc %d>",
+ client->cl_rawcred.client_principal->name,
+ mechname.length, (char *)mechname.value,
+ client->cl_qop, client->rawcred.service);
+
+ gss_release_buffer(&min_stat, &mechname);
+ }
+#endif /* DEBUG */
+ }
+ return (TRUE);
+}
+
+static bool_t
+svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
+ gss_qop_t *qop)
+{
+ struct opaque_auth *oa;
+ gss_buffer_desc rpcbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+ gss_qop_t qop_state;
+ int32_t rpchdr[128 / sizeof(int32_t)];
+ int32_t *buf;
+
+ rpc_gss_log_debug("in svc_rpc_gss_validate()");
+
+ memset(rpchdr, 0, sizeof(rpchdr));
+
+ /* Reconstruct RPC header for signing (from xdr_callmsg). */
+ buf = rpchdr;
+ IXDR_PUT_LONG(buf, msg->rm_xid);
+ IXDR_PUT_ENUM(buf, msg->rm_direction);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
+ oa = &msg->rm_call.cb_cred;
+ IXDR_PUT_ENUM(buf, oa->oa_flavor);
+ IXDR_PUT_LONG(buf, oa->oa_length);
+ if (oa->oa_length) {
+ memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
+ buf += RNDUP(oa->oa_length) / sizeof(int32_t);
+ }
+ rpcbuf.value = rpchdr;
+ rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
+
+ checksum.value = msg->rm_call.cb_verf.oa_base;
+ checksum.length = msg->rm_call.cb_verf.oa_length;
+
+ maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
+ &qop_state);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_verify_mic", client->cl_mech,
+ maj_stat, min_stat);
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+
+ *qop = qop_state;
+ return (TRUE);
+}
+
+static bool_t
+svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
+ struct svc_req *rqst, u_int seq)
+{
+ gss_buffer_desc signbuf;
+ gss_buffer_desc mic;
+ OM_uint32 maj_stat, min_stat;
+ uint32_t nseq;
+
+ rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
+
+ nseq = htonl(seq);
+ signbuf.value = &nseq;
+ signbuf.length = sizeof(nseq);
+
+ maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
+ &signbuf, &mic);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+
+ KASSERT(mic.length <= MAX_AUTH_BYTES,
+ ("MIC too large for RPCSEC_GSS"));
+
+ rqst->rq_verf.oa_flavor = RPCSEC_GSS;
+ rqst->rq_verf.oa_length = mic.length;
+ bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
+
+ gss_release_buffer(&min_stat, &mic);
+
+ return (TRUE);
+}
+
+static bool_t
+svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
+{
+ struct svc_rpc_gss_callback *scb;
+ rpc_gss_lock_t lock;
+ void *cookie;
+ bool_t cb_res;
+ bool_t result;
+
+ /*
+ * See if we have a callback for this guy.
+ */
+ result = TRUE;
+ SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
+ if (scb->cb_callback.program == rqst->rq_prog
+ && scb->cb_callback.version == rqst->rq_vers) {
+ /*
+ * This one matches. Call the callback and see
+ * if it wants to veto or something.
+ */
+ lock.locked = FALSE;
+ lock.raw_cred = &client->cl_rawcred;
+ cb_res = scb->cb_callback.callback(rqst,
+ client->cl_creds,
+ client->cl_ctx,
+ &lock,
+ &cookie);
+
+ if (!cb_res) {
+ client->cl_state = CLIENT_STALE;
+ result = FALSE;
+ break;
+ }
+
+ /*
+ * The callback accepted the connection - it
+ * is responsible for freeing client->cl_creds
+ * now.
+ */
+ client->cl_creds = GSS_C_NO_CREDENTIAL;
+ client->cl_locked = lock.locked;
+ client->cl_cookie = cookie;
+ return (TRUE);
+ }
+ }
+
+ /*
+ * Either no callback exists for this program/version or one
+ * of the callbacks rejected the connection. We just need to
+ * clean up the delegated client creds, if any.
+ */
+ if (client->cl_creds) {
+ OM_uint32 min_ver;
+ gss_release_cred(&min_ver, &client->cl_creds);
+ }
+ return (result);
+}
+
+static bool_t
+svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
+{
+ u_int32_t offset;
+ int word, bit;
+ bool_t result;
+
+ sx_xlock(&client->cl_lock);
+ if (seq <= client->cl_seqlast) {
+ /*
+ * The request sequence number is less than
+ * the largest we have seen so far. If it is
+ * outside the window or if we have seen a
+ * request with this sequence before, silently
+ * discard it.
+ */
+ offset = client->cl_seqlast - seq;
+ if (offset >= SVC_RPC_GSS_SEQWINDOW) {
+ result = FALSE;
+ goto out;
+ }
+ word = offset / 32;
+ bit = offset % 32;
+ if (client->cl_seqmask[word] & (1 << bit)) {
+ result = FALSE;
+ goto out;
+ }
+ }
+
+ result = TRUE;
+out:
+ sx_xunlock(&client->cl_lock);
+ return (result);
+}
+
+static void
+svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
+{
+ int offset, i, word, bit;
+ uint32_t carry, newcarry;
+
+ sx_xlock(&client->cl_lock);
+ if (seq > client->cl_seqlast) {
+ /*
+ * This request has a sequence number greater
+ * than any we have seen so far. Advance the
+ * seq window and set bit zero of the window
+ * (which corresponds to the new sequence
+ * number)
+ */
+ offset = seq - client->cl_seqlast;
+ while (offset > 32) {
+ for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
+ i > 0; i--) {
+ client->cl_seqmask[i] = client->cl_seqmask[i-1];
+ }
+ client->cl_seqmask[0] = 0;
+ offset -= 32;
+ }
+ carry = 0;
+ for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
+ newcarry = client->cl_seqmask[i] >> (32 - offset);
+ client->cl_seqmask[i] =
+ (client->cl_seqmask[i] << offset) | carry;
+ carry = newcarry;
+ }
+ client->cl_seqmask[0] |= 1;
+ client->cl_seqlast = seq;
+ } else {
+ offset = client->cl_seqlast - seq;
+ word = offset / 32;
+ bit = offset % 32;
+ client->cl_seqmask[word] |= (1 << bit);
+ }
+ sx_xunlock(&client->cl_lock);
+}
+
+enum auth_stat
+svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
+
+{
+ OM_uint32 min_stat;
+ XDR xdrs;
+ struct svc_rpc_gss_cookedcred *cc;
+ struct svc_rpc_gss_client *client;
+ struct rpc_gss_cred gc;
+ struct rpc_gss_init_res gr;
+ gss_qop_t qop;
+ int call_stat;
+ enum auth_stat result;
+
+ rpc_gss_log_debug("in svc_rpc_gss()");
+
+ /* Garbage collect old clients. */
+ svc_rpc_gss_timeout_clients();
+
+ /* Initialize reply. */
+ rqst->rq_verf = _null_auth;
+
+ /* Deserialize client credentials. */
+ if (rqst->rq_cred.oa_length <= 0)
+ return (AUTH_BADCRED);
+
+ memset(&gc, 0, sizeof(gc));
+
+ xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
+ rqst->rq_cred.oa_length, XDR_DECODE);
+
+ if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
+ XDR_DESTROY(&xdrs);
+ return (AUTH_BADCRED);
+ }
+ XDR_DESTROY(&xdrs);
+
+ client = NULL;
+
+ /* Check version. */
+ if (gc.gc_version != RPCSEC_GSS_VERSION) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+
+ /* Check the proc and find the client (or create it) */
+ if (gc.gc_proc == RPCSEC_GSS_INIT) {
+ if (gc.gc_handle.length != 0) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+ client = svc_rpc_gss_create_client();
+ refcount_acquire(&client->cl_refs);
+ } else {
+ struct svc_rpc_gss_clientid *p;
+ if (gc.gc_handle.length != sizeof(*p)) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+ p = gc.gc_handle.value;
+ client = svc_rpc_gss_find_client(p);
+ if (!client) {
+ /*
+ * Can't find the client - we may have
+ * destroyed it - tell the other side to
+ * re-authenticate.
+ */
+ result = RPCSEC_GSS_CREDPROBLEM;
+ goto out;
+ }
+ }
+ cc = rqst->rq_clntcred;
+ cc->cc_client = client;
+ cc->cc_service = gc.gc_svc;
+ cc->cc_seq = gc.gc_seq;
+
+ /*
+ * The service and sequence number must be ignored for
+ * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
+ */
+ if (gc.gc_proc != RPCSEC_GSS_INIT
+ && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
+ /*
+ * Check for sequence number overflow.
+ */
+ if (gc.gc_seq >= MAXSEQ) {
+ result = RPCSEC_GSS_CTXPROBLEM;
+ goto out;
+ }
+
+ /*
+ * Check for valid service.
+ */
+ if (gc.gc_svc != rpc_gss_svc_none &&
+ gc.gc_svc != rpc_gss_svc_integrity &&
+ gc.gc_svc != rpc_gss_svc_privacy) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+ }
+
+ /* Handle RPCSEC_GSS control procedure. */
+ switch (gc.gc_proc) {
+
+ case RPCSEC_GSS_INIT:
+ case RPCSEC_GSS_CONTINUE_INIT:
+ if (rqst->rq_proc != NULLPROC) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+
+ memset(&gr, 0, sizeof(gr));
+ if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+
+ if (gr.gr_major == GSS_S_COMPLETE) {
+ /*
+ * We borrow the space for the call verf to
+ * pack our reply verf.
+ */
+ rqst->rq_verf = msg->rm_call.cb_verf;
+ if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+ } else {
+ rqst->rq_verf = _null_auth;
+ }
+
+ call_stat = svc_sendreply(rqst,
+ (xdrproc_t) xdr_rpc_gss_init_res,
+ (caddr_t) &gr);
+
+ gss_release_buffer(&min_stat, &gr.gr_token);
+
+ if (!call_stat) {
+ result = AUTH_FAILED;
+ break;
+ }
+
+ if (gr.gr_major == GSS_S_COMPLETE)
+ client->cl_state = CLIENT_ESTABLISHED;
+
+ result = RPCSEC_GSS_NODISPATCH;
+ break;
+
+ case RPCSEC_GSS_DATA:
+ case RPCSEC_GSS_DESTROY:
+ if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
+ result = RPCSEC_GSS_NODISPATCH;
+ break;
+ }
+
+ if (!svc_rpc_gss_validate(client, msg, &qop)) {
+ result = RPCSEC_GSS_CREDPROBLEM;
+ break;
+ }
+
+ /*
+ * We borrow the space for the call verf to pack our
+ * reply verf.
+ */
+ rqst->rq_verf = msg->rm_call.cb_verf;
+ if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
+ result = RPCSEC_GSS_CTXPROBLEM;
+ break;
+ }
+
+ svc_rpc_gss_update_seq(client, gc.gc_seq);
+
+ /*
+ * Change the SVCAUTH ops on the request to point at
+ * our own code so that we can unwrap the arguments
+ * and wrap the result. The caller will re-set this on
+ * every request to point to a set of null wrap/unwrap
+ * methods. Acquire an extra reference to the client
+ * which will be released by svc_rpc_gss_release()
+ * after the request has finished processing.
+ */
+ refcount_acquire(&client->cl_refs);
+ rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
+ rqst->rq_auth.svc_ah_private = cc;
+
+ if (gc.gc_proc == RPCSEC_GSS_DATA) {
+ /*
+ * We might be ready to do a callback to the server to
+ * see if it wants to accept/reject the connection.
+ */
+ sx_xlock(&client->cl_lock);
+ if (!client->cl_done_callback) {
+ client->cl_done_callback = TRUE;
+ client->cl_qop = qop;
+ client->cl_rawcred.qop = _rpc_gss_num_to_qop(
+ client->cl_rawcred.mechanism, qop);
+ if (!svc_rpc_gss_callback(client, rqst)) {
+ result = AUTH_REJECTEDCRED;
+ sx_xunlock(&client->cl_lock);
+ break;
+ }
+ }
+ sx_xunlock(&client->cl_lock);
+
+ /*
+ * If the server has locked this client to a
+ * particular service+qop pair, enforce that
+ * restriction now.
+ */
+ if (client->cl_locked) {
+ if (client->cl_rawcred.service != gc.gc_svc) {
+ result = AUTH_FAILED;
+ break;
+ } else if (client->cl_qop != qop) {
+ result = AUTH_BADVERF;
+ break;
+ }
+ }
+
+ /*
+ * If the qop changed, look up the new qop
+ * name for rawcred.
+ */
+ if (client->cl_qop != qop) {
+ client->cl_qop = qop;
+ client->cl_rawcred.qop = _rpc_gss_num_to_qop(
+ client->cl_rawcred.mechanism, qop);
+ }
+
+ /*
+ * Make sure we use the right service value
+ * for unwrap/wrap.
+ */
+ if (client->cl_rawcred.service != gc.gc_svc) {
+ client->cl_rawcred.service = gc.gc_svc;
+ svc_rpc_gss_set_flavor(client);
+ }
+
+ result = AUTH_OK;
+ } else {
+ if (rqst->rq_proc != NULLPROC) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+
+ call_stat = svc_sendreply(rqst,
+ (xdrproc_t) xdr_void, (caddr_t) NULL);
+
+ if (!call_stat) {
+ result = AUTH_FAILED;
+ break;
+ }
+
+ svc_rpc_gss_forget_client(client);
+
+ result = RPCSEC_GSS_NODISPATCH;
+ break;
+ }
+ break;
+
+ default:
+ result = AUTH_BADCRED;
+ break;
+ }
+out:
+ if (client)
+ svc_rpc_gss_release_client(client);
+
+ xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
+ return (result);
+}
+
+static bool_t
+svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
+{
+ struct svc_rpc_gss_cookedcred *cc;
+ struct svc_rpc_gss_client *client;
+
+ rpc_gss_log_debug("in svc_rpc_gss_wrap()");
+
+ cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
+ client = cc->cc_client;
+ if (client->cl_state != CLIENT_ESTABLISHED
+ || cc->cc_service == rpc_gss_svc_none) {
+ return (TRUE);
+ }
+
+ return (xdr_rpc_gss_wrap_data(mp,
+ client->cl_ctx, client->cl_qop,
+ cc->cc_service, cc->cc_seq));
+}
+
+static bool_t
+svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
+{
+ struct svc_rpc_gss_cookedcred *cc;
+ struct svc_rpc_gss_client *client;
+
+ rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
+
+ cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
+ client = cc->cc_client;
+ if (client->cl_state != CLIENT_ESTABLISHED
+ || cc->cc_service == rpc_gss_svc_none) {
+ return (TRUE);
+ }
+
+ return (xdr_rpc_gss_unwrap_data(mp,
+ client->cl_ctx, client->cl_qop,
+ cc->cc_service, cc->cc_seq));
+}
+
+static void
+svc_rpc_gss_release(SVCAUTH *auth)
+{
+ struct svc_rpc_gss_cookedcred *cc;
+ struct svc_rpc_gss_client *client;
+
+ rpc_gss_log_debug("in svc_rpc_gss_release()");
+
+ cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
+ client = cc->cc_client;
+ svc_rpc_gss_release_client(client);
+}
diff --git a/sys/rpc/svc.c b/sys/rpc/svc.c
index d6d6d78..8af9e80 100644
--- a/sys/rpc/svc.c
+++ b/sys/rpc/svc.c
@@ -49,37 +49,105 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/lock.h>
#include <sys/kernel.h>
+#include <sys/kthread.h>
#include <sys/malloc.h>
+#include <sys/mbuf.h>
#include <sys/mutex.h>
+#include <sys/proc.h>
#include <sys/queue.h>
+#include <sys/socketvar.h>
#include <sys/systm.h>
#include <sys/ucred.h>
#include <rpc/rpc.h>
#include <rpc/rpcb_clnt.h>
+#include <rpc/replay.h>
#include <rpc/rpc_com.h>
#define SVC_VERSQUIET 0x0001 /* keep quiet about vers mismatch */
-#define version_keepquiet(xp) ((u_long)(xp)->xp_p3 & SVC_VERSQUIET)
+#define version_keepquiet(xp) (SVC_EXT(xp)->xp_flags & SVC_VERSQUIET)
static struct svc_callout *svc_find(SVCPOOL *pool, rpcprog_t, rpcvers_t,
char *);
-static void __xprt_do_unregister (SVCXPRT *xprt, bool_t dolock);
+static void svc_new_thread(SVCPOOL *pool);
+static void xprt_unregister_locked(SVCXPRT *xprt);
/* *************** SVCXPRT related stuff **************** */
+static int svcpool_minthread_sysctl(SYSCTL_HANDLER_ARGS);
+static int svcpool_maxthread_sysctl(SYSCTL_HANDLER_ARGS);
+
SVCPOOL*
-svcpool_create(void)
+svcpool_create(const char *name, struct sysctl_oid_list *sysctl_base)
{
SVCPOOL *pool;
pool = malloc(sizeof(SVCPOOL), M_RPC, M_WAITOK|M_ZERO);
mtx_init(&pool->sp_lock, "sp_lock", NULL, MTX_DEF);
+ pool->sp_name = name;
+ pool->sp_state = SVCPOOL_INIT;
+ pool->sp_proc = NULL;
TAILQ_INIT(&pool->sp_xlist);
TAILQ_INIT(&pool->sp_active);
TAILQ_INIT(&pool->sp_callouts);
+ LIST_INIT(&pool->sp_threads);
+ LIST_INIT(&pool->sp_idlethreads);
+ pool->sp_minthreads = 1;
+ pool->sp_maxthreads = 1;
+ pool->sp_threadcount = 0;
+
+ /*
+ * Don't use more than a quarter of mbuf clusters or more than
+ * 45Mb buffering requests.
+ */
+ pool->sp_space_high = nmbclusters * MCLBYTES / 4;
+ if (pool->sp_space_high > 45 << 20)
+ pool->sp_space_high = 45 << 20;
+ pool->sp_space_low = 2 * pool->sp_space_high / 3;
+
+ sysctl_ctx_init(&pool->sp_sysctl);
+ if (sysctl_base) {
+ SYSCTL_ADD_PROC(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "minthreads", CTLTYPE_INT | CTLFLAG_RW,
+ pool, 0, svcpool_minthread_sysctl, "I", "");
+ SYSCTL_ADD_PROC(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "maxthreads", CTLTYPE_INT | CTLFLAG_RW,
+ pool, 0, svcpool_maxthread_sysctl, "I", "");
+ SYSCTL_ADD_INT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "threads", CTLFLAG_RD, &pool->sp_threadcount, 0, "");
+
+ SYSCTL_ADD_UINT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "request_space_used", CTLFLAG_RD,
+ &pool->sp_space_used, 0,
+ "Space in parsed but not handled requests.");
+
+ SYSCTL_ADD_UINT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "request_space_used_highest", CTLFLAG_RD,
+ &pool->sp_space_used_highest, 0,
+ "Highest space used since reboot.");
+
+ SYSCTL_ADD_UINT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "request_space_high", CTLFLAG_RW,
+ &pool->sp_space_high, 0,
+ "Maximum space in parsed but not handled requests.");
+
+ SYSCTL_ADD_UINT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "request_space_low", CTLFLAG_RW,
+ &pool->sp_space_low, 0,
+ "Low water mark for request space.");
+
+ SYSCTL_ADD_UINT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "request_space_throttled", CTLFLAG_RD,
+ &pool->sp_space_throttled, 0,
+ "Whether nfs requests are currently throttled");
+
+ SYSCTL_ADD_UINT(&pool->sp_sysctl, sysctl_base, OID_AUTO,
+ "request_space_throttle_count", CTLFLAG_RD,
+ &pool->sp_space_throttle_count, 0,
+ "Count of times throttling based on request space has occurred");
+ }
return pool;
}
@@ -87,16 +155,17 @@ svcpool_create(void)
void
svcpool_destroy(SVCPOOL *pool)
{
- SVCXPRT *xprt;
+ SVCXPRT *xprt, *nxprt;
struct svc_callout *s;
+ struct svcxprt_list cleanup;
+ TAILQ_INIT(&cleanup);
mtx_lock(&pool->sp_lock);
while (TAILQ_FIRST(&pool->sp_xlist)) {
xprt = TAILQ_FIRST(&pool->sp_xlist);
- mtx_unlock(&pool->sp_lock);
- SVC_DESTROY(xprt);
- mtx_lock(&pool->sp_lock);
+ xprt_unregister_locked(xprt);
+ TAILQ_INSERT_TAIL(&cleanup, xprt, xp_link);
}
while (TAILQ_FIRST(&pool->sp_callouts)) {
@@ -107,9 +176,97 @@ svcpool_destroy(SVCPOOL *pool)
}
mtx_destroy(&pool->sp_lock);
+
+ TAILQ_FOREACH_SAFE(xprt, &cleanup, xp_link, nxprt) {
+ SVC_RELEASE(xprt);
+ }
+
+ if (pool->sp_rcache)
+ replay_freecache(pool->sp_rcache);
+
+ sysctl_ctx_free(&pool->sp_sysctl);
free(pool, M_RPC);
}
+static bool_t
+svcpool_active(SVCPOOL *pool)
+{
+ enum svcpool_state state = pool->sp_state;
+
+ if (state == SVCPOOL_INIT || state == SVCPOOL_CLOSING)
+ return (FALSE);
+ return (TRUE);
+}
+
+/*
+ * Sysctl handler to set the minimum thread count on a pool
+ */
+static int
+svcpool_minthread_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ SVCPOOL *pool;
+ int newminthreads, error, n;
+
+ pool = oidp->oid_arg1;
+ newminthreads = pool->sp_minthreads;
+ error = sysctl_handle_int(oidp, &newminthreads, 0, req);
+ if (error == 0 && newminthreads != pool->sp_minthreads) {
+ if (newminthreads > pool->sp_maxthreads)
+ return (EINVAL);
+ mtx_lock(&pool->sp_lock);
+ if (newminthreads > pool->sp_minthreads
+ && svcpool_active(pool)) {
+ /*
+ * If the pool is running and we are
+ * increasing, create some more threads now.
+ */
+ n = newminthreads - pool->sp_threadcount;
+ if (n > 0) {
+ mtx_unlock(&pool->sp_lock);
+ while (n--)
+ svc_new_thread(pool);
+ mtx_lock(&pool->sp_lock);
+ }
+ }
+ pool->sp_minthreads = newminthreads;
+ mtx_unlock(&pool->sp_lock);
+ }
+ return (error);
+}
+
+/*
+ * Sysctl handler to set the maximum thread count on a pool
+ */
+static int
+svcpool_maxthread_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ SVCPOOL *pool;
+ SVCTHREAD *st;
+ int newmaxthreads, error;
+
+ pool = oidp->oid_arg1;
+ newmaxthreads = pool->sp_maxthreads;
+ error = sysctl_handle_int(oidp, &newmaxthreads, 0, req);
+ if (error == 0 && newmaxthreads != pool->sp_maxthreads) {
+ if (newmaxthreads < pool->sp_minthreads)
+ return (EINVAL);
+ mtx_lock(&pool->sp_lock);
+ if (newmaxthreads < pool->sp_maxthreads
+ && svcpool_active(pool)) {
+ /*
+ * If the pool is running and we are
+ * decreasing, wake up some idle threads to
+ * encourage them to exit.
+ */
+ LIST_FOREACH(st, &pool->sp_idlethreads, st_ilink)
+ cv_signal(&st->st_cond);
+ }
+ pool->sp_maxthreads = newmaxthreads;
+ mtx_unlock(&pool->sp_lock);
+ }
+ return (error);
+}
+
/*
* Activate a transport handle.
*/
@@ -125,40 +282,70 @@ xprt_register(SVCXPRT *xprt)
mtx_unlock(&pool->sp_lock);
}
-void
-xprt_unregister(SVCXPRT *xprt)
-{
- __xprt_do_unregister(xprt, TRUE);
-}
-
-void
-__xprt_unregister_unlocked(SVCXPRT *xprt)
-{
- __xprt_do_unregister(xprt, FALSE);
-}
-
/*
- * De-activate a transport handle.
+ * De-activate a transport handle. Note: the locked version doesn't
+ * release the transport - caller must do that after dropping the pool
+ * lock.
*/
static void
-__xprt_do_unregister(SVCXPRT *xprt, bool_t dolock)
+xprt_unregister_locked(SVCXPRT *xprt)
{
SVCPOOL *pool = xprt->xp_pool;
- //__svc_generic_cleanup(xprt);
-
- if (dolock)
- mtx_lock(&pool->sp_lock);
-
if (xprt->xp_active) {
TAILQ_REMOVE(&pool->sp_active, xprt, xp_alink);
xprt->xp_active = FALSE;
}
TAILQ_REMOVE(&pool->sp_xlist, xprt, xp_link);
xprt->xp_registered = FALSE;
+}
- if (dolock)
- mtx_unlock(&pool->sp_lock);
+void
+xprt_unregister(SVCXPRT *xprt)
+{
+ SVCPOOL *pool = xprt->xp_pool;
+
+ mtx_lock(&pool->sp_lock);
+ xprt_unregister_locked(xprt);
+ mtx_unlock(&pool->sp_lock);
+
+ SVC_RELEASE(xprt);
+}
+
+static void
+xprt_assignthread(SVCXPRT *xprt)
+{
+ SVCPOOL *pool = xprt->xp_pool;
+ SVCTHREAD *st;
+
+ /*
+ * Attempt to assign a service thread to this
+ * transport.
+ */
+ LIST_FOREACH(st, &pool->sp_idlethreads, st_ilink) {
+ if (st->st_xprt == NULL && STAILQ_EMPTY(&st->st_reqs))
+ break;
+ }
+ if (st) {
+ SVC_ACQUIRE(xprt);
+ xprt->xp_thread = st;
+ st->st_xprt = xprt;
+ cv_signal(&st->st_cond);
+ } else {
+ /*
+ * See if we can create a new thread. The
+ * actual thread creation happens in
+ * svc_run_internal because our locking state
+ * is poorly defined (we are typically called
+ * from a socket upcall). Don't create more
+ * than one thread per second.
+ */
+ if (pool->sp_state == SVCPOOL_ACTIVE
+ && pool->sp_lastcreatetime < time_uptime
+ && pool->sp_threadcount < pool->sp_maxthreads) {
+ pool->sp_state = SVCPOOL_THREADWANTED;
+ }
+ }
}
void
@@ -166,30 +353,42 @@ xprt_active(SVCXPRT *xprt)
{
SVCPOOL *pool = xprt->xp_pool;
+ if (!xprt->xp_registered) {
+ /*
+ * Race with xprt_unregister - we lose.
+ */
+ return;
+ }
+
mtx_lock(&pool->sp_lock);
if (!xprt->xp_active) {
TAILQ_INSERT_TAIL(&pool->sp_active, xprt, xp_alink);
xprt->xp_active = TRUE;
+ xprt_assignthread(xprt);
}
- wakeup(&pool->sp_active);
mtx_unlock(&pool->sp_lock);
}
void
-xprt_inactive(SVCXPRT *xprt)
+xprt_inactive_locked(SVCXPRT *xprt)
{
SVCPOOL *pool = xprt->xp_pool;
- mtx_lock(&pool->sp_lock);
-
if (xprt->xp_active) {
TAILQ_REMOVE(&pool->sp_active, xprt, xp_alink);
xprt->xp_active = FALSE;
}
- wakeup(&pool->sp_active);
+}
+
+void
+xprt_inactive(SVCXPRT *xprt)
+{
+ SVCPOOL *pool = xprt->xp_pool;
+ mtx_lock(&pool->sp_lock);
+ xprt_inactive_locked(xprt);
mtx_unlock(&pool->sp_lock);
}
@@ -253,9 +452,11 @@ rpcb_it:
if (nconf) {
bool_t dummy;
struct netconfig tnc;
+ struct netbuf nb;
tnc = *nconf;
- dummy = rpcb_set(prog, vers, &tnc,
- &((SVCXPRT *) xprt)->xp_ltaddr);
+ nb.buf = &xprt->xp_ltaddr;
+ nb.len = xprt->xp_ltaddr.ss_len;
+ dummy = rpcb_set(prog, vers, &tnc, &nb);
return (dummy);
}
return (TRUE);
@@ -305,270 +506,809 @@ svc_find(SVCPOOL *pool, rpcprog_t prog, rpcvers_t vers, char *netid)
/* ******************* REPLY GENERATION ROUTINES ************ */
+static bool_t
+svc_sendreply_common(struct svc_req *rqstp, struct rpc_msg *rply,
+ struct mbuf *body)
+{
+ SVCXPRT *xprt = rqstp->rq_xprt;
+ bool_t ok;
+
+ if (rqstp->rq_args) {
+ m_freem(rqstp->rq_args);
+ rqstp->rq_args = NULL;
+ }
+
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ rply, svc_getrpccaller(rqstp), body);
+
+ if (!SVCAUTH_WRAP(&rqstp->rq_auth, &body))
+ return (FALSE);
+
+ ok = SVC_REPLY(xprt, rply, rqstp->rq_addr, body);
+ if (rqstp->rq_addr) {
+ free(rqstp->rq_addr, M_SONAME);
+ rqstp->rq_addr = NULL;
+ }
+
+ return (ok);
+}
+
/*
* Send a reply to an rpc request
*/
bool_t
-svc_sendreply(SVCXPRT *xprt, xdrproc_t xdr_results, void * xdr_location)
+svc_sendreply(struct svc_req *rqstp, xdrproc_t xdr_results, void * xdr_location)
{
struct rpc_msg rply;
+ struct mbuf *m;
+ XDR xdrs;
+ bool_t ok;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_ACCEPTED;
- rply.acpted_rply.ar_verf = xprt->xp_verf;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
rply.acpted_rply.ar_stat = SUCCESS;
- rply.acpted_rply.ar_results.where = xdr_location;
- rply.acpted_rply.ar_results.proc = xdr_results;
+ rply.acpted_rply.ar_results.where = NULL;
+ rply.acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
+
+ MGET(m, M_WAIT, MT_DATA);
+ MCLGET(m, M_WAIT);
+ m->m_len = 0;
+ xdrmbuf_create(&xdrs, m, XDR_ENCODE);
+ ok = xdr_results(&xdrs, xdr_location);
+ XDR_DESTROY(&xdrs);
+
+ if (ok) {
+ return (svc_sendreply_common(rqstp, &rply, m));
+ } else {
+ m_freem(m);
+ return (FALSE);
+ }
+}
- return (SVC_REPLY(xprt, &rply));
+bool_t
+svc_sendreply_mbuf(struct svc_req *rqstp, struct mbuf *m)
+{
+ struct rpc_msg rply;
+
+ rply.rm_xid = rqstp->rq_xid;
+ rply.rm_direction = REPLY;
+ rply.rm_reply.rp_stat = MSG_ACCEPTED;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
+ rply.acpted_rply.ar_stat = SUCCESS;
+ rply.acpted_rply.ar_results.where = NULL;
+ rply.acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
+
+ return (svc_sendreply_common(rqstp, &rply, m));
}
/*
* No procedure error reply
*/
void
-svcerr_noproc(SVCXPRT *xprt)
+svcerr_noproc(struct svc_req *rqstp)
{
+ SVCXPRT *xprt = rqstp->rq_xprt;
struct rpc_msg rply;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_ACCEPTED;
- rply.acpted_rply.ar_verf = xprt->xp_verf;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
rply.acpted_rply.ar_stat = PROC_UNAVAIL;
- SVC_REPLY(xprt, &rply);
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ &rply, svc_getrpccaller(rqstp), NULL);
+
+ svc_sendreply_common(rqstp, &rply, NULL);
}
/*
* Can't decode args error reply
*/
void
-svcerr_decode(SVCXPRT *xprt)
+svcerr_decode(struct svc_req *rqstp)
{
+ SVCXPRT *xprt = rqstp->rq_xprt;
struct rpc_msg rply;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_ACCEPTED;
- rply.acpted_rply.ar_verf = xprt->xp_verf;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
rply.acpted_rply.ar_stat = GARBAGE_ARGS;
- SVC_REPLY(xprt, &rply);
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ &rply, (struct sockaddr *) &xprt->xp_rtaddr, NULL);
+
+ svc_sendreply_common(rqstp, &rply, NULL);
}
/*
* Some system error
*/
void
-svcerr_systemerr(SVCXPRT *xprt)
+svcerr_systemerr(struct svc_req *rqstp)
{
+ SVCXPRT *xprt = rqstp->rq_xprt;
struct rpc_msg rply;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_ACCEPTED;
- rply.acpted_rply.ar_verf = xprt->xp_verf;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
rply.acpted_rply.ar_stat = SYSTEM_ERR;
- SVC_REPLY(xprt, &rply);
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ &rply, svc_getrpccaller(rqstp), NULL);
+
+ svc_sendreply_common(rqstp, &rply, NULL);
}
/*
* Authentication error reply
*/
void
-svcerr_auth(SVCXPRT *xprt, enum auth_stat why)
+svcerr_auth(struct svc_req *rqstp, enum auth_stat why)
{
+ SVCXPRT *xprt = rqstp->rq_xprt;
struct rpc_msg rply;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_DENIED;
rply.rjcted_rply.rj_stat = AUTH_ERROR;
rply.rjcted_rply.rj_why = why;
- SVC_REPLY(xprt, &rply);
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ &rply, svc_getrpccaller(rqstp), NULL);
+
+ svc_sendreply_common(rqstp, &rply, NULL);
}
/*
* Auth too weak error reply
*/
void
-svcerr_weakauth(SVCXPRT *xprt)
+svcerr_weakauth(struct svc_req *rqstp)
{
- svcerr_auth(xprt, AUTH_TOOWEAK);
+ svcerr_auth(rqstp, AUTH_TOOWEAK);
}
/*
* Program unavailable error reply
*/
void
-svcerr_noprog(SVCXPRT *xprt)
+svcerr_noprog(struct svc_req *rqstp)
{
+ SVCXPRT *xprt = rqstp->rq_xprt;
struct rpc_msg rply;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_ACCEPTED;
- rply.acpted_rply.ar_verf = xprt->xp_verf;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
rply.acpted_rply.ar_stat = PROG_UNAVAIL;
- SVC_REPLY(xprt, &rply);
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ &rply, svc_getrpccaller(rqstp), NULL);
+
+ svc_sendreply_common(rqstp, &rply, NULL);
}
/*
* Program version mismatch error reply
*/
void
-svcerr_progvers(SVCXPRT *xprt, rpcvers_t low_vers, rpcvers_t high_vers)
+svcerr_progvers(struct svc_req *rqstp, rpcvers_t low_vers, rpcvers_t high_vers)
{
+ SVCXPRT *xprt = rqstp->rq_xprt;
struct rpc_msg rply;
+ rply.rm_xid = rqstp->rq_xid;
rply.rm_direction = REPLY;
rply.rm_reply.rp_stat = MSG_ACCEPTED;
- rply.acpted_rply.ar_verf = xprt->xp_verf;
+ rply.acpted_rply.ar_verf = rqstp->rq_verf;
rply.acpted_rply.ar_stat = PROG_MISMATCH;
rply.acpted_rply.ar_vers.low = (uint32_t)low_vers;
rply.acpted_rply.ar_vers.high = (uint32_t)high_vers;
- SVC_REPLY(xprt, &rply);
+ if (xprt->xp_pool->sp_rcache)
+ replay_setreply(xprt->xp_pool->sp_rcache,
+ &rply, svc_getrpccaller(rqstp), NULL);
+
+ svc_sendreply_common(rqstp, &rply, NULL);
}
-/* ******************* SERVER INPUT STUFF ******************* */
+/*
+ * Allocate a new server transport structure. All fields are
+ * initialized to zero and xp_p3 is initialized to point at an
+ * extension structure to hold various flags and authentication
+ * parameters.
+ */
+SVCXPRT *
+svc_xprt_alloc()
+{
+ SVCXPRT *xprt;
+ SVCXPRT_EXT *ext;
+
+ xprt = mem_alloc(sizeof(SVCXPRT));
+ memset(xprt, 0, sizeof(SVCXPRT));
+ ext = mem_alloc(sizeof(SVCXPRT_EXT));
+ memset(ext, 0, sizeof(SVCXPRT_EXT));
+ xprt->xp_p3 = ext;
+ refcount_init(&xprt->xp_refs, 1);
+
+ return (xprt);
+}
/*
- * Get server side input from some transport.
- *
- * Statement of authentication parameters management:
- * This function owns and manages all authentication parameters, specifically
- * the "raw" parameters (msg.rm_call.cb_cred and msg.rm_call.cb_verf) and
- * the "cooked" credentials (rqst->rq_clntcred).
- * In-kernel, we represent non-trivial cooked creds with struct ucred.
- * In all events, all three parameters are freed upon exit from this routine.
- * The storage is trivially management on the call stack in user land, but
- * is mallocated in kernel land.
+ * Free a server transport structure.
*/
+void
+svc_xprt_free(xprt)
+ SVCXPRT *xprt;
+{
-static void
-svc_getreq(SVCXPRT *xprt)
+ mem_free(xprt->xp_p3, sizeof(SVCXPRT_EXT));
+ mem_free(xprt, sizeof(SVCXPRT));
+}
+
+/* ******************* SERVER INPUT STUFF ******************* */
+
+/*
+ * Read RPC requests from a transport and queue them to be
+ * executed. We handle authentication and replay cache replies here.
+ * Actually dispatching the RPC is deferred till svc_executereq.
+ */
+static enum xprt_stat
+svc_getreq(SVCXPRT *xprt, struct svc_req **rqstp_ret)
{
SVCPOOL *pool = xprt->xp_pool;
- struct svc_req r;
+ struct svc_req *r;
struct rpc_msg msg;
- int prog_found;
- rpcvers_t low_vers;
- rpcvers_t high_vers;
+ struct mbuf *args;
enum xprt_stat stat;
- char cred_area[2*MAX_AUTH_BYTES + sizeof(struct xucred)];
-
- msg.rm_call.cb_cred.oa_base = cred_area;
- msg.rm_call.cb_verf.oa_base = &cred_area[MAX_AUTH_BYTES];
- r.rq_clntcred = &cred_area[2*MAX_AUTH_BYTES];
/* now receive msgs from xprtprt (support batch calls) */
- do {
- if (SVC_RECV(xprt, &msg)) {
-
- /* now find the exported program and call it */
- struct svc_callout *s;
- enum auth_stat why;
-
- r.rq_xprt = xprt;
- r.rq_prog = msg.rm_call.cb_prog;
- r.rq_vers = msg.rm_call.cb_vers;
- r.rq_proc = msg.rm_call.cb_proc;
- r.rq_cred = msg.rm_call.cb_cred;
- /* first authenticate the message */
- if ((why = _authenticate(&r, &msg)) != AUTH_OK) {
- svcerr_auth(xprt, why);
+ r = malloc(sizeof(*r), M_RPC, M_WAITOK|M_ZERO);
+
+ msg.rm_call.cb_cred.oa_base = r->rq_credarea;
+ msg.rm_call.cb_verf.oa_base = &r->rq_credarea[MAX_AUTH_BYTES];
+ r->rq_clntcred = &r->rq_credarea[2*MAX_AUTH_BYTES];
+ if (SVC_RECV(xprt, &msg, &r->rq_addr, &args)) {
+ enum auth_stat why;
+
+ /*
+ * Handle replays and authenticate before queuing the
+ * request to be executed.
+ */
+ SVC_ACQUIRE(xprt);
+ r->rq_xprt = xprt;
+ if (pool->sp_rcache) {
+ struct rpc_msg repmsg;
+ struct mbuf *repbody;
+ enum replay_state rs;
+ rs = replay_find(pool->sp_rcache, &msg,
+ svc_getrpccaller(r), &repmsg, &repbody);
+ switch (rs) {
+ case RS_NEW:
+ break;
+ case RS_DONE:
+ SVC_REPLY(xprt, &repmsg, r->rq_addr,
+ repbody);
+ if (r->rq_addr) {
+ free(r->rq_addr, M_SONAME);
+ r->rq_addr = NULL;
+ }
+ goto call_done;
+
+ default:
goto call_done;
}
- /* now match message with a registered service*/
- prog_found = FALSE;
- low_vers = (rpcvers_t) -1L;
- high_vers = (rpcvers_t) 0L;
- TAILQ_FOREACH(s, &pool->sp_callouts, sc_link) {
- if (s->sc_prog == r.rq_prog) {
- if (s->sc_vers == r.rq_vers) {
- (*s->sc_dispatch)(&r, xprt);
- goto call_done;
- } /* found correct version */
- prog_found = TRUE;
- if (s->sc_vers < low_vers)
- low_vers = s->sc_vers;
- if (s->sc_vers > high_vers)
- high_vers = s->sc_vers;
- } /* found correct program */
- }
+ }
+
+ r->rq_xid = msg.rm_xid;
+ r->rq_prog = msg.rm_call.cb_prog;
+ r->rq_vers = msg.rm_call.cb_vers;
+ r->rq_proc = msg.rm_call.cb_proc;
+ r->rq_size = sizeof(*r) + m_length(args, NULL);
+ r->rq_args = args;
+ if ((why = _authenticate(r, &msg)) != AUTH_OK) {
/*
- * if we got here, the program or version
- * is not served ...
+ * RPCSEC_GSS uses this return code
+ * for requests that form part of its
+ * context establishment protocol and
+ * should not be dispatched to the
+ * application.
*/
- if (prog_found)
- svcerr_progvers(xprt, low_vers, high_vers);
- else
- svcerr_noprog(xprt);
- /* Fall through to ... */
+ if (why != RPCSEC_GSS_NODISPATCH)
+ svcerr_auth(r, why);
+ goto call_done;
}
+
+ if (!SVCAUTH_UNWRAP(&r->rq_auth, &r->rq_args)) {
+ svcerr_decode(r);
+ goto call_done;
+ }
+
/*
- * Check if the xprt has been disconnected in a
- * recursive call in the service dispatch routine.
- * If so, then break.
+ * Everything checks out, return request to caller.
*/
- mtx_lock(&pool->sp_lock);
- if (!xprt->xp_registered) {
- mtx_unlock(&pool->sp_lock);
- break;
- }
- mtx_unlock(&pool->sp_lock);
+ *rqstp_ret = r;
+ r = NULL;
+ }
call_done:
- if ((stat = SVC_STAT(xprt)) == XPRT_DIED) {
- SVC_DESTROY(xprt);
- break;
+ if (r) {
+ svc_freereq(r);
+ r = NULL;
+ }
+ if ((stat = SVC_STAT(xprt)) == XPRT_DIED) {
+ xprt_unregister(xprt);
+ }
+
+ return (stat);
+}
+
+static void
+svc_executereq(struct svc_req *rqstp)
+{
+ SVCXPRT *xprt = rqstp->rq_xprt;
+ SVCPOOL *pool = xprt->xp_pool;
+ int prog_found;
+ rpcvers_t low_vers;
+ rpcvers_t high_vers;
+ struct svc_callout *s;
+
+ /* now match message with a registered service*/
+ prog_found = FALSE;
+ low_vers = (rpcvers_t) -1L;
+ high_vers = (rpcvers_t) 0L;
+ TAILQ_FOREACH(s, &pool->sp_callouts, sc_link) {
+ if (s->sc_prog == rqstp->rq_prog) {
+ if (s->sc_vers == rqstp->rq_vers) {
+ /*
+ * We hand ownership of r to the
+ * dispatch method - they must call
+ * svc_freereq.
+ */
+ (*s->sc_dispatch)(rqstp, xprt);
+ return;
+ } /* found correct version */
+ prog_found = TRUE;
+ if (s->sc_vers < low_vers)
+ low_vers = s->sc_vers;
+ if (s->sc_vers > high_vers)
+ high_vers = s->sc_vers;
+ } /* found correct program */
+ }
+
+ /*
+ * if we got here, the program or version
+ * is not served ...
+ */
+ if (prog_found)
+ svcerr_progvers(rqstp, low_vers, high_vers);
+ else
+ svcerr_noprog(rqstp);
+
+ svc_freereq(rqstp);
+}
+
+static void
+svc_checkidle(SVCPOOL *pool)
+{
+ SVCXPRT *xprt, *nxprt;
+ time_t timo;
+ struct svcxprt_list cleanup;
+
+ TAILQ_INIT(&cleanup);
+ TAILQ_FOREACH_SAFE(xprt, &pool->sp_xlist, xp_link, nxprt) {
+ /*
+ * Only some transports have idle timers. Don't time
+ * something out which is just waking up.
+ */
+ if (!xprt->xp_idletimeout || xprt->xp_thread)
+ continue;
+
+ timo = xprt->xp_lastactive + xprt->xp_idletimeout;
+ if (time_uptime > timo) {
+ xprt_unregister_locked(xprt);
+ TAILQ_INSERT_TAIL(&cleanup, xprt, xp_link);
}
- } while (stat == XPRT_MOREREQS);
+ }
+
+ mtx_unlock(&pool->sp_lock);
+ TAILQ_FOREACH_SAFE(xprt, &cleanup, xp_link, nxprt) {
+ SVC_RELEASE(xprt);
+ }
+ mtx_lock(&pool->sp_lock);
+
}
-void
-svc_run(SVCPOOL *pool)
+static void
+svc_assign_waiting_sockets(SVCPOOL *pool)
+{
+ SVCXPRT *xprt;
+
+ TAILQ_FOREACH(xprt, &pool->sp_active, xp_alink) {
+ if (!xprt->xp_thread) {
+ xprt_assignthread(xprt);
+ }
+ }
+}
+
+static bool_t
+svc_request_space_available(SVCPOOL *pool)
+{
+
+ mtx_assert(&pool->sp_lock, MA_OWNED);
+
+ if (pool->sp_space_throttled) {
+ /*
+ * Below the low-water yet? If so, assign any waiting sockets.
+ */
+ if (pool->sp_space_used < pool->sp_space_low) {
+ pool->sp_space_throttled = FALSE;
+ svc_assign_waiting_sockets(pool);
+ return TRUE;
+ }
+
+ return FALSE;
+ } else {
+ if (pool->sp_space_used
+ >= pool->sp_space_high) {
+ pool->sp_space_throttled = TRUE;
+ pool->sp_space_throttle_count++;
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+}
+
+static void
+svc_run_internal(SVCPOOL *pool, bool_t ismaster)
{
+ SVCTHREAD *st, *stpref;
SVCXPRT *xprt;
+ enum xprt_stat stat;
+ struct svc_req *rqstp;
int error;
+ st = mem_alloc(sizeof(*st));
+ st->st_xprt = NULL;
+ STAILQ_INIT(&st->st_reqs);
+ cv_init(&st->st_cond, "rpcsvc");
+
mtx_lock(&pool->sp_lock);
+ LIST_INSERT_HEAD(&pool->sp_threads, st, st_link);
- pool->sp_exited = FALSE;
+ /*
+ * If we are a new thread which was spawned to cope with
+ * increased load, set the state back to SVCPOOL_ACTIVE.
+ */
+ if (pool->sp_state == SVCPOOL_THREADSTARTING)
+ pool->sp_state = SVCPOOL_ACTIVE;
- while (!pool->sp_exited) {
- xprt = TAILQ_FIRST(&pool->sp_active);
- if (!xprt) {
- error = msleep(&pool->sp_active, &pool->sp_lock, PCATCH,
- "rpcsvc", 0);
- if (error)
+ while (pool->sp_state != SVCPOOL_CLOSING) {
+ /*
+ * Check for idle transports once per second.
+ */
+ if (time_uptime > pool->sp_lastidlecheck) {
+ pool->sp_lastidlecheck = time_uptime;
+ svc_checkidle(pool);
+ }
+
+ xprt = st->st_xprt;
+ if (!xprt && STAILQ_EMPTY(&st->st_reqs)) {
+ /*
+ * Enforce maxthreads count.
+ */
+ if (pool->sp_threadcount > pool->sp_maxthreads)
+ break;
+
+ /*
+ * Before sleeping, see if we can find an
+ * active transport which isn't being serviced
+ * by a thread.
+ */
+ if (svc_request_space_available(pool)) {
+ TAILQ_FOREACH(xprt, &pool->sp_active,
+ xp_alink) {
+ if (!xprt->xp_thread) {
+ SVC_ACQUIRE(xprt);
+ xprt->xp_thread = st;
+ st->st_xprt = xprt;
+ break;
+ }
+ }
+ }
+ if (st->st_xprt)
+ continue;
+
+ LIST_INSERT_HEAD(&pool->sp_idlethreads, st, st_ilink);
+ error = cv_timedwait_sig(&st->st_cond, &pool->sp_lock,
+ 5 * hz);
+ LIST_REMOVE(st, st_ilink);
+
+ /*
+ * Reduce worker thread count when idle.
+ */
+ if (error == EWOULDBLOCK) {
+ if (!ismaster
+ && (pool->sp_threadcount
+ > pool->sp_minthreads)
+ && !st->st_xprt
+ && STAILQ_EMPTY(&st->st_reqs))
+ break;
+ }
+ if (error == EWOULDBLOCK)
+ continue;
+ if (error) {
+ if (pool->sp_state != SVCPOOL_CLOSING) {
+ mtx_unlock(&pool->sp_lock);
+ svc_exit(pool);
+ mtx_lock(&pool->sp_lock);
+ }
break;
+ }
+
+ if (pool->sp_state == SVCPOOL_THREADWANTED) {
+ pool->sp_state = SVCPOOL_THREADSTARTING;
+ pool->sp_lastcreatetime = time_uptime;
+ mtx_unlock(&pool->sp_lock);
+ svc_new_thread(pool);
+ mtx_lock(&pool->sp_lock);
+ }
continue;
}
+ if (xprt) {
+ /*
+ * Drain the transport socket and queue up any
+ * RPCs.
+ */
+ xprt->xp_lastactive = time_uptime;
+ stat = XPRT_IDLE;
+ do {
+ if (!svc_request_space_available(pool))
+ break;
+ rqstp = NULL;
+ mtx_unlock(&pool->sp_lock);
+ stat = svc_getreq(xprt, &rqstp);
+ mtx_lock(&pool->sp_lock);
+ if (rqstp) {
+ /*
+ * See if the application has
+ * a preference for some other
+ * thread.
+ */
+ stpref = st;
+ if (pool->sp_assign)
+ stpref = pool->sp_assign(st,
+ rqstp);
+
+ pool->sp_space_used +=
+ rqstp->rq_size;
+ if (pool->sp_space_used
+ > pool->sp_space_used_highest)
+ pool->sp_space_used_highest =
+ pool->sp_space_used;
+ rqstp->rq_thread = stpref;
+ STAILQ_INSERT_TAIL(&stpref->st_reqs,
+ rqstp, rq_link);
+ stpref->st_reqcount++;
+
+ /*
+ * If we assigned the request
+ * to another thread, make
+ * sure its awake and continue
+ * reading from the
+ * socket. Otherwise, try to
+ * find some other thread to
+ * read from the socket and
+ * execute the request
+ * immediately.
+ */
+ if (stpref != st) {
+ cv_signal(&stpref->st_cond);
+ continue;
+ } else {
+ break;
+ }
+ }
+ } while (stat == XPRT_MOREREQS
+ && pool->sp_state != SVCPOOL_CLOSING);
+
+ /*
+ * Move this transport to the end of the
+ * active list to ensure fairness when
+ * multiple transports are active. If this was
+ * the last queued request, svc_getreq will
+ * end up calling xprt_inactive to remove from
+ * the active list.
+ */
+ xprt->xp_thread = NULL;
+ st->st_xprt = NULL;
+ if (xprt->xp_active) {
+ xprt_assignthread(xprt);
+ TAILQ_REMOVE(&pool->sp_active, xprt, xp_alink);
+ TAILQ_INSERT_TAIL(&pool->sp_active, xprt,
+ xp_alink);
+ }
+ mtx_unlock(&pool->sp_lock);
+ SVC_RELEASE(xprt);
+ mtx_lock(&pool->sp_lock);
+ }
+
/*
- * Move this transport to the end to ensure fairness
- * when multiple transports are active. If this was
- * the last queued request, svc_getreq will end up
- * calling xprt_inactive to remove from the active
- * list.
+ * Execute what we have queued.
*/
- TAILQ_REMOVE(&pool->sp_active, xprt, xp_alink);
- TAILQ_INSERT_TAIL(&pool->sp_active, xprt, xp_alink);
+ while ((rqstp = STAILQ_FIRST(&st->st_reqs)) != NULL) {
+ size_t sz = rqstp->rq_size;
+ mtx_unlock(&pool->sp_lock);
+ svc_executereq(rqstp);
+ mtx_lock(&pool->sp_lock);
+ pool->sp_space_used -= sz;
+ }
+ }
- mtx_unlock(&pool->sp_lock);
- svc_getreq(xprt);
- mtx_lock(&pool->sp_lock);
+ if (st->st_xprt) {
+ xprt = st->st_xprt;
+ st->st_xprt = NULL;
+ SVC_RELEASE(xprt);
+ }
+
+ KASSERT(STAILQ_EMPTY(&st->st_reqs), ("stray reqs on exit"));
+ LIST_REMOVE(st, st_link);
+ pool->sp_threadcount--;
+
+ mtx_unlock(&pool->sp_lock);
+
+ cv_destroy(&st->st_cond);
+ mem_free(st, sizeof(*st));
+
+ if (!ismaster)
+ wakeup(pool);
+}
+
+static void
+svc_thread_start(void *arg)
+{
+
+ svc_run_internal((SVCPOOL *) arg, FALSE);
+ kthread_exit();
+}
+
+static void
+svc_new_thread(SVCPOOL *pool)
+{
+ struct thread *td;
+
+ pool->sp_threadcount++;
+ kthread_add(svc_thread_start, pool,
+ pool->sp_proc, &td, 0, 0,
+ "%s: service", pool->sp_name);
+}
+
+void
+svc_run(SVCPOOL *pool)
+{
+ int i;
+ struct proc *p;
+ struct thread *td;
+
+ p = curproc;
+ td = curthread;
+ snprintf(td->td_name, sizeof(td->td_name),
+ "%s: master", pool->sp_name);
+ pool->sp_state = SVCPOOL_ACTIVE;
+ pool->sp_proc = p;
+ pool->sp_lastcreatetime = time_uptime;
+ pool->sp_threadcount = 1;
+
+ for (i = 1; i < pool->sp_minthreads; i++) {
+ svc_new_thread(pool);
}
+ svc_run_internal(pool, TRUE);
+
+ mtx_lock(&pool->sp_lock);
+ while (pool->sp_threadcount > 0)
+ msleep(pool, &pool->sp_lock, 0, "svcexit", 0);
mtx_unlock(&pool->sp_lock);
}
void
svc_exit(SVCPOOL *pool)
{
+ SVCTHREAD *st;
+
mtx_lock(&pool->sp_lock);
- pool->sp_exited = TRUE;
- wakeup(&pool->sp_active);
+
+ pool->sp_state = SVCPOOL_CLOSING;
+ LIST_FOREACH(st, &pool->sp_idlethreads, st_ilink)
+ cv_signal(&st->st_cond);
+
mtx_unlock(&pool->sp_lock);
}
+
+bool_t
+svc_getargs(struct svc_req *rqstp, xdrproc_t xargs, void *args)
+{
+ struct mbuf *m;
+ XDR xdrs;
+ bool_t stat;
+
+ m = rqstp->rq_args;
+ rqstp->rq_args = NULL;
+
+ xdrmbuf_create(&xdrs, m, XDR_DECODE);
+ stat = xargs(&xdrs, args);
+ XDR_DESTROY(&xdrs);
+
+ return (stat);
+}
+
+bool_t
+svc_freeargs(struct svc_req *rqstp, xdrproc_t xargs, void *args)
+{
+ XDR xdrs;
+
+ if (rqstp->rq_addr) {
+ free(rqstp->rq_addr, M_SONAME);
+ rqstp->rq_addr = NULL;
+ }
+
+ xdrs.x_op = XDR_FREE;
+ return (xargs(&xdrs, args));
+}
+
+void
+svc_freereq(struct svc_req *rqstp)
+{
+ SVCTHREAD *st;
+ SVCXPRT *xprt;
+ SVCPOOL *pool;
+
+ st = rqstp->rq_thread;
+ xprt = rqstp->rq_xprt;
+ if (xprt)
+ pool = xprt->xp_pool;
+ else
+ pool = NULL;
+ if (st) {
+ mtx_lock(&pool->sp_lock);
+ KASSERT(rqstp == STAILQ_FIRST(&st->st_reqs),
+ ("Freeing request out of order"));
+ STAILQ_REMOVE_HEAD(&st->st_reqs, rq_link);
+ st->st_reqcount--;
+ if (pool->sp_done)
+ pool->sp_done(st, rqstp);
+ mtx_unlock(&pool->sp_lock);
+ }
+
+ if (rqstp->rq_auth.svc_ah_ops)
+ SVCAUTH_RELEASE(&rqstp->rq_auth);
+
+ if (rqstp->rq_xprt) {
+ SVC_RELEASE(rqstp->rq_xprt);
+ }
+
+ if (rqstp->rq_addr)
+ free(rqstp->rq_addr, M_SONAME);
+
+ if (rqstp->rq_args)
+ m_freem(rqstp->rq_args);
+
+ free(rqstp, M_RPC);
+}
diff --git a/sys/rpc/svc.h b/sys/rpc/svc.h
index 21c7491..eac9bc0 100644
--- a/sys/rpc/svc.h
+++ b/sys/rpc/svc.h
@@ -47,6 +47,9 @@
#include <sys/queue.h>
#include <sys/_lock.h>
#include <sys/_mutex.h>
+#include <sys/_sx.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
#endif
/*
@@ -92,8 +95,23 @@ enum xprt_stat {
};
struct __rpc_svcxprt;
+struct mbuf;
struct xp_ops {
+#ifdef _KERNEL
+ /* receive incoming requests */
+ bool_t (*xp_recv)(struct __rpc_svcxprt *, struct rpc_msg *,
+ struct sockaddr **, struct mbuf **);
+ /* get transport status */
+ enum xprt_stat (*xp_stat)(struct __rpc_svcxprt *);
+ /* send reply */
+ bool_t (*xp_reply)(struct __rpc_svcxprt *, struct rpc_msg *,
+ struct sockaddr *, struct mbuf *);
+ /* destroy this struct */
+ void (*xp_destroy)(struct __rpc_svcxprt *);
+ /* catch-all function */
+ bool_t (*xp_control)(struct __rpc_svcxprt *, const u_int, void *);
+#else
/* receive incoming requests */
bool_t (*xp_recv)(struct __rpc_svcxprt *, struct rpc_msg *);
/* get transport status */
@@ -106,9 +124,6 @@ struct xp_ops {
bool_t (*xp_freeargs)(struct __rpc_svcxprt *, xdrproc_t, void *);
/* destroy this struct */
void (*xp_destroy)(struct __rpc_svcxprt *);
-#ifdef _KERNEL
- /* catch-all function */
- bool_t (*xp_control)(struct __rpc_svcxprt *, const u_int, void *);
#endif
};
@@ -121,32 +136,35 @@ struct xp_ops2 {
#ifdef _KERNEL
struct __rpc_svcpool;
+struct __rpc_svcthread;
#endif
/*
- * Server side transport handle
+ * Server side transport handle. In the kernel, transports have a
+ * reference count which tracks the number of currently assigned
+ * worker threads plus one for the service pool's reference.
*/
typedef struct __rpc_svcxprt {
#ifdef _KERNEL
- struct mtx xp_lock;
+ volatile u_int xp_refs;
+ struct sx xp_lock;
struct __rpc_svcpool *xp_pool; /* owning pool (see below) */
TAILQ_ENTRY(__rpc_svcxprt) xp_link;
TAILQ_ENTRY(__rpc_svcxprt) xp_alink;
bool_t xp_registered; /* xprt_register has been called */
bool_t xp_active; /* xprt_active has been called */
+ struct __rpc_svcthread *xp_thread; /* assigned service thread */
struct socket* xp_socket;
const struct xp_ops *xp_ops;
char *xp_netid; /* network token */
- struct netbuf xp_ltaddr; /* local transport address */
- struct netbuf xp_rtaddr; /* remote transport address */
- struct opaque_auth xp_verf; /* raw response verifier */
- uint32_t xp_xid; /* current transaction ID */
- XDR xp_xdrreq; /* xdr stream for decoding request */
- XDR xp_xdrrep; /* xdr stream for encoding reply */
+ struct sockaddr_storage xp_ltaddr; /* local transport address */
+ struct sockaddr_storage xp_rtaddr; /* remote transport address */
void *xp_p1; /* private: for use by svc ops */
void *xp_p2; /* private: for use by svc ops */
void *xp_p3; /* private: for use by svc lib */
int xp_type; /* transport type */
+ int xp_idletimeout; /* idle time before closing */
+ time_t xp_lastactive; /* time of last RPC */
#else
int xp_fd;
u_short xp_port; /* associated port number */
@@ -167,6 +185,33 @@ typedef struct __rpc_svcxprt {
#endif
} SVCXPRT;
+/*
+ * Interface to server-side authentication flavors.
+ */
+typedef struct __rpc_svcauth {
+ struct svc_auth_ops {
+#ifdef _KERNEL
+ int (*svc_ah_wrap)(struct __rpc_svcauth *, struct mbuf **);
+ int (*svc_ah_unwrap)(struct __rpc_svcauth *, struct mbuf **);
+ void (*svc_ah_release)(struct __rpc_svcauth *);
+#else
+ int (*svc_ah_wrap)(struct __rpc_svcauth *, XDR *,
+ xdrproc_t, caddr_t);
+ int (*svc_ah_unwrap)(struct __rpc_svcauth *, XDR *,
+ xdrproc_t, caddr_t);
+#endif
+ } *svc_ah_ops;
+ void *svc_ah_private;
+} SVCAUTH;
+
+/*
+ * Server transport extensions (accessed via xp_p3).
+ */
+typedef struct __rpc_svcxprt_ext {
+ int xp_flags; /* versquiet */
+ SVCAUTH xp_auth; /* interface to auth methods */
+} SVCXPRT_EXT;
+
#ifdef _KERNEL
/*
@@ -184,6 +229,61 @@ struct svc_callout {
};
TAILQ_HEAD(svc_callout_list, svc_callout);
+struct __rpc_svcthread;
+
+/*
+ * Service request
+ */
+struct svc_req {
+ STAILQ_ENTRY(svc_req) rq_link; /* list of requests for a thread */
+ struct __rpc_svcthread *rq_thread; /* thread which is to execute this */
+ uint32_t rq_xid; /* RPC transaction ID */
+ uint32_t rq_prog; /* service program number */
+ uint32_t rq_vers; /* service protocol version */
+ uint32_t rq_proc; /* the desired procedure */
+ size_t rq_size; /* space used by request */
+ struct mbuf *rq_args; /* XDR-encoded procedure arguments */
+ struct opaque_auth rq_cred; /* raw creds from the wire */
+ struct opaque_auth rq_verf; /* verifier for the reply */
+ void *rq_clntcred; /* read only cooked cred */
+ SVCAUTH rq_auth; /* interface to auth methods */
+ SVCXPRT *rq_xprt; /* associated transport */
+ struct sockaddr *rq_addr; /* reply address or NULL if connected */
+ void *rq_p1; /* application workspace */
+ int rq_p2; /* application workspace */
+ uint64_t rq_p3; /* application workspace */
+ char rq_credarea[3*MAX_AUTH_BYTES];
+};
+STAILQ_HEAD(svc_reqlist, svc_req);
+
+#define svc_getrpccaller(rq) \
+ ((rq)->rq_addr ? (rq)->rq_addr : \
+ (struct sockaddr *) &(rq)->rq_xprt->xp_rtaddr)
+
+/*
+ * This structure is used to manage a thread which is executing
+ * requests from a service pool. A service thread is in one of three
+ * states:
+ *
+ * SVCTHREAD_SLEEPING waiting for a request to process
+ * SVCTHREAD_ACTIVE processing a request
+ * SVCTHREAD_EXITING exiting after finishing current request
+ *
+ * Threads which have no work to process sleep on the pool's sp_active
+ * list. When a transport becomes active, it is assigned a service
+ * thread to read and execute pending RPCs.
+ */
+typedef struct __rpc_svcthread {
+ SVCXPRT *st_xprt; /* transport we are processing */
+ struct svc_reqlist st_reqs; /* RPC requests to execute */
+ int st_reqcount; /* number of queued reqs */
+ struct cv st_cond; /* sleeping for work */
+ LIST_ENTRY(__rpc_svcthread) st_link; /* all threads list */
+ LIST_ENTRY(__rpc_svcthread) st_ilink; /* idle threads list */
+ LIST_ENTRY(__rpc_svcthread) st_alink; /* application thread list */
+} SVCTHREAD;
+LIST_HEAD(svcthread_list, __rpc_svcthread);
+
/*
* In the kernel, we can't use global variables to store lists of
* transports etc. since otherwise we could not have two unrelated RPC
@@ -197,15 +297,55 @@ TAILQ_HEAD(svc_callout_list, svc_callout);
* server.
*/
TAILQ_HEAD(svcxprt_list, __rpc_svcxprt);
+enum svcpool_state {
+ SVCPOOL_INIT, /* svc_run not called yet */
+ SVCPOOL_ACTIVE, /* normal running state */
+ SVCPOOL_THREADWANTED, /* new service thread requested */
+ SVCPOOL_THREADSTARTING, /* new service thread started */
+ SVCPOOL_CLOSING /* svc_exit called */
+};
+typedef SVCTHREAD *pool_assign_fn(SVCTHREAD *, struct svc_req *);
+typedef void pool_done_fn(SVCTHREAD *, struct svc_req *);
typedef struct __rpc_svcpool {
struct mtx sp_lock; /* protect the transport lists */
+ const char *sp_name; /* pool name (e.g. "nfsd", "NLM" */
+ enum svcpool_state sp_state; /* current pool state */
+ struct proc *sp_proc; /* process which is in svc_run */
struct svcxprt_list sp_xlist; /* all transports in the pool */
struct svcxprt_list sp_active; /* transports needing service */
struct svc_callout_list sp_callouts; /* (prog,vers)->dispatch list */
- bool_t sp_exited; /* true if shutting down */
+ struct svcthread_list sp_threads; /* service threads */
+ struct svcthread_list sp_idlethreads; /* idle service threads */
+ int sp_minthreads; /* minimum service thread count */
+ int sp_maxthreads; /* maximum service thread count */
+ int sp_threadcount; /* current service thread count */
+ time_t sp_lastcreatetime; /* when we last started a thread */
+ time_t sp_lastidlecheck; /* when we last checked idle transports */
+
+ /*
+ * Hooks to allow an application to control request to thread
+ * placement.
+ */
+ pool_assign_fn *sp_assign;
+ pool_done_fn *sp_done;
+
+ /*
+ * These variables are used to put an upper bound on the
+ * amount of memory used by RPC requests which are queued
+ * waiting for execution.
+ */
+ unsigned int sp_space_low;
+ unsigned int sp_space_high;
+ unsigned int sp_space_used;
+ unsigned int sp_space_used_highest;
+ bool_t sp_space_throttled;
+ int sp_space_throttle_count;
+
+ struct replay_cache *sp_rcache; /* optional replay cache */
+ struct sysctl_ctx_list sp_sysctl;
} SVCPOOL;
-#endif
+#else
/*
* Service request
@@ -224,6 +364,8 @@ struct svc_req {
*/
#define svc_getrpccaller(x) (&(x)->xp_rtaddr)
+#endif
+
/*
* Operations defined on an SVCXPRT handle
*
@@ -232,6 +374,32 @@ struct svc_req {
* xdrproc_t xargs;
* void * argsp;
*/
+#ifdef _KERNEL
+
+#define SVC_ACQUIRE(xprt) \
+ refcount_acquire(&(xprt)->xp_refs)
+
+#define SVC_RELEASE(xprt) \
+ if (refcount_release(&(xprt)->xp_refs)) \
+ SVC_DESTROY(xprt)
+
+#define SVC_RECV(xprt, msg, addr, args) \
+ (*(xprt)->xp_ops->xp_recv)((xprt), (msg), (addr), (args))
+
+#define SVC_STAT(xprt) \
+ (*(xprt)->xp_ops->xp_stat)(xprt)
+
+#define SVC_REPLY(xprt, msg, addr, m) \
+ (*(xprt)->xp_ops->xp_reply) ((xprt), (msg), (addr), (m))
+
+#define SVC_DESTROY(xprt) \
+ (*(xprt)->xp_ops->xp_destroy)(xprt)
+
+#define SVC_CONTROL(xprt, rq, in) \
+ (*(xprt)->xp_ops->xp_control)((xprt), (rq), (in))
+
+#else
+
#define SVC_RECV(xprt, msg) \
(*(xprt)->xp_ops->xp_recv)((xprt), (msg))
#define svc_recv(xprt, msg) \
@@ -262,12 +430,32 @@ struct svc_req {
#define svc_destroy(xprt) \
(*(xprt)->xp_ops->xp_destroy)(xprt)
-#ifdef _KERNEL
-#define SVC_CONTROL(xprt, rq, in) \
- (*(xprt)->xp_ops->xp_control)((xprt), (rq), (in))
-#else
#define SVC_CONTROL(xprt, rq, in) \
(*(xprt)->xp_ops2->xp_control)((xprt), (rq), (in))
+
+#endif
+
+#define SVC_EXT(xprt) \
+ ((SVCXPRT_EXT *) xprt->xp_p3)
+
+#define SVC_AUTH(xprt) \
+ (SVC_EXT(xprt)->xp_auth)
+
+/*
+ * Operations defined on an SVCAUTH handle
+ */
+#ifdef _KERNEL
+#define SVCAUTH_WRAP(auth, mp) \
+ ((auth)->svc_ah_ops->svc_ah_wrap(auth, mp))
+#define SVCAUTH_UNWRAP(auth, mp) \
+ ((auth)->svc_ah_ops->svc_ah_unwrap(auth, mp))
+#define SVCAUTH_RELEASE(auth) \
+ ((auth)->svc_ah_ops->svc_ah_release(auth))
+#else
+#define SVCAUTH_WRAP(auth, xdrs, xfunc, xwhere) \
+ ((auth)->svc_ah_ops->svc_ah_wrap(auth, xdrs, xfunc, xwhere))
+#define SVCAUTH_UNWRAP(auth, xdrs, xfunc, xwhere) \
+ ((auth)->svc_ah_ops->svc_ah_unwrap(auth, xdrs, xfunc, xwhere))
#endif
/*
@@ -332,6 +520,7 @@ __END_DECLS
__BEGIN_DECLS
extern void xprt_active(SVCXPRT *);
extern void xprt_inactive(SVCXPRT *);
+extern void xprt_inactive_locked(SVCXPRT *);
__END_DECLS
#endif
@@ -363,6 +552,17 @@ __END_DECLS
*/
__BEGIN_DECLS
+#ifdef _KERNEL
+extern bool_t svc_sendreply(struct svc_req *, xdrproc_t, void *);
+extern bool_t svc_sendreply_mbuf(struct svc_req *, struct mbuf *);
+extern void svcerr_decode(struct svc_req *);
+extern void svcerr_weakauth(struct svc_req *);
+extern void svcerr_noproc(struct svc_req *);
+extern void svcerr_progvers(struct svc_req *, rpcvers_t, rpcvers_t);
+extern void svcerr_auth(struct svc_req *, enum auth_stat);
+extern void svcerr_noprog(struct svc_req *);
+extern void svcerr_systemerr(struct svc_req *);
+#else
extern bool_t svc_sendreply(SVCXPRT *, xdrproc_t, void *);
extern void svcerr_decode(SVCXPRT *);
extern void svcerr_weakauth(SVCXPRT *);
@@ -371,6 +571,7 @@ extern void svcerr_progvers(SVCXPRT *, rpcvers_t, rpcvers_t);
extern void svcerr_auth(SVCXPRT *, enum auth_stat);
extern void svcerr_noprog(SVCXPRT *);
extern void svcerr_systemerr(SVCXPRT *);
+#endif
extern int rpc_reg(rpcprog_t, rpcvers_t, rpcproc_t,
char *(*)(char *), xdrproc_t, xdrproc_t,
char *);
@@ -410,6 +611,8 @@ extern void rpctest_service(void);
__END_DECLS
__BEGIN_DECLS
+extern SVCXPRT *svc_xprt_alloc(void);
+extern void svc_xprt_free(SVCXPRT *);
#ifndef _KERNEL
extern void svc_getreq(int);
extern void svc_getreqset(fd_set *);
@@ -421,6 +624,10 @@ extern void svc_exit(void);
#else
extern void svc_run(SVCPOOL *);
extern void svc_exit(SVCPOOL *);
+extern bool_t svc_getargs(struct svc_req *, xdrproc_t, void *);
+extern bool_t svc_freeargs(struct svc_req *, xdrproc_t, void *);
+extern void svc_freereq(struct svc_req *);
+
#endif
__END_DECLS
@@ -441,7 +648,8 @@ __BEGIN_DECLS
/*
* Create a new service pool.
*/
-extern SVCPOOL* svcpool_create(void);
+extern SVCPOOL* svcpool_create(const char *name,
+ struct sysctl_oid_list *sysctl_base);
/*
* Destroy a service pool, including all registered transports.
diff --git a/sys/rpc/svc_auth.c b/sys/rpc/svc_auth.c
index 22d4e61..6d5a79b 100644
--- a/sys/rpc/svc_auth.c
+++ b/sys/rpc/svc_auth.c
@@ -52,6 +52,13 @@ __FBSDID("$FreeBSD$");
#include <rpc/rpc.h>
+static enum auth_stat (*_svcauth_rpcsec_gss)(struct svc_req *,
+ struct rpc_msg *) = NULL;
+static int (*_svcauth_rpcsec_gss_getcred)(struct svc_req *,
+ struct ucred **, int *);
+
+static struct svc_auth_ops svc_auth_null_ops;
+
/*
* The call rpc message, msg has been obtained from the wire. The msg contains
* the raw form of credentials and verifiers. authenticate returns AUTH_OK
@@ -77,8 +84,8 @@ _authenticate(struct svc_req *rqst, struct rpc_msg *msg)
enum auth_stat dummy;
rqst->rq_cred = msg->rm_call.cb_cred;
- rqst->rq_xprt->xp_verf.oa_flavor = _null_auth.oa_flavor;
- rqst->rq_xprt->xp_verf.oa_length = 0;
+ rqst->rq_auth.svc_ah_ops = &svc_auth_null_ops;
+ rqst->rq_auth.svc_ah_private = NULL;
cred_flavor = rqst->rq_cred.oa_flavor;
switch (cred_flavor) {
case AUTH_NULL:
@@ -90,6 +97,11 @@ _authenticate(struct svc_req *rqst, struct rpc_msg *msg)
case AUTH_SHORT:
dummy = _svcauth_short(rqst, msg);
return (dummy);
+ case RPCSEC_GSS:
+ if (!_svcauth_rpcsec_gss)
+ return (AUTH_REJECTEDCRED);
+ dummy = _svcauth_rpcsec_gss(rqst, msg);
+ return (dummy);
default:
break;
}
@@ -97,21 +109,65 @@ _authenticate(struct svc_req *rqst, struct rpc_msg *msg)
return (AUTH_REJECTEDCRED);
}
+/*
+ * A set of null auth methods used by any authentication protocols
+ * that don't need to inspect or modify the message body.
+ */
+static bool_t
+svcauth_null_wrap(SVCAUTH *auth, struct mbuf **mp)
+{
+
+ return (TRUE);
+}
+
+static bool_t
+svcauth_null_unwrap(SVCAUTH *auth, struct mbuf **mp)
+{
+
+ return (TRUE);
+}
+
+static void
+svcauth_null_release(SVCAUTH *auth)
+{
+
+}
+
+static struct svc_auth_ops svc_auth_null_ops = {
+ svcauth_null_wrap,
+ svcauth_null_unwrap,
+ svcauth_null_release,
+};
+
/*ARGSUSED*/
enum auth_stat
_svcauth_null(struct svc_req *rqst, struct rpc_msg *msg)
{
+
+ rqst->rq_verf = _null_auth;
return (AUTH_OK);
}
int
-svc_getcred(struct svc_req *rqst, struct ucred *cr, int *flavorp)
+svc_auth_reg(int flavor,
+ enum auth_stat (*svcauth)(struct svc_req *, struct rpc_msg *),
+ int (*getcred)(struct svc_req *, struct ucred **, int *))
{
+
+ if (flavor == RPCSEC_GSS) {
+ _svcauth_rpcsec_gss = svcauth;
+ _svcauth_rpcsec_gss_getcred = getcred;
+ }
+ return (TRUE);
+}
+
+int
+svc_getcred(struct svc_req *rqst, struct ucred **crp, int *flavorp)
+{
+ struct ucred *cr = NULL;
int flavor, i;
struct xucred *xcr;
- KASSERT(!crshared(cr), ("svc_getcred with shared cred"));
-
flavor = rqst->rq_cred.oa_flavor;
if (flavorp)
*flavorp = flavor;
@@ -119,13 +175,20 @@ svc_getcred(struct svc_req *rqst, struct ucred *cr, int *flavorp)
switch (flavor) {
case AUTH_UNIX:
xcr = (struct xucred *) rqst->rq_clntcred;
+ cr = crget();
cr->cr_uid = cr->cr_ruid = cr->cr_svuid = xcr->cr_uid;
cr->cr_ngroups = xcr->cr_ngroups;
for (i = 0; i < xcr->cr_ngroups; i++)
cr->cr_groups[i] = xcr->cr_groups[i];
- cr->cr_rgid = cr->cr_groups[0];
+ cr->cr_rgid = cr->cr_svgid = cr->cr_groups[0];
+ *crp = cr;
return (TRUE);
+ case RPCSEC_GSS:
+ if (!_svcauth_rpcsec_gss_getcred)
+ return (FALSE);
+ return (_svcauth_rpcsec_gss_getcred(rqst, crp, flavorp));
+
default:
return (FALSE);
}
diff --git a/sys/rpc/svc_auth.h b/sys/rpc/svc_auth.h
index 26c191a..9e23876 100644
--- a/sys/rpc/svc_auth.h
+++ b/sys/rpc/svc_auth.h
@@ -47,19 +47,31 @@
*/
__BEGIN_DECLS
extern enum auth_stat _authenticate(struct svc_req *, struct rpc_msg *);
+#ifdef _KERNEL
+extern int svc_auth_reg(int,
+ enum auth_stat (*)(struct svc_req *, struct rpc_msg *),
+ int (*)(struct svc_req *, struct ucred **, int *));
+#else
+extern int svc_auth_reg(int, enum auth_stat (*)(struct svc_req *,
+ struct rpc_msg *));
+#endif
-extern int svc_getcred(struct svc_req *, struct ucred *, int *);
+
+extern int svc_getcred(struct svc_req *, struct ucred **, int *);
/*
* struct svc_req *req; -- RPC request
- * struct ucred *cr -- Kernel cred to modify
+ * struct ucred **crp -- Kernel cred to modify
* int *flavorp -- Return RPC auth flavor
*
* Retrieve unix creds corresponding to an RPC request, if
* possible. The auth flavor (AUTH_NONE or AUTH_UNIX) is returned in
- * *flavorp. If the flavor is AUTH_UNIX the caller's ucred structure
- * will be modified to reflect the values from the request. Return's
- * non-zero if credentials were retrieved form the request, otherwise
- * zero.
+ * *flavorp. If the flavor is AUTH_UNIX the caller's ucred pointer
+ * will be modified to point at a ucred structure which reflects the
+ * values from the request. The caller should call crfree on this
+ * pointer.
+ *
+ * Return's non-zero if credentials were retrieved from the request,
+ * otherwise zero.
*/
__END_DECLS
diff --git a/sys/rpc/svc_auth_unix.c b/sys/rpc/svc_auth_unix.c
index 9c6cdd7..0c11a4a 100644
--- a/sys/rpc/svc_auth_unix.c
+++ b/sys/rpc/svc_auth_unix.c
@@ -120,8 +120,7 @@ _svcauth_unix(struct svc_req *rqst, struct rpc_msg *msg)
goto done;
}
- rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NULL;
- rqst->rq_xprt->xp_verf.oa_length = 0;
+ rqst->rq_verf = _null_auth;
stat = AUTH_OK;
done:
XDR_DESTROY(&xdrs);
diff --git a/sys/rpc/svc_dg.c b/sys/rpc/svc_dg.c
index 666b952..72721b0 100644
--- a/sys/rpc/svc_dg.c
+++ b/sys/rpc/svc_dg.c
@@ -53,6 +53,7 @@ __FBSDID("$FreeBSD$");
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
+#include <sys/sx.h>
#include <sys/systm.h>
#include <sys/uio.h>
@@ -61,10 +62,10 @@ __FBSDID("$FreeBSD$");
#include <rpc/rpc_com.h>
static enum xprt_stat svc_dg_stat(SVCXPRT *);
-static bool_t svc_dg_recv(SVCXPRT *, struct rpc_msg *);
-static bool_t svc_dg_reply(SVCXPRT *, struct rpc_msg *);
-static bool_t svc_dg_getargs(SVCXPRT *, xdrproc_t, void *);
-static bool_t svc_dg_freeargs(SVCXPRT *, xdrproc_t, void *);
+static bool_t svc_dg_recv(SVCXPRT *, struct rpc_msg *,
+ struct sockaddr **, struct mbuf **);
+static bool_t svc_dg_reply(SVCXPRT *, struct rpc_msg *,
+ struct sockaddr *, struct mbuf *);
static void svc_dg_destroy(SVCXPRT *);
static bool_t svc_dg_control(SVCXPRT *, const u_int, void *);
static void svc_dg_soupcall(struct socket *so, void *arg, int waitflag);
@@ -72,9 +73,7 @@ static void svc_dg_soupcall(struct socket *so, void *arg, int waitflag);
static struct xp_ops svc_dg_ops = {
.xp_recv = svc_dg_recv,
.xp_stat = svc_dg_stat,
- .xp_getargs = svc_dg_getargs,
.xp_reply = svc_dg_reply,
- .xp_freeargs = svc_dg_freeargs,
.xp_destroy = svc_dg_destroy,
.xp_control = svc_dg_control,
};
@@ -116,9 +115,8 @@ svc_dg_create(SVCPOOL *pool, struct socket *so, size_t sendsize,
return (NULL);
}
- xprt = mem_alloc(sizeof (SVCXPRT));
- memset(xprt, 0, sizeof (SVCXPRT));
- mtx_init(&xprt->xp_lock, "xprt->xp_lock", NULL, MTX_DEF);
+ xprt = svc_xprt_alloc();
+ sx_init(&xprt->xp_lock, "xprt->xp_lock");
xprt->xp_pool = pool;
xprt->xp_socket = so;
xprt->xp_p1 = NULL;
@@ -129,16 +127,9 @@ svc_dg_create(SVCPOOL *pool, struct socket *so, size_t sendsize,
if (error)
goto freedata;
- xprt->xp_ltaddr.buf = mem_alloc(sizeof (struct sockaddr_storage));
- xprt->xp_ltaddr.maxlen = sizeof (struct sockaddr_storage);
- xprt->xp_ltaddr.len = sa->sa_len;
- memcpy(xprt->xp_ltaddr.buf, sa, sa->sa_len);
+ memcpy(&xprt->xp_ltaddr, sa, sa->sa_len);
free(sa, M_SONAME);
- xprt->xp_rtaddr.buf = mem_alloc(sizeof (struct sockaddr_storage));
- xprt->xp_rtaddr.maxlen = sizeof (struct sockaddr_storage);
- xprt->xp_rtaddr.len = 0;
-
xprt_register(xprt);
SOCKBUF_LOCK(&so->so_rcv);
@@ -151,7 +142,7 @@ svc_dg_create(SVCPOOL *pool, struct socket *so, size_t sendsize,
freedata:
(void) printf(svc_dg_str, __no_mem_str);
if (xprt) {
- (void) mem_free(xprt, sizeof (SVCXPRT));
+ svc_xprt_free(xprt);
}
return (NULL);
}
@@ -161,34 +152,34 @@ static enum xprt_stat
svc_dg_stat(SVCXPRT *xprt)
{
+ if (soreadable(xprt->xp_socket))
+ return (XPRT_MOREREQS);
+
return (XPRT_IDLE);
}
static bool_t
-svc_dg_recv(SVCXPRT *xprt, struct rpc_msg *msg)
+svc_dg_recv(SVCXPRT *xprt, struct rpc_msg *msg,
+ struct sockaddr **addrp, struct mbuf **mp)
{
struct uio uio;
struct sockaddr *raddr;
struct mbuf *mreq;
+ XDR xdrs;
int error, rcvflag;
/*
+ * Serialise access to the socket.
+ */
+ sx_xlock(&xprt->xp_lock);
+
+ /*
* The socket upcall calls xprt_active() which will eventually
* cause the server to call us here. We attempt to read a
* packet from the socket and process it. If the read fails,
* we have drained all pending requests so we call
* xprt_inactive().
- *
- * The lock protects us in the case where a new packet arrives
- * on the socket after our call to soreceive fails with
- * EWOULDBLOCK - the call to xprt_active() in the upcall will
- * happen only after our call to xprt_inactive() which ensures
- * that we will remain active. It might be possible to use
- * SOCKBUF_LOCK for this - its not clear to me what locks are
- * held during the upcall.
*/
- mtx_lock(&xprt->xp_lock);
-
uio.uio_resid = 1000000000;
uio.uio_td = curthread;
mreq = NULL;
@@ -196,8 +187,19 @@ svc_dg_recv(SVCXPRT *xprt, struct rpc_msg *msg)
error = soreceive(xprt->xp_socket, &raddr, &uio, &mreq, NULL, &rcvflag);
if (error == EWOULDBLOCK) {
- xprt_inactive(xprt);
- mtx_unlock(&xprt->xp_lock);
+ /*
+ * We must re-test for readability after taking the
+ * lock to protect us in the case where a new packet
+ * arrives on the socket after our call to soreceive
+ * fails with EWOULDBLOCK. The pool lock protects us
+ * from racing the upcall after our soreadable() call
+ * returns false.
+ */
+ mtx_lock(&xprt->xp_pool->sp_lock);
+ if (!soreadable(xprt->xp_socket))
+ xprt_inactive_locked(xprt);
+ mtx_unlock(&xprt->xp_pool->sp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
@@ -208,45 +210,52 @@ svc_dg_recv(SVCXPRT *xprt, struct rpc_msg *msg)
xprt->xp_socket->so_rcv.sb_flags &= ~SB_UPCALL;
SOCKBUF_UNLOCK(&xprt->xp_socket->so_rcv);
xprt_inactive(xprt);
- mtx_unlock(&xprt->xp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
- mtx_unlock(&xprt->xp_lock);
-
- KASSERT(raddr->sa_len < xprt->xp_rtaddr.maxlen,
- ("Unexpected remote address length"));
- memcpy(xprt->xp_rtaddr.buf, raddr, raddr->sa_len);
- xprt->xp_rtaddr.len = raddr->sa_len;
- free(raddr, M_SONAME);
+ sx_xunlock(&xprt->xp_lock);
- xdrmbuf_create(&xprt->xp_xdrreq, mreq, XDR_DECODE);
- if (! xdr_callmsg(&xprt->xp_xdrreq, msg)) {
- XDR_DESTROY(&xprt->xp_xdrreq);
+ xdrmbuf_create(&xdrs, mreq, XDR_DECODE);
+ if (! xdr_callmsg(&xdrs, msg)) {
+ XDR_DESTROY(&xdrs);
return (FALSE);
}
- xprt->xp_xid = msg->rm_xid;
+
+ *addrp = raddr;
+ *mp = xdrmbuf_getall(&xdrs);
+ XDR_DESTROY(&xdrs);
return (TRUE);
}
static bool_t
-svc_dg_reply(SVCXPRT *xprt, struct rpc_msg *msg)
+svc_dg_reply(SVCXPRT *xprt, struct rpc_msg *msg,
+ struct sockaddr *addr, struct mbuf *m)
{
+ XDR xdrs;
struct mbuf *mrep;
- bool_t stat = FALSE;
+ bool_t stat = TRUE;
int error;
MGETHDR(mrep, M_WAIT, MT_DATA);
- MCLGET(mrep, M_WAIT);
mrep->m_len = 0;
- xdrmbuf_create(&xprt->xp_xdrrep, mrep, XDR_ENCODE);
- msg->rm_xid = xprt->xp_xid;
- if (xdr_replymsg(&xprt->xp_xdrrep, msg)) {
+ xdrmbuf_create(&xdrs, mrep, XDR_ENCODE);
+
+ if (msg->rm_reply.rp_stat == MSG_ACCEPTED &&
+ msg->rm_reply.rp_acpt.ar_stat == SUCCESS) {
+ if (!xdr_replymsg(&xdrs, msg))
+ stat = FALSE;
+ else
+ xdrmbuf_append(&xdrs, m);
+ } else {
+ stat = xdr_replymsg(&xdrs, msg);
+ }
+
+ if (stat) {
m_fixhdr(mrep);
- error = sosend(xprt->xp_socket,
- (struct sockaddr *) xprt->xp_rtaddr.buf, NULL, mrep, NULL,
+ error = sosend(xprt->xp_socket, addr, NULL, mrep, NULL,
0, curthread);
if (!error) {
stat = TRUE;
@@ -255,61 +264,29 @@ svc_dg_reply(SVCXPRT *xprt, struct rpc_msg *msg)
m_freem(mrep);
}
- /*
- * This frees the request mbuf chain as well. The reply mbuf
- * chain was consumed by sosend.
- */
- XDR_DESTROY(&xprt->xp_xdrreq);
- XDR_DESTROY(&xprt->xp_xdrrep);
+ XDR_DESTROY(&xdrs);
xprt->xp_p2 = NULL;
return (stat);
}
-static bool_t
-svc_dg_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
-{
-
- return (xdr_args(&xprt->xp_xdrreq, args_ptr));
-}
-
-static bool_t
-svc_dg_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
-{
- XDR xdrs;
-
- /*
- * Free the request mbuf here - this allows us to handle
- * protocols where not all requests have replies
- * (i.e. NLM). Note that xdrmbuf_destroy handles being called
- * twice correctly - the mbuf will only be freed once.
- */
- XDR_DESTROY(&xprt->xp_xdrreq);
-
- xdrs.x_op = XDR_FREE;
- return (xdr_args(&xdrs, args_ptr));
-}
-
static void
svc_dg_destroy(SVCXPRT *xprt)
{
+
SOCKBUF_LOCK(&xprt->xp_socket->so_rcv);
xprt->xp_socket->so_upcallarg = NULL;
xprt->xp_socket->so_upcall = NULL;
xprt->xp_socket->so_rcv.sb_flags &= ~SB_UPCALL;
SOCKBUF_UNLOCK(&xprt->xp_socket->so_rcv);
- xprt_unregister(xprt);
-
- mtx_destroy(&xprt->xp_lock);
+ sx_destroy(&xprt->xp_lock);
if (xprt->xp_socket)
(void)soclose(xprt->xp_socket);
- if (xprt->xp_rtaddr.buf)
- (void) mem_free(xprt->xp_rtaddr.buf, xprt->xp_rtaddr.maxlen);
- if (xprt->xp_ltaddr.buf)
- (void) mem_free(xprt->xp_ltaddr.buf, xprt->xp_ltaddr.maxlen);
- (void) mem_free(xprt, sizeof (SVCXPRT));
+ if (xprt->xp_netid)
+ (void) mem_free(xprt->xp_netid, strlen(xprt->xp_netid) + 1);
+ svc_xprt_free(xprt);
}
static bool_t
@@ -328,7 +305,5 @@ svc_dg_soupcall(struct socket *so, void *arg, int waitflag)
{
SVCXPRT *xprt = (SVCXPRT *) arg;
- mtx_lock(&xprt->xp_lock);
xprt_active(xprt);
- mtx_unlock(&xprt->xp_lock);
}
diff --git a/sys/rpc/svc_generic.c b/sys/rpc/svc_generic.c
index 1f9b2e2..790b4ba 100644
--- a/sys/rpc/svc_generic.c
+++ b/sys/rpc/svc_generic.c
@@ -178,102 +178,13 @@ svc_tp_create(
"svc_tp_create: Could not register prog %u vers %u on %s\n",
(unsigned)prognum, (unsigned)versnum,
nconf->nc_netid);
- SVC_DESTROY(xprt);
+ xprt_unregister(xprt);
return (NULL);
}
return (xprt);
}
/*
- * Bind a socket to a privileged IP port
- */
-int bindresvport(struct socket *so, struct sockaddr *sa);
-int
-bindresvport(struct socket *so, struct sockaddr *sa)
-{
- int old, error, af;
- bool_t freesa = FALSE;
- struct sockaddr_in *sin;
-#ifdef INET6
- struct sockaddr_in6 *sin6;
-#endif
- struct sockopt opt;
- int proto, portrange, portlow;
- u_int16_t *portp;
- socklen_t salen;
-
- if (sa == NULL) {
- error = so->so_proto->pr_usrreqs->pru_sockaddr(so, &sa);
- if (error)
- return (error);
- freesa = TRUE;
- af = sa->sa_family;
- salen = sa->sa_len;
- memset(sa, 0, sa->sa_len);
- } else {
- af = sa->sa_family;
- salen = sa->sa_len;
- }
-
- switch (af) {
- case AF_INET:
- proto = IPPROTO_IP;
- portrange = IP_PORTRANGE;
- portlow = IP_PORTRANGE_LOW;
- sin = (struct sockaddr_in *)sa;
- portp = &sin->sin_port;
- break;
-#ifdef INET6
- case AF_INET6:
- proto = IPPROTO_IPV6;
- portrange = IPV6_PORTRANGE;
- portlow = IPV6_PORTRANGE_LOW;
- sin6 = (struct sockaddr_in6 *)sa;
- portp = &sin6->sin6_port;
- break;
-#endif
- default:
- return (EPFNOSUPPORT);
- }
-
- sa->sa_family = af;
- sa->sa_len = salen;
-
- if (*portp == 0) {
- bzero(&opt, sizeof(opt));
- opt.sopt_dir = SOPT_GET;
- opt.sopt_level = proto;
- opt.sopt_name = portrange;
- opt.sopt_val = &old;
- opt.sopt_valsize = sizeof(old);
- error = sogetopt(so, &opt);
- if (error)
- goto out;
-
- opt.sopt_dir = SOPT_SET;
- opt.sopt_val = &portlow;
- error = sosetopt(so, &opt);
- if (error)
- goto out;
- }
-
- error = sobind(so, sa, curthread);
-
- if (*portp == 0) {
- if (error) {
- opt.sopt_dir = SOPT_SET;
- opt.sopt_val = &old;
- sosetopt(so, &opt);
- }
- }
-out:
- if (freesa)
- free(sa, M_SONAME);
-
- return (error);
-}
-
-/*
* If so is NULL, then it opens a socket for the given transport
* provider (nconf cannot be NULL then). If the t_state is T_UNBND and
* bindaddr is NON-NULL, it performs a t_bind using the bindaddr. For
@@ -401,7 +312,7 @@ freedata:
if (xprt) {
if (!madeso) /* so that svc_destroy doesnt close fd */
xprt->xp_socket = NULL;
- SVC_DESTROY(xprt);
+ xprt_unregister(xprt);
}
return (NULL);
}
diff --git a/sys/rpc/svc_vc.c b/sys/rpc/svc_vc.c
index 47530da..e3f0350 100644
--- a/sys/rpc/svc_vc.c
+++ b/sys/rpc/svc_vc.c
@@ -54,6 +54,7 @@ __FBSDID("$FreeBSD$");
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
+#include <sys/sx.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <netinet/tcp.h>
@@ -62,16 +63,17 @@ __FBSDID("$FreeBSD$");
#include <rpc/rpc_com.h>
-static bool_t svc_vc_rendezvous_recv(SVCXPRT *, struct rpc_msg *);
+static bool_t svc_vc_rendezvous_recv(SVCXPRT *, struct rpc_msg *,
+ struct sockaddr **, struct mbuf **);
static enum xprt_stat svc_vc_rendezvous_stat(SVCXPRT *);
static void svc_vc_rendezvous_destroy(SVCXPRT *);
static bool_t svc_vc_null(void);
static void svc_vc_destroy(SVCXPRT *);
static enum xprt_stat svc_vc_stat(SVCXPRT *);
-static bool_t svc_vc_recv(SVCXPRT *, struct rpc_msg *);
-static bool_t svc_vc_getargs(SVCXPRT *, xdrproc_t, void *);
-static bool_t svc_vc_freeargs(SVCXPRT *, xdrproc_t, void *);
-static bool_t svc_vc_reply(SVCXPRT *, struct rpc_msg *);
+static bool_t svc_vc_recv(SVCXPRT *, struct rpc_msg *,
+ struct sockaddr **, struct mbuf **);
+static bool_t svc_vc_reply(SVCXPRT *, struct rpc_msg *,
+ struct sockaddr *, struct mbuf *);
static bool_t svc_vc_control(SVCXPRT *xprt, const u_int rq, void *in);
static bool_t svc_vc_rendezvous_control (SVCXPRT *xprt, const u_int rq,
void *in);
@@ -83,9 +85,8 @@ static void svc_vc_soupcall(struct socket *so, void *arg, int waitflag);
static struct xp_ops svc_vc_rendezvous_ops = {
.xp_recv = svc_vc_rendezvous_recv,
.xp_stat = svc_vc_rendezvous_stat,
- .xp_getargs = (bool_t (*)(SVCXPRT *, xdrproc_t, void *))svc_vc_null,
- .xp_reply = (bool_t (*)(SVCXPRT *, struct rpc_msg *))svc_vc_null,
- .xp_freeargs = (bool_t (*)(SVCXPRT *, xdrproc_t, void *))svc_vc_null,
+ .xp_reply = (bool_t (*)(SVCXPRT *, struct rpc_msg *,
+ struct sockaddr *, struct mbuf *))svc_vc_null,
.xp_destroy = svc_vc_rendezvous_destroy,
.xp_control = svc_vc_rendezvous_control
};
@@ -93,9 +94,7 @@ static struct xp_ops svc_vc_rendezvous_ops = {
static struct xp_ops svc_vc_ops = {
.xp_recv = svc_vc_recv,
.xp_stat = svc_vc_stat,
- .xp_getargs = svc_vc_getargs,
.xp_reply = svc_vc_reply,
- .xp_freeargs = svc_vc_freeargs,
.xp_destroy = svc_vc_destroy,
.xp_control = svc_vc_control
};
@@ -141,28 +140,21 @@ svc_vc_create(SVCPOOL *pool, struct socket *so, size_t sendsize,
return (xprt);
}
- xprt = mem_alloc(sizeof(SVCXPRT));
- mtx_init(&xprt->xp_lock, "xprt->xp_lock", NULL, MTX_DEF);
+ xprt = svc_xprt_alloc();
+ sx_init(&xprt->xp_lock, "xprt->xp_lock");
xprt->xp_pool = pool;
xprt->xp_socket = so;
xprt->xp_p1 = NULL;
xprt->xp_p2 = NULL;
- xprt->xp_p3 = NULL;
- xprt->xp_verf = _null_auth;
xprt->xp_ops = &svc_vc_rendezvous_ops;
error = so->so_proto->pr_usrreqs->pru_sockaddr(so, &sa);
if (error)
goto cleanup_svc_vc_create;
- xprt->xp_ltaddr.buf = mem_alloc(sizeof (struct sockaddr_storage));
- xprt->xp_ltaddr.maxlen = sizeof (struct sockaddr_storage);
- xprt->xp_ltaddr.len = sa->sa_len;
- memcpy(xprt->xp_ltaddr.buf, sa, sa->sa_len);
+ memcpy(&xprt->xp_ltaddr, sa, sa->sa_len);
free(sa, M_SONAME);
- xprt->xp_rtaddr.maxlen = 0;
-
xprt_register(xprt);
solisten(so, SOMAXCONN, curthread);
@@ -176,7 +168,7 @@ svc_vc_create(SVCPOOL *pool, struct socket *so, size_t sendsize,
return (xprt);
cleanup_svc_vc_create:
if (xprt)
- mem_free(xprt, sizeof(*xprt));
+ svc_xprt_free(xprt);
return (NULL);
}
@@ -218,29 +210,27 @@ svc_vc_create_conn(SVCPOOL *pool, struct socket *so, struct sockaddr *raddr)
cd = mem_alloc(sizeof(*cd));
cd->strm_stat = XPRT_IDLE;
- xprt = mem_alloc(sizeof(SVCXPRT));
- mtx_init(&xprt->xp_lock, "xprt->xp_lock", NULL, MTX_DEF);
+ xprt = svc_xprt_alloc();
+ sx_init(&xprt->xp_lock, "xprt->xp_lock");
xprt->xp_pool = pool;
xprt->xp_socket = so;
xprt->xp_p1 = cd;
xprt->xp_p2 = NULL;
- xprt->xp_p3 = NULL;
- xprt->xp_verf = _null_auth;
xprt->xp_ops = &svc_vc_ops;
- xprt->xp_rtaddr.buf = mem_alloc(sizeof (struct sockaddr_storage));
- xprt->xp_rtaddr.maxlen = sizeof (struct sockaddr_storage);
- xprt->xp_rtaddr.len = raddr->sa_len;
- memcpy(xprt->xp_rtaddr.buf, raddr, raddr->sa_len);
+ /*
+ * See http://www.connectathon.org/talks96/nfstcp.pdf - client
+ * has a 5 minute timer, server has a 6 minute timer.
+ */
+ xprt->xp_idletimeout = 6 * 60;
+
+ memcpy(&xprt->xp_rtaddr, raddr, raddr->sa_len);
error = so->so_proto->pr_usrreqs->pru_sockaddr(so, &sa);
if (error)
goto cleanup_svc_vc_create;
- xprt->xp_ltaddr.buf = mem_alloc(sizeof (struct sockaddr_storage));
- xprt->xp_ltaddr.maxlen = sizeof (struct sockaddr_storage);
- xprt->xp_ltaddr.len = sa->sa_len;
- memcpy(xprt->xp_ltaddr.buf, sa, sa->sa_len);
+ memcpy(&xprt->xp_ltaddr, sa, sa->sa_len);
free(sa, M_SONAME);
xprt_register(xprt);
@@ -255,19 +245,13 @@ svc_vc_create_conn(SVCPOOL *pool, struct socket *so, struct sockaddr *raddr)
* Throw the transport into the active list in case it already
* has some data buffered.
*/
- mtx_lock(&xprt->xp_lock);
+ sx_xlock(&xprt->xp_lock);
xprt_active(xprt);
- mtx_unlock(&xprt->xp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (xprt);
cleanup_svc_vc_create:
if (xprt) {
- if (xprt->xp_ltaddr.buf)
- mem_free(xprt->xp_ltaddr.buf,
- sizeof(struct sockaddr_storage));
- if (xprt->xp_rtaddr.buf)
- mem_free(xprt->xp_rtaddr.buf,
- sizeof(struct sockaddr_storage));
mem_free(xprt, sizeof(*xprt));
}
if (cd)
@@ -335,7 +319,8 @@ done:
/*ARGSUSED*/
static bool_t
-svc_vc_rendezvous_recv(SVCXPRT *xprt, struct rpc_msg *msg)
+svc_vc_rendezvous_recv(SVCXPRT *xprt, struct rpc_msg *msg,
+ struct sockaddr **addrp, struct mbuf **mp)
{
struct socket *so = NULL;
struct sockaddr *sa = NULL;
@@ -347,22 +332,27 @@ svc_vc_rendezvous_recv(SVCXPRT *xprt, struct rpc_msg *msg)
* connection from the socket and turn it into a new
* transport. If the accept fails, we have drained all pending
* connections so we call xprt_inactive().
- *
- * The lock protects us in the case where a new connection arrives
- * on the socket after our call to accept fails with
- * EWOULDBLOCK - the call to xprt_active() in the upcall will
- * happen only after our call to xprt_inactive() which ensures
- * that we will remain active. It might be possible to use
- * SOCKBUF_LOCK for this - its not clear to me what locks are
- * held during the upcall.
*/
- mtx_lock(&xprt->xp_lock);
+ sx_xlock(&xprt->xp_lock);
error = svc_vc_accept(xprt->xp_socket, &so);
if (error == EWOULDBLOCK) {
- xprt_inactive(xprt);
- mtx_unlock(&xprt->xp_lock);
+ /*
+ * We must re-test for new connections after taking
+ * the lock to protect us in the case where a new
+ * connection arrives after our call to accept fails
+ * with EWOULDBLOCK. The pool lock protects us from
+ * racing the upcall after our TAILQ_EMPTY() call
+ * returns false.
+ */
+ ACCEPT_LOCK();
+ mtx_lock(&xprt->xp_pool->sp_lock);
+ if (TAILQ_EMPTY(&xprt->xp_socket->so_comp))
+ xprt_inactive_locked(xprt);
+ mtx_unlock(&xprt->xp_pool->sp_lock);
+ ACCEPT_UNLOCK();
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
@@ -373,11 +363,11 @@ svc_vc_rendezvous_recv(SVCXPRT *xprt, struct rpc_msg *msg)
xprt->xp_socket->so_rcv.sb_flags &= ~SB_UPCALL;
SOCKBUF_UNLOCK(&xprt->xp_socket->so_rcv);
xprt_inactive(xprt);
- mtx_unlock(&xprt->xp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
- mtx_unlock(&xprt->xp_lock);
+ sx_xunlock(&xprt->xp_lock);
sa = 0;
error = soaccept(so, &sa);
@@ -420,18 +410,13 @@ svc_vc_destroy_common(SVCXPRT *xprt)
xprt->xp_socket->so_rcv.sb_flags &= ~SB_UPCALL;
SOCKBUF_UNLOCK(&xprt->xp_socket->so_rcv);
- xprt_unregister(xprt);
-
- mtx_destroy(&xprt->xp_lock);
+ sx_destroy(&xprt->xp_lock);
if (xprt->xp_socket)
(void)soclose(xprt->xp_socket);
- if (xprt->xp_rtaddr.buf)
- (void) mem_free(xprt->xp_rtaddr.buf, xprt->xp_rtaddr.maxlen);
- if (xprt->xp_ltaddr.buf)
- (void) mem_free(xprt->xp_ltaddr.buf, xprt->xp_ltaddr.maxlen);
- (void) mem_free(xprt, sizeof (SVCXPRT));
-
+ if (xprt->xp_netid)
+ (void) mem_free(xprt->xp_netid, strlen(xprt->xp_netid) + 1);
+ svc_xprt_free(xprt);
}
static void
@@ -483,32 +468,48 @@ svc_vc_stat(SVCXPRT *xprt)
/*
* Return XPRT_MOREREQS if we have buffered data and we are
- * mid-record or if we have enough data for a record marker.
+ * mid-record or if we have enough data for a record
+ * marker. Since this is only a hint, we read mpending and
+ * resid outside the lock. We do need to take the lock if we
+ * have to traverse the mbuf chain.
*/
if (cd->mpending) {
if (cd->resid)
return (XPRT_MOREREQS);
n = 0;
+ sx_xlock(&xprt->xp_lock);
m = cd->mpending;
while (m && n < sizeof(uint32_t)) {
n += m->m_len;
m = m->m_next;
}
+ sx_xunlock(&xprt->xp_lock);
if (n >= sizeof(uint32_t))
return (XPRT_MOREREQS);
}
+ if (soreadable(xprt->xp_socket))
+ return (XPRT_MOREREQS);
+
return (XPRT_IDLE);
}
static bool_t
-svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
+svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg,
+ struct sockaddr **addrp, struct mbuf **mp)
{
struct cf_conn *cd = (struct cf_conn *) xprt->xp_p1;
struct uio uio;
struct mbuf *m;
+ XDR xdrs;
int error, rcvflag;
+ /*
+ * Serialise access to the socket and our own record parsing
+ * state.
+ */
+ sx_xlock(&xprt->xp_lock);
+
for (;;) {
/*
* If we have an mbuf chain in cd->mpending, try to parse a
@@ -539,7 +540,9 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
}
if (n < sizeof(uint32_t))
goto readmore;
- cd->mpending = m_pullup(cd->mpending, sizeof(uint32_t));
+ if (cd->mpending->m_len < sizeof(uint32_t))
+ cd->mpending = m_pullup(cd->mpending,
+ sizeof(uint32_t));
memcpy(&header, mtod(cd->mpending, uint32_t *),
sizeof(header));
header = ntohl(header);
@@ -557,8 +560,12 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
*/
while (cd->mpending && cd->resid) {
m = cd->mpending;
- cd->mpending = m_split(cd->mpending, cd->resid,
- M_WAIT);
+ if (cd->mpending->m_next
+ || cd->mpending->m_len > cd->resid)
+ cd->mpending = m_split(cd->mpending,
+ cd->resid, M_WAIT);
+ else
+ cd->mpending = NULL;
if (cd->mreq)
m_last(cd->mreq)->m_next = m;
else
@@ -582,13 +589,18 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
* Success - we have a complete record in
* cd->mreq.
*/
- xdrmbuf_create(&xprt->xp_xdrreq, cd->mreq, XDR_DECODE);
+ xdrmbuf_create(&xdrs, cd->mreq, XDR_DECODE);
cd->mreq = NULL;
- if (! xdr_callmsg(&xprt->xp_xdrreq, msg)) {
- XDR_DESTROY(&xprt->xp_xdrreq);
+ sx_xunlock(&xprt->xp_lock);
+
+ if (! xdr_callmsg(&xdrs, msg)) {
+ XDR_DESTROY(&xdrs);
return (FALSE);
}
- xprt->xp_xid = msg->rm_xid;
+
+ *addrp = NULL;
+ *mp = xdrmbuf_getall(&xdrs);
+ XDR_DESTROY(&xdrs);
return (TRUE);
}
@@ -602,17 +614,7 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
* the result in cd->mpending. If the read fails,
* we have drained both cd->mpending and the socket so
* we can call xprt_inactive().
- *
- * The lock protects us in the case where a new packet arrives
- * on the socket after our call to soreceive fails with
- * EWOULDBLOCK - the call to xprt_active() in the upcall will
- * happen only after our call to xprt_inactive() which ensures
- * that we will remain active. It might be possible to use
- * SOCKBUF_LOCK for this - its not clear to me what locks are
- * held during the upcall.
*/
- mtx_lock(&xprt->xp_lock);
-
uio.uio_resid = 1000000000;
uio.uio_td = curthread;
m = NULL;
@@ -621,8 +623,20 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
&rcvflag);
if (error == EWOULDBLOCK) {
- xprt_inactive(xprt);
- mtx_unlock(&xprt->xp_lock);
+ /*
+ * We must re-test for readability after
+ * taking the lock to protect us in the case
+ * where a new packet arrives on the socket
+ * after our call to soreceive fails with
+ * EWOULDBLOCK. The pool lock protects us from
+ * racing the upcall after our soreadable()
+ * call returns false.
+ */
+ mtx_lock(&xprt->xp_pool->sp_lock);
+ if (!soreadable(xprt->xp_socket))
+ xprt_inactive_locked(xprt);
+ mtx_unlock(&xprt->xp_pool->sp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
@@ -634,7 +648,7 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
SOCKBUF_UNLOCK(&xprt->xp_socket->so_rcv);
xprt_inactive(xprt);
cd->strm_stat = XPRT_DIED;
- mtx_unlock(&xprt->xp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
@@ -642,8 +656,9 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
/*
* EOF - the other end has closed the socket.
*/
+ xprt_inactive(xprt);
cd->strm_stat = XPRT_DIED;
- mtx_unlock(&xprt->xp_lock);
+ sx_xunlock(&xprt->xp_lock);
return (FALSE);
}
@@ -651,53 +666,38 @@ svc_vc_recv(SVCXPRT *xprt, struct rpc_msg *msg)
m_last(cd->mpending)->m_next = m;
else
cd->mpending = m;
-
- mtx_unlock(&xprt->xp_lock);
}
}
static bool_t
-svc_vc_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
-{
-
- return (xdr_args(&xprt->xp_xdrreq, args_ptr));
-}
-
-static bool_t
-svc_vc_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, void *args_ptr)
+svc_vc_reply(SVCXPRT *xprt, struct rpc_msg *msg,
+ struct sockaddr *addr, struct mbuf *m)
{
XDR xdrs;
-
- /*
- * Free the request mbuf here - this allows us to handle
- * protocols where not all requests have replies
- * (i.e. NLM). Note that xdrmbuf_destroy handles being called
- * twice correctly - the mbuf will only be freed once.
- */
- XDR_DESTROY(&xprt->xp_xdrreq);
-
- xdrs.x_op = XDR_FREE;
- return (xdr_args(&xdrs, args_ptr));
-}
-
-static bool_t
-svc_vc_reply(SVCXPRT *xprt, struct rpc_msg *msg)
-{
struct mbuf *mrep;
- bool_t stat = FALSE;
+ bool_t stat = TRUE;
int error;
/*
* Leave space for record mark.
*/
MGETHDR(mrep, M_WAIT, MT_DATA);
- MCLGET(mrep, M_WAIT);
mrep->m_len = 0;
mrep->m_data += sizeof(uint32_t);
- xdrmbuf_create(&xprt->xp_xdrrep, mrep, XDR_ENCODE);
- msg->rm_xid = xprt->xp_xid;
- if (xdr_replymsg(&xprt->xp_xdrrep, msg)) {
+ xdrmbuf_create(&xdrs, mrep, XDR_ENCODE);
+
+ if (msg->rm_reply.rp_stat == MSG_ACCEPTED &&
+ msg->rm_reply.rp_acpt.ar_stat == SUCCESS) {
+ if (!xdr_replymsg(&xdrs, msg))
+ stat = FALSE;
+ else
+ xdrmbuf_append(&xdrs, m);
+ } else {
+ stat = xdr_replymsg(&xdrs, msg);
+ }
+
+ if (stat) {
m_fixhdr(mrep);
/*
@@ -716,12 +716,7 @@ svc_vc_reply(SVCXPRT *xprt, struct rpc_msg *msg)
m_freem(mrep);
}
- /*
- * This frees the request mbuf chain as well. The reply mbuf
- * chain was consumed by sosend.
- */
- XDR_DESTROY(&xprt->xp_xdrreq);
- XDR_DESTROY(&xprt->xp_xdrrep);
+ XDR_DESTROY(&xdrs);
xprt->xp_p2 = NULL;
return (stat);
@@ -739,9 +734,7 @@ svc_vc_soupcall(struct socket *so, void *arg, int waitflag)
{
SVCXPRT *xprt = (SVCXPRT *) arg;
- mtx_lock(&xprt->xp_lock);
xprt_active(xprt);
- mtx_unlock(&xprt->xp_lock);
}
#if 0
@@ -757,7 +750,7 @@ __rpc_get_local_uid(SVCXPRT *transp, uid_t *uid) {
struct sockaddr *sa;
sock = transp->xp_fd;
- sa = (struct sockaddr *)transp->xp_rtaddr.buf;
+ sa = (struct sockaddr *)transp->xp_rtaddr;
if (sa->sa_family == AF_LOCAL) {
ret = getpeereid(sock, &euid, &egid);
if (ret == 0)
diff --git a/sys/rpc/xdr.h b/sys/rpc/xdr.h
index bebd448..947bf4f 100644
--- a/sys/rpc/xdr.h
+++ b/sys/rpc/xdr.h
@@ -348,6 +348,8 @@ extern void xdrmem_create(XDR *, char *, u_int, enum xdr_op);
/* XDR using mbufs */
struct mbuf;
extern void xdrmbuf_create(XDR *, struct mbuf *, enum xdr_op);
+extern void xdrmbuf_append(XDR *, struct mbuf *);
+extern struct mbuf * xdrmbuf_getall(XDR *);
/* XDR pseudo records for tcp */
extern void xdrrec_create(XDR *, u_int, u_int, void *,
OpenPOWER on IntegriCloud