Add heimdal-0.6.3
[dragonfly.git] / crypto / heimdal-0.6.3 / appl / kx / kx.c
1 /*
2  * Copyright (c) 1995-2003 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 "kx.h"
35
36 RCSID("$Id: kx.c,v 1.72 2003/04/16 17:33:02 joda Exp $");
37
38 static int nchild;
39 static int donep;
40
41 /*
42  * Signal handler that justs waits for the children when they die.
43  */
44
45 static RETSIGTYPE
46 childhandler (int sig)
47 {
48      pid_t pid;
49      int status;
50
51      do { 
52          pid = waitpid (-1, &status, WNOHANG|WUNTRACED);
53          if (pid > 0 && (WIFEXITED(status) || WIFSIGNALED(status)))
54              if (--nchild == 0 && donep)
55                  exit (0);
56      } while(pid > 0);
57      signal (SIGCHLD, childhandler);
58      SIGRETURN(0);
59 }
60
61 /*
62  * Handler for SIGUSR1.
63  * This signal means that we should wait until there are no children
64  * left and then exit.
65  */
66
67 static RETSIGTYPE
68 usr1handler (int sig)
69 {
70     donep = 1;
71
72     SIGRETURN(0);
73 }
74
75 /*
76  * Almost the same as for SIGUSR1, except we should exit immediately
77  * if there are no active children.
78  */
79
80 static RETSIGTYPE
81 usr2handler (int sig)
82 {
83     donep = 1;
84     if (nchild == 0)
85         exit (0);
86
87     SIGRETURN(0);
88 }
89
90 /*
91  * Establish authenticated connection.  Return socket or -1.
92  */
93
94 static int
95 connect_host (kx_context *kc)
96 {
97     struct addrinfo *ai, *a;
98     struct addrinfo hints;
99     int error;
100     char portstr[NI_MAXSERV];
101     socklen_t addrlen;
102     int s;
103     struct sockaddr_storage thisaddr_ss;
104     struct sockaddr *thisaddr = (struct sockaddr *)&thisaddr_ss;
105
106     memset (&hints, 0, sizeof(hints));
107     hints.ai_socktype = SOCK_STREAM;
108     hints.ai_protocol = IPPROTO_TCP;
109
110     snprintf (portstr, sizeof(portstr), "%u", ntohs(kc->port));
111
112     error = getaddrinfo (kc->host, portstr, &hints, &ai);
113     if (error) {
114         warnx ("%s: %s", kc->host, gai_strerror(error));
115         return -1;
116     }
117     
118     for (a = ai; a != NULL; a = a->ai_next) {
119         s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
120         if (s < 0)
121             continue;
122         if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
123             warn ("connect(%s)", kc->host);
124             close (s);
125             continue;
126         }
127         break;
128     }
129
130     if (a == NULL) {
131         freeaddrinfo (ai);
132         return -1;
133     }
134
135     addrlen = sizeof(thisaddr_ss);
136     if (getsockname (s, thisaddr, &addrlen) < 0 ||
137         addrlen != a->ai_addrlen)
138         err(1, "getsockname(%s)", kc->host);
139     memcpy (&kc->__ss_this, thisaddr, sizeof(kc->__ss_this));
140     kc->thisaddr_len = addrlen;
141     memcpy (&kc->__ss_that, a->ai_addr, sizeof(kc->__ss_that));
142     kc->thataddr_len = a->ai_addrlen;
143     freeaddrinfo (ai);
144     if ((*kc->authenticate)(kc, s))
145         return -1;
146     return s;
147 }
148
149 /*
150  * Get rid of the cookie that we were sent and get the correct one
151  * from our own cookie file instead and then just copy data in both
152  * directions.
153  */
154
155 static int
156 passive_session (int xserver, int fd, kx_context *kc)
157 {
158     if (replace_cookie (xserver, fd, XauFileName(), 1))
159         return 1;
160     else
161         return copy_encrypted (kc, xserver, fd);
162 }
163
164 static int
165 active_session (int xserver, int fd, kx_context *kc)
166 {
167     if (verify_and_remove_cookies (xserver, fd, 1))
168         return 1;
169     else
170         return copy_encrypted (kc, xserver, fd);
171 }
172
173 /*
174  * fork (unless debugp) and print the output that will be used by the
175  * script to capture the display, xauth cookie and pid.
176  */
177
178 static void
179 status_output (int debugp)
180 {
181     if(debugp)
182         printf ("%u\t%s\t%s\n", (unsigned)getpid(), display, xauthfile);
183     else {
184         pid_t pid;
185         
186         pid = fork();
187         if (pid < 0) {
188             err(1, "fork");
189         } else if (pid > 0) {
190             printf ("%u\t%s\t%s\n", (unsigned)pid, display, xauthfile);
191             exit (0);
192         } else {
193             fclose(stdout);
194         }
195     }
196 }
197
198 /*
199  * Obtain an authenticated connection on `kc'.  Send a kx message
200  * saying we are `kc->user' and want to use passive mode.  Wait for
201  * answer on that connection and fork of a child for every new
202  * connection we have to make.
203  */
204
205 static int
206 doit_passive (kx_context *kc)
207 {
208      int otherside;
209      u_char msg[1024], *p;
210      int len;
211      u_int32_t tmp;
212      const char *host = kc->host;
213
214      otherside = connect_host (kc);
215
216      if (otherside < 0)
217          return 1;
218 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
219      if (kc->keepalive_flag) {
220          int one = 1;
221
222          setsockopt (otherside, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
223                      sizeof(one));
224      }
225 #endif
226
227      p = msg;
228      *p++ = INIT;
229      len = strlen(kc->user);
230      p += KRB_PUT_INT (len, p, sizeof(msg) - 1, 4);
231      memcpy(p, kc->user, len);
232      p += len;
233      *p++ = PASSIVE | (kc->keepalive_flag ? KEEP_ALIVE : 0);
234      if (kx_write (kc, otherside, msg, p - msg) != p - msg)
235          err (1, "write to %s", host);
236      len = kx_read (kc, otherside, msg, sizeof(msg));
237      if (len <= 0)
238          errx (1,
239                "error reading initial message from %s: "
240                "this probably means it's using an old version.",
241                host);
242      p = (u_char *)msg;
243      if (*p == ERROR) {
244          p++;
245          p += krb_get_int (p, &tmp, 4, 0);
246          errx (1, "%s: %.*s", host, (int)tmp, p);
247      } else if (*p != ACK) {
248          errx (1, "%s: strange msg %d", host, *p);
249      } else
250          p++;
251      p += krb_get_int (p, &tmp, 4, 0);
252      memcpy(display, p, tmp);
253      display[tmp] = '\0';
254      p += tmp;
255
256      p += krb_get_int (p, &tmp, 4, 0);
257      memcpy(xauthfile, p, tmp);
258      xauthfile[tmp] = '\0';
259      p += tmp;
260
261      status_output (kc->debug_flag);
262      for (;;) {
263          pid_t child;
264
265          len = kx_read (kc, otherside, msg, sizeof(msg));
266          if (len < 0)
267              err (1, "read from %s", host);
268          else if (len == 0)
269              return 0;
270
271          p = (u_char *)msg;
272          if (*p == ERROR) {
273              p++;
274              p += krb_get_int (p, &tmp, 4, 0);
275              errx (1, "%s: %.*s", host, (int)tmp, p);
276          } else if(*p != NEW_CONN) {
277              errx (1, "%s: strange msg %d", host, *p);
278          } else {
279              p++;
280              p += krb_get_int (p, &tmp, 4, 0);
281          }
282          
283          ++nchild;
284          child = fork ();
285          if (child < 0) {
286              warn("fork");
287              continue;
288          } else if (child == 0) {
289              int fd;
290              int xserver;
291
292              close (otherside);
293
294              socket_set_port(kc->thataddr, htons(tmp));
295                  
296              fd = socket (kc->thataddr->sa_family, SOCK_STREAM, 0);
297              if (fd < 0)
298                  err(1, "socket");
299 #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT)
300              {
301                  int one = 1;
302
303                  setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (void *)&one,
304                              sizeof(one));
305              }
306 #endif
307 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
308              if (kc->keepalive_flag) {
309                  int one = 1;
310
311                  setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
312                              sizeof(one));
313              }
314 #endif
315
316              if (connect (fd, kc->thataddr, kc->thataddr_len) < 0)
317                  err(1, "connect(%s)", host);
318              {
319                  int d = 0;
320                  char *s;
321
322                  s = getenv ("DISPLAY");
323                  if (s != NULL) {
324                      s = strchr (s, ':');
325                      if (s != NULL)
326                          d = atoi (s + 1);
327                  }
328
329                  xserver = connect_local_xsocket (d);
330                  if (xserver < 0)
331                      return 1;
332              }
333              return passive_session (xserver, fd, kc);
334          } else {
335          }
336      }
337 }
338
339 /*
340  * Allocate a local pseudo-xserver and wait for connections 
341  */
342
343 static int
344 doit_active (kx_context *kc)
345 {
346     int otherside;
347     int nsockets;
348     struct x_socket *sockets;
349     u_char msg[1024], *p;
350     int len = strlen(kc->user);
351     int tmp, tmp2;
352     char *s;
353     int i;
354     size_t rem;
355     u_int32_t other_port;
356     int error;
357     const char *host = kc->host;
358
359     otherside = connect_host (kc);
360     if (otherside < 0)
361         return 1;
362 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
363     if (kc->keepalive_flag) {
364         int one = 1;
365
366         setsockopt (otherside, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
367                     sizeof(one));
368     }
369 #endif
370     p = msg;
371     rem = sizeof(msg);
372     *p++ = INIT;
373     --rem;
374     len = strlen(kc->user);
375     tmp = KRB_PUT_INT (len, p, rem, 4);
376     if (tmp < 0)
377         return 1;
378     p += tmp;
379     rem -= tmp;
380     memcpy(p, kc->user, len);
381     p += len;
382     rem -= len;
383     *p++ = (kc->keepalive_flag ? KEEP_ALIVE : 0);
384     --rem;
385
386     s = getenv("DISPLAY");
387     if (s == NULL || (s = strchr(s, ':')) == NULL) 
388         s = ":0";
389     len = strlen (s);
390     tmp = KRB_PUT_INT (len, p, rem, 4);
391     if (tmp < 0)
392         return 1;
393     rem -= tmp;
394     p += tmp;
395     memcpy (p, s, len);
396     p += len;
397     rem -= len;
398
399     s = getenv("XAUTHORITY");
400     if (s == NULL)
401         s = "";
402     len = strlen (s);
403     tmp = KRB_PUT_INT (len, p, rem, 4);
404     if (tmp < 0)
405         return 1;
406     p += len;
407     rem -= len;
408     memcpy (p, s, len);
409     p += len;
410     rem -= len;
411
412     if (kx_write (kc, otherside, msg, p - msg) != p - msg)
413         err (1, "write to %s", host);
414
415     len = kx_read (kc, otherside, msg, sizeof(msg));
416     if (len < 0)
417         err (1, "read from %s", host);
418     p = (u_char *)msg;
419     if (*p == ERROR) {
420         u_int32_t u32;
421
422         p++;
423         p += krb_get_int (p, &u32, 4, 0);
424         errx (1, "%s: %.*s", host, (int)u32, p);
425     } else if (*p != ACK) {
426         errx (1, "%s: strange msg %d", host, *p);
427     } else
428         p++;
429
430     tmp2 = get_xsockets (&nsockets, &sockets, kc->tcp_flag);
431     if (tmp2 < 0)
432         return 1;
433     display_num = tmp2;
434     if (kc->tcp_flag)
435         snprintf (display, display_size, "localhost:%u", display_num);
436     else
437         snprintf (display, display_size, ":%u", display_num);
438     error = create_and_write_cookie (xauthfile, xauthfile_size,
439                                      cookie, cookie_len);
440     if (error) {
441         warnx ("failed creating cookie file: %s", strerror(error));
442         return 1;
443     }
444     status_output (kc->debug_flag);
445     for (;;) {
446         fd_set fdset;
447         pid_t child;
448         int fd, thisfd = -1;
449         socklen_t zero = 0;
450
451         FD_ZERO(&fdset);
452         for (i = 0; i < nsockets; ++i) {
453             if (sockets[i].fd >= FD_SETSIZE) 
454                 errx (1, "fd too large");
455             FD_SET(sockets[i].fd, &fdset);
456         }
457         if (select(FD_SETSIZE, &fdset, NULL, NULL, NULL) <= 0)
458             continue;
459         for (i = 0; i < nsockets; ++i)
460             if (FD_ISSET(sockets[i].fd, &fdset)) {
461                 thisfd = sockets[i].fd;
462                 break;
463             }
464         fd = accept (thisfd, NULL, &zero);
465         if (fd < 0) {
466             if (errno == EINTR)
467                 continue;
468             else
469                 err(1, "accept");
470         }
471
472         p = msg;
473         *p++ = NEW_CONN;
474         if (kx_write (kc, otherside, msg, p - msg) != p - msg)
475             err (1, "write to %s", host);
476         len = kx_read (kc, otherside, msg, sizeof(msg));
477         if (len < 0)
478             err (1, "read from %s", host);
479         p = (u_char *)msg;
480         if (*p == ERROR) {
481             u_int32_t val;
482
483             p++;
484             p += krb_get_int (p, &val, 4, 0);
485             errx (1, "%s: %.*s", host, (int)val, p);
486         } else if (*p != NEW_CONN) {
487             errx (1, "%s: strange msg %d", host, *p);
488         } else {
489             p++;
490             p += krb_get_int (p, &other_port, 4, 0);
491         }
492
493         ++nchild;
494         child = fork ();
495         if (child < 0) {
496             warn("fork");
497             continue;
498         } else if (child == 0) {
499             int s;
500
501             for (i = 0; i < nsockets; ++i)
502                 close (sockets[i].fd);
503
504             close (otherside);
505
506             socket_set_port(kc->thataddr, htons(tmp));
507
508             s = socket (kc->thataddr->sa_family, SOCK_STREAM, 0);
509             if (s < 0)
510                 err(1, "socket");
511 #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT)
512             {
513                 int one = 1;
514
515                 setsockopt (s, IPPROTO_TCP, TCP_NODELAY, (void *)&one,
516                             sizeof(one));
517             }
518 #endif
519 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
520             if (kc->keepalive_flag) {
521                 int one = 1;
522
523                 setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
524                             sizeof(one));
525             }
526 #endif
527
528             if (connect (s, kc->thataddr, kc->thataddr_len) < 0)
529                 err(1, "connect");
530
531             return active_session (fd, s, kc);
532         } else {
533             close (fd);
534         }
535     }
536 }
537
538 /*
539  * Should we interpret `disp' as this being a passive call?
540  */
541
542 static int
543 check_for_passive (const char *disp)
544 {
545     char local_hostname[MaxHostNameLen];
546
547     gethostname (local_hostname, sizeof(local_hostname));
548
549     return disp != NULL &&
550         (*disp == ':'
551          || strncmp(disp, "unix", 4) == 0
552          || strncmp(disp, "localhost", 9) == 0
553          || strncmp(disp, local_hostname, strlen(local_hostname)) == 0);
554 }
555
556 /*
557  * Set up signal handlers and then call the functions.
558  */
559
560 static int
561 doit (kx_context *kc, int passive_flag)
562 {
563     signal (SIGCHLD, childhandler);
564     signal (SIGUSR1, usr1handler);
565     signal (SIGUSR2, usr2handler);
566     if (passive_flag)
567         return doit_passive (kc);
568     else
569         return doit_active  (kc);
570 }
571
572 #ifdef KRB4
573
574 /*
575  * Start a v4-authenticatated kx connection.
576  */
577
578 static int
579 doit_v4 (const char *host, int port, const char *user, 
580          int passive_flag, int debug_flag, int keepalive_flag, int tcp_flag)
581 {
582     int ret;
583     kx_context context;
584
585     krb4_make_context (&context);
586     context_set (&context,
587                  host, user, port, debug_flag, keepalive_flag, tcp_flag);
588
589     ret = doit (&context, passive_flag);
590     context_destroy (&context);
591     return ret;
592 }
593 #endif /* KRB4 */
594
595 #ifdef KRB5
596
597 /*
598  * Start a v5-authenticatated kx connection.
599  */
600
601 static int
602 doit_v5 (const char *host, int port, const char *user,
603          int passive_flag, int debug_flag, int keepalive_flag, int tcp_flag)
604 {
605     int ret;
606     kx_context context;
607
608     krb5_make_context (&context);
609     context_set (&context,
610                  host, user, port, debug_flag, keepalive_flag, tcp_flag);
611
612     ret = doit (&context, passive_flag);
613     context_destroy (&context);
614     return ret;
615 }
616 #endif /* KRB5 */
617
618 /*
619  * Variables set from the arguments
620  */
621
622 #ifdef KRB4
623 static int use_v4               = -1;
624 #ifdef HAVE_KRB_ENABLE_DEBUG
625 static int krb_debug_flag       = 0;
626 #endif /* HAVE_KRB_ENABLE_DEBUG */
627 #endif /* KRB4 */
628 #ifdef KRB5
629 static int use_v5               = -1;
630 #endif
631 static char *port_str           = NULL;
632 static const char *user         = NULL;
633 static int tcp_flag             = 0;
634 static int passive_flag         = 0;
635 static int keepalive_flag       = 1;
636 static int debug_flag           = 0;
637 static int version_flag         = 0;
638 static int help_flag            = 0;
639
640 struct getargs args[] = {
641 #ifdef KRB4
642     { "krb4",   '4', arg_flag,          &use_v4,        "Use Kerberos V4",
643       NULL },
644 #ifdef HAVE_KRB_ENABLE_DEBUG
645     { "krb4-debug", 'D', arg_flag,      &krb_debug_flag,
646       "enable krb4 debugging" },
647 #endif /* HAVE_KRB_ENABLE_DEBUG */
648 #endif /* KRB4 */
649 #ifdef KRB5
650     { "krb5",   '5', arg_flag,          &use_v5,        "Use Kerberos V5",
651       NULL },
652 #endif
653     { "port",   'p', arg_string,        &port_str,      "Use this port",
654       "number-of-service" },
655     { "user",   'l', arg_string,        &user,          "Run as this user",
656       NULL },
657     { "tcp",    't', arg_flag,          &tcp_flag,
658       "Use a TCP connection for X11" },
659     { "passive", 'P', arg_flag,         &passive_flag,
660       "Force a passive connection" },
661     { "keepalive", 'k', arg_negative_flag, &keepalive_flag,
662       "disable keep-alives" },
663     { "debug",  'd',    arg_flag,       &debug_flag,
664       "Enable debug information" },
665     { "version", 0,  arg_flag,          &version_flag,  "Print version",
666       NULL },
667     { "help",    0,  arg_flag,          &help_flag,     NULL,
668       NULL }
669 };
670
671 static void
672 usage(int ret)
673 {
674     arg_printusage (args,
675                     sizeof(args) / sizeof(args[0]),
676                     NULL,
677                     "host");
678     exit (ret);
679 }
680
681 /*
682  * kx - forward an x-connection over a kerberos-encrypted channel.
683  */
684
685 int
686 main(int argc, char **argv)
687 {
688     int port    = 0;
689     int optind  = 0;
690     int ret     = 1;
691     char *host  = NULL;
692
693     setprogname (argv[0]);
694
695     if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv,
696                 &optind))
697         usage (1);
698
699     if (help_flag)
700         usage (0);
701
702     if (version_flag) {
703         print_version (NULL);
704         return 0;
705     }
706
707     if (optind != argc - 1)
708         usage (1);
709
710     host = argv[optind];
711
712     if (port_str) {
713         struct servent *s = roken_getservbyname (port_str, "tcp");
714
715         if (s)
716             port = s->s_port;
717         else {
718             char *ptr;
719
720             port = strtol (port_str, &ptr, 10);
721             if (port == 0 && ptr == port_str)
722                 errx (1, "Bad port `%s'", port_str);
723             port = htons(port);
724         }
725     }
726
727     if (user == NULL) {
728         user = get_default_username ();
729         if (user == NULL)
730             errx (1, "who are you?");
731     }
732
733     if (!passive_flag)
734         passive_flag = check_for_passive (getenv("DISPLAY"));
735
736 #if defined(HAVE_KERNEL_ENABLE_DEBUG)
737     if (krb_debug_flag)
738         krb_enable_debug ();
739 #endif
740
741 #if defined(KRB4) && defined(KRB5)
742     if(use_v4 == -1 && use_v5 == 1)
743         use_v4 = 0;
744     if(use_v5 == -1 && use_v4 == 1)
745         use_v5 = 0;
746 #endif    
747
748 #ifdef KRB5
749     if (ret && use_v5) {
750         if (port == 0)
751             port = krb5_getportbyname(NULL, "kx", "tcp", KX_PORT);
752         ret = doit_v5 (host, port, user,
753                        passive_flag, debug_flag, keepalive_flag, tcp_flag);
754     }
755 #endif
756 #ifdef KRB4
757     if (ret && use_v4) {
758         if (port == 0)
759             port = k_getportbyname("kx", "tcp", htons(KX_PORT));
760         ret = doit_v4 (host, port, user, 
761                        passive_flag, debug_flag, keepalive_flag, tcp_flag);
762     }
763 #endif
764     return ret;
765 }