1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 
7 /*
8  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
9  *
10  *	Openvision retains the copyright to derivative works of
11  *	this source code.  Do *NOT* create a derivative of this
12  *	source code before consulting with your legal department.
13  *	Do *NOT* integrate *ANY* of this source code into another
14  *	product before consulting with your legal department.
15  *
16  *	For further information, read the top-level Openvision
17  *	copyright which is contained in the top-level MIT Kerberos
18  *	copyright.
19  *
20  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
21  *
22  */
23 
24 
25 /*
26  * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved.
27  */
28 
29 
30 #include <stdio.h>
31 #ifdef HAVE_MEMORY_H
32 #include <memory.h>
33 #endif
34 #include <time.h>
35 #include <locale.h>
36 
37 #include <kadm5/admin.h>
38 #include "admin_internal.h"
39 
40 #include <krb5.h>
41 #include <strings.h>
42 
43 #define string_text error_message
44 
45 const char *chpw_error_message(kadm5_ret_t code);
46 
47 /*
48  * Function: kadm5_chpass_principal_util
49  *
50  * Purpose: Wrapper around chpass_principal. We can read new pw, change pw and return useful messages
51  *
52  * Arguments:
53  *
54  *      princ          (input) a krb5b_principal structure for the
55  *                     principal whose password we should change.
56  *
57  *      new_password   (input) NULL or a null terminated string with the
58  *                     the principal's desired new password.  If new_password
59  *                     is NULL then this routine will read a new password.
60  *
61  *	pw_ret		(output) if non-NULL, points to a static buffer
62  *			containing the new password (if password is prompted
63  *			internally), or to the new_password argument (if
64  *			that is non-NULL).  If the former, then the buffer
65  *			is only valid until the next call to the function,
66  *			and the caller should be sure to zero it when
67  *			it is no longer needed.
68  *
69  *      msg_ret         (output) a useful message is copied here.
70  *
71  *      <return value>  exit status of 0 for success, else the com err code
72  *                      for the last significant routine called.
73  *
74  * Requires:
75  *
76  *      A msg_ret should point to a buffer large enough for the messasge.
77  *
78  * Effects:
79  *
80  * Modifies:
81  *
82  *
83  */
84 
_kadm5_chpass_principal_util(void * server_handle,void * lhandle,krb5_principal princ,char * new_pw,char ** ret_pw,char * msg_ret,unsigned int msg_len)85 kadm5_ret_t _kadm5_chpass_principal_util(void *server_handle,
86 					 void *lhandle,
87 					 krb5_principal princ,
88 					 char *new_pw,
89 					 char **ret_pw,
90 					 char *msg_ret,
91 					 unsigned int msg_len)
92 {
93   int code, code2;
94   unsigned int pwsize;
95   static char buffer[255];
96   char *new_password;
97   kadm5_principal_ent_rec princ_ent;
98   kadm5_policy_ent_rec policy_ent;
99   krb5_chgpwd_prot passwd_protocol;
100 
101   _KADM5_CHECK_HANDLE(server_handle);
102 
103   if (ret_pw)
104     *ret_pw = NULL;
105 
106   if (new_pw != NULL) {
107     new_password = new_pw;
108   } else { /* read the password */
109     krb5_context context;
110 
111     if ((code = (int) kadm5_init_krb5_context(&context)) == 0) {
112       pwsize = sizeof(buffer);
113       code = krb5_read_password(context, KADM5_PW_FIRST_PROMPT,
114 				KADM5_PW_SECOND_PROMPT,
115 				buffer, &pwsize);
116       krb5_free_context(context);
117     }
118 
119     if (code == 0)
120       new_password = buffer;
121     else {
122 #ifdef ZEROPASSWD
123       memset(buffer, 0, sizeof(buffer));
124 #endif
125       if (code == KRB5_LIBOS_BADPWDMATCH) {
126 	(void) strncpy(msg_ret, string_text(CHPASS_UTIL_NEW_PASSWORD_MISMATCH),
127 		msg_len - 1);
128 	msg_ret[msg_len - 1] = '\0';
129 	return(code);
130       } else {
131         (void) strncpy(msg_ret, error_message(code), msg_len - 1);
132         (void) strncat(msg_ret, " ", msg_len - 1);
133         (void) strncat(msg_ret, string_text(CHPASS_UTIL_WHILE_READING_PASSWORD),
134 		msg_len - 1);
135         (void) strncat(msg_ret, string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED),
136 		msg_len - 1);
137 	msg_ret[msg_len - 1] = '\0';
138 	return(code);
139       }
140     }
141     if (pwsize == 0) {
142 #ifdef ZEROPASSWD
143       memset(buffer, 0, sizeof(buffer));
144 #endif
145       strncpy(msg_ret, string_text(CHPASS_UTIL_NO_PASSWORD_READ), msg_len - 1);
146       msg_ret[msg_len - 1] = '\0';
147       return(KRB5_LIBOS_CANTREADPWD); /* could do better */
148     }
149   }
150 
151   if (ret_pw)
152     *ret_pw = new_password;
153 
154 	passwd_protocol = _kadm5_get_kpasswd_protocol(server_handle);
155 	if (passwd_protocol == KRB5_CHGPWD_CHANGEPW_V2) {
156 		kadm5_ret_t srvr_rsp_code;
157 		krb5_data   srvr_msg;
158 
159 		srvr_msg.length = 0;
160 		srvr_msg.data = NULL;
161 
162 		code = kadm5_chpass_principal_v2(server_handle, princ,
163 						new_password,
164 						&srvr_rsp_code,
165 						&srvr_msg);
166 		if (srvr_rsp_code) {
167 			sprintf(msg_ret, "%s%s%.*s\n",
168 				chpw_error_message(srvr_rsp_code),
169 				srvr_msg.length? ": " : "",
170 				srvr_msg.length,
171 				srvr_msg.data ? srvr_msg.data : "");
172 
173 			return (srvr_rsp_code);
174 		}
175 		return (code);
176 
177 	} else if (passwd_protocol == KRB5_CHGPWD_RPCSEC) {
178 		code = kadm5_chpass_principal(server_handle, princ,
179 					    new_password);
180 
181 #ifdef ZEROPASSWD
182   if (!ret_pw)
183     memset(buffer, 0, sizeof(buffer)); /* in case we read a new password */
184 #endif
185 
186   if (code == KADM5_OK) {
187     strncpy(msg_ret, string_text(CHPASS_UTIL_PASSWORD_CHANGED), msg_len - 1);
188     msg_ret[msg_len - 1] = '\0';
189     return(0);
190   }
191 
192   if ((code != KADM5_PASS_Q_TOOSHORT) &&
193       (code != KADM5_PASS_REUSE) &&(code != KADM5_PASS_Q_CLASS) &&
194       (code != KADM5_PASS_Q_DICT) && (code != KADM5_PASS_TOOSOON)) {
195     /* Can't get more info for other errors */
196     sprintf(buffer, "%s %s", error_message(code),
197 	    string_text(CHPASS_UTIL_WHILE_TRYING_TO_CHANGE));
198     sprintf(msg_ret, "%s\n%s\n", string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED),
199 	    buffer);
200     return(code);
201   }
202 
203   /* Ok, we have a password quality error. Return a good message */
204 
205   if (code == KADM5_PASS_REUSE) {
206     strncpy(msg_ret, string_text(CHPASS_UTIL_PASSWORD_REUSE), msg_len - 1);
207     msg_ret[msg_len - 1] = '\0';
208     return(code);
209   }
210 
211   if (code == KADM5_PASS_Q_DICT) {
212     strncpy(msg_ret, string_text(CHPASS_UTIL_PASSWORD_IN_DICTIONARY),
213 	    msg_len - 1);
214     msg_ret[msg_len - 1] = '\0';
215     return(code);
216   }
217 
218   /* Look up policy for the remaining messages */
219 
220   code2 = kadm5_get_principal (lhandle, princ, &princ_ent,
221 			       KADM5_PRINCIPAL_NORMAL_MASK);
222   if (code2 != 0) {
223     strncpy(msg_ret, error_message(code2), msg_len - 1);
224     strncat(msg_ret, " ", msg_len - 1 - strlen(msg_ret));
225     strncat(msg_ret, string_text(CHPASS_UTIL_GET_PRINC_INFO), msg_len - 1 - strlen(msg_ret));
226     strncat(msg_ret, "\n", msg_len - 1 - strlen(msg_ret));
227     strncat(msg_ret, error_message(code), msg_len - 1 - strlen(msg_ret));
228     strncat(msg_ret, " ", msg_len - 1 - strlen(msg_ret));
229     strncat(msg_ret, string_text(CHPASS_UTIL_WHILE_TRYING_TO_CHANGE),
230 	    msg_len - 1 - strlen(msg_ret));
231     strncat(msg_ret, "\n\n", msg_len - 1 - strlen(msg_ret));
232     strncat(msg_ret, string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED),
233 	    msg_len - 1 - strlen(msg_ret));
234     strncat(msg_ret, "\n", msg_len - 1 - strlen(msg_ret));
235     msg_ret[msg_len - 1] = '\0';
236     return(code);
237   }
238 
239   if ((princ_ent.aux_attributes & KADM5_POLICY) == 0) {
240     strncpy(msg_ret, error_message(code), msg_len - 1 - strlen(msg_ret));
241     strncat(msg_ret, " ", msg_len - 1 - strlen(msg_ret));
242     strncpy(msg_ret, string_text(CHPASS_UTIL_NO_POLICY_YET_Q_ERROR),
243 	    msg_len - 1 - strlen(msg_ret));
244     strncat(msg_ret, "\n\n", msg_len - 1 - strlen(msg_ret));
245     strncpy(msg_ret, string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED),
246 	    msg_len - 1 - strlen(msg_ret));
247     msg_ret[msg_len - 1] = '\0';
248 
249     (void) kadm5_free_principal_ent(lhandle, &princ_ent);
250     return(code);
251   }
252 
253   code2 = kadm5_get_policy(lhandle, princ_ent.policy,
254 			   &policy_ent);
255   if (code2 != 0) {
256     sprintf(msg_ret, "%s %s\n%s %s\n\n%s\n ", error_message(code2),
257 	    string_text(CHPASS_UTIL_GET_POLICY_INFO),
258 	    error_message(code),
259 	    string_text(CHPASS_UTIL_WHILE_TRYING_TO_CHANGE),
260 	    string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED));
261     (void) kadm5_free_principal_ent(lhandle, &princ_ent);
262     return(code);
263   }
264 
265   if (code == KADM5_PASS_Q_TOOSHORT) {
266     sprintf(msg_ret, string_text(CHPASS_UTIL_PASSWORD_TOO_SHORT),
267 	    policy_ent.pw_min_length);
268     (void) kadm5_free_principal_ent(lhandle, &princ_ent);
269     (void) kadm5_free_policy_ent(lhandle, &policy_ent);
270     return(code);
271   }
272 
273 
274   if (code == KADM5_PASS_Q_CLASS) {
275     sprintf(msg_ret, string_text(CHPASS_UTIL_TOO_FEW_CLASSES),
276 	    policy_ent.pw_min_classes);
277     (void) kadm5_free_principal_ent(lhandle, &princ_ent);
278     (void) kadm5_free_policy_ent(lhandle, &policy_ent);
279     return(code);
280   }
281 
282   if (code == KADM5_PASS_TOOSOON) {
283     time_t until;
284     char *time_string, *ptr;
285 
286     until = princ_ent.last_pwd_change + policy_ent.pw_min_life;
287 
288     time_string = ctime(&until);
289     if (*(ptr = &time_string[strlen(time_string)-1]) == '\n')
290       *ptr = '\0';
291 
292     sprintf(msg_ret, string_text(CHPASS_UTIL_PASSWORD_TOO_SOON),
293 	    time_string);
294     (void) kadm5_free_principal_ent(lhandle, &princ_ent);
295     (void) kadm5_free_policy_ent(lhandle, &policy_ent);
296     return(code);
297   } else {
298 
299   /* We should never get here, but just in case ... */
300   sprintf(buffer, "%s %s", error_message(code),
301 	  string_text(CHPASS_UTIL_WHILE_TRYING_TO_CHANGE));
302   sprintf(msg_ret, "%s\n%s\n", string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED),
303 	  buffer);
304   (void) kadm5_free_principal_ent(lhandle, &princ_ent);
305   (void) kadm5_free_policy_ent(lhandle, &policy_ent);
306   return(code);
307 		}
308 	} else {
309 		sprintf(msg_ret, "%s\n%s\n",
310 			string_text(CHPASS_UTIL_PASSWORD_NOT_CHANGED),
311 			"Password protocol in krb5.conf is not supported\n");
312 		return (-1);
313 	}
314 }
315 
316 /*
317  * krb5_chpw_result_code_string
318  *
319  * convert the return code received from the password server
320  * to a human-readable string.
321  */
322 const char *
chpw_error_message(kadm5_ret_t result_code)323 chpw_error_message(kadm5_ret_t result_code)
324 {
325 	switch (result_code) {
326 	case KRB5_KPASSWD_MALFORMED:
327 		return (dgettext(TEXT_DOMAIN, "Malformed request error"));
328 	case KRB5_KPASSWD_HARDERROR:
329 		return (dgettext(TEXT_DOMAIN, "Server error"));
330 	case KRB5_KPASSWD_AUTHERROR:
331 		return (dgettext(TEXT_DOMAIN, "Authentication error"));
332 	case KRB5_KPASSWD_SOFTERROR:
333 		return (dgettext(TEXT_DOMAIN, "Password change rejected"));
334 	case KRB5_KPASSWD_ACCESSDENIED:
335 		return (dgettext(TEXT_DOMAIN,
336 				"Not authorized to change password"));
337 	case KRB5_KPASSWD_BAD_VERSION:
338 		return (dgettext(TEXT_DOMAIN, "Protocol version unsupported"));
339 	case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
340 		return (dgettext(TEXT_DOMAIN,
341 				"initial flag required in changepw request"));
342 	case KRB5_KPASSWD_POLICY_REJECT:
343 		return (dgettext(TEXT_DOMAIN, "new password fails policy"));
344 	case KRB5_KPASSWD_BAD_PRINCIPAL:
345 		return (dgettext(TEXT_DOMAIN,
346 		    "target principal does not exist for "
347 		    "changepw request"));
348 	case KRB5_KPASSWD_ETYPE_NOSUPP:
349 		return (dgettext(TEXT_DOMAIN,
350 		    "changepw request key sequence has an "
351 		    "unsupported Etype"));
352 	default:
353 		return (dgettext(TEXT_DOMAIN, "Password change failed"));
354 	}
355 }
356