1 /*
2 *----------------------------------------------------------------------------
3 *
4 * msktconf.cpp
5 *
6 * (C) 2004-2006 Dan Perry (dperry@pppl.gov)
7 * (C) 2006 Brian Elliott Finley (finley@anl.gov)
8 * (C) 2009-2010 Doug Engert (deengert@anl.gov)
9 * (C) 2010 James Y Knight (foom@fuhm.net)
10 * (C) 2010-2013 Ken Dreyer <ktdreyer at ktdreyer.com>
11 * (C) 2012-2017 Mark Proehl <mark at mproehl.net>
12 * (C) 2012-2017 Olaf Flebbe <of at oflebbe.de>
13 * (C) 2013-2017 Daniel Kobras <d.kobras at science-computing.de>
14 *
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28 *
29 *-----------------------------------------------------------------------------
30 */
31
32 #include "msktutil.h"
33
34 #include <fstream>
35 #include <cctype>
36
37
create_default_machine_password(const std::string & sAMAccountName)38 std::string create_default_machine_password(const std::string &sAMAccountName)
39 {
40 std::string machine_password(sAMAccountName);
41
42 /* Default machine password after 'reset account' is created with the
43 * following algorithm:
44 *
45 * 1) Remove trailing $ from sAMAcountName
46 * 2) Truncate to first 14 characters
47 * 3) Convert all characters to lowercase
48 *
49 */
50
51 /* Remove trailing '$' */
52 if (machine_password[machine_password.size() - 1] == '$') {
53 machine_password.resize(machine_password.size() - 1);
54 }
55
56 /* Truncate to first 14 characters */
57 if (machine_password.size() > MAX_DEF_MACH_PASS_LEN) {
58 machine_password.resize(MAX_DEF_MACH_PASS_LEN);
59 }
60
61 /* Convert all characters to lowercase */
62 for (size_t i = 0; i < machine_password.size(); i++) {
63 machine_password[i] = std::tolower(machine_password[i]);
64 }
65
66 VERBOSE("Default machine password for %s is %s",
67 sAMAccountName.c_str(),
68 machine_password.c_str());
69
70 return machine_password;
71 }
72
73
74 /* Filenames to delete on exit (temporary config / ccaches) */
75 static std::string g_config_filename;
76 static std::string g_ccache_filename;
77
get_tempfile_name(const char * name)78 std::string get_tempfile_name(const char *name)
79 {
80 std::string full_template = sform("%s/%s-XXXXXX", TMP_DIR, name);
81 char *template_arr = strdup(full_template.c_str());
82
83 int fd = mkstemp(template_arr);
84 if (fd < 0) {
85 error_exit("mkstemp failed");
86 }
87
88 /* Didn't need an fd, just to have the filename created securely. */
89 close(fd);
90 std::string tempfile_name = std::string(template_arr);
91 free(template_arr);
92
93 return tempfile_name;
94 }
95
96
create_fake_krb5_conf(msktutil_flags * flags)97 void create_fake_krb5_conf(msktutil_flags *flags)
98 {
99 g_config_filename = get_tempfile_name(".msktkrb5.conf");
100 std::ofstream file(g_config_filename.c_str());
101
102 file << "[libdefaults]\n"
103 << " default_realm = " << flags->realm_name << "\n"
104 << " dns_lookup_kdc = false\n"
105 << " udp_preference_limit = 1\n"
106 << " default_ccache_name = " << KRB5CCache::defaultName() << "\n";
107
108 if (flags->allow_weak_crypto) {
109 file << " allow_weak_crypto = true\n";
110 }
111
112 if (flags->enctypes == VALUE_ON) {
113 file << " default_tkt_enctypes =";
114 if (flags->supportedEncryptionTypes & 0x1) {
115 file << " des-cbc-crc";
116 }
117 if (flags->supportedEncryptionTypes & 0x2) {
118 file << " des-cbc-md5";
119 }
120 if (flags->supportedEncryptionTypes & 0x4) {
121 file << " arcfour-hmac-md5";
122 }
123 if (flags->supportedEncryptionTypes & 0x8) {
124 file << " aes128-cts";
125 }
126 if (flags->supportedEncryptionTypes & 0x10) {
127 file << " aes256-cts";
128 }
129 file << "\n";
130 }
131 if ((flags->no_reverse_lookups) || (flags->no_canonical_name)) {
132 file << " rdns = false\n";
133 }
134
135 file << "[realms]\n"
136 << " " << flags->realm_name << " = {\n"
137 << " kdc = " << flags->server << "\n"
138 << " admin_server = " << flags->server << "\n"
139 << " }\n";
140 file.close();
141
142 #ifdef HAVE_SETENV
143 int ret = setenv("KRB5_CONFIG", g_config_filename.c_str(), 1);
144 if (ret) {
145 error_exit("setenv failed");
146 }
147 #else
148 int ret = putenv(strdup((std::string("KRB5_CONFIG=") + g_config_filename).c_str()));
149 if (ret) {
150 error_exit("putenv failed");
151 }
152 #endif
153
154 VERBOSE("Created a fake krb5.conf file: %s", g_config_filename.c_str());
155
156 destroy_g_context();
157 initialize_g_context();
158 }
159
160
remove_fake_krb5_conf()161 void remove_fake_krb5_conf()
162 {
163 if (!g_config_filename.empty()) {
164 unlink(g_config_filename.c_str());
165 g_config_filename.clear();
166 }
167 }
168
169
remove_ccache()170 void remove_ccache()
171 {
172 if (!g_ccache_filename.empty()) {
173 unlink(g_ccache_filename.c_str());
174 g_ccache_filename.clear();
175 }
176 }
177
178
switch_default_ccache(const char * ccache_name)179 void switch_default_ccache(const char *ccache_name)
180 {
181 VERBOSE("Using the local credential cache: %s", ccache_name);
182
183 /* Is this setenv really necessary given krb5_cc_set_default_name?
184 * ...answer: YES, because ldap's sasl won't be using our context
185 * object, and may in fact be using a different implementation of
186 * kerberos entirely! */
187 #ifdef HAVE_SETENV
188 if (setenv("KRB5CCNAME", ccache_name, 1)) {
189 error_exit("setenv failed");
190 }
191 #else
192 if (!putenv(strdup((std::string("KRB5CCNAME=")+ ccache_name).c_str()))) {
193 error_exit("putenv failed");
194 }
195 #endif
196 krb5_cc_set_default_name(g_context, ccache_name);
197 }
198
199
try_machine_keytab_princ(msktutil_flags * flags,const std::string & principal_name,const char * ccache_name)200 bool try_machine_keytab_princ(msktutil_flags *flags,
201 const std::string &principal_name,
202 const char *ccache_name)
203 {
204 try {
205 VERBOSE("Trying to authenticate for %s from local keytab",
206 principal_name.c_str());
207 KRB5Keytab keytab(flags->keytab_readname);
208 KRB5Principal principal(principal_name);
209 KRB5Creds creds(principal, keytab);
210 KRB5CCache ccache(ccache_name);
211 ccache.initialize(principal);
212 ccache.store(creds);
213 switch_default_ccache(ccache_name);
214 return true;
215 } catch (KRB5Exception &e) {
216 VERBOSE("%s", e.what());
217 VERBOSE("Authentication with keytab failed");
218 return false;
219 }
220 }
221
222
try_machine_password(msktutil_flags * flags,const char * ccache_name)223 bool try_machine_password(msktutil_flags *flags, const char *ccache_name)
224 {
225 try {
226 VERBOSE("Trying to authenticate for %s with password",
227 flags->sAMAccountName.c_str());
228 KRB5Principal principal(flags->sAMAccountName);
229 KRB5Creds creds(principal,
230 /*password:*/
231 create_default_machine_password(flags->sAMAccountName));
232 KRB5CCache ccache(ccache_name);
233 ccache.initialize(principal);
234 ccache.store(creds);
235 switch_default_ccache(ccache_name);
236 return true;
237 } catch (KRB5Exception &e) {
238 VERBOSE("%s", e.what());
239 VERBOSE("Authentication with password failed");
240 return false;
241 }
242 }
243
244
try_machine_supplied_password(msktutil_flags * flags,const char * ccache_name)245 bool try_machine_supplied_password(msktutil_flags *flags,
246 const char *ccache_name)
247 {
248 try {
249 VERBOSE("Trying to authenticate for %s with supplied password",
250 flags->sAMAccountName.c_str());
251 KRB5Principal principal(flags->sAMAccountName);
252 KRB5Creds creds(principal, /*password:*/ flags->old_account_password);
253 KRB5CCache ccache(ccache_name);
254 ccache.initialize(principal);
255 ccache.store(creds);
256 switch_default_ccache(ccache_name);
257 return true;
258 } catch (KRB5Exception &e) {
259 VERBOSE("%s", e.what());
260 if (e.err() == KRB5KDC_ERR_KEY_EXP) {
261 VERBOSE("Password needs to be changed");
262 flags->password_expired = true;
263 return false;
264 } else {
265 VERBOSE("Authentication with supplied password failed");
266 return false;
267 }
268 }
269 }
270
271
get_creds(msktutil_flags * flags)272 bool get_creds(msktutil_flags *flags)
273 {
274 g_ccache_filename = get_tempfile_name(".mskt_krb5_ccache");
275 std::string ccache_name = "FILE:" + g_ccache_filename;
276 try {
277 KRB5Principal principal(flags->sAMAccountName);
278 KRB5Creds creds(principal, /*password:*/ flags->password);
279 KRB5CCache ccache(ccache_name.c_str());
280 ccache.initialize(principal);
281 ccache.store(creds);
282 switch_default_ccache(ccache_name.c_str());
283 return true;
284 } catch (KRB5Exception &e) {
285 VERBOSE("%s", e.what());
286 VERBOSE("Authentication with password failed");
287 return false;
288 }
289 }
290
291
try_user_creds()292 bool try_user_creds()
293 {
294 try {
295 VERBOSE("Checking if default ticket cache has tickets");
296 /* The following is for the side effect of throwing an
297 * exception or not. */
298 KRB5CCache ccache(KRB5CCache::defaultName());
299 KRB5Principal princ(ccache);
300
301 return true;
302 } catch(KRB5Exception &e) {
303 VERBOSE("%s", e.what());
304 VERBOSE("User ticket cache was not valid");
305 return false;
306 }
307 }
308
309
find_working_creds(msktutil_flags * flags)310 int find_working_creds(msktutil_flags *flags)
311 {
312 /* We try some different ways, in order:
313 * 1) Use principal from keytab. Try both:
314 * a) sAMAccountName
315 * b) host/full-hostname (for compat with older msktutil which
316 * didn't write the first).
317 * 2) Use principal sAMAccountName with default password
318 * (sAMAccountName_nodollar)
319 * 3) Use supplied credentials (--old-account-password)
320 * When the supplied password has expired (e.g. because
321 * the service account has been newly created) we cannot find
322 * any working credentials here and have to return
323 * AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD.
324 * In this case working credentials need to be obtained
325 * after changing password
326 * 4) Calling user's existing credentials from their credential
327 * cache.
328 */
329 if (!flags->user_creds_only) {
330 std::string host_princ = "host/" + flags->hostname;
331 /*
332 * NOTE: we have to use an actual file for the credential
333 * cache, and not a MEMORY: type, because libsasl may be using
334 * heimdal, while this program may be compiled against MIT
335 * kerberos. So, while it's all in the same process and you'd
336 * think an in-mem ccache would be the right thing, the two
337 * kerberos implementations cannot share an in-memory ccache,
338 * so we have to use a file. Sigh.
339 */
340 g_ccache_filename = get_tempfile_name(".mskt_krb5_ccache");
341 std::string ccache_name = "FILE:" + g_ccache_filename;
342
343 if (!flags->keytab_auth_princ.empty() &&
344 access(flags->keytab_file.c_str(), R_OK) == 0 &&
345 try_machine_keytab_princ(flags,
346 flags->keytab_auth_princ,
347 ccache_name.c_str())) {
348 return AUTH_FROM_EXPLICIT_KEYTAB;
349 }
350 if (access(flags->keytab_file.c_str(), R_OK) == 0 &&
351 try_machine_keytab_princ(flags,
352 flags->sAMAccountName,
353 ccache_name.c_str())) {
354 return AUTH_FROM_SAM_KEYTAB;
355 }
356 if (access(flags->keytab_file.c_str(), R_OK) == 0 &&
357 try_machine_keytab_princ(flags,
358 flags->sAMAccountName_uppercase,
359 ccache_name.c_str())) {
360 return AUTH_FROM_SAM_UPPERCASE_KEYTAB;
361 }
362 if (access(flags->keytab_file.c_str(), R_OK) == 0 &&
363 try_machine_keytab_princ(flags,
364 host_princ,
365 ccache_name.c_str())) {
366 return AUTH_FROM_HOSTNAME_KEYTAB;
367 }
368 if (try_machine_password(flags, ccache_name.c_str())) {
369 return AUTH_FROM_PASSWORD;
370 }
371 if (strlen(flags->old_account_password.c_str())) {
372 if (try_machine_supplied_password(flags, ccache_name.c_str())) {
373 return AUTH_FROM_SUPPLIED_PASSWORD;
374 }
375 if (flags->password_expired) {
376 return AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD;
377 }
378 }
379 }
380 if (try_user_creds()) {
381 return AUTH_FROM_USER_CREDS;
382 }
383
384 return AUTH_NONE;
385 }
386