1#include <stdio.h>
2#include <stdlib.h>
3#include <syslog.h>
4#include <string.h>
5
6#include <OpenDirectory/OpenDirectory.h>
7
8/*
9 * Return values for od_print_record().
10 */
11typedef enum {
12	OD_CB_KEEPGOING,		/* continue the search */
13	OD_CB_REJECTED,			/* this record had a problem - keep going */
14	OD_CB_ERROR			/* error - quit and return an error */
15} callback_ret_t;
16
17static void pr_msg(const char *fmt, ...);
18static int od_search(CFStringRef attr_to_match, char *value_to_match);
19
20int
21main(int argc, char **argv)
22{
23	int ret;
24	char *pattern;
25
26	if (argc < 2 || argc > 3) {
27		fprintf(stderr, "Usage: dumammap <map name> [ <key> ]\n");
28		return 1;
29	}
30	if (argc == 2) {
31		/*
32		 * Dump the entire map.
33		 */
34		ret = od_search(kODAttributeTypeMetaAutomountMap, argv[1]);
35	} else {
36		/*
37		 * Dump an entry in the map.
38		 * First, construct the string value to search for.
39		 */
40		if (asprintf(&pattern, "%s,automountMapName=%s", argv[2],
41		    argv[1]) == -1) {
42			pr_msg("malloc failed");
43			return 2;
44		}
45
46		/*
47		 * Now search for that entry.
48		 */
49		ret = od_search(kODAttributeTypeRecordName, pattern);
50		free(pattern);
51	}
52	return ret ? 0 : 2;
53}
54
55/*
56 * Get a C string from a CFStringRef.
57 * The string is allocated with malloc(), and must be freed when it's
58 * no longer needed.
59 */
60static char *
61od_CFStringtoCString(CFStringRef cfstr)
62{
63	char *string;
64	CFIndex length;
65
66	length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
67	    kCFStringEncodingUTF8);
68	string = malloc(length + 1);
69	if (string == NULL)
70		return (NULL);
71	if (!CFStringGetCString(cfstr, string, length + 1,
72	    kCFStringEncodingUTF8)) {
73		free(string);
74		return (NULL);
75	}
76	return (string);
77}
78
79
80static char *
81od_get_error_string(CFErrorRef err)
82{
83	CFStringRef errstringref;
84	char *errstring;
85
86	if (err != NULL) {
87		errstringref = CFErrorCopyDescription(err);
88		errstring = od_CFStringtoCString(errstringref);
89		CFRelease(errstringref);
90	} else
91		errstring = strdup("Unknown error");
92	return (errstring);
93}
94
95/*
96 * Looks for kODAttributeTypeRecordName and
97 * kODAttributeTypeAutomountInformation; if it finds them, it prints them.
98 */
99static callback_ret_t
100od_print_record(ODRecordRef record)
101{
102	CFErrorRef error;
103	char *errstring;
104	CFArrayRef keys;
105	CFStringRef key;
106	CFArrayRef values;
107	CFStringRef value;
108	char *key_cstring, *value_cstring;
109
110	/*
111	 * Get kODAttributeTypeRecordName and
112	 * kODAttributeTypeAutomountInformation for this record.
113	 *
114	 * Even though LDAP allows for multiple values per attribute, we take
115	 * only the 1st value for each attribute because the automount data is
116	 * organized as such (same as NIS+).
117	 */
118	error = NULL;
119	keys = ODRecordCopyValues(record, kODAttributeTypeRecordName, &error);
120	if (keys == NULL) {
121		if (error != NULL) {
122			errstring = od_get_error_string(error);
123			pr_msg("od_print_record: can't get kODAttributeTypeRecordName attribute for record: %s",
124			    errstring);
125			free(errstring);
126			return (OD_CB_ERROR);
127		} else {
128			/*
129			 * We just reject records missing the attributes
130			 * we need.
131			 */
132			pr_msg("od_print_record: record has no kODAttributeTypeRecordName attribute");
133			return (OD_CB_REJECTED);
134		}
135	}
136	if (CFArrayGetCount(keys) == 0) {
137		/*
138		 * We just reject records missing the attributes
139		 * we need.
140		 */
141		CFRelease(keys);
142		pr_msg("od_print_record: record has no kODAttributeTypeRecordName attribute");
143		return (OD_CB_REJECTED);
144	}
145	key = CFArrayGetValueAtIndex(keys, 0);
146	error = NULL;
147	values = ODRecordCopyValues(record,
148	    kODAttributeTypeAutomountInformation, &error);
149	if (values == NULL) {
150		CFRelease(keys);
151		if (error != NULL) {
152			errstring = od_get_error_string(error);
153			pr_msg("od_print_record: can't get kODAttributeTypeAutomountInformation attribute for record: %s",
154			    errstring);
155			free(errstring);
156			return (OD_CB_ERROR);
157		} else {
158			/*
159			 * We just reject records missing the attributes
160			 * we need.
161			 */
162			pr_msg("od_print_record: record has no kODAttributeTypeAutomountInformation attribute");
163			return (OD_CB_REJECTED);
164		}
165	}
166	if (CFArrayGetCount(values) == 0) {
167		/*
168		 * We just reject records missing the attributes
169		 * we need.
170		 */
171		CFRelease(values);
172		CFRelease(keys);
173		pr_msg("od_print_record: record has no kODAttributeTypeRecordName attribute");
174		return (OD_CB_REJECTED);
175	}
176	value = CFArrayGetValueAtIndex(values, 0);
177
178	/*
179	 * We have both of the attributes we need.
180	 */
181	key_cstring = od_CFStringtoCString(key);
182	value_cstring = od_CFStringtoCString(value);
183	printf("%s %s\n", key_cstring, value_cstring);
184	free(key_cstring);
185	free(value_cstring);
186	CFRelease(values);
187	CFRelease(keys);
188	return (OD_CB_KEEPGOING);
189}
190
191/*
192 * Fetch all the map records in Open Directory that have a certain attribute
193 * that matches a certain value and pass those records to od_print_record().
194 */
195static int
196od_search(CFStringRef attr_to_match, char *value_to_match)
197{
198	int ret;
199	CFErrorRef error;
200	char *errstring;
201	ODNodeRef node_ref;
202	CFArrayRef attrs;
203	CFStringRef value_to_match_cfstr;
204	ODQueryRef query_ref;
205	CFArrayRef results;
206	CFIndex num_results;
207	CFIndex i;
208	ODRecordRef record;
209	callback_ret_t callback_ret;
210
211	/*
212	 * Create the search node.
213	 */
214	error = NULL;
215	node_ref = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault,
216	     kODNodeTypeAuthentication, &error);
217	if (node_ref == NULL) {
218		errstring = od_get_error_string(error);
219		pr_msg("od_search: can't create search node for /Search: %s",
220		    errstring);
221		free(errstring);
222		return (0);
223	}
224
225	/*
226	 * Create the query.
227	 */
228	value_to_match_cfstr = CFStringCreateWithCString(kCFAllocatorDefault,
229	    value_to_match, kCFStringEncodingUTF8);
230	if (value_to_match_cfstr == NULL) {
231		CFRelease(node_ref);
232		pr_msg("od_search: can't make CFString from %s",
233		    value_to_match);
234		return (0);
235	}
236	attrs = CFArrayCreate(kCFAllocatorDefault,
237	    (const void *[2]){kODAttributeTypeRecordName,
238	                      kODAttributeTypeAutomountInformation}, 2,
239	    &kCFTypeArrayCallBacks);
240	if (attrs == NULL) {
241		CFRelease(value_to_match_cfstr);
242		CFRelease(node_ref);
243		pr_msg("od_search: can't make array of attribute types");
244		return (0);
245	}
246	error = NULL;
247	query_ref = ODQueryCreateWithNode(kCFAllocatorDefault, node_ref,
248	    kODRecordTypeAutomount, attr_to_match, kODMatchEqualTo,
249	    value_to_match_cfstr, attrs, 0, &error);
250	CFRelease(attrs);
251	CFRelease(value_to_match_cfstr);
252	if (query_ref == NULL) {
253		CFRelease(node_ref);
254		errstring = od_get_error_string(error);
255		pr_msg("od_search: can't create query: %s",
256		    errstring);
257		free(errstring);
258		return (0);
259	}
260
261	/*
262	 * Wait for the query to get all the results, and then copy them.
263	 */
264	error = NULL;
265	results = ODQueryCopyResults(query_ref, false, &error);
266	if (results == NULL) {
267		CFRelease(query_ref);
268		CFRelease(node_ref);
269		errstring = od_get_error_string(error);
270		pr_msg("od_search: query failed: %s", errstring);
271		free(errstring);
272		return (0);
273	}
274
275	ret = 0;	/* we haven't found any records yet */
276	num_results = CFArrayGetCount(results);
277	for (i = 0; i < num_results; i++) {
278		/*
279		 * We've found a record.
280		 */
281		record = (ODRecordRef)CFArrayGetValueAtIndex(results, i);
282		callback_ret = od_print_record(record);
283		if (callback_ret == OD_CB_KEEPGOING) {
284			/*
285			 * We processed one record, but we want
286			 * to keep processing records.
287			 */
288			ret = 1;
289		} else if (callback_ret == OD_CB_ERROR) {
290			/*
291			 * Fatal error - give up.
292			 */
293			break;
294		}
295
296		/*
297		 * Otherwise it's OD_CB_REJECTED, which is a non-fatal
298		 * error.  We haven't found a record, so we shouldn't
299		 * return __NSW_SUCCESS yet, but if we do find a
300		 * record, we shouldn't fail.
301		 */
302	}
303	CFRelease(results);
304	CFRelease(query_ref);
305	CFRelease(node_ref);
306	return (ret);
307}
308
309/*
310 * Print an error.
311 */
312static void
313pr_msg(const char *fmt, ...)
314{
315	va_list ap;
316
317	(void) fprintf(stderr, "dumpammap: ");
318	va_start(ap, fmt);
319	(void) vfprintf(stderr, fmt, ap);
320	putc('\n', stderr);
321	va_end(ap);
322}
323
324