xref: /illumos-gate/usr/src/lib/libslp/clib/SLPUtils.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, 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  * Copyright 2004 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 /*
30  * Public utilities and convenience calls (from the API spec):
31  *	SLPFindScopes (queries for all known scopes)
32  *	SLPEscape / Unescape
33  *	SLPFree
34  *	SLPSet/GetProperty
35  */
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <syslog.h>
41 #include <netdb.h>
42 #include <unistd.h>
43 #include <libintl.h>
44 #include <slp-internal.h>
45 
46 struct scopes_tree {
47 	void *scopes;
48 	int len;
49 };
50 
51 typedef SLPBoolean SLPScopeCallback(SLPHandle, const char *, SLPError, void *);
52 
53 static SLPSrvURLCallback collate_scopes;
54 static void collect_scopes(void *, VISIT, int, void *);
55 static SLPBoolean unpackSAAdvert_scope(slp_handle_impl_t *, char *,
56 					SLPScopeCallback, void *,
57 					void **, int *);
58 static SLPError SAAdvert_for_scopes(SLPHandle, void **);
59 static SLPError slp_unescape(const char *, char **, SLPBoolean, const char);
60 
61 /*
62  * Finds scopes according the the user administrative model.
63  */
64 SLPError SLPFindScopes(SLPHandle hSLP, char **ppcScopes) {
65 	SLPError err;
66 	char *reply, *unesc_reply;
67 	void *stree = NULL;
68 	void *collator = NULL;
69 
70 	if (!hSLP || !ppcScopes) {
71 		return (SLP_PARAMETER_BAD);
72 	}
73 
74 	/* first try administratively configured scopes */
75 	if ((err = slp_administrative_scopes(ppcScopes, SLP_FALSE))
76 	    != SLP_OK) {
77 		return (err);
78 	}
79 
80 	if (*ppcScopes) {
81 	    /* got scopes */
82 	    return (SLP_OK);
83 	}
84 
85 	/* DAs from active and passive discovery */
86 	if ((err = slp_find_das("", &reply)) != SLP_OK &&
87 	    err != SLP_NETWORK_ERROR)
88 		return (err);
89 
90 	/* Unpack the reply */
91 	if (reply) {
92 		int numResults = 0;	/* placeholder; not actually used */
93 
94 		/* tag call as internal */
95 		((slp_handle_impl_t *)hSLP)->internal_call = SLP_TRUE;
96 
97 		(void) slp_unpackSrvReply(
98 			hSLP, reply, collate_scopes,
99 			&stree, &collator, &numResults);
100 		/* invoke last call */
101 		(void) slp_unpackSrvReply(
102 			hSLP, NULL, collate_scopes,
103 			&stree, &collator, &numResults);
104 		free(reply);
105 
106 		/* revert internal call tag */
107 		((slp_handle_impl_t *)hSLP)->internal_call = SLP_FALSE;
108 	}
109 
110 	/* Finally, if no results yet, try SA discovery */
111 	if (!stree) {
112 	    (void) SAAdvert_for_scopes(hSLP, &stree);
113 	}
114 
115 	if (!stree) {
116 		/* found none, so just return "default" */
117 		if (!(*ppcScopes = strdup("default"))) {
118 			slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory");
119 			return (SLP_MEMORY_ALLOC_FAILED);
120 		}
121 		return (SLP_OK);
122 	}
123 
124 	/* we now have a btree, each leaf of which is a unique scope */
125 	slp_twalk(stree, collect_scopes, 0, (void *) ppcScopes);
126 
127 	/* unescape scopes list */
128 	if ((err = slp_unescape(*ppcScopes, &unesc_reply, SLP_FALSE, '%'))
129 	    == SLP_OK) {
130 		free(*ppcScopes);
131 		*ppcScopes = unesc_reply;
132 	} else {
133 		free(unesc_reply);
134 	}
135 
136 	return (err);
137 }
138 
139 /*
140  * Finds scopes according to the adminstrative scoping model. A
141  * comma-seperated list of scopes is returned in *ppcScopes; the
142  * caller must free *ppcScopes.
143  * If the return_default parameter is true, and no scopes are found,
144  * *ppcScopes will be set to 'default', otherwise, *ppcScopes will
145  * be NULL. This helps simplify internal memory management.
146  */
147 SLPError slp_administrative_scopes(char **ppcScopes,
148 					SLPBoolean return_default) {
149 	const char *useScopes;
150 
151 	*ppcScopes = NULL;
152 
153 	/* @@@ first try DHCP */
154 	/* next try the useScopes property */
155 	useScopes = SLPGetProperty(SLP_CONFIG_USESCOPES);
156 
157 	if (useScopes && *useScopes) {
158 		if (!(*ppcScopes = strdup(useScopes))) {
159 			slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory");
160 			return (SLP_MEMORY_ALLOC_FAILED);
161 		}
162 		return (SLP_OK);
163 	}
164 
165 	/* found none, so just return "default" */
166 	if (return_default && !(*ppcScopes = strdup("default"))) {
167 		slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory");
168 		return (SLP_MEMORY_ALLOC_FAILED);
169 	}
170 	return (SLP_OK);
171 }
172 
173 /*
174  * This function operates on the same btree as the collate_scopes().
175  * The difference is that this one is called for each
176  * SAAdvert recieved.
177  */
178 /* ARGSUSED */
179 static SLPBoolean saadvert_callback(SLPHandle hp, char *scopes,
180 					SLPError err, void **stree) {
181 	char *s, *tstate;
182 
183 	if (err != SLP_OK) {
184 		return (SLP_TRUE);
185 	}
186 
187 	for (
188 		s = strtok_r((char *)scopes, ",", &tstate);
189 		s;
190 		s = strtok_r(NULL, ",", &tstate)) {
191 
192 		char *ascope, **srch;
193 
194 		if (!(ascope = strdup(s))) {	/* no memory! */
195 			slp_err(LOG_CRIT, 0, "collate_scopes",
196 				"out of memory");
197 			return (SLP_TRUE);
198 		}
199 
200 		srch = slp_tsearch(
201 			(void *) ascope, stree,
202 			(int (*)(const void *, const void *)) slp_strcasecmp);
203 		if (*srch != ascope)
204 			/* scope is already in there, so just free ascope */
205 			free(ascope);
206 	}
207 
208 	return (SLP_TRUE);
209 }
210 
211 /*
212  * Generates an SAAdvert solicitation, and returns any scopes found
213  * from all recieved SAAdverts in stree. stree must be a btree
214  * structure.
215  */
216 static SLPError SAAdvert_for_scopes(SLPHandle hSLP, void **stree) {
217 	SLPError err;
218 	SLPBoolean sync_state;
219 	slp_handle_impl_t *hp = (slp_handle_impl_t *)hSLP;
220 	char *predicate;
221 	const char *type_hint;
222 
223 	/* get type hint, if set */
224 	if ((type_hint = SLPGetProperty(SLP_CONFIG_TYPEHINT)) != NULL &&
225 		*type_hint != 0) {
226 
227 		size_t hintlen = strlen(type_hint);
228 		size_t predlen = strlen("(service-type=)");
229 
230 		/* check bounds */
231 		if (hintlen > (SLP_MAX_STRINGLEN - predlen)) {
232 			return (SLP_PARAMETER_BAD);
233 		}
234 		if (!(predicate = malloc(hintlen + predlen + 1))) {
235 			slp_err(LOG_CRIT, 0, "SAAdvert_for_scopes",
236 				"out of memory");
237 			return (SLP_MEMORY_ALLOC_FAILED);
238 		}
239 		(void) strcpy(predicate, "(service-type=");
240 		(void) strcat(predicate, type_hint);
241 		(void) strcat(predicate, ")");
242 	} else {
243 		predicate = "";
244 		type_hint = NULL;
245 	}
246 
247 	/* No callback for SLPFindScopes, so force synchronous mode only */
248 	sync_state = hp->async;
249 	hp->async = SLP_FALSE;
250 
251 	if ((err = slp_start_call(hp)) != SLP_OK)
252 		return (err);
253 
254 	err = slp_packSrvRqst("service:service-agent", predicate, hp);
255 
256 	if (err == SLP_OK)
257 		err = slp_ua_common(hSLP, "",
258 				    (SLPGenericAppCB *)saadvert_callback,
259 				    stree,
260 				    (SLPMsgReplyCB *)unpackSAAdvert_scope);
261 
262 	if (type_hint) {
263 		free(predicate);
264 	}
265 
266 	if (err != SLP_OK)
267 		slp_end_call(hp);
268 
269 	/* restore sync state */
270 	hp->async = sync_state;
271 
272 	return (err);
273 }
274 
275 /*
276  * Unpack an SAAdvert and pass each set of scopes into cb.
277  */
278 /* ARGSUSED */
279 static SLPBoolean unpackSAAdvert_scope(slp_handle_impl_t *hSLP, char *reply,
280 					SLPScopeCallback cb, void *cookie,
281 					void **collator, int *numResults) {
282 	char *surl, *scopes, *attrs;
283 	SLPBoolean cont;
284 
285 	if (!reply) {
286 		cb(hSLP, NULL, SLP_LAST_CALL, cookie);
287 		return (SLP_FALSE);
288 	}
289 
290 	/* tag call as internal; gets all scopes, regardless of maxResults */
291 	hSLP->internal_call = SLP_TRUE;
292 
293 	if (slp_unpackSAAdvert(reply, &surl, &scopes, &attrs) != SLP_OK) {
294 		return (SLP_TRUE);
295 	}
296 
297 	cont = cb(hSLP, scopes, SLP_OK, cookie);
298 
299 	/* revert internal_call tag */
300 	hSLP->internal_call = SLP_FALSE;
301 
302 	free(surl);
303 	free(scopes);
304 	free(attrs);
305 
306 	return (cont);
307 }
308 
309 /*
310  * Creates a service request for finding DAs or SAs (based on 'filter'),
311  * and sends it to slpd, returning the reply in 'reply'.
312  */
313 SLPError slp_find_das(const char *filter, char **reply) {
314 	SLPError err;
315 	char *msg, hostname[MAXHOSTNAMELEN];
316 
317 	/* Try the cache first */
318 	if (*reply = slp_find_das_cached(filter)) {
319 		return (SLP_OK);
320 	}
321 
322 	/*
323 	 * create a find scopes message:
324 	 * this is a SrvRqst for the type directory-agent.sun.
325 	 */
326 	/* use the local host's name for the scope */
327 	(void) gethostname(hostname, MAXHOSTNAMELEN);
328 
329 	err = slp_packSrvRqst_single(
330 		SLP_SUN_DA_TYPE, hostname, filter, &msg, "en");
331 
332 	if (err == SLP_OK) {
333 		err = slp_send2slpd(msg, reply);
334 		free(msg);
335 	}
336 
337 	/* Add the reply to the cache */
338 	if (err == SLP_OK) {
339 		slp_put_das_cached(filter, *reply, slp_get_length(*reply));
340 	}
341 
342 	return (err);
343 }
344 
345 /*
346  * This is called for each URL entry in the DA service reply (sun private).
347  * Contained within the cookie is a btree, to which it adds new
348  * scopes from the URL entry. The scopes are retrieved from the btree
349  * by traversing the tree in SLPFindScopes().
350  * SLPHandle h is NULL, so don't touch it!
351  */
352 /*ARGSUSED*/
353 static SLPBoolean collate_scopes(SLPHandle h, const char *u,
354 					unsigned short lifetime,
355 					SLPError errCode, void *cookie) {
356 	SLPSrvURL *surl;
357 	char *s, *tstate, *p, *url;
358 	void **collator = cookie;
359 
360 	if (errCode != SLP_OK)
361 		return (SLP_TRUE);
362 
363 	/* dup url so as not to corrupt da cache */
364 	if (!(url = strdup(u))) {
365 		slp_err(LOG_CRIT, 0, "collate_scopes", "out of memory");
366 		return (SLP_FALSE);
367 	}
368 
369 	/* parse url into a SLPSrvURL struct */
370 	if (SLPParseSrvURL(url, &surl) != SLP_OK)
371 		return (SLP_TRUE);	/* bad URL; skip it */
372 
373 	/* collate the scopes using the btree stree->scopes: */
374 	/* skip the 'scopes=' part */
375 	if (!(p = strchr(surl->s_pcSrvPart, '='))) {
376 		free(surl);
377 		return (SLP_TRUE);	/* bad URL; skip it */
378 	}
379 	p++;
380 
381 	for (
382 		s = strtok_r(p, ",", &tstate);
383 		s;
384 		s = strtok_r(NULL, ",", &tstate)) {
385 
386 		char *ascope, **srch;
387 
388 		if (!(ascope = strdup(s))) {	/* no memory! */
389 			slp_err(LOG_CRIT, 0, "collate_scopes",
390 				"out of memory");
391 			free(surl);
392 			return (SLP_TRUE);
393 		}
394 
395 		srch = slp_tsearch(
396 			(void *) ascope, collator,
397 			(int (*)(const void *, const void *)) slp_strcasecmp);
398 		if (*srch != ascope)
399 			/* scope is already in there, so just free ascope */
400 			free(ascope);
401 	}
402 
403 	free(url);
404 	free(surl);
405 
406 	return (SLP_TRUE);
407 }
408 
409 /*
410  * Each time we visit a node for the last time, copy that scope into
411  * the scope collection and free the scope string and the node.
412  */
413 /*ARGSUSED*/
414 static void collect_scopes(void *node, VISIT order, int level, void *cookie) {
415 	char **scopes = (char **)cookie;
416 
417 	if (order == endorder || order == leaf) {
418 		char *s = *(char **)node;
419 		slp_add2list(s, scopes, SLP_FALSE);
420 		free(s);
421 		free(node);
422 	}
423 }
424 
425 void SLPFree(void *pvMem) {
426 	if (pvMem)
427 		free(pvMem);
428 }
429 
430 /*
431  * Escape / Unescape
432  */
433 
434 #define	isBadTagChar(c)	((c) == '*' || (c) == '_' || \
435 			(c) == '\n' || (c) == '\t' || (c) == '\r')
436 
437 #define	isReserved(c)	((c) <= 31 || (c) == '(' || (c) == ')' || \
438 			(c) == ',' || (c) == '\\' || (c) == '!' || \
439 			(c) == '<' || (c) == '=' || (c) == '>' || \
440 			(c) == '~')
441 
442 SLPError SLPEscape(const char *pcInbuf, char **ppcOutBuf, SLPBoolean isTag) {
443 	char *buf, *pin, *pout;
444 
445 	if (!pcInbuf || !ppcOutBuf)
446 		return (SLP_PARAMETER_BAD);
447 
448 	if (!(buf = malloc(strlen(pcInbuf) * 3 + 1))) {
449 		slp_err(LOG_CRIT, 0, "SLPEscape", "out of memory");
450 		return (SLP_MEMORY_ALLOC_FAILED);
451 	}
452 	*ppcOutBuf = buf;
453 
454 	for (pin = (char *)pcInbuf, pout = buf; *pin; ) {
455 		int len;
456 
457 		/* If char is start of multibyte char, just copy it in */
458 		if ((len = mblen(pin, MB_CUR_MAX)) > 1) {
459 			int i;
460 			for (i = 0; i < len && *pin; i++)
461 				*pout++ = *pin++;
462 			continue;
463 		}
464 
465 		/* check for bad tag */
466 		if (isTag && isBadTagChar(*pin))
467 			return (SLP_PARSE_ERROR);
468 
469 		if (isReserved(*pin)) {
470 			if (isTag)
471 				return (SLP_PARSE_ERROR);
472 			(void) sprintf(pout, "\\%.2x", *pin);
473 			pout += 3;
474 			pin++;
475 		} else {
476 			*pout++ = *pin++;
477 		}
478 	}
479 	*pout = 0;
480 
481 	return (SLP_OK);
482 }
483 
484 SLPError SLPUnescape(const char *pcInbuf, char **ppcOutBuf, SLPBoolean isTag) {
485 	if (!pcInbuf || !ppcOutBuf)
486 		return (SLP_PARAMETER_BAD);
487 
488 	return (slp_unescape(pcInbuf, ppcOutBuf, isTag, '\\'));
489 }
490 
491 
492 /*
493  * The actual unescaping routine; allows for different escape chars.
494  */
495 static SLPError slp_unescape(const char *pcInbuf, char **ppcOutBuf,
496 				SLPBoolean isTag, const char esc_char) {
497 	char *buf, *pin, *pout, conv[3];
498 
499 	if (!(buf = malloc(strlen(pcInbuf) * 3 + 1))) {
500 		slp_err(LOG_CRIT, 0, "SLPEscape", "out of memory");
501 		return (SLP_MEMORY_ALLOC_FAILED);
502 	}
503 	*ppcOutBuf = buf;
504 
505 	conv[2] = 0;
506 	for (pin = (char *)pcInbuf, pout = buf; *pin; ) {
507 		int len;
508 
509 		/* If char is start of multibyte char, just copy it in */
510 		if ((len = mblen(pin, MB_CUR_MAX)) > 1) {
511 			int i;
512 			for (i = 0; i < len && *pin; i++)
513 				*pout++ = *pin++;
514 			continue;
515 		}
516 
517 		if (*pin == esc_char) {
518 			if (!pin[1] || !pin[2])
519 				return (SLP_PARSE_ERROR);
520 			pin++;
521 			conv[0] = *pin++;
522 			conv[1] = *pin++;
523 			*pout++ = (char)strtol(conv, NULL, 16);
524 			if (isTag && isBadTagChar(*pout))
525 				return (SLP_PARSE_ERROR);
526 		} else {
527 			*pout++ = *pin++;
528 		}
529 	}
530 	*pout = 0;
531 
532 	return (SLP_OK);
533 }
534 
535 /*
536  * Properties
537  *
538  * All properties are stored in a global tree (prop_table). This
539  * tree is created and accessed by slp_tsearch and slp_tfind.
540  */
541 struct prop_entry {
542 	const char *key, *val;
543 };
544 typedef struct prop_entry slp_prop_entry_t;
545 
546 /* Global properties table */
547 static void *slp_props = NULL;
548 static mutex_t prop_table_lock = DEFAULTMUTEX;
549 
550 static void setDefaults();
551 
552 static int compare_props(const void *a, const void *b) {
553 	return (strcmp(
554 		((slp_prop_entry_t *)a)->key,
555 		((slp_prop_entry_t *)b)->key));
556 }
557 
558 void SLPSetProperty(const char *pcName, const char *pcValue) {
559 	slp_prop_entry_t *pe, **pe2;
560 
561 	if (!slp_props) setDefaults();
562 
563 	if (!pcName || !pcValue) {
564 		return;
565 	}
566 
567 	if (!(pe = malloc(sizeof (*pe)))) {
568 		slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory");
569 		return;
570 	}
571 
572 	/* place the strings under library ownership */
573 	if (!(pe->key = strdup(pcName))) {
574 		free(pe);
575 		slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory");
576 		return;
577 	}
578 
579 	if (!(pe->val = strdup(pcValue))) {
580 		free((void *) pe->key);
581 		free(pe);
582 		slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory");
583 		return;
584 	}
585 
586 	/* is pcName already set? */
587 	(void) mutex_lock(&prop_table_lock);
588 	pe2 = slp_tsearch((void *) pe, &slp_props, compare_props);
589 	if (pe != *pe2) {
590 		/* this prop is already set; overwrite the old value */
591 		free((void *) (*pe2)->val);
592 		(*pe2)->val = pe->val;
593 		free((void *) pe->key);
594 		free(pe);
595 	}
596 	(void) mutex_unlock(&prop_table_lock);
597 }
598 
599 const char *SLPGetProperty(const char *pcName) {
600 	slp_prop_entry_t pe[1], **ans;
601 
602 	if (!slp_props) setDefaults();
603 
604 	if (!pcName) {
605 		return (NULL);
606 	}
607 
608 	pe->key = pcName;
609 
610 	(void) mutex_lock(&prop_table_lock);
611 	ans = slp_tfind(pe, &slp_props, compare_props);
612 	(void) mutex_unlock(&prop_table_lock);
613 	if (ans)
614 		return ((*ans)->val);
615 	return (NULL);
616 }
617 
618 static void setDefaults() {
619 	slp_prop_entry_t *pe;
620 	static mutex_t lock = DEFAULTMUTEX;
621 
622 	(void) mutex_lock(&lock);
623 	if (slp_props) {
624 		(void) mutex_unlock(&lock);
625 		return;
626 	}
627 
628 	pe = malloc(sizeof (*pe));
629 	pe->key = strdup(SLP_CONFIG_ISBROADCASTONLY);
630 	pe->val = strdup("false");
631 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
632 
633 	pe = malloc(sizeof (*pe));
634 	pe->key = strdup(SLP_CONFIG_MULTICASTTTL);
635 	pe->val = strdup("255");
636 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
637 
638 	pe = malloc(sizeof (*pe));
639 	pe->key = strdup(SLP_CONFIG_MULTICASTMAXWAIT);
640 	pe->val = strdup("15000");
641 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
642 
643 	pe = malloc(sizeof (*pe));
644 	pe->key = strdup(SLP_CONFIG_DATAGRAMTIMEOUTS);
645 	pe->val = strdup("2000,2000,2000");
646 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
647 
648 	pe = malloc(sizeof (*pe));
649 	pe->key = strdup(SLP_CONFIG_MULTICASTTIMEOUTS);
650 	pe->val = strdup("1000,3000,3000,3000,3000");
651 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
652 
653 	pe = malloc(sizeof (*pe));
654 	pe->key = SLP_CONFIG_MTU; pe->val = "1400";
655 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
656 
657 	pe = malloc(sizeof (*pe));
658 	pe->key = strdup(SLP_CONFIG_MAXRESULTS);
659 	pe->val = strdup("-1");
660 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
661 
662 	pe = malloc(sizeof (*pe));
663 	pe->key = strdup(SLP_CONFIG_SECURITY_ON);
664 	pe->val = strdup("false");
665 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
666 
667 	pe = malloc(sizeof (*pe));
668 	pe->key = strdup(SLP_CONFIG_BYPASS_AUTH);
669 	pe->val = strdup("false");
670 	(void) slp_tsearch((void *) pe, &slp_props, compare_props);
671 
672 	slp_readConfig();
673 
674 	(void) mutex_unlock(&lock);
675 }
676 
677 static const char *error_strings[] = {
678 	"OK",				/* 0 */
679 	"Language not supported",	/* -1 */
680 	"Parse error",			/* -2 */
681 	"Invalid registration",		/* -3 */
682 	"Scope not supported",		/* -4 */
683 	"Invalid error number",		/* -5 */
684 	"Authentication absent",	/* -6 */
685 	"Authentication failed",	/* -7 */
686 	"Invalid error number",		/* -8 */
687 	"Invalid error number",		/* -9 */
688 	"Invalid error number",		/* -10 */
689 	"Invalid error number",		/* -11 */
690 	"Invalid error number",		/* -12 */
691 	"Invalid update",		/* -13 */
692 	"Invalid error number",		/* -14 */
693 	"Invalid error number",		/* -15 */
694 	"Invalid error number",		/* -16 */
695 	"Not implemented",		/* -17 */
696 	"Buffer overflow",		/* -18 */
697 	"Network timed out",		/* -19 */
698 	"Network init failed",		/* -20 */
699 	"Memory alloc failed",		/* -21 */
700 	"Parameter bad",		/* -22 */
701 	"Network error",		/* -23 */
702 	"Internal system error",	/* -24 */
703 	"Handle in use",		/* -25 */
704 	"Type error"			/* -26 */
705 };
706 
707 #define	SLP_MAX_ERR_CNT	26
708 
709 const char *slp_strerror(SLPError err) {
710 	int abserr;
711 	const char *str;
712 
713 	if (err == SLP_LAST_CALL) {
714 		str = "Last call";
715 	} else if (err == SLP_SECURITY_UNAVAILABLE) {
716 		str = "Security Implementation Unavailable";
717 	} else {
718 		abserr = abs(err);
719 		if (abserr > SLP_MAX_ERR_CNT) {
720 			str = "Invalid error number";
721 		} else {
722 			str = error_strings[abserr];
723 		}
724 	}
725 
726 	return (dgettext("libslp", str));
727 }
728