xref: /illumos-gate/usr/src/cmd/krb5/kadmin/kpasswd/kpasswd.c (revision 355b4669e025ff377602b6fc7caaf30dbc218371)
1 /*
2  * Copyright 1998-2002 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.24 1997/02/20\
30  * 06:12:57 probe Exp $
31  *
32  *
33  */
34 
35 static char rcsid[] = "$Id: kpasswd.c,v 1.24 1997/02/20 "
36                       "06:12:57 probe Exp $";
37 
38 #include <kadm5/admin.h>
39 #include <krb5.h>
40 
41 #include "kpasswd_strings.h"
42 #define	string_text error_message
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 	int pwsize;
114 	char password[255];	/* I don't really like 255 */
115 				/* but that's what kinit uses */
116 	char msg_ret[1024], admin_realm[1024];
117 	kadm5_principal_ent_rec principal_entry;
118 	kadm5_policy_ent_rec policy_entry;
119 	void *server_handle;
120 	kadm5_config_params params;
121 	char *cpw_service;
122 
123 	memset((char *)&params, 0, sizeof (params));
124 	memset(&principal_entry, 0, sizeof (principal_entry));
125 	memset(&policy_entry, 0, sizeof (policy_entry));
126 
127 	if (argc > 2) {
128 		com_err(whoami, KPW_STR_USAGE, 0);
129 		return (7);
130 		/* NOTREACHED */
131 	}
132 	/*
133 	 *  Get principal name to change
134 	 */
135 
136 	/*
137 	 * Look on the command line first, followed by the default
138 	 * credential cache, followed by defaulting to the Unix user name
139 	 */
140 
141 	if (argc == 2)
142 		princ_str = strdup(argv[1]);
143 	else {
144 		code = krb5_cc_default(context, &ccache);
145 		/* If we succeed, find who is in the credential cache */
146 		if (code == 0) {
147 			/* Get default principal from cache if one exists */
148 			code = krb5_cc_get_principal(context, ccache, &princ);
149 			/*
150 			 * if we got a principal, unparse it, otherwise get
151 			 * out of the if with an error code
152 			 */
153 			(void) krb5_cc_close(context, ccache);
154 			if (code == 0) {
155 				code = krb5_unparse_name(context,
156 							princ, &princ_str);
157 				if (code != 0) {
158 					com_err(whoami, code,
159 						string_text(
160 							KPW_STR_UNPARSE_NAME));
161 					return (MISC_EXIT_STATUS);
162 				}
163 			}
164 		}
165 		/* this is a crock.. we want to compare against */
166 		/*
167 		 * "KRB5_CC_DOESNOTEXIST" but there is no such error code,
168 		 * and
169 		 */
170 		/*
171 		 * both the file and stdio types return FCC_NOFILE.  If
172 		 * there is
173 		 */
174 		/* ever another ccache type (or if the error codes are ever */
175 		/* fixed), this code will have to be updated. */
176 		if (code && code != KRB5_FCC_NOFILE) {
177 			com_err(whoami, code,
178 				string_text(KPW_STR_WHILE_LOOKING_AT_CC));
179 			return (MISC_EXIT_STATUS);
180 		}
181 		/* if either krb5_cc failed check the passwd file */
182 		if (code != 0) {
183 			pw = getpwuid(getuid());
184 			if (pw == NULL) {
185 				com_err(whoami, 0,
186 				    string_text(KPW_STR_NOT_IN_PASSWD_FILE));
187 				return (MISC_EXIT_STATUS);
188 			}
189 			princ_str = strdup(pw->pw_name);
190 		}
191 	}
192 
193 	display_intro_message(string_text(KPW_STR_CHANGING_PW_FOR), princ_str);
194 
195 	/*
196 	 * Need to get a krb5_principal, unless we started from with one
197 	 * from the credential cache
198 	 */
199 
200 	if (!princ) {
201 		code = krb5_parse_name(context, princ_str, &princ);
202 		if (code != 0) {
203 			com_err(whoami, code,
204 				string_text(KPW_STR_PARSE_NAME), princ_str);
205 			free(princ_str);
206 			return (MISC_EXIT_STATUS);
207 		}
208 	}
209 	pwsize = sizeof (password);
210 	code = read_old_password(context, password, &pwsize);
211 
212 	if (code != 0) {
213 		memset(password, 0, sizeof (password));
214 		com_err(whoami, code,
215 			string_text(KPW_STR_WHILE_READING_PASSWORD));
216 		krb5_free_principal(context, princ);
217 		free(princ_str);
218 		return (MISC_EXIT_STATUS);
219 	}
220 	if (pwsize == 0) {
221 		memset(password, 0, sizeof (password));
222 		com_err(whoami, 0, string_text(KPW_STR_NO_PASSWORD_READ));
223 		krb5_free_principal(context, princ);
224 		free(princ_str);
225 		return (5);
226 	}
227 
228 	snprintf(admin_realm, sizeof (admin_realm),
229 		krb5_princ_realm(context, princ)->data);
230 	params.mask |= KADM5_CONFIG_REALM;
231 	params.realm = admin_realm;
232 
233 
234 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
235 		fprintf(stderr, gettext("%s: unable to get host based "
236 					"service name for realm %s\n"),
237 			whoami, admin_realm);
238 		exit(1);
239 	}
240 
241 	code = kadm5_init_with_password(princ_str, password, cpw_service,
242 					&params, KADM5_STRUCT_VERSION,
243 					KADM5_API_VERSION_2, &server_handle);
244 	free(cpw_service);
245 	if (code != 0) {
246 		if (code == KADM5_BAD_PASSWORD)
247 			com_err(whoami, 0,
248 				string_text(KPW_STR_OLD_PASSWORD_INCORRECT));
249 		else
250 			com_err(whoami, 0,
251 				string_text(KPW_STR_CANT_OPEN_ADMIN_SERVER),
252 				admin_realm,
253 				error_message(code));
254 		krb5_free_principal(context, princ);
255 		free(princ_str);
256 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
257 	}
258 
259 	/*
260 	 * we can only check the policy if the server speaks
261 	 * RPCSEC_GSS
262 	 */
263 	if (_kadm5_get_kpasswd_protocol(server_handle) == KRB5_CHGPWD_RPCSEC) {
264 		/* Explain policy restrictions on new password if any. */
265 		/*
266 		 * Note: copy of this exists in login
267 		 * (kverify.c/get_verified_in_tkt).
268 		 */
269 
270 		code = kadm5_get_principal(server_handle, princ,
271 					&principal_entry,
272 					KADM5_PRINCIPAL_NORMAL_MASK);
273 		if (code != 0) {
274 			com_err(whoami, 0,
275 				string_text((code == KADM5_UNK_PRINC)
276 					    ? KPW_STR_PRIN_UNKNOWN :
277 					    KPW_STR_CANT_GET_POLICY_INFO),
278 				princ_str);
279 			krb5_free_principal(context, princ);
280 			free(princ_str);
281 			(void) kadm5_destroy(server_handle);
282 			return ((code == KADM5_UNK_PRINC) ? 1 :
283 				MISC_EXIT_STATUS);
284 		}
285 		if ((principal_entry.aux_attributes & KADM5_POLICY) != 0) {
286 			code = kadm5_get_policy(server_handle,
287 						principal_entry.policy,
288 						&policy_entry);
289 			if (code != 0) {
290 				/*
291 				 * doesn't matter which error comes back,
292 				 * there's no nice recovery or need to
293 				 * differentiate to the user
294 				 */
295 				com_err(whoami, 0,
296 				string_text(KPW_STR_CANT_GET_POLICY_INFO),
297 				princ_str);
298 				(void) kadm5_free_principal_ent(server_handle,
299 							&principal_entry);
300 				krb5_free_principal(context, princ);
301 				free(princ_str);
302 				free(princ_str);
303 				(void) kadm5_destroy(server_handle);
304 				return (MISC_EXIT_STATUS);
305 			}
306 			com_err(whoami, 0,
307 				string_text(KPW_STR_POLICY_EXPLANATION),
308 				princ_str, principal_entry.policy,
309 				policy_entry.pw_min_length,
310 				policy_entry.pw_min_classes);
311 			if (code = kadm5_free_principal_ent(server_handle,
312 						    &principal_entry)) {
313 				(void) kadm5_free_policy_ent(server_handle,
314 							    &policy_entry);
315 				krb5_free_principal(context, princ);
316 				free(princ_str);
317 				com_err(whoami, code,
318 				string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
319 				(void) kadm5_destroy(server_handle);
320 				return (MISC_EXIT_STATUS);
321 			}
322 			if (code = kadm5_free_policy_ent(server_handle,
323 							&policy_entry)) {
324 				krb5_free_principal(context, princ);
325 				free(princ_str);
326 				com_err(whoami, code,
327 				string_text(KPW_STR_WHILE_FREEING_POLICY));
328 				(void) kadm5_destroy(server_handle);
329 				return (MISC_EXIT_STATUS);
330 			}
331 		} else {
332 			/*
333 			 * kpasswd *COULD* output something here to
334 			 * encourage the choice of good passwords,
335 			 * in the absence of an enforced policy.
336 			 */
337 			if (code = kadm5_free_principal_ent(server_handle,
338 						    &principal_entry)) {
339 				krb5_free_principal(context, princ);
340 				free(princ_str);
341 				com_err(whoami, code,
342 				string_text(KPW_STR_WHILE_FREEING_PRINCIPAL));
343 				(void) kadm5_destroy(server_handle);
344 				return (MISC_EXIT_STATUS);
345 			}
346 		}
347 	} /* if protocol == KRB5_CHGPWD_RPCSEC */
348 
349 	pwsize = sizeof (password);
350 	code = read_new_password(server_handle, password,
351 			 &pwsize, msg_ret, sizeof (msg_ret), princ);
352 	memset(password, 0, sizeof (password));
353 
354 	if (code)
355 		com_err(whoami, 0, msg_ret);
356 
357 	krb5_free_principal(context, princ);
358 	free(princ_str);
359 
360 	(void) kadm5_destroy(server_handle);
361 
362 	if (code == KRB5_LIBOS_CANTREADPWD)
363 		return (5);
364 	else if (code)
365 		return (4);
366 	else
367 		return (0);
368 }
369