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