/* -- user.c -- */ #include "x11vnc.h" #include "solid.h" #include "cleanup.h" #include "scan.h" #include "screen.h" #include "unixpw.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 (unixpw_in_progress) return; 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; iut_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; signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGTERM, SIG_DFL); 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) { 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 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; } if (unixpw_in_progress) return; now = time(0); if (now > last_check + 1) { if (read_passwds(passwdfile)) { install_passwds(); } last_check = now; } }