get_init_creds_password: DNS SRV off causes bogus REALM_UNKNOWN
[krb5.git] / src / lib / krb5 / krb / gic_pwd.c
1 #include "k5-int.h"
2 #include "com_err.h"
3
4 static krb5_error_code
5 krb5_get_as_key_password(
6     krb5_context context,
7     krb5_principal client,
8     krb5_enctype etype,
9     krb5_prompter_fct prompter,
10     void *prompter_data,
11     krb5_data *salt,
12     krb5_data *params,
13     krb5_keyblock *as_key,
14     void *gak_data)
15 {
16     krb5_data *password;
17     krb5_error_code ret;
18     krb5_data defsalt;
19     char *clientstr;
20     char promptstr[1024];
21     krb5_prompt prompt;
22     krb5_prompt_type prompt_type;
23
24     password = (krb5_data *) gak_data;
25
26     /* If there's already a key of the correct etype, we're done.
27        If the etype is wrong, free the existing key, and make
28        a new one.
29
30        XXX This was the old behavior, and was wrong in hw preauth
31        cases.  Is this new behavior -- always asking -- correct in all
32        cases?  */
33
34     if (as_key->length) {
35         if (as_key->enctype != etype) {
36             krb5_free_keyblock_contents (context, as_key);
37             as_key->length = 0;
38         }
39     }
40
41     if (password->data[0] == '\0') {
42         if (prompter == NULL)
43             return(EIO);
44
45         if ((ret = krb5_unparse_name(context, client, &clientstr)))
46           return(ret);
47
48         strcpy(promptstr, "Password for ");
49         strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1);
50         promptstr[sizeof(promptstr)-1] = '\0';
51
52         free(clientstr);
53
54         prompt.prompt = promptstr;
55         prompt.hidden = 1;
56         prompt.reply = password;
57         prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
58
59         /* PROMPTER_INVOCATION */
60         krb5int_set_prompt_types(context, &prompt_type);
61         if ((ret = (((*prompter)(context, prompter_data, NULL, NULL,
62                                 1, &prompt))))) {
63             krb5int_set_prompt_types(context, 0);
64             return(ret);
65         }
66         krb5int_set_prompt_types(context, 0);
67     }
68
69     if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
70         if ((ret = krb5_principal2salt(context, client, &defsalt)))
71             return(ret);
72
73         salt = &defsalt;
74     } else {
75         defsalt.length = 0;
76     }
77
78     ret = krb5_c_string_to_key_with_params(context, etype, password, salt,
79                                            params->data?params:NULL, as_key);
80
81     if (defsalt.length)
82         krb5_xfree(defsalt.data);
83
84     return(ret);
85 }
86
87 krb5_error_code KRB5_CALLCONV
88 krb5_get_init_creds_password(krb5_context context, krb5_creds *creds, krb5_principal client, char *password, krb5_prompter_fct prompter, void *data, krb5_deltat start_time, char *in_tkt_service, krb5_get_init_creds_opt *options)
89 {
90    krb5_error_code ret, ret2;
91    int use_master;
92    krb5_kdc_rep *as_reply;
93    int tries;
94    krb5_creds chpw_creds;
95    krb5_get_init_creds_opt chpw_opts;
96    krb5_data pw0, pw1;
97    char banner[1024], pw0array[1024], pw1array[1024];
98    krb5_prompt prompt[2];
99    krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
100
101    use_master = 0;
102    as_reply = NULL;
103    memset(&chpw_creds, 0, sizeof(chpw_creds));
104
105    pw0.data = pw0array;
106
107    if (password) {
108       if ((pw0.length = strlen(password)) > sizeof(pw0array)) {
109          ret = EINVAL;
110          goto cleanup;
111       }
112       strcpy(pw0.data, password);
113    } else {
114       pw0.data[0] = '\0';
115       pw0.length = sizeof(pw0array);
116    }
117
118    pw1.data = pw1array;
119    pw1.data[0] = '\0';
120    pw1.length = sizeof(pw1array);
121
122    /* first try: get the requested tkt from any kdc */
123
124    ret = krb5_get_init_creds(context, creds, client, prompter, data,
125                              start_time, in_tkt_service, options,
126                              krb5_get_as_key_password, (void *) &pw0,
127                              use_master, &as_reply);
128
129    /* check for success */
130
131    if (ret == 0)
132       goto cleanup;
133
134    /* If all the kdc's are unavailable, or if the error was due to a
135       user interrupt, or preauth errored out, fail */
136
137    if ((ret == KRB5_KDC_UNREACH) ||
138        (ret == KRB5_PREAUTH_FAILED) ||
139        (ret == KRB5_LIBOS_PWDINTR) ||
140            (ret == KRB5_REALM_CANT_RESOLVE))
141       goto cleanup;
142
143    /* if the reply did not come from the master kdc, try again with
144       the master kdc */
145
146    if (!use_master) {
147       use_master = 1;
148
149       ret2 = krb5_get_init_creds(context, creds, client, prompter, data,
150                                  start_time, in_tkt_service, options,
151                                  krb5_get_as_key_password, (void *) &pw0,
152                                  use_master, &as_reply);
153       
154       if (ret2 == 0) {
155          ret = 0;
156          goto cleanup;
157       }
158
159       /* if the master is unreachable, return the error from the
160          slave we were able to contact */
161
162       if ((ret2 == KRB5_KDC_UNREACH) ||
163           (ret2 == KRB5_REALM_CANT_RESOLVE) ||
164           (ret2 == KRB5_REALM_UNKNOWN))
165          goto cleanup;
166
167       ret = ret2;
168    }
169
170 #ifdef USE_LOGIN_LIBRARY
171         if (ret == KRB5KDC_ERR_KEY_EXP)
172                 goto cleanup;   /* Login library will deal appropriately with this error */
173 #endif
174
175    /* at this point, we have an error from the master.  if the error
176       is not password expired, or if it is but there's no prompter,
177       return this error */
178
179    if ((ret != KRB5KDC_ERR_KEY_EXP) ||
180        (prompter == NULL))
181       goto cleanup;
182
183    /* ok, we have an expired password.  Give the user a few chances
184       to change it */
185
186    /* use a minimal set of options */
187
188    krb5_get_init_creds_opt_init(&chpw_opts);
189    krb5_get_init_creds_opt_set_tkt_life(&chpw_opts, 5*60);
190    krb5_get_init_creds_opt_set_renew_life(&chpw_opts, 0);
191    krb5_get_init_creds_opt_set_forwardable(&chpw_opts, 0);
192    krb5_get_init_creds_opt_set_proxiable(&chpw_opts, 0);
193
194    if ((ret = krb5_get_init_creds(context, &chpw_creds, client,
195                                   prompter, data,
196                                   start_time, "kadmin/changepw", &chpw_opts,
197                                   krb5_get_as_key_password, (void *) &pw0,
198                                   use_master, NULL)))
199       goto cleanup;
200
201    prompt[0].prompt = "Enter new password";
202    prompt[0].hidden = 1;
203    prompt[0].reply = &pw0;
204    prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
205
206    prompt[1].prompt = "Enter it again";
207    prompt[1].hidden = 1;
208    prompt[1].reply = &pw1;
209    prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
210
211    strcpy(banner, "Password expired.  You must change it now.");
212
213    for (tries = 3; tries; tries--) {
214       pw0.length = sizeof(pw0array);
215       pw1.length = sizeof(pw1array);
216
217       /* PROMPTER_INVOCATION */
218       krb5int_set_prompt_types(context, prompt_types);
219       if ((ret = ((*prompter)(context, data, 0, banner,
220                               sizeof(prompt)/sizeof(prompt[0]), prompt))))
221          goto cleanup;
222       krb5int_set_prompt_types(context, 0);
223
224
225       if (strcmp(pw0.data, pw1.data) != 0) {
226          ret = KRB5_LIBOS_BADPWDMATCH;
227          sprintf(banner, "%s.  Please try again.", error_message(ret));
228       } else if (pw0.length == 0) {
229          ret = KRB5_CHPW_PWDNULL;
230          sprintf(banner, "%s.  Please try again.", error_message(ret));
231       } else {
232          int result_code;
233          krb5_data code_string;
234          krb5_data result_string;
235
236          if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
237                                          &result_code, &code_string,
238                                          &result_string)))
239             goto cleanup;
240
241          /* the change succeeded.  go on */
242
243          if (result_code == 0) {
244             krb5_xfree(result_string.data);
245             break;
246          }
247
248          /* set this in case the retry loop falls through */
249
250          ret = KRB5_CHPW_FAIL;
251
252          if (result_code != KRB5_KPASSWD_SOFTERROR) {
253             krb5_xfree(result_string.data);
254             goto cleanup;
255          }
256
257          /* the error was soft, so try again */
258
259          /* 100 is I happen to know that no code_string will be longer
260             than 100 chars */
261
262          if (result_string.length > (sizeof(banner)-100))
263             result_string.length = sizeof(banner)-100;
264
265          sprintf(banner, "%.*s%s%.*s.  Please try again.\n",
266                  (int) code_string.length, code_string.data,
267                  result_string.length ? ": " : "",
268                  (int) result_string.length,
269                  result_string.data ? result_string.data : "");
270
271          krb5_xfree(code_string.data);
272          krb5_xfree(result_string.data);
273       }
274    }
275
276    if (ret)
277       goto cleanup;
278
279    /* the password change was successful.  Get an initial ticket
280       from the master.  this is the last try.  the return from this
281       is final.  */
282
283    ret = krb5_get_init_creds(context, creds, client, prompter, data,
284                              start_time, in_tkt_service, options,
285                              krb5_get_as_key_password, (void *) &pw0,
286                              use_master, &as_reply);
287
288 cleanup:
289    krb5int_set_prompt_types(context, 0);
290    /* if getting the password was successful, then check to see if the
291       password is about to expire, and warn if so */
292
293    if (ret == 0) {
294       krb5_timestamp now;
295       krb5_last_req_entry **last_req;
296       int hours;
297
298       /* XXX 7 days should be configurable.  This is all pretty ad hoc,
299          and could probably be improved if I was willing to screw around
300          with timezones, etc. */
301
302       if (prompter &&
303           (!in_tkt_service ||
304            (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
305           ((ret = krb5_timeofday(context, &now)) == 0) &&
306           as_reply->enc_part2->key_exp &&
307           ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
308           (hours >= 0)) {
309          if (hours < 1)
310             sprintf(banner,
311                     "Warning: Your password will expire in less than one hour.");
312          else if (hours <= 48)
313             sprintf(banner, "Warning: Your password will expire in %d hour%s.",
314                     hours, (hours == 1)?"":"s");
315          else
316             sprintf(banner, "Warning: Your password will expire in %d days.",
317                     hours/24);
318
319          /* ignore an error here */
320          /* PROMPTER_INVOCATION */
321          (*prompter)(context, data, 0, banner, 0, 0);
322       } else if (prompter &&
323                  (!in_tkt_service ||
324                   (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
325                  as_reply->enc_part2 && as_reply->enc_part2->last_req) {
326          /*
327           * Check the last_req fields
328           */
329
330          for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++)
331             if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
332                 (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
333                krb5_deltat delta;
334                char ts[256];
335
336                if ((ret = krb5_timeofday(context, &now)))
337                   break;
338
339                if ((ret = krb5_timestamp_to_string((*last_req)->value,
340                                                    ts, sizeof(ts))))
341                   break;
342
343                delta = (*last_req)->value - now;
344
345                if (delta < 3600)
346                   sprintf(banner,
347                     "Warning: Your password will expire in less than one "
348                      "hour on %s", ts);
349                else if (delta < 86400*2)
350                   sprintf(banner,
351                      "Warning: Your password will expire in %d hour%s on %s",
352                      delta / 3600, delta < 7200 ? "" : "s", ts);
353                else
354                   sprintf(banner,
355                      "Warning: Your password will expire in %d days on %s",
356                      delta / 86400, ts);
357                /* ignore an error here */
358                /* PROMPTER_INVOCATION */
359                (*prompter)(context, data, 0, banner, 0, 0);
360             }
361       }
362    }
363
364    memset(pw0array, 0, sizeof(pw0array));
365    memset(pw1array, 0, sizeof(pw1array));
366    krb5_free_cred_contents(context, &chpw_creds);
367    if (as_reply)
368       krb5_free_kdc_rep(context, as_reply);
369
370    return(ret);
371 }
372 void krb5int_populate_gic_opt (
373     krb5_context context, krb5_get_init_creds_opt *opt,
374     krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes,
375     krb5_preauthtype *pre_auth_types)
376 {
377   int i;
378     krb5_get_init_creds_opt_init(opt);
379     if (addrs)
380       krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs);
381     if (ktypes) {
382         for (i=0; ktypes[i]; i++);
383         if (i)
384             krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i);
385     }
386     if (pre_auth_types) {
387         for (i=0; pre_auth_types[i]; i++);
388         if (i)
389             krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i);
390     }
391     if (options&KDC_OPT_FORWARDABLE)
392         krb5_get_init_creds_opt_set_forwardable(opt, 1);
393     else krb5_get_init_creds_opt_set_forwardable(opt, 0);
394     if (options&KDC_OPT_PROXIABLE)
395         krb5_get_init_creds_opt_set_proxiable(opt, 1);
396     else krb5_get_init_creds_opt_set_proxiable(opt, 0);
397     
398
399 }
400
401 /*
402   Rewrites get_in_tkt in terms of newer get_init_creds API.
403  Attempts to get an initial ticket for creds->client to use server
404  creds->server, (realm is taken from creds->client), with options
405  options, and using creds->times.starttime, creds->times.endtime,
406  creds->times.renew_till as from, till, and rtime.  
407  creds->times.renew_till is ignored unless the RENEWABLE option is requested.
408
409  If addrs is non-NULL, it is used for the addresses requested.  If it is
410  null, the system standard addresses are used.
411
412  If password is non-NULL, it is converted using the cryptosystem entry
413  point for a string conversion routine, seeded with the client's name.
414  If password is passed as NULL, the password is read from the terminal,
415  and then converted into a key.
416
417  A succesful call will place the ticket in the credentials cache ccache.
418
419  returns system errors, encryption errors
420  */
421 krb5_error_code KRB5_CALLCONV
422 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options,
423                               krb5_address *const *addrs, krb5_enctype *ktypes,
424                               krb5_preauthtype *pre_auth_types,
425                               const char *password, krb5_ccache ccache,
426                               krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
427 {
428     krb5_error_code retval;
429     krb5_data pw0;
430     char pw0array[1024];
431     krb5_get_init_creds_opt opt;
432     char * server;
433     krb5_principal server_princ, client_princ;
434
435     pw0array[0] = '\0';
436     pw0.data = pw0array;
437     if (password) {
438         pw0.length = strlen(password);
439         if (pw0.length > sizeof(pw0array))
440             return EINVAL;
441         strncpy(pw0.data, password, sizeof(pw0array));
442         if (pw0.length == 0)
443             pw0.length = sizeof(pw0array);
444     } else {
445         pw0.length = sizeof(pw0array);
446     }
447     krb5int_populate_gic_opt(context, &opt,
448                              options, addrs, ktypes,
449                              pre_auth_types);
450     retval = krb5_unparse_name( context, creds->server, &server);
451     if (retval)
452       return (retval);
453     server_princ = creds->server;
454     client_princ = creds->client;
455         retval = krb5_get_init_creds (context,
456                                            creds, creds->client,  
457                                            krb5_prompter_posix,  NULL,
458                                            0, server, &opt,
459                                       krb5_get_as_key_password, &pw0,
460                                       0, ret_as_reply);
461           krb5_free_unparsed_name( context, server);
462         if (retval) {
463           return (retval);
464         }
465         if (creds->server)
466             krb5_free_principal( context, creds->server);
467         if (creds->client)
468             krb5_free_principal( context, creds->client);
469         creds->client = client_princ;
470         creds->server = server_princ;
471         /* store it in the ccache! */
472         if (ccache)
473           if ((retval = krb5_cc_store_cred(context, ccache, creds)))
474             return (retval);
475         return retval;
476   }
477