xref: /illumos-gate/usr/src/lib/libnsl/ipsec/algs.c (revision 004388eb)
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 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include "mt.h"
30 #include <sys/types.h>
31 #include <sys/errno.h>
32 #include <sys/stat.h>
33 #include <ipsec_util.h>
34 #include <netdb.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <synch.h>
38 #include <string.h>
39 #include <strings.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <syslog.h>
43 
44 /* Globals... */
45 static rwlock_t proto_rw = DEFAULTRWLOCK; /* Protects cached algorithm list. */
46 static time_t proto_last_update;
47 static ipsec_proto_t *protos;
48 static int num_protos;
49 
50 void
51 _clean_trash(ipsec_proto_t *proto, int num)
52 {
53 	int alg_offset;
54 
55 	if (proto == NULL)
56 		return;
57 
58 	while (num-- != 0) {
59 		free(proto[num].proto_name);
60 		free(proto[num].proto_pkg);
61 		for (alg_offset = 0; alg_offset < proto[num].proto_numalgs;
62 		    alg_offset++)
63 			freeipsecalgent(proto[num].proto_algs[alg_offset]);
64 		free(proto[num].proto_algs);
65 		for (alg_offset = 0; alg_offset < proto[num].proto_algs_npkgs;
66 		    alg_offset++)
67 			free(proto[num].proto_algs_pkgs[alg_offset].pkg_name);
68 		free(proto[num].proto_algs_pkgs);
69 	}
70 
71 	free(proto);
72 }
73 
74 static const char *pipechar = "|";
75 static const char *comma = ",";
76 static const char *dash = "-";
77 static const char *slash = "/";
78 
79 /*
80  * Returns >= 0 if success (and > 0 means "increment").
81  * Returns -1 if failure.
82  */
83 static int
84 build_keysizes(int **sizep, char *input_string)
85 {
86 	char *lasts, *token;
87 	int *key_sizes = NULL, num_sizes, key_low, key_high, key_default;
88 	int key_increment = 0;
89 
90 	/*
91 	 * Okay, let's check the format of the key string.  It'll be either:
92 	 *
93 	 * enumeration: size1,size2...,sizeN
94 	 * range: defaultSize/sizeLow-sizeHi,increment
95 	 *
96 	 * In the case of an enumeration, the default key size is the
97 	 * first one in the list.
98 	 */
99 
100 	if (strchr(input_string, '/') != NULL) {
101 		/* key sizes specified by range */
102 
103 		/* default */
104 		token = strtok_r(input_string, slash, &lasts);
105 		if (token == NULL || (key_default = atoi(token)) == 0)
106 			return (-1);
107 
108 		/* low */
109 		token = strtok_r(NULL, dash, &lasts);
110 		if (token == NULL || (key_low = atoi(token)) == 0)
111 			return (-1);
112 
113 		/* high */
114 		token = strtok_r(NULL, comma, &lasts);
115 		if (token == NULL || (key_high = atoi(token)) == 0 ||
116 		    key_high <= key_low)
117 			return (-1);
118 
119 		/* increment */
120 		token = strtok_r(NULL, "", &lasts);
121 		if (token == NULL || (key_increment = atoi(token)) == 0)
122 			return (-1);
123 
124 		key_sizes = (int *)malloc(LIBIPSEC_ALGS_KEY_NUM_VAL *
125 		    sizeof (int));
126 		if (key_sizes == NULL)
127 			return (-1);
128 
129 		key_sizes[LIBIPSEC_ALGS_KEY_DEF_IDX] = key_default;
130 		key_sizes[LIBIPSEC_ALGS_KEY_MIN_IDX] = key_low;
131 		key_sizes[LIBIPSEC_ALGS_KEY_MAX_IDX] = key_high;
132 		key_sizes[LIBIPSEC_ALGS_KEY_MAX_IDX + 1] = 0;
133 	} else {
134 		/* key sizes specified by enumeration */
135 
136 		key_sizes = (int *)malloc(sizeof (int));
137 		if (key_sizes == NULL)
138 			return (-1);
139 		num_sizes = 0;
140 
141 		token = strtok_r(input_string, comma, &lasts);
142 		if (token == NULL || key_sizes == NULL)
143 			return (-1);
144 		*key_sizes = 0;
145 		do {
146 			int *nks;
147 
148 			nks = (int *)realloc(key_sizes,
149 			    sizeof (int) * ((++num_sizes) + 1));
150 			if (nks == NULL) {
151 				free(key_sizes);
152 				return (-1);
153 			}
154 			key_sizes = nks;
155 			/* Can't check for atoi() == 0 here... */
156 			key_sizes[num_sizes - 1] = atoi(token);
157 			key_sizes[num_sizes] = 0;
158 		} while ((token = strtok_r(NULL, comma, &lasts)) != NULL);
159 	}
160 
161 	*sizep = key_sizes;
162 
163 	return (key_increment);
164 }
165 
166 /*
167  * Find the execution mode corresponding to the given string.
168  * Returns 0 on success, -1 on failure.
169  */
170 int
171 _str_to_ipsec_exec_mode(char *str, ipsecalgs_exec_mode_t *exec_mode)
172 {
173 	if (strcmp(str, "sync") == 0) {
174 		*exec_mode = LIBIPSEC_ALGS_EXEC_SYNC;
175 		return (0);
176 	} else if (strcmp(str, "async") == 0) {
177 		*exec_mode = LIBIPSEC_ALGS_EXEC_ASYNC;
178 		return (0);
179 	}
180 
181 	return (-1);
182 }
183 
184 /*
185  * Given a file pointer, read all the text from the file and convert it into
186  * a bunch of ipsec_proto_t's, each with an array of struct ipsecalgent
187  * pointers - one for each algorithm.
188  */
189 static ipsec_proto_t *
190 build_list(FILE *f, int *num)
191 {
192 	char line[1024];
193 	char *token, *lasts, *alg_names, *ef_name, *key_string, *block_string;
194 	char *proto_name;
195 	ipsec_proto_t *rc = NULL, *new_proto = NULL;
196 	int *block_sizes, *key_sizes;
197 	int rc_num = 0, key_increment;
198 	int new_num, alg_num, num_sizes;
199 	struct ipsecalgent *curalg, **newalglist;
200 	char cur_pkg[1024];
201 	boolean_t doing_pkg = B_FALSE;
202 	ipsecalgs_exec_mode_t exec_mode;
203 	char diag_buf[128];
204 
205 	diag_buf[0] = '\0';
206 
207 	while (fgets(line, sizeof (line), f) != NULL) {
208 		if (strncasecmp(line, LIBIPSEC_ALGS_LINE_PROTO,
209 		    sizeof (LIBIPSEC_ALGS_LINE_PROTO) - 1) != 0 &&
210 		    strncasecmp(line, LIBIPSEC_ALGS_LINE_ALG,
211 		    sizeof (LIBIPSEC_ALGS_LINE_ALG) - 1) != 0 &&
212 		    strncasecmp(line, LIBIPSEC_ALGS_LINE_PKGSTART,
213 		    sizeof (LIBIPSEC_ALGS_LINE_PKGSTART) - 1) != 0 &&
214 		    strncasecmp(line, LIBIPSEC_ALGS_LINE_PKGEND,
215 		    sizeof (LIBIPSEC_ALGS_LINE_PKGEND) - 1) != 0) {
216 			if ((token = strtok_r(line, " \t\n", &lasts)) == NULL ||
217 			    token[0] == '#') {
218 				continue;
219 			} else {
220 				(void) snprintf(diag_buf, sizeof (diag_buf),
221 					"non-recognized start of line");
222 				goto bail;
223 			}
224 		}
225 
226 		if (strncasecmp(line, LIBIPSEC_ALGS_LINE_PROTO,
227 		    sizeof (LIBIPSEC_ALGS_LINE_PROTO) - 1) == 0) {
228 			/* current line defines a new protocol */
229 
230 			/* skip the protocol token */
231 			token = strtok_r(line, pipechar, &lasts);
232 
233 			/* protocol number */
234 			token = strtok_r(NULL, pipechar, &lasts);
235 			if (token == NULL || (new_num = atoi(token)) == 0) {
236 				(void) snprintf(diag_buf, sizeof (diag_buf),
237 				    "invalid protocol number");
238 				goto bail;
239 			}
240 
241 			/* protocol name */
242 			token = strtok_r(NULL, pipechar, &lasts);
243 			if (token == NULL) {
244 				(void) snprintf(diag_buf, sizeof (diag_buf),
245 				    "cannot read protocol name");
246 				goto bail;
247 			}
248 			proto_name = token;
249 
250 			/* execution mode */
251 			token = strtok_r(NULL, pipechar, &lasts);
252 			if (token == NULL) {
253 				(void) snprintf(diag_buf, sizeof (diag_buf),
254 				    "cannot read execution mode");
255 				goto bail;
256 			}
257 			/* remove trailing '\n' */
258 			token[strlen(token) - 1] = '\0';
259 			if (_str_to_ipsec_exec_mode(token, &exec_mode) != 0) {
260 				(void) snprintf(diag_buf, sizeof (diag_buf),
261 				    "invalid execution mode: \"%s\"", token);
262 				goto bail;
263 			}
264 
265 			/* initialize protocol structure */
266 			rc_num++;
267 			new_proto = (ipsec_proto_t *)realloc(rc,
268 			    sizeof (ipsec_proto_t) * rc_num);
269 			rc = new_proto;
270 			if (new_proto == NULL)
271 				goto bail;
272 			new_proto += (rc_num - 1);
273 			new_proto->proto_num = new_num;
274 			new_proto->proto_algs = NULL;
275 			new_proto->proto_numalgs = 0;
276 			new_proto->proto_name = strdup(proto_name);
277 			if (new_proto->proto_name == NULL)
278 				goto bail;
279 			new_proto->proto_exec_mode = exec_mode;
280 
281 			if (doing_pkg) {
282 				/* record proto as being part of current pkg */
283 				new_proto->proto_pkg = strdup(cur_pkg);
284 				if (new_proto->proto_pkg == NULL)
285 					goto bail;
286 			} else {
287 				new_proto->proto_pkg = NULL;
288 			}
289 
290 			new_proto->proto_algs_pkgs = NULL;
291 			new_proto->proto_algs_npkgs = 0;
292 
293 		} else if (strncasecmp(line, LIBIPSEC_ALGS_LINE_ALG,
294 		    sizeof (LIBIPSEC_ALGS_LINE_ALG) - 1) == 0) {
295 			/* current line defines a new algorithm */
296 
297 			/* skip the algorithm token */
298 			token = strtok_r(line, pipechar, &lasts);
299 
300 			/* protocol number */
301 			token = strtok_r(NULL, pipechar, &lasts);
302 			if (token == NULL || (new_num = atoi(token)) == 0) {
303 				(void) snprintf(diag_buf, sizeof (diag_buf),
304 				    "invalid algorithm number");
305 				goto bail;
306 			}
307 
308 			/* We can be O(N) for now.  There aren't that many. */
309 			for (new_proto = rc; new_proto < (rc + new_num);
310 			    new_proto++)
311 				if (new_proto->proto_num == new_num)
312 					break;
313 			if (new_proto == (rc + new_num)) {
314 				(void) snprintf(diag_buf, sizeof (diag_buf),
315 				    "invalid protocol number %d for algorithm",
316 				    new_num);
317 				goto bail;
318 			}
319 
320 			/* algorithm number */
321 			token = strtok_r(NULL, pipechar, &lasts);
322 			if (token == NULL) {
323 				(void) snprintf(diag_buf, sizeof (diag_buf),
324 				    "cannot read algorithm number");
325 				goto bail;
326 			}
327 			/* Can't check for 0 here. */
328 			alg_num = atoi(token);
329 
330 			/* algorithm names */
331 			token = strtok_r(NULL, pipechar, &lasts);
332 			if (token == NULL) {
333 				(void) snprintf(diag_buf, sizeof (diag_buf),
334 				    "cannot read algorithm number");
335 				goto bail;
336 			}
337 			alg_names = token;
338 
339 			/* mechanism name */
340 			token = strtok_r(NULL, pipechar, &lasts);
341 			if (token == NULL) {
342 				(void) snprintf(diag_buf, sizeof (diag_buf),
343 				    "cannot read mechanism name for alg %d "
344 				    "(proto %d)", alg_num,
345 				    new_proto->proto_num);
346 				goto bail;
347 			}
348 			ef_name = token;
349 
350 			/* key sizes */
351 			token = strtok_r(NULL, pipechar, &lasts);
352 			if (token == NULL) {
353 				(void) snprintf(diag_buf, sizeof (diag_buf),
354 				    "cannot read key sizes for alg %d "
355 				    "(proto %d)", alg_num,
356 				    new_proto->proto_num);
357 				goto bail;
358 			}
359 			key_string = token;
360 
361 			/* block sizes */
362 			token = strtok_r(NULL, pipechar, &lasts);
363 			if (token == NULL) {
364 				(void) snprintf(diag_buf, sizeof (diag_buf),
365 				    "cannot read mechanism name for alg %d "
366 				    "(proto %d)", alg_num,
367 				    new_proto->proto_num);
368 				goto bail;
369 			}
370 			block_string = token;
371 
372 			/* extract key sizes */
373 			key_increment = build_keysizes(&key_sizes, key_string);
374 			if (key_increment == -1) {
375 				(void) snprintf(diag_buf, sizeof (diag_buf),
376 				    "invalid key sizes for alg %d (proto %d)",
377 				    alg_num, new_proto->proto_num);
378 				goto bail;
379 			}
380 
381 			/* extract block sizes */
382 			block_sizes = (int *)malloc(sizeof (int));
383 			if (block_sizes == NULL) {
384 				free(key_sizes);
385 				goto bail;
386 			}
387 			num_sizes = 0;
388 			token = strtok_r(block_string, comma, &lasts);
389 			if (token == NULL) {
390 				(void) snprintf(diag_buf, sizeof (diag_buf),
391 				    "invalid block sizes for alg %d (proto %d)",
392 				    alg_num, new_proto->proto_num);
393 				free(key_sizes);
394 				goto bail;
395 			}
396 			*block_sizes = 0;
397 			do {
398 				int *nbk;
399 
400 				nbk = (int *)realloc(block_sizes,
401 				    sizeof (int) * ((++num_sizes) + 1));
402 				if (nbk == NULL) {
403 					free(key_sizes);
404 					free(block_sizes);
405 					goto bail;
406 				}
407 				block_sizes = nbk;
408 				/* Can't check for 0 here... */
409 				block_sizes[num_sizes - 1] = atoi(token);
410 				block_sizes[num_sizes] = 0;
411 			} while ((token = strtok_r(NULL, comma, &lasts)) !=
412 			    NULL);
413 
414 			/* Allocate a new struct ipsecalgent. */
415 			curalg = (struct ipsecalgent *)calloc(
416 			    sizeof (struct ipsecalgent), 1);
417 			if (curalg == NULL) {
418 				free(key_sizes);
419 				free(block_sizes);
420 				goto bail;
421 			}
422 			curalg->a_proto_num = new_num;
423 			curalg->a_alg_num = alg_num;
424 			curalg->a_block_sizes = block_sizes;
425 			curalg->a_key_sizes = key_sizes;
426 			curalg->a_key_increment = key_increment;
427 			if ((curalg->a_mech_name = strdup(ef_name)) == NULL) {
428 				freeipsecalgent(curalg);
429 				goto bail;
430 			}
431 			/* Set names. */
432 			curalg->a_names = (char **)malloc(sizeof (char *));
433 			num_sizes = 0;	/* Recycle "sizes" */
434 			token = strtok_r(alg_names, comma, &lasts);
435 			if (curalg->a_names == NULL || token == NULL) {
436 				freeipsecalgent(curalg);
437 				goto bail;
438 			}
439 			do {
440 				char **nnames;
441 
442 				nnames = (char **)realloc(curalg->a_names,
443 				    sizeof (char *) * ((++num_sizes) + 1));
444 				if (nnames == NULL) {
445 					freeipsecalgent(curalg);
446 					goto bail;
447 				}
448 				curalg->a_names = nnames;
449 				curalg->a_names[num_sizes] = NULL;
450 				curalg->a_names[num_sizes - 1] =
451 				    strdup(token);
452 				if (curalg->a_names[num_sizes - 1] == NULL) {
453 					freeipsecalgent(curalg);
454 					goto bail;
455 				}
456 			} while ((token = strtok_r(NULL, comma, &lasts)) !=
457 			    NULL);
458 
459 			if (doing_pkg) {
460 				/* record alg as being part of current pkg */
461 				int npkgs = new_proto->proto_algs_npkgs;
462 
463 				new_proto->proto_algs_pkgs = realloc(
464 				    new_proto->proto_algs_pkgs,
465 				    (npkgs + 1) * sizeof (ipsecalgs_pkg_t));
466 				if (new_proto->proto_algs_pkgs == NULL)
467 					goto bail;
468 
469 				new_proto->proto_algs_pkgs[npkgs].alg_num =
470 					curalg->a_alg_num;
471 				new_proto->proto_algs_pkgs[npkgs].pkg_name =
472 					strdup(cur_pkg);
473 				if (new_proto->proto_algs_pkgs[npkgs].pkg_name
474 				    == NULL)
475 					goto bail;
476 
477 				new_proto->proto_algs_npkgs = npkgs + 1;
478 			}
479 
480 			/* add new alg to protocol */
481 			newalglist = realloc(new_proto->proto_algs,
482 			    (new_proto->proto_numalgs + 1) *
483 			    sizeof (struct ipsecalgent *));
484 			if (newalglist == NULL) {
485 				freeipsecalgent(curalg);
486 				goto bail;
487 			}
488 			newalglist[new_proto->proto_numalgs] = curalg;
489 			new_proto->proto_numalgs++;
490 			new_proto->proto_algs = newalglist;
491 
492 		} else if (strncasecmp(line, LIBIPSEC_ALGS_LINE_PKGSTART,
493 		    sizeof (LIBIPSEC_ALGS_LINE_PKGSTART) - 1) == 0) {
494 			/* start of package delimiter */
495 			if (doing_pkg) {
496 				(void) snprintf(diag_buf, sizeof (diag_buf),
497 				    "duplicate package start delimiters");
498 				goto bail;
499 			}
500 			(void) strncpy(cur_pkg, line +
501 			    (sizeof (LIBIPSEC_ALGS_LINE_PKGSTART) - 1),
502 			    sizeof (cur_pkg));
503 			/* remove trailing '\n' */
504 			cur_pkg[strlen(cur_pkg) - 1] = '\0';
505 			doing_pkg = B_TRUE;
506 
507 		} else {
508 			/* end of package delimiter */
509 			char tmp_pkg[1024];
510 
511 			if (!doing_pkg) {
512 				(void) snprintf(diag_buf, sizeof (diag_buf),
513 				    "end package delimiter without start");
514 				goto bail;
515 			}
516 			/*
517 			 * Get specified pkg name, fail if it doesn't match
518 			 * the package specified by the last # Begin.
519 			 */
520 			(void) strncpy(tmp_pkg, line +
521 			    (sizeof (LIBIPSEC_ALGS_LINE_PKGEND) - 1),
522 			    sizeof (tmp_pkg));
523 			/* remove trailing '\n' */
524 			tmp_pkg[strlen(tmp_pkg) - 1] = '\0';
525 			if (strncmp(cur_pkg, tmp_pkg, sizeof (cur_pkg)) != 0)
526 				goto bail;
527 			doing_pkg = B_FALSE;
528 		}
529 	}
530 
531 	*num = rc_num;
532 	return (rc);
533 
534 bail:
535 	if (strlen(diag_buf) > 0) {
536 		syslog(LOG_ERR, "possibly corrupt %s file: %s\n",
537 		    INET_IPSECALGSFILE, diag_buf);
538 	}
539 	_clean_trash(rc, rc_num);
540 	return (NULL);
541 }
542 
543 /*
544  * If alg_context is NULL, update the library's cached copy of
545  * INET_IPSECALGSFILE.  If alg_context is non-NULL, hang a
546  * library-internal representation of a cached copy.  The latter is useful
547  * for routines in libipsecutil that _write_ the contents out.
548  */
549 void
550 _build_internal_algs(ipsec_proto_t **alg_context, int *alg_nums)
551 {
552 	FILE *f;
553 	int rc, trash_num;
554 	ipsec_proto_t *new_protos = NULL, *trash;
555 	time_t filetime;
556 	struct stat statbuf;
557 
558 	/*
559 	 * Construct new_protos from the file.
560 	 */
561 	if (alg_context == NULL) {
562 		/*
563 		 * Check the time w/o holding the lock.  This is just a
564 		 * cache reality check.  We'll do it again for real if this
565 		 * surface check fails.
566 		 */
567 		if (stat(INET_IPSECALGSFILE, &statbuf) == -1 ||
568 		    (statbuf.st_mtime < proto_last_update &&
569 			protos != NULL))
570 			return;
571 		(void) rw_wrlock(&proto_rw);
572 	}
573 
574 	f = fopen(INET_IPSECALGSFILE, "rF");
575 	if (f != NULL) {
576 		rc = fstat(fileno(f), &statbuf);
577 		if (rc != -1) {
578 			/*
579 			 * Update if the file is newer than our
580 			 * last cached copy.
581 			 */
582 			filetime = statbuf.st_mtime;
583 			if (alg_context != NULL ||
584 			    filetime > proto_last_update)
585 				new_protos = build_list(f, &rc);
586 		}
587 		/* Since f is read-only, can avoid all of the failures... */
588 		(void) fclose(f);
589 	}
590 
591 	if (alg_context == NULL) {
592 		/*
593 		 * If we have failed anywhere above, new_protoss will be NULL.
594 		 * This way, the previous cached protos will still be intact.
595 		 */
596 		if (new_protos != NULL) {
597 			proto_last_update = filetime;
598 			trash = protos;
599 			trash_num = num_protos;
600 			protos = new_protos;
601 			num_protos = rc;
602 		} else {
603 			/*
604 			 * Else the original protocols and algorithms lists
605 			 * remains the same.
606 			 */
607 			trash = NULL;
608 		}
609 		(void) rw_unlock(&proto_rw);
610 		_clean_trash(trash, trash_num);
611 	} else {
612 		/*
613 		 * Assume caller has done the appropriate locking,
614 		 * cleanup, etc.  And if new_protos is NULL, it's the caller's
615 		 * problem.
616 		 */
617 		*alg_context = new_protos;
618 		*alg_nums = rc;
619 	}
620 
621 }
622 
623 /*
624  * Assume input is 0-terminated.
625  */
626 static int *
627 duplicate_intarr(int *orig)
628 {
629 	size_t allocsize = sizeof (int);
630 	int *iwalker = orig;
631 
632 	if (orig == NULL)
633 		return (NULL);
634 
635 	while (*iwalker != 0) {
636 		allocsize += sizeof (int);
637 		iwalker++;
638 	}
639 
640 	iwalker = malloc(allocsize);
641 	if (iwalker != NULL)
642 		(void) memcpy(iwalker, orig, allocsize);
643 
644 	return (iwalker);
645 }
646 
647 /*
648  * Assume input is NULL terminated.
649  */
650 static char **
651 duplicate_strarr(char **orig)
652 {
653 	int i;
654 	char **swalker;
655 	char **newbie;
656 
657 	if (orig == NULL)
658 		return (NULL);
659 
660 	/* count number of elements in source array */
661 	for (swalker = orig; *swalker != NULL; swalker++);
662 
663 	/* use calloc() to get NULL-initialization */
664 	newbie = calloc(swalker - orig + 1, sizeof (char *));
665 
666 	if (newbie != NULL) {
667 		/* do the copy */
668 		for (i = 0; orig[i] != NULL; i++) {
669 			newbie[i] = strdup(orig[i]);
670 			if (newbie[i] == NULL) {
671 				for (swalker = newbie; *swalker != NULL;
672 				    swalker++)
673 					free(*swalker);
674 				free(newbie);
675 				return (NULL);
676 			}
677 		}
678 	}
679 
680 	return (newbie);
681 }
682 
683 struct ipsecalgent *
684 _duplicate_alg(struct ipsecalgent *orig)
685 {
686 	struct ipsecalgent *rc;
687 
688 	/* use calloc() to get NULL-initialization. */
689 	rc = calloc(1, sizeof (struct ipsecalgent));
690 	if (rc == NULL)
691 		return (NULL);
692 
693 	rc->a_proto_num = orig->a_proto_num;
694 	rc->a_alg_num = orig->a_alg_num;
695 	rc->a_key_increment = orig->a_key_increment;
696 	rc->a_mech_name = strdup(orig->a_mech_name);
697 	rc->a_block_sizes = duplicate_intarr(orig->a_block_sizes);
698 	rc->a_key_sizes = duplicate_intarr(orig->a_key_sizes);
699 	rc->a_names = duplicate_strarr(orig->a_names);
700 
701 	if (rc->a_mech_name == NULL || rc->a_block_sizes == NULL ||
702 	    rc->a_key_sizes == NULL || rc->a_names == NULL) {
703 		freeipsecalgent(rc);
704 		return (NULL);
705 	}
706 
707 	return (rc);
708 }
709 
710 /*
711  * Assume the rwlock is held for reading.
712  */
713 static ipsec_proto_t *
714 findprotobynum(int proto_num)
715 {
716 	int i;
717 
718 	for (i = 0; i < num_protos; i++) {
719 		if (protos[i].proto_num == proto_num)
720 			return (protos + i);
721 	}
722 
723 	return (NULL);
724 }
725 
726 static ipsec_proto_t *
727 findprotobyname(const char *name)
728 {
729 	int i;
730 
731 	if (name == NULL)
732 		return (NULL);
733 
734 	for (i = 0; i < num_protos; i++) {
735 		/* Can use strcasecmp because our proto_name is bounded. */
736 		if (strcasecmp(protos[i].proto_name, name) == 0)
737 			return (protos + i);
738 	}
739 
740 	return (NULL);
741 }
742 
743 int *
744 _real_getipsecprotos(int *nentries)
745 {
746 	int *rc, i;
747 
748 	if (nentries == NULL)
749 		return (NULL);
750 
751 	_build_internal_algs(NULL, NULL);
752 
753 	(void) rw_rdlock(&proto_rw);
754 	*nentries = num_protos;
755 	/*
756 	 * Allocate 1 byte if there are no protocols so a non-NULL return
757 	 * happens.
758 	 */
759 	rc = malloc((num_protos == 0) ? 1 : num_protos * sizeof (int));
760 	if (rc != NULL) {
761 		for (i = 0; i < num_protos; i++)
762 			rc[i] = protos[i].proto_num;
763 	}
764 	(void) rw_unlock(&proto_rw);
765 	return (rc);
766 }
767 
768 int *
769 _real_getipsecalgs(int *nentries, int proto_num)
770 {
771 	int *rc = NULL, i;
772 	ipsec_proto_t *proto;
773 
774 	if (nentries == NULL)
775 		return (NULL);
776 
777 	_build_internal_algs(NULL, NULL);
778 
779 	(void) rw_rdlock(&proto_rw);
780 	proto = findprotobynum(proto_num);
781 	if (proto != NULL) {
782 		*nentries = proto->proto_numalgs;
783 		/*
784 		 * Allocate 1 byte if there are no algorithms so a non-NULL
785 		 * return happens.
786 		 */
787 		rc = malloc((proto->proto_numalgs == 0) ? 1 :
788 		    proto->proto_numalgs * sizeof (int));
789 		if (rc != NULL) {
790 			for (i = 0; i < proto->proto_numalgs; i++)
791 				rc[i] = proto->proto_algs[i]->a_alg_num;
792 		}
793 	}
794 	(void) rw_unlock(&proto_rw);
795 	return (rc);
796 }
797 
798 struct ipsecalgent *
799 getipsecalgbyname(const char *name, int proto_num, int *errnop)
800 {
801 	ipsec_proto_t *proto;
802 	struct ipsecalgent *rc = NULL;
803 	int i, my_errno = ENOENT;
804 	char **name_check;
805 
806 	_build_internal_algs(NULL, NULL);
807 	if (name == NULL) {
808 		my_errno = EFAULT;
809 		goto bail;
810 	}
811 
812 	(void) rw_rdlock(&proto_rw);
813 	proto = findprotobynum(proto_num);
814 	if (proto != NULL) {
815 		for (i = 0; i < proto->proto_numalgs; i++) {
816 			for (name_check = proto->proto_algs[i]->a_names;
817 			    *name_check != NULL; name_check++) {
818 				/*
819 				 * Can use strcasecmp because our name_check
820 				 * is bounded.
821 				 */
822 				if (strcasecmp(*name_check, name) == 0) {
823 					/* found match */
824 					rc = _duplicate_alg(
825 					    proto->proto_algs[i]);
826 					my_errno = (rc == NULL) ? ENOMEM : 0;
827 					(void) rw_unlock(&proto_rw);
828 					goto bail;
829 				}
830 			}
831 		}
832 	} else {
833 		my_errno = EINVAL;
834 	}
835 
836 	(void) rw_unlock(&proto_rw);
837 bail:
838 	if (errnop != NULL)
839 		*errnop = my_errno;
840 	return (rc);
841 }
842 
843 struct ipsecalgent *
844 getipsecalgbynum(int alg_num, int proto_num, int *errnop)
845 {
846 	ipsec_proto_t *proto;
847 	struct ipsecalgent *rc = NULL;
848 	int i, my_errno = ENOENT;
849 
850 	_build_internal_algs(NULL, NULL);
851 
852 	(void) rw_rdlock(&proto_rw);
853 
854 	proto = findprotobynum(proto_num);
855 	if (proto != NULL) {
856 		for (i = 0; i < proto->proto_numalgs; i++) {
857 			if (proto->proto_algs[i]->a_alg_num == alg_num) {
858 				rc = _duplicate_alg(proto->proto_algs[i]);
859 				my_errno = (rc == NULL) ? ENOMEM : 0;
860 				break;
861 			}
862 		}
863 	} else {
864 		my_errno = EINVAL;
865 	}
866 
867 	(void) rw_unlock(&proto_rw);
868 	if (errnop != NULL)
869 		*errnop = my_errno;
870 	return (rc);
871 }
872 
873 int
874 getipsecprotobyname(const char *proto_name)
875 {
876 	int rc = -1;
877 	ipsec_proto_t *proto;
878 
879 	_build_internal_algs(NULL, NULL);
880 
881 	(void) rw_rdlock(&proto_rw);
882 	proto = findprotobyname(proto_name);
883 	if (proto != NULL)
884 		rc = proto->proto_num;
885 	(void) rw_unlock(&proto_rw);
886 	return (rc);
887 }
888 
889 char *
890 getipsecprotobynum(int proto_num)
891 {
892 	ipsec_proto_t *proto;
893 	char *rc = NULL;
894 
895 	_build_internal_algs(NULL, NULL);
896 
897 	(void) rw_rdlock(&proto_rw);
898 	proto = findprotobynum(proto_num);
899 	if (proto != NULL)
900 		rc = strdup(proto->proto_name);
901 
902 	(void) rw_unlock(&proto_rw);
903 	return (rc);
904 }
905 
906 void
907 freeipsecalgent(struct ipsecalgent *ptr)
908 {
909 	char **walker;
910 
911 	if (ptr == NULL)
912 		return;
913 
914 	if (ptr->a_names != NULL) {
915 		for (walker = ptr->a_names; *walker != NULL; walker++)
916 			free(*walker);
917 	}
918 
919 	/*
920 	 * Remember folks, free(NULL) works.
921 	 */
922 	free(ptr->a_names);
923 	free(ptr->a_mech_name);
924 	free(ptr->a_block_sizes);
925 	free(ptr->a_key_sizes);
926 	free(ptr);
927 }
928