diff options
author | runge <runge> | 2006-01-09 01:54:38 +0000 |
---|---|---|
committer | runge <runge> | 2006-01-09 01:54:38 +0000 |
commit | 71f2ec79180185a6c3db0c87f9d53c491dc31e76 (patch) | |
tree | 67c341571cbeb1bd9a0744cc8eb03b30ef04f381 /x11vnc/user.c | |
parent | def301266373e462f4a5e90eab443087ccfc7ccc (diff) | |
download | libtdevnc-71f2ec79180185a6c3db0c87f9d53c491dc31e76.tar.gz libtdevnc-71f2ec79180185a6c3db0c87f9d53c491dc31e76.zip |
x11vnc: the big split.
Diffstat (limited to 'x11vnc/user.c')
-rw-r--r-- | x11vnc/user.c | 930 |
1 files changed, 930 insertions, 0 deletions
diff --git a/x11vnc/user.c b/x11vnc/user.c new file mode 100644 index 0000000..6d173ab --- /dev/null +++ b/x11vnc/user.c @@ -0,0 +1,930 @@ +/* -- user.c -- */ + +#include "x11vnc.h" +#include "solid.h" +#include "cleanup.h" +#include "scan.h" +#include "screen.h" + +void check_switched_user(void); +void lurk_loop(char *str); +int switch_user(char *user, int fb_mode); +int read_passwds(char *passfile); +void install_passwds(void); +void check_new_passwds(void); + + +static void switch_user_task_dummy(void); +static void switch_user_task_solid_bg(void); +static char *get_login_list(int with_display); +static char **user_list(char *user_str); +static void user2uid(char *user, uid_t *uid, char **name, char **home); +static int lurk(char **users); +static int guess_user_and_switch(char *str, int fb_mode); +static int try_user_and_display(uid_t uid, char *dpystr); +static int switch_user_env(uid_t uid, char *name, char *home, int fb_mode); +static void try_to_switch_users(void); + + +/* tasks for after we switch */ +static void switch_user_task_dummy(void) { + ; /* dummy does nothing */ +} +static void switch_user_task_solid_bg(void) { + /* we have switched users, some things to do. */ + if (use_solid_bg && client_count) { + solid_bg(0); + } +} + +void check_switched_user(void) { + static time_t sched_switched_user = 0; + static int did_solid = 0; + static int did_dummy = 0; + int delay = 15; + time_t now = time(0); + + if (started_as_root == 1 && users_list) { + try_to_switch_users(); + if (started_as_root == 2) { + /* + * schedule the switch_user_tasks() call + * 15 secs is for piggy desktops to start up. + * might not be enough for slow machines... + */ + sched_switched_user = now; + did_dummy = 0; + did_solid = 0; + /* add other activities */ + } + } + if (! sched_switched_user) { + return; + } + + if (! did_dummy) { + switch_user_task_dummy(); + did_dummy = 1; + } + if (! did_solid) { + int doit = 0; + char *ss = solid_str; + if (now >= sched_switched_user + delay) { + doit = 1; + } else if (ss && strstr(ss, "root:") == ss) { + if (now >= sched_switched_user + 3) { + doit = 1; + } + } else if (strcmp("root", guess_desktop())) { + usleep(1000 * 1000); + doit = 1; + } + if (doit) { + switch_user_task_solid_bg(); + did_solid = 1; + } + } + + if (did_dummy && did_solid) { + sched_switched_user = 0; + } +} + +/* utilities for switching users */ +static char *get_login_list(int with_display) { + char *out; +#if LIBVNCSERVER_HAVE_UTMPX_H + int i, cnt, max = 200, ut_namesize = 32; + int dpymax = 1000, sawdpy[1000]; + struct utmpx *utx; + + /* size based on "username:999," * max */ + out = (char *) malloc(max * (ut_namesize+1+3+1) + 1); + out[0] = '\0'; + + for (i=0; i<dpymax; i++) { + sawdpy[i] = 0; + } + + setutxent(); + cnt = 0; + while (1) { + char *user, *line, *host, *id; + char tmp[10]; + int d = -1; + utx = getutxent(); + if (! utx) { + break; + } + if (utx->ut_type != USER_PROCESS) { + continue; + } + user = lblanks(utx->ut_user); + if (*user == '\0') { + continue; + } + if (strchr(user, ',')) { + continue; /* unlikely, but comma is our sep. */ + } + + line = lblanks(utx->ut_line); + host = lblanks(utx->ut_host); + id = lblanks(utx->ut_id); + + if (with_display) { + if (0 && line[0] != ':' && strcmp(line, "dtlocal")) { + /* XXX useful? */ + continue; + } + + if (line[0] == ':') { + if (sscanf(line, ":%d", &d) != 1) { + d = -1; + } + } + if (d < 0 && host[0] == ':') { + if (sscanf(host, ":%d", &d) != 1) { + d = -1; + } + } + if (d < 0 && id[0] == ':') { + if (sscanf(id, ":%d", &d) != 1) { + d = -1; + } + } + + if (d < 0 || d >= dpymax || sawdpy[d]) { + continue; + } + sawdpy[d] = 1; + sprintf(tmp, ":%d", d); + } else { + /* try to eliminate repeats */ + int repeat = 0; + char *q; + + q = out; + while ((q = strstr(q, user)) != NULL) { + char *p = q + strlen(user) + strlen(":DPY"); + if (q == out || *(q-1) == ',') { + /* bounded on left. */ + if (*p == ',' || *p == '\0') { + /* bounded on right. */ + repeat = 1; + break; + } + } + q = p; + } + if (repeat) { + continue; + } + sprintf(tmp, ":DPY"); + } + + if (*out) { + strcat(out, ","); + } + strcat(out, user); + strcat(out, tmp); + + cnt++; + if (cnt >= max) { + break; + } + } + endutxent(); +#else + out = strdup(""); +#endif + return out; +} + +static char **user_list(char *user_str) { + int n, i; + char *p, **list; + + p = user_str; + n = 1; + while (*p++) { + if (*p == ',') { + n++; + } + } + list = (char **) malloc((n+1)*sizeof(char *)); + + p = strtok(user_str, ","); + i = 0; + while (p) { + list[i++] = p; + p = strtok(NULL, ","); + } + list[i] = NULL; + return list; +} + +static void user2uid(char *user, uid_t *uid, char **name, char **home) { + int numerical = 1; + char *q; + + *uid = (uid_t) -1; + *name = NULL; + *home = NULL; + + q = user; + while (*q) { + if (! isdigit(*q++)) { + numerical = 0; + break; + } + } + + if (numerical) { + int u = atoi(user); + + if (u < 0) { + return; + } + *uid = (uid_t) u; + } + +#if LIBVNCSERVER_HAVE_PWD_H + if (1) { + struct passwd *pw; + if (numerical) { + pw = getpwuid(*uid); + } else { + pw = getpwnam(user); + } + if (pw) { + *uid = pw->pw_uid; + *name = pw->pw_name; /* n.b. use immediately */ + *home = pw->pw_dir; + } + } +#endif +} + + +static int lurk(char **users) { + uid_t uid; + int success = 0, dmin = -1, dmax = -1; + char *p, *logins, **u; + + if ((u = users) != NULL && *u != NULL && *(*u) == ':') { + int len; + char *tmp; + + /* extract min and max display numbers */ + tmp = *u; + if (strchr(tmp, '-')) { + if (sscanf(tmp, ":%d-%d", &dmin, &dmax) != 2) { + dmin = -1; + dmax = -1; + } + } + if (dmin < 0) { + if (sscanf(tmp, ":%d", &dmin) != 1) { + dmin = -1; + dmax = -1; + } else { + dmax = dmin; + } + } + if ((dmin < 0 || dmax < 0) || dmin > dmax || dmax > 10000) { + dmin = -1; + dmax = -1; + } + + /* get user logins regardless of having a display: */ + logins = get_login_list(0); + + /* + * now we append the list in users (might as well try + * them) this will probably allow weird ways of starting + * xservers to work. + */ + len = strlen(logins); + u++; + while (*u != NULL) { + len += strlen(*u) + strlen(":DPY,"); + u++; + } + tmp = (char *) malloc(len+1); + strcpy(tmp, logins); + + /* now concatenate them: */ + u = users+1; + while (*u != NULL) { + char *q, chk[100]; + snprintf(chk, 100, "%s:DPY", *u); + q = strstr(tmp, chk); + if (q) { + char *p = q + strlen(chk); + + if (q == tmp || *(q-1) == ',') { + /* bounded on left. */ + if (*p == ',' || *p == '\0') { + /* bounded on right. */ + u++; + continue; + } + } + } + + if (*tmp) { + strcat(tmp, ","); + } + strcat(tmp, *u); + strcat(tmp, ":DPY"); + u++; + } + free(logins); + logins = tmp; + + } else { + logins = get_login_list(1); + } + + p = strtok(logins, ","); + while (p) { + char *user, *name, *home, dpystr[10]; + char *q, *t; + int ok = 1, dn; + + t = strdup(p); /* bob:0 */ + q = strchr(t, ':'); + if (! q) { + free(t); + break; + } + *q = '\0'; + user = t; + snprintf(dpystr, 10, ":%s", q+1); + + if (users) { + u = users; + ok = 0; + while (*u != NULL) { + if (*(*u) == ':') { + u++; + continue; + } + if (!strcmp(user, *u++)) { + ok = 1; + break; + } + } + } + + user2uid(user, &uid, &name, &home); + free(t); + + if (! uid) { + ok = 0; + } + + if (! ok) { + p = strtok(NULL, ","); + continue; + } + + for (dn = dmin; dn <= dmax; dn++) { + if (dn >= 0) { + sprintf(dpystr, ":%d", dn); + } + if (try_user_and_display(uid, dpystr)) { + if (switch_user_env(uid, name, home, 0)) { + rfbLog("lurk: now user: %s @ %s\n", + name, dpystr); + started_as_root = 2; + success = 1; + } + set_env("DISPLAY", dpystr); + break; + } + } + if (success) { + break; + } + + p = strtok(NULL, ","); + } + free(logins); + return success; +} + +void lurk_loop(char *str) { + char *tstr = NULL, **users = NULL; + + if (strstr(str, "lurk=") != str) { + exit(1); + } + rfbLog("lurking for logins using: '%s'\n", str); + if (strlen(str) > strlen("lurk=")) { + char *q = strchr(str, '='); + tstr = strdup(q+1); + users = user_list(tstr); + } + + while (1) { + if (lurk(users)) { + break; + } + sleep(3); + } + if (tstr) { + free(tstr); + } + if (users) { + free(users); + } +} + +static int guess_user_and_switch(char *str, int fb_mode) { + char *dstr, *d = DisplayString(dpy); + char *p, *tstr = NULL, *allowed = NULL, *logins, **users = NULL; + int dpy1, ret = 0; + + /* pick out ":N" */ + dstr = strchr(d, ':'); + if (! dstr) { + return 0; + } + if (sscanf(dstr, ":%d", &dpy1) != 1) { + return 0; + } + if (dpy1 < 0) { + return 0; + } + + if (strstr(str, "guess=") == str && strlen(str) > strlen("guess=")) { + allowed = strchr(str, '='); + allowed++; + + tstr = strdup(allowed); + users = user_list(tstr); + } + + /* loop over the utmpx entries looking for this display */ + logins = get_login_list(1); + p = strtok(logins, ","); + while (p) { + char *user, *q, *t; + int dpy2, ok = 1; + + t = strdup(p); + q = strchr(t, ':'); + if (! q) { + free(t); + break; + } + *q = '\0'; + user = t; + dpy2 = atoi(q+1); + + if (users) { + char **u = users; + ok = 0; + while (*u != NULL) { + if (!strcmp(user, *u++)) { + ok = 1; + break; + } + } + } + if (dpy1 != dpy2) { + ok = 0; + } + + if (! ok) { + free(t); + p = strtok(NULL, ","); + continue; + } + if (switch_user(user, fb_mode)) { + rfbLog("switched to guessed user: %s\n", user); + free(t); + ret = 1; + break; + } + + p = strtok(NULL, ","); + } + if (tstr) { + free(tstr); + } + if (users) { + free(users); + } + if (logins) { + free(logins); + } + return ret; +} + +static int try_user_and_display(uid_t uid, char *dpystr) { + /* NO strtoks */ +#if LIBVNCSERVER_HAVE_FORK && LIBVNCSERVER_HAVE_SYS_WAIT_H && LIBVNCSERVER_HAVE_PWD_H + pid_t pid, pidw; + char *home, *name; + int st; + struct passwd *pw; + + pw = getpwuid(uid); + if (pw) { + name = pw->pw_name; + home = pw->pw_dir; + } else { + return 0; + } + + /* + * We fork here and try to open the display again as the + * new user. Unreadable XAUTHORITY could be a problem... + * This is not really needed since we have DISPLAY open but: + * 1) is a good indicator this user owns the session and 2) + * some activities do spawn new X apps, e.g. xmessage(1), etc. + */ + if ((pid = fork()) > 0) { + ; + } else if (pid == -1) { + fprintf(stderr, "could not fork\n"); + rfbLogPerror("fork"); + return 0; + } else { + /* child */ + Display *dpy2 = NULL; + int rc; + + rc = switch_user_env(uid, name, home, 0); + if (! rc) { + exit(1); + } + + fclose(stderr); + dpy2 = XOpenDisplay(dpystr); + if (dpy2) { + XCloseDisplay(dpy2); + exit(0); /* success */ + } else { + exit(2); /* fail */ + } + } + + /* see what the child says: */ + pidw = waitpid(pid, &st, 0); + if (pidw == pid && WIFEXITED(st) && WEXITSTATUS(st) == 0) { + return 1; + } +#endif /* LIBVNCSERVER_HAVE_FORK ... */ + return 0; +} + +int switch_user(char *user, int fb_mode) { + /* NO strtoks */ + int doit = 0; + uid_t uid = 0; + char *name, *home; + + if (*user == '+') { + doit = 1; + user++; + } + + if (strstr(user, "guess=") == user) { + return guess_user_and_switch(user, fb_mode); + } + + user2uid(user, &uid, &name, &home); + + if (uid == (uid_t) -1 || uid == 0) { + return 0; + } + + if (! doit && dpy) { + /* see if this display works: */ + char *dstr = DisplayString(dpy); + doit = try_user_and_display(uid, dstr); + } + + if (doit) { + int rc = switch_user_env(uid, name, home, fb_mode); + if (rc) { + started_as_root = 2; + } + return rc; + } else { + return 0; + } +} + +static int switch_user_env(uid_t uid, char *name, char *home, int fb_mode) { + /* NO strtoks */ + char *xauth; + int reset_fb = 0; + +#if !LIBVNCSERVER_HAVE_SETUID + return 0; +#else + /* + * OK tricky here, we need to free the shm... otherwise + * we won't be able to delete it as the other user... + */ + if (fb_mode == 1 && using_shm) { + reset_fb = 1; + clean_shm(0); + free_tiles(); + } + if (setuid(uid) != 0) { + if (reset_fb) { + /* 2 means we did clean_shm and free_tiles */ + do_new_fb(2); + } + return 0; + } +#endif + if (reset_fb) { + do_new_fb(2); + } + + xauth = getenv("XAUTHORITY"); + if (xauth && access(xauth, R_OK) != 0) { + *(xauth-2) = '_'; /* yow */ + } + + set_env("USER", name); + set_env("LOGNAME", name); + set_env("HOME", home); + return 1; +} + +static void try_to_switch_users(void) { + static time_t last_try = 0; + time_t now = time(0); + char *users, *p; + + if (getuid() && geteuid()) { + rfbLog("try_to_switch_users: not root\n"); + started_as_root = 2; + return; + } + if (!last_try) { + last_try = now; + } else if (now <= last_try + 2) { + /* try every 3 secs or so */ + return; + } + last_try = now; + + users = strdup(users_list); + + if (strstr(users, "guess=") == users) { + if (switch_user(users, 1)) { + started_as_root = 2; + } + free(users); + return; + } + + p = strtok(users, ","); + while (p) { + if (switch_user(p, 1)) { + started_as_root = 2; + rfbLog("try_to_switch_users: now %s\n", p); + break; + } + p = strtok(NULL, ","); + } + free(users); +} + +int read_passwds(char *passfile) { + char line[1024]; + char *filename; + char **old_passwd_list = passwd_list; + int remove = 0; + int read_mode = 0; + int begin_vo = -1; + struct stat sbuf; + int linecount = 0, i, max; + FILE *in; + static time_t last_read = 0; + static int read_cnt = 0; + int db_passwd = 0; + + filename = passfile; + if (strstr(filename, "rm:") == filename) { + filename += strlen("rm:"); + remove = 1; + } else if (strstr(filename, "read:") == filename) { + filename += strlen("read:"); + read_mode = 1; + if (stat(filename, &sbuf) == 0) { + if (sbuf.st_mtime <= last_read) { + return 0; + } + last_read = sbuf.st_mtime; + } + } + + if (stat(filename, &sbuf) == 0) { + /* (poor...) upper bound to number of lines */ + max = (int) sbuf.st_size; + last_read = sbuf.st_mtime; + } else { + max = 64; + } + + /* create 1 more than max to have it be the ending NULL */ + passwd_list = (char **) malloc( (max+1) * (sizeof(char *)) ); + for (i=0; i<max+1; i++) { + passwd_list[i] = NULL; + } + + in = fopen(filename, "r"); + if (in == NULL) { + rfbLog("cannot open passwdfile: %s\n", passfile); + rfbLogPerror("fopen"); + if (remove) { + unlink(filename); + } + clean_up_exit(1); + } + + if (getenv("DEBUG_PASSWDFILE") != NULL) { + db_passwd = 1; + } + + while (fgets(line, 1024, in) != NULL) { + char *p; + int blank = 1; + int len = strlen(line); + + if (db_passwd) { + fprintf(stderr, "read_passwds: raw line: %s\n", line); + } + + if (len == 0) { + continue; + } else if (line[len-1] == '\n') { + line[len-1] = '\0'; + } + if (line[0] == '\0') { + continue; + } + if (strstr(line, "__SKIP__") != NULL) { + continue; + } + if (strstr(line, "__COMM__") == line) { + continue; + } + if (!strcmp(line, "__BEGIN_VIEWONLY__")) { + if (begin_vo < 0) { + begin_vo = linecount; + } + continue; + } + if (line[0] == '#') { + /* commented out, cannot have password beginning with # */ + continue; + } + p = line; + while (*p != '\0') { + if (! isspace(*p)) { + blank = 0; + break; + } + p++; + } + if (blank) { + continue; + } + + passwd_list[linecount++] = strdup(line); + if (db_passwd) { + fprintf(stderr, "read_passwds: keepline: %s\n", line); + fprintf(stderr, "read_passwds: begin_vo: %d\n", begin_vo); + } + + if (linecount >= max) { + break; + } + } + fclose(in); + + for (i=0; i<1024; i++) { + line[i] = '\0'; + } + + if (remove) { + unlink(filename); + } + + if (! linecount) { + rfbLog("cannot read a valid line from passwdfile: %s\n", + passfile); + if (read_cnt == 0) { + clean_up_exit(1); + } else { + return 0; + } + } + read_cnt++; + + for (i=0; i<linecount; i++) { + char *q, *p = passwd_list[i]; + if (!strcmp(p, "__EMPTY__")) { + *p = '\0'; + } else if ((q = strstr(p, "__COMM__")) != NULL) { + *q = '\0'; + } + passwd_list[i] = strdup(p); + if (db_passwd) { + fprintf(stderr, "read_passwds: trimline: %s\n", p); + } + strzero(p); + } + + begin_viewonly = begin_vo; + if (read_mode && read_cnt > 1) { + if (viewonly_passwd) { + free(viewonly_passwd); + viewonly_passwd = NULL; + } + } + + if (begin_viewonly < 0 && linecount == 2) { + /* for compatibility with previous 2-line usage: */ + viewonly_passwd = strdup(passwd_list[1]); + if (db_passwd) { + fprintf(stderr, "read_passwds: linecount is 2.\n"); + } + if (screen) { + char **apd = (char **) screen->authPasswdData; + if (apd) { + if (apd[0] != NULL) { + strzero(apd[0]); + } + apd[0] = strdup(passwd_list[0]); + } + } + begin_viewonly = 1; + } + + if (old_passwd_list != NULL) { + char *p; + i = 0; + while (old_passwd_list[i] != NULL) { + p = old_passwd_list[i]; + strzero(p); + free(old_passwd_list[i]); + i++; + } + free(old_passwd_list); + } + return 1; +} + +void install_passwds(void) { + if (viewonly_passwd) { + /* append the view only passwd after the normal passwd */ + char **passwds_new = (char **) malloc(3*sizeof(char *)); + char **passwds_old = (char **) screen->authPasswdData; + passwds_new[0] = passwds_old[0]; + passwds_new[1] = viewonly_passwd; + passwds_new[2] = NULL; + screen->authPasswdData = (void*) passwds_new; + } else if (passwd_list) { + int i = 0; + while(passwd_list[i] != NULL) { + i++; + } + if (begin_viewonly < 0) { + begin_viewonly = i+1; + } + screen->authPasswdData = (void*) passwd_list; + screen->authPasswdFirstViewOnly = begin_viewonly; + } +} + +void check_new_passwds(void) { + static time_t last_check = 0; + time_t now; + + if (! passwdfile) { + return; + } + if (strstr(passwdfile, "read:") != passwdfile) { + return; + } + now = time(0); + if (now > last_check + 1) { + if (read_passwds(passwdfile)) { + install_passwds(); + } + last_check = now; + } +} + + |