Merge branch 'v4-0-test' of ssh://git.samba.org/data/git/samba into 4-0-local
[kai/samba.git] / source4 / heimdal / lib / krb5 / keytab_krb4.c
1 /*
2  * Copyright (c) 1997 - 2002 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 "krb5_locl.h"
35
36 RCSID("$Id: keytab_krb4.c 22532 2008-01-27 11:59:18Z lha $");
37
38 struct krb4_kt_data {
39     char *filename;
40 };
41
42 static krb5_error_code
43 krb4_kt_resolve(krb5_context context, const char *name, krb5_keytab id)
44 {
45     struct krb4_kt_data *d;
46
47     d = malloc (sizeof(*d));
48     if (d == NULL) {
49         krb5_set_error_string (context, "malloc: out of memory");
50         return ENOMEM;
51     }
52     d->filename = strdup (name);
53     if (d->filename == NULL) {
54         free(d);
55         krb5_set_error_string (context, "malloc: out of memory");
56         return ENOMEM;
57     }
58     id->data = d;
59     return 0;
60 }
61
62 static krb5_error_code
63 krb4_kt_get_name (krb5_context context,
64                   krb5_keytab id,
65                   char *name,
66                   size_t name_sz)
67 {
68     struct krb4_kt_data *d = id->data;
69
70     strlcpy (name, d->filename, name_sz);
71     return 0;
72 }
73
74 static krb5_error_code
75 krb4_kt_close (krb5_context context,
76                krb5_keytab id)
77 {
78     struct krb4_kt_data *d = id->data;
79
80     free (d->filename);
81     free (d);
82     return 0;
83 }
84
85 struct krb4_cursor_extra_data {
86     krb5_keytab_entry entry;
87     int num;
88 };
89
90 static int
91 open_flock(const char *filename, int flags, int mode)
92 {
93     int lock_mode;
94     int tries = 0;
95     int fd = open(filename, flags, mode);
96     if(fd < 0)
97         return fd;
98     if((flags & O_ACCMODE) == O_RDONLY)
99         lock_mode = LOCK_SH | LOCK_NB;
100     else
101         lock_mode = LOCK_EX | LOCK_NB;
102     while(flock(fd, lock_mode) < 0) {
103         if(++tries < 5) {
104             sleep(1);
105         } else {
106             close(fd);
107             return -1;
108         }
109     }
110     return fd;
111 }
112
113
114
115 static krb5_error_code
116 krb4_kt_start_seq_get_int (krb5_context context,
117                            krb5_keytab id,
118                            int flags,
119                            krb5_kt_cursor *c)
120 {
121     struct krb4_kt_data *d = id->data;
122     struct krb4_cursor_extra_data *ed;
123     int ret;
124
125     ed = malloc (sizeof(*ed));
126     if (ed == NULL) {
127         krb5_set_error_string (context, "malloc: out of memory");
128         return ENOMEM;
129     }
130     ed->entry.principal = NULL;
131     ed->num = -1;
132     c->data = ed;
133     c->fd = open_flock (d->filename, flags, 0);
134     if (c->fd < 0) {
135         ret = errno;
136         free (ed);
137         krb5_set_error_string(context, "keytab krb5 open %s failed: %s", 
138                               d->filename, strerror(ret));
139         return ret;
140     }
141     c->sp = krb5_storage_from_fd(c->fd);
142     if(c->sp == NULL) {
143         close(c->fd);
144         free(ed);
145         krb5_set_error_string(context, "malloc: out of memory");
146         return ENOMEM;
147     }
148     krb5_storage_set_eof_code(c->sp, KRB5_KT_END);
149     return 0;
150 }
151
152 static krb5_error_code
153 krb4_kt_start_seq_get (krb5_context context,
154                        krb5_keytab id,
155                        krb5_kt_cursor *c)
156 {
157     return krb4_kt_start_seq_get_int (context, id, O_BINARY | O_RDONLY, c);
158 }
159
160 static krb5_error_code
161 read_v4_entry (krb5_context context,
162                struct krb4_kt_data *d,
163                krb5_kt_cursor *c,
164                struct krb4_cursor_extra_data *ed)
165 {
166     unsigned char des_key[8];
167     krb5_error_code ret;
168     char *service, *instance, *realm;
169     int8_t kvno;
170
171     ret = krb5_ret_stringz(c->sp, &service);
172     if (ret)
173         return ret;
174     ret = krb5_ret_stringz(c->sp, &instance);
175     if (ret) {
176         free (service);
177         return ret;
178     }
179     ret = krb5_ret_stringz(c->sp, &realm);
180     if (ret) {
181         free (service);
182         free (instance);
183         return ret;
184     }
185     ret = krb5_425_conv_principal (context, service, instance, realm,
186                                    &ed->entry.principal);
187     free (service);
188     free (instance);
189     free (realm);
190     if (ret)
191         return ret;
192     ret = krb5_ret_int8(c->sp, &kvno);
193     if (ret) {
194         krb5_free_principal (context, ed->entry.principal);
195         return ret;
196     }
197     ret = krb5_storage_read(c->sp, des_key, sizeof(des_key));
198     if (ret < 0) {
199         krb5_free_principal(context, ed->entry.principal);
200         return ret;
201     }
202     if (ret < 8) {
203         krb5_free_principal(context, ed->entry.principal);
204         return EINVAL;
205     }
206     ed->entry.vno = kvno;
207     ret = krb5_data_copy (&ed->entry.keyblock.keyvalue,
208                           des_key, sizeof(des_key));
209     if (ret)
210         return ret;
211     ed->entry.timestamp = time(NULL);
212     ed->num = 0;
213     return 0;
214 }
215
216 static krb5_error_code
217 krb4_kt_next_entry (krb5_context context,
218                     krb5_keytab id,
219                     krb5_keytab_entry *entry,
220                     krb5_kt_cursor *c)
221 {
222     krb5_error_code ret;
223     struct krb4_kt_data *d = id->data;
224     struct krb4_cursor_extra_data *ed = c->data;
225     const krb5_enctype keytypes[] = {ETYPE_DES_CBC_MD5,
226                                      ETYPE_DES_CBC_MD4,
227                                      ETYPE_DES_CBC_CRC};
228
229     if (ed->num == -1) {
230         ret = read_v4_entry (context, d, c, ed);
231         if (ret)
232             return ret;
233     }
234     ret = krb5_kt_copy_entry_contents (context,
235                                        &ed->entry,
236                                        entry);
237     if (ret)
238         return ret;
239     entry->keyblock.keytype = keytypes[ed->num];
240     if (++ed->num == 3) {
241         krb5_kt_free_entry (context, &ed->entry);
242         ed->num = -1;
243     }
244     return 0;
245 }
246
247 static krb5_error_code
248 krb4_kt_end_seq_get (krb5_context context,
249                      krb5_keytab id,
250                      krb5_kt_cursor *c)
251 {
252     struct krb4_cursor_extra_data *ed = c->data;
253
254     krb5_storage_free (c->sp);
255     if (ed->num != -1)
256         krb5_kt_free_entry (context, &ed->entry);
257     free (c->data);
258     close (c->fd);
259     return 0;
260 }
261
262 static krb5_error_code
263 krb4_store_keytab_entry(krb5_context context, 
264                         krb5_keytab_entry *entry, 
265                         krb5_storage *sp)
266 {
267     krb5_error_code ret;
268 #define ANAME_SZ 40
269 #define INST_SZ 40
270 #define REALM_SZ 40
271     char service[ANAME_SZ];
272     char instance[INST_SZ];
273     char realm[REALM_SZ];
274     ret = krb5_524_conv_principal (context, entry->principal,
275                                    service, instance, realm);
276     if (ret)
277         return ret;
278     if (entry->keyblock.keyvalue.length == 8
279         && entry->keyblock.keytype == ETYPE_DES_CBC_MD5) {
280         ret = krb5_store_stringz(sp, service);
281         ret = krb5_store_stringz(sp, instance);
282         ret = krb5_store_stringz(sp, realm);
283         ret = krb5_store_int8(sp, entry->vno);
284         ret = krb5_storage_write(sp, entry->keyblock.keyvalue.data, 8);
285     }
286     return 0;
287 }
288
289 static krb5_error_code
290 krb4_kt_add_entry (krb5_context context,
291                    krb5_keytab id,
292                    krb5_keytab_entry *entry)
293 {
294     struct krb4_kt_data *d = id->data;
295     krb5_storage *sp;
296     krb5_error_code ret;
297     int fd;
298
299     fd = open_flock (d->filename, O_WRONLY | O_APPEND | O_BINARY, 0);
300     if (fd < 0) {
301         fd = open_flock (d->filename,
302                    O_WRONLY | O_APPEND | O_BINARY | O_CREAT, 0600);
303         if (fd < 0) {
304             ret = errno;
305             krb5_set_error_string(context, "open(%s): %s", d->filename,
306                                   strerror(ret));
307             return ret;
308         }
309     }
310     sp = krb5_storage_from_fd(fd);
311     if(sp == NULL) {
312         close(fd);
313         return ENOMEM;
314     }
315     krb5_storage_set_eof_code(sp, KRB5_KT_END);
316     ret = krb4_store_keytab_entry(context, entry, sp);
317     krb5_storage_free(sp);
318     if(close (fd) < 0)
319         return errno;
320     return ret;
321 }
322
323 static krb5_error_code
324 krb4_kt_remove_entry(krb5_context context,
325                      krb5_keytab id,
326                      krb5_keytab_entry *entry)
327 {
328     struct krb4_kt_data *d = id->data;
329     krb5_error_code ret;
330     krb5_keytab_entry e;
331     krb5_kt_cursor cursor;
332     krb5_storage *sp;
333     int remove_flag = 0;
334     
335     sp = krb5_storage_emem();
336     if (sp == NULL) {
337         krb5_set_error_string(context, "malloc: out of memory");
338         return ENOMEM;
339     }
340     ret = krb5_kt_start_seq_get(context, id, &cursor);
341     if (ret) {
342         krb5_storage_free(sp);
343         return ret;
344     }   
345     while(krb5_kt_next_entry(context, id, &e, &cursor) == 0) {
346         if(!krb5_kt_compare(context, &e, entry->principal, 
347                             entry->vno, entry->keyblock.keytype)) {
348             ret = krb4_store_keytab_entry(context, &e, sp);
349             if(ret) {
350                 krb5_kt_free_entry(context, &e);
351                 krb5_storage_free(sp);
352                 return ret;
353             }
354         } else
355             remove_flag = 1;
356         krb5_kt_free_entry(context, &e);
357     }
358     krb5_kt_end_seq_get(context, id, &cursor);
359     if(remove_flag) {
360         int fd;
361         unsigned char buf[1024];
362         ssize_t n;
363         krb5_data data;
364         struct stat st;
365
366         krb5_storage_to_data(sp, &data);
367         krb5_storage_free(sp);
368
369         fd = open_flock (d->filename, O_RDWR | O_BINARY, 0);
370         if(fd < 0) {
371             memset(data.data, 0, data.length);
372             krb5_data_free(&data);
373             if(errno == EACCES || errno == EROFS) {
374                 krb5_set_error_string(context, "failed to open %s for writing",
375                                       d->filename);
376                 return KRB5_KT_NOWRITE;
377             }
378             return errno;
379         }
380
381         if(write(fd, data.data, data.length) != data.length) {
382             memset(data.data, 0, data.length);
383             krb5_data_free(&data);
384             close(fd);
385             krb5_set_error_string(context, "failed writing to file %s", 
386                                   d->filename);
387             return errno;
388         }
389         memset(data.data, 0, data.length);
390         if(fstat(fd, &st) < 0) {
391             krb5_data_free(&data);
392             close(fd);
393             krb5_set_error_string(context, "failed getting size of file %s", 
394                                   d->filename);
395             return errno;
396         }
397         st.st_size -= data.length;
398         memset(buf, 0, sizeof(buf));
399         while(st.st_size > 0) {
400             n = min(st.st_size, sizeof(buf));
401             n = write(fd, buf, n);
402             if(n <= 0) {
403                 krb5_data_free(&data);
404                 close(fd);
405                 krb5_set_error_string(context, "failed writing to file %s",
406                                       d->filename);
407                 return errno;
408                 
409             }
410             st.st_size -= n;
411         }
412         if(ftruncate(fd, data.length) < 0) {
413             krb5_data_free(&data);
414             close(fd);
415             krb5_set_error_string(context, "failed truncating file %s",
416                                   d->filename);
417             return errno;
418         }
419         krb5_data_free(&data);
420         if(close(fd) < 0) {
421             krb5_set_error_string(context, "error closing %s",
422                                   d->filename);
423             return errno;
424         }
425         return 0;
426     } else {
427         krb5_storage_free(sp);
428         krb5_set_error_string(context, "Keytab entry not found");
429         return KRB5_KT_NOTFOUND;
430     }
431 }
432
433
434 const krb5_kt_ops krb4_fkt_ops = {
435     "krb4",
436     krb4_kt_resolve,
437     krb4_kt_get_name,
438     krb4_kt_close,
439     NULL,                       /* get */
440     krb4_kt_start_seq_get,
441     krb4_kt_next_entry,
442     krb4_kt_end_seq_get,
443     krb4_kt_add_entry,          /* add_entry */
444     krb4_kt_remove_entry        /* remove_entry */
445 };
446
447 const krb5_kt_ops krb5_srvtab_fkt_ops = {
448     "SRVTAB",
449     krb4_kt_resolve,
450     krb4_kt_get_name,
451     krb4_kt_close,
452     NULL,                       /* get */
453     krb4_kt_start_seq_get,
454     krb4_kt_next_entry,
455     krb4_kt_end_seq_get,
456     krb4_kt_add_entry,          /* add_entry */
457     krb4_kt_remove_entry        /* remove_entry */
458 };