xref: /illumos-gate/usr/src/lib/libnsl/ipsec/algs.c (revision 683007910b81ed9ff942db7c464d1b6df524048a)
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 2005 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 <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 = NULL;
553 	int fd, 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 	fd = open(INET_IPSECALGSFILE, O_RDONLY);
575 	if (fd != -1) {
576 		f = fdopen(fd, "r");
577 		if (f == NULL) {
578 			(void) close(fd);
579 		} else {
580 			rc = fstat(fd, &statbuf);
581 			if (rc != -1) {
582 				/*
583 				 * Update if the file is newer than our
584 				 * last cached copy.
585 				 */
586 				filetime = statbuf.st_mtime;
587 				if (alg_context != NULL ||
588 				    filetime > proto_last_update)
589 					new_protos = build_list(f, &rc);
590 			}
591 		}
592 	}
593 
594 	if (alg_context == NULL) {
595 		/*
596 		 * If we have failed anywhere above, new_protoss will be NULL.
597 		 * This way, the previous cached protos will still be intact.
598 		 */
599 		if (new_protos != NULL) {
600 			proto_last_update = filetime;
601 			trash = protos;
602 			trash_num = num_protos;
603 			protos = new_protos;
604 			num_protos = rc;
605 		} else {
606 			/*
607 			 * Else the original protocols and algorithms lists
608 			 * remains the same.
609 			 */
610 			trash = NULL;
611 		}
612 		(void) rw_unlock(&proto_rw);
613 		_clean_trash(trash, trash_num);
614 	} else {
615 		/*
616 		 * Assume caller has done the appropriate locking,
617 		 * cleanup, etc.  And if new_protos is NULL, it's the caller's
618 		 * problem.
619 		 */
620 		*alg_context = new_protos;
621 		*alg_nums = rc;
622 	}
623 
624 	/* Since f is read-only, can avoid all of the failures... */
625 	if (f != NULL)
626 		(void) fclose(f);
627 }
628 
629 /*
630  * Assume input is 0-terminated.
631  */
632 static int *
633 duplicate_intarr(int *orig)
634 {
635 	size_t allocsize = sizeof (int);
636 	int *iwalker = orig;
637 
638 	if (orig == NULL)
639 		return (NULL);
640 
641 	while (*iwalker != 0) {
642 		allocsize += sizeof (int);
643 		iwalker++;
644 	}
645 
646 	iwalker = malloc(allocsize);
647 	if (iwalker != NULL)
648 		(void) memcpy(iwalker, orig, allocsize);
649 
650 	return (iwalker);
651 }
652 
653 /*
654  * Assume input is NULL terminated.
655  */
656 static char **
657 duplicate_strarr(char **orig)
658 {
659 	int i;
660 	char **swalker;
661 	char **newbie;
662 
663 	if (orig == NULL)
664 		return (NULL);
665 
666 	/* count number of elements in source array */
667 	for (swalker = orig; *swalker != NULL; swalker++);
668 
669 	/* use calloc() to get NULL-initialization */
670 	newbie = calloc(swalker - orig + 1, sizeof (char *));
671 
672 	if (newbie != NULL) {
673 		/* do the copy */
674 		for (i = 0; orig[i] != NULL; i++) {
675 			newbie[i] = strdup(orig[i]);
676 			if (newbie[i] == NULL) {
677 				for (swalker = newbie; *swalker != NULL;
678 				    swalker++)
679 					free(*swalker);
680 				free(newbie);
681 				return (NULL);
682 			}
683 		}
684 	}
685 
686 	return (newbie);
687 }
688 
689 struct ipsecalgent *
690 _duplicate_alg(struct ipsecalgent *orig)
691 {
692 	struct ipsecalgent *rc;
693 
694 	/* use calloc() to get NULL-initialization. */
695 	rc = calloc(1, sizeof (struct ipsecalgent));
696 	if (rc == NULL)
697 		return (NULL);
698 
699 	rc->a_proto_num = orig->a_proto_num;
700 	rc->a_alg_num = orig->a_alg_num;
701 	rc->a_key_increment = orig->a_key_increment;
702 	rc->a_mech_name = strdup(orig->a_mech_name);
703 	rc->a_block_sizes = duplicate_intarr(orig->a_block_sizes);
704 	rc->a_key_sizes = duplicate_intarr(orig->a_key_sizes);
705 	rc->a_names = duplicate_strarr(orig->a_names);
706 
707 	if (rc->a_mech_name == NULL || rc->a_block_sizes == NULL ||
708 	    rc->a_key_sizes == NULL || rc->a_names == NULL) {
709 		freeipsecalgent(rc);
710 		return (NULL);
711 	}
712 
713 	return (rc);
714 }
715 
716 /*
717  * Assume the rwlock is held for reading.
718  */
719 static ipsec_proto_t *
720 findprotobynum(int proto_num)
721 {
722 	int i;
723 
724 	for (i = 0; i < num_protos; i++) {
725 		if (protos[i].proto_num == proto_num)
726 			return (protos + i);
727 	}
728 
729 	return (NULL);
730 }
731 
732 static ipsec_proto_t *
733 findprotobyname(const char *name)
734 {
735 	int i;
736 
737 	if (name == NULL)
738 		return (NULL);
739 
740 	for (i = 0; i < num_protos; i++) {
741 		/* Can use strcasecmp because our proto_name is bounded. */
742 		if (strcasecmp(protos[i].proto_name, name) == 0)
743 			return (protos + i);
744 	}
745 
746 	return (NULL);
747 }
748 
749 int *
750 _real_getipsecprotos(int *nentries)
751 {
752 	int *rc, i;
753 
754 	if (nentries == NULL)
755 		return (NULL);
756 
757 	_build_internal_algs(NULL, NULL);
758 
759 	(void) rw_rdlock(&proto_rw);
760 	*nentries = num_protos;
761 	/*
762 	 * Allocate 1 byte if there are no protocols so a non-NULL return
763 	 * happens.
764 	 */
765 	rc = malloc((num_protos == 0) ? 1 : num_protos * sizeof (int));
766 	if (rc != NULL) {
767 		for (i = 0; i < num_protos; i++)
768 			rc[i] = protos[i].proto_num;
769 	}
770 	(void) rw_unlock(&proto_rw);
771 	return (rc);
772 }
773 
774 int *
775 _real_getipsecalgs(int *nentries, int proto_num)
776 {
777 	int *rc = NULL, i;
778 	ipsec_proto_t *proto;
779 
780 	if (nentries == NULL)
781 		return (NULL);
782 
783 	_build_internal_algs(NULL, NULL);
784 
785 	(void) rw_rdlock(&proto_rw);
786 	proto = findprotobynum(proto_num);
787 	if (proto != NULL) {
788 		*nentries = proto->proto_numalgs;
789 		/*
790 		 * Allocate 1 byte if there are no algorithms so a non-NULL
791 		 * return happens.
792 		 */
793 		rc = malloc((proto->proto_numalgs == 0) ? 1 :
794 		    proto->proto_numalgs * sizeof (int));
795 		if (rc != NULL) {
796 			for (i = 0; i < proto->proto_numalgs; i++)
797 				rc[i] = proto->proto_algs[i]->a_alg_num;
798 		}
799 	}
800 	(void) rw_unlock(&proto_rw);
801 	return (rc);
802 }
803 
804 struct ipsecalgent *
805 getipsecalgbyname(const char *name, int proto_num, int *errnop)
806 {
807 	ipsec_proto_t *proto;
808 	struct ipsecalgent *rc = NULL;
809 	int i, my_errno = ENOENT;
810 	char **name_check;
811 
812 	_build_internal_algs(NULL, NULL);
813 	if (name == NULL) {
814 		my_errno = EFAULT;
815 		goto bail;
816 	}
817 
818 	(void) rw_rdlock(&proto_rw);
819 	proto = findprotobynum(proto_num);
820 	if (proto != NULL) {
821 		for (i = 0; i < proto->proto_numalgs; i++) {
822 			for (name_check = proto->proto_algs[i]->a_names;
823 			    *name_check != NULL; name_check++) {
824 				/*
825 				 * Can use strcasecmp because our name_check
826 				 * is bounded.
827 				 */
828 				if (strcasecmp(*name_check, name) == 0) {
829 					/* found match */
830 					rc = _duplicate_alg(
831 					    proto->proto_algs[i]);
832 					my_errno = (rc == NULL) ? ENOMEM : 0;
833 					(void) rw_unlock(&proto_rw);
834 					goto bail;
835 				}
836 			}
837 		}
838 	} else {
839 		my_errno = EINVAL;
840 	}
841 
842 	(void) rw_unlock(&proto_rw);
843 bail:
844 	if (errnop != NULL)
845 		*errnop = my_errno;
846 	return (rc);
847 }
848 
849 struct ipsecalgent *
850 getipsecalgbynum(int alg_num, int proto_num, int *errnop)
851 {
852 	ipsec_proto_t *proto;
853 	struct ipsecalgent *rc = NULL;
854 	int i, my_errno = ENOENT;
855 
856 	_build_internal_algs(NULL, NULL);
857 
858 	(void) rw_rdlock(&proto_rw);
859 
860 	proto = findprotobynum(proto_num);
861 	if (proto != NULL) {
862 		for (i = 0; i < proto->proto_numalgs; i++) {
863 			if (proto->proto_algs[i]->a_alg_num == alg_num) {
864 				rc = _duplicate_alg(proto->proto_algs[i]);
865 				my_errno = (rc == NULL) ? ENOMEM : 0;
866 				break;
867 			}
868 		}
869 	} else {
870 		my_errno = EINVAL;
871 	}
872 
873 	(void) rw_unlock(&proto_rw);
874 	if (errnop != NULL)
875 		*errnop = my_errno;
876 	return (rc);
877 }
878 
879 int
880 getipsecprotobyname(const char *proto_name)
881 {
882 	int rc = -1;
883 	ipsec_proto_t *proto;
884 
885 	_build_internal_algs(NULL, NULL);
886 
887 	(void) rw_rdlock(&proto_rw);
888 	proto = findprotobyname(proto_name);
889 	if (proto != NULL)
890 		rc = proto->proto_num;
891 	(void) rw_unlock(&proto_rw);
892 	return (rc);
893 }
894 
895 char *
896 getipsecprotobynum(int proto_num)
897 {
898 	ipsec_proto_t *proto;
899 	char *rc = NULL;
900 
901 	_build_internal_algs(NULL, NULL);
902 
903 	(void) rw_rdlock(&proto_rw);
904 	proto = findprotobynum(proto_num);
905 	if (proto != NULL)
906 		rc = strdup(proto->proto_name);
907 
908 	(void) rw_unlock(&proto_rw);
909 	return (rc);
910 }
911 
912 void
913 freeipsecalgent(struct ipsecalgent *ptr)
914 {
915 	char **walker;
916 
917 	if (ptr == NULL)
918 		return;
919 
920 	if (ptr->a_names != NULL) {
921 		for (walker = ptr->a_names; *walker != NULL; walker++)
922 			free(*walker);
923 	}
924 
925 	/*
926 	 * Remember folks, free(NULL) works.
927 	 */
928 	free(ptr->a_names);
929 	free(ptr->a_mech_name);
930 	free(ptr->a_block_sizes);
931 	free(ptr->a_key_sizes);
932 	free(ptr);
933 }
934