/* * Copyright (c) 1995 - 2003 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "kx.h" RCSID("$Id: kxd.c,v 1.71.2.2 2003/05/15 15:11:35 lha Exp $"); static pid_t wait_on_pid = -1; static int done = 0; /* * Signal handler that justs waits for the children when they die. */ static RETSIGTYPE childhandler (int sig) { pid_t pid; int status; do { pid = waitpid (-1, &status, WNOHANG|WUNTRACED); if (pid > 0 && pid == wait_on_pid) done = 1; } while(pid > 0); signal (SIGCHLD, childhandler); SIGRETURN(0); } /* * Print the error message `format' and `...' on fd and die. */ void fatal (kx_context *kc, int fd, char *format, ...) { u_char msg[1024]; u_char *p; va_list args; int len; va_start(args, format); p = msg; *p++ = ERROR; vsnprintf ((char *)p + 4, sizeof(msg) - 5, format, args); syslog (LOG_ERR, "%s", (char *)p + 4); len = strlen ((char *)p + 4); p += KRB_PUT_INT (len, p, 4, 4); p += len; kx_write (kc, fd, msg, p - msg); va_end(args); exit (1); } /* * Remove all sockets and cookie files. */ static void cleanup(int nsockets, struct x_socket *sockets) { int i; if(xauthfile[0]) unlink(xauthfile); for (i = 0; i < nsockets; ++i) { if (sockets[i].pathname != NULL) { unlink (sockets[i].pathname); free (sockets[i].pathname); } } } /* * Prepare to receive a connection on `sock'. */ static int recv_conn (int sock, kx_context *kc, int *dispnr, int *nsockets, struct x_socket **sockets, int tcp_flag) { u_char msg[1024], *p; char user[256]; socklen_t addrlen; struct passwd *passwd; char remotehost[MaxHostNameLen]; char remoteaddr[INET6_ADDRSTRLEN]; int ret = 1; int flags; int len; u_int32_t tmp32; addrlen = sizeof(kc->__ss_this); kc->thisaddr = (struct sockaddr*)&kc->__ss_this; if (getsockname (sock, kc->thisaddr, &addrlen) < 0) { syslog (LOG_ERR, "getsockname: %m"); exit (1); } kc->thisaddr_len = addrlen; addrlen = sizeof(kc->__ss_that); kc->thataddr = (struct sockaddr*)&kc->__ss_that; if (getpeername (sock, kc->thataddr, &addrlen) < 0) { syslog (LOG_ERR, "getpeername: %m"); exit (1); } kc->thataddr_len = addrlen; getnameinfo_verified (kc->thataddr, kc->thataddr_len, remotehost, sizeof(remotehost), NULL, 0, 0); if (net_read (sock, msg, 4) != 4) { syslog (LOG_ERR, "read: %m"); exit (1); } #ifdef KRB5 if (ret && recv_v5_auth (kc, sock, msg) == 0) ret = 0; #endif #ifdef KRB4 if (ret && recv_v4_auth (kc, sock, msg) == 0) ret = 0; #endif if (ret) { syslog (LOG_ERR, "unrecognized auth protocol: %x %x %x %x", msg[0], msg[1], msg[2], msg[3]); exit (1); } len = kx_read (kc, sock, msg, sizeof(msg)); if (len < 0) { syslog (LOG_ERR, "kx_read failed"); exit (1); } p = (u_char *)msg; if (*p != INIT) fatal(kc, sock, "Bad message"); p++; p += krb_get_int (p, &tmp32, 4, 0); len = min(sizeof(user), tmp32); memcpy (user, p, len); p += tmp32; user[len] = '\0'; passwd = k_getpwnam (user); if (passwd == NULL) fatal (kc, sock, "cannot find uid for %s", user); if (context_userok (kc, user) != 0) fatal (kc, sock, "%s not allowed to login as %s", kc->user, user); flags = *p++; if (flags & PASSIVE) { pid_t pid; int tmp; tmp = get_xsockets (nsockets, sockets, tcp_flag); if (tmp < 0) { fatal (kc, sock, "Cannot create X socket(s): %s", strerror(errno)); } *dispnr = tmp; if (chown_xsockets (*nsockets, *sockets, passwd->pw_uid, passwd->pw_gid)) { cleanup (*nsockets, *sockets); fatal (kc, sock, "Cannot chown sockets: %s", strerror(errno)); } pid = fork(); if (pid == -1) { cleanup (*nsockets, *sockets); fatal (kc, sock, "fork: %s", strerror(errno)); } else if (pid != 0) { wait_on_pid = pid; while (!done) pause (); cleanup (*nsockets, *sockets); exit (0); } } if (setgid (passwd->pw_gid) || initgroups(passwd->pw_name, passwd->pw_gid) || #ifdef HAVE_GETUDBNAM /* XXX this happens on crays */ setjob(passwd->pw_uid, 0) == -1 || #endif setuid(passwd->pw_uid)) { syslog(LOG_ERR, "setting uid/groups: %m"); fatal (kc, sock, "cannot set uid"); } ret = getnameinfo(kc->thataddr, kc->thataddr_len, remoteaddr, sizeof(remoteaddr), NULL, 0, NI_NUMERICHOST); if (ret != 0) fatal (kc, sock, "getnameinfo failed: %s", gai_strerror(ret)); syslog (LOG_INFO, "from %s(%s): %s -> %s", remotehost, remoteaddr, kc->user, user); umask(077); if (!(flags & PASSIVE)) { p += krb_get_int (p, &tmp32, 4, 0); len = min(tmp32, display_size); memcpy (display, p, len); display[len] = '\0'; p += tmp32; p += krb_get_int (p, &tmp32, 4, 0); len = min(tmp32, xauthfile_size); memcpy (xauthfile, p, len); xauthfile[len] = '\0'; p += tmp32; } #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT) if (flags & KEEP_ALIVE) { int one = 1; setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&one, sizeof(one)); } #endif return flags; } /* * */ static int passive_session (kx_context *kc, int fd, int sock, int cookiesp) { if (verify_and_remove_cookies (fd, sock, cookiesp)) return 1; else return copy_encrypted (kc, fd, sock); } /* * */ static int active_session (kx_context *kc, int fd, int sock, int cookiesp) { fd = connect_local_xsocket(0); if (replace_cookie (fd, sock, xauthfile, cookiesp)) return 1; else return copy_encrypted (kc, fd, sock); } /* * Handle a new connection. */ static int doit_conn (kx_context *kc, int fd, int meta_sock, int flags, int cookiesp) { int sock, sock2, port; struct sockaddr_storage __ss_addr; struct sockaddr *addr = (struct sockaddr*)&__ss_addr; struct sockaddr_storage __ss_thisaddr; struct sockaddr *thisaddr = (struct sockaddr*)&__ss_thisaddr; socklen_t addrlen; u_char msg[1024], *p; sock = socket (kc->thisaddr->sa_family, SOCK_STREAM, 0); if (sock < 0) { syslog (LOG_ERR, "socket: %m"); return 1; } #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT) { int one = 1; setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (void *)&one, sizeof(one)); } #endif #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT) if (flags & KEEP_ALIVE) { int one = 1; setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&one, sizeof(one)); } #endif memset (&__ss_addr, 0, sizeof(__ss_addr)); addr->sa_family = kc->thisaddr->sa_family; if (kc->thisaddr_len > sizeof(__ss_addr)) { syslog(LOG_ERR, "error in af"); return 1; } if (bind (sock, addr, kc->thisaddr_len) < 0) { syslog (LOG_ERR, "bind: %m"); return 1; } addrlen = sizeof(__ss_addr); if (getsockname (sock, addr, &addrlen) < 0) { syslog (LOG_ERR, "getsockname: %m"); return 1; } if (listen (sock, SOMAXCONN) < 0) { syslog (LOG_ERR, "listen: %m"); return 1; } port = socket_get_port(addr); p = msg; *p++ = NEW_CONN; p += KRB_PUT_INT (ntohs(port), p, 4, 4); if (kx_write (kc, meta_sock, msg, p - msg) < 0) { syslog (LOG_ERR, "write: %m"); return 1; } addrlen = sizeof(__ss_thisaddr); sock2 = accept (sock, thisaddr, &addrlen); if (sock2 < 0) { syslog (LOG_ERR, "accept: %m"); return 1; } close (sock); close (meta_sock); if (flags & PASSIVE) return passive_session (kc, fd, sock2, cookiesp); else return active_session (kc, fd, sock2, cookiesp); } /* * Is the current user the owner of the console? */ static void check_user_console (kx_context *kc, int fd) { struct stat sb; if (stat ("/dev/console", &sb) < 0) fatal (kc, fd, "Cannot stat /dev/console: %s", strerror(errno)); if (getuid() != sb.st_uid) fatal (kc, fd, "Permission denied"); } /* close down the new connection with a reasonable error message */ static void close_connection(int fd, const char *message) { char buf[264]; /* max message */ char *p; int lsb = 0; size_t mlen; mlen = strlen(message); if(mlen > 255) mlen = 255; /* read first part of connection packet, to get byte order */ if(read(fd, buf, 6) != 6) { close(fd); return; } if(buf[0] == 0x6c) lsb++; p = buf; *p++ = 0; /* failed */ *p++ = mlen; /* length of message */ p += 4; /* skip protocol version */ p += 2; /* skip additional length */ memcpy(p, message, mlen); /* copy message */ p += mlen; while((p - buf) % 4) /* pad to multiple of 4 bytes */ *p++ = 0; /* now fill in length of additional data */ if(lsb) { buf[6] = (p - buf - 8) / 4; buf[7] = 0; }else{ buf[6] = 0; buf[7] = (p - buf - 8) / 4; } write(fd, buf, p - buf); close(fd); } /* * Handle a passive session on `sock' */ static int doit_passive (kx_context *kc, int sock, int flags, int dispnr, int nsockets, struct x_socket *sockets, int tcp_flag) { int tmp; int len; size_t rem; u_char msg[1024], *p; int error; display_num = dispnr; if (tcp_flag) snprintf (display, display_size, "localhost:%u", display_num); else snprintf (display, display_size, ":%u", display_num); error = create_and_write_cookie (xauthfile, xauthfile_size, cookie, cookie_len); if (error) { cleanup(nsockets, sockets); fatal (kc, sock, "Cookie-creation failed: %s", strerror(error)); return 1; } p = msg; rem = sizeof(msg); *p++ = ACK; --rem; len = strlen (display); tmp = KRB_PUT_INT (len, p, rem, 4); if (tmp < 0 || rem < len + 4) { syslog (LOG_ERR, "doit: buffer too small"); cleanup(nsockets, sockets); return 1; } p += tmp; rem -= tmp; memcpy (p, display, len); p += len; rem -= len; len = strlen (xauthfile); tmp = KRB_PUT_INT (len, p, rem, 4); if (tmp < 0 || rem < len + 4) { syslog (LOG_ERR, "doit: buffer too small"); cleanup(nsockets, sockets); return 1; } p += tmp; rem -= tmp; memcpy (p, xauthfile, len); p += len; rem -= len; if(kx_write (kc, sock, msg, p - msg) < 0) { syslog (LOG_ERR, "write: %m"); cleanup(nsockets, sockets); return 1; } for (;;) { pid_t child; int fd = -1; fd_set fds; int i; int ret; int cookiesp = TRUE; FD_ZERO(&fds); if (sock >= FD_SETSIZE) { syslog (LOG_ERR, "fd too large"); cleanup(nsockets, sockets); return 1; } FD_SET(sock, &fds); for (i = 0; i < nsockets; ++i) { if (sockets[i].fd >= FD_SETSIZE) { syslog (LOG_ERR, "fd too large"); cleanup(nsockets, sockets); return 1; } FD_SET(sockets[i].fd, &fds); } ret = select(FD_SETSIZE, &fds, NULL, NULL, NULL); if(ret <= 0) continue; if(FD_ISSET(sock, &fds)){ /* there are no processes left on the remote side */ cleanup(nsockets, sockets); exit(0); } else if(ret) { for (i = 0; i < nsockets; ++i) { if (FD_ISSET(sockets[i].fd, &fds)) { if (sockets[i].flags == TCP) { struct sockaddr_storage __ss_peer; struct sockaddr *peer = (struct sockaddr*)&__ss_peer; socklen_t len = sizeof(__ss_peer); fd = accept (sockets[i].fd, peer, &len); if (fd < 0 && errno != EINTR) syslog (LOG_ERR, "accept: %m"); /* XXX */ if (fd >= 0 && suspicious_address (fd, peer)) { close (fd); fd = -1; errno = EINTR; } } else if(sockets[i].flags == UNIX_SOCKET) { socklen_t zero = 0; fd = accept (sockets[i].fd, NULL, &zero); if (fd < 0 && errno != EINTR) syslog (LOG_ERR, "accept: %m"); #ifdef MAY_HAVE_X11_PIPES } else if(sockets[i].flags == STREAM_PIPE) { /* * this code tries to handle the * send fd-over-pipe stuff for * solaris */ struct strrecvfd strrecvfd; ret = ioctl (sockets[i].fd, I_RECVFD, &strrecvfd); if (ret < 0 && errno != EINTR) { syslog (LOG_ERR, "ioctl I_RECVFD: %m"); } /* XXX */ if (ret == 0) { if (strrecvfd.uid != getuid()) { close (strrecvfd.fd); fd = -1; errno = EINTR; } else { fd = strrecvfd.fd; cookiesp = FALSE; } } #endif /* MAY_HAVE_X11_PIPES */ } else abort (); break; } } } if (fd < 0) { if (errno == EINTR) continue; else return 1; } child = fork (); if (child < 0) { syslog (LOG_ERR, "fork: %m"); if(errno != EAGAIN) return 1; close_connection(fd, strerror(errno)); } else if (child == 0) { for (i = 0; i < nsockets; ++i) close (sockets[i].fd); return doit_conn (kc, fd, sock, flags, cookiesp); } else { close (fd); } } } /* * Handle an active session on `sock' */ static int doit_active (kx_context *kc, int sock, int flags, int tcp_flag) { u_char msg[1024], *p; check_user_console (kc, sock); p = msg; *p++ = ACK; if(kx_write (kc, sock, msg, p - msg) < 0) { syslog (LOG_ERR, "write: %m"); return 1; } for (;;) { pid_t child; int len; len = kx_read (kc, sock, msg, sizeof(msg)); if (len < 0) { syslog (LOG_ERR, "read: %m"); return 1; } p = (u_char *)msg; if (*p != NEW_CONN) { syslog (LOG_ERR, "bad_message: %d", *p); return 1; } child = fork (); if (child < 0) { syslog (LOG_ERR, "fork: %m"); if (errno != EAGAIN) return 1; } else if (child == 0) { return doit_conn (kc, sock, sock, flags, 1); } else { } } } /* * Receive a connection on `sock' and process it. */ static int doit(int sock, int tcp_flag) { int ret; kx_context context; int dispnr; int nsockets; struct x_socket *sockets; int flags; flags = recv_conn (sock, &context, &dispnr, &nsockets, &sockets, tcp_flag); if (flags & PASSIVE) ret = doit_passive (&context, sock, flags, dispnr, nsockets, sockets, tcp_flag); else ret = doit_active (&context, sock, flags, tcp_flag); context_destroy (&context); return ret; } static char *port_str = NULL; static int inetd_flag = 1; static int tcp_flag = 0; static int version_flag = 0; static int help_flag = 0; struct getargs args[] = { { "inetd", 'i', arg_negative_flag, &inetd_flag, "Not started from inetd" }, { "tcp", 't', arg_flag, &tcp_flag, "Use TCP" }, { "port", 'p', arg_string, &port_str, "Use this port", "port" }, { "version", 0, arg_flag, &version_flag }, { "help", 0, arg_flag, &help_flag } }; static void usage(int ret) { arg_printusage (args, sizeof(args) / sizeof(args[0]), NULL, "host"); exit (ret); } /* * kxd - receive a forwarded X conncection */ int main (int argc, char **argv) { int port; int optind = 0; setprogname (argv[0]); roken_openlog ("kxd", LOG_ODELAY | LOG_PID, LOG_DAEMON); if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv, &optind)) usage (1); if (help_flag) usage (0); if (version_flag) { print_version (NULL); return 0; } if(port_str) { struct servent *s = roken_getservbyname (port_str, "tcp"); if (s) port = s->s_port; else { char *ptr; port = strtol (port_str, &ptr, 10); if (port == 0 && ptr == port_str) errx (1, "bad port `%s'", port_str); port = htons(port); } } else { #if defined(KRB5) port = krb5_getportbyname(NULL, "kx", "tcp", KX_PORT); #elif defined(KRB4) port = k_getportbyname ("kx", "tcp", htons(KX_PORT)); #else #error define KRB4 or KRB5 #endif } if (!inetd_flag) mini_inetd (port); signal (SIGCHLD, childhandler); return doit(STDIN_FILENO, tcp_flag); }