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