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-1994 OpenVision Technologies, Inc., All Rights Reserved.
27  *
28  * $Header$
29  *
30  *
31  */
32 
33 static char rcsid[] = "$Id: kpasswd.c 17258 2005-06-21 01:36:03Z raeburn $";
34 
35 #include <kadm5/admin.h>
36 #include <krb5.h>
37 
38 #include "kpasswd_strings.h"
39 #define string_text error_message
40 
41 #include "kpasswd.h"
42 
43 #include <stdio.h>
44 #include <pwd.h>
45 #include <string.h>
46 #include <libintl.h>
47 
48 extern char *whoami;
49 
50 extern void display_intro_message();
51 extern long read_old_password();
52 extern long read_new_password();
53 
54 #define MISC_EXIT_STATUS 6
55 
56 /*
57  * Function: kpasswd
58  *
59  * Purpose: Initialize and call lower level routines to change a password
60  *
61  * Arguments:
62  *
63  *	context		(r) krb5_context to use
64  *	argc/argv	(r) principal name to use, optional
65  *	read_old_password (f) function to read old password
66  *	read_new_password (f) function to read new and change password
67  *	display_intro_message (f) function to display intro message
68  *	whoami		(extern) argv[0]
69  *
70  * Returns:
71  *                      exit status of 0 for success
72  *			1 principal unknown
73  *			2 old password wrong
74  *			3 cannot initialize admin server session
75  *			4 new passwd mismatch or error trying to change pw
76  *                      5 password not typed
77  *                      6 misc error
78  *                      7 incorrect usage
79  *
80  * Requires:
81  *	Passwords cannot be more than 255 characters long.
82  *
83  * Effects:
84  *
85  * If argc is 2, the password for the principal specified in argv[1]
86  * is changed; otherwise, the principal of the default credential
87  * cache or username is used.  display_intro_message is called with
88  * the arguments KPW_STR_CHANGING_PW_FOR and the principal name.
89  * read_old_password is then called to prompt for the old password.
90  * The admin system is then initialized, the principal's policy
91  * retrieved and explained, if appropriate, and finally
92  * read_new_password is called to read the new password and change the
93  * principal's password (presumably ovsec_kadm_chpass_principal).
94  * admin system is de-initialized before the function returns.
95  *
96  * Modifies:
97  *
98  * Changes the principal's password.
99  *
100  */
101 int
kpasswd(context,argc,argv)102 kpasswd(context, argc, argv)
103    krb5_context context;
104    int argc;
105    char *argv[];
106 {
107   kadm5_ret_t code;
108   krb5_ccache ccache = NULL;
109   krb5_principal princ = 0;
110   char *princ_str;
111   struct passwd *pw = 0;
112   unsigned int pwsize;
113   char password[255];  /* I don't really like 255 but that's what kinit uses */
114   char msg_ret[1024], admin_realm[1024];
115   kadm5_principal_ent_rec principal_entry;
116   kadm5_policy_ent_rec policy_entry;
117   void *server_handle;
118   kadm5_config_params params;
119   char *cpw_service;
120 
121 	memset((char *)&params, 0, sizeof (params));
122 	memset(&principal_entry, 0, sizeof (principal_entry));
123 	memset(&policy_entry, 0, sizeof (policy_entry));
124 
125   if (argc > 2) {
126       com_err(whoami, KPW_STR_USAGE, 0);
127       return(7);
128       /*NOTREACHED*/
129     }
130 
131   /************************************
132    *  Get principal name to change    *
133    ************************************/
134 
135   /* Look on the command line first, followed by the default credential
136      cache, followed by defaulting to the Unix user name */
137 
138   if (argc == 2)
139     princ_str = strdup(argv[1]);
140   else {
141     code = krb5_cc_default(context, &ccache);
142     /* If we succeed, find who is in the credential cache */
143     if (code == 0) {
144       /* Get default principal from cache if one exists */
145       code = krb5_cc_get_principal(context, ccache, &princ);
146       /* if we got a principal, unparse it, otherwise get out of the if
147 	 with an error code */
148       (void) krb5_cc_close(context, ccache);
149       if (code == 0) {
150 	code = krb5_unparse_name(context, princ, &princ_str);
151 	if (code != 0) {
152 	  com_err(whoami,  code, string_text(KPW_STR_UNPARSE_NAME));
153 	  return(MISC_EXIT_STATUS);
154 	}
155       }
156     }
157 
158     /* this is a crock.. we want to compare against */
159     /* "KRB5_CC_DOESNOTEXIST" but there is no such error code, and */
160     /* both the file and stdio types return FCC_NOFILE.  If there is */
161     /* ever another ccache type (or if the error codes are ever */
162     /* fixed), this code will have to be updated. */
163     if (code && code != KRB5_FCC_NOFILE) {
164       com_err(whoami, code, string_text(KPW_STR_WHILE_LOOKING_AT_CC));
165       return(MISC_EXIT_STATUS);
166     }
167 
168     /* if either krb5_cc failed check the passwd file */
169     if (code != 0) {
170       pw = getpwuid( getuid());
171       if (pw == NULL) {
172 	com_err(whoami, 0, string_text(KPW_STR_NOT_IN_PASSWD_FILE));
173 	return(MISC_EXIT_STATUS);
174       }
175       princ_str = strdup(pw->pw_name);
176     }
177   }
178 
179   display_intro_message(string_text(KPW_STR_CHANGING_PW_FOR), princ_str);
180 
181   /* Need to get a krb5_principal, unless we started from with one from
182      the credential cache */
183 
184   if (! princ) {
185       code = krb5_parse_name (context, princ_str, &princ);
186       if (code != 0) {
187 	  com_err(whoami, code, string_text(KPW_STR_PARSE_NAME), princ_str);
188 	  free(princ_str);
189 	  return(MISC_EXIT_STATUS);
190       }
191   }
192 
193   pwsize = sizeof(password);
194   code = read_old_password(context, password, &pwsize);
195 
196   if (code != 0) {
197     memset(password, 0, sizeof(password));
198     com_err(whoami, code, string_text(KPW_STR_WHILE_READING_PASSWORD));
199     krb5_free_principal(context, princ);
200     free(princ_str);
201     return(MISC_EXIT_STATUS);
202   }
203   if (pwsize == 0) {
204     memset(password, 0, sizeof(password));
205     com_err(whoami, 0, string_text(KPW_STR_NO_PASSWORD_READ));
206     krb5_free_principal(context, princ);
207     free(princ_str);
208     return(5);
209   }
210 
211 	snprintf(admin_realm, sizeof (admin_realm),
212 		krb5_princ_realm(context, princ)->data);
213 	params.mask |= KADM5_CONFIG_REALM;
214 	params.realm = admin_realm;
215 
216 
217 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
218 		fprintf(stderr, gettext("%s: unable to get host based "
219 					"service name for realm %s\n"),
220 			whoami, admin_realm);
221 		exit(1);
222 	}
223 
224 	code = kadm5_init_with_password(princ_str, password, cpw_service,
225 					&params, KADM5_STRUCT_VERSION,
226 					KADM5_API_VERSION_2, NULL,
227 					&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