1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26 /*
27 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
28 */
29
30 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
31 /* All Rights Reserved */
32
33 /*
34 * Portions of this source code were derived from Berkeley 4.3 BSD
35 * under license from the Regents of the University of California.
36 */
37
38 /*
39 * publickey.c
40 *
41 *
42 * Public and Private (secret) key lookup routines. These functions
43 * are used by the secure RPC auth_des flavor to get the public and
44 * private keys for secure RPC principals. Originally designed to
45 * talk only to YP, AT&T modified them to talk to files, and now
46 * they can also talk to NIS+. The policy for these lookups is now
47 * defined in terms of the nameservice switch as follows :
48 * publickey: nis files
49 *
50 */
51 #include "mt.h"
52 #include "../rpc/rpc_mt.h"
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <syslog.h>
57 #include <assert.h>
58 #include <sys/types.h>
59 #include <pwd.h>
60 #include "nsswitch.h"
61 #include <rpc/rpc.h>
62 #include <rpc/key_prot.h>
63 #include <rpcsvc/nis.h>
64 #include <rpcsvc/ypclnt.h>
65 #include <rpcsvc/nis_dhext.h>
66 #include <thread.h>
67 #include "../nis/gen/nis_clnt.h"
68 #include <nss_dbdefs.h>
69
70
71 static const char *PKMAP = "publickey.byname";
72 static const char *PKFILE = "/etc/publickey";
73 static const char dh_caps_str[] = "DH";
74 static const char des_caps_str[] = AUTH_DES_AUTH_TYPE;
75
76 static char *netname2hashname(const char *, char *, int, keylen_t,
77 algtype_t);
78
79 #define WORKBUFSIZE 1024
80
81 extern int xdecrypt();
82
83 extern int __yp_match_cflookup(char *, char *, char *, int, char **,
84 int *, int *);
85
86
87 /*
88 * default publickey policy:
89 * publickey: nis [NOTFOUND = return] files
90 */
91
92
93 /* NSW_NOTSUCCESS NSW_NOTFOUND NSW_UNAVAIL NSW_TRYAGAIN */
94 #define DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE}
95
96 static struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL},
97 lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files};
98 static struct __nsw_switchconfig publickey_default =
99 {0, "publickey", 2, &lookup_nis};
100
101 #ifndef NUL
102 #define NUL '\0'
103 #endif
104
105 extern mutex_t serialize_pkey;
106
107 static int extract_secret();
108
109 /*
110 * db_root is used for switch backends.
111 */
112 static DEFINE_NSS_DB_ROOT(db_root);
113
114 /*
115 * str2key
116 */
117 /* ARGSUSED */
118 static int
str2key(const char * instr,int lenstr,void * ent,char * buffer,int buflen)119 str2key(const char *instr, int lenstr, void *ent, char *buffer, int buflen)
120 {
121 if (lenstr + 1 > buflen)
122 return (NSS_STR_PARSE_ERANGE);
123 /*
124 * We copy the input string into the output buffer
125 */
126 (void) memcpy(buffer, instr, lenstr);
127 buffer[lenstr] = '\0';
128
129 return (NSS_STR_PARSE_SUCCESS);
130 }
131 /*
132 * These functions are the "backends" for the switch for public keys. They
133 * get both the public and private keys from each of the supported name
134 * services (nis, files). They are passed the appropriate parameters
135 * and return 0 if unsuccessful with *errp set, or 1 when they got just the
136 * public key and 3 when they got both the public and private keys.
137 *
138 *
139 * getkey_nis()
140 *
141 * Internal implementation of getpublickey() using NIS (aka Yellow Pages,
142 * aka YP).
143 *
144 * NOTE : *** this function returns nsswitch codes and _not_ the
145 * value returned by getpublickey.
146 */
147 static int
getkeys_nis(int * errp,char * netname,char * pkey,char * skey,char * passwd)148 getkeys_nis(int *errp, char *netname, char *pkey, char *skey, char *passwd)
149 {
150 char *domain;
151 char *keyval = NULL;
152 int keylen, err, r = 0;
153 char *p;
154 int len;
155
156 p = strchr(netname, '@');
157 if (!p) {
158 *errp = __NSW_UNAVAIL;
159 return (0);
160 }
161
162 domain = ++p;
163
164
165 /*
166 * Instead of calling yp_match(), we use __yp_match_cflookup() here
167 * which has time-out control for the binding operation to nis
168 * servers.
169 */
170 err = __yp_match_cflookup(domain, (char *)PKMAP, netname,
171 strlen(netname), &keyval, &keylen, 0);
172
173 switch (err) {
174 case YPERR_KEY :
175 if (keyval)
176 free(keyval);
177 *errp = __NSW_NOTFOUND;
178 return (0);
179 default :
180 if (keyval)
181 free(keyval);
182 *errp = __NSW_UNAVAIL;
183 return (0);
184 case 0:
185 break;
186 }
187
188 p = strchr(keyval, ':');
189 if (p == NULL) {
190 free(keyval);
191 *errp = __NSW_NOTFOUND;
192 return (0);
193 }
194 *p = 0;
195 if (pkey) {
196 len = strlen(keyval);
197 if (len > HEXKEYBYTES) {
198 free(keyval);
199 *errp = __NSW_NOTFOUND;
200 return (0);
201 }
202 (void) strcpy(pkey, keyval);
203 }
204 r = 1;
205 p++;
206 if (skey && extract_secret(p, skey, passwd))
207 r |= 2;
208 free(keyval);
209 *errp = __NSW_SUCCESS;
210 return (r);
211 }
212
213 /*
214 * getkey_files()
215 *
216 * The files version of getpublickey. This function attempts to
217 * get the publickey from the file PKFILE .
218 *
219 * This function defines the format of the /etc/publickey file to
220 * be :
221 * netname <whitespace> publickey:privatekey
222 *
223 * NOTE : *** this function returns nsswitch codes and _not_ the
224 * value returned by getpublickey.
225 */
226
227 static int
getkeys_files(int * errp,char * netname,char * pkey,char * skey,char * passwd)228 getkeys_files(int *errp, char *netname, char *pkey, char *skey, char *passwd)
229 {
230 char *mkey;
231 char *mval;
232 char buf[WORKBUFSIZE];
233 int r = 0;
234 char *res;
235 FILE *fd;
236 char *p;
237 char *lasts;
238
239 fd = fopen(PKFILE, "rF");
240 if (fd == NULL) {
241 *errp = __NSW_UNAVAIL;
242 return (0);
243 }
244
245 /* Search through the file linearly :-( */
246 while ((res = fgets(buf, WORKBUFSIZE, fd)) != NULL) {
247
248 if ((res[0] == '#') || (res[0] == '\n'))
249 continue;
250 else {
251 mkey = strtok_r(buf, "\t ", &lasts);
252 if (mkey == NULL) {
253 syslog(LOG_INFO,
254 "getpublickey: Bad record in %s for %s",
255 PKFILE, netname);
256 continue;
257 }
258 mval = strtok_r(NULL, " \t#\n", &lasts);
259 if (mval == NULL) {
260 syslog(LOG_INFO,
261 "getpublickey: Bad record in %s for %s",
262 PKFILE, netname);
263 continue;
264 }
265 /* NOTE : Case insensitive compare. */
266 if (strcasecmp(mkey, netname) == 0) {
267 p = strchr(mval, ':');
268 if (p == NULL) {
269 syslog(LOG_INFO,
270 "getpublickey: Bad record in %s for %s",
271 PKFILE, netname);
272 continue;
273 }
274
275 *p = 0;
276 if (pkey) {
277 int len = strlen(mval);
278
279 if (len > HEXKEYBYTES) {
280 syslog(LOG_INFO,
281 "getpublickey: Bad record in %s for %s",
282 PKFILE, netname);
283 continue;
284 }
285 (void) strcpy(pkey, mval);
286 }
287 r = 1;
288 p++;
289 if (skey && extract_secret(p, skey, passwd))
290 r |= 2;
291 (void) fclose(fd);
292 *errp = __NSW_SUCCESS;
293 return (r);
294 }
295 }
296 }
297
298 (void) fclose(fd);
299 *errp = __NSW_NOTFOUND;
300 return (0);
301 }
302
303 /*
304 * getpublickey(netname, key)
305 *
306 * This is the actual exported interface for this function.
307 */
308
309 int
__getpublickey_cached(char * netname,char * pkey,int * from_cache)310 __getpublickey_cached(char *netname, char *pkey, int *from_cache)
311 {
312 return (__getpublickey_cached_g(netname, KEYSIZE, 0, pkey,
313 HEXKEYBYTES+1, from_cache));
314 }
315
316 int
getpublickey(const char * netname,char * pkey)317 getpublickey(const char *netname, char *pkey)
318 {
319 return (__getpublickey_cached((char *)netname, pkey, (int *)0));
320 }
321
322 void
__getpublickey_flush(const char * netname)323 __getpublickey_flush(const char *netname)
324 {
325 __getpublickey_flush_g(netname, 192, 0);
326 }
327
328 int
getsecretkey(const char * netname,char * skey,const char * passwd)329 getsecretkey(const char *netname, char *skey, const char *passwd)
330 {
331 return (getsecretkey_g(netname, KEYSIZE, 0, skey, HEXKEYBYTES+1,
332 passwd));
333 }
334
335 /*
336 * Routines to cache publickeys.
337 */
338
339
340 /*
341 * Generic DH (any size keys) version of extract_secret.
342 */
343 static int
extract_secret_g(char * raw,char * private,int prilen,char * passwd,char * netname,keylen_t keylen,algtype_t algtype)344 extract_secret_g(
345 char *raw, /* in */
346 char *private, /* out */
347 int prilen, /* in */
348 char *passwd, /* in */
349 char *netname, /* in */
350 keylen_t keylen, /* in */
351 algtype_t algtype) /* in */
352 {
353 char *buf = malloc(strlen(raw) + 1); /* private tmp buf */
354 char *p;
355
356 if (!buf || !passwd || !raw || !private || !prilen ||
357 !VALID_KEYALG(keylen, algtype)) {
358 if (private)
359 *private = NUL;
360 if (buf)
361 free(buf);
362 return (0);
363 }
364
365 (void) strcpy(buf, raw);
366
367 /* strip off pesky colon if it exists */
368 p = strchr(buf, ':');
369 if (p) {
370 *p = 0;
371 }
372
373 /* raw buf has chksum appended, so let's verify it too */
374 if (!xdecrypt_g(buf, keylen, algtype, passwd, netname, TRUE)) {
375 private[0] = 0;
376 free(buf);
377 return (1); /* yes, return 1 even if xdecrypt fails */
378 }
379
380 if (strlen(buf) >= prilen) {
381 private[0] = 0;
382 free(buf);
383 return (0);
384 }
385
386 (void) strcpy(private, buf);
387 free(buf);
388 return (1);
389 }
390
391 /*
392 * extract_secret()
393 *
394 * This generic function will extract the private key
395 * from a string using the given password. Note that
396 * it uses the DES based function xdecrypt()
397 */
398 static int
extract_secret(char * raw,char * private,char * passwd)399 extract_secret(char *raw, char *private, char *passwd)
400 {
401 return (extract_secret_g(raw, private, HEXKEYBYTES+1, passwd,
402 NULL, KEYSIZE, 0));
403 }
404
405 /*
406 * getkeys_ldap_g()
407 *
408 * Fetches the key pair from LDAP. This version handles any size
409 * DH keys.
410 */
411
412 void
_nss_initf_publickey(nss_db_params_t * p)413 _nss_initf_publickey(nss_db_params_t *p)
414 {
415 p->name = NSS_DBNAM_PUBLICKEY;
416 p->default_config = NSS_DEFCONF_PUBLICKEY;
417 }
418
419
420 static int
getkeys_ldap_g(int * err,char * netname,char * pkey,int pkeylen,char * skey,int skeylen,char * passwd,keylen_t keylen,algtype_t algtype)421 getkeys_ldap_g(
422 int *err, /* in */
423 char *netname, /* in */
424 char *pkey, /* out */
425 int pkeylen, /* in */
426 char *skey, /* out */
427 int skeylen, /* in */
428 char *passwd, /* in */
429 keylen_t keylen, /* in */
430 algtype_t algtype) /* in */
431 {
432 int r = 0;
433 char *p;
434 char keytypename[NIS_MAXNAMELEN+1];
435 int len;
436 const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
437 int rc = 0;
438 nss_XbyY_args_t arg;
439 nss_XbyY_buf_t *buf = NULL;
440 char *keyval;
441
442 NSS_XbyY_ALLOC(&buf, 0, NSS_BUFLEN_PUBLICKEY);
443
444 NSS_XbyY_INIT(&arg, buf->result, buf->buffer, buf->buflen, str2key);
445 arg.key.pkey.name = netname;
446
447 /*
448 * LDAP stores the public and secret key info in entries using
449 * nisKeyObject objectclass. Each key is tagged with the
450 * keytype, keylength, and algorithm. The tag has the following
451 * format: {<keytype><keylength>-<algorithm>}. For example,
452 * {DH192-0}.
453 */
454 if (classic_des)
455 (void) strcpy(keytypename, "{DH192-0}");
456 else
457 (void) sprintf(keytypename, "{%s%d-%d}",
458 dh_caps_str, keylen, algtype);
459 arg.key.pkey.keytype = keytypename;
460
461 if (nss_search(&db_root, _nss_initf_publickey, NSS_DBOP_KEYS_BYNAME,
462 &arg) != NSS_SUCCESS) {
463 NSS_XbyY_FREE(&buf);
464 *err = __NSW_NOTFOUND;
465 return (0);
466 }
467 keyval = buf->buffer;
468 p = strchr(keyval, ':');
469 if (p == NULL) {
470 NSS_XbyY_FREE(&buf);
471 *err = __NSW_NOTFOUND;
472 return (0);
473 }
474 *p = 0;
475 if (pkey) {
476 len = strlen(keyval);
477 if (len > HEXKEYBYTES) {
478 NSS_XbyY_FREE(&buf);
479 *err = __NSW_NOTFOUND;
480 return (0);
481 }
482 (void) strcpy(pkey, keyval);
483 }
484 r = 1;
485 p++;
486 if (skey && extract_secret(p, skey, passwd))
487 r |= 2;
488 NSS_XbyY_FREE(&buf);
489 *err = __NSW_SUCCESS;
490 return (r);
491 }
492
493
494 /*
495 * Convert a netname to a name we will hash on. For classic_des,
496 * just copy netname as is. But for new and improved ("now in
497 * new longer sizes!") DHEXT, add a ":keylen-algtype" suffix to hash on.
498 *
499 * Returns the hashname string on success or NULL on failure.
500 */
501 static char *
netname2hashname(const char * netname,char * hashname,int bufsiz,keylen_t keylen,algtype_t algtype)502 netname2hashname(
503 const char *netname,
504 char *hashname,
505 int bufsiz,
506 keylen_t keylen,
507 algtype_t algtype)
508 {
509 const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
510
511 if (!netname || !hashname || !bufsiz)
512 return (NULL);
513
514 if (classic_des) {
515 if (bufsiz > strlen(netname))
516 (void) strcpy(hashname, netname);
517 else
518 return (NULL);
519 } else {
520 char tmp[128];
521 (void) sprintf(tmp, ":%d-%d", keylen, algtype);
522 if (bufsiz > (strlen(netname) + strlen(tmp)))
523 (void) sprintf(hashname, "%s%s", netname, tmp);
524 else
525 return (NULL);
526 }
527
528 return (hashname);
529 }
530
531 /*
532 * Flush netname's publickey of the given key length and algorithm type.
533 */
534 void
__getpublickey_flush_g(const char * netname,keylen_t keylen,algtype_t algtype)535 __getpublickey_flush_g(const char *netname, keylen_t keylen, algtype_t algtype)
536 {
537 char hashname[MAXNETNAMELEN+1];
538
539 (void) netname2hashname(netname, hashname, MAXNETNAMELEN, keylen,
540 algtype);
541 }
542
543 /*
544 * Generic DH (any size keys) version of __getpublickey_cached.
545 */
546 int
__getpublickey_cached_g(const char netname[],keylen_t keylen,algtype_t algtype,char * pkey,size_t pkeylen,int * from_cache)547 __getpublickey_cached_g(const char netname[], /* in */
548 keylen_t keylen, /* in */
549 algtype_t algtype, /* in */
550 char *pkey, /* out */
551 size_t pkeylen, /* in */
552 int *from_cache) /* in/out */
553 {
554 int needfree = 1, res, err;
555 struct __nsw_switchconfig *conf;
556 struct __nsw_lookup *look;
557 enum __nsw_parse_err perr;
558 const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
559 int retry_cache = 0;
560
561 if (!netname || !pkey)
562 return (0);
563
564 conf = __nsw_getconfig("publickey", &perr);
565 if (!conf) {
566 conf = &publickey_default;
567 needfree = 0;
568 }
569 for (look = conf->lookups; look; look = look->next) {
570 if (strcmp(look->service_name, "ldap") == 0) {
571 res = getkeys_ldap_g(&err, (char *)netname,
572 pkey, pkeylen, NULL, 0, NULL,
573 keylen, algtype);
574 /* long DH keys will not be in nis or files */
575 } else if (classic_des &&
576 strcmp(look->service_name, "nis") == 0)
577 res = getkeys_nis(&err, (char *)netname, pkey,
578 NULL, NULL);
579 else if (classic_des &&
580 strcmp(look->service_name, "files") == 0)
581 res = getkeys_files(&err, (char *)netname, pkey,
582 NULL, NULL);
583 else {
584 syslog(LOG_INFO, "Unknown publickey nameservice '%s'",
585 look->service_name);
586 err = __NSW_UNAVAIL;
587 res = 0;
588 }
589
590 switch (look->actions[err]) {
591 case __NSW_CONTINUE :
592 continue;
593 case __NSW_RETURN :
594 if (needfree)
595 __nsw_freeconfig(conf);
596 return ((res & 1) != 0);
597 default :
598 syslog(LOG_INFO, "Unknown action for nameservice %s",
599 look->service_name);
600 }
601 }
602
603 if (needfree)
604 __nsw_freeconfig(conf);
605 return (0);
606 }
607
608
609
610 /*
611 * Generic (all sizes) DH version of getpublickey.
612 */
613 int
getpublickey_g(const char * netname,int keylen,int algtype,char * pkey,size_t pkeylen)614 getpublickey_g(
615 const char *netname, /* in */
616 int keylen, /* in */
617 int algtype, /* in */
618 char *pkey, /* out */
619 size_t pkeylen) /* in */
620 {
621 return (__getpublickey_cached_g(netname, keylen, algtype, pkey,
622 pkeylen, (int *)0));
623 }
624
625 /*
626 * Generic (all sizes) DH version of getsecretkey_g.
627 */
628 int
getsecretkey_g(const char * netname,keylen_t keylen,algtype_t algtype,char * skey,size_t skeylen,const char * passwd)629 getsecretkey_g(
630 const char *netname, /* in */
631 keylen_t keylen, /* in */
632 algtype_t algtype, /* in */
633 char *skey, /* out */
634 size_t skeylen, /* in */
635 const char *passwd) /* in */
636 {
637 int needfree = 1, res, err;
638 struct __nsw_switchconfig *conf;
639 struct __nsw_lookup *look;
640 enum __nsw_parse_err perr;
641 const bool_t classic_des = AUTH_DES_KEY(keylen, algtype);
642
643 if (!netname || !skey || !skeylen)
644 return (0);
645
646 conf = __nsw_getconfig("publickey", &perr);
647
648 if (!conf) {
649 conf = &publickey_default;
650 needfree = 0;
651 }
652
653 for (look = conf->lookups; look; look = look->next) {
654 if (strcmp(look->service_name, "ldap") == 0)
655 res = getkeys_ldap_g(&err, (char *)netname,
656 NULL, 0, skey, skeylen,
657 (char *)passwd, keylen, algtype);
658 /* long DH keys will not be in nis or files */
659 else if (classic_des && strcmp(look->service_name, "nis") == 0)
660 res = getkeys_nis(&err, (char *)netname,
661 NULL, skey, (char *)passwd);
662 else if (classic_des &&
663 strcmp(look->service_name, "files") == 0)
664 res = getkeys_files(&err, (char *)netname,
665 NULL, skey, (char *)passwd);
666 else {
667 syslog(LOG_INFO, "Unknown publickey nameservice '%s'",
668 look->service_name);
669 err = __NSW_UNAVAIL;
670 res = 0;
671 }
672 switch (look->actions[err]) {
673 case __NSW_CONTINUE :
674 continue;
675 case __NSW_RETURN :
676 if (needfree)
677 __nsw_freeconfig(conf);
678 return ((res & 2) != 0);
679 default :
680 syslog(LOG_INFO, "Unknown action for nameservice %s",
681 look->service_name);
682 }
683 }
684 if (needfree)
685 __nsw_freeconfig(conf);
686 return (0);
687 }
688