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 (c) 1999-2001 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <user_attr.h>
31 #include <pwd.h>
32 #include <grp.h>
33 #include <userdefs.h>
34 #include <project.h>
35 #include <memory.h>
36 #include <nss_dbdefs.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/param.h>
41 #include <sys/mman.h>
42 
43 #pragma weak setprojent = _setprojent
44 #pragma weak endprojent = _endprojent
45 #pragma weak getprojent = _getprojent
46 #pragma weak fgetprojent = _fgetprojent
47 #pragma weak getprojbyid = _getprojbyid
48 #pragma weak getprojbyname = _getprojbyname
49 #pragma weak getdefaultproj = _getdefaultproj
50 #pragma weak inproj = _inproj
51 #pragma weak getprojidbyname = _getprojidbyname
52 
53 #define	DEFAULT_PROJECT	1
54 #define	NORMAL_PROJECT	0
55 
56 static int ismember(struct project *, const char *, gid_t, int);
57 static int str2project(const char *, int, void *, char *, int);
58 
59 static DEFINE_NSS_DB_ROOT(db_root);
60 static DEFINE_NSS_GETENT(context);
61 
62 void
63 _nss_initf_project(nss_db_params_t *p)
64 {
65 	p->name	= NSS_DBNAM_PROJECT;
66 	p->default_config = NSS_DEFCONF_PROJECT;
67 }
68 
69 void
70 _setprojent(void)
71 {
72 	nss_setent(&db_root, _nss_initf_project, &context);
73 }
74 
75 void
76 _endprojent(void)
77 {
78 	nss_endent(&db_root, _nss_initf_project, &context);
79 	nss_delete(&db_root);
80 }
81 
82 struct project *
83 _getprojent(struct project *result, void *buffer, size_t buflen)
84 {
85 	nss_XbyY_args_t arg;
86 
87 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2project);
88 	(void) nss_getent(&db_root, _nss_initf_project, &context, &arg);
89 	return ((struct project *)NSS_XbyY_FINI(&arg));
90 }
91 
92 struct project *
93 _fgetprojent(FILE *f, struct project *result, void *buffer, size_t buflen)
94 {
95 	extern void _nss_XbyY_fgets(FILE *, nss_XbyY_args_t *);
96 	nss_XbyY_args_t arg;
97 
98 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2project);
99 	_nss_XbyY_fgets(f, &arg);
100 	return ((struct project *)NSS_XbyY_FINI(&arg));
101 }
102 
103 struct project *
104 _getprojbyid(projid_t projid, struct project *result,
105     void *buffer, size_t buflen)
106 {
107 	nss_XbyY_args_t arg;
108 
109 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2project);
110 	arg.key.projid = projid;
111 	(void) nss_search(&db_root, _nss_initf_project,
112 	    NSS_DBOP_PROJECT_BYID, &arg);
113 	return ((struct project *)NSS_XbyY_FINI(&arg));
114 }
115 
116 struct project *
117 _getprojbyname(const char *name, struct project *result,
118     void *buffer, size_t buflen)
119 {
120 	nss_XbyY_args_t arg;
121 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2project);
122 	arg.key.name = name;
123 	(void) nss_search(&db_root, _nss_initf_project,
124 	    NSS_DBOP_PROJECT_BYNAME, &arg);
125 	return ((struct project *)NSS_XbyY_FINI(&arg));
126 }
127 
128 /*
129  * The following routine checks if user specified by the second argument
130  * is allowed to join the project specified as project structure in first
131  * argument.  Information about user's default group and whether or not
132  * the project specified in the first argument is user's default project
133  * (i.e., user_attr, "default", "user.username", or "group.groupname"
134  * should also be provided.  If is_default is set to DEFAULT_PROJECT,
135  * then this function returns 1 (true), unless specified user explicitly
136  * excluded with "!user", or "!group" wildcards.
137  */
138 static int
139 ismember(struct project *proj, const char *user, gid_t gid, int is_default)
140 {
141 	char grbuf[NSS_BUFLEN_GROUP];
142 	char groupname[MAXGLEN + 1];
143 	int res = is_default;
144 	struct group grp;
145 	int group_ok = 0;
146 	char **u, **g;
147 	char *member;
148 
149 	if (getgrgid_r(gid, &grp, grbuf, NSS_BUFLEN_GROUP) != NULL) {
150 		group_ok = 1;
151 		(void) snprintf(groupname, MAXGLEN, grp.gr_name);
152 	}
153 
154 	/*
155 	 * Scan project's user list.
156 	 */
157 	for (u = proj->pj_users; *u; u++) {
158 		member = *u;
159 		if (member[0] == '!' &&
160 		    (strcmp(member + 1, user) == 0 ||
161 		    strcmp(member + 1, "*") == 0))
162 			return (0);
163 		if (strcmp(member, "*") == 0 || strcmp(member, user) == 0)
164 			res = 1;
165 	}
166 
167 	/*
168 	 * Scan project's group list.
169 	 */
170 	for (g = proj->pj_groups; *g; g++) {
171 		member = *g;
172 		/*
173 		 * Check if user's default group is included here.
174 		 */
175 		if (group_ok) {
176 			if (member[0] == '!' &&
177 			    (strcmp(member + 1, groupname) == 0 ||
178 			    strcmp(member + 1, "*") == 0))
179 				return (0);
180 			if (strcmp(member, "*") == 0 ||
181 			    strcmp(member, groupname) == 0)
182 				res = 1;
183 		}
184 		/*
185 		 * Check if user is a member of one of project's groups.
186 		 */
187 		if (getgrnam_r(member, &grp, grbuf, NSS_BUFLEN_GROUP) != NULL) {
188 			for (u = grp.gr_mem; *u; u++)
189 				if (strcmp(*u, user) == 0)
190 					res = 1;
191 		}
192 	}
193 	return (res);
194 }
195 
196 struct project *
197 _getdefaultproj(const char *user, struct project *result,
198     void *buffer, size_t buflen)
199 {
200 	char projname[PROJNAME_MAX + 1];
201 	nss_XbyY_args_t arg;
202 	userattr_t *uattr;
203 	struct passwd p;
204 	struct group g;
205 	char *attrproj;
206 
207 	NSS_XbyY_INIT(&arg, result, buffer, buflen, str2project);
208 
209 	/*
210 	 * Need user's default group ID for ismember() calls later
211 	 */
212 	if (getpwnam_r(user, &p, buffer, buflen) == NULL)
213 		return (NULL);
214 
215 	/*
216 	 * Check user_attr database first
217 	 */
218 	if ((uattr = getusernam(user)) != NULL) {
219 		if ((attrproj = kva_match(uattr->attr, "project")) != NULL) {
220 			arg.key.name = attrproj;
221 			(void) nss_search(&db_root, _nss_initf_project,
222 			    NSS_DBOP_PROJECT_BYNAME, &arg);
223 			if ((result = NSS_XbyY_FINI(&arg)) != NULL) {
224 				free_userattr(uattr);
225 				return (result);
226 			}
227 		}
228 		free_userattr(uattr);
229 	}
230 
231 	/*
232 	 * Check user.{username} and group.{groupname} projects
233 	 */
234 	(void) snprintf(projname, PROJNAME_MAX, "user.%s", user);
235 	arg.key.name = projname;
236 	(void) nss_search(&db_root, _nss_initf_project,
237 	    NSS_DBOP_PROJECT_BYNAME, &arg);
238 	if ((result = NSS_XbyY_FINI(&arg)) != NULL &&
239 	    ismember(result, user, p.pw_gid, DEFAULT_PROJECT))
240 		return (result);
241 	if (getgrgid_r(p.pw_gid, &g, buffer, buflen) != NULL) {
242 		(void) snprintf(projname, PROJNAME_MAX, "group.%s", g.gr_name);
243 		arg.key.name = projname;
244 		(void) nss_search(&db_root, _nss_initf_project,
245 		    NSS_DBOP_PROJECT_BYNAME, &arg);
246 		if ((result = NSS_XbyY_FINI(&arg)) != NULL &&
247 		    ismember(result, user, p.pw_gid, DEFAULT_PROJECT))
248 			return (result);
249 	}
250 	arg.key.name = "default";
251 	(void) nss_search(&db_root, _nss_initf_project,
252 	    NSS_DBOP_PROJECT_BYNAME, &arg);
253 	if ((result = NSS_XbyY_FINI(&arg)) != NULL &&
254 	    ismember(result, user, p.pw_gid, DEFAULT_PROJECT))
255 		return (result);
256 	return (NULL);
257 }
258 
259 int
260 _inproj(const char *user, const char *name, void *buffer, size_t buflen)
261 {
262 	char projname[PROJNAME_MAX + 1];
263 	char grbuf[NSS_BUFLEN_GROUP];
264 	nss_XbyY_args_t arg;
265 	struct project proj;
266 	struct passwd pwd;
267 	userattr_t *uattr;
268 	struct group grp;
269 	char *attrproj;
270 	gid_t gid;
271 
272 	NSS_XbyY_INIT(&arg, &proj, buffer, buflen, str2project);
273 
274 	/*
275 	 * 0. Sanity checks.
276 	 */
277 	if (getpwnam_r(user, &pwd, buffer, buflen) == NULL)
278 		return (0);		/* user does not exist */
279 	gid = pwd.pw_gid;
280 	if (getprojbyname(name, &proj, buffer, buflen) == NULL)
281 		return (0);		/* project does not exist */
282 
283 	/*
284 	 * 1. Check for special "default" project.
285 	 */
286 	if (strcmp("default", name) == 0)
287 		return (ismember(&proj, user, gid, DEFAULT_PROJECT));
288 
289 	/*
290 	 * 2. Check user_attr database.
291 	 */
292 	if ((uattr = getusernam(user)) != NULL) {
293 		if ((attrproj = kva_match(uattr->attr, "project")) != NULL) {
294 			if (strcmp(attrproj, name) == 0) {
295 				free_userattr(uattr);
296 				return (ismember(&proj, user, gid,
297 				    DEFAULT_PROJECT));
298 			}
299 		}
300 		free_userattr(uattr);
301 	}
302 
303 	/*
304 	 * 3. Check if this is a special "user.username" project.
305 	 *
306 	 * User "username" is considered to be a member of project
307 	 * "user.username" even if project's user lists do not
308 	 * include "username".
309 	 */
310 	(void) snprintf(projname, PROJNAME_MAX, "user.%s", user);
311 	if (strcmp(projname, name) == 0)
312 		return (ismember(&proj, user, gid, DEFAULT_PROJECT));
313 
314 	/*
315 	 * 4. Check if this is a special "group.groupname" project.
316 	 *
317 	 * User "username" with default group "groupname" is considered
318 	 * to be a member of project "group.groupname" even if project's
319 	 * group list does not include "groupname".
320 	 */
321 	if (getgrgid_r(gid, &grp, grbuf, NSS_LINELEN_GROUP) != NULL) {
322 		(void) snprintf(projname, PROJNAME_MAX,
323 		    "group.%s", grp.gr_name);
324 		if (strcmp(projname, name) == 0)
325 			return (ismember(&proj, user, gid, DEFAULT_PROJECT));
326 	}
327 
328 	/*
329 	 * 5. Handle all other (non-default) projects.
330 	 */
331 	return (ismember(&proj, user, gid, NORMAL_PROJECT));
332 }
333 
334 /*
335  * Just a quick wrapper around getprojbyname so that the caller does not
336  * need to allocate the buffer.
337  */
338 projid_t
339 _getprojidbyname(const char *name)
340 {
341 	struct project proj;
342 	char buf[PROJECT_BUFSZ];
343 
344 	if (getprojbyname(name, &proj, &buf, PROJECT_BUFSZ) != NULL)
345 		return (proj.pj_projid);
346 	else
347 		return ((projid_t)-1);
348 }
349 
350 static char *
351 gettok(char **nextpp, char sep)
352 {
353 	char *p = *nextpp;
354 	char *q = p;
355 	char c;
356 
357 	if (p == NULL)
358 		return (NULL);
359 	while ((c = *q) != '\0' && c != sep)
360 		q++;
361 	if (c == '\0')
362 		*nextpp = 0;
363 	else {
364 		*q++ = '\0';
365 		*nextpp = q;
366 	}
367 	return (p);
368 }
369 
370 
371 /*
372  * Return values: 0 = success, 1 = parse error, 2 = erange ...
373  * The structure pointer passed in is a structure in the caller's space
374  * wherein the field pointers would be set to areas in the buffer if
375  * need be. instring and buffer should be separate areas.
376  */
377 static int
378 str2project(const char *instr, int lenstr, void *ent, char *buffer, int buflen)
379 {
380 	struct project *project = ent;
381 	char *p, *next;
382 	char *users, *groups;
383 	char **uglist;
384 	char **limit;
385 
386 	if (lenstr + 1 > buflen)
387 		return (NSS_STR_PARSE_ERANGE);
388 	/*
389 	 * We copy the input string into the output buffer and
390 	 * operate on it in place.
391 	 */
392 	(void) memcpy(buffer, instr, lenstr);
393 	buffer[lenstr] = '\0';
394 	next = buffer;
395 
396 	limit = (char **)ROUND_DOWN(buffer + buflen, sizeof (char *));
397 
398 	/*
399 	 * Parsers for passwd and group have always been pretty rigid;
400 	 * we wouldn't want to buck a Unix tradition
401 	 */
402 	p = gettok(&next, ':');
403 	if (p == NULL || *p == '\0' || strlen(p) > PROJNAME_MAX) {
404 		/*
405 		 * empty or very long project names are not allowed
406 		 */
407 		return (NSS_STR_PARSE_ERANGE);
408 	}
409 	project->pj_name = p;
410 
411 	p = gettok(&next, ':');
412 	if (p == NULL || *p == '\0') {
413 		/*
414 		 * projid field shouldn't be empty
415 		 */
416 		return (NSS_STR_PARSE_PARSE);
417 	}
418 	project->pj_projid = (projid_t)strtol(p, NULL, 10);
419 	if (project->pj_projid < 0) {
420 		/*
421 		 * projids should be positive number
422 		 */
423 		project->pj_projid = 0;
424 		return (NSS_STR_PARSE_PARSE);
425 	}
426 
427 	p = gettok(&next, ':');
428 	if (p == NULL) {
429 		/*
430 		 * comment field can be empty but should not be last field
431 		 */
432 		return (NSS_STR_PARSE_PARSE);
433 	}
434 	project->pj_comment = p;
435 
436 	if ((users = gettok(&next, ':')) == NULL) {
437 		/*
438 		 * users field should not be last field
439 		 */
440 		return (NSS_STR_PARSE_PARSE);
441 	}
442 
443 	if ((groups = gettok(&next, ':')) == NULL) {
444 		/*
445 		 * groups field should not be last field
446 		 */
447 		return (NSS_STR_PARSE_PARSE);
448 	}
449 
450 	if (next == NULL) {
451 		/*
452 		 * attributes field should be last
453 		 */
454 		return (NSS_STR_PARSE_PARSE);
455 	}
456 
457 	project->pj_attr = next;
458 
459 	uglist = (char **)ROUND_UP(buffer + lenstr + 1, sizeof (char *));
460 	*uglist = NULL;
461 	project->pj_users = uglist;
462 	while (uglist < limit) {
463 		p = gettok(&users, ',');
464 		if (p == NULL || *p == '\0') {
465 			*uglist = 0;
466 			break;
467 		}
468 		*uglist++ = p;
469 	}
470 	if (uglist >= limit)
471 		return (NSS_STR_PARSE_ERANGE);
472 
473 	uglist++;
474 	*uglist = NULL;
475 	project->pj_groups = uglist;
476 	while (uglist < limit) {
477 		p = gettok(&groups, ',');
478 		if (p == NULL || *p == '\0') {
479 			*uglist = 0;
480 			break;
481 		}
482 		*uglist++ = p;
483 	}
484 	if (uglist >= limit)
485 		return (NSS_STR_PARSE_ERANGE);
486 
487 	return (NSS_STR_PARSE_SUCCESS);
488 }
489