summaryrefslogblamecommitdiffstats
path: root/src/pwgui/main.cpp
blob: fa131b19902b57fb373df6d27c5908a323e9c223 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                         



                   


                   
                    
 





                                                                         
                                       
 

                                                                                                                                                




                                                      


















                                                                                   



























                                                                                               
                                                                                
                       





                          








                                                                                                           



                                                 
                            




                                                                         




                                                                                                             
                     

              
                                                                      











                                                                           
                             
 

                                                                                                      



                                                                     
                                    
                                          












                                                               
                             






                                 
                                                 
                  



                                                                                     
                      







                                                                                                    
                  
                                             
                         

















                                                                                                                                               

                    
                                                                                
           














                                                                    




                                                                                     



                                                                                     
                          

                                     



                                                           

                                        



                                                                 
                                 



                                                                      
                                                        







                                                                                                                     








                                                        






                                                                     





                              
                                                    







































                                          
#include "pwgui.h"
#include "config.h"
#include <QApplication>
#include <cups/backend.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>

#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>, 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;
  }
}