#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.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 *, XDR *);
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 *, struct opaque_auth *);
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
};
struct rpc_gss_data {
rpc_gss_options_req_t gd_options;
enum rpcsec_gss_state gd_state;
gss_buffer_desc gd_verf;
CLIENT *gd_clnt;
gss_name_t gd_name;
gss_OID gd_mech;
gss_qop_t gd_qop;
gss_ctx_id_t gd_ctx;
struct rpc_gss_cred gd_cred;
u_int gd_win;
};
#define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private)
static struct timeval AUTH_TIMEOUT = { 25, 0 };
AUTH *
rpc_gss_seccreate(CLIENT *clnt, 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)
{
AUTH *auth, *save_auth;
rpc_gss_options_ret_t options;
gss_OID oid;
u_int qop_num;
struct rpc_gss_data *gd;
OM_uint32 maj_stat = 0, min_stat = 0;
gss_buffer_desc principal_desc;
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;
}
if (!options_ret)
options_ret = &options;
if (service == rpc_gss_svc_default)
service = rpc_gss_svc_integrity;
memset(options_ret, 0, sizeof(*options_ret));
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;
free(auth);
return (NULL);
}
auth->ah_ops = &rpc_gss_ops;
auth->ah_private = (caddr_t) gd;
auth->ah_cred.oa_flavor = RPCSEC_GSS;
principal_desc.value = (void *)(intptr_t) principal;
principal_desc.length = strlen(principal);
maj_stat = gss_import_name(&min_stat, &principal_desc,
GSS_C_NT_HOSTBASED_SERVICE, &gd->gd_name);
if (maj_stat != GSS_S_COMPLETE) {
options_ret->major_status = maj_stat;
options_ret->minor_status = min_stat;
goto bad;
}
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;
}
gd->gd_clnt = clnt;
gd->gd_ctx = GSS_C_NO_CONTEXT;
gd->gd_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;
save_auth = clnt->cl_auth;
clnt->cl_auth = auth;
if (!rpc_gss_init(auth, options_ret)) {
clnt->cl_auth = save_auth;
goto bad;
}
clnt->cl_auth = save_auth;
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_nextverf(__unused AUTH *auth)
{
}
static bool_t
rpc_gss_marshal(__unused AUTH *auth, __unused XDR *xdrs)
{
return (FALSE);
}
static bool_t
rpc_gss_validate(AUTH *auth, struct opaque_auth *verf)
{
struct rpc_gss_data *gd;
gss_qop_t qop_state;
uint32_t num;
gss_buffer_desc signbuf, checksum;
OM_uint32 maj_stat, min_stat;
log_debug("in rpc_gss_validate()");
gd = AUTH_PRIVATE(auth);
if (gd->gd_state == RPCSEC_GSS_CONTEXT) {
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) {
fprintf(stderr, "gss_validate: out of memory\n");
_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
return (FALSE);
}
memcpy(gd->gd_verf.value, verf->oa_base, verf->oa_length);
gd->gd_verf.length = verf->oa_length;
return (TRUE);
}
num = htonl(gd->gd_cred.gc_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) {
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);
return (FALSE);
}
return (TRUE);
}
static bool_t
rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
{
struct rpc_gss_data *gd;
struct rpc_gss_init_res gr;
gss_buffer_desc *recv_tokenp, recv_token, send_token;
OM_uint32 maj_stat, min_stat, call_stat;
const char *mech;
log_debug("in rpc_gss_refresh()");
gd = AUTH_PRIVATE(auth);
if (gd->gd_state != RPCSEC_GSS_START)
return (TRUE);
gd->gd_state = RPCSEC_GSS_CONTEXT;
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));
recv_tokenp = GSS_C_NO_BUFFER;
for (;;) {
maj_stat = gss_init_sec_context(&min_stat,
gd->gd_options.my_cred,
&gd->gd_ctx,
gd->gd_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,
&send_token,
&options_ret->ret_flags,
&options_ret->time_req);
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 (maj_stat != GSS_S_COMPLETE &&
maj_stat != GSS_S_CONTINUE_NEEDED) {
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));
call_stat = clnt_call(gd->gd_clnt, 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) {
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;
}
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;
}
if (gr.gr_token.length != 0) {
recv_token = gr.gr_token;
recv_tokenp = &recv_token;
}
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;
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) {
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;
return (FALSE);
}
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;
if (rpc_gss_oid_to_mech(gd->gd_mech, &mech)) {
strlcpy(options_ret->actual_mechanism,
mech,
sizeof(options_ret->actual_mechanism));
}
gd->gd_state = RPCSEC_GSS_ESTABLISHED;
gd->gd_cred.gc_proc = RPCSEC_GSS_DATA;
gd->gd_cred.gc_seq = 0;
gd->gd_win = gr.gr_win;
break;
}
}
xdr_free((xdrproc_t) xdr_gss_buffer_desc,
(char *) &gd->gd_verf);
if (gd->gd_cred.gc_proc != RPCSEC_GSS_DATA) {
rpc_createerr.cf_stat = RPC_AUTHERROR;
_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
return (FALSE);
}
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 (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;
OM_uint32 min_stat;
log_debug("in rpc_gss_destroy_context()");
gd = AUTH_PRIVATE(auth);
if (gd->gd_state == RPCSEC_GSS_ESTABLISHED && send_destroy) {
gd->gd_cred.gc_proc = RPCSEC_GSS_DESTROY;
clnt_call(gd->gd_clnt, NULLPROC,
(xdrproc_t)xdr_void, NULL,
(xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT);
}
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);
gd->gd_state = RPCSEC_GSS_START;
}
static void
rpc_gss_destroy(AUTH *auth)
{
struct rpc_gss_data *gd;
OM_uint32 min_stat;
log_debug("in rpc_gss_destroy()");
gd = AUTH_PRIVATE(auth);
rpc_gss_destroy_context(auth, TRUE);
if (gd->gd_name != GSS_C_NO_NAME)
gss_release_name(&min_stat, &gd->gd_name);
if (gd->gd_verf.value)
xdr_free((xdrproc_t) xdr_gss_buffer_desc,
(char *) &gd->gd_verf);
mem_free(gd, sizeof(*gd));
mem_free(auth, sizeof(*auth));
}
bool_t
__rpc_gss_wrap(AUTH *auth, void *header, size_t headerlen,
XDR* xdrs, xdrproc_t xdr_args, void *args_ptr)
{
XDR tmpxdrs;
char credbuf[MAX_AUTH_BYTES];
char tmpheader[MAX_AUTH_BYTES];
struct opaque_auth creds, verf;
struct rpc_gss_data *gd;
gss_buffer_desc rpcbuf, checksum;
OM_uint32 maj_stat, min_stat;
bool_t xdr_stat;
log_debug("in rpc_gss_wrap()");
gd = AUTH_PRIVATE(auth);
if (gd->gd_state == RPCSEC_GSS_ESTABLISHED)
gd->gd_cred.gc_seq++;
xdrmem_create(&tmpxdrs, credbuf, sizeof(credbuf), XDR_ENCODE);
if (!xdr_rpc_gss_cred(&tmpxdrs, &gd->gd_cred)) {
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);
xdrmem_create(&tmpxdrs, tmpheader, sizeof(tmpheader), XDR_ENCODE);
if (!XDR_PUTBYTES(&tmpxdrs, header, headerlen) ||
!xdr_opaque_auth(&tmpxdrs, &creds)) {
XDR_DESTROY(&tmpxdrs);
_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
return (FALSE);
}
headerlen = XDR_GETPOS(&tmpxdrs);
XDR_DESTROY(&tmpxdrs);
if (!XDR_PUTBYTES(xdrs, tmpheader, headerlen)) {
_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
return (FALSE);
}
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);
}
} else {
rpcbuf.length = headerlen;
rpcbuf.value = tmpheader;
maj_stat = gss_get_mic(&min_stat, gd->gd_ctx, gd->gd_qop,
&rpcbuf, &checksum);
if (maj_stat != GSS_S_COMPLETE) {
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) {
return (xdr_args(xdrs, args_ptr));
}
return (xdr_rpc_gss_wrap_data(xdrs, xdr_args, args_ptr,
gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
gd->gd_cred.gc_seq));
}
bool_t
__rpc_gss_unwrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, void *xdr_ptr)
{
struct rpc_gss_data *gd;
log_debug("in rpc_gss_unwrap()");
gd = AUTH_PRIVATE(auth);
if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
gd->gd_cred.gc_svc == rpc_gss_svc_none) {
return (xdr_func(xdrs, xdr_ptr));
}
return (xdr_rpc_gss_unwrap_data(xdrs, xdr_func, xdr_ptr,
gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
gd->gd_cred.gc_seq));
}
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 {
log_status("gss_wrap_size_limit", gd->gd_mech,
maj_stat, min_stat);
return (0);
}
}