#include "pwgui.h" #include "config.h" #include #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(); static char *helper_urlencode(char *s, char *enc); 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) { int pipefd[2]; pipe(pipefd); pid_t pid = fork(); if (pid == 0) { // Child close(pipefd[0]); // close reading end dup2(pipefd[1], 2); // send stderr to pipe close(pipefd[1]); // no longer needed after dup // Newer backend can read credentials from environment if (user != NULL) setenv("AUTH_USERNAME", user, 1); if (password != NULL) setenv("AUTH_PASSWORD", password, 1); // For ipp14 (at least) we need to build a new URI with ://username:password@.... if (user != NULL && password != NULL && strncmp(uri, "ipp14", 5) == 0) { char *newstr = (char*)malloc(strlen(uri) + strlen(user) * 3 + strlen(password) * 3 + 20); char *ptr = newstr; ptr += sprintf(ptr, "ipp14://"); ptr = helper_urlencode(user, ptr); *ptr++ = ':'; ptr = helper_urlencode(password, ptr); *ptr++ = '@'; ptr += sprintf(ptr, "%s", uri + 8); // TODO: If uri already contains credentials, skip over them (check if there's a @ before the first /, starting at index 8) uri = newstr; setenv("DEVICE_URI", uri, 1); // Since we've already forked and execv anyways there's no need to clean up anything... } 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... close(pipefd[1]); // close writing end char buffer[BUFLEN]; bool needAuth = false; int readlen = 0; while ((readlen = read(pipefd[0], buffer, BUFLEN-1)) > 0) { buffer[readlen] = '\0'; if (strstr(buffer, "Unable to get printer status (Unauthorized)!") != NULL) { needAuth = true; if (kill(pid, SIGTERM) < 0) { fprintf(stderr, "ERROR: Sending kill1 to backend %d failed: %d\n", (int)pid, errno); } break; } } close(pipefd[0]); int status; if (waitpid(pid, &status, WNOHANG) == 0) { if (kill(pid, SIGKILL) == 0) { // Try to reap zombie sleep(1); waitpid(pid, &status, WNOHANG); } } if (needAuth) { fprintf(stderr, "ERROR: Killed backend because of 'unauthorized' message (iprint crap?), trying with auth\n"); return CUPS_BACKEND_AUTH_REQUIRED; } if (!WIFEXITED(status)) { fprintf(stderr, "ERROR: Running backend %s failed!\n", backend); return CUPS_BACKEND_FAILED; } status = WEXITSTATUS(status); if (status != CUPS_BACKEND_OK) fprintf(stderr, "ERROR: Backend returned %d\n", status); 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; } } /** * Make sure enc is 3 times as large as s */ static char *helper_urlencode(char *source, char *enc) { static char table[256] = {3}; int len; if (table[0] == 3) { int i; for (i = 0; i < 256; i++) { table[i] = (isalnum(i) || i == '~' || i == '-' || i == '.' || i == '_') ? i : 0; } } unsigned char *s = (unsigned char*)source; for (; *s != '\0'; s++) { if (table[*s] != '\0') len = sprintf(enc, "%c", table[*s]); else len = sprintf(enc, "%%%02X", *s); enc += len; } *enc = '\0'; return enc; }