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 */
34static struct berval passwd_filter = BER_BVC("(objectClass=posixAccount)");
35
36/* the attributes used in searches */
37static 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 */
60static struct berval default_passwd_userPassword	= BER_BVC("*"); /* unmatchable */
61static struct berval default_passwd_homeDirectory	= BER_BVC("");
62static struct berval default_passwd_loginShell		= BER_BVC("");
63
64static struct berval shadow_passwd = BER_BVC("x");
65
66NSSOV_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*/
81int 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 */
113int 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
150int 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
165int 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
201NSSOV_CBPRIV(passwd,
202	char buf[256];
203	struct berval name;
204	struct berval id;);
205
206static struct berval shadowclass = BER_BVC("shadowAccount");
207
208static 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
393NSSOV_CB(passwd)
394
395NSSOV_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
413NSSOV_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
428NSSOV_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