xref: /freebsd/contrib/openbsm/libbsm/bsm_control.c (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
1 /*-
2  * Copyright (c) 2004, 2009 Apple Inc.
3  * Copyright (c) 2006, 2016 Robert N. M. Watson
4  * All rights reserved.
5  *
6  * Portions of this software were developed by BAE Systems, the University of
7  * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL
8  * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent
9  * Computing (TC) research program.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1.  Redistributions of source code must retain the above copyright
15  *     notice, this list of conditions and the following disclaimer.
16  * 2.  Redistributions in binary form must reproduce the above copyright
17  *     notice, this list of conditions and the following disclaimer in the
18  *     documentation and/or other materials provided with the distribution.
19  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
20  *     its contributors may be used to endorse or promote products derived
21  *     from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
27  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
32  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #include <config/config.h>
37 
38 #include <bsm/libbsm.h>
39 
40 #include <ctype.h>
41 #include <errno.h>
42 #include <string.h>
43 #include <strings.h>
44 #ifdef HAVE_PTHREAD_MUTEX_LOCK
45 #include <pthread.h>
46 #endif
47 #include <stdio.h>
48 #include <stdlib.h>
49 
50 #ifndef HAVE_STRLCAT
51 #include <compat/strlcat.h>
52 #endif
53 #ifndef HAVE_STRLCPY
54 #include <compat/strlcpy.h>
55 #endif
56 
57 #include <sys/stat.h>
58 
59 /*
60  * Parse the contents of the audit_control file to return the audit control
61  * parameters.  These static fields are protected by 'mutex'.
62  */
63 static FILE	*fp = NULL;
64 static char	linestr[AU_LINE_MAX];
65 static char	*delim = ":";
66 
67 static char	inacdir = 0;
68 static char	ptrmoved = 0;
69 
70 #ifdef HAVE_PTHREAD_MUTEX_LOCK
71 static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
72 #endif
73 
74 /*
75  * Audit policy string token table for au_poltostr() and au_strtopol().
76  */
77 struct audit_polstr {
78 	long		 ap_policy;
79 	const char	*ap_str;
80 };
81 
82 static struct audit_polstr au_polstr[] = {
83 	{ AUDIT_CNT,		"cnt"		},
84 	{ AUDIT_AHLT,		"ahlt"		},
85 	{ AUDIT_ARGV,		"argv"		},
86 	{ AUDIT_ARGE,		"arge"		},
87 	{ AUDIT_SEQ,		"seq"		},
88 	{ AUDIT_WINDATA,	"windata"	},
89 	{ AUDIT_USER,		"user"		},
90 	{ AUDIT_GROUP,		"group"		},
91 	{ AUDIT_TRAIL,		"trail"		},
92 	{ AUDIT_PATH,		"path"		},
93 	{ AUDIT_SCNT,		"scnt"		},
94 	{ AUDIT_PUBLIC,		"public"	},
95 	{ AUDIT_ZONENAME,	"zonename"	},
96 	{ AUDIT_PERZONE,	"perzone"	},
97 	{ -1,			NULL		}
98 };
99 
100 /*
101  * Returns the string value corresponding to the given label from the
102  * configuration file.
103  *
104  * Must be called with mutex held.
105  */
106 static int
107 getstrfromtype_locked(const char *name, char **str)
108 {
109 	char *type, *nl;
110 	char *tokptr;
111 	char *last;
112 
113 	*str = NULL;
114 
115 	if ((fp == NULL) && ((fp = fopen(AUDIT_CONTROL_FILE, "r")) == NULL))
116 		return (-1); /* Error */
117 
118 	while (1) {
119 		if (fgets(linestr, AU_LINE_MAX, fp) == NULL) {
120 			if (ferror(fp))
121 				return (-1);
122 			return (0);	/* EOF */
123 		}
124 
125 		if (linestr[0] == '#')
126 			continue;
127 
128 		/* Remove trailing new line character and white space. */
129 		nl = strchr(linestr, '\0') - 1;
130 		while (nl >= linestr && ('\n' == *nl || ' ' == *nl ||
131 			'\t' == *nl)) {
132 			*nl = '\0';
133 			nl--;
134 		}
135 
136 		tokptr = linestr;
137 		if ((type = strtok_r(tokptr, delim, &last)) != NULL) {
138 			if (strcmp(name, type) == 0) {
139 				/* Found matching name. */
140 				*str = last;
141 				return (0); /* Success */
142 			}
143 		}
144 	}
145 }
146 
147 /*
148  * Convert a given time value with a multiplier (seconds, hours, days, years) to
149  * seconds.  Return 0 on success.
150  */
151 static int
152 au_timetosec(time_t *seconds, u_long value, char mult)
153 {
154 	if (NULL == seconds)
155 		return (-1);
156 
157 	switch(mult) {
158 	case 's':
159 		/* seconds */
160 		*seconds = (time_t)value;
161 		break;
162 
163 	case 'h':
164 		/* hours */
165 		*seconds = (time_t)value * 60 * 60;
166 		break;
167 
168 	case 'd':
169 		/* days */
170 		*seconds = (time_t)value * 60 * 60 * 24;
171 		break;
172 
173 	case 'y':
174 		/* years.  Add a day for each 4th (leap) year. */
175 		*seconds = (time_t)value * 60 * 60 * 24 * 364 +
176 		    ((time_t)value / 4) * 60 * 60 * 24;
177 		break;
178 
179 	default:
180 		return (-1);
181 	}
182 	return (0);
183 }
184 
185 /*
186  * Convert a given disk space value with a multiplier (bytes, kilobytes,
187  * megabytes, gigabytes) to bytes.  Return 0 on success.
188  */
189 static int
190 au_spacetobytes(size_t *bytes, u_long value, char mult)
191 {
192 	if (NULL == bytes)
193 		return (-1);
194 
195 	switch(mult) {
196 	case 'B':
197 	case ' ':
198 		/* Bytes */
199 		*bytes = (size_t)value;
200 		break;
201 
202 	case 'K':
203 		/* Kilobytes */
204 		*bytes = (size_t)value * 1024;
205 		break;
206 
207 	case 'M':
208 		/* Megabytes */
209 		*bytes = (size_t)value * 1024 * 1024;
210 		break;
211 
212 	case 'G':
213 		/* Gigabytes */
214 		*bytes = (size_t)value * 1024 * 1024 * 1024;
215 		break;
216 
217 	default:
218 		return (-1);
219 	}
220 	return (0);
221 }
222 
223 /*
224  * Convert a policy to a string.  Return -1 on failure, or >= 0 representing
225  * the actual size of the string placed in the buffer (excluding terminating
226  * nul).
227  */
228 ssize_t
229 au_poltostr(int policy, size_t maxsize, char *buf)
230 {
231 	int first = 1;
232 	int i = 0;
233 
234 	if (maxsize < 1)
235 		return (-1);
236 	buf[0] = '\0';
237 
238 	do {
239 		if (policy & au_polstr[i].ap_policy) {
240 			if (!first && strlcat(buf, ",", maxsize) >= maxsize)
241 				return (-1);
242 			if (strlcat(buf, au_polstr[i].ap_str, maxsize) >=
243 			    maxsize)
244 				return (-1);
245 			first = 0;
246 		}
247 	} while (NULL != au_polstr[++i].ap_str);
248 
249 	return (strlen(buf));
250 }
251 
252 /*
253  * Convert a string to a policy.  Return -1 on failure (with errno EINVAL,
254  * ENOMEM) or 0 on success.
255  */
256 int
257 au_strtopol(const char *polstr, int *policy)
258 {
259 	char *bufp, *string;
260 	char *buffer;
261 	int i, matched;
262 
263 	*policy = 0;
264 	buffer = strdup(polstr);
265 	if (buffer == NULL)
266 		return (-1);
267 
268 	bufp = buffer;
269 	while ((string = strsep(&bufp, ",")) != NULL) {
270 		matched = i = 0;
271 
272 		do {
273 			if (strcmp(string, au_polstr[i].ap_str) == 0) {
274 				*policy |= au_polstr[i].ap_policy;
275 				matched = 1;
276 				break;
277 			}
278 		} while (NULL != au_polstr[++i].ap_str);
279 
280 		if (!matched) {
281 			free(buffer);
282 			errno = EINVAL;
283 			return (-1);
284 		}
285 	}
286 	free(buffer);
287 	return (0);
288 }
289 
290 /*
291  * Rewind the file pointer to beginning.
292  */
293 static void
294 setac_locked(void)
295 {
296 	static time_t lastctime = 0;
297 	struct stat sbuf;
298 
299 	ptrmoved = 1;
300 	if (fp != NULL) {
301 		/*
302 		 * Check to see if the file on disk has changed.  If so,
303 		 * force a re-read of the file by closing it.
304 		 */
305 		if (fstat(fileno(fp), &sbuf) < 0)
306 			goto closefp;
307 		if (lastctime != sbuf.st_ctime) {
308 			lastctime = sbuf.st_ctime;
309 closefp:
310 			fclose(fp);
311 			fp = NULL;
312 			return;
313 		}
314 
315 		fseek(fp, 0, SEEK_SET);
316 	}
317 }
318 
319 void
320 setac(void)
321 {
322 
323 #ifdef HAVE_PTHREAD_MUTEX_LOCK
324 	pthread_mutex_lock(&mutex);
325 #endif
326 	setac_locked();
327 #ifdef HAVE_PTHREAD_MUTEX_LOCK
328 	pthread_mutex_unlock(&mutex);
329 #endif
330 }
331 
332 /*
333  * Close the audit_control file.
334  */
335 void
336 endac(void)
337 {
338 
339 #ifdef HAVE_PTHREAD_MUTEX_LOCK
340 	pthread_mutex_lock(&mutex);
341 #endif
342 	ptrmoved = 1;
343 	if (fp != NULL) {
344 		fclose(fp);
345 		fp = NULL;
346 	}
347 #ifdef HAVE_PTHREAD_MUTEX_LOCK
348 	pthread_mutex_unlock(&mutex);
349 #endif
350 }
351 
352 /*
353  * Return audit directory information from the audit control file.
354  */
355 int
356 getacdir(char *name, int len)
357 {
358 	char *dir;
359 	int ret = 0;
360 
361 	/*
362 	 * Check if another function was called between successive calls to
363 	 * getacdir.
364 	 */
365 #ifdef HAVE_PTHREAD_MUTEX_LOCK
366 	pthread_mutex_lock(&mutex);
367 #endif
368 	if (inacdir && ptrmoved) {
369 		ptrmoved = 0;
370 		if (fp != NULL)
371 			fseek(fp, 0, SEEK_SET);
372 		ret = 2;
373 	}
374 	if (getstrfromtype_locked(DIR_CONTROL_ENTRY, &dir) < 0) {
375 #ifdef HAVE_PTHREAD_MUTEX_LOCK
376 		pthread_mutex_unlock(&mutex);
377 #endif
378 		return (-2);
379 	}
380 	if (dir == NULL) {
381 #ifdef HAVE_PTHREAD_MUTEX_LOCK
382 		pthread_mutex_unlock(&mutex);
383 #endif
384 		return (-1);
385 	}
386 	if (strlen(dir) >= (size_t)len) {
387 #ifdef HAVE_PTHREAD_MUTEX_LOCK
388 		pthread_mutex_unlock(&mutex);
389 #endif
390 		return (-3);
391 	}
392 	strlcpy(name, dir, len);
393 #ifdef HAVE_PTHREAD_MUTEX_LOCK
394 	pthread_mutex_unlock(&mutex);
395 #endif
396 	return (ret);
397 }
398 
399 /*
400  * Return 1 if dist value is set to 'yes' or 'on'.
401  * Return 0 if dist value is set to something else.
402  * Return negative value on error.
403  */
404 int
405 getacdist(void)
406 {
407 	char *str;
408 	int ret;
409 
410 #ifdef HAVE_PTHREAD_MUTEX_LOCK
411 	pthread_mutex_lock(&mutex);
412 #endif
413 	setac_locked();
414 	if (getstrfromtype_locked(DIST_CONTROL_ENTRY, &str) < 0) {
415 #ifdef HAVE_PTHREAD_MUTEX_LOCK
416 		pthread_mutex_unlock(&mutex);
417 #endif
418 		return (-2);
419 	}
420 	if (str == NULL) {
421 #ifdef HAVE_PTHREAD_MUTEX_LOCK
422 		pthread_mutex_unlock(&mutex);
423 #endif
424 		return (0);
425 	}
426 	if (strcasecmp(str, "on") == 0 || strcasecmp(str, "yes") == 0)
427 		ret = 1;
428 	else
429 		ret = 0;
430 #ifdef HAVE_PTHREAD_MUTEX_LOCK
431 	pthread_mutex_unlock(&mutex);
432 #endif
433 	return (ret);
434 }
435 
436 /*
437  * Return the minimum free diskspace value from the audit control file.
438  */
439 int
440 getacmin(int *min_val)
441 {
442 	char *min;
443 
444 #ifdef HAVE_PTHREAD_MUTEX_LOCK
445 	pthread_mutex_lock(&mutex);
446 #endif
447 	setac_locked();
448 	if (getstrfromtype_locked(MINFREE_CONTROL_ENTRY, &min) < 0) {
449 #ifdef HAVE_PTHREAD_MUTEX_LOCK
450 		pthread_mutex_unlock(&mutex);
451 #endif
452 		return (-2);
453 	}
454 	if (min == NULL) {
455 #ifdef HAVE_PTHREAD_MUTEX_LOCK
456 		pthread_mutex_unlock(&mutex);
457 #endif
458 		return (-1);
459 	}
460 	*min_val = atoi(min);
461 #ifdef HAVE_PTHREAD_MUTEX_LOCK
462 	pthread_mutex_unlock(&mutex);
463 #endif
464 	return (0);
465 }
466 
467 /*
468  * Return the desired trail rotation size from the audit control file.
469  */
470 int
471 getacfilesz(size_t *filesz_val)
472 {
473 	char *str;
474 	size_t val;
475 	char mult;
476 	int nparsed;
477 
478 #ifdef HAVE_PTHREAD_MUTEX_LOCK
479 	pthread_mutex_lock(&mutex);
480 #endif
481 	setac_locked();
482 	if (getstrfromtype_locked(FILESZ_CONTROL_ENTRY, &str) < 0) {
483 #ifdef HAVE_PTHREAD_MUTEX_LOCK
484 		pthread_mutex_unlock(&mutex);
485 #endif
486 		return (-2);
487 	}
488 	if (str == NULL) {
489 #ifdef HAVE_PTHREAD_MUTEX_LOCK
490 		pthread_mutex_unlock(&mutex);
491 #endif
492 		errno = EINVAL;
493 		return (-1);
494 	}
495 
496 	/* Trim off any leading white space. */
497 	while (*str == ' ' || *str == '\t')
498 		str++;
499 
500 	nparsed = sscanf(str, "%ju%c", (uintmax_t *)&val, &mult);
501 
502 	switch (nparsed) {
503 	case 1:
504 		/* If no multiplier then assume 'B' (bytes). */
505 		mult = 'B';
506 		/* fall through */
507 	case 2:
508 		if (au_spacetobytes(filesz_val, val, mult) == 0)
509 			break;
510 		/* fall through */
511 	default:
512 		errno = EINVAL;
513 #ifdef HAVE_PTHREAD_MUTEX_LOCK
514 		pthread_mutex_unlock(&mutex);
515 #endif
516 		return (-1);
517 	}
518 
519 	/*
520 	 * The file size must either be 0 or >= MIN_AUDIT_FILE_SIZE.  0
521 	 * indicates no rotation size.
522 	 */
523 	if (*filesz_val < 0 || (*filesz_val > 0 &&
524 		*filesz_val < MIN_AUDIT_FILE_SIZE)) {
525 #ifdef HAVE_PTHREAD_MUTEX_LOCK
526 		pthread_mutex_unlock(&mutex);
527 #endif
528 		filesz_val = 0L;
529 		errno = EINVAL;
530 		return (-1);
531 	}
532 #ifdef HAVE_PTHREAD_MUTEX_LOCK
533 	pthread_mutex_unlock(&mutex);
534 #endif
535 	return (0);
536 }
537 
538 static int
539 getaccommon(const char *name, char *auditstr, int len)
540 {
541 	char *str;
542 
543 #ifdef HAVE_PTHREAD_MUTEX_LOCK
544 	pthread_mutex_lock(&mutex);
545 #endif
546 	setac_locked();
547 	if (getstrfromtype_locked(name, &str) < 0) {
548 #ifdef HAVE_PTHREAD_MUTEX_LOCK
549 		pthread_mutex_unlock(&mutex);
550 #endif
551 		return (-2);
552 	}
553 
554 	/*
555 	 * getstrfromtype_locked() can return NULL for an empty value -- make
556 	 * sure to handle this by coercing the NULL back into an empty string.
557 	 */
558 	if (str != NULL && (strlen(str) >= (size_t)len)) {
559 #ifdef HAVE_PTHREAD_MUTEX_LOCK
560 		pthread_mutex_unlock(&mutex);
561 #endif
562 		return (-3);
563 	}
564 	strlcpy(auditstr, str != NULL ? str : "", len);
565 #ifdef HAVE_PTHREAD_MUTEX_LOCK
566 	pthread_mutex_unlock(&mutex);
567 #endif
568 	return (0);
569 }
570 
571 /*
572  * Return the system audit value from the audit contol file.
573  */
574 int
575 getacflg(char *auditstr, int len)
576 {
577 
578 	return (getaccommon(FLAGS_CONTROL_ENTRY, auditstr, len));
579 }
580 
581 /*
582  * Return the non attributable flags from the audit contol file.
583  */
584 int
585 getacna(char *auditstr, int len)
586 {
587 
588 	return (getaccommon(NA_CONTROL_ENTRY, auditstr, len));
589 }
590 
591 /*
592  * Return the policy field from the audit control file.
593  */
594 int
595 getacpol(char *auditstr, size_t len)
596 {
597 
598 	return (getaccommon(POLICY_CONTROL_ENTRY, auditstr, len));
599 }
600 
601 int
602 getachost(char *auditstr, size_t len)
603 {
604 
605 	return (getaccommon(HOST_CONTROL_ENTRY, auditstr, len));
606 }
607 
608 /*
609  * Set expiration conditions.
610  */
611 static int
612 setexpirecond(time_t *age, size_t *size, u_long value, char mult)
613 {
614 
615 	if (isupper(mult) || ' ' == mult)
616 		return (au_spacetobytes(size, value, mult));
617 	else
618 		return (au_timetosec(age, value, mult));
619 }
620 
621 /*
622  * Return the expire-after field from the audit control file.
623  */
624 int
625 getacexpire(int *andflg, time_t *age, size_t *size)
626 {
627 	char *str;
628 	int nparsed;
629 	u_long val1, val2;
630 	char mult1, mult2;
631 	char andor[AU_LINE_MAX];
632 
633 	*age = 0L;
634 	*size = 0LL;
635 	*andflg = 0;
636 
637 #ifdef HAVE_PTHREAD_MUTEX_LOCK
638 	pthread_mutex_lock(&mutex);
639 #endif
640 	setac_locked();
641 	if (getstrfromtype_locked(EXPIRE_AFTER_CONTROL_ENTRY, &str) < 0) {
642 #ifdef HAVE_PTHREAD_MUTEX_LOCK
643 		pthread_mutex_unlock(&mutex);
644 #endif
645 		return (-2);
646 	}
647 	if (str == NULL) {
648 #ifdef HAVE_PTHREAD_MUTEX_LOCK
649 		pthread_mutex_unlock(&mutex);
650 #endif
651 		return (-1);
652 	}
653 
654 	/* First, trim off any leading white space. */
655 	while (*str == ' ' || *str == '\t')
656 		str++;
657 
658 	nparsed = sscanf(str, "%lu%c%[ \tadnorADNOR]%lu%c", &val1, &mult1,
659 	    andor, &val2, &mult2);
660 
661 	switch (nparsed) {
662 	case 1:
663 		/* If no multiplier then assume 'B' (Bytes). */
664 		mult1 = 'B';
665 		/* fall through */
666 	case 2:
667 		/* One expiration condition. */
668 		if (setexpirecond(age, size, val1, mult1) != 0) {
669 #ifdef HAVE_PTHREAD_MUTEX_LOCK
670 			pthread_mutex_unlock(&mutex);
671 #endif
672 			return (-1);
673 		}
674 		break;
675 
676 	case 5:
677 		/* Two expiration conditions. */
678 		if (setexpirecond(age, size, val1, mult1) != 0 ||
679 		    setexpirecond(age, size, val2, mult2) != 0) {
680 #ifdef HAVE_PTHREAD_MUTEX_LOCK
681 			pthread_mutex_unlock(&mutex);
682 #endif
683 			return (-1);
684 		}
685 		if (strcasestr(andor, "and") != NULL)
686 			*andflg = 1;
687 		else if (strcasestr(andor, "or") != NULL)
688 			*andflg = 0;
689 		else {
690 #ifdef HAVE_PTHREAD_MUTEX_LOCK
691 			pthread_mutex_unlock(&mutex);
692 #endif
693 			return (-1);
694 		}
695 		break;
696 
697 	default:
698 #ifdef HAVE_PTHREAD_MUTEX_LOCK
699 		pthread_mutex_unlock(&mutex);
700 #endif
701 		return (-1);
702 	}
703 
704 #ifdef HAVE_PTHREAD_MUTEX_LOCK
705 	pthread_mutex_unlock(&mutex);
706 #endif
707 	return (0);
708 }
709 /*
710  * Return the desired queue size from the audit control file.
711  */
712 int
713 getacqsize(int *qsz_val)
714 {
715 	char *str;
716 	int nparsed;
717 
718 #ifdef HAVE_PTHREAD_MUTEX_LOCK
719 	pthread_mutex_lock(&mutex);
720 #endif
721 	setac_locked();
722 	if (getstrfromtype_locked(QSZ_CONTROL_ENTRY, &str) < 0) {
723 #ifdef HAVE_PTHREAD_MUTEX_LOCK
724 		pthread_mutex_unlock(&mutex);
725 #endif
726 		return (-2);
727 	}
728 	if (str == NULL) {
729 #ifdef HAVE_PTHREAD_MUTEX_LOCK
730 		pthread_mutex_unlock(&mutex);
731 #endif
732 		*qsz_val = USE_DEFAULT_QSZ;
733 		return (0);
734 	}
735 
736 	/* Trim off any leading white space. */
737 	while (*str == ' ' || *str == '\t')
738 		str++;
739 
740 	nparsed = sscanf(str, "%d", (int *)qsz_val);
741 
742 	if (nparsed != 1) {
743 		errno = EINVAL;
744 #ifdef HAVE_PTHREAD_MUTEX_LOCK
745 		pthread_mutex_unlock(&mutex);
746 #endif
747 		return (-1);
748 	}
749 
750 	/* The queue size must either be 0 or < AQ_MAXHIGH */
751 	if (*qsz_val < 0 || *qsz_val > AQ_MAXHIGH) {
752 #ifdef HAVE_PTHREAD_MUTEX_LOCK
753 		pthread_mutex_unlock(&mutex);
754 #endif
755 		qsz_val = 0L;
756 		errno = EINVAL;
757 		return (-1);
758 	}
759 #ifdef HAVE_PTHREAD_MUTEX_LOCK
760 	pthread_mutex_unlock(&mutex);
761 #endif
762 	return (0);
763 }
764