1/* passwd.c - password lookup routines */
2/* $OpenLDAP$ */
3/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 *
5 * Copyright 2008-2011 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 */
32static struct berval passwd_filter = BER_BVC("(objectClass=posixAccount)");
33
34/* the attributes used in searches */
35static 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 */
58static struct berval default_passwd_userPassword	= BER_BVC("*"); /* unmatchable */
59static struct berval default_passwd_homeDirectory	= BER_BVC("");
60static struct berval default_passwd_loginShell		= BER_BVC("");
61
62static struct berval shadow_passwd = BER_BVC("x");
63
64NSSOV_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*/
79int 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 */
111int 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
148int 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
163int 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
199NSSOV_CBPRIV(passwd,
200	char buf[256];
201	struct berval name;
202	struct berval id;);
203
204static struct berval shadowclass = BER_BVC("shadowAccount");
205
206static int write_passwd(nssov_passwd_cbp *cbp,Entry *entry)
207{
208	int32_t tmpint32;
209	struct berval tmparr[2], tmpuid[2];
210	const char **tmpvalues;
211	char *tmp;
212	struct berval *names;
213	struct berval *uids;
214	struct berval passwd = {0};
215	gid_t gid;
216	struct berval gecos;
217	struct berval homedir;
218	struct berval shell;
219	Attribute *a;
220	int i,j;
221	int use_shadow = 0;
222	/* get the usernames for this entry */
223	if (BER_BVISNULL(&cbp->name))
224	{
225		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[UID_KEY].an_desc);
226		if (!a)
227		{
228			Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
229				entry->e_name.bv_val, cbp->mi->mi_attrs[UID_KEY].an_desc->ad_cname.bv_val,0);
230			return 0;
231		}
232		names = a->a_vals;
233	}
234	else
235	{
236		names=tmparr;
237		names[0]=cbp->name;
238		BER_BVZERO(&names[1]);
239	}
240	/* get the password for this entry */
241	a = attr_find(entry->e_attrs, slap_schema.si_ad_objectClass);
242	if ( a ) {
243		for ( i=0; i<a->a_numvals; i++) {
244			if ( bvmatch( &shadowclass, &a->a_nvals[i] )) {
245				use_shadow = 1;
246				break;
247			}
248		}
249	}
250	if ( use_shadow )
251	{
252		/* if the entry has a shadowAccount entry, point to that instead */
253		passwd = shadow_passwd;
254	}
255	else
256	{
257		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[PWD_KEY].an_desc);
258		if (a)
259			get_userpassword(&a->a_vals[0], &passwd);
260		if (BER_BVISNULL(&passwd))
261			passwd=default_passwd_userPassword;
262	}
263	/* get the uids for this entry */
264	if (BER_BVISNULL(&cbp->id))
265	{
266		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[UIDN_KEY].an_desc);
267        if ( !a )
268		{
269			Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
270				entry->e_name.bv_val, cbp->mi->mi_attrs[UIDN_KEY].an_desc->ad_cname.bv_val,0);
271			return 0;
272		}
273		uids = a->a_vals;
274	}
275	else
276	{
277		uids = tmpuid;
278		uids[0] = cbp->id;
279		BER_BVZERO(&uids[1]);
280	}
281	/* get the gid for this entry */
282	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[GIDN_KEY].an_desc);
283	if (!a)
284	{
285		Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
286			entry->e_name.bv_val, cbp->mi->mi_attrs[GIDN_KEY].an_desc->ad_cname.bv_val,0);
287		return 0;
288	}
289	else if (a->a_numvals != 1)
290	{
291		Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s values\n",
292			entry->e_name.bv_val, cbp->mi->mi_attrs[GIDN_KEY].an_desc->ad_cname.bv_val,0);
293	}
294	gid=(gid_t)strtol(a->a_vals[0].bv_val,&tmp,0);
295	if ((a->a_vals[0].bv_val[0]=='\0')||(*tmp!='\0'))
296	{
297		Debug(LDAP_DEBUG_ANY,"passwd entry %s contains non-numeric %s value\n",
298			entry->e_name.bv_val, cbp->mi->mi_attrs[GIDN_KEY].an_desc->ad_cname.bv_val,0);
299		return 0;
300	}
301	/* get the gecos for this entry (fall back to cn) */
302	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[GEC_KEY].an_desc);
303	if (!a)
304		a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[CN_KEY].an_desc);
305	if (!a || !a->a_numvals)
306	{
307		Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s or %s value\n",
308			entry->e_name.bv_val,
309			cbp->mi->mi_attrs[GEC_KEY].an_desc->ad_cname.bv_val,
310			cbp->mi->mi_attrs[CN_KEY].an_desc->ad_cname.bv_val);
311		return 0;
312	}
313	else if (a->a_numvals > 1)
314	{
315		Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s or %s values\n",
316			entry->e_name.bv_val,
317			cbp->mi->mi_attrs[GEC_KEY].an_desc->ad_cname.bv_val,
318			cbp->mi->mi_attrs[CN_KEY].an_desc->ad_cname.bv_val);
319	}
320	gecos=a->a_vals[0];
321	/* get the home directory for this entry */
322	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[DIR_KEY].an_desc);
323	if (!a)
324	{
325		Debug(LDAP_DEBUG_ANY,"passwd entry %s does not contain %s value\n",
326			entry->e_name.bv_val, cbp->mi->mi_attrs[DIR_KEY].an_desc->ad_cname.bv_val,0);
327		homedir=default_passwd_homeDirectory;
328	}
329	else
330	{
331		if (a->a_numvals > 1)
332		{
333			Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s values\n",
334				entry->e_name.bv_val, cbp->mi->mi_attrs[DIR_KEY].an_desc->ad_cname.bv_val,0);
335		}
336		homedir=a->a_vals[0];
337		if (homedir.bv_val[0]=='\0')
338			homedir=default_passwd_homeDirectory;
339	}
340	/* get the shell for this entry */
341	a = attr_find(entry->e_attrs, cbp->mi->mi_attrs[SHL_KEY].an_desc);
342	if (!a)
343	{
344		shell=default_passwd_loginShell;
345	}
346	else
347	{
348		if (a->a_numvals > 1)
349		{
350			Debug(LDAP_DEBUG_ANY,"passwd entry %s contains multiple %s values\n",
351				entry->e_name.bv_val, cbp->mi->mi_attrs[SHL_KEY].an_desc->ad_cname.bv_val,0);
352		}
353		shell=a->a_vals[0];
354		if (shell.bv_val[0]=='\0')
355			shell=default_passwd_loginShell;
356	}
357	/* write the entries */
358	for (i=0;!BER_BVISNULL(&names[i]);i++)
359	{
360		if (!isvalidusername(&names[i]))
361		{
362			Debug(LDAP_DEBUG_ANY,"nssov: passwd entry %s contains invalid user name: \"%s\"\n",
363				entry->e_name.bv_val,names[i].bv_val,0);
364		}
365		else
366		{
367			for (j=0;!BER_BVISNULL(&uids[j]);j++)
368			{
369				char *tmp;
370				uid_t uid;
371				uid = strtol(uids[j].bv_val, &tmp, 0);
372				if ( *tmp ) {
373					Debug(LDAP_DEBUG_ANY,"nssov: passwd entry %s contains non-numeric %s value: \"%s\"\n",
374						entry->e_name.bv_val, cbp->mi->mi_attrs[UIDN_KEY].an_desc->ad_cname.bv_val,
375						names[i].bv_val);
376					continue;
377				}
378				WRITE_INT32(cbp->fp,NSLCD_RESULT_BEGIN);
379				WRITE_BERVAL(cbp->fp,&names[i]);
380				WRITE_BERVAL(cbp->fp,&passwd);
381				WRITE_TYPE(cbp->fp,uid,uid_t);
382				WRITE_TYPE(cbp->fp,gid,gid_t);
383				WRITE_BERVAL(cbp->fp,&gecos);
384				WRITE_BERVAL(cbp->fp,&homedir);
385				WRITE_BERVAL(cbp->fp,&shell);
386			}
387		}
388	}
389	return 0;
390}
391
392NSSOV_CB(passwd)
393
394NSSOV_HANDLE(
395	passwd,byname,
396	char fbuf[1024];
397	struct berval filter = {sizeof(fbuf)};
398	filter.bv_val = fbuf;
399	READ_STRING(fp,cbp.buf);
400	cbp.name.bv_len = tmpint32;
401	cbp.name.bv_val = cbp.buf;
402	if (!isvalidusername(&cbp.name)) {
403		Debug(LDAP_DEBUG_ANY,"nssov_passwd_byname(%s): invalid user name\n",cbp.name.bv_val,0,0);
404		return -1;
405	}
406	BER_BVZERO(&cbp.id); ,
407	Debug(LDAP_DEBUG_TRACE,"nssov_passwd_byname(%s)\n",cbp.name.bv_val,0,0);,
408	NSLCD_ACTION_PASSWD_BYNAME,
409	nssov_filter_byname(cbp.mi,UID_KEY,&cbp.name,&filter)
410)
411
412NSSOV_HANDLE(
413	passwd,byuid,
414	uid_t uid;
415	char fbuf[1024];
416	struct berval filter = {sizeof(fbuf)};
417	filter.bv_val = fbuf;
418	READ_TYPE(fp,uid,uid_t);
419	cbp.id.bv_val = cbp.buf;
420	cbp.id.bv_len = snprintf(cbp.buf,sizeof(cbp.buf),"%d",uid);
421	BER_BVZERO(&cbp.name);,
422	Debug(LDAP_DEBUG_TRACE,"nssov_passwd_byuid(%s)\n",cbp.id.bv_val,0,0);,
423	NSLCD_ACTION_PASSWD_BYUID,
424	nssov_filter_byid(cbp.mi,UIDN_KEY,&cbp.id,&filter)
425)
426
427NSSOV_HANDLE(
428	passwd,all,
429	struct berval filter;
430	/* no parameters to read */
431	BER_BVZERO(&cbp.name);
432	BER_BVZERO(&cbp.id);,
433	Debug(LDAP_DEBUG_TRACE,"nssov_passwd_all()\n",0,0,0);,
434	NSLCD_ACTION_PASSWD_ALL,
435	(filter=cbp.mi->mi_filter,0)
436)
437