X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/3fcce26ccd46099a7c7206318cefa43d951ba60e..ad55cd035c604a3aa5f76a1f52b9f1a153b44790:/session.c diff --git a/session.c b/session.c index 388d96bb..296dfade 100644 --- a/session.c +++ b/session.c @@ -1,14 +1,39 @@ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved - */ -/* + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * * SSH2 support by Markus Friedl. * Copyright (c) 2000 Markus Friedl. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "includes.h" -RCSID("$OpenBSD: session.c,v 1.23 2000/07/11 08:11:33 deraadt Exp $"); +RCSID("$OpenBSD: session.c,v 1.37 2000/09/07 20:27:53 deraadt Exp $"); #include "xmalloc.h" #include "ssh.h" @@ -41,14 +66,25 @@ RCSID("$OpenBSD: session.c,v 1.23 2000/07/11 08:11:33 deraadt Exp $"); # include #endif +#ifdef HAVE_CYGWIN +#include +#include +#define is_winnt (GetVersion() < 0x80000000) +#endif + /* AIX limits */ #if defined(HAVE_GETUSERATTR) && !defined(S_UFSIZE_HARD) && defined(S_UFSIZE) -# define S_UFSIZE_HARD S_UFSIZE -# define S_UCPU_HARD S_UCPU -# define S_UDATA_HARD S_UDATA -# define S_USTACK_HARD S_USTACK -# define S_URSS_HARD S_URSS -# define S_UCORE_HARD S_UCORE +# define S_UFSIZE_HARD S_UFSIZE "_hard" +# define S_UCPU_HARD S_UCPU "_hard" +# define S_UDATA_HARD S_UDATA "_hard" +# define S_USTACK_HARD S_USTACK "_hard" +# define S_URSS_HARD S_URSS "_hard" +# define S_UCORE_HARD S_UCORE "_hard" +# define S_UNOFILE_HARD S_UNOFILE "_hard" +#endif + +#ifdef HAVE_LOGIN_CAP +#include #endif /* types */ @@ -84,6 +120,7 @@ void session_pty_cleanup(Session *s); void session_proctitle(Session *s); void do_exec_pty(Session *s, const char *command, struct passwd * pw); void do_exec_no_pty(Session *s, const char *command, struct passwd * pw); +void do_login(Session *s); void do_child(const char *command, struct passwd * pw, const char *term, @@ -100,12 +137,16 @@ static const char *__progname = "sshd"; extern int log_stderr; extern int debug_flag; +extern unsigned int utmp_len; extern int startup_pipe; /* Local Xauthority file. */ static char *xauthfile; +/* original command from peer. */ +char *original_command = NULL; + /* data */ #define MAX_SESSIONS 10 Session sessions[MAX_SESSIONS]; @@ -114,6 +155,10 @@ Session sessions[MAX_SESSIONS]; char *aixloginmsg; #endif /* WITH_AIXAUTHENTICATE */ +#ifdef HAVE_LOGIN_CAP +static login_cap_t *lc; +#endif + /* * Remove local Xauthority file. */ @@ -166,7 +211,7 @@ void do_authenticated(struct passwd * pw) { Session *s; - int type; + int type, fd; int compression_level = 0, enable_compression_after_reply = 0; int have_pty = 0; char *command; @@ -197,6 +242,13 @@ do_authenticated(struct passwd * pw) s = session_new(); s->pw = pw; +#ifdef HAVE_LOGIN_CAP + if ((lc = login_getclass(pw->pw_class)) == NULL) { + error("unable to get login class"); + return; + } +#endif + /* * We stay in this loop until the client requests to execute a shell * or a command. @@ -314,7 +366,9 @@ do_authenticated(struct passwd * pw) break; } strlcat(xauthfile, "/cookies", MAXPATHLEN); - open(xauthfile, O_RDWR|O_CREAT|O_EXCL, 0600); + fd = open(xauthfile, O_RDWR|O_CREAT|O_EXCL, 0600); + if (fd >= 0) + close(fd); restore_uid(); fatal_add_cleanup(xauthfile_cleanup_proc, NULL); success = 1; @@ -359,6 +413,7 @@ do_authenticated(struct passwd * pw) packet_integrity_check(plen, 0, type); } if (forced_command != NULL) { + original_command = command; command = forced_command; debug("Forced command '%.500s'", forced_command); } @@ -479,6 +534,10 @@ do_exec_no_pty(Session *s, const char *command, struct passwd * pw) do_child(command, pw, NULL, s->display, s->auth_proto, s->auth_data, NULL); /* NOTREACHED */ } +#ifdef HAVE_CYGWIN + if (is_winnt) + cygwin_set_impersonation_token(INVALID_HANDLE_VALUE); +#endif if (pid < 0) packet_disconnect("fork failed: %.100s", strerror(errno)); s->pid = pid; @@ -522,35 +581,14 @@ do_exec_no_pty(Session *s, const char *command, struct passwd * pw) void do_exec_pty(Session *s, const char *command, struct passwd * pw) { - FILE *f; - char buf[100], *time_string; - char line[256]; - const char *hostname; int fdout, ptyfd, ttyfd, ptymaster; - int quiet_login; pid_t pid; - socklen_t fromlen; - struct sockaddr_storage from; - struct stat st; - time_t last_login_time; if (s == NULL) fatal("do_exec_pty: no session"); ptyfd = s->ptyfd; ttyfd = s->ttyfd; - /* Get remote host name. */ - hostname = get_canonical_hostname(); - - /* - * Get the time when the user last logged in. Buf will be set to - * contain the hostname the last login was from. - */ - if (!options.use_login) { - last_login_time = get_last_login_time(pw->pw_uid, pw->pw_name, - buf, sizeof(buf)); - } - #ifdef USE_PAM do_pam_session(pw->pw_name, s->tty); do_pam_setcred(); @@ -558,10 +596,7 @@ do_exec_pty(Session *s, const char *command, struct passwd * pw) /* Fork the child. */ if ((pid = fork()) == 0) { - pid = getpid(); - - /* Child. Reinitialize the log because the pid has - changed. */ + /* Child. Reinitialize the log because the pid has changed. */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Close the master side of the pseudo tty. */ @@ -585,86 +620,19 @@ do_exec_pty(Session *s, const char *command, struct passwd * pw) /* Close the extra descriptor for the pseudo tty. */ close(ttyfd); -/* XXXX ? move to do_child() ??*/ - /* - * Get IP address of client. This is needed because we want - * to record where the user logged in from. If the - * connection is not a socket, let the ip address be 0.0.0.0. - */ - memset(&from, 0, sizeof(from)); - if (packet_connection_is_on_socket()) { - fromlen = sizeof(from); - if (getpeername(packet_get_connection_in(), - (struct sockaddr *) & from, &fromlen) < 0) { - debug("getpeername: %.100s", strerror(errno)); - fatal_cleanup(); - } - } - /* Record that there was a login on that terminal. */ - record_login(pid, s->tty, pw->pw_name, pw->pw_uid, hostname, - (struct sockaddr *)&from); - - /* Check if .hushlogin exists. */ - snprintf(line, sizeof line, "%.200s/.hushlogin", pw->pw_dir); - quiet_login = stat(line, &st) >= 0; + /* record login, etc. similar to login(1) */ + if (command == NULL && !options.use_login) + do_login(s); -#ifdef USE_PAM - if (!quiet_login) - print_pam_messages(); -#endif /* USE_PAM */ - - /* - * If the user has logged in before, display the time of last - * login. However, don't display anything extra if a command - * has been specified (so that ssh can be used to execute - * commands on a remote machine without users knowing they - * are going to another machine). Login(1) will do this for - * us as well, so check if login(1) is used - */ - if (command == NULL && last_login_time != 0 && !quiet_login && - !options.use_login) { - /* Convert the date to a string. */ - time_string = ctime(&last_login_time); - /* Remove the trailing newline. */ - if (strchr(time_string, '\n')) - *strchr(time_string, '\n') = 0; - /* Display the last login time. Host if displayed - if known. */ - if (strcmp(buf, "") == 0) - printf("Last login: %s\r\n", time_string); - else - printf("Last login: %s from %s\r\n", time_string, buf); - } - /* - * Print /etc/motd unless a command was specified or printing - * it was disabled in server options or login(1) will be - * used. Note that some machines appear to print it in - * /etc/profile or similar. - */ - if (command == NULL && options.print_motd && !quiet_login && - !options.use_login) { - /* Print /etc/motd if it exists. */ - f = fopen("/etc/motd", "r"); - if (f) { - while (fgets(line, sizeof(line), f)) - fputs(line, stdout); - fclose(f); - } - } -#if defined(WITH_AIXAUTHENTICATE) - /* - * AIX handles the lastlog info differently. Display it here. - */ - if (command == NULL && aixloginmsg && *aixloginmsg && - !quiet_login && !options.use_login) { - printf("%s\n", aixloginmsg); - } -#endif /* Do common processing for the child, such as execing the command. */ do_child(command, pw, s->term, s->display, s->auth_proto, s->auth_data, s->tty); /* NOTREACHED */ } +#ifdef HAVE_CYGWIN + if (is_winnt) + cygwin_set_impersonation_token(INVALID_HANDLE_VALUE); +#endif if (pid < 0) packet_disconnect("fork failed: %.100s", strerror(errno)); s->pid = pid; @@ -697,6 +665,102 @@ do_exec_pty(Session *s, const char *command, struct passwd * pw) } } +const char * +get_remote_name_or_ip(void) +{ + static const char *remote = ""; + if (utmp_len > 0) + remote = get_canonical_hostname(); + if (utmp_len == 0 || strlen(remote) > utmp_len) + remote = get_remote_ipaddr(); + return remote; +} + +/* administrative, login(1)-like work */ +void +do_login(Session *s) +{ + FILE *f; + char *time_string; + char buf[256]; + char hostname[MAXHOSTNAMELEN]; + socklen_t fromlen; + struct sockaddr_storage from; + struct stat st; + time_t last_login_time; + struct passwd * pw = s->pw; + pid_t pid = getpid(); + + /* + * Get IP address of client. If the connection is not a socket, let + * the address be 0.0.0.0. + */ + memset(&from, 0, sizeof(from)); + if (packet_connection_is_on_socket()) { + fromlen = sizeof(from); + if (getpeername(packet_get_connection_in(), + (struct sockaddr *) & from, &fromlen) < 0) { + debug("getpeername: %.100s", strerror(errno)); + fatal_cleanup(); + } + } + + /* Get the time and hostname when the user last logged in. */ + last_login_time = get_last_login_time(pw->pw_uid, pw->pw_name, + hostname, sizeof(hostname)); + + /* Get the time and hostname when the user last logged in. */ + hostname[0] = '\0'; + last_login_time = get_last_login_time(pw->pw_uid, pw->pw_name, + hostname, sizeof(hostname)); + + /* Record that there was a login on that tty from the remote host. */ + record_login(pid, s->tty, pw->pw_name, pw->pw_uid, + get_remote_name_or_ip(), (struct sockaddr *)&from); + + /* Done if .hushlogin exists. */ + snprintf(buf, sizeof(buf), "%.200s/.hushlogin", pw->pw_dir); +#ifdef HAVE_LOGIN_CAP + if (login_getcapbool(lc, "hushlogin", 0) || stat(buf, &st) >= 0) +#else + if (stat(buf, &st) >= 0) +#endif + return; + +#ifdef USE_PAM + print_pam_messages(); + /* If password change is needed, do it now. */ + do_pam_chauthtok(); +#endif /* USE_PAM */ +#ifdef WITH_AIXAUTHENTICATE + if (aixloginmsg && *aixloginmsg) + printf("%s\n", aixloginmsg); +#endif /* WITH_AIXAUTHENTICATE */ + + if (last_login_time != 0) { + time_string = ctime(&last_login_time); + if (strchr(time_string, '\n')) + *strchr(time_string, '\n') = 0; + if (strcmp(buf, "") == 0) + printf("Last login: %s\r\n", time_string); + else + printf("Last login: %s from %s\r\n", time_string, hostname); + } + if (options.print_motd) { +#ifdef HAVE_LOGIN_CAP + f = fopen(login_getcapstr(lc, "welcome", "/etc/motd", + "/etc/motd"), "r"); +#else + f = fopen("/etc/motd", "r"); +#endif + if (f) { + while (fgets(buf, sizeof(buf), f)) + fputs(buf, stdout); + fclose(f); + } + } +} + /* * Sets the value of the given variable in the environment. If the variable * already exists, its value is overriden. @@ -898,10 +962,10 @@ do_child(const char *command, struct passwd * pw, const char *term, const char *display, const char *auth_proto, const char *auth_data, const char *ttyname) { - const char *shell, *cp = NULL; + const char *shell, *hostname = NULL, *cp = NULL; char buf[256]; char cmd[1024]; - FILE *f; + FILE *f = NULL; unsigned int envsize, i; char **env; extern char **environ; @@ -916,24 +980,26 @@ do_child(const char *command, struct passwd * pw, const char *term, options.use_login = 0; #ifndef USE_PAM /* pam_nologin handles this */ - f = fopen("/etc/nologin", "r"); - if (f) { - /* /etc/nologin exists. Print its contents and exit. */ - while (fgets(buf, sizeof(buf), f)) - fputs(buf, stderr); - fclose(f); - if (pw->pw_uid != 0) + if (!options.use_login) { +# ifdef HAVE_LOGIN_CAP + if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid) + f = fopen(login_getcapstr(lc, "nologin", _PATH_NOLOGIN, + _PATH_NOLOGIN), "r"); +# else /* HAVE_LOGIN_CAP */ + if (pw->pw_uid) + f = fopen(_PATH_NOLOGIN, "r"); +# endif /* HAVE_LOGIN_CAP */ + if (f) { + /* /etc/nologin exists. Print its contents and exit. */ + while (fgets(buf, sizeof(buf), f)) + fputs(buf, stderr); + fclose(f); exit(254); + } } #endif /* USE_PAM */ -#ifndef HAVE_OSF_SIA - /* Set login name in the kernel. */ - if (setlogin(pw->pw_name) < 0) - error("setlogin failed: %s", strerror(errno)); -#endif - - /* Set uid, gid, and groups. */ + /* Set login name, uid, gid, and groups. */ /* Login(1) does this as well, and it needs uid 0 for the "-h" switch, so we let login(1) to this for us. */ if (!options.use_login) { @@ -953,11 +1019,23 @@ do_child(const char *command, struct passwd * pw, const char *term, exit(1); } #else /* HAVE_OSF_SIA */ +#ifdef HAVE_CYGWIN + if (is_winnt) { +#else if (getuid() == 0 || geteuid() == 0) { -#if defined(HAVE_GETUSERATTR) +#endif +# ifdef HAVE_GETUSERATTR set_limits_from_userattr(pw->pw_name); -#endif /* defined(HAVE_GETUSERATTR) */ - +# endif /* HAVE_GETUSERATTR */ +# ifdef HAVE_LOGIN_CAP + if (setusercontext(lc, pw, pw->pw_uid, + (LOGIN_SETALL & ~LOGIN_SETPATH)) < 0) { + perror("unable to set user context"); + exit(1); + } +# else /* HAVE_LOGIN_CAP */ + if (setlogin(pw->pw_name) < 0) + error("setlogin failed: %s", strerror(errno)); if (setgid(pw->pw_gid) < 0) { perror("setgid"); exit(1); @@ -968,38 +1046,42 @@ do_child(const char *command, struct passwd * pw, const char *term, exit(1); } endgrent(); - -#ifdef WITH_IRIX_ARRAY +# ifdef WITH_IRIX_ARRAY /* initialize array session */ if (newarraysess() != 0) fatal("Failed to set up new array session: %.100s", strerror(errno)); -#endif /* WITH_IRIX_ARRAY */ - -#ifdef WITH_IRIX_PROJECT +# endif /* WITH_IRIX_ARRAY */ +# ifdef WITH_IRIX_PROJECT /* initialize irix project info */ if ((projid = getdfltprojuser(pw->pw_name)) == -1) { debug("Failed to get project id, using projid 0"); projid = 0; } - if (setprid(projid)) fatal("Failed to initialize project %d for %s: %.100s", (int)projid, pw->pw_name, strerror(errno)); -#endif /* WITH_IRIX_PROJECT */ - +# endif /* WITH_IRIX_PROJECT */ /* Permanently switch to the desired uid. */ permanently_set_uid(pw->pw_uid); +# endif /* HAVE_LOGIN_CAP */ } - if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) - fatal("Failed to set uids to %d.", (int) pw->pw_uid); #endif /* HAVE_OSF_SIA */ + +#ifdef HAVE_CYGWIN + if (is_winnt) +#endif + if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) + fatal("Failed to set uids to %u.", (u_int) pw->pw_uid); } /* * Get the shell from the password data. An empty shell field is * legal, and means /bin/sh. */ shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell; +#ifdef HAVE_LOGIN_CAP + shell = login_getcapstr(lc, "shell", (char *)shell, (char *)shell); +#endif #ifdef AFS /* Try to get AFS tokens for the local cell. */ @@ -1018,12 +1100,41 @@ do_child(const char *command, struct passwd * pw, const char *term, env = xmalloc(envsize * sizeof(char *)); env[0] = NULL; +#ifdef HAVE_CYGWIN + /* + * The Windows environment contains some setting which are + * important for a running system. They must not be dropped. + */ + { + char **ep; + for (ep = environ; *ep; ++ep) { + char *esp = strchr(*ep, '='); + *esp = '\0'; + child_set_env(&env, &envsize, *ep, esp + 1); + *esp = '='; + } + } +#endif + if (!options.use_login) { /* Set basic environment. */ child_set_env(&env, &envsize, "USER", pw->pw_name); child_set_env(&env, &envsize, "LOGNAME", pw->pw_name); child_set_env(&env, &envsize, "HOME", pw->pw_dir); +#ifdef HAVE_LOGIN_CAP + (void) setusercontext(lc, pw, pw->pw_uid, LOGIN_SETPATH); + child_set_env(&env, &envsize, "PATH", getenv("PATH")); +#else +#ifndef HAVE_CYGWIN + /* + * There's no standard path on Windows. The path contains + * important components pointing to the system directories, + * needed for loading shared libraries. So the path better + * remains intact here. + */ child_set_env(&env, &envsize, "PATH", _PATH_STDPATH); +#endif +#endif snprintf(buf, sizeof buf, "%.200s/%.50s", _PATH_MAILDIR, pw->pw_name); @@ -1060,6 +1171,9 @@ do_child(const char *command, struct passwd * pw, const char *term, child_set_env(&env, &envsize, "TERM", term); if (display) child_set_env(&env, &envsize, "DISPLAY", display); + if (original_command) + child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", + original_command); #ifdef _AIX { @@ -1107,6 +1221,9 @@ do_child(const char *command, struct passwd * pw, const char *term, for (i = 0; env[i]; i++) fprintf(stderr, " %.200s\n", env[i]); } + /* we have to stash the hostname before we close our socket. */ + if (options.use_login) + hostname = get_remote_name_or_ip(); /* * Close the connection descriptors; note that this is the child, and * the server will still have the socket open, and it is important @@ -1143,9 +1260,14 @@ do_child(const char *command, struct passwd * pw, const char *term, close(i); /* Change current directory to the user\'s home directory. */ - if (chdir(pw->pw_dir) < 0) + if (chdir(pw->pw_dir) < 0) { fprintf(stderr, "Could not chdir to home directory %s: %s\n", pw->pw_dir, strerror(errno)); +#ifdef HAVE_LOGIN_CAP + if (login_getcapbool(lc, "requirehome", 0)) + exit(1); +#endif + } /* * Must take new environment into use so that .ssh/rc, /etc/sshrc and @@ -1189,11 +1311,13 @@ do_child(const char *command, struct passwd * pw, const char *term, "Running %.100s add %.100s %.100s %.100s\n", options.xauth_location, display, auth_proto, auth_data); +#ifndef HAVE_CYGWIN if (screen != NULL) fprintf(stderr, "Adding %.*s/unix%s %s %s\n", - screen-display, display, + (int)(screen-display), display, screen, auth_proto, auth_data); +#endif } snprintf(cmd, sizeof cmd, "%s -q -", options.xauth_location); @@ -1201,10 +1325,12 @@ do_child(const char *command, struct passwd * pw, const char *term, if (f) { fprintf(f, "add %s %s %s\n", display, auth_proto, auth_data); +#ifndef HAVE_CYGWIN if (screen != NULL) fprintf(f, "add %.*s/unix%s %s %s\n", - screen-display, display, + (int)(screen-display), display, screen, auth_proto, auth_data); +#endif pclose(f); } else { fprintf(stderr, "Could not run %s\n", @@ -1263,8 +1389,8 @@ do_child(const char *command, struct passwd * pw, const char *term, } else { /* Launch login(1). */ - execl(LOGIN_PROGRAM, "login", "-h", get_remote_ipaddr(), - "-p", "-f", "--", pw->pw_name, NULL); + execl(LOGIN_PROGRAM, "login", "-h", hostname, + "-p", "-f", "--", pw->pw_name, NULL); /* Login couldn't be executed, die. */ @@ -1474,6 +1600,7 @@ session_subsystem_req(Session *s) int session_x11_req(Session *s) { + int fd; if (no_x11_forwarding_flag) { debug("X11 forwarding disabled in user configuration file."); return 0; @@ -1518,7 +1645,9 @@ session_x11_req(Session *s) return 0; } strlcat(xauthfile, "/cookies", MAXPATHLEN); - open(xauthfile, O_RDWR|O_CREAT|O_EXCL, 0600); + fd = open(xauthfile, O_RDWR|O_CREAT|O_EXCL, 0600); + if (fd >= 0) + close(fd); restore_uid(); fatal_add_cleanup(xauthfile_cleanup_proc, s); return 1; @@ -1545,7 +1674,7 @@ session_exec_req(Session *s) char *command = packet_get_string(&len); packet_done(); if (forced_command) { - xfree(command); + original_command = command; command = forced_command; debug("Forced command '%.500s'", forced_command); } @@ -1800,6 +1929,10 @@ session_proctitle(Session *s) void do_authenticated2(void) { +#ifdef HAVE_LOGIN_CAP + struct passwd *pw; +#endif + /* * Cancel the alarm we set to limit the time taken for * authentication. @@ -1809,6 +1942,13 @@ do_authenticated2(void) close(startup_pipe); startup_pipe = -1; } +#ifdef HAVE_LOGIN_CAP + pw = auth_get_user(); + if ((lc = login_getclass(pw->pw_class)) == NULL) { + error("unable to get login class"); + return; + } +#endif server_loop2(); if (xauthfile) xauthfile_cleanup_proc(NULL);