1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #pragma ident "%Z%%M% %I% %E% SMI"
27
28 /*
29 * DESCRIPTION: This is the N2L equivalent of changepasswd.c. The traditional
30 * version modifies the NIS source files and then initiates a
31 * ypmake to make the maps and push them.
32 *
33 * For N2L there are no source files and the policy is that the
34 * definitive information is that contained in the DIT. Old
35 * information is read from LDAP. Assuming this authenticates, and
36 * the change is acceptable, this information is modified and
37 * written back to LDAP.
38 *
39 * Related map entries are then found and updated finally
40 * yppushes of the changed maps are initiated. Since the
41 * definitive information has already correctly been updated the
42 * code is tolerant of some errors during this operation.
43 *
44 * What was previously in the maps is irrelevant.
45 *
46 * Some less than perfect code (like inline constants for
47 * return values and a few globals) is retained from the original.
48 */
49
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <ctype.h>
53 #include <unistd.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <stdio.h>
57 #include <errno.h>
58 #include <syslog.h>
59 #include <pwd.h>
60 #include <signal.h>
61 #include <crypt.h>
62 #include <rpc/rpc.h>
63 #include <rpcsvc/yppasswd.h>
64 #include <utmpx.h>
65 #include <shadow.h>
66
67 #include <ndbm.h>
68 /* DO NOT INCLUDE SHIM_HOOKS.H */
69 #include "shim.h"
70 #include "yptol.h"
71 #include "../ldap_util.h"
72
73 /* Constants */
74 #define CRYPTPWSIZE CRYPT_MAXCIPHERTEXTLEN
75 #define STRSIZE 100
76 #define FINGERSIZE (4 * STRSIZE - 4)
77 #define SHELLSIZE (STRSIZE - 2)
78
79 #define UTUSERLEN (sizeof (((struct utmpx *)0)->ut_user))
80 #define COLON_CHAR ':'
81
82 /*
83 * Path to DBM files. This is only required for N2L mode. Traditional mode
84 * works with the source files and uses the NIS Makefile to generate the maps.
85 * Seems to be hard coded in the rest of NIS so same is done here.
86 */
87 #define YPDBPATH "/var/yp"
88
89 /* Names of password and adjunct mappings. Used to access DIT */
90 #define BYNAME ".byname"
91 #define BYUID ".byuid"
92 #define BYGID ".bygid"
93 #define PASSWD_MAPPING "passwd" BYNAME
94 #define PASSWD_ADJUNCT_MAPPING "passwd.adjunct" BYNAME
95 #define AGEING_MAPPING "ageing" BYNAME
96
97 /* Bitmasks used in list of fields to change */
98 #define CNG_PASSWD 0x0001
99 #define CNG_SH 0x0002
100 #define CNG_GECOS 0x0004
101
102 /* Globals :-( */
103 extern int single, nogecos, noshell, nopw, mflag;
104
105 /*
106 * Structure for containing the information is currently in the DIT. This is
107 * similar to the passwd structure defined in getpwent(3C) apart from.
108 *
109 * 1. Since GID and UID are never changed they are not converted to integers.
110 * 2. There are extra fields to hold adjunct information.
111 * 3. There are extra fields to hold widely used information.
112 */
113 struct passwd_entry {
114 char *pw_name;
115 char *pw_passwd;
116 char *pw_uid;
117 char *pw_gid;
118 char *pw_gecos;
119 char *pw_dir;
120 char *pw_shell;
121 char *adjunct_tail; /* Tail of adjunct entry (opaque) */
122 bool_t adjunct; /* Flag indicating if DIT has adjunct info */
123 char *pwd_str; /* New password string */
124 char *adjunct_str; /* New adjunct string */
125 };
126
127 /* Prototypes */
128 extern bool_t validloginshell(char *sh, char *arg, int);
129 extern int validstr(char *str, size_t size);
130
131 suc_code write_shadow_info(char *, struct spwd *);
132 int put_new_info(struct passwd_entry *, char *);
133 char *create_pwd_str(struct passwd_entry *, bool_t);
134 int proc_domain(struct yppasswd *, bool_t, char *);
135 int proc_request(struct yppasswd *, struct passwd_entry *, bool_t, char *);
136 int modify_ent(struct yppasswd *, struct passwd_entry *t, bool_t, char *);
137 int get_change_list(struct yppasswd *, struct passwd_entry *);
138 struct passwd_entry *get_old_info(char *, char *);
139 static char *get_next_token(char *, char **, char *);
140 void free_pwd_entry(struct passwd_entry *);
141 struct spwd *get_old_shadow(char *, char *);
142 suc_code decode_shadow_entry(datum *, struct spwd *);
143 void free_shadow_entry(struct spwd *);
144 int proc_maps(char *, struct passwd_entry *);
145 int proc_map_list(char **, char *, struct passwd_entry *, bool_t);
146 int update_single_map(char *, struct passwd_entry *, bool_t);
147 bool_t strend(char *s1, char *s2);
148
149 /*
150 * FUNCTION: shim_changepasswd()
151 *
152 * DESCRIPTION: N2L version of changepasswd(). When this is called 'useshadow'
153 * etc. will have been set up but are meaningless. We work out
154 * what to change based on information from the DIT.
155 *
156 * INPUTS: Identical to changepasswd()
157 *
158 * OUTPUTS: Identical to changepasswd()
159 */
160 void
shim_changepasswd(SVCXPRT * transp)161 shim_changepasswd(SVCXPRT *transp)
162 {
163 struct yppasswd yppwd;
164 bool_t root_on_master = FALSE;
165 char domain[MAXNETNAMELEN+1];
166 char **domain_list;
167 int dom_count, i;
168
169 int ret, ans = 2; /* Answer codes */
170
171 /* Clean out yppwd ... maybe we don't trust RPC */
172 memset(&yppwd, 0, sizeof (struct yppasswd));
173
174 /* Get the RPC args */
175 if (!svc_getargs(transp, xdr_yppasswd, (caddr_t)&yppwd)) {
176 svcerr_decode(transp);
177 return;
178 }
179
180 /* Perform basic validation */
181 if ((!validstr(yppwd.newpw.pw_passwd, CRYPTPWSIZE)) ||
182 (!validstr(yppwd.newpw.pw_name, UTUSERLEN)) ||
183 (!validstr(yppwd.newpw.pw_gecos, FINGERSIZE)) ||
184 (!validstr(yppwd.newpw.pw_shell, SHELLSIZE))) {
185 svcerr_decode(transp);
186 return;
187 }
188
189 /*
190 * Special case: root on the master server can change other
191 * users' passwords without first entering the old password.
192 * We need to ensure that this is indeed root on the master
193 * server. (bug 1253949)
194 */
195 if (strcmp(transp->xp_netid, "ticlts") == 0) {
196 svc_local_cred_t cred;
197 if (!svc_get_local_cred(transp, &cred)) {
198 logmsg(MSG_NOTIMECHECK, LOG_ERR,
199 "Couldn't get local user credentials");
200 } else if (cred.ruid == 0)
201 root_on_master = TRUE;
202 }
203
204 /*
205 * Get the domain name. This is tricky because a N2L server may be
206 * handling multiple domains. There is nothing in the request to
207 * indicate which one we are trying to change a passwd for. First
208 * we try to get a list of password related domains from the mapping
209 * file.
210 */
211 if (0 !=
212 (dom_count = get_mapping_yppasswdd_domain_list(&domain_list))) {
213 /* Got a domain list ... process all the domains */
214 for (i = 0; i < dom_count; i ++) {
215 ret = proc_domain(&yppwd, root_on_master,
216 domain_list[i]);
217
218 /* If one has worked don't care if others fail */
219 if (0 != ans)
220 ans = ret;
221 }
222 }
223 else
224 {
225 /*
226 * There was no domain list in the mapping file. The
227 * traditional version of this code calls ypmake which picks
228 * up the domain returned by getdomainname(). Fall back to the
229 * same mechanism.
230 */
231 if (0 > getdomainname(domain, MAXNETNAMELEN+1)) {
232 logmsg(MSG_NOTIMECHECK, LOG_ERR,
233 "Could not get any domain info");
234 } else {
235 /* Got one domain ... process it. */
236 ans = proc_domain(&yppwd, root_on_master, domain);
237 }
238 }
239
240 /* Send reply packet */
241 if (!svc_sendreply(transp, xdr_int, (char *)&ans))
242 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
243 "could not reply to RPC call");
244 }
245
246 /*
247 * FUNCTION : proc_domain()
248 *
249 * DESCRIPTION: Process a request for one domain
250 *
251 * GIVEN : Pointer to the request.
252 * Root on master flag
253 * Domain
254 *
255 * OUTPUTS : Answer code for reply
256 */
257 int
proc_domain(struct yppasswd * yppwd,bool_t root_on_master,char * domain)258 proc_domain(struct yppasswd *yppwd, bool_t root_on_master, char *domain)
259 {
260 struct passwd_entry *old_pwd;
261 char *p;
262 int ans = 2;
263
264 /* security hole fix from original source */
265 for (p = yppwd->newpw.pw_name; (*p != '\0'); p++)
266 if ((*p == ':') || !(isprint(*p)))
267 *p = '$'; /* you lose buckwheat */
268 for (p = yppwd->newpw.pw_passwd; (*p != '\0'); p++)
269 if ((*p == ':') || !(isprint(*p)))
270 *p = '$'; /* you lose buckwheat */
271
272 /* Get old info from DIT for this domain */
273 old_pwd = get_old_info(yppwd->newpw.pw_name, domain);
274 if (NULL == old_pwd) {
275 logmsg(MSG_NOTIMECHECK, LOG_ERR,
276 "Could not get old information for %s in "
277 "domain %s", yppwd->newpw.pw_name, domain);
278 return (ans);
279 }
280
281 /* Have a request that can be replied to */
282 ans = proc_request(yppwd, old_pwd, root_on_master, domain);
283 free_pwd_entry(old_pwd);
284
285 return (ans);
286 }
287
288 /*
289 * FUNCTION : proc_request()
290 *
291 * DESCRIPTION: Process a request
292 *
293 * GIVEN : Pointer to the request.
294 * Pointer to old information from LDAP
295 * Root on master flag
296 * Domain
297 *
298 * OUTPUTS : Answer code for reply
299 */
300 int
proc_request(struct yppasswd * yppwd,struct passwd_entry * old_pwd,bool_t root_on_master,char * domain)301 proc_request(struct yppasswd *yppwd, struct passwd_entry *old_pwd,
302 bool_t root_on_master, char *domain)
303 {
304 struct sigaction sa, osa1, osa2, osa3;
305 int ans;
306
307 /* Authenticate */
308 if ((0 != strcmp(crypt(yppwd->oldpass, old_pwd->pw_passwd),
309 old_pwd->pw_passwd)) && !root_on_master) {
310 logmsg(MSG_NOTIMECHECK, LOG_NOTICE, "Passwd incorrect %s",
311 yppwd->newpw.pw_name);
312 return (7);
313 }
314
315 /* Work out what we have to change and change it */
316 ans = modify_ent(yppwd, old_pwd, root_on_master, domain);
317 if (0 != ans)
318 return (ans);
319
320 /*
321 * Generate passwd and adjunct map entries. This creates extra
322 * malloced strings in old_pwd. These will be freed when
323 * free_pwd_entry() is called to free up the rest of the structure.
324 */
325 old_pwd->pwd_str = create_pwd_str(old_pwd, FALSE);
326 if (NULL == old_pwd->pwd_str) {
327 logmsg(MSG_NOTIMECHECK, LOG_ERR,
328 "Could not create passwd entry");
329 return (2);
330 }
331 if (old_pwd->adjunct) {
332 old_pwd->adjunct_str = create_pwd_str(old_pwd, TRUE);
333 if (NULL == old_pwd->adjunct_str) {
334 logmsg(MSG_NOTIMECHECK, LOG_ERR,
335 "Could not create adjunct entry");
336 return (2);
337 }
338 } else {
339 old_pwd->adjunct_str = NULL;
340 }
341
342 /* Put the information back to DIT */
343 ans = put_new_info(old_pwd, domain);
344 if (0 != ans) {
345 return (ans);
346 }
347
348 /* Are going to be forking pushes, set up signals */
349 memset(&sa, 0, sizeof (struct sigaction));
350 sa.sa_handler = SIG_IGN;
351 sigaction(SIGTSTP, &sa, (struct sigaction *)0);
352 sigaction(SIGHUP, &sa, &osa1);
353 sigaction(SIGINT, &sa, &osa2);
354 sigaction(SIGQUIT, &sa, &osa3);
355
356 /* Update and push all the maps */
357 ans = proc_maps(domain, old_pwd);
358
359 /* Tidy up signals */
360 sigaction(SIGHUP, &osa1, (struct sigaction *)0);
361 sigaction(SIGINT, &osa2, (struct sigaction *)0);
362 sigaction(SIGQUIT, &osa3, (struct sigaction *)0);
363
364 return (ans);
365 }
366
367 /*
368 * FUNCTION: proc_maps()
369 *
370 * DESCRIPTION: Gets all the map lists and processes them.
371 *
372 * INPUTS: Domain name
373 * New info to write into maps
374 *
375 * OUTPUT : Answer code
376 */
377 int
proc_maps(char * domain,struct passwd_entry * pwd)378 proc_maps(char *domain, struct passwd_entry *pwd)
379 {
380 char **map_list; /* Array of passwd or adjunct maps */
381 int ans = 0;
382
383 /* Get list of passwd maps from mapping file */
384 map_list = get_passwd_list(FALSE, domain);
385 if (map_list != NULL) {
386 /* Process list of passwd maps */
387 ans = proc_map_list(map_list, domain, pwd, FALSE);
388 free_passwd_list(map_list);
389 if (0 != ans)
390 return (ans);
391 }
392
393 /*
394 * If we get here either there were no passwd maps or there were
395 * some and they were processed successfully. Either case is good
396 * continue and process passwd.adjunct maps.
397 */
398
399 /* Get list of adjunct maps from mapping file */
400 map_list = get_passwd_list(TRUE, domain);
401 if (map_list != NULL) {
402 /*
403 * Process list of adjunct maps. If the required information
404 * is not present in LDAP then the updates attempts will log
405 * an error. No need to make the check here
406 */
407 ans = proc_map_list(map_list, domain, pwd, TRUE);
408 free_passwd_list(map_list);
409 }
410
411 return (ans);
412 }
413
414 /*
415 * FUNCTION: proc_map_list()
416 *
417 * DESCRIPTION: Finds entries in one list of map that need to be updated.
418 * updates them and writes them back.
419 *
420 * INPUTS: Null terminated list of maps to process.
421 * Domain name
422 * Information to write (including user name)
423 * Flag indicating if this is the adjunct list
424 *
425 * OUTPUTS: An error code
426 */
427 int
proc_map_list(char ** map_list,char * domain,struct passwd_entry * pwd,bool_t adjunct_flag)428 proc_map_list(char **map_list, char *domain,
429 struct passwd_entry *pwd, bool_t adjunct_flag)
430 {
431 char *myself = "proc_map_list";
432 char *map_name;
433 char cmdbuf[BUFSIZ];
434 int map_name_len = 0;
435 int index, ans = 0;
436 int res;
437
438 /* If this is a adjunct list check LDAP had some adjunct info */
439 if ((adjunct_flag) && (!pwd->adjunct)) {
440 logmsg(MSG_NOTIMECHECK, LOG_INFO,
441 "Have adjunct map list but no adjunct data in DIT");
442 /* Not a disaster */
443 return (0);
444 }
445
446 /* Allocate enough buffer to take longest map name */
447 for (index = 0; map_list[index] != NULL; index ++)
448 if (map_name_len < strlen(map_list[index]))
449 map_name_len = strlen(map_list[index]);
450 map_name_len += strlen(YPDBPATH);
451 map_name_len += strlen(NTOL_PREFIX);
452 map_name_len += strlen(domain);
453 map_name_len += 3;
454 if (NULL == (map_name = am(myself, map_name_len))) {
455 logmsg(MSG_NOMEM, LOG_ERR, "Could not alloc map name");
456 return (2);
457 }
458
459 /* For all maps in list */
460 for (index = 0; map_list[index] != NULL; index ++) {
461
462 /* Generate full map name */
463 strcpy(map_name, YPDBPATH);
464 add_separator(map_name);
465 strcat(map_name, domain);
466 add_separator(map_name);
467 strcat(map_name, NTOL_PREFIX);
468 strcat(map_name, map_list[index]);
469
470 if (0 != (ans = update_single_map(map_name, pwd, adjunct_flag)))
471 break;
472 }
473
474 /* Done with full map path */
475 sfree(map_name);
476
477 /*
478 * If (ans != 0) then one more maps have failed. LDAP has however been
479 * updates. This is the definitive source for information there is no
480 * need to unwind. (This was probably due to maps that were already
481 * corrupt).
482 */
483
484 /*
485 * If it all worked fork off push operations for the maps. Since we
486 * want the map to end up with it's traditional name on the slave send
487 * the name without its LDAP_ prefix. The slave will call ypxfrd
488 * which, since it is running in N2L mode, will put the prefix back on
489 * before reading the file.
490 */
491 if (mflag && (0 == ans)) {
492 for (index = 0; (map_name = map_list[index]) != NULL;
493 index ++) {
494 if (fork() == 0) {
495 /*
496 * Define full path to yppush. Probably also
497 * best for security.
498 */
499 strcpy(cmdbuf, "/usr/lib/netsvc/yp/yppush ");
500 strcat(cmdbuf, map_name);
501 if (0 > system(cmdbuf))
502 logmsg(MSG_NOTIMECHECK, LOG_ERR,
503 "Could not initiate yppush");
504 exit(0);
505 }
506 }
507 }
508 return (ans);
509 }
510
511 /*
512 * FUNCTION : update_single_map()
513 *
514 * DESCRIPTION: Updates one map. This is messy because we want to lock the map
515 * to prevent other processes from updating it at the same time.
516 * This mandates that we open it using the shim. When we
517 * write to it however we DO NOT want to write through to LDAP
518 * i.e. do not want to use the shim.
519 *
520 * Solution : Do not include shim_hooks.h but call the shim
521 * versions of dbm_functions explicitly where needed.
522 *
523 * INPUT : Full name of map
524 * Information to write (including user name)
525 * Flag indicating if this is an adjunct map.
526 *
527 * OUTPUT : Answer code
528 *
529 */
530 int
update_single_map(char * map_name,struct passwd_entry * pwd,bool_t adjunct_flag)531 update_single_map(char *map_name, struct passwd_entry *pwd, bool_t adjunct_flag)
532 {
533 DBM *map;
534 int res;
535 datum data, key;
536
537 /* Set up data */
538 if (adjunct_flag)
539 data.dptr = pwd->adjunct_str;
540 else
541 data.dptr = pwd->pwd_str;
542 data.dsize = strlen(data.dptr);
543
544 /* Set up key dependent on which type of map this is */
545 key.dptr = NULL;
546 if (strend(map_name, BYNAME))
547 key.dptr = pwd->pw_name;
548 if (strend(map_name, BYUID))
549 key.dptr = pwd->pw_uid;
550 if (strend(map_name, BYGID))
551 key.dptr = pwd->pw_gid;
552
553 if (NULL == key.dptr) {
554 logmsg(MSG_NOTIMECHECK, LOG_ERR,
555 "Unrecognized map type %s", map_name);
556 return (0); /* Next map */
557 }
558 key.dsize = strlen(key.dptr);
559
560 /* Open the map */
561 map = shim_dbm_open(map_name, O_RDWR, 0600);
562 if (NULL == map) {
563 logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not open %s", map_name);
564 return (0); /* Next map */
565 }
566
567 /* Lock map for update. Painful and may block but have to do it */
568 if (SUCCESS != lock_map_update((map_ctrl *)map)) {
569 logmsg(MSG_NOTIMECHECK, LOG_ERR,
570 "Could not lock map %s for update", map_name);
571 shim_dbm_close(map);
572 return (2);
573 }
574
575 /* Do the update use simple DBM operation */
576 res = dbm_store(((map_ctrl *)map)->entries, key, data, DBM_REPLACE);
577
578 /* update entry TTL. If we fail not a problem will just timeout early */
579 update_entry_ttl((map_ctrl *)map, &key, TTL_RAND);
580
581 /*
582 * Map has been modified so update YP_LAST_MODIFIED. In the vanilla
583 * NIS case this would have been done by the ypmake done after updating
584 * the passwd source file. If this fails not a great problem the map
585 */
586 if (FAILURE == update_timestamp(((map_ctrl *)map)->entries)) {
587 logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not update "
588 "YP_LAST_MODIFIED %s will not be pushed this time",
589 map_name);
590 }
591
592 /*
593 * Possibly should hold the lock until after push is complete
594 * but this could deadlock if client is slow and ypxfrd also
595 * decides to do an update.
596 */
597 unlock_map_update((map_ctrl *)map);
598
599 /* Close the map */
600 shim_dbm_close(map);
601
602 if (0 != res) {
603 logmsg(MSG_NOTIMECHECK, LOG_ERR,
604 "Could not update map %s", map_name);
605 return (2);
606 }
607
608 return (0);
609 }
610
611 /*
612 * FUNCTION : strend()
613 *
614 * DESCRIPTION: Determines if one string ends with another.
615 */
616 bool_t
strend(char * s1,char * s2)617 strend(char *s1, char *s2)
618 {
619 int len_dif;
620
621 len_dif = strlen(s1) - strlen(s2);
622 if (0 > len_dif)
623 return (FALSE);
624 if (0 == strcmp(s1 + len_dif, s2))
625 return (TRUE);
626 return (FALSE);
627 }
628
629 /*
630 * FUNCTION: modify_ent()
631 *
632 * DESCRIPTION: Modify an entry to reflect a request.
633 *
634 * INPUT: Pointer to the request.
635 * Pointer to the entry to modify.
636 * Flag indication if we are root on master
637 * Domain
638 *
639 * OUTPUT: Error code
640 */
641 int
modify_ent(struct yppasswd * yppwd,struct passwd_entry * old_ent,bool_t root_on_master,char * domain)642 modify_ent(struct yppasswd *yppwd, struct passwd_entry *old_ent,
643 bool_t root_on_master, char *domain)
644 {
645 int change_list;
646 struct spwd *shadow;
647 time_t now;
648
649 /* Get list of changes */
650 change_list = get_change_list(yppwd, old_ent);
651
652 if (!change_list) {
653 logmsg(MSG_NOTIMECHECK, LOG_NOTICE,
654 "No change for %s", yppwd->newpw.pw_name);
655 return (3);
656 }
657
658 /* Check that the shell we have been given is acceptable. */
659 if ((change_list & CNG_SH) && (!validloginshell(old_ent->pw_shell,
660 yppwd->newpw.pw_shell, root_on_master)))
661 return (2);
662
663 /*
664 * If changing the password do any aging checks.
665 * Since there are no shadow maps this is done by accessing
666 * attributes in the DIT via the mapping system.
667 */
668 if (change_list & CNG_PASSWD) {
669
670 /* Try to get shadow information */
671 shadow = get_old_shadow(yppwd->newpw.pw_name, domain);
672
673 /* If there is shadow information make password aging checks */
674 if (NULL != shadow) {
675 now = DAY_NOW;
676 /* password aging - bug for bug compatibility */
677 if (shadow->sp_max != -1) {
678 if (now < shadow->sp_lstchg + shadow->sp_min) {
679 logmsg(MSG_NOTIMECHECK, LOG_ERR,
680 "Sorry: < %ld days since "
681 "the last change", shadow->sp_min);
682 free_shadow_entry(shadow);
683 return (2);
684 }
685 }
686
687 /* Update time of change */
688 shadow->sp_lstchg = now;
689
690 /* Write it back */
691 write_shadow_info(domain, shadow);
692
693 free_shadow_entry(shadow);
694 }
695 }
696
697 /* Make changes to old entity */
698 if (change_list & CNG_GECOS) {
699 if (NULL != old_ent->pw_gecos)
700 sfree(old_ent->pw_gecos);
701 old_ent->pw_gecos = strdup(yppwd->newpw.pw_gecos);
702 if (NULL == old_ent->pw_gecos) {
703 logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate gecos");
704 return (2);
705 }
706 }
707
708 if (change_list & CNG_SH) {
709 if (NULL != old_ent->pw_shell)
710 sfree(old_ent->pw_shell);
711 old_ent->pw_shell = strdup(yppwd->newpw.pw_shell);
712 if (NULL == old_ent->pw_shell) {
713 logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate shell");
714 return (2);
715 }
716 }
717
718 if (change_list & CNG_PASSWD) {
719 if (NULL != old_ent->pw_passwd)
720 sfree(old_ent->pw_passwd);
721 old_ent->pw_passwd = strdup(yppwd->newpw.pw_passwd);
722 if (NULL == old_ent->pw_passwd) {
723 logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate passwd");
724 return (2);
725 }
726 }
727
728 return (0);
729 }
730
731 /*
732 * FUNCTION : get_change_list()
733 *
734 * DESCRIPTION: Works out what we have to change.
735 *
736 * INPUTS : Request.
737 * Structure containing current state of entry
738 *
739 * OUTPUTS : A bitmask signaling what to change. (Implemented in this
740 * way to make it easy to pass between functions).
741 */
742 int
get_change_list(struct yppasswd * yppwd,struct passwd_entry * old_ent)743 get_change_list(struct yppasswd *yppwd, struct passwd_entry *old_ent)
744 {
745 int list = 0;
746 char *p;
747
748 p = yppwd->newpw.pw_passwd;
749 if ((!nopw) &&
750 p && *p &&
751 !(*p++ == '#' && *p++ == '#' &&
752 (strcmp(p, old_ent->pw_name) == 0)) &&
753 (strcmp(crypt(old_ent->pw_passwd,
754 yppwd->newpw.pw_passwd), yppwd->newpw.pw_passwd) != 0))
755 list |= CNG_PASSWD;
756
757 if ((NULL != old_ent->pw_shell) &&
758 (!noshell) &&
759 (strcmp(old_ent->pw_shell, yppwd->newpw.pw_shell) != 0)) {
760 if (single)
761 list = 0;
762 list |= CNG_SH;
763 }
764
765 if ((NULL != old_ent->pw_gecos) &&
766 (!nogecos) &&
767 (strcmp(old_ent->pw_gecos, yppwd->newpw.pw_gecos) != 0)) {
768 if (single)
769 list = 0;
770 list |= CNG_GECOS;
771 }
772
773 return (list);
774 }
775
776 /*
777 * FUNCTION : decode_pwd_entry()
778 *
779 * DESCRIPTION: Pulls apart a password entry. Because the password entry has
780 * come from the mapping system it can be assumed to be correctly
781 * formatted and relatively simple parsing can be done.
782 *
783 * Substrings are put into malloced memory. Caller to free.
784 *
785 * For adjunct files most of it is left empty.
786 *
787 * It would be nice to use getpwent and friends for this work but
788 * these only seem to exist for files and it seems excessive to
789 * create a temporary file for this operation.
790 *
791 * INPUTS: Pointer to datum containing password string.
792 * Pointer to structure in which to return results
793 * Flag indicating if we are decoding passwd or passwd.adjunct
794 *
795 * OUTPUTS: SUCCESS = Decoded successfully
796 * FAILURE = Not decoded successfully. Caller to tidy up.
797 */
798 suc_code
decode_pwd_entry(datum * data,struct passwd_entry * pwd,bool_t adjunct)799 decode_pwd_entry(datum *data, struct passwd_entry *pwd, bool_t adjunct)
800 {
801 char *myself = "decode_pwd_entry";
802 char *p, *str_end, *temp;
803
804 /* Work out last location in string */
805 str_end = data->dptr + data->dsize;
806
807 /* Name */
808 if (NULL == (p = get_next_token(data->dptr, &temp, str_end)))
809 return (FAILURE);
810 if (adjunct) {
811 /* If we found an adjunct version this is the one to use */
812 if (NULL != pwd->pw_name)
813 sfree(pwd->pw_name);
814 }
815 pwd->pw_name = temp;
816
817 /* Password */
818 if (NULL == (p = get_next_token(p, &temp, str_end)))
819 return (FAILURE);
820 if (adjunct) {
821 /* If we found an adjunct version this is the one to use */
822 if (NULL != pwd->pw_passwd)
823 sfree(pwd->pw_passwd);
824 }
825 pwd->pw_passwd = temp;
826
827 if (adjunct) {
828 /* Store adjunct information in opaque string */
829 pwd->adjunct_tail = am(myself, str_end - p + 1);
830 if (NULL == pwd->adjunct_tail)
831 return (FAILURE);
832 strncpy(pwd->adjunct_tail, p, str_end - p);
833 pwd->adjunct_tail[str_end - p] = '\0';
834
835 /* Remember that LDAP contained adjunct data */
836 pwd->adjunct = TRUE;
837 return (SUCCESS);
838 }
839
840 /* If we get here not adjunct. Decode rest of passwd */
841
842 /* UID */
843 if (NULL == (p = get_next_token(p, &(pwd->pw_uid), str_end)))
844 return (FAILURE);
845
846 /* GID */
847 if (NULL == (p = get_next_token(p, &(pwd->pw_gid), str_end)))
848 return (FAILURE);
849
850 /* Gecos */
851 if (NULL == (p = get_next_token(p, &(pwd->pw_gecos), str_end)))
852 return (FAILURE);
853
854 /* Home dir */
855 if (NULL == (p = get_next_token(p, &(pwd->pw_dir), str_end)))
856 return (FAILURE);
857
858 /* Shell may not be present so don't check return */
859 get_next_token(p, &(pwd->pw_shell), str_end);
860
861 if (NULL == pwd->pw_shell)
862 return (FAILURE);
863
864 return (SUCCESS);
865 }
866
867 /*
868 * FUNCTION : get_next_token()
869 *
870 * DESCRIPTION: Gets the next token from a string upto the next colon or the
871 * end of the string. The duplicates this token into malloced
872 * memory removing any spaces.
873 *
874 * INPUTS : String to search for token. NOT NULL TERMINATED
875 * Location to return result (NULL if result not required)
876 * Last location in string
877 *
878 * OUTPUT : Pointer into the string immediately after the token.
879 * NULL if end of string reached or error.
880 */
881 static char *
get_next_token(char * str,char ** op,char * str_end)882 get_next_token(char *str, char **op, char *str_end)
883 {
884 char *myself = "get_next_token";
885 char *p, *tok_start, *tok_end;
886
887 p = str;
888 /* Skip leading whitespace */
889 while (' ' == *p)
890 p++;
891 tok_start = p;
892 tok_end = p;
893
894 while ((str_end + 1 != p) && (COLON_CHAR != *p)) {
895 if (' ' != *p)
896 tok_end = p;
897 p++;
898 }
899
900 /* Required string is now between start and end */
901 if (NULL != op) {
902 *op = am(myself, tok_end - tok_start + 2);
903 if (NULL == *op) {
904 logmsg(MSG_NOMEM, LOG_ERR,
905 "Could not alloc memory for token");
906 return (NULL);
907 }
908 strncpy(*op, tok_start, tok_end - tok_start + 1);
909
910 /* Terminate token */
911 (*op)[tok_end - tok_start + 1] = '\0';
912
913 }
914
915 /* Check if we reached the end of the input string */
916 if ('\0' == *p)
917 return (NULL);
918
919 /* There is some more */
920 p++;
921 return (p);
922 }
923
924 /*
925 * FUNCTION : free_pwd_entry()
926 *
927 * DESCRIPTION: Frees up a pwd_entry structure and its contents.
928 *
929 * INPUTS: Pointer to the structure to free.
930 *
931 * OUTPUT: Nothing
932 */
933 void
free_pwd_entry(struct passwd_entry * pwd)934 free_pwd_entry(struct passwd_entry *pwd)
935 {
936 /* Free up strings */
937 if (NULL != pwd->pw_name)
938 sfree(pwd->pw_name);
939
940 if (NULL != pwd->pw_passwd)
941 sfree(pwd->pw_passwd);
942
943 if (NULL != pwd->pw_gecos)
944 sfree(pwd->pw_gecos);
945
946 if (NULL != pwd->pw_shell)
947 sfree(pwd->pw_shell);
948
949 if (NULL != pwd->pw_dir)
950 sfree(pwd->pw_dir);
951
952 if (NULL != pwd->adjunct_tail)
953 sfree(pwd->adjunct_tail);
954
955 if (NULL != pwd->pwd_str)
956 sfree(pwd->pwd_str);
957
958 if (NULL != pwd->adjunct_str)
959 sfree(pwd->adjunct_str);
960
961 /* Free up structure */
962 sfree(pwd);
963 }
964
965 /*
966 * FUNCTION : create_pwd_str()
967 *
968 * DESCRIPTION: Builds up a new password entity string from a passwd structure.
969 *
970 * INPUTS : Structure containing password details
971 * Flag indicating if we should create an adjunct or passwd string.
972 *
973 * OUTPUTS : String in malloced memory (to be freed by caller).
974 * NULL on failure.
975 */
976 char *
create_pwd_str(struct passwd_entry * pwd,bool_t adjunct)977 create_pwd_str(struct passwd_entry *pwd, bool_t adjunct)
978 {
979 char *myself = "create_pwd_str";
980 char *s;
981 int len;
982
983 /* Separator string so we can strcat separator onto things */
984 char sep_str[2] = {COLON_CHAR, '\0'};
985
986 /* Work out the size */
987 len = strlen(pwd->pw_name) + 1;
988 len += strlen(pwd->pw_passwd) + 1;
989 if (adjunct) {
990 len += strlen(pwd->adjunct_tail) + 1;
991 } else {
992 len += strlen(pwd->pw_uid) + 1;
993 len += strlen(pwd->pw_gid) + 1;
994 len += strlen(pwd->pw_gecos) + 1;
995 len += strlen(pwd->pw_dir) + 1;
996 len += strlen(pwd->pw_shell) + 1;
997 }
998
999 /* Allocate some memory for it */
1000 s = am(myself, len);
1001 if (NULL == s)
1002 return (NULL);
1003
1004 strcpy(s, pwd->pw_name);
1005 strcat(s, sep_str);
1006 if (!adjunct) {
1007 /* Build up a passwd string */
1008
1009 /* If LDAP contains adjunct info then passwd is 'x' */
1010 if (pwd->adjunct) {
1011 strcat(s, "##");
1012 strcat(s, pwd->pw_name);
1013 } else {
1014 strcat(s, pwd->pw_passwd);
1015 }
1016 strcat(s, sep_str);
1017 strcat(s, pwd->pw_uid);
1018 strcat(s, sep_str);
1019 strcat(s, pwd->pw_gid);
1020 strcat(s, sep_str);
1021 strcat(s, pwd->pw_gecos);
1022 strcat(s, sep_str);
1023 strcat(s, pwd->pw_dir);
1024 strcat(s, sep_str);
1025 strcat(s, pwd->pw_shell);
1026 } else {
1027 /* Build up a passwd_adjunct string */
1028 strcat(s, pwd->pw_passwd);
1029 strcat(s, sep_str);
1030 strcat(s, pwd->adjunct_tail);
1031 }
1032
1033 return (s);
1034 }
1035
1036 /*
1037 * FUNCTION: get_old_info()
1038 *
1039 * DESCRIPTION: Gets as much information as possible from LDAP about one user.
1040 *
1041 * This goes through the mapping system. This is messy because
1042 * them mapping system will build up a password entry from the
1043 * contents of the DIT. We then have to parse this to recover
1044 * it's individual fields.
1045 *
1046 * INPUT: Pointer to user name
1047 * Domain
1048 *
1049 * OUTPUT: The info in malloced space. To be freed by caller.
1050 * NULL on failure.
1051 */
1052 struct passwd_entry *
get_old_info(char * name,char * domain)1053 get_old_info(char *name, char *domain)
1054 {
1055 char *myself = "get_old_info";
1056 struct passwd_entry *old_passwd;
1057 char *p;
1058 datum key, data;
1059 suc_code res;
1060
1061 /* Get the password entry */
1062 key.dptr = name;
1063 key.dsize = strlen(key.dptr);
1064 read_from_dit(PASSWD_MAPPING, domain, &key, &data);
1065 if (NULL == data.dptr) {
1066 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1067 "Could not read old pwd for %s", name);
1068 return (NULL);
1069 }
1070
1071 /* Pull password apart */
1072 old_passwd = am(myself, sizeof (struct passwd_entry));
1073 if (NULL == old_passwd) {
1074 logmsg(MSG_NOMEM, LOG_ERR, "Could not alloc for pwd decode");
1075 sfree(data.dptr);
1076 return (NULL);
1077 }
1078
1079 /* No data yet */
1080 old_passwd->pw_name = NULL;
1081 old_passwd->pw_passwd = NULL;
1082 old_passwd->pw_uid = NULL;
1083 old_passwd->pw_gid = NULL;
1084 old_passwd->pw_gecos = NULL;
1085 old_passwd->pw_dir = NULL;
1086 old_passwd->pw_shell = NULL;
1087 old_passwd->adjunct_tail = NULL;
1088 old_passwd->pwd_str = NULL;
1089 old_passwd->adjunct_str = NULL;
1090 old_passwd->adjunct = FALSE;
1091
1092 res = decode_pwd_entry(&data, old_passwd, FALSE);
1093 sfree(data.dptr);
1094 if (SUCCESS != res) {
1095 free_pwd_entry(old_passwd);
1096 return (NULL);
1097 }
1098
1099 /* Try to get the adjunct entry */
1100 read_from_dit(PASSWD_ADJUNCT_MAPPING, domain, &key, &data);
1101 if (NULL == data.dptr) {
1102 /* Fine just no adjunct data */
1103 old_passwd->adjunct = FALSE;
1104 } else {
1105 res = decode_pwd_entry(&data, old_passwd, TRUE);
1106 sfree(data.dptr);
1107 if (SUCCESS != res) {
1108 free_pwd_entry(old_passwd);
1109 return (NULL);
1110 }
1111 }
1112
1113 return (old_passwd);
1114 }
1115
1116 /*
1117 * FUNCTION : put_new_info()
1118 *
1119 * DESCRIPTION: Generates new map strings and puts them back to LDAP
1120 *
1121 * INPUTS: Info to put back
1122 * Domain
1123 *
1124 * OUTPUT: Answer code.
1125 */
1126 int
put_new_info(struct passwd_entry * pwd,char * domain)1127 put_new_info(struct passwd_entry *pwd, char *domain)
1128 {
1129 datum key, data;
1130
1131 /* Write it back to LDAP */
1132 data.dptr = pwd->pwd_str;
1133 data.dsize = strlen(data.dptr);
1134 key.dptr = pwd->pw_name;
1135 key.dsize = strlen(key.dptr);
1136 if (SUCCESS != write_to_dit(PASSWD_MAPPING, domain, key, data,
1137 TRUE, FALSE))
1138 return (2);
1139
1140
1141 /* If DIT contains adjunct information do the same for adjunct */
1142 if (pwd->adjunct) {
1143 data.dptr = pwd->adjunct_str;
1144 data.dsize = strlen(data.dptr);
1145 key.dptr = pwd->pw_name;
1146 key.dsize = strlen(key.dptr);
1147 if (SUCCESS != write_to_dit(PASSWD_ADJUNCT_MAPPING, domain,
1148 key, data, TRUE, FALSE))
1149 return (2);
1150 }
1151
1152 return (0);
1153
1154 }
1155
1156 /*
1157 * FUNCTION : get_old_shadow()
1158 *
1159 * DESCRIPTION :Extracts and decodes shadow information from the DIT
1160 * See also comments under decode_pwd_entry().
1161 *
1162 * INPUTS : User name
1163 * Domain name
1164 *
1165 * OUTPUT : Shadow information in malloced memory. To be freed by caller.
1166 */
1167 struct spwd *
get_old_shadow(char * name,char * domain)1168 get_old_shadow(char *name, char *domain)
1169 {
1170 char *myself = "get_old_shadow";
1171 struct spwd *sp;
1172 datum key, data;
1173 suc_code res;
1174
1175 /* Get the info */
1176 key.dptr = name;
1177 key.dsize = strlen(key.dptr); /* Len excluding terminator */
1178 read_from_dit(AGEING_MAPPING, domain, &key, &data);
1179
1180 if (NULL == data.dptr) {
1181 /* OK just have no shadow info in DIT */
1182 return (NULL);
1183 }
1184
1185 /* Pull shadow apart */
1186 if (NULL == (sp = am(myself, sizeof (struct spwd)))) {
1187 logmsg(MSG_NOMEM, LOG_ERR,
1188 "Could not alloc for shadow decode");
1189 sfree(data.dptr);
1190 return (NULL);
1191 }
1192 sp->sp_namp = NULL;
1193 sp->sp_pwdp = NULL;
1194
1195 res = decode_shadow_entry(&data, sp);
1196 sfree(data.dptr);
1197 if (SUCCESS != res) {
1198 free_shadow_entry(sp);
1199 return (NULL);
1200 }
1201
1202 return (sp);
1203 }
1204
1205 /*
1206 * FUNCTION : decode_shadow_entry()
1207 *
1208 * DESCRIPTION: Pulls apart ageing information. For convenience this is stored
1209 * in a partially filled spwd structure.
1210 *
1211 * SEE COMMENTS FOR decode_pwd_entry()
1212 */
1213 suc_code
decode_shadow_entry(datum * data,struct spwd * sp)1214 decode_shadow_entry(datum *data, struct spwd *sp)
1215 {
1216 char *p, *str_end, *temp;
1217
1218 /* Work out last location in string */
1219 str_end = data->dptr + data->dsize;
1220
1221 /* Name */
1222 if (NULL == (p = get_next_token(data->dptr, &(sp->sp_namp), str_end)))
1223 return (FAILURE);
1224
1225 /* date of last change */
1226 if (NULL == (p = get_next_token(p, &temp, str_end)))
1227 return (FAILURE);
1228 sp->sp_lstchg = atoi(temp);
1229
1230 /* min days to passwd change */
1231 if (NULL == (p = get_next_token(p, &temp, str_end)))
1232 return (FAILURE);
1233 sp->sp_min = atoi(temp);
1234
1235 /* max days to passwd change */
1236 if (NULL == (p = get_next_token(p, &temp, str_end)))
1237 return (FAILURE);
1238 sp->sp_max = atoi(temp);
1239
1240 /* warning period */
1241 if (NULL == (p = get_next_token(p, &temp, str_end)))
1242 return (FAILURE);
1243 sp->sp_warn = atoi(temp);
1244
1245 /* max days inactive */
1246 if (NULL == (p = get_next_token(p, &temp, str_end)))
1247 return (FAILURE);
1248 sp->sp_inact = atoi(temp);
1249
1250 /* account expiry date */
1251 if (NULL == (p = get_next_token(p, &temp, str_end)))
1252 return (FAILURE);
1253 sp->sp_expire = atoi(temp);
1254
1255 /* flag */
1256 if (NULL != (p = get_next_token(p, &temp, str_end)))
1257 return (FAILURE);
1258 sp->sp_flag = atoi(temp);
1259
1260 return (SUCCESS);
1261 }
1262
1263 /*
1264 * FUNCTION : write_shadow_info()
1265 *
1266 * DESCRIPTION: Writes shadow information back to the DIT.
1267 *
1268 * INPUTS : Domain
1269 * Information to write
1270 *
1271 * OUTPUT : Success code
1272 *
1273 */
1274 suc_code
write_shadow_info(char * domain,struct spwd * sp)1275 write_shadow_info(char *domain, struct spwd *sp)
1276 {
1277 char *myself = "write_shadow_info";
1278 datum key, data;
1279 char *str;
1280 suc_code res;
1281 int len;
1282
1283 /* Work out how long string will be */
1284 len = strlen(sp->sp_namp) + 1;
1285
1286 /*
1287 * Bit crude but if we assume 1 byte is 3 decimal characters
1288 * will get enough buffer for the longs and some spare.
1289 */
1290 len += 7 * (3 * sizeof (long) + 1);
1291
1292 /* Allocate some memory */
1293 str = am(myself, len);
1294 if (NULL == str) {
1295 logmsg(MSG_NOMEM, LOG_ERR, "Could not aloc for shadow write");
1296 return (FAILURE);
1297 }
1298
1299 /* Build up shadow string */
1300 sprintf(str, "%s%c%d%c%d%c%d%c%d%c%d%c%d%c%d",
1301 sp->sp_namp, COLON_CHAR,
1302 sp->sp_lstchg, COLON_CHAR,
1303 sp->sp_min, COLON_CHAR,
1304 sp->sp_max, COLON_CHAR,
1305 sp->sp_warn, COLON_CHAR,
1306 sp->sp_inact, COLON_CHAR,
1307 sp->sp_expire, COLON_CHAR,
1308 sp->sp_flag);
1309
1310 /* Write it */
1311 data.dptr = str;
1312 data.dsize = strlen(data.dptr);
1313 key.dptr = sp->sp_namp;
1314 key.dsize = strlen(key.dptr);
1315 res = write_to_dit(AGEING_MAPPING, domain, key, data, TRUE, FALSE);
1316
1317 sfree(str);
1318 return (res);
1319 }
1320
1321 /*
1322 * FUNCTION : free_shadow_entry()
1323 *
1324 * DESCRIPTION: Frees up a shadow information structure
1325 *
1326 * INPUTS : Structure to free
1327 *
1328 * OUTPUTS : Nothing
1329 */
1330 void
free_shadow_entry(struct spwd * spwd)1331 free_shadow_entry(struct spwd *spwd)
1332 {
1333 if (NULL != spwd->sp_namp)
1334 sfree(spwd->sp_namp);
1335
1336 if (NULL != spwd->sp_pwdp)
1337 sfree(spwd->sp_pwdp);
1338
1339 /* No need to free numerics */
1340
1341 /* Free up structure */
1342 sfree(spwd);
1343 }
1344