1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
10  *
11  *	Openvision retains the copyright to derivative works of
12  *	this source code.  Do *NOT* create a derivative of this
13  *	source code before consulting with your legal department.
14  *	Do *NOT* integrate *ANY* of this source code into another
15  *	product before consulting with your legal department.
16  *
17  *	For further information, read the top-level Openvision
18  *	copyright which is contained in the top-level MIT Kerberos
19  *	copyright.
20  *
21  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
22  *
23  */
24 
25 
26 /*
27  * Copyright 1993-1994 OpenVision Technologies, Inc., All Rights Reserved.
28  *
29  * $Header: /cvs/krbdev/krb5/src/kadmin/passwd/kpasswd.c,v 1.25 2001/02/26 18:22:08 epeisach Exp $
30  *
31  *
32  */
33 
34 static char rcsid[] = "$Id: kpasswd.c,v 1.25 2001/02/26 18:22:08 epeisach Exp $";
35 
36 #include <kadm5/admin.h>
37 #include <krb5.h>
38 
39 #include "kpasswd_strings.h"
40 #define string_text error_message
41 
42 #include "kpasswd.h"
43 
44 #include <stdio.h>
45 #include <pwd.h>
46 #include <string.h>
47 #include <libintl.h>
48 
49 extern char *whoami;
50 
51 extern void display_intro_message();
52 extern long read_old_password();
53 extern long read_new_password();
54 
55 #define MISC_EXIT_STATUS 6
56 
57 /*
58  * Function: kpasswd
59  *
60  * Purpose: Initialize and call lower level routines to change a password
61  *
62  * Arguments:
63  *
64  *	context		(r) krb5_context to use
65  *	argc/argv	(r) principal name to use, optional
66  *	read_old_password (f) function to read old password
67  *	read_new_password (f) function to read new and change password
68  *	display_intro_message (f) function to display intro message
69  *	whoami		(extern) argv[0]
70  *
71  * Returns:
72  *                      exit status of 0 for success
73  *			1 principal unknown
74  *			2 old password wrong
75  *			3 cannot initialize admin server session
76  *			4 new passwd mismatch or error trying to change pw
77  *                      5 password not typed
78  *                      6 misc error
79  *                      7 incorrect usage
80  *
81  * Requires:
82  *	Passwords cannot be more than 255 characters long.
83  *
84  * Effects:
85  *
86  * If argc is 2, the password for the principal specified in argv[1]
87  * is changed; otherwise, the principal of the default credential
88  * cache or username is used.  display_intro_message is called with
89  * the arguments KPW_STR_CHANGING_PW_FOR and the principal name.
90  * read_old_password is then called to prompt for the old password.
91  * The admin system is then initialized, the principal's policy
92  * retrieved and explained, if appropriate, and finally
93  * read_new_password is called to read the new password and change the
94  * principal's password (presumably ovsec_kadm_chpass_principal).
95  * admin system is de-initialized before the function returns.
96  *
97  * Modifies:
98  *
99  * Changes the principal's password.
100  *
101  */
102 int
103 kpasswd(context, argc, argv)
104    krb5_context context;
105    int argc;
106    char *argv[];
107 {
108   kadm5_ret_t code;
109   krb5_ccache ccache = NULL;
110   krb5_principal princ = 0;
111   char *princ_str;
112   struct passwd *pw = 0;
113   unsigned int pwsize;
114   char password[255];  /* I don't really like 255 but that's what kinit uses */
115   char msg_ret[1024], admin_realm[1024];
116   kadm5_principal_ent_rec principal_entry;
117   kadm5_policy_ent_rec policy_entry;
118   void *server_handle;
119   kadm5_config_params params;
120   char *cpw_service;
121 
122 	memset((char *)&params, 0, sizeof (params));
123 	memset(&principal_entry, 0, sizeof (principal_entry));
124 	memset(&policy_entry, 0, sizeof (policy_entry));
125 
126   if (argc > 2) {
127       com_err(whoami, KPW_STR_USAGE, 0);
128       return(7);
129       /*NOTREACHED*/
130     }
131 
132   /************************************
133    *  Get principal name to change    *
134    ************************************/
135 
136   /* Look on the command line first, followed by the default credential
137      cache, followed by defaulting to the Unix user name */
138 
139   if (argc == 2)
140     princ_str = strdup(argv[1]);
141   else {
142     code = krb5_cc_default(context, &ccache);
143     /* If we succeed, find who is in the credential cache */
144     if (code == 0) {
145       /* Get default principal from cache if one exists */
146       code = krb5_cc_get_principal(context, ccache, &princ);
147       /* if we got a principal, unparse it, otherwise get out of the if
148 	 with an error code */
149       (void) krb5_cc_close(context, ccache);
150       if (code == 0) {
151 	code = krb5_unparse_name(context, princ, &princ_str);
152 	if (code != 0) {
153 	  com_err(whoami,  code, string_text(KPW_STR_UNPARSE_NAME));
154 	  return(MISC_EXIT_STATUS);
155 	}
156       }
157     }
158 
159     /* this is a crock.. we want to compare against */
160     /* "KRB5_CC_DOESNOTEXIST" but there is no such error code, and */
161     /* both the file and stdio types return FCC_NOFILE.  If there is */
162     /* ever another ccache type (or if the error codes are ever */
163     /* fixed), this code will have to be updated. */
164     if (code && code != KRB5_FCC_NOFILE) {
165       com_err(whoami, code, string_text(KPW_STR_WHILE_LOOKING_AT_CC));
166       return(MISC_EXIT_STATUS);
167     }
168 
169     /* if either krb5_cc failed check the passwd file */
170     if (code != 0) {
171       pw = getpwuid( getuid());
172       if (pw == NULL) {
173 	com_err(whoami, 0, string_text(KPW_STR_NOT_IN_PASSWD_FILE));
174 	return(MISC_EXIT_STATUS);
175       }
176       princ_str = strdup(pw->pw_name);
177     }
178   }
179 
180   display_intro_message(string_text(KPW_STR_CHANGING_PW_FOR), princ_str);
181 
182   /* Need to get a krb5_principal, unless we started from with one from
183      the credential cache */
184 
185   if (! princ) {
186       code = krb5_parse_name (context, princ_str, &princ);
187       if (code != 0) {
188 	  com_err(whoami, code, string_text(KPW_STR_PARSE_NAME), princ_str);
189 	  free(princ_str);
190 	  return(MISC_EXIT_STATUS);
191       }
192   }
193 
194   pwsize = sizeof(password);
195   code = read_old_password(context, password, &pwsize);
196 
197   if (code != 0) {
198     memset(password, 0, sizeof(password));
199     com_err(whoami, code, string_text(KPW_STR_WHILE_READING_PASSWORD));
200     krb5_free_principal(context, princ);
201     free(princ_str);
202     return(MISC_EXIT_STATUS);
203   }
204   if (pwsize == 0) {
205     memset(password, 0, sizeof(password));
206     com_err(whoami, 0, string_text(KPW_STR_NO_PASSWORD_READ));
207     krb5_free_principal(context, princ);
208     free(princ_str);
209     return(5);
210   }
211 
212 	snprintf(admin_realm, sizeof (admin_realm),
213 		krb5_princ_realm(context, princ)->data);
214 	params.mask |= KADM5_CONFIG_REALM;
215 	params.realm = admin_realm;
216 
217 
218 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
219 		fprintf(stderr, gettext("%s: unable to get host based "
220 					"service name for realm %s\n"),
221 			whoami, admin_realm);
222 		exit(1);
223 	}
224 
225 	code = kadm5_init_with_password(princ_str, password, cpw_service,
226 					&params, KADM5_STRUCT_VERSION,
227 					KADM5_API_VERSION_2, &server_handle);
228 	free(cpw_service);
229 	if (code != 0) {
230 		if (code == KADM5_BAD_PASSWORD)
231 			com_err(whoami, 0,
232 				string_text(KPW_STR_OLD_PASSWORD_INCORRECT));
233 		else
234 			com_err(whoami, 0,
235 				string_text(KPW_STR_CANT_OPEN_ADMIN_SERVER),
236 				admin_realm,
237 				error_message(code));
238 		krb5_free_principal(context, princ);
239 		free(princ_str);
240 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
241 	}
242 
243 	/*
244 	 * we can only check the policy if the server speaks
245 	 * RPCSEC_GSS
246 	 */
247 	if (_kadm5_get_kpasswd_protocol(server_handle) == KRB5_CHGPWD_RPCSEC) {
248 		/* Explain policy restrictions on new password if any. */
249 		/*
250 		 * Note: copy of this exists in login
251 		 * (kverify.c/get_verified_in_tkt).
252 		 */
253 
254 		code = kadm5_get_principal(server_handle, princ,
255 					&principal_entry,
256 					KADM5_PRINCIPAL_NORMAL_MASK);
257 		if (code != 0) {
258 			com_err(whoami, 0,
259 				string_text((code == KADM5_UNK_PRINC)
260 					    ? KPW_STR_PRIN_UNKNOWN :
261 					    KPW_STR_CANT_GET_POLICY_INFO),
262 				princ_str);
263 			krb5_free_principal(context, princ);
264 			free(princ_str);
265 			(void) kadm5_destroy(server_handle);
266 			return ((code == KADM5_UNK_PRINC) ? 1 :
267 				MISC_EXIT_STATUS);
268 		}
269 		if ((principal_entry.aux_attributes & KADM5_POLICY) != 0) {
270 			code = kadm5_get_policy(server_handle,
271 						principal_entry.policy,
272 						&policy_entry);
273 			if (code != 0) {
274 				/*
275 				 * doesn't matter which error comes back,
276 				 * there's no nice recovery or need to
277 				 * differentiate to the user
278 				 */
279 				com_err(whoami, 0,
280 				string_text(KPW_STR_CANT_GET_POLICY_INFO),
281 				princ_str);
282 				(void) kadm5_free_principal_ent(server_handle,
283 							&principal_entry);
284 				krb5_free_principal(context, princ);
285 				free(princ_str);
286 				free(princ_str);
287 				(void) kadm5_destroy(server_handle);
288 				return (MISC_EXIT_STATUS);
289 			}
290 			com_err(whoami, 0,
291 				string_text(KPW_STR_POLICY_EXPLANATION),
292 				princ_str, principal_entry.policy,
293 				policy_entry.pw_min_length,
294 				policy_entry.pw_min_classes);
295 			if (code = kadm5_free_principal_ent(server_handle,
296 						    &principal_entry)) {
297 				(void) kadm5_free_policy_ent(server_handle,
298 							    &policy_entry);
299 				krb5_free_principal(context, princ);
300 				free(princ_str);
301 				com_err(whoami, code,
302 				string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
303 				(void) kadm5_destroy(server_handle);
304 				return (MISC_EXIT_STATUS);
305 			}
306 			if (code = kadm5_free_policy_ent(server_handle,
307 							&policy_entry)) {
308 				krb5_free_principal(context, princ);
309 				free(princ_str);
310 				com_err(whoami, code,
311 				string_text(KPW_STR_WHILE_FREEING_POLICY));
312 				(void) kadm5_destroy(server_handle);
313 				return (MISC_EXIT_STATUS);
314 			}
315 		} else {
316 			/*
317 			 * kpasswd *COULD* output something here to
318 			 * encourage the choice of good passwords,
319 			 * in the absence of an enforced policy.
320 			 */
321 			if (code = kadm5_free_principal_ent(server_handle,
322 						    &principal_entry)) {
323 				krb5_free_principal(context, princ);
324 				free(princ_str);
325 				com_err(whoami, code,
326 				string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
327 				(void) kadm5_destroy(server_handle);
328 				return (MISC_EXIT_STATUS);
329 			}
330 		}
331 	} /* if protocol == KRB5_CHGPWD_RPCSEC */
332 
333   pwsize = sizeof(password);
334   code = read_new_password(server_handle, password, &pwsize, msg_ret, sizeof (msg_ret), princ);
335   memset(password, 0, sizeof(password));
336 
337   if (code)
338     com_err(whoami, 0, msg_ret);
339 
340   krb5_free_principal(context, princ);
341   free(princ_str);
342 
343   (void) kadm5_destroy(server_handle);
344 
345   if (code == KRB5_LIBOS_CANTREADPWD)
346      return(5);
347   else if (code)
348      return(4);
349   else
350      return(0);
351 }
352