Fix bug #6297 - owner of sticky directory cannot delete files created by others.
[samba.git] / source4 / heimdal / kdc / 524.c
1 /*
2  * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of the Institute nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include "kdc_locl.h"
35
36 RCSID("$Id$");
37
38 #include <krb5-v4compat.h>
39
40 /*
41  * fetch the server from `t', returning the name in malloced memory in
42  * `spn' and the entry itself in `server'
43  */
44
45 static krb5_error_code
46 fetch_server (krb5_context context,
47               krb5_kdc_configuration *config,
48               const Ticket *t,
49               char **spn,
50               hdb_entry_ex **server,
51               const char *from)
52 {
53     krb5_error_code ret;
54     krb5_principal sprinc;
55
56     ret = _krb5_principalname2krb5_principal(context, &sprinc,
57                                              t->sname, t->realm);
58     if (ret) {
59         kdc_log(context, config, 0, "_krb5_principalname2krb5_principal: %s",
60                 krb5_get_err_text(context, ret));
61         return ret;
62     }
63     ret = krb5_unparse_name(context, sprinc, spn);
64     if (ret) {
65         krb5_free_principal(context, sprinc);
66         kdc_log(context, config, 0, "krb5_unparse_name: %s",
67                 krb5_get_err_text(context, ret));
68         return ret;
69     }
70     ret = _kdc_db_fetch(context, config, sprinc, HDB_F_GET_SERVER,
71                         NULL, server);
72     krb5_free_principal(context, sprinc);
73     if (ret) {
74         kdc_log(context, config, 0,
75         "Request to convert ticket from %s for unknown principal %s: %s",
76                 from, *spn, krb5_get_err_text(context, ret));
77         if (ret == HDB_ERR_NOENTRY)
78             ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
79         return ret;
80     }
81     return 0;
82 }
83
84 static krb5_error_code
85 log_524 (krb5_context context,
86          krb5_kdc_configuration *config,
87          const EncTicketPart *et,
88          const char *from,
89          const char *spn)
90 {
91     krb5_principal client;
92     char *cpn;
93     krb5_error_code ret;
94
95     ret = _krb5_principalname2krb5_principal(context, &client,
96                                              et->cname, et->crealm);
97     if (ret) {
98         kdc_log(context, config, 0, "_krb5_principalname2krb5_principal: %s",
99                 krb5_get_err_text (context, ret));
100         return ret;
101     }
102     ret = krb5_unparse_name(context, client, &cpn);
103     if (ret) {
104         krb5_free_principal(context, client);
105         kdc_log(context, config, 0, "krb5_unparse_name: %s",
106                 krb5_get_err_text (context, ret));
107         return ret;
108     }
109     kdc_log(context, config, 1, "524-REQ %s from %s for %s", cpn, from, spn);
110     free(cpn);
111     krb5_free_principal(context, client);
112     return 0;
113 }
114
115 static krb5_error_code
116 verify_flags (krb5_context context,
117               krb5_kdc_configuration *config,
118               const EncTicketPart *et,
119               const char *spn)
120 {
121     if(et->endtime < kdc_time){
122         kdc_log(context, config, 0, "Ticket expired (%s)", spn);
123         return KRB5KRB_AP_ERR_TKT_EXPIRED;
124     }
125     if(et->flags.invalid){
126         kdc_log(context, config, 0, "Ticket not valid (%s)", spn);
127         return KRB5KRB_AP_ERR_TKT_NYV;
128     }
129     return 0;
130 }
131
132 /*
133  * set the `et->caddr' to the most appropriate address to use, where
134  * `addr' is the address the request was received from.
135  */
136
137 static krb5_error_code
138 set_address (krb5_context context,
139              krb5_kdc_configuration *config,
140              EncTicketPart *et,
141              struct sockaddr *addr,
142              const char *from)
143 {
144     krb5_error_code ret;
145     krb5_address *v4_addr;
146
147     v4_addr = malloc (sizeof(*v4_addr));
148     if (v4_addr == NULL)
149         return ENOMEM;
150
151     ret = krb5_sockaddr2address(context, addr, v4_addr);
152     if(ret) {
153         free (v4_addr);
154         kdc_log(context, config, 0, "Failed to convert address (%s)", from);
155         return ret;
156     }
157         
158     if (et->caddr && !krb5_address_search (context, v4_addr, et->caddr)) {
159         kdc_log(context, config, 0, "Incorrect network address (%s)", from);
160         krb5_free_address(context, v4_addr);
161         free (v4_addr);
162         return KRB5KRB_AP_ERR_BADADDR;
163     }
164     if(v4_addr->addr_type == KRB5_ADDRESS_INET) {
165         /* we need to collapse the addresses in the ticket to a
166            single address; best guess is to use the address the
167            connection came from */
168         
169         if (et->caddr != NULL) {
170             free_HostAddresses(et->caddr);
171         } else {
172             et->caddr = malloc (sizeof (*et->caddr));
173             if (et->caddr == NULL) {
174                 krb5_free_address(context, v4_addr);
175                 free(v4_addr);
176                 return ENOMEM;
177             }
178         }
179         et->caddr->val = v4_addr;
180         et->caddr->len = 1;
181     } else {
182         krb5_free_address(context, v4_addr);
183         free(v4_addr);
184     }
185     return 0;
186 }
187
188
189 static krb5_error_code
190 encrypt_v4_ticket(krb5_context context,
191                   krb5_kdc_configuration *config,
192                   void *buf,
193                   size_t len,
194                   krb5_keyblock *skey,
195                   EncryptedData *reply)
196 {
197     krb5_crypto crypto;
198     krb5_error_code ret;
199     ret = krb5_crypto_init(context, skey, ETYPE_DES_PCBC_NONE, &crypto);
200     if (ret) {
201         free(buf);
202         kdc_log(context, config, 0, "krb5_crypto_init failed: %s",
203                 krb5_get_err_text(context, ret));
204         return ret;
205     }
206
207     ret = krb5_encrypt_EncryptedData(context,
208                                      crypto,
209                                      KRB5_KU_TICKET,
210                                      buf,
211                                      len,
212                                      0,
213                                      reply);
214     krb5_crypto_destroy(context, crypto);
215     if(ret) {
216         kdc_log(context, config, 0, "Failed to encrypt data: %s",
217                 krb5_get_err_text(context, ret));
218         return ret;
219     }
220     return 0;
221 }
222
223 static krb5_error_code
224 encode_524_response(krb5_context context,
225                     krb5_kdc_configuration *config,
226                     const char *spn, const EncTicketPart et,
227                     const Ticket *t, hdb_entry_ex *server,
228                     EncryptedData *ticket, int *kvno)
229 {
230     krb5_error_code ret;
231     int use_2b;
232     size_t len;
233
234     use_2b = krb5_config_get_bool(context, NULL, "kdc", "use_2b", spn, NULL);
235     if(use_2b) {
236         ASN1_MALLOC_ENCODE(EncryptedData,
237                            ticket->cipher.data, ticket->cipher.length,
238                            &t->enc_part, &len, ret);
239         
240         if (ret) {
241             kdc_log(context, config, 0,
242                     "Failed to encode v4 (2b) ticket (%s)", spn);
243             return ret;
244         }
245         
246         ticket->etype = 0;
247         ticket->kvno = NULL;
248         *kvno = 213; /* 2b's use this magic kvno */
249     } else {
250         unsigned char buf[MAX_KTXT_LEN + 4 * 4];
251         Key *skey;
252         
253         if (!config->enable_v4_cross_realm && strcmp (et.crealm, t->realm) != 0) {
254             kdc_log(context, config, 0, "524 cross-realm %s -> %s disabled", et.crealm,
255                     t->realm);
256             return KRB5KDC_ERR_POLICY;
257         }
258
259         ret = _kdc_encode_v4_ticket(context, config,
260                                     buf + sizeof(buf) - 1, sizeof(buf),
261                                     &et, &t->sname, &len);
262         if(ret){
263             kdc_log(context, config, 0,
264                     "Failed to encode v4 ticket (%s)", spn);
265             return ret;
266         }
267         ret = _kdc_get_des_key(context, server, TRUE, FALSE, &skey);
268         if(ret){
269             kdc_log(context, config, 0,
270                     "no suitable DES key for server (%s)", spn);
271             return ret;
272         }
273         ret = encrypt_v4_ticket(context, config, buf + sizeof(buf) - len, len,
274                                 &skey->key, ticket);
275         if(ret){
276             kdc_log(context, config, 0,
277                     "Failed to encrypt v4 ticket (%s)", spn);
278             return ret;
279         }
280         *kvno = server->entry.kvno;
281     }
282
283     return 0;
284 }
285
286 /*
287  * process a 5->4 request, based on `t', and received `from, addr',
288  * returning the reply in `reply'
289  */
290
291 krb5_error_code
292 _kdc_do_524(krb5_context context,
293             krb5_kdc_configuration *config,
294             const Ticket *t, krb5_data *reply,
295             const char *from, struct sockaddr *addr)
296 {
297     krb5_error_code ret = 0;
298     krb5_crypto crypto;
299     hdb_entry_ex *server = NULL;
300     Key *skey;
301     krb5_data et_data;
302     EncTicketPart et;
303     EncryptedData ticket;
304     krb5_storage *sp;
305     char *spn = NULL;
306     unsigned char buf[MAX_KTXT_LEN + 4 * 4];
307     size_t len;
308     int kvno = 0;
309
310     if(!config->enable_524) {
311         ret = KRB5KDC_ERR_POLICY;
312         kdc_log(context, config, 0,
313                 "Rejected ticket conversion request from %s", from);
314         goto out;
315     }
316
317     ret = fetch_server (context, config, t, &spn, &server, from);
318     if (ret) {
319         goto out;
320     }
321
322     ret = hdb_enctype2key(context, &server->entry, t->enc_part.etype, &skey);
323     if(ret){
324         kdc_log(context, config, 0,
325                 "No suitable key found for server (%s) from %s", spn, from);
326         goto out;
327     }
328     ret = krb5_crypto_init(context, &skey->key, 0, &crypto);
329     if (ret) {
330         kdc_log(context, config, 0, "krb5_crypto_init failed: %s",
331                 krb5_get_err_text(context, ret));
332         goto out;
333     }
334     ret = krb5_decrypt_EncryptedData (context,
335                                       crypto,
336                                       KRB5_KU_TICKET,
337                                       &t->enc_part,
338                                       &et_data);
339     krb5_crypto_destroy(context, crypto);
340     if(ret){
341         kdc_log(context, config, 0,
342                 "Failed to decrypt ticket from %s for %s", from, spn);
343         goto out;
344     }
345     ret = krb5_decode_EncTicketPart(context, et_data.data, et_data.length,
346                                     &et, &len);
347     krb5_data_free(&et_data);
348     if(ret){
349         kdc_log(context, config, 0,
350                 "Failed to decode ticket from %s for %s", from, spn);
351         goto out;
352     }
353
354     ret = log_524 (context, config, &et, from, spn);
355     if (ret) {
356         free_EncTicketPart(&et);
357         goto out;
358     }
359
360     ret = verify_flags (context, config, &et, spn);
361     if (ret) {
362         free_EncTicketPart(&et);
363         goto out;
364     }
365
366     ret = set_address (context, config, &et, addr, from);
367     if (ret) {
368         free_EncTicketPart(&et);
369         goto out;
370     }
371
372     ret = encode_524_response(context, config, spn, et, t,
373                               server, &ticket, &kvno);
374     free_EncTicketPart(&et);
375
376  out:
377     /* make reply */
378     memset(buf, 0, sizeof(buf));
379     sp = krb5_storage_from_mem(buf, sizeof(buf));
380     if (sp) {
381         krb5_store_int32(sp, ret);
382         if(ret == 0){
383             krb5_store_int32(sp, kvno);
384             krb5_store_data(sp, ticket.cipher);
385             /* Aargh! This is coded as a KTEXT_ST. */
386             krb5_storage_seek(sp, MAX_KTXT_LEN - ticket.cipher.length, SEEK_CUR);
387             krb5_store_int32(sp, 0); /* mbz */
388             free_EncryptedData(&ticket);
389         }
390         ret = krb5_storage_to_data(sp, reply);
391         reply->length = krb5_storage_seek(sp, 0, SEEK_CUR);
392         krb5_storage_free(sp);
393     } else
394         krb5_data_zero(reply);
395     if(spn)
396         free(spn);
397     if(server)
398         _kdc_free_ent (context, server);
399     return ret;
400 }