/* * Title: The Chroot Shell: A chroot jail wrapper for ordinary Unix shells * File: chrsh.c * Version: 1.1 * * Copyright (c) 1998-2000,2009,2024 Aaron D. Gifford * * Usage of the works is permitted provided that this instrument is * retained with the works, so that any entity that uses the works is * notified of this instrument. * * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. * * Written by Aaron D. Gifford - https://www.aarongifford.com/ * * Contributors include: * * H. D. Moore * * Thanks to: * + The Apache Group for many ideas borrowed from suexec.c * + H. D. Moore for converting logging to SYSLOG and fixing a username * buffer bug * * * BUG FIXES, COMMENTS, and SUGGESTIONS are always WELCOME! * * Please send bug fixes, suggestions, or comments to * Aaron D. Gifford via his web site at: * https://www.aarongifford.com/contact.html * * I may or may not reply. I did this in my own spare time. * I will NOT reply to messages that basically require me * to give technical training or support related to this * software. I just don't have time for that. * * Likewise I may or may not include such fixes in future * releases (IF future releases ever occur). * * As you can tell, this was developed on FreeBSD, so there * are few if any cross-platform compatibility features. * If you get it working, e-mail me a diff -u and perhaps * I'll include it in a future version. * * The latest version of this file may be available from the * web page at: * https://www.aarongifford.com/computers/chrsh.html * * See the same web page for additional documentation, and/or * examples of usage. */ /* ========== CONFIGURATION ========== */ /* The name of THIS shell */ #define CHRSHNAME "chrsh" /* FULL path to and name of this shell */ #define CHRSHPATH "/usr/local/bin/chrsh" /* The chroot jail main directory must be owned and permissioned thus */ #define JAILDIRUID 0 #define JAILDIRGID 0 #define JAILDIRMODE 0555 /* The jailed user's chrooted home directory must be this mode */ #define HOMEDIRMODE 0751 /* * Log chrsh activity here: * * If you wish to use SYSLOG logging, define LOG_USESYSLOG * * If you wish to log to a file, define LOG_USEFILE with * the complete path to the log file including filename. * * YOU MUST DEFINE either LOG_USESYSLOG or LOG_USEFILE! */ #define LOG_USESYSLOG /* #define LOG_USEFILE "/var/log/chrsh.log" */ /* All chrsh users MUST have UIDs and GIDs GREATER than these */ #define MINUID 2300 #define MINGID 2300 /* The final jailed shell must be owned by this UID/GID */ #define SHELLUID 0 #define SHELLGID 0 /* The jailed shell's mode must match this */ #define SHELLMODE 0555 /* REMEMBER to make sure it has execute permission :) */ /* ========== END OF CONFIG ========== */ #if (!defined(LOG_USESYSLOG) && !defined(LOG_USEFILE)) || (defined(LOG_USESYSLOG) && defined(LOG_USEFILE)) # error "You MUST define either LOG_USESYSLOG or LOG_USEFILE (not both)!" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Required even if LOG_USESYSLOG is NOT defined */ static char *prog; static uid_t uid; static gid_t gid; static char *username = NULL; static char *groupname = NULL; #ifdef LOG_USEFILE static FILE *logfp = NULL; #endif /* LOG_USEFILE */ /* * Open log file for appending, or fail; */ static int open_log() { int logfd = -1; struct sockaddr sock; int i, m; socklen_t l; #ifdef LOG_USEFILE if ((logfp = fopen(LOG_USEFILE, "a")) == NULL) { fprintf(stderr, "%s: Failed to open log file!\n", prog); fprintf(stderr, "%s: Call to fopen() failed with error (%d): \"%s\"\n", prog, errno, strerror(errno)); exit(1); } logfd = fileno(logfp); #endif /* LOG_USEFILE */ #ifdef LOG_USESYSLOG openlog(prog, LOG_PID | LOG_CONS | LOG_NDELAY, LOG_AUTHPRIV); /* * Since before open_log() gets called, all file descriptors except * stdin, stdout, and stderr have been closed, we should be able to * deduce the filedescriptor used for syslogging. This should work * so log as LOG_NDELAY is used AND so long as the syslog descriptor * is either a unix socket, or internet socket. */ l = sizeof(struct sockaddr); for (m = getdtablesize(), i = 3; i < m; i++) { if (getsockname(i, (struct sockaddr *)&sock, &l) == 0) { /* * Assume the first valid file descriptor we find is * the syslog descriptor... */ logfd = i; break; } } #endif /* LOG_USESYSLOG */ #ifdef DEBUG printf("Logging file descriptor is %d\n", logfd); #endif /* DEBUG */ return logfd; } #ifdef LOG_USEFILE static char *month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char *weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static void v_do_log(const char *format, va_list ap) { pid_t pid; time_t seconds; struct tm *tm; if (!logfp) { fprintf(stderr, "%s: WARNING: Log file is not open.\n", prog); open_log(); } pid = getpid(); seconds = time(NULL); tm = localtime(&seconds); fprintf(logfp, "%s (%ld) %s %.2d-%s-%d %.2d:%.2d:%.2d ", prog, pid, weekday[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec); fprintf(logfp, "User='%s' (%d) Group='%s' (%d): ", username != NULL ? username : "", uid, groupname != NULL ? groupname : "", gid); vfprintf(logfp, format, ap); fflush(logfp); } #endif /* LOG_USEFILE */ static void do_log(int loglevel, const char *format, ...) { va_list ap; va_start(ap, format); #ifdef LOG_USEFILE v_do_log(format, ap); #endif /* LOG_USEFILE */ #ifdef LOG_USESYSLOG syslog(loglevel, "User='%s' (%d) Group='%s' (%d):", username != NULL ? username : "", uid, groupname != NULL ? groupname : "", gid); vsyslog(loglevel, format, ap); #endif /* LOG_USESYSLOG */ va_end(ap); } void clean_exit(int exitcode) { #ifdef LOG_USEFILE if (logfp != NULL) fclose(logfp); #endif /* LOG_USEFILE */ #ifdef LOG_USESYSLOG closelog(); #endif /* LOG_USESYSLOG */ exit(exitcode); } /* * WARNING: * The routine used below does NOT check individual path components, so * the jailed environment is not guaranteed to be safe. The admin. who * sets up the environment should MAKE SURE that ALL path components of * all jailed users' home subdirectories are correctly permissioned and * owned, as are the chroot main dir, the /bin and /etc subdirs, the * dir in which shells reside, and all other files. I didn't want to * to build the end-all of path/file/directory checking monsters to * check all these factors. That's the admin's job. If any components * are world writable, race conditions and worse WILL almost CERTAINLY * end up in a root compromise. */ int checkjailed(char *item, uid_t uid, gid_t gid, int isdir, mode_t mode) { struct stat statinfo; char *type; type = isdir ? "subdirectory" : "file"; if (lstat(item, &statinfo)) { do_log(LOG_WARNING, "Unable to stat %s %s of the jail.", item, type); return 1; } if ((!(statinfo.st_mode & S_IFDIR) && isdir) || (!(statinfo.st_mode & S_IFREG) && !isdir)) { do_log(LOG_WARNING, "Jailed %s %s is not a %s.", item, type, (isdir ? "directory" : "regular file")); return 2; } if (statinfo.st_mode & (S_IWGRP | S_IWOTH)) { do_log(LOG_WARNING, "Jailed %s %s is world writable.", item, type); return 3; } if ((statinfo.st_mode & ALLPERMS) != mode) { do_log(LOG_WARNING, "Jailed %s %s mode does not match expected mode %lo.", item, type, mode); return 4; } if (statinfo.st_uid != uid) { do_log(LOG_WARNING, "Jailed %s %s is not owned by expected UID %ld.", item, type, uid); return 5; } if (statinfo.st_gid != gid) { do_log(LOG_WARNING, "Jailed %s %s is not owned by expected GID %ld.", item, type, gid); return 6; } return 0; } /* * Case sensitive comparison of an environment variable's * name name with an arbitrary string. Returns 1 if they * match, 0 if they don't. This is backwards from the * basic strcmp() function. */ int envmatch(char *env, char *str) { if (!env || !str) return 0; while (*env && *str) { if (*env++ != *str++) return 0; } return (!*str && *env == '=') ? 1 : 0; } int main(int argc, char *argv[], char *env[]) { char wd[PATH_MAX]; char peername[64]; struct passwd *pw; struct group *gr; struct stat statinfo; struct sockaddr sock; int m; socklen_t i; int logfd = -1; char *gecos; char *home; char *jail; char *shell; char *cmd; char **newargv; char **newenv; char *c; prog = argv[0]; /* First things first... */ /* * Close EVERY file descriptor EXCEPT stdin, stdout, and stderr: */ for(m = getdtablesize(), i = 3; i < m; i++) { /* * Loop to repeat close() if the call failes with EINTR */ while (close(i) != 0 && errno == EINTR); } /* * Save the calling UID and GID -- these SHOULD be the * UID and GID of the chroot jailed user. */ uid = getuid(); gid = getgid(); /* * Open the log file for appending (if LOG_USEFILE is defined) * or initialize for SYSLOG logging (if LOG_USESYSLOG is defined) */ logfd = open_log(); /* * Is STDIN a TCP/IP socket? If so, get the peer IP address and port number. * Be careful with getsockname() and getpeername() since some systems will * return a successful call when the file descriptor is a UNIX-domain socket * but will return a zero-length name, so you can't check the sa_family or * sin_family structure member in these cases. */ peername[0] = '\0'; i = sizeof(struct sockaddr); /* First, see if the local descriptor is an Internet socket */ if ((m = getsockname(fileno(stdin), (struct sockaddr *)&sock, &i)) == 0 && i > 0) { struct sockaddr_in peer; /* * We managed to get a non-zero socket name structure, so now * we'll see what kind of socket this file descriptor is */ if (sock.sa_family != PF_INET && sock.sa_family != PF_UNIX) { do_log(LOG_WARNING, "The STDIN socket is of an unrecognized family (%d).", sock.sa_family); clean_exit(99); } i = sizeof(struct sockaddr_in); if (getpeername(fileno(stdin), (struct sockaddr *)&peer, &i) != 0) { do_log(LOG_WARNING, "Call to getpeername() returned error %d: %s", errno, strerror(errno)); clean_exit(100); } else if (peer.sin_family == AF_INET && i > 0) { /* * !!! WARNING !!! If your system's snprintf() function does * NOT guarantee null/zero-byte termination even for excessive * lengths, then YOUR SYSTEM IS BROKEN and this call is insecure. * Most modern OSs don't have that problem, including the *BSDs * and up-to-date Linux systems (at least that I'm aware of). */ snprintf(peername, sizeof(peername), "[%s] port %d", inet_ntoa(peer.sin_addr), peer.sin_port); } } else if (m != 0 && errno != ENOTSOCK) { /* Hmm, something WEIRD is going on! */ do_log(LOG_WARNING, "Call to getsockname() returned error %d: %s", errno, strerror(errno)); clean_exit(101); } /* * Are we root? Can do the chroot()? */ if (setgid(0) || setuid(0) || getuid() != geteuid() || getgid() != getegid()) { do_log(LOG_WARNING, "Unable to obtain root permission in order to perform chroot() function."); clean_exit(102); } if (uid == 0 || uid < MINUID) { do_log(LOG_WARNING, "Cannot operate as forbidden UID %ld.", uid); clean_exit(103); } if (gid == 0 || gid < MINGID) { do_log(LOG_WARNING, "Cannot operate as forbidden GID %ld.", gid); clean_exit(104); } /* * Make sure this program is called with the * correct name and number of arguments. * Remember, a login shell will have a '-' * prepended to it, while an interactive * shell will not. */ if (strcmp(prog, CHRSHNAME) && (*prog != '-' || strcmp(prog+1, CHRSHNAME))) { do_log(LOG_WARNING, "invalid program name (%s)", prog); clean_exit(111); } /* * Get the current pw structure and save off much of * the data. If the UID can't be found, log the error. */ if ((pw = getpwuid(uid)) == NULL) { do_log(LOG_WARNING, "UID %ld does not seem to exist.", uid); clean_exit(131); } if (!(username = strdup(pw->pw_name))) { do_log(LOG_WARNING, "Unable to allocate memory for user name."); clean_exit(133); } if (gid != pw->pw_gid) { do_log(LOG_WARNING, "Running GID %ld does not match password entry GID.", gid); clean_exit(134); } if (!(gecos = strdup(pw->pw_gecos))) { do_log(LOG_WARNING, "Unable to allocate memory for GECOS information."); clean_exit(135); } if (strcmp(CHRSHPATH,pw->pw_shell)) { do_log(LOG_WARNING, "User's shell '%s' does not match the full path and name '%s' of this program.", pw->pw_shell, CHRSHPATH); clean_exit(136); } if (!(home = jail = strdup(pw->pw_dir))) { do_log(LOG_WARNING, "Unable to allocate memory for home directory."); clean_exit(137); } /* Check the format of the home dir for the /chroot/jail/./home/subdir format: Simple state engine: m == 0 Reading chroot jail part of dir m == 1 Reading chroot jail part of dir, "/" encountered m == 2 Reading separator "/./" -- "." encountered and expecting final "/" m == 3 Reading home subdir part of dir m == 4 Reading home subdir part of dir, "/" encountered */ for (c = home, m = 0; *c; c++) { switch (*c) { case '/': switch (m) { case 0: m = 1; break; case 1: /* ERROR: A double "//" is unacceptable! */ do_log(LOG_WARNING, "Unusual double slash \"//\" in chroot jail directory portion of user's home directory."); clean_exit(140); case 2: home = c; m = 4; break; case 3: m = 4; break; default: /* ERROR: A double "//" is unacceptable! */ do_log(LOG_WARNING, "Unusual double slash \"//\" in home subdirectory part of user's home directory."); clean_exit(141); } break; case '.': switch (m) { case 0: /* * A '.' character IS permitted in the path * IF it does NOT immediately follow a '/' * slash character. */ m = 0; break; case 1: *(c-1) = '\0'; /* Terminate the chroot jail portion. */ m = 2; break; default: /* ERROR: A period "." should not appear anywhere else. */ do_log(LOG_WARNING, "Home subdirectory portion of user's home directory contains a spurious period \".\" character."); } break; default: switch (m) { case 0: break; case 1: m = 0; break; case 2: /* ERROR: Expecting a "/" to begin the subdir part */ do_log(LOG_WARNING, "Expecting a slash \"/\" following the jail/subdirectory separator."); clean_exit(143); default: m = 3; } } } if (m == 4) { /* Trim trailing "/" from subdirectory portion */ *(c-1) = '\0'; } #ifdef DEBUG do_log(LOG_WARNING, "DEBUG: home==\"%s\" (%ld) jail==\"%s\" (%ld)", home, strlen(home), jail, strlen(jail)); #endif /* DEBUG */ if (strlen(jail) < 2) { do_log(LOG_WARNING, "Jail portion of home directory must contain a real subdirectory. Using the root directory \"/\" is not permitted."); clean_exit(145); } /* * Now do the same for the group structure, logging the * error if the GID doesn't appear to exist. */ if ((gr = getgrgid(gid)) == NULL) { do_log(LOG_WARNING, "GID %ld does not seem to exist.", gid); clean_exit(151); } if (!(groupname = strdup(gr->gr_name))) { do_log(LOG_WARNING, "Unable to allocate space for user's group name: '%s'", gr->gr_name); clean_exit(152); } /* * Now check the jail directory out to see if it is properly * owned and permissioned. Change working dir. to the jail * directory and destination for the future chroot(). */ if ((chdir(jail) != 0) || getcwd(wd, PATH_MAX) == NULL || strcmp(jail, wd)) { do_log(LOG_WARNING, "Unable to change working directory to the jail directory: %s", jail); clean_exit(181); } if (lstat(jail, &statinfo)) { do_log(LOG_WARNING, "Unable to stat the jail directory: %s", jail); clean_exit(185); } if (statinfo.st_mode & (S_IWGRP | S_IWOTH)) { do_log(LOG_WARNING, "Jail directory is world writable."); clean_exit(186); } if ((statinfo.st_mode & ALLPERMS) != JAILDIRMODE) { do_log(LOG_WARNING, "Jail directory mode does not match expected mode %lo.", JAILDIRMODE); clean_exit(187); } if (statinfo.st_uid != JAILDIRUID) { do_log(LOG_WARNING, "Jail directory is not owned by expected UID %ld.", JAILDIRUID); clean_exit(188); } if (statinfo.st_gid != JAILDIRGID) { do_log(LOG_WARNING, "Jail directory is not owned by expected GID %ld.", JAILDIRGID); clean_exit(189); } /* * Now is the TIME! Gonna do it! Here we go... * We are now chroot()-ing to the jail directory... */ if (chroot(jail)) { do_log(LOG_WARNING, "Unable to chroot() to the jail directory: %s", jail); clean_exit(190); } /* * Now look for and check out /bin and /etc in the jail... */ if ((m = checkjailed("/etc", JAILDIRUID, JAILDIRGID, 1, JAILDIRMODE)) != 0) { clean_exit(m+200); } if ((m = checkjailed("/bin", JAILDIRUID, JAILDIRGID, 1, JAILDIRMODE)) != 0) { clean_exit(m+210); } /* * Check out the user's home subdir in the jail... */ if ((m = checkjailed(home, uid, gid, 1, HOMEDIRMODE)) != 0) { clean_exit(m+220); } /* * Now double-check that the user exists within the jail * environment and that the info. matches what we expect. */ if ((pw = getpwuid(uid)) == NULL) { do_log(LOG_WARNING, "UID %ld does not exist in jail environment.", uid); clean_exit(231); } if (strcmp(username, pw->pw_name)) { do_log(LOG_WARNING, "User's jailed user name does not match executing user's name '%s'", username); clean_exit(233); } if (pw->pw_gid != gid) { do_log(LOG_WARNING, "User's jailed GID does not match.", gid); clean_exit(235); } if (strcmp(gecos, pw->pw_gecos)) { do_log(LOG_WARNING, "User's jailed user GECOS information does not match \"%s\".", gecos); clean_exit(237); } if (strcmp(home, pw->pw_dir)) { do_log(LOG_WARNING, "User's jailed home directory does not match \"%s\".", home); clean_exit(238); } /* * Does the user's jailed shell exist? */ for (m=0, shell=getusershell(); !m && shell && *shell; shell=getusershell()) { if (!strcmp(pw->pw_shell, shell)) { m = 1; break; } } endusershell(); if (!m) { do_log(LOG_WARNING, "Unable to find user's jailed shell \"%s\" in the jailed version of /etc/shells.", pw->pw_shell); clean_exit(240); } if (!(shell = strdup(pw->pw_shell))) { do_log(LOG_WARNING, "Unable to allocate space for user's jailed shell \"%s\".", pw->pw_shell); clean_exit(241); } if ((m = checkjailed(shell, SHELLUID, SHELLGID, 0, SHELLMODE)) != 0) { clean_exit(242+m); } /* * Check out the user's jailed group. */ if ((gr = getgrgid(gid)) == NULL) { do_log(LOG_WARNING, "Jailed GID %ld does not seem to exist.", gid); clean_exit(250); } if (strcmp(groupname, gr->gr_name)) { do_log(LOG_WARNING, "Jailed group name does not match \"%s\"", groupname); clean_exit(251); } /* * Now become the user. */ if (setgid(gid)) { do_log(LOG_WARNING, "Failed to set the GID to user's GID %ld.", gid); clean_exit(260); } if (initgroups(username, gid)) { do_log(LOG_WARNING, "Failed to initgroups for user \"%s\" GID %ld in jailed environment.", username, gid); clean_exit(263); } if (setuid(uid)) { do_log(LOG_WARNING, "Failed to set the UID to the user's UID %ld.", uid); clean_exit(265); } /* * See if we (as the user) can enter the user's home directory */ if ((m = chdir(home)) != 0 || getcwd(wd, PATH_MAX) == NULL || strcmp(home, wd)) { getcwd(wd, PATH_MAX); do_log(LOG_WARNING, "Unable to change working directory to the user's jailed home directory \"%s\".", home); clean_exit(269); } /* * See we can execute the shell as the user. If not, log * an error now while we still have access to the log file. */ if (lstat(shell, &statinfo)) { /* * Theoretically, this shouldn't happen since we * already stat'd this file once. Also, this check * is really redundant IF the SHELLMODE define is * correctly defined. */ do_log(LOG_WARNING, "Unable to stat user's jailed shell \"%s\"", shell); clean_exit(270); } if (!(statinfo.st_mode & S_IXUSR)) { do_log(LOG_WARNING, "User cannot execute jailed shell \"%s\" because execute permission is missing.", shell); clean_exit(271); } /* * Set up the new environment array, copying everything across * EXCEPT the HOME and SHELL items, which will be recreated. */ /* * First, count how many env. vars there are * EXCEPT the above noted two... */ for (m = 0, newenv = env; *newenv; newenv++, m++) { if (envmatch(*newenv, "HOME") || envmatch(*newenv, "SHELL")) m--; /* Don't count these two env. vars */ } if (!(newenv = (char**)malloc(sizeof(char*) * (m + 3)))) { do_log(LOG_WARNING, "Unable to allocate space for the new environment."); clean_exit(280); } /* * Just use the old ones EXCEPT for SHELL and HOME */ for (m = 0, i = 0; env[m] != NULL; m++) { if (!envmatch(env[m], "HOME") && !envmatch(env[m], "SHELL")) { newenv[i++] = env[m]; } } /* * Make a new SHELL env. variable */ if (!(newenv[i] = (char*)malloc(sizeof(char) * (strlen(shell) + 7)))) { do_log(LOG_WARNING, "Unable to allocate space for the SHELL environment variable."); clean_exit(281); } strcpy(newenv[i],"SHELL="); strcpy(newenv[i++]+6,shell); /* * Make a new HOME env. variable */ if (!(newenv[i] = (char*)malloc(sizeof(char) * (strlen(home) + 6)))) { do_log(LOG_WARNING, "Unable to allocate space for the HOME environment variable."); clean_exit(282); } strcpy(newenv[i],"HOME="); strcpy(newenv[i++]+5,home); /* * Now terminate the env. array */ newenv[i] = NULL; /* * Set up the new argument array. */ for(c = cmd = shell; *c; c++) { if (*c == '/') cmd = c+1; } if (!(newargv = (char**)malloc(sizeof(char*) * argc))) { do_log(LOG_WARNING, "Unable to allocate space for argv[]"); clean_exit(280); } if (*prog == '-') { /* If this program was called with a '-' char. prepended, do the same */ if (!(newargv[0] = (char*)malloc(sizeof(char) * (strlen(cmd) + 2)))) { do_log(LOG_WARNING, "Unable to allocate space for argv[0] \"-%s\"", cmd); clean_exit(285); } *newargv[0] = '-'; strcpy(newargv[0]+1,cmd); } else { if (!(newargv[0] = strdup(cmd))) { do_log(LOG_WARNING, "Unable to allocate space for argv[0] \"%s\"", cmd); clean_exit(286); } } /* Pass on any arguments to the real shell */ i = 1; while (i < argc) { if (!(newargv[i] = strdup(argv[i]))) { do_log(LOG_WARNING, "Unable to allocate space for user supplied argv[%d]", i); clean_exit(287); } i++; } newargv[i] = NULL; /* Make a hopefully final log entry */ if (*peername) { do_log(LOG_WARNING, "User from %s is jailed. Executing shell '%s'.", peername, shell); } else { do_log(LOG_WARNING, "User is jailed. Executing shell '%s'.", shell); } /* * Free up some of the space we allocated: */ free(gecos); free(jail); /* This free's up home too */ free(username); username = NULL; free(groupname); groupname = NULL; /* DON'T free shell 'cause we use it below */ /* * Here we attempt to flag the logging file descriptor * (either the open log file or the syslog descriptor) * for automatic closure if the execve() call succeeds. * This lets us continue to use the same logging * descriptor in logging problems should the call fail * while prohibiting the jailed process from having * access to the descriptor. We wouldn't want just * anyone having the ability to write to our log file * or happily go syslog-ing away with the root-opened * descriptor. * * The below fcntl() call may be FreeBSD specific. * I have no idea how portable this behavior is to * other OSs. */ if (logfd >= 3 && fcntl(logfd, F_SETFD, 1) == -1) { do_log(LOG_WARNING, "Unable to flag log file for automatic closing. Call to fcntl() failed: (%d) \"%s\"", errno, strerror(errno)); clean_exit(290); } /* * THE TIME OF FINAL JUDGEMENT HAS ARRIVED! * Execute the command, replacing our image with its own. */ execve(shell, newargv, newenv); /* * The earth blew up and everybody died! * Log a whole lot of stuff to help track down the problem. */ do_log(LOG_WARNING, "Bad kharma! The chrsh execve() call failed with error %d: %s", errno, strerror(errno)); do_log(LOG_WARNING, " shell==\"%s\" cmd==\"%s\"", shell, cmd); for (i=0; newargv[i]; i++) do_log(LOG_WARNING, " arg[%d]==\"%s\"", i, newargv[i]); for (i=0; newenv[i]; i++) do_log(LOG_WARNING, " env[%d]==\"%s\"", i, newenv[i]); do_log(LOG_WARNING, "Check the shell executable to make sure it is valid and not corrupt. Make sure any necessary dynamic libraries are present if the shell is not staticly linked. Also check for necessary system files that may be required in the jailed environment."); /* Theoretically we never get to here, right? ;) */ clean_exit(1000); /* And this is to eliminate warnings when compiling with -Wall */ return 0; }