/* * Copyright (c) 1995 - 2001 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: common.c,v 1.68 2003/04/16 16:45:39 joda Exp $"); char x_socket[MaxPathLen]; u_int32_t display_num; char display[MaxPathLen]; int display_size = sizeof(display); char xauthfile[MaxPathLen]; int xauthfile_size = sizeof(xauthfile); u_char cookie[16]; size_t cookie_len = sizeof(cookie); #ifndef X_UNIX_PATH #define X_UNIX_PATH "/tmp/.X11-unix/X" #endif #ifndef X_PIPE_PATH #define X_PIPE_PATH "/tmp/.X11-pipe/X" #endif /* * Allocate a unix domain socket in `s' for display `dpy' and with * filename `pattern' * * 0 if all is OK * -1 if bind failed badly * 1 if dpy is already used */ static int try_socket (struct x_socket *s, int dpy, const char *pattern) { struct sockaddr_un addr; int fd; fd = socket (AF_UNIX, SOCK_STREAM, 0); if (fd < 0) err (1, "socket AF_UNIX"); memset (&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; snprintf (addr.sun_path, sizeof(addr.sun_path), pattern, dpy); if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close (fd); if (errno == EADDRINUSE || errno == EACCES /* Cray return EACCESS */ #ifdef ENOTUNIQ || errno == ENOTUNIQ /* bug in Solaris 2.4 */ #endif ) return 1; else return -1; } s->fd = fd; s->pathname = strdup (addr.sun_path); if (s->pathname == NULL) errx (1, "strdup: out of memory"); s->flags = UNIX_SOCKET; return 0; } #ifdef MAY_HAVE_X11_PIPES /* * Allocate a stream (masqueraded as a named pipe) * * 0 if all is OK * -1 if bind failed badly * 1 if dpy is already used */ static int try_pipe (struct x_socket *s, int dpy, const char *pattern) { char path[MAXPATHLEN]; int ret; int fd; int pipefd[2]; snprintf (path, sizeof(path), pattern, dpy); fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd < 0) { if (errno == EEXIST) return 1; else return -1; } close (fd); ret = pipe (pipefd); if (ret < 0) err (1, "pipe"); ret = ioctl (pipefd[1], I_PUSH, "connld"); if (ret < 0) { if(errno == ENOSYS) return -1; err (1, "ioctl I_PUSH"); } ret = fattach (pipefd[1], path); if (ret < 0) err (1, "fattach %s", path); s->fd = pipefd[0]; close (pipefd[1]); s->pathname = strdup (path); if (s->pathname == NULL) errx (1, "strdup: out of memory"); s->flags = STREAM_PIPE; return 0; } #endif /* MAY_HAVE_X11_PIPES */ /* * Try to create a TCP socket in `s' corresponding to display `dpy'. * * 0 if all is OK * -1 if bind failed badly * 1 if dpy is already used */ static int try_tcp (struct x_socket *s, int dpy) { struct sockaddr_in tcpaddr; struct in_addr local; int one = 1; int fd; memset(&local, 0, sizeof(local)); local.s_addr = htonl(INADDR_LOOPBACK); fd = socket (AF_INET, SOCK_STREAM, 0); if (fd < 0) err (1, "socket AF_INET"); #if defined(TCP_NODELAY) && defined(HAVE_SETSOCKOPT) setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (void *)&one, sizeof(one)); #endif memset (&tcpaddr, 0, sizeof(tcpaddr)); tcpaddr.sin_family = AF_INET; tcpaddr.sin_addr = local; tcpaddr.sin_port = htons(6000 + dpy); if (bind (fd, (struct sockaddr *)&tcpaddr, sizeof(tcpaddr)) < 0) { close (fd); if (errno == EADDRINUSE) return 1; else return -1; } s->fd = fd; s->pathname = NULL; s->flags = TCP; return 0; } /* * The potential places to create unix sockets. */ static char *x_sockets[] = { X_UNIX_PATH "%u", "/var/X/.X11-unix/X" "%u", "/usr/spool/sockets/X11/" "%u", NULL }; /* * Dito for stream pipes. */ #ifdef MAY_HAVE_X11_PIPES static char *x_pipes[] = { X_PIPE_PATH "%u", "/var/X/.X11-pipe/X" "%u", NULL }; #endif /* * Create the directory corresponding to dirname of `path' or fail. */ static void try_mkdir (const char *path) { char *dir; char *p; int oldmask; if((dir = strdup (path)) == NULL) errx (1, "strdup: out of memory"); p = strrchr (dir, '/'); if (p) *p = '\0'; oldmask = umask(0); mkdir (dir, 01777); umask (oldmask); free (dir); } /* * Allocate a display, returning the number of sockets in `number' and * all the corresponding sockets in `sockets'. If `tcp_socket' is * true, also allcoaet a TCP socket. * * The return value is the display allocated or -1 if an error occurred. */ int get_xsockets (int *number, struct x_socket **sockets, int tcp_socket) { int dpy; struct x_socket *s; int n; int i; s = malloc (sizeof(*s) * 5); if (s == NULL) errx (1, "malloc: out of memory"); try_mkdir (X_UNIX_PATH); try_mkdir (X_PIPE_PATH); for(dpy = 4; dpy < 256; ++dpy) { char **path; int tmp = 0; n = 0; for (path = x_sockets; *path; ++path) { tmp = try_socket (&s[n], dpy, *path); if (tmp == -1) { if (errno != ENOTDIR && errno != ENOENT) return -1; } else if (tmp == 1) { while(--n >= 0) { close (s[n].fd); free (s[n].pathname); } break; } else if (tmp == 0) ++n; } if (tmp == 1) continue; #ifdef MAY_HAVE_X11_PIPES for (path = x_pipes; *path; ++path) { tmp = try_pipe (&s[n], dpy, *path); if (tmp == -1) { if (errno != ENOTDIR && errno != ENOENT && errno != ENOSYS) return -1; } else if (tmp == 1) { while (--n >= 0) { close (s[n].fd); free (s[n].pathname); } break; } else if (tmp == 0) ++n; } if (tmp == 1) continue; #endif if (tcp_socket) { tmp = try_tcp (&s[n], dpy); if (tmp == -1) return -1; else if (tmp == 1) { while (--n >= 0) { close (s[n].fd); free (s[n].pathname); } break; } else if (tmp == 0) ++n; } break; } if (dpy == 256) errx (1, "no free x-servers"); for (i = 0; i < n; ++i) if (s[i].flags & LISTENP && listen (s[i].fd, SOMAXCONN) < 0) err (1, "listen %s", s[i].pathname ? s[i].pathname : "tcp"); *number = n; *sockets = s; return dpy; } /* * Change owner on the `n' sockets in `sockets' to `uid', `gid'. * Return 0 is succesful or -1 if an error occurred. */ int chown_xsockets (int n, struct x_socket *sockets, uid_t uid, gid_t gid) { int i; for (i = 0; i < n; ++i) if (sockets[i].pathname != NULL) if (chown (sockets[i].pathname, uid, gid) < 0) return -1; return 0; } /* * Connect to local display `dnr' with local transport or TCP. * Return a file descriptor. */ int connect_local_xsocket (unsigned dnr) { int fd; char **path; for (path = x_sockets; *path; ++path) { struct sockaddr_un addr; fd = socket (AF_UNIX, SOCK_STREAM, 0); if (fd < 0) break; memset (&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; snprintf (addr.sun_path, sizeof(addr.sun_path), *path, dnr); if (connect (fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) return fd; close(fd); } { struct sockaddr_in addr; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) err (1, "socket AF_INET"); memset (&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = htons(6000 + dnr); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) return fd; close(fd); } err (1, "connecting to local display %u", dnr); } /* * Create a cookie file with a random cookie for the localhost. The * file name will be stored in `xauthfile' (but not larger than * `xauthfile_size'), and the cookie returned in `cookie', `cookie_sz'. * Return 0 if succesful, or errno. */ int create_and_write_cookie (char *xauthfile, size_t xauthfile_size, u_char *cookie, size_t cookie_sz) { Xauth auth; char tmp[64]; int fd; FILE *f; char hostname[MaxHostNameLen]; int saved_errno; gethostname (hostname, sizeof(hostname)); auth.family = FamilyLocal; auth.address = hostname; auth.address_length = strlen(auth.address); snprintf (tmp, sizeof(tmp), "%d", display_num); auth.number_length = strlen(tmp); auth.number = tmp; auth.name = COOKIE_TYPE; auth.name_length = strlen(auth.name); auth.data_length = cookie_sz; auth.data = (char*)cookie; #ifdef KRB5 krb5_generate_random_block (cookie, cookie_sz); #else krb_generate_random_block (cookie, cookie_sz); #endif strlcpy(xauthfile, "/tmp/AXXXXXX", xauthfile_size); fd = mkstemp(xauthfile); if(fd < 0) { saved_errno = errno; syslog(LOG_ERR, "create_and_write_cookie: mkstemp: %m"); return saved_errno; } f = fdopen(fd, "r+"); if(f == NULL){ saved_errno = errno; close(fd); return errno; } if(XauWriteAuth(f, &auth) == 0) { saved_errno = errno; fclose(f); return saved_errno; } /* * I would like to write a cookie for localhost:n here, but some * stupid code in libX11 will not look for cookies of that type, * so we are forced to use FamilyWild instead. */ auth.family = FamilyWild; auth.address_length = 0; if (XauWriteAuth(f, &auth) == 0) { saved_errno = errno; fclose (f); return saved_errno; } if(fclose(f)) return errno; return 0; } /* * Verify and remove cookies. Read and parse a X-connection from * `fd'. Check the cookie used is the same as in `cookie'. Remove the * cookie and copy the rest of it to `sock'. * Expect cookies iff cookiesp. * Return 0 iff ok. * * The protocol is as follows: * * C->S: [Bl] 1 * unused 1 * protocol major version 2 * protocol minor version 2 * length of auth protocol name(n) 2 * length of auth protocol data 2 * unused 2 * authorization protocol name n * pad pad(n) * authorization protocol data d * pad pad(d) * * S->C: Failed * 0 1 * length of reason 1 * protocol major version 2 * protocol minor version 2 * length in 4 bytes unit of * additional data (n+p)/4 2 * reason n * unused p = pad(n) */ int verify_and_remove_cookies (int fd, int sock, int cookiesp) { u_char beg[12]; int bigendianp; unsigned n, d, npad, dpad; char *protocol_name, *protocol_data; u_char zeros[6] = {0, 0, 0, 0, 0, 0}; u_char refused[20] = {0, 10, 0, 0, /* protocol major version */ 0, 0, /* protocol minor version */ 0, 0, /* length of additional data / 4 */ 'b', 'a', 'd', ' ', 'c', 'o', 'o', 'k', 'i', 'e', 0, 0}; if (net_read (fd, beg, sizeof(beg)) != sizeof(beg)) return 1; if (net_write (sock, beg, 6) != 6) return 1; bigendianp = beg[0] == 'B'; if (bigendianp) { n = (beg[6] << 8) | beg[7]; d = (beg[8] << 8) | beg[9]; } else { n = (beg[7] << 8) | beg[6]; d = (beg[9] << 8) | beg[8]; } npad = (4 - (n % 4)) % 4; dpad = (4 - (d % 4)) % 4; protocol_name = malloc(n + npad); if (n + npad != 0 && protocol_name == NULL) return 1; protocol_data = malloc(d + dpad); if (d + dpad != 0 && protocol_data == NULL) { free (protocol_name); return 1; } if (net_read (fd, protocol_name, n + npad) != n + npad) goto fail; if (net_read (fd, protocol_data, d + dpad) != d + dpad) goto fail; if (cookiesp) { if (strncmp (protocol_name, COOKIE_TYPE, strlen(COOKIE_TYPE)) != 0) goto refused; if (d != cookie_len || memcmp (protocol_data, cookie, cookie_len) != 0) goto refused; } free (protocol_name); free (protocol_data); if (net_write (sock, zeros, 6) != 6) return 1; return 0; refused: refused[2] = beg[2]; refused[3] = beg[3]; refused[4] = beg[4]; refused[5] = beg[5]; if (bigendianp) refused[7] = 3; else refused[6] = 3; net_write (fd, refused, sizeof(refused)); fail: free (protocol_name); free (protocol_data); return 1; } /* * Return 0 iff `cookie' is compatible with the cookie for the * localhost with name given in `ai' (or `hostname') and display * number in `disp_nr'. */ static int match_local_auth (Xauth* auth, struct addrinfo *ai, const char *hostname, int disp_nr) { int auth_disp; char *tmp_disp; struct addrinfo *a; tmp_disp = malloc(auth->number_length + 1); if (tmp_disp == NULL) return -1; memcpy(tmp_disp, auth->number, auth->number_length); tmp_disp[auth->number_length] = '\0'; auth_disp = atoi(tmp_disp); free (tmp_disp); if (auth_disp != disp_nr) return 1; for (a = ai; a != NULL; a = a->ai_next) { if ((auth->family == FamilyLocal || auth->family == FamilyWild) && a->ai_canonname != NULL && strncmp (auth->address, a->ai_canonname, auth->address_length) == 0) return 0; } if (hostname != NULL && (auth->family == FamilyLocal || auth->family == FamilyWild) && strncmp (auth->address, hostname, auth->address_length) == 0) return 0; return 1; } /* * Find `our' cookie from the cookie file `f' and return it or NULL. */ static Xauth* find_auth_cookie (FILE *f) { Xauth *ret = NULL; char local_hostname[MaxHostNameLen]; char *display = getenv("DISPLAY"); char d[MaxHostNameLen + 4]; char *colon; struct addrinfo *ai; struct addrinfo hints; int disp; int error; if(display == NULL) display = ":0"; strlcpy(d, display, sizeof(d)); display = d; colon = strchr (display, ':'); if (colon == NULL) disp = 0; else { *colon = '\0'; disp = atoi (colon + 1); } if (strcmp (display, "") == 0 || strncmp (display, "unix", 4) == 0 || strncmp (display, "localhost", 9) == 0) { gethostname (local_hostname, sizeof(local_hostname)); display = local_hostname; } memset (&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; error = getaddrinfo (display, NULL, &hints, &ai); if (error) ai = NULL; for (; (ret = XauReadAuth (f)) != NULL; XauDisposeAuth(ret)) { if (match_local_auth (ret, ai, display, disp) == 0) { if (ai != NULL) freeaddrinfo (ai); return ret; } } if (ai != NULL) freeaddrinfo (ai); return NULL; } /* * Get rid of the cookie that we were sent and get the correct one * from our own cookie file instead. */ int replace_cookie(int xserver, int fd, char *filename, int cookiesp) /* XXX */ { u_char beg[12]; int bigendianp; unsigned n, d, npad, dpad; FILE *f; u_char zeros[6] = {0, 0, 0, 0, 0, 0}; if (net_read (fd, beg, sizeof(beg)) != sizeof(beg)) return 1; if (net_write (xserver, beg, 6) != 6) return 1; bigendianp = beg[0] == 'B'; if (bigendianp) { n = (beg[6] << 8) | beg[7]; d = (beg[8] << 8) | beg[9]; } else { n = (beg[7] << 8) | beg[6]; d = (beg[9] << 8) | beg[8]; } if (n != 0 || d != 0) return 1; f = fopen(filename, "r"); if (f != NULL) { Xauth *auth = find_auth_cookie (f); u_char len[6] = {0, 0, 0, 0, 0, 0}; fclose (f); if (auth != NULL) { n = auth->name_length; d = auth->data_length; } else { n = 0; d = 0; } if (bigendianp) { len[0] = n >> 8; len[1] = n & 0xFF; len[2] = d >> 8; len[3] = d & 0xFF; } else { len[0] = n & 0xFF; len[1] = n >> 8; len[2] = d & 0xFF; len[3] = d >> 8; } if (net_write (xserver, len, 6) != 6) { XauDisposeAuth(auth); return 1; } if(n != 0 && net_write (xserver, auth->name, n) != n) { XauDisposeAuth(auth); return 1; } npad = (4 - (n % 4)) % 4; if (npad && net_write (xserver, zeros, npad) != npad) { XauDisposeAuth(auth); return 1; } if (d != 0 && net_write (xserver, auth->data, d) != d) { XauDisposeAuth(auth); return 1; } XauDisposeAuth(auth); dpad = (4 - (d % 4)) % 4; if (dpad && net_write (xserver, zeros, dpad) != dpad) return 1; } else { if(net_write(xserver, zeros, 6) != 6) return 1; } return 0; } /* * Some simple controls on the address and corresponding socket */ int suspicious_address (int sock, struct sockaddr *addr) { char data[40]; socklen_t len = sizeof(data); switch (addr->sa_family) { case AF_INET: return ((struct sockaddr_in *)addr)->sin_addr.s_addr != htonl(INADDR_LOOPBACK) #if defined(IP_OPTIONS) && defined(HAVE_GETSOCKOPT) || getsockopt (sock, IPPROTO_IP, IP_OPTIONS, data, &len) < 0 || len != 0 #endif ; break; #ifdef HAVE_IPV6 case AF_INET6: /* XXX check route headers */ return !IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6*)addr)->sin6_addr); #endif default: return 1; } } /* * This really sucks, but these functions are used and if we're not * linking against libkrb they don't exist. Using the heimdal storage * functions will not work either cause we do not always link with * libkrb5 either. */ #ifndef KRB4 int krb_get_int(void *f, u_int32_t *to, int size, int lsb) { int i; unsigned char *from = (unsigned char *)f; *to = 0; if(lsb){ for(i = size-1; i >= 0; i--) *to = (*to << 8) | from[i]; }else{ for(i = 0; i < size; i++) *to = (*to << 8) | from[i]; } return size; } int krb_put_int(u_int32_t from, void *to, size_t rem, int size) { int i; unsigned char *p = (unsigned char *)to; if (rem < size) return -1; for(i = size - 1; i >= 0; i--){ p[i] = from & 0xff; from >>= 8; } return size; } #endif /* !KRB4 */