f126641b4a93bbbca0b8924c472d03ac1413692a
[krb5.git] / src / krb524 / krb524d.c
1 /*
2  * Copyright (C) 2002 by the Massachusetts Institute of Technology.
3  * All rights reserved.
4  *
5  * Export of this software from the United States of America may
6  *   require a specific license from the United States Government.
7  *   It is the responsibility of any person or organization contemplating
8  *   export to obtain such a license before exporting.
9  * 
10  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
11  * distribute this software and its documentation for any purpose and
12  * without fee is hereby granted, provided that the above copyright
13  * notice appear in all copies and that both that copyright notice and
14  * this permission notice appear in supporting documentation, and that
15  * the name of M.I.T. not be used in advertising or publicity pertaining
16  * to distribution of the software without specific, written prior
17  * permission.  Furthermore if you modify this software you must label
18  * your software as modified software and not distribute it in such a
19  * fashion that it might be confused with the original M.I.T. software.
20  * M.I.T. makes no representations about the suitability of
21  * this software for any purpose.  It is provided "as is" without express
22  * or implied warranty.
23  * Copyright 1994 by OpenVision Technologies, Inc.
24  * 
25  * Permission to use, copy, modify, distribute, and sell this software
26  * and its documentation for any purpose is hereby granted without fee,
27  * provided that the above copyright notice appears in all copies and
28  * that both that copyright notice and this permission notice appear in
29  * supporting documentation, and that the name of OpenVision not be used
30  * in advertising or publicity pertaining to distribution of the software
31  * without specific, written prior permission. OpenVision makes no
32  * representations about the suitability of this software for any
33  * purpose.  It is provided "as is" without express or implied warranty.
34  * 
35  * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
36  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
37  * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
38  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
39  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
40  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
41  * PERFORMANCE OF THIS SOFTWARE.
42  */
43
44 #include <krb5.h>
45 #include <kadm5/admin.h>
46 #include <krb5/adm_proto.h>
47 #include <com_err.h>
48 #include <stdarg.h>
49
50 #include <assert.h>
51 #include <stdio.h>
52 #ifdef HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif
55 #include <string.h>
56 #include <signal.h>
57 #include <sys/types.h>
58 #include <sys/time.h>
59 #include <sys/signal.h>
60 #include <netinet/in.h>
61
62 #include <krb.h>
63 #include "krb524d.h"
64
65 #if defined(NEED_DAEMON_PROTO)
66 extern int daemon(int, int);
67 #endif
68
69 #define TIMEOUT 60
70 #define TKT_BUFSIZ 2048
71 #define MSGSIZE 8192
72
73 char *whoami;
74 int signalled = 0;
75 static int debug = 0;
76 void *handle = NULL;
77
78 int use_keytab, use_master;
79 int allow_v4_crossrealm = 0;
80 char *keytab = NULL;
81 krb5_keytab kt;
82
83 void init_keytab(krb5_context), 
84     init_master(krb5_context, kadm5_config_params *),
85     cleanup_and_exit(int, krb5_context);
86 krb5_error_code do_connection(int, krb5_context);
87 krb5_error_code lookup_service_key(krb5_context, krb5_principal, 
88                                    krb5_enctype, krb5_kvno, 
89                                    krb5_keyblock *, krb5_kvno *);
90 krb5_error_code  kdc_get_server_key(krb5_context, krb5_principal, 
91                                     krb5_keyblock *, krb5_kvno *,
92                                     krb5_enctype, krb5_kvno);
93
94 static krb5_error_code
95 handle_classic_v4 (krb5_context context, krb5_ticket *v5tkt,
96                    struct sockaddr_in *saddr,
97                    krb5_data *tktdata, krb5_kvno *v4kvno);
98 static krb5_error_code 
99 afs_return_v4(krb5_context, const krb5_principal , int *use_v5);
100
101 static void usage(context)
102      krb5_context context;
103 {
104      fprintf(stderr, "Usage: %s [-k[eytab]] [-m[aster] [-r realm]] [-nofork] [-p portnum]\n", whoami);
105      cleanup_and_exit(1, context);
106 }
107
108 static RETSIGTYPE request_exit(signo)
109      int signo;
110 {
111      signalled = 1;
112 }
113
114 int (*encode_v4tkt)(KTEXT, char *, unsigned int *) = 0;
115
116 int main(argc, argv)
117      int argc;
118      char **argv;
119 {
120      struct servent *serv;
121      struct sockaddr_in saddr;
122      struct timeval timeout;
123      int ret, s, nofork;
124      fd_set rfds;
125      krb5_context context;
126      krb5_error_code retval;
127      kadm5_config_params config_params;
128      unsigned long port = 0;
129
130      whoami = ((whoami = strrchr(argv[0], '/')) ? whoami + 1 : argv[0]);
131
132      retval = krb5_init_context(&context);
133      if (retval) {
134              com_err(whoami, retval, "while initializing krb5");
135              exit(1);
136      }
137
138      {
139          krb5int_access k5int;
140          retval = krb5int_accessor(&k5int, KRB5INT_ACCESS_VERSION);
141          if (retval != 0) {
142              com_err(whoami, retval,
143                      "while accessing krb5 library internal support");
144              exit(1);
145          }
146          encode_v4tkt = k5int.krb524_encode_v4tkt;
147          if (encode_v4tkt == NULL) {
148              com_err(whoami, 0,
149                      "krb4 support disabled in krb5 support library");
150              exit(1);
151          }
152      }
153
154      argv++; argc--;
155      use_master = use_keytab = nofork = 0;
156      config_params.mask = 0;
157      
158      while (argc) {
159        if (strncmp(*argv, "-X", 2) == 0) {
160          allow_v4_crossrealm = 1;
161        }
162        else if (strncmp(*argv, "-k", 2) == 0)
163                use_keytab = 1;
164           else if (strncmp(*argv, "-m", 2) == 0)
165                use_master = 1;
166           else if (strcmp(*argv, "-nofork") == 0)
167                nofork = 1;
168           else if (strcmp(*argv, "-r") == 0) {
169                argv++; argc--;
170                if (argc == 0 || !use_master)
171                     usage(context);
172                config_params.mask |= KADM5_CONFIG_REALM;
173                config_params.realm = *argv;
174           }
175           else if (strcmp(*argv, "-p") == 0) {
176               char *endptr = 0;
177               argv++; argc--;
178               if (argc == 0)
179                   usage (context);
180               if (port != 0) {
181                   com_err (whoami, 0,
182                            "port number may only be specified once");
183                   exit (1);
184               }
185               port = strtoul (*argv, &endptr, 0);
186               if (*endptr != '\0' || port > 65535 || port == 0) {
187                   com_err (whoami, 0,
188                            "invalid port number %s, must be 1..65535\n",
189                            *argv);
190                   exit (1);
191               }
192           }
193           else
194                break;
195           argv++; argc--;
196      }
197      if (argc || use_keytab + use_master > 1 ||
198          use_keytab + use_master == 0) {
199           use_keytab = use_master = 0;
200           usage(context);
201      }
202      
203      signal(SIGINT, request_exit);
204      signal(SIGHUP, SIG_IGN);
205      signal(SIGTERM, request_exit);
206
207      krb5_klog_init(context, "krb524d", whoami, !nofork);
208
209      if (use_keytab)
210           init_keytab(context);
211      if (use_master)
212           init_master(context, &config_params);
213
214      memset((char *) &saddr, 0, sizeof(struct sockaddr_in));
215      saddr.sin_family = AF_INET;
216      saddr.sin_addr.s_addr = INADDR_ANY;
217      if (port == 0) {
218          serv = getservbyname(KRB524_SERVICE, "udp");
219          if (serv == NULL) {
220              com_err(whoami, 0, "service entry `%s' not found, using %d",
221                      KRB524_SERVICE, KRB524_PORT);
222              saddr.sin_port = htons(KRB524_PORT);
223          } else
224              saddr.sin_port = serv->s_port;
225      } else
226          saddr.sin_port = htons(port);
227           
228      if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
229           com_err(whoami, errno, "creating main socket");
230           cleanup_and_exit(1, context);
231      }
232      if ((ret = bind(s, (struct sockaddr *) &saddr,
233                      sizeof(struct sockaddr_in))) < 0) {
234           com_err(whoami, errno, "binding main socket");
235           cleanup_and_exit(1, context);
236      }
237      if (!nofork && daemon(0, 0)) {
238          com_err(whoami, errno, "while detaching from tty");
239          cleanup_and_exit(1, context);
240      }
241
242      while (1) {
243           FD_ZERO(&rfds);
244           FD_SET(s, &rfds);
245           timeout.tv_sec = TIMEOUT;
246           timeout.tv_usec = 0;
247
248           ret = select(s+1, &rfds, NULL, NULL, &timeout);
249           if (signalled)
250                cleanup_and_exit(0, context);
251           else if (ret == 0) {
252                if (use_master) {
253                     ret = kadm5_flush(handle);
254                     if (ret && ret != KRB5_KDB_DBNOTINITED) {
255                          com_err(whoami, ret, "closing kerberos database");
256                          cleanup_and_exit(1, context);
257                     }
258                }
259           } else if (ret < 0 && errno != EINTR) {
260                com_err(whoami, errno, "in select");
261                cleanup_and_exit(1, context);
262           } else if (FD_ISSET(s, &rfds)) {
263                if (debug)
264                     printf("received packet\n");
265                if ((ret = do_connection(s, context))) {
266                     com_err(whoami, ret, "handling packet");
267                }
268           } else
269                com_err(whoami, 0, "impossible situation occurred!");
270      }
271
272      cleanup_and_exit(0, context);
273 }
274
275 void cleanup_and_exit(ret, context)
276      int ret;
277      krb5_context context;
278 {
279      if (use_master && handle) {
280           (void) kadm5_destroy(handle);
281      }
282      if (use_keytab && kt) krb5_kt_close(context, kt);
283      krb5_klog_close(context);
284      krb5_free_context(context);
285      exit(ret);
286 }
287
288 void init_keytab(context)
289      krb5_context context;
290 {
291      int ret;
292      use_keytab = 0;
293      if (keytab == NULL) {
294           if ((ret = krb5_kt_default(context, &kt))) {
295                com_err(whoami, ret, "while opening default keytab");
296                cleanup_and_exit(1, context);
297           }
298      } else {
299           if ((ret = krb5_kt_resolve(context, keytab, &kt))) {
300                com_err(whoami, ret, "while resolving keytab %s",
301                        keytab);
302                cleanup_and_exit(1, context);
303           }
304      }
305      use_keytab = 1;            /* now safe to close keytab */
306 }
307
308 void init_master(context, params)
309      krb5_context context;
310      kadm5_config_params *params;
311 {
312      int ret;
313
314      use_master = 0;
315      if ((ret = kadm5_init(whoami, NULL, KADM5_ADMIN_SERVICE, params,
316                            KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL,
317                            &handle))) {
318           com_err(whoami, ret, "initializing kadm5 library");
319           cleanup_and_exit(1, context);
320      }
321      use_master = 1;            /* now safe to close kadm5 */
322 }
323
324 krb5_error_code do_connection(s, context)
325      int s;
326      krb5_context context;
327 {
328      struct sockaddr saddr;
329      krb5_ticket *v5tkt = 0;
330      krb5_data msgdata, tktdata;
331      char msgbuf[MSGSIZE], tktbuf[TKT_BUFSIZ], *p;
332      int ret;
333      socklen_t saddrlen;
334      krb5_int32 n; /* Must be 4 bytes */
335      krb5_kvno v4kvno;
336
337      msgdata.data = msgbuf;
338      msgdata.length = MSGSIZE;
339      tktdata.data = tktbuf;
340      tktdata.length = TKT_BUFSIZ;
341      saddrlen = sizeof(struct sockaddr);
342      ret = recvfrom(s, msgdata.data, (int) msgdata.length, 0, &saddr, &saddrlen);
343      if (ret < 0) {
344        /* if recvfrom fails, we probably don't have a valid saddr to 
345           use for the reply, so don't even try to respond. */
346        return errno;
347      }
348      if (debug)
349           printf("message received\n");
350
351      if ((ret = decode_krb5_ticket(&msgdata, &v5tkt))) {
352           switch (ret) {
353           case KRB5KDC_ERR_BAD_PVNO:
354           case ASN1_MISPLACED_FIELD:
355           case ASN1_MISSING_FIELD:
356           case ASN1_BAD_ID:
357           case KRB5_BADMSGTYPE:
358             /* don't even answer parse errors */
359             return ret;
360             break;
361           default:
362             /* try and recognize our own error packet */
363             if (msgdata.length == sizeof(krb5_int32))
364               return KRB5_BADMSGTYPE;
365             else
366               goto error;
367           }
368      }
369      if (debug)
370           printf("V5 ticket decoded\n");
371      
372      if( krb5_princ_size(context, v5tkt->server) >= 1
373          &&krb5_princ_component(context, v5tkt->server, 0)->length == 3
374          &&strncmp(krb5_princ_component(context, v5tkt->server, 0)->data,
375                    "afs", 3) == 0) {
376          krb5_data *enc_part;
377          int use_v5;
378          if ((ret = afs_return_v4(context, v5tkt->server,
379                                   &use_v5)) != 0) 
380              goto error;
381          if ((ret = encode_krb5_enc_data( &v5tkt->enc_part, &enc_part)) != 0) 
382              goto error;
383          if (!(use_v5 )|| enc_part->length >= 344) {
384              krb5_free_data(context, enc_part);
385              if ((ret = handle_classic_v4(context, v5tkt,
386                                          (struct sockaddr_in *) &saddr, &tktdata,
387                                          &v4kvno)) != 0)
388                  goto error;
389          } else {
390            KTEXT_ST fake_v4tkt;
391            fake_v4tkt.mbz = 0;
392            fake_v4tkt.length = enc_part->length;
393            memcpy(fake_v4tkt.dat, enc_part->data, enc_part->length);
394              v4kvno = (0x100-0x2b); /*protocol constant indicating  v5
395                                      * enc part only*/
396              krb5_free_data(context, enc_part);
397              ret = encode_v4tkt(&fake_v4tkt, tktdata.data, &tktdata.length);
398          }
399      } else {
400          if ((ret = handle_classic_v4(context, v5tkt,
401                                      (struct sockaddr_in *) &saddr, &tktdata,
402                                      &v4kvno)) != 0)
403              goto error;
404      }
405      
406         error:
407      /* create the reply */
408      p = msgdata.data;
409      msgdata.length = 0;
410      
411      n = htonl(ret);
412      memcpy(p, (char *) &n, sizeof(krb5_int32));
413      p += sizeof(krb5_int32);
414      msgdata.length += sizeof(krb5_int32);
415
416      if (ret)
417           goto write_msg;
418
419      n = htonl(v4kvno);
420      memcpy(p, (char *) &n, sizeof(krb5_int32));
421      p += sizeof(krb5_int32);
422      msgdata.length += sizeof(krb5_int32);
423
424      memcpy(p, tktdata.data, tktdata.length);
425      p += tktdata.length;
426      msgdata.length += tktdata.length;
427
428 write_msg:
429      if (ret)
430           (void) sendto(s, msgdata.data, (int) msgdata.length, 0, &saddr, saddrlen);
431      else
432           if (sendto(s, msgdata.data, msgdata.length, 0, &saddr, saddrlen)<0)
433                ret = errno;
434      if (debug)
435           printf("reply written\n");
436      if (v5tkt)
437        krb5_free_ticket(context, v5tkt);
438      
439                
440      return ret;
441 }
442
443 krb5_error_code lookup_service_key(context, p, ktype, kvno, key, kvnop)
444      krb5_context context;
445      krb5_principal p;
446      krb5_enctype ktype;
447      krb5_kvno kvno;
448      krb5_keyblock *key;
449      krb5_kvno *kvnop;
450 {
451      int ret;
452      krb5_keytab_entry entry;
453
454      if (use_keytab) {
455           if ((ret = krb5_kt_get_entry(context, kt, p, kvno, ktype, &entry)))
456                return ret;
457           *key = entry.key;
458           key->contents = malloc(key->length);
459           if (key->contents)
460               memcpy(key->contents, entry.key.contents, key->length);
461           else if (key->length) {
462               /* out of memory? */
463               ret = errno;
464               memset (key, 0, sizeof (*key));
465               return ret;
466           }
467
468           krb5_kt_free_entry(context, &entry);
469           return 0;
470      } else if (use_master) {
471           return kdc_get_server_key(context, p, key, kvnop, ktype, kvno);
472      }
473      return 0;
474 }
475
476 krb5_error_code kdc_get_server_key(context, service, key, kvnop, ktype, kvno)
477     krb5_context context;
478     krb5_principal service;
479     krb5_keyblock *key;
480     krb5_kvno *kvnop;
481     krb5_enctype ktype;
482     krb5_kvno kvno;
483 {
484     krb5_error_code ret;
485     kadm5_principal_ent_rec server;
486     
487     if ((ret = kadm5_get_principal(handle, service, &server,
488                                    KADM5_KEY_DATA|KADM5_ATTRIBUTES)))
489          return ret;
490
491     if (server.attributes & KRB5_KDB_DISALLOW_ALL_TIX
492         || server.attributes & KRB5_KDB_DISALLOW_SVR) {
493         kadm5_free_principal_ent(handle, &server);
494         return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
495     }
496
497     /*
498      * We try kadm5_decrypt_key twice because in the case of a
499      * ENCTYPE_DES_CBC_CRC key, we prefer to find a krb4 salt type
500      * over a normal key.  Note this may create a problem if the
501      * server key is passworded and has both a normal and v4 salt.
502      * There is no good solution to this.
503      */
504     if ((ret = kadm5_decrypt_key(handle,
505                                  &server,
506                                  ktype,
507                                  (ktype == ENCTYPE_DES_CBC_CRC) ? 
508                                  KRB5_KDB_SALTTYPE_V4 : -1,
509                                  kvno,
510                                  key, NULL, kvnop)) &&
511         (ret = kadm5_decrypt_key(handle,
512                                  &server,
513                                  ktype,
514                                  -1,
515                                  kvno,
516                                  key, NULL, kvnop))) {
517          kadm5_free_principal_ent(handle, &server);
518          return (KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
519     }
520
521     kadm5_free_principal_ent(handle, &server);
522     return ret;
523 }
524
525 /*
526  * We support two  kinds of v4 credentials.  There are real v4
527  *   credentials, and  a Kerberos v5 enc part masquerading as a krb4
528  *  credential to be used by modern AFS implementations; this function
529  *  handles the classic v4 case.
530  */
531
532 static krb5_error_code
533 handle_classic_v4 (krb5_context context, krb5_ticket *v5tkt,
534                    struct sockaddr_in *saddr,
535                    krb5_data *tktdata, krb5_kvno *v4kvno)
536 {
537     krb5_error_code ret;
538     krb5_keyblock v5_service_key, v4_service_key;
539      KTEXT_ST v4tkt;
540
541     v5_service_key.contents = NULL;
542     v4_service_key.contents = NULL;
543     
544              if ((ret = lookup_service_key(context, v5tkt->server,
545                                    v5tkt->enc_part.enctype,
546                                    v5tkt->enc_part.kvno,
547                                    &v5_service_key, NULL)))
548           goto error;
549
550      if ( (ret = lookup_service_key(context, v5tkt->server,
551                                    ENCTYPE_DES_CBC_CRC,
552                                    0,
553                                    &v4_service_key, v4kvno)))
554          goto error;
555
556      if (debug)
557           printf("service key retrieved\n");
558      if ((ret = krb5_decrypt_tkt_part(context, &v5_service_key, v5tkt))) {
559        goto error;
560      }
561
562     if (!(allow_v4_crossrealm || krb5_realm_compare(context, v5tkt->server,
563                                                     v5tkt->enc_part2->client))) {
564 ret =  KRB5KDC_ERR_POLICY ;
565  goto error;
566     }
567     krb5_free_enc_tkt_part(context, v5tkt->enc_part2);
568     v5tkt->enc_part2= NULL;
569
570          ret = krb524_convert_tkt_skey(context, v5tkt, &v4tkt, &v5_service_key,
571                                    &v4_service_key,
572                                    (struct sockaddr_in *)saddr);
573      if (ret)
574           goto error;
575
576      if (debug)
577           printf("credentials converted\n");
578
579      ret = encode_v4tkt(&v4tkt, tktdata->data, &tktdata->length);
580      if (ret)
581           goto error;
582      if (debug)
583           printf("v4 credentials encoded\n");
584
585  error:
586      if (v5tkt->enc_part2) {
587          krb5_free_enc_tkt_part(context, v5tkt->enc_part2);
588          v5tkt->enc_part2 = NULL;
589      }
590
591      if(v5_service_key.contents)
592        krb5_free_keyblock_contents(context, &v5_service_key);
593      if (v4_service_key.contents)
594          krb5_free_keyblock_contents(context, &v4_service_key);
595      return ret;
596 }
597
598 /*
599  * afs_return_v4: a predicate to determine whether we want to try
600  * using the afs krb5 encrypted part encoding or whether we  just
601  * return krb4.  Takes a principal, and checks the configuration file.
602  */
603 static krb5_error_code 
604 afs_return_v4 (krb5_context context, const krb5_principal princ,
605                int *use_v5)
606 {
607     krb5_error_code ret;
608     char *unparsed_name;
609     char *cp;
610     krb5_data realm;
611     assert(use_v5 != NULL);
612     ret = krb5_unparse_name(context, princ, &unparsed_name);
613         if (ret != 0)
614         return ret;
615 /* Trim out trailing realm component into separate string.*/
616     for (cp = unparsed_name; *cp != '\0'; cp++) {
617         if (*cp == '\\') {
618             cp++; /* We trust unparse_name not to leave a singleton
619                    * backslash*/
620             continue;
621         }
622         if (*cp == '@') {
623             *cp = '\0';
624             realm.data = cp+1;
625             realm.length = strlen((char *) realm.data);
626                     break;
627         }
628     }
629      krb5_appdefault_boolean(context, "afs_krb5",
630                                   &realm, unparsed_name, 1,
631                                   use_v5);
632     krb5_free_unparsed_name(context, unparsed_name);
633     return ret;
634 }