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