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