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