Release 0.0j
[heimdal.git] / appl / kx / kx.c
blob04eb7d74ad35128bd330b5f8a12d8af9aaf95ad7
1 /*
2 * Copyright (c) 1995, 1996, 1997 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.
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.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by the Kungliga Tekniska
20 * Högskolan and its contributors.
22 * 4. Neither the name of the Institute nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
39 #include "kx.h"
41 RCSID("$Id$");
43 static int nchild;
44 static int donep;
47 * Signal handler that justs waits for the children when they die.
50 static RETSIGTYPE
51 childhandler (int sig)
53 pid_t pid;
54 int status;
56 do {
57 pid = waitpid (-1, &status, WNOHANG|WUNTRACED);
58 if (pid > 0 && (WIFEXITED(status) || WIFSIGNALED(status)))
59 if (--nchild == 0 && donep)
60 exit (0);
61 } while(pid > 0);
62 signal (SIGCHLD, childhandler);
63 SIGRETURN(0);
67 * Handler for SIGUSR1.
68 * This signal means that we should wait until there are no children
69 * left and then exit.
72 static RETSIGTYPE
73 usr1handler (int sig)
75 donep = 1;
77 SIGRETURN(0);
81 * Almost the same as for SIGUSR1, except we should exit immediately
82 * if there are no active children.
85 static RETSIGTYPE
86 usr2handler (int sig)
88 donep = 1;
89 if (nchild == 0)
90 exit (0);
92 SIGRETURN(0);
96 * Establish authenticated connection
99 static int
100 connect_host (char *host, char *user, des_cblock *key,
101 des_key_schedule schedule, int port,
102 struct sockaddr_in *thisaddr,
103 struct sockaddr_in *thataddr)
105 CREDENTIALS cred;
106 KTEXT_ST text;
107 MSG_DAT msg;
108 int status;
109 int addrlen;
110 struct hostent *hostent;
111 int s;
112 char **p;
114 hostent = gethostbyname (host);
115 if (hostent == NULL) {
116 warnx ("gethostbyname '%s' failed: %s", host,
117 #ifdef HAVE_H_ERRNO
118 hstrerror(h_errno)
119 #else
120 "unknown error"
121 #endif
123 return -1;
126 memset (thataddr, 0, sizeof(*thataddr));
127 thataddr->sin_family = AF_INET;
128 thataddr->sin_port = port;
129 for(p = hostent->h_addr_list; *p; ++p) {
130 memcpy (&thataddr->sin_addr, *p, sizeof(thataddr->sin_addr));
132 s = socket (AF_INET, SOCK_STREAM, 0);
133 if (s < 0)
134 err (1, "socket");
136 if (connect (s, (struct sockaddr *)thataddr, sizeof(*thataddr)) < 0) {
137 warn ("connect(%s)", host);
138 close (s);
139 continue;
140 } else {
141 break;
144 if (*p == NULL)
145 return -1;
147 addrlen = sizeof(*thisaddr);
148 if (getsockname (s, (struct sockaddr *)thisaddr, &addrlen) < 0 ||
149 addrlen != sizeof(*thisaddr))
150 err(1, "getsockname(%s)", host);
151 status = krb_sendauth (KOPT_DO_MUTUAL, s, &text, "rcmd",
152 host, krb_realmofhost (host),
153 getpid(), &msg, &cred, schedule,
154 thisaddr, thataddr, KX_VERSION);
155 if (status != KSUCCESS) {
156 warnx ("%s: %s\n", host, krb_get_err_text(status));
157 return -1;
159 memcpy(key, cred.session, sizeof(des_cblock));
160 return s;
164 * Get rid of the cookie that we were sent and get the correct one
165 * from our own cookie file instead.
168 static int
169 passive_session (int xserver, int fd, des_cblock *iv,
170 des_key_schedule schedule)
172 if (replace_cookie (xserver, fd, XauFileName()))
173 return 1;
174 else
175 return copy_encrypted (xserver, fd, iv, schedule);
178 static int
179 active_session (int xserver, int fd, des_cblock *iv,
180 des_key_schedule schedule)
182 if (verify_and_remove_cookies (xserver, fd))
183 return 1;
184 else
185 return copy_encrypted (xserver, fd, iv, schedule);
188 static void
189 status_output (int debugp)
191 if(debugp)
192 printf ("%u\t%s\t%s\n", (unsigned)getpid(), display, xauthfile);
193 else {
194 pid_t pid;
196 pid = fork();
197 if (pid < 0) {
198 err(1, "fork");
199 } else if (pid > 0) {
200 printf ("%u\t%s\t%s\n", (unsigned)pid, display, xauthfile);
201 exit (0);
202 } else {
203 fclose(stdout);
209 * Obtain an authenticated connection to `host' on `port'. Send a kx
210 * message saying we are `user' and want to use passive mode. Wait
211 * for answer on that connection and fork of a child for every new
212 * connection we have to make.
215 static int
216 doit_passive (char *host, char *user, int debugp, int keepalivep,
217 int port)
219 des_key_schedule schedule;
220 des_cblock key;
221 int otherside;
222 struct sockaddr_in me, him;
223 u_char msg[1024], *p;
224 int len;
225 void *ret;
226 u_int32_t tmp;
228 otherside = connect_host (host, user, &key, schedule, port,
229 &me, &him);
230 if (otherside < 0)
231 return 1;
232 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
233 if (keepalivep) {
234 int one = 1;
236 setsockopt (otherside, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
237 sizeof(one));
239 #endif
241 p = msg;
242 *p++ = INIT;
243 len = strlen(user);
244 p += krb_put_int (len, p, 4);
245 strncpy(p, user, len);
246 p += len;
247 *p++ = PASSIVE | (keepalivep ? KEEP_ALIVE : 0);
248 if (write_encrypted (otherside, msg, p - msg, schedule,
249 &key, &me, &him) < 0)
250 err (1, "write to %s", host);
251 len = read_encrypted (otherside, msg, sizeof(msg), &ret,
252 schedule, &key, &him, &me);
253 if (len <= 0)
254 errx (1,
255 "error reading initial message from %s: "
256 "this probably means it's using an old version.",
257 host);
258 p = (u_char *)ret;
259 if (*p == ERROR) {
260 p++;
261 p += krb_get_int (p, &tmp, 4, 0);
262 errx (1, "%s: %.*s", host, (int)tmp, p);
263 } else if (*p != ACK) {
264 errx (1, "%s: strange msg %d", host, *p);
265 } else
266 p++;
267 p += krb_get_int (p, &tmp, 4, 0);
268 strncpy(display, p, tmp);
269 display[tmp] = '\0';
270 p += tmp;
272 p += krb_get_int (p, &tmp, 4, 0);
273 strncpy(xauthfile, p, tmp);
274 xauthfile[tmp] = '\0';
275 p += tmp;
277 status_output (debugp);
278 for (;;) {
279 pid_t child;
281 len = read_encrypted (otherside, msg, sizeof(msg), &ret,
282 schedule, &key, &him, &me);
283 if (len < 0)
284 err (1, "read from %s", host);
285 else if (len == 0)
286 return 0;
288 p = (u_char *)ret;
289 if (*p == ERROR) {
290 p++;
291 p += krb_get_int (p, &tmp, 4, 0);
292 errx (1, "%s: %.*s", host, (int)tmp, p);
293 } else if(*p != NEW_CONN) {
294 errx (1, "%s: strange msg %d", host, *p);
295 } else {
296 p++;
297 p += krb_get_int (p, &tmp, 4, 0);
300 ++nchild;
301 child = fork ();
302 if (child < 0) {
303 warn("fork");
304 continue;
305 } else if (child == 0) {
306 struct sockaddr_in addr;
307 int fd;
308 int xserver;
310 addr = him;
311 close (otherside);
313 addr.sin_port = htons(tmp);
314 fd = socket (AF_INET, SOCK_STREAM, 0);
315 if (fd < 0)
316 err(1, "socket");
317 #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT)
319 int one = 1;
321 setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (void *)&one,
322 sizeof(one));
324 #endif
325 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
326 if (keepalivep) {
327 int one = 1;
329 setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
330 sizeof(one));
332 #endif
334 if (connect (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
335 err(1, "connect(%s)", host);
336 xserver = connect_local_xsocket (0);
337 if (xserver < 0)
338 return 1;
339 return passive_session (xserver, fd, &key, schedule);
340 } else {
346 * Allocate a local pseudo-xserver and wait for connections
349 static int
350 doit_active (char *host, char *user,
351 int debugpp, int keepalivep, int tcpp, int port)
353 des_key_schedule schedule;
354 des_cblock key;
355 int otherside;
356 int rendez_vous1 = 0, rendez_vous2 = 0;
357 struct sockaddr_in me, him;
358 u_char msg[1024], *p;
359 int len = strlen(user);
360 void *ret;
361 u_int32_t tmp;
362 char *s;
364 otherside = connect_host (host, user, &key, schedule, port,
365 &me, &him);
366 if (otherside < 0)
367 return 1;
368 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
369 if (keepalivep) {
370 int one = 1;
372 setsockopt (otherside, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
373 sizeof(one));
375 #endif
376 p = msg;
377 *p++ = INIT;
378 len = strlen(user);
379 p += krb_put_int (len, p, 4);
380 strncpy(p, user, len);
381 p += len;
382 *p++ = (keepalivep ? KEEP_ALIVE : 0);
384 s = getenv("DISPLAY");
385 if (s == NULL || (s = strchr(s, ':')) == NULL)
386 s = ":0";
387 len = strlen (s);
388 p += krb_put_int (len, p, 4);
389 strncpy (p, s, len);
390 p += len;
392 s = getenv("XAUTHORITY");
393 if (s == NULL)
394 s = "";
395 len = strlen (s);
396 p += krb_put_int (len, p, 4);
397 strncpy (p, s, len);
398 p += len;
400 if (write_encrypted (otherside, msg, p - msg, schedule,
401 &key, &me, &him) < 0)
402 err (1, "write to %s", host);
404 len = read_encrypted (otherside, msg, sizeof(msg), &ret,
405 schedule, &key, &him, &me);
406 if (len < 0)
407 err (1, "read from %s", host);
408 p = (u_char *)ret;
409 if (*p == ERROR) {
410 p++;
411 p += krb_get_int (p, &tmp, 4, 0);
412 errx (1, "%s: %.*s", host, (int)tmp, p);
413 } else if (*p != ACK) {
414 errx (1, "%s: strange msg %d", host, *p);
415 } else
416 p++;
418 tmp = get_xsockets (&rendez_vous1,
419 tcpp ? &rendez_vous2 : NULL);
420 if (tmp < 0)
421 return 1;
422 display_num = tmp;
423 if (tcpp)
424 snprintf (display, display_size, "localhost:%u", display_num);
425 else
426 snprintf (display, display_size, ":%u", display_num);
427 strncpy(xauthfile, tempnam("/tmp", NULL), xauthfile_size);
428 if (create_and_write_cookie (xauthfile, cookie, cookie_len))
429 return 1;
430 status_output (debugpp);
431 for (;;) {
432 fd_set fdset;
433 pid_t child;
434 int fd, thisfd;
435 int zero = 0;
437 FD_ZERO(&fdset);
438 if (rendez_vous1)
439 FD_SET(rendez_vous1, &fdset);
440 if (rendez_vous2)
441 FD_SET(rendez_vous2, &fdset);
442 if (select(FD_SETSIZE, &fdset, NULL, NULL, NULL) <= 0)
443 continue;
444 if (rendez_vous1 && FD_ISSET(rendez_vous1, &fdset))
445 thisfd = rendez_vous1;
446 else if (rendez_vous2 && FD_ISSET(rendez_vous2, &fdset))
447 thisfd = rendez_vous2;
448 else
449 continue;
451 fd = accept (thisfd, NULL, &zero);
452 if (fd < 0)
453 if (errno == EINTR)
454 continue;
455 else
456 err(1, "accept");
458 p = msg;
459 *p++ = NEW_CONN;
460 if (write_encrypted (otherside, msg, p - msg, schedule,
461 &key, &me, &him) < 0)
462 err (1, "write to %s", host);
463 len = read_encrypted (otherside, msg, sizeof(msg), &ret,
464 schedule, &key, &him, &me);
465 if (len < 0)
466 err (1, "read from %s", host);
467 p = (u_char *)ret;
468 if (*p == ERROR) {
469 p++;
470 p += krb_get_int (p, &tmp, 4, 0);
471 errx (1, "%s: %.*s", host, (int)tmp, p);
472 } else if (*p != NEW_CONN) {
473 errx (1, "%s: strange msg %d", host, *p);
474 } else {
475 p++;
476 p += krb_get_int (p, &tmp, 4, 0);
479 ++nchild;
480 child = fork ();
481 if (child < 0) {
482 warn("fork");
483 continue;
484 } else if (child == 0) {
485 int s;
486 struct sockaddr_in addr;
488 if (rendez_vous1)
489 close (rendez_vous1);
490 if (rendez_vous2)
491 close (rendez_vous2);
493 addr = him;
494 close (otherside);
496 addr.sin_port = htons(tmp);
497 s = socket (AF_INET, SOCK_STREAM, 0);
498 if (s < 0)
499 err(1, "socket");
500 #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT)
502 int one = 1;
504 setsockopt (s, IPPROTO_TCP, TCP_NODELAY, (void *)&one,
505 sizeof(one));
507 #endif
508 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
509 if (keepalivep) {
510 int one = 1;
512 setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, (void *)&one,
513 sizeof(one));
515 #endif
517 if (connect (s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
518 err(1, "connect");
520 return active_session (fd, s, &key, schedule);
521 } else {
522 close (fd);
531 static int
532 check_for_passive (const char *disp)
534 char local_hostname[MaxHostNameLen];
536 gethostname (local_hostname, sizeof(local_hostname));
538 return disp != NULL &&
539 (*disp == ':'
540 || strncmp(disp, "unix", 4) == 0
541 || strncmp(disp, "localhost", 9) == 0
542 || strncmp(disp, local_hostname, strlen(local_hostname) == 0));
545 static void
546 usage(void)
548 fprintf (stderr, "Usage: %s [-p port] [-d] [-t] [-l remoteuser] host\n",
549 __progname);
550 exit (1);
554 * kx - forward x connection over a kerberos-encrypted channel.
559 main(int argc, char **argv)
561 int force_passive = 0;
562 int keepalivep = 1;
563 char *user = NULL;
564 int debugp = 0, tcpp = 0;
565 int c;
566 int port = 0;
568 set_progname (argv[0]);
569 while((c = getopt(argc, argv, "ktdl:p:P")) != EOF) {
570 switch(c) {
571 case 'd' :
572 debugp = 1;
573 break;
574 case 'k':
575 keepalivep = 0;
576 break;
577 case 't' :
578 tcpp = 1;
579 break;
580 case 'l' :
581 user = optarg;
582 break;
583 case 'p' :
584 port = htons(atoi (optarg));
585 break;
586 case 'P' :
587 force_passive = 1;
588 break;
589 case '?':
590 default:
591 usage();
595 argc -= optind;
596 argv += optind;
598 if (argc != 1)
599 usage ();
600 if (user == NULL) {
601 struct passwd *p = k_getpwuid (getuid ());
602 if (p == NULL)
603 errx(1, "Who are you?");
604 user = strdup (p->pw_name);
606 if (port == 0)
607 port = k_getportbyname ("kx", "tcp", htons(KX_PORT));
608 signal (SIGCHLD, childhandler);
609 signal (SIGUSR1, usr1handler);
610 signal (SIGUSR2, usr2handler);
611 if (check_for_passive(getenv("DISPLAY")))
612 return doit_passive (argv[0], user, debugp, keepalivep, port);
613 else
614 return doit_active (argv[0], user, debugp, keepalivep, tcpp, port);