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 *	ns_od.c
24 *
25 * Based on ns_ldap.c
26 *
27 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28 * Use is subject to license terms.
29 */
30
31/*
32 * Portions Copyright 2007-2012 Apple Inc.
33 */
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <syslog.h>
38#include <string.h>
39
40#include <OpenDirectory/OpenDirectory.h>
41
42#include "automount.h"
43#include "automount_od.h"
44
45/*
46 * Callback routines for od_process_record_attributes() and od_search().
47 */
48typedef enum {
49	OD_CB_KEEPGOING,		/* continue the search */
50	OD_CB_DONE,			/* we're done with the search */
51	OD_CB_REJECTED,			/* this record had a problem - keep going */
52	OD_CB_ERROR			/* error - quit and return __NSW_UNAVAIL */
53} callback_ret_t;
54typedef callback_ret_t (*callback_fn)(CFStringRef key, CFStringRef value,
55    void *udata);
56
57static callback_ret_t mastermap_callback(CFStringRef key, CFStringRef value,
58    void *udata);
59static callback_ret_t directmap_callback(CFStringRef key, CFStringRef value,
60    void *udata);
61static callback_ret_t match_callback(CFStringRef key, CFStringRef value,
62    void *udata);
63static callback_ret_t readdir_callback(CFStringRef key, CFStringRef value,
64    void *udata);
65
66struct match_cbdata {
67	const char *map;
68	const char *key;
69	char **od_line;
70	int *od_len;
71};
72
73struct loadmaster_cbdata {
74	char *defopts;
75	char **stack;
76	char ***stkptr;
77};
78
79struct loaddirect_cbdata {
80	char *opts;
81	char *localmap;
82	char **stack;
83	char ***stkptr;
84};
85
86struct dir_cbdata {
87	struct dir_entry **list;
88	struct dir_entry *last;
89	int error;
90};
91
92static int od_match(const char *map, const char *key, char **od_line,
93    int *od_len);
94static int od_search(CFStringRef attr_to_match, char *value_to_match,
95    callback_fn callback, void *udata);
96
97/*
98 * Get the C-string length of a CFString.
99 */
100static inline CFIndex
101od_cfstrlen(CFStringRef cfstr)
102{
103	return (CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
104	    kCFStringEncodingUTF8));
105}
106
107/*
108 * Given a CFString and its C-string length, copy it to a buffer.
109 */
110static inline Boolean
111od_cfstrlcpy(char *string, CFStringRef cfstr, size_t size)
112{
113	return (CFStringGetCString(cfstr, string, (CFIndex)size,
114	    kCFStringEncodingUTF8));
115}
116
117/*
118 * Get a C string from a CFStringRef.
119 * The string is allocated with malloc(), and must be freed when it's
120 * no longer needed.
121 */
122static char *
123od_CFStringtoCString(CFStringRef cfstr)
124{
125	char *string;
126	CFIndex length;
127
128	length = od_cfstrlen(cfstr);
129	string = malloc(length + 1);
130	if (string == NULL)
131		return (NULL);
132	if (!od_cfstrlcpy(string, cfstr, length + 1)) {
133		free(string);
134		return (NULL);
135	}
136	return (string);
137}
138
139
140char *
141od_get_error_string(CFErrorRef err)
142{
143	CFStringRef errstringref;
144	char *errstring;
145
146	if (err != NULL) {
147		errstringref = CFErrorCopyDescription(err);
148		errstring = od_CFStringtoCString(errstringref);
149		CFRelease(errstringref);
150	} else
151		errstring = strdup("Unknown error");
152	return (errstring);
153}
154
155/*ARGSUSED*/
156void
157init_od(__unused char **stack, __unused char ***stkptr)
158{
159}
160
161/*ARGSUSED*/
162int
163getmapent_od(const char *key, const char *map, struct mapline *ml,
164    __unused char **stack, __unused char ***stkptr,
165    bool_t *iswildcard, __unused bool_t isrestricted)
166{
167	char *od_line = NULL;
168	char *lp;
169	int od_len;
170	size_t len;
171	int nserr;
172
173	if (trace > 1)
174		trace_prt(1, "getmapent_od called\n");
175
176	if (trace > 1) {
177		trace_prt(1, "getmapent_od: key=[ %s ]\n", key);
178	}
179
180	if (iswildcard)
181		*iswildcard = FALSE;
182	nserr = od_match(map, key, &od_line, &od_len);
183	if (nserr) {
184		if (nserr == __NSW_NOTFOUND) {
185			/* Try the default entry "*" */
186			if ((nserr = od_match(map, "*", &od_line,
187			    &od_len)))
188				goto done;
189			else {
190				if (iswildcard)
191					*iswildcard = TRUE;
192			}
193		} else
194			goto done;
195	}
196
197	/*
198	 * at this point we are sure that od_match
199	 * succeeded so massage the entry by
200	 * 1. ignoring # and beyond
201	 * 2. trim the trailing whitespace
202	 */
203	if ((lp = strchr(od_line, '#')) != NULL)
204		*lp = '\0';
205	len = strlen(od_line);
206	if (len == 0) {
207		nserr = __NSW_NOTFOUND;
208		goto done;
209	}
210	lp = &od_line[len - 1];
211	while (lp > od_line && isspace(*lp))
212		*lp-- = '\0';
213	if (lp == od_line) {
214		nserr = __NSW_NOTFOUND;
215		goto done;
216	}
217	(void) strncpy(ml->linebuf, od_line, LINESZ);
218	unquote(ml->linebuf, ml->lineqbuf);
219	nserr = __NSW_SUCCESS;
220done:
221	if (od_line)
222		free((char *)od_line);
223
224	if (trace > 1)
225		trace_prt(1, "getmapent_od: exiting ...\n");
226
227	return (nserr);
228}
229
230static callback_ret_t
231match_callback(CFStringRef key, CFStringRef value, void *udata)
232{
233	char *key_str, *value_str;
234	CFIndex value_len;
235	struct match_cbdata *temp = (struct match_cbdata *)udata;
236	char **od_line = temp->od_line;
237	int *od_len = temp->od_len;
238
239	if (trace > 1) {
240		key_str = od_CFStringtoCString(key);
241		value_str = od_CFStringtoCString(value);
242		if (key_str != NULL && value_str != NULL) {
243			trace_prt(1, "  match_callback called: key %s, value %s\n",
244			    key_str, value_str);
245		}
246		free(value_str);
247		free(key_str);
248	}
249
250	/*
251	 * value contains a list of mount options AND mount locations
252	 * for a particular mount point (key).
253	 * For example:
254	 *
255	 * key: /work
256	 *	^^^^^
257	 *	(mount point)
258	 *
259	 * value:
260	 *	        -rw,intr,nosuid,noquota hosta:/export/work
261	 *		^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
262	 *		(    mount options    ) (remote mount location)
263	 *
264	 */
265	value_len = od_cfstrlen(value);
266	*od_len = (int)(value_len + 1);
267
268	/*
269	 * so check for the length; it should be less than
270	 * LINESZ
271	 */
272	if (*od_len > LINESZ) {
273		key_str = od_CFStringtoCString(key);
274		pr_msg(
275		    "Open Directory map %s, entry for %s"
276		    " is too long %d chars (max %d)",
277		    temp->map, key_str, *od_len, LINESZ);
278		free(key_str);
279		return (OD_CB_REJECTED);
280	}
281	*od_line = (char *)malloc(*od_len);
282	if (*od_line == NULL) {
283		pr_msg("match_callback: malloc failed");
284		return (OD_CB_ERROR);
285	}
286
287	if (!od_cfstrlcpy(*od_line, value, *od_len)) {
288		key_str = od_CFStringtoCString(key);
289		pr_msg("match_callback: can't get line for %s", key_str);
290		free(key_str);
291		free(*od_line);
292		return (OD_CB_ERROR);
293	}
294
295	if (trace > 1)
296		trace_prt(1, "  match_callback: found: %s\n", *od_line);
297
298	return (OD_CB_DONE);
299}
300
301static int
302od_match(const char *map, const char *key, char **od_line, int *od_len)
303{
304	int ret;
305	char *pattern;
306	struct match_cbdata cbdata;
307
308	if (trace > 1) {
309		trace_prt(1, "od_match called\n");
310		trace_prt(1, "od_match: key =[ %s ] map = %s\n", key, map);
311	}
312
313	/* Construct the string value to search for. */
314	if (asprintf(&pattern, "%s,automountMapName=%s", key, map) == -1) {
315		pr_msg("od_match: malloc failed");
316		ret = __NSW_UNAVAIL;
317		goto done;
318	}
319
320	if (trace > 1)
321		trace_prt(1, "  od_match: Searching for %s\n", pattern);
322
323	cbdata.map = map;
324	cbdata.key = key;
325	cbdata.od_line = od_line;
326	cbdata.od_len = od_len;
327	ret = od_search(kODAttributeTypeRecordName, pattern, match_callback,
328	    (void *) &cbdata);
329	free(pattern);
330
331	if (trace > 1) {
332		if (ret == __NSW_NOTFOUND)
333			trace_prt(1, "  od_match: no entries found\n");
334		else if (ret != __NSW_UNAVAIL)
335			trace_prt(1,
336			    "  od_match: od_search FAILED: %d\n", ret);
337		else
338			trace_prt(1, "  od_match: od_search OK\n");
339	}
340
341	if (verbose) {
342		if (ret == __NSW_NOTFOUND)
343			pr_msg("od_search for %s in %s failed", key, map);
344	}
345
346done:
347	return (ret);
348}
349
350int
351loadmaster_od(char *mapname, char *defopts, char **stack, char ***stkptr)
352{
353	int res;
354	struct loadmaster_cbdata master_cbdata;
355
356	if (trace > 1)
357		trace_prt(1, "loadmaster_od called\n");
358
359	master_cbdata.defopts = defopts;
360	master_cbdata.stack = stack;
361	master_cbdata.stkptr = stkptr;
362
363	if (trace > 1)
364		trace_prt(1, "loadmaster_od: Requesting list in %s\n",
365		    mapname);
366
367	res = od_search(kODAttributeTypeMetaAutomountMap, mapname,
368	    mastermap_callback, (void *) &master_cbdata);
369
370	if (trace > 1)
371		trace_prt(1,
372			"loadmaster_od: od_search just returned: %d\n",
373			res);
374
375	return (res);
376}
377
378int
379loaddirect_od(char *nsmap, char *localmap, char *opts,
380    char **stack, char ***stkptr)
381{
382	struct loaddirect_cbdata direct_cbdata;
383
384	if (trace > 1) {
385		trace_prt(1, "loaddirect_od called\n");
386	}
387
388	if (trace > 1)
389		trace_prt(1, "loaddirect_od: Requesting list for %s in %s\n",
390		    localmap, nsmap);
391
392	direct_cbdata.opts = opts;
393	direct_cbdata.localmap = localmap;
394	direct_cbdata.stack = stack;
395	direct_cbdata.stkptr = stkptr;
396	return (od_search(kODAttributeTypeMetaAutomountMap, nsmap,
397	    directmap_callback, (void *) &direct_cbdata));
398}
399
400static callback_ret_t
401mastermap_callback(CFStringRef key, CFStringRef invalue, void *udata)
402{
403	char *key_str, *value_str, *value;
404	CFIndex key_len, value_len;
405	char *pmap, *opts;
406	char dir[LINESZ], map[LINESZ], qbuff[LINESZ];
407	struct loadmaster_cbdata *temp = (struct loadmaster_cbdata *)udata;
408	char *defopts = temp->defopts;
409	char **stack = temp->stack;
410	char ***stkptr = temp->stkptr;
411	CFIndex i;
412
413	if (trace > 1) {
414		key_str = od_CFStringtoCString(key);
415		value_str = od_CFStringtoCString(invalue);
416		if (key_str != NULL && value_str != NULL) {
417			trace_prt(1, "  mastermap_callback called: key %s, value %s\n",
418			    key_str, value_str);
419		}
420		free(value_str);
421		free(key_str);
422	}
423
424	key_len = od_cfstrlen(key);
425	value_len = od_cfstrlen(invalue);
426	if (key_len >= LINESZ || value_len >= LINESZ)
427		return (OD_CB_KEEPGOING);
428	if (key_len < 2 || value_len < 2)
429		return (OD_CB_KEEPGOING);
430
431	value_str = od_CFStringtoCString(invalue);
432	value = value_str;
433	i = value_len;
434	while (i > 0 && isspace((unsigned char)*value)) {
435		value++;
436		i--;
437	}
438	if (*value == '\0') {
439		free(value_str);
440		return (OD_CB_KEEPGOING);
441	}
442	if (!od_cfstrlcpy(dir, key, key_len)) {
443		free(value_str);
444		return (OD_CB_KEEPGOING);
445	}
446	if (isspace((unsigned char)dir[0]) || dir[0] == '#') {
447		free(value_str);
448		return (OD_CB_KEEPGOING);
449	}
450
451	if (trace > 1)
452		trace_prt(1, "mastermap_callback: dir= [ %s ]\n", dir);
453	for (i = 0; i < LINESZ; i++)
454		qbuff[i] = ' ';
455	switch (macro_expand("", dir, qbuff, sizeof (dir))) {
456
457	case MEXPAND_OK:
458		break;
459
460	case MEXPAND_LINE_TOO_LONG:
461		pr_msg(
462		    "%s in Open Directory map: entry too long (max %zu chars)",
463		    dir, sizeof (dir) - 1);
464		free(value_str);
465		return (OD_CB_KEEPGOING);
466
467	case MEXPAND_VARNAME_TOO_LONG:
468		pr_msg(
469		    "%s in Open Directory map: variable name too long",
470		    dir);
471		free(value_str);
472		return (OD_CB_KEEPGOING);
473	}
474	strlcpy(map, value, sizeof (map));	/* we know this will not truncate */
475	free(value_str);
476	if (trace > 1)
477		trace_prt(1, "mastermap_callback: map= [ %s ]\n", map);
478	switch (macro_expand("", map, qbuff, sizeof (map))) {
479
480	case MEXPAND_OK:
481		break;
482
483	case MEXPAND_LINE_TOO_LONG:
484		pr_msg(
485		    "%s in Open Directory map: entry too long (max %zu chars)",
486		    map, sizeof (map) - 1);
487		return (OD_CB_KEEPGOING);
488
489	case MEXPAND_VARNAME_TOO_LONG:
490		pr_msg(
491		    "%s in Open Directory map: variable name too long",
492		    map);
493		return (OD_CB_KEEPGOING);
494	}
495	pmap = map;
496	while (*pmap && isspace(*pmap))
497		pmap++;		/* skip blanks in front of map */
498	opts = pmap;
499	while (*opts && !isspace(*opts))
500		opts++;
501	if (*opts) {
502		*opts++ = '\0';
503		while (*opts && isspace(*opts))
504			opts++;
505		if (*opts == '-')
506			opts++;
507			else
508			opts = defopts;
509	}
510	/*
511	 * Check for no embedded blanks.
512	 */
513	if (strcspn(opts, " \t") == strlen(opts)) {
514		if (trace > 1)
515			trace_prt(1,
516			"mastermap_callback: dir=[ %s ], pmap=[ %s ]\n",
517			    dir, pmap);
518		dirinit(dir, pmap, opts, 0, stack, stkptr);
519	} else {
520		/* XXX - this was "dn=" for LDAP; is that the server name? */
521		pr_msg(
522	"Warning: invalid entry for %s in Open Directory ignored.\n",
523		    dir);
524	}
525	if (trace > 1)
526		trace_prt(1, "mastermap_callback exiting...\n");
527	return (OD_CB_KEEPGOING);
528}
529
530static callback_ret_t
531directmap_callback(CFStringRef key, __unused CFStringRef value, void *udata)
532{
533	char *str;
534	CFIndex key_len;
535	char dir[MAXFILENAMELEN+1];
536	struct loaddirect_cbdata *temp = (struct loaddirect_cbdata *)udata;
537	char *opts = temp->opts;
538	char *localmap = temp->localmap;
539	char **stack = temp->stack;
540	char ***stkptr = temp->stkptr;
541
542	if (trace > 1) {
543		str = od_CFStringtoCString(key);
544		if (str != NULL) {
545			trace_prt(1, "  directmap_callback called: key %s\n",
546			    str);
547			free(str);
548		}
549	}
550
551	key_len = od_cfstrlen(key);
552	if (key_len > (CFIndex)MAXFILENAMELEN || key_len < 2)
553		return (OD_CB_KEEPGOING);
554
555	if (!od_cfstrlcpy(dir, key, key_len))
556		return (OD_CB_KEEPGOING);
557	if (isspace((unsigned char)dir[0]) || dir[0] == '#')
558		return (OD_CB_KEEPGOING);	/* ignore blank lines and comments */
559
560	dirinit(dir, localmap, opts, 1, stack, stkptr);
561
562	return (OD_CB_KEEPGOING);
563}
564
565int
566getmapkeys_od(char *nsmap, struct dir_entry **list, int *error,
567    int *cache_time, __unused char **stack, __unused char ***stkptr)
568{
569	int res;
570	struct dir_cbdata readdir_cbdata;
571
572	if (trace > 1)
573		trace_prt(1, "getmapkeys_od called\n");
574
575	*cache_time = RDDIR_CACHE_TIME;
576	*error = 0;
577
578	if (trace > 1)
579		trace_prt(1, "getmapkeys_od: Requesting list in %s\n",
580		    nsmap);
581
582	readdir_cbdata.list = list;
583	readdir_cbdata.last = NULL;
584	readdir_cbdata.error = 0;
585	res = od_search(kODAttributeTypeMetaAutomountMap, nsmap,
586	    readdir_callback, (void *) &readdir_cbdata);
587
588	if (trace > 1)
589		trace_prt(1, "  getmapkeys_od: od_search returned %d\n",
590			res);
591
592	if (readdir_cbdata.error)
593		*error = readdir_cbdata.error;
594
595	if (res != __NSW_SUCCESS) {
596		if (*error == 0)
597			*error = EIO;
598	}
599
600	return (res);
601}
602
603static callback_ret_t
604readdir_callback(CFStringRef inkey, CFStringRef value, void *udata)
605{
606	char *str;
607	CFIndex inkeylen, value_len;
608	struct dir_cbdata *temp = (struct dir_cbdata *)udata;
609	struct dir_entry **list = temp->list;
610	struct dir_entry *last = temp->last;
611	char key[MAXFILENAMELEN+1];
612	char linebuf[LINESZ], lineqbuf[LINESZ];
613	int error;
614
615	if (trace > 1) {
616		str = od_CFStringtoCString(inkey);
617		if (str != NULL) {
618			trace_prt(1, "  readdir_callback called: key %s\n",
619			    str);
620			free(str);
621		}
622	}
623
624	inkeylen = od_cfstrlen(inkey);
625	if (inkeylen > (CFIndex)MAXFILENAMELEN)
626		return (OD_CB_KEEPGOING);
627
628	if (inkeylen == 0)
629		return (OD_CB_KEEPGOING);	/* ignore empty lines */
630	if (!od_cfstrlcpy(key, inkey, inkeylen))
631		return (OD_CB_KEEPGOING);
632	if (isspace((unsigned char)key[0]) || key[0] == '#')
633		return (OD_CB_KEEPGOING);	/* ignore blank lines and comments */
634
635	/*
636	 * Wildcard entry should be ignored - following entries should continue
637	 * to be read to corroborate with the way we search for entries in
638	 * LDAP, i.e., first for an exact key match and then a wildcard
639	 * if there's no exact key match.
640	 */
641	if (key[0] == '*' && key[1] == '\0')
642		return (OD_CB_KEEPGOING);
643
644	value_len = od_cfstrlen(value);
645	if (value_len >= LINESZ)
646		return (OD_CB_KEEPGOING);
647	if (value_len < 2)
648		return (OD_CB_KEEPGOING);
649
650	if (!od_cfstrlcpy(linebuf, value, value_len))
651		return (OD_CB_KEEPGOING);
652	unquote(linebuf, lineqbuf);
653	error = add_dir_entry(key, linebuf, lineqbuf, list, &last);
654	if (error != -1) {
655		if (error != 0) {
656			temp->error = error;
657			return (OD_CB_ERROR);
658		}
659		temp->last = last;
660	}
661
662	if (trace > 1)
663		trace_prt(1, "readdir_callback returning OD_CB_KEEPGOING...\n");
664
665	return (OD_CB_KEEPGOING);
666}
667
668/*
669 * Looks for kODAttributeTypeRecordName and
670 * kODAttributeTypeAutomountInformation; if it finds them, it calls
671 * the specified callback with that information.
672 */
673static callback_ret_t
674od_process_record_attributes(ODRecordRef record, callback_fn callback,
675    void *udata)
676{
677	CFErrorRef error;
678	char *errstring;
679	CFArrayRef keys;
680	CFStringRef key;
681	CFArrayRef values;
682	CFStringRef value;
683	callback_ret_t ret;
684
685	if (trace > 1) {
686		trace_prt(1,
687		"od_process_record_attributes entered\n");
688	}
689
690	/*
691	 * Get kODAttributeTypeRecordName and
692	 * kODAttributeTypeAutomountInformation for this record.
693	 *
694	 * Even though LDAP allows for multiple values per attribute, we take
695	 * only the 1st value for each attribute because the automount data is
696	 * organized as such (same as NIS+).
697	 */
698	error = NULL;
699	keys = ODRecordCopyValues(record, kODAttributeTypeRecordName, &error);
700	if (keys == NULL) {
701		if (error != NULL) {
702			errstring = od_get_error_string(error);
703			pr_msg("od_process_record_attributes: can't get kODAttributeTypeRecordName attribute for record: %s",
704			    errstring);
705			free(errstring);
706			return (OD_CB_ERROR);
707		} else {
708			/*
709			 * We just reject records missing the attributes
710			 * we need.
711			 */
712			pr_msg("od_process_record_attributes: record has no kODAttributeTypeRecordName attribute");
713			return (OD_CB_REJECTED);
714		}
715	}
716	if (CFArrayGetCount(keys) == 0) {
717		/*
718		 * We just reject records missing the attributes
719		 * we need.
720		 */
721		CFRelease(keys);
722		pr_msg("od_process_record_attributes: record has no kODAttributeTypeRecordName attribute");
723		return (OD_CB_REJECTED);
724	}
725	key = CFArrayGetValueAtIndex(keys, 0);
726	error = NULL;
727	values = ODRecordCopyValues(record,
728	    kODAttributeTypeAutomountInformation, &error);
729	if (values == NULL) {
730		CFRelease(keys);
731		if (error != NULL) {
732			errstring = od_get_error_string(error);
733			pr_msg("od_process_record_attributes: can't get kODAttributeTypeAutomountInformation attribute for record: %s",
734			    errstring);
735			free(errstring);
736			return (OD_CB_ERROR);
737		} else {
738			/*
739			 * We just reject records missing the attributes
740			 * we need.
741			 */
742			pr_msg("od_process_record_attributes: record has no kODAttributeTypeAutomountInformation attribute");
743			return (OD_CB_REJECTED);
744		}
745	}
746	if (CFArrayGetCount(values) == 0) {
747		/*
748		 * We just reject records missing the attributes
749		 * we need.
750		 */
751		CFRelease(values);
752		CFRelease(keys);
753		pr_msg("od_process_record_attributes: record has no kODAttributeTypeRecordName attribute");
754		return (OD_CB_REJECTED);
755	}
756	value = CFArrayGetValueAtIndex(values, 0);
757
758	/*
759	 * We have both of the attributes we need.
760	 */
761	ret = (*callback)(key, value, udata);
762	CFRelease(values);
763	CFRelease(keys);
764	return (ret);
765}
766
767/*
768 * Fetch all the map records in Open Directory that have a certain attribute
769 * that matches a certain value and pass those records to a callback function.
770 */
771static int
772od_search(CFStringRef attr_to_match, char *value_to_match, callback_fn callback,
773    void *udata)
774{
775	int ret;
776	CFErrorRef error;
777	char *errstring;
778	ODNodeRef node_ref;
779	CFArrayRef attrs;
780	CFStringRef value_to_match_cfstr;
781	ODQueryRef query_ref;
782	CFArrayRef results;
783	CFIndex num_results;
784	CFIndex i;
785	ODRecordRef record;
786	callback_ret_t callback_ret;
787
788	/*
789	 * Create the search node.
790	 */
791	error = NULL;
792	node_ref = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault,
793	     kODNodeTypeAuthentication, &error);
794	if (node_ref == NULL) {
795		errstring = od_get_error_string(error);
796		pr_msg("od_search: can't create search node for /Search: %s",
797		    errstring);
798		free(errstring);
799		return (__NSW_UNAVAIL);
800	}
801
802	/*
803	 * Create the query.
804	 */
805	value_to_match_cfstr = CFStringCreateWithCString(kCFAllocatorDefault,
806	    value_to_match, kCFStringEncodingUTF8);
807	if (value_to_match_cfstr == NULL) {
808		CFRelease(node_ref);
809		pr_msg("od_search: can't make CFString from %s",
810		    value_to_match);
811		return (__NSW_UNAVAIL);
812	}
813	attrs = CFArrayCreate(kCFAllocatorDefault,
814	    (const void *[2]){kODAttributeTypeRecordName,
815	                      kODAttributeTypeAutomountInformation}, 2,
816	    &kCFTypeArrayCallBacks);
817	if (attrs == NULL) {
818		CFRelease(value_to_match_cfstr);
819		CFRelease(node_ref);
820		pr_msg("od_search: can't make array of attribute types");
821		return (__NSW_UNAVAIL);
822	}
823	error = NULL;
824	query_ref = ODQueryCreateWithNode(kCFAllocatorDefault, node_ref,
825	    kODRecordTypeAutomount, attr_to_match, kODMatchEqualTo,
826	    value_to_match_cfstr, attrs, 0, &error);
827	CFRelease(attrs);
828	CFRelease(value_to_match_cfstr);
829	if (query_ref == NULL) {
830		CFRelease(node_ref);
831		errstring = od_get_error_string(error);
832		pr_msg("od_search: can't create query: %s",
833		    errstring);
834		free(errstring);
835		return (__NSW_UNAVAIL);
836	}
837
838	/*
839	 * Wait for the query to get all the results, and then copy them.
840	 */
841	error = NULL;
842	results = ODQueryCopyResults(query_ref, false, &error);
843	if (results == NULL) {
844		CFRelease(query_ref);
845		CFRelease(node_ref);
846		errstring = od_get_error_string(error);
847		pr_msg("od_search: query failed: %s", errstring);
848		free(errstring);
849		return (__NSW_UNAVAIL);
850	}
851
852	ret = __NSW_NOTFOUND;	/* we haven't found any records yet */
853	num_results = CFArrayGetCount(results);
854	for (i = 0; i < num_results; i++) {
855		/*
856		 * We've found a record.
857		 */
858		record = (ODRecordRef)CFArrayGetValueAtIndex(results, i);
859		callback_ret = od_process_record_attributes(record,
860		    callback, udata);
861		if (callback_ret == OD_CB_KEEPGOING) {
862			/*
863			 * We processed one record, but we want
864			 * to keep processing records.
865			 */
866			ret = __NSW_SUCCESS;
867		} else if (callback_ret == OD_CB_DONE) {
868			/*
869			 * We processed one record, and we don't
870			 * want to see any more records.
871			 */
872			ret = __NSW_SUCCESS;
873			break;
874		} else if (callback_ret == OD_CB_ERROR) {
875			/*
876			 * Fatal error - give up.
877			 */
878			ret = __NSW_UNAVAIL;
879			break;
880		}
881
882		/*
883		 * Otherwise it's OD_CB_REJECTED, which is a non-fatal
884		 * error.  We haven't found a record, so we shouldn't
885		 * return __NSW_SUCCESS yet, but if we do find a
886		 * record, we shouldn't fail.
887		 */
888	}
889	CFRelease(results);
890	CFRelease(query_ref);
891	CFRelease(node_ref);
892	return (ret);
893}
894