1 /*
2    schema conversion routines
3 
4    Copyright (C) Andrew Bartlett 2006-2008
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 
19 */
20 
21 #include "includes.h"
22 #include "ldb.h"
23 #include "dsdb/samdb/samdb.h"
24 #include "system/locale.h"
25 
26 #define SEPERATOR "\n  "
27 
28 struct attr_map {
29 	char *old_attr;
30 	char *new_attr;
31 };
32 
33 struct oid_map {
34 	char *old_oid;
35 	char *new_oid;
36 };
37 
print_schema_recursive(char * append_to_string,struct dsdb_schema * schema,const char * print_class,enum dsdb_schema_convert_target target,const char ** attrs_skip,const struct attr_map * attr_map,const struct oid_map * oid_map)38 static char *print_schema_recursive(char *append_to_string, struct dsdb_schema *schema, const char *print_class,
39 				    enum dsdb_schema_convert_target target,
40 				    const char **attrs_skip, const struct attr_map *attr_map, const struct oid_map *oid_map)
41 {
42 	char *out = append_to_string;
43 	const struct dsdb_class *objectclass;
44 	objectclass = dsdb_class_by_lDAPDisplayName(schema, print_class);
45 	if (!objectclass) {
46 		DEBUG(0, ("Cannot find class %s in schema\n", print_class));
47 		return NULL;
48 	}
49 
50 	do {
51 		TALLOC_CTX *mem_ctx = talloc_new(append_to_string);
52 		const char *name = objectclass->lDAPDisplayName;
53 		const char *oid = objectclass->governsID_oid;
54 		const char *subClassOf = objectclass->subClassOf;
55 		int objectClassCategory = objectclass->objectClassCategory;
56 		const char **must;
57 		const char **may;
58 		char *schema_entry = NULL;
59 		struct ldb_val objectclass_name_as_ldb_val = data_blob_string_const(objectclass->lDAPDisplayName);
60 		struct ldb_message_element objectclass_name_as_el = {
61 			.name = "objectClass",
62 			.num_values = 1,
63 			.values = &objectclass_name_as_ldb_val
64 		};
65 		unsigned int j;
66 		unsigned int attr_idx;
67 
68 		if (!mem_ctx) {
69 			DEBUG(0, ("Failed to create new talloc context\n"));
70 			return NULL;
71 		}
72 
73 		/* We have been asked to skip some attributes/objectClasses */
74 		if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
75 			continue;
76 		}
77 
78 		/* We might have been asked to remap this oid, due to a conflict */
79 		for (j=0; oid_map && oid_map[j].old_oid; j++) {
80 			if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
81 				oid =  oid_map[j].new_oid;
82 				break;
83 			}
84 		}
85 
86 		/* We might have been asked to remap this name, due to a conflict */
87 		for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
88 			if (strcasecmp(name, attr_map[j].old_attr) == 0) {
89 				name =  attr_map[j].new_attr;
90 				break;
91 			}
92 		}
93 
94 		/* We might have been asked to remap this subClassOf, due to a conflict */
95 		for (j=0; subClassOf && attr_map && attr_map[j].old_attr; j++) {
96 			if (strcasecmp(subClassOf, attr_map[j].old_attr) == 0) {
97 				subClassOf =  attr_map[j].new_attr;
98 				break;
99 			}
100 		}
101 
102 		may = dsdb_full_attribute_list(mem_ctx, schema, &objectclass_name_as_el, DSDB_SCHEMA_ALL_MAY);
103 
104 		for (j=0; may && may[j]; j++) {
105 			/* We might have been asked to remap this name, due to a conflict */
106 			for (attr_idx=0; attr_map && attr_map[attr_idx].old_attr; attr_idx++) {
107 				if (strcasecmp(may[j], attr_map[attr_idx].old_attr) == 0) {
108 					may[j] =  attr_map[attr_idx].new_attr;
109 					break;
110 				}
111 			}
112 		}
113 
114 		must = dsdb_full_attribute_list(mem_ctx, schema, &objectclass_name_as_el, DSDB_SCHEMA_ALL_MUST);
115 
116 		for (j=0; must && must[j]; j++) {
117 			/* We might have been asked to remap this name, due to a conflict */
118 			for (attr_idx=0; attr_map && attr_map[attr_idx].old_attr; attr_idx++) {
119 				if (strcasecmp(must[j], attr_map[attr_idx].old_attr) == 0) {
120 					must[j] =  attr_map[attr_idx].new_attr;
121 					break;
122 				}
123 			}
124 		}
125 
126 		schema_entry = schema_class_description(mem_ctx, target,
127 							SEPERATOR,
128 							oid,
129 							name,
130 							NULL,
131 							subClassOf,
132 							objectClassCategory,
133 							must,
134 							may,
135 							NULL);
136 		if (schema_entry == NULL) {
137 			talloc_free(mem_ctx);
138 			DEBUG(0, ("failed to generate schema description for %s\n", name));
139 			return NULL;
140 		}
141 
142 		switch (target) {
143 		case TARGET_OPENLDAP:
144 			out = talloc_asprintf_append(out, "objectclass %s\n\n", schema_entry);
145 			break;
146 		case TARGET_FEDORA_DS:
147 			out = talloc_asprintf_append(out, "objectClasses: %s\n", schema_entry);
148 			break;
149 		default:
150 			talloc_free(mem_ctx);
151 			DEBUG(0,(__location__ " Wrong type of target %u!", (unsigned)target));
152 			return NULL;
153 		}
154 		talloc_free(mem_ctx);
155 	} while (0);
156 
157 
158 	for (objectclass=schema->classes; objectclass; objectclass = objectclass->next) {
159 		if (ldb_attr_cmp(objectclass->subClassOf, print_class) == 0
160 		    && ldb_attr_cmp(objectclass->lDAPDisplayName, print_class) != 0) {
161 			out = print_schema_recursive(out, schema, objectclass->lDAPDisplayName,
162 						     target, attrs_skip, attr_map, oid_map);
163 		}
164 	}
165 	return out;
166 }
167 
168 /* Routine to linearise our internal schema into the format that
169    OpenLDAP and Fedora DS use for their backend.
170 
171    The 'mappings' are of a format like:
172 
173 #Standard OpenLDAP attributes
174 labeledURI
175 #The memberOf plugin provides this attribute
176 memberOf
177 #These conflict with OpenLDAP builtins
178 attributeTypes:samba4AttributeTypes
179 2.5.21.5:1.3.6.1.4.1.7165.4.255.7
180 
181 */
182 
183 
dsdb_convert_schema_to_openldap(struct ldb_context * ldb,char * target_str,const char * mappings)184 char *dsdb_convert_schema_to_openldap(struct ldb_context *ldb, char *target_str, const char *mappings)
185 {
186 	/* Read list of attributes to skip, OIDs to map */
187 	TALLOC_CTX *mem_ctx = talloc_new(ldb);
188 	char *line;
189 	char *out;
190 	const char **attrs_skip = NULL;
191 	unsigned int num_skip = 0;
192 	struct oid_map *oid_map = NULL;
193 	unsigned int num_oid_maps = 0;
194 	struct attr_map *attr_map = NULL;
195 	unsigned int num_attr_maps = 0;
196 	struct dsdb_attribute *attribute;
197 	struct dsdb_schema *schema;
198 	enum dsdb_schema_convert_target target;
199 
200 	char *next_line = talloc_strdup(mem_ctx, mappings);
201 
202 	if (!target_str || strcasecmp(target_str, "openldap") == 0) {
203 		target = TARGET_OPENLDAP;
204 	} else if (strcasecmp(target_str, "fedora-ds") == 0) {
205 		target = TARGET_FEDORA_DS;
206 	} else {
207 		talloc_free(mem_ctx);
208 		DEBUG(0, ("Invalid target type for schema conversion %s\n", target_str));
209 		return NULL;
210 	}
211 
212 	/* The mappings are line-separated, and specify details such as OIDs to skip etc */
213 	while (1) {
214 		line = next_line;
215 		next_line = strchr(line, '\n');
216 		if (!next_line) {
217 			break;
218 		}
219 		next_line[0] = '\0';
220 		next_line++;
221 
222 		/* Blank Line */
223 		if (line[0] == '\0') {
224 			continue;
225 		}
226 		/* Comment */
227 		if (line[0] == '#') {
228 			continue;
229 		}
230 
231 		if (isdigit(line[0])) {
232 			char *p = strchr(line, ':');
233 			if (!p) {
234 				DEBUG(0, ("schema mapping file line has OID but no OID to map to: %s\n", line));
235 				return NULL;
236 			}
237 			p[0] = '\0';
238 			p++;
239 			oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
240 			trim_string(line, " ", " ");
241 			oid_map[num_oid_maps].old_oid = talloc_strdup(oid_map, line);
242 			trim_string(p, " ", " ");
243 			oid_map[num_oid_maps].new_oid = p;
244 			num_oid_maps++;
245 			oid_map[num_oid_maps].old_oid = NULL;
246 		} else {
247 			char *p = strchr(line, ':');
248 			if (p) {
249 				/* remap attribute/objectClass */
250 				p[0] = '\0';
251 				p++;
252 				attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
253 				trim_string(line, " ", " ");
254 				attr_map[num_attr_maps].old_attr = talloc_strdup(attr_map, line);
255 				trim_string(p, " ", " ");
256 				attr_map[num_attr_maps].new_attr = p;
257 				num_attr_maps++;
258 				attr_map[num_attr_maps].old_attr = NULL;
259 			} else {
260 				/* skip attribute/objectClass */
261 				attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
262 				trim_string(line, " ", " ");
263 				attrs_skip[num_skip] = talloc_strdup(attrs_skip, line);
264 				num_skip++;
265 				attrs_skip[num_skip] = NULL;
266 			}
267 		}
268 	}
269 
270 	schema = dsdb_get_schema(ldb, mem_ctx);
271 	if (!schema) {
272 		talloc_free(mem_ctx);
273 		DEBUG(0, ("No schema on ldb to convert!\n"));
274 		return NULL;
275 	}
276 
277 	switch (target) {
278 	case TARGET_OPENLDAP:
279 		out = talloc_strdup(mem_ctx, "");
280 		break;
281 	case TARGET_FEDORA_DS:
282 		out = talloc_strdup(mem_ctx, "dn: cn=schema\n");
283 		break;
284 	default:
285 		talloc_free(mem_ctx);
286 		DEBUG(0,(__location__ " Wrong type of target %u!", (unsigned)target));
287 		return NULL;
288 	}
289 
290 	for (attribute=schema->attributes; attribute; attribute = attribute->next) {
291 		const char *name = attribute->lDAPDisplayName;
292 		const char *oid = attribute->attributeID_oid;
293 		const char *syntax = attribute->attributeSyntax_oid;
294 		const char *equality = NULL, *substring = NULL;
295 		bool single_value = attribute->isSingleValued;
296 
297 		char *schema_entry = NULL;
298 		unsigned int j;
299 
300 		/* We have been asked to skip some attributes/objectClasses */
301 		if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
302 			continue;
303 		}
304 
305 		/* We might have been asked to remap this oid, due to a conflict */
306 		for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
307 			if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
308 				oid =  oid_map[j].new_oid;
309 				break;
310 			}
311 		}
312 
313 		if (attribute->syntax) {
314 			/* We might have been asked to remap this oid,
315 			 * due to a conflict, or lack of
316 			 * implementation */
317 			syntax = attribute->syntax->ldap_oid;
318 			/* We might have been asked to remap this oid, due to a conflict */
319 			for (j=0; syntax && oid_map && oid_map[j].old_oid; j++) {
320 				if (strcasecmp(syntax, oid_map[j].old_oid) == 0) {
321 					syntax =  oid_map[j].new_oid;
322 					break;
323 				}
324 			}
325 
326 			equality = attribute->syntax->equality;
327 			substring = attribute->syntax->substring;
328 		}
329 
330 		/* We might have been asked to remap this name, due to a conflict */
331 		for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
332 			if (strcasecmp(name, attr_map[j].old_attr) == 0) {
333 				name =  attr_map[j].new_attr;
334 				break;
335 			}
336 		}
337 
338 		schema_entry = schema_attribute_description(mem_ctx,
339 							    target,
340 							    SEPERATOR,
341 							    oid,
342 							    name,
343 							    equality,
344 							    substring,
345 							    syntax,
346 							    single_value,
347 							    false,
348 							    NULL, NULL,
349 							    NULL, NULL,
350 							    false, false);
351 
352 		if (schema_entry == NULL) {
353 			talloc_free(mem_ctx);
354 			DEBUG(0, ("failed to generate attribute description for %s\n", name));
355 			return NULL;
356 		}
357 
358 		switch (target) {
359 		case TARGET_OPENLDAP:
360 			out = talloc_asprintf_append(out, "attributetype %s\n\n", schema_entry);
361 			break;
362 		case TARGET_FEDORA_DS:
363 			out = talloc_asprintf_append(out, "attributeTypes: %s\n", schema_entry);
364 			break;
365 		default:
366 			talloc_free(mem_ctx);
367 			DEBUG(0,(__location__ " Wrong type of target %u!", (unsigned)target));
368 			return NULL;
369 		}
370 	}
371 
372 	out = print_schema_recursive(out, schema, "top", target, attrs_skip, attr_map, oid_map);
373 
374 	talloc_steal(ldb, out);
375 	talloc_free(mem_ctx);
376 
377 	return out;
378 }
379 
380