From 8ae112083bbe26848f41d09c95559acb96b3ccb0 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 7 Feb 2014 01:48:59 +0100 Subject: Getting there, slowly: - Check if the backend is invoked as a result of the printergui sending a printjob If not, just relay to the real backend and appear transparent If yes, copy environment from the printergui (X access etc) and show the username/password dialog. - Drop privileges whereever neccessary (when invoking backend, when showing GUI) TODO: Use pipe to send user/pass to backend from the GUI. Make OK and Cancel in the GUI work. --- src/maingui/printergui.cpp | 4 +- src/pwgui/main.cpp | 200 ++++++++++++++++++++++++++++++++++++++------- src/pwgui/pwgui.cpp | 18 ++-- src/pwgui/pwgui.h | 3 +- 4 files changed, 187 insertions(+), 38 deletions(-) diff --git a/src/maingui/printergui.cpp b/src/maingui/printergui.cpp index 3744ffc..90d4731 100644 --- a/src/maingui/printergui.cpp +++ b/src/maingui/printergui.cpp @@ -214,11 +214,13 @@ void PrinterGui::on_buttonPrint_clicked() } cupsSetUser(this->user); + char jobtitle[100]; + snprintf(jobtitle, 100, "gui-%d", (int)getpid()); // Drucken if( 0 == cupsPrintFile(dest->name, file, - NULL, + jobtitle, dest->num_options, dest->options)) { QMessageBox::critical(this, "CUPS Fehler", cupsLastErrorString()); diff --git a/src/pwgui/main.cpp b/src/pwgui/main.cpp index d75f280..c33b3f6 100644 --- a/src/pwgui/main.cpp +++ b/src/pwgui/main.cpp @@ -10,12 +10,29 @@ #include #include #include +#include +#include +#include +#include #define NAMELEN 400 #define BUFLEN 1000 +static int pid = -1; +// UID and GUI of user we should drop privileges to +static int ruid = 65534, rgid = 65534; +static char ruser[NAMELEN] = ""; +// Copy of the GUIs environment, so we can access X +// Whatever you do, make sure this has at least two nullchars at the end! +static char gui_env[BUFLEN] = "\0\0\0"; + static int run_backend(char *backend, char *uri, char *jobid, char *user, char *title, char *copies, char *options, char *file, char *password); +static bool helper_getpiduid(char *user, char *title); +static bool helper_loadlpuser(); +static void helper_dropprivs(); +static void helper_copyenv(); + int main(int argc, char *argv[]) { char tmpfile[NAMELEN]; @@ -35,6 +52,41 @@ int main(int argc, char *argv[]) return CUPS_BACKEND_FAILED; } + // Determine device uri + char *env = getenv("DEVICE_URI"); + if (env != NULL && strchr(env, ':') != NULL) { + snprintf(device, NAMELEN, "%s", env); + } else if (strstr(argv[0], ":/") != NULL) { + snprintf(device, NAMELEN, "%s", argv[0]); + } else { + fputs("ERROR: No device URI given.\n", stderr); + return CUPS_BACKEND_FAILED; + } + + // Get backend from uri + char *colon = strchr(device, ':'); + *colon = '\0'; + snprintf(backend, NAMELEN, "%s/%s", BACKEND_PATH, device); + *colon = ':'; + // Is valid? + if (access(backend, X_OK | R_OK) != 0) { + fprintf(stderr, "ERROR: Backend %s is not executable.\n", backend); + return CUPS_BACKEND_FAILED; + } + + // argv[3] is title, get printergui pid from it + if (!helper_getpiduid(argv[2], argv[3])) { + // El cheapo validation failed. Don't enable "smart mode" (GUI etc), just exec real backend + // Mimic cups behaviour wrt dropping privs (Only 0700 or 0500 == root) + helper_loadlpuser(); + struct stat st; + if (stat(backend, &st) != 0 || (st.st_mode & 0077) != 0) helper_dropprivs(); + execv(backend, argv); + exit(127); + } + kill(pid, SIGTERM); + + // Get document to print if (argc == 6) { // Data comes from stdin, save... snprintf(tmpfile, NAMELEN, "/tmp/print-%s-%d-%s-%d", argv[1], (int)time(NULL), argv[2], (int)getpid()); @@ -68,38 +120,48 @@ int main(int argc, char *argv[]) // } - // Determine device uri - char *env = getenv("DEVICE_URI"); - if (env != NULL && strchr(env, ':') != NULL) { - snprintf(device, NAMELEN, "%s", env); - } else if (strstr(argv[0], ":/") != NULL) { - snprintf(device, NAMELEN, "%s", argv[0]); - } else { - fputs("ERROR: No device URI given.\n", stderr); - return CUPS_BACKEND_FAILED; - } - - // Get backend from uri - char *colon = strchr(device, ':'); - *colon = '\0'; - snprintf(backend, NAMELEN, "%s/%s", BACKEND_PATH, device); - *colon = ':'; - // Is valid? - if (access(backend, X_OK | R_OK) != 0) { - fprintf(stderr, "ERROR: Backend %s is not executable.\n", backend); - return CUPS_BACKEND_FAILED; - } - // Try right away with what we got spoolres = run_backend(backend, device, argv[1], argv[2], argv[3], argv[4], argv[5], tmpfile, NULL); - if (spoolres == CUPS_BACKEND_OK) return spoolres; // Yay - - // Dialog - TODO: Drop privileges here! - QApplication a(argc, argv); - PwGui w(NULL); - w.show(); - return a.exec(); - // TODO ..... + if (spoolres != CUPS_BACKEND_AUTH_REQUIRED) return spoolres; // Yay + + // Seems we need the dialog + int status; + do { + int pfd[2]; + if (pipe(pfd) != 0) { + fputs("ERROR: Could not create pipe for GUI.\n", stderr); + return CUPS_BACKEND_FAILED; + } + pid_t pid = fork(); + if (pid == 0) { + // Child - GUI + close(pfd[0]); + helper_dropprivs(); + helper_copyenv(); + QApplication a(argc, argv); + PwGui w(pfd[1]); + w.show(); + exit(a.exec()); + return CUPS_BACKEND_FAILED; + } + // Main (Parent) + close(pfd[1]); + // Read from pipe + char creds[NAMELEN], *pass = NULL; + int bytes = read(pfd[0], creds, NAMELEN - 1); + // Wait for child to die + waitpid(pid, NULL, 0); // Don't check status, just look at pipe data + if (bytes <= 0) { + fputs("ERROR: Could not read anything from pipe after showing GUI.\n", stderr); + return CUPS_BACKEND_CANCEL; + } + creds[bytes] = '\0'; + int len = strlen(creds); + if (len < bytes) pass = creds + len + 1; + // Run backend with pimped user/pass + status = run_backend(backend, device, argv[1], creds, argv[3], argv[4], argv[5], tmpfile, pass); + } while (status != CUPS_BACKEND_OK); + return CUPS_BACKEND_OK; } static int run_backend(char *backend, char *uri, char *jobid, char *user, char *title, char *copies, char *options, char *file, char *password) @@ -118,6 +180,10 @@ static int run_backend(char *backend, char *uri, char *jobid, char *user, char * args[5] = options; args[6] = file; args[7] = NULL; + // Priv dropping + struct stat st; + if (stat(backend, &st) != 0 || (st.st_mode & 0077) != 0) helper_dropprivs(); + // Exec execv(backend, args); exit(127); return 127; @@ -133,3 +199,77 @@ static int run_backend(char *backend, char *uri, char *jobid, char *user, char * return WEXITSTATUS(status); } +static bool helper_getpiduid(char *user, char *title) +{ + // it has to be gui-, PID has to be an instance of printergui + // and we have to be able to kill it, only then we assume we should bother the user + // with an authentication dialog + if (strncmp(title, "gui-", 4) != 0) return false; // Wrong job title + struct stat st; + struct passwd *pw = getpwnam(user); + if (pw == NULL) return false; + int p = atoi(title + 4); + char bin[PATH_MAX+1], tmp[100]; + snprintf(tmp, 100, "/proc/%d/exe", p); + if (realpath(tmp, bin) == NULL) return false; + char *last = strrchr(bin, '/'); + if (last == NULL || strcmp(last, "/printergui") != 0) return false; // Wrong process + // PID passed via job title seems to be the printergui + if (lstat(tmp, &st) < 0) return false; + if (st.st_uid != pw->pw_uid) return false; // Print job user doesn't match printergui process owner + // All checks passed, make stuff global + pid = p; + ruid = pw->pw_uid; + rgid = pw->pw_gid; + snprintf(ruser, NAMELEN, "%s", user); + // Finally, try to copy the environment of the process + snprintf(tmp, 100, "/proc/%d/environ", p); + int fh = open(tmp, O_RDONLY); + if (fh >= 0) { + int bytes = read(fh, gui_env, BUFLEN - 2); + close(fh); + if (bytes >= 0) { + gui_env[bytes+0] = '\0'; + gui_env[bytes+1] = '\0'; + } + } + return true; +} + +static bool helper_loadlpuser() +{ + struct passwd *pw = getpwnam("lp"); + if (pw == NULL) return false; + ruid = pw->pw_uid; + rgid = pw->pw_gid; + return true; +} + +static void helper_dropprivs() +{ + if (ruid == 0) return; + initgroups(ruser, rgid); + setgid(rgid); + setuid(ruid); + chdir("/"); + if (setuid(0) != -1) { + fputs("ERROR: setuid-fu!?\n", stderr); + exit(CUPS_BACKEND_FAILED); + } +} + +static void helper_copyenv() +{ + char *ptr = gui_env; + while (strlen(ptr) > 0) { + char *equal = strchr(ptr, '='); + if (equal != NULL) { + char *value = equal + 1; + *equal = '\0'; + setenv(ptr, value, 1); + *equal = '='; + } + ptr += strlen(ptr) + 1; + } +} + diff --git a/src/pwgui/pwgui.cpp b/src/pwgui/pwgui.cpp index 31dd822..448cee8 100644 --- a/src/pwgui/pwgui.cpp +++ b/src/pwgui/pwgui.cpp @@ -5,20 +5,24 @@ #include // ____________________________________________________________________________ -PwGui::PwGui(QWidget *parent) : +PwGui::PwGui(int pfd, QWidget *parent) : QMainWindow(parent), - ui(new Ui::PwGui) { + ui(new Ui::PwGui), + pipefd(pfd) +{ // Initialize UI initializeUI(); } // ____________________________________________________________________________ -PwGui::~PwGui() { +PwGui::~PwGui() +{ delete ui; } // ____________________________________________________________________________ -void PwGui::initializeUI() { +void PwGui::initializeUI() +{ ui->setupUi(this); /* @@ -43,14 +47,16 @@ void PwGui::initializeUI() { } /* -void PwGui::on_buttonCancel_clicked() { +void PwGui::on_buttonCancel_clicked() +{ // Quit with code 1 QCoreApplication::instance()->exit(1); } */ // ____________________________________________________________________________ -void PwGui::on_checkStatusTimer() { +void PwGui::on_checkStatusTimer() +{ static int retries = 0; if (++retries > 20) { QCoreApplication::instance()->quit(); diff --git a/src/pwgui/pwgui.h b/src/pwgui/pwgui.h index 3e56ec8..95a7a05 100644 --- a/src/pwgui/pwgui.h +++ b/src/pwgui/pwgui.h @@ -16,7 +16,7 @@ class PwGui : public QMainWindow Q_OBJECT public: - explicit PwGui(QWidget *parent = 0); + explicit PwGui(int pfd, QWidget *parent = 0); ~PwGui(); private slots: @@ -26,6 +26,7 @@ private: Ui::PwGui *ui; void initializeUI(); QTimer* checkStatusTimer; + int pipefd; }; #endif // AUTHENTICATION_H -- cgit v1.2.3-55-g7522