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