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