summaryrefslogtreecommitdiffstats
path: root/x11vnc/unixpw.c
diff options
context:
space:
mode:
Diffstat (limited to 'x11vnc/unixpw.c')
-rw-r--r--x11vnc/unixpw.c622
1 files changed, 531 insertions, 91 deletions
diff --git a/x11vnc/unixpw.c b/x11vnc/unixpw.c
index 1ab59d7..c5bf198 100644
--- a/x11vnc/unixpw.c
+++ b/x11vnc/unixpw.c
@@ -13,39 +13,37 @@ extern char *ptsname(int);
#include "xinerama.h"
#include <rfb/default8x16.h>
-/* much to do for it to work on *BSD ... */
-
#if LIBVNCSERVER_HAVE_FORK
-#if LIBVNCSERVER_HAVE_SETSID
#if LIBVNCSERVER_HAVE_SYS_WAIT_H
-#if LIBVNCSERVER_HAVE_PWD_H
-#if LIBVNCSERVER_HAVE_SETUID
#if LIBVNCSERVER_HAVE_WAITPID
-#if LIBVNCSERVER_HAVE_TERMIOS_H
-#if LIBVNCSERVER_HAVE_SYS_IOCTL_H
-#if LIBVNCSERVER_HAVE_GRANTPT
#define UNIXPW
-#include <sys/ioctl.h>
-#include <termios.h>
-#endif
-#endif
#endif
#endif
#endif
+
+#if LIBVNCSERVER_HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
#endif
+#if LIBVNCSERVER_HAVE_TERMIOS_H
+#include <termios.h>
#endif
+#if 0
+#include <sys/stropts.h>
#endif
+#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
+#define IS_BSD
#endif
void unixpw_screen(int init);
void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init);
-void unixpw_accept(void);
+void unixpw_accept(char *user);
void unixpw_deny(void);
+int su_verify(char *user, char *pass);
static int white(void);
static int text_x(void);
static int text_y(void);
-static int su_verify(char *user, char *pass);
+static void set_db(void);
static void unixpw_verify(char *user, char *pass);
int unixpw_in_progress = 0;
@@ -56,6 +54,8 @@ static int in_login = 0, in_passwd = 0, tries = 0;
static int char_row = 0, char_col = 0;
static int char_x = 0, char_y = 0, char_w = 8, char_h = 16;
+static int db = 0;
+
static int white(void) {
static unsigned long black_pix = 0, white_pix = 1, set = 0;
@@ -108,20 +108,203 @@ void unixpw_screen(int init) {
mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0);
}
-static int su_verify(char *user, char *pass) {
-#ifdef UNIXPW
- int status, fd, sfd;
+
+#ifdef MAXPATHLEN
+static char slave_str[MAXPATHLEN];
+#else
+static char slave_str[4096];
+#endif
+
+char *get_pty_ptmx(int *fd_p) {
char *slave;
+ int fd = -1, i, ndevs = 4, tmp;
+ char *devs[] = {
+ "/dev/ptmx",
+ "/dev/ptm/clone",
+ "/dev/ptc",
+ "/dev/ptmx_bsd"
+ };
+
+ *fd_p = -1;
+
+#if LIBVNCSERVER_HAVE_GRANTPT
+
+ for (i=0; i < ndevs; i++) {
+
+#ifdef O_NOCTTY
+ fd = open(devs[i], O_RDWR|O_NOCTTY);
+#else
+ fd = open(devs[i], O_RDWR);
+#endif
+ if (fd >= 0) {
+ break;
+ }
+ }
+
+ if (fd < 0) {
+ rfbLogPerror("open /dev/ptmx");
+ return NULL;
+ }
+
+#if 0
+#if defined(FIONBIO)
+ tmp = 1;
+ ioctl(fd, FIONBIO, &tmp);
+#endif
+#endif
+
+#if LIBVNCSERVER_HAVE_SYS_IOCTL_H && defined(TIOCPKT)
+ tmp = 0;
+ ioctl(fd, TIOCPKT, (char *) &tmp);
+#endif
+
+ if (grantpt(fd) != 0) {
+ rfbLogPerror("grantpt");
+ close(fd);
+ return NULL;
+ }
+ if (unlockpt(fd) != 0) {
+ rfbLogPerror("unlockpt");
+ close(fd);
+ return NULL;
+ }
+
+ slave = ptsname(fd);
+ if (! slave) {
+ rfbLogPerror("ptsname");
+ close(fd);
+ return NULL;
+ }
+
+#if LIBVNCSERVER_HAVE_SYS_IOCTL_H && defined(TIOCFLUSH)
+ ioctl(fd, TIOCFLUSH, (char *) 0);
+#endif
+
+
+
+ strcpy(slave_str, slave);
+ *fd_p = fd;
+ return slave_str;
+
+#else
+ return NULL;
+
+#endif /* GRANTPT */
+}
+
+
+char *get_pty_loop(int *fd_p) {
+ char *slave;
+ char master_str[16];
+ int fd = -1, i;
+ char c;
+
+ *fd_p = -1;
+
+ /* for *BSD loop over /dev/ptyXY */
+
+ for (c = 'p'; c <= 'z'; c++) {
+ for (i=0; i < 16; i++) {
+ sprintf(master_str, "/dev/pty%c%x", c, i);
+#ifdef O_NOCTTY
+ fd = open(master_str, O_RDWR|O_NOCTTY);
+#else
+ fd = open(master_str, O_RDWR);
+#endif
+ if (fd >= 0) {
+ break;
+ }
+ }
+ if (fd >= 0) {
+ break;
+ }
+ }
+ if (fd < 0) {
+ return NULL;
+ }
+
+#if LIBVNCSERVER_HAVE_SYS_IOCTL_H && defined(TIOCFLUSH)
+ ioctl(fd, TIOCFLUSH, (char *) 0);
+#endif
+
+ sprintf(slave_str, "/dev/tty%c%x", c, i);
+ *fd_p = fd;
+ return slave_str;
+}
+
+char *get_pty(int *fd_p) {
+ if (getenv("BSD_PTY")) {
+ return get_pty_loop(fd_p);
+ }
+#ifdef IS_BSD
+ return get_pty_loop(fd_p);
+#else
+#if LIBVNCSERVER_HAVE_GRANTPT
+ return get_pty_ptmx(fd_p);
+#else
+ return get_pty_loop(fd_p);
+#endif
+#endif
+}
+
+void try_to_be_nobody(void) {
+
+#if LIBVNCSERVER_HAVE_PWD_H
+ struct passwd *pw;
+ pw = getpwnam("nobody");
+
+ if (pw) {
+#if LIBVNCSERVER_HAVE_SETUID
+ setuid(pw->pw_uid);
+#endif
+#if LIBVNCSERVER_HAVE_SETEUID
+ seteuid(pw->pw_uid);
+#endif
+#if LIBVNCSERVER_HAVE_SETGID
+ setgid(pw->pw_gid);
+#endif
+#if LIBVNCSERVER_HAVE_SETEGID
+ setegid(pw->pw_gid);
+#endif
+ }
+
+#endif /* PWD_H */
+}
+
+
+static int slave_fd = -1;
+static void close_alarm (int sig) {
+ if (slave_fd >= 0) {
+ close(slave_fd);
+ }
+}
+
+int su_verify(char *user, char *pass) {
+#ifndef UNIXPW
+ return 0;
+#else
+ int i, j, status, fd = -1, sfd, tfd;
+ char *slave, *bin_true = NULL, *bin_su = NULL;
pid_t pid, pidw;
struct stat sbuf;
+ static int first = 1;
+ char instr[16];
+
+ if (first) {
+ set_db();
+ first = 0;
+ }
if (unixpw_list) {
- char *p, *str = strdup(unixpw_list);
+ char *p, *q, *str = strdup(unixpw_list);
int ok = 0;
p = strtok(str, ",");
while (p) {
- if (!strcmp(user, p)) {
+ if ( (q = strchr(p, ':')) != NULL ) {
+ *q = '\0'; /* get rid of options. */
+ }
+ if (!strcmp(user, p) || !strcmp("*", p)) {
ok = 1;
break;
}
@@ -132,39 +315,40 @@ static int su_verify(char *user, char *pass) {
return 0;
}
}
- if (stat("/bin/su", &sbuf) != 0) {
+
+ if (stat("/bin/su", &sbuf) == 0) {
+ bin_su = "/bin/su";
+ } else if (stat("/usr/bin/su", &sbuf) == 0) {
+ bin_su = "/usr/bin/su";
+ }
+ if (bin_su == NULL) {
rfbLogPerror("existence /bin/su");
return 0;
}
- if (stat("/bin/true", &sbuf) != 0) {
+
+ if (stat("/bin/true", &sbuf) == 0) {
+ bin_true = "/bin/true";
+ } if (stat("/usr/bin/true", &sbuf) == 0) {
+ bin_true = "/usr/bin/true";
+ }
+ if (bin_true == NULL) {
rfbLogPerror("existence /bin/true");
return 0;
}
-
- fd = open("/dev/ptmx", O_RDWR|O_NOCTTY);
- if (fd < 0) {
- rfbLogPerror("open /dev/ptmx");
+ slave = get_pty(&fd);
+ if (slave == NULL) {
+ rfbLogPerror("get_pty failed.");
return 0;
}
+if (db) fprintf(stderr, "slave is: %s fd=%d\n", slave, fd);
- if (grantpt(fd) != 0) {
- rfbLogPerror("grantpt");
- close(fd);
- return 0;
- }
- if (unlockpt(fd) != 0) {
- rfbLogPerror("unlockpt");
- close(fd);
+ if (fd < 0) {
+ rfbLogPerror("get_pty fd < 0");
return 0;
}
- slave = ptsname(fd);
- if (! slave) {
- rfbLogPerror("ptsname");
- close(fd);
- return 0;
- }
+ fcntl(fd, F_SETFD, 1);
pid = fork();
if (pid < 0) {
@@ -175,40 +359,28 @@ static int su_verify(char *user, char *pass) {
if (pid == 0) {
int ttyfd;
- struct passwd *pw;
-
- close(fd);
-
- pw = getpwnam("nobody");
-
- if (pw) {
- setuid(pw->pw_uid);
-#if LIBVNCSERVER_HAVE_SETEUID
- seteuid(pw->pw_uid);
-#endif
- setgid(pw->pw_gid);
-#if LIBVNCSERVER_HAVE_SETEGID
- setegid(pw->pw_gid);
-#endif
- }
-
- if (getuid() == 0 || geteuid() == 0) {
- fprintf(stderr, "could not switch to user nobody.\n");
- exit(1);
- }
+ char tmp[256];
+#if LIBVNCSERVER_HAVE_SETSID
if (setsid() == -1) {
perror("setsid");
exit(1);
}
+#else
+ if (setpgrp() == -1) {
+ perror("setpgrp");
+ exit(1);
+ }
-#ifdef TIOCNOTTY
+#if LIBVNCSERVER_HAVE_SYS_IOCTL_H && defined(TIOCNOTTY)
ttyfd = open("/dev/tty", O_RDWR);
if (ttyfd >= 0) {
- (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
+ (void) ioctl(ttyfd, TIOCNOTTY, (char *) 0);
close(ttyfd);
}
-#endif
+#endif
+
+#endif /* SETSID */
close(0);
close(1);
@@ -216,24 +388,189 @@ static int su_verify(char *user, char *pass) {
sfd = open(slave, O_RDWR);
if (sfd < 0) {
- fprintf(stderr, "failed: %s\n", slave);
- perror("open");
exit(1);
}
-#ifdef TIOCSCTTY
- if (ioctl(sfd, TIOCSCTTY, (char *) 0) != 0) {
- perror("ioctl");
+ /* sfd should be 0 since we closed 0. */
+
+#ifdef F_SETFL
+ fcntl (sfd, F_SETFL, O_NONBLOCK);
+#endif
+ if (fcntl(sfd, F_DUPFD, 1) == -1) {
+ exit(1);
+ }
+ if (fcntl(sfd, F_DUPFD, 2) == -1) {
+ exit(1);
+ }
+
+ unlink("/tmp/isatty");
+ unlink("/tmp/isastream");
+#if LIBVNCSERVER_HAVE_SYS_IOCTL_H
+#if 0
+ if (isastream(sfd)) {
+tfd = open("/tmp/isastream", O_CREAT|O_WRONLY, 0600);
+close(tfd);
+ ioctl(sfd, I_PUSH, "ptem");
+ ioctl(sfd, I_PUSH, "ldterm");
+ ioctl(sfd, I_PUSH, "ttcompat");
+ }
+#endif
+#if 1
+#if defined(TIOCSCTTY) && !defined(sun) && !defined(hpux)
+ ioctl(sfd, TIOCSCTTY, (char *) 0);
+#endif
+#endif
+ if (isatty(sfd)) {
+ char nam[256];
+tfd = open("/tmp/isatty", O_CREAT|O_WRONLY, 0600);
+close(tfd);
+ sprintf(nam, "stty -a < %s > /tmp/isatty 2>&1", slave);
+ system(nam);
+ }
+
+#endif /* SYS_IOCTL_H */
+
+ chdir("/");
+
+ try_to_be_nobody();
+#if LIBVNCSERVER_HAVE_GETUID
+ if (getuid() == 0 || geteuid() == 0) {
exit(1);
}
+#else
+ exit(1);
#endif
- execlp("/bin/su", "/bin/su", user, "-c", "/bin/true",
- (char *) NULL);
+
+ set_env("LC_ALL", "C");
+ set_env("LANG", "C");
+ set_env("SHELL", "/bin/sh");
+
+ execlp(bin_su, bin_su, user, "-c", bin_true, (char *) NULL);
exit(1);
}
+ if (db) fprintf(stderr, "pid: %d\n", pid);
+ if (db > 3) {
+ char cmd[32];
+ usleep( 100 * 1000 );
+ sprintf(cmd, "ps wu %d", pid);
+ system(cmd);
+ sprintf(cmd, "stty -a < %s", slave);
+ system(cmd);
+ }
+
usleep( 500 * 1000 );
+
+ /* send the password "early" (i.e. before we drain) */
+if (0) {
+ int k;
+ for (k = 0; k < strlen(pass); k++) {
+ write(fd, pass+k, 1);
+ usleep(100 * 1000);
+ }
+} else {
write(fd, pass, strlen(pass));
+}
+
+ /*
+ * set an alarm for blocking read() to close the master
+ * (presumably terminating the child. we avoid SIGTERM for now)
+ */
+ slave_fd = fd;
+ signal(SIGALRM, close_alarm);
+ alarm(10);
+
+ /*
+ * In addition to checking exit code below, we watch for the
+ * appearance of the string "Password:". BSD does not seem to
+ * ask for a password trying to su to yourself.
+ */
+ for (i=0; i<16; i++) {
+ instr[i] = '\0';
+ }
+ j = 0;
+ for (i=0; i < strlen("Password:"); i++) {
+ char pstr[] = "password:";
+ char buf[2];
+ int n;
+
+ buf[0] = '\0';
+ buf[1] = '\0';
+
+ n = read(fd, buf, 1);
+
+if (db == 1) fprintf(stderr, "%d ", n, db > 1 ? buf : "");
+if (db > 1) fprintf(stderr, "%s", buf);
+
+ if (db > 3 && n == 1 && buf[0] == ':') {
+ char cmd[32];
+ usleep( 100 * 1000 );
+ sprintf(cmd, "ps wu %d", pid);
+ system(cmd);
+ sprintf(cmd, "stty -a < %s", slave);
+ system(cmd);
+ }
+ if (n == 1) {
+ if (isspace(buf[0])) {
+ continue;
+ }
+ instr[j++] = tolower(buf[0]);
+ }
+ if (n <= 0 || strstr(pstr, instr) != pstr) {
+ rfbLog("\"Password:\" did not appear: '%s' n=%d\n",
+ instr, n);
+ if (db > 3 && n == 1) {
+ continue;
+ }
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+ slave_fd = -1;
+ close(fd);
+ kill(pid, SIGTERM);
+ waitpid(pid, &status, WNOHANG);
+ return 0;
+ }
+ }
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+
+ usleep( 250 * 1000 );
+
+#if 0
+ tcdrain(fd);
+#endif
+
+ signal(SIGALRM, close_alarm);
+ alarm(15);
+
+ /*
+ * try to drain the output, hopefully never as much as 4096 (motd?)
+ * if we don't drain we may block at waitpid. If we close(fd), the
+ * make cause child to die by signal.
+ */
+ for (i = 0; i<4096; i++) {
+ char buf[2];
+ int n;
+
+ buf[0] = '\0';
+ buf[1] = '\0';
+
+ n = read(fd, buf, 1);
+
+if (db == 1) fprintf(stderr, "%d ", n, db > 1 ? buf : "");
+if (db > 1) fprintf(stderr, "%s", buf);
+
+ if (n <= 0) {
+ break;
+ }
+ }
+
+if (db) fprintf(stderr, "\n");
+
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+ slave_fd = -1;
+
pidw = waitpid(pid, &status, 0);
close(fd);
@@ -241,26 +578,23 @@ static int su_verify(char *user, char *pass) {
return 0;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
- return 1;
+ return 1; /* this is the only return of success. */
} else {
return 0;
}
-#else
- return 0;
-#endif
+#endif /* UNIXPW */
}
-static int db = 0;
-
static void unixpw_verify(char *user, char *pass) {
int x, y;
char li[] = "Login incorrect";
char log[] = "login: ";
if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "********");
+ rfbLog("unixpw_verify: %s\n", user);
if (su_verify(user, pass)) {
- unixpw_accept();
+ unixpw_accept(user);
return;
}
@@ -290,16 +624,20 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "***
}
}
+static void set_db(void) {
+ if (getenv("DEBUG_UNIXPW")) {
+ db = atoi(getenv("DEBUG_UNIXPW"));
+ }
+}
+
void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) {
int x, y, i, nmax = 100;
static char user[100], pass[100];
static int u_cnt = 0, p_cnt = 0, first = 1;
- char str[100];
+ char keystr[100];
if (first) {
- if (getenv("DEBUG_UNIXPW")) {
- db = atoi(getenv("DEBUG_UNIXPW"));
- }
+ set_db();
first = 0;
}
@@ -319,7 +657,18 @@ void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) {
return;
}
- if (down) {
+ X_LOCK;
+ sprintf(keystr, "%s", XKeysymToString(keysym));
+ X_UNLOCK;
+
+ if (db > 2) {
+ fprintf(stderr, "%s / %s 0x%x %s\n", in_login ? "login":"pass ",
+ down ? "down":"up ", keysym, keystr);
+ }
+
+ if (keysym == XK_Return || keysym == XK_Linefeed) {
+ ; /* let "up" pass down below for Return case */
+ } else if (! down) {
return;
}
@@ -340,6 +689,14 @@ void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) {
if (keysym == XK_Return || keysym == XK_Linefeed) {
char pw[] = "Password: ";
+ if (down) {
+ /*
+ * require Up so the Return Up is not processed
+ * by the normal session after login.
+ */
+ return;
+ }
+
in_login = 0;
in_passwd = 1;
@@ -360,23 +717,23 @@ void unixpw_keystroke(rfbBool down, rfbKeySym keysym, int init) {
}
if (u_cnt >= nmax - 1) {
rfbLog("unixpw_deny: username too long\n");
+ for (i=0; i<nmax; i++) {
+ user[i] = '\0';
+ pass[i] = '\0';
+ }
unixpw_deny();
return;
}
- X_LOCK;
- sprintf(str, "%s", XKeysymToString(keysym));
- X_UNLOCK;
-
- user[u_cnt++] = str[0];
+ user[u_cnt++] = keystr[0];
x = text_x();
y = text_y();
-if (db) fprintf(stderr, "u_cnt: %d %d/%d ks: 0x%x %s\n", u_cnt, x, y, keysym, str);
+if (db && db <= 2) fprintf(stderr, "u_cnt: %d %d/%d ks: 0x%x %s\n", u_cnt, x, y, keysym, keystr);
- str[1] = '\0';
- rfbDrawString(screen, &default8x16Font, x, y, str, white());
+ keystr[1] = '\0';
+ rfbDrawString(screen, &default8x16Font, x, y, keystr, white());
mark_rect_as_modified(x, y-char_h, x+char_w, y, 0);
char_col++;
@@ -390,10 +747,21 @@ if (db) fprintf(stderr, "u_cnt: %d %d/%d ks: 0x%x %s\n", u_cnt, x, y, keysym, s
return;
}
if (keysym == XK_Return || keysym == XK_Linefeed) {
+ if (down) {
+ /*
+ * require Up so the Return Up is not processed
+ * by the normal session after login.
+ */
+ return;
+ }
in_login = 0;
in_passwd = 0;
pass[p_cnt++] = '\n';
unixpw_verify(user, pass);
+ for (i=0; i<nmax; i++) {
+ user[i] = '\0';
+ pass[i] = '\0';
+ }
return;
}
if (keysym <= ' ' || keysym >= 0x7f) {
@@ -401,14 +769,86 @@ if (db) fprintf(stderr, "u_cnt: %d %d/%d ks: 0x%x %s\n", u_cnt, x, y, keysym, s
}
if (p_cnt >= nmax - 2) {
rfbLog("unixpw_deny: password too long\n");
+ for (i=0; i<nmax; i++) {
+ user[i] = '\0';
+ pass[i] = '\0';
+ }
unixpw_deny();
return;
}
pass[p_cnt++] = (char) keysym;
+ } else {
+ /* should not happen... clean up a bit. */
+ u_cnt = 0;
+ p_cnt = 0;
+ for (i=0; i<nmax; i++) {
+ user[i] = '\0';
+ pass[i] = '\0';
+ }
}
}
-void unixpw_accept(void) {
+static void apply_opts (char *user) {
+ char *p, *q, *str, *opts = NULL, *opts_star = NULL;
+ ClientData *cd = (ClientData *) unixpw_client->clientData;
+ rfbClientPtr cl = unixpw_client;
+ int i;
+
+ if (! unixpw_list) {
+ return;
+ }
+ str = strdup(unixpw_list);
+
+ /* apply any per-user options. */
+ p = strtok(str, ",");
+ while (p) {
+ if ( (q = strchr(p, ':')) != NULL ) {
+ *q = '\0'; /* get rid of options. */
+ } else {
+ p = strtok(NULL, ",");
+ continue;
+ }
+ if (!strcmp(user, p)) {
+ opts = strdup(q+1);
+ }
+ if (!strcmp("*", p)) {
+ opts_star = strdup(q+1);
+ }
+ p = strtok(NULL, ",");
+ }
+ free(str);
+
+ for (i=0; i < 2; i++) {
+ char *s = (i == 0) ? opts_star : opts;
+ if (s == NULL) {
+ continue;
+ }
+ p = strtok(s, "+");
+ while (p) {
+ if (!strcmp(p, "viewonly")) {
+ cl->viewOnly = TRUE;
+ strncpy(cd->input, "-", CILEN);
+ } else if (!strcmp(p, "fullaccess")) {
+ cl->viewOnly = FALSE;
+ strncpy(cd->input, "-", CILEN);
+ } else if ((q = strstr(p, "input=")) == p) {
+ q += strlen("input=");
+ strncpy(cd->input, q, CILEN);
+ } else if (!strcmp(p, "deny")) {
+ cl->viewOnly = TRUE;
+ unixpw_deny();
+ break;
+ }
+ p = strtok(NULL, "+");
+ }
+ free(s);
+ }
+}
+
+void unixpw_accept(char *user) {
+
+ apply_opts(user);
+
unixpw_in_progress = 0;
unixpw_client = NULL;
mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0);