#include "pwgui.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NAMELEN 400 #define BUFLEN 1000 #define ENVLEN 10000 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[ENVLEN] = "\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]; char device[NAMELEN]; char backend[NAMELEN]; int spoolres; // Pretty much what smbspool does, but in a generalized way if (argc > 2 && strstr(argv[0], ":/") == NULL && strstr(argv[1], ":/") != NULL) { argv++; argc--; } // First check parameter count if (argc != 6 && argc != 7) { fputs("ERROR: Invalid number of arguments passed.\n", stderr); 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 & 0011) != 0) helper_dropprivs(); kill(pid, SIGTERM); 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()); int fh = open(tmpfile, O_CREAT | O_WRONLY | O_TRUNC, 0600); if (fh < 0) { fprintf(stderr, "ERROR: Could not open %s for writing..\n", tmpfile); return CUPS_BACKEND_FAILED; } char buffer[BUFLEN]; int bytes, ret; int total = 0; for (;;) { bytes = read(STDIN_FILENO, buffer, BUFLEN); if (bytes == 0) break; if (bytes < 0) { fprintf(stderr, "ERROR: Could not read print job from STDIN.\n"); remove(tmpfile); return CUPS_BACKEND_FAILED; } if ((ret = write(fh, buffer, bytes)) != bytes) { fprintf(stderr, "ERROR: Could not write %d bytes to %s (wrote %d)\n", (int)bytes, tmpfile, (int)ret); remove(tmpfile); return CUPS_BACKEND_FAILED; } total += bytes; } close(fh); fprintf(stderr, "ERROR: Read %d bytes from stdin.\n", total); // } else { // File given, check if file exists snprintf(tmpfile, NAMELEN, "%s", argv[6]); int fh = open(tmpfile, O_RDONLY); if (fh < 0) { fprintf(stderr, "ERROR: Could not open %s for reading..\n", tmpfile); return CUPS_BACKEND_FAILED; } close(fh); // } chown(tmpfile, ruid, rgid); // 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_AUTH_REQUIRED) return spoolres; // Yay // Seems we need the dialog int status; char creds[NAMELEN], *pass = NULL; snprintf(creds, NAMELEN, "%s", argv[2]); 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], creds); w.show(); exit(a.exec()); return CUPS_BACKEND_FAILED; } // Main (Parent) close(pfd[1]); // Read from pipe int bytes = read(pfd[0], creds, NAMELEN - 1); close(pfd[0]); // 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); remove(tmpfile); 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); remove(tmpfile); fprintf(stderr, "ERROR: Job submitted.\n"); 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) { pid_t pid = fork(); if (pid == 0) { // Child if (user != NULL) setenv("AUTH_USERNAME", user, 1); if (password != NULL) setenv("AUTH_PASSWORD", password, 1); char *args[8]; args[0] = uri; args[1] = jobid; args[2] = user; args[3] = title; args[4] = copies; args[5] = options; args[6] = file; args[7] = NULL; // Priv dropping struct stat st; if (stat(backend, &st) != 0 || (st.st_mode & 0011) != 0) helper_dropprivs(); // Exec execv(backend, args); exit(127); return 127; } // Main - wait for it... int status; waitpid(pid, &status, 0); if (!WIFEXITED(status)) { fprintf(stderr, "ERROR: Running backend %s failed!\n", backend); return CUPS_BACKEND_FAILED; } 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) { fprintf(stderr, "WARNING: Job Title doesnt start with 'gui-' (Is: %s)\n", title); return false; // Wrong job title } int p = atoi(title + 4); struct stat st; struct passwd *pw = getpwnam(user); if (pw == NULL) { fprintf(stderr, "WARNING: Cannot getpwnam %s\n", user); return false; } char bin[PATH_MAX+1], tmp[100]; snprintf(tmp, 100, "/proc/%d/exe", p); if (realpath(tmp, bin) == NULL) { fprintf(stderr, "WARNING: Cannot get realpath of %s\n", tmp); return false; } char *last = strrchr(bin, '/'); if (last == NULL || strcmp(last, "/printergui") != 0) { fprintf(stderr, "WARNING: %s does not end in /printergui\n", bin); return false; // Wrong process } // PID passed via job title seems to be the printergui if (lstat(tmp, &st) < 0) { fprintf(stderr, "WARNING: Could not lstat() %s\n", tmp); return false; } if (st.st_uid != pw->pw_uid) { fprintf(stderr, "WARNING: Owner of %s: %d, owner of job: %d (%s)\n", tmp, (int)st.st_uid, (int)pw->pw_uid, user); 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) { char *ptr = gui_env; int bytes = 0, ret; while ((ret = read(fh, ptr, ENVLEN - (ptr - gui_env) - 2)) > 0) { bytes += ret; ptr += ret; if (bytes + 3 >= ENVLEN) break; } close(fh); if (bytes >= 0) { gui_env[bytes+0] = '\0'; gui_env[bytes+1] = '\0'; } } fprintf(stderr, "DEBUG: getpiduid successful!\n"); 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; } }