1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Portions Copyright 2021, Chris Fraire <cfraire@me.com>.
26  */
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <strings.h>
31 #include <locale.h>
32 #include <netdb.h>
33 #include "k5-int.h"
34 
35 #define	QUOTE(x)	#x
36 #define	VAL2STR(x)	QUOTE(x)
37 
38 static char *whoami = NULL;
39 
40 static void kt_add_entry(krb5_context ctx, krb5_keytab kt,
41 	const krb5_principal princ, const krb5_principal sprinc,
42 	krb5_enctype enctype, krb5_kvno kvno, const char *pw);
43 
44 static krb5_error_code kt_remove_entries(krb5_context ctx, krb5_keytab kt,
45 	const krb5_principal princ);
46 
47 static void usage();
48 
49 int
main(int argc,char ** argv)50 main(int argc, char **argv)
51 {
52 	krb5_context ctx = NULL;
53 	krb5_error_code code = 0;
54 	krb5_enctype *enctypes = NULL;
55 	int enctype_count = 0;
56 	krb5_ccache cc = NULL;
57 	krb5_keytab kt = NULL;
58 	krb5_kvno kvno = 1;
59 	krb5_principal victim, salt = NULL;
60 	char *vprincstr, *ktname, *token, *lasts, *newpw;
61 	int c, result_code, i, len, nflag = 0;
62 	krb5_data result_code_string, result_string;
63 
64 	(void) setlocale(LC_ALL, "");
65 
66 #if !defined(TEXT_DOMAIN)
67 #define	TEXT_DOMAIN "SYS_TEST"
68 #endif /* TEXT_DOMAIN */
69 
70 	(void) textdomain(TEXT_DOMAIN);
71 
72 	/* Misc init stuff */
73 	(void) memset(&result_code_string, 0, sizeof (result_code_string));
74 	(void) memset(&result_string, 0, sizeof (result_string));
75 
76 	whoami = argv[0];
77 
78 	code = krb5_init_context(&ctx);
79 	if (code != 0) {
80 		com_err(whoami, code, gettext("krb5_init_context() failed"));
81 		exit(1);
82 	}
83 
84 	while ((c = getopt(argc, argv, "v:c:k:e:ns:")) != -1) {
85 		switch (c) {
86 		case 'n':
87 			nflag++;
88 			break;
89 		case 'k':
90 			if (kt != NULL)
91 				usage();
92 			len = snprintf(NULL, 0, "WRFILE:%s", optarg) + 1;
93 			if ((ktname = malloc(len)) == NULL) {
94 				(void) fprintf(stderr,
95 				    gettext("Couldn't allocate memory\n"));
96 				exit(1);
97 			}
98 			(void) snprintf(ktname, len, "WRFILE:%s", optarg);
99 			if ((code = krb5_kt_resolve(ctx, ktname, &kt)) != 0) {
100 				com_err(whoami, code,
101 				    gettext("Couldn't open/create "
102 				    "keytab %s"), optarg);
103 				exit(1);
104 			}
105 			break;
106 		case 'c':
107 			if (cc != NULL)
108 				usage();
109 			if ((code = krb5_cc_resolve(ctx, optarg, &cc)) != 0) {
110 				com_err(whoami, code,
111 				    gettext("Couldn't open ccache %s"), optarg);
112 				exit(1);
113 			}
114 			break;
115 		case 'e':
116 			len = strlen(optarg);
117 			token = strtok_r(optarg, ",\t ", &lasts);
118 
119 			if (token == NULL)
120 				usage();
121 
122 			do {
123 				if (enctype_count++ == 0) {
124 					enctypes = malloc(sizeof (*enctypes));
125 				} else {
126 					enctypes = realloc(enctypes,
127 					    sizeof (*enctypes) * enctype_count);
128 				}
129 				if (enctypes == NULL) {
130 					(void) fprintf(stderr, gettext
131 					    ("Couldn't allocate memory"));
132 					exit(1);
133 				}
134 				code = krb5_string_to_enctype(token,
135 				    &enctypes[enctype_count - 1]);
136 
137 				if (code != 0) {
138 					com_err(whoami, code, gettext("Unknown "
139 					    "or unsupported enctype %s"),
140 					    optarg);
141 					exit(1);
142 				}
143 			} while ((token = strtok_r(NULL, ",\t ", &lasts)) !=
144 			    NULL);
145 			break;
146 		case 'v':
147 			kvno = (krb5_kvno) atoi(optarg);
148 			break;
149 		case 's':
150 			vprincstr = optarg;
151 			code = krb5_parse_name(ctx, vprincstr, &salt);
152 			if (code != 0) {
153 				com_err(whoami, code,
154 				    gettext("krb5_parse_name(%s) failed"),
155 				    vprincstr);
156 				exit(1);
157 			}
158 			break;
159 		default:
160 			usage();
161 			break;
162 		}
163 	}
164 
165 	if (nflag && enctype_count == 0)
166 		usage();
167 
168 	if (nflag == 0 && cc == NULL &&
169 	    (code = krb5_cc_default(ctx, &cc)) != 0) {
170 		com_err(whoami, code, gettext("Could not find a ccache"));
171 		exit(1);
172 	}
173 
174 	if (enctype_count > 0 && kt == NULL &&
175 	    (code = krb5_kt_default(ctx, &kt)) != 0) {
176 		com_err(whoami, code, gettext("No keytab specified"));
177 		exit(1);
178 	}
179 
180 	if (argc != (optind + 1))
181 		usage();
182 
183 	vprincstr = argv[optind];
184 	code = krb5_parse_name(ctx, vprincstr, &victim);
185 	if (code != 0) {
186 		com_err(whoami, code, gettext("krb5_parse_name(%s) failed"),
187 		    vprincstr);
188 		exit(1);
189 	}
190 
191 	if (!isatty(fileno(stdin))) {
192 		char buf[PASS_MAX + 1];
193 
194 		if (scanf("%" VAL2STR(PASS_MAX) "s", &buf) != 1) {
195 			(void) fprintf(stderr,
196 			    gettext("Couldn't read new password\n"));
197 			exit(1);
198 		}
199 
200 		newpw = strdup(buf);
201 		if (newpw == NULL) {
202 			(void) fprintf(stderr,
203 			    gettext("Couldn't allocate memory\n"));
204 			exit(1);
205 		}
206 	} else {
207 		newpw = getpassphrase(gettext("Enter new password: "));
208 		if (newpw == NULL) {
209 			(void) fprintf(stderr,
210 			    gettext("Couldn't read new password\n"));
211 			exit(1);
212 		}
213 
214 		newpw = strdup(newpw);
215 		if (newpw == NULL) {
216 			(void) fprintf(stderr,
217 			    gettext("Couldn't allocate memory\n"));
218 			exit(1);
219 		}
220 	}
221 
222 	if (nflag == 0) {
223 		code = krb5_set_password_using_ccache(ctx, cc, newpw, victim,
224 		    &result_code, &result_code_string, &result_string);
225 		if (code != 0) {
226 			com_err(whoami, code,
227 			    gettext("krb5_set_password() failed"));
228 			exit(1);
229 		}
230 		krb5_cc_close(ctx, cc);
231 
232 		(void) printf("Result: %.*s (%d) %.*s\n",
233 		    result_code == 0 ?
234 		    strlen("success") : result_code_string.length,
235 		    result_code == 0 ? "success" : result_code_string.data,
236 		    result_code,
237 		    result_string.length, result_string.data);
238 
239 		if (result_code != 0) {
240 			(void) fprintf(stderr, gettext("Exiting...\n"));
241 			exit(result_code);
242 		}
243 	}
244 
245 	if (enctype_count && (code = kt_remove_entries(ctx, kt, victim)))
246 		goto error;
247 
248 	if (salt == NULL)
249 		salt = victim;
250 
251 	for (i = 0; i < enctype_count; i++)
252 		kt_add_entry(ctx, kt, victim, salt, enctypes[i], kvno, newpw);
253 
254 error:
255 	if (kt != NULL)
256 		krb5_kt_close(ctx, kt);
257 
258 	return (code ? 1 : 0);
259 }
260 
261 static
262 krb5_error_code
kt_remove_entries(krb5_context ctx,krb5_keytab kt,const krb5_principal princ)263 kt_remove_entries(krb5_context ctx, krb5_keytab kt, const krb5_principal princ)
264 {
265 	krb5_error_code code;
266 	krb5_kt_cursor cursor;
267 	krb5_keytab_entry entry;
268 
269 	/*
270 	 * This is not a fatal error, we expect this to fail in the majority
271 	 * of cases (when clients are first initialized).
272 	 */
273 	code = krb5_kt_get_entry(ctx, kt, princ, 0, 0, &entry);
274 	if (code != 0) {
275 		com_err(whoami, code,
276 		    gettext("Could not retrieve entry in keytab"));
277 		return (0);
278 	}
279 
280 	krb5_kt_free_entry(ctx, &entry);
281 
282 	code = krb5_kt_start_seq_get(ctx, kt, &cursor);
283 	if (code != 0) {
284 		com_err(whoami, code, gettext("While starting keytab scan"));
285 		return (code);
286 	}
287 
288 	while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) {
289 		if (krb5_principal_compare(ctx, princ, entry.principal)) {
290 
291 			code = krb5_kt_end_seq_get(ctx, kt, &cursor);
292 			if (code != 0) {
293 				com_err(whoami, code,
294 				    gettext("While temporarily "
295 				    "ending keytab scan"));
296 				return (code);
297 			}
298 
299 			code = krb5_kt_remove_entry(ctx, kt, &entry);
300 			if (code != 0) {
301 				com_err(whoami, code,
302 				    gettext("While deleting entry "
303 				    "from keytab"));
304 				return (code);
305 			}
306 
307 			code = krb5_kt_start_seq_get(ctx, kt, &cursor);
308 			if (code != 0) {
309 				com_err(whoami, code,
310 				    gettext("While restarting keytab scan"));
311 				return (code);
312 			}
313 		}
314 
315 		krb5_kt_free_entry(ctx, &entry);
316 	}
317 
318 	if (code && code != KRB5_KT_END) {
319 		com_err(whoami, code, gettext("While scanning keytab"));
320 		return (code);
321 	}
322 
323 	if ((code = krb5_kt_end_seq_get(ctx, kt, &cursor))) {
324 		com_err(whoami, code, gettext("While ending keytab scan"));
325 		return (code);
326 	}
327 
328 	return (0);
329 }
330 
331 static
332 void
kt_add_entry(krb5_context ctx,krb5_keytab kt,const krb5_principal princ,const krb5_principal sprinc,krb5_enctype enctype,krb5_kvno kvno,const char * pw)333 kt_add_entry(krb5_context ctx, krb5_keytab kt, const krb5_principal princ,
334     const krb5_principal sprinc, krb5_enctype enctype, krb5_kvno kvno,
335     const char *pw)
336 {
337 	krb5_keytab_entry *entry;
338 	krb5_data password, salt;
339 	krb5_keyblock key;
340 	krb5_error_code code;
341 	char enctype_name[100];
342 
343 	if ((code = krb5_enctype_to_string(enctype, enctype_name,
344 	    sizeof (enctype_name)))) {
345 		com_err(whoami, code, gettext("Enctype %d has no name!"),
346 		    enctype);
347 		return;
348 	}
349 	if ((entry = (krb5_keytab_entry *) malloc(sizeof (*entry))) == NULL) {
350 		(void) fprintf(stderr, gettext("Couldn't allocate memory"));
351 		return;
352 	}
353 
354 	(void) memset((char *)entry, 0, sizeof (*entry));
355 
356 	password.length = strlen(pw);
357 	password.data = (char *)pw;
358 
359 	if ((code = krb5_principal2salt(ctx, sprinc, &salt)) != 0) {
360 		com_err(whoami, code,
361 		    gettext("Could not compute salt for %s"), enctype_name);
362 		return;
363 	}
364 
365 	code = krb5_c_string_to_key(ctx, enctype, &password, &salt, &key);
366 
367 	if (code != 0) {
368 		com_err(whoami, code,
369 		    gettext("Could not convert to key for %s"), enctype_name);
370 		krb5_xfree(salt.data);
371 		return;
372 	}
373 
374 	(void) memcpy(&entry->key, &key, sizeof (krb5_keyblock));
375 	entry->vno = kvno;
376 	entry->principal = princ;
377 
378 	if ((code = krb5_kt_add_entry(ctx, kt, entry)) != 0) {
379 		com_err(whoami, code,
380 		    gettext("Could not add entry to keytab"));
381 	}
382 }
383 
384 static
385 void
usage()386 usage()
387 {
388 	(void) fprintf(stderr, gettext("Usage: %s [-c ccache] [-k keytab] "
389 	    "[-e enctype_list] [-s salt_name] [-n] princ\n"), whoami);
390 	(void) fprintf(stderr,
391 	    gettext("\t-n\tDon't set the principal's password\n"));
392 	(void) fprintf(stderr, gettext("\tenctype_list is a comma or whitespace"
393 	    " separated list\n"));
394 	(void) fprintf(stderr, gettext("\tIf -n is used then -k and -e must be "
395 	    "used\n"));
396 
397 	exit(1);
398 }
399