From 2fb03513fc4b5d35a398f1ceb4b439fe4bb5fb74 Mon Sep 17 00:00:00 2001 From: dfr Date: Mon, 3 Nov 2008 10:38:00 +0000 Subject: 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_). 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/@' 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 --- etc/gss/mech | 2 +- etc/rc.d/Makefile | 2 +- etc/rc.d/gssd | 18 + etc/rc.d/nfsd | 2 +- include/rpc/xdr.h | 3 + lib/libc/sys/Symbol.map | 1 + lib/libc/xdr/Symbol.map | 6 + lib/libc/xdr/xdr.c | 92 ++ lib/librpcsec_gss/svc_rpcsec_gss.c | 43 +- sbin/mount_nfs/mount_nfs.c | 54 +- sys/compat/freebsd32/syscalls.master | 2 + sys/conf/files | 123 +- sys/conf/options | 9 + sys/fs/unionfs/union_vfsops.c | 2 +- sys/kern/syscalls.master | 2 + sys/kern/vfs_export.c | 18 +- sys/kern/vfs_mount.c | 14 + sys/kgssapi/gss_accept_sec_context.c | 138 +++ sys/kgssapi/gss_acquire_cred.c | 105 ++ sys/kgssapi/gss_add_oid_set_member.c | 76 ++ sys/kgssapi/gss_canonicalize_name.c | 76 ++ sys/kgssapi/gss_create_empty_oid_set.c | 55 + sys/kgssapi/gss_delete_sec_context.c | 91 ++ sys/kgssapi/gss_display_status.c | 79 ++ sys/kgssapi/gss_export_name.c | 71 ++ sys/kgssapi/gss_get_mic.c | 89 ++ sys/kgssapi/gss_impl.c | 266 ++++ sys/kgssapi/gss_import_name.c | 79 ++ sys/kgssapi/gss_init_sec_context.c | 135 ++ sys/kgssapi/gss_names.c | 176 +++ sys/kgssapi/gss_pname_to_uid.c | 122 ++ sys/kgssapi/gss_release_buffer.c | 52 + sys/kgssapi/gss_release_cred.c | 69 ++ sys/kgssapi/gss_release_name.c | 74 ++ sys/kgssapi/gss_release_oid_set.c | 52 + sys/kgssapi/gss_set_cred_option.c | 77 ++ sys/kgssapi/gss_test_oid_set_member.c | 54 + sys/kgssapi/gss_unwrap.c | 97 ++ sys/kgssapi/gss_verify_mic.c | 87 ++ sys/kgssapi/gss_wrap.c | 96 ++ sys/kgssapi/gss_wrap_size_limit.c | 56 + sys/kgssapi/gssapi.h | 620 ++++++++++ sys/kgssapi/gssapi_impl.h | 67 + sys/kgssapi/gssd.x | 265 ++++ sys/kgssapi/gssd_prot.c | 244 ++++ sys/kgssapi/gsstest.c | 1141 +++++++++++++++++ sys/kgssapi/kgss_if.m | 95 ++ sys/kgssapi/krb5/kcrypto.c | 266 ++++ sys/kgssapi/krb5/kcrypto.h | 154 +++ sys/kgssapi/krb5/kcrypto_aes.c | 384 ++++++ sys/kgssapi/krb5/kcrypto_arcfour.c | 220 ++++ sys/kgssapi/krb5/kcrypto_des.c | 262 ++++ sys/kgssapi/krb5/kcrypto_des3.c | 402 ++++++ sys/kgssapi/krb5/krb5_mech.c | 2100 ++++++++++++++++++++++++++++++++ sys/modules/kgssapi/Makefile | 55 + sys/modules/kgssapi_krb5/Makefile | 21 + sys/modules/nfsclient/Makefile | 4 +- sys/modules/nfsserver/Makefile | 4 +- sys/nfsclient/nfs.h | 25 +- sys/nfsclient/nfs_krpc.c | 769 ++++++++++++ sys/nfsclient/nfs_socket.c | 4 + sys/nfsclient/nfs_subs.c | 8 +- sys/nfsclient/nfs_vfsops.c | 61 +- sys/nfsclient/nfsmount.h | 34 + sys/nfsserver/nfs.h | 90 +- sys/nfsserver/nfs_fha.c | 597 +++++++++ sys/nfsserver/nfs_fha.h | 28 + sys/nfsserver/nfs_serv.c | 67 +- sys/nfsserver/nfs_srvcache.c | 4 + sys/nfsserver/nfs_srvkrpc.c | 565 +++++++++ sys/nfsserver/nfs_srvsock.c | 5 + sys/nfsserver/nfs_srvsubs.c | 65 +- sys/nfsserver/nfs_syscalls.c | 8 +- sys/nfsserver/nfsm_subs.h | 5 +- sys/nfsserver/nfsrvcache.h | 4 + sys/nlm/nlm.h | 2 +- sys/nlm/nlm_advlock.c | 9 +- sys/nlm/nlm_prot_impl.c | 116 +- sys/nlm/nlm_prot_svc.c | 72 +- sys/rpc/auth.h | 45 +- sys/rpc/auth_none.c | 20 +- sys/rpc/auth_unix.c | 34 +- sys/rpc/clnt.h | 70 +- sys/rpc/clnt_dg.c | 310 +++-- sys/rpc/clnt_rc.c | 126 +- sys/rpc/clnt_vc.c | 239 ++-- sys/rpc/replay.c | 248 ++++ sys/rpc/replay.h | 85 ++ sys/rpc/rpc_com.h | 1 + sys/rpc/rpc_generic.c | 134 ++ sys/rpc/rpc_msg.h | 2 +- sys/rpc/rpc_prot.c | 81 +- sys/rpc/rpcsec_gss.h | 189 +++ sys/rpc/rpcsec_gss/rpcsec_gss.c | 1064 ++++++++++++++++ sys/rpc/rpcsec_gss/rpcsec_gss_conf.c | 163 +++ sys/rpc/rpcsec_gss/rpcsec_gss_int.h | 94 ++ sys/rpc/rpcsec_gss/rpcsec_gss_misc.c | 53 + sys/rpc/rpcsec_gss/rpcsec_gss_prot.c | 359 ++++++ sys/rpc/rpcsec_gss/svc_rpcsec_gss.c | 1485 ++++++++++++++++++++++ sys/rpc/svc.c | 1048 +++++++++++++--- sys/rpc/svc.h | 244 +++- sys/rpc/svc_auth.c | 75 +- sys/rpc/svc_auth.h | 24 +- sys/rpc/svc_auth_unix.c | 3 +- sys/rpc/svc_dg.c | 157 +-- sys/rpc/svc_generic.c | 93 +- sys/rpc/svc_vc.c | 247 ++-- sys/rpc/xdr.h | 2 + sys/sys/mount.h | 24 +- sys/xdr/xdr_mbuf.c | 70 +- tools/regression/kgssapi/Makefile | 9 + tools/regression/kgssapi/gsstest.c | 307 +++++ tools/regression/rpcsec_gss/Makefile | 9 + tools/regression/rpcsec_gss/rpctest.c | 400 ++++++ usr.sbin/Makefile | 5 + usr.sbin/gssd/Makefile | 29 + usr.sbin/gssd/gssd.8 | 70 ++ usr.sbin/gssd/gssd.c | 610 ++++++++++ usr.sbin/mountd/exports.5 | 22 + usr.sbin/mountd/mountd.c | 92 +- usr.sbin/nfsd/nfsd.c | 115 +- 121 files changed, 18973 insertions(+), 991 deletions(-) create mode 100755 etc/rc.d/gssd create mode 100644 sys/kgssapi/gss_accept_sec_context.c create mode 100644 sys/kgssapi/gss_acquire_cred.c create mode 100644 sys/kgssapi/gss_add_oid_set_member.c create mode 100644 sys/kgssapi/gss_canonicalize_name.c create mode 100644 sys/kgssapi/gss_create_empty_oid_set.c create mode 100644 sys/kgssapi/gss_delete_sec_context.c create mode 100644 sys/kgssapi/gss_display_status.c create mode 100644 sys/kgssapi/gss_export_name.c create mode 100644 sys/kgssapi/gss_get_mic.c create mode 100644 sys/kgssapi/gss_impl.c create mode 100644 sys/kgssapi/gss_import_name.c create mode 100644 sys/kgssapi/gss_init_sec_context.c create mode 100644 sys/kgssapi/gss_names.c create mode 100644 sys/kgssapi/gss_pname_to_uid.c create mode 100644 sys/kgssapi/gss_release_buffer.c create mode 100644 sys/kgssapi/gss_release_cred.c create mode 100644 sys/kgssapi/gss_release_name.c create mode 100644 sys/kgssapi/gss_release_oid_set.c create mode 100644 sys/kgssapi/gss_set_cred_option.c create mode 100644 sys/kgssapi/gss_test_oid_set_member.c create mode 100644 sys/kgssapi/gss_unwrap.c create mode 100644 sys/kgssapi/gss_verify_mic.c create mode 100644 sys/kgssapi/gss_wrap.c create mode 100644 sys/kgssapi/gss_wrap_size_limit.c create mode 100644 sys/kgssapi/gssapi.h create mode 100644 sys/kgssapi/gssapi_impl.h create mode 100644 sys/kgssapi/gssd.x create mode 100644 sys/kgssapi/gssd_prot.c create mode 100644 sys/kgssapi/gsstest.c create mode 100644 sys/kgssapi/kgss_if.m create mode 100644 sys/kgssapi/krb5/kcrypto.c create mode 100644 sys/kgssapi/krb5/kcrypto.h create mode 100644 sys/kgssapi/krb5/kcrypto_aes.c create mode 100644 sys/kgssapi/krb5/kcrypto_arcfour.c create mode 100644 sys/kgssapi/krb5/kcrypto_des.c create mode 100644 sys/kgssapi/krb5/kcrypto_des3.c create mode 100644 sys/kgssapi/krb5/krb5_mech.c create mode 100644 sys/modules/kgssapi/Makefile create mode 100644 sys/modules/kgssapi_krb5/Makefile create mode 100644 sys/nfsclient/nfs_krpc.c create mode 100644 sys/nfsserver/nfs_fha.c create mode 100644 sys/nfsserver/nfs_fha.h create mode 100644 sys/nfsserver/nfs_srvkrpc.c create mode 100644 sys/rpc/replay.c create mode 100644 sys/rpc/replay.h create mode 100644 sys/rpc/rpcsec_gss.h create mode 100644 sys/rpc/rpcsec_gss/rpcsec_gss.c create mode 100644 sys/rpc/rpcsec_gss/rpcsec_gss_conf.c create mode 100644 sys/rpc/rpcsec_gss/rpcsec_gss_int.h create mode 100644 sys/rpc/rpcsec_gss/rpcsec_gss_misc.c create mode 100644 sys/rpc/rpcsec_gss/rpcsec_gss_prot.c create mode 100644 sys/rpc/rpcsec_gss/svc_rpcsec_gss.c create mode 100644 tools/regression/kgssapi/Makefile create mode 100644 tools/regression/kgssapi/gsstest.c create mode 100644 tools/regression/rpcsec_gss/Makefile create mode 100644 tools/regression/rpcsec_gss/rpctest.c create mode 100644 usr.sbin/gssd/Makefile create mode 100644 usr.sbin/gssd/gssd.8 create mode 100644 usr.sbin/gssd/gssd.c diff --git a/etc/gss/mech b/etc/gss/mech index d2eaa4c..7cc82c7 100644 --- a/etc/gss/mech +++ b/etc/gss/mech @@ -1,6 +1,6 @@ # $FreeBSD$ # # Name OID Library name Kernel module -kerberosv5 1.2.840.113554.1.2.2 /usr/lib/libgssapi_krb5.so.10 - +kerberosv5 1.2.840.113554.1.2.2 /usr/lib/libgssapi_krb5.so.10 kgssapi_krb5 spnego 1.3.6.1.5.5.2 /usr/lib/libgssapi_spnego.so.10 - #ntlm 1.3.6.1.4.1.311.2.2.10 /usr/lib/libgssapi_ntlm.so.10 - diff --git a/etc/rc.d/Makefile b/etc/rc.d/Makefile index 7d01004..d28fb8b 100755 --- a/etc/rc.d/Makefile +++ b/etc/rc.d/Makefile @@ -11,7 +11,7 @@ FILES= DAEMON FILESYSTEMS LOGIN NETWORKING SERVERS \ dmesg dumpon \ early.sh encswap \ fsck ftp-proxy ftpd \ - gbde geli geli2 \ + gbde geli geli2 gssd \ hcsecd \ hostapd hostid hostname \ idmapd inetd initrandom \ diff --git a/etc/rc.d/gssd b/etc/rc.d/gssd new file mode 100755 index 0000000..3788307 --- /dev/null +++ b/etc/rc.d/gssd @@ -0,0 +1,18 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: gssd +# REQUIRE: root +# KEYWORD: nojail shutdown + +. /etc/rc.subr + +name="gssd" + +load_rc_config $name +rcvar="gssd_enable" +command="${gssd:-/usr/sbin/${name}}" +eval ${name}_flags=\"${gssd_flags}\" +run_rc_command "$1" diff --git a/etc/rc.d/nfsd b/etc/rc.d/nfsd index b60c5c8..84bda25 100755 --- a/etc/rc.d/nfsd +++ b/etc/rc.d/nfsd @@ -4,7 +4,7 @@ # # PROVIDE: nfsd -# REQUIRE: mountd +# REQUIRE: mountd hostname gssd # KEYWORD: nojail shutdown . /etc/rc.subr diff --git a/include/rpc/xdr.h b/include/rpc/xdr.h index db29cc3..a02291b 100644 --- a/include/rpc/xdr.h +++ b/include/rpc/xdr.h @@ -294,10 +294,13 @@ extern bool_t xdr_short(XDR *, short *); extern bool_t xdr_u_short(XDR *, u_short *); extern bool_t xdr_int16_t(XDR *, int16_t *); extern bool_t xdr_u_int16_t(XDR *, u_int16_t *); +extern bool_t xdr_uint16_t(XDR *, u_int16_t *); extern bool_t xdr_int32_t(XDR *, int32_t *); extern bool_t xdr_u_int32_t(XDR *, u_int32_t *); +extern bool_t xdr_uint32_t(XDR *, u_int32_t *); extern bool_t xdr_int64_t(XDR *, int64_t *); extern bool_t xdr_u_int64_t(XDR *, u_int64_t *); +extern bool_t xdr_uint64_t(XDR *, u_int64_t *); extern bool_t xdr_bool(XDR *, bool_t *); extern bool_t xdr_enum(XDR *, enum_t *); extern bool_t xdr_array(XDR *, char **, u_int *, u_int, u_int, xdrproc_t); diff --git a/lib/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map index 5217ab0..02ed73b 100644 --- a/lib/libc/sys/Symbol.map +++ b/lib/libc/sys/Symbol.map @@ -982,4 +982,5 @@ FBSDprivate_1.0 { __sys_writev; __error_unthreaded; nlm_syscall; + gssd_syscall; }; diff --git a/lib/libc/xdr/Symbol.map b/lib/libc/xdr/Symbol.map index 4cceeb7..0739b7b 100644 --- a/lib/libc/xdr/Symbol.map +++ b/lib/libc/xdr/Symbol.map @@ -45,3 +45,9 @@ FBSD_1.0 { /* xdr_sizeof; */ /* Why is xdr_sizeof.c not included in Makefileinc? */ xdrstdio_create; }; + +FBSD_1.1 { + xdr_uint16_t; + xdr_uint32_t; + xdr_uint64_t; +}; diff --git a/lib/libc/xdr/xdr.c b/lib/libc/xdr/xdr.c index c1e5eec..337cdc0 100644 --- a/lib/libc/xdr/xdr.c +++ b/lib/libc/xdr/xdr.c @@ -263,6 +263,36 @@ xdr_u_int32_t(xdrs, u_int32_p) return (FALSE); } +/* + * XDR unsigned 32-bit integers + * same as xdr_int32_t - open coded to save a proc call! + */ +bool_t +xdr_uint32_t(xdrs, u_int32_p) + XDR *xdrs; + uint32_t *u_int32_p; +{ + u_long l; + + switch (xdrs->x_op) { + + case XDR_ENCODE: + l = (u_long) *u_int32_p; + return (XDR_PUTLONG(xdrs, (long *)&l)); + + case XDR_DECODE: + if (!XDR_GETLONG(xdrs, (long *)&l)) { + return (FALSE); + } + *u_int32_p = (u_int32_t) l; + return (TRUE); + + case XDR_FREE: + return (TRUE); + } + /* NOTREACHED */ + return (FALSE); +} /* * XDR short integers @@ -385,6 +415,36 @@ xdr_u_int16_t(xdrs, u_int16_p) return (FALSE); } +/* + * XDR unsigned 16-bit integers + */ +bool_t +xdr_uint16_t(xdrs, u_int16_p) + XDR *xdrs; + uint16_t *u_int16_p; +{ + u_long l; + + switch (xdrs->x_op) { + + case XDR_ENCODE: + l = (u_long) *u_int16_p; + return (XDR_PUTLONG(xdrs, (long *)&l)); + + case XDR_DECODE: + if (!XDR_GETLONG(xdrs, (long *)&l)) { + return (FALSE); + } + *u_int16_p = (u_int16_t) l; + return (TRUE); + + case XDR_FREE: + return (TRUE); + } + /* NOTREACHED */ + return (FALSE); +} + /* * XDR a char @@ -806,6 +866,38 @@ xdr_u_int64_t(xdrs, ullp) return (FALSE); } +/* + * XDR unsigned 64-bit integers + */ +bool_t +xdr_uint64_t(xdrs, ullp) + XDR *xdrs; + uint64_t *ullp; +{ + u_long ul[2]; + + switch (xdrs->x_op) { + case XDR_ENCODE: + ul[0] = (u_long)(*ullp >> 32) & 0xffffffff; + ul[1] = (u_long)(*ullp) & 0xffffffff; + if (XDR_PUTLONG(xdrs, (long *)&ul[0]) == FALSE) + return (FALSE); + return (XDR_PUTLONG(xdrs, (long *)&ul[1])); + case XDR_DECODE: + if (XDR_GETLONG(xdrs, (long *)&ul[0]) == FALSE) + return (FALSE); + if (XDR_GETLONG(xdrs, (long *)&ul[1]) == FALSE) + return (FALSE); + *ullp = (u_int64_t) + (((u_int64_t)ul[0] << 32) | ((u_int64_t)ul[1])); + return (TRUE); + case XDR_FREE: + return (TRUE); + } + /* NOTREACHED */ + return (FALSE); +} + /* * XDR hypers diff --git a/lib/librpcsec_gss/svc_rpcsec_gss.c b/lib/librpcsec_gss/svc_rpcsec_gss.c index acafa09..276126b 100644 --- a/lib/librpcsec_gss/svc_rpcsec_gss.c +++ b/lib/librpcsec_gss/svc_rpcsec_gss.c @@ -168,7 +168,7 @@ rpc_gss_set_callback(rpc_gss_callback_t *cb) { struct svc_rpc_gss_callback *scb; - scb = malloc(sizeof(struct svc_rpc_gss_callback)); + scb = mem_alloc(sizeof(struct svc_rpc_gss_callback)); if (!scb) { _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM); return (FALSE); @@ -255,7 +255,7 @@ rpc_gss_get_principal_name(rpc_gss_principal_t *principal, namelen += strlen(domain) + 1; } - buf.value = malloc(namelen); + buf.value = mem_alloc(namelen); buf.length = namelen; strcpy((char *) buf.value, name); if (node) { @@ -273,7 +273,7 @@ rpc_gss_get_principal_name(rpc_gss_principal_t *principal, */ maj_stat = gss_import_name(&min_stat, &buf, GSS_C_NT_USER_NAME, &gss_name); - free(buf.value); + mem_free(buf.value, buf.length); if (maj_stat != GSS_S_COMPLETE) { log_status("gss_import_name", mech_oid, maj_stat, min_stat); return (FALSE); @@ -300,7 +300,7 @@ rpc_gss_get_principal_name(rpc_gss_principal_t *principal, } gss_release_name(&min_stat, &gss_mech_name); - result = malloc(sizeof(int) + buf.length); + result = mem_alloc(sizeof(int) + buf.length); if (!result) { gss_release_buffer(&min_stat, &buf); return (FALSE); @@ -443,7 +443,9 @@ svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client) gss_release_name(&min_stat, &client->cl_cname); if (client->cl_rawcred.client_principal) - free(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_verf.value) gss_release_buffer(&min_stat, &client->cl_verf); @@ -527,7 +529,7 @@ gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str) * here for "{ " and "}\0". */ string_length += 4; - if ((bp = (char *) malloc(string_length))) { + if ((bp = (char *) mem_alloc(string_length))) { strcpy(bp, "{ "); number = (unsigned long) cp[0]; sprintf(numstr, "%ld ", number/40); @@ -634,8 +636,15 @@ svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client, client->cl_sname = sname; break; } + client->cl_sname = sname; + break; } } + if (!sname) { + xdr_free((xdrproc_t) xdr_gss_buffer_desc, + (char *) &recv_tok); + return (FALSE); + } } else { gr->gr_major = gss_accept_sec_context( &gr->gr_minor, @@ -663,11 +672,11 @@ svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client, log_status("accept_sec_context", client->cl_mech, gr->gr_major, gr->gr_minor); client->cl_state = CLIENT_STALE; - return (FALSE); + return (TRUE); } gr->gr_handle.value = &client->cl_id; - gr->gr_handle.length = sizeof(uint32_t); + gr->gr_handle.length = sizeof(client->cl_id); gr->gr_win = SVC_RPC_GSS_SEQWINDOW; /* Save client info. */ @@ -703,7 +712,7 @@ svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client, return (FALSE); } client->cl_rawcred.client_principal = - malloc(sizeof(*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, @@ -718,6 +727,7 @@ svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client, * kerberos5, this uses krb5_aname_to_localname. */ svc_rpc_gss_build_ucred(client, client->cl_cname); + gss_release_name(&min_stat, &client->cl_cname); #ifdef DEBUG { @@ -892,13 +902,12 @@ svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq) * discard it. */ offset = client->cl_seqlast - seq; - if (offset >= client->cl_win) + if (offset >= SVC_RPC_GSS_SEQWINDOW) return (FALSE); word = offset / 32; bit = offset % 32; if (client->cl_seqmask[word] & (1 << bit)) return (FALSE); - client->cl_seqmask[word] |= (1 << bit); } return (TRUE); @@ -907,7 +916,7 @@ svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq) static void svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq) { - int offset, i; + int offset, i, word, bit; uint32_t carry, newcarry; if (seq > client->cl_seqlast) { @@ -936,7 +945,13 @@ svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq) } 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); } + } enum auth_stat @@ -983,6 +998,10 @@ svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg) /* 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(); } else { if (gc.gc_handle.length != sizeof(uint32_t)) { diff --git a/sbin/mount_nfs/mount_nfs.c b/sbin/mount_nfs/mount_nfs.c index 71ac14e..fdfecc4 100644 --- a/sbin/mount_nfs/mount_nfs.c +++ b/sbin/mount_nfs/mount_nfs.c @@ -134,6 +134,7 @@ struct sockaddr *addr; int addrlen = 0; u_char *fh = NULL; int fhsize = 0; +int secflavor = -1; enum mountmode { ANY, @@ -151,6 +152,8 @@ enum tryret { }; int fallback_mount(struct iovec *iov, int iovlen, int mntflags); +int sec_name_to_num(char *sec); +char *sec_num_to_name(int num); int getnfsargs(char *, struct iovec **iov, int *iovlen); int getnfs4args(char *, struct iovec **iov, int *iovlen); /* void set_rpc_maxgrouplist(int); */ @@ -308,6 +311,21 @@ main(int argc, char *argv[]) atoi(val)); if (portspec == NULL) err(1, "asprintf"); + } else if (strcmp(opt, "sec") == 0) { + /* + * Don't add this option to + * the iovec yet - we will + * negotiate which sec flavor + * to use with the remote + * mountd. + */ + pass_flag_to_nmount=0; + secflavor = sec_name_to_num(val); + if (secflavor < 0) { + errx(1, + "illegal sec value -- %s", + val); + } } else if (strcmp(opt, "retrycnt") == 0) { pass_flag_to_nmount=0; num = strtol(val, &p, 10); @@ -635,6 +653,36 @@ fallback_mount(struct iovec *iov, int iovlen, int mntflags) } int +sec_name_to_num(char *sec) +{ + if (!strcmp(sec, "krb5")) + return (RPCSEC_GSS_KRB5); + if (!strcmp(sec, "krb5i")) + return (RPCSEC_GSS_KRB5I); + if (!strcmp(sec, "krb5p")) + return (RPCSEC_GSS_KRB5P); + if (!strcmp(sec, "sys")) + return (AUTH_SYS); + return (-1); +} + +char * +sec_num_to_name(int flavor) +{ + switch (flavor) { + case RPCSEC_GSS_KRB5: + return ("krb5"); + case RPCSEC_GSS_KRB5I: + return ("krb5i"); + case RPCSEC_GSS_KRB5P: + return ("krb5p"); + case AUTH_SYS: + return ("sys"); + } + return (NULL); +} + +int getnfsargs(char *spec, struct iovec **iov, int *iovlen) { struct addrinfo hints, *ai_nfs, *ai; @@ -904,6 +952,7 @@ nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr, CLIENT *clp; struct netconfig *nconf, *nconf_mnt; const char *netid, *netid_mnt; + char *secname; int doconnect, nfsvers, mntvers, sotype; enum clnt_stat stat; enum mountmode trymntmode; @@ -1033,7 +1082,7 @@ tryagain: &rpc_createerr.cf_error)); } clp->cl_auth = authsys_create_default(); - nfhret.auth = -1; + nfhret.auth = secflavor; nfhret.vers = mntvers; stat = clnt_call(clp, RPCMNT_MOUNT, (xdrproc_t)xdr_dir, spec, (xdrproc_t)xdr_fh, &nfhret, @@ -1074,6 +1123,9 @@ tryagain: build_iovec(iov, iovlen, "addr", addr, addrlen); build_iovec(iov, iovlen, "fh", fh, fhsize); + secname = sec_num_to_name(nfhret.auth); + if (secname) + build_iovec(iov, iovlen, "sec", secname, (size_t)-1); if (nfsvers == 3) build_iovec(iov, iovlen, "nfsv3", NULL, 0); diff --git a/sys/compat/freebsd32/syscalls.master b/sys/compat/freebsd32/syscalls.master index fa9cbd2..d4c1d2e 100644 --- a/sys/compat/freebsd32/syscalls.master +++ b/sys/compat/freebsd32/syscalls.master @@ -854,3 +854,5 @@ 503 AUE_UNLINKAT NOPROTO { int unlinkat(int fd, char *path, \ int flag); } 504 AUE_POSIX_OPENPT NOPROTO { int posix_openpt(int flags); } +; 505 is initialised by the kgssapi code, if present. +505 AUE_NULL UNIMPL gssd_syscall diff --git a/sys/conf/files b/sys/conf/files index 073f737..af7f0b0 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -339,7 +339,7 @@ crypto/camellia/camellia.c optional crypto | ipsec crypto/camellia/camellia-api.c optional crypto | ipsec crypto/des/des_ecb.c optional crypto | ipsec | netsmb crypto/des/des_setkey.c optional crypto | ipsec | netsmb -crypto/rc4/rc4.c optional netgraph_mppc_encryption +crypto/rc4/rc4.c optional netgraph_mppc_encryption | kgssapi crypto/rijndael/rijndael-alg-fst.c optional crypto | geom_bde | \ ipsec | random | wlan_ccmp crypto/rijndael/rijndael-api-fst.c optional geom_bde | random @@ -1746,6 +1746,56 @@ kern/vfs_subr.c standard kern/vfs_syscalls.c standard kern/vfs_vnops.c standard # +# Kernel GSS-API +# +gssd.h optional kgssapi \ + dependency "$S/kgssapi/gssd.x" \ + compile-with "rpcgen -hM $S/kgssapi/gssd.x | grep -v pthread.h > gssd.h" \ + no-obj no-implicit-rule before-depend local \ + clean "gssd.h" +gssd_xdr.c optional kgssapi \ + dependency "$S/kgssapi/gssd.x gssd.h" \ + compile-with "rpcgen -c $S/kgssapi/gssd.x -o gssd_xdr.c" \ + no-implicit-rule before-depend local \ + clean "gssd_xdr.c" +gssd_clnt.c optional kgssapi \ + dependency "$S/kgssapi/gssd.x gssd.h" \ + compile-with "rpcgen -lM $S/kgssapi/gssd.x | grep -v string.h > gssd_clnt.c" \ + no-implicit-rule before-depend local \ + clean "gssd_clnt.c" +kgssapi/gss_accept_sec_context.c optional kgssapi +kgssapi/gss_add_oid_set_member.c optional kgssapi +kgssapi/gss_acquire_cred.c optional kgssapi +kgssapi/gss_canonicalize_name.c optional kgssapi +kgssapi/gss_create_empty_oid_set.c optional kgssapi +kgssapi/gss_delete_sec_context.c optional kgssapi +kgssapi/gss_display_status.c optional kgssapi +kgssapi/gss_export_name.c optional kgssapi +kgssapi/gss_get_mic.c optional kgssapi +kgssapi/gss_init_sec_context.c optional kgssapi +kgssapi/gss_impl.c optional kgssapi +kgssapi/gss_import_name.c optional kgssapi +kgssapi/gss_names.c optional kgssapi +kgssapi/gss_pname_to_uid.c optional kgssapi +kgssapi/gss_release_buffer.c optional kgssapi +kgssapi/gss_release_cred.c optional kgssapi +kgssapi/gss_release_name.c optional kgssapi +kgssapi/gss_release_oid_set.c optional kgssapi +kgssapi/gss_set_cred_option.c optional kgssapi +kgssapi/gss_test_oid_set_member.c optional kgssapi +kgssapi/gss_unwrap.c optional kgssapi +kgssapi/gss_verify_mic.c optional kgssapi +kgssapi/gss_wrap.c optional kgssapi +kgssapi/gss_wrap_size_limit.c optional kgssapi +kgssapi/gssd_prot.c optional kgssapi +kgssapi/krb5/krb5_mech.c optional kgssapi +kgssapi/krb5/kcrypto.c optional kgssapi +kgssapi/krb5/kcrypto_aes.c optional kgssapi +kgssapi/krb5/kcrypto_arcfour.c optional kgssapi +kgssapi/krb5/kcrypto_des.c optional kgssapi +kgssapi/krb5/kcrypto_des3.c optional kgssapi +kgssapi/kgss_if.m optional kgssapi +kgssapi/gsstest.c optional kgssapi_debug # These files in libkern/ are those needed by all architectures. Some # of the files in libkern/ are only needed on some architectures, e.g., # libkern/divdi3.c is needed by i386 but not alpha. Also, some of these @@ -2106,18 +2156,21 @@ nfsclient/krpc_subr.c optional bootp nfsclient nfsclient/nfs_bio.c optional nfsclient nfsclient/nfs_diskless.c optional nfsclient nfs_root nfsclient/nfs_node.c optional nfsclient -nfsclient/nfs_socket.c optional nfsclient +nfsclient/nfs_socket.c optional nfsclient nfs_legacyrpc +nfsclient/nfs_krpc.c optional nfsclient nfsclient/nfs_subs.c optional nfsclient nfsclient/nfs_nfsiod.c optional nfsclient nfsclient/nfs_vfsops.c optional nfsclient nfsclient/nfs_vnops.c optional nfsclient nfsclient/nfs_lock.c optional nfsclient +nfsserver/nfs_fha.c optional nfsserver nfsserver/nfs_serv.c optional nfsserver -nfsserver/nfs_srvsock.c optional nfsserver -nfsserver/nfs_srvcache.c optional nfsserver +nfsserver/nfs_srvkrpc.c optional nfsserver +nfsserver/nfs_srvsock.c optional nfsserver nfs_legacyrpc +nfsserver/nfs_srvcache.c optional nfsserver nfs_legacyrpc nfsserver/nfs_srvsubs.c optional nfsserver -nfsserver/nfs_syscalls.c optional nfsserver -nlm/nlm_advlock.c optional nfslockd +nfsserver/nfs_syscalls.c optional nfsserver nfs_legacyrpc +nlm/nlm_advlock.c optional nfslockd nfsclient nlm/nlm_prot_clnt.c optional nfslockd nlm/nlm_prot_impl.c optional nfslockd nlm/nlm_prot_server.c optional nfslockd @@ -2143,27 +2196,33 @@ pci/intpm.c optional intpm pci pci/ncr.c optional ncr pci pci/nfsmb.c optional nfsmb pci pci/viapm.c optional viapm pci -rpc/auth_none.c optional krpc | nfslockd -rpc/auth_unix.c optional krpc | nfslockd -rpc/authunix_prot.c optional krpc | nfslockd -rpc/clnt_dg.c optional krpc | nfslockd -rpc/clnt_rc.c optional krpc | nfslockd -rpc/clnt_vc.c optional krpc | nfslockd -rpc/getnetconfig.c optional krpc | nfslockd -rpc/inet_ntop.c optional krpc | nfslockd -rpc/inet_pton.c optional krpc | nfslockd -rpc/rpc_callmsg.c optional krpc | nfslockd -rpc/rpc_generic.c optional krpc | nfslockd -rpc/rpc_prot.c optional krpc | nfslockd -rpc/rpcb_clnt.c optional krpc | nfslockd -rpc/rpcb_prot.c optional krpc | nfslockd +rpc/auth_none.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/auth_unix.c optional krpc | nfslockd | nfsclient +rpc/authunix_prot.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/clnt_dg.c optional krpc | nfslockd | nfsclient +rpc/clnt_rc.c optional krpc | nfslockd | nfsclient +rpc/clnt_vc.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/getnetconfig.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/inet_ntop.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/inet_pton.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/replay.c optional krpc | nfslockd | nfsserver +rpc/rpc_callmsg.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/rpc_generic.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/rpc_prot.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/rpcb_clnt.c optional krpc | nfslockd | nfsclient | nfsserver +rpc/rpcb_prot.c optional krpc | nfslockd | nfsclient | nfsserver rpc/rpcclnt.c optional nfsclient -rpc/svc.c optional krpc | nfslockd -rpc/svc_auth.c optional krpc | nfslockd -rpc/svc_auth_unix.c optional krpc | nfslockd -rpc/svc_dg.c optional krpc | nfslockd -rpc/svc_generic.c optional krpc | nfslockd -rpc/svc_vc.c optional krpc | nfslockd +rpc/svc.c optional krpc | nfslockd | nfsserver +rpc/svc_auth.c optional krpc | nfslockd | nfsserver +rpc/svc_auth_unix.c optional krpc | nfslockd | nfsserver +rpc/svc_dg.c optional krpc | nfslockd | nfsserver +rpc/svc_generic.c optional krpc | nfslockd | nfsserver +rpc/svc_vc.c optional krpc | nfslockd | nfsserver +rpc/rpcsec_gss/rpcsec_gss.c optional krpc kgssapi | nfslockd kgssapi +rpc/rpcsec_gss/rpcsec_gss_conf.c optional krpc kgssapi | nfslockd kgssapi +rpc/rpcsec_gss/rpcsec_gss_misc.c optional krpc kgssapi | nfslockd kgssapi +rpc/rpcsec_gss/rpcsec_gss_prot.c optional krpc kgssapi | nfslockd kgssapi +rpc/rpcsec_gss/svc_rpcsec_gss.c optional krpc kgssapi | nfslockd kgssapi security/audit/audit.c optional audit security/audit/audit_arg.c optional audit security/audit/audit_bsm.c optional audit @@ -2251,12 +2310,12 @@ vm/vm_reserv.c standard vm/vm_unix.c standard vm/vm_zeroidle.c standard vm/vnode_pager.c standard -xdr/xdr.c optional krpc | nfslockd -xdr/xdr_array.c optional krpc | nfslockd -xdr/xdr_mbuf.c optional krpc | nfslockd -xdr/xdr_mem.c optional krpc | nfslockd -xdr/xdr_reference.c optional krpc | nfslockd -xdr/xdr_sizeof.c optional krpc | nfslockd +xdr/xdr.c optional krpc | nfslockd | nfsclient | nfsserver +xdr/xdr_array.c optional krpc | nfslockd | nfsclient | nfsserver +xdr/xdr_mbuf.c optional krpc | nfslockd | nfsclient | nfsserver +xdr/xdr_mem.c optional krpc | nfslockd | nfsclient | nfsserver +xdr/xdr_reference.c optional krpc | nfslockd | nfsclient | nfsserver +xdr/xdr_sizeof.c optional krpc | nfslockd | nfsclient | nfsserver # gnu/fs/xfs/xfs_alloc.c optional xfs \ compile-with "${NORMAL_C} -I$S/gnu/fs/xfs/FreeBSD -I$S/gnu/fs/xfs/FreeBSD/support -I$S/gnu/fs/xfs" \ diff --git a/sys/conf/options b/sys/conf/options index 0823c82..f20547d 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -214,6 +214,10 @@ PSEUDOFS_TRACE opt_pseudofs.h # Broken - ffs_snapshot() dependency from ufs_lookup() :-( FFS opt_ffs_broken_fixme.h +# In-kernel GSS-API +KGSSAPI opt_kgssapi.h +KGSSAPI_DEBUG opt_kgssapi.h + # These static filesystems have one slightly bogus static dependency in # sys/i386/i386/autoconf.c. If any of these filesystems are # statically compiled into the kernel, code for mounting them as root @@ -222,6 +226,11 @@ NFSCLIENT opt_nfs.h NFSSERVER opt_nfs.h NFS4CLIENT opt_nfs.h +# Use this option to compile both NFS client and server using the +# legacy RPC implementation instead of the newer KRPC system (which +# supports modern features such as RPCSEC_GSS +NFS_LEGACYRPC opt_nfs.h + # filesystems and libiconv bridge CD9660_ICONV opt_dontuse.h MSDOSFS_ICONV opt_dontuse.h diff --git a/sys/fs/unionfs/union_vfsops.c b/sys/fs/unionfs/union_vfsops.c index 2e873fe..e0a3188 100644 --- a/sys/fs/unionfs/union_vfsops.c +++ b/sys/fs/unionfs/union_vfsops.c @@ -521,7 +521,7 @@ unionfs_fhtovp(struct mount *mp, struct fid *fidp, struct vnode **vpp) static int unionfs_checkexp(struct mount *mp, struct sockaddr *nam, int *extflagsp, - struct ucred **credanonp) + struct ucred **credanonp, int *numsecflavors, int **secflavors) { return (EOPNOTSUPP); } diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master index 38adf73..924d750 100644 --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -895,5 +895,7 @@ char *path2); } 503 AUE_UNLINKAT STD { int unlinkat(int fd, char *path, int flag); } 504 AUE_POSIX_OPENPT STD { int posix_openpt(int flags); } +; 505 is initialised by the kgssapi code, if present. +505 AUE_NULL NOSTD { int gssd_syscall(char *path); } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master diff --git a/sys/kern/vfs_export.c b/sys/kern/vfs_export.c index 775ed26..223c2fe 100644 --- a/sys/kern/vfs_export.c +++ b/sys/kern/vfs_export.c @@ -68,6 +68,8 @@ struct netcred { struct radix_node netc_rnodes[2]; int netc_exflags; struct ucred netc_anon; + int netc_numsecflavors; + int netc_secflavors[MAXSECFLAVORS]; }; /* @@ -120,6 +122,9 @@ vfs_hang_addrlist(struct mount *mp, struct netexport *nep, np->netc_anon.cr_ngroups = argp->ex_anon.cr_ngroups; bcopy(argp->ex_anon.cr_groups, np->netc_anon.cr_groups, sizeof(np->netc_anon.cr_groups)); + np->netc_numsecflavors = argp->ex_numsecflavors; + bcopy(argp->ex_secflavors, np->netc_secflavors, + sizeof(np->netc_secflavors)); refcount_init(&np->netc_anon.cr_ref, 1); MNT_ILOCK(mp); mp->mnt_flag |= MNT_DEFEXPORTED; @@ -203,6 +208,9 @@ vfs_hang_addrlist(struct mount *mp, struct netexport *nep, np->netc_anon.cr_ngroups = argp->ex_anon.cr_ngroups; bcopy(argp->ex_anon.cr_groups, np->netc_anon.cr_groups, sizeof(np->netc_anon.cr_groups)); + np->netc_numsecflavors = argp->ex_numsecflavors; + bcopy(argp->ex_secflavors, np->netc_secflavors, + sizeof(np->netc_secflavors)); refcount_init(&np->netc_anon.cr_ref, 1); return (0); out: @@ -253,6 +261,10 @@ vfs_export(struct mount *mp, struct export_args *argp) struct netexport *nep; int error; + if (argp->ex_numsecflavors < 0 + || argp->ex_numsecflavors >= MAXSECFLAVORS) + return (EINVAL); + nep = mp->mnt_export; error = 0; lockmgr(&mp->mnt_explock, LK_EXCLUSIVE, NULL); @@ -441,7 +453,7 @@ vfs_export_lookup(struct mount *mp, struct sockaddr *nam) int vfs_stdcheckexp(struct mount *mp, struct sockaddr *nam, int *extflagsp, - struct ucred **credanonp) + struct ucred **credanonp, int *numsecflavors, int **secflavors) { struct netcred *np; @@ -452,6 +464,10 @@ vfs_stdcheckexp(struct mount *mp, struct sockaddr *nam, int *extflagsp, return (EACCES); *extflagsp = np->netc_exflags; *credanonp = &np->netc_anon; + if (numsecflavors) + *numsecflavors = np->netc_numsecflavors; + if (secflavors) + *secflavors = np->netc_secflavors; return (0); } diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c index 5a216c9..4285b4c 100644 --- a/sys/kern/vfs_mount.c +++ b/sys/kern/vfs_mount.c @@ -827,6 +827,7 @@ vfs_domount( struct vnode *vp; struct mount *mp; struct vfsconf *vfsp; + struct oexport_args oexport; struct export_args export; int error, flag = 0; struct vattr va; @@ -1010,6 +1011,19 @@ vfs_domount( if (vfs_copyopt(mp->mnt_optnew, "export", &export, sizeof(export)) == 0) error = vfs_export(mp, &export); + else if (vfs_copyopt(mp->mnt_optnew, "export", &oexport, + sizeof(oexport)) == 0) { + export.ex_flags = oexport.ex_flags; + export.ex_root = oexport.ex_root; + export.ex_anon = oexport.ex_anon; + export.ex_addr = oexport.ex_addr; + export.ex_addrlen = oexport.ex_addrlen; + export.ex_mask = oexport.ex_mask; + export.ex_masklen = oexport.ex_masklen; + export.ex_indexfile = oexport.ex_indexfile; + export.ex_numsecflavors = 0; + error = vfs_export(mp, &export); + } } if (!error) { diff --git a/sys/kgssapi/gss_accept_sec_context.c b/sys/kgssapi/gss_accept_sec_context.c new file mode 100644 index 0000000..59f2803 --- /dev/null +++ b/sys/kgssapi/gss_accept_sec_context.c @@ -0,0 +1,138 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include + +#include "gssd.h" +#include "kgss_if.h" + +OM_uint32 gss_accept_sec_context(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_cred_id_t acceptor_cred_handle, + const gss_buffer_t input_token, + const gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle) +{ + struct accept_sec_context_res res; + struct accept_sec_context_args args; + enum clnt_stat stat; + gss_ctx_id_t ctx = *context_handle; + gss_name_t name; + gss_cred_id_t cred; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (ctx) + args.ctx = ctx->handle; + else + args.ctx = 0; + if (acceptor_cred_handle) + args.cred = acceptor_cred_handle->handle; + else + args.cred = 0; + args.input_token = *input_token; + args.input_chan_bindings = input_chan_bindings; + + bzero(&res, sizeof(res)); + stat = gssd_accept_sec_context_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE + && res.major_status != GSS_S_CONTINUE_NEEDED) { + *minor_status = res.minor_status; + xdr_free((xdrproc_t) xdr_accept_sec_context_res, &res); + return (res.major_status); + } + + *minor_status = res.minor_status; + + if (!ctx) { + ctx = kgss_create_context(res.mech_type); + if (!ctx) { + xdr_free((xdrproc_t) xdr_accept_sec_context_res, &res); + *minor_status = 0; + return (GSS_S_BAD_MECH); + } + } + *context_handle = ctx; + + ctx->handle = res.ctx; + name = malloc(sizeof(struct _gss_name_t), M_GSSAPI, M_WAITOK); + name->handle = res.src_name; + if (src_name) { + *src_name = name; + } else { + OM_uint32 junk; + gss_release_name(&junk, &name); + } + if (mech_type) + *mech_type = KGSS_MECH_TYPE(ctx); + kgss_copy_buffer(&res.output_token, output_token); + if (ret_flags) + *ret_flags = res.ret_flags; + if (time_rec) + *time_rec = res.time_rec; + cred = malloc(sizeof(struct _gss_cred_id_t), M_GSSAPI, M_WAITOK); + cred->handle = res.delegated_cred_handle; + if (delegated_cred_handle) { + *delegated_cred_handle = cred; + } else { + OM_uint32 junk; + gss_release_cred(&junk, &cred); + } + + xdr_free((xdrproc_t) xdr_accept_sec_context_res, &res); + + /* + * If the context establishment is complete, export it from + * userland and hand the result (which includes key material + * etc.) to the kernel implementation. + */ + if (res.major_status == GSS_S_COMPLETE) + res.major_status = kgss_transfer_context(ctx); + + return (res.major_status); +} diff --git a/sys/kgssapi/gss_acquire_cred.c b/sys/kgssapi/gss_acquire_cred.c new file mode 100644 index 0000000..e5fe821 --- /dev/null +++ b/sys/kgssapi/gss_acquire_cred.c @@ -0,0 +1,105 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_acquire_cred(OM_uint32 *minor_status, + const gss_name_t desired_name, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 major_status; + struct acquire_cred_res res; + struct acquire_cred_args args; + enum clnt_stat stat; + gss_cred_id_t cred; + int i; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.uid = curthread->td_ucred->cr_uid; + if (desired_name) + args.desired_name = desired_name->handle; + else + args.desired_name = 0; + args.time_req = time_req; + args.desired_mechs = desired_mechs; + args.cred_usage = cred_usage; + + bzero(&res, sizeof(res)); + stat = gssd_acquire_cred_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + *minor_status = 0; + cred = malloc(sizeof(struct _gss_cred_id_t), M_GSSAPI, M_WAITOK); + cred->handle = res.output_cred; + *output_cred_handle = cred; + if (actual_mechs) { + major_status = gss_create_empty_oid_set(minor_status, + actual_mechs); + if (major_status) + return (major_status); + for (i = 0; i < res.actual_mechs->count; i++) { + major_status = gss_add_oid_set_member(minor_status, + &res.actual_mechs->elements[i], actual_mechs); + if (major_status) + return (major_status); + } + } + if (time_rec) + *time_rec = res.time_rec; + + xdr_free((xdrproc_t) xdr_acquire_cred_res, &res); + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_add_oid_set_member.c b/sys/kgssapi/gss_add_oid_set_member.c new file mode 100644 index 0000000..ecb8a85 --- /dev/null +++ b/sys/kgssapi/gss_add_oid_set_member.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +OM_uint32 +gss_add_oid_set_member(OM_uint32 *minor_status, + const gss_OID member_oid, + gss_OID_set *oid_set) +{ + OM_uint32 major_status; + gss_OID_set set = *oid_set; + gss_OID new_elements; + gss_OID new_oid; + int t; + + *minor_status = 0; + + major_status = gss_test_oid_set_member(minor_status, + member_oid, *oid_set, &t); + if (major_status) + return (major_status); + if (t) + return (GSS_S_COMPLETE); + + new_elements = malloc((set->count + 1) * sizeof(gss_OID_desc), + M_GSSAPI, M_WAITOK); + + new_oid = &new_elements[set->count]; + new_oid->elements = malloc(member_oid->length, M_GSSAPI, M_WAITOK); + new_oid->length = member_oid->length; + memcpy(new_oid->elements, member_oid->elements, member_oid->length); + + if (set->elements) { + memcpy(new_elements, set->elements, + set->count * sizeof(gss_OID_desc)); + free(set->elements, M_GSSAPI); + } + set->elements = new_elements; + set->count++; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_canonicalize_name.c b/sys/kgssapi/gss_canonicalize_name.c new file mode 100644 index 0000000..bea3dd8 --- /dev/null +++ b/sys/kgssapi/gss_canonicalize_name.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_canonicalize_name(OM_uint32 *minor_status, + gss_name_t input_name, + const gss_OID mech_type, + gss_name_t *output_name) +{ + struct canonicalize_name_res res; + struct canonicalize_name_args args; + enum clnt_stat stat; + gss_name_t name; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.input_name = input_name->handle; + args.mech_type = mech_type; + + bzero(&res, sizeof(res)); + stat = gssd_canonicalize_name_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + name = malloc(sizeof(struct _gss_name_t), M_GSSAPI, M_WAITOK); + name->handle = res.output_name; + *minor_status = 0; + *output_name = name; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_create_empty_oid_set.c b/sys/kgssapi/gss_create_empty_oid_set.c new file mode 100644 index 0000000..dd9965c --- /dev/null +++ b/sys/kgssapi/gss_create_empty_oid_set.c @@ -0,0 +1,55 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +OM_uint32 +gss_create_empty_oid_set(OM_uint32 *minor_status, + gss_OID_set *oid_set) +{ + gss_OID_set set; + + *minor_status = 0; + *oid_set = GSS_C_NO_OID_SET; + + set = malloc(sizeof(gss_OID_set_desc), M_GSSAPI, M_WAITOK); + + set->count = 0; + set->elements = 0; + *oid_set = set; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_delete_sec_context.c b/sys/kgssapi/gss_delete_sec_context.c new file mode 100644 index 0000000..e1582a2 --- /dev/null +++ b/sys/kgssapi/gss_delete_sec_context.c @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_delete_sec_context(OM_uint32 *minor_status, gss_ctx_id_t *context_handle, + gss_buffer_t output_token) +{ + struct delete_sec_context_res res; + struct delete_sec_context_args args; + enum clnt_stat stat; + gss_ctx_id_t ctx; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (*context_handle) { + ctx = *context_handle; + + /* + * If we are past the context establishment phase, let + * the in-kernel code do the delete, otherwise + * userland needs to deal with it. + */ + if (ctx->handle) { + args.ctx = ctx->handle; + + bzero(&res, sizeof(res)); + stat = gssd_delete_sec_context_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (output_token) + kgss_copy_buffer(&res.output_token, + output_token); + xdr_free((xdrproc_t) xdr_delete_sec_context_res, &res); + + kgss_delete_context(ctx, NULL); + } else { + kgss_delete_context(ctx, output_token); + } + *context_handle = NULL; + } else { + if (output_token) { + output_token->length = 0; + output_token->value = NULL; + } + } + + *minor_status = 0; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_display_status.c b/sys/kgssapi/gss_display_status.c new file mode 100644 index 0000000..0b5b79d --- /dev/null +++ b/sys/kgssapi/gss_display_status.c @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_display_status(OM_uint32 *minor_status, + OM_uint32 status_value, + int status_type, + const gss_OID mech_type, + OM_uint32 *message_context, + gss_buffer_t status_string) /* status_string */ +{ + struct display_status_res res; + struct display_status_args args; + enum clnt_stat stat; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.status_value = status_value; + args.status_type = status_type; + args.mech_type = mech_type; + args.message_context = *message_context; + + bzero(&res, sizeof(res)); + stat = gssd_display_status_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + *minor_status = 0; + *message_context = res.message_context; + kgss_copy_buffer(&res.status_string, status_string); + xdr_free((xdrproc_t) xdr_display_status_res, &res); + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_export_name.c b/sys/kgssapi/gss_export_name.c new file mode 100644 index 0000000..63c1e8a --- /dev/null +++ b/sys/kgssapi/gss_export_name.c @@ -0,0 +1,71 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_export_name(OM_uint32 *minor_status, gss_name_t input_name, + gss_buffer_t exported_name) +{ + struct export_name_res res; + struct export_name_args args; + enum clnt_stat stat; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.input_name = input_name->handle; + + bzero(&res, sizeof(res)); + stat = gssd_export_name_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + *minor_status = 0; + kgss_copy_buffer(&res.exported_name, exported_name); + xdr_free((xdrproc_t) xdr_export_name_res, &res); + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_get_mic.c b/sys/kgssapi/gss_get_mic.c new file mode 100644 index 0000000..1e8dd52 --- /dev/null +++ b/sys/kgssapi/gss_get_mic.c @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include + +#include "kgss_if.h" + +OM_uint32 +gss_get_mic(OM_uint32 *minor_status, + const gss_ctx_id_t ctx, + gss_qop_t qop_req, + const gss_buffer_t message_buffer, + gss_buffer_t message_token) +{ + OM_uint32 maj_stat; + struct mbuf *m, *mic; + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + MGET(m, M_WAITOK, MT_DATA); + if (message_buffer->length > MLEN) + MCLGET(m, M_WAITOK); + m_append(m, message_buffer->length, message_buffer->value); + + maj_stat = KGSS_GET_MIC(ctx, minor_status, qop_req, m, &mic); + + m_freem(m); + if (maj_stat == GSS_S_COMPLETE) { + message_token->length = m_length(mic, NULL); + message_token->value = malloc(message_token->length, + M_GSSAPI, M_WAITOK); + m_copydata(mic, 0, message_token->length, + message_token->value); + m_freem(mic); + } + + return (maj_stat); +} + +OM_uint32 +gss_get_mic_mbuf(OM_uint32 *minor_status, const gss_ctx_id_t ctx, + gss_qop_t qop_req, struct mbuf *m, struct mbuf **micp) +{ + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + return (KGSS_GET_MIC(ctx, minor_status, qop_req, m, micp)); +} + diff --git a/sys/kgssapi/gss_impl.c b/sys/kgssapi/gss_impl.c new file mode 100644 index 0000000..01d940a --- /dev/null +++ b/sys/kgssapi/gss_impl.c @@ -0,0 +1,266 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gssd.h" +#include "kgss_if.h" + +MALLOC_DEFINE(M_GSSAPI, "GSS-API", "GSS-API"); + +/* + * Syscall hooks + */ +static int gssd_syscall_offset = SYS_gssd_syscall; +static struct sysent gssd_syscall_prev_sysent; +MAKE_SYSENT(gssd_syscall); +static bool_t gssd_syscall_registered = FALSE; + +struct kgss_mech_list kgss_mechs; +CLIENT *kgss_gssd_handle; + +static void +kgss_init(void *dummy) +{ + int error; + + LIST_INIT(&kgss_mechs); + error = syscall_register(&gssd_syscall_offset, &gssd_syscall_sysent, + &gssd_syscall_prev_sysent); + if (error) + printf("Can't register GSSD syscall\n"); + else + gssd_syscall_registered = TRUE; +} +SYSINIT(kgss_init, SI_SUB_LOCK, SI_ORDER_FIRST, kgss_init, NULL); + +static void +kgss_uninit(void *dummy) +{ + + if (gssd_syscall_registered) + syscall_deregister(&gssd_syscall_offset, + &gssd_syscall_prev_sysent); +} +SYSUNINIT(kgss_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, kgss_uninit, NULL); + +int +gssd_syscall(struct thread *td, struct gssd_syscall_args *uap) +{ + struct sockaddr_un sun; + struct netconfig *nconf; + char path[MAXPATHLEN]; + int error; + + error = priv_check(td, PRIV_NFS_DAEMON); + if (error) + return (error); + + if (kgss_gssd_handle) + CLNT_DESTROY(kgss_gssd_handle); + + error = copyinstr(uap->path, path, sizeof(path), NULL); + if (error) + return (error); + + sun.sun_family = AF_LOCAL; + strcpy(sun.sun_path, path); + sun.sun_len = SUN_LEN(&sun); + + nconf = getnetconfigent("local"); + kgss_gssd_handle = clnt_reconnect_create(nconf, + (struct sockaddr *) &sun, GSSD, GSSDVERS, + RPC_MAXDATASIZE, RPC_MAXDATASIZE); + + return (0); +} + +int +kgss_oid_equal(const gss_OID oid1, const gss_OID oid2) +{ + + if (oid1 == oid2) + return (1); + if (!oid1 || !oid2) + return (0); + if (oid1->length != oid2->length) + return (0); + if (memcmp(oid1->elements, oid2->elements, oid1->length)) + return (0); + return (1); +} + +void +kgss_install_mech(gss_OID mech_type, const char *name, struct kobj_class *cls) +{ + struct kgss_mech *km; + + km = malloc(sizeof(struct kgss_mech), M_GSSAPI, M_WAITOK); + km->km_mech_type = mech_type; + km->km_mech_name = name; + km->km_class = cls; + LIST_INSERT_HEAD(&kgss_mechs, km, km_link); +} + +void +kgss_uninstall_mech(gss_OID mech_type) +{ + struct kgss_mech *km; + + LIST_FOREACH(km, &kgss_mechs, km_link) { + if (kgss_oid_equal(km->km_mech_type, mech_type)) { + LIST_REMOVE(km, km_link); + free(km, M_GSSAPI); + return; + } + } +} + +gss_OID +kgss_find_mech_by_name(const char *name) +{ + struct kgss_mech *km; + + LIST_FOREACH(km, &kgss_mechs, km_link) { + if (!strcmp(km->km_mech_name, name)) { + return (km->km_mech_type); + } + } + return (GSS_C_NO_OID); +} + +const char * +kgss_find_mech_by_oid(const gss_OID oid) +{ + struct kgss_mech *km; + + LIST_FOREACH(km, &kgss_mechs, km_link) { + if (kgss_oid_equal(km->km_mech_type, oid)) { + return (km->km_mech_name); + } + } + return (NULL); +} + +gss_ctx_id_t +kgss_create_context(gss_OID mech_type) +{ + struct kgss_mech *km; + gss_ctx_id_t ctx; + + LIST_FOREACH(km, &kgss_mechs, km_link) { + if (kgss_oid_equal(km->km_mech_type, mech_type)) + break; + } + if (!km) + return (NULL); + + ctx = (gss_ctx_id_t) kobj_create(km->km_class, M_GSSAPI, M_WAITOK); + KGSS_INIT(ctx); + + return (ctx); +} + +void +kgss_delete_context(gss_ctx_id_t ctx, gss_buffer_t output_token) +{ + + KGSS_DELETE(ctx, output_token); + kobj_delete((kobj_t) ctx, M_GSSAPI); +} + +OM_uint32 +kgss_transfer_context(gss_ctx_id_t ctx) +{ + struct export_sec_context_res res; + struct export_sec_context_args args; + enum clnt_stat stat; + OM_uint32 maj_stat; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.ctx = ctx->handle; + bzero(&res, sizeof(res)); + stat = gssd_export_sec_context_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + return (GSS_S_FAILURE); + } + + maj_stat = KGSS_IMPORT(ctx, res.format, &res.interprocess_token); + ctx->handle = 0; + + xdr_free((xdrproc_t) xdr_export_sec_context_res, &res); + + return (maj_stat); +} + +void +kgss_copy_buffer(const gss_buffer_t from, gss_buffer_t to) +{ + to->length = from->length; + if (from->length) { + to->value = malloc(from->length, M_GSSAPI, M_WAITOK); + bcopy(from->value, to->value, from->length); + } else { + to->value = NULL; + } +} + +/* + * Kernel module glue + */ +static int +kgssapi_modevent(module_t mod, int type, void *data) +{ + + return (0); +} +static moduledata_t kgssapi_mod = { + "kgssapi", + kgssapi_modevent, + NULL, +}; +DECLARE_MODULE(kgssapi, kgssapi_mod, SI_SUB_VFS, SI_ORDER_ANY); +MODULE_DEPEND(kgssapi, krpc, 1, 1, 1); +MODULE_VERSION(kgssapi, 1); diff --git a/sys/kgssapi/gss_import_name.c b/sys/kgssapi/gss_import_name.c new file mode 100644 index 0000000..c8019c5 --- /dev/null +++ b/sys/kgssapi/gss_import_name.c @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_import_name(OM_uint32 *minor_status, + const gss_buffer_t input_name_buffer, + const gss_OID input_name_type, + gss_name_t *output_name) +{ + struct import_name_res res; + struct import_name_args args; + enum clnt_stat stat; + gss_name_t name; + + *minor_status = 0; + *output_name = GSS_C_NO_NAME; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.input_name_buffer = *input_name_buffer; + args.input_name_type = input_name_type; + + bzero(&res, sizeof(res)); + stat = gssd_import_name_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + name = malloc(sizeof(struct _gss_name_t), M_GSSAPI, M_WAITOK); + name->handle = res.output_name; + *minor_status = 0; + *output_name = name; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_init_sec_context.c b/sys/kgssapi/gss_init_sec_context.c new file mode 100644 index 0000000..0b7cee3 --- /dev/null +++ b/sys/kgssapi/gss_init_sec_context.c @@ -0,0 +1,135 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "gssd.h" +#include "kgss_if.h" + +OM_uint32 +gss_init_sec_context(OM_uint32 * minor_status, + const gss_cred_id_t initiator_cred_handle, + gss_ctx_id_t * context_handle, + const gss_name_t target_name, + const gss_OID input_mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + const gss_buffer_t input_token, + gss_OID * actual_mech_type, + gss_buffer_t output_token, + OM_uint32 * ret_flags, + OM_uint32 * time_rec) +{ + struct init_sec_context_res res; + struct init_sec_context_args args; + enum clnt_stat stat; + gss_ctx_id_t ctx = *context_handle; + + *minor_status = 0; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + args.uid = curthread->td_ucred->cr_uid; + if (initiator_cred_handle) + args.cred = initiator_cred_handle->handle; + else + args.cred = 0; + if (ctx) + args.ctx = ctx->handle; + else + args.ctx = 0; + args.name = target_name->handle; + args.mech_type = input_mech_type; + args.req_flags = req_flags; + args.time_req = time_req; + args.input_chan_bindings = input_chan_bindings; + if (input_token) + args.input_token = *input_token; + else { + args.input_token.length = 0; + args.input_token.value = NULL; + } + + bzero(&res, sizeof(res)); + stat = gssd_init_sec_context_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE + && res.major_status != GSS_S_CONTINUE_NEEDED) { + *minor_status = res.minor_status; + xdr_free((xdrproc_t) xdr_init_sec_context_res, &res); + return (res.major_status); + } + + *minor_status = res.minor_status; + + if (!ctx) { + ctx = kgss_create_context(res.actual_mech_type); + if (!ctx) { + xdr_free((xdrproc_t) xdr_init_sec_context_res, &res); + *minor_status = 0; + return (GSS_S_BAD_MECH); + } + } + *context_handle = ctx; + ctx->handle = res.ctx; + if (actual_mech_type) + *actual_mech_type = KGSS_MECH_TYPE(ctx); + kgss_copy_buffer(&res.output_token, output_token); + if (ret_flags) + *ret_flags = res.ret_flags; + if (time_rec) + *time_rec = res.time_rec; + + xdr_free((xdrproc_t) xdr_init_sec_context_res, &res); + + /* + * If the context establishment is complete, export it from + * userland and hand the result (which includes key material + * etc.) to the kernel implementation. + */ + if (res.major_status == GSS_S_COMPLETE) + res.major_status = kgss_transfer_context(ctx); + + return (res.major_status); +} diff --git a/sys/kgssapi/gss_names.c b/sys/kgssapi/gss_names.c new file mode 100644 index 0000000..a83693c --- /dev/null +++ b/sys/kgssapi/gss_names.c @@ -0,0 +1,176 @@ +/*- + * Copyright (c) 2005 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 +__FBSDID("$FreeBSD$"); + +#include +#include + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x01"}, + * corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant + * GSS_C_NT_USER_NAME should be initialized to point + * to that gss_OID_desc. + */ +static gss_OID_desc GSS_C_NT_USER_NAME_storage = + {10, (void *)(uintptr_t)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"}; +gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_storage; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x02"}, + * corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}. + * The constant GSS_C_NT_MACHINE_UID_NAME should be + * initialized to point to that gss_OID_desc. + */ +static gss_OID_desc GSS_C_NT_MACHINE_UID_NAME_storage = + {10, (void *)(uintptr_t)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}; +gss_OID GSS_C_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_storage; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x03"}, + * corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) string_uid_name(3)}. + * The constant GSS_C_NT_STRING_UID_NAME should be + * initialized to point to that gss_OID_desc. + */ +static gss_OID_desc GSS_C_NT_STRING_UID_NAME_storage = + {10, (void *)(uintptr_t)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"}; +gss_OID GSS_C_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_storage; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + * corresponding to an object-identifier value of + * {iso(1) org(3) dod(6) internet(1) security(5) + * nametypes(6) gss-host-based-services(2)). The constant + * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point + * to that gss_OID_desc. This is a deprecated OID value, and + * implementations wishing to support hostbased-service names + * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID, + * defined below, to identify such names; + * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym + * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input + * parameter, but should not be emitted by GSS-API + * implementations + */ +static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X_storage = + {6, (void *)(uintptr_t)"\x2b\x06\x01\x05\x06\x02"}; +gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &GSS_C_NT_HOSTBASED_SERVICE_X_storage; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x04"}, corresponding to an + * object-identifier value of {iso(1) member-body(2) + * Unites States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) service_name(4)}. The constant + * GSS_C_NT_HOSTBASED_SERVICE should be initialized + * to point to that gss_OID_desc. + */ +static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_storage = + {10, (void *)(uintptr_t)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}; +gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_storage; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + * corresponding to an object identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 3(gss-anonymous-name)}. The constant + * and GSS_C_NT_ANONYMOUS should be initialized to point + * to that gss_OID_desc. + */ +static gss_OID_desc GSS_C_NT_ANONYMOUS_storage = + {6, (void *)(uintptr_t)"\x2b\x06\01\x05\x06\x03"}; +gss_OID GSS_C_NT_ANONYMOUS = &GSS_C_NT_ANONYMOUS_storage; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, + * corresponding to an object-identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 4(gss-api-exported-name)}. The constant + * GSS_C_NT_EXPORT_NAME should be initialized to point + * to that gss_OID_desc. + */ +static gss_OID_desc GSS_C_NT_EXPORT_NAME_storage = + {6, (void *)(uintptr_t)"\x2b\x06\x01\x05\x06\x04"}; +gss_OID GSS_C_NT_EXPORT_NAME = &GSS_C_NT_EXPORT_NAME_storage; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * krb5(2) krb5_name(1)}. The recommended symbolic name for this type + * is "GSS_KRB5_NT_PRINCIPAL_NAME". + */ +static gss_OID_desc GSS_KRB5_NT_PRINCIPAL_NAME_storage = + {10, (void *)(uintptr_t)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01"}; +gss_OID GSS_KRB5_NT_PRINCIPAL_NAME = &GSS_KRB5_NT_PRINCIPAL_NAME_storage; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) user_name(1)}. The recommended symbolic name for this + * type is "GSS_KRB5_NT_USER_NAME". + */ +gss_OID GSS_KRB5_NT_USER_NAME = &GSS_C_NT_USER_NAME_storage; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) machine_uid_name(2)}. The recommended symbolic name for + * this type is "GSS_KRB5_NT_MACHINE_UID_NAME". + */ +gss_OID GSS_KRB5_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_storage; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) string_uid_name(3)}. The recommended symbolic name for + * this type is "GSS_KRB5_NT_STRING_UID_NAME". + */ +gss_OID GSS_KRB5_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_storage; + + diff --git a/sys/kgssapi/gss_pname_to_uid.c b/sys/kgssapi/gss_pname_to_uid.c new file mode 100644 index 0000000..b83fd73 --- /dev/null +++ b/sys/kgssapi/gss_pname_to_uid.c @@ -0,0 +1,122 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "kgss_if.h" + +OM_uint32 +gss_pname_to_uid(OM_uint32 *minor_status, const gss_name_t pname, + const gss_OID mech, uid_t *uidp) +{ + struct pname_to_uid_res res; + struct pname_to_uid_args args; + enum clnt_stat stat; + + *minor_status = 0; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (pname == GSS_C_NO_NAME) + return (GSS_S_BAD_NAME); + + args.pname = pname->handle; + args.mech = mech; + + bzero(&res, sizeof(res)); + stat = gssd_pname_to_uid_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + *uidp = res.uid; + return (GSS_S_COMPLETE); +} + +OM_uint32 +gss_pname_to_unix_cred(OM_uint32 *minor_status, const gss_name_t pname, + const gss_OID mech, uid_t *uidp, gid_t *gidp, + int *numgroups, gid_t *groups) + +{ + struct pname_to_uid_res res; + struct pname_to_uid_args args; + enum clnt_stat stat; + int i, n; + + *minor_status = 0; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (pname == GSS_C_NO_NAME) + return (GSS_S_BAD_NAME); + + args.pname = pname->handle; + args.mech = mech; + + bzero(&res, sizeof(res)); + stat = gssd_pname_to_uid_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + *uidp = res.uid; + *gidp = res.gid; + n = res.gidlist.gidlist_len; + if (n > *numgroups) + n = *numgroups; + for (i = 0; i < n; i++) + groups[i] = res.gidlist.gidlist_val[i]; + *numgroups = n; + + xdr_free((xdrproc_t) xdr_pname_to_uid_res, &res); + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_release_buffer.c b/sys/kgssapi/gss_release_buffer.c new file mode 100644 index 0000000..ea5efc9 --- /dev/null +++ b/sys/kgssapi/gss_release_buffer.c @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +OM_uint32 +gss_release_buffer(OM_uint32 *minor_status, gss_buffer_t buffer) +{ + + *minor_status = 0; + if (buffer->value) { + free(buffer->value, M_GSSAPI); + } + buffer->length = 0; + buffer->value = NULL; + + return (GSS_S_COMPLETE); +} + diff --git a/sys/kgssapi/gss_release_cred.c b/sys/kgssapi/gss_release_cred.c new file mode 100644 index 0000000..6c68496 --- /dev/null +++ b/sys/kgssapi/gss_release_cred.c @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_release_cred(OM_uint32 *minor_status, gss_cred_id_t *cred_handle) +{ + struct release_cred_res res; + struct release_cred_args args; + enum clnt_stat stat; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (*cred_handle) { + args.cred = (*cred_handle)->handle; + stat = gssd_release_cred_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + free((*cred_handle), M_GSSAPI); + *cred_handle = NULL; + + *minor_status = res.minor_status; + return (res.major_status); + } + + *minor_status = 0; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_release_name.c b/sys/kgssapi/gss_release_name.c new file mode 100644 index 0000000..6f27e74 --- /dev/null +++ b/sys/kgssapi/gss_release_name.c @@ -0,0 +1,74 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_release_name(OM_uint32 *minor_status, gss_name_t *input_name) +{ + struct release_name_res res; + struct release_name_args args; + enum clnt_stat stat; + gss_name_t name; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (*input_name) { + name = *input_name; + args.input_name = name->handle; + + stat = gssd_release_name_1(&args, &res, kgss_gssd_handle); + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + free(name, M_GSSAPI); + *input_name = NULL; + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + } + + *minor_status = 0; + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_release_oid_set.c b/sys/kgssapi/gss_release_oid_set.c new file mode 100644 index 0000000..34b802a --- /dev/null +++ b/sys/kgssapi/gss_release_oid_set.c @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +OM_uint32 +gss_release_oid_set(OM_uint32 *minor_status, + gss_OID_set *set) +{ + + *minor_status = 0; + if (set && *set) { + if ((*set)->elements) + free((*set)->elements, M_GSSAPI); + free(*set, M_GSSAPI); + *set = GSS_C_NO_OID_SET; + } + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_set_cred_option.c b/sys/kgssapi/gss_set_cred_option.c new file mode 100644 index 0000000..ce781af --- /dev/null +++ b/sys/kgssapi/gss_set_cred_option.c @@ -0,0 +1,77 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "gssd.h" + +OM_uint32 +gss_set_cred_option(OM_uint32 *minor_status, + gss_cred_id_t *cred, + const gss_OID option_name, + const gss_buffer_t option_value) +{ + struct set_cred_option_res res; + struct set_cred_option_args args; + enum clnt_stat stat; + + *minor_status = 0; + + if (!kgss_gssd_handle) + return (GSS_S_FAILURE); + + if (cred) + args.cred = (*cred)->handle; + else + args.cred = 0; + args.option_name = option_name; + args.option_value = *option_value; + + bzero(&res, sizeof(res)); + stat = gssd_set_cred_option_1(&args, &res, kgss_gssd_handle); + + if (stat != RPC_SUCCESS) { + *minor_status = stat; + return (GSS_S_FAILURE); + } + + if (res.major_status != GSS_S_COMPLETE) { + *minor_status = res.minor_status; + return (res.major_status); + } + + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_test_oid_set_member.c b/sys/kgssapi/gss_test_oid_set_member.c new file mode 100644 index 0000000..9642478 --- /dev/null +++ b/sys/kgssapi/gss_test_oid_set_member.c @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +OM_uint32 +gss_test_oid_set_member(OM_uint32 *minor_status, + const gss_OID member, + const gss_OID_set set, + int *present) +{ + size_t i; + + *present = 0; + for (i = 0; i < set->count; i++) + if (kgss_oid_equal(member, &set->elements[i])) + *present = 1; + + *minor_status = 0; + return (GSS_S_COMPLETE); +} diff --git a/sys/kgssapi/gss_unwrap.c b/sys/kgssapi/gss_unwrap.c new file mode 100644 index 0000000..3b6d614 --- /dev/null +++ b/sys/kgssapi/gss_unwrap.c @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include + +#include "kgss_if.h" + +OM_uint32 +gss_unwrap(OM_uint32 *minor_status, + const gss_ctx_id_t ctx, + const gss_buffer_t input_message_buffer, + gss_buffer_t output_message_buffer, + int *conf_state, + gss_qop_t *qop_state) +{ + OM_uint32 maj_stat; + struct mbuf *m; + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + MGET(m, M_WAITOK, MT_DATA); + if (input_message_buffer->length > MLEN) + MCLGET(m, M_WAITOK); + m_append(m, input_message_buffer->length, input_message_buffer->value); + + maj_stat = KGSS_UNWRAP(ctx, minor_status, &m, conf_state, qop_state); + + /* + * On success, m is the wrapped message, on failure, m is + * freed. + */ + if (maj_stat == GSS_S_COMPLETE) { + output_message_buffer->length = m_length(m, NULL); + output_message_buffer->value = + malloc(output_message_buffer->length, + M_GSSAPI, M_WAITOK); + m_copydata(m, 0, output_message_buffer->length, + output_message_buffer->value); + m_freem(m); + } + + return (maj_stat); +} + +OM_uint32 +gss_unwrap_mbuf(OM_uint32 *minor_status, + const gss_ctx_id_t ctx, + struct mbuf **mp, + int *conf_state, + gss_qop_t *qop_state) +{ + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + return (KGSS_UNWRAP(ctx, minor_status, mp, conf_state, qop_state)); +} + diff --git a/sys/kgssapi/gss_verify_mic.c b/sys/kgssapi/gss_verify_mic.c new file mode 100644 index 0000000..0a8e7c4 --- /dev/null +++ b/sys/kgssapi/gss_verify_mic.c @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include + +#include "kgss_if.h" + +OM_uint32 +gss_verify_mic(OM_uint32 *minor_status, + const gss_ctx_id_t ctx, + const gss_buffer_t message_buffer, + const gss_buffer_t token_buffer, + gss_qop_t *qop_state) +{ + OM_uint32 maj_stat; + struct mbuf *m, *mic; + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + MGET(m, M_WAITOK, MT_DATA); + if (message_buffer->length > MLEN) + MCLGET(m, M_WAITOK); + m_append(m, message_buffer->length, message_buffer->value); + + MGET(mic, M_WAITOK, MT_DATA); + if (token_buffer->length > MLEN) + MCLGET(mic, M_WAITOK); + m_append(mic, token_buffer->length, token_buffer->value); + + maj_stat = KGSS_VERIFY_MIC(ctx, minor_status, m, mic, qop_state); + + m_freem(m); + m_freem(mic); + + return (maj_stat); +} + +OM_uint32 +gss_verify_mic_mbuf(OM_uint32 *minor_status, const gss_ctx_id_t ctx, + struct mbuf *m, struct mbuf *mic, gss_qop_t *qop_state) +{ + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + return (KGSS_VERIFY_MIC(ctx, minor_status, m, mic, qop_state)); +} + diff --git a/sys/kgssapi/gss_wrap.c b/sys/kgssapi/gss_wrap.c new file mode 100644 index 0000000..99bf686 --- /dev/null +++ b/sys/kgssapi/gss_wrap.c @@ -0,0 +1,96 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include + +#include "kgss_if.h" + +OM_uint32 +gss_wrap(OM_uint32 *minor_status, + const gss_ctx_id_t ctx, + int conf_req_flag, + gss_qop_t qop_req, + const gss_buffer_t input_message_buffer, + int *conf_state, + gss_buffer_t output_message_buffer) +{ + OM_uint32 maj_stat; + struct mbuf *m; + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + MGET(m, M_WAITOK, MT_DATA); + if (input_message_buffer->length > MLEN) + MCLGET(m, M_WAITOK); + m_append(m, input_message_buffer->length, input_message_buffer->value); + + maj_stat = KGSS_WRAP(ctx, minor_status, conf_req_flag, qop_req, + &m, conf_state); + + /* + * On success, m is the wrapped message, on failure, m is + * freed. + */ + if (maj_stat == GSS_S_COMPLETE) { + output_message_buffer->length = m_length(m, NULL); + output_message_buffer->value = + malloc(output_message_buffer->length, + M_GSSAPI, M_WAITOK); + m_copydata(m, 0, output_message_buffer->length, + output_message_buffer->value); + m_freem(m); + } + + return (maj_stat); +} + +OM_uint32 +gss_wrap_mbuf(OM_uint32 *minor_status, const gss_ctx_id_t ctx, + int conf_req_flag, gss_qop_t qop_req, struct mbuf **mp, int *conf_state) +{ + + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + return (KGSS_WRAP(ctx, minor_status, conf_req_flag, qop_req, + mp, conf_state)); +} diff --git a/sys/kgssapi/gss_wrap_size_limit.c b/sys/kgssapi/gss_wrap_size_limit.c new file mode 100644 index 0000000..17bedb2 --- /dev/null +++ b/sys/kgssapi/gss_wrap_size_limit.c @@ -0,0 +1,56 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "kgss_if.h" + +OM_uint32 +gss_wrap_size_limit(OM_uint32 *minor_status, + const gss_ctx_id_t ctx, + int conf_req_flag, + gss_qop_t qop_req, + OM_uint32 req_output_size, + OM_uint32 *max_input_size) +{ + if (!ctx) { + *minor_status = 0; + return (GSS_S_NO_CONTEXT); + } + + return (KGSS_WRAP_SIZE_LIMIT(ctx, minor_status, conf_req_flag, + qop_req, req_output_size, max_input_size)); +} diff --git a/sys/kgssapi/gssapi.h b/sys/kgssapi/gssapi.h new file mode 100644 index 0000000..c8f86c6 --- /dev/null +++ b/sys/kgssapi/gssapi.h @@ -0,0 +1,620 @@ +/* + * Copyright (C) The Internet Society (2000). All Rights Reserved. + * + * This document and translations of it may be copied and furnished to + * others, and derivative works that comment on or otherwise explain it + * or assist in its implementation may be prepared, copied, published + * and distributed, in whole or in part, without restriction of any + * kind, provided that the above copyright notice and this paragraph are + * included on all such copies and derivative works. However, this + * document itself may not be modified in any way, such as by removing + * the copyright notice or references to the Internet Society or other + * Internet organizations, except as needed for the purpose of + * developing Internet standards in which case the procedures for + * copyrights defined in the Internet Standards process must be + * followed, or as required to translate it into languages other than + * English. + * + * The limited permissions granted above are perpetual and will not be + * revoked by the Internet Society or its successors or assigns. + * + * This document and the information contained herein is provided on an + * "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + * TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + * HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * $FreeBSD$ + */ + +#ifndef _KGSSAPI_GSSAPI_H_ +#define _KGSSAPI_GSSAPI_H_ + +/* + * A cut-down version of the GSS-API for in-kernel use + */ + +/* + * Now define the three implementation-dependent types. + */ +typedef struct _gss_ctx_id_t *gss_ctx_id_t; +typedef struct _gss_cred_id_t *gss_cred_id_t; +typedef struct _gss_name_t *gss_name_t; + +/* + * We can't use X/Open definitions, so roll our own. + */ +typedef uint32_t OM_uint32; +typedef uint64_t OM_uint64; + +typedef struct gss_OID_desc_struct { + OM_uint32 length; + void *elements; +} gss_OID_desc, *gss_OID; + +typedef struct gss_OID_set_desc_struct { + size_t count; + gss_OID elements; +} gss_OID_set_desc, *gss_OID_set; + +typedef struct gss_buffer_desc_struct { + size_t length; + void *value; +} gss_buffer_desc, *gss_buffer_t; + +typedef struct gss_channel_bindings_struct { + OM_uint32 initiator_addrtype; + gss_buffer_desc initiator_address; + OM_uint32 acceptor_addrtype; + gss_buffer_desc acceptor_address; + gss_buffer_desc application_data; +} *gss_channel_bindings_t; + +/* + * For now, define a QOP-type as an OM_uint32 + */ +typedef OM_uint32 gss_qop_t; + +typedef int gss_cred_usage_t; + +/* + * Flag bits for context-level services. + */ +#define GSS_C_DELEG_FLAG 1 +#define GSS_C_MUTUAL_FLAG 2 +#define GSS_C_REPLAY_FLAG 4 +#define GSS_C_SEQUENCE_FLAG 8 +#define GSS_C_CONF_FLAG 16 +#define GSS_C_INTEG_FLAG 32 +#define GSS_C_ANON_FLAG 64 +#define GSS_C_PROT_READY_FLAG 128 +#define GSS_C_TRANS_FLAG 256 + +/* + * Credential usage options + */ +#define GSS_C_BOTH 0 +#define GSS_C_INITIATE 1 +#define GSS_C_ACCEPT 2 + +/* + * Status code types for gss_display_status + */ +#define GSS_C_GSS_CODE 1 +#define GSS_C_MECH_CODE 2 + +/* + * The constant definitions for channel-bindings address families + */ +#define GSS_C_AF_UNSPEC 0 +#define GSS_C_AF_LOCAL 1 +#define GSS_C_AF_INET 2 +#define GSS_C_AF_IMPLINK 3 +#define GSS_C_AF_PUP 4 +#define GSS_C_AF_CHAOS 5 +#define GSS_C_AF_NS 6 +#define GSS_C_AF_NBS 7 +#define GSS_C_AF_ECMA 8 +#define GSS_C_AF_DATAKIT 9 +#define GSS_C_AF_CCITT 10 +#define GSS_C_AF_SNA 11 +#define GSS_C_AF_DECnet 12 +#define GSS_C_AF_DLI 13 +#define GSS_C_AF_LAT 14 +#define GSS_C_AF_HYLINK 15 +#define GSS_C_AF_APPLETALK 16 +#define GSS_C_AF_BSC 17 +#define GSS_C_AF_DSS 18 +#define GSS_C_AF_OSI 19 +#define GSS_C_AF_X25 21 +#define GSS_C_AF_NULLADDR 255 + +/* + * Various Null values + */ +#define GSS_C_NO_NAME ((gss_name_t) 0) +#define GSS_C_NO_BUFFER ((gss_buffer_t) 0) +#define GSS_C_NO_OID ((gss_OID) 0) +#define GSS_C_NO_OID_SET ((gss_OID_set) 0) +#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0) +#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0) +#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0) +#define GSS_C_EMPTY_BUFFER {0, NULL} + +/* + * Some alternate names for a couple of the above + * values. These are defined for V1 compatibility. + */ +#define GSS_C_NULL_OID GSS_C_NO_OID +#define GSS_C_NULL_OID_SET GSS_C_NO_OID_SET + +/* + * Define the default Quality of Protection for per-message + * services. Note that an implementation that offers multiple + * levels of QOP may define GSS_C_QOP_DEFAULT to be either zero + * (as done here) to mean "default protection", or to a specific + * explicit QOP value. However, a value of 0 should always be + * interpreted by a GSS-API implementation as a request for the + * default protection level. + */ +#define GSS_C_QOP_DEFAULT 0 + +/* + * Expiration time of 2^32-1 seconds means infinite lifetime for a + * credential or security context + */ +#define GSS_C_INDEFINITE 0xfffffffful + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x01"}, + * corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant + * GSS_C_NT_USER_NAME should be initialized to point + * to that gss_OID_desc. + */ +extern gss_OID GSS_C_NT_USER_NAME; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x02"}, + * corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}. + * The constant GSS_C_NT_MACHINE_UID_NAME should be + * initialized to point to that gss_OID_desc. + */ +extern gss_OID GSS_C_NT_MACHINE_UID_NAME; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x03"}, + * corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) string_uid_name(3)}. + * The constant GSS_C_NT_STRING_UID_NAME should be + * initialized to point to that gss_OID_desc. + */ +extern gss_OID GSS_C_NT_STRING_UID_NAME; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + * corresponding to an object-identifier value of + * {iso(1) org(3) dod(6) internet(1) security(5) + * nametypes(6) gss-host-based-services(2)). The constant + * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point + * to that gss_OID_desc. This is a deprecated OID value, and + * implementations wishing to support hostbased-service names + * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID, + * defined below, to identify such names; + * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym + * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input + * parameter, but should not be emitted by GSS-API + * implementations + */ +extern gss_OID GSS_C_NT_HOSTBASED_SERVICE_X; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {10, (void *)"\x2a\x86\x48\x86\xf7\x12" + * "\x01\x02\x01\x04"}, corresponding to an + * object-identifier value of {iso(1) member-body(2) + * Unites States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) service_name(4)}. The constant + * GSS_C_NT_HOSTBASED_SERVICE should be initialized + * to point to that gss_OID_desc. + */ +extern gss_OID GSS_C_NT_HOSTBASED_SERVICE; + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + * corresponding to an object identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 3(gss-anonymous-name)}. The constant + * and GSS_C_NT_ANONYMOUS should be initialized to point + * to that gss_OID_desc. + */ +extern gss_OID GSS_C_NT_ANONYMOUS; + + +/* + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value + * {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, + * corresponding to an object-identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 4(gss-api-exported-name)}. The constant + * GSS_C_NT_EXPORT_NAME should be initialized to point + * to that gss_OID_desc. + */ +extern gss_OID GSS_C_NT_EXPORT_NAME; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * krb5(2) krb5_name(1)}. The recommended symbolic name for this type + * is "GSS_KRB5_NT_PRINCIPAL_NAME". + */ +extern gss_OID GSS_KRB5_NT_PRINCIPAL_NAME; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) user_name(1)}. The recommended symbolic name for this + * type is "GSS_KRB5_NT_USER_NAME". + */ +extern gss_OID GSS_KRB5_NT_USER_NAME; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) machine_uid_name(2)}. The recommended symbolic name for + * this type is "GSS_KRB5_NT_MACHINE_UID_NAME". + */ +extern gss_OID GSS_KRB5_NT_MACHINE_UID_NAME; + +/* + * This name form shall be represented by the Object Identifier {iso(1) + * member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) + * generic(1) string_uid_name(3)}. The recommended symbolic name for + * this type is "GSS_KRB5_NT_STRING_UID_NAME". + */ +extern gss_OID GSS_KRB5_NT_STRING_UID_NAME; + +/* Major status codes */ + +#define GSS_S_COMPLETE 0 + +/* + * Some "helper" definitions to make the status code macros obvious. + */ +#define GSS_C_CALLING_ERROR_OFFSET 24 +#define GSS_C_ROUTINE_ERROR_OFFSET 16 +#define GSS_C_SUPPLEMENTARY_OFFSET 0 +#define GSS_C_CALLING_ERROR_MASK 0377ul +#define GSS_C_ROUTINE_ERROR_MASK 0377ul +#define GSS_C_SUPPLEMENTARY_MASK 0177777ul + +/* + * The macros that test status codes for error conditions. + * Note that the GSS_ERROR() macro has changed slightly from + * the V1 GSS-API so that it now evaluates its argument + * only once. + */ +#define GSS_CALLING_ERROR(x) \ + (x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET)) +#define GSS_ROUTINE_ERROR(x) \ + (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)) +#define GSS_SUPPLEMENTARY_INFO(x) \ + (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET)) +#define GSS_ERROR(x) \ + (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \ + (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))) + +/* + * Now the actual status code definitions + */ + +/* + * Calling errors: + */ +#define GSS_S_CALL_INACCESSIBLE_READ \ +(1ul << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_INACCESSIBLE_WRITE \ +(2ul << GSS_C_CALLING_ERROR_OFFSET) +#define GSS_S_CALL_BAD_STRUCTURE \ +(3ul << GSS_C_CALLING_ERROR_OFFSET) + +/* + * Routine errors: + */ +#define GSS_S_BAD_MECH (1ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAME (2ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_NAMETYPE (3ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_BINDINGS (4ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_STATUS (5ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_SIG (6ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_MIC GSS_S_BAD_SIG +#define GSS_S_NO_CRED (7ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NO_CONTEXT (8ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_TOKEN (9ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CREDENTIALS_EXPIRED (11ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_CONTEXT_EXPIRED (12ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_FAILURE (13ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_BAD_QOP (14ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAUTHORIZED (15ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_UNAVAILABLE (16ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_DUPLICATE_ELEMENT (17ul << GSS_C_ROUTINE_ERROR_OFFSET) +#define GSS_S_NAME_NOT_MN (18ul << GSS_C_ROUTINE_ERROR_OFFSET) + +/* + * Supplementary info bits: + */ +#define GSS_S_CONTINUE_NEEDED \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0)) +#define GSS_S_DUPLICATE_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1)) +#define GSS_S_OLD_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2)) +#define GSS_S_UNSEQ_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3)) +#define GSS_S_GAP_TOKEN \ + (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4)) + +__BEGIN_DECLS + +/* + * Finally, function prototypes for the GSS-API routines. + */ +OM_uint32 gss_acquire_cred + (OM_uint32 *, /* minor_status */ + const gss_name_t, /* desired_name */ + OM_uint32, /* time_req */ + const gss_OID_set, /* desired_mechs */ + gss_cred_usage_t, /* cred_usage */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 * /* time_rec */ + ); + +OM_uint32 gss_release_cred + (OM_uint32 *, /* minor_status */ + gss_cred_id_t * /* cred_handle */ + ); + +OM_uint32 gss_init_sec_context + (OM_uint32 *, /* minor_status */ + const gss_cred_id_t, /* initiator_cred_handle */ + gss_ctx_id_t *, /* context_handle */ + const gss_name_t, /* target_name */ + const gss_OID, /* mech_type */ + OM_uint32, /* req_flags */ + OM_uint32, /* time_req */ + const gss_channel_bindings_t, + /* input_chan_bindings */ + const gss_buffer_t, /* input_token */ + gss_OID *, /* actual_mech_type */ + gss_buffer_t, /* output_token */ + OM_uint32 *, /* ret_flags */ + OM_uint32 * /* time_rec */ + ); + +OM_uint32 gss_accept_sec_context + (OM_uint32 *, /* minor_status */ + gss_ctx_id_t *, /* context_handle */ + const gss_cred_id_t, /* acceptor_cred_handle */ + const gss_buffer_t, /* input_token_buffer */ + const gss_channel_bindings_t, + /* input_chan_bindings */ + gss_name_t *, /* src_name */ + gss_OID *, /* mech_type */ + gss_buffer_t, /* output_token */ + OM_uint32 *, /* ret_flags */ + OM_uint32 *, /* time_rec */ + gss_cred_id_t * /* delegated_cred_handle */ + ); + +OM_uint32 gss_delete_sec_context + (OM_uint32 *, /* minor_status */ + gss_ctx_id_t *, /* context_handle */ + gss_buffer_t /* output_token */ + ); + +OM_uint32 gss_get_mic + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + gss_qop_t, /* qop_req */ + const gss_buffer_t, /* message_buffer */ + gss_buffer_t /* message_token */ + ); + +OM_uint32 gss_verify_mic + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + const gss_buffer_t, /* message_buffer */ + const gss_buffer_t, /* token_buffer */ + gss_qop_t * /* qop_state */ + ); + +OM_uint32 gss_wrap + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + int, /* conf_req_flag */ + gss_qop_t, /* qop_req */ + const gss_buffer_t, /* input_message_buffer */ + int *, /* conf_state */ + gss_buffer_t /* output_message_buffer */ + ); + +OM_uint32 gss_unwrap + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + const gss_buffer_t, /* input_message_buffer */ + gss_buffer_t, /* output_message_buffer */ + int *, /* conf_state */ + gss_qop_t * /* qop_state */ + ); + +OM_uint32 gss_display_status + (OM_uint32 *, /* minor_status */ + OM_uint32, /* status_value */ + int, /* status_type */ + const gss_OID, /* mech_type */ + OM_uint32 *, /* message_context */ + gss_buffer_t /* status_string */ + ); + +OM_uint32 gss_import_name + (OM_uint32 *, /* minor_status */ + const gss_buffer_t, /* input_name_buffer */ + const gss_OID, /* input_name_type */ + gss_name_t * /* output_name */ + ); + +OM_uint32 gss_export_name + (OM_uint32 *, /* minor_status */ + const gss_name_t, /* input_name */ + gss_buffer_t /* exported_name */ + ); + +OM_uint32 gss_release_name + (OM_uint32 *, /* minor_status */ + gss_name_t * /* input_name */ + ); + +OM_uint32 gss_release_buffer + (OM_uint32 *, /* minor_status */ + gss_buffer_t /* buffer */ + ); + +OM_uint32 gss_release_oid_set + (OM_uint32 *, /* minor_status */ + gss_OID_set * /* set */ + ); + +OM_uint32 gss_wrap_size_limit ( + OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + int, /* conf_req_flag */ + gss_qop_t, /* qop_req */ + OM_uint32, /* req_output_size */ + OM_uint32 * /* max_input_size */ + ); + +OM_uint32 gss_create_empty_oid_set ( + OM_uint32 *, /* minor_status */ + gss_OID_set * /* oid_set */ + ); + +OM_uint32 gss_add_oid_set_member ( + OM_uint32 *, /* minor_status */ + const gss_OID, /* member_oid */ + gss_OID_set * /* oid_set */ + ); + +OM_uint32 gss_test_oid_set_member ( + OM_uint32 *, /* minor_status */ + const gss_OID, /* member */ + const gss_OID_set, /* set */ + int * /* present */ + ); + +OM_uint32 gss_canonicalize_name ( + OM_uint32 *, /* minor_status */ + const gss_name_t, /* input_name */ + const gss_OID, /* mech_type */ + gss_name_t * /* output_name */ + ); + +/* + * Other extensions and helper functions. + */ + +OM_uint32 gss_set_cred_option + (OM_uint32 *, /* minor status */ + gss_cred_id_t *, /* cred */ + const gss_OID, /* option to set */ + const gss_buffer_t /* option value */ + ); + +OM_uint32 gss_pname_to_uid + (OM_uint32 *, /* minor status */ + const gss_name_t pname, /* principal name */ + const gss_OID mech, /* mechanism to query */ + uid_t *uidp /* pointer to UID for result */ + ); + +/* + * On entry, *numgroups is set to the maximum number of groups to return. On exit, *numgroups is set to the actual number of groups returned. + */ +OM_uint32 gss_pname_to_unix_cred + (OM_uint32 *, /* minor status */ + const gss_name_t pname, /* principal name */ + const gss_OID mech, /* mechanism to query */ + uid_t *uidp, /* pointer to UID for result */ + gid_t *gidp, /* pointer to GID for result */ + int *numgroups, /* number of groups */ + gid_t *groups /* pointer to group list */ + ); + +/* + * Mbuf oriented message signing and encryption. + * + * Get_mic allocates an mbuf to hold the message checksum. Verify_mic + * may modify the passed-in mic but will not free it. + * + * Wrap and unwrap + * consume the message and generate a new mbuf chain with the + * result. The original message is freed on error. + */ +struct mbuf; +OM_uint32 gss_get_mic_mbuf + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + gss_qop_t, /* qop_req */ + struct mbuf *, /* message_buffer */ + struct mbuf ** /* message_token */ + ); + +OM_uint32 gss_verify_mic_mbuf + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + struct mbuf *, /* message_buffer */ + struct mbuf *, /* token_buffer */ + gss_qop_t * /* qop_state */ + ); + +OM_uint32 gss_wrap_mbuf + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + int, /* conf_req_flag */ + gss_qop_t, /* qop_req */ + struct mbuf **, /* message_buffer */ + int * /* conf_state */ + ); + +OM_uint32 gss_unwrap_mbuf + (OM_uint32 *, /* minor_status */ + const gss_ctx_id_t, /* context_handle */ + struct mbuf **, /* message_buffer */ + int *, /* conf_state */ + gss_qop_t * /* qop_state */ + ); + +__END_DECLS + +#endif /* _KGSSAPI_GSSAPI_H_ */ diff --git a/sys/kgssapi/gssapi_impl.h b/sys/kgssapi/gssapi_impl.h new file mode 100644 index 0000000..629b80b --- /dev/null +++ b/sys/kgssapi/gssapi_impl.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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$ + */ + +#include "gssd.h" + +MALLOC_DECLARE(M_GSSAPI); + +struct _gss_ctx_id_t { + KOBJ_FIELDS; + gssd_ctx_id_t handle; +}; + +struct _gss_cred_id_t { + gssd_cred_id_t handle; +}; + +struct _gss_name_t { + gssd_name_t handle; +}; + +struct kgss_mech { + LIST_ENTRY(kgss_mech) km_link; + gss_OID km_mech_type; + const char *km_mech_name; + struct kobj_class *km_class; +}; +LIST_HEAD(kgss_mech_list, kgss_mech); + +extern CLIENT *kgss_gssd_handle; +extern struct kgss_mech_list kgss_mechs; + +int kgss_oid_equal(const gss_OID oid1, const gss_OID oid2); +extern void kgss_install_mech(gss_OID mech_type, const char *name, + struct kobj_class *cls); +extern void kgss_uninstall_mech(gss_OID mech_type); +extern gss_OID kgss_find_mech_by_name(const char *name); +extern const char *kgss_find_mech_by_oid(const gss_OID oid); +extern gss_ctx_id_t kgss_create_context(gss_OID mech_type); +extern void kgss_delete_context(gss_ctx_id_t ctx, gss_buffer_t output_token); +extern OM_uint32 kgss_transfer_context(gss_ctx_id_t ctx); +extern void kgss_copy_buffer(const gss_buffer_t from, gss_buffer_t to); diff --git a/sys/kgssapi/gssd.x b/sys/kgssapi/gssd.x new file mode 100644 index 0000000..f5682a0 --- /dev/null +++ b/sys/kgssapi/gssd.x @@ -0,0 +1,265 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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$ */ + +#ifdef RPC_HDR + +%#ifdef _KERNEL +%#include +%#else +%#include +%#endif + +%extern bool_t xdr_gss_buffer_desc(XDR *xdrs, gss_buffer_desc *buf); +%extern bool_t xdr_gss_OID_desc(XDR *xdrs, gss_OID_desc *oid); +%extern bool_t xdr_gss_OID(XDR *xdrs, gss_OID *oidp); +%extern bool_t xdr_gss_OID_set_desc(XDR *xdrs, gss_OID_set_desc *set); +%extern bool_t xdr_gss_OID_set(XDR *xdrs, gss_OID_set *setp); +%extern bool_t xdr_gss_channel_bindings_t(XDR *xdrs, gss_channel_bindings_t *chp); + +#endif + +typedef uint64_t gssd_ctx_id_t; +typedef uint64_t gssd_cred_id_t; +typedef uint64_t gssd_name_t; + +struct init_sec_context_res { + uint32_t major_status; + uint32_t minor_status; + gssd_ctx_id_t ctx; + gss_OID actual_mech_type; + gss_buffer_desc output_token; + uint32_t ret_flags; + uint32_t time_rec; +}; + +struct init_sec_context_args { + uint32_t uid; + gssd_cred_id_t cred; + gssd_ctx_id_t ctx; + gssd_name_t name; + gss_OID mech_type; + uint32_t req_flags; + uint32_t time_req; + gss_channel_bindings_t input_chan_bindings; + gss_buffer_desc input_token; +}; + +struct accept_sec_context_res { + uint32_t major_status; + uint32_t minor_status; + gssd_ctx_id_t ctx; + gssd_name_t src_name; + gss_OID mech_type; + gss_buffer_desc output_token; + uint32_t ret_flags; + uint32_t time_rec; + gssd_cred_id_t delegated_cred_handle; +}; + +struct accept_sec_context_args { + gssd_ctx_id_t ctx; + gssd_cred_id_t cred; + gss_buffer_desc input_token; + gss_channel_bindings_t input_chan_bindings; +}; + +struct delete_sec_context_res { + uint32_t major_status; + uint32_t minor_status; + gss_buffer_desc output_token; +}; + +struct delete_sec_context_args { + gssd_ctx_id_t ctx; +}; + +enum sec_context_format { + KGSS_HEIMDAL_0_6, + KGSS_HEIMDAL_1_1 +}; + +struct export_sec_context_res { + uint32_t major_status; + uint32_t minor_status; + enum sec_context_format format; + gss_buffer_desc interprocess_token; +}; + +struct export_sec_context_args { + gssd_ctx_id_t ctx; +}; + +struct import_name_res { + uint32_t major_status; + uint32_t minor_status; + gssd_name_t output_name; +}; + +struct import_name_args { + gss_buffer_desc input_name_buffer; + gss_OID input_name_type; +}; + +struct canonicalize_name_res { + uint32_t major_status; + uint32_t minor_status; + gssd_name_t output_name; +}; + +struct canonicalize_name_args { + gssd_name_t input_name; + gss_OID mech_type; +}; + +struct export_name_res { + uint32_t major_status; + uint32_t minor_status; + gss_buffer_desc exported_name; +}; + +struct export_name_args { + gssd_name_t input_name; +}; + +struct release_name_res { + uint32_t major_status; + uint32_t minor_status; +}; + +struct release_name_args { + gssd_name_t input_name; +}; + +struct pname_to_uid_res { + uint32_t major_status; + uint32_t minor_status; + uint32_t uid; + uint32_t gid; + uint32_t gidlist<>; +}; + +struct pname_to_uid_args { + gssd_name_t pname; + gss_OID mech; +}; + +struct acquire_cred_res { + uint32_t major_status; + uint32_t minor_status; + gssd_cred_id_t output_cred; + gss_OID_set actual_mechs; + uint32_t time_rec; +}; + +struct acquire_cred_args { + uint32_t uid; + gssd_name_t desired_name; + uint32_t time_req; + gss_OID_set desired_mechs; + int cred_usage; +}; + +struct set_cred_option_res { + uint32_t major_status; + uint32_t minor_status; +}; + +struct set_cred_option_args { + gssd_cred_id_t cred; + gss_OID option_name; + gss_buffer_desc option_value; +}; + +struct release_cred_res { + uint32_t major_status; + uint32_t minor_status; +}; + +struct release_cred_args { + gssd_cred_id_t cred; +}; + +struct display_status_res { + uint32_t major_status; + uint32_t minor_status; + uint32_t message_context; + gss_buffer_desc status_string; +}; + +struct display_status_args { + uint32_t status_value; + int status_type; + gss_OID mech_type; + uint32_t message_context; +}; + +program GSSD { + version GSSDVERS { + void GSSD_NULL(void) = 0; + + init_sec_context_res + GSSD_INIT_SEC_CONTEXT(init_sec_context_args) = 1; + + accept_sec_context_res + GSSD_ACCEPT_SEC_CONTEXT(accept_sec_context_args) = 2; + + delete_sec_context_res + GSSD_DELETE_SEC_CONTEXT(delete_sec_context_args) = 3; + + export_sec_context_res + GSSD_EXPORT_SEC_CONTEXT(export_sec_context_args) = 4; + + import_name_res + GSSD_IMPORT_NAME(import_name_args) = 5; + + canonicalize_name_res + GSSD_CANONICALIZE_NAME(canonicalize_name_args) = 6; + + export_name_res + GSSD_EXPORT_NAME(export_name_args) = 7; + + release_name_res + GSSD_RELEASE_NAME(release_name_args) = 8; + + pname_to_uid_res + GSSD_PNAME_TO_UID(pname_to_uid_args) = 9; + + acquire_cred_res + GSSD_ACQUIRE_CRED(acquire_cred_args) = 10; + + set_cred_option_res + GSSD_SET_CRED_OPTION(set_cred_option_args) = 11; + + release_cred_res + GSSD_RELEASE_CRED(release_cred_args) = 12; + + display_status_res + GSSD_DISPLAY_STATUS(display_status_args) = 13; + } = 1; +} = 0x40677373; diff --git a/sys/kgssapi/gssd_prot.c b/sys/kgssapi/gssd_prot.c new file mode 100644 index 0000000..3b8fbc5 --- /dev/null +++ b/sys/kgssapi/gssd_prot.c @@ -0,0 +1,244 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#ifdef _KERNEL +#include +#else +#include +#include +#endif + +#include +#include + +#include "gssd.h" + +bool_t +xdr_gss_buffer_desc(XDR *xdrs, gss_buffer_desc *buf) +{ + char *val; + u_int len; + + len = buf->length; + val = buf->value; + if (!xdr_bytes(xdrs, &val, &len, ~0)) + return (FALSE); + buf->length = len; + buf->value = val; + + return (TRUE); +} + +bool_t +xdr_gss_OID_desc(XDR *xdrs, gss_OID_desc *oid) +{ + char *val; + u_int len; + + len = oid->length; + val = oid->elements; + if (!xdr_bytes(xdrs, &val, &len, ~0)) + return (FALSE); + oid->length = len; + oid->elements = val; + + return (TRUE); +} + +bool_t +xdr_gss_OID(XDR *xdrs, gss_OID *oidp) +{ + gss_OID oid; + bool_t is_null; + + switch (xdrs->x_op) { + case XDR_ENCODE: + oid = *oidp; + if (oid) { + is_null = FALSE; + if (!xdr_bool(xdrs, &is_null) + || !xdr_gss_OID_desc(xdrs, oid)) + return (FALSE); + } else { + is_null = TRUE; + if (!xdr_bool(xdrs, &is_null)) + return (FALSE); + } + break; + + case XDR_DECODE: + if (!xdr_bool(xdrs, &is_null)) + return (FALSE); + if (is_null) { + *oidp = GSS_C_NO_OID; + } else { + oid = mem_alloc(sizeof(gss_OID_desc)); + memset(oid, 0, sizeof(*oid)); + if (!xdr_gss_OID_desc(xdrs, oid)) + return (FALSE); + *oidp = oid; + } + break; + + case XDR_FREE: + oid = *oidp; + if (oid) { + xdr_gss_OID_desc(xdrs, oid); + mem_free(oid, sizeof(gss_OID_desc)); + } + } + + return (TRUE); +} + +bool_t +xdr_gss_OID_set_desc(XDR *xdrs, gss_OID_set_desc *set) +{ + caddr_t addr; + u_int len; + + len = set->count; + addr = (caddr_t) set->elements; + if (!xdr_array(xdrs, &addr, &len, ~0, sizeof(gss_OID_desc), + (xdrproc_t) xdr_gss_OID_desc)) + return (FALSE); + set->count = len; + set->elements = (gss_OID) addr; + + return (TRUE); +} + +bool_t +xdr_gss_OID_set(XDR *xdrs, gss_OID_set *setp) +{ + gss_OID_set set; + bool_t is_null; + + switch (xdrs->x_op) { + case XDR_ENCODE: + set = *setp; + if (set) { + is_null = FALSE; + if (!xdr_bool(xdrs, &is_null) + || !xdr_gss_OID_set_desc(xdrs, set)) + return (FALSE); + } else { + is_null = TRUE; + if (!xdr_bool(xdrs, &is_null)) + return (FALSE); + } + break; + + case XDR_DECODE: + if (!xdr_bool(xdrs, &is_null)) + return (FALSE); + if (is_null) { + *setp = GSS_C_NO_OID_SET; + } else { + set = mem_alloc(sizeof(gss_OID_set_desc)); + memset(set, 0, sizeof(*set)); + if (!xdr_gss_OID_set_desc(xdrs, set)) + return (FALSE); + *setp = set; + } + break; + + case XDR_FREE: + set = *setp; + if (set) { + xdr_gss_OID_set_desc(xdrs, set); + mem_free(set, sizeof(gss_OID_set_desc)); + } + } + + return (TRUE); +} + +bool_t +xdr_gss_channel_bindings_t(XDR *xdrs, gss_channel_bindings_t *chp) +{ + gss_channel_bindings_t ch; + bool_t is_null; + + switch (xdrs->x_op) { + case XDR_ENCODE: + ch = *chp; + if (ch) { + is_null = FALSE; + if (!xdr_bool(xdrs, &is_null) + || !xdr_uint32_t(xdrs, &ch->initiator_addrtype) + || !xdr_gss_buffer_desc(xdrs, + &ch->initiator_address) + || !xdr_uint32_t(xdrs, &ch->acceptor_addrtype) + || !xdr_gss_buffer_desc(xdrs, + &ch->acceptor_address) + || !xdr_gss_buffer_desc(xdrs, + &ch->application_data)) + return (FALSE); + } else { + is_null = TRUE; + if (!xdr_bool(xdrs, &is_null)) + return (FALSE); + } + break; + + case XDR_DECODE: + if (!xdr_bool(xdrs, &is_null)) + return (FALSE); + if (is_null) { + *chp = GSS_C_NO_CHANNEL_BINDINGS; + } else { + ch = mem_alloc(sizeof(*ch)); + memset(ch, 0, sizeof(*ch)); + if (!xdr_uint32_t(xdrs, &ch->initiator_addrtype) + || !xdr_gss_buffer_desc(xdrs, + &ch->initiator_address) + || !xdr_uint32_t(xdrs, &ch->acceptor_addrtype) + || !xdr_gss_buffer_desc(xdrs, + &ch->acceptor_address) + || !xdr_gss_buffer_desc(xdrs, + &ch->application_data)) + return (FALSE); + *chp = ch; + } + break; + + case XDR_FREE: + ch = *chp; + if (ch) { + xdr_gss_buffer_desc(xdrs, &ch->initiator_address); + xdr_gss_buffer_desc(xdrs, &ch->acceptor_address); + xdr_gss_buffer_desc(xdrs, &ch->application_data); + mem_free(ch, sizeof(*ch)); + } + } + + return (TRUE); +} diff --git a/sys/kgssapi/gsstest.c b/sys/kgssapi/gsstest.c new file mode 100644 index 0000000..1764703 --- /dev/null +++ b/sys/kgssapi/gsstest.c @@ -0,0 +1,1141 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static void +report_error(gss_OID mech, OM_uint32 maj, OM_uint32 min) +{ + OM_uint32 maj_stat, min_stat; + OM_uint32 message_context; + gss_buffer_desc buf; + + uprintf("major_stat=%d, minor_stat=%d\n", maj, min); + message_context = 0; + do { + maj_stat = gss_display_status(&min_stat, maj, + GSS_C_GSS_CODE, GSS_C_NO_OID, &message_context, &buf); + if (GSS_ERROR(maj_stat)) + break; + uprintf("%.*s\n", (int)buf.length, (char *) buf.value); + gss_release_buffer(&min_stat, &buf); + } while (message_context); + if (mech && min) { + message_context = 0; + do { + maj_stat = gss_display_status(&min_stat, min, + GSS_C_MECH_CODE, mech, &message_context, &buf); + if (GSS_ERROR(maj_stat)) + break; + uprintf("%.*s\n", (int)buf.length, (char *) buf.value); + gss_release_buffer(&min_stat, &buf); + } while (message_context); + } +} + +#if 0 +static void +send_token_to_peer(const gss_buffer_t token) +{ + const uint8_t *p; + size_t i; + + printf("send token:\n"); + printf("%d ", (int) token->length); + p = (const uint8_t *) token->value; + for (i = 0; i < token->length; i++) + printf("%02x", *p++); + printf("\n"); +} + +static void +receive_token_from_peer(gss_buffer_t token) +{ + char line[8192]; + char *p; + uint8_t *q; + int len, val; + + printf("receive token:\n"); + fgets(line, sizeof(line), stdin); + if (line[strlen(line) - 1] != '\n') { + printf("token truncated\n"); + exit(1); + } + p = line; + if (sscanf(line, "%d ", &len) != 1) { + printf("bad token\n"); + exit(1); + } + p = strchr(p, ' ') + 1; + token->length = len; + token->value = malloc(len); + q = (uint8_t *) token->value; + while (len) { + if (sscanf(p, "%02x", &val) != 1) { + printf("bad token\n"); + exit(1); + } + *q++ = val; + p += 2; + len--; + } +} +#endif + +#if 0 +void +server(int argc, char** argv) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + gss_ctx_id_t context_hdl = GSS_C_NO_CONTEXT; + gss_name_t client_name; + gss_OID mech_type; + + if (argc != 1) + usage(); + + do { + receive_token_from_peer(&input_token); + maj_stat = gss_accept_sec_context(&min_stat, + &context_hdl, + GSS_C_NO_CREDENTIAL, + &input_token, + GSS_C_NO_CHANNEL_BINDINGS, + &client_name, + &mech_type, + &output_token, + NULL, + NULL, + NULL); + if (GSS_ERROR(maj_stat)) { + report_error(mech_type, maj_stat, min_stat); + } + if (output_token.length != 0) { + send_token_to_peer(&output_token); + gss_release_buffer(&min_stat, &output_token); + } + if (GSS_ERROR(maj_stat)) { + if (context_hdl != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, + &context_hdl, + GSS_C_NO_BUFFER); + break; + } + } while (maj_stat & GSS_S_CONTINUE_NEEDED); + + if (client_name) { + gss_buffer_desc name_desc; + char buf[512]; + + gss_display_name(&min_stat, client_name, &name_desc, NULL); + memcpy(buf, name_desc.value, name_desc.length); + buf[name_desc.length] = 0; + gss_release_buffer(&min_stat, &name_desc); + printf("client name is %s\n", buf); + } + + receive_token_from_peer(&input_token); + gss_unwrap(&min_stat, context_hdl, &input_token, &output_token, + NULL, NULL); + printf("%.*s\n", (int)output_token.length, (char *) output_token.value); + gss_release_buffer(&min_stat, &output_token); +} +#endif + +/* 1.2.752.43.13.14 */ +static gss_OID_desc gss_krb5_set_allowable_enctypes_x_desc = +{6, (void *) "\x2a\x85\x70\x2b\x0d\x0e"}; + +gss_OID GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X = &gss_krb5_set_allowable_enctypes_x_desc; +#define ETYPE_DES_CBC_CRC 1 + +/* + * Create an initiator context and acceptor context in the kernel and + * use them to exchange signed and sealed messages. + */ +static int +gsstest_1(void) +{ + OM_uint32 maj_stat, min_stat; + OM_uint32 smaj_stat, smin_stat; + int context_established = 0; + gss_ctx_id_t client_context = GSS_C_NO_CONTEXT; + gss_ctx_id_t server_context = GSS_C_NO_CONTEXT; + gss_cred_id_t client_cred = GSS_C_NO_CREDENTIAL; + gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; + gss_name_t name = GSS_C_NO_NAME; + gss_name_t received_name = GSS_C_NO_NAME; + gss_buffer_desc name_desc; + gss_buffer_desc client_token, server_token, message_buf; + gss_OID mech, actual_mech, mech_type; + static gss_OID_desc krb5_desc = + {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; +#if 0 + static gss_OID_desc spnego_desc = + {6, (void *)"\x2b\x06\x01\x05\x05\x02"}; + static gss_OID_desc ntlm_desc = + {10, (void *)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"}; +#endif + char enctype[sizeof(uint32_t)]; + + mech = GSS_C_NO_OID; + + { + static char sbuf[512]; + snprintf(sbuf, sizeof(sbuf), "nfs@%s", hostname); + name_desc.value = sbuf; + } + + name_desc.length = strlen((const char *) name_desc.value); + maj_stat = gss_import_name(&min_stat, &name_desc, + GSS_C_NT_HOSTBASED_SERVICE, &name); + if (GSS_ERROR(maj_stat)) { + printf("gss_import_name failed\n"); + report_error(mech, maj_stat, min_stat); + goto out; + } + + maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME, + 0, GSS_C_NO_OID_SET, GSS_C_INITIATE, &client_cred, + NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_acquire_cred (client) failed\n"); + report_error(mech, maj_stat, min_stat); + goto out; + } + + enctype[0] = (ETYPE_DES_CBC_CRC >> 24) & 0xff; + enctype[1] = (ETYPE_DES_CBC_CRC >> 16) & 0xff; + enctype[2] = (ETYPE_DES_CBC_CRC >> 8) & 0xff; + enctype[3] = ETYPE_DES_CBC_CRC & 0xff; + message_buf.length = sizeof(enctype); + message_buf.value = enctype; + maj_stat = gss_set_cred_option(&min_stat, &client_cred, + GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X, &message_buf); + if (GSS_ERROR(maj_stat)) { + printf("gss_set_cred_option failed\n"); + report_error(mech, maj_stat, min_stat); + goto out; + } + + server_token.length = 0; + server_token.value = NULL; + while (!context_established) { + client_token.length = 0; + client_token.value = NULL; + maj_stat = gss_init_sec_context(&min_stat, + client_cred, + &client_context, + name, + mech, + GSS_C_MUTUAL_FLAG|GSS_C_CONF_FLAG|GSS_C_INTEG_FLAG, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + &server_token, + &actual_mech, + &client_token, + NULL, + NULL); + if (server_token.length) + gss_release_buffer(&smin_stat, &server_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_init_sec_context failed\n"); + report_error(mech, maj_stat, min_stat); + goto out; + } + + if (client_token.length != 0) { + if (!server_cred) { + gss_OID_set_desc oid_set; + oid_set.count = 1; + oid_set.elements = &krb5_desc; + smaj_stat = gss_acquire_cred(&smin_stat, + name, 0, &oid_set, GSS_C_ACCEPT, &server_cred, + NULL, NULL); + if (GSS_ERROR(smaj_stat)) { + printf("gss_acquire_cred (server) failed\n"); + report_error(mech_type, smaj_stat, smin_stat); + goto out; + } + } + smaj_stat = gss_accept_sec_context(&smin_stat, + &server_context, + server_cred, + &client_token, + GSS_C_NO_CHANNEL_BINDINGS, + &received_name, + &mech_type, + &server_token, + NULL, + NULL, + NULL); + if (GSS_ERROR(smaj_stat)) { + printf("gss_accept_sec_context failed\n"); + report_error(mech_type, smaj_stat, smin_stat); + goto out; + } + gss_release_buffer(&min_stat, &client_token); + } + if (GSS_ERROR(maj_stat)) { + if (client_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, + &client_context, + GSS_C_NO_BUFFER); + break; + } + + if (maj_stat == GSS_S_COMPLETE) { + context_established = 1; + } + } + + message_buf.length = strlen("Hello world"); + message_buf.value = (void *) "Hello world"; + + maj_stat = gss_get_mic(&min_stat, client_context, + GSS_C_QOP_DEFAULT, &message_buf, &client_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_get_mic failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + maj_stat = gss_verify_mic(&min_stat, server_context, + &message_buf, &client_token, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_verify_mic failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + gss_release_buffer(&min_stat, &client_token); + + maj_stat = gss_wrap(&min_stat, client_context, + TRUE, GSS_C_QOP_DEFAULT, &message_buf, NULL, &client_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_wrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + maj_stat = gss_unwrap(&min_stat, server_context, + &client_token, &server_token, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_unwrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + if (message_buf.length != server_token.length + || memcmp(message_buf.value, server_token.value, + message_buf.length)) + printf("unwrap result corrupt\n"); + + gss_release_buffer(&min_stat, &client_token); + gss_release_buffer(&min_stat, &server_token); + +out: + if (client_context) + gss_delete_sec_context(&min_stat, &client_context, + GSS_C_NO_BUFFER); + if (server_context) + gss_delete_sec_context(&min_stat, &server_context, + GSS_C_NO_BUFFER); + if (client_cred) + gss_release_cred(&min_stat, &client_cred); + if (server_cred) + gss_release_cred(&min_stat, &server_cred); + if (name) + gss_release_name(&min_stat, &name); + if (received_name) + gss_release_name(&min_stat, &received_name); + + return (0); +} + +/* + * Interoperability with userland. This takes several steps: + * + * 1. Accept an initiator token from userland, return acceptor + * token. Repeat this step until both userland and kernel return + * GSS_S_COMPLETE. + * + * 2. Receive a signed message from userland and verify the + * signature. Return a signed reply to userland for it to verify. + * + * 3. Receive a wrapped message from userland and unwrap it. Return a + * wrapped reply to userland. + */ +static int +gsstest_2(int step, const gss_buffer_t input_token, + OM_uint32 *maj_stat_res, OM_uint32 *min_stat_res, gss_buffer_t output_token) +{ + OM_uint32 maj_stat, min_stat; + static int context_established = 0; + static gss_ctx_id_t server_context = GSS_C_NO_CONTEXT; + static gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL; + static gss_name_t name = GSS_C_NO_NAME; + gss_buffer_desc name_desc; + gss_buffer_desc message_buf; + gss_OID mech_type = GSS_C_NO_OID; + char enctype[sizeof(uint32_t)]; + int error = EINVAL; + + maj_stat = GSS_S_FAILURE; + min_stat = 0; + switch (step) { + + case 1: + if (server_context == GSS_C_NO_CONTEXT) { + static char sbuf[512]; + snprintf(sbuf, sizeof(sbuf), "nfs@%s", hostname); + name_desc.value = sbuf; + name_desc.length = strlen((const char *) + name_desc.value); + maj_stat = gss_import_name(&min_stat, &name_desc, + GSS_C_NT_HOSTBASED_SERVICE, &name); + if (GSS_ERROR(maj_stat)) { + printf("gss_import_name failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + maj_stat = gss_acquire_cred(&min_stat, + name, 0, GSS_C_NO_OID_SET, GSS_C_ACCEPT, + &server_cred, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_acquire_cred (server) failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + enctype[0] = (ETYPE_DES_CBC_CRC >> 24) & 0xff; + enctype[1] = (ETYPE_DES_CBC_CRC >> 16) & 0xff; + enctype[2] = (ETYPE_DES_CBC_CRC >> 8) & 0xff; + enctype[3] = ETYPE_DES_CBC_CRC & 0xff; + message_buf.length = sizeof(enctype); + message_buf.value = enctype; + maj_stat = gss_set_cred_option(&min_stat, &server_cred, + GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X, &message_buf); + if (GSS_ERROR(maj_stat)) { + printf("gss_set_cred_option failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + } + + maj_stat = gss_accept_sec_context(&min_stat, + &server_context, + server_cred, + input_token, + GSS_C_NO_CHANNEL_BINDINGS, + NULL, + &mech_type, + output_token, + NULL, + NULL, + NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_accept_sec_context failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + if (maj_stat == GSS_S_COMPLETE) { + context_established = 1; + } + *maj_stat_res = maj_stat; + *min_stat_res = min_stat; + break; + + case 2: + message_buf.length = strlen("Hello world"); + message_buf.value = (void *) "Hello world"; + + maj_stat = gss_verify_mic(&min_stat, server_context, + &message_buf, input_token, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_verify_mic failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + maj_stat = gss_get_mic(&min_stat, server_context, + GSS_C_QOP_DEFAULT, &message_buf, output_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_get_mic failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + break; + + case 3: + maj_stat = gss_unwrap(&min_stat, server_context, + input_token, &message_buf, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_unwrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + gss_release_buffer(&min_stat, &message_buf); + + message_buf.length = strlen("Hello world"); + message_buf.value = (void *) "Hello world"; + maj_stat = gss_wrap(&min_stat, server_context, + TRUE, GSS_C_QOP_DEFAULT, &message_buf, NULL, output_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_wrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + break; + + case 4: + maj_stat = gss_unwrap(&min_stat, server_context, + input_token, &message_buf, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_unwrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + gss_release_buffer(&min_stat, &message_buf); + + message_buf.length = strlen("Hello world"); + message_buf.value = (void *) "Hello world"; + maj_stat = gss_wrap(&min_stat, server_context, + FALSE, GSS_C_QOP_DEFAULT, &message_buf, NULL, output_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_wrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + break; + + case 5: + error = 0; + goto out; + } + *maj_stat_res = maj_stat; + *min_stat_res = min_stat; + return (0); + +out: + *maj_stat_res = maj_stat; + *min_stat_res = min_stat; + if (server_context) + gss_delete_sec_context(&min_stat, &server_context, + GSS_C_NO_BUFFER); + if (server_cred) + gss_release_cred(&min_stat, &server_cred); + if (name) + gss_release_name(&min_stat, &name); + + return (error); +} + +/* + * Create an RPC client handle for the given (address,prog,vers) + * triple using UDP. + */ +static CLIENT * +gsstest_get_rpc(struct sockaddr *sa, rpcprog_t prog, rpcvers_t vers) +{ + struct thread *td = curthread; + const char* protofmly; + struct sockaddr_storage ss; + struct socket *so; + CLIENT *rpcb; + struct timeval timo; + RPCB parms; + char *uaddr; + enum clnt_stat stat = RPC_SUCCESS; + int rpcvers = RPCBVERS4; + bool_t do_tcp = FALSE; + struct portmap mapping; + u_short port = 0; + + /* + * First we need to contact the remote RPCBIND service to find + * the right port. + */ + memcpy(&ss, sa, sa->sa_len); + switch (ss.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_port = htons(111); + protofmly = "inet"; + socreate(AF_INET, &so, SOCK_DGRAM, 0, td->td_ucred, td); + break; + +#ifdef INET6 + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_port = htons(111); + protofmly = "inet6"; + socreate(AF_INET6, &so, SOCK_DGRAM, 0, td->td_ucred, td); + break; +#endif + + default: + /* + * Unsupported address family - fail. + */ + return (NULL); + } + + rpcb = clnt_dg_create(so, (struct sockaddr *)&ss, + RPCBPROG, rpcvers, 0, 0); + if (!rpcb) + return (NULL); + +try_tcp: + parms.r_prog = prog; + parms.r_vers = vers; + if (do_tcp) + parms.r_netid = "tcp"; + else + parms.r_netid = "udp"; + parms.r_addr = ""; + parms.r_owner = ""; + + /* + * Use the default timeout. + */ + timo.tv_sec = 25; + timo.tv_usec = 0; +again: + switch (rpcvers) { + case RPCBVERS4: + case RPCBVERS: + /* + * Try RPCBIND 4 then 3. + */ + uaddr = NULL; + stat = CLNT_CALL(rpcb, (rpcprog_t) RPCBPROC_GETADDR, + (xdrproc_t) xdr_rpcb, &parms, + (xdrproc_t) xdr_wrapstring, &uaddr, timo); + if (stat == RPC_PROGVERSMISMATCH) { + if (rpcvers == RPCBVERS4) + rpcvers = RPCBVERS; + else if (rpcvers == RPCBVERS) + rpcvers = PMAPVERS; + CLNT_CONTROL(rpcb, CLSET_VERS, &rpcvers); + goto again; + } else if (stat == RPC_SUCCESS) { + /* + * We have a reply from the remote RPCBIND - turn it + * into an appropriate address and make a new client + * that can talk to the remote service. + * + * XXX fixup IPv6 scope ID. + */ + struct netbuf *a; + a = __rpc_uaddr2taddr_af(ss.ss_family, uaddr); + xdr_free((xdrproc_t) xdr_wrapstring, &uaddr); + if (!a) { + CLNT_DESTROY(rpcb); + return (NULL); + } + memcpy(&ss, a->buf, a->len); + free(a->buf, M_RPC); + free(a, M_RPC); + } + break; + case PMAPVERS: + /* + * Try portmap. + */ + mapping.pm_prog = parms.r_prog; + mapping.pm_vers = parms.r_vers; + mapping.pm_prot = do_tcp ? IPPROTO_TCP : IPPROTO_UDP; + mapping.pm_port = 0; + + stat = CLNT_CALL(rpcb, (rpcprog_t) PMAPPROC_GETPORT, + (xdrproc_t) xdr_portmap, &mapping, + (xdrproc_t) xdr_u_short, &port, timo); + + if (stat == RPC_SUCCESS) { + switch (ss.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_port = + htons(port); + break; + +#ifdef INET6 + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_port = + htons(port); + break; +#endif + } + } + break; + default: + panic("invalid rpcvers %d", rpcvers); + } + /* + * We may have a positive response from the portmapper, but + * the requested service was not found. Make sure we received + * a valid port. + */ + switch (ss.ss_family) { + case AF_INET: + port = ((struct sockaddr_in *)&ss)->sin_port; + break; +#ifdef INET6 + case AF_INET6: + port = ((struct sockaddr_in6 *)&ss)->sin6_port; + break; +#endif + } + if (stat != RPC_SUCCESS || !port) { + /* + * If we were able to talk to rpcbind or portmap, but the udp + * variant wasn't available, ask about tcp. + * + * XXX - We could also check for a TCP portmapper, but + * if the host is running a portmapper at all, we should be able + * to hail it over UDP. + */ + if (stat == RPC_SUCCESS && !do_tcp) { + do_tcp = TRUE; + goto try_tcp; + } + + /* Otherwise, bad news. */ + printf("gsstest_get_rpc: failed to contact remote rpcbind, " + "stat = %d, port = %d\n", + (int) stat, port); + CLNT_DESTROY(rpcb); + return (NULL); + } + + if (do_tcp) { + /* + * Destroy the UDP client we used to speak to rpcbind and + * recreate as a TCP client. + */ + struct netconfig *nconf = NULL; + + CLNT_DESTROY(rpcb); + + switch (ss.ss_family) { + case AF_INET: + nconf = getnetconfigent("tcp"); + break; +#ifdef INET6 + case AF_INET6: + nconf = getnetconfigent("tcp6"); + break; +#endif + } + + rpcb = clnt_reconnect_create(nconf, (struct sockaddr *)&ss, + prog, vers, 0, 0); + } else { + /* + * Re-use the client we used to speak to rpcbind. + */ + CLNT_CONTROL(rpcb, CLSET_SVC_ADDR, &ss); + CLNT_CONTROL(rpcb, CLSET_PROG, &prog); + CLNT_CONTROL(rpcb, CLSET_VERS, &vers); + } + + return (rpcb); +} + +/* + * RPCSEC_GSS client + */ +static int +gsstest_3(void) +{ + struct sockaddr_in sin; + char service[128]; + CLIENT *client; + AUTH *auth; + rpc_gss_options_ret_t options_ret; + enum clnt_stat stat; + struct timeval tv; + rpc_gss_service_t svc; + int i; + + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = 0; + + client = gsstest_get_rpc((struct sockaddr *) &sin, 123456, 1); + if (!client) { + uprintf("Can't connect to service\n"); + return(1); + } + + snprintf(service, sizeof(service), "host@%s", hostname); + + auth = rpc_gss_seccreate(client, curthread->td_ucred, + service, "kerberosv5", rpc_gss_svc_privacy, + NULL, NULL, &options_ret); + if (!auth) { + gss_OID oid; + uprintf("Can't authorize to service (mech=%s)\n", + options_ret.actual_mechanism); + oid = GSS_C_NO_OID; + rpc_gss_mech_to_oid(options_ret.actual_mechanism, &oid); + report_error(oid, options_ret.major_status, + options_ret.minor_status); + CLNT_DESTROY(client); + return (1); + } + + for (svc = rpc_gss_svc_none; svc <= rpc_gss_svc_privacy; svc++) { + const char *svc_names[] = { + "rpc_gss_svc_default", + "rpc_gss_svc_none", + "rpc_gss_svc_integrity", + "rpc_gss_svc_privacy" + }; + int num; + + rpc_gss_set_defaults(auth, svc, NULL); + + client->cl_auth = auth; + tv.tv_sec = 5; + tv.tv_usec = 0; + for (i = 42; i < 142; i++) { + num = i; + stat = CLNT_CALL(client, 1, + (xdrproc_t) xdr_int, (char *) &num, + (xdrproc_t) xdr_int, (char *) &num, tv); + if (stat == RPC_SUCCESS) { + if (num != i + 100) + uprintf("unexpected reply %d\n", num); + } else { + uprintf("call failed, stat=%d\n", (int) stat); + break; + } + } + if (i == 142) + uprintf("call succeeded with %s\n", svc_names[svc]); + } + + AUTH_DESTROY(auth); + CLNT_RELEASE(client); + + return (0); +} + +/* + * RPCSEC_GSS server + */ +static rpc_gss_principal_t server_acl = NULL; +static bool_t server_new_context(struct svc_req *req, gss_cred_id_t deleg, + gss_ctx_id_t gss_context, rpc_gss_lock_t *lock, void **cookie); +static void server_program_1(struct svc_req *rqstp, register SVCXPRT *transp); + +static int +gsstest_4(void) +{ + SVCPOOL *pool; + char principal[128 + 5]; + const char **mechs; + static rpc_gss_callback_t cb; + + snprintf(principal, sizeof(principal), "host@%s", hostname); + + mechs = rpc_gss_get_mechanisms(); + while (*mechs) { + if (!rpc_gss_set_svc_name(principal, *mechs, GSS_C_INDEFINITE, + 123456, 1)) { + rpc_gss_error_t e; + + rpc_gss_get_error(&e); + printf("setting name for %s for %s failed: %d, %d\n", + principal, *mechs, + e.rpc_gss_error, e.system_error); + } + mechs++; + } + + cb.program = 123456; + cb.version = 1; + cb.callback = server_new_context; + rpc_gss_set_callback(&cb); + + pool = svcpool_create("gsstest", NULL); + + svc_create(pool, server_program_1, 123456, 1, NULL); + svc_run(pool); + + rpc_gss_clear_svc_name(123456, 1); + rpc_gss_clear_callback(&cb); + + svcpool_destroy(pool); + + return (0); +} + +static void +server_program_1(struct svc_req *rqstp, register SVCXPRT *transp) +{ + rpc_gss_rawcred_t *rcred; + rpc_gss_ucred_t *ucred; + int i, num; + + if (rqstp->rq_cred.oa_flavor != RPCSEC_GSS) { + svcerr_weakauth(rqstp); + return; + } + + if (!rpc_gss_getcred(rqstp, &rcred, &ucred, NULL)) { + svcerr_systemerr(rqstp); + return; + } + + printf("svc=%d, mech=%s, uid=%d, gid=%d, gids={", + rcred->service, rcred->mechanism, ucred->uid, ucred->gid); + for (i = 0; i < ucred->gidlen; i++) { + if (i > 0) printf(","); + printf("%d", ucred->gidlist[i]); + } + printf("}\n"); + + switch (rqstp->rq_proc) { + case 0: + if (!svc_getargs(rqstp, (xdrproc_t) xdr_void, 0)) { + svcerr_decode(rqstp); + goto out; + } + if (!svc_sendreply(rqstp, (xdrproc_t) xdr_void, 0)) { + svcerr_systemerr(rqstp); + } + goto out; + + case 1: + if (!svc_getargs(rqstp, (xdrproc_t) xdr_int, + (char *) &num)) { + svcerr_decode(rqstp); + goto out; + } + num += 100; + if (!svc_sendreply(rqstp, (xdrproc_t) xdr_int, + (char *) &num)) { + svcerr_systemerr(rqstp); + } + goto out; + + default: + svcerr_noproc(rqstp); + goto out; + } + +out: + return; +} + +static void +print_principal(rpc_gss_principal_t principal) +{ + int i, len, n; + uint8_t *p; + + len = principal->len; + p = (uint8_t *) principal->name; + while (len > 0) { + n = len; + if (n > 16) + n = 16; + for (i = 0; i < n; i++) + printf("%02x ", p[i]); + for (; i < 16; i++) + printf(" "); + printf("|"); + for (i = 0; i < n; i++) + printf("%c", isprint(p[i]) ? p[i] : '.'); + printf("|\n"); + len -= n; + p += n; + } +} + +static bool_t +server_new_context(__unused struct svc_req *req, + gss_cred_id_t deleg, + __unused gss_ctx_id_t gss_context, + rpc_gss_lock_t *lock, + __unused void **cookie) +{ + rpc_gss_rawcred_t *rcred = lock->raw_cred; + OM_uint32 junk; + + printf("new security context version=%d, mech=%s, qop=%s:\n", + rcred->version, rcred->mechanism, rcred->qop); + print_principal(rcred->client_principal); + + if (server_acl) { + if (rcred->client_principal->len != server_acl->len + || memcmp(rcred->client_principal->name, server_acl->name, + server_acl->len)) { + return (FALSE); + } + } + gss_release_cred(&junk, &deleg); + + return (TRUE); +} + +/* + * Hook up a syscall for gssapi testing. + */ + +struct gsstest_args { + int a_op; + void *a_args; + void *a_res; +}; + +struct gsstest_2_args { + int step; /* test step number */ + gss_buffer_desc input_token; /* token from userland */ + gss_buffer_desc output_token; /* buffer to receive reply token */ +}; +struct gsstest_2_res { + OM_uint32 maj_stat; /* maj_stat from kernel */ + OM_uint32 min_stat; /* min_stat from kernel */ + gss_buffer_desc output_token; /* reply token (using space from gsstest_2_args.output) */ +}; + +static int +gsstest(struct thread *td, struct gsstest_args *uap) +{ + int error; + + switch (uap->a_op) { + case 1: + return (gsstest_1()); + + case 2: { + struct gsstest_2_args args; + struct gsstest_2_res res; + gss_buffer_desc input_token, output_token; + OM_uint32 junk; + + error = copyin(uap->a_args, &args, sizeof(args)); + if (error) + return (error); + input_token.length = args.input_token.length; + input_token.value = malloc(input_token.length, M_GSSAPI, + M_WAITOK); + error = copyin(args.input_token.value, input_token.value, + input_token.length); + if (error) { + gss_release_buffer(&junk, &input_token); + return (error); + } + output_token.length = 0; + output_token.value = NULL; + gsstest_2(args.step, &input_token, + &res.maj_stat, &res.min_stat, &output_token); + gss_release_buffer(&junk, &input_token); + if (output_token.length > args.output_token.length) { + gss_release_buffer(&junk, &output_token); + return (EOVERFLOW); + } + res.output_token.length = output_token.length; + res.output_token.value = args.output_token.value; + error = copyout(output_token.value, res.output_token.value, + output_token.length); + gss_release_buffer(&junk, &output_token); + if (error) + return (error); + + return (copyout(&res, uap->a_res, sizeof(res))); + + break; + } + case 3: + return (gsstest_3()); + case 4: + return (gsstest_4()); + } + + return (EINVAL); +} + +/* + * The `sysent' for the new syscall + */ +static struct sysent gsstest_sysent = { + 3, /* sy_narg */ + (sy_call_t *) gsstest /* sy_call */ +}; + +/* + * The offset in sysent where the syscall is allocated. + */ +static int gsstest_offset = NO_SYSCALL; + +/* + * The function called at load/unload. + */ + + +static int +gsstest_load(struct module *module, int cmd, void *arg) +{ + int error = 0; + + switch (cmd) { + case MOD_LOAD : + break; + case MOD_UNLOAD : + break; + default : + error = EOPNOTSUPP; + break; + } + return error; +} + +SYSCALL_MODULE(gsstest_syscall, &gsstest_offset, &gsstest_sysent, + gsstest_load, NULL); diff --git a/sys/kgssapi/kgss_if.m b/sys/kgssapi/kgss_if.m new file mode 100644 index 0000000..53d499a --- /dev/null +++ b/sys/kgssapi/kgss_if.m @@ -0,0 +1,95 @@ +#- +# Copyright (c) 2008 Isilon Inc http://www.isilon.com/ +# Authors: Doug Rabson +# Developed with Red Inc: Alfred Perlstein +# +# 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$ + +# Interface for the in-kernel part of a GSS-API mechanism + +#include +#include "gssd.h" + +INTERFACE kgss; + +METHOD void init { + gss_ctx_id_t ctx; +}; + +METHOD OM_uint32 import { + gss_ctx_id_t ctx; + enum sec_context_format format; + const gss_buffer_t context_token; +}; + +METHOD void delete { + gss_ctx_id_t ctx; + gss_buffer_t output_token; +}; + +METHOD gss_OID mech_type { + gss_ctx_id_t ctx; +}; + +METHOD OM_uint32 get_mic { + gss_ctx_id_t ctx; + OM_uint32 *minor_status; + gss_qop_t qop_req; + struct mbuf *message_buffer; + struct mbuf **message_token; +}; + +METHOD OM_uint32 verify_mic { + gss_ctx_id_t ctx; + OM_uint32 *minor_status; + struct mbuf *message_buffer; + struct mbuf *token_buffer; + gss_qop_t *qop_state; +}; + +METHOD OM_uint32 wrap { + gss_ctx_id_t ctx; + OM_uint32 *minor_status; + int conf_req_flag; + gss_qop_t qop_req; + struct mbuf **message_buffer; + int *conf_state; +}; + +METHOD OM_uint32 unwrap { + gss_ctx_id_t ctx; + OM_uint32 *minor_status; + struct mbuf **message_buffer; + int *conf_state; + gss_qop_t *qop_state; +}; + +METHOD OM_uint32 wrap_size_limit { + gss_ctx_id_t ctx; + OM_uint32 *minor_status; + int conf_req_flag; + gss_qop_t qop_req; + OM_uint32 req_ouput_size; + OM_uint32 *max_input_size; +} diff --git a/sys/kgssapi/krb5/kcrypto.c b/sys/kgssapi/krb5/kcrypto.c new file mode 100644 index 0000000..27ee3ec --- /dev/null +++ b/sys/kgssapi/krb5/kcrypto.c @@ -0,0 +1,266 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "kcrypto.h" + +static struct krb5_encryption_class *krb5_encryption_classes[] = { + &krb5_des_encryption_class, + &krb5_des3_encryption_class, + &krb5_aes128_encryption_class, + &krb5_aes256_encryption_class, + &krb5_arcfour_encryption_class, + &krb5_arcfour_56_encryption_class, + NULL +}; + +struct krb5_encryption_class * +krb5_find_encryption_class(int etype) +{ + int i; + + for (i = 0; krb5_encryption_classes[i]; i++) { + if (krb5_encryption_classes[i]->ec_type == etype) + return (krb5_encryption_classes[i]); + } + return (NULL); +} + +struct krb5_key_state * +krb5_create_key(const struct krb5_encryption_class *ec) +{ + struct krb5_key_state *ks; + + ks = malloc(sizeof(struct krb5_key_state), M_GSSAPI, M_WAITOK); + ks->ks_class = ec; + refcount_init(&ks->ks_refs, 1); + ks->ks_key = malloc(ec->ec_keylen, M_GSSAPI, M_WAITOK); + ec->ec_init(ks); + + return (ks); +} + +void +krb5_free_key(struct krb5_key_state *ks) +{ + + if (refcount_release(&ks->ks_refs)) { + ks->ks_class->ec_destroy(ks); + bzero(ks->ks_key, ks->ks_class->ec_keylen); + free(ks->ks_key, M_GSSAPI); + free(ks, M_GSSAPI); + } +} + +static size_t +gcd(size_t a, size_t b) +{ + + if (b == 0) + return (a); + return gcd(b, a % b); +} + +static size_t +lcm(size_t a, size_t b) +{ + return ((a * b) / gcd(a, b)); +} + +/* + * Rotate right 13 of a variable precision number in 'in', storing the + * result in 'out'. The number is assumed to be big-endian in memory + * representation. + */ +static void +krb5_rotate_right_13(uint8_t *out, uint8_t *in, size_t numlen) +{ + uint32_t carry; + size_t i; + + /* + * Special case when numlen == 1. A rotate right 13 of a + * single byte number changes to a rotate right 5. + */ + if (numlen == 1) { + carry = in[0] >> 5; + out[0] = (in[0] << 3) | carry; + return; + } + + carry = ((in[numlen - 2] & 31) << 8) | in[numlen - 1]; + for (i = 2; i < numlen; i++) { + out[i] = ((in[i - 2] & 31) << 3) | (in[i - 1] >> 5); + } + out[1] = ((carry & 31) << 3) | (in[0] >> 5); + out[0] = carry >> 5; +} + +/* + * Add two variable precision numbers in big-endian representation + * using ones-complement arithmetic. + */ +static void +krb5_ones_complement_add(uint8_t *out, const uint8_t *in, size_t len) +{ + int n, i; + + /* + * First calculate the 2s complement sum, remembering the + * carry. + */ + n = 0; + for (i = len - 1; i >= 0; i--) { + n = out[i] + in[i] + n; + out[i] = n; + n >>= 8; + } + /* + * Then add back the carry. + */ + for (i = len - 1; n && i >= 0; i--) { + n = out[i] + n; + out[i] = n; + n >>= 8; + } +} + +static void +krb5_n_fold(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +{ + size_t tmplen; + uint8_t *tmp; + size_t i; + uint8_t *p; + + tmplen = lcm(inlen, outlen); + tmp = malloc(tmplen, M_GSSAPI, M_WAITOK); + + bcopy(in, tmp, inlen); + for (i = inlen, p = tmp; i < tmplen; i += inlen, p += inlen) { + krb5_rotate_right_13(p + inlen, p, inlen); + } + bzero(out, outlen); + for (i = 0, p = tmp; i < tmplen; i += outlen, p += outlen) { + krb5_ones_complement_add(out, p, outlen); + } + free(tmp, M_GSSAPI); +} + +struct krb5_key_state * +krb5_derive_key(struct krb5_key_state *inkey, + void *constant, size_t constantlen) +{ + struct krb5_key_state *dk; + const struct krb5_encryption_class *ec = inkey->ks_class; + uint8_t *folded; + uint8_t *bytes, *p, *q; + struct mbuf *m; + int randomlen, i; + + /* + * Expand the constant to blocklen bytes. + */ + folded = malloc(ec->ec_blocklen, M_GSSAPI, M_WAITOK); + krb5_n_fold(folded, ec->ec_blocklen, constant, constantlen); + + /* + * Generate enough bytes for keybits rounded up to a multiple + * of blocklen. + */ + randomlen = ((ec->ec_keybits/8 + ec->ec_blocklen - 1) / ec->ec_blocklen) + * ec->ec_blocklen; + bytes = malloc(randomlen, M_GSSAPI, M_WAITOK); + MGET(m, M_WAITOK, MT_DATA); + m->m_len = ec->ec_blocklen; + for (i = 0, p = bytes, q = folded; i < randomlen; + q = p, i += ec->ec_blocklen, p += ec->ec_blocklen) { + bcopy(q, m->m_data, ec->ec_blocklen); + krb5_encrypt(inkey, m, 0, ec->ec_blocklen, NULL, 0); + bcopy(m->m_data, p, ec->ec_blocklen); + } + m_free(m); + + dk = krb5_create_key(ec); + krb5_random_to_key(dk, bytes); + + free(folded, M_GSSAPI); + free(bytes, M_GSSAPI); + + return (dk); +} + +static struct krb5_key_state * +krb5_get_usage_key(struct krb5_key_state *basekey, int usage, int which) +{ + const struct krb5_encryption_class *ec = basekey->ks_class; + + if (ec->ec_flags & EC_DERIVED_KEYS) { + uint8_t constant[5]; + + constant[0] = usage >> 24; + constant[1] = usage >> 16; + constant[2] = usage >> 8; + constant[3] = usage; + constant[4] = which; + return (krb5_derive_key(basekey, constant, 5)); + } else { + refcount_acquire(&basekey->ks_refs); + return (basekey); + } +} + +struct krb5_key_state * +krb5_get_encryption_key(struct krb5_key_state *basekey, int usage) +{ + + return (krb5_get_usage_key(basekey, usage, 0xaa)); +} + +struct krb5_key_state * +krb5_get_integrity_key(struct krb5_key_state *basekey, int usage) +{ + + return (krb5_get_usage_key(basekey, usage, 0x55)); +} + +struct krb5_key_state * +krb5_get_checksum_key(struct krb5_key_state *basekey, int usage) +{ + + return (krb5_get_usage_key(basekey, usage, 0x99)); +} diff --git a/sys/kgssapi/krb5/kcrypto.h b/sys/kgssapi/krb5/kcrypto.h new file mode 100644 index 0000000..5486641 --- /dev/null +++ b/sys/kgssapi/krb5/kcrypto.h @@ -0,0 +1,154 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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$ + */ + +#include + +#define ETYPE_NULL 0 +#define ETYPE_DES_CBC_CRC 1 +#define ETYPE_DES_CBC_MD4 2 +#define ETYPE_DES_CBC_MD5 3 +#define ETYPE_DES3_CBC_MD5 5 +#define ETYPE_OLD_DES3_CBC_SHA1 7 +#define ETYPE_DES3_CBC_SHA1 16 +#define ETYPE_AES128_CTS_HMAC_SHA1_96 17 +#define ETYPE_AES256_CTS_HMAC_SHA1_96 18 +#define ETYPE_ARCFOUR_HMAC_MD5 23 +#define ETYPE_ARCFOUR_HMAC_MD5_56 24 + +/* + * Key usages for des3-cbc-sha1 tokens + */ +#define KG_USAGE_SEAL 22 +#define KG_USAGE_SIGN 23 +#define KG_USAGE_SEQ 24 + +/* + * Key usages for RFC4121 tokens + */ +#define KG_USAGE_ACCEPTOR_SEAL 22 +#define KG_USAGE_ACCEPTOR_SIGN 23 +#define KG_USAGE_INITIATOR_SEAL 24 +#define KG_USAGE_INITIATOR_SIGN 25 + +struct krb5_key_state; + +typedef void init_func(struct krb5_key_state *ks); +typedef void destroy_func(struct krb5_key_state *ks); +typedef void set_key_func(struct krb5_key_state *ks, const void *in); +typedef void random_to_key_func(struct krb5_key_state *ks, const void *in); +typedef void encrypt_func(const struct krb5_key_state *ks, + struct mbuf *inout, size_t skip, size_t len, void *ivec, size_t ivlen); +typedef void checksum_func(const struct krb5_key_state *ks, int usage, + struct mbuf *inout, size_t skip, size_t inlen, size_t outlen); + +struct krb5_encryption_class { + const char *ec_name; + int ec_type; + int ec_flags; +#define EC_DERIVED_KEYS 1 + size_t ec_blocklen; + size_t ec_msgblocklen; + size_t ec_checksumlen; + size_t ec_keybits; /* key length in bits */ + size_t ec_keylen; /* size of key in memory */ + init_func *ec_init; + destroy_func *ec_destroy; + set_key_func *ec_set_key; + random_to_key_func *ec_random_to_key; + encrypt_func *ec_encrypt; + encrypt_func *ec_decrypt; + checksum_func *ec_checksum; +}; + +struct krb5_key_state { + const struct krb5_encryption_class *ks_class; + volatile u_int ks_refs; + void *ks_key; + void *ks_priv; +}; + +extern struct krb5_encryption_class krb5_des_encryption_class; +extern struct krb5_encryption_class krb5_des3_encryption_class; +extern struct krb5_encryption_class krb5_aes128_encryption_class; +extern struct krb5_encryption_class krb5_aes256_encryption_class; +extern struct krb5_encryption_class krb5_arcfour_encryption_class; +extern struct krb5_encryption_class krb5_arcfour_56_encryption_class; + +static __inline void +krb5_set_key(struct krb5_key_state *ks, const void *keydata) +{ + + ks->ks_class->ec_set_key(ks, keydata); +} + +static __inline void +krb5_random_to_key(struct krb5_key_state *ks, const void *keydata) +{ + + ks->ks_class->ec_random_to_key(ks, keydata); +} + +static __inline void +krb5_encrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + + ks->ks_class->ec_encrypt(ks, inout, skip, len, ivec, ivlen); +} + +static __inline void +krb5_decrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + + ks->ks_class->ec_decrypt(ks, inout, skip, len, ivec, ivlen); +} + +static __inline void +krb5_checksum(const struct krb5_key_state *ks, int usage, + struct mbuf *inout, size_t skip, size_t inlen, size_t outlen) +{ + + ks->ks_class->ec_checksum(ks, usage, inout, skip, inlen, outlen); +} + +extern struct krb5_encryption_class * + krb5_find_encryption_class(int etype); +extern struct krb5_key_state * + krb5_create_key(const struct krb5_encryption_class *ec); +extern void krb5_free_key(struct krb5_key_state *ks); +extern struct krb5_key_state * + krb5_derive_key(struct krb5_key_state *inkey, + void *constant, size_t constantlen); +extern struct krb5_key_state * + krb5_get_encryption_key(struct krb5_key_state *basekey, int usage); +extern struct krb5_key_state * + krb5_get_integrity_key(struct krb5_key_state *basekey, int usage); +extern struct krb5_key_state * + krb5_get_checksum_key(struct krb5_key_state *basekey, int usage); diff --git a/sys/kgssapi/krb5/kcrypto_aes.c b/sys/kgssapi/krb5/kcrypto_aes.c new file mode 100644 index 0000000..d2dac21 --- /dev/null +++ b/sys/kgssapi/krb5/kcrypto_aes.c @@ -0,0 +1,384 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kcrypto.h" + +struct aes_state { + struct mtx as_lock; + uint64_t as_session; +}; + +static void +aes_init(struct krb5_key_state *ks) +{ + struct aes_state *as; + + as = malloc(sizeof(struct aes_state), M_GSSAPI, M_WAITOK|M_ZERO); + mtx_init(&as->as_lock, "gss aes lock", NULL, MTX_DEF); + ks->ks_priv = as; +} + +static void +aes_destroy(struct krb5_key_state *ks) +{ + struct aes_state *as = ks->ks_priv; + + if (as->as_session) + crypto_freesession(as->as_session); + mtx_destroy(&as->as_lock); + free(ks->ks_priv, M_GSSAPI); +} + +static void +aes_set_key(struct krb5_key_state *ks, const void *in) +{ + void *kp = ks->ks_key; + struct aes_state *as = ks->ks_priv; + struct cryptoini cri[2]; + + if (kp != in) + bcopy(in, kp, ks->ks_class->ec_keylen); + + if (as->as_session) + crypto_freesession(as->as_session); + + bzero(cri, sizeof(cri)); + + /* + * We only want the first 96 bits of the HMAC. + */ + cri[0].cri_alg = CRYPTO_SHA1_HMAC; + cri[0].cri_klen = ks->ks_class->ec_keybits; + cri[0].cri_mlen = 12; + cri[0].cri_key = ks->ks_key; + cri[0].cri_next = &cri[1]; + + cri[1].cri_alg = CRYPTO_AES_CBC; + cri[1].cri_klen = ks->ks_class->ec_keybits; + cri[1].cri_mlen = 0; + cri[1].cri_key = ks->ks_key; + cri[1].cri_next = NULL; + + crypto_newsession(&as->as_session, cri, + CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE); +} + +static void +aes_random_to_key(struct krb5_key_state *ks, const void *in) +{ + + aes_set_key(ks, in); +} + +static int +aes_crypto_cb(struct cryptop *crp) +{ + int error; + struct aes_state *as = (struct aes_state *) crp->crp_opaque; + + if (CRYPTO_SESID2CAPS(as->as_session) & CRYPTOCAP_F_SYNC) + return (0); + + error = crp->crp_etype; + if (error == EAGAIN) + error = crypto_dispatch(crp); + mtx_lock(&as->as_lock); + if (error || (crp->crp_flags & CRYPTO_F_DONE)) + wakeup(crp); + mtx_unlock(&as->as_lock); + + return (0); +} + +static void +aes_encrypt_1(const struct krb5_key_state *ks, int buftype, void *buf, + size_t skip, size_t len, void *ivec, int encdec) +{ + struct aes_state *as = ks->ks_priv; + struct cryptop *crp; + struct cryptodesc *crd; + int error; + + crp = crypto_getreq(1); + crd = crp->crp_desc; + + crd->crd_skip = skip; + crd->crd_len = len; + crd->crd_flags = CRD_F_IV_EXPLICIT | CRD_F_IV_PRESENT | encdec; + if (ivec) { + bcopy(ivec, crd->crd_iv, 16); + } else { + bzero(crd->crd_iv, 16); + } + crd->crd_next = NULL; + crd->crd_alg = CRYPTO_AES_CBC; + + crp->crp_sid = as->as_session; + crp->crp_flags = buftype | CRYPTO_F_CBIFSYNC; + crp->crp_buf = buf; + crp->crp_opaque = (void *) as; + crp->crp_callback = aes_crypto_cb; + + error = crypto_dispatch(crp); + + if ((CRYPTO_SESID2CAPS(as->as_session) & CRYPTOCAP_F_SYNC) == 0) { + mtx_lock(&as->as_lock); + if (!error && !(crp->crp_flags & CRYPTO_F_DONE)) + error = msleep(crp, &as->as_lock, 0, "gssaes", 0); + mtx_unlock(&as->as_lock); + } + + crypto_freereq(crp); +} + +static void +aes_encrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + size_t blocklen = 16, plen; + struct { + uint8_t cn_1[16], cn[16]; + } last2; + int i, off; + + /* + * AES encryption with cyphertext stealing: + * + * CTSencrypt(P[0], ..., P[n], IV, K): + * len = length(P[n]) + * (C[0], ..., C[n-2], E[n-1]) = + * CBCencrypt(P[0], ..., P[n-1], IV, K) + * P = pad(P[n], 0, blocksize) + * E[n] = CBCencrypt(P, E[n-1], K); + * C[n-1] = E[n] + * C[n] = E[n-1]{0..len-1} + */ + plen = len % blocklen; + if (len == blocklen) { + /* + * Note: caller will ensure len >= blocklen. + */ + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len, ivec, + CRD_F_ENCRYPT); + } else if (plen == 0) { + /* + * This is equivalent to CBC mode followed by swapping + * the last two blocks. We assume that neither of the + * last two blocks cross iov boundaries. + */ + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len, ivec, + CRD_F_ENCRYPT); + off = skip + len - 2 * blocklen; + m_copydata(inout, off, 2 * blocklen, (void*) &last2); + m_copyback(inout, off, blocklen, last2.cn); + m_copyback(inout, off + blocklen, blocklen, last2.cn_1); + } else { + /* + * This is the difficult case. We encrypt all but the + * last partial block first. We then create a padded + * copy of the last block and encrypt that using the + * second to last encrypted block as IV. Once we have + * the encrypted versions of the last two blocks, we + * reshuffle to create the final result. + */ + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len - plen, + ivec, CRD_F_ENCRYPT); + + /* + * Copy out the last two blocks, pad the last block + * and encrypt it. Rearrange to get the final + * result. The cyphertext for cn_1 is in cn. The + * cyphertext for cn is the first plen bytes of what + * is in cn_1 now. + */ + off = skip + len - blocklen - plen; + m_copydata(inout, off, blocklen + plen, (void*) &last2); + for (i = plen; i < blocklen; i++) + last2.cn[i] = 0; + aes_encrypt_1(ks, 0, last2.cn, 0, blocklen, last2.cn_1, + CRD_F_ENCRYPT); + m_copyback(inout, off, blocklen, last2.cn); + m_copyback(inout, off + blocklen, plen, last2.cn_1); + } +} + +static void +aes_decrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + size_t blocklen = 16, plen; + struct { + uint8_t cn_1[16], cn[16]; + } last2; + int i, off, t; + + /* + * AES decryption with cyphertext stealing: + * + * CTSencrypt(C[0], ..., C[n], IV, K): + * len = length(C[n]) + * E[n] = C[n-1] + * X = decrypt(E[n], K) + * P[n] = (X ^ C[n]){0..len-1} + * E[n-1] = {C[n,0],...,C[n,len-1],X[len],...,X[blocksize-1]} + * (P[0],...,P[n-1]) = CBCdecrypt(C[0],...,C[n-2],E[n-1], IV, K) + */ + plen = len % blocklen; + if (len == blocklen) { + /* + * Note: caller will ensure len >= blocklen. + */ + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len, ivec, 0); + } else if (plen == 0) { + /* + * This is equivalent to CBC mode followed by swapping + * the last two blocks. + */ + off = skip + len - 2 * blocklen; + m_copydata(inout, off, 2 * blocklen, (void*) &last2); + m_copyback(inout, off, blocklen, last2.cn); + m_copyback(inout, off + blocklen, blocklen, last2.cn_1); + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len, ivec, 0); + } else { + /* + * This is the difficult case. We first decrypt the + * second to last block with a zero IV to make X. The + * plaintext for the last block is the XOR of X and + * the last cyphertext block. + * + * We derive a new cypher text for the second to last + * block by mixing the unused bytes of X with the last + * cyphertext block. The result of that can be + * decrypted with the rest in CBC mode. + */ + off = skip + len - plen - blocklen; + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, off, blocklen, + NULL, 0); + m_copydata(inout, off, blocklen + plen, (void*) &last2); + + for (i = 0; i < plen; i++) { + t = last2.cn[i]; + last2.cn[i] ^= last2.cn_1[i]; + last2.cn_1[i] = t; + } + + m_copyback(inout, off, blocklen + plen, (void*) &last2); + aes_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len - plen, + ivec, 0); + } + +} + +static void +aes_checksum(const struct krb5_key_state *ks, int usage, + struct mbuf *inout, size_t skip, size_t inlen, size_t outlen) +{ + struct aes_state *as = ks->ks_priv; + struct cryptop *crp; + struct cryptodesc *crd; + int error; + + crp = crypto_getreq(1); + crd = crp->crp_desc; + + crd->crd_skip = skip; + crd->crd_len = inlen; + crd->crd_inject = skip + inlen; + crd->crd_flags = 0; + crd->crd_next = NULL; + crd->crd_alg = CRYPTO_SHA1_HMAC; + + crp->crp_sid = as->as_session; + crp->crp_ilen = inlen; + crp->crp_olen = 12; + crp->crp_etype = 0; + crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; + crp->crp_buf = (void *) inout; + crp->crp_opaque = (void *) as; + crp->crp_callback = aes_crypto_cb; + + error = crypto_dispatch(crp); + + if ((CRYPTO_SESID2CAPS(as->as_session) & CRYPTOCAP_F_SYNC) == 0) { + mtx_lock(&as->as_lock); + if (!error && !(crp->crp_flags & CRYPTO_F_DONE)) + error = msleep(crp, &as->as_lock, 0, "gssaes", 0); + mtx_unlock(&as->as_lock); + } + + crypto_freereq(crp); +} + +struct krb5_encryption_class krb5_aes128_encryption_class = { + "aes128-cts-hmac-sha1-96", /* name */ + ETYPE_AES128_CTS_HMAC_SHA1_96, /* etype */ + EC_DERIVED_KEYS, /* flags */ + 16, /* blocklen */ + 1, /* msgblocklen */ + 12, /* checksumlen */ + 128, /* keybits */ + 16, /* keylen */ + aes_init, + aes_destroy, + aes_set_key, + aes_random_to_key, + aes_encrypt, + aes_decrypt, + aes_checksum +}; + +struct krb5_encryption_class krb5_aes256_encryption_class = { + "aes256-cts-hmac-sha1-96", /* name */ + ETYPE_AES256_CTS_HMAC_SHA1_96, /* etype */ + EC_DERIVED_KEYS, /* flags */ + 16, /* blocklen */ + 1, /* msgblocklen */ + 12, /* checksumlen */ + 256, /* keybits */ + 32, /* keylen */ + aes_init, + aes_destroy, + aes_set_key, + aes_random_to_key, + aes_encrypt, + aes_decrypt, + aes_checksum +}; diff --git a/sys/kgssapi/krb5/kcrypto_arcfour.c b/sys/kgssapi/krb5/kcrypto_arcfour.c new file mode 100644 index 0000000..d672186 --- /dev/null +++ b/sys/kgssapi/krb5/kcrypto_arcfour.c @@ -0,0 +1,220 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kcrypto.h" + +static void +arcfour_init(struct krb5_key_state *ks) +{ + + ks->ks_priv = NULL; +} + +static void +arcfour_destroy(struct krb5_key_state *ks) +{ + +} + +static void +arcfour_set_key(struct krb5_key_state *ks, const void *in) +{ + void *kp = ks->ks_key; + + if (kp != in) + bcopy(in, kp, 16); +} + +static void +arcfour_random_to_key(struct krb5_key_state *ks, const void *in) +{ + + arcfour_set_key(ks, in); +} + +static void +arcfour_hmac(uint8_t *key, uint8_t *data, size_t datalen, + uint8_t *result) +{ + uint8_t buf[64]; + MD5_CTX md5; + int i; + + for (i = 0; i < 16; i++) + buf[i] = key[i] ^ 0x36; + for (; i < 64; i++) + buf[i] = 0x36; + + MD5Init(&md5); + MD5Update(&md5, buf, 64); + MD5Update(&md5, data, datalen); + MD5Final(result, &md5); + + for (i = 0; i < 16; i++) + buf[i] = key[i] ^ 0x5c; + for (; i < 64; i++) + buf[i] = 0x5c; + + MD5Init(&md5); + MD5Update(&md5, buf, 64); + MD5Update(&md5, result, 16); + MD5Final(result, &md5); +} + +static void +arcfour_derive_key(const struct krb5_key_state *ks, uint32_t usage, + uint8_t *newkey) +{ + uint8_t t[4]; + + t[0] = (usage >> 24); + t[1] = (usage >> 16); + t[2] = (usage >> 8); + t[3] = (usage >> 0); + if (ks->ks_class->ec_type == ETYPE_ARCFOUR_HMAC_MD5_56) { + uint8_t L40[14] = "fortybits"; + bcopy(t, L40 + 10, 4); + arcfour_hmac(ks->ks_key, L40, 14, newkey); + memset(newkey + 7, 0xab, 9); + } else { + arcfour_hmac(ks->ks_key, t, 4, newkey); + } +} + +static int +rc4_crypt_int(void *rs, void *buf, u_int len) +{ + + rc4_crypt(rs, buf, buf, len); + return (0); +} + +static void +arcfour_encrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + struct rc4_state rs; + uint8_t newkey[16]; + + arcfour_derive_key(ks, 0, newkey); + + /* + * If we have an IV, then generate a new key from it using HMAC. + */ + if (ivec) { + uint8_t kk[16]; + arcfour_hmac(newkey, ivec, ivlen, kk); + rc4_init(&rs, kk, 16); + } else { + rc4_init(&rs, newkey, 16); + } + + m_apply(inout, skip, len, rc4_crypt_int, &rs); +} + +static int +MD5Update_int(void *ctx, void *buf, u_int len) +{ + + MD5Update(ctx, buf, len); + return (0); +} + +static void +arcfour_checksum(const struct krb5_key_state *ks, int usage, + struct mbuf *inout, size_t skip, size_t inlen, size_t outlen) +{ + MD5_CTX md5; + uint8_t Ksign[16]; + uint8_t t[4]; + uint8_t sgn_cksum[16]; + + arcfour_hmac(ks->ks_key, "signaturekey", 13, Ksign); + + t[0] = usage >> 0; + t[1] = usage >> 8; + t[2] = usage >> 16; + t[3] = usage >> 24; + + MD5Init(&md5); + MD5Update(&md5, t, 4); + m_apply(inout, skip, inlen, MD5Update_int, &md5); + MD5Final(sgn_cksum, &md5); + + arcfour_hmac(Ksign, sgn_cksum, 16, sgn_cksum); + m_copyback(inout, skip + inlen, outlen, sgn_cksum); +} + +struct krb5_encryption_class krb5_arcfour_encryption_class = { + "arcfour-hmac-md5", /* name */ + ETYPE_ARCFOUR_HMAC_MD5, /* etype */ + 0, /* flags */ + 1, /* blocklen */ + 1, /* msgblocklen */ + 8, /* checksumlen */ + 128, /* keybits */ + 16, /* keylen */ + arcfour_init, + arcfour_destroy, + arcfour_set_key, + arcfour_random_to_key, + arcfour_encrypt, + arcfour_encrypt, + arcfour_checksum +}; + +struct krb5_encryption_class krb5_arcfour_56_encryption_class = { + "arcfour-hmac-md5-56", /* name */ + ETYPE_ARCFOUR_HMAC_MD5_56, /* etype */ + 0, /* flags */ + 1, /* blocklen */ + 1, /* msgblocklen */ + 8, /* checksumlen */ + 128, /* keybits */ + 16, /* keylen */ + arcfour_init, + arcfour_destroy, + arcfour_set_key, + arcfour_random_to_key, + arcfour_encrypt, + arcfour_encrypt, + arcfour_checksum +}; diff --git a/sys/kgssapi/krb5/kcrypto_des.c b/sys/kgssapi/krb5/kcrypto_des.c new file mode 100644 index 0000000..3958897 --- /dev/null +++ b/sys/kgssapi/krb5/kcrypto_des.c @@ -0,0 +1,262 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kcrypto.h" + +struct des1_state { + struct mtx ds_lock; + uint64_t ds_session; +}; + +static void +des1_init(struct krb5_key_state *ks) +{ + struct des1_state *ds; + + ds = malloc(sizeof(struct des1_state), M_GSSAPI, M_WAITOK|M_ZERO); + mtx_init(&ds->ds_lock, "gss des lock", NULL, MTX_DEF); + ks->ks_priv = ds; +} + +static void +des1_destroy(struct krb5_key_state *ks) +{ + struct des1_state *ds = ks->ks_priv; + + if (ds->ds_session) + crypto_freesession(ds->ds_session); + mtx_destroy(&ds->ds_lock); + free(ks->ks_priv, M_GSSAPI); + +} + +static void +des1_set_key(struct krb5_key_state *ks, const void *in) +{ + void *kp = ks->ks_key; + struct des1_state *ds = ks->ks_priv; + struct cryptoini cri[1]; + + if (kp != in) + bcopy(in, kp, ks->ks_class->ec_keylen); + + if (ds->ds_session) + crypto_freesession(ds->ds_session); + + bzero(cri, sizeof(cri)); + + cri[0].cri_alg = CRYPTO_DES_CBC; + cri[0].cri_klen = 64; + cri[0].cri_mlen = 0; + cri[0].cri_key = ks->ks_key; + cri[0].cri_next = NULL; + + crypto_newsession(&ds->ds_session, cri, + CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE); +} + +static void +des1_random_to_key(struct krb5_key_state *ks, const void *in) +{ + uint8_t *outkey = ks->ks_key; + const uint8_t *inkey = in; + + /* + * Expand 56 bits of random data to 64 bits as follows + * (in the example, bit number 1 is the MSB of the 56 + * bits of random data): + * + * expanded = + * 1 2 3 4 5 6 7 p + * 9 10 11 12 13 14 15 p + * 17 18 19 20 21 22 23 p + * 25 26 27 28 29 30 31 p + * 33 34 35 36 37 38 39 p + * 41 42 43 44 45 46 47 p + * 49 50 51 52 53 54 55 p + * 56 48 40 32 24 16 8 p + */ + outkey[0] = inkey[0]; + outkey[1] = inkey[1]; + outkey[2] = inkey[2]; + outkey[3] = inkey[3]; + outkey[4] = inkey[4]; + outkey[5] = inkey[5]; + outkey[6] = inkey[6]; + outkey[7] = (((inkey[0] & 1) << 1) + | ((inkey[1] & 1) << 2) + | ((inkey[2] & 1) << 3) + | ((inkey[3] & 1) << 4) + | ((inkey[4] & 1) << 5) + | ((inkey[5] & 1) << 6) + | ((inkey[6] & 1) << 7)); + des_set_odd_parity((des_cblock *) outkey); + if (des_is_weak_key((des_cblock *) outkey)) + outkey[7] ^= 0xf0; + + des1_set_key(ks, ks->ks_key); +} + +static int +des1_crypto_cb(struct cryptop *crp) +{ + int error; + struct des1_state *ds = (struct des1_state *) crp->crp_opaque; + + if (CRYPTO_SESID2CAPS(ds->ds_session) & CRYPTOCAP_F_SYNC) + return (0); + + error = crp->crp_etype; + if (error == EAGAIN) + error = crypto_dispatch(crp); + mtx_lock(&ds->ds_lock); + if (error || (crp->crp_flags & CRYPTO_F_DONE)) + wakeup(crp); + mtx_unlock(&ds->ds_lock); + + return (0); +} + +static void +des1_encrypt_1(const struct krb5_key_state *ks, int buftype, void *buf, + size_t skip, size_t len, void *ivec, int encdec) +{ + struct des1_state *ds = ks->ks_priv; + struct cryptop *crp; + struct cryptodesc *crd; + int error; + + crp = crypto_getreq(1); + crd = crp->crp_desc; + + crd->crd_skip = skip; + crd->crd_len = len; + crd->crd_flags = CRD_F_IV_EXPLICIT | CRD_F_IV_PRESENT | encdec; + if (ivec) { + bcopy(ivec, crd->crd_iv, 8); + } else { + bzero(crd->crd_iv, 8); + } + crd->crd_next = NULL; + crd->crd_alg = CRYPTO_DES_CBC; + + crp->crp_sid = ds->ds_session; + crp->crp_flags = buftype | CRYPTO_F_CBIFSYNC; + crp->crp_buf = buf; + crp->crp_opaque = (void *) ds; + crp->crp_callback = des1_crypto_cb; + + error = crypto_dispatch(crp); + + if ((CRYPTO_SESID2CAPS(ds->ds_session) & CRYPTOCAP_F_SYNC) == 0) { + mtx_lock(&ds->ds_lock); + if (!error && !(crp->crp_flags & CRYPTO_F_DONE)) + error = msleep(crp, &ds->ds_lock, 0, "gssdes", 0); + mtx_unlock(&ds->ds_lock); + } + + crypto_freereq(crp); +} + +static void +des1_encrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + + des1_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len, ivec, + CRD_F_ENCRYPT); +} + +static void +des1_decrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + + des1_encrypt_1(ks, CRYPTO_F_IMBUF, inout, skip, len, ivec, 0); +} + +static int +MD5Update_int(void *ctx, void *buf, u_int len) +{ + + MD5Update(ctx, buf, len); + return (0); +} + +static void +des1_checksum(const struct krb5_key_state *ks, int usage, + struct mbuf *inout, size_t skip, size_t inlen, size_t outlen) +{ + char hash[16]; + MD5_CTX md5; + + /* + * This checksum is specifically for GSS-API. First take the + * MD5 checksum of the message, then calculate the CBC mode + * checksum of that MD5 checksum using a zero IV. + */ + MD5Init(&md5); + m_apply(inout, skip, inlen, MD5Update_int, &md5); + MD5Final(hash, &md5); + + des1_encrypt_1(ks, 0, hash, 0, 16, NULL, CRD_F_ENCRYPT); + m_copyback(inout, skip + inlen, outlen, hash + 8); +} + +struct krb5_encryption_class krb5_des_encryption_class = { + "des-cbc-md5", /* name */ + ETYPE_DES_CBC_CRC, /* etype */ + 0, /* flags */ + 8, /* blocklen */ + 8, /* msgblocklen */ + 8, /* checksumlen */ + 56, /* keybits */ + 8, /* keylen */ + des1_init, + des1_destroy, + des1_set_key, + des1_random_to_key, + des1_encrypt, + des1_decrypt, + des1_checksum +}; diff --git a/sys/kgssapi/krb5/kcrypto_des3.c b/sys/kgssapi/krb5/kcrypto_des3.c new file mode 100644 index 0000000..ea39c10 --- /dev/null +++ b/sys/kgssapi/krb5/kcrypto_des3.c @@ -0,0 +1,402 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kcrypto.h" + +#define DES3_FLAGS (CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE) + +struct des3_state { + struct mtx ds_lock; + uint64_t ds_session; +}; + +static void +des3_init(struct krb5_key_state *ks) +{ + struct des3_state *ds; + + ds = malloc(sizeof(struct des3_state), M_GSSAPI, M_WAITOK|M_ZERO); + mtx_init(&ds->ds_lock, "gss des3 lock", NULL, MTX_DEF); + ks->ks_priv = ds; +} + +static void +des3_destroy(struct krb5_key_state *ks) +{ + struct des3_state *ds = ks->ks_priv; + + if (ds->ds_session) + crypto_freesession(ds->ds_session); + mtx_destroy(&ds->ds_lock); + free(ks->ks_priv, M_GSSAPI); +} + +static void +des3_set_key(struct krb5_key_state *ks, const void *in) +{ + void *kp = ks->ks_key; + struct des3_state *ds = ks->ks_priv; + struct cryptoini cri[2]; + + if (kp != in) + bcopy(in, kp, ks->ks_class->ec_keylen); + + if (ds->ds_session) + crypto_freesession(ds->ds_session); + + bzero(cri, sizeof(cri)); + + cri[0].cri_alg = CRYPTO_SHA1_HMAC; + cri[0].cri_klen = 192; + cri[0].cri_mlen = 0; + cri[0].cri_key = ks->ks_key; + cri[0].cri_next = &cri[1]; + + cri[1].cri_alg = CRYPTO_3DES_CBC; + cri[1].cri_klen = 192; + cri[1].cri_mlen = 0; + cri[1].cri_key = ks->ks_key; + cri[1].cri_next = NULL; + + crypto_newsession(&ds->ds_session, cri, + CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE); +} + +static void +des3_random_to_key(struct krb5_key_state *ks, const void *in) +{ + uint8_t *outkey; + const uint8_t *inkey; + int subkey; + + for (subkey = 0, outkey = ks->ks_key, inkey = in; subkey < 3; + subkey++, outkey += 8, inkey += 7) { + /* + * Expand 56 bits of random data to 64 bits as follows + * (in the example, bit number 1 is the MSB of the 56 + * bits of random data): + * + * expanded = + * 1 2 3 4 5 6 7 p + * 9 10 11 12 13 14 15 p + * 17 18 19 20 21 22 23 p + * 25 26 27 28 29 30 31 p + * 33 34 35 36 37 38 39 p + * 41 42 43 44 45 46 47 p + * 49 50 51 52 53 54 55 p + * 56 48 40 32 24 16 8 p + */ + outkey[0] = inkey[0]; + outkey[1] = inkey[1]; + outkey[2] = inkey[2]; + outkey[3] = inkey[3]; + outkey[4] = inkey[4]; + outkey[5] = inkey[5]; + outkey[6] = inkey[6]; + outkey[7] = (((inkey[0] & 1) << 1) + | ((inkey[1] & 1) << 2) + | ((inkey[2] & 1) << 3) + | ((inkey[3] & 1) << 4) + | ((inkey[4] & 1) << 5) + | ((inkey[5] & 1) << 6) + | ((inkey[6] & 1) << 7)); + des_set_odd_parity((des_cblock *) outkey); + if (des_is_weak_key((des_cblock *) outkey)) + outkey[7] ^= 0xf0; + } + + des3_set_key(ks, ks->ks_key); +} + +static int +des3_crypto_cb(struct cryptop *crp) +{ + int error; + struct des3_state *ds = (struct des3_state *) crp->crp_opaque; + + if (CRYPTO_SESID2CAPS(ds->ds_session) & CRYPTOCAP_F_SYNC) + return (0); + + error = crp->crp_etype; + if (error == EAGAIN) + error = crypto_dispatch(crp); + mtx_lock(&ds->ds_lock); + if (error || (crp->crp_flags & CRYPTO_F_DONE)) + wakeup(crp); + mtx_unlock(&ds->ds_lock); + + return (0); +} + +static void +des3_encrypt_1(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, int encdec) +{ + struct des3_state *ds = ks->ks_priv; + struct cryptop *crp; + struct cryptodesc *crd; + int error; + + crp = crypto_getreq(1); + crd = crp->crp_desc; + + crd->crd_skip = skip; + crd->crd_len = len; + crd->crd_flags = CRD_F_IV_EXPLICIT | CRD_F_IV_PRESENT | encdec; + if (ivec) { + bcopy(ivec, crd->crd_iv, 8); + } else { + bzero(crd->crd_iv, 8); + } + crd->crd_next = NULL; + crd->crd_alg = CRYPTO_3DES_CBC; + + crp->crp_sid = ds->ds_session; + crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; + crp->crp_buf = (void *) inout; + crp->crp_opaque = (void *) ds; + crp->crp_callback = des3_crypto_cb; + + error = crypto_dispatch(crp); + + if ((CRYPTO_SESID2CAPS(ds->ds_session) & CRYPTOCAP_F_SYNC) == 0) { + mtx_lock(&ds->ds_lock); + if (!error && !(crp->crp_flags & CRYPTO_F_DONE)) + error = msleep(crp, &ds->ds_lock, 0, "gssdes3", 0); + mtx_unlock(&ds->ds_lock); + } + + crypto_freereq(crp); +} + +static void +des3_encrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + + des3_encrypt_1(ks, inout, skip, len, ivec, CRD_F_ENCRYPT); +} + +static void +des3_decrypt(const struct krb5_key_state *ks, struct mbuf *inout, + size_t skip, size_t len, void *ivec, size_t ivlen) +{ + + des3_encrypt_1(ks, inout, skip, len, ivec, 0); +} + +static void +des3_checksum(const struct krb5_key_state *ks, int usage, + struct mbuf *inout, size_t skip, size_t inlen, size_t outlen) +{ + struct des3_state *ds = ks->ks_priv; + struct cryptop *crp; + struct cryptodesc *crd; + int error; + + crp = crypto_getreq(1); + crd = crp->crp_desc; + + crd->crd_skip = skip; + crd->crd_len = inlen; + crd->crd_inject = skip + inlen; + crd->crd_flags = 0; + crd->crd_next = NULL; + crd->crd_alg = CRYPTO_SHA1_HMAC; + + crp->crp_sid = ds->ds_session; + crp->crp_ilen = inlen; + crp->crp_olen = 20; + crp->crp_etype = 0; + crp->crp_flags = CRYPTO_F_IMBUF | CRYPTO_F_CBIFSYNC; + crp->crp_buf = (void *) inout; + crp->crp_opaque = (void *) ds; + crp->crp_callback = des3_crypto_cb; + + error = crypto_dispatch(crp); + + if ((CRYPTO_SESID2CAPS(ds->ds_session) & CRYPTOCAP_F_SYNC) == 0) { + mtx_lock(&ds->ds_lock); + if (!error && !(crp->crp_flags & CRYPTO_F_DONE)) + error = msleep(crp, &ds->ds_lock, 0, "gssdes3", 0); + mtx_unlock(&ds->ds_lock); + } + + crypto_freereq(crp); +} + +struct krb5_encryption_class krb5_des3_encryption_class = { + "des3-cbc-sha1", /* name */ + ETYPE_DES3_CBC_SHA1, /* etype */ + EC_DERIVED_KEYS, /* flags */ + 8, /* blocklen */ + 8, /* msgblocklen */ + 20, /* checksumlen */ + 168, /* keybits */ + 24, /* keylen */ + des3_init, + des3_destroy, + des3_set_key, + des3_random_to_key, + des3_encrypt, + des3_decrypt, + des3_checksum +}; + +#if 0 +struct des3_dk_test { + uint8_t key[24]; + uint8_t usage[8]; + size_t usagelen; + uint8_t dk[24]; +}; +struct des3_dk_test tests[] = { + {{0xdc, 0xe0, 0x6b, 0x1f, 0x64, 0xc8, 0x57, 0xa1, 0x1c, 0x3d, 0xb5, + 0x7c, 0x51, 0x89, 0x9b, 0x2c, 0xc1, 0x79, 0x10, 0x08, 0xce, 0x97, + 0x3b, 0x92}, + {0x00, 0x00, 0x00, 0x01, 0x55}, 5, + {0x92, 0x51, 0x79, 0xd0, 0x45, 0x91, 0xa7, 0x9b, 0x5d, 0x31, 0x92, + 0xc4, 0xa7, 0xe9, 0xc2, 0x89, 0xb0, 0x49, 0xc7, 0x1f, 0x6e, 0xe6, + 0x04, 0xcd}}, + + {{0x5e, 0x13, 0xd3, 0x1c, 0x70, 0xef, 0x76, 0x57, 0x46, 0x57, 0x85, + 0x31, 0xcb, 0x51, 0xc1, 0x5b, 0xf1, 0x1c, 0xa8, 0x2c, 0x97, 0xce, + 0xe9, 0xf2}, + {0x00, 0x00, 0x00, 0x01, 0xaa}, 5, + {0x9e, 0x58, 0xe5, 0xa1, 0x46, 0xd9, 0x94, 0x2a, 0x10, 0x1c, 0x46, + 0x98, 0x45, 0xd6, 0x7a, 0x20, 0xe3, 0xc4, 0x25, 0x9e, 0xd9, 0x13, + 0xf2, 0x07}}, + + {{0x98, 0xe6, 0xfd, 0x8a, 0x04, 0xa4, 0xb6, 0x85, 0x9b, 0x75, 0xa1, + 0x76, 0x54, 0x0b, 0x97, 0x52, 0xba, 0xd3, 0xec, 0xd6, 0x10, 0xa2, + 0x52, 0xbc}, + {0x00, 0x00, 0x00, 0x01, 0x55}, 5, + {0x13, 0xfe, 0xf8, 0x0d, 0x76, 0x3e, 0x94, 0xec, 0x6d, 0x13, 0xfd, + 0x2c, 0xa1, 0xd0, 0x85, 0x07, 0x02, 0x49, 0xda, 0xd3, 0x98, 0x08, + 0xea, 0xbf}}, + + {{0x62, 0x2a, 0xec, 0x25, 0xa2, 0xfe, 0x2c, 0xad, 0x70, 0x94, 0x68, + 0x0b, 0x7c, 0x64, 0x94, 0x02, 0x80, 0x08, 0x4c, 0x1a, 0x7c, 0xec, + 0x92, 0xb5}, + {0x00, 0x00, 0x00, 0x01, 0xaa}, 5, + {0xf8, 0xdf, 0xbf, 0x04, 0xb0, 0x97, 0xe6, 0xd9, 0xdc, 0x07, 0x02, + 0x68, 0x6b, 0xcb, 0x34, 0x89, 0xd9, 0x1f, 0xd9, 0xa4, 0x51, 0x6b, + 0x70, 0x3e}}, + + {{0xd3, 0xf8, 0x29, 0x8c, 0xcb, 0x16, 0x64, 0x38, 0xdc, 0xb9, 0xb9, + 0x3e, 0xe5, 0xa7, 0x62, 0x92, 0x86, 0xa4, 0x91, 0xf8, 0x38, 0xf8, + 0x02, 0xfb}, + {0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73}, 8, + {0x23, 0x70, 0xda, 0x57, 0x5d, 0x2a, 0x3d, 0xa8, 0x64, 0xce, 0xbf, + 0xdc, 0x52, 0x04, 0xd5, 0x6d, 0xf7, 0x79, 0xa7, 0xdf, 0x43, 0xd9, + 0xda, 0x43}}, + + {{0xc1, 0x08, 0x16, 0x49, 0xad, 0xa7, 0x43, 0x62, 0xe6, 0xa1, 0x45, + 0x9d, 0x01, 0xdf, 0xd3, 0x0d, 0x67, 0xc2, 0x23, 0x4c, 0x94, 0x07, + 0x04, 0xda}, + {0x00, 0x00, 0x00, 0x01, 0x55}, 5, + {0x34, 0x80, 0x57, 0xec, 0x98, 0xfd, 0xc4, 0x80, 0x16, 0x16, 0x1c, + 0x2a, 0x4c, 0x7a, 0x94, 0x3e, 0x92, 0xae, 0x49, 0x2c, 0x98, 0x91, + 0x75, 0xf7}}, + + {{0x5d, 0x15, 0x4a, 0xf2, 0x38, 0xf4, 0x67, 0x13, 0x15, 0x57, 0x19, + 0xd5, 0x5e, 0x2f, 0x1f, 0x79, 0x0d, 0xd6, 0x61, 0xf2, 0x79, 0xa7, + 0x91, 0x7c}, + {0x00, 0x00, 0x00, 0x01, 0xaa}, 5, + {0xa8, 0x80, 0x8a, 0xc2, 0x67, 0xda, 0xda, 0x3d, 0xcb, 0xe9, 0xa7, + 0xc8, 0x46, 0x26, 0xfb, 0xc7, 0x61, 0xc2, 0x94, 0xb0, 0x13, 0x15, + 0xe5, 0xc1}}, + + {{0x79, 0x85, 0x62, 0xe0, 0x49, 0x85, 0x2f, 0x57, 0xdc, 0x8c, 0x34, + 0x3b, 0xa1, 0x7f, 0x2c, 0xa1, 0xd9, 0x73, 0x94, 0xef, 0xc8, 0xad, + 0xc4, 0x43}, + {0x00, 0x00, 0x00, 0x01, 0x55}, 5, + {0xc8, 0x13, 0xf8, 0x8a, 0x3b, 0xe3, 0xb3, 0x34, 0xf7, 0x54, 0x25, + 0xce, 0x91, 0x75, 0xfb, 0xe3, 0xc8, 0x49, 0x3b, 0x89, 0xc8, 0x70, + 0x3b, 0x49}}, + + {{0x26, 0xdc, 0xe3, 0x34, 0xb5, 0x45, 0x29, 0x2f, 0x2f, 0xea, 0xb9, + 0xa8, 0x70, 0x1a, 0x89, 0xa4, 0xb9, 0x9e, 0xb9, 0x94, 0x2c, 0xec, + 0xd0, 0x16}, + {0x00, 0x00, 0x00, 0x01, 0xaa}, 5, + {0xf4, 0x8f, 0xfd, 0x6e, 0x83, 0xf8, 0x3e, 0x73, 0x54, 0xe6, 0x94, + 0xfd, 0x25, 0x2c, 0xf8, 0x3b, 0xfe, 0x58, 0xf7, 0xd5, 0xba, 0x37, + 0xec, 0x5d}}, +}; +#define N_TESTS (sizeof(tests) / sizeof(tests[0])) + +int +main(int argc, char **argv) +{ + struct krb5_key_state *key, *dk; + uint8_t *dkp; + int j, i; + + for (j = 0; j < N_TESTS; j++) { + struct des3_dk_test *t = &tests[j]; + key = krb5_create_key(&des3_encryption_class); + krb5_set_key(key, t->key); + dk = krb5_derive_key(key, t->usage, t->usagelen); + krb5_free_key(key); + if (memcmp(dk->ks_key, t->dk, 24)) { + printf("DES3 dk("); + for (i = 0; i < 24; i++) + printf("%02x", t->key[i]); + printf(", "); + for (i = 0; i < t->usagelen; i++) + printf("%02x", t->usage[i]); + printf(") failed\n"); + printf("should be: "); + for (i = 0; i < 24; i++) + printf("%02x", t->dk[i]); + printf("\n result was: "); + dkp = dk->ks_key; + for (i = 0; i < 24; i++) + printf("%02x", dkp[i]); + printf("\n"); + } + krb5_free_key(dk); + } + + return (0); +} +#endif diff --git a/sys/kgssapi/krb5/krb5_mech.c b/sys/kgssapi/krb5/krb5_mech.c new file mode 100644 index 0000000..2a1c0b6 --- /dev/null +++ b/sys/kgssapi/krb5/krb5_mech.c @@ -0,0 +1,2100 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include "opt_inet6.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kgss_if.h" +#include "kcrypto.h" + +#define GSS_TOKEN_SENT_BY_ACCEPTOR 1 +#define GSS_TOKEN_SEALED 2 +#define GSS_TOKEN_ACCEPTOR_SUBKEY 4 + +static gss_OID_desc krb5_mech_oid = +{9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; + +struct krb5_data { + size_t kd_length; + void *kd_data; +}; + +struct krb5_keyblock { + uint16_t kk_type; /* encryption type */ + struct krb5_data kk_key; /* key data */ +}; + +struct krb5_address { + uint16_t ka_type; + struct krb5_data ka_addr; +}; + +/* + * The km_elem array is ordered so that the highest received sequence + * number is listed first. + */ +struct krb5_msg_order { + uint32_t km_flags; + uint32_t km_start; + uint32_t km_length; + uint32_t km_jitter_window; + uint32_t km_first_seq; + uint32_t *km_elem; +}; + +struct krb5_context { + struct _gss_ctx_id_t kc_common; + struct mtx kc_lock; + uint32_t kc_ac_flags; + uint32_t kc_ctx_flags; + uint32_t kc_more_flags; +#define LOCAL 1 +#define OPEN 2 +#define COMPAT_OLD_DES3 4 +#define COMPAT_OLD_DES3_SELECTED 8 +#define ACCEPTOR_SUBKEY 16 + struct krb5_address kc_local_address; + struct krb5_address kc_remote_address; + uint16_t kc_local_port; + uint16_t kc_remote_port; + struct krb5_keyblock kc_keyblock; + struct krb5_keyblock kc_local_subkey; + struct krb5_keyblock kc_remote_subkey; + volatile uint32_t kc_local_seqnumber; + uint32_t kc_remote_seqnumber; + uint32_t kc_keytype; + uint32_t kc_cksumtype; + struct krb5_data kc_source_name; + struct krb5_data kc_target_name; + uint32_t kc_lifetime; + struct krb5_msg_order kc_msg_order; + struct krb5_key_state *kc_tokenkey; + struct krb5_key_state *kc_encryptkey; + struct krb5_key_state *kc_checksumkey; + + struct krb5_key_state *kc_send_seal_Ke; + struct krb5_key_state *kc_send_seal_Ki; + struct krb5_key_state *kc_send_seal_Kc; + struct krb5_key_state *kc_send_sign_Kc; + + struct krb5_key_state *kc_recv_seal_Ke; + struct krb5_key_state *kc_recv_seal_Ki; + struct krb5_key_state *kc_recv_seal_Kc; + struct krb5_key_state *kc_recv_sign_Kc; +}; + +static uint16_t +get_uint16(const uint8_t **pp, size_t *lenp) +{ + const uint8_t *p = *pp; + uint16_t v; + + if (*lenp < 2) + return (0); + + v = (p[0] << 8) | p[1]; + *pp = p + 2; + *lenp = *lenp - 2; + + return (v); +} + +static uint32_t +get_uint32(const uint8_t **pp, size_t *lenp) +{ + const uint8_t *p = *pp; + uint32_t v; + + if (*lenp < 4) + return (0); + + v = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + *pp = p + 4; + *lenp = *lenp - 4; + + return (v); +} + +static void +get_data(const uint8_t **pp, size_t *lenp, struct krb5_data *dp) +{ + size_t sz = get_uint32(pp, lenp); + + dp->kd_length = sz; + dp->kd_data = malloc(sz, M_GSSAPI, M_WAITOK); + + if (*lenp < sz) + sz = *lenp; + bcopy(*pp, dp->kd_data, sz); + (*pp) += sz; + (*lenp) -= sz; +} + +static void +delete_data(struct krb5_data *dp) +{ + if (dp->kd_data) { + free(dp->kd_data, M_GSSAPI); + dp->kd_length = 0; + dp->kd_data = NULL; + } +} + +static void +get_address(const uint8_t **pp, size_t *lenp, struct krb5_address *ka) +{ + + ka->ka_type = get_uint16(pp, lenp); + get_data(pp, lenp, &ka->ka_addr); +} + +static void +delete_address(struct krb5_address *ka) +{ + delete_data(&ka->ka_addr); +} + +static void +get_keyblock(const uint8_t **pp, size_t *lenp, struct krb5_keyblock *kk) +{ + + kk->kk_type = get_uint16(pp, lenp); + get_data(pp, lenp, &kk->kk_key); +} + +static void +delete_keyblock(struct krb5_keyblock *kk) +{ + if (kk->kk_key.kd_data) + bzero(kk->kk_key.kd_data, kk->kk_key.kd_length); + delete_data(&kk->kk_key); +} + +static void +copy_key(struct krb5_keyblock *from, struct krb5_keyblock **to) +{ + + if (from->kk_key.kd_length) + *to = from; + else + *to = NULL; +} + +/* + * Return non-zero if we are initiator. + */ +static __inline int +is_initiator(struct krb5_context *kc) +{ + return (kc->kc_more_flags & LOCAL); +} + +/* + * Return non-zero if we are acceptor. + */ +static __inline int +is_acceptor(struct krb5_context *kc) +{ + return !(kc->kc_more_flags & LOCAL); +} + +static void +get_initiator_subkey(struct krb5_context *kc, struct krb5_keyblock **kdp) +{ + + if (is_initiator(kc)) + copy_key(&kc->kc_local_subkey, kdp); + else + copy_key(&kc->kc_remote_subkey, kdp); + if (!*kdp) + copy_key(&kc->kc_keyblock, kdp); +} + +static void +get_acceptor_subkey(struct krb5_context *kc, struct krb5_keyblock **kdp) +{ + + if (is_initiator(kc)) + copy_key(&kc->kc_remote_subkey, kdp); + else + copy_key(&kc->kc_local_subkey, kdp); +} + +static OM_uint32 +get_keys(struct krb5_context *kc) +{ + struct krb5_keyblock *keydata; + struct krb5_encryption_class *ec; + struct krb5_key_state *key; + int etype; + + keydata = NULL; + get_acceptor_subkey(kc, &keydata); + if (!keydata) + if ((kc->kc_more_flags & ACCEPTOR_SUBKEY) == 0) + get_initiator_subkey(kc, &keydata); + if (!keydata) + return (GSS_S_FAILURE); + + /* + * GSS-API treats all DES etypes the same and all DES3 etypes + * the same. + */ + switch (keydata->kk_type) { + case ETYPE_DES_CBC_CRC: + case ETYPE_DES_CBC_MD4: + case ETYPE_DES_CBC_MD5: + etype = ETYPE_DES_CBC_CRC; + break; + + case ETYPE_DES3_CBC_MD5: + case ETYPE_DES3_CBC_SHA1: + case ETYPE_OLD_DES3_CBC_SHA1: + etype = ETYPE_DES3_CBC_SHA1; + + default: + etype = keydata->kk_type; + } + + ec = krb5_find_encryption_class(etype); + if (!ec) + return (GSS_S_FAILURE); + + key = krb5_create_key(ec); + krb5_set_key(key, keydata->kk_key.kd_data); + kc->kc_tokenkey = key; + + switch (etype) { + case ETYPE_DES_CBC_CRC: + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: { + /* + * Single DES and ARCFOUR uses a 'derived' key (XOR + * with 0xf0) for encrypting wrap tokens. The original + * key is used for checksums and sequence numbers. + */ + struct krb5_key_state *ekey; + uint8_t *ekp, *kp; + int i; + + ekey = krb5_create_key(ec); + ekp = ekey->ks_key; + kp = key->ks_key; + for (i = 0; i < ec->ec_keylen; i++) + ekp[i] = kp[i] ^ 0xf0; + krb5_set_key(ekey, ekp); + kc->kc_encryptkey = ekey; + refcount_acquire(&key->ks_refs); + kc->kc_checksumkey = key; + break; + } + + case ETYPE_DES3_CBC_SHA1: + /* + * Triple DES uses a RFC 3961 style derived key with + * usage number KG_USAGE_SIGN for checksums. The + * original key is used for encryption and sequence + * numbers. + */ + kc->kc_checksumkey = krb5_get_checksum_key(key, KG_USAGE_SIGN); + refcount_acquire(&key->ks_refs); + kc->kc_encryptkey = key; + break; + + default: + /* + * We need eight derived keys four for sending and + * four for receiving. + */ + if (is_initiator(kc)) { + /* + * We are initiator. + */ + kc->kc_send_seal_Ke = krb5_get_encryption_key(key, + KG_USAGE_INITIATOR_SEAL); + kc->kc_send_seal_Ki = krb5_get_integrity_key(key, + KG_USAGE_INITIATOR_SEAL); + kc->kc_send_seal_Kc = krb5_get_checksum_key(key, + KG_USAGE_INITIATOR_SEAL); + kc->kc_send_sign_Kc = krb5_get_checksum_key(key, + KG_USAGE_INITIATOR_SIGN); + + kc->kc_recv_seal_Ke = krb5_get_encryption_key(key, + KG_USAGE_ACCEPTOR_SEAL); + kc->kc_recv_seal_Ki = krb5_get_integrity_key(key, + KG_USAGE_ACCEPTOR_SEAL); + kc->kc_recv_seal_Kc = krb5_get_checksum_key(key, + KG_USAGE_ACCEPTOR_SEAL); + kc->kc_recv_sign_Kc = krb5_get_checksum_key(key, + KG_USAGE_ACCEPTOR_SIGN); + } else { + /* + * We are acceptor. + */ + kc->kc_send_seal_Ke = krb5_get_encryption_key(key, + KG_USAGE_ACCEPTOR_SEAL); + kc->kc_send_seal_Ki = krb5_get_integrity_key(key, + KG_USAGE_ACCEPTOR_SEAL); + kc->kc_send_seal_Kc = krb5_get_checksum_key(key, + KG_USAGE_ACCEPTOR_SEAL); + kc->kc_send_sign_Kc = krb5_get_checksum_key(key, + KG_USAGE_ACCEPTOR_SIGN); + + kc->kc_recv_seal_Ke = krb5_get_encryption_key(key, + KG_USAGE_INITIATOR_SEAL); + kc->kc_recv_seal_Ki = krb5_get_integrity_key(key, + KG_USAGE_INITIATOR_SEAL); + kc->kc_recv_seal_Kc = krb5_get_checksum_key(key, + KG_USAGE_INITIATOR_SEAL); + kc->kc_recv_sign_Kc = krb5_get_checksum_key(key, + KG_USAGE_INITIATOR_SIGN); + } + break; + } + + return (GSS_S_COMPLETE); +} + +static void +krb5_init(struct krb5_context *kc) +{ + + mtx_init(&kc->kc_lock, "krb5 gss lock", NULL, MTX_DEF); +} + +static OM_uint32 +krb5_import(struct krb5_context *kc, + enum sec_context_format format, + const gss_buffer_t context_token) +{ + OM_uint32 res; + const uint8_t *p = (const uint8_t *) context_token->value; + size_t len = context_token->length; + uint32_t flags; + int i; + + /* + * We support heimdal 0.6 and heimdal 1.1 + */ + if (format != KGSS_HEIMDAL_0_6 && format != KGSS_HEIMDAL_1_1) + return (GSS_S_DEFECTIVE_TOKEN); + +#define SC_LOCAL_ADDRESS 1 +#define SC_REMOTE_ADDRESS 2 +#define SC_KEYBLOCK 4 +#define SC_LOCAL_SUBKEY 8 +#define SC_REMOTE_SUBKEY 16 + + /* + * Ensure that the token starts with krb5 oid. + */ + if (p[0] != 0x00 || p[1] != krb5_mech_oid.length + || len < krb5_mech_oid.length + 2 + || bcmp(krb5_mech_oid.elements, p + 2, + krb5_mech_oid.length)) + return (GSS_S_DEFECTIVE_TOKEN); + p += krb5_mech_oid.length + 2; + len -= krb5_mech_oid.length + 2; + + flags = get_uint32(&p, &len); + kc->kc_ac_flags = get_uint32(&p, &len); + if (flags & SC_LOCAL_ADDRESS) + get_address(&p, &len, &kc->kc_local_address); + if (flags & SC_REMOTE_ADDRESS) + get_address(&p, &len, &kc->kc_remote_address); + kc->kc_local_port = get_uint16(&p, &len); + kc->kc_remote_port = get_uint16(&p, &len); + if (flags & SC_KEYBLOCK) + get_keyblock(&p, &len, &kc->kc_keyblock); + if (flags & SC_LOCAL_SUBKEY) + get_keyblock(&p, &len, &kc->kc_local_subkey); + if (flags & SC_REMOTE_SUBKEY) + get_keyblock(&p, &len, &kc->kc_remote_subkey); + kc->kc_local_seqnumber = get_uint32(&p, &len); + kc->kc_remote_seqnumber = get_uint32(&p, &len); + kc->kc_keytype = get_uint32(&p, &len); + kc->kc_cksumtype = get_uint32(&p, &len); + get_data(&p, &len, &kc->kc_source_name); + get_data(&p, &len, &kc->kc_target_name); + kc->kc_ctx_flags = get_uint32(&p, &len); + kc->kc_more_flags = get_uint32(&p, &len); + kc->kc_lifetime = get_uint32(&p, &len); + /* + * Heimdal 1.1 adds the message order stuff. + */ + if (format == KGSS_HEIMDAL_1_1) { + kc->kc_msg_order.km_flags = get_uint32(&p, &len); + kc->kc_msg_order.km_start = get_uint32(&p, &len); + kc->kc_msg_order.km_length = get_uint32(&p, &len); + kc->kc_msg_order.km_jitter_window = get_uint32(&p, &len); + kc->kc_msg_order.km_first_seq = get_uint32(&p, &len); + kc->kc_msg_order.km_elem = + malloc(kc->kc_msg_order.km_jitter_window * sizeof(uint32_t), + M_GSSAPI, M_WAITOK); + for (i = 0; i < kc->kc_msg_order.km_jitter_window; i++) + kc->kc_msg_order.km_elem[i] = get_uint32(&p, &len); + } else { + kc->kc_msg_order.km_flags = 0; + } + + res = get_keys(kc); + if (GSS_ERROR(res)) + return (res); + + /* + * We don't need these anymore. + */ + delete_keyblock(&kc->kc_keyblock); + delete_keyblock(&kc->kc_local_subkey); + delete_keyblock(&kc->kc_remote_subkey); + + return (GSS_S_COMPLETE); +} + +static void +krb5_delete(struct krb5_context *kc, gss_buffer_t output_token) +{ + + delete_address(&kc->kc_local_address); + delete_address(&kc->kc_remote_address); + delete_keyblock(&kc->kc_keyblock); + delete_keyblock(&kc->kc_local_subkey); + delete_keyblock(&kc->kc_remote_subkey); + delete_data(&kc->kc_source_name); + delete_data(&kc->kc_target_name); + if (kc->kc_msg_order.km_elem) + free(kc->kc_msg_order.km_elem, M_GSSAPI); + if (output_token) { + output_token->length = 0; + output_token->value = NULL; + } + if (kc->kc_tokenkey) { + krb5_free_key(kc->kc_tokenkey); + if (kc->kc_encryptkey) { + krb5_free_key(kc->kc_encryptkey); + krb5_free_key(kc->kc_checksumkey); + } else { + krb5_free_key(kc->kc_send_seal_Ke); + krb5_free_key(kc->kc_send_seal_Ki); + krb5_free_key(kc->kc_send_seal_Kc); + krb5_free_key(kc->kc_send_sign_Kc); + krb5_free_key(kc->kc_recv_seal_Ke); + krb5_free_key(kc->kc_recv_seal_Ki); + krb5_free_key(kc->kc_recv_seal_Kc); + krb5_free_key(kc->kc_recv_sign_Kc); + } + } + mtx_destroy(&kc->kc_lock); +} + +static gss_OID +krb5_mech_type(struct krb5_context *kc) +{ + + return (&krb5_mech_oid); +} + +/* + * Make a token with the given type and length (the length includes + * the TOK_ID), initialising the token header appropriately. Return a + * pointer to the TOK_ID of the token. A new mbuf is allocated with + * the framing header plus hlen bytes of space. + * + * Format is as follows: + * + * 0x60 [APPLICATION 0] SEQUENCE + * DER encoded length length of oid + type + inner token length + * 0x06 NN OID of mechanism type + * TT TT TOK_ID + * data for inner token + * + * 1: der encoded length + */ +static void * +krb5_make_token(char tok_id[2], size_t hlen, size_t len, struct mbuf **mp) +{ + size_t inside_len, len_len, tlen; + gss_OID oid = &krb5_mech_oid; + struct mbuf *m; + uint8_t *p; + + inside_len = 2 + oid->length + len; + if (inside_len < 128) + len_len = 1; + else if (inside_len < 0x100) + len_len = 2; + else if (inside_len < 0x10000) + len_len = 3; + else if (inside_len < 0x1000000) + len_len = 4; + else + len_len = 5; + + tlen = 1 + len_len + 2 + oid->length + hlen; + KASSERT(tlen <= MLEN, ("token head too large")); + MGET(m, M_WAITOK, MT_DATA); + M_ALIGN(m, tlen); + m->m_len = tlen; + + p = (uint8_t *) m->m_data; + *p++ = 0x60; + switch (len_len) { + case 1: + *p++ = inside_len; + break; + case 2: + *p++ = 0x81; + *p++ = inside_len; + break; + case 3: + *p++ = 0x82; + *p++ = inside_len >> 8; + *p++ = inside_len; + break; + case 4: + *p++ = 0x83; + *p++ = inside_len >> 16; + *p++ = inside_len >> 8; + *p++ = inside_len; + break; + case 5: + *p++ = 0x84; + *p++ = inside_len >> 24; + *p++ = inside_len >> 16; + *p++ = inside_len >> 8; + *p++ = inside_len; + break; + } + + *p++ = 0x06; + *p++ = oid->length; + bcopy(oid->elements, p, oid->length); + p += oid->length; + + p[0] = tok_id[0]; + p[1] = tok_id[1]; + + *mp = m; + + return (p); +} + +/* + * Verify a token, checking the inner token length and mechanism oid. + * pointer to the first byte of the TOK_ID. The length of the + * encapsulated data is checked to be at least len bytes; the actual + * length of the encapsulated data (including TOK_ID) is returned in + * *encap_len. + * + * If can_pullup is TRUE and the token header is fragmented, we will + * rearrange it. + * + * Format is as follows: + * + * 0x60 [APPLICATION 0] SEQUENCE + * DER encoded length length of oid + type + inner token length + * 0x06 NN OID of mechanism type + * TT TT TOK_ID + * data for inner token + * + * 1: der encoded length + */ +static void * +krb5_verify_token(char tok_id[2], size_t len, struct mbuf **mp, + size_t *encap_len, bool_t can_pullup) +{ + struct mbuf *m; + size_t tlen, hlen, len_len, inside_len; + gss_OID oid = &krb5_mech_oid; + uint8_t *p; + + m = *mp; + tlen = m_length(m, NULL); + if (tlen < 2) + return (NULL); + + /* + * Ensure that at least the framing part of the token is + * contigous. + */ + if (m->m_len < 2) { + if (can_pullup) + *mp = m = m_pullup(m, 2); + else + return (NULL); + } + + p = m->m_data; + + if (*p++ != 0x60) + return (NULL); + + if (*p < 0x80) { + inside_len = *p++; + len_len = 1; + } else { + /* + * Ensure there is enough space for the DER encoded length. + */ + len_len = (*p & 0x7f) + 1; + if (tlen < len_len + 1) + return (NULL); + if (m->m_len < len_len + 1) { + if (can_pullup) + *mp = m = m_pullup(m, len_len + 1); + else + return (NULL); + p = m->m_data + 1; + } + + switch (*p++) { + case 0x81: + inside_len = *p++; + break; + + case 0x82: + inside_len = (p[0] << 8) | p[1]; + p += 2; + break; + + case 0x83: + inside_len = (p[0] << 16) | (p[1] << 8) | p[2]; + p += 3; + break; + + case 0x84: + inside_len = (p[0] << 24) | (p[1] << 16) + | (p[2] << 8) | p[3]; + p += 4; + break; + + default: + return (NULL); + } + } + + if (tlen != inside_len + len_len + 1) + return (NULL); + if (inside_len < 2 + oid->length + len) + return (NULL); + + /* + * Now that we know the value of len_len, we can pullup the + * whole header. The header is 1 + len_len + 2 + oid->length + + * len bytes. + */ + hlen = 1 + len_len + 2 + oid->length + len; + if (m->m_len < hlen) { + if (can_pullup) + *mp = m = m_pullup(m, hlen); + else + return (NULL); + p = m->m_data + 1 + len_len; + } + + if (*p++ != 0x06) + return (NULL); + if (*p++ != oid->length) + return (NULL); + if (bcmp(oid->elements, p, oid->length)) + return (NULL); + p += oid->length; + + if (p[0] != tok_id[0]) + return (NULL); + + if (p[1] != tok_id[1]) + return (NULL); + + *encap_len = inside_len - 2 - oid->length; + + return (p); +} + +static void +krb5_insert_seq(struct krb5_msg_order *mo, uint32_t seq, int index) +{ + int i; + + if (mo->km_length < mo->km_jitter_window) + mo->km_length++; + + for (i = mo->km_length - 1; i > index; i--) + mo->km_elem[i] = mo->km_elem[i - 1]; + mo->km_elem[index] = seq; +} + +/* + * Check sequence numbers according to RFC 2743 section 1.2.3. + */ +static OM_uint32 +krb5_sequence_check(struct krb5_context *kc, uint32_t seq) +{ + OM_uint32 res = GSS_S_FAILURE; + struct krb5_msg_order *mo = &kc->kc_msg_order; + int check_sequence = mo->km_flags & GSS_C_SEQUENCE_FLAG; + int check_replay = mo->km_flags & GSS_C_REPLAY_FLAG; + int i; + + mtx_lock(&kc->kc_lock); + + /* + * Message is in-sequence with no gap. + */ + if (mo->km_length == 0 || seq == mo->km_elem[0] + 1) { + /* + * This message is received in-sequence with no gaps. + */ + krb5_insert_seq(mo, seq, 0); + res = GSS_S_COMPLETE; + goto out; + } + + if (seq > mo->km_elem[0]) { + /* + * This message is received in-sequence with a gap. + */ + krb5_insert_seq(mo, seq, 0); + if (check_sequence) + res = GSS_S_GAP_TOKEN; + else + res = GSS_S_COMPLETE; + goto out; + } + + if (seq < mo->km_elem[mo->km_length - 1]) { + if (check_replay && !check_sequence) + res = GSS_S_OLD_TOKEN; + else + res = GSS_S_UNSEQ_TOKEN; + goto out; + } + + for (i = 0; i < mo->km_length; i++) { + if (mo->km_elem[i] == seq) { + res = GSS_S_DUPLICATE_TOKEN; + goto out; + } + if (mo->km_elem[i] < seq) { + /* + * We need to insert this seq here, + */ + krb5_insert_seq(mo, seq, i); + if (check_replay && !check_sequence) + res = GSS_S_COMPLETE; + else + res = GSS_S_UNSEQ_TOKEN; + goto out; + } + } + +out: + mtx_unlock(&kc->kc_lock); + + return (res); +} + +static uint8_t sgn_alg_des_md5[] = { 0x00, 0x00 }; +static uint8_t seal_alg_des[] = { 0x00, 0x00 }; +static uint8_t sgn_alg_des3_sha1[] = { 0x04, 0x00 }; +static uint8_t seal_alg_des3[] = { 0x02, 0x00 }; +static uint8_t seal_alg_rc4[] = { 0x10, 0x00 }; +static uint8_t sgn_alg_hmac_md5[] = { 0x11, 0x00 }; + +/* + * Return the size of the inner token given the use of the key's + * encryption class. For wrap tokens, the length of the padded + * plaintext will be added to this. + */ +static size_t +token_length(struct krb5_key_state *key) +{ + + return (16 + key->ks_class->ec_checksumlen); +} + +static OM_uint32 +krb5_get_mic_old(struct krb5_context *kc, struct mbuf *m, + struct mbuf **micp, uint8_t sgn_alg[2]) +{ + struct mbuf *mlast, *mic, *tm; + uint8_t *p, dir; + size_t tlen, mlen, cklen; + uint32_t seq; + char buf[8]; + + mlen = m_length(m, &mlast); + + tlen = token_length(kc->kc_tokenkey); + p = krb5_make_token("\x01\x01", tlen, tlen, &mic); + p += 2; /* TOK_ID */ + *p++ = sgn_alg[0]; /* SGN_ALG */ + *p++ = sgn_alg[1]; + + *p++ = 0xff; /* filler */ + *p++ = 0xff; + *p++ = 0xff; + *p++ = 0xff; + + /* + * SGN_CKSUM: + * + * Calculate the keyed checksum of the token header plus the + * message. + */ + cklen = kc->kc_checksumkey->ks_class->ec_checksumlen; + + mic->m_len = p - (uint8_t *) mic->m_data; + mic->m_next = m; + MGET(tm, M_WAITOK, MT_DATA); + tm->m_len = cklen; + mlast->m_next = tm; + + krb5_checksum(kc->kc_checksumkey, 15, mic, mic->m_len - 8, + 8 + mlen, cklen); + bcopy(tm->m_data, p + 8, cklen); + mic->m_next = NULL; + mlast->m_next = NULL; + m_free(tm); + + /* + * SND_SEQ: + * + * Take the four bytes of the sequence number least + * significant first followed by four bytes of direction + * marker (zero for initiator and 0xff for acceptor). Encrypt + * that data using the SGN_CKSUM as IV. Note: ARC4 wants the + * sequence number big-endian. + */ + seq = atomic_fetchadd_32(&kc->kc_local_seqnumber, 1); + if (sgn_alg[0] == 0x11) { + p[0] = (seq >> 24); + p[1] = (seq >> 16); + p[2] = (seq >> 8); + p[3] = (seq >> 0); + } else { + p[0] = (seq >> 0); + p[1] = (seq >> 8); + p[2] = (seq >> 16); + p[3] = (seq >> 24); + } + if (is_initiator(kc)) { + dir = 0; + } else { + dir = 0xff; + } + p[4] = dir; + p[5] = dir; + p[6] = dir; + p[7] = dir; + bcopy(p + 8, buf, 8); + + /* + * Set the mic buffer to its final size so that the encrypt + * can see the SND_SEQ part. + */ + mic->m_len += 8 + cklen; + krb5_encrypt(kc->kc_tokenkey, mic, mic->m_len - cklen - 8, 8, buf, 8); + + *micp = mic; + return (GSS_S_COMPLETE); +} + +static OM_uint32 +krb5_get_mic_new(struct krb5_context *kc, struct mbuf *m, + struct mbuf **micp) +{ + struct krb5_key_state *key = kc->kc_send_sign_Kc; + struct mbuf *mlast, *mic; + uint8_t *p; + int flags; + size_t mlen, cklen; + uint32_t seq; + + mlen = m_length(m, &mlast); + cklen = key->ks_class->ec_checksumlen; + + KASSERT(16 + cklen <= MLEN, ("checksum too large for an mbuf")); + MGET(mic, M_WAITOK, MT_DATA); + M_ALIGN(mic, 16 + cklen); + mic->m_len = 16 + cklen; + p = mic->m_data; + + /* TOK_ID */ + p[0] = 0x04; + p[1] = 0x04; + + /* Flags */ + flags = 0; + if (is_acceptor(kc)) + flags |= GSS_TOKEN_SENT_BY_ACCEPTOR; + if (kc->kc_more_flags & ACCEPTOR_SUBKEY) + flags |= GSS_TOKEN_ACCEPTOR_SUBKEY; + p[2] = flags; + + /* Filler */ + p[3] = 0xff; + p[4] = 0xff; + p[5] = 0xff; + p[6] = 0xff; + p[7] = 0xff; + + /* SND_SEQ */ + p[8] = 0; + p[9] = 0; + p[10] = 0; + p[11] = 0; + seq = atomic_fetchadd_32(&kc->kc_local_seqnumber, 1); + p[12] = (seq >> 24); + p[13] = (seq >> 16); + p[14] = (seq >> 8); + p[15] = (seq >> 0); + + /* + * SGN_CKSUM: + * + * Calculate the keyed checksum of the message plus the first + * 16 bytes of the token header. + */ + mlast->m_next = mic; + krb5_checksum(key, 0, m, 0, mlen + 16, cklen); + mlast->m_next = NULL; + + *micp = mic; + return (GSS_S_COMPLETE); +} + +static OM_uint32 +krb5_get_mic(struct krb5_context *kc, OM_uint32 *minor_status, + gss_qop_t qop_req, struct mbuf *m, struct mbuf **micp) +{ + + *minor_status = 0; + + if (qop_req != GSS_C_QOP_DEFAULT) + return (GSS_S_BAD_QOP); + + if (time_uptime > kc->kc_lifetime) + return (GSS_S_CONTEXT_EXPIRED); + + switch (kc->kc_tokenkey->ks_class->ec_type) { + case ETYPE_DES_CBC_CRC: + return (krb5_get_mic_old(kc, m, micp, sgn_alg_des_md5)); + + case ETYPE_DES3_CBC_SHA1: + return (krb5_get_mic_old(kc, m, micp, sgn_alg_des3_sha1)); + + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: + return (krb5_get_mic_old(kc, m, micp, sgn_alg_hmac_md5)); + + default: + return (krb5_get_mic_new(kc, m, micp)); + } + + return (GSS_S_FAILURE); +} + +static OM_uint32 +krb5_verify_mic_old(struct krb5_context *kc, struct mbuf *m, struct mbuf *mic, + uint8_t sgn_alg[2]) +{ + struct mbuf *mlast, *tm; + uint8_t *p, *tp, dir; + size_t mlen, tlen, elen, miclen; + size_t cklen; + uint32_t seq; + + mlen = m_length(m, &mlast); + + tlen = token_length(kc->kc_tokenkey); + p = krb5_verify_token("\x01\x01", tlen, &mic, &elen, FALSE); + if (!p) + return (GSS_S_DEFECTIVE_TOKEN); +#if 0 + /* + * Disable this check - heimdal-1.1 generates DES3 MIC tokens + * that are 2 bytes too big. + */ + if (elen != tlen) + return (GSS_S_DEFECTIVE_TOKEN); +#endif + /* TOK_ID */ + p += 2; + + /* SGN_ALG */ + if (p[0] != sgn_alg[0] || p[1] != sgn_alg[1]) + return (GSS_S_DEFECTIVE_TOKEN); + p += 2; + + if (p[0] != 0xff || p[1] != 0xff || p[2] != 0xff || p[3] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + p += 4; + + /* + * SGN_CKSUM: + * + * Calculate the keyed checksum of the token header plus the + * message. + */ + cklen = kc->kc_checksumkey->ks_class->ec_checksumlen; + miclen = mic->m_len; + mic->m_len = p - (uint8_t *) mic->m_data; + mic->m_next = m; + MGET(tm, M_WAITOK, MT_DATA); + tm->m_len = cklen; + mlast->m_next = tm; + + krb5_checksum(kc->kc_checksumkey, 15, mic, mic->m_len - 8, + 8 + mlen, cklen); + mic->m_next = NULL; + mlast->m_next = NULL; + if (bcmp(tm->m_data, p + 8, cklen)) { + m_free(tm); + return (GSS_S_BAD_SIG); + } + + /* + * SND_SEQ: + * + * Take the four bytes of the sequence number least + * significant first followed by four bytes of direction + * marker (zero for initiator and 0xff for acceptor). Encrypt + * that data using the SGN_CKSUM as IV. Note: ARC4 wants the + * sequence number big-endian. + */ + bcopy(p, tm->m_data, 8); + tm->m_len = 8; + krb5_decrypt(kc->kc_tokenkey, tm, 0, 8, p + 8, 8); + + tp = tm->m_data; + if (sgn_alg[0] == 0x11) { + seq = tp[3] | (tp[2] << 8) | (tp[1] << 16) | (tp[0] << 24); + } else { + seq = tp[0] | (tp[1] << 8) | (tp[2] << 16) | (tp[3] << 24); + } + + if (is_initiator(kc)) { + dir = 0xff; + } else { + dir = 0; + } + if (tp[4] != dir || tp[5] != dir || tp[6] != dir || tp[7] != dir) { + m_free(tm); + return (GSS_S_DEFECTIVE_TOKEN); + } + m_free(tm); + + if (kc->kc_msg_order.km_flags & + (GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG)) { + return (krb5_sequence_check(kc, seq)); + } + + return (GSS_S_COMPLETE); +} + +static OM_uint32 +krb5_verify_mic_new(struct krb5_context *kc, struct mbuf *m, struct mbuf *mic) +{ + OM_uint32 res; + struct krb5_key_state *key = kc->kc_recv_sign_Kc; + struct mbuf *mlast; + uint8_t *p; + int flags; + size_t mlen, cklen; + char buf[32]; + + mlen = m_length(m, &mlast); + cklen = key->ks_class->ec_checksumlen; + + KASSERT(mic->m_next == NULL, ("MIC should be contiguous")); + if (mic->m_len != 16 + cklen) + return (GSS_S_DEFECTIVE_TOKEN); + p = mic->m_data; + + /* TOK_ID */ + if (p[0] != 0x04) + return (GSS_S_DEFECTIVE_TOKEN); + if (p[1] != 0x04) + return (GSS_S_DEFECTIVE_TOKEN); + + /* Flags */ + flags = 0; + if (is_initiator(kc)) + flags |= GSS_TOKEN_SENT_BY_ACCEPTOR; + if (kc->kc_more_flags & ACCEPTOR_SUBKEY) + flags |= GSS_TOKEN_ACCEPTOR_SUBKEY; + if (p[2] != flags) + return (GSS_S_DEFECTIVE_TOKEN); + + /* Filler */ + if (p[3] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + if (p[4] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + if (p[5] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + if (p[6] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + if (p[7] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + + /* SND_SEQ */ + if (kc->kc_msg_order.km_flags & + (GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG)) { + uint32_t seq; + if (p[8] || p[9] || p[10] || p[11]) { + res = GSS_S_UNSEQ_TOKEN; + } else { + seq = (p[12] << 24) | (p[13] << 16) + | (p[14] << 8) | p[15]; + res = krb5_sequence_check(kc, seq); + } + if (GSS_ERROR(res)) + return (res); + } else { + res = GSS_S_COMPLETE; + } + + /* + * SGN_CKSUM: + * + * Calculate the keyed checksum of the message plus the first + * 16 bytes of the token header. + */ + m_copydata(mic, 16, cklen, buf); + mlast->m_next = mic; + krb5_checksum(key, 0, m, 0, mlen + 16, cklen); + mlast->m_next = NULL; + if (bcmp(buf, p + 16, cklen)) { + return (GSS_S_BAD_SIG); + } + + return (GSS_S_COMPLETE); +} + +static OM_uint32 +krb5_verify_mic(struct krb5_context *kc, OM_uint32 *minor_status, + struct mbuf *m, struct mbuf *mic, gss_qop_t *qop_state) +{ + + *minor_status = 0; + if (qop_state) + *qop_state = GSS_C_QOP_DEFAULT; + + if (time_uptime > kc->kc_lifetime) + return (GSS_S_CONTEXT_EXPIRED); + + switch (kc->kc_tokenkey->ks_class->ec_type) { + case ETYPE_DES_CBC_CRC: + return (krb5_verify_mic_old(kc, m, mic, sgn_alg_des_md5)); + + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: + return (krb5_verify_mic_old(kc, m, mic, sgn_alg_hmac_md5)); + + case ETYPE_DES3_CBC_SHA1: + return (krb5_verify_mic_old(kc, m, mic, sgn_alg_des3_sha1)); + + default: + return (krb5_verify_mic_new(kc, m, mic)); + } + + return (GSS_S_FAILURE); +} + +static OM_uint32 +krb5_wrap_old(struct krb5_context *kc, int conf_req_flag, + struct mbuf **mp, int *conf_state, + uint8_t sgn_alg[2], uint8_t seal_alg[2]) +{ + struct mbuf *m, *mlast, *tm, *cm, *pm; + size_t mlen, tlen, padlen, datalen; + uint8_t *p, dir; + size_t cklen; + uint8_t buf[8]; + uint32_t seq; + + /* + * How many trailing pad bytes do we need? + */ + m = *mp; + mlen = m_length(m, &mlast); + tlen = kc->kc_tokenkey->ks_class->ec_msgblocklen; + padlen = tlen - (mlen % tlen); + + /* + * The data part of the token has eight bytes of random + * confounder prepended and followed by up to eight bytes of + * padding bytes each of which is set to the number of padding + * bytes. + */ + datalen = mlen + 8 + padlen; + tlen = token_length(kc->kc_tokenkey); + + p = krb5_make_token("\x02\x01", tlen, datalen + tlen, &tm); + p += 2; /* TOK_ID */ + *p++ = sgn_alg[0]; /* SGN_ALG */ + *p++ = sgn_alg[1]; + if (conf_req_flag) { + *p++ = seal_alg[0]; /* SEAL_ALG */ + *p++ = seal_alg[1]; + } else { + *p++ = 0xff; /* SEAL_ALG = none */ + *p++ = 0xff; + } + + *p++ = 0xff; /* filler */ + *p++ = 0xff; + + /* + * Copy the padded message data. + */ + if (M_LEADINGSPACE(m) >= 8) { + m->m_data -= 8; + m->m_len += 8; + } else { + MGET(cm, M_WAITOK, MT_DATA); + cm->m_len = 8; + cm->m_next = m; + m = cm; + } + arc4rand(m->m_data, 8, 0); + if (M_TRAILINGSPACE(mlast) >= padlen) { + memset(mlast->m_data + mlast->m_len, padlen, padlen); + mlast->m_len += padlen; + } else { + MGET(pm, M_WAITOK, MT_DATA); + memset(pm->m_data, padlen, padlen); + pm->m_len = padlen; + mlast->m_next = pm; + mlast = pm; + } + tm->m_next = m; + + /* + * SGN_CKSUM: + * + * Calculate the keyed checksum of the token header plus the + * padded message. Fiddle with tm->m_len so that we only + * checksum the 8 bytes of head that we care about. + */ + cklen = kc->kc_checksumkey->ks_class->ec_checksumlen; + tlen = tm->m_len; + tm->m_len = p - (uint8_t *) tm->m_data; + MGET(cm, M_WAITOK, MT_DATA); + cm->m_len = cklen; + mlast->m_next = cm; + krb5_checksum(kc->kc_checksumkey, 13, tm, tm->m_len - 8, + datalen + 8, cklen); + tm->m_len = tlen; + mlast->m_next = NULL; + bcopy(cm->m_data, p + 8, cklen); + m_free(cm); + + /* + * SND_SEQ: + * + * Take the four bytes of the sequence number least + * significant first (most signficant first for ARCFOUR) + * followed by four bytes of direction marker (zero for + * initiator and 0xff for acceptor). Encrypt that data using + * the SGN_CKSUM as IV. + */ + seq = atomic_fetchadd_32(&kc->kc_local_seqnumber, 1); + if (sgn_alg[0] == 0x11) { + p[0] = (seq >> 24); + p[1] = (seq >> 16); + p[2] = (seq >> 8); + p[3] = (seq >> 0); + } else { + p[0] = (seq >> 0); + p[1] = (seq >> 8); + p[2] = (seq >> 16); + p[3] = (seq >> 24); + } + if (is_initiator(kc)) { + dir = 0; + } else { + dir = 0xff; + } + p[4] = dir; + p[5] = dir; + p[6] = dir; + p[7] = dir; + krb5_encrypt(kc->kc_tokenkey, tm, p - (uint8_t *) tm->m_data, + 8, p + 8, 8); + + if (conf_req_flag) { + /* + * Encrypt the padded message with an IV of zero for + * DES and DES3, or an IV of the sequence number in + * big-endian format for ARCFOUR. + */ + if (seal_alg[0] == 0x10) { + buf[0] = (seq >> 24); + buf[1] = (seq >> 16); + buf[2] = (seq >> 8); + buf[3] = (seq >> 0); + krb5_encrypt(kc->kc_encryptkey, m, 0, datalen, + buf, 4); + } else { + krb5_encrypt(kc->kc_encryptkey, m, 0, datalen, + NULL, 0); + } + } + + if (conf_state) + *conf_state = conf_req_flag; + + *mp = tm; + return (GSS_S_COMPLETE); +} + +static OM_uint32 +krb5_wrap_new(struct krb5_context *kc, int conf_req_flag, + struct mbuf **mp, int *conf_state) +{ + struct krb5_key_state *Ke = kc->kc_send_seal_Ke; + struct krb5_key_state *Ki = kc->kc_send_seal_Ki; + struct krb5_key_state *Kc = kc->kc_send_seal_Kc; + const struct krb5_encryption_class *ec = Ke->ks_class; + struct mbuf *m, *mlast, *tm; + uint8_t *p; + int flags, EC; + size_t mlen, blen, mblen, cklen, ctlen; + uint32_t seq; + static char zpad[32]; + + m = *mp; + mlen = m_length(m, &mlast); + + blen = ec->ec_blocklen; + mblen = ec->ec_msgblocklen; + cklen = ec->ec_checksumlen; + + if (conf_req_flag) { + /* + * For sealed messages, we need space for 16 bytes of + * header, blen confounder, plaintext, padding, copy + * of header and checksum. + * + * We pad to mblen (which may be different from + * blen). If the encryption class is using CTS, mblen + * will be one (i.e. no padding required). + */ + if (mblen > 1) + EC = mlen % mblen; + else + EC = 0; + ctlen = blen + mlen + EC + 16; + + /* + * Put initial header and confounder before the + * message. + */ + M_PREPEND(m, 16 + blen, M_WAITOK); + + /* + * Append padding + copy of header and checksum. Try + * to fit this into the end of the original message, + * otherwise allocate a trailer. + */ + if (M_TRAILINGSPACE(mlast) >= EC + 16 + cklen) { + tm = NULL; + mlast->m_len += EC + 16 + cklen; + } else { + MGET(tm, M_WAITOK, MT_DATA); + tm->m_len = EC + 16 + cklen; + mlast->m_next = tm; + } + } else { + /* + * For unsealed messages, we need 16 bytes of header + * plus space for the plaintext and a checksum. EC is + * set to the checksum size. We leave space in tm for + * a copy of the header - this will be trimmed later. + */ + M_PREPEND(m, 16, M_WAITOK); + + MGET(tm, M_WAITOK, MT_DATA); + tm->m_len = cklen + 16; + mlast->m_next = tm; + ctlen = 0; + EC = cklen; + } + + p = m->m_data; + + /* TOK_ID */ + p[0] = 0x05; + p[1] = 0x04; + + /* Flags */ + flags = 0; + if (conf_req_flag) + flags = GSS_TOKEN_SEALED; + if (is_acceptor(kc)) + flags |= GSS_TOKEN_SENT_BY_ACCEPTOR; + if (kc->kc_more_flags & ACCEPTOR_SUBKEY) + flags |= GSS_TOKEN_ACCEPTOR_SUBKEY; + p[2] = flags; + + /* Filler */ + p[3] = 0xff; + + /* EC + RRC - set to zero initially */ + p[4] = 0; + p[5] = 0; + p[6] = 0; + p[7] = 0; + + /* SND_SEQ */ + p[8] = 0; + p[9] = 0; + p[10] = 0; + p[11] = 0; + seq = atomic_fetchadd_32(&kc->kc_local_seqnumber, 1); + p[12] = (seq >> 24); + p[13] = (seq >> 16); + p[14] = (seq >> 8); + p[15] = (seq >> 0); + + if (conf_req_flag) { + /* + * Encrypt according to RFC 4121 section 4.2 and RFC + * 3961 section 5.3. Note: we don't generate tokens + * with RRC values other than zero. If we did, we + * should zero RRC in the copied header. + */ + arc4rand(p + 16, blen, 0); + if (EC) { + m_copyback(m, 16 + blen + mlen, EC, zpad); + } + m_copyback(m, 16 + blen + mlen + EC, 16, p); + + krb5_checksum(Ki, 0, m, 16, ctlen, cklen); + krb5_encrypt(Ke, m, 16, ctlen, NULL, 0); + } else { + /* + * The plaintext message is followed by a checksum of + * the plaintext plus a version of the header where EC + * and RRC are set to zero. Also, the original EC must + * be our checksum size. + */ + bcopy(p, tm->m_data, 16); + krb5_checksum(Kc, 0, m, 16, mlen + 16, cklen); + tm->m_data += 16; + tm->m_len -= 16; + } + + /* + * Finally set EC to its actual value + */ + p[4] = EC >> 8; + p[5] = EC; + + *mp = m; + return (GSS_S_COMPLETE); +} + +static OM_uint32 +krb5_wrap(struct krb5_context *kc, OM_uint32 *minor_status, + int conf_req_flag, gss_qop_t qop_req, + struct mbuf **mp, int *conf_state) +{ + + *minor_status = 0; + if (conf_state) + *conf_state = 0; + + if (qop_req != GSS_C_QOP_DEFAULT) + return (GSS_S_BAD_QOP); + + if (time_uptime > kc->kc_lifetime) + return (GSS_S_CONTEXT_EXPIRED); + + switch (kc->kc_tokenkey->ks_class->ec_type) { + case ETYPE_DES_CBC_CRC: + return (krb5_wrap_old(kc, conf_req_flag, + mp, conf_state, sgn_alg_des_md5, seal_alg_des)); + + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: + return (krb5_wrap_old(kc, conf_req_flag, + mp, conf_state, sgn_alg_hmac_md5, seal_alg_rc4)); + + case ETYPE_DES3_CBC_SHA1: + return (krb5_wrap_old(kc, conf_req_flag, + mp, conf_state, sgn_alg_des3_sha1, seal_alg_des3)); + + default: + return (krb5_wrap_new(kc, conf_req_flag, mp, conf_state)); + } + + return (GSS_S_FAILURE); +} + +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; + } + } +} + +static OM_uint32 +krb5_unwrap_old(struct krb5_context *kc, struct mbuf **mp, int *conf_state, + uint8_t sgn_alg[2], uint8_t seal_alg[2]) +{ + OM_uint32 res; + struct mbuf *m, *mlast, *hm, *cm; + uint8_t *p, dir; + size_t mlen, tlen, elen, datalen, padlen; + size_t cklen; + uint8_t buf[32]; + uint32_t seq; + int i, conf; + + m = *mp; + mlen = m_length(m, &mlast); + + tlen = token_length(kc->kc_tokenkey); + cklen = kc->kc_tokenkey->ks_class->ec_checksumlen; + + p = krb5_verify_token("\x02\x01", tlen, &m, &elen, TRUE); + *mp = m; + if (!p) + return (GSS_S_DEFECTIVE_TOKEN); + datalen = elen - tlen; + + /* + * Trim the framing header first to make life a little easier + * later. + */ + m_adj(m, p - (uint8_t *) m->m_data); + + /* TOK_ID */ + p += 2; + + /* SGN_ALG */ + if (p[0] != sgn_alg[0] || p[1] != sgn_alg[1]) + return (GSS_S_DEFECTIVE_TOKEN); + p += 2; + + /* SEAL_ALG */ + if (p[0] == seal_alg[0] && p[1] == seal_alg[1]) + conf = 1; + else if (p[0] == 0xff && p[1] == 0xff) + conf = 0; + else + return (GSS_S_DEFECTIVE_TOKEN); + p += 2; + + if (p[0] != 0xff || p[1] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + p += 2; + + /* + * SND_SEQ: + * + * Take the four bytes of the sequence number least + * significant first (most significant for ARCFOUR) followed + * by four bytes of direction marker (zero for initiator and + * 0xff for acceptor). Encrypt that data using the SGN_CKSUM + * as IV. + */ + krb5_decrypt(kc->kc_tokenkey, m, 8, 8, p + 8, 8); + if (sgn_alg[0] == 0x11) { + seq = p[3] | (p[2] << 8) | (p[1] << 16) | (p[0] << 24); + } else { + seq = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + } + + if (is_initiator(kc)) { + dir = 0xff; + } else { + dir = 0; + } + if (p[4] != dir || p[5] != dir || p[6] != dir || p[7] != dir) + return (GSS_S_DEFECTIVE_TOKEN); + + if (kc->kc_msg_order.km_flags & + (GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG)) { + res = krb5_sequence_check(kc, seq); + if (GSS_ERROR(res)) + return (res); + } else { + res = GSS_S_COMPLETE; + } + + /* + * If the token was encrypted, decode it in-place. + */ + if (conf) { + /* + * Decrypt the padded message with an IV of zero for + * DES and DES3 or an IV of the big-endian encoded + * sequence number for ARCFOUR. + */ + if (seal_alg[0] == 0x10) { + krb5_decrypt(kc->kc_encryptkey, m, 16 + cklen, + datalen, p, 4); + } else { + krb5_decrypt(kc->kc_encryptkey, m, 16 + cklen, + datalen, NULL, 0); + } + } + if (conf_state) + *conf_state = conf; + + /* + * Check the trailing pad bytes. + */ + KASSERT(mlast->m_len > 0, ("Unexpected empty mbuf")); + padlen = mlast->m_data[mlast->m_len - 1]; + m_copydata(m, tlen + datalen - padlen, padlen, buf); + for (i = 0; i < padlen; i++) { + if (buf[i] != padlen) { + return (GSS_S_DEFECTIVE_TOKEN); + } + } + + /* + * SGN_CKSUM: + * + * Calculate the keyed checksum of the token header plus the + * padded message. We do a little mbuf surgery to trim out the + * parts we don't want to checksum. + */ + hm = m; + *mp = m = m_split(m, 16 + cklen, M_WAITOK); + mlast = m_last(m); + hm->m_len = 8; + hm->m_next = m; + MGET(cm, M_WAITOK, MT_DATA); + cm->m_len = cklen; + mlast->m_next = cm; + + krb5_checksum(kc->kc_checksumkey, 13, hm, 0, datalen + 8, cklen); + hm->m_next = NULL; + mlast->m_next = NULL; + + if (bcmp(cm->m_data, hm->m_data + 16, cklen)) { + m_freem(hm); + m_free(cm); + return (GSS_S_BAD_SIG); + } + m_freem(hm); + m_free(cm); + + /* + * Trim off the confounder and padding. + */ + m_adj(m, 8); + if (mlast->m_len >= padlen) { + mlast->m_len -= padlen; + } else { + m_trim(m, datalen - 8 - padlen); + } + + *mp = m; + return (res); +} + +static OM_uint32 +krb5_unwrap_new(struct krb5_context *kc, struct mbuf **mp, int *conf_state) +{ + OM_uint32 res; + struct krb5_key_state *Ke = kc->kc_recv_seal_Ke; + struct krb5_key_state *Ki = kc->kc_recv_seal_Ki; + struct krb5_key_state *Kc = kc->kc_recv_seal_Kc; + const struct krb5_encryption_class *ec = Ke->ks_class; + struct mbuf *m, *mlast, *hm, *cm; + uint8_t *p, *pp; + int sealed, flags, EC, RRC; + size_t blen, cklen, ctlen, mlen, plen, tlen; + char buf[32], buf2[32]; + + m = *mp; + mlen = m_length(m, &mlast); + + if (mlen <= 16) + return (GSS_S_DEFECTIVE_TOKEN); + if (m->m_len < 16) { + m = m_pullup(m, 16); + *mp = m; + } + p = m->m_data; + + /* TOK_ID */ + if (p[0] != 0x05) + return (GSS_S_DEFECTIVE_TOKEN); + if (p[1] != 0x04) + return (GSS_S_DEFECTIVE_TOKEN); + + /* Flags */ + sealed = p[2] & GSS_TOKEN_SEALED; + flags = sealed; + if (is_initiator(kc)) + flags |= GSS_TOKEN_SENT_BY_ACCEPTOR; + if (kc->kc_more_flags & ACCEPTOR_SUBKEY) + flags |= GSS_TOKEN_ACCEPTOR_SUBKEY; + if (p[2] != flags) + return (GSS_S_DEFECTIVE_TOKEN); + + /* Filler */ + if (p[3] != 0xff) + return (GSS_S_DEFECTIVE_TOKEN); + + /* EC + RRC */ + EC = (p[4] << 8) + p[5]; + RRC = (p[6] << 8) + p[7]; + + /* SND_SEQ */ + if (kc->kc_msg_order.km_flags & + (GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG)) { + uint32_t seq; + if (p[8] || p[9] || p[10] || p[11]) { + res = GSS_S_UNSEQ_TOKEN; + } else { + seq = (p[12] << 24) | (p[13] << 16) + | (p[14] << 8) | p[15]; + res = krb5_sequence_check(kc, seq); + } + if (GSS_ERROR(res)) + return (res); + } else { + res = GSS_S_COMPLETE; + } + + /* + * Separate the header before dealing with RRC. We only need + * to keep the header if the message isn't encrypted. + */ + if (sealed) { + hm = NULL; + m_adj(m, 16); + } else { + hm = m; + *mp = m = m_split(m, 16, M_WAITOK); + mlast = m_last(m); + } + + /* + * Undo the effects of RRC by rotating left. + */ + if (RRC > 0) { + struct mbuf *rm; + size_t rlen; + + rlen = mlen - 16; + if (RRC <= sizeof(buf) && m->m_len >= rlen) { + /* + * Simple case, just rearrange the bytes in m. + */ + bcopy(m->m_data, buf, RRC); + bcopy(m->m_data + RRC, m->m_data, rlen - RRC); + bcopy(buf, m->m_data + rlen - RRC, RRC); + } else { + /* + * More complicated - rearrange the mbuf + * chain. + */ + rm = m; + *mp = m = m_split(m, RRC, M_WAITOK); + m_cat(m, rm); + mlast = rm; + } + } + + blen = ec->ec_blocklen; + cklen = ec->ec_checksumlen; + if (sealed) { + /* + * Decrypt according to RFC 4121 section 4.2 and RFC + * 3961 section 5.3. The message must be large enough + * for a blocksize confounder, at least one block of + * cyphertext and a checksum. + */ + if (mlen < 16 + 2*blen + cklen) + return (GSS_S_DEFECTIVE_TOKEN); + + ctlen = mlen - 16 - cklen; + krb5_decrypt(Ke, m, 0, ctlen, NULL, 0); + + /* + * The size of the plaintext is ctlen minus blocklen + * (for the confounder), 16 (for the copy of the token + * header) and EC (for the filler). The actual + * plaintext starts after the confounder. + */ + plen = ctlen - blen - 16 - EC; + pp = p + 16 + blen; + + /* + * Checksum the padded plaintext. + */ + m_copydata(m, ctlen, cklen, buf); + krb5_checksum(Ki, 0, m, 0, ctlen, cklen); + m_copydata(m, ctlen, cklen, buf2); + + if (bcmp(buf, buf2, cklen)) + return (GSS_S_BAD_SIG); + + /* + * Trim the message back to just plaintext. + */ + m_adj(m, blen); + tlen = 16 + EC + cklen; + if (mlast->m_len >= tlen) { + mlast->m_len -= tlen; + } else { + m_trim(m, plen); + } + } else { + /* + * The plaintext message is followed by a checksum of + * the plaintext plus a version of the header where EC + * and RRC are set to zero. Also, the original EC must + * be our checksum size. + */ + if (mlen < 16 + cklen || EC != cklen) + return (GSS_S_DEFECTIVE_TOKEN); + + /* + * The size of the plaintext is simply the message + * size less header and checksum. The plaintext starts + * right after the header (which we have saved in hm). + */ + plen = mlen - 16 - cklen; + + /* + * Insert a copy of the header (with EC and RRC set to + * zero) between the plaintext message and the + * checksum. + */ + p = hm->m_data; + p[4] = p[5] = p[6] = p[7] = 0; + + cm = m_split(m, plen, M_WAITOK); + mlast = m_last(m); + m->m_next = hm; + hm->m_next = cm; + + bcopy(cm->m_data, buf, cklen); + krb5_checksum(Kc, 0, m, 0, plen + 16, cklen); + if (bcmp(cm->m_data, buf, cklen)) + return (GSS_S_BAD_SIG); + + /* + * The checksum matches, discard all buf the plaintext. + */ + mlast->m_next = NULL; + m_freem(hm); + } + + if (conf_state) + *conf_state = (sealed != 0); + + return (res); +} + +static OM_uint32 +krb5_unwrap(struct krb5_context *kc, OM_uint32 *minor_status, + struct mbuf **mp, int *conf_state, gss_qop_t *qop_state) +{ + OM_uint32 maj_stat; + + *minor_status = 0; + if (qop_state) + *qop_state = GSS_C_QOP_DEFAULT; + if (conf_state) + *conf_state = 0; + + if (time_uptime > kc->kc_lifetime) + return (GSS_S_CONTEXT_EXPIRED); + + switch (kc->kc_tokenkey->ks_class->ec_type) { + case ETYPE_DES_CBC_CRC: + maj_stat = krb5_unwrap_old(kc, mp, conf_state, + sgn_alg_des_md5, seal_alg_des); + break; + + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: + maj_stat = krb5_unwrap_old(kc, mp, conf_state, + sgn_alg_hmac_md5, seal_alg_rc4); + break; + + case ETYPE_DES3_CBC_SHA1: + maj_stat = krb5_unwrap_old(kc, mp, conf_state, + sgn_alg_des3_sha1, seal_alg_des3); + break; + + default: + maj_stat = krb5_unwrap_new(kc, mp, conf_state); + break; + } + + if (GSS_ERROR(maj_stat)) { + m_freem(*mp); + *mp = NULL; + } + + return (maj_stat); +} + +static OM_uint32 +krb5_wrap_size_limit(struct krb5_context *kc, OM_uint32 *minor_status, + int conf_req_flag, gss_qop_t qop_req, OM_uint32 req_output_size, + OM_uint32 *max_input_size) +{ + const struct krb5_encryption_class *ec; + OM_uint32 overhead; + + *minor_status = 0; + *max_input_size = 0; + + if (qop_req != GSS_C_QOP_DEFAULT) + return (GSS_S_BAD_QOP); + + ec = kc->kc_tokenkey->ks_class; + switch (ec->ec_type) { + case ETYPE_DES_CBC_CRC: + case ETYPE_DES3_CBC_SHA1: + case ETYPE_ARCFOUR_HMAC_MD5: + case ETYPE_ARCFOUR_HMAC_MD5_56: + /* + * up to 5 bytes for [APPLICATION 0] SEQUENCE + * 2 + krb5 oid length + * 8 bytes of header + * 8 bytes of confounder + * maximum of 8 bytes of padding + * checksum + */ + overhead = 5 + 2 + krb5_mech_oid.length; + overhead += 8 + 8 + ec->ec_msgblocklen; + overhead += ec->ec_checksumlen; + break; + + default: + if (conf_req_flag) { + /* + * 16 byts of header + * blocklen bytes of confounder + * up to msgblocklen - 1 bytes of padding + * 16 bytes for copy of header + * checksum + */ + overhead = 16 + ec->ec_blocklen; + overhead += ec->ec_msgblocklen - 1; + overhead += 16; + overhead += ec->ec_checksumlen; + } else { + /* + * 16 bytes of header plus checksum. + */ + overhead = 16 + ec->ec_checksumlen; + } + } + + *max_input_size = req_output_size - overhead; + + return (GSS_S_COMPLETE); +} + +static kobj_method_t krb5_methods[] = { + KOBJMETHOD(kgss_init, krb5_init), + KOBJMETHOD(kgss_import, krb5_import), + KOBJMETHOD(kgss_delete, krb5_delete), + KOBJMETHOD(kgss_mech_type, krb5_mech_type), + KOBJMETHOD(kgss_get_mic, krb5_get_mic), + KOBJMETHOD(kgss_verify_mic, krb5_verify_mic), + KOBJMETHOD(kgss_wrap, krb5_wrap), + KOBJMETHOD(kgss_unwrap, krb5_unwrap), + KOBJMETHOD(kgss_wrap_size_limit, krb5_wrap_size_limit), + { 0, 0 } +}; + +static struct kobj_class krb5_class = { + "kerberosv5", + krb5_methods, + sizeof(struct krb5_context) +}; + +/* + * Kernel module glue + */ +static int +kgssapi_krb5_modevent(module_t mod, int type, void *data) +{ + + switch (type) { + case MOD_LOAD: + kgss_install_mech(&krb5_mech_oid, "kerberosv5", &krb5_class); + break; + + case MOD_UNLOAD: + kgss_uninstall_mech(&krb5_mech_oid); + break; + } + + + return (0); +} +static moduledata_t kgssapi_krb5_mod = { + "kgssapi_krb5", + kgssapi_krb5_modevent, + NULL, +}; +DECLARE_MODULE(kgssapi_krb5, kgssapi_krb5_mod, SI_SUB_VFS, SI_ORDER_ANY); +MODULE_DEPEND(kgssapi_krb5, kgssapi, 1, 1, 1); +MODULE_DEPEND(kgssapi_krb5, crypto, 1, 1, 1); +MODULE_DEPEND(kgssapi_krb5, rc4, 1, 1, 1); +MODULE_VERSION(kgssapi_krb5, 1); diff --git a/sys/modules/kgssapi/Makefile b/sys/modules/kgssapi/Makefile new file mode 100644 index 0000000..223ef24 --- /dev/null +++ b/sys/modules/kgssapi/Makefile @@ -0,0 +1,55 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../kgssapi ${.CURDIR}/../../rpc/rpcsec_gss +KMOD= kgssapi + +SRCS= gss_accept_sec_context.c \ + gss_add_oid_set_member.c \ + gss_acquire_cred.c \ + gss_canonicalize_name.c \ + gss_create_empty_oid_set.c \ + gss_delete_sec_context.c \ + gss_display_status.c \ + gss_export_name.c \ + gss_get_mic.c \ + gss_init_sec_context.c \ + gss_impl.c \ + gss_import_name.c \ + gss_names.c \ + gss_pname_to_uid.c \ + gss_release_buffer.c \ + gss_release_cred.c \ + gss_release_name.c \ + gss_release_oid_set.c \ + gss_set_cred_option.c \ + gss_test_oid_set_member.c \ + gss_unwrap.c \ + gss_verify_mic.c \ + gss_wrap.c \ + gss_wrap_size_limit.c \ + gssd_prot.c + +SRCS+= rpcsec_gss.c \ + rpcsec_gss_conf.c \ + rpcsec_gss_misc.c \ + rpcsec_gss_prot.c \ + svc_rpcsec_gss.c + +SRCS+= kgss_if.h kgss_if.c +MFILES= kgssapi/kgss_if.m + +SRCS+= gssd.h gssd_xdr.c gssd_clnt.c +CLEANFILES= gssd.h gssd_xdr.c gssd_clnt.c + +S= ${.CURDIR}/../.. + +gssd.h: $S/kgssapi/gssd.x + rpcgen -hM $S/kgssapi/gssd.x | grep -v pthread.h > gssd.h + +gssd_xdr.c: $S/kgssapi/gssd.x + rpcgen -c $S/kgssapi/gssd.x -o gssd_xdr.c + +gssd_clnt.c: $S/kgssapi/gssd.x + rpcgen -lM $S/kgssapi/gssd.x | grep -v string.h > gssd_clnt.c + +.include diff --git a/sys/modules/kgssapi_krb5/Makefile b/sys/modules/kgssapi_krb5/Makefile new file mode 100644 index 0000000..c2ee417 --- /dev/null +++ b/sys/modules/kgssapi_krb5/Makefile @@ -0,0 +1,21 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../kgssapi/krb5 +KMOD= kgssapi_krb5 + +SRCS= krb5_mech.c \ + kcrypto.c \ + kcrypto_des.c \ + kcrypto_des3.c \ + kcrypto_aes.c \ + kcrypto_arcfour.c + +SRCS+= kgss_if.h gssd.h +MFILES= kgssapi/kgss_if.m + +S= ${.CURDIR}/../.. + +gssd.h: $S/kgssapi/gssd.x + rpcgen -hM $S/kgssapi/gssd.x | grep -v pthread.h > gssd.h + +.include diff --git a/sys/modules/nfsclient/Makefile b/sys/modules/nfsclient/Makefile index 1804201..1562a64 100644 --- a/sys/modules/nfsclient/Makefile +++ b/sys/modules/nfsclient/Makefile @@ -6,11 +6,11 @@ KMOD= nfsclient SRCS= vnode_if.h \ nfs_bio.c nfs_lock.c nfs_node.c nfs_socket.c nfs_subs.c nfs_nfsiod.c \ - nfs_vfsops.c nfs_vnops.c nfs_common.c \ + nfs_vfsops.c nfs_vnops.c nfs_common.c nfs_krpc.c \ opt_inet.h opt_nfs.h opt_bootp.h opt_nfsroot.h SRCS+= nfs4_dev.c nfs4_idmap.c nfs4_socket.c nfs4_subs.c \ nfs4_vfs_subs.c nfs4_vfsops.c nfs4_vn_subs.c nfs4_vnops.c -SRCS+= opt_inet6.h +SRCS+= opt_inet6.h opt_kgssapi.h # USE THE RPCCLNT: CFLAGS+= -DRPCCLNT_DEBUG diff --git a/sys/modules/nfsserver/Makefile b/sys/modules/nfsserver/Makefile index 8e33c46..771794a 100644 --- a/sys/modules/nfsserver/Makefile +++ b/sys/modules/nfsserver/Makefile @@ -3,8 +3,8 @@ .PATH: ${.CURDIR}/../../nfsserver ${.CURDIR}/../../nfs KMOD= nfsserver SRCS= vnode_if.h \ - nfs_serv.c nfs_srvsock.c nfs_srvcache.c nfs_srvsubs.c nfs_syscalls.c \ - nfs_common.c \ + nfs_serv.c nfs_srvkrpc.c nfs_srvsock.c nfs_srvcache.c nfs_srvsubs.c \ + nfs_syscalls.c nfs_common.c \ opt_mac.h \ opt_nfs.h SRCS+= opt_inet6.h diff --git a/sys/nfsclient/nfs.h b/sys/nfsclient/nfs.h index 9e52420..c5b1c51 100644 --- a/sys/nfsclient/nfs.h +++ b/sys/nfsclient/nfs.h @@ -132,7 +132,9 @@ MALLOC_DECLARE(M_NFSDIRECTIO); extern struct uma_zone *nfsmount_zone; +#ifdef NFS_LEGACYRPC extern struct callout nfs_callout; +#endif extern struct nfsstats nfsstats; extern struct mtx nfs_iod_mtx; @@ -157,6 +159,8 @@ extern int nfsv3_procid[NFS_NPROCS]; (e) != ERESTART && (e) != EWOULDBLOCK && \ ((s) & PR_CONNREQUIRED) == 0) +#ifdef NFS_LEGACYRPC + /* * Nfs outstanding request list element */ @@ -196,6 +200,17 @@ extern TAILQ_HEAD(nfs_reqq, nfsreq) nfs_reqq; #define R_GETONEREP 0x80 /* Probe for one reply only */ #define R_PIN_REQ 0x100 /* Pin request down (rexmit in prog or other) */ +#else + +/* + * This is only needed to keep things working while we support + * compiling for both RPC implementations. + */ +struct nfsreq; +struct nfsmount; + +#endif + struct buf; struct socket; struct uio; @@ -291,12 +306,18 @@ vfs_init_t nfs_init; vfs_uninit_t nfs_uninit; int nfs_mountroot(struct mount *mp, struct thread *td); +#ifdef NFS_LEGACYRPC #ifndef NFS4_USE_RPCCLNT int nfs_send(struct socket *, struct sockaddr *, struct mbuf *, struct nfsreq *); int nfs_connect_lock(struct nfsreq *); void nfs_connect_unlock(struct nfsreq *); +void nfs_up(struct nfsreq *, struct nfsmount *, struct thread *, + const char *, int); +void nfs_down(struct nfsreq *, struct nfsmount *, struct thread *, + const char *, int, int); #endif /* ! NFS4_USE_RPCCLNT */ +#endif int nfs_vinvalbuf(struct vnode *, int, struct thread *, int); int nfs_readrpc(struct vnode *, struct uio *, struct ucred *); @@ -309,10 +330,6 @@ int nfs_nfsiodnew(void); int nfs_asyncio(struct nfsmount *, struct buf *, struct ucred *, struct thread *); int nfs_doio(struct vnode *, struct buf *, struct ucred *, struct thread *); void nfs_doio_directwrite (struct buf *); -void nfs_up(struct nfsreq *, struct nfsmount *, struct thread *, - const char *, int); -void nfs_down(struct nfsreq *, struct nfsmount *, struct thread *, - const char *, int, int); int nfs_readlinkrpc(struct vnode *, struct uio *, struct ucred *); int nfs_sigintr(struct nfsmount *, struct nfsreq *, struct thread *); int nfs_readdirplusrpc(struct vnode *, struct uio *, struct ucred *); diff --git a/sys/nfsclient/nfs_krpc.c b/sys/nfsclient/nfs_krpc.c new file mode 100644 index 0000000..8a5f805 --- /dev/null +++ b/sys/nfsclient/nfs_krpc.c @@ -0,0 +1,769 @@ +/*- + * Copyright (c) 1989, 1991, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Rick Macklem at The University of Guelph. + * + * 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. + * 4. 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 BY THE REGENTS 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 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. + * + * @(#)nfs_socket.c 8.5 (Berkeley) 3/30/95 + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Socket operations for use by nfs + */ + +#include "opt_inet6.h" +#include "opt_kgssapi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef NFS_LEGACYRPC + +static int nfs_realign_test; +static int nfs_realign_count; +static int nfs_bufpackets = 4; +static int nfs_reconnects; +static int nfs3_jukebox_delay = 10; +static int nfs_skip_wcc_data_onerr = 1; +static int fake_wchan; + +SYSCTL_DECL(_vfs_nfs); + +SYSCTL_INT(_vfs_nfs, OID_AUTO, realign_test, CTLFLAG_RW, &nfs_realign_test, 0, + "Number of realign tests done"); +SYSCTL_INT(_vfs_nfs, OID_AUTO, realign_count, CTLFLAG_RW, &nfs_realign_count, 0, + "Number of mbuf realignments done"); +SYSCTL_INT(_vfs_nfs, OID_AUTO, bufpackets, CTLFLAG_RW, &nfs_bufpackets, 0, + "Buffer reservation size 2 < x < 64"); +SYSCTL_INT(_vfs_nfs, OID_AUTO, reconnects, CTLFLAG_RD, &nfs_reconnects, 0, + "Number of times the nfs client has had to reconnect"); +SYSCTL_INT(_vfs_nfs, OID_AUTO, nfs3_jukebox_delay, CTLFLAG_RW, &nfs3_jukebox_delay, 0, + "Number of seconds to delay a retry after receiving EJUKEBOX"); +SYSCTL_INT(_vfs_nfs, OID_AUTO, skip_wcc_data_onerr, CTLFLAG_RW, &nfs_skip_wcc_data_onerr, 0, + "Disable weak cache consistency checking when server returns an error"); + +static void nfs_down(struct nfsmount *, struct thread *, const char *, + int, int); +static void nfs_up(struct nfsmount *, struct thread *, const char *, + int, int); +static int nfs_msg(struct thread *, const char *, const char *, int); + +extern int nfsv2_procid[]; + +struct nfs_cached_auth { + int ca_refs; /* refcount, including 1 from the cache */ + uid_t ca_uid; /* uid that corresponds to this auth */ + AUTH *ca_auth; /* RPC auth handle */ +}; + +/* + * RTT estimator + */ + +static enum nfs_rto_timer_t nfs_proct[NFS_NPROCS] = { + NFS_DEFAULT_TIMER, /* NULL */ + NFS_GETATTR_TIMER, /* GETATTR */ + NFS_DEFAULT_TIMER, /* SETATTR */ + NFS_LOOKUP_TIMER, /* LOOKUP */ + NFS_GETATTR_TIMER, /* ACCESS */ + NFS_READ_TIMER, /* READLINK */ + NFS_READ_TIMER, /* READ */ + NFS_WRITE_TIMER, /* WRITE */ + NFS_DEFAULT_TIMER, /* CREATE */ + NFS_DEFAULT_TIMER, /* MKDIR */ + NFS_DEFAULT_TIMER, /* SYMLINK */ + NFS_DEFAULT_TIMER, /* MKNOD */ + NFS_DEFAULT_TIMER, /* REMOVE */ + NFS_DEFAULT_TIMER, /* RMDIR */ + NFS_DEFAULT_TIMER, /* RENAME */ + NFS_DEFAULT_TIMER, /* LINK */ + NFS_READ_TIMER, /* READDIR */ + NFS_READ_TIMER, /* READDIRPLUS */ + NFS_DEFAULT_TIMER, /* FSSTAT */ + NFS_DEFAULT_TIMER, /* FSINFO */ + NFS_DEFAULT_TIMER, /* PATHCONF */ + NFS_DEFAULT_TIMER, /* COMMIT */ + NFS_DEFAULT_TIMER, /* NOOP */ +}; + +/* + * Choose the correct RTT timer for this NFS procedure. + */ +static inline enum nfs_rto_timer_t +nfs_rto_timer(u_int32_t procnum) +{ + return nfs_proct[procnum]; +} + +/* + * Initialize the RTT estimator state for a new mount point. + */ +static void +nfs_init_rtt(struct nfsmount *nmp) +{ + int i; + + for (i = 0; i < NFS_MAX_TIMER; i++) { + nmp->nm_timers[i].rt_srtt = hz; + nmp->nm_timers[i].rt_deviate = 0; + nmp->nm_timers[i].rt_rtxcur = hz; + } +} + +/* + * Initialize sockets and congestion for a new NFS connection. + * We do not free the sockaddr if error. + */ +int +nfs_connect(struct nfsmount *nmp, struct nfsreq *rep) +{ + int rcvreserve, sndreserve; + int pktscale; + struct sockaddr *saddr; + struct ucred *origcred; + struct thread *td = curthread; + CLIENT *client; + struct netconfig *nconf; + rpcvers_t vers; + int one = 1, retries; + + /* + * We need to establish the socket using the credentials of + * the mountpoint. Some parts of this process (such as + * sobind() and soconnect()) will use the curent thread's + * credential instead of the socket credential. To work + * around this, temporarily change the current thread's + * credential to that of the mountpoint. + * + * XXX: It would be better to explicitly pass the correct + * credential to sobind() and soconnect(). + */ + origcred = td->td_ucred; + td->td_ucred = nmp->nm_mountp->mnt_cred; + saddr = nmp->nm_nam; + + vers = NFS_VER2; + if (nmp->nm_flag & NFSMNT_NFSV3) + vers = NFS_VER3; + else if (nmp->nm_flag & NFSMNT_NFSV4) + vers = NFS_VER4; + if (saddr->sa_family == AF_INET) + if (nmp->nm_sotype == SOCK_DGRAM) + nconf = getnetconfigent("udp"); + else + nconf = getnetconfigent("tcp"); + else + if (nmp->nm_sotype == SOCK_DGRAM) + nconf = getnetconfigent("udp6"); + else + nconf = getnetconfigent("tcp6"); + + /* + * Get buffer reservation size from sysctl, but impose reasonable + * limits. + */ + pktscale = nfs_bufpackets; + if (pktscale < 2) + pktscale = 2; + if (pktscale > 64) + pktscale = 64; + mtx_lock(&nmp->nm_mtx); + if (nmp->nm_sotype == SOCK_DGRAM) { + sndreserve = (nmp->nm_wsize + NFS_MAXPKTHDR) * pktscale; + rcvreserve = (max(nmp->nm_rsize, nmp->nm_readdirsize) + + NFS_MAXPKTHDR) * pktscale; + } else if (nmp->nm_sotype == SOCK_SEQPACKET) { + sndreserve = (nmp->nm_wsize + NFS_MAXPKTHDR) * pktscale; + rcvreserve = (max(nmp->nm_rsize, nmp->nm_readdirsize) + + NFS_MAXPKTHDR) * pktscale; + } else { + if (nmp->nm_sotype != SOCK_STREAM) + panic("nfscon sotype"); + sndreserve = (nmp->nm_wsize + NFS_MAXPKTHDR + + sizeof (u_int32_t)) * pktscale; + rcvreserve = (nmp->nm_rsize + NFS_MAXPKTHDR + + sizeof (u_int32_t)) * pktscale; + } + mtx_unlock(&nmp->nm_mtx); + + client = clnt_reconnect_create(nconf, saddr, NFS_PROG, vers, + sndreserve, rcvreserve); + CLNT_CONTROL(client, CLSET_WAITCHAN, "nfsreq"); + if (nmp->nm_flag & NFSMNT_INT) + CLNT_CONTROL(client, CLSET_INTERRUPTIBLE, &one); + if (nmp->nm_flag & NFSMNT_RESVPORT) + CLNT_CONTROL(client, CLSET_PRIVPORT, &one); + if (nmp->nm_flag & NFSMNT_SOFT) + retries = nmp->nm_retry; + else + retries = INT_MAX; + CLNT_CONTROL(client, CLSET_RETRIES, &retries); + + mtx_lock(&nmp->nm_mtx); + if (nmp->nm_client) { + /* + * Someone else already connected. + */ + CLNT_RELEASE(client); + } else { + nmp->nm_client = client; + } + + /* + * Protocols that do not require connections may be optionally left + * unconnected for servers that reply from a port other than NFS_PORT. + */ + if (!(nmp->nm_flag & NFSMNT_NOCONN)) { + mtx_unlock(&nmp->nm_mtx); + CLNT_CONTROL(client, CLSET_CONNECT, &one); + } else { + mtx_unlock(&nmp->nm_mtx); + } + + /* Restore current thread's credentials. */ + td->td_ucred = origcred; + + mtx_lock(&nmp->nm_mtx); + /* Initialize other non-zero congestion variables */ + nfs_init_rtt(nmp); + mtx_unlock(&nmp->nm_mtx); + return (0); +} + +/* + * NFS disconnect. Clean up and unlink. + */ +void +nfs_disconnect(struct nfsmount *nmp) +{ + CLIENT *client; + + mtx_lock(&nmp->nm_mtx); + if (nmp->nm_client) { + client = nmp->nm_client; + nmp->nm_client = NULL; + mtx_unlock(&nmp->nm_mtx); +#ifdef KGSSAPI + rpc_gss_secpurge(client); +#endif + CLNT_CLOSE(client); + CLNT_RELEASE(client); + } else { + mtx_unlock(&nmp->nm_mtx); + } +} + +void +nfs_safedisconnect(struct nfsmount *nmp) +{ + + nfs_disconnect(nmp); +} + +static AUTH * +nfs_getauth(struct nfsmount *nmp, struct ucred *cred) +{ +#ifdef KGSSAPI + rpc_gss_service_t svc; + AUTH *auth; +#endif + + switch (nmp->nm_secflavor) { +#ifdef KGSSAPI + case RPCSEC_GSS_KRB5: + case RPCSEC_GSS_KRB5I: + case RPCSEC_GSS_KRB5P: + if (!nmp->nm_mech_oid) { + if (!rpc_gss_mech_to_oid("kerberosv5", + &nmp->nm_mech_oid)) + return (NULL); + } + if (nmp->nm_secflavor == RPCSEC_GSS_KRB5) + svc = rpc_gss_svc_none; + else if (nmp->nm_secflavor == RPCSEC_GSS_KRB5I) + svc = rpc_gss_svc_integrity; + else + svc = rpc_gss_svc_privacy; + auth = rpc_gss_secfind(nmp->nm_client, cred, + nmp->nm_principal, nmp->nm_mech_oid, svc); + if (auth) + return (auth); + /* fallthrough */ +#endif + case AUTH_SYS: + default: + return (authunix_create(cred)); + + } +} + +/* + * Callback from the RPC code to generate up/down notifications. + */ + +struct nfs_feedback_arg { + struct nfsmount *nf_mount; + int nf_lastmsg; /* last tprintf */ + int nf_tprintfmsg; + struct thread *nf_td; +}; + +static void +nfs_feedback(int type, int proc, void *arg) +{ + struct nfs_feedback_arg *nf = (struct nfs_feedback_arg *) arg; + struct nfsmount *nmp = nf->nf_mount; + struct timeval now; + + getmicrouptime(&now); + + switch (type) { + case FEEDBACK_REXMIT2: + case FEEDBACK_RECONNECT: + if (nf->nf_lastmsg + nmp->nm_tprintf_delay < now.tv_sec) { + nfs_down(nmp, nf->nf_td, + "not responding", 0, NFSSTA_TIMEO); + nf->nf_tprintfmsg = TRUE; + nf->nf_lastmsg = now.tv_sec; + } + break; + + case FEEDBACK_OK: + nfs_up(nf->nf_mount, nf->nf_td, + "is alive again", NFSSTA_TIMEO, nf->nf_tprintfmsg); + break; + } +} + +/* + * nfs_request - goes something like this + * - fill in request struct + * - links it into list + * - calls nfs_send() for first transmit + * - calls nfs_receive() to get reply + * - break down rpc header and return with nfs reply pointed to + * by mrep or error + * nb: always frees up mreq mbuf list + */ +int +nfs_request(struct vnode *vp, struct mbuf *mreq, int procnum, + struct thread *td, struct ucred *cred, struct mbuf **mrp, + struct mbuf **mdp, caddr_t *dposp) +{ + struct mbuf *mrep; + u_int32_t *tl; + struct nfsmount *nmp; + struct mbuf *md; + time_t waituntil; + caddr_t dpos; + int error = 0; + struct timeval now; + AUTH *auth = NULL; + enum nfs_rto_timer_t timer; + struct nfs_feedback_arg nf; + struct rpc_callextra ext; + enum clnt_stat stat; + struct timeval timo; + + /* Reject requests while attempting a forced unmount. */ + if (vp->v_mount->mnt_kern_flag & MNTK_UNMOUNTF) { + m_freem(mreq); + return (ESTALE); + } + nmp = VFSTONFS(vp->v_mount); + if ((nmp->nm_flag & NFSMNT_NFSV4) != 0) + return nfs4_request(vp, mreq, procnum, td, cred, mrp, mdp, dposp); + bzero(&nf, sizeof(struct nfs_feedback_arg)); + nf.nf_mount = nmp; + nf.nf_td = td; + getmicrouptime(&now); + nf.nf_lastmsg = now.tv_sec - + ((nmp->nm_tprintf_delay) - (nmp->nm_tprintf_initial_delay)); + + /* + * XXX if not already connected call nfs_connect now. Longer + * term, change nfs_mount to call nfs_connect unconditionally + * and let clnt_reconnect_create handle reconnects. + */ + if (!nmp->nm_client) + nfs_connect(nmp, NULL); + + auth = nfs_getauth(nmp, cred); + if (!auth) { + m_freem(mreq); + return (EACCES); + } + bzero(&ext, sizeof(ext)); + ext.rc_auth = auth; + + ext.rc_feedback = nfs_feedback; + ext.rc_feedback_arg = &nf; + + /* + * Use a conservative timeout for RPCs other than getattr, + * lookup, read or write. The justification for doing "other" + * this way is that these RPCs happen so infrequently that + * timer est. would probably be stale. Also, since many of + * these RPCs are non-idempotent, a conservative timeout is + * desired. + */ + timer = nfs_rto_timer(procnum); + if (timer != NFS_DEFAULT_TIMER) { + ext.rc_timers = &nmp->nm_timers[timer - 1]; + } else { + ext.rc_timers = NULL; + } + + nfsstats.rpcrequests++; +tryagain: + timo.tv_sec = nmp->nm_timeo / NFS_HZ; + timo.tv_usec = (nmp->nm_timeo * 1000000) / NFS_HZ; + mrep = NULL; + stat = CLNT_CALL_MBUF(nmp->nm_client, &ext, + (nmp->nm_flag & NFSMNT_NFSV3) ? procnum : nfsv2_procid[procnum], + mreq, &mrep, timo); + + /* + * If there was a successful reply and a tprintf msg. + * tprintf a response. + */ + if (stat == RPC_SUCCESS) { + error = 0; + } else if (stat == RPC_TIMEDOUT) { + error = ETIMEDOUT; + } else if (stat == RPC_VERSMISMATCH) { + error = EOPNOTSUPP; + } else if (stat == RPC_PROGVERSMISMATCH) { + error = EPROTONOSUPPORT; + } else { + error = EACCES; + } + md = mrep; + if (error) { + m_freem(mreq); + AUTH_DESTROY(auth); + return (error); + } + + KASSERT(mrep != NULL, ("mrep shouldn't be NULL if no error\n")); + + dpos = mtod(mrep, caddr_t); + tl = nfsm_dissect(u_int32_t *, NFSX_UNSIGNED); + if (*tl != 0) { + error = fxdr_unsigned(int, *tl); + if ((nmp->nm_flag & NFSMNT_NFSV3) && + error == NFSERR_TRYLATER) { + m_freem(mrep); + error = 0; + waituntil = time_second + nfs3_jukebox_delay; + while (time_second < waituntil) { + (void) tsleep(&fake_wchan, PSOCK, "nqnfstry", hz); + } + goto tryagain; + } + + /* + * If the File Handle was stale, invalidate the lookup + * cache, just in case. + */ + if (error == ESTALE) + cache_purge(vp); + /* + * Skip wcc data on NFS errors for now. NetApp filers + * return corrupt postop attrs in the wcc data for NFS + * err EROFS. Not sure if they could return corrupt + * postop attrs for others errors. + */ + if ((nmp->nm_flag & NFSMNT_NFSV3) && !nfs_skip_wcc_data_onerr) { + *mrp = mrep; + *mdp = md; + *dposp = dpos; + error |= NFSERR_RETERR; + } else + m_freem(mrep); + m_freem(mreq); + AUTH_DESTROY(auth); + return (error); + } + + m_freem(mreq); + *mrp = mrep; + *mdp = md; + *dposp = dpos; + AUTH_DESTROY(auth); + return (0); + +nfsmout: + m_freem(mreq); + if (auth) + AUTH_DESTROY(auth); + return (error); +} + +/* + * Mark all of an nfs mount's outstanding requests with R_SOFTTERM and + * wait for all requests to complete. This is used by forced unmounts + * to terminate any outstanding RPCs. + */ +int +nfs_nmcancelreqs(struct nfsmount *nmp) +{ + + if (nmp->nm_client) + CLNT_CLOSE(nmp->nm_client); + return (0); +} + +/* + * Any signal that can interrupt an NFS operation in an intr mount + * should be added to this set. SIGSTOP and SIGKILL cannot be masked. + */ +int nfs_sig_set[] = { + SIGINT, + SIGTERM, + SIGHUP, + SIGKILL, + SIGSTOP, + SIGQUIT +}; + +/* + * Check to see if one of the signals in our subset is pending on + * the process (in an intr mount). + */ +static int +nfs_sig_pending(sigset_t set) +{ + int i; + + for (i = 0 ; i < sizeof(nfs_sig_set)/sizeof(int) ; i++) + if (SIGISMEMBER(set, nfs_sig_set[i])) + return (1); + return (0); +} + +/* + * The set/restore sigmask functions are used to (temporarily) overwrite + * the process p_sigmask during an RPC call (for example). These are also + * used in other places in the NFS client that might tsleep(). + */ +void +nfs_set_sigmask(struct thread *td, sigset_t *oldset) +{ + sigset_t newset; + int i; + struct proc *p; + + SIGFILLSET(newset); + if (td == NULL) + td = curthread; /* XXX */ + p = td->td_proc; + /* Remove the NFS set of signals from newset */ + PROC_LOCK(p); + mtx_lock(&p->p_sigacts->ps_mtx); + for (i = 0 ; i < sizeof(nfs_sig_set)/sizeof(int) ; i++) { + /* + * But make sure we leave the ones already masked + * by the process, ie. remove the signal from the + * temporary signalmask only if it wasn't already + * in p_sigmask. + */ + if (!SIGISMEMBER(td->td_sigmask, nfs_sig_set[i]) && + !SIGISMEMBER(p->p_sigacts->ps_sigignore, nfs_sig_set[i])) + SIGDELSET(newset, nfs_sig_set[i]); + } + mtx_unlock(&p->p_sigacts->ps_mtx); + PROC_UNLOCK(p); + kern_sigprocmask(td, SIG_SETMASK, &newset, oldset, 0); +} + +void +nfs_restore_sigmask(struct thread *td, sigset_t *set) +{ + if (td == NULL) + td = curthread; /* XXX */ + kern_sigprocmask(td, SIG_SETMASK, set, NULL, 0); +} + +/* + * NFS wrapper to msleep(), that shoves a new p_sigmask and restores the + * old one after msleep() returns. + */ +int +nfs_msleep(struct thread *td, void *ident, struct mtx *mtx, int priority, char *wmesg, int timo) +{ + sigset_t oldset; + int error; + struct proc *p; + + if ((priority & PCATCH) == 0) + return msleep(ident, mtx, priority, wmesg, timo); + if (td == NULL) + td = curthread; /* XXX */ + nfs_set_sigmask(td, &oldset); + error = msleep(ident, mtx, priority, wmesg, timo); + nfs_restore_sigmask(td, &oldset); + p = td->td_proc; + return (error); +} + +/* + * Test for a termination condition pending on the process. + * This is used for NFSMNT_INT mounts. + */ +int +nfs_sigintr(struct nfsmount *nmp, struct nfsreq *rep, struct thread *td) +{ + struct proc *p; + sigset_t tmpset; + + if ((nmp->nm_flag & NFSMNT_NFSV4) != 0) + return nfs4_sigintr(nmp, rep, td); + /* Terminate all requests while attempting a forced unmount. */ + if (nmp->nm_mountp->mnt_kern_flag & MNTK_UNMOUNTF) + return (EIO); + if (!(nmp->nm_flag & NFSMNT_INT)) + return (0); + if (td == NULL) + return (0); + p = td->td_proc; + PROC_LOCK(p); + tmpset = p->p_siglist; + SIGSETOR(tmpset, td->td_siglist); + SIGSETNAND(tmpset, td->td_sigmask); + mtx_lock(&p->p_sigacts->ps_mtx); + SIGSETNAND(tmpset, p->p_sigacts->ps_sigignore); + mtx_unlock(&p->p_sigacts->ps_mtx); + if ((SIGNOTEMPTY(p->p_siglist) || SIGNOTEMPTY(td->td_siglist)) + && nfs_sig_pending(tmpset)) { + PROC_UNLOCK(p); + return (EINTR); + } + PROC_UNLOCK(p); + return (0); +} + +static int +nfs_msg(struct thread *td, const char *server, const char *msg, int error) +{ + struct proc *p; + + p = td ? td->td_proc : NULL; + if (error) { + tprintf(p, LOG_INFO, "nfs server %s: %s, error %d\n", server, + msg, error); + } else { + tprintf(p, LOG_INFO, "nfs server %s: %s\n", server, msg); + } + return (0); +} + +static void +nfs_down(struct nfsmount *nmp, struct thread *td, const char *msg, + int error, int flags) +{ + if (nmp == NULL) + return; + mtx_lock(&nmp->nm_mtx); + if ((flags & NFSSTA_TIMEO) && !(nmp->nm_state & NFSSTA_TIMEO)) { + nmp->nm_state |= NFSSTA_TIMEO; + mtx_unlock(&nmp->nm_mtx); + vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid, + VQ_NOTRESP, 0); + } else + mtx_unlock(&nmp->nm_mtx); + mtx_lock(&nmp->nm_mtx); + if ((flags & NFSSTA_LOCKTIMEO) && !(nmp->nm_state & NFSSTA_LOCKTIMEO)) { + nmp->nm_state |= NFSSTA_LOCKTIMEO; + mtx_unlock(&nmp->nm_mtx); + vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid, + VQ_NOTRESPLOCK, 0); + } else + mtx_unlock(&nmp->nm_mtx); + nfs_msg(td, nmp->nm_mountp->mnt_stat.f_mntfromname, msg, error); +} + +static void +nfs_up(struct nfsmount *nmp, struct thread *td, const char *msg, + int flags, int tprintfmsg) +{ + if (nmp == NULL) + return; + if (tprintfmsg) { + nfs_msg(td, nmp->nm_mountp->mnt_stat.f_mntfromname, msg, 0); + } + + mtx_lock(&nmp->nm_mtx); + if ((flags & NFSSTA_TIMEO) && (nmp->nm_state & NFSSTA_TIMEO)) { + nmp->nm_state &= ~NFSSTA_TIMEO; + mtx_unlock(&nmp->nm_mtx); + vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid, + VQ_NOTRESP, 1); + } else + mtx_unlock(&nmp->nm_mtx); + + mtx_lock(&nmp->nm_mtx); + if ((flags & NFSSTA_LOCKTIMEO) && (nmp->nm_state & NFSSTA_LOCKTIMEO)) { + nmp->nm_state &= ~NFSSTA_LOCKTIMEO; + mtx_unlock(&nmp->nm_mtx); + vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid, + VQ_NOTRESPLOCK, 1); + } else + mtx_unlock(&nmp->nm_mtx); +} + +#endif /* !NFS_LEGACYRPC */ diff --git a/sys/nfsclient/nfs_socket.c b/sys/nfsclient/nfs_socket.c index a3b23a1..a61962f 100644 --- a/sys/nfsclient/nfs_socket.c +++ b/sys/nfsclient/nfs_socket.c @@ -74,6 +74,8 @@ __FBSDID("$FreeBSD$"); #include +#ifdef NFS_LEGACYRPC + #define TRUE 1 #define FALSE 0 @@ -1976,3 +1978,5 @@ nfs_up(rep, nmp, td, msg, flags) mtx_unlock(&nmp->nm_mtx); #endif } + +#endif /* NFS_LEGACYRPC */ diff --git a/sys/nfsclient/nfs_subs.c b/sys/nfsclient/nfs_subs.c index b97fc3f..be6ab92 100644 --- a/sys/nfsclient/nfs_subs.c +++ b/sys/nfsclient/nfs_subs.c @@ -99,8 +99,10 @@ static enum vtype nv2tov_type[8]= { int nfs_ticks; int nfs_pbuf_freecnt = -1; /* start out unlimited */ +#ifdef NFS_LEGACYRPC struct nfs_reqq nfs_reqq; struct mtx nfs_reqq_mtx; +#endif struct nfs_bufq nfs_bufq; static struct mtx nfs_xid_mtx; @@ -430,9 +432,11 @@ nfs_init(struct vfsconf *vfsp) /* * Initialize reply list and start timer */ +#ifdef NFS_LEGACYRPC TAILQ_INIT(&nfs_reqq); - callout_init(&nfs_callout, CALLOUT_MPSAFE); mtx_init(&nfs_reqq_mtx, "NFS reqq lock", NULL, MTX_DEF); + callout_init(&nfs_callout, CALLOUT_MPSAFE); +#endif mtx_init(&nfs_iod_mtx, "NFS iod lock", NULL, MTX_DEF); mtx_init(&nfs_xid_mtx, "NFS xid lock", NULL, MTX_DEF); @@ -446,10 +450,12 @@ nfs_uninit(struct vfsconf *vfsp) { int i; +#ifdef NFS_LEGACYRPC callout_stop(&nfs_callout); KASSERT(TAILQ_EMPTY(&nfs_reqq), ("nfs_uninit: request queue not empty")); +#endif /* * Tell all nfsiod processes to exit. Clear nfs_iodmax, and wakeup diff --git a/sys/nfsclient/nfs_vfsops.c b/sys/nfsclient/nfs_vfsops.c index d3044ff..00332f0 100644 --- a/sys/nfsclient/nfs_vfsops.c +++ b/sys/nfsclient/nfs_vfsops.c @@ -67,6 +67,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include @@ -142,6 +143,12 @@ VFS_SET(nfs_vfsops, nfs, VFCF_NETWORK); /* So that loader and kldload(2) can find us, wherever we are.. */ MODULE_VERSION(nfs, 1); +#ifndef NFS_LEGACYRPC +MODULE_DEPEND(nfs, krpc, 1, 1, 1); +#endif +#ifdef KGSSAPI +MODULE_DEPEND(nfs, kgssapi, 1, 1, 1); +#endif static struct nfs_rpcops nfs_rpcops = { nfs_readrpc, @@ -546,6 +553,26 @@ nfs_mountdiskless(char *path, return (0); } +#ifndef NFS_LEGACYRPC +static int +nfs_sec_name_to_num(char *sec) +{ + if (!strcmp(sec, "krb5")) + return (RPCSEC_GSS_KRB5); + if (!strcmp(sec, "krb5i")) + return (RPCSEC_GSS_KRB5I); + if (!strcmp(sec, "krb5p")) + return (RPCSEC_GSS_KRB5P); + if (!strcmp(sec, "sys")) + return (AUTH_SYS); + /* + * Userland should validate the string but we will try and + * cope with unexpected values. + */ + return (AUTH_SYS); +} +#endif + static void nfs_decode_args(struct mount *mp, struct nfsmount *nmp, struct nfs_args *argp, const char *hostname) @@ -554,6 +581,10 @@ nfs_decode_args(struct mount *mp, struct nfsmount *nmp, struct nfs_args *argp, int adjsock; int maxio; char *p; +#ifndef NFS_LEGACYRPC + char *secname; + char *principal; +#endif s = splnet(); @@ -705,7 +736,13 @@ nfs_decode_args(struct mount *mp, struct nfsmount *nmp, struct nfs_args *argp, nmp->nm_sotype = argp->sotype; nmp->nm_soproto = argp->proto; - if (nmp->nm_so && adjsock) { + if ( +#ifdef NFS_LEGACYRPC + nmp->nm_so +#else + nmp->nm_client +#endif + && adjsock) { nfs_safedisconnect(nmp); if (nmp->nm_sotype == SOCK_DGRAM) while (nfs_connect(nmp, NULL)) { @@ -721,6 +758,24 @@ nfs_decode_args(struct mount *mp, struct nfsmount *nmp, struct nfs_args *argp, if (p) *p = '\0'; } + +#ifndef NFS_LEGACYRPC + if (vfs_getopt(mp->mnt_optnew, "sec", + (void **) &secname, NULL) == 0) { + nmp->nm_secflavor = nfs_sec_name_to_num(secname); + } else { + nmp->nm_secflavor = AUTH_SYS; + } + + if (vfs_getopt(mp->mnt_optnew, "principal", + (void **) &principal, NULL) == 0) { + strlcpy(nmp->nm_principal, principal, + sizeof(nmp->nm_principal)); + } else { + snprintf(nmp->nm_principal, sizeof(nmp->nm_principal), + "nfs@%s", nmp->nm_hostname); + } +#endif } static const char *nfs_opts[] = { "from", "nfs_args", @@ -729,8 +784,8 @@ static const char *nfs_opts[] = { "from", "nfs_args", "async", "dumbtimer", "noconn", "nolockd", "intr", "rdirplus", "resvport", "readdirsize", "soft", "hard", "mntudp", "tcp", "udp", "wsize", "rsize", "retrans", "acregmin", "acregmax", "acdirmin", "acdirmax", - "deadthresh", "hostname", "timeout", "addr", "fh", "nfsv3", - "maxgroups", + "deadthresh", "hostname", "timeout", "addr", "fh", "nfsv3", "sec", + "maxgroups", "principal", NULL }; /* diff --git a/sys/nfsclient/nfsmount.h b/sys/nfsclient/nfsmount.h index 6fa7f8b..75360fe 100644 --- a/sys/nfsclient/nfsmount.h +++ b/sys/nfsclient/nfsmount.h @@ -36,6 +36,25 @@ #ifndef _NFSCLIENT_NFSMOUNT_H_ #define _NFSCLIENT_NFSMOUNT_H_ +#ifndef NFS_LEGACYRPC + +#undef RPC_SUCCESS +#undef RPC_PROGUNAVAIL +#undef RPC_PROCUNAVAIL +#undef AUTH_OK +#undef AUTH_BADCRED +#undef AUTH_BADVERF +#undef AUTH_TOOWEAK + +#include +#include +#include +#include + +#endif + +#ifdef NFS_LEGACYRPC + struct nfs_tcp_mountstate { int rpcresid; #define NFS_TCP_EXPECT_RPCMARKER 0x0001 /* Expect to see a RPC/TCP marker next */ @@ -45,6 +64,8 @@ struct nfs_tcp_mountstate { int sock_send_inprog; }; +#endif + /* * Mount structure. * One allocated on every NFS mount. @@ -59,18 +80,22 @@ struct nfsmount { u_char nm_fh[NFSX_V4FH]; /* File handle of root dir */ int nm_fhsize; /* Size of root file handle */ struct rpcclnt nm_rpcclnt; /* rpc state */ +#ifdef NFS_LEGACYRPC struct socket *nm_so; /* Rpc socket */ +#endif int nm_sotype; /* Type of socket */ int nm_soproto; /* and protocol */ int nm_soflags; /* pr_flags for socket protocol */ struct sockaddr *nm_nam; /* Addr of server */ int nm_timeo; /* Init timer for NFSMNT_DUMBTIMR */ int nm_retry; /* Max retries */ +#ifdef NFS_LEGACYRPC int nm_srtt[NFS_MAX_TIMER], /* RTT Timers for rpcs */ nm_sdrtt[NFS_MAX_TIMER]; int nm_sent; /* Request send count */ int nm_cwnd; /* Request send window */ int nm_timeouts; /* Request timeouts */ +#endif int nm_deadthresh; /* Threshold of timeouts-->dead server*/ int nm_rsize; /* Max size of read rpc */ int nm_wsize; /* Max size of write rpc */ @@ -90,8 +115,17 @@ struct nfsmount { struct nfs_rpcops *nm_rpcops; int nm_tprintf_initial_delay; /* initial delay */ int nm_tprintf_delay; /* interval for messages */ +#ifdef NFS_LEGACYRPC struct nfs_tcp_mountstate nm_nfstcpstate; +#endif char nm_hostname[MNAMELEN]; /* server's name */ +#ifndef NFS_LEGACYRPC + int nm_secflavor; /* auth flavor to use for rpc */ + struct __rpc_client *nm_client; + struct rpc_timers nm_timers[NFS_MAX_TIMER]; /* RTT Timers for rpcs */ + char nm_principal[MNAMELEN]; /* GSS-API principal of server */ + gss_OID nm_mech_oid; /* OID of selected GSS-API mechanism */ +#endif /* NFSv4 */ uint64_t nm_clientid; diff --git a/sys/nfsserver/nfs.h b/sys/nfsserver/nfs.h index beb9359..2709377 100644 --- a/sys/nfsserver/nfs.h +++ b/sys/nfsserver/nfs.h @@ -89,13 +89,26 @@ * Structures for the nfssvc(2) syscall. Not that anyone but nfsd and mount_nfs * should ever try and use it. */ -struct nfsd_args { + +/* + * Add a socket to monitor for NFS requests. + */ +struct nfsd_addsock_args { int sock; /* Socket to serve */ caddr_t name; /* Client addr for connection based sockets */ int namelen; /* Length of name */ }; /* + * Start processing requests. + */ +struct nfsd_nfsd_args { + const char *principal; /* GSS-API service principal name */ + int minthreads; /* minimum service thread count */ + int maxthreads; /* maximum service thread count */ +}; + +/* * XXX to allow amd to include nfs.h without nfsproto.h */ #ifdef NFS_NPROCS @@ -105,8 +118,9 @@ struct nfsd_args { /* * Flags for nfssvc() system call. */ -#define NFSSVC_NFSD 0x004 +#define NFSSVC_OLDNFSD 0x004 #define NFSSVC_ADDSOCK 0x008 +#define NFSSVC_NFSD 0x010 /* * vfs.nfsrv sysctl(3) identifiers @@ -167,6 +181,7 @@ extern int32_t (*nfsrv3_procs[NFS_NPROCS])(struct nfsrv_descript *nd, #define NWDELAYHASH(sock, f) \ (&(sock)->ns_wdelayhashtbl[(*((u_int32_t *)(f))) % NFS_WDELAYHASHSIZ]) +#ifdef NFS_LEGACYRPC /* * Network address hash list element */ @@ -257,11 +272,37 @@ struct nfsrv_descript { struct timeval nd_starttime; /* Time RPC initiated */ fhandle_t nd_fh; /* File handle */ struct ucred *nd_cr; /* Credentials */ + int nd_credflavor; /* Security flavor */ }; +#else + +/* + * This structure is used by the server for describing each request. + */ +struct nfsrv_descript { + struct mbuf *nd_mrep; /* Request mbuf list */ + struct mbuf *nd_md; /* Current dissect mbuf */ + struct mbuf *nd_mreq; /* Reply mbuf list */ + struct sockaddr *nd_nam; /* and socket addr */ + struct sockaddr *nd_nam2; /* return socket addr */ + caddr_t nd_dpos; /* Current dissect pos */ + u_int32_t nd_procnum; /* RPC # */ + int nd_stable; /* storage type */ + int nd_flag; /* nd_flag */ + int nd_repstat; /* Reply status */ + fhandle_t nd_fh; /* File handle */ + struct ucred *nd_cr; /* Credentials */ + int nd_credflavor; /* Security flavor */ +}; + +#endif + /* Bits for "nd_flag" */ #define ND_NFSV3 0x08 +#ifdef NFS_LEGACYRPC + extern TAILQ_HEAD(nfsd_head, nfsd) nfsd_head; extern int nfsd_head_flag; #define NFSD_CHECKSLP 0x01 @@ -273,6 +314,8 @@ extern int nfsd_head_flag; ((o)->nd_eoff >= (n)->nd_off && \ !bcmp((caddr_t)&(o)->nd_fh, (caddr_t)&(n)->nd_fh, NFSX_V3FH)) +#endif + /* * Defines for WebNFS */ @@ -315,38 +358,42 @@ extern int nfs_debug; #endif +#ifdef NFS_LEGACYRPC +int netaddr_match(int, union nethostaddr *, struct sockaddr *); int nfs_getreq(struct nfsrv_descript *, struct nfsd *, int); int nfsrv_send(struct socket *, struct sockaddr *, struct mbuf *); -struct mbuf *nfs_rephead(int, struct nfsrv_descript *, int, struct mbuf **, - caddr_t *); +int nfsrv_dorec(struct nfssvc_sock *, struct nfsd *, + struct nfsrv_descript **); int nfs_slplock(struct nfssvc_sock *, int); void nfs_slpunlock(struct nfssvc_sock *); +void nfsrv_initcache(void); +void nfsrv_destroycache(void); +void nfsrv_timer(void *); +int nfsrv_getcache(struct nfsrv_descript *, struct mbuf **); +void nfsrv_updatecache(struct nfsrv_descript *, int, struct mbuf *); +void nfsrv_cleancache(void); +void nfsrv_rcv(struct socket *so, void *arg, int waitflag); +void nfsrv_slpderef(struct nfssvc_sock *slp); +void nfsrv_wakenfsd(struct nfssvc_sock *slp); +int nfsrv_writegather(struct nfsrv_descript **, struct nfssvc_sock *, + struct mbuf **); +#endif +struct mbuf *nfs_rephead(int, struct nfsrv_descript *, int, struct mbuf **, + caddr_t *); void nfsm_srvfattr(struct nfsrv_descript *, struct vattr *, struct nfs_fattr *); void nfsm_srvwcc(struct nfsrv_descript *, int, struct vattr *, int, struct vattr *, struct mbuf **, char **); void nfsm_srvpostopattr(struct nfsrv_descript *, int, struct vattr *, struct mbuf **, char **); -int netaddr_match(int, union nethostaddr *, struct sockaddr *); -int nfs_namei(struct nameidata *, fhandle_t *, int, - struct nfssvc_sock *, struct sockaddr *, struct mbuf **, +int nfs_namei(struct nameidata *, struct nfsrv_descript *, fhandle_t *, + int, struct nfssvc_sock *, struct sockaddr *, struct mbuf **, caddr_t *, struct vnode **, int, struct vattr *, int *, int); void nfsm_adj(struct mbuf *, int, int); int nfsm_mbuftouio(struct mbuf **, struct uio *, int, caddr_t *); -void nfsrv_initcache(void); -void nfsrv_destroycache(void); -void nfsrv_timer(void *); -int nfsrv_dorec(struct nfssvc_sock *, struct nfsd *, - struct nfsrv_descript **); -int nfsrv_getcache(struct nfsrv_descript *, struct mbuf **); -void nfsrv_updatecache(struct nfsrv_descript *, int, struct mbuf *); -void nfsrv_cleancache(void); void nfsrv_init(int); int nfsrv_errmap(struct nfsrv_descript *, int); void nfsrvw_sort(gid_t *, int); -void nfsrv_wakenfsd(struct nfssvc_sock *slp); -int nfsrv_writegather(struct nfsrv_descript **, struct nfssvc_sock *, - struct mbuf **); int nfsrv3_access(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, struct mbuf **mrq); @@ -354,8 +401,9 @@ int nfsrv_commit(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, struct mbuf **mrq); int nfsrv_create(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, struct mbuf **mrq); -int nfsrv_fhtovp(fhandle_t *, int, struct vnode **, int *, struct ucred *, - struct nfssvc_sock *, struct sockaddr *, int *, int); +int nfsrv_fhtovp(fhandle_t *, int, struct vnode **, int *, + struct nfsrv_descript *, struct nfssvc_sock *, struct sockaddr *, + int *, int); int nfsrv_setpublicfs(struct mount *, struct netexport *, struct export_args *); int nfs_ispublicfh(fhandle_t *); @@ -399,8 +447,6 @@ int nfsrv_symlink(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, struct mbuf **mrq); int nfsrv_write(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, struct mbuf **mrq); -void nfsrv_rcv(struct socket *so, void *arg, int waitflag); -void nfsrv_slpderef(struct nfssvc_sock *slp); #endif /* _KERNEL */ #endif diff --git a/sys/nfsserver/nfs_fha.c b/sys/nfsserver/nfs_fha.c new file mode 100644 index 0000000..a2e4ca3 --- /dev/null +++ b/sys/nfsserver/nfs_fha.c @@ -0,0 +1,597 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef NFS_LEGACYRPC + +static MALLOC_DEFINE(M_NFS_FHA, "NFS FHA", "NFS FHA"); + +/* Sysctl defaults. */ +#define DEF_BIN_SHIFT 18 /* 256k */ +#define DEF_MAX_NFSDS_PER_FH 8 +#define DEF_MAX_REQS_PER_NFSD 4 + +struct fha_ctls { + u_int32_t bin_shift; + u_int32_t max_nfsds_per_fh; + u_int32_t max_reqs_per_nfsd; +} fha_ctls; + +struct sysctl_ctx_list fha_clist; + +SYSCTL_DECL(_vfs_nfsrv); +SYSCTL_DECL(_vfs_nfsrv_fha); + +/* Static sysctl node for the fha from the top-level vfs_nfsrv node. */ +SYSCTL_NODE(_vfs_nfsrv, OID_AUTO, fha, CTLFLAG_RD, 0, "fha node"); + +/* This is the global structure that represents the state of the fha system. */ +static struct fha_global { + struct fha_hash_entry_list *hashtable; + u_long hashmask; +} g_fha; + +/* + * These are the entries in the filehandle hash. They talk about a specific + * file, requests against which are being handled by one or more nfsds. We keep + * a chain of nfsds against the file. We only have more than one if reads are + * ongoing, and then only if the reads affect disparate regions of the file. + * + * In general, we want to assign a new request to an existing nfsd if it is + * going to contend with work happening already on that nfsd, or if the + * operation is a read and the nfsd is already handling a proximate read. We + * do this to avoid jumping around in the read stream unnecessarily, and to + * avoid contention between threads over single files. + */ +struct fha_hash_entry { + LIST_ENTRY(fha_hash_entry) link; + u_int64_t fh; + u_int16_t num_reads; + u_int16_t num_writes; + u_int8_t num_threads; + struct svcthread_list threads; +}; +LIST_HEAD(fha_hash_entry_list, fha_hash_entry); + +/* A structure used for passing around data internally. */ +struct fha_info { + u_int64_t fh; + off_t offset; + int locktype; +}; + +static int fhe_stats_sysctl(SYSCTL_HANDLER_ARGS); + +static void +nfs_fha_init(void *foo) +{ + + /* + * A small hash table to map filehandles to fha_hash_entry + * structures. + */ + g_fha.hashtable = hashinit(256, M_NFS_FHA, &g_fha.hashmask); + + /* + * Initialize the sysctl context list for the fha module. + */ + sysctl_ctx_init(&fha_clist); + + fha_ctls.bin_shift = DEF_BIN_SHIFT; + fha_ctls.max_nfsds_per_fh = DEF_MAX_NFSDS_PER_FH; + fha_ctls.max_reqs_per_nfsd = DEF_MAX_REQS_PER_NFSD; + + SYSCTL_ADD_UINT(&fha_clist, SYSCTL_STATIC_CHILDREN(_vfs_nfsrv_fha), + OID_AUTO, "bin_shift", CTLFLAG_RW, + &fha_ctls.bin_shift, 0, "For FHA reads, no two requests will " + "contend if they're 2^(bin_shift) bytes apart"); + + SYSCTL_ADD_UINT(&fha_clist, SYSCTL_STATIC_CHILDREN(_vfs_nfsrv_fha), + OID_AUTO, "max_nfsds_per_fh", CTLFLAG_RW, + &fha_ctls.max_nfsds_per_fh, 0, "Maximum nfsd threads that " + "should be working on requests for the same file handle"); + + SYSCTL_ADD_UINT(&fha_clist, SYSCTL_STATIC_CHILDREN(_vfs_nfsrv_fha), + OID_AUTO, "max_reqs_per_nfsd", CTLFLAG_RW, + &fha_ctls.max_reqs_per_nfsd, 0, "Maximum requests that " + "single nfsd thread should be working on at any time"); + + SYSCTL_ADD_OID(&fha_clist, SYSCTL_STATIC_CHILDREN(_vfs_nfsrv_fha), + OID_AUTO, "fhe_stats", CTLTYPE_STRING | CTLFLAG_RD, 0, 0, + fhe_stats_sysctl, "A", ""); +} + +static void +nfs_fha_uninit(void *foo) +{ + + hashdestroy(g_fha.hashtable, M_NFS_FHA, g_fha.hashmask); +} + +SYSINIT(nfs_fha, SI_SUB_ROOT_CONF, SI_ORDER_ANY, nfs_fha_init, NULL); +SYSUNINIT(nfs_fha, SI_SUB_ROOT_CONF, SI_ORDER_ANY, nfs_fha_uninit, NULL); + +/* + * This just specifies that offsets should obey affinity when within + * the same 1Mbyte (1<<20) chunk for the file (reads only for now). + */ +static void +fha_extract_info(struct svc_req *req, struct fha_info *i) +{ + struct mbuf *md = req->rq_args; + fhandle_t fh; + caddr_t dpos = mtod(md, caddr_t); + static u_int64_t random_fh = 0; + int error; + int v3 = (req->rq_vers == 3); + u_int32_t *tl; + rpcproc_t procnum; + + /* + * We start off with a random fh. If we get a reasonable + * procnum, we set the fh. If there's a concept of offset + * that we're interested in, we set that. + */ + i->fh = ++random_fh; + i->offset = 0; + i->locktype = LK_EXCLUSIVE; + + /* + * Extract the procnum and convert to v3 form if necessary. + */ + procnum = req->rq_proc; + if (!v3) + procnum = nfsrv_nfsv3_procid[procnum]; + + /* + * We do affinity for most. However, we divide a realm of affinity + * by file offset so as to allow for concurrent random access. We + * only do this for reads today, but this may change when IFS supports + * efficient concurrent writes. + */ + if (procnum == NFSPROC_FSSTAT || + procnum == NFSPROC_FSINFO || + procnum == NFSPROC_PATHCONF || + procnum == NFSPROC_NOOP || + procnum == NFSPROC_NULL) + goto out; + + /* Grab the filehandle. */ + error = nfsm_srvmtofh_xx(&fh, v3, &md, &dpos); + if (error) + goto out; + + i->fh = *(const u_int64_t *)(fh.fh_fid.fid_data); + + /* Content ourselves with zero offset for all but reads. */ + if (procnum != NFSPROC_READ) + goto out; + + if (v3) { + tl = nfsm_dissect_xx_nonblock(2 * NFSX_UNSIGNED, &md, &dpos); + if (tl == NULL) + goto out; + i->offset = fxdr_hyper(tl); + } else { + tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, &md, &dpos); + if (tl == NULL) + goto out; + i->offset = fxdr_unsigned(u_int32_t, *tl); + } + out: + switch (procnum) { + case NFSPROC_NULL: + case NFSPROC_GETATTR: + case NFSPROC_LOOKUP: + case NFSPROC_ACCESS: + case NFSPROC_READLINK: + case NFSPROC_READ: + case NFSPROC_READDIR: + case NFSPROC_READDIRPLUS: + i->locktype = LK_SHARED; + break; + case NFSPROC_SETATTR: + case NFSPROC_WRITE: + case NFSPROC_CREATE: + case NFSPROC_MKDIR: + case NFSPROC_SYMLINK: + case NFSPROC_MKNOD: + case NFSPROC_REMOVE: + case NFSPROC_RMDIR: + case NFSPROC_RENAME: + case NFSPROC_LINK: + case NFSPROC_FSSTAT: + case NFSPROC_FSINFO: + case NFSPROC_PATHCONF: + case NFSPROC_COMMIT: + case NFSPROC_NOOP: + i->locktype = LK_EXCLUSIVE; + break; + } +} + +static struct fha_hash_entry * +fha_hash_entry_new(u_int64_t fh) +{ + struct fha_hash_entry *e; + + e = malloc(sizeof(*e), M_NFS_FHA, M_WAITOK); + e->fh = fh; + e->num_reads = 0; + e->num_writes = 0; + e->num_threads = 0; + LIST_INIT(&e->threads); + + return e; +} + +static void +fha_hash_entry_destroy(struct fha_hash_entry *e) +{ + + if (e->num_reads + e->num_writes) + panic("nonempty fhe"); + free(e, M_NFS_FHA); +} + +static void +fha_hash_entry_remove(struct fha_hash_entry *e) +{ + + LIST_REMOVE(e, link); + fha_hash_entry_destroy(e); +} + +static struct fha_hash_entry * +fha_hash_entry_lookup(SVCPOOL *pool, u_int64_t fh) +{ + struct fha_hash_entry *fhe, *new_fhe; + + LIST_FOREACH(fhe, &g_fha.hashtable[fh % g_fha.hashmask], link) { + if (fhe->fh == fh) + break; + } + + if (!fhe) { + /* Allocate a new entry. */ + mtx_unlock(&pool->sp_lock); + new_fhe = fha_hash_entry_new(fh); + mtx_lock(&pool->sp_lock); + + /* Double-check to make sure we still need the new entry. */ + LIST_FOREACH(fhe, &g_fha.hashtable[fh % g_fha.hashmask], link) { + if (fhe->fh == fh) + break; + } + if (!fhe) { + fhe = new_fhe; + LIST_INSERT_HEAD(&g_fha.hashtable[fh % g_fha.hashmask], + fhe, link); + } else { + fha_hash_entry_destroy(new_fhe); + } + } + + return fhe; +} + +static void +fha_hash_entry_add_thread(struct fha_hash_entry *fhe, SVCTHREAD *thread) +{ + LIST_INSERT_HEAD(&fhe->threads, thread, st_alink); + fhe->num_threads++; +} + +static void +fha_hash_entry_remove_thread(struct fha_hash_entry *fhe, SVCTHREAD *thread) +{ + + LIST_REMOVE(thread, st_alink); + fhe->num_threads--; +} + +/* + * Account for an ongoing operation associated with this file. + */ +static void +fha_hash_entry_add_op(struct fha_hash_entry *fhe, int locktype, int count) +{ + + if (LK_EXCLUSIVE == locktype) + fhe->num_writes += count; + else + fhe->num_reads += count; +} + +static SVCTHREAD * +get_idle_thread(SVCPOOL *pool) +{ + SVCTHREAD *st; + + LIST_FOREACH(st, &pool->sp_idlethreads, st_ilink) { + if (st->st_xprt == NULL && STAILQ_EMPTY(&st->st_reqs)) + return (st); + } + return (NULL); +} + + +/* + * Get the service thread currently associated with the fhe that is + * appropriate to handle this operation. + */ +SVCTHREAD * +fha_hash_entry_choose_thread(SVCPOOL *pool, struct fha_hash_entry *fhe, + struct fha_info *i, SVCTHREAD *this_thread); + +SVCTHREAD * +fha_hash_entry_choose_thread(SVCPOOL *pool, struct fha_hash_entry *fhe, + struct fha_info *i, SVCTHREAD *this_thread) +{ + SVCTHREAD *thread, *min_thread = NULL; + int req_count, min_count = 0; + off_t offset1, offset2; + + LIST_FOREACH(thread, &fhe->threads, st_alink) { + req_count = thread->st_reqcount; + + /* If there are any writes in progress, use the first thread. */ + if (fhe->num_writes) { +#if 0 + ITRACE_CURPROC(ITRACE_NFS, ITRACE_INFO, + "fha: %p(%d)w", thread, req_count); +#endif + return (thread); + } + + /* + * Check for read locality, making sure that we won't + * exceed our per-thread load limit in the process. + */ + offset1 = i->offset >> fha_ctls.bin_shift; + offset2 = STAILQ_FIRST(&thread->st_reqs)->rq_p3 + >> fha_ctls.bin_shift; + if (offset1 == offset2) { + if ((fha_ctls.max_reqs_per_nfsd == 0) || + (req_count < fha_ctls.max_reqs_per_nfsd)) { +#if 0 + ITRACE_CURPROC(ITRACE_NFS, ITRACE_INFO, + "fha: %p(%d)r", thread, req_count); +#endif + return (thread); + } + } + + /* + * We don't have a locality match, so skip this thread, + * but keep track of the most attractive thread in case + * we need to come back to it later. + */ +#if 0 + ITRACE_CURPROC(ITRACE_NFS, ITRACE_INFO, + "fha: %p(%d)s off1 %llu off2 %llu", thread, + req_count, offset1, offset2); +#endif + if ((min_thread == NULL) || (req_count < min_count)) { + min_count = req_count; + min_thread = thread; + } + } + + /* + * We didn't find a good match yet. See if we can add + * a new thread to this file handle entry's thread list. + */ + if ((fha_ctls.max_nfsds_per_fh == 0) || + (fhe->num_threads < fha_ctls.max_nfsds_per_fh)) { + /* + * We can add a new thread, so try for an idle thread + * first, and fall back to this_thread if none are idle. + */ + if (STAILQ_EMPTY(&this_thread->st_reqs)) { + thread = this_thread; +#if 0 + ITRACE_CURPROC(ITRACE_NFS, ITRACE_INFO, + "fha: %p(%d)t", thread, thread->st_reqcount); +#endif + } else if ((thread = get_idle_thread(pool))) { +#if 0 + ITRACE_CURPROC(ITRACE_NFS, ITRACE_INFO, + "fha: %p(%d)i", thread, thread->st_reqcount); +#endif + } else { + thread = this_thread; +#if 0 + ITRACE_CURPROC(ITRACE_NFS, ITRACE_INFO, + "fha: %p(%d)b", thread, thread->st_reqcount); +#endif + } + fha_hash_entry_add_thread(fhe, thread); + } else { + /* + * We don't want to use any more threads for this file, so + * go back to the most attractive nfsd we're already using. + */ + thread = min_thread; + } + + return (thread); +} + +/* + * After getting a request, try to assign it to some thread. Usually we + * handle it ourselves. + */ +SVCTHREAD * +fha_assign(SVCTHREAD *this_thread, struct svc_req *req) +{ + SVCPOOL *pool; + SVCTHREAD *thread; + struct fha_info i; + struct fha_hash_entry *fhe; + + /* + * Only do placement if this is an NFS request. + */ + if (req->rq_prog != NFS_PROG) + return (this_thread); + + if (req->rq_vers != 2 && req->rq_vers != 3) + return (this_thread); + + pool = req->rq_xprt->xp_pool; + fha_extract_info(req, &i); + + /* + * We save the offset associated with this request for later + * nfsd matching. + */ + fhe = fha_hash_entry_lookup(pool, i.fh); + req->rq_p1 = fhe; + req->rq_p2 = i.locktype; + req->rq_p3 = i.offset; + + /* + * Choose a thread, taking into consideration locality, thread load, + * and the number of threads already working on this file. + */ + thread = fha_hash_entry_choose_thread(pool, fhe, &i, this_thread); + KASSERT(thread, ("fha_assign: NULL thread!")); + fha_hash_entry_add_op(fhe, i.locktype, 1); + + return (thread); +} + +/* + * Called when we're done with an operation. The request has already + * been de-queued. + */ +void +fha_nd_complete(SVCTHREAD *thread, struct svc_req *req) +{ + struct fha_hash_entry *fhe = req->rq_p1; + + /* + * This may be called for reqs that didn't go through + * fha_assign (e.g. extra NULL ops used for RPCSEC_GSS. + */ + if (!fhe) + return; + + fha_hash_entry_add_op(fhe, req->rq_p2, -1); + + if (thread->st_reqcount == 0) { + fha_hash_entry_remove_thread(fhe, thread); + if (0 == fhe->num_reads + fhe->num_writes) + fha_hash_entry_remove(fhe); + } +} + +extern SVCPOOL *nfsrv_pool; + +static int +fhe_stats_sysctl(SYSCTL_HANDLER_ARGS) +{ + int error, count, i; + struct sbuf sb; + struct fha_hash_entry *fhe; + bool_t first = TRUE; + SVCTHREAD *thread; + + sbuf_new(&sb, NULL, 4096, SBUF_FIXEDLEN); + + if (!nfsrv_pool) { + sbuf_printf(&sb, "NFSD not running\n"); + goto out; + } + + mtx_lock(&nfsrv_pool->sp_lock); + count = 0; + for (i = 0; i <= g_fha.hashmask; i++) + if (!LIST_EMPTY(&g_fha.hashtable[i])) + count++; + + if (count == 0) { + sbuf_printf(&sb, "No file handle entries.\n"); + goto out; + } + + for (i = 0; i <= g_fha.hashmask; i++) { + LIST_FOREACH(fhe, &g_fha.hashtable[i], link) { + sbuf_printf(&sb, "%sfhe %p: {\n", first ? "" : ", ", fhe); + + sbuf_printf(&sb, " fh: %ju\n", (uintmax_t) fhe->fh); + sbuf_printf(&sb, " num_reads: %d\n", fhe->num_reads); + sbuf_printf(&sb, " num_writes: %d\n", fhe->num_writes); + sbuf_printf(&sb, " num_threads: %d\n", fhe->num_threads); + + LIST_FOREACH(thread, &fhe->threads, st_alink) { + sbuf_printf(&sb, " thread %p (count %d)\n", + thread, thread->st_reqcount); + } + + sbuf_printf(&sb, "}"); + first = FALSE; + + /* Limit the output. */ + if (++count > 128) { + sbuf_printf(&sb, "..."); + break; + } + } + } + + out: + if (nfsrv_pool) + mtx_unlock(&nfsrv_pool->sp_lock); + sbuf_trim(&sb); + sbuf_finish(&sb); + error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); + sbuf_delete(&sb); + return (error); +} + +#endif /* !NFS_LEGACYRPC */ diff --git a/sys/nfsserver/nfs_fha.h b/sys/nfsserver/nfs_fha.h new file mode 100644 index 0000000..e7344ed --- /dev/null +++ b/sys/nfsserver/nfs_fha.h @@ -0,0 +1,28 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * + * 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$ */ + +void fha_nd_complete(SVCTHREAD *, struct svc_req *); +SVCTHREAD *fha_assign(SVCTHREAD *, struct svc_req *); diff --git a/sys/nfsserver/nfs_serv.c b/sys/nfsserver/nfs_serv.c index a0a0308..d528769 100644 --- a/sys/nfsserver/nfs_serv.c +++ b/sys/nfsserver/nfs_serv.c @@ -142,8 +142,10 @@ SYSCTL_STRUCT(_vfs_nfsrv, NFS_NFSRVSTATS, nfsrvstats, CTLFLAG_RW, static int nfsrv_access(struct vnode *, accmode_t, struct ucred *, int, int); +#ifdef NFS_LEGACYRPC static void nfsrvw_coalesce(struct nfsrv_descript *, struct nfsrv_descript *); +#endif /* * Clear nameidata fields that are tested in nsfmout cleanup code prior @@ -216,7 +218,7 @@ nfsrv3_access(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, fhp = &nfh.fh_generic; nfsm_srvmtofh(fhp); tl = nfsm_dissect_nonblock(u_int32_t *, NFSX_UNSIGNED); - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { nfsm_reply(NFSX_UNSIGNED); @@ -283,7 +285,7 @@ nfsrv_getattr(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, vfslocked = 0; fhp = &nfh.fh_generic; nfsm_srvmtofh(fhp); - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, nam, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { nfsm_reply(0); @@ -392,7 +394,7 @@ nfsrv_setattr(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, /* * Now that we have all the fields, lets do it. */ - error = nfsrv_fhtovp(fhp, 1, &vp, &tvfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &tvfslocked, nfsd, slp, nam, &rdonly, TRUE); vfslocked = nfsrv_lockedpair(vfslocked, tvfslocked); if (error) { @@ -505,7 +507,7 @@ nfsrv_lookup(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nd.ni_cnd.cn_cred = cred; nd.ni_cnd.cn_nameiop = LOOKUP; nd.ni_cnd.cn_flags = LOCKLEAF | SAVESTART | MPSAFE; - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirattr, &dirattr_ret, pubflag); vfslocked = NDHASGIANT(&nd); @@ -715,7 +717,7 @@ nfsrv_readlink(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, uiop->uio_rw = UIO_READ; uiop->uio_segflg = UIO_SYSSPACE; uiop->uio_td = NULL; - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { nfsm_reply(2 * NFSX_UNSIGNED); @@ -811,7 +813,7 @@ nfsrv_read(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, * as well. */ - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { vp = NULL; @@ -1112,7 +1114,7 @@ nfsrv_write(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, error = 0; goto nfsmout; } - error = nfsrv_fhtovp(fhp, 1, &vp, &tvfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &tvfslocked, nfsd, slp, nam, &rdonly, TRUE); vfslocked = nfsrv_lockedpair(vfslocked, tvfslocked); if (error) { @@ -1227,6 +1229,16 @@ nfsmout: return(error); } +#ifdef NFS_LEGACYRPC + +/* + * XXX dfr - write gathering isn't supported by the new RPC code since + * its really only useful for NFSv2. If there is a real need, we could + * attempt to fit it into the filehandle affinity system, e.g. by + * looking to see if there are queued write requests that overlap this + * one. + */ + /* * For the purposes of write gathering, we must decide if the credential * associated with two pending requests have equivilent privileges. Since @@ -1432,7 +1444,7 @@ loop1: cred = nfsd->nd_cr; v3 = (nfsd->nd_flag & ND_NFSV3); forat_ret = aftat_ret = 1; - error = nfsrv_fhtovp(&nfsd->nd_fh, 1, &vp, &vfslocked, cred, + error = nfsrv_fhtovp(&nfsd->nd_fh, 1, &vp, &vfslocked, nfsd, slp, nfsd->nd_nam, &rdonly, TRUE); if (!error) { if (v3) @@ -1634,6 +1646,8 @@ nfsrvw_coalesce(struct nfsrv_descript *owp, struct nfsrv_descript *nfsd) } } +#endif + /* * nfs create service * now does a truncate to 0 length via. setattr if it already exists @@ -1697,7 +1711,7 @@ nfsrv_create(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, * be valid at all if an error occurs so we have to invalidate it * prior to calling nfsm_reply ( which might goto nfsmout ). */ - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (dirp && !v3) { @@ -1987,7 +2001,7 @@ nfsrv_mknod(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, * nfsmout. */ - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (error) { @@ -2169,7 +2183,7 @@ nfsrv_remove(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nd.ni_cnd.cn_cred = cred; nd.ni_cnd.cn_nameiop = DELETE; nd.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF | MPSAFE; - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (dirp && !v3) { @@ -2296,7 +2310,7 @@ nfsrv_rename(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, fromnd.ni_cnd.cn_cred = cred; fromnd.ni_cnd.cn_nameiop = DELETE; fromnd.ni_cnd.cn_flags = WANTPARENT | SAVESTART | MPSAFE; - error = nfs_namei(&fromnd, ffhp, len, slp, nam, &md, + error = nfs_namei(&fromnd, nfsd, ffhp, len, slp, nam, &md, &dpos, &fdirp, v3, &fdirfor, &fdirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &fromnd); if (fdirp && !v3) { @@ -2319,7 +2333,7 @@ nfsrv_rename(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, tond.ni_cnd.cn_cred = cred; tond.ni_cnd.cn_nameiop = RENAME; tond.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF | NOCACHE | SAVESTART | MPSAFE; - error = nfs_namei(&tond, tfhp, len2, slp, nam, &md, + error = nfs_namei(&tond, nfsd, tfhp, len2, slp, nam, &md, &dpos, &tdirp, v3, &tdirfor, &tdirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &tond); if (tdirp && !v3) { @@ -2512,7 +2526,7 @@ nfsrv_link(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nfsm_srvmtofh(dfhp); nfsm_srvnamesiz(len); - error = nfsrv_fhtovp(fhp, TRUE, &vp, &tvfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, TRUE, &vp, &tvfslocked, nfsd, slp, nam, &rdonly, TRUE); vfslocked = nfsrv_lockedpair(vfslocked, tvfslocked); if (error) { @@ -2535,7 +2549,7 @@ nfsrv_link(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nd.ni_cnd.cn_cred = cred; nd.ni_cnd.cn_nameiop = CREATE; nd.ni_cnd.cn_flags = LOCKPARENT | MPSAFE | MPSAFE; - error = nfs_namei(&nd, dfhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, dfhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (dirp && !v3) { @@ -2664,7 +2678,7 @@ nfsrv_symlink(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nd.ni_cnd.cn_cred = cred; nd.ni_cnd.cn_nameiop = CREATE; nd.ni_cnd.cn_flags = LOCKPARENT | SAVESTART | MPSAFE; - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (error == 0) { @@ -2847,7 +2861,7 @@ nfsrv_mkdir(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nd.ni_cnd.cn_nameiop = CREATE; nd.ni_cnd.cn_flags = LOCKPARENT | MPSAFE; - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (dirp && !v3) { @@ -3005,7 +3019,7 @@ nfsrv_rmdir(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, nd.ni_cnd.cn_cred = cred; nd.ni_cnd.cn_nameiop = DELETE; nd.ni_cnd.cn_flags = LOCKPARENT | LOCKLEAF | MPSAFE; - error = nfs_namei(&nd, fhp, len, slp, nam, &md, &dpos, + error = nfs_namei(&nd, nfsd, fhp, len, slp, nam, &md, &dpos, &dirp, v3, &dirfor, &dirfor_ret, FALSE); vfslocked = nfsrv_lockedpair_nd(vfslocked, &nd); if (dirp && !v3) { @@ -3180,7 +3194,7 @@ nfsrv_readdir(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, if (siz > xfer) siz = xfer; fullsiz = siz; - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (!error && vp->v_type != VDIR) { error = ENOTDIR; @@ -3474,7 +3488,7 @@ nfsrv_readdirplus(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, if (siz > xfer) siz = xfer; fullsiz = siz; - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (!error && vp->v_type != VDIR) { error = ENOTDIR; @@ -3815,7 +3829,7 @@ nfsrv_commit(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, off = fxdr_hyper(tl); tl += 2; cnt = fxdr_unsigned(int, *tl); - error = nfsrv_fhtovp(fhp, 1, &vp, &tvfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &tvfslocked, nfsd, slp, nam, &rdonly, TRUE); vfslocked = nfsrv_lockedpair(vfslocked, tvfslocked); if (error) { @@ -3960,7 +3974,7 @@ nfsrv_statfs(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, vfslocked = 0; fhp = &nfh.fh_generic; nfsm_srvmtofh(fhp); - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { nfsm_reply(NFSX_UNSIGNED); @@ -4055,7 +4069,7 @@ nfsrv_fsinfo(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, fhp = &nfh.fh_generic; vfslocked = 0; nfsm_srvmtofh(fhp); - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { nfsm_reply(NFSX_UNSIGNED); @@ -4080,10 +4094,7 @@ nfsrv_fsinfo(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, * There should be filesystem VFS OP(s) to get this information. * For now, assume ufs. */ - if (slp->ns_so->so_type == SOCK_DGRAM) - pref = NFS_MAXDGRAMDATA; - else - pref = NFS_MAXDATA; + pref = NFS_SRVMAXDATA(nfsd); sip->fs_rtmax = txdr_unsigned(pref); sip->fs_rtpref = txdr_unsigned(pref); sip->fs_rtmult = txdr_unsigned(NFS_FABLKSIZE); @@ -4133,7 +4144,7 @@ nfsrv_pathconf(struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, vfslocked = 0; fhp = &nfh.fh_generic; nfsm_srvmtofh(fhp); - error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, cred, slp, + error = nfsrv_fhtovp(fhp, 1, &vp, &vfslocked, nfsd, slp, nam, &rdonly, TRUE); if (error) { nfsm_reply(NFSX_UNSIGNED); diff --git a/sys/nfsserver/nfs_srvcache.c b/sys/nfsserver/nfs_srvcache.c index 1e36a45..5121690 100644 --- a/sys/nfsserver/nfs_srvcache.c +++ b/sys/nfsserver/nfs_srvcache.c @@ -57,6 +57,8 @@ __FBSDID("$FreeBSD$"); #include #include +#ifdef NFS_LEGACYRPC + static long numnfsrvcache; static long desirednfsrvcache; @@ -385,3 +387,5 @@ nfsrv_cleancache(void) } numnfsrvcache = 0; } + +#endif /* NFS_LEGACYRPC */ diff --git a/sys/nfsserver/nfs_srvkrpc.c b/sys/nfsserver/nfs_srvkrpc.c new file mode 100644 index 0000000..509dc58 --- /dev/null +++ b/sys/nfsserver/nfs_srvkrpc.c @@ -0,0 +1,565 @@ +/*- + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Rick Macklem at The University of Guelph. + * + * 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. + * 4. 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 BY THE REGENTS 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 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. + * + * @(#)nfs_syscalls.c 8.5 (Berkeley) 3/30/95 + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_inet6.h" +#include "opt_kgssapi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef INET6 +#include +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef NFS_LEGACYRPC + +static MALLOC_DEFINE(M_NFSSVC, "nfss_srvsock", "Nfs server structure"); + +MALLOC_DEFINE(M_NFSRVDESC, "nfss_srvdesc", "NFS server socket descriptor"); +MALLOC_DEFINE(M_NFSD, "nfss_daemon", "Nfs server daemon structure"); + +#define TRUE 1 +#define FALSE 0 + +SYSCTL_DECL(_vfs_nfsrv); + +SVCPOOL *nfsrv_pool; +int nfsd_waiting = 0; +int nfsrv_numnfsd = 0; +static int nfs_realign_test; +static int nfs_realign_count; +struct callout nfsrv_callout; +static eventhandler_tag nfsrv_nmbclusters_tag; + +static int nfs_privport = 0; +SYSCTL_INT(_vfs_nfsrv, NFS_NFSPRIVPORT, nfs_privport, CTLFLAG_RW, + &nfs_privport, 0, + "Only allow clients using a privileged port"); +SYSCTL_INT(_vfs_nfsrv, OID_AUTO, gatherdelay, CTLFLAG_RW, + &nfsrvw_procrastinate, 0, + "Delay value for write gathering"); +SYSCTL_INT(_vfs_nfsrv, OID_AUTO, gatherdelay_v3, CTLFLAG_RW, + &nfsrvw_procrastinate_v3, 0, + "Delay in seconds for NFSv3 write gathering"); +SYSCTL_INT(_vfs_nfsrv, OID_AUTO, realign_test, CTLFLAG_RW, + &nfs_realign_test, 0, ""); +SYSCTL_INT(_vfs_nfsrv, OID_AUTO, realign_count, CTLFLAG_RW, + &nfs_realign_count, 0, ""); + +static int nfssvc_addsock(struct file *, struct thread *); +static int nfssvc_nfsd(struct thread *, struct nfsd_nfsd_args *); + +extern u_long sb_max_adj; + +int32_t (*nfsrv3_procs[NFS_NPROCS])(struct nfsrv_descript *nd, + struct nfssvc_sock *slp, struct mbuf **mreqp) = { + nfsrv_null, + nfsrv_getattr, + nfsrv_setattr, + nfsrv_lookup, + nfsrv3_access, + nfsrv_readlink, + nfsrv_read, + nfsrv_write, + nfsrv_create, + nfsrv_mkdir, + nfsrv_symlink, + nfsrv_mknod, + nfsrv_remove, + nfsrv_rmdir, + nfsrv_rename, + nfsrv_link, + nfsrv_readdir, + nfsrv_readdirplus, + nfsrv_statfs, + nfsrv_fsinfo, + nfsrv_pathconf, + nfsrv_commit, + nfsrv_noop +}; + +/* + * NFS server system calls + */ + +/* + * Nfs server psuedo system call for the nfsd's + * Based on the flag value it either: + * - adds a socket to the selection list + * - remains in the kernel as an nfsd + * - remains in the kernel as an nfsiod + * For INET6 we suppose that nfsd provides only IN6P_IPV6_V6ONLY sockets + * and that mountd provides + * - sockaddr with no IPv4-mapped addresses + * - mask for both INET and INET6 families if there is IPv4-mapped overlap + */ +#ifndef _SYS_SYSPROTO_H_ +struct nfssvc_args { + int flag; + caddr_t argp; +}; +#endif +int +nfssvc(struct thread *td, struct nfssvc_args *uap) +{ + struct file *fp; + struct nfsd_addsock_args addsockarg; + struct nfsd_nfsd_args nfsdarg; + int error; + + KASSERT(!mtx_owned(&Giant), ("nfssvc(): called with Giant")); + + error = priv_check(td, PRIV_NFS_DAEMON); + if (error) + return (error); + if (uap->flag & NFSSVC_ADDSOCK) { + error = copyin(uap->argp, (caddr_t)&addsockarg, + sizeof(addsockarg)); + if (error) + return (error); + if ((error = fget(td, addsockarg.sock, &fp)) != 0) + return (error); + if (fp->f_type != DTYPE_SOCKET) { + fdrop(fp, td); + return (error); /* XXXRW: Should be EINVAL? */ + } + error = nfssvc_addsock(fp, td); + fdrop(fp, td); + } else if (uap->flag & NFSSVC_OLDNFSD) { + error = nfssvc_nfsd(td, NULL); + } else if (uap->flag & NFSSVC_NFSD) { + if (!uap->argp) + return (EINVAL); + error = copyin(uap->argp, (caddr_t)&nfsdarg, + sizeof(nfsdarg)); + if (error) + return (error); + error = nfssvc_nfsd(td, &nfsdarg); + } else { + error = ENXIO; + } + if (error == EINTR || error == ERESTART) + error = 0; + return (error); +} + +/* + * Generate the rpc reply header + * siz arg. is used to decide if adding a cluster is worthwhile + */ +struct mbuf * +nfs_rephead(int siz, struct nfsrv_descript *nd, int err, + struct mbuf **mbp, caddr_t *bposp) +{ + u_int32_t *tl; + struct mbuf *mreq; + caddr_t bpos; + struct mbuf *mb; + + if (err == EBADRPC) + return (NULL); + + nd->nd_repstat = err; + if (err && (nd->nd_flag & ND_NFSV3) == 0) /* XXX recheck */ + siz = 0; + + MGET(mreq, M_WAIT, MT_DATA); + + /* + * If this is a big reply, use a cluster + */ + mreq->m_len = 0; + if (siz >= MINCLSIZE) { + MCLGET(mreq, M_WAIT); + } + mb = mreq; + bpos = mtod(mb, caddr_t); + + if (err != NFSERR_RETVOID) { + tl = nfsm_build(u_int32_t *, NFSX_UNSIGNED); + if (err) + *tl = txdr_unsigned(nfsrv_errmap(nd, err)); + else + *tl = 0; + } + + *mbp = mb; + *bposp = bpos; + if (err != 0 && err != NFSERR_RETVOID) + nfsrvstats.srvrpc_errs++; + + return (mreq); +} + +/* + * nfs_realign: + * + * Check for badly aligned mbuf data and realign by copying the unaligned + * portion of the data into a new mbuf chain and freeing the portions + * of the old chain that were replaced. + * + * We cannot simply realign the data within the existing mbuf chain + * because the underlying buffers may contain other rpc commands and + * we cannot afford to overwrite them. + * + * We would prefer to avoid this situation entirely. The situation does + * not occur with NFS/UDP and is supposed to only occassionally occur + * with TCP. Use vfs.nfs.realign_count and realign_test to check this. + */ +static void +nfs_realign(struct mbuf **pm) /* XXX COMMON */ +{ + struct mbuf *m; + struct mbuf *n = NULL; + int off = 0; + + ++nfs_realign_test; + while ((m = *pm) != NULL) { + if ((m->m_len & 0x3) || (mtod(m, intptr_t) & 0x3)) { + MGET(n, M_WAIT, MT_DATA); + if (m->m_len >= MINCLSIZE) { + MCLGET(n, M_WAIT); + } + n->m_len = 0; + break; + } + pm = &m->m_next; + } + + /* + * If n is non-NULL, loop on m copying data, then replace the + * portion of the chain that had to be realigned. + */ + if (n != NULL) { + ++nfs_realign_count; + while (m) { + m_copyback(n, off, m->m_len, mtod(m, caddr_t)); + off += m->m_len; + m = m->m_next; + } + m_freem(*pm); + *pm = n; + } +} + +static void +nfssvc_program(struct svc_req *rqst, SVCXPRT *xprt) +{ + rpcproc_t procnum; + int32_t (*proc)(struct nfsrv_descript *nd, struct nfssvc_sock *slp, + struct mbuf **mreqp); + int flag; + struct nfsrv_descript nd; + struct mbuf *mreq, *mrep; + int error; + + if (rqst->rq_vers == NFS_VER2) { + if (rqst->rq_proc > NFSV2PROC_STATFS) { + svcerr_noproc(rqst); + svc_freereq(rqst); + return; + } + procnum = nfsrv_nfsv3_procid[rqst->rq_proc]; + flag = 0; + } else { + if (rqst->rq_proc >= NFS_NPROCS) { + svcerr_noproc(rqst); + svc_freereq(rqst); + return; + } + procnum = rqst->rq_proc; + flag = ND_NFSV3; + } + proc = nfsrv3_procs[procnum]; + + mreq = mrep = NULL; + mreq = rqst->rq_args; + rqst->rq_args = NULL; + nfs_realign(&mreq); + + /* + * Note: we want rq_addr, not svc_getrpccaller - + * NFS_SRVMAXDATA uses a NULL value for nd_nam2 to detect TCP + * mounts. + */ + memset(&nd, 0, sizeof(nd)); + nd.nd_md = nd.nd_mrep = mreq; + nd.nd_dpos = mtod(mreq, caddr_t); + nd.nd_nam = (struct sockaddr *) &xprt->xp_ltaddr; + nd.nd_nam2 = rqst->rq_addr; + nd.nd_procnum = procnum; + nd.nd_cr = NULL; + nd.nd_flag = flag; + + if (proc != nfsrv_null) { + if (!svc_getcred(rqst, &nd.nd_cr, &nd.nd_credflavor)) { + svcerr_weakauth(rqst); + svc_freereq(rqst); + return; + } +#ifdef MAC + mac_cred_associate_nfsd(nd.nd_cr); +#endif + } + nfsrvstats.srvrpccnt[nd.nd_procnum]++; + + error = proc(&nd, NULL, &mrep); + + if (nd.nd_cr) + crfree(nd.nd_cr); + + if (mrep == NULL) { + svcerr_decode(rqst); + svc_freereq(rqst); + return; + } + if (error && error != NFSERR_RETVOID) { + svcerr_systemerr(rqst); + svc_freereq(rqst); + return; + } + if (!svc_sendreply_mbuf(rqst, mrep)) + svcerr_systemerr(rqst); + svc_freereq(rqst); +} + +/* + * Adds a socket to the list for servicing by nfsds. + */ +static int +nfssvc_addsock(struct file *fp, struct thread *td) +{ + int siz; + struct socket *so; + int error; + SVCXPRT *xprt; + + so = fp->f_data; + + siz = sb_max_adj; + error = soreserve(so, siz, siz); + if (error) { + return (error); + } + + /* + * Steal the socket from userland so that it doesn't close + * unexpectedly. + */ + if (so->so_type == SOCK_DGRAM) + xprt = svc_dg_create(nfsrv_pool, so, 0, 0); + else + xprt = svc_vc_create(nfsrv_pool, so, 0, 0); + if (xprt) { + fp->f_ops = &badfileops; + fp->f_data = NULL; + svc_reg(xprt, NFS_PROG, NFS_VER2, nfssvc_program, NULL); + svc_reg(xprt, NFS_PROG, NFS_VER3, nfssvc_program, NULL); + } + + return (0); +} + +/* + * Called by nfssvc() for nfsds. Just loops around servicing rpc requests + * until it is killed by a signal. + */ +static int +nfssvc_nfsd(struct thread *td, struct nfsd_nfsd_args *args) +{ +#ifdef KGSSAPI + char principal[128]; + int error; +#endif + +#ifdef KGSSAPI + if (args) { + error = copyinstr(args->principal, principal, + sizeof(principal), NULL); + if (error) + return (error); + } else { + snprintf(principal, sizeof(principal), "nfs@%s", hostname); + } +#endif + + /* + * Only the first nfsd actually does any work. The RPC code + * adds threads to it as needed. Any extra processes offered + * by nfsd just exit. If nfsd is new enough, it will call us + * once with a structure that specifies how many threads to + * use. + */ + NFSD_LOCK(); + if (nfsrv_numnfsd == 0) { + nfsrv_numnfsd++; + + NFSD_UNLOCK(); + +#ifdef KGSSAPI + rpc_gss_set_svc_name(principal, "kerberosv5", + GSS_C_INDEFINITE, NFS_PROG, NFS_VER2); + rpc_gss_set_svc_name(principal, "kerberosv5", + GSS_C_INDEFINITE, NFS_PROG, NFS_VER3); +#endif + + if (args) { + nfsrv_pool->sp_minthreads = args->minthreads; + nfsrv_pool->sp_maxthreads = args->maxthreads; + } else { + nfsrv_pool->sp_minthreads = 4; + nfsrv_pool->sp_maxthreads = 4; + } + + svc_run(nfsrv_pool); + +#ifdef KGSSAPI + rpc_gss_clear_svc_name(NFS_PROG, NFS_VER2); + rpc_gss_clear_svc_name(NFS_PROG, NFS_VER3); +#endif + + NFSD_LOCK(); + nfsrv_numnfsd--; + nfsrv_init(TRUE); + } + NFSD_UNLOCK(); + + return (0); +} + +/* + * Size the NFS server's duplicate request cache at 1/2 the + * nmbclusters, floating within a (64, 2048) range. This is to + * prevent all mbuf clusters being tied up in the NFS dupreq + * cache for small values of nmbclusters. + */ +static size_t +nfsrv_replay_size(void) +{ + size_t replaysiz; + + replaysiz = nmbclusters / 2; + if (replaysiz > NFSRVCACHE_MAX_SIZE) + replaysiz = NFSRVCACHE_MAX_SIZE; + if (replaysiz < NFSRVCACHE_MIN_SIZE) + replaysiz = NFSRVCACHE_MIN_SIZE; + replaysiz *= MCLBYTES; + + return (replaysiz); +} + +/* + * Called when nmbclusters changes - we resize the replay cache + * accordingly. + */ +static void +nfsrv_nmbclusters_change(void *tag) +{ + + if (nfsrv_pool) + replay_setsize(nfsrv_pool->sp_rcache, nfsrv_replay_size()); +} + +/* + * Initialize the data structures for the server. + * Handshake with any new nfsds starting up to avoid any chance of + * corruption. + */ +void +nfsrv_init(int terminating) +{ + + NFSD_LOCK_ASSERT(); + + if (terminating) { + NFSD_UNLOCK(); + EVENTHANDLER_DEREGISTER(nmbclusters_change, + nfsrv_nmbclusters_tag); + svcpool_destroy(nfsrv_pool); + nfsrv_pool = NULL; + NFSD_LOCK(); + } else + nfs_pub.np_valid = 0; + + NFSD_UNLOCK(); + + nfsrv_pool = svcpool_create("nfsd", SYSCTL_STATIC_CHILDREN(_vfs_nfsrv)); + nfsrv_pool->sp_rcache = replay_newcache(nfsrv_replay_size()); + nfsrv_pool->sp_assign = fha_assign; + nfsrv_pool->sp_done = fha_nd_complete; + nfsrv_nmbclusters_tag = EVENTHANDLER_REGISTER(nmbclusters_change, + nfsrv_nmbclusters_change, NULL, EVENTHANDLER_PRI_FIRST); + + NFSD_LOCK(); +} + +#endif /* !NFS_LEGACYRPC */ diff --git a/sys/nfsserver/nfs_srvsock.c b/sys/nfsserver/nfs_srvsock.c index f4362f4..6f42e31 100644 --- a/sys/nfsserver/nfs_srvsock.c +++ b/sys/nfsserver/nfs_srvsock.c @@ -70,6 +70,8 @@ __FBSDID("$FreeBSD$"); #include +#ifdef NFS_LEGACYRPC + #define TRUE 1 #define FALSE 0 @@ -383,6 +385,7 @@ nfs_getreq(struct nfsrv_descript *nd, struct nfsd *nfsd, int has_header) } if (len > 0) nfsm_adv(nfsm_rndup(len)); + nd->nd_credflavor = RPCAUTH_UNIX; } else { nd->nd_repstat = (NFSERR_AUTHERR | AUTH_REJECTCRED); nd->nd_procnum = NFSPROC_NOOP; @@ -809,3 +812,5 @@ nfsrv_timer(void *arg) NFSD_UNLOCK(); callout_reset(&nfsrv_callout, nfsrv_ticks, nfsrv_timer, NULL); } + +#endif /* NFS_LEGACYRPC */ diff --git a/sys/nfsserver/nfs_srvsubs.c b/sys/nfsserver/nfs_srvsubs.c index d738fdf..2cce8be 100644 --- a/sys/nfsserver/nfs_srvsubs.c +++ b/sys/nfsserver/nfs_srvsubs.c @@ -93,10 +93,12 @@ static const nfstype nfsv2_type[9] = { NFNON, NFREG, NFDIR, NFBLK, NFCHR, int nfsrv_ticks; +#ifdef NFS_LEGACYRPC struct nfssvc_sockhead nfssvc_sockhead; int nfssvc_sockhead_flag; struct nfsd_head nfsd_head; int nfsd_head_flag; +#endif static int nfssvc_offset = SYS_nfssvc; static struct sysent nfssvc_prev_sysent; @@ -545,12 +547,18 @@ nfsrv_modevent(module_t mod, int type, void *data) if (nfsrv_ticks < 1) nfsrv_ticks = 1; +#ifdef NFS_LEGACYRPC nfsrv_initcache(); /* Init the server request cache */ NFSD_LOCK(); nfsrv_init(0); /* Init server data structures */ callout_init(&nfsrv_callout, CALLOUT_MPSAFE); NFSD_UNLOCK(); nfsrv_timer(0); +#else + NFSD_LOCK(); + nfsrv_init(0); /* Init server data structures */ + NFSD_UNLOCK(); +#endif error = syscall_register(&nfssvc_offset, &nfssvc_sysent, &nfssvc_prev_sysent); @@ -568,7 +576,9 @@ nfsrv_modevent(module_t mod, int type, void *data) if (registered) syscall_deregister(&nfssvc_offset, &nfssvc_prev_sysent); callout_drain(&nfsrv_callout); +#ifdef NFS_LEGACYRPC nfsrv_destroycache(); /* Free the server request cache */ +#endif mtx_destroy(&nfsd_mtx); break; default: @@ -604,8 +614,9 @@ MODULE_VERSION(nfsserver, 1); * released by the caller. */ int -nfs_namei(struct nameidata *ndp, fhandle_t *fhp, int len, - struct nfssvc_sock *slp, struct sockaddr *nam, struct mbuf **mdp, +nfs_namei(struct nameidata *ndp, struct nfsrv_descript *nfsd, + fhandle_t *fhp, int len, struct nfssvc_sock *slp, + struct sockaddr *nam, struct mbuf **mdp, caddr_t *dposp, struct vnode **retdirp, int v3, struct vattr *retdirattrp, int *retdirattr_retp, int pubflag) { @@ -667,7 +678,7 @@ nfs_namei(struct nameidata *ndp, fhandle_t *fhp, int len, * Extract and set starting directory. */ error = nfsrv_fhtovp(fhp, FALSE, &dp, &dvfslocked, - ndp->ni_cnd.cn_cred, slp, nam, &rdonly, pubflag); + nfsd, slp, nam, &rdonly, pubflag); if (error) goto out; vfslocked = VFS_LOCK_GIANT(dp->v_mount); @@ -1079,17 +1090,21 @@ nfsm_srvfattr(struct nfsrv_descript *nfsd, struct vattr *vap, */ int nfsrv_fhtovp(fhandle_t *fhp, int lockflag, struct vnode **vpp, int *vfslockedp, - struct ucred *cred, struct nfssvc_sock *slp, struct sockaddr *nam, - int *rdonlyp, int pubflag) + struct nfsrv_descript *nfsd, struct nfssvc_sock *slp, + struct sockaddr *nam, int *rdonlyp, int pubflag) { struct mount *mp; int i; - struct ucred *credanon; + struct ucred *cred, *credanon; int error, exflags; #ifdef MNT_EXNORESPORT /* XXX needs mountd and /etc/exports help yet */ struct sockaddr_int *saddr; #endif + int credflavor; int vfslocked; + int numsecflavors, *secflavors; + int v3 = nfsd->nd_flag & ND_NFSV3; + int mountreq; *vfslockedp = 0; *vpp = NULL; @@ -1104,9 +1119,35 @@ nfsrv_fhtovp(fhandle_t *fhp, int lockflag, struct vnode **vpp, int *vfslockedp, if (!mp) return (ESTALE); vfslocked = VFS_LOCK_GIANT(mp); - error = VFS_CHECKEXP(mp, nam, &exflags, &credanon); + error = VFS_CHECKEXP(mp, nam, &exflags, &credanon, + &numsecflavors, &secflavors); if (error) goto out; + credflavor = nfsd->nd_credflavor; + for (i = 0; i < numsecflavors; i++) { + if (secflavors[i] == credflavor) + break; + } + if (i == numsecflavors) { + /* + * RFC 2623 section 2.3.2 - allow certain procedures + * used at NFS client mount time even if they have + * weak authentication. + */ + mountreq = FALSE; + if (v3) { + if (nfsd->nd_procnum == NFSPROC_FSINFO) + mountreq = TRUE; + } else { + if (nfsd->nd_procnum == NFSPROC_FSSTAT + || nfsd->nd_procnum == NFSPROC_GETATTR) + mountreq = TRUE; + } + if (!mountreq) { + error = NFSERR_AUTHERR | AUTH_REJECTCRED; + goto out; + } + } error = VFS_FHTOVP(mp, &fhp->fh_fid, vpp); if (error) goto out; @@ -1126,6 +1167,7 @@ nfsrv_fhtovp(fhandle_t *fhp, int lockflag, struct vnode **vpp, int *vfslockedp, /* * Check/setup credentials. */ + cred = nfsd->nd_cr; if (cred->cr_uid == 0 || (exflags & MNT_EXPORTANON)) { cred->cr_uid = credanon->cr_uid; for (i = 0; i < credanon->cr_ngroups && i < NGROUPS; i++) @@ -1168,6 +1210,8 @@ nfs_ispublicfh(fhandle_t *fhp) return (TRUE); } +#ifdef NFS_LEGACYRPC + /* * This function compares two net addresses by family and returns TRUE * if they are the same host. @@ -1210,6 +1254,8 @@ netaddr_match(int family, union nethostaddr *haddr, struct sockaddr *nam) return (0); } +#endif + /* * Map errnos to NFS error numbers. For Version 3 also filter out error * numbers not specified for the associated procedure. @@ -1364,13 +1410,12 @@ nfsm_clget_xx(u_int32_t **tl, struct mbuf *mb, struct mbuf **mp, } int -nfsm_srvmtofh_xx(fhandle_t *f, struct nfsrv_descript *nfsd, struct mbuf **md, - caddr_t *dpos) +nfsm_srvmtofh_xx(fhandle_t *f, int v3, struct mbuf **md, caddr_t *dpos) { u_int32_t *tl; int fhlen; - if (nfsd->nd_flag & ND_NFSV3) { + if (v3) { tl = nfsm_dissect_xx_nonblock(NFSX_UNSIGNED, md, dpos); if (tl == NULL) return EBADRPC; diff --git a/sys/nfsserver/nfs_syscalls.c b/sys/nfsserver/nfs_syscalls.c index d5e5e67..0c936c9 100644 --- a/sys/nfsserver/nfs_syscalls.c +++ b/sys/nfsserver/nfs_syscalls.c @@ -73,6 +73,8 @@ __FBSDID("$FreeBSD$"); #include #include +#ifdef NFS_LEGACYRPC + static MALLOC_DEFINE(M_NFSSVC, "nfss_srvsock", "Nfs server structure"); MALLOC_DEFINE(M_NFSRVDESC, "nfss_srvdesc", "NFS server socket descriptor"); @@ -130,7 +132,7 @@ nfssvc(struct thread *td, struct nfssvc_args *uap) { struct file *fp; struct sockaddr *nam; - struct nfsd_args nfsdarg; + struct nfsd_addsock_args nfsdarg; int error; KASSERT(!mtx_owned(&Giant), ("nfssvc(): called with Giant")); @@ -170,7 +172,7 @@ nfssvc(struct thread *td, struct nfssvc_args *uap) } error = nfssvc_addsock(fp, nam); fdrop(fp, td); - } else if (uap->flag & NFSSVC_NFSD) { + } else if (uap->flag & NFSSVC_OLDNFSD) { error = nfssvc_nfsd(); } else { error = ENXIO; @@ -727,3 +729,5 @@ nfsrv_init(int terminating) TAILQ_INSERT_TAIL(&nfssvc_sockhead, nfs_cltpsock, ns_chain); #endif } + +#endif /* NFS_LEGACYRPC */ diff --git a/sys/nfsserver/nfsm_subs.h b/sys/nfsserver/nfsm_subs.h index f2dafc4..fc9b76d 100644 --- a/sys/nfsserver/nfsm_subs.h +++ b/sys/nfsserver/nfsm_subs.h @@ -75,8 +75,7 @@ int nfsm_srvstrsiz_xx(int *s, int m, struct mbuf **md, caddr_t *dpos); int nfsm_srvnamesiz_xx(int *s, int m, struct mbuf **md, caddr_t *dpos); int nfsm_srvnamesiz0_xx(int *s, int m, struct mbuf **md, caddr_t *dpos); -int nfsm_srvmtofh_xx(fhandle_t *f, struct nfsrv_descript *nfsd, - struct mbuf **md, caddr_t *dpos); +int nfsm_srvmtofh_xx(fhandle_t *f, int v3, struct mbuf **md, caddr_t *dpos); int nfsm_srvsattr_xx(struct vattr *a, struct mbuf **md, caddr_t *dpos); #define nfsm_srvstrsiz(s, m) \ @@ -112,7 +111,7 @@ do { \ #define nfsm_srvmtofh(f) \ do { \ int t1; \ - t1 = nfsm_srvmtofh_xx((f), nfsd, &md, &dpos); \ + t1 = nfsm_srvmtofh_xx((f), nfsd->nd_flag & ND_NFSV3, &md, &dpos); \ if (t1) { \ error = t1; \ nfsm_reply(0); \ diff --git a/sys/nfsserver/nfsrvcache.h b/sys/nfsserver/nfsrvcache.h index 66176f4..9c527e9 100644 --- a/sys/nfsserver/nfsrvcache.h +++ b/sys/nfsserver/nfsrvcache.h @@ -44,6 +44,8 @@ #define NFSRVCACHE_MAX_SIZE 2048 #define NFSRVCACHE_MIN_SIZE 64 +#ifdef NFS_LEGACYRPC + struct nfsrvcache { TAILQ_ENTRY(nfsrvcache) rc_lru; /* LRU chain */ LIST_ENTRY(nfsrvcache) rc_hash; /* Hash chain */ @@ -83,3 +85,5 @@ struct nfsrvcache { #define RC_NAM 0x40 #endif + +#endif diff --git a/sys/nlm/nlm.h b/sys/nlm/nlm.h index addd07e..27b921f 100644 --- a/sys/nlm/nlm.h +++ b/sys/nlm/nlm.h @@ -93,7 +93,7 @@ extern void nlm_host_release(struct nlm_host *host); * Return an RPC client handle that can be used to talk to the NLM * running on the given host. */ -extern CLIENT *nlm_host_get_rpc(struct nlm_host *host); +extern CLIENT *nlm_host_get_rpc(struct nlm_host *host, bool_t isserver); /* * Return the system ID for a host. diff --git a/sys/nlm/nlm_advlock.c b/sys/nlm/nlm_advlock.c index 5d1cc83..2c1f1a6 100644 --- a/sys/nlm/nlm_advlock.c +++ b/sys/nlm/nlm_advlock.c @@ -267,6 +267,7 @@ nlm_advlock_internal(struct vnode *vp, void *id, int op, struct flock *fl, ext.rc_feedback = nlm_feedback; ext.rc_feedback_arg = &nf; + ext.rc_timers = NULL; ns = NULL; if (flags & F_FLOCK) { @@ -753,7 +754,7 @@ nlm_setlock(struct nlm_host *host, struct rpc_callextra *ext, retry = 5*hz; for (;;) { - client = nlm_host_get_rpc(host); + client = nlm_host_get_rpc(host, FALSE); if (!client) return (ENOLCK); /* XXX retry? */ @@ -834,7 +835,7 @@ nlm_setlock(struct nlm_host *host, struct rpc_callextra *ext, cancel.alock = args.alock; do { - client = nlm_host_get_rpc(host); + client = nlm_host_get_rpc(host, FALSE); if (!client) /* XXX retry? */ return (ENOLCK); @@ -942,7 +943,7 @@ nlm_clearlock(struct nlm_host *host, struct rpc_callextra *ext, return (error); for (;;) { - client = nlm_host_get_rpc(host); + client = nlm_host_get_rpc(host, FALSE); if (!client) return (ENOLCK); /* XXX retry? */ @@ -1023,7 +1024,7 @@ nlm_getlock(struct nlm_host *host, struct rpc_callextra *ext, args.exclusive = exclusive; for (;;) { - client = nlm_host_get_rpc(host); + client = nlm_host_get_rpc(host, FALSE); if (!client) return (ENOLCK); /* XXX retry? */ diff --git a/sys/nlm/nlm_prot_impl.c b/sys/nlm/nlm_prot_impl.c index 831d330..1add718 100644 --- a/sys/nlm/nlm_prot_impl.c +++ b/sys/nlm/nlm_prot_impl.c @@ -26,6 +26,7 @@ */ #include "opt_inet6.h" +#include "opt_nfs.h" #include __FBSDID("$FreeBSD$"); @@ -205,6 +206,12 @@ enum nlm_host_state { NLM_MONITOR_FAILED, NLM_RECOVERING }; + +struct nlm_rpc { + CLIENT *nr_client; /* (l) RPC client handle */ + time_t nr_create_time; /* (l) when client was created */ +}; + struct nlm_host { struct mtx nh_lock; volatile u_int nh_refs; /* (a) reference count */ @@ -213,12 +220,12 @@ struct nlm_host { uint32_t nh_sysid; /* (c) our allocaed system ID */ char nh_sysid_string[10]; /* (c) string rep. of sysid */ struct sockaddr_storage nh_addr; /* (s) remote address of host */ - CLIENT *nh_rpc; /* (l) RPC handle to send to host */ + struct nlm_rpc nh_srvrpc; /* (l) RPC for server replies */ + struct nlm_rpc nh_clntrpc; /* (l) RPC for client requests */ rpcvers_t nh_vers; /* (s) NLM version of host */ int nh_state; /* (s) last seen NSM state of host */ enum nlm_host_state nh_monstate; /* (l) local NSM monitoring state */ time_t nh_idle_timeout; /* (s) Time at which host is idle */ - time_t nh_rpc_create_time; /* (s) Time we create RPC client */ struct sysctl_ctx_list nh_sysctl; /* (c) vfs.nlm.sysid nodes */ struct nlm_async_lock_list nh_pending; /* (l) pending async locks */ struct nlm_async_lock_list nh_finished; /* (l) finished async locks */ @@ -283,7 +290,7 @@ nlm_copy_netobj(struct netobj *dst, struct netobj *src, static CLIENT * nlm_get_rpc(struct sockaddr *sa, rpcprog_t prog, rpcvers_t vers) { - const char *wchan = "nlmrcv"; + char *wchan = "nlmrcv"; const char* protofmly; struct sockaddr_storage ss; struct socket *so; @@ -472,7 +479,7 @@ again: rpcb = clnt_reconnect_create(nconf, (struct sockaddr *)&ss, prog, vers, 0, 0); - CLNT_CONTROL(rpcb, CLSET_WAITCHAN, &wchan); + CLNT_CONTROL(rpcb, CLSET_WAITCHAN, wchan); rpcb->cl_auth = nlm_auth; } else { @@ -482,7 +489,7 @@ again: CLNT_CONTROL(rpcb, CLSET_SVC_ADDR, &ss); CLNT_CONTROL(rpcb, CLSET_PROG, &prog); CLNT_CONTROL(rpcb, CLSET_VERS, &vers); - CLNT_CONTROL(rpcb, CLSET_WAITCHAN, &wchan); + CLNT_CONTROL(rpcb, CLSET_WAITCHAN, wchan); rpcb->cl_auth = nlm_auth; } @@ -646,13 +653,17 @@ nlm_host_destroy(struct nlm_host *host) TAILQ_REMOVE(&nlm_hosts, host, nh_link); mtx_unlock(&nlm_global_lock); - if (host->nh_rpc) - CLNT_RELEASE(host->nh_rpc); + if (host->nh_srvrpc.nr_client) + CLNT_RELEASE(host->nh_srvrpc.nr_client); + if (host->nh_clntrpc.nr_client) + CLNT_RELEASE(host->nh_clntrpc.nr_client); mtx_destroy(&host->nh_lock); sysctl_ctx_free(&host->nh_sysctl); free(host, M_NLM); } +#ifdef NFSCLIENT + /* * Thread start callback for client lock recovery */ @@ -677,6 +688,8 @@ nlm_client_recovery_start(void *arg) kthread_exit(); } +#endif + /* * This is called when we receive a host state change notification. We * unlock any active locks owned by the host. When rpc.lockd is @@ -716,6 +729,7 @@ nlm_host_notify(struct nlm_host *host, int newstate) lf_clearremotesys(host->nh_sysid); host->nh_state = newstate; +#ifdef NFSCLIENT /* * If we have any remote locks for this host (i.e. it * represents a remote NFS server that our local NFS client @@ -730,6 +744,7 @@ nlm_host_notify(struct nlm_host *host, int newstate) kthread_add(nlm_client_recovery_start, host, curproc, &td, 0, 0, "NFS lock recovery for %s", host->nh_caller_name); } +#endif } /* @@ -783,7 +798,6 @@ nlm_create_host(const char* caller_name) host->nh_sysid = nlm_next_sysid++; snprintf(host->nh_sysid_string, sizeof(host->nh_sysid_string), "%d", host->nh_sysid); - host->nh_rpc = NULL; host->nh_vers = 0; host->nh_state = 0; host->nh_monstate = NLM_UNMONITORED; @@ -933,15 +947,15 @@ nlm_find_host_by_name(const char *name, const struct sockaddr *addr, * have an RPC client handle, make sure the address is * the same, otherwise discard the client handle. */ - if (host->nh_addr.ss_len && host->nh_rpc) { + if (host->nh_addr.ss_len && host->nh_srvrpc.nr_client) { if (!nlm_compare_addr( (struct sockaddr *) &host->nh_addr, addr) || host->nh_vers != vers) { CLIENT *client; mtx_lock(&host->nh_lock); - client = host->nh_rpc; - host->nh_rpc = NULL; + client = host->nh_srvrpc.nr_client; + host->nh_srvrpc.nr_client = NULL; mtx_unlock(&host->nh_lock); if (client) { CLNT_RELEASE(client); @@ -1173,12 +1187,18 @@ nlm_host_monitor(struct nlm_host *host, int state) * running on the given host. */ CLIENT * -nlm_host_get_rpc(struct nlm_host *host) +nlm_host_get_rpc(struct nlm_host *host, bool_t isserver) { + struct nlm_rpc *rpc; CLIENT *client; mtx_lock(&host->nh_lock); + if (isserver) + rpc = &host->nh_srvrpc; + else + rpc = &host->nh_clntrpc; + /* * We can't hold onto RPC handles for too long - the async * call/reply protocol used by some NLM clients makes it hard @@ -1187,33 +1207,33 @@ nlm_host_get_rpc(struct nlm_host *host) * holding any locks, it won't bother to notify us. We * expire the RPC handles after two minutes. */ - if (host->nh_rpc && time_uptime > host->nh_rpc_create_time + 2*60) { - client = host->nh_rpc; - host->nh_rpc = NULL; + if (rpc->nr_client && time_uptime > rpc->nr_create_time + 2*60) { + client = rpc->nr_client; + rpc->nr_client = NULL; mtx_unlock(&host->nh_lock); CLNT_RELEASE(client); mtx_lock(&host->nh_lock); } - if (!host->nh_rpc) { + if (!rpc->nr_client) { mtx_unlock(&host->nh_lock); client = nlm_get_rpc((struct sockaddr *)&host->nh_addr, NLM_PROG, host->nh_vers); mtx_lock(&host->nh_lock); if (client) { - if (host->nh_rpc) { + if (rpc->nr_client) { mtx_unlock(&host->nh_lock); CLNT_DESTROY(client); mtx_lock(&host->nh_lock); } else { - host->nh_rpc = client; - host->nh_rpc_create_time = time_uptime; + rpc->nr_client = client; + rpc->nr_create_time = time_uptime; } } } - client = host->nh_rpc; + client = rpc->nr_client; if (client) CLNT_ACQUIRE(client); mtx_unlock(&host->nh_lock); @@ -1439,8 +1459,10 @@ nlm_server_main(int addr_count, char **addrs) enum clnt_stat stat; struct nlm_host *host, *nhost; struct nlm_waiting_lock *nw; +#ifdef NFSCLIENT vop_advlock_t *old_nfs_advlock; vop_reclaim_t *old_nfs_reclaim; +#endif int v4_used; #ifdef INET6 int v6_used; @@ -1512,7 +1534,7 @@ nlm_server_main(int addr_count, char **addrs) goto out; } - pool = svcpool_create(); + pool = svcpool_create("NLM", NULL); error = nlm_register_services(pool, addr_count, addrs); if (error) @@ -1541,16 +1563,20 @@ nlm_server_main(int addr_count, char **addrs) printf("NLM: local NSM state is %d\n", smstat.state); nlm_nsm_state = smstat.state; +#ifdef NFSCLIENT old_nfs_advlock = nfs_advlock_p; nfs_advlock_p = nlm_advlock; old_nfs_reclaim = nfs_reclaim_p; nfs_reclaim_p = nlm_reclaim; +#endif svc_run(pool); error = 0; +#ifdef NFSCLIENT nfs_advlock_p = old_nfs_advlock; nfs_reclaim_p = old_nfs_reclaim; +#endif out: if (pool) @@ -1595,7 +1621,8 @@ out: } TAILQ_FOREACH_SAFE(host, &nlm_hosts, nh_link, nhost) { mtx_lock(&host->nh_lock); - if (host->nh_rpc) { + if (host->nh_srvrpc.nr_client + || host->nh_clntrpc.nr_client) { if (host->nh_addr.ss_family == AF_INET) v4_used++; #ifdef INET6 @@ -1607,7 +1634,12 @@ out: * correctly with the fact that a socket may * be used by many rpc handles. */ - CLNT_CONTROL(host->nh_rpc, CLSET_FD_CLOSE, 0); + if (host->nh_srvrpc.nr_client) + CLNT_CONTROL(host->nh_srvrpc.nr_client, + CLSET_FD_CLOSE, 0); + if (host->nh_clntrpc.nr_client) + CLNT_CONTROL(host->nh_clntrpc.nr_client, + CLSET_FD_CLOSE, 0); } mtx_unlock(&host->nh_lock); } @@ -1687,11 +1719,10 @@ static int nlm_get_vfs_state(struct nlm_host *host, struct svc_req *rqstp, fhandle_t *fhp, struct vfs_state *vs) { - int error, exflags, freecred; + int error, exflags; struct ucred *cred = NULL, *credanon; memset(vs, 0, sizeof(*vs)); - freecred = FALSE; vs->vs_mp = vfs_getvfs(&fhp->fh_fsid); if (!vs->vs_mp) { @@ -1700,7 +1731,7 @@ nlm_get_vfs_state(struct nlm_host *host, struct svc_req *rqstp, vs->vs_vfslocked = VFS_LOCK_GIANT(vs->vs_mp); error = VFS_CHECKEXP(vs->vs_mp, (struct sockaddr *)&host->nh_addr, - &exflags, &credanon); + &exflags, &credanon, NULL, NULL); if (error) goto out; @@ -1714,16 +1745,13 @@ nlm_get_vfs_state(struct nlm_host *host, struct svc_req *rqstp, goto out; vs->vs_vnlocked = TRUE; - cred = crget(); - freecred = TRUE; - if (!svc_getcred(rqstp, cred, NULL)) { + if (!svc_getcred(rqstp, &cred, NULL)) { error = EINVAL; goto out; } if (cred->cr_uid == 0 || (exflags & MNT_EXPORTANON)) { crfree(cred); - cred = credanon; - freecred = FALSE; + cred = crhold(credanon); } /* @@ -1741,7 +1769,7 @@ nlm_get_vfs_state(struct nlm_host *host, struct svc_req *rqstp, vs->vs_vnlocked = FALSE; out: - if (freecred) + if (cred) crfree(cred); return (error); @@ -1788,7 +1816,7 @@ nlm_do_test(nlm4_testargs *argp, nlm4_testres *result, struct svc_req *rqstp, memset(&vs, 0, sizeof(vs)); host = nlm_find_host_by_name(argp->alock.caller_name, - (struct sockaddr *) rqstp->rq_xprt->xp_rtaddr.buf, rqstp->rq_vers); + svc_getrpccaller(rqstp), rqstp->rq_vers); if (!host) { result->stat.stat = nlm4_denied_nolocks; return (ENOMEM); @@ -1866,7 +1894,7 @@ nlm_do_test(nlm4_testargs *argp, nlm4_testres *result, struct svc_req *rqstp, out: nlm_release_vfs_state(&vs); if (rpcp) - *rpcp = nlm_host_get_rpc(host); + *rpcp = nlm_host_get_rpc(host, TRUE); nlm_host_release(host); return (0); } @@ -1885,7 +1913,7 @@ nlm_do_lock(nlm4_lockargs *argp, nlm4_res *result, struct svc_req *rqstp, memset(&vs, 0, sizeof(vs)); host = nlm_find_host_by_name(argp->alock.caller_name, - (struct sockaddr *) rqstp->rq_xprt->xp_rtaddr.buf, rqstp->rq_vers); + svc_getrpccaller(rqstp), rqstp->rq_vers); if (!host) { result->stat.stat = nlm4_denied_nolocks; return (ENOMEM); @@ -1937,7 +1965,7 @@ nlm_do_lock(nlm4_lockargs *argp, nlm4_res *result, struct svc_req *rqstp, /* * First, make sure we can contact the host's NLM. */ - client = nlm_host_get_rpc(host); + client = nlm_host_get_rpc(host, TRUE); if (!client) { result->stat.stat = nlm4_failed; goto out; @@ -2049,7 +2077,7 @@ nlm_do_lock(nlm4_lockargs *argp, nlm4_res *result, struct svc_req *rqstp, out: nlm_release_vfs_state(&vs); if (rpcp) - *rpcp = nlm_host_get_rpc(host); + *rpcp = nlm_host_get_rpc(host, TRUE); nlm_host_release(host); return (0); } @@ -2069,7 +2097,7 @@ nlm_do_cancel(nlm4_cancargs *argp, nlm4_res *result, struct svc_req *rqstp, memset(&vs, 0, sizeof(vs)); host = nlm_find_host_by_name(argp->alock.caller_name, - (struct sockaddr *) rqstp->rq_xprt->xp_rtaddr.buf, rqstp->rq_vers); + svc_getrpccaller(rqstp), rqstp->rq_vers); if (!host) { result->stat.stat = nlm4_denied_nolocks; return (ENOMEM); @@ -2140,7 +2168,7 @@ nlm_do_cancel(nlm4_cancargs *argp, nlm4_res *result, struct svc_req *rqstp, out: nlm_release_vfs_state(&vs); if (rpcp) - *rpcp = nlm_host_get_rpc(host); + *rpcp = nlm_host_get_rpc(host, TRUE); nlm_host_release(host); return (0); } @@ -2159,7 +2187,7 @@ nlm_do_unlock(nlm4_unlockargs *argp, nlm4_res *result, struct svc_req *rqstp, memset(&vs, 0, sizeof(vs)); host = nlm_find_host_by_name(argp->alock.caller_name, - (struct sockaddr *) rqstp->rq_xprt->xp_rtaddr.buf, rqstp->rq_vers); + svc_getrpccaller(rqstp), rqstp->rq_vers); if (!host) { result->stat.stat = nlm4_denied_nolocks; return (ENOMEM); @@ -2203,7 +2231,7 @@ nlm_do_unlock(nlm4_unlockargs *argp, nlm4_res *result, struct svc_req *rqstp, out: nlm_release_vfs_state(&vs); if (rpcp) - *rpcp = nlm_host_get_rpc(host); + *rpcp = nlm_host_get_rpc(host, TRUE); nlm_host_release(host); return (0); } @@ -2218,9 +2246,7 @@ nlm_do_granted(nlm4_testargs *argp, nlm4_res *result, struct svc_req *rqstp, memset(result, 0, sizeof(*result)); - host = nlm_find_host_by_addr( - (struct sockaddr *) rqstp->rq_xprt->xp_rtaddr.buf, - rqstp->rq_vers); + host = nlm_find_host_by_addr(svc_getrpccaller(rqstp), rqstp->rq_vers); if (!host) { result->stat.stat = nlm4_denied_nolocks; return (ENOMEM); @@ -2247,7 +2273,7 @@ nlm_do_granted(nlm4_testargs *argp, nlm4_res *result, struct svc_req *rqstp, } mtx_unlock(&nlm_global_lock); if (rpcp) - *rpcp = nlm_host_get_rpc(host); + *rpcp = nlm_host_get_rpc(host, TRUE); nlm_host_release(host); return (0); } diff --git a/sys/nlm/nlm_prot_svc.c b/sys/nlm/nlm_prot_svc.c index 3b1a140..5141f87 100644 --- a/sys/nlm/nlm_prot_svc.c +++ b/sys/nlm/nlm_prot_svc.c @@ -57,8 +57,9 @@ nlm_prog_0(struct svc_req *rqstp, SVCXPRT *transp) switch (rqstp->rq_proc) { case NULLPROC: - (void) svc_sendreply(transp, + (void) svc_sendreply(rqstp, (xdrproc_t) xdr_void, (char *)NULL); + svc_freereq(rqstp); return; case NLM_SM_NOTIFY: @@ -68,19 +69,22 @@ nlm_prog_0(struct svc_req *rqstp, SVCXPRT *transp) break; default: - svcerr_noproc(transp); + svcerr_noproc(rqstp); + svc_freereq(rqstp); return; } (void) memset((char *)&argument, 0, sizeof (argument)); - if (!svc_getargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { - svcerr_decode(transp); + if (!svc_getargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { + svcerr_decode(rqstp); + svc_freereq(rqstp); return; } retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp); - if (retval > 0 && !svc_sendreply(transp, xdr_result, (char *)&result)) { - svcerr_systemerr(transp); + if (retval > 0 && !svc_sendreply(rqstp, xdr_result, (char *)&result)) { + svcerr_systemerr(rqstp); } - if (!svc_freeargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { + svc_freereq(rqstp); + if (!svc_freeargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { printf("unable to free arguments"); //exit(1); } @@ -121,8 +125,9 @@ nlm_prog_1(struct svc_req *rqstp, SVCXPRT *transp) switch (rqstp->rq_proc) { case NULLPROC: - (void) svc_sendreply(transp, + (void) svc_sendreply(rqstp, (xdrproc_t) xdr_void, (char *)NULL); + svc_freereq(rqstp); return; case NLM_TEST: @@ -216,22 +221,25 @@ nlm_prog_1(struct svc_req *rqstp, SVCXPRT *transp) break; default: - svcerr_noproc(transp); + svcerr_noproc(rqstp); + svc_freereq(rqstp); return; } (void) memset((char *)&argument, 0, sizeof (argument)); - if (!svc_getargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { - svcerr_decode(transp); + if (!svc_getargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { + svcerr_decode(rqstp); + svc_freereq(rqstp); return; } retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp); - if (retval > 0 && !svc_sendreply(transp, xdr_result, (char *)&result)) { - svcerr_systemerr(transp); + if (retval > 0 && !svc_sendreply(rqstp, xdr_result, (char *)&result)) { + svcerr_systemerr(rqstp); } - if (!svc_freeargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { + if (!svc_freeargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { printf("unable to free arguments"); //exit(1); } + svc_freereq(rqstp); if (!nlm_prog_1_freeresult(transp, xdr_result, (caddr_t) &result)) printf("unable to free results"); @@ -258,8 +266,9 @@ nlm_prog_3(struct svc_req *rqstp, SVCXPRT *transp) switch (rqstp->rq_proc) { case NULLPROC: - (void) svc_sendreply(transp, + (void) svc_sendreply(rqstp, (xdrproc_t) xdr_void, (char *)NULL); + svc_freereq(rqstp); return; case NLM_TEST: @@ -305,22 +314,25 @@ nlm_prog_3(struct svc_req *rqstp, SVCXPRT *transp) break; default: - svcerr_noproc(transp); + svcerr_noproc(rqstp); + svc_freereq(rqstp); return; } (void) memset((char *)&argument, 0, sizeof (argument)); - if (!svc_getargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { - svcerr_decode(transp); + if (!svc_getargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { + svcerr_decode(rqstp); + svc_freereq(rqstp); return; } retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp); - if (retval > 0 && !svc_sendreply(transp, xdr_result, (char *)&result)) { - svcerr_systemerr(transp); + if (retval > 0 && !svc_sendreply(rqstp, xdr_result, (char *)&result)) { + svcerr_systemerr(rqstp); } - if (!svc_freeargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { + if (!svc_freeargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { printf("unable to free arguments"); //exit(1); } + svc_freereq(rqstp); if (!nlm_prog_3_freeresult(transp, xdr_result, (caddr_t) &result)) printf("unable to free results"); @@ -367,8 +379,9 @@ nlm_prog_4(struct svc_req *rqstp, SVCXPRT *transp) switch (rqstp->rq_proc) { case NULLPROC: - (void) svc_sendreply(transp, + (void) svc_sendreply(rqstp, (xdrproc_t) xdr_void, (char *)NULL); + svc_freereq(rqstp); return; case NLM4_TEST: @@ -486,22 +499,25 @@ nlm_prog_4(struct svc_req *rqstp, SVCXPRT *transp) break; default: - svcerr_noproc(transp); + svcerr_noproc(rqstp); + svc_freereq(rqstp); return; } (void) memset((char *)&argument, 0, sizeof (argument)); - if (!svc_getargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { - svcerr_decode(transp); + if (!svc_getargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { + svcerr_decode(rqstp); + svc_freereq(rqstp); return; } retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp); - if (retval > 0 && !svc_sendreply(transp, xdr_result, (char *)&result)) { - svcerr_systemerr(transp); + if (retval > 0 && !svc_sendreply(rqstp, xdr_result, (char *)&result)) { + svcerr_systemerr(rqstp); } - if (!svc_freeargs(transp, xdr_argument, (char *)(caddr_t) &argument)) { + if (!svc_freeargs(rqstp, xdr_argument, (char *)(caddr_t) &argument)) { printf("unable to free arguments"); //exit(1); } + svc_freereq(rqstp); if (!nlm_prog_4_freeresult(transp, xdr_result, (caddr_t) &result)) printf("unable to free results"); 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 #include #include +#include #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 #include #include +#include #include /* 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 #include +#include #include #include #include @@ -46,11 +47,12 @@ __FBSDID("$FreeBSD$"); #include 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 #include #include +#include #include #include #include #include #include +#include #include #include @@ -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 + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 + * Developed with Red Inc: Alfred Perlstein + * + * 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 #include #include +#include #include #include #include @@ -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 + +#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 . + 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 = # + 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 . + 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 + +/* 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#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 . + 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 + +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 . + 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; ilength; 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; ilength; 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 " + "", + 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 #include #include +#include #include +#include #include +#include #include +#include #include #include #include #include +#include #include #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 #include #include +#include +#include +#include #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 +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 #include #include +#include #include #include @@ -61,10 +62,10 @@ __FBSDID("$FreeBSD$"); #include 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 #include #include +#include #include #include #include @@ -62,16 +63,17 @@ __FBSDID("$FreeBSD$"); #include -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 *, diff --git a/sys/sys/mount.h b/sys/sys/mount.h index 7438c18..bd56521 100644 --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -365,8 +365,23 @@ struct fhandle { typedef struct fhandle fhandle_t; /* + * Old export arguments without security flavor list + */ +struct oexport_args { + int ex_flags; /* export related flags */ + uid_t ex_root; /* mapping for root uid */ + struct xucred ex_anon; /* mapping for anonymous user */ + struct sockaddr *ex_addr; /* net address to which exported */ + u_char ex_addrlen; /* and the net address length */ + struct sockaddr *ex_mask; /* mask of valid bits in saddr */ + u_char ex_masklen; /* and the smask length */ + char *ex_indexfile; /* index file for WebNFS URLs */ +}; + +/* * Export arguments for local filesystem mount calls. */ +#define MAXSECFLAVORS 5 struct export_args { int ex_flags; /* export related flags */ uid_t ex_root; /* mapping for root uid */ @@ -376,6 +391,8 @@ struct export_args { struct sockaddr *ex_mask; /* mask of valid bits in saddr */ u_char ex_masklen; /* and the smask length */ char *ex_indexfile; /* index file for WebNFS URLs */ + int ex_numsecflavors; /* security flavor count */ + int ex_secflavors[MAXSECFLAVORS]; /* list of security flavors */ }; /* @@ -542,7 +559,8 @@ typedef int vfs_vget_t(struct mount *mp, ino_t ino, int flags, struct vnode **vpp); typedef int vfs_fhtovp_t(struct mount *mp, struct fid *fhp, struct vnode **vpp); typedef int vfs_checkexp_t(struct mount *mp, struct sockaddr *nam, - int *extflagsp, struct ucred **credanonp); + int *extflagsp, struct ucred **credanonp, + int *numsecflavors, int **secflavors); typedef int vfs_init_t(struct vfsconf *); typedef int vfs_uninit_t(struct vfsconf *); typedef int vfs_extattrctl_t(struct mount *mp, int cmd, @@ -584,8 +602,8 @@ vfs_statfs_t __vfs_statfs; (*(MP)->mnt_op->vfs_vget)(MP, INO, FLAGS, VPP) #define VFS_FHTOVP(MP, FIDP, VPP) \ (*(MP)->mnt_op->vfs_fhtovp)(MP, FIDP, VPP) -#define VFS_CHECKEXP(MP, NAM, EXFLG, CRED) \ - (*(MP)->mnt_op->vfs_checkexp)(MP, NAM, EXFLG, CRED) +#define VFS_CHECKEXP(MP, NAM, EXFLG, CRED, NUMSEC, SEC) \ + (*(MP)->mnt_op->vfs_checkexp)(MP, NAM, EXFLG, CRED, NUMSEC, SEC) #define VFS_EXTATTRCTL(MP, C, FN, NS, N, P) \ (*(MP)->mnt_op->vfs_extattrctl)(MP, C, FN, NS, N, P) #define VFS_SYSCTL(MP, OP, REQ) \ diff --git a/sys/xdr/xdr_mbuf.c b/sys/xdr/xdr_mbuf.c index 770dfc3..e6f7c9d 100644 --- a/sys/xdr/xdr_mbuf.c +++ b/sys/xdr/xdr_mbuf.c @@ -78,6 +78,51 @@ xdrmbuf_create(XDR *xdrs, struct mbuf *m, enum xdr_op op) } } +void +xdrmbuf_append(XDR *xdrs, struct mbuf *madd) +{ + struct mbuf *m; + + KASSERT(xdrs->x_ops == &xdrmbuf_ops && xdrs->x_op == XDR_ENCODE, + ("xdrmbuf_append: invalid XDR stream")); + + if (m_length(madd, NULL) == 0) { + m_freem(madd); + return; + } + + m = (struct mbuf *) xdrs->x_private; + m->m_next = madd; + + m = m_last(madd); + xdrs->x_private = m; + xdrs->x_handy = m->m_len; +} + +struct mbuf * +xdrmbuf_getall(XDR *xdrs) +{ + struct mbuf *m0, *m; + + KASSERT(xdrs->x_ops == &xdrmbuf_ops && xdrs->x_op == XDR_DECODE, + ("xdrmbuf_append: invalid XDR stream")); + + m0 = (struct mbuf *) xdrs->x_base; + m = (struct mbuf *) xdrs->x_private; + if (m0 != m) { + while (m0->m_next != m) + m0 = m0->m_next; + m0->m_next = NULL; + xdrs->x_private = NULL; + } else { + xdrs->x_base = NULL; + xdrs->x_private = NULL; + } + + m_adj(m, xdrs->x_handy); + return (m); +} + static void xdrmbuf_destroy(XDR *xdrs) { @@ -92,9 +137,16 @@ xdrmbuf_destroy(XDR *xdrs) static bool_t xdrmbuf_getlong(XDR *xdrs, long *lp) { + int32_t *p; int32_t t; - xdrmbuf_getbytes(xdrs, (char *) &t, sizeof(int32_t)); + p = xdrmbuf_inline(xdrs, sizeof(int32_t)); + if (p) { + t = *p; + } else { + xdrmbuf_getbytes(xdrs, (char *) &t, sizeof(int32_t)); + } + *lp = ntohl(t); return (TRUE); } @@ -104,10 +156,16 @@ xdrmbuf_putlong(xdrs, lp) XDR *xdrs; const long *lp; { + int32_t *p; int32_t t = htonl(*lp); - xdrmbuf_putbytes(xdrs, (char *) &t, sizeof(int32_t)); - return (TRUE); + p = xdrmbuf_inline(xdrs, sizeof(int32_t)); + if (p) { + *p = t; + return (TRUE); + } else { + return (xdrmbuf_putbytes(xdrs, (char *) &t, sizeof(int32_t))); + } } static bool_t @@ -130,7 +188,7 @@ xdrmbuf_getbytes(XDR *xdrs, char *addr, u_int len) sz = m->m_len - xdrs->x_handy; if (sz > len) sz = len; - memcpy(addr, mtod(m, const char *) + xdrs->x_handy, sz); + bcopy(mtod(m, const char *) + xdrs->x_handy, addr, sz); addr += sz; xdrs->x_handy += sz; @@ -157,7 +215,7 @@ xdrmbuf_putbytes(XDR *xdrs, const char *addr, u_int len) sz = M_TRAILINGSPACE(m) + (m->m_len - xdrs->x_handy); if (sz > len) sz = len; - memcpy(mtod(m, char *) + xdrs->x_handy, addr, sz); + bcopy(addr, mtod(m, char *) + xdrs->x_handy, sz); addr += sz; xdrs->x_handy += sz; if (xdrs->x_handy > m->m_len) @@ -167,6 +225,8 @@ xdrmbuf_putbytes(XDR *xdrs, const char *addr, u_int len) if (xdrs->x_handy == m->m_len && M_TRAILINGSPACE(m) == 0) { if (!m->m_next) { MGET(n, M_TRYWAIT, m->m_type); + if (m->m_flags & M_EXT) + MCLGET(n, M_TRYWAIT); m->m_next = n; } m = m->m_next; diff --git a/tools/regression/kgssapi/Makefile b/tools/regression/kgssapi/Makefile new file mode 100644 index 0000000..49059fa --- /dev/null +++ b/tools/regression/kgssapi/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +PROG= gsstest +NO_MAN= +WARNS?= 2 +LDADD= -lgssapi -lgssapi_krb5 +DEBUG_FLAGS= -g -O0 + +.include diff --git a/tools/regression/kgssapi/gsstest.c b/tools/regression/kgssapi/gsstest.c new file mode 100644 index 0000000..acebf4c --- /dev/null +++ b/tools/regression/kgssapi/gsstest.c @@ -0,0 +1,307 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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$ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct gsstest_2_args { + int step; /* test step number */ + gss_buffer_desc input_token; /* token from userland */ + gss_buffer_desc output_token; /* buffer to receive reply token */ +}; +struct gsstest_2_res { + OM_uint32 maj_stat; /* maj_stat from kernel */ + OM_uint32 min_stat; /* min_stat from kernel */ + gss_buffer_desc output_token; /* reply token (using space from gsstest_2_args.output) */ +}; + +static void +report_error(gss_OID mech, OM_uint32 maj, OM_uint32 min) +{ + OM_uint32 maj_stat, min_stat; + OM_uint32 message_context; + gss_buffer_desc buf; + + printf("major_stat=%d, minor_stat=%d\n", maj, min); + message_context = 0; + do { + maj_stat = gss_display_status(&min_stat, maj, + GSS_C_GSS_CODE, GSS_C_NO_OID, &message_context, &buf); + printf("%.*s\n", (int)buf.length, (char *) buf.value); + gss_release_buffer(&min_stat, &buf); + } while (message_context); + if (mech) { + message_context = 0; + do { + maj_stat = gss_display_status(&min_stat, min, + GSS_C_MECH_CODE, mech, &message_context, &buf); + printf("%.*s\n", (int)buf.length, (char *) buf.value); + gss_release_buffer(&min_stat, &buf); + } while (message_context); + } +} + +int +main(int argc, char **argv) +{ + struct module_stat stat; + int mod; + int syscall_num; + + stat.version = sizeof(stat); + mod = modfind("gsstest_syscall"); + if (mod < 0) { + fprintf(stderr, "%s: kernel support not present\n", argv[0]); + exit(1); + } + modstat(mod, &stat); + syscall_num = stat.data.intval; + + switch (atoi(argv[1])) { + case 1: + syscall(syscall_num, 1, NULL, NULL); + break; + + case 2: { + struct gsstest_2_args args; + struct gsstest_2_res res; + char hostname[512]; + char token_buffer[8192]; + OM_uint32 maj_stat, min_stat; + gss_ctx_id_t client_context = GSS_C_NO_CONTEXT; + gss_cred_id_t client_cred; + gss_OID mech_type = GSS_C_NO_OID; + gss_buffer_desc name_buf, message_buf; + gss_name_t name; + int32_t enctypes[] = { + ETYPE_DES_CBC_CRC, + ETYPE_ARCFOUR_HMAC_MD5, + ETYPE_ARCFOUR_HMAC_MD5_56, + ETYPE_AES256_CTS_HMAC_SHA1_96, + ETYPE_AES128_CTS_HMAC_SHA1_96, + ETYPE_DES3_CBC_SHA1, + }; + int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]); + int established; + int i; + + for (i = 0; i < num_enctypes; i++) { + printf("testing etype %d\n", enctypes[i]); + args.output_token.length = sizeof(token_buffer); + args.output_token.value = token_buffer; + + gethostname(hostname, sizeof(hostname)); + snprintf(token_buffer, sizeof(token_buffer), + "nfs@%s", hostname); + name_buf.length = strlen(token_buffer); + name_buf.value = token_buffer; + maj_stat = gss_import_name(&min_stat, &name_buf, + GSS_C_NT_HOSTBASED_SERVICE, &name); + if (GSS_ERROR(maj_stat)) { + printf("gss_import_name failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + maj_stat = gss_acquire_cred(&min_stat, GSS_C_NO_NAME, + 0, GSS_C_NO_OID_SET, GSS_C_INITIATE, &client_cred, + NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_acquire_cred (client) failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, + client_cred, 1, &enctypes[i]); + if (GSS_ERROR(maj_stat)) { + printf("gss_krb5_set_allowable_enctypes failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + res.output_token.length = 0; + res.output_token.value = 0; + established = 0; + while (!established) { + maj_stat = gss_init_sec_context(&min_stat, + client_cred, + &client_context, + name, + GSS_C_NO_OID, + (GSS_C_MUTUAL_FLAG + |GSS_C_CONF_FLAG + |GSS_C_INTEG_FLAG + |GSS_C_SEQUENCE_FLAG + |GSS_C_REPLAY_FLAG), + 0, + GSS_C_NO_CHANNEL_BINDINGS, + &res.output_token, + &mech_type, + &args.input_token, + NULL, + NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_init_sec_context failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + if (args.input_token.length) { + args.step = 1; + syscall(syscall_num, 2, &args, &res); + gss_release_buffer(&min_stat, + &args.input_token); + if (res.maj_stat != GSS_S_COMPLETE + && res.maj_stat != GSS_S_CONTINUE_NEEDED) { + printf("gss_accept_sec_context (kernel) failed\n"); + report_error(mech_type, res.maj_stat, + res.min_stat); + goto out; + } + } + if (maj_stat == GSS_S_COMPLETE) + established = 1; + } + + message_buf.value = "Hello world"; + message_buf.length = strlen((char *) message_buf.value); + + maj_stat = gss_get_mic(&min_stat, client_context, + GSS_C_QOP_DEFAULT, &message_buf, &args.input_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_get_mic failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + args.step = 2; + syscall(syscall_num, 2, &args, &res); + gss_release_buffer(&min_stat, &args.input_token); + if (GSS_ERROR(res.maj_stat)) { + printf("kernel gss_verify_mic failed\n"); + report_error(mech_type, res.maj_stat, res.min_stat); + goto out; + } + + maj_stat = gss_verify_mic(&min_stat, client_context, + &message_buf, &res.output_token, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_verify_mic failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + maj_stat = gss_wrap(&min_stat, client_context, + TRUE, GSS_C_QOP_DEFAULT, &message_buf, NULL, + &args.input_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_wrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + args.step = 3; + syscall(syscall_num, 2, &args, &res); + gss_release_buffer(&min_stat, &args.input_token); + if (GSS_ERROR(res.maj_stat)) { + printf("kernel gss_unwrap failed\n"); + report_error(mech_type, res.maj_stat, res.min_stat); + goto out; + } + + maj_stat = gss_unwrap(&min_stat, client_context, + &res.output_token, &message_buf, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_unwrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + gss_release_buffer(&min_stat, &message_buf); + + maj_stat = gss_wrap(&min_stat, client_context, + FALSE, GSS_C_QOP_DEFAULT, &message_buf, NULL, + &args.input_token); + if (GSS_ERROR(maj_stat)) { + printf("gss_wrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + + args.step = 4; + syscall(syscall_num, 2, &args, &res); + gss_release_buffer(&min_stat, &args.input_token); + if (GSS_ERROR(res.maj_stat)) { + printf("kernel gss_unwrap failed\n"); + report_error(mech_type, res.maj_stat, res.min_stat); + goto out; + } + + maj_stat = gss_unwrap(&min_stat, client_context, + &res.output_token, &message_buf, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + printf("gss_unwrap failed\n"); + report_error(mech_type, maj_stat, min_stat); + goto out; + } + gss_release_buffer(&min_stat, &message_buf); + + args.step = 5; + syscall(syscall_num, 2, &args, &res); + + gss_release_name(&min_stat, &name); + gss_release_cred(&min_stat, &client_cred); + gss_delete_sec_context(&min_stat, &client_context, + GSS_C_NO_BUFFER); + } + + break; + } + case 3: + syscall(syscall_num, 3, NULL, NULL); + break; + case 4: + syscall(syscall_num, 4, NULL, NULL); + break; + } + return (0); + +out: + return (1); +} diff --git a/tools/regression/rpcsec_gss/Makefile b/tools/regression/rpcsec_gss/Makefile new file mode 100644 index 0000000..29b14d6 --- /dev/null +++ b/tools/regression/rpcsec_gss/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +PROG= rpctest +NO_MAN= +WARNS?= 6 +LDADD= -lrpcsec_gss +DEBUG_FLAGS= -g -O0 + +.include diff --git a/tools/regression/rpcsec_gss/rpctest.c b/tools/regression/rpcsec_gss/rpctest.c new file mode 100644 index 0000000..847ae3b --- /dev/null +++ b/tools/regression/rpcsec_gss/rpctest.c @@ -0,0 +1,400 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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$ + */ + +#ifdef __FreeBSD__ +#include +#else +#define __unused +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static rpc_gss_principal_t server_acl = NULL; + +static void +usage(void) +{ + printf("rpctest client | server\n"); + exit(1); +} + +static void +print_principal(rpc_gss_principal_t principal) +{ + int i, len, n; + uint8_t *p; + + len = principal->len; + p = (uint8_t *) principal->name; + while (len > 0) { + n = len; + if (n > 16) + n = 16; + for (i = 0; i < n; i++) + printf("%02x ", p[i]); + for (; i < 16; i++) + printf(" "); + printf("|"); + for (i = 0; i < n; i++) + printf("%c", isprint(p[i]) ? p[i] : '.'); + printf("|\n"); + len -= n; + p += n; + } +} + +static void +test_client(int argc, const char **argv) +{ + rpcproc_t prog = 123456; + rpcvers_t vers = 1; + const char *netid = "tcp"; + char hostname[128], service[128+5]; + CLIENT *client; + AUTH *auth; + const char **mechs; + rpc_gss_options_req_t options_req; + rpc_gss_options_ret_t options_ret; + rpc_gss_service_t svc; + struct timeval tv; + enum clnt_stat stat; + + if (argc == 2) + strlcpy(hostname, argv[1], sizeof(hostname)); + else + gethostname(hostname, sizeof(hostname)); + + client = clnt_create(hostname, prog, vers, netid); + if (!client) { + printf("rpc_createerr.cf_stat = %d\n", + rpc_createerr.cf_stat); + printf("rpc_createerr.cf_error.re_errno = %d\n", + rpc_createerr.cf_error.re_errno); + return; + } + + strcpy(service, "host"); + strcat(service, "@"); + strcat(service, hostname); + + mechs = rpc_gss_get_mechanisms(); + auth = NULL; + while (*mechs) { + options_req.req_flags = GSS_C_MUTUAL_FLAG; + options_req.time_req = 600; + options_req.my_cred = GSS_C_NO_CREDENTIAL; + options_req.input_channel_bindings = NULL; + auth = rpc_gss_seccreate(client, service, + *mechs, + rpc_gss_svc_none, + NULL, + &options_req, + &options_ret); + if (auth) + break; + mechs++; + } + if (!auth) { + clnt_pcreateerror("rpc_gss_seccreate"); + printf("Can't authenticate with server %s.\n", + hostname); + exit(1); + } + client->cl_auth = auth; + + for (svc = rpc_gss_svc_none; svc <= rpc_gss_svc_privacy; svc++) { + const char *svc_names[] = { + "rpc_gss_svc_default", + "rpc_gss_svc_none", + "rpc_gss_svc_integrity", + "rpc_gss_svc_privacy" + }; + int num; + + rpc_gss_set_defaults(auth, svc, NULL); + tv.tv_sec = 5; + tv.tv_usec = 0; + num = 42; + stat = CLNT_CALL(client, 1, + (xdrproc_t) xdr_int, (char *) &num, + (xdrproc_t) xdr_int, (char *) &num, tv); + if (stat == RPC_SUCCESS) { + printf("succeeded with %s\n", svc_names[svc]); + if (num != 142) + printf("unexpected reply %d\n", num); + } else { + clnt_perror(client, "call failed"); + } + } + AUTH_DESTROY(auth); + CLNT_DESTROY(client); +} + +static void +server_program_1(struct svc_req *rqstp, register SVCXPRT *transp) +{ + rpc_gss_rawcred_t *rcred; + rpc_gss_ucred_t *ucred; + int i, num; + + if (rqstp->rq_cred.oa_flavor != RPCSEC_GSS) { + svcerr_weakauth(transp); + return; + } + + if (!rpc_gss_getcred(rqstp, &rcred, &ucred, NULL)) { + svcerr_systemerr(transp); + return; + } + + printf("svc=%d, mech=%s, uid=%d, gid=%d, gids={", + rcred->service, rcred->mechanism, ucred->uid, ucred->gid); + for (i = 0; i < ucred->gidlen; i++) { + if (i > 0) printf(","); + printf("%d", ucred->gidlist[i]); + } + printf("}\n"); + + switch (rqstp->rq_proc) { + case 0: + if (!svc_getargs(transp, (xdrproc_t) xdr_void, 0)) { + svcerr_decode(transp); + goto out; + } + if (!svc_sendreply(transp, (xdrproc_t) xdr_void, 0)) { + svcerr_systemerr(transp); + } + goto out; + + case 1: + if (!svc_getargs(transp, (xdrproc_t) xdr_int, + (char *) &num)) { + svcerr_decode(transp); + goto out; + } + num += 100; + if (!svc_sendreply(transp, (xdrproc_t) xdr_int, + (char *) &num)) { + svcerr_systemerr(transp); + } + goto out; + + default: + svcerr_noproc(transp); + goto out; + } + +out: + return; +} + +#if 0 +static void +report_error(gss_OID mech, OM_uint32 maj, OM_uint32 min) +{ + OM_uint32 maj_stat, min_stat; + OM_uint32 message_context; + gss_buffer_desc buf; + + printf("major_stat=%d, minor_stat=%d\n", maj, min); + + message_context = 0; + do { + maj_stat = gss_display_status(&min_stat, maj, + GSS_C_GSS_CODE, GSS_C_NO_OID, &message_context, &buf); + printf("%.*s\n", (int)buf.length, (char *) buf.value); + gss_release_buffer(&min_stat, &buf); + } while (message_context); + if (mech) { + message_context = 0; + do { + maj_stat = gss_display_status(&min_stat, min, + GSS_C_MECH_CODE, mech, &message_context, &buf); + printf("%.*s\n", (int)buf.length, (char *) buf.value); + gss_release_buffer(&min_stat, &buf); + } while (message_context); + } + exit(1); +} +#endif + +static bool_t +server_new_context(__unused struct svc_req *req, + __unused gss_cred_id_t deleg, + __unused gss_ctx_id_t gss_context, + rpc_gss_lock_t *lock, + __unused void **cookie) +{ + rpc_gss_rawcred_t *rcred = lock->raw_cred; + + printf("new security context version=%d, mech=%s, qop=%s:\n", + rcred->version, rcred->mechanism, rcred->qop); + print_principal(rcred->client_principal); + + if (!server_acl) + return (TRUE); + + if (rcred->client_principal->len != server_acl->len + || memcmp(rcred->client_principal->name, server_acl->name, + server_acl->len)) { + return (FALSE); + } + + return (TRUE); +} + +static void +test_server(__unused int argc, __unused const char **argv) +{ + char hostname[128]; + char principal[128 + 5]; + const char **mechs; + static rpc_gss_callback_t cb; + + if (argc == 3) { + if (!rpc_gss_get_principal_name(&server_acl, argv[1], + argv[2], NULL, NULL)) { + printf("Can't create %s ACL entry for %s\n", + argv[1], argv[2]); + return; + } + } + + gethostname(hostname, sizeof(hostname));; + snprintf(principal, sizeof(principal), "host@%s", hostname); + + mechs = rpc_gss_get_mechanisms(); + while (*mechs) { + if (!rpc_gss_set_svc_name(principal, *mechs, GSS_C_INDEFINITE, + 123456, 1)) { + rpc_gss_error_t e; + + rpc_gss_get_error(&e); + printf("setting name for %s for %s failed: %d, %d\n", + principal, *mechs, + e.rpc_gss_error, e.system_error); + +#if 0 + gss_OID mech_oid; + gss_OID_set_desc oid_set; + gss_name_t name; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc namebuf; + gss_cred_id_t cred; + + rpc_gss_mech_to_oid(*mechs, &mech_oid); + oid_set.count = 1; + oid_set.elements = mech_oid; + + namebuf.value = principal; + namebuf.length = strlen(principal); + maj_stat = gss_import_name(&min_stat, &namebuf, + GSS_C_NT_HOSTBASED_SERVICE, &name); + if (maj_stat) { + printf("gss_import_name failed\n"); + report_error(mech_oid, maj_stat, min_stat); + } + maj_stat = gss_acquire_cred(&min_stat, name, + 0, &oid_set, GSS_C_ACCEPT, &cred, NULL, NULL); + if (maj_stat) { + printf("gss_acquire_cred failed\n"); + report_error(mech_oid, maj_stat, min_stat); + } +#endif + } + mechs++; + } + + cb.program = 123456; + cb.version = 1; + cb.callback = server_new_context; + rpc_gss_set_callback(&cb); + + svc_create(server_program_1, 123456, 1, 0); + svc_run(); +} + +static void +test_get_principal_name(int argc, const char **argv) +{ + const char *mechname, *name, *node, *domain; + rpc_gss_principal_t principal; + + if (argc < 3 || argc > 5) { + printf("usage: rpctest principal " + "[ [] ]\n"); + exit(1); + } + + mechname = argv[1]; + name = argv[2]; + node = NULL; + domain = NULL; + if (argc > 3) { + node = argv[3]; + if (argc > 4) + domain = argv[4]; + } + + if (rpc_gss_get_principal_name(&principal, mechname, name, + node, domain)) { + printf("succeeded:\n"); + print_principal(principal); + free(principal); + } else { + printf("failed\n"); + } +} + +int +main(int argc, const char **argv) +{ + + if (argc < 2) + usage(); + if (!strcmp(argv[1], "client")) + test_client(argc - 1, argv + 1); + else if (!strcmp(argv[1], "server")) + test_server(argc - 1, argv + 1); + else if (!strcmp(argv[1], "principal")) + test_get_principal_name(argc - 1, argv + 1); + else + usage(); + + return (0); +} diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 7b10edb..993eb8a 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -63,6 +63,7 @@ SUBDIR= ${_ac} \ getfmac \ getpmac \ gstat \ + ${_gssd} \ ifmcstat \ inetd \ iostat \ @@ -256,6 +257,10 @@ _fdwrite= fdwrite _freebsd-update= freebsd-update .endif +.if ${MK_GSSAPI} != no +_gssd= gssd +.endif + .if ${MK_INET6} != "no" _faithd= faithd _ip6addrctl= ip6addrctl diff --git a/usr.sbin/gssd/Makefile b/usr.sbin/gssd/Makefile new file mode 100644 index 0000000..ffdb5aa --- /dev/null +++ b/usr.sbin/gssd/Makefile @@ -0,0 +1,29 @@ +# $FreeBSD$ + +PROG= gssd +MAN= gssd.8 +SRCS= gssd.c gssd_svc.c gssd_xdr.c gssd_prot.c + +CFLAGS+= -I. +WARNS?= 1 + +DPADD= ${LIBGSSAPI} +LDADD= -lgssapi + +CLEANFILES= gssd_svc.c gssd.h + +RPCSRC= ${.CURDIR}/../../sys/kgssapi/gssd.x +RPCGEN= rpcgen -L -C -M + +gssd_svc.c: ${RPCSRC} gssd.h + ${RPCGEN} -m -o ${.TARGET} ${RPCSRC} + +gssd_xdr.c: ${RPCSRC} gssd.h + ${RPCGEN} -c -o ${.TARGET} ${RPCSRC} + +gssd.h: ${RPCSRC} + ${RPCGEN} -h -o ${.TARGET} ${RPCSRC} + +.PATH: ${.CURDIR}/../../sys/kgssapi + +.include diff --git a/usr.sbin/gssd/gssd.8 b/usr.sbin/gssd/gssd.8 new file mode 100644 index 0000000..20a9a96 --- /dev/null +++ b/usr.sbin/gssd/gssd.8 @@ -0,0 +1,70 @@ +.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/ +.\" Authors: Doug Rabson +.\" Developed with Red Inc: Alfred Perlstein +.\" +.\" 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$ +.\" +.\" Note: The date here should be updated whenever a non-trivial +.\" change is made to the manual page. +.Dd August 25, 2008 +.Dt GSSD 8 +.Os +.Sh NAME +.Nm gssd +.Nd "Generic Security Services Daemon" +.Sh SYNOPSIS +.Nm +.Op Fl d +.Sh DESCRIPTION +The +.Nm +program provides support for the kernel GSS-API implementation. +.Pp +The options are as follows: +.Bl -tag +.It Fl d +Run in debug mode. +In this mode, +.Nm +will not for when it starts. +.El +.Sh FILES +.Bl -tag -width ".Pa /etc/krb5.keytab" -compact +.It Pa /etc/krb5.keytab +Contains Kerberos service principals which may be used as credentials +by kernel GSS-API services. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr gssapi 3 +.Sh HISTORY +The +.Nm +manual page example first appeared in +.Fx 8.0 . +.Sh AUTHORS +This +manual page was written by +.An Doug Rabson Aq dfr@FreeBSD.org . diff --git a/usr.sbin/gssd/gssd.c b/usr.sbin/gssd/gssd.c new file mode 100644 index 0000000..ba2805b --- /dev/null +++ b/usr.sbin/gssd/gssd.c @@ -0,0 +1,610 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson + * Developed with Red Inc: Alfred Perlstein + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gssd.h" + +#ifndef _PATH_GSS_MECH +#define _PATH_GSS_MECH "/etc/gss/mech" +#endif +#ifndef _PATH_GSSDSOCK +#define _PATH_GSSDSOCK "/var/run/gssd.sock" +#endif + +struct gss_resource { + LIST_ENTRY(gss_resource) gr_link; + uint64_t gr_id; /* indentifier exported to kernel */ + void* gr_res; /* GSS-API resource pointer */ +}; +LIST_HEAD(gss_resource_list, gss_resource) gss_resources; +int gss_resource_count; +uint32_t gss_next_id; +uint32_t gss_start_time; +int debug_level; + +static void gssd_load_mech(void); + +extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp); +extern int gssd_syscall(char *path); + +int +main(int argc, char **argv) +{ + /* + * We provide an RPC service on a local-domain socket. The + * kernel's GSS-API code will pass what it can't handle + * directly to us. + */ + struct sockaddr_un sun; + int fd, oldmask, ch, debug; + SVCXPRT *xprt; + + debug = 0; + while ((ch = getopt(argc, argv, "d")) != -1) { + switch (ch) { + case 'd': + debug_level++; + break; + default: + fprintf(stderr, "usage: %s [-d]\n", argv[0]); + exit(1); + break; + } + } + + gssd_load_mech(); + + if (!debug_level) + daemon(0, 0); + + memset(&sun, 0, sizeof sun); + sun.sun_family = AF_LOCAL; + unlink(_PATH_GSSDSOCK); + strcpy(sun.sun_path, _PATH_GSSDSOCK); + sun.sun_len = SUN_LEN(&sun); + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (!fd) { + err(1, "Can't create local gssd socket"); + } + oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) { + err(1, "Can't bind local gssd socket"); + } + umask(oldmask); + if (listen(fd, SOMAXCONN) < 0) { + err(1, "Can't listen on local gssd socket"); + } + xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE); + if (!xprt) { + err(1, "Can't create transport for local gssd socket"); + } + if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) { + err(1, "Can't register service for local gssd socket"); + } + + LIST_INIT(&gss_resources); + gss_next_id = 1; + gss_start_time = time(0); + + gssd_syscall(_PATH_GSSDSOCK); + svc_run(); + + return (0); +} + +static void +gssd_load_mech(void) +{ + FILE *fp; + char buf[256]; + char *p; + char *name, *oid, *lib, *kobj; + + fp = fopen(_PATH_GSS_MECH, "r"); + if (!fp) + return; + + while (fgets(buf, sizeof(buf), fp)) { + if (*buf == '#') + continue; + p = buf; + name = strsep(&p, "\t\n "); + if (p) while (isspace(*p)) p++; + oid = strsep(&p, "\t\n "); + if (p) while (isspace(*p)) p++; + lib = strsep(&p, "\t\n "); + if (p) while (isspace(*p)) p++; + kobj = strsep(&p, "\t\n "); + if (!name || !oid || !lib || !kobj) + continue; + + if (strcmp(kobj, "-")) { + /* + * Attempt to load the kernel module if its + * not already present. + */ + if (modfind(kobj) < 0) { + if (kldload(kobj) < 0) { + fprintf(stderr, + "%s: can't find or load kernel module %s for %s\n", + getprogname(), kobj, name); + } + } + } + } + fclose(fp); +} + +static void * +gssd_find_resource(uint64_t id) +{ + struct gss_resource *gr; + + if (!id) + return (NULL); + + LIST_FOREACH(gr, &gss_resources, gr_link) + if (gr->gr_id == id) + return (gr->gr_res); + + return (NULL); +} + +static uint64_t +gssd_make_resource(void *res) +{ + struct gss_resource *gr; + + if (!res) + return (0); + + gr = malloc(sizeof(struct gss_resource)); + if (!gr) + return (0); + gr->gr_id = (gss_next_id++) + ((uint64_t) gss_start_time << 32); + gr->gr_res = res; + LIST_INSERT_HEAD(&gss_resources, gr, gr_link); + gss_resource_count++; + if (debug_level > 1) + printf("%d resources allocated\n", gss_resource_count); + + return (gr->gr_id); +} + +static void +gssd_delete_resource(uint64_t id) +{ + struct gss_resource *gr; + + LIST_FOREACH(gr, &gss_resources, gr_link) { + if (gr->gr_id == id) { + LIST_REMOVE(gr, gr_link); + free(gr); + gss_resource_count--; + if (debug_level > 1) + printf("%d resources allocated\n", + gss_resource_count); + return; + } + } +} + +bool_t +gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp) +{ + + return (TRUE); +} + +bool_t +gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp) +{ + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_name_t name = GSS_C_NO_NAME; + char ccname[strlen("FILE:/tmp/krb5cc_") + 6 + 1]; + + snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", + (int) argp->uid); + setenv("KRB5CCNAME", ccname, TRUE); + + memset(result, 0, sizeof(*result)); + if (argp->cred) { + cred = gssd_find_resource(argp->cred); + if (!cred) { + result->major_status = GSS_S_CREDENTIALS_EXPIRED; + return (TRUE); + } + } + if (argp->ctx) { + ctx = gssd_find_resource(argp->ctx); + if (!ctx) { + result->major_status = GSS_S_CONTEXT_EXPIRED; + return (TRUE); + } + } + if (argp->name) { + name = gssd_find_resource(argp->name); + if (!name) { + result->major_status = GSS_S_BAD_NAME; + return (TRUE); + } + } + + memset(result, 0, sizeof(*result)); + result->major_status = gss_init_sec_context(&result->minor_status, + cred, &ctx, name, argp->mech_type, + argp->req_flags, argp->time_req, argp->input_chan_bindings, + &argp->input_token, &result->actual_mech_type, + &result->output_token, &result->ret_flags, &result->time_rec); + + if (result->major_status == GSS_S_COMPLETE + || result->major_status == GSS_S_CONTINUE_NEEDED) { + if (argp->ctx) + result->ctx = argp->ctx; + else + result->ctx = gssd_make_resource(ctx); + } + + return (TRUE); +} + +bool_t +gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp) +{ + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + gss_name_t src_name; + gss_cred_id_t delegated_cred_handle; + + memset(result, 0, sizeof(*result)); + if (argp->ctx) { + ctx = gssd_find_resource(argp->ctx); + if (!ctx) { + result->major_status = GSS_S_CONTEXT_EXPIRED; + return (TRUE); + } + } + if (argp->cred) { + cred = gssd_find_resource(argp->cred); + if (!cred) { + result->major_status = GSS_S_CREDENTIALS_EXPIRED; + return (TRUE); + } + } + + memset(result, 0, sizeof(*result)); + result->major_status = gss_accept_sec_context(&result->minor_status, + &ctx, cred, &argp->input_token, argp->input_chan_bindings, + &src_name, &result->mech_type, &result->output_token, + &result->ret_flags, &result->time_rec, + &delegated_cred_handle); + + if (result->major_status == GSS_S_COMPLETE + || result->major_status == GSS_S_CONTINUE_NEEDED) { + if (argp->ctx) + result->ctx = argp->ctx; + else + result->ctx = gssd_make_resource(ctx); + result->src_name = gssd_make_resource(src_name); + result->delegated_cred_handle = + gssd_make_resource(delegated_cred_handle); + } + + return (TRUE); +} + +bool_t +gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp) +{ + gss_ctx_id_t ctx = gssd_find_resource(argp->ctx); + + if (ctx) { + result->major_status = gss_delete_sec_context( + &result->minor_status, &ctx, &result->output_token); + gssd_delete_resource(argp->ctx); + } else { + result->major_status = GSS_S_COMPLETE; + result->minor_status = 0; + } + + return (TRUE); +} + +bool_t +gssd_export_sec_context_1_svc(export_sec_context_args *argp, export_sec_context_res *result, struct svc_req *rqstp) +{ + gss_ctx_id_t ctx = gssd_find_resource(argp->ctx); + + if (ctx) { + result->major_status = gss_export_sec_context( + &result->minor_status, &ctx, + &result->interprocess_token); + result->format = KGSS_HEIMDAL_1_1; + gssd_delete_resource(argp->ctx); + } else { + result->major_status = GSS_S_FAILURE; + result->minor_status = 0; + result->interprocess_token.length = 0; + result->interprocess_token.value = NULL; + } + + return (TRUE); +} + +bool_t +gssd_import_name_1_svc(import_name_args *argp, import_name_res *result, struct svc_req *rqstp) +{ + gss_name_t name; + + result->major_status = gss_import_name(&result->minor_status, + &argp->input_name_buffer, argp->input_name_type, &name); + + if (result->major_status == GSS_S_COMPLETE) + result->output_name = gssd_make_resource(name); + else + result->output_name = 0; + + return (TRUE); +} + +bool_t +gssd_canonicalize_name_1_svc(canonicalize_name_args *argp, canonicalize_name_res *result, struct svc_req *rqstp) +{ + gss_name_t name = gssd_find_resource(argp->input_name); + gss_name_t output_name; + + memset(result, 0, sizeof(*result)); + if (!name) { + result->major_status = GSS_S_BAD_NAME; + return (TRUE); + } + + result->major_status = gss_canonicalize_name(&result->minor_status, + name, argp->mech_type, &output_name); + + if (result->major_status == GSS_S_COMPLETE) + result->output_name = gssd_make_resource(output_name); + else + result->output_name = 0; + + return (TRUE); +} + +bool_t +gssd_export_name_1_svc(export_name_args *argp, export_name_res *result, struct svc_req *rqstp) +{ + gss_name_t name = gssd_find_resource(argp->input_name); + + memset(result, 0, sizeof(*result)); + if (!name) { + result->major_status = GSS_S_BAD_NAME; + return (TRUE); + } + + result->major_status = gss_export_name(&result->minor_status, + name, &result->exported_name); + + return (TRUE); +} + +bool_t +gssd_release_name_1_svc(release_name_args *argp, release_name_res *result, struct svc_req *rqstp) +{ + gss_name_t name = gssd_find_resource(argp->input_name); + + if (name) { + result->major_status = gss_release_name(&result->minor_status, + &name); + gssd_delete_resource(argp->input_name); + } else { + result->major_status = GSS_S_COMPLETE; + result->minor_status = 0; + } + + return (TRUE); +} + +bool_t +gssd_pname_to_uid_1_svc(pname_to_uid_args *argp, pname_to_uid_res *result, struct svc_req *rqstp) +{ + gss_name_t name = gssd_find_resource(argp->pname); + uid_t uid; + char buf[128]; + struct passwd pwd, *pw; + + memset(result, 0, sizeof(*result)); + if (name) { + result->major_status = + gss_pname_to_uid(&result->minor_status, + name, argp->mech, &uid); + if (result->major_status == GSS_S_COMPLETE) { + result->uid = uid; + getpwuid_r(uid, &pwd, buf, sizeof(buf), &pw); + if (pw) { + int len = NGRPS; + int groups[NGRPS]; + result->gid = pw->pw_gid; + getgrouplist(pw->pw_name, pw->pw_gid, + groups, &len); + result->gidlist.gidlist_len = len; + result->gidlist.gidlist_val = + mem_alloc(len * sizeof(int)); + memcpy(result->gidlist.gidlist_val, groups, + len * sizeof(int)); + } else { + result->gid = 65534; + result->gidlist.gidlist_len = 0; + result->gidlist.gidlist_val = NULL; + } + } + } else { + result->major_status = GSS_S_BAD_NAME; + result->minor_status = 0; + } + + return (TRUE); +} + +bool_t +gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struct svc_req *rqstp) +{ + gss_name_t desired_name = GSS_C_NO_NAME; + gss_cred_id_t cred; + char ccname[strlen("FILE:/tmp/krb5cc_") + 6 + 1]; + + snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", + (int) argp->uid); + setenv("KRB5CCNAME", ccname, TRUE); + + memset(result, 0, sizeof(*result)); + if (argp->desired_name) { + desired_name = gssd_find_resource(argp->desired_name); + if (!desired_name) { + result->major_status = GSS_S_BAD_NAME; + return (TRUE); + } + } + + result->major_status = gss_acquire_cred(&result->minor_status, + desired_name, argp->time_req, argp->desired_mechs, + argp->cred_usage, &cred, &result->actual_mechs, &result->time_rec); + + if (result->major_status == GSS_S_COMPLETE) + result->output_cred = gssd_make_resource(cred); + else + result->output_cred = 0; + + return (TRUE); +} + +bool_t +gssd_set_cred_option_1_svc(set_cred_option_args *argp, set_cred_option_res *result, struct svc_req *rqstp) +{ + gss_cred_id_t cred = gssd_find_resource(argp->cred); + + memset(result, 0, sizeof(*result)); + if (!cred) { + result->major_status = GSS_S_CREDENTIALS_EXPIRED; + return (TRUE); + } + + result->major_status = gss_set_cred_option(&result->minor_status, + &cred, argp->option_name, &argp->option_value); + + return (TRUE); +} + +bool_t +gssd_release_cred_1_svc(release_cred_args *argp, release_cred_res *result, struct svc_req *rqstp) +{ + gss_cred_id_t cred = gssd_find_resource(argp->cred); + + if (cred) { + result->major_status = gss_release_cred(&result->minor_status, + &cred); + gssd_delete_resource(argp->cred); + } else { + result->major_status = GSS_S_COMPLETE; + result->minor_status = 0; + } + + return (TRUE); +} + +bool_t +gssd_display_status_1_svc(display_status_args *argp, display_status_res *result, struct svc_req *rqstp) +{ + + result->message_context = argp->message_context; + result->major_status = gss_display_status(&result->minor_status, + argp->status_value, argp->status_type, argp->mech_type, + &result->message_context, &result->status_string); + + return (TRUE); +} + +int +gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) +{ + /* + * We don't use XDR to free the results - anything which was + * allocated came from GSS-API. We use xdr_result to figure + * out what to do. + */ + OM_uint32 junk; + + if (xdr_result == (xdrproc_t) xdr_init_sec_context_res) { + init_sec_context_res *p = (init_sec_context_res *) result; + gss_release_buffer(&junk, &p->output_token); + } else if (xdr_result == (xdrproc_t) xdr_accept_sec_context_res) { + accept_sec_context_res *p = (accept_sec_context_res *) result; + gss_release_buffer(&junk, &p->output_token); + } else if (xdr_result == (xdrproc_t) xdr_delete_sec_context_res) { + delete_sec_context_res *p = (delete_sec_context_res *) result; + gss_release_buffer(&junk, &p->output_token); + } else if (xdr_result == (xdrproc_t) xdr_export_sec_context_res) { + export_sec_context_res *p = (export_sec_context_res *) result; + if (p->interprocess_token.length) + memset(p->interprocess_token.value, 0, + p->interprocess_token.length); + gss_release_buffer(&junk, &p->interprocess_token); + } else if (xdr_result == (xdrproc_t) xdr_export_name_res) { + export_name_res *p = (export_name_res *) result; + gss_release_buffer(&junk, &p->exported_name); + } else if (xdr_result == (xdrproc_t) xdr_acquire_cred_res) { + acquire_cred_res *p = (acquire_cred_res *) result; + gss_release_oid_set(&junk, &p->actual_mechs); + } else if (xdr_result == (xdrproc_t) xdr_pname_to_uid_res) { + pname_to_uid_res *p = (pname_to_uid_res *) result; + if (p->gidlist.gidlist_val) + free(p->gidlist.gidlist_val); + } else if (xdr_result == (xdrproc_t) xdr_display_status_res) { + display_status_res *p = (display_status_res *) result; + gss_release_buffer(&junk, &p->status_string); + } + + return (TRUE); +} diff --git a/usr.sbin/mountd/exports.5 b/usr.sbin/mountd/exports.5 index f04b6b1..dca4f2d 100644 --- a/usr.sbin/mountd/exports.5 +++ b/usr.sbin/mountd/exports.5 @@ -149,6 +149,17 @@ option is given, all users (including root) will be mapped to that credential in place of their own. .Pp +.Sm off +.Fl sec Li = Sy flavor1:flavor2... +.Sm on +specifies a colon separated list of acceptable security flavors to be +used for remote access. +Supported security flavors are sys, krb5, krb5i and krb5p. +If multiple flavors are listed, they should be ordered with the most +preferred flavor first. +If this option is not present, +the default security flavor list of just sys is used. +.Pp The .Fl ro option specifies that the file system should be exported read-only @@ -305,6 +316,8 @@ the default remote mount-point file /u2 -maproot=root friends /u2 -alldirs -network cis-net -mask cis-mask /cdrom -alldirs,quiet,ro -network 192.168.33.0 -mask 255.255.255.0 +/private -sec=krb5i +/secret -sec=krb5p .Ed .Pp Given that @@ -411,6 +424,15 @@ While there is no CD-ROM medium mounted under it would export the (normally empty) directory .Pa /cdrom of the root file system instead. +.Pp +The file system rooted at +.Pa /private +will be exported using Kerberos 5 authentication and will require +integrity protected messages for all accesses. +The file system rooted at +.Pa /secret +will also be exported using Kerberos 5 authentication and all messages +used to access it will be encrypted. .Sh SEE ALSO .Xr netgroup 5 , .Xr mountd 8 , diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c index fbcb4ec..82ab1b8 100644 --- a/usr.sbin/mountd/mountd.c +++ b/usr.sbin/mountd/mountd.c @@ -113,6 +113,8 @@ struct exportlist { fsid_t ex_fs; char *ex_fsdir; char *ex_indexfile; + int ex_numsecflavors; + int ex_secflavors[MAXSECFLAVORS]; }; /* ex_flag bits */ #define EX_LINKED 0x1 @@ -150,6 +152,8 @@ struct fhreturn { int fhr_flag; int fhr_vers; nfsfh_t fhr_fh; + int fhr_numsecflavors; + int *fhr_secflavors; }; /* Global defs */ @@ -240,6 +244,7 @@ struct pidfh *pfh = NULL; #define OP_HAVEMASK 0x80 /* A mask was specified or inferred. */ #define OP_QUIET 0x100 #define OP_MASKLEN 0x200 +#define OP_SEC 0x400 #ifdef DEBUG int debug = 1; @@ -817,6 +822,8 @@ mntsrv(rqstp, transp) sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; } + fhr.fhr_numsecflavors = ep->ex_numsecflavors; + fhr.fhr_secflavors = ep->ex_secflavors; if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (caddr_t)&fhr)) syslog(LOG_ERR, "can't send reply"); @@ -934,6 +941,7 @@ xdr_fhs(xdrsp, cp) { struct fhreturn *fhrp = (struct fhreturn *)cp; u_long ok = 0, len, auth; + int i; if (!xdr_long(xdrsp, &ok)) return (0); @@ -946,11 +954,20 @@ xdr_fhs(xdrsp, cp) return (0); if (!xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, len)) return (0); - auth = RPCAUTH_UNIX; - len = 1; - if (!xdr_long(xdrsp, &len)) - return (0); - return (xdr_long(xdrsp, &auth)); + if (fhrp->fhr_numsecflavors) { + if (!xdr_int(xdrsp, &fhrp->fhr_numsecflavors)) + return (0); + for (i = 0; i < fhrp->fhr_numsecflavors; i++) + if (!xdr_int(xdrsp, &fhrp->fhr_secflavors[i])) + return (0); + return (1); + } else { + auth = AUTH_SYS; + len = 1; + if (!xdr_long(xdrsp, &len)) + return (0); + return (xdr_long(xdrsp, &auth)); + } }; return (0); } @@ -1765,6 +1782,57 @@ free_dir(dp) } /* + * Parse a colon separated list of security flavors + */ +int +parsesec(seclist, ep) + char *seclist; + struct exportlist *ep; +{ + char *cp, savedc; + int flavor; + + ep->ex_numsecflavors = 0; + for (;;) { + cp = strchr(seclist, ':'); + if (cp) { + savedc = *cp; + *cp = '\0'; + } + + if (!strcmp(seclist, "sys")) + flavor = AUTH_SYS; + else if (!strcmp(seclist, "krb5")) + flavor = RPCSEC_GSS_KRB5; + else if (!strcmp(seclist, "krb5i")) + flavor = RPCSEC_GSS_KRB5I; + else if (!strcmp(seclist, "krb5p")) + flavor = RPCSEC_GSS_KRB5P; + else { + if (cp) + *cp = savedc; + syslog(LOG_ERR, "bad sec flavor: %s", seclist); + return (1); + } + if (ep->ex_numsecflavors == MAXSECFLAVORS) { + if (cp) + *cp = savedc; + syslog(LOG_ERR, "too many sec flavors: %s", seclist); + return (1); + } + ep->ex_secflavors[ep->ex_numsecflavors] = flavor; + ep->ex_numsecflavors++; + if (cp) { + *cp = savedc; + seclist = cp + 1; + } else { + break; + } + } + return (0); +} + +/* * Parse the option string and update fields. * Option arguments may either be -