Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / lib / pam / pam_modules / unix / unix_update_authtok_nisplus.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: unix_update_authtok_nisplus.c /main/5 1996/05/09 04:36:35 drk $ */
24
25 /*
26  * Copyright (c) 1992-1995, by Sun Microsystems, Inc.
27  * All rights reserved.
28  */
29
30 #ident  "@(#)unix_update_authtok_nisplus.c 1.55 96/02/02 SMI"
31
32 #include "unix_headers.h"
33
34 #ifdef PAM_NISPLUS
35 static int              update_attr(pam_handle_t *, char *, char **, char *,
36                                 int, char **, char **,
37                                 struct passwd *, int, int,
38                                 nis_result *, int);
39 static int              talk_to_npd(pam_handle_t *, char *, char **, char *,
40                                 char *, char *, char *,
41                                 struct passwd *, int, int,
42                                 char **, char **,
43                                 nis_result *, int, int);
44 static char             *reencrypt_secret(char *, char *, char *);
45 static nis_error        revert2oldpasswd(char *, nis_result *);
46
47 int
48 update_authtok_nisplus(
49         pam_handle_t *pamh,
50         char *domain,
51         char *field,
52         char *data[],                   /* Depending on field: it can store */
53                                         /* encrypted new passwd or new */
54                                         /* attributes */
55         char *old,                      /* old passwd: clear version */
56         char *oldrpc,                   /* old rpc passwd: clear version */
57         char *new,                      /* new passwd: clear version */
58         int  opwcmd,                    /* old passwd cmd: nispasswd */
59         struct passwd *nisplus_pwd,
60         char *curcryptsecret,
61         int privileged,
62         nis_result *passwd_res,
63         nis_result *cred_res,
64         int debug,
65         int nowarn)
66 {
67         char tmpcryptsecret[HEXKEYBYTES+KEYCHECKSUMSIZE+1];
68         char *newcryptsecret = NULL;
69         entry_col       ecol[8];
70         nis_object      *eobj;
71         nis_result      *mres;
72         char            mname[NIS_MAXNAMELEN];
73         nis_name        pwd_domain;
74         nis_error       niserr = 0;
75         struct spwd     sp;
76         char            shadow[80];
77         int             rc = 0;
78         char            messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
79         int             failover = FALSE;
80         char            *gecos = NULL, *shell = NULL;
81         char            *prognamep;
82         char            *usrname;
83
84         if ((rc = pam_get_item(pamh, PAM_SERVICE, (void **)&prognamep))
85                                                         != PAM_SUCCESS ||
86             (rc = pam_get_item(pamh, PAM_USER, (void **)&usrname))
87                                                         != PAM_SUCCESS)
88                 return (rc);
89
90         /*
91          * Passwd is setuid program. We want the real user to send out
92          * any nis+ requests. The correct identity should have been set
93          * in ck_perm() when checking privilege.
94          */
95         if (debug) {
96                 syslog(LOG_DEBUG,
97                         "the effective uid while updating NIS+ passwd is %d",
98                         geteuid());
99         }
100
101         if (opwcmd == FALSE) {
102                 /*
103                  * Attempt to let NIS+ NPD do the password update.
104                  * If the passwd entry is not present (in passwd_res)
105                  *      try NPD for the local domain.
106                  * If the passwd entry is present call NPD in the
107                  *      domain the passwd entry resides in.
108                  *      NPD wants only the domainname so strip off
109                  *      the org_dir portion of the passwd directory.
110                  */
111                 if (passwd_res == NULL || passwd_res->status != NIS_SUCCESS) {
112                         /*
113                          * CAVEAT:
114                          * Should never get here; ck_perm() should fail.
115                          *
116                          * It is a waste of time to try NPD for some values
117                          * of passwd_res->status; additional checks advised
118                          * if ever it is possible to get here.
119                          */
120                         rc = talk_to_npd(pamh, field, data, domain, usrname,
121                                 old, new, nisplus_pwd, failover, privileged,
122                                 &shell, &gecos, passwd_res, debug, nowarn);
123                 } else {
124                         pwd_domain = NIS_RES_OBJECT(passwd_res)->zo_domain;
125                         if (strcmp(nis_leaf_of(pwd_domain), "org_dir") == 0) {
126                                 pwd_domain = nis_domain_of(
127                                         NIS_RES_OBJECT(passwd_res)->zo_domain);
128                         }
129
130                         rc = talk_to_npd(pamh, field, data, pwd_domain, usrname,
131                                 old, new, nisplus_pwd, failover, privileged,
132                                 &shell, &gecos, passwd_res, debug, nowarn);
133                 }
134
135                 if (rc == PAM_SUCCESS || rc == PAM_NISPLUS_PARTIAL_SUCCESS) {
136                         sprintf(messages[0], PAM_MSG(pamh, 108,
137                                 "NIS+ password information changed for %s"),
138                                 usrname);
139                         (void) __pam_display_msg(pamh, PAM_TEXT_INFO,
140                                 1, messages, NULL);
141
142                         if (rc == PAM_SUCCESS) {
143                                 sprintf(messages[0], PAM_MSG(pamh, 109,
144                                 "NIS+ credential information changed for %s"),
145                                         usrname);
146                                 (void) __pam_display_msg(
147                                         pamh, PAM_TEXT_INFO, 1,
148                                         messages, NULL);
149                         }
150                         return (PAM_SUCCESS);
151                 }
152                 /* failover to use old protocol */
153                 if (debug)
154                         syslog(LOG_DEBUG,
155                             "Failed to use new passwd update protocol");
156
157                 /*
158                  * There are two reasons we will get here:
159                  * 1. passwd, shell, gecos update failed (true failover)
160                  * 2. we are updating passwd attrs other than the above
161                  *    three attrs. In this case, rc is equal to PAM_PERM_DENIED
162                  *    (i.e. attrs not supported by new protocol)
163                  */
164                 failover = TRUE;
165         }
166
167         if (strcmp(field, "passwd") == 0) {
168                 /*
169                  * Old style nisplus update
170                  *
171                  * Obtain the old aging information. And modify, if need be,
172                  * on top. At least the lstchg field needs to be changed.
173                  */
174                 /* old protocol requires user credential info */
175                 if (cred_res == NULL || cred_res->status != NIS_SUCCESS) {
176                         syslog(LOG_ERR, "%s%s: %s", prognamep, NISPLUS_MSG,
177                                 "Failover: user credential is required.");
178                         return (PAM_AUTHTOK_RECOVERY_ERR);
179                 }
180
181                 nisplus_populate_age(NIS_RES_OBJECT(passwd_res), &sp);
182
183                 (void) memcpy(tmpcryptsecret, curcryptsecret,
184                     HEXKEYBYTES + KEYCHECKSUMSIZE + 1);
185
186                 /* same user check? */
187                 if ((!privileged) && (newcryptsecret = reencrypt_secret
188                     (tmpcryptsecret, oldrpc, new)) == NULL) {
189                         sprintf(messages[0], " ");
190                         sprintf(messages[1], PAM_MSG(pamh, 110,
191                                 "Unable to reencrypt NIS+ credentials for %s;"),
192                                 usrname);
193                         (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
194                                 2, messages, NULL);
195                         return (PAM_AUTHTOK_RECOVERY_ERR);
196                 }
197
198                 /* update passwd at server */
199                 (void) memset((char *)ecol, 0, sizeof (ecol));
200                 ecol[1].ec_value.ec_value_val = *data;
201                 ecol[1].ec_value.ec_value_len = strlen(*data) + 1;
202                 ecol[1].ec_flags = EN_CRYPT|EN_MODIFIED;
203
204                 /* update last change field */
205                 sp.sp_lstchg = DAY_NOW;
206                 if (sp.sp_max == 0) {
207                         /* passwd was forced to changed: turn off aging */
208                         sp.sp_max = -1;
209                         sp.sp_min = -1;
210                 }
211
212                 /* prepare shadow column */
213                 if (sp.sp_expire == -1) {
214                         sprintf(shadow, "%ld:%ld:%ld:%ld:%ld::%lu",
215                                 sp.sp_lstchg,
216                                 sp.sp_min,
217                                 sp.sp_max,
218                                 sp.sp_warn,
219                                 sp.sp_inact,
220                                 sp.sp_flag);
221                 } else {
222                         sprintf(shadow, "%ld:%ld:%ld:%ld:%ld:%ld:%lu",
223                                 sp.sp_lstchg,
224                                 sp.sp_min,
225                                 sp.sp_max,
226                                 sp.sp_warn,
227                                 sp.sp_inact,
228                                 sp.sp_expire,
229                                 sp.sp_flag);
230                 }
231                 ecol[7].ec_value.ec_value_val = shadow;
232                 ecol[7].ec_value.ec_value_len = strlen(shadow) + 1;
233                 ecol[7].ec_flags = EN_CRYPT|EN_MODIFIED;
234
235                 /*
236                  * build entry based on the one we got back from the server
237                  */
238                 eobj = nis_clone_object(NIS_RES_OBJECT(passwd_res), NULL);
239                 if (eobj == NULL) {
240                         syslog(LOG_ERR, "%s%s: %s", prognamep, NISPLUS_MSG,
241                                 "clone object failed");
242                         return (PAM_AUTHTOK_RECOVERY_ERR);
243                 }
244                 eobj->EN_data.en_cols.en_cols_val = ecol;
245                 eobj->EN_data.en_cols.en_cols_len = 8;
246
247                 /* strlen("[name=],.") + null + "." = 11 */
248                 if ((strlen(usrname) +
249                     strlen(NIS_RES_OBJECT(passwd_res)->zo_name) +
250                     strlen(NIS_RES_OBJECT(passwd_res)->zo_domain) + 11) >
251                         (size_t) NIS_MAXNAMELEN) {
252                         syslog(LOG_ERR, "%s%s: %s", prognamep, NISPLUS_MSG,
253                                 "NIS+ name too long");
254                         return (PAM_BUF_ERR);
255                 }
256                 sprintf(mname, "[name=%s],%s.%s", usrname,
257                     NIS_RES_OBJECT(passwd_res)->zo_name,
258                     NIS_RES_OBJECT(passwd_res)->zo_domain);
259                 if (mname[strlen(mname) - 1] != '.')
260                         (void) strcat(mname, ".");
261                 mres = nis_modify_entry(mname, eobj, 0);
262
263                 /*
264                  * It is possible that we have permission to modify the
265                  * encrypted password but not the shadow column in the
266                  * NIS+ table. In this case, we should try updating only
267                  * the password field and not the aging stuff (lstchg).
268                  * With the current NIS+ passwd table format, this would
269                  * be the case most of the times.
270                  */
271                 if (mres->status == NIS_PERMISSION) {
272                         ecol[7].ec_flags = 0;
273                         mres = nis_modify_entry(mname, eobj, 0);
274                         if (mres->status != NIS_SUCCESS) {
275                                 sprintf(messages[0], PAM_MSG(pamh, 111,
276                                 "%s%s: Password information update failed"),
277                                         prognamep, NISPLUS_MSG);
278                                 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
279                                         1, messages, NULL);
280
281                                 (void) nis_freeresult(mres);
282                                 return (PAM_AUTHTOK_RECOVERY_ERR);
283                         }
284                 }
285                 /* set column stuff to NULL so that we can free eobj */
286                 eobj->EN_data.en_cols.en_cols_val = NULL;
287                 eobj->EN_data.en_cols.en_cols_len = 0;
288                 (void) nis_destroy_object(eobj);
289                 (void) nis_freeresult(mres);
290
291                 sprintf(messages[0], PAM_MSG(pamh, 112,
292                         "NIS+ password information changed for %s"),
293                         usrname);
294                 (void) __pam_display_msg(pamh, PAM_TEXT_INFO,
295                         1, messages, NULL);
296
297                 if (privileged) {
298                         sprintf(messages[0], " ");
299                         sprintf(messages[1], PAM_MSG(pamh, 113,
300         "The NIS+ credential information for %s will not be changed."),
301                         usrname);
302                         sprintf(messages[2], PAM_MSG(pamh, 114,
303         "User %s must do the following to update his/her"), usrname);
304                         sprintf(messages[3], PAM_MSG(pamh, 115,
305         "credential information:"));
306                         sprintf(messages[4], PAM_MSG(pamh, 116,
307         "Use NEW passwd for login and OLD passwd for keylogin."));
308                         sprintf(messages[5], PAM_MSG(pamh, 117,
309         "Use \"chkey -p\" to reencrypt the credentials with the"));
310                         sprintf(messages[6], PAM_MSG(pamh, 118,
311         "new login passwd."));
312                         sprintf(messages[7], PAM_MSG(pamh, 119,
313         "The user must keylogin explicitly after their next login."),
314                                 usrname);
315                         sprintf(messages[8], " ");
316                         (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
317                                 9, messages, NULL);
318
319                         return (PAM_SUCCESS);
320                 }
321
322                 /* update cred at server */
323                 (void) memset((char *)ecol, 0, sizeof (ecol));
324                 ecol[4].ec_value.ec_value_val = newcryptsecret;
325                 ecol[4].ec_value.ec_value_len = strlen(newcryptsecret) + 1;
326                 ecol[4].ec_flags = EN_CRYPT|EN_MODIFIED;
327                 eobj = nis_clone_object(NIS_RES_OBJECT(cred_res), NULL);
328                 if (eobj == NULL) {
329                         syslog(LOG_ERR, "%s%s: %s", prognamep, NISPLUS_MSG,
330                                 "clone object failed");
331                         return (PAM_AUTHTOK_RECOVERY_ERR);
332                 }
333                 eobj->EN_data.en_cols.en_cols_val = ecol;
334                 eobj->EN_data.en_cols.en_cols_len = 5;
335
336                 /*
337                  * Now, if one were stupid enough to run nispasswd as/for root
338                  * on some machine, it would have looked up and modified
339                  * the password entry for "root" in passwd.org_dir. Now,
340                  * should we really apply this new password to the cred
341                  * entry for "<machinename>.<domainname>" ?
342                  *
343                  * POLICY: NO. We have no way of identifying a root user in
344                  * NIS+ passwd table for each root@machinename. We do not
345                  * allow the one password for [name=root], passwd.org_dir
346                  * to apply to all "<machinename>.<domainname>" principals.
347                  * If somebody let a root entry in passwd table, it probably
348                  * has modify permissions for a distinguished NIS+ principal
349                  * which we let be associated only with NIS+ principal
350                  * root.<domainname>. Does this make any sense ?
351                  */
352
353                 /* strlen("[cname=.,auth_type=DES],.") + null + "." = 26 */
354                 if ((strlen(ENTRY_VAL(NIS_RES_OBJECT(cred_res), 0)) +
355                     strlen(NIS_RES_OBJECT(cred_res)->zo_name) +
356                     strlen(NIS_RES_OBJECT(cred_res)->zo_domain) + 26) >
357                         (size_t) NIS_MAXNAMELEN) {
358                         syslog(LOG_ERR, "%s%s: %s", prognamep, NISPLUS_MSG,
359                                 "NIS+ name too long");
360                         return (PAM_BUF_ERR);
361                 }
362                 sprintf(mname, "[cname=%s,auth_type=DES],%s.%s",
363                     ENTRY_VAL(NIS_RES_OBJECT(cred_res), 0),
364                     NIS_RES_OBJECT(cred_res)->zo_name,
365                     NIS_RES_OBJECT(cred_res)->zo_domain);
366                 if (mname[strlen(mname) - 1] != '.')
367                         (void) strcat(mname, ".");
368                 mres = nis_modify_entry(mname, eobj, 0);
369                 if (mres->status != NIS_SUCCESS) {
370
371                         /* attempt to revert back to the old passwd */
372                         niserr = revert2oldpasswd(usrname, passwd_res);
373
374                         if (niserr != NIS_SUCCESS) {
375                                 sprintf(messages[0], "");
376                                 sprintf(messages[1], PAM_MSG(pamh, 120,
377                 "WARNING: Could not reencrypt NIS+ credentials for %s;"),
378                                     usrname);
379                                 sprintf(messages[2], PAM_MSG(pamh, 121,
380                 "login and keylogin passwords differ."));
381                                 sprintf(messages[3], PAM_MSG(pamh, 122,
382                 "Use NEW passwd for login and OLD passwd for keylogin."));
383                                 sprintf(messages[4], PAM_MSG(pamh, 117,
384                 "Use \"chkey -p\" to reencrypt the credentials with the"));
385                                 sprintf(messages[5], PAM_MSG(pamh, 118,
386                 "new login passwd."));
387                                 sprintf(messages[6], PAM_MSG(pamh, 123,
388                 "You must keylogin explicitly after your next login."));
389                                 sprintf(messages[7], "");
390                                 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
391                                     8, messages, NULL);
392
393                                 return (PAM_AUTHTOK_RECOVERY_ERR);
394                         }
395
396                         sprintf(messages[0], PAM_MSG(pamh, 124,
397 "%s%s: couldn't change password for %s."),
398                                 prognamep, NISPLUS_MSG, usrname);
399                         sprintf(messages[1], PAM_MSG(pamh, 125,
400 "Reason: failed to update the cred table with reencrypted credentials."));
401                         sprintf(messages[2], PAM_MSG(pamh, 126,
402 "Please notify your System Administrator."));
403                         (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
404                                 3, messages, NULL);
405
406                         (void) nis_freeresult(mres);
407                         return (PAM_AUTHTOK_RECOVERY_ERR);
408                 }
409                 /* set column stuff to NULL so that we can free eobj */
410                 eobj->EN_data.en_cols.en_cols_val = NULL;
411                 eobj->EN_data.en_cols.en_cols_len = 0;
412                 (void) nis_destroy_object(eobj);
413                 (void) nis_freeresult(mres);
414
415                 sprintf(messages[0], PAM_MSG(pamh, 109,
416                         "NIS+ credential information changed for %s"),
417                         usrname);
418                 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
419                         1, messages, NULL);
420         } else
421                 return (update_attr(pamh, field, data, usrname, 1, &shell,
422                         &gecos, nisplus_pwd, failover, privileged,
423                         passwd_res, nowarn));
424
425         return (PAM_SUCCESS);
426 }
427
428
429 /*
430  * The function uses the new protocol to update passwd attributes via
431  * passwd daemon.
432  */
433 static int
434 talk_to_npd(pam_handle_t *pamh, char *field, char **data, char *domain,
435         char *user, char *oldpass, char *newpass,
436         struct passwd *nisplus_pwd, int failover, int privileged,
437         char **shell, char **gecos,
438         nis_result *passwd_res, int debug, int nowarn)
439 {
440         CLIENT          *clnt = NULL;
441         char            *old_passwd = NULL;
442         char            srv_pubkey[HEXKEYBYTES + 1];
443         char            u_pubkey[HEXKEYBYTES + 1];
444         char            u_seckey[HEXKEYBYTES + 1];
445         des_block       deskey;
446         unsigned long   ident = 0, randval = 0;
447         int             error = 0, status, srv_keysize = HEXKEYBYTES + 1;
448         int             retcode = PAM_SYSTEM_ERR;
449         char            messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
450         nispasswd_error         *errlist = NULL;
451         nispasswd_error         *p = NULL;
452
453         if (user == NULL || domain == NULL || *domain == '\0') {
454                 retcode = PAM_AUTHTOK_RECOVERY_ERR;
455                 goto out;
456         }
457
458         if (debug)
459                 syslog(LOG_DEBUG, "domain=%s, user=%s", domain, user);
460
461         /*
462          * Let's do a quick check whether the attrs are really of interest.
463          * We don't want to prompt for user passwd which is sure not to be
464          * used.
465          */
466         retcode = update_attr(pamh, field, data, NULL, 0, NULL, NULL,
467                         nisplus_pwd, failover, privileged,
468                         passwd_res, nowarn);
469         if (retcode != PAM_SUCCESS)
470                 goto out;
471
472         if (oldpass == NULL) {
473                 /*
474                  * This is possible from unix_set_authtokattr().
475                  * Old passwd is required to change any attributes.
476                  * This is imposed by new protocol to support users
477                  * without credentials.
478                  */
479
480                 retcode = __pam_get_authtok(pamh, PAM_PROMPT, 0, PASSWORD_LEN,
481                         PAM_MSG(pamh, 63, "Enter login(NIS+) password: "),
482                         &old_passwd);
483                 if (retcode != PAM_SUCCESS)
484                         goto out;
485         } else {
486                 old_passwd = strdup(oldpass);
487                 if (old_passwd == NULL) {
488                         retcode = PAM_BUF_ERR;
489                         goto out;
490                 }
491         }
492
493         /* get gecos, shell and other */
494         retcode = update_attr(pamh, field, data, user, 0, shell, gecos,
495                 nisplus_pwd, failover, privileged,
496                 passwd_res, nowarn);
497         if (retcode != PAM_SUCCESS)
498                 goto out;
499
500         if (npd_makeclnthandle(domain, &clnt, srv_pubkey, srv_keysize) ==
501                                                                 FALSE) {
502                 syslog(LOG_ALERT,
503                 "Couldn't make a client handle to NIS+ password daemon");
504                 retcode = PAM_AUTHTOK_RECOVERY_ERR;
505                 goto out;
506         }
507
508 /* again: doesn't need to generate a new pair of keys */
509         /* generate a key-pair for this user */
510         (void) __gen_dhkeys(u_pubkey, u_seckey, old_passwd);
511
512         /*
513          * get the common des key from the servers' pubkey and
514          * the users secret key
515          */
516         if (__get_cmnkey(srv_pubkey, u_seckey, &deskey) == FALSE) {
517                 syslog(LOG_ALERT, "Couldn't get a common DES key");
518                 retcode = PAM_AUTHTOK_RECOVERY_ERR;
519                 goto out;
520         }
521 again:
522         status = nispasswd_auth(user, domain, old_passwd, u_pubkey, &deskey,
523                 clnt, &ident, &randval, &error);
524         if (status == NPD_FAILED) {
525                 switch (error) {
526                 case NPD_NOTMASTER:
527                         syslog(LOG_ALERT,
528         "Password update daemon is not running with NIS+ master server");
529                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
530                         goto out;
531                 case NPD_SYSTEMERR:
532                         syslog(LOG_ALERT, "NIS+ system error");
533                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
534                         goto out;
535                 case NPD_IDENTINVALID:
536                         syslog(LOG_ALERT, "NIS+ identifier invalid");
537                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
538                         goto out;
539                 case NPD_PASSINVALID:
540                         syslog(LOG_ALERT, "NIS+ password invalid");
541                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
542                         goto out;
543                 case NPD_NOSUCHENTRY:
544                         syslog(LOG_ALERT, "No NIS+ password entry for %s",
545                                 user);
546                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
547                         goto out;
548                 case NPD_NISERROR:
549                         syslog(LOG_ALERT, "NIS+ error");
550                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
551                         goto out;
552                 case NPD_CKGENFAILED:
553                         syslog(LOG_ALERT,
554                             "Couldn't generate a common DES key");
555                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
556                         goto out;
557                 case NPD_NOPASSWD:
558                         syslog(LOG_ALERT, "No NIS+ password for %s", user);
559                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
560                         goto out;
561                 case NPD_NOTAGED:
562                         syslog(LOG_ALERT, "NIS+ passwd has not aged enough");
563                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
564                         goto out;
565                 case NPD_NOSHDWINFO:
566                         syslog(LOG_ALERT, "No shadow password information");
567                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
568                         goto out;
569                 default:
570                         syslog(LOG_ALERT, "NIS+ fatal error: %d", error);
571                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
572                         goto out;
573                 }
574         }
575         if (status == NPD_TRYAGAIN) {
576                 /*
577                  * call nispasswd_auth() again after getting another
578                  * passwd. Note that ident is now non-zero.
579                  */
580                 if (debug)
581                         syslog(LOG_DEBUG,
582                             "status=tryagain; ident=%ld, randval=%ld",
583                             ident, randval);
584
585                 if (old_passwd) {
586                         memset(old_passwd, 0, strlen(old_passwd));
587                         free(old_passwd);
588                 }
589
590                 /* wrong passwd: get auth token again */
591                 sprintf(messages[0], PAM_MSG(pamh, 127,
592                         "NIS+ Password incorrect: try again"));
593                 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
594                     messages, NULL);
595
596                 retcode = __pam_get_authtok(pamh, PAM_PROMPT, 0, PASSWORD_LEN,
597                         PAM_MSG(pamh, 63, "Enter login(NIS+) password: "),
598                         &old_passwd);
599                 if (retcode != PAM_SUCCESS)
600                         goto out;
601
602                 goto again;
603         }
604         if (status == NPD_SUCCESS) {
605                 /* send the new passwd & other changes */
606                 if (debug)
607                         syslog(LOG_DEBUG,
608                                 "status=success; ident=%ld, randval=%ld",
609                                 ident, randval);
610                 if (newpass == NULL) {
611                         /*
612                          * This is possible from unix_set_authtokattr().
613                          * Just use the same passwd so that we have a
614                          * meaningful passwd field.
615                          */
616                         newpass = old_passwd;
617                 }
618
619                 /* gecos and shell could be NULL if we just change passwd */
620                 status = nispasswd_pass(clnt, ident, randval, &deskey,
621                                 newpass, *gecos, *shell, &error, &errlist);
622
623                 if (status == NPD_FAILED) {
624                         sprintf(messages[0], PAM_MSG(pamh, 128,
625                                 "NIS+ password information update failed \
626 while talking to NIS+ passwd daemon"));
627                         (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
628                                 1, messages, NULL);
629                         if (debug)
630                                 syslog(LOG_DEBUG, "error=%d", error);
631                         retcode = PAM_AUTHTOK_RECOVERY_ERR;
632                         goto out;
633                 }
634                 /*
635                  * WHAT SHOULD BE DONE FOR THE PARTIAL SUCCESS CASE ??
636                  * I'll just print out some messages
637                  */
638                 if (status == NPD_PARTIALSUCCESS) {
639                         syslog(LOG_ALERT,
640                         "Password information is partially updated.");
641                         for (p = errlist; p != NULL; p = p->next) {
642                                 if (p->npd_field == NPD_GECOS) {
643                                         sprintf(messages[0], PAM_MSG(pamh, 129,
644         "GECOS information was not updated: check NIS+ permissions."));
645                                         (void) __pam_display_msg(pamh,
646                                                 PAM_ERROR_MSG, 1,
647                                                 messages, NULL);
648                                 } else if (p->npd_field == NPD_SHELL) {
649                                         sprintf(messages[0], PAM_MSG(pamh, 130,
650         "SHELL information was not updated: check NIS+ permissions."));
651                                         (void) __pam_display_msg(pamh,
652                                                 PAM_ERROR_MSG, 1,
653                                                 messages, NULL);
654                                 } else if (p->npd_field == NPD_SECRETKEY) {
655                                         sprintf(messages[0], PAM_MSG(pamh, 131,
656                 "NIS+ Credential information was not updated."));
657                                         (void) __pam_display_msg(pamh,
658                                                 PAM_ERROR_MSG, 1,
659                                                 messages, NULL);
660                                 }
661                         }
662                         /* check for collision with PAM_* return code */
663                         (void) __npd_free_errlist(errlist);
664                         retcode = PAM_NISPLUS_PARTIAL_SUCCESS;
665                         goto out;
666                 }
667                 (void) __npd_free_errlist(errlist);
668         }
669         retcode = PAM_SUCCESS;
670 out:
671         if (old_passwd) {
672                 memset(old_passwd, 0, strlen(old_passwd));
673                 free(old_passwd);
674         }
675         return (retcode);
676 }
677
678
679 static int
680 update_attr(pam_handle_t *pamh, char *field, char **data, char *usrname,
681         int opwcmd, char **sh_p, char **gecos_p,
682         struct passwd *nisplus_pwd, int failover, int privileged,
683         nis_result *passwd_res, int nowarn)
684 {
685         entry_col       ecol[8];
686         nis_object      *eobj;
687         nis_result      *mres;
688         char            mname[NIS_MAXNAMELEN];
689         struct spwd     sp;             /* new attr values in here */
690         char            *value;
691         int             maxdate;
692         int             mindate;
693         int             warndate;
694         static char     lkstring[] = "*LK*"; /* ??? in header */
695         int             flag = 0;       /* any change in shadow column */
696         char            **data_p = data;
697         char            shadow[80];
698         char            *newhome;
699         char            *newgecos;
700         char            *newsh;
701         char            messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
702
703         if (strcmp(field, "attr") == 0) {
704                 /*
705                  * Obtain the old aging information. And modify, if need be,
706                  * on top.
707                  */
708                 if (opwcmd)
709                         nisplus_populate_age(NIS_RES_OBJECT(passwd_res), &sp);
710
711                 (void) memset((char *)ecol, 0, sizeof (ecol));
712                 while (*data != NULL) {
713                         /* AUTHTOK_DEL: not applicable */
714
715                         /* check attribute: AUTHTOK_LK */
716                         if ((value = attr_match("AUTHTOK_LK", *data))
717                                                                 != NULL) {
718                                 /* new update protocol doesn't support this */
719                                 if (opwcmd == FALSE)
720                                         return (PAM_PERM_DENIED);
721
722                                 if (strcmp(value, "1") == 0) {
723                                         /* lock password */
724                                         ecol[1].ec_value.ec_value_val =
725                                             &lkstring[0];
726                                         ecol[1].ec_value.ec_value_len =
727                                             strlen(&lkstring[0]) + 1;
728                                         ecol[1].ec_flags = EN_CRYPT|EN_MODIFIED;
729
730                                         if (!(attr_find
731                                             ("AUTHTOK_EXP", data_p))) {
732                                                 sp.sp_lstchg = DAY_NOW;
733                                                 flag = 1;
734                                         }
735                                 }
736                                 data++;
737                                 continue;
738                         }
739
740                         /* check attribute: AUTHTOK_EXP */
741                         if ((value = attr_match("AUTHTOK_EXP", *data))
742                             != NULL) {
743                                 /* new update protocol doesn't support this */
744                                 if (opwcmd == FALSE)
745                                         return (PAM_PERM_DENIED);
746
747                                 if (strcmp(value, "1") == 0) {
748                                         /* expire password */
749                                         sp.sp_lstchg = (long) 0;
750                                         flag = 1;
751                                 }
752                                 data++;
753                                 continue;
754                         }
755
756                         /* check attribute: AUTHTOK_MAXAGE */
757                         if ((value = attr_match("AUTHTOK_MAXAGE", *data))
758                             != NULL) {
759                                 /* new update protocol doesn't support this */
760                                 if (opwcmd == FALSE)
761                                         return (PAM_PERM_DENIED);
762
763                                 /* set max field */
764                                 maxdate = (int)atol(value);
765                                 if (!(attr_find
766                                         ("AUTHTOK_MINAGE", data_p)) &&
767                                         sp.sp_min == -1)
768                                         sp.sp_min = 0;
769                                 if (maxdate == -1) {    /* turn off aging */
770                                         sp.sp_min = -1;
771                                         sp.sp_warn = -1;
772                                 } else if (sp.sp_max == -1)
773                                         sp.sp_lstchg = DAY_NOW;
774
775                                 sp.sp_max = maxdate;
776                                 flag = 1;
777                                 data++;
778                                 continue;
779                         }
780
781                         /* check attribute: AUTHTOK_MINAGE */
782                         if ((value = attr_match("AUTHTOK_MINAGE", *data))
783                             != NULL) {
784                                 /* new update protocol doesn't support this */
785                                 if (opwcmd == FALSE)
786                                         return (PAM_PERM_DENIED);
787
788                                 /* set min field */
789                                 mindate = (int)atol(value);
790                                 if (!(attr_find
791                                         ("AUTHTOK_MAXAGE", data_p)) &&
792                                     sp.sp_max == -1 && mindate != -1)
793                                         return (PAM_AUTHTOK_ERR);
794                                 sp.sp_min = mindate;
795                                 flag = 1;
796                                 data++;
797                                 continue;
798                         }
799
800                         /* check attribute: AUTHTOK_WARNDATE */
801                         if ((value = attr_match("AUTHTOK_WARNDATE", *data))
802                             != NULL) {
803                                 /* new update protocol doesn't support this */
804                                 if (opwcmd == FALSE)
805                                         return (PAM_PERM_DENIED);
806
807                                 /* set warn field */
808                                 warndate = (int)atol(value);
809                                 if (sp.sp_max == -1 && warndate != -1)
810                                         return (PAM_AUTHTOK_ERR);
811                                 sp.sp_warn = warndate;
812                                 flag = 1;
813                                 data++;
814                                 continue;
815                         }
816
817                         if ((value = attr_match("AUTHTOK_SHELL", *data))
818                             != NULL) {
819                                 /* see if quick check */
820                                 if (usrname == NULL)
821                                         return (PAM_SUCCESS);
822
823                                 if (nisplus_pwd == NULL && opwcmd) {
824                                     if (!nowarn) {
825                                         sprintf(messages[0], PAM_MSG(pamh, 132,
826                                             "No NIS+ record"));
827                                         (void) __pam_display_msg(pamh,
828                                                 PAM_ERROR_MSG, 1,
829                                                 messages, NULL);
830                                     }
831                                     return (PAM_AUTHTOK_RECOVERY_ERR);
832                                 }
833
834                                 /*
835                                  * If failover, we already got the shell info
836                                  * in "shell". Don't ask again.
837                                  */
838                                 if (failover)
839                                         newsh = *sh_p;
840                                 else
841                                         newsh = getloginshell(pamh,
842                                             nisplus_pwd->pw_shell,
843                                             privileged, nowarn);
844
845                                 /* if NULL, shell unchanged */
846                                 if (newsh == NULL)
847                                         return (PAM_SUCCESS);
848
849                                 if (opwcmd || failover) {
850                                         ecol[6].ec_value.ec_value_val = newsh;
851                                         ecol[6].ec_value.ec_value_len =
852                                             strlen(newsh) + 1;
853                                         ecol[6].ec_flags = EN_MODIFIED;
854                                 } else
855                                         *sh_p = newsh;
856                                 data++;
857                                 continue;
858                         }
859
860                         if ((value = attr_match("AUTHTOK_HOMEDIR", *data))
861                             != NULL) {
862                                 /* new update protocol doesn't support this */
863                                 if (opwcmd == FALSE)
864                                         return (PAM_PERM_DENIED);
865
866                                 /* home directory */
867                                 if (nisplus_pwd == NULL) {
868                                     if (!nowarn) {
869                                         sprintf(messages[0], PAM_MSG(pamh, 132,
870                                             "No NIS+ record"));
871                                         (void) __pam_display_msg(pamh,
872                                                 PAM_ERROR_MSG, 1,
873                                                 messages, NULL);
874                                     }
875                                     return (PAM_AUTHTOK_RECOVERY_ERR);
876                                 }
877                                 newhome = gethomedir(pamh, nisplus_pwd->pw_dir,
878                                                 nowarn);
879                                 /* if NULL, homedir unchanged */
880                                 if (newhome == NULL)
881                                         return (PAM_SUCCESS);
882                                 ecol[5].ec_value.ec_value_val = newhome;
883                                 ecol[5].ec_value.ec_value_len =
884                                     strlen(newhome) + 1;
885                                 ecol[5].ec_flags = EN_MODIFIED;
886                                 data++;
887                                 continue;
888                         }
889
890                         if ((value = attr_match("AUTHTOK_GECOS", *data))
891                             != NULL) {
892                                 /* see if quick check */
893                                 if (usrname == NULL)
894                                         return (PAM_SUCCESS);
895
896                                 /* finger information */
897                                 if (nisplus_pwd == NULL && opwcmd) {
898                                     if (!nowarn) {
899                                         sprintf(messages[0], PAM_MSG(pamh, 132,
900                                             "No NIS+ record"));
901                                         (void) __pam_display_msg(pamh,
902                                                 PAM_ERROR_MSG, 1,
903                                                 messages, NULL);
904                                     }
905                                     return (PAM_AUTHTOK_RECOVERY_ERR);
906                                 }
907                                 if (failover) {
908                                         newgecos = *gecos_p;
909                                 } else {
910                                         newgecos = getfingerinfo(pamh,
911                                                 nisplus_pwd->pw_gecos, nowarn);
912                                 }
913
914                                 /* if NULL, gecos unchanged */
915                                 if (newgecos == NULL)
916                                         return (PAM_SUCCESS);
917
918                                 if (opwcmd || failover) {
919                                         ecol[4].ec_value.ec_value_val =
920                                             newgecos;
921                                         ecol[4].ec_value.ec_value_len =
922                                             strlen(newgecos) + 1;
923                                         ecol[4].ec_flags = EN_MODIFIED;
924                                 } else
925                                         *gecos_p = newgecos;
926                                 data++;
927                                 continue;
928                         }
929                 } /* while */
930
931                 if (usrname == NULL)
932                         return (PAM_SUCCESS);
933
934                 if (flag && opwcmd) {
935                         /* prepare shadow column */
936                         if (sp.sp_expire == -1) {
937                                 sprintf(shadow, "%ld:%ld:%ld:%ld:%ld::%lu",
938                                     sp.sp_lstchg,
939                                     sp.sp_min,
940                                     sp.sp_max,
941                                     sp.sp_warn,
942                                     sp.sp_inact,
943                                     sp.sp_flag);
944                         } else {
945                                 sprintf(shadow, "%ld:%ld:%ld:%ld:%ld:%ld:%lu",
946                                     sp.sp_lstchg,
947                                     sp.sp_min,
948                                     sp.sp_max,
949                                     sp.sp_warn,
950                                     sp.sp_inact,
951                                     sp.sp_expire,
952                                     sp.sp_flag);
953                         }
954                         ecol[7].ec_value.ec_value_val = shadow;
955                         ecol[7].ec_value.ec_value_len = strlen(shadow) + 1;
956                         ecol[7].ec_flags = EN_CRYPT|EN_MODIFIED;
957                 }
958
959                 if (opwcmd || failover) {
960                         eobj = nis_clone_object(NIS_RES_OBJECT(passwd_res),
961                             NULL);
962                         if (eobj == NULL) {
963                                 syslog(LOG_ERR, "NIS+ clone object failed");
964                                 return (PAM_AUTHTOK_RECOVERY_ERR);
965                         }
966                         eobj->EN_data.en_cols.en_cols_val = ecol;
967                         eobj->EN_data.en_cols.en_cols_len = 8;
968
969                         /* strlen("[name=],.") + null + "." = 17 */
970                         if ((strlen(usrname) +
971                             strlen(NIS_RES_OBJECT(passwd_res)->zo_name) +
972                             strlen(NIS_RES_OBJECT(passwd_res)->zo_domain) +
973                             17) > (size_t) NIS_MAXNAMELEN) {
974                                 syslog(LOG_ERR, "NIS+ name too long");
975                                 return (PAM_BUF_ERR);
976                         }
977                         sprintf(mname, "[name=%s],%s.%s", usrname,
978                                 NIS_RES_OBJECT(passwd_res)->zo_name,
979                                 NIS_RES_OBJECT(passwd_res)->zo_domain);
980                         if (mname[strlen(mname) - 1] != '.')
981                                 (void) strcat(mname, ".");
982                         mres = nis_modify_entry(mname, eobj, 0);
983                         if (mres->status != NIS_SUCCESS) {
984                                 sprintf(messages[0], PAM_MSG(pamh, 133,
985                 "NIS+ password information update failed (update_attr)"));
986                                 (void) __pam_display_msg(pamh,
987                                         PAM_ERROR_MSG, 1, messages, NULL);
988
989                                 return (PAM_AUTHTOK_RECOVERY_ERR);
990                         }
991
992                         sprintf(messages[0], PAM_MSG(pamh, 112,
993                                 "NIS+ password information changed for %s"),
994                                 usrname);
995                         (void) __pam_display_msg(pamh, PAM_TEXT_INFO,
996                                 1, messages, NULL);
997                 }
998         }
999         return (PAM_SUCCESS);
1000 }
1001
1002 /*
1003  * Return reencrypted secret key.
1004  * The first two if statements should always succeed as these tests
1005  * are also carried out in getnewpasswd().
1006  */
1007 static char *
1008 reencrypt_secret(char *oldsecret, char *oldpass, char *newpass)
1009 {
1010         static char crypt[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
1011
1012         if (xdecrypt(oldsecret, oldpass) == 0)
1013                 return (NULL); /* cbc_crypt failed */
1014
1015         if (memcmp(oldsecret, &(oldsecret[HEXKEYBYTES]), KEYCHECKSUMSIZE) != 0)
1016                 return (NULL); /* didn't really decrypt */
1017
1018         (void) memcpy(crypt, oldsecret, HEXKEYBYTES);
1019         (void) memcpy(crypt + HEXKEYBYTES, oldsecret, KEYCHECKSUMSIZE);
1020         crypt[HEXKEYBYTES + KEYCHECKSUMSIZE] = 0;
1021
1022         if (xencrypt(crypt, newpass) == 0)
1023                 return (NULL); /* cbc_crypt encryption failed */
1024
1025         return (crypt);
1026 }
1027
1028
1029 /*
1030  * Revert back to the old passwd
1031  */
1032 static nis_error
1033 revert2oldpasswd(char *usrname, nis_result *passwd_res)
1034 {
1035         entry_col ecol[8];
1036         nis_object *eobj;
1037         nis_result *mres;
1038         char mname[NIS_MAXNAMELEN];
1039
1040         /*
1041          * clear column data
1042          */
1043         (void) memset((char *) ecol, 0, sizeof (ecol));
1044
1045         /*
1046          * passwd (col 1)
1047          */
1048         ecol[1].ec_value.ec_value_val =
1049                 ENTRY_VAL(NIS_RES_OBJECT(passwd_res), 1);
1050         ecol[1].ec_value.ec_value_len =
1051                 ENTRY_LEN(NIS_RES_OBJECT(passwd_res), 1);
1052         ecol[1].ec_flags = EN_CRYPT|EN_MODIFIED;
1053
1054         /*
1055          * build entry based on the global "passwd_res"
1056          */
1057         eobj = nis_clone_object(NIS_RES_OBJECT(passwd_res), NULL);
1058         if (eobj == NULL)
1059                 return (NIS_SYSTEMERROR);
1060         eobj->EN_data.en_cols.en_cols_val = ecol;
1061         eobj->EN_data.en_cols.en_cols_len = 8;
1062
1063         sprintf(mname, "[name=%s],%s.%s", usrname,
1064                 NIS_RES_OBJECT(passwd_res)->zo_name,
1065                 NIS_RES_OBJECT(passwd_res)->zo_domain);
1066
1067         mres = nis_modify_entry(mname, eobj, 0);
1068         return (mres->status);
1069 }
1070
1071 #endif /* PAM_NISPLUS */