[lxc-devel] [PATCH 1/3] lxc-attach: Try really hard to determine login shell
Christian Seiler
christian at iwakd.de
Wed Mar 6 19:53:17 UTC 2013
If no command is specified, and using getpwuid() to determine the login
shell fails, try to spawn a process that executes the utility 'getent'.
getpwuid() may fail because of incompatibilities between the NSS
implementations on the host and in the container.
Signed-off-by: Christian Seiler <christian at iwakd.de>
---
src/lxc/attach.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/lxc/attach.h | 2 +
src/lxc/lxc_attach.c | 18 +++++-
3 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/src/lxc/attach.c b/src/lxc/attach.c
index af3d7a0..d1b3b0a 100644
--- a/src/lxc/attach.c
+++ b/src/lxc/attach.c
@@ -32,7 +32,9 @@
#include <sys/prctl.h>
#include <sys/mount.h>
#include <sys/syscall.h>
+#include <sys/wait.h>
#include <linux/unistd.h>
+#include <pwd.h>
#if !HAVE_DECL_PR_CAPBSET_DROP
#define PR_CAPBSET_DROP 24
@@ -275,3 +277,155 @@ int lxc_attach_drop_privs(struct lxc_proc_context_info *ctx)
return 0;
}
+
+char *lxc_attach_getpwshell(uid_t uid)
+{
+ /* local variables */
+ pid_t pid;
+ int pipes[2];
+ int ret;
+ int fd;
+ char *result = NULL;
+
+ /* we need to fork off a process that runs the
+ * getent program, and we need to capture its
+ * output, so we use a pipe for that purpose
+ */
+ ret = pipe(pipes);
+ if (ret < 0)
+ return NULL;
+
+ pid = fork();
+ if (pid < 0) {
+ close(pipes[0]);
+ close(pipes[1]);
+ return NULL;
+ }
+
+ if (pid) {
+ /* parent process */
+ FILE *pipe_f;
+ char *line = NULL;
+ size_t line_bufsz = 0;
+ int found = 0;
+ int status;
+
+ close(pipes[1]);
+
+ pipe_f = fdopen(pipes[0], "r");
+ while (getline(&line, &line_bufsz, pipe_f) != -1) {
+ char *token;
+ char *saveptr = NULL;
+ long value;
+ char *endptr = NULL;
+ int i;
+
+ /* if we already found something, just continue
+ * to read until the pipe doesn't deliver any more
+ * data, but don't modify the existing data
+ * structure
+ */
+ if (found)
+ continue;
+
+ /* trim line on the right hand side */
+ for (i = strlen(line); line && i > 0 && (line[i - 1] == '\n' || line[i - 1] == '\r'); --i)
+ line[i - 1] = '\0';
+
+ /* split into tokens: first user name */
+ token = strtok_r(line, ":", &saveptr);
+ if (!token)
+ continue;
+ /* next: dummy password field */
+ token = strtok_r(NULL, ":", &saveptr);
+ if (!token)
+ continue;
+ /* next: user id */
+ token = strtok_r(NULL, ":", &saveptr);
+ value = token ? strtol(token, &endptr, 10) : 0;
+ if (!token || !endptr || *endptr || value == LONG_MIN || value == LONG_MAX)
+ continue;
+ /* dummy sanity check: user id matches */
+ if ((uid_t) value != uid)
+ continue;
+ /* skip fields: gid, gecos, dir, go to next field 'shell' */
+ for (i = 0; i < 4; i++) {
+ token = strtok_r(NULL, ":", &saveptr);
+ if (!token)
+ break;
+ }
+ if (!token)
+ continue;
+ result = strdup(token);
+
+ /* sanity check that there are no fields after that */
+ token = strtok_r(NULL, ":", &saveptr);
+ if (token)
+ continue;
+
+ found = 1;
+ }
+
+ free(line);
+ fclose(pipe_f);
+ again:
+ if (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR)
+ goto again;
+ return NULL;
+ }
+
+ /* some sanity checks: if anything even hinted at going
+ * wrong: we can't be sure we have a valid result, so
+ * we assume we don't
+ */
+
+ if (!WIFEXITED(status))
+ return NULL;
+
+ if (WEXITSTATUS(status) != 0)
+ return NULL;
+
+ if (!found)
+ return NULL;
+
+ return result;
+ } else {
+ /* child process */
+ char uid_buf[32];
+ char *arguments[] = {
+ "getent",
+ "passwd",
+ uid_buf,
+ NULL
+ };
+
+ close(pipes[0]);
+
+ /* we want to capture stdout */
+ dup2(pipes[1], 1);
+ close(pipes[1]);
+
+ /* get rid of stdin/stderr, so we try to associate it
+ * with /dev/null
+ */
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0) {
+ close(0);
+ close(2);
+ } else {
+ dup2(fd, 0);
+ dup2(fd, 2);
+ close(fd);
+ }
+
+ /* finish argument list */
+ ret = snprintf(uid_buf, sizeof(uid_buf), "%ld", (long) uid);
+ if (ret <= 0)
+ exit(-1);
+
+ /* try to run getent program */
+ (void) execvp("getent", arguments);
+ exit(-1);
+ }
+}
diff --git a/src/lxc/attach.h b/src/lxc/attach.h
index 4d4f719..f448b1e 100644
--- a/src/lxc/attach.h
+++ b/src/lxc/attach.h
@@ -38,4 +38,6 @@ extern int lxc_attach_to_ns(pid_t other_pid, int which);
extern int lxc_attach_remount_sys_proc();
extern int lxc_attach_drop_privs(struct lxc_proc_context_info *ctx);
+extern char *lxc_attach_getpwshell(uid_t uid);
+
#endif
diff --git a/src/lxc/lxc_attach.c b/src/lxc/lxc_attach.c
index 1f60266..c129eb0 100644
--- a/src/lxc/lxc_attach.c
+++ b/src/lxc/lxc_attach.c
@@ -132,6 +132,7 @@ int main(int argc, char *argv[])
uid_t uid;
char *curdir;
int cgroup_ipc_sockets[2];
+ char *user_shell;
ret = lxc_caps_init();
if (ret)
@@ -438,7 +439,20 @@ int main(int argc, char *argv[])
uid = getuid();
passwd = getpwuid(uid);
- if (!passwd) {
+
+ /* this probably happens because of incompatible nss
+ * implementations in host and container (remember, this
+ * code is still using the host's glibc but our mount
+ * namespace is in the container)
+ * we may try to get the information by spawning a
+ * [getent passwd uid] process and parsing the result
+ */
+ if (!passwd)
+ user_shell = lxc_attach_getpwshell(uid);
+ else
+ user_shell = passwd->pw_shell;
+
+ if (!user_shell) {
SYSERROR("failed to get passwd " \
"entry for uid '%d'", uid);
return -1;
@@ -446,7 +460,7 @@ int main(int argc, char *argv[])
{
char *const args[] = {
- passwd->pw_shell,
+ user_shell,
NULL,
};
--
1.7.10.4
More information about the lxc-devel
mailing list