2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
2N/A/* All Rights Reserved */
2N/A
2N/A/*
2N/A * Portions of this source code were derived from Berkeley 4.3 BSD
2N/A * under license from the Regents of the University of California.
2N/A */
2N/A
2N/A/*
2N/A * publickey.c
2N/A *
2N/A *
2N/A * Public and Private (secret) key lookup routines. These functions
2N/A * are used by the secure RPC auth_des flavor to get the public and
2N/A * private keys for secure RPC principals. Originally designed to
2N/A * talk only to YP, AT&T modified them to talk to files, and now
2N/A * they can also talk to NIS+. The policy for these lookups is now
2N/A * defined in terms of the nameservice switch as follows :
2N/A * publickey: nis files
2N/A *
2N/A */
2N/A#include "mt.h"
2N/A#include "../rpc/rpc_mt.h"
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A#include <syslog.h>
2N/A#include <assert.h>
2N/A#include <sys/types.h>
2N/A#include <pwd.h>
2N/A#include "nsswitch.h"
2N/A#include <rpc/rpc.h>
2N/A#include <rpc/key_prot.h>
2N/A#include <rpcsvc/nis.h>
2N/A#include <rpcsvc/ypclnt.h>
2N/A#include <rpcsvc/nis_dhext.h>
2N/A#include <thread.h>
2N/A#include "../nis/gen/nis_clnt.h"
2N/A#include <nss_dbdefs.h>
2N/A
2N/A
2N/Astatic const char *PKMAP = "publickey.byname";
2N/Astatic const char *PKFILE = "/etc/publickey";
2N/Astatic const char dh_caps_str[] = "DH";
2N/Astatic const char des_caps_str[] = AUTH_DES_AUTH_TYPE;
2N/A
2N/Astatic char *netname2hashname(const char *, char *, int, keylen_t,
2N/A algtype_t);
2N/A
2N/A#define WORKBUFSIZE 1024
2N/A
2N/Aextern int xdecrypt();
2N/A
2N/Aextern int __yp_match_cflookup(char *, char *, char *, int, char **,
2N/A int *, int *);
2N/A
2N/A
2N/A/*
2N/A * default publickey policy:
2N/A * publickey: nis [NOTFOUND = return] files
2N/A */
2N/A
2N/A
2N/A/* NSW_NOTSUCCESS NSW_NOTFOUND NSW_UNAVAIL NSW_TRYAGAIN */
2N/A#define DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE}
2N/A
2N/Astatic struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL},
2N/A lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files};
2N/Astatic struct __nsw_switchconfig publickey_default =
2N/A {0, "publickey", 2, &lookup_nis};
2N/A
2N/A#ifndef NUL
2N/A#define NUL '\0'
2N/A#endif
2N/A
2N/Aextern mutex_t serialize_pkey;
2N/A
2N/Astatic int extract_secret();
2N/A
2N/A/*
2N/A * db_root is used for switch backends.
2N/A */
2N/Astatic DEFINE_NSS_DB_ROOT(db_root);
2N/A
2N/A/*
2N/A * str2key
2N/A */
2N/A/* ARGSUSED */
2N/Astatic int
2N/Astr2key(const char *instr, int lenstr,
2N/A void *ent, char *buffer, int buflen) {
2N/A if (lenstr + 1 > buflen)
2N/A return (NSS_STR_PARSE_ERANGE);
2N/A /*
2N/A * We copy the input string into the output buffer
2N/A */
2N/A (void) memcpy(buffer, instr, lenstr);
2N/A buffer[lenstr] = '\0';
2N/A
2N/A return (NSS_STR_PARSE_SUCCESS);
2N/A}
2N/A/*
2N/A * These functions are the "backends" for the switch for public keys. They
2N/A * get both the public and private keys from each of the supported name
2N/A * services (nis, files). They are passed the appropriate parameters
2N/A * and return 0 if unsuccessful with *errp set, or 1 when they got just the
2N/A * public key and 3 when they got both the public and private keys.
2N/A *
2N/A *
2N/A * getkey_nis()
2N/A *
2N/A * Internal implementation of getpublickey() using NIS (aka Yellow Pages,
2N/A * aka YP).
2N/A *
2N/A * NOTE : *** this function returns nsswitch codes and _not_ the
2N/A * value returned by getpublickey.
2N/A */
2N/Astatic int
2N/Agetkeys_nis(int *errp, char *netname, char *pkey, char *skey, char *passwd)
2N/A{
2N/A char *domain;
2N/A char *keyval = NULL;
2N/A int keylen, err, r = 0;
2N/A char *p;
2N/A int len;
2N/A
2N/A p = strchr(netname, '@');
2N/A if (!p) {
2N/A *errp = __NSW_UNAVAIL;
2N/A return (0);
2N/A }
2N/A
2N/A domain = ++p;
2N/A
2N/A
2N/A /*
2N/A * Instead of calling yp_match(), we use __yp_match_cflookup() here
2N/A * which has time-out control for the binding operation to nis
2N/A * servers.
2N/A */
2N/A err = __yp_match_cflookup(domain, (char *)PKMAP, netname,
2N/A strlen(netname), &keyval, &keylen, 0);
2N/A
2N/A switch (err) {
2N/A case YPERR_KEY :
2N/A if (keyval)
2N/A free(keyval);
2N/A *errp = __NSW_NOTFOUND;
2N/A return (0);
2N/A default :
2N/A if (keyval)
2N/A free(keyval);
2N/A *errp = __NSW_UNAVAIL;
2N/A return (0);
2N/A case 0:
2N/A break;
2N/A }
2N/A
2N/A p = strchr(keyval, ':');
2N/A if (p == NULL) {
2N/A free(keyval);
2N/A *errp = __NSW_NOTFOUND;
2N/A return (0);
2N/A }
2N/A *p = 0;
2N/A if (pkey) {
2N/A len = strlen(keyval);
2N/A if (len > HEXKEYBYTES) {
2N/A free(keyval);
2N/A *errp = __NSW_NOTFOUND;
2N/A return (0);
2N/A }
2N/A (void) strcpy(pkey, keyval);
2N/A }
2N/A r = 1;
2N/A p++;
2N/A if (skey && extract_secret(p, skey, passwd))
2N/A r |= 2;
2N/A free(keyval);
2N/A *errp = __NSW_SUCCESS;
2N/A return (r);
2N/A}
2N/A
2N/A/*
2N/A * getkey_files()
2N/A *
2N/A * The files version of getpublickey. This function attempts to
2N/A * get the publickey from the file PKFILE .
2N/A *
2N/A * This function defines the format of the /etc/publickey file to
2N/A * be :
2N/A * netname <whitespace> publickey:privatekey
2N/A *
2N/A * NOTE : *** this function returns nsswitch codes and _not_ the
2N/A * value returned by getpublickey.
2N/A */
2N/A
2N/Astatic int
2N/Agetkeys_files(int *errp, char *netname, char *pkey, char *skey, char *passwd)
2N/A{
2N/A char *mkey;
2N/A char *mval;
2N/A char buf[WORKBUFSIZE];
2N/A int r = 0;
2N/A char *res;
2N/A FILE *fd;
2N/A char *p;
2N/A char *lasts;
2N/A
2N/A fd = fopen(PKFILE, "rF");
2N/A if (fd == NULL) {
2N/A *errp = __NSW_UNAVAIL;
2N/A return (0);
2N/A }
2N/A
2N/A /* Search through the file linearly :-( */
2N/A while ((res = fgets(buf, WORKBUFSIZE, fd)) != NULL) {
2N/A
2N/A if ((res[0] == '#') || (res[0] == '\n'))
2N/A continue;
2N/A else {
2N/A mkey = strtok_r(buf, "\t ", &lasts);
2N/A if (mkey == NULL) {
2N/A syslog(LOG_INFO,
2N/A "getpublickey: Bad record in %s for %s",
2N/A PKFILE, netname);
2N/A continue;
2N/A }
2N/A mval = strtok_r(NULL, " \t#\n", &lasts);
2N/A if (mval == NULL) {
2N/A syslog(LOG_INFO,
2N/A "getpublickey: Bad record in %s for %s",
2N/A PKFILE, netname);
2N/A continue;
2N/A }
2N/A /* NOTE : Case insensitive compare. */
2N/A if (strcasecmp(mkey, netname) == 0) {
2N/A p = strchr(mval, ':');
2N/A if (p == NULL) {
2N/A syslog(LOG_INFO,
2N/A "getpublickey: Bad record in %s for %s",
2N/A PKFILE, netname);
2N/A continue;
2N/A }
2N/A
2N/A *p = 0;
2N/A if (pkey) {
2N/A int len = strlen(mval);
2N/A
2N/A if (len > HEXKEYBYTES) {
2N/A syslog(LOG_INFO,
2N/A "getpublickey: Bad record in %s for %s",
2N/A PKFILE, netname);
2N/A continue;
2N/A }
2N/A (void) strcpy(pkey, mval);
2N/A }
2N/A r = 1;
2N/A p++;
2N/A if (skey && extract_secret(p, skey, passwd))
2N/A r |= 2;
2N/A (void) fclose(fd);
2N/A *errp = __NSW_SUCCESS;
2N/A return (r);
2N/A }
2N/A }
2N/A }
2N/A
2N/A (void) fclose(fd);
2N/A *errp = __NSW_NOTFOUND;
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * getpublickey(netname, key)
2N/A *
2N/A * This is the actual exported interface for this function.
2N/A */
2N/A
2N/Aint
2N/A__getpublickey_cached(char *netname, char *pkey, int *from_cache)
2N/A{
2N/A return (__getpublickey_cached_g(netname, KEYSIZE, 0, pkey,
2N/A HEXKEYBYTES+1, from_cache));
2N/A}
2N/A
2N/Aint
2N/Agetpublickey(const char *netname, char *pkey)
2N/A{
2N/A return (__getpublickey_cached((char *)netname, pkey, (int *)0));
2N/A}
2N/A
2N/Avoid
2N/A__getpublickey_flush(const char *netname)
2N/A{
2N/A __getpublickey_flush_g(netname, 192, 0);
2N/A}
2N/A
2N/Aint
2N/Agetsecretkey(const char *netname, char *skey, const char *passwd)
2N/A{
2N/A return (getsecretkey_g(netname, KEYSIZE, 0, skey, HEXKEYBYTES+1,
2N/A passwd));
2N/A}
2N/A
2N/A/*
2N/A * Routines to cache publickeys.
2N/A */
2N/A
2N/A
2N/A/*
2N/A * Generic DH (any size keys) version of extract_secret.
2N/A */
2N/Astatic int
2N/Aextract_secret_g(
2N/A char *raw, /* in */
2N/A char *private, /* out */
2N/A int prilen, /* in */
2N/A char *passwd, /* in */
2N/A char *netname, /* in */
2N/A keylen_t keylen, /* in */
2N/A algtype_t algtype) /* in */
2N/A
2N/A{
2N/A char *buf = malloc(strlen(raw) + 1); /* private tmp buf */
2N/A char *p;
2N/A
2N/A if (!buf || !passwd || !raw || !private || !prilen ||
2N/A !VALID_KEYALG(keylen, algtype)) {
2N/A if (private)
2N/A *private = NUL;
2N/A if (buf)
2N/A free(buf);
2N/A return (0);
2N/A }
2N/A
2N/A (void) strcpy(buf, raw);
2N/A
2N/A /* strip off pesky colon if it exists */
2N/A p = strchr(buf, ':');
2N/A if (p) {
2N/A *p = 0;
2N/A }
2N/A
2N/A /* raw buf has chksum appended, so let's verify it too */
2N/A if (!xdecrypt_g(buf, keylen, algtype, passwd, netname, TRUE)) {
2N/A private[0] = 0;
2N/A free(buf);
2N/A return (1); /* yes, return 1 even if xdecrypt fails */
2N/A }
2N/A
2N/A if (strlen(buf) >= prilen) {
2N/A private[0] = 0;
2N/A free(buf);
2N/A return (0);
2N/A }
2N/A
2N/A (void) strcpy(private, buf);
2N/A free(buf);
2N/A return (1);
2N/A}
2N/A
2N/A/*
2N/A * extract_secret()
2N/A *
2N/A * This generic function will extract the private key
2N/A * from a string using the given password. Note that
2N/A * it uses the DES based function xdecrypt()
2N/A */
2N/Astatic int
2N/Aextract_secret(char *raw, char *private, char *passwd)
2N/A{
2N/A return (extract_secret_g(raw, private, HEXKEYBYTES+1, passwd,
2N/A NULL, KEYSIZE, 0));
2N/A}
2N/A
2N/A/*
2N/A * getkeys_ldap_g()
2N/A *
2N/A * Fetches the key pair from LDAP. This version handles any size
2N/A * DH keys.
2N/A */
2N/A
2N/Avoid
2N/A_nss_initf_publickey(nss_db_params_t *p)
2N/A{
2N/A p->name = NSS_DBNAM_PUBLICKEY;
2N/A p->default_config = NSS_DEFCONF_PUBLICKEY;
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Agetkeys_ldap_g(
2N/A int *err, /* in */
2N/A char *netname, /* in */
2N/A char *pkey, /* out */
2N/A int pkeylen, /* in */
2N/A char *skey, /* out */
2N/A int skeylen, /* in */
2N/A char *passwd, /* in */
2N/A keylen_t keylen, /* in */
2N/A algtype_t algtype) /* in */
2N/A{
2N/A int r = 0;
2N/A char *p;
2N/A char keytypename[NIS_MAXNAMELEN+1];
2N/A int len;
2N/A const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
2N/A int rc = 0;
2N/A nss_XbyY_args_t arg;
2N/A nss_XbyY_buf_t *buf = NULL;
2N/A char *keyval;
2N/A
2N/A NSS_XbyY_ALLOC(&buf, 0, NSS_BUFLEN_PUBLICKEY);
2N/A
2N/A NSS_XbyY_INIT(&arg, buf->result, buf->buffer, buf->buflen, str2key);
2N/A arg.key.pkey.name = netname;
2N/A
2N/A /*
2N/A * LDAP stores the public and secret key info in entries using
2N/A * nisKeyObject objectclass. Each key is tagged with the
2N/A * keytype, keylength, and algorithm. The tag has the following
2N/A * format: {<keytype><keylength>-<algorithm>}. For example,
2N/A * {DH192-0}.
2N/A */
2N/A if (classic_des)
2N/A (void) strcpy(keytypename, "{DH192-0}");
2N/A else
2N/A (void) sprintf(keytypename, "{%s%d-%d}",
2N/A dh_caps_str, keylen, algtype);
2N/A arg.key.pkey.keytype = keytypename;
2N/A
2N/A if (nss_search(&db_root, _nss_initf_publickey, NSS_DBOP_KEYS_BYNAME,
2N/A &arg) != NSS_SUCCESS) {
2N/A NSS_XbyY_FREE(&buf);
2N/A *err = __NSW_NOTFOUND;
2N/A return (0);
2N/A }
2N/A keyval = buf->buffer;
2N/A p = strchr(keyval, ':');
2N/A if (p == NULL) {
2N/A NSS_XbyY_FREE(&buf);
2N/A *err = __NSW_NOTFOUND;
2N/A return (0);
2N/A }
2N/A *p = 0;
2N/A if (pkey) {
2N/A len = strlen(keyval);
2N/A if (len > HEXKEYBYTES) {
2N/A NSS_XbyY_FREE(&buf);
2N/A *err = __NSW_NOTFOUND;
2N/A return (0);
2N/A }
2N/A (void) strcpy(pkey, keyval);
2N/A }
2N/A r = 1;
2N/A p++;
2N/A if (skey && extract_secret(p, skey, passwd))
2N/A r |= 2;
2N/A NSS_XbyY_FREE(&buf);
2N/A *err = __NSW_SUCCESS;
2N/A return (r);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Convert a netname to a name we will hash on. For classic_des,
2N/A * just copy netname as is. But for new and improved ("now in
2N/A * new longer sizes!") DHEXT, add a ":keylen-algtype" suffix to hash on.
2N/A *
2N/A * Returns the hashname string on success or NULL on failure.
2N/A */
2N/Astatic char *
2N/Anetname2hashname(
2N/A const char *netname,
2N/A char *hashname,
2N/A int bufsiz,
2N/A keylen_t keylen,
2N/A algtype_t algtype)
2N/A{
2N/A const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
2N/A
2N/A if (!netname || !hashname || !bufsiz)
2N/A return (NULL);
2N/A
2N/A if (classic_des) {
2N/A if (bufsiz > strlen(netname))
2N/A (void) strcpy(hashname, netname);
2N/A else
2N/A return (NULL);
2N/A } else {
2N/A char tmp[128];
2N/A (void) sprintf(tmp, ":%d-%d", keylen, algtype);
2N/A if (bufsiz > (strlen(netname) + strlen(tmp)))
2N/A (void) sprintf(hashname, "%s%s", netname, tmp);
2N/A else
2N/A return (NULL);
2N/A }
2N/A
2N/A return (hashname);
2N/A}
2N/A
2N/A/*
2N/A * Flush netname's publickey of the given key length and algorithm type.
2N/A */
2N/Avoid
2N/A__getpublickey_flush_g(const char *netname, keylen_t keylen, algtype_t algtype)
2N/A{
2N/A char *p, hashname[MAXNETNAMELEN+1];
2N/A p = netname2hashname(netname, hashname, MAXNETNAMELEN, keylen, algtype);
2N/A}
2N/A
2N/A/*
2N/A * Generic DH (any size keys) version of __getpublickey_cached.
2N/A */
2N/Aint
2N/A__getpublickey_cached_g(const char netname[], /* in */
2N/A keylen_t keylen, /* in */
2N/A algtype_t algtype, /* in */
2N/A char *pkey, /* out */
2N/A size_t pkeylen, /* in */
2N/A int *from_cache) /* in/out */
2N/A{
2N/A int needfree = 1, res, err;
2N/A struct __nsw_switchconfig *conf;
2N/A struct __nsw_lookup *look;
2N/A enum __nsw_parse_err perr;
2N/A const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
2N/A int retry_cache = 0;
2N/A
2N/A if (!netname || !pkey)
2N/A return (0);
2N/A
2N/A conf = __nsw_getconfig("publickey", &perr);
2N/A if (!conf) {
2N/A conf = &publickey_default;
2N/A needfree = 0;
2N/A }
2N/A for (look = conf->lookups; look; look = look->next) {
2N/A if (strcmp(look->service_name, "ldap") == 0) {
2N/A res = getkeys_ldap_g(&err, (char *)netname,
2N/A pkey, pkeylen, NULL, 0, NULL,
2N/A keylen, algtype);
2N/A /* long DH keys will not be in nis or files */
2N/A } else if (classic_des &&
2N/A strcmp(look->service_name, "nis") == 0)
2N/A res = getkeys_nis(&err, (char *)netname, pkey,
2N/A NULL, NULL);
2N/A else if (classic_des &&
2N/A strcmp(look->service_name, "files") == 0)
2N/A res = getkeys_files(&err, (char *)netname, pkey,
2N/A NULL, NULL);
2N/A else {
2N/A syslog(LOG_INFO, "Unknown publickey nameservice '%s'",
2N/A look->service_name);
2N/A err = __NSW_UNAVAIL;
2N/A }
2N/A
2N/A switch (look->actions[err]) {
2N/A case __NSW_CONTINUE :
2N/A continue;
2N/A case __NSW_RETURN :
2N/A if (needfree)
2N/A __nsw_freeconfig(conf);
2N/A return ((res & 1) != 0);
2N/A default :
2N/A syslog(LOG_INFO, "Unknown action for nameservice %s",
2N/A look->service_name);
2N/A }
2N/A }
2N/A
2N/A if (needfree)
2N/A __nsw_freeconfig(conf);
2N/A return (0);
2N/A}
2N/A
2N/A
2N/A
2N/A/*
2N/A * Generic (all sizes) DH version of getpublickey.
2N/A */
2N/Aint
2N/Agetpublickey_g(
2N/A const char *netname, /* in */
2N/A int keylen, /* in */
2N/A int algtype, /* in */
2N/A char *pkey, /* out */
2N/A size_t pkeylen) /* in */
2N/A{
2N/A return (__getpublickey_cached_g(netname, keylen, algtype, pkey,
2N/A pkeylen, (int *)0));
2N/A}
2N/A
2N/A/*
2N/A * Generic (all sizes) DH version of getsecretkey_g.
2N/A */
2N/Aint
2N/Agetsecretkey_g(
2N/A const char *netname, /* in */
2N/A keylen_t keylen, /* in */
2N/A algtype_t algtype, /* in */
2N/A char *skey, /* out */
2N/A size_t skeylen, /* in */
2N/A const char *passwd) /* in */
2N/A{
2N/A int needfree = 1, res, err;
2N/A struct __nsw_switchconfig *conf;
2N/A struct __nsw_lookup *look;
2N/A enum __nsw_parse_err perr;
2N/A const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
2N/A
2N/A if (!netname || !skey || !skeylen)
2N/A return (0);
2N/A
2N/A conf = __nsw_getconfig("publickey", &perr);
2N/A
2N/A if (!conf) {
2N/A conf = &publickey_default;
2N/A needfree = 0;
2N/A }
2N/A
2N/A for (look = conf->lookups; look; look = look->next) {
2N/A if (strcmp(look->service_name, "ldap") == 0)
2N/A res = getkeys_ldap_g(&err, (char *)netname,
2N/A NULL, 0, skey, skeylen,
2N/A (char *)passwd, keylen, algtype);
2N/A /* long DH keys will not be in nis or files */
2N/A else if (classic_des && strcmp(look->service_name, "nis") == 0)
2N/A res = getkeys_nis(&err, (char *)netname,
2N/A NULL, skey, (char *)passwd);
2N/A else if (classic_des &&
2N/A strcmp(look->service_name, "files") == 0)
2N/A res = getkeys_files(&err, (char *)netname,
2N/A NULL, skey, (char *)passwd);
2N/A else {
2N/A syslog(LOG_INFO, "Unknown publickey nameservice '%s'",
2N/A look->service_name);
2N/A err = __NSW_UNAVAIL;
2N/A }
2N/A switch (look->actions[err]) {
2N/A case __NSW_CONTINUE :
2N/A continue;
2N/A case __NSW_RETURN :
2N/A if (needfree)
2N/A __nsw_freeconfig(conf);
2N/A return ((res & 2) != 0);
2N/A default :
2N/A syslog(LOG_INFO, "Unknown action for nameservice %s",
2N/A look->service_name);
2N/A }
2N/A }
2N/A if (needfree)
2N/A __nsw_freeconfig(conf);
2N/A return (0);
2N/A}