Do not backup master.passwd if pwd_mkdb returned an error. This
[pw-darwin.git] / chpass / pw_yp.c
1 /*
2 * Copyright (c) 1995
3 * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by Bill Paul.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 * NIS interface routines for chpass
33 *
34 * Written by Bill Paul <wpaul@ctr.columbia.edu>
35 * Center for Telecommunications Research
36 * Columbia University, New York City
37 *
38 * $Id: pw_yp.c,v 1.4 1996/03/04 15:34:43 wpaul Exp $
39 */
40
41 #ifdef YP
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <netdb.h>
46 #include <time.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <pwd.h>
50 #include <errno.h>
51 #include <err.h>
52 #include <unistd.h>
53 #include <db.h>
54 #include <fcntl.h>
55 #include <utmp.h>
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <sys/param.h>
59 #include <limits.h>
60 #include <rpc/rpc.h>
61 #include <rpcsvc/yp.h>
62 struct dom_binding {};
63 #include <rpcsvc/ypclnt.h>
64 #include <rpcsvc/yppasswd.h>
65 #include <pw_util.h>
66 #include "pw_yp.h"
67 #include "ypxfr_extern.h"
68 #include "yppasswd_comm.h"
69 #include "yppasswd_private.h"
70
71 #define PERM_SECURE (S_IRUSR|S_IWUSR)
72 static HASHINFO openinfo = {
73 4096, /* bsize */
74 32, /* ffactor */
75 256, /* nelem */
76 2048 * 1024, /* cachesize */
77 NULL, /* hash */
78 0, /* lorder */
79 };
80
81 int force_old = 0;
82 int _use_yp = 0;
83 int suser_override = 0;
84 char *yp_domain = NULL;
85 char *yp_server = NULL;
86
87 extern char *tempname;
88
89 /* Save the local and NIS password information */
90 struct passwd local_password;
91 struct passwd yp_password;
92
93 void copy_yp_pass(p, x, m)
94 char *p;
95 int x, m;
96 {
97 register char *t, *s = p;
98 static char *buf;
99
100 yp_password.pw_fields = 0;
101
102 buf = (char *)realloc(buf, m + 10);
103 bzero(buf, m + 10);
104
105 /* Turn all colons into NULLs */
106 while (strchr(s, ':')) {
107 s = (strchr(s, ':') + 1);
108 *(s - 1)= '\0';
109 }
110
111 t = buf;
112 #define EXPAND(e) e = t; while ((*t++ = *p++));
113 EXPAND(yp_password.pw_name);
114 yp_password.pw_fields |= _PWF_NAME;
115 EXPAND(yp_password.pw_passwd);
116 yp_password.pw_fields |= _PWF_PASSWD;
117 yp_password.pw_uid = atoi(p);
118 p += (strlen(p) + 1);
119 yp_password.pw_fields |= _PWF_UID;
120 yp_password.pw_gid = atoi(p);
121 p += (strlen(p) + 1);
122 yp_password.pw_fields |= _PWF_GID;
123 if (x) {
124 EXPAND(yp_password.pw_class);
125 yp_password.pw_fields |= _PWF_CLASS;
126 yp_password.pw_change = atol(p);
127 p += (strlen(p) + 1);
128 yp_password.pw_fields |= _PWF_CHANGE;
129 yp_password.pw_expire = atol(p);
130 p += (strlen(p) + 1);
131 yp_password.pw_fields |= _PWF_EXPIRE;
132 }
133 EXPAND(yp_password.pw_gecos);
134 yp_password.pw_fields |= _PWF_GECOS;
135 EXPAND(yp_password.pw_dir);
136 yp_password.pw_fields |= _PWF_DIR;
137 EXPAND(yp_password.pw_shell);
138 yp_password.pw_fields |= _PWF_SHELL;
139
140 return;
141 }
142
143 void copy_local_pass(p,m)
144 char *p;
145 int m;
146 {
147 register char *t;
148 static char *buf;
149
150 buf = (char *)realloc(buf, m + 10);
151 bzero(buf, m + 10);
152
153 t = buf;
154 EXPAND(local_password.pw_name);
155 EXPAND(local_password.pw_passwd);
156 bcopy(p, (char *)&local_password.pw_uid, sizeof(int));
157 p += sizeof(int);
158 bcopy(p, (char *)&local_password.pw_gid, sizeof(int));
159 p += sizeof(int);
160 bcopy(p, (char *)&local_password.pw_change, sizeof(time_t));
161 p += sizeof(time_t);
162 EXPAND(local_password.pw_class);
163 EXPAND(local_password.pw_gecos);
164 EXPAND(local_password.pw_dir);
165 EXPAND(local_password.pw_shell);
166 bcopy(p, (char *)&local_password.pw_expire, sizeof(time_t));
167 p += sizeof(time_t);
168 bcopy(p, (char *)&local_password.pw_fields, sizeof local_password.pw_fields);
169 p += sizeof local_password.pw_fields;
170
171 return;
172 }
173
174 /*
175 * It is not mandatory that an NIS master server also be a client.
176 * However, if the NIS master is not configured as a client, then the
177 * domain name will not be set and ypbind will not be running, so we
178 * will be unable to use the ypclnt routines inside libc. We therefore
179 * need our own magic version of yp_match() which we can use in any
180 * environment.
181 */
182 static int my_yp_match(server, domain, map, key, keylen, result, resultlen)
183 char *server;
184 char *domain;
185 char *map;
186 char *key;
187 unsigned long keylen;
188 char **result;
189 unsigned long *resultlen;
190 {
191 ypreq_key ypkey;
192 ypresp_val *ypval;
193 CLIENT *clnt;
194 static char buf[YPMAXRECORD + 2];
195
196 bzero((char *)buf, sizeof(buf));
197
198 if ((clnt = clnt_create(server, YPPROG,YPVERS,"udp")) == NULL) {
199 warnx("%s", clnt_spcreateerror("failed to create handle"));
200 pw_error(tempname, 0, 1);
201 }
202
203 ypkey.domain = domain;
204 ypkey.map = map;
205 ypkey.key.keydat_len = keylen;
206 ypkey.key.keydat_val = key;
207
208 if ((ypval = ypproc_match_2(&ypkey, clnt)) == NULL) {
209 clnt_destroy(clnt);
210 warnx("%s",clnt_sperror(clnt,"YPPROC_MATCH failed"));
211 pw_error(tempname, 0, 1);
212 }
213
214 clnt_destroy(clnt);
215
216 if (ypval->stat != YP_TRUE) {
217 int stat = ypval->stat;
218 xdr_free(xdr_ypresp_val, (char *)ypval);
219 if (stat == YP_NOMAP && strstr(map, "master.passwd"))
220 return(1);
221 if (stat == YP_NOKEY)
222 return(1);
223 warnx("ypmatch failed: %s", yperr_string(ypprot_err(stat)));
224 pw_error(tempname, 0, 1);
225 }
226
227
228 strncpy((char *)&buf, ypval->val.valdat_val, ypval->val.valdat_len);
229
230 *result = (char *)&buf;
231 *resultlen = ypval->val.valdat_len;
232
233 xdr_free(xdr_ypresp_val, (char *)ypval);
234
235 return(0);
236 }
237
238 /*
239 * Check if the user we're working with is local or in NIS.
240 */
241 int use_yp (user, uid, which)
242 char *user;
243 uid_t uid;
244 int which; /* 0 = use username, 1 = use uid */
245 {
246 int user_local = 0, user_yp = 0, user_exists = 0;
247 DB *dbp;
248 DBT key,data;
249 char bf[UT_NAMESIZE + 2];
250 char *result;
251 char *server;
252 int resultlen;
253 char ubuf[UT_NAMESIZE + 2];
254
255 if (which) {
256 snprintf(ubuf, sizeof(ubuf), "%lu", uid);
257 user = (char *)&ubuf;
258 }
259
260 /* Grope around for the user in the usual way */
261 if (which) {
262 if (getpwuid(uid) != NULL)
263 user_exists = 1;
264 } else {
265 if (getpwnam(user) != NULL)
266 user_exists = 1;
267 }
268
269 /* Now grope directly through the user database */
270 if ((dbp = dbopen(_PATH_SMP_DB, O_RDONLY, PERM_SECURE,
271 DB_HASH, &openinfo)) == NULL) {
272 warn("error opening database: %s.", _PATH_MP_DB);
273 pw_error(tempname, 0, 1);
274 }
275
276 /* Is NIS turned on */
277 bf[0] = _PW_KEYYPENABLED;
278 key.data = (u_char *)bf;
279 key.size = 1;
280 if ((!(dbp->get)(dbp,&key,&data,0) && _yp_check(NULL)) ||
281 (yp_domain && yp_server)) {
282 server = get_yp_master(0);
283
284 /* Is the user in the NIS passwd map */
285 if (!my_yp_match(server, yp_domain, which ? "passwd.byuid" :
286 "passwd.byname", user, strlen(user),
287 &result, &resultlen)) {
288 user_yp = user_exists = 1;
289 *(char *)(result + resultlen) = '\0';
290 copy_yp_pass(result, 0, resultlen);
291 }
292 /* Is the user in the NIS master.passwd map */
293 if (user_yp && !my_yp_match(server, yp_domain, which ?
294 "master.passwd.byuid" : "master.passwd.byname",
295 user, strlen(user),
296 &result, &resultlen)) {
297 *(char *)(result + resultlen) = '\0';
298 copy_yp_pass(result, 1, resultlen);
299 }
300 }
301
302 /* Is the user in the local password database */
303
304 bf[0] = which ? _PW_KEYBYUID : _PW_KEYBYNAME;
305 if (which)
306 bcopy((char *)&uid, bf + 1, sizeof(uid));
307 else
308 bcopy((char *)user, bf + 1, MIN(strlen(user), UT_NAMESIZE));
309 key.data = (u_char *)bf;
310 key.size = which ? sizeof(uid) + 1 : strlen(user) + 1;
311 if (!(dbp->get)(dbp,&key,&data,0)) {
312 user_local = 1;
313 copy_local_pass(data.data, data.size);
314 }
315
316 (dbp->close)(dbp);
317
318 if (user_local && user_yp && user_exists)
319 return(USER_YP_AND_LOCAL);
320 else if (!user_local && user_yp && user_exists)
321 return(USER_YP_ONLY);
322 else if (user_local && !user_yp && user_exists)
323 return(USER_LOCAL_ONLY);
324 else if (!user_exists)
325 return(USER_UNKNOWN);
326 return(-1);
327 }
328
329 /*
330 * Find the name of the NIS master server for this domain
331 * and make sure it's running yppasswdd.
332 */
333 char *get_yp_master(getserver)
334 int getserver;
335 {
336 char *mastername;
337 int rval, localport;
338 struct stat st;
339
340 /*
341 * Sometimes we are called just to probe for rpc.yppasswdd and
342 * set the suser_override flag. Just return NULL and leave
343 * suser_override at 0 if _use_yp doesn't indicate that NIS is
344 * in use and we weren't called from use_yp() itself.
345 * Without this check, we might try probing and fail with an NIS
346 * error in non-NIS environments.
347 */
348 if ((_use_yp == USER_UNKNOWN || _use_yp == USER_LOCAL_ONLY) &&
349 getserver)
350 return(NULL);
351
352 /* Get default NIS domain. */
353
354 if (yp_domain == NULL && (rval = yp_get_default_domain(&yp_domain))) {
355 warnx("can't get local NIS domain name: %s",yperr_string(rval));
356 pw_error(tempname, 0, 1);
357 }
358
359 /* Get master server of passwd map. */
360
361 if ((mastername = ypxfr_get_master(yp_domain, "passwd.byname",
362 yp_server, yp_server ? 0 : 1)) == NULL) {
363 warnx("can't get name of master NIS server");
364 pw_error(tempname, 0, 1);
365 }
366
367 if (!getserver)
368 return(mastername);
369
370 /* Check if yppasswdd is out there. */
371
372 if ((rval = getrpcport(mastername, YPPASSWDPROG, YPPASSWDPROC_UPDATE,
373 IPPROTO_UDP)) == 0) {
374 warnx("rpc.yppasswdd is not running on the NIS master server");
375 pw_error(tempname, 0, 1);
376 }
377
378 /*
379 * Make sure it's on a reserved port.
380 * XXX Might break with yppasswdd servers running on Solaris 2.x.
381 */
382
383 if (rval >= IPPORT_RESERVED) {
384 warnx("rpc.yppasswdd server not running on reserved port");
385 pw_error(tempname, 0, 1);
386 }
387
388 /* See if _we_ are the master server. */
389 if (!force_old && !getuid() && (localport = getrpcport("localhost",
390 YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP)) != 0) {
391 if (localport == rval && stat(sockname, &st) != -1) {
392 suser_override = 1;
393 mastername = "localhost";
394 }
395 }
396
397 /* Everything checks out: return the name of the server. */
398
399 return (mastername);
400 }
401
402 /*
403 * Ask the user for his NIS password and submit the new information
404 * to yppasswdd. Note that rpc.yppasswdd requires password authentication
405 * and only allows changes to existing records rather than the addition
406 * of new records. (To do actual updates we would need something like
407 * secure RPC and ypupdated, which FreeBSD doesn't have yet.) The FreeBSD
408 * rpc.yppasswdd has some special hooks to allow the superuser update
409 * information without specifying a password, however this only works
410 * for the superuser on the NIS master server.
411 */
412 void yp_submit(pw)
413 struct passwd *pw;
414 {
415 struct yppasswd yppasswd;
416 struct master_yppasswd master_yppasswd;
417 CLIENT *clnt;
418 char *master, *password;
419 int *status = NULL;
420 struct rpc_err err;
421
422 _use_yp = 1;
423
424 /* Get NIS master server name */
425
426 master = get_yp_master(1);
427
428 /* Populate the yppasswd structure that gets handed to yppasswdd. */
429
430 if (suser_override) {
431 master_yppasswd.newpw.pw_passwd = strdup(pw->pw_passwd);
432 master_yppasswd.newpw.pw_name = strdup(pw->pw_name);
433 master_yppasswd.newpw.pw_uid = pw->pw_uid;
434 master_yppasswd.newpw.pw_gid = pw->pw_gid;
435 master_yppasswd.newpw.pw_expire = pw->pw_expire;
436 master_yppasswd.newpw.pw_change = pw->pw_change;
437 master_yppasswd.newpw.pw_fields = pw->pw_fields;
438 master_yppasswd.newpw.pw_gecos = strdup(pw->pw_gecos);
439 master_yppasswd.newpw.pw_dir = strdup(pw->pw_dir);
440 master_yppasswd.newpw.pw_shell = strdup(pw->pw_shell);
441 master_yppasswd.newpw.pw_class = strdup(pw->pw_class);
442 master_yppasswd.oldpass = ""; /* not really needed */
443 master_yppasswd.domain = yp_domain;
444 } else {
445 yppasswd.newpw.pw_passwd = strdup(pw->pw_passwd);
446 yppasswd.newpw.pw_name = strdup(pw->pw_name);
447 yppasswd.newpw.pw_uid = pw->pw_uid;
448 yppasswd.newpw.pw_gid = pw->pw_gid;
449 yppasswd.newpw.pw_gecos = strdup(pw->pw_gecos);
450 yppasswd.newpw.pw_dir = strdup(pw->pw_dir);
451 yppasswd.newpw.pw_shell = strdup(pw->pw_shell);
452 yppasswd.oldpass = "";
453 }
454
455 /* Get the user's password for authentication purposes. */
456
457 printf ("Changing NIS information for %s on %s\n",
458 pw->pw_name, master);
459
460 if (pw->pw_passwd[0] && !suser_override) {
461 password = getpass("Please enter password: ");
462 if (strncmp(crypt(password,pw->pw_passwd),
463 pw->pw_passwd,strlen(pw->pw_passwd))) {
464 warnx("Password incorrect.");
465 pw_error(tempname, 0, 1);
466 }
467 yppasswd.oldpass = password; /* XXX */
468 }
469
470 if (suser_override) {
471 /* Talk to server via AF_UNIX socket. */
472 if (senddat(&master_yppasswd)) {
473 warnx("failed to contact local rpc.yppasswdd");
474 pw_error(tempname, 0, 1);
475 }
476 /* Get return code. */
477 status = getresp();
478 } else {
479 /* Create a handle to yppasswdd. */
480
481 if ((clnt = clnt_create(master, YPPASSWDPROG,
482 YPPASSWDVERS, "udp")) == NULL) {
483 warnx("failed to contact rpc.yppasswdd on %s: %s",
484 master, clnt_spcreateerror(""));
485 pw_error(tempname, 0, 1);
486 }
487
488 clnt->cl_auth = authunix_create_default();
489
490 status = yppasswdproc_update_1(&yppasswd, clnt);
491
492 clnt_geterr(clnt, &err);
493
494 auth_destroy(clnt->cl_auth);
495 clnt_destroy(clnt);
496 }
497
498 /* Call failed: signal the error. */
499
500 if ((!suser_override && err.re_status) != RPC_SUCCESS || status == NULL || *status) {
501 warnx("NIS update failed: %s", (err.re_status != RPC_SUCCESS &&
502 !suser_override) ? clnt_sperrno(err.re_status) :
503 "rpc.yppasswdd returned error status");
504 pw_error(NULL, 0, 1);
505 }
506
507 /* Success. */
508
509 if (suser_override)
510 warnx("NIS information changed on host %s, domain %s",
511 master, yp_domain);
512 else
513 warnx("NIS information changed on host %s", master);
514
515 return;
516 }
517 #endif /* YP */