1 /*
2  * Copyright 2007 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, NULL,
228 					&server_handle);
229 	free(cpw_service);
230 	if (code != 0) {
231 		if (code == KADM5_BAD_PASSWORD)
232 			com_err(whoami, 0,
233 				string_text(KPW_STR_OLD_PASSWORD_INCORRECT));
234 		else
235 			com_err(whoami, 0,
236 				string_text(KPW_STR_CANT_OPEN_ADMIN_SERVER),
237 				admin_realm,
238 				error_message(code));
239 		krb5_free_principal(context, princ);
240 		free(princ_str);
241 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
242 	}
243 
244 	/*
245 	 * we can only check the policy if the server speaks
246 	 * RPCSEC_GSS
247 	 */
248 	if (_kadm5_get_kpasswd_protocol(server_handle) == KRB5_CHGPWD_RPCSEC) {
249 		/* Explain policy restrictions on new password if any. */
250 		/*
251 		 * Note: copy of this exists in login
252 		 * (kverify.c/get_verified_in_tkt).
253 		 */
254 
255 		code = kadm5_get_principal(server_handle, princ,
256 					&principal_entry,
257 					KADM5_PRINCIPAL_NORMAL_MASK);
258 		if (code != 0) {
259 			com_err(whoami, 0,
260 				string_text((code == KADM5_UNK_PRINC)
261 					    ? KPW_STR_PRIN_UNKNOWN :
262 					    KPW_STR_CANT_GET_POLICY_INFO),
263 				princ_str);
264 			krb5_free_principal(context, princ);
265 			free(princ_str);
266 			(void) kadm5_destroy(server_handle);
267 			return ((code == KADM5_UNK_PRINC) ? 1 :
268 				MISC_EXIT_STATUS);
269 		}
270 		if ((principal_entry.aux_attributes & KADM5_POLICY) != 0) {
271 			code = kadm5_get_policy(server_handle,
272 						principal_entry.policy,
273 						&policy_entry);
274 			if (code != 0) {
275 				/*
276 				 * doesn't matter which error comes back,
277 				 * there's no nice recovery or need to
278 				 * differentiate to the user
279 				 */
280 				com_err(whoami, 0,
281 				string_text(KPW_STR_CANT_GET_POLICY_INFO),
282 				princ_str);
283 				(void) kadm5_free_principal_ent(server_handle,
284 							&principal_entry);
285 				krb5_free_principal(context, princ);
286 				free(princ_str);
287 				free(princ_str);
288 				(void) kadm5_destroy(server_handle);
289 				return (MISC_EXIT_STATUS);
290 			}
291 			com_err(whoami, 0,
292 				string_text(KPW_STR_POLICY_EXPLANATION),
293 				princ_str, principal_entry.policy,
294 				policy_entry.pw_min_length,
295 				policy_entry.pw_min_classes);
296 			if (code = kadm5_free_principal_ent(server_handle,
297 						    &principal_entry)) {
298 				(void) kadm5_free_policy_ent(server_handle,
299 							    &policy_entry);
300 				krb5_free_principal(context, princ);
301 				free(princ_str);
302 				com_err(whoami, code,
303 				string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
304 				(void) kadm5_destroy(server_handle);
305 				return (MISC_EXIT_STATUS);
306 			}
307 			if (code = kadm5_free_policy_ent(server_handle,
308 							&policy_entry)) {
309 				krb5_free_principal(context, princ);
310 				free(princ_str);
311 				com_err(whoami, code,
312 				string_text(KPW_STR_WHILE_FREEING_POLICY));
313 				(void) kadm5_destroy(server_handle);
314 				return (MISC_EXIT_STATUS);
315 			}
316 		} else {
317 			/*
318 			 * kpasswd *COULD* output something here to
319 			 * encourage the choice of good passwords,
320 			 * in the absence of an enforced policy.
321 			 */
322 			if (code = kadm5_free_principal_ent(server_handle,
323 						    &principal_entry)) {
324 				krb5_free_principal(context, princ);
325 				free(princ_str);
326 				com_err(whoami, code,
327 				string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
328 				(void) kadm5_destroy(server_handle);
329 				return (MISC_EXIT_STATUS);
330 			}
331 		}
332 	} /* if protocol == KRB5_CHGPWD_RPCSEC */
333 
334   pwsize = sizeof(password);
335   code = read_new_password(server_handle, password, &pwsize, msg_ret, sizeof (msg_ret), princ);
336   memset(password, 0, sizeof(password));
337 
338   if (code)
339     com_err(whoami, 0, msg_ret);
340 
341   krb5_free_principal(context, princ);
342   free(princ_str);
343 
344   (void) kadm5_destroy(server_handle);
345 
346   if (code == KRB5_LIBOS_CANTREADPWD)
347      return(5);
348   else if (code)
349      return(4);
350   else
351      return(0);
352 }
353