* gic_pwd.c (krb5_get_as_key_password): If the as_key enctype is already set to
[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(context, client, etype, prompter, prompter_data,
6                          salt, as_key, gak_data)
7      krb5_context context;
8      krb5_principal client;
9      krb5_enctype etype;
10      krb5_prompter_fct prompter;
11      void *prompter_data;
12      krb5_data *salt;
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
23     password = (krb5_data *) gak_data;
24
25     /* If there's already a key of the correct etype, we're done.
26        If the etype is wrong, free the existing key, and make
27        a new one.
28
29        XXX This was the old behavior, and was wrong in hw preauth
30        cases.  Is this new behavior -- always asking -- correct in all
31        cases?  */
32
33     if (as_key->length) {
34         if (as_key->enctype != etype) {
35             krb5_free_keyblock_contents (context, as_key);
36             as_key->length = 0;
37         }
38     }
39
40     if (password->data[0] == '\0') {
41         if (prompter == NULL)
42             return(EIO);
43
44         if ((ret = krb5_unparse_name(context, client, &clientstr)))
45             return(ret);
46
47         strcpy(promptstr, "Password for ");
48         strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1);
49         promptstr[sizeof(promptstr)-1] = '\0';
50
51         free(clientstr);
52
53         prompt.prompt = promptstr;
54         prompt.hidden = 1;
55         prompt.reply = password;
56
57         if (ret = (((*prompter)(context, prompter_data, NULL, NULL,
58                                 1, &prompt))))
59             return(ret);
60     }
61
62     if ((salt->length == -1) && (salt->data == NULL)) {
63         if ((ret = krb5_principal2salt(context, client, &defsalt)))
64             return(ret);
65
66         salt = &defsalt;
67     } else {
68         defsalt.length = 0;
69     }
70
71     ret = krb5_c_string_to_key(context, etype, password, salt, as_key);
72
73     if (defsalt.length)
74         krb5_xfree(defsalt.data);
75
76     return(ret);
77 }
78
79 KRB5_DLLIMP krb5_error_code KRB5_CALLCONV
80 krb5_get_init_creds_password(context, creds, client, password, prompter, data,
81                              start_time, in_tkt_service, options)
82      krb5_context context;
83      krb5_creds *creds;
84      krb5_principal client;
85      char *password;
86      krb5_prompter_fct prompter;
87      void *data;
88      krb5_deltat start_time;
89      char *in_tkt_service;
90      krb5_get_init_creds_opt *options;
91 {
92    krb5_error_code ret, ret2;
93    int master;
94    krb5_kdc_rep *as_reply;
95    int tries;
96    krb5_creds chpw_creds;
97    krb5_get_init_creds_opt chpw_opts;
98    krb5_data pw0, pw1;
99    char banner[1024], pw0array[1024], pw1array[1024];
100    krb5_prompt prompt[2];
101
102    master = 0;
103    as_reply = NULL;
104    memset(&chpw_creds, 0, sizeof(chpw_creds));
105
106    pw0.data = pw0array;
107
108    if (password) {
109       if ((pw0.length = strlen(password)) > sizeof(pw0array)) {
110          ret = EINVAL;
111          goto cleanup;
112       }
113       strcpy(pw0.data, password);
114    } else {
115       pw0.data[0] = '\0';
116       pw0.length = sizeof(pw0array);
117    }
118
119    pw1.data = pw1array;
120    pw1.data[0] = '\0';
121    pw1.length = sizeof(pw1array);
122
123    /* first try: get the requested tkt from any kdc */
124
125    ret = krb5_get_init_creds(context, creds, client, prompter, data,
126                              start_time, in_tkt_service, options,
127                              krb5_get_as_key_password, (void *) &pw0,
128                              &master, &as_reply);
129
130    /* check for success */
131
132    if (ret == 0)
133       goto cleanup;
134
135    /* If all the kdc's are unavailable, or if the error was due to a
136       user interrupt, fail */
137
138    if ((ret == KRB5_KDC_UNREACH) ||
139        (ret == KRB5_LIBOS_PWDINTR))
140       goto cleanup;
141
142    /* if the reply did not come from the master kdc, try again with
143       the master kdc */
144
145    if (!master) {
146       master = 1;
147
148       ret2 = krb5_get_init_creds(context, creds, client, prompter, data,
149                                  start_time, in_tkt_service, options,
150                                  krb5_get_as_key_password, (void *) &pw0,
151                                  &master, &as_reply);
152       
153       if (ret2 == 0) {
154          ret = 0;
155          goto cleanup;
156       }
157
158       /* if the master is unreachable, return the error from the
159          slave we were able to contact */
160
161       if (ret2 == KRB5_KDC_UNREACH)
162          goto cleanup;
163
164       ret = ret2;
165    }
166
167    /* at this point, we have an error from the master.  if the error
168       is not password expired, or if it is but there's no prompter,
169       return this error */
170
171    if ((ret != KRB5KDC_ERR_KEY_EXP) ||
172        (prompter == NULL))
173       goto cleanup;
174
175    /* ok, we have an expired password.  Give the user a few chances
176       to change it */
177
178    /* use a minimal set of options */
179
180    krb5_get_init_creds_opt_init(&chpw_opts);
181    krb5_get_init_creds_opt_set_tkt_life(&chpw_opts, 5*60);
182    krb5_get_init_creds_opt_set_renew_life(&chpw_opts, 0);
183    krb5_get_init_creds_opt_set_forwardable(&chpw_opts, 0);
184    krb5_get_init_creds_opt_set_proxiable(&chpw_opts, 0);
185
186    if ((ret = krb5_get_init_creds(context, &chpw_creds, client,
187                                   prompter, data,
188                                   start_time, "kadmin/changepw", &chpw_opts,
189                                   krb5_get_as_key_password, (void *) &pw0,
190                                   &master, NULL)))
191       goto cleanup;
192
193    prompt[0].prompt = "Enter new password";
194    prompt[0].hidden = 1;
195    prompt[0].reply = &pw0;
196
197    prompt[1].prompt = "Enter it again";
198    prompt[1].hidden = 1;
199    prompt[1].reply = &pw1;
200
201    strcpy(banner, "Password expired.  You must change it now.");
202
203    for (tries = 3; tries; tries--) {
204       pw0.length = sizeof(pw0array);
205       pw1.length = sizeof(pw1array);
206
207       if (ret = ((*prompter)(context, data, 0, banner,
208                              sizeof(prompt)/sizeof(prompt[0]), prompt)))
209          goto cleanup;
210
211       if (strcmp(pw0.data, pw1.data) != 0) {
212          ret = KRB5_LIBOS_BADPWDMATCH;
213          sprintf(banner, "%s.  Please try again.", error_message(ret));
214       } else if (pw0.length == 0) {
215          ret = KRB5_CHPW_PWDNULL;
216          sprintf(banner, "%s.  Please try again.", error_message(ret));
217       } else {
218          int result_code;
219          krb5_data code_string;
220          krb5_data result_string;
221
222          if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
223                                          &result_code, &code_string,
224                                          &result_string)))
225             goto cleanup;
226
227          /* the change succeeded.  go on */
228
229          if (result_code == 0) {
230             krb5_xfree(result_string.data);
231             break;
232          }
233
234          /* set this in case the retry loop falls through */
235
236          ret = KRB5_CHPW_FAIL;
237
238          if (result_code != KRB5_KPASSWD_SOFTERROR) {
239             krb5_xfree(result_string.data);
240             goto cleanup;
241          }
242
243          /* the error was soft, so try again */
244
245          /* 100 is I happen to know that no code_string will be longer
246             than 100 chars */
247
248          if (result_string.length > (sizeof(banner)-100))
249             result_string.length = sizeof(banner)-100;
250
251          sprintf(banner, "%.*s%s%.*s.  Please try again.\n",
252                  code_string.length, code_string.data,
253                  result_string.length?": ":"",
254                  result_string.length, result_string.data);
255
256          krb5_xfree(code_string.data);
257          krb5_xfree(result_string.data);
258       }
259    }
260
261    if (ret)
262       goto cleanup;
263
264    /* the password change was successful.  Get an initial ticket
265       from the master.  this is the last try.  the return from this
266       is final.  */
267
268    ret = krb5_get_init_creds(context, creds, client, prompter, data,
269                              start_time, in_tkt_service, options,
270                              krb5_get_as_key_password, (void *) &pw0,
271                              &master, &as_reply);
272
273 cleanup:
274    /* if getting the password was successful, then check to see if the
275       password is about to expire, and warn if so */
276
277    if (ret == 0) {
278       krb5_timestamp now;
279       int hours;
280
281       /* XXX 7 days should be configurable.  This is all pretty ad hoc,
282          and could probably be improved if I was willing to screw around
283          with timezones, etc. */
284
285       if (prompter &&
286           (in_tkt_service &&
287            (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
288           ((ret = krb5_timeofday(context, &now)) == 0) &&
289           as_reply->enc_part2->key_exp &&
290           ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
291           (hours >= 0)) {
292          if (hours < 1)
293             sprintf(banner,
294                     "Warning: Your password will expire in less than one hour.");
295          else if (hours <= 48)
296             sprintf(banner, "Warning: Your password will expire in %d hour%s.",
297                     hours, (hours == 1)?"":"s");
298          else
299             sprintf(banner, "Warning: Your password will expire in %d days.",
300                     hours/24);
301
302          /* ignore an error here */
303          (*prompter)(context, data, 0, banner, 0, 0);
304       }
305    }
306
307    memset(pw0array, 0, sizeof(pw0array));
308    memset(pw1array, 0, sizeof(pw1array));
309    krb5_free_cred_contents(context, &chpw_creds);
310    if (as_reply)
311       krb5_free_kdc_rep(context, as_reply);
312
313    return(ret);
314 }