#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <syslog.h>
#include <synch.h>
#include <rpc/rpc.h>
#include <nfs/nfs_sec.h>
#include <rpc/rpcsec_gss.h>
#ifdef WNFS_SEC_NEGO
#include "webnfs.h"
#endif
#define GETBYNAME 1
#define GETBYNUM 2
struct sc_data {
char *string;
int value;
};
static struct sc_data sc_service[] = {
"default", rpc_gss_svc_default,
"-", rpc_gss_svc_none,
"none", rpc_gss_svc_none,
"integrity", rpc_gss_svc_integrity,
"privacy", rpc_gss_svc_privacy,
NULL, SC_FAILURE
};
static mutex_t matching_lock = DEFAULTMUTEX;
static char *gettoken(char *, int);
extern int atoi(const char *str);
extern bool_t rpc_gss_get_principal_name(rpc_gss_principal_t *, char *,
char *, char *, char *);
extern bool_t rpc_gss_mech_to_oid(char *, rpc_gss_OID *);
extern bool_t rpc_gss_qop_to_num(char *, char *, uint_t *);
static int
blank(char *cp)
{
while (*cp && isspace(*cp)) {
cp++;
}
return (*cp == '\0');
}
static int
comment(char *cp)
{
while (*cp && isspace(*cp)) {
cp++;
}
return (*cp == '#');
}
static unsigned long
getvalue(char *cp, struct sc_data sc_data[])
{
int i;
for (i = 0; sc_data[i].string; i++) {
if (strcmp(sc_data[i].string, cp) == 0) {
break;
}
}
return (sc_data[i].value);
}
static void
shift1left(char *p)
{
for (; *p; p++)
*p = *(p + 1);
}
static char *
gettoken(char *cp, int skip)
{
static char *savep;
register char *p;
register char *retp;
p = (cp == NULL)? savep: cp;
if (p == 0) {
return (NULL);
}
while (isspace(*p))
p++;
if (*p == '\0') {
return (NULL);
}
retp = p;
while (*p) {
if (isspace(*p)) {
if (skip == TRUE) {
shift1left(p);
continue;
} else
break;
}
if (*p == '\\' && *(p + 1) != '\n' && isspace(*(p + 1))) {
shift1left(p);
}
p++;
}
if (*p == '\0') {
savep = 0;
} else {
*p = '\0';
savep = ++p;
}
return (retp);
}
static bool_t
matchname(char *line, char *name, seconfig_t *secp)
{
char *tok1, *tok2;
char *secname, *gss_mech, *gss_qop;
if ((secname = gettoken(line, FALSE)) == NULL) {
return (FALSE);
}
if (strcmp(secname, name) != 0) {
return (FALSE);
}
tok1 = tok2 = NULL;
if (((tok1 = gettoken(NULL, FALSE)) == NULL) ||
((gss_mech = gettoken(NULL, FALSE)) == NULL) ||
((gss_qop = gettoken(NULL, FALSE)) == NULL) ||
((tok2 = gettoken(NULL, FALSE)) == NULL) ||
((secp->sc_service = getvalue(tok2, sc_service))
== SC_FAILURE)) {
return (FALSE);
}
secp->sc_nfsnum = atoi(tok1);
(void) strcpy(secp->sc_name, secname);
(void) strcpy(secp->sc_gss_mech, gss_mech);
secp->sc_gss_mech_type = NULL;
if (secp->sc_gss_mech[0] != '-') {
if (!rpc_gss_mech_to_oid(gss_mech, &secp->sc_gss_mech_type) ||
!rpc_gss_qop_to_num(gss_qop, gss_mech, &secp->sc_qop)) {
return (FALSE);
}
}
return (TRUE);
}
static bool_t
matchnum(char *line, int num, seconfig_t *secp)
{
char *tok1, *tok2;
char *secname, *gss_mech, *gss_qop;
if ((secname = gettoken(line, FALSE)) == NULL) {
return (FALSE);
}
tok1 = tok2 = NULL;
if ((tok1 = gettoken(NULL, FALSE)) == NULL) {
return (FALSE);
}
if ((secp->sc_nfsnum = atoi(tok1)) != num) {
return (FALSE);
}
if (((gss_mech = gettoken(NULL, FALSE)) == NULL) ||
((gss_qop = gettoken(NULL, FALSE)) == NULL) ||
((tok2 = gettoken(NULL, FALSE)) == NULL) ||
((secp->sc_service = getvalue(tok2, sc_service))
== SC_FAILURE)) {
return (FALSE);
}
(void) strcpy(secp->sc_name, secname);
(void) strcpy(secp->sc_gss_mech, gss_mech);
if (secp->sc_gss_mech[0] != '-') {
if (!rpc_gss_mech_to_oid(gss_mech, &secp->sc_gss_mech_type) ||
!rpc_gss_qop_to_num(gss_qop, gss_mech, &secp->sc_qop)) {
return (FALSE);
}
}
return (TRUE);
}
static void
get_rpcnum(seconfig_t *secp)
{
if (secp->sc_gss_mech[0] != '-') {
secp->sc_rpcnum = RPCSEC_GSS;
} else {
secp->sc_rpcnum = secp->sc_nfsnum;
}
}
static int
parsehostname(char *hostname, char *inst, char *realm)
{
char *h, *r;
if (!hostname)
return (0);
h = (char *)strdup(hostname);
if (!h) {
syslog(LOG_ERR, "parsehostname: no memory\n");
return (0);
}
r = (char *)strchr(h, '@');
if (!r) {
(void) strcpy(inst, h);
(void) strcpy(realm, "");
} else {
*r++ = '\0';
(void) strcpy(inst, h);
(void) strcpy(realm, r);
}
free(h);
return (1);
}
char *
nfs_get_qop_name(seconfig_t *entryp)
{
char *tok;
char *secname, *gss_qop = NULL;
char line[BUFSIZ];
FILE *fp;
(void) mutex_lock(&matching_lock);
if ((fp = fopen(NFSSEC_CONF, "r")) == NULL) {
(void) mutex_unlock(&matching_lock);
return (NULL);
}
while (fgets(line, BUFSIZ, fp)) {
if (!(blank(line) || comment(line))) {
if ((secname = gettoken(line, FALSE)) == NULL) {
continue;
}
if (strcmp(secname, entryp->sc_name) == 0) {
tok = NULL;
if ((tok = gettoken(NULL, FALSE)) == NULL) {
goto err;
}
if (atoi(tok) != entryp->sc_nfsnum)
goto err;
if ((gettoken(NULL, FALSE) == NULL) ||
((gss_qop = gettoken(NULL, FALSE))
== NULL)) {
goto err;
}
break;
}
}
}
err:
(void) fclose(fp);
(void) mutex_unlock(&matching_lock);
return (gss_qop);
}
AUTH *
nfs_create_ah(CLIENT *cl, char *hostname, seconfig_t *nfs_sec)
{
char netname[MAXNETNAMELEN+1];
char svc_name[MAXNETNAMELEN+1];
char *gss_qop;
static int window = 60;
if (nfs_sec == NULL)
goto err;
switch (nfs_sec->sc_rpcnum) {
case AUTH_UNIX:
case AUTH_NONE:
return (NULL);
case AUTH_DES:
if (!host2netname(netname, hostname, NULL))
goto err;
return (authdes_seccreate(netname, window, hostname,
NULL));
case RPCSEC_GSS:
if (cl == NULL)
goto err;
if (nfs_sec->sc_gss_mech_type == NULL) {
syslog(LOG_ERR,
"nfs_create_ah: need mechanism information\n");
goto err;
}
(void) sprintf(svc_name, "nfs@%s", hostname);
gss_qop = nfs_get_qop_name(nfs_sec);
if (gss_qop == NULL)
goto err;
return (rpc_gss_seccreate(cl, svc_name,
nfs_sec->sc_gss_mech, nfs_sec->sc_service, gss_qop,
NULL, NULL));
default:
syslog(LOG_ERR, "nfs_create_ah: unknown flavor\n");
return (NULL);
}
err:
syslog(LOG_ERR, "nfs_create_ah: failed to make auth handle\n");
return (NULL);
}
#ifdef WNFS_SEC_NEGO
enum snego_stat
nfs_sec_nego(rpcprog_t vers, CLIENT *clnt, char *fspath, struct snego_t *snego)
{
enum clnt_stat rpc_stat;
static int MAX_V2_CNT = (WNL_FHSIZE/sizeof (int)) - 1;
static int MAX_V3_CNT = (WNL3_FHSIZE/sizeof (int)) - 1;
static struct timeval TIMEOUT = { 25, 0 };
int status;
if (clnt == NULL || fspath == NULL || snego == NULL)
return (SNEGO_FAILURE);
if (vers == WNL_V2) {
wnl_diropargs arg;
wnl_diropres clnt_res;
memset((char *)&arg.dir, 0, sizeof (wnl_fh));
arg.name = fspath;
memset((char *)&clnt_res, 0, sizeof (clnt_res));
rpc_stat = clnt_call(clnt, WNLPROC_LOOKUP,
(xdrproc_t)xdr_wnl_diropargs, (caddr_t)&arg,
(xdrproc_t)xdr_wnl_diropres, (caddr_t)&clnt_res,
TIMEOUT);
if (rpc_stat == RPC_SUCCESS && clnt_res.status == WNL_OK)
return (SNEGO_DEF_VALID);
if (rpc_stat != RPC_AUTHERROR)
return (SNEGO_FAILURE);
{
struct rpc_err e;
wnl_diropres res;
char *p;
int tot = 0;
CLNT_GETERR(clnt, &e);
if (e.re_why != AUTH_TOOWEAK)
return (SNEGO_FAILURE);
if ((p = malloc(strlen(fspath)+3)) == NULL) {
syslog(LOG_ERR, "no memory\n");
return (SNEGO_FAILURE);
}
p[0] = (char)WNL_SEC_NEGO;
strcpy(&p[2], fspath);
do {
p[1] = (char)(1+snego->cnt);
arg.name = p;
memset((char *)&res, 0, sizeof (wnl_diropres));
if (wnlproc_lookup_2(&arg, &res, clnt) !=
RPC_SUCCESS || res.status != WNL_OK) {
free(p);
return (SNEGO_FAILURE);
}
{
char *c = (char *)&res.wnl_diropres_u.
wnl_diropres.file;
int ii;
int cnt = ((int)*c)/sizeof (uint_t);
int *ip = (int *)(c+sizeof (int));
tot += cnt;
if (tot >= MAX_FLAVORS) {
free(p);
return (SNEGO_ARRAY_TOO_SMALL);
}
status = (int)*(c+1);
if (cnt > MAX_V2_CNT || cnt < 0) {
free(p);
return (SNEGO_FAILURE);
}
for (ii = 0; ii < cnt; ii++)
snego->array[snego->cnt+ii] =
ntohl(*(ip+ii));
snego->cnt += cnt;
}
} while (status);
free(p);
return (SNEGO_SUCCESS);
}
} else if (vers == WNL_V3) {
WNL_LOOKUP3args arg;
WNL_LOOKUP3res clnt_res;
memset((char *)&arg.what.dir, 0, sizeof (wnl_fh3));
arg.what.name = fspath;
arg.what.dir.data.data_len = 0;
arg.what.dir.data.data_val = 0;
memset((char *)&clnt_res, 0, sizeof (clnt_res));
rpc_stat = clnt_call(clnt, WNLPROC3_LOOKUP,
(xdrproc_t)xdr_WNL_LOOKUP3args, (caddr_t)&arg,
(xdrproc_t)xdr_WNL_LOOKUP3res, (caddr_t)&clnt_res,
TIMEOUT);
if (rpc_stat == RPC_SUCCESS && clnt_res.status == WNL3_OK)
return (SNEGO_DEF_VALID);
if (rpc_stat != RPC_AUTHERROR)
return (SNEGO_FAILURE);
{
struct rpc_err e;
WNL_LOOKUP3res res;
char *p;
int tot = 0;
CLNT_GETERR(clnt, &e);
if (e.re_why != AUTH_TOOWEAK)
return (SNEGO_FAILURE);
if ((p = malloc(strlen(fspath)+3)) == NULL) {
syslog(LOG_ERR, "no memory\n");
return (SNEGO_FAILURE);
}
p[0] = (char)WNL_SEC_NEGO;
strcpy(&p[2], fspath);
do {
p[1] = (char)(1+snego->cnt);
arg.what.name = p;
memset((char *)&res, 0,
sizeof (WNL_LOOKUP3res));
if (wnlproc3_lookup_3(&arg, &res, clnt) !=
RPC_SUCCESS || res.status != WNL3_OK) {
free(p);
return (SNEGO_FAILURE);
}
{
char *c = res.WNL_LOOKUP3res_u.
res_ok.object.data.data_val;
int ii;
int len = res.WNL_LOOKUP3res_u.res_ok.
object.data.data_len;
int cnt;
int *ip = (int *)(c+sizeof (int));
cnt = len/sizeof (uint_t) - 1;
tot += cnt;
if (tot >= MAX_FLAVORS) {
free(p);
return (SNEGO_ARRAY_TOO_SMALL);
}
status = (int)(*c);
if (cnt > MAX_V3_CNT || cnt < 0) {
free(p);
return (SNEGO_FAILURE);
}
for (ii = 0; ii < cnt; ii++)
snego->array[snego->cnt+ii] =
ntohl(*(ip+ii));
snego->cnt += cnt;
}
} while (status);
free(p);
return (SNEGO_SUCCESS);
}
}
return (SNEGO_FAILURE);
}
#endif
static int
get_seconfig(int whichway, char *name, int num,
rpc_gss_service_t service, seconfig_t *entryp)
{
char line[BUFSIZ];
FILE *fp;
if ((whichway == GETBYNAME) && (name == NULL))
return (SC_NOTFOUND);
(void) mutex_lock(&matching_lock);
if ((fp = fopen(NFSSEC_CONF, "r")) == NULL) {
(void) mutex_unlock(&matching_lock);
return (SC_OPENFAIL);
}
while (fgets(line, BUFSIZ, fp)) {
if (!(blank(line) || comment(line))) {
switch (whichway) {
case GETBYNAME:
if (matchname(line, name, entryp)) {
goto found;
}
break;
case GETBYNUM:
if (matchnum(line, num, entryp)) {
goto found;
}
break;
default:
break;
}
}
}
(void) fclose(fp);
(void) mutex_unlock(&matching_lock);
return (SC_NOTFOUND);
found:
(void) fclose(fp);
(void) mutex_unlock(&matching_lock);
(void) get_rpcnum(entryp);
return (SC_NOERROR);
}
int
nfs_getseconfig_byname(char *secmode_name, seconfig_t *entryp)
{
if (!entryp)
return (SC_NOMEM);
return (get_seconfig(GETBYNAME, secmode_name, 0, rpc_gss_svc_none,
entryp));
}
int
nfs_getseconfig_bynumber(int nfs_secnum, seconfig_t *entryp)
{
if (!entryp)
return (SC_NOMEM);
return (get_seconfig(GETBYNUM, NULL, nfs_secnum, rpc_gss_svc_none,
entryp));
}
int
nfs_getseconfig_default(seconfig_t *secp)
{
if (secp == NULL)
return (SC_NOMEM);
return (nfs_getseconfig_byname("default", secp));
}
void
nfs_free_secdata(sec_data_t *secdata)
{
dh_k4_clntdata_t *dkdata;
gss_clntdata_t *gdata;
if (!secdata)
return;
switch (secdata->rpcflavor) {
case AUTH_UNIX:
case AUTH_NONE:
break;
case AUTH_DES:
dkdata = (dh_k4_clntdata_t *)secdata->data;
if (dkdata) {
if (dkdata->netname)
free(dkdata->netname);
if (dkdata->syncaddr.buf)
free(dkdata->syncaddr.buf);
free(dkdata);
}
break;
case RPCSEC_GSS:
gdata = (gss_clntdata_t *)secdata->data;
if (gdata) {
if (gdata->mechanism.elements)
free(gdata->mechanism.elements);
free(gdata);
}
break;
default:
break;
}
free(secdata);
}
sec_data_t *
nfs_clnt_secdata(seconfig_t *secp, char *hostname, struct knetconfig *knconf,
struct netbuf *syncaddr, int flags)
{
char netname[MAXNETNAMELEN+1];
sec_data_t *secdata;
dh_k4_clntdata_t *dkdata;
gss_clntdata_t *gdata;
secdata = malloc(sizeof (sec_data_t));
if (!secdata) {
syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n");
return (NULL);
}
(void) memset(secdata, 0, sizeof (sec_data_t));
secdata->secmod = secp->sc_nfsnum;
secdata->rpcflavor = secp->sc_rpcnum;
secdata->uid = secp->sc_uid;
secdata->flags = flags;
switch (secp->sc_rpcnum) {
case AUTH_UNIX:
case AUTH_NONE:
secdata->data = NULL;
break;
case AUTH_DES:
if (!host2netname(netname, hostname, NULL)) {
syslog(LOG_ERR, "host2netname: %s: unknown\n",
hostname);
goto err_out;
}
dkdata = malloc(sizeof (dh_k4_clntdata_t));
if (!dkdata) {
syslog(LOG_ERR,
"nfs_clnt_secdata: no memory\n");
goto err_out;
}
(void) memset((char *)dkdata, 0,
sizeof (dh_k4_clntdata_t));
if ((dkdata->netname = strdup(netname)) == NULL) {
syslog(LOG_ERR,
"nfs_clnt_secdata: no memory\n");
goto err_out;
}
dkdata->netnamelen = strlen(netname);
dkdata->knconf = knconf;
dkdata->syncaddr = *syncaddr;
dkdata->syncaddr.buf = malloc(syncaddr->len);
if (dkdata->syncaddr.buf == NULL) {
syslog(LOG_ERR,
"nfs_clnt_secdata: no memory\n");
goto err_out;
}
(void) memcpy(dkdata->syncaddr.buf, syncaddr->buf,
syncaddr->len);
secdata->data = (caddr_t)dkdata;
break;
case RPCSEC_GSS:
if (secp->sc_gss_mech_type == NULL) {
syslog(LOG_ERR,
"nfs_clnt_secdata: need mechanism information\n");
goto err_out;
}
gdata = malloc(sizeof (gss_clntdata_t));
if (!gdata) {
syslog(LOG_ERR,
"nfs_clnt_secdata: no memory\n");
goto err_out;
}
(void) strcpy(gdata->uname, "nfs");
if (!parsehostname(hostname, gdata->inst,
gdata->realm)) {
syslog(LOG_ERR,
"nfs_clnt_secdata: bad host name\n");
goto err_out;
}
gdata->mechanism.length =
secp->sc_gss_mech_type->length;
if (!(gdata->mechanism.elements =
malloc(secp->sc_gss_mech_type->length))) {
syslog(LOG_ERR,
"nfs_clnt_secdata: no memory\n");
goto err_out;
}
(void) memcpy(gdata->mechanism.elements,
secp->sc_gss_mech_type->elements,
secp->sc_gss_mech_type->length);
gdata->qop = secp->sc_qop;
gdata->service = secp->sc_service;
secdata->data = (caddr_t)gdata;
break;
default:
syslog(LOG_ERR, "nfs_clnt_secdata: unknown flavor\n");
goto err_out;
}
return (secdata);
err_out:
free(secdata);
return (NULL);
}
bool_t
nfs_get_root_principal(seconfig_t *seconfig, char *host, caddr_t *rootname_p)
{
char netname[MAXNETNAMELEN+1], node[MAX_NAME_LEN];
char secdomain[MAX_NAME_LEN];
rpc_gss_principal_t gssname;
switch (seconfig->sc_rpcnum) {
case AUTH_DES:
if (!host2netname(netname, host, NULL)) {
syslog(LOG_ERR,
"nfs_get_root_principal: unknown host: %s\n", host);
return (FALSE);
}
*rootname_p = strdup(netname);
if (!*rootname_p) {
syslog(LOG_ERR,
"nfs_get_root_principal: no memory\n");
return (FALSE);
}
break;
case RPCSEC_GSS:
if (!parsehostname(host, node, secdomain)) {
syslog(LOG_ERR,
"nfs_get_root_principal: bad host name\n");
return (FALSE);
}
if (!rpc_gss_get_principal_name(&gssname,
seconfig->sc_gss_mech, "root", node, secdomain)) {
syslog(LOG_ERR,
"nfs_get_root_principal: can not get principal name : %s\n", host);
return (FALSE);
}
*rootname_p = (caddr_t)gssname;
break;
default:
return (FALSE);
}
return (TRUE);
}
int
nfs_syslog_scerr(int scerror, char msg[])
{
switch (scerror) {
case SC_NOMEM :
(void) sprintf(msg, "%s : no memory", NFSSEC_CONF);
return (0);
case SC_OPENFAIL :
(void) sprintf(msg, "can not open %s", NFSSEC_CONF);
return (0);
case SC_NOTFOUND :
(void) sprintf(msg, "has no entry in %s", NFSSEC_CONF);
return (0);
case SC_BADENTRIES :
(void) sprintf(msg, "bad entry in %s", NFSSEC_CONF);
return (0);
default:
msg[0] = '\0';
return (-1);
}
}