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