xref: /netbsd-src/external/bsd/openldap/dist/contrib/slapd-modules/nssov/passwd.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1 /*	$NetBSD: passwd.c,v 1.3 2021/08/14 16:14:52 christos Exp $	*/
2 
3 /* passwd.c - password lookup routines */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6  *
7  * Copyright 2008-2021 The OpenLDAP Foundation.
8  * Portions Copyright 2008 by Howard Chu, Symas Corp.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted only as authorized by the OpenLDAP
13  * Public License.
14  *
15  * A copy of this license is available in the file LICENSE in the
16  * top-level directory of the distribution or, alternatively, at
17  * <http://www.OpenLDAP.org/license.html>.
18  */
19 /* ACKNOWLEDGEMENTS:
20  * This code references portions of the nss-ldapd package
21  * written by Arthur de Jong. The nss-ldapd code was forked
22  * from the nss-ldap library written by Luke Howard.
23  */
24 
25 #include "nssov.h"
26 
27 /* ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
28  *	 DESC 'Abstraction of an account with POSIX attributes'
29  *	 MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
30  *	 MAY ( userPassword $ loginShell $ gecos $ description ) )
31  */
32 
33 /* the basic search filter for searches */
34 static struct berval passwd_filter = BER_BVC("(objectClass=posixAccount)");
35 
36 /* the attributes used in searches */
37 static struct berval passwd_keys[] = {
38 	BER_BVC("uid"),
39 	BER_BVC("userPassword"),
40 	BER_BVC("uidNumber"),
41 	BER_BVC("gidNumber"),
42 	BER_BVC("gecos"),
43 	BER_BVC("cn"),
44 	BER_BVC("homeDirectory"),
45 	BER_BVC("loginShell"),
46 	BER_BVC("objectClass"),
47 	BER_BVNULL
48 };
49 
50 #define UID_KEY	0
51 #define	PWD_KEY	1
52 #define UIDN_KEY	2
53 #define GIDN_KEY	3
54 #define GEC_KEY	4
55 #define CN_KEY	5
56 #define DIR_KEY	6
57 #define SHL_KEY	7
58 
59 /* default values for attributes */
60 static struct berval default_passwd_userPassword	= BER_BVC("*"); /* unmatchable */
61 static struct berval default_passwd_homeDirectory	= BER_BVC("");
62 static struct berval default_passwd_loginShell		= BER_BVC("");
63 
64 static struct berval shadow_passwd = BER_BVC("x");
65 
NSSOV_INIT(passwd)66 NSSOV_INIT(passwd)
67 
68 /*
69 	 Checks to see if the specified name is a valid user name.
70 
71 	 This test is based on the definition from POSIX (IEEE Std 1003.1, 2004, 3.426 User Name
72 	 and 3.276 Portable Filename Character Set):
73 	 http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_426
74 	 http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_276
75 
76 	 The standard defines user names valid if they contain characters from
77 	 the set [A-Za-z0-9._-] where the hyphen should not be used as first
78 	 character. As an extension this test allows the dolar '$' sign as the last
79 	 character to support Samba special accounts.
80 */
81 int isvalidusername(struct berval *bv)
82 {
83 	int i;
84 	char *name = bv->bv_val;
85 	if ((name==NULL)||(name[0]=='\0'))
86 		return 0;
87 	/* check first character */
88 	if ( ! ( (name[0]>='A' && name[0] <= 'Z') ||
89 					 (name[0]>='a' && name[0] <= 'z') ||
90 					 (name[0]>='0' && name[0] <= '9') ||
91 					 name[0]=='.' || name[0]=='_' ) )
92 		return 0;
93 	/* check other characters */
94 	for (i=1;i<bv->bv_len;i++)
95 	{
96 		if ( name[i]=='$' )
97 		{
98 			/* if the char is $ we require it to be the last char */
99 			if (name[i+1]!='\0')
100 				return 0;
101 		}
102 		else if ( ! ( (name[i]>='A' && name[i] <= 'Z') ||
103 									(name[i]>='a' && name[i] <= 'z') ||
104 									(name[i]>='0' && name[i] <= '9') ||
105 									name[i]=='.' || name[i]=='_'	|| name[i]=='-') )
106 			return 0;
107 	}
108 	/* no test failed so it must be good */
109 	return -1;
110 }
111 
112 /* return 1 on success */
nssov_dn2uid(Operation * op,nssov_info * ni,struct berval * dn,struct berval * uid)113 int nssov_dn2uid(Operation *op,nssov_info *ni,struct berval *dn,struct berval *uid)
114 {
115 	nssov_mapinfo *mi = &ni->ni_maps[NM_passwd];
116 	AttributeDescription *ad = mi->mi_attrs[UID_KEY].an_desc;
117 	Entry *e;
118 
119 	/* check for empty string */
120 	if (!dn->bv_len)
121 		return 0;
122 	/* try to look up uid within DN string */
123 	if (!strncmp(dn->bv_val,ad->ad_cname.bv_val,ad->ad_cname.bv_len) &&
124 		dn->bv_val[ad->ad_cname.bv_len] == '=')
125 	{
126 		struct berval bv, rdn;
127 		dnRdn(dn, &rdn);
128 		/* check if it is valid */
129 		bv.bv_val = dn->bv_val + ad->ad_cname.bv_len + 1;
130 		bv.bv_len = rdn.bv_len - ad->ad_cname.bv_len - 1;
131 		if (!isvalidusername(&bv))
132 			return 0;
133 		ber_dupbv_x( uid, &bv, op->o_tmpmemctx );
134 		return 1;
135 	}
136 	/* look up the uid from the entry itself */
137 	if (be_entry_get_rw( op, dn, NULL, ad, 0, &e) == LDAP_SUCCESS)
138 	{
139 		Attribute *a = attr_find(e->e_attrs, ad);
140 		if (a) {
141 			ber_dupbv_x(uid, &a->a_vals[0], op->o_tmpmemctx);
142 		}
143 		be_entry_release_r(op, e);
144 		if (a)
145 			return 1;
146 	}
147 	return 0;
148 }
149 
nssov_name2dn_cb(Operation * op,SlapReply * rs)150 int nssov_name2dn_cb(Operation *op,SlapReply *rs)
151 {
152 	if ( rs->sr_type == REP_SEARCH )
153 	{
154 		struct berval *bv = op->o_callback->sc_private;
155 		if ( !BER_BVISNULL(bv)) {
156 			op->o_tmpfree( bv->bv_val, op->o_tmpmemctx );
157 			BER_BVZERO(bv);
158 			return LDAP_ALREADY_EXISTS;
159 		}
160 		ber_dupbv_x(bv, &rs->sr_entry->e_name, op->o_tmpmemctx);
161 	}
162 	return LDAP_SUCCESS;
163 }
164 
nssov_uid2dn(Operation * op,nssov_info * ni,struct berval * uid,struct berval * dn)165 int nssov_uid2dn(Operation *op,nssov_info *ni,struct berval *uid,struct berval *dn)
166 {
167 	nssov_mapinfo *mi = &ni->ni_maps[NM_passwd];
168 	char fbuf[1024];
169 	struct berval filter = {sizeof(fbuf),fbuf};
170 	slap_callback cb = {0};
171 	SlapReply rs = {REP_RESULT};
172 	Operation op2;
173 	int rc;
174 
175 	/* if it isn't a valid username, just bail out now */
176 	if (!isvalidusername(uid))
177 		return 0;
178 	/* we have to look up the entry */
179 	nssov_filter_byid(mi,UID_KEY,uid,&filter);
180 	BER_BVZERO(dn);
181 	cb.sc_private = dn;
182 	cb.sc_response = nssov_name2dn_cb;
183 	op2 = *op;
184 	op2.o_callback = &cb;
185 	op2.o_req_dn = mi->mi_base;
186 	op2.o_req_ndn = mi->mi_base;
187 	op2.ors_scope = mi->mi_scope;
188 	op2.ors_filterstr = filter;
189 	op2.ors_filter = str2filter_x( op, filter.bv_val );
190 	op2.ors_attrs = slap_anlist_no_attrs;
191 	op2.ors_tlimit = SLAP_NO_LIMIT;
192 	op2.ors_slimit = SLAP_NO_LIMIT;
193 	rc = op2.o_bd->be_search( &op2, &rs );
194 	filter_free_x( op, op2.ors_filter, 1 );
195 	return rc == LDAP_SUCCESS && !BER_BVISNULL(dn);
196 }
197 
198 /* the maximum number of uidNumber attributes per entry */
199 #define MAXUIDS_PER_ENTRY 5
200 
201 NSSOV_CBPRIV(passwd,
202 	char buf[256];
203 	struct berval name;
204 	struct berval id;);
205 
206 static struct berval shadowclass = BER_BVC("shadowAccount");
207 
write_passwd(nssov_passwd_cbp * cbp,Entry * entry)208 static int write_passwd(nssov_passwd_cbp *cbp,Entry *entry)
209 {
210 	int32_t tmpint32;
211 	struct berval tmparr[2], tmpuid[2];
212 	char *tmp;
213 	struct berval *names;
214 	struct berval *uids;
215 	struct berval passwd = {0};
216 	gid_t gid;
217 	struct berval gecos;
218 	struct berval homedir;
219 	struct berval shell;
220 	Attribute *a;
221 	int i,j;
222 	int use_shadow = 0;
223 	/* get the usernames for this entry */
224 	if (BER_BVISNULL(&cbp->name))
225 	{
226 		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[UID_KEY].an_desc);
227 		if (!a)
228 		{
229 			Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
230 				entry->e_name.bv_val, cbp->mi->mi_attrs[UID_KEY].an_desc->ad_cname.bv_val );
231 			return 0;
232 		}
233 		names = a->a_vals;
234 	}
235 	else
236 	{
237 		names=tmparr;
238 		names[0]=cbp->name;
239 		BER_BVZERO(&names[1]);
240 	}
241 	/* get the password for this entry */
242 	a = attr_find(entry->e_attrs, slap_schema.si_ad_objectClass);
243 	if ( a ) {
244 		for ( i=0; i<a->a_numvals; i++) {
245 			if ( bvmatch( &shadowclass, &a->a_nvals[i] )) {
246 				use_shadow = 1;
247 				break;
248 			}
249 		}
250 	}
251 	if ( use_shadow )
252 	{
253 		/* if the entry has a shadowAccount entry, point to that instead */
254 		passwd = shadow_passwd;
255 	}
256 	else
257 	{
258 		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[PWD_KEY].an_desc);
259 		if (a)
260 			get_userpassword(&a->a_vals[0], &passwd);
261 		if (BER_BVISNULL(&passwd))
262 			passwd=default_passwd_userPassword;
263 	}
264 	/* get the uids for this entry */
265 	if (BER_BVISNULL(&cbp->id))
266 	{
267 		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[UIDN_KEY].an_desc);
268         if ( !a )
269 		{
270 			Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
271 				entry->e_name.bv_val, cbp->mi->mi_attrs[UIDN_KEY].an_desc->ad_cname.bv_val );
272 			return 0;
273 		}
274 		uids = a->a_vals;
275 	}
276 	else
277 	{
278 		uids = tmpuid;
279 		uids[0] = cbp->id;
280 		BER_BVZERO(&uids[1]);
281 	}
282 	/* get the gid for this entry */
283 	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[GIDN_KEY].an_desc);
284 	if (!a)
285 	{
286 		Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
287 			entry->e_name.bv_val, cbp->mi->mi_attrs[GIDN_KEY].an_desc->ad_cname.bv_val );
288 		return 0;
289 	}
290 	else if (a->a_numvals != 1)
291 	{
292 		Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s values\n",
293 			entry->e_name.bv_val, cbp->mi->mi_attrs[GIDN_KEY].an_desc->ad_cname.bv_val );
294 	}
295 	gid=(gid_t)strtol(a->a_vals[0].bv_val,&tmp,0);
296 	if ((a->a_vals[0].bv_val[0]=='\0')||(*tmp!='\0'))
297 	{
298 		Debug(LDAP_DEBUG_ANY,"passwd entry %s contains non-numeric %s value\n",
299 			entry->e_name.bv_val, cbp->mi->mi_attrs[GIDN_KEY].an_desc->ad_cname.bv_val );
300 		return 0;
301 	}
302 	/* get the gecos for this entry (fall back to cn) */
303 	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[GEC_KEY].an_desc);
304 	if (!a)
305 		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[CN_KEY].an_desc);
306 	if (!a || !a->a_numvals)
307 	{
308 		Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s or %s value\n",
309 			entry->e_name.bv_val,
310 			cbp->mi->mi_attrs[GEC_KEY].an_desc->ad_cname.bv_val,
311 			cbp->mi->mi_attrs[CN_KEY].an_desc->ad_cname.bv_val);
312 		return 0;
313 	}
314 	else if (a->a_numvals > 1)
315 	{
316 		Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s or %s values\n",
317 			entry->e_name.bv_val,
318 			cbp->mi->mi_attrs[GEC_KEY].an_desc->ad_cname.bv_val,
319 			cbp->mi->mi_attrs[CN_KEY].an_desc->ad_cname.bv_val);
320 	}
321 	gecos=a->a_vals[0];
322 	/* get the home directory for this entry */
323 	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[DIR_KEY].an_desc);
324 	if (!a)
325 	{
326 		Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
327 			entry->e_name.bv_val, cbp->mi->mi_attrs[DIR_KEY].an_desc->ad_cname.bv_val );
328 		homedir=default_passwd_homeDirectory;
329 	}
330 	else
331 	{
332 		if (a->a_numvals > 1)
333 		{
334 			Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s values\n",
335 				entry->e_name.bv_val, cbp->mi->mi_attrs[DIR_KEY].an_desc->ad_cname.bv_val );
336 		}
337 		homedir=a->a_vals[0];
338 		if (homedir.bv_val[0]=='\0')
339 			homedir=default_passwd_homeDirectory;
340 	}
341 	/* get the shell for this entry */
342 	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[SHL_KEY].an_desc);
343 	if (!a)
344 	{
345 		shell=default_passwd_loginShell;
346 	}
347 	else
348 	{
349 		if (a->a_numvals > 1)
350 		{
351 			Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s values\n",
352 				entry->e_name.bv_val, cbp->mi->mi_attrs[SHL_KEY].an_desc->ad_cname.bv_val );
353 		}
354 		shell=a->a_vals[0];
355 		if (shell.bv_val[0]=='\0')
356 			shell=default_passwd_loginShell;
357 	}
358 	/* write the entries */
359 	for (i=0;!BER_BVISNULL(&names[i]);i++)
360 	{
361 		if (!isvalidusername(&names[i]))
362 		{
363 			Debug(LDAP_DEBUG_ANY,"nssov: passwd entry %s contains invalid user name: \"%s\"\n",
364 				entry->e_name.bv_val,names[i].bv_val );
365 		}
366 		else
367 		{
368 			for (j=0;!BER_BVISNULL(&uids[j]);j++)
369 			{
370 				char *tmp;
371 				uid_t uid;
372 				uid = strtol(uids[j].bv_val, &tmp, 0);
373 				if ( *tmp ) {
374 					Debug(LDAP_DEBUG_ANY,"nssov: passwd entry %s contains non-numeric %s value: \"%s\"\n",
375 						entry->e_name.bv_val, cbp->mi->mi_attrs[UIDN_KEY].an_desc->ad_cname.bv_val,
376 						names[i].bv_val);
377 					continue;
378 				}
379 				WRITE_INT32(cbp->fp,NSLCD_RESULT_BEGIN);
380 				WRITE_BERVAL(cbp->fp,&names[i]);
381 				WRITE_BERVAL(cbp->fp,&passwd);
382 				WRITE_INT32(cbp->fp,uid);
383 				WRITE_INT32(cbp->fp,gid);
384 				WRITE_BERVAL(cbp->fp,&gecos);
385 				WRITE_BERVAL(cbp->fp,&homedir);
386 				WRITE_BERVAL(cbp->fp,&shell);
387 			}
388 		}
389 	}
390 	return 0;
391 }
392 
393 NSSOV_CB(passwd)
394 
395 NSSOV_HANDLE(
396 	passwd,byname,
397 	char fbuf[1024];
398 	struct berval filter = {sizeof(fbuf)};
399 	filter.bv_val = fbuf;
400 	READ_STRING(fp,cbp.buf);
401 	cbp.name.bv_len = tmpint32;
402 	cbp.name.bv_val = cbp.buf;
403 	if (!isvalidusername(&cbp.name)) {
404 		Debug(LDAP_DEBUG_ANY,"nssov_passwd_byname(%s): invalid user name\n",cbp.name.bv_val);
405 		return -1;
406 	}
407 	BER_BVZERO(&cbp.id); ,
408 	Debug(LDAP_DEBUG_TRACE,"nssov_passwd_byname(%s)\n",cbp.name.bv_val);,
409 	NSLCD_ACTION_PASSWD_BYNAME,
410 	nssov_filter_byname(cbp.mi,UID_KEY,&cbp.name,&filter)
411 )
412 
413 NSSOV_HANDLE(
414 	passwd,byuid,
415 	uid_t uid;
416 	char fbuf[1024];
417 	struct berval filter = {sizeof(fbuf)};
418 	filter.bv_val = fbuf;
419 	READ_INT32(fp,uid);
420 	cbp.id.bv_val = cbp.buf;
421 	cbp.id.bv_len = snprintf(cbp.buf,sizeof(cbp.buf),"%d",uid);
422 	BER_BVZERO(&cbp.name);,
423 	Debug(LDAP_DEBUG_TRACE,"nssov_passwd_byuid(%s)\n",cbp.id.bv_val);,
424 	NSLCD_ACTION_PASSWD_BYUID,
425 	nssov_filter_byid(cbp.mi,UIDN_KEY,&cbp.id,&filter)
426 )
427 
428 NSSOV_HANDLE(
429 	passwd,all,
430 	struct berval filter;
431 	/* no parameters to read */
432 	BER_BVZERO(&cbp.name);
433 	BER_BVZERO(&cbp.id);,
434 	Debug(LDAP_DEBUG_TRACE,"nssov_passwd_all()\n");,
435 	NSLCD_ACTION_PASSWD_ALL,
436 	(filter=cbp.mi->mi_filter,0)
437 )
438