Changes from APPLIANCE_HEAD:
[ira/wip.git] / source3 / printing / printing.c
index b23dd3aa73ebfcc4d5d29d0415f9cf8528bed343..57d0c2b8a3728d886cd6da26b0f0f18ef1b04a8c 100644 (file)
@@ -1,3 +1,4 @@
+#define OLD_NTDOMAIN 1
 /* 
    Unix SMB/Netbios implementation.
    Version 3.0
@@ -20,6 +21,7 @@
 */
 
 #include "includes.h"
+
 extern int DEBUGLEVEL;
 
 /* 
@@ -59,7 +61,9 @@ static pid_t local_pid;
 #define UNIX_JOB_START PRINT_MAX_JOBID
 
 #define PRINT_SPOOL_PREFIX "smbprn."
-#define PRINT_DATABASE_VERSION 1
+#define PRINT_DATABASE_VERSION 2
+
+static int get_queue_status(int, print_status_struct *);
 
 /****************************************************************************
 initialise the printing backend. Called once at startup. 
@@ -67,6 +71,8 @@ Does not survive a fork
 ****************************************************************************/
 BOOL print_backend_init(void)
 {
+       char *sversion = "INFO/version";
+
        if (tdb && local_pid == sys_getpid()) return True;
        tdb = tdb_open(lock_path("printing.tdb"), 0, 0, O_RDWR|O_CREAT, 0600);
        if (!tdb) {
@@ -75,14 +81,14 @@ BOOL print_backend_init(void)
        local_pid = sys_getpid();
 
        /* handle a Samba upgrade */
-       tdb_writelock(tdb);
-       if (tdb_get_int(tdb, "INFO/version") != PRINT_DATABASE_VERSION) {
+       tdb_lock_bystring(tdb, sversion);
+       if (tdb_fetch_int(tdb, sversion) != PRINT_DATABASE_VERSION) {
                tdb_traverse(tdb, (tdb_traverse_func)tdb_delete, NULL);
-               tdb_store_int(tdb, "INFO/version", PRINT_DATABASE_VERSION);
+               tdb_store_int(tdb, sversion, PRINT_DATABASE_VERSION);
        }
-       tdb_writeunlock(tdb);
+       tdb_unlock_bystring(tdb, sversion);
 
-       return True;
+       return nt_printing_init();
 }
 
 /****************************************************************************
@@ -123,20 +129,23 @@ static BOOL print_job_store(int jobid, struct printjob *pjob)
        TDB_DATA d;
        d.dptr = (void *)pjob;
        d.dsize = sizeof(*pjob);
-       return (0 == tdb_store(tdb, print_key(jobid), d, TDB_REPLACE));
+
+       return (tdb_store(tdb, print_key(jobid), d, TDB_REPLACE) == 0);
 }
 
 /****************************************************************************
 run a given print command 
+a null terminated list of value/substitute pairs is provided
+for local substitution strings
 ****************************************************************************/
 static int print_run_command(int snum,char *command, 
                             char *outfile,
-                            char *a1, char *v1, 
-                            char *a2, char *v2)
+                            ...)
 {
        pstring syscmd;
-       char *p;
+       char *p, *arg;
        int ret;
+       va_list ap;
 
        if (!command || !*command) return -1;
 
@@ -146,18 +155,24 @@ static int print_run_command(int snum,char *command,
        }
 
        pstrcpy(syscmd, command);
-       if (a1) pstring_sub(syscmd, a1, v1);
-       if (a2) pstring_sub(syscmd, a2, v2);
+
+       va_start(ap, outfile);
+       while ((arg = va_arg(ap, char *))) {
+               char *value = va_arg(ap,char *);
+               pstring_sub(syscmd, arg, value);
+       }
+       va_end(ap);
   
        p = PRINTERNAME(snum);
        if (!p || !*p) p = SERVICE(snum);
   
-       pstring_sub(syscmd, "%p", p);  
+       pstring_sub(syscmd, "%p", p);
        standard_sub_snum(snum,syscmd);
   
        ret = smbrun(syscmd,outfile,False);
 
        DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+
        return ret;
 }
 
@@ -185,14 +200,18 @@ list a unix job in the print database
 static void print_unix_job(int snum, print_queue_struct *q)
 {
        int jobid = q->job + UNIX_JOB_START;
-       struct printjob pj;
+       struct printjob pj, *old_pj;
+
+       /* Preserve the timestamp on an existing unix print job */
+
+       old_pj = print_job_find(jobid);
 
        ZERO_STRUCT(pj);
 
        pj.pid = (pid_t)-1;
        pj.sysjob = q->job;
        pj.fd = -1;
-       pj.starttime = q->time;
+       pj.starttime = old_pj ? old_pj->starttime : q->time;
        pj.status = q->status;
        pj.size = q->size;
        pj.spooled = True;
@@ -208,7 +227,7 @@ static void print_unix_job(int snum, print_queue_struct *q)
 
 struct traverse_struct {
        print_queue_struct *queue;
-       int qcount, snum;
+       int qcount, snum, maxcount;
 };
 
 /* utility fn to delete any jobs that are no longer active */
@@ -230,6 +249,7 @@ static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void
        if (!pjob.smbjob) {
                /* remove a unix job if it isn't in the system queue
                    any more */
+
                for (i=0;i<ts->qcount;i++) {
                        if (jobid == ts->queue[i].job + UNIX_JOB_START) break;
                }
@@ -253,11 +273,23 @@ static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void
                if (jobid == qid) break;
        }
        
+       /* The job isn't in the system queue - we have to assume it has
+          completed, so delete the database entry. */
+
        if (i == ts->qcount) {
-               /* the job isn't in the system queue - we have to
-                   assume it has completed, so delete the database
-                   entry */
-               tdb_delete(t, key);
+               time_t cur_t = time(NULL);
+
+               /* A race can occur between the time a job is spooled and
+                  when it appears in the lpq output.  This happens when
+                  the job is added to printing.tdb when another smbd
+                  running print_queue_update() has completed a lpq and
+                  is currently traversing the printing tdb and deleting jobs.
+                  A workaround is to not delete the job if it has been 
+                  submitted less than lp_lpqcachetime() seconds ago. */
+
+               if ((cur_t - pjob.starttime) > lp_lpqcachetime()) {
+                       tdb_delete(t, key);
+               }
        }
 
        return 0;
@@ -285,38 +317,56 @@ static void print_queue_update(int snum)
        int numlines, i, qcount;
        print_queue_struct *queue = NULL;
        print_status_struct status;
+       print_status_struct old_status;
        struct printjob *pjob;
        struct traverse_struct tstruct;
-       fstring keystr;
+       fstring keystr, printer_name;
        TDB_DATA data, key;
  
+       fstrcpy(printer_name, lp_servicename(snum));
+       
+       /*
+        * Update the cache time FIRST ! Stops others doing this
+        * if the lpq takes a long time.
+        */
+
+       slprintf(keystr, sizeof(keystr), "CACHE/%s", printer_name);
+       tdb_store_int(tdb, keystr, (int)time(NULL));
+
        slprintf(tmp_file, sizeof(tmp_file), "%s/smblpq.%d", path, local_pid);
 
        unlink(tmp_file);
-       print_run_command(snum, cmd, tmp_file,
-                         NULL, NULL, NULL, NULL);
+       print_run_command(snum, cmd, tmp_file, NULL);
 
        numlines = 0;
-       qlines = file_lines_load(tmp_file, &numlines);
+       qlines = file_lines_load(tmp_file, &numlines, True);
        unlink(tmp_file);
 
        /* turn the lpq output into a series of job structures */
        qcount = 0;
        ZERO_STRUCT(status);
-       for (i=0; i<numlines; i++) {
-               queue = Realloc(queue,sizeof(print_queue_struct)*(qcount+1));
-               if (!queue) {
-                       qcount = 0;
-                       break;
-               }
-               /* parse the line */
-               if (parse_lpq_entry(snum,qlines[i],
-                                   &queue[qcount],&status,qcount==0)) {
-                       qcount++;
-               }
-       }               
+       if (numlines)
+               queue = (print_queue_struct *)malloc(sizeof(print_queue_struct)*(numlines+1));
+
+       if (queue) {
+               for (i=0; i<numlines; i++) {
+                       /* parse the line */
+                       if (parse_lpq_entry(snum,qlines[i],
+                                           &queue[qcount],&status,qcount==0)) {
+                               qcount++;
+                       }
+               }               
+       }
        file_lines_free(qlines);
 
+       DEBUG(3, ("%d job%s in queue for %s\n", qcount, (qcount != 1) ?
+               "s" : "", printer_name));
+
+       /* Lock the queue for the database update */
+
+       slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name);
+       tdb_lock_bystring(tdb, keystr);
+
        /*
          any job in the internal database that is marked as spooled
          and doesn't exist in the system queue is considered finished
@@ -362,16 +412,39 @@ static void print_queue_update(int snum)
 
        safe_free(tstruct.queue);
 
-       /* store the queue status structure */
-       slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
-       data.dptr = (void *)&status;
-       data.dsize = sizeof(status);
+       /*
+        * Get the old print status. We will use this to compare the
+        * number of jobs. If they have changed we need to send a
+        * "changed" message to the smbds.
+        */
+
+       if( qcount != get_queue_status(snum, &old_status)) {
+               DEBUG(10,("print_queue_update: queue status change %d jobs -> %d jobs for printer %s\n",
+                               old_status.qcount, qcount, printer_name ));
+               message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+       }
+
+       /* store the new queue status structure */
+       slprintf(keystr, sizeof(keystr), "STATUS/%s", printer_name);
        key.dptr = keystr;
        key.dsize = strlen(keystr);
+
+       status.qcount = qcount;
+       data.dptr = (void *)&status;
+       data.dsize = sizeof(status);
        tdb_store(tdb, key, data, TDB_REPLACE); 
 
-       /* update the cache time */
-       slprintf(keystr, sizeof(keystr), "CACHE/%s", lp_servicename(snum));
+       /* Unlock for database update */
+
+       slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name);
+       tdb_unlock_bystring(tdb, keystr);
+
+       /*
+        * Update the cache time again. We want to do this call
+        * as little as possible...
+        */
+
+       slprintf(keystr, sizeof(keystr), "CACHE/%s", printer_name);
        tdb_store_int(tdb, keystr, (int)time(NULL));
 }
 
@@ -463,25 +536,64 @@ static BOOL print_job_delete1(int jobid)
                print_run_command(snum, 
                                  lp_lprmcommand(snum), NULL,
                                  "%j", jobstr,
-                                 NULL, NULL);
+                                 "%T", http_timestring(pjob->starttime),
+                                 NULL);
        }
 
        return True;
 }
 
+/****************************************************************************
+return true if the current user owns the print job
+****************************************************************************/
+static BOOL is_owner(struct current_user *user, int jobid)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       user_struct *vuser;
+
+       if (!pjob || !user) return False;
+
+       if ((vuser = get_valid_user_struct(user->vuid)) != NULL) {
+               return strequal(pjob->user, vuser->user.smb_name);
+       } else {
+               return strequal(pjob->user, uidtoname(user->uid));
+       }
+}
+
 /****************************************************************************
 delete a print job
 ****************************************************************************/
-BOOL print_job_delete(int jobid)
+BOOL print_job_delete(struct current_user *user, int jobid, int *errcode)
 {
        int snum = print_job_snum(jobid);
+       char *printer_name;
+       BOOL owner;
+       
+       owner = is_owner(user, jobid);
+       
+       /* Check access against security descriptor or whether the user
+          owns their job. */
+
+       if (!owner && 
+           !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               DEBUG(3, ("delete denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
 
        if (!print_job_delete1(jobid)) return False;
 
        /* force update the database and say the delete failed if the
            job still exists */
+
        print_queue_update(snum);
 
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
        return !print_job_exists(jobid);
 }
 
@@ -489,56 +601,100 @@ BOOL print_job_delete(int jobid)
 /****************************************************************************
 pause a job
 ****************************************************************************/
-BOOL print_job_pause(int jobid)
+BOOL print_job_pause(struct current_user *user, int jobid, int *errcode)
 {
        struct printjob *pjob = print_job_find(jobid);
        int snum, ret = -1;
+       char *printer_name;
        fstring jobstr;
-       if (!pjob) return False;
+       BOOL owner;
+       
+       if (!pjob || !user) return False;
 
        if (!pjob->spooled || pjob->sysjob == -1) return False;
 
        snum = print_job_snum(jobid);
+       owner = is_owner(user, jobid);
+
+       if (!owner &&
+           !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               DEBUG(3, ("pause denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
 
        /* need to pause the spooled entry */
        slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
        ret = print_run_command(snum, 
                                lp_lppausecommand(snum), NULL,
                                "%j", jobstr,
-                               NULL, NULL);
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
 
        /* force update the database */
        print_cache_flush(snum);
 
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
        /* how do we tell if this succeeded? */
-       return ret == 0;
+
+       return True;
 }
 
 /****************************************************************************
 resume a job
 ****************************************************************************/
-BOOL print_job_resume(int jobid)
+BOOL print_job_resume(struct current_user *user, int jobid, int *errcode)
 {
        struct printjob *pjob = print_job_find(jobid);
+       char *printer_name;
        int snum, ret;
        fstring jobstr;
-       if (!pjob) return False;
+       BOOL owner;
+       
+       if (!pjob || !user) return False;
 
        if (!pjob->spooled || pjob->sysjob == -1) return False;
 
        snum = print_job_snum(jobid);
+       owner = is_owner(user, jobid);
+
+       if (!is_owner(user, jobid) &&
+           !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               DEBUG(3, ("resume denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
 
        slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
        ret = print_run_command(snum, 
                                lp_lpresumecommand(snum), NULL,
                                "%j", jobstr,
-                               NULL, NULL);
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
 
        /* force update the database */
        print_cache_flush(snum);
 
-       /* how do we tell if this succeeded? */
-       return ret == 0;
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
@@ -554,17 +710,84 @@ int print_job_write(int jobid, const char *buf, int size)
        return write(fd, buf, size);
 }
 
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static BOOL print_cache_expired(int snum)
+{
+       fstring key;
+       time_t t2, t = time(NULL);
+
+       slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum));
+       t2 = tdb_fetch_int(tdb, key);
+       if (t2 == ((time_t)-1) || (t - t2) >= lp_lpqcachetime()) {
+               DEBUG(3, ("print cache expired\n"));
+               return True;
+       }
+       return False;
+}
+
+/****************************************************************************
+ Get the queue status - do not update if db is out of date.
+****************************************************************************/
+
+static int get_queue_status(int snum, print_status_struct *status)
+{
+       fstring keystr;
+       TDB_DATA data, key;
+
+       ZERO_STRUCTP(status);
+       slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
+       key.dptr = keystr;
+       key.dsize = strlen(keystr);
+       data = tdb_fetch(tdb, key);
+       if (data.dptr) {
+               if (data.dsize == sizeof(print_status_struct)) {
+                       memcpy(status, data.dptr, sizeof(print_status_struct));
+               }
+               free(data.dptr);
+       }
+       return status->qcount;
+}
+
+/****************************************************************************
+ Determine the number of jobs in a queue.
+****************************************************************************/
+
+static int print_queue_length(int snum)
+{
+       print_status_struct status;
+
+       /* make sure the database is up to date */
+       if (print_cache_expired(snum)) print_queue_update(snum);
+
+       /* also fetch the queue status */
+       return get_queue_status(snum, &status);
+}
 
 /***************************************************************************
 start spooling a job - return the jobid
 ***************************************************************************/
-int print_job_start(int snum, char *jobname)
+int print_job_start(struct current_user *user, int snum, char *jobname)
 {
        int jobid;
        char *path;
        struct printjob pjob;
        int next_jobid;
-       extern struct current_user current_user;
+       user_struct *vuser;
+
+       errno = 0;
+
+       if (!print_access_check(user, snum, PRINTER_ACCESS_USE)) {
+               DEBUG(3, ("print_job_start: job start denied by security descriptor\n"));
+               return -1;
+       }
+
+       if (!print_time_access_check(snum)) {
+               DEBUG(3, ("print_job_start: job start denied by time check\n"));
+               return -1;
+       }
 
        path = lp_pathname(snum);
 
@@ -584,6 +807,11 @@ int print_job_start(int snum, char *jobname)
                return -1;
        }
 
+       if (lp_maxprintjobs(snum) && print_queue_length(snum) > lp_maxprintjobs(snum)) {
+               errno = ENOSPC;
+               return -1;
+       }
+
        /* create the database entry */
        ZERO_STRUCT(pjob);
        pjob.pid = local_pid;
@@ -596,13 +824,20 @@ int print_job_start(int snum, char *jobname)
        pjob.smbjob = True;
 
        fstrcpy(pjob.jobname, jobname);
-       fstrcpy(pjob.user, uidtoname(current_user.uid));
+
+       if ((vuser = get_valid_user_struct(user->vuid)) != NULL) {
+               fstrcpy(pjob.user, vuser->user.smb_name);
+       } else {
+               fstrcpy(pjob.user, uidtoname(user->uid));
+       }
+
        fstrcpy(pjob.qname, lp_servicename(snum));
 
        /* lock the database */
-       tdb_writelock(tdb);
+       tdb_lock_bystring(tdb, "INFO/nextjob");
 
-       next_jobid = tdb_get_int(tdb, "INFO/nextjob");
+ next_jobnum:
+       next_jobid = tdb_fetch_int(tdb, "INFO/nextjob");
        if (next_jobid == -1) next_jobid = 1;
 
        for (jobid = next_jobid+1; jobid != next_jobid; ) {
@@ -621,17 +856,20 @@ int print_job_start(int snum, char *jobname)
 
           we unlink first to cope with old spool files and also to beat
           a symlink security hole - it allows us to use O_EXCL 
+          There may be old spool files owned by other users lying around.
        */
        slprintf(pjob.filename, sizeof(pjob.filename), "%s/%s%d", 
                 path, PRINT_SPOOL_PREFIX, jobid);
        if (unlink(pjob.filename) == -1 && errno != ENOENT) {
-               goto fail;
+               goto next_jobnum;
        }
        pjob.fd = sys_open(pjob.filename,O_WRONLY|O_CREAT|O_EXCL,0600);
        if (pjob.fd == -1) goto fail;
 
        print_job_store(jobid, &pjob);
 
+       tdb_unlock_bystring(tdb, "INFO/nextjob");
+
        /*
         * If the printer is marked as postscript output a leading
         * file identifier to ensure the file is treated as a raw
@@ -643,7 +881,6 @@ int print_job_start(int snum, char *jobname)
                print_job_write(jobid, "%!\n",3);
        }
 
-       tdb_writeunlock(tdb);
        return jobid;
 
  fail:
@@ -651,13 +888,14 @@ int print_job_start(int snum, char *jobname)
                tdb_delete(tdb, print_key(jobid));
        }
 
-       tdb_writeunlock(tdb);
-       return jobid;
+       tdb_unlock_bystring(tdb, "INFO/nextjob");
+       return -1;
 }
 
 /****************************************************************************
-print a file - called on closing the file. This spools the job
+ Print a file - called on closing the file. This spools the job.
 ****************************************************************************/
+
 BOOL print_job_end(int jobid)
 {
        struct printjob *pjob = print_job_find(jobid);
@@ -665,15 +903,19 @@ BOOL print_job_end(int jobid)
        SMB_STRUCT_STAT sbuf;
        pstring current_directory;
        pstring print_directory;
-       char *wd, *p;
+       char *wd, *p, *printer_name;
+       pstring jobname;
 
-       if (!pjob) return False;
+       if (!pjob)
+               return False;
 
-       if (pjob->spooled || pjob->pid != local_pid) return False;
+       if (pjob->spooled || pjob->pid != local_pid)
+               return False;
 
        snum = print_job_snum(jobid);
 
-       if (sys_fstat(pjob->fd, &sbuf) == 0) pjob->size = sbuf.st_size;
+       if (sys_fstat(pjob->fd, &sbuf) == 0)
+               pjob->size = sbuf.st_size;
 
        close(pjob->fd);
        pjob->fd = -1;
@@ -688,20 +930,28 @@ BOOL print_job_end(int jobid)
        /* we print from the directory path to give the best chance of
            parsing the lpq output */
        wd = sys_getwd(current_directory);
-       if (!wd) return False;          
+       if (!wd)
+               return False;           
 
        pstrcpy(print_directory, pjob->filename);
        p = strrchr(print_directory,'/');
-       if (!p) return False;
+       if (!p)
+               return False;
        *p++ = 0;
 
-       if (chdir(print_directory) != 0) return False;
+       if (chdir(print_directory) != 0)
+               return False;
+
+       pstrcpy(jobname, pjob->jobname);
+       pstring_sub(jobname, "'", "_");
 
        /* send it to the system spooler */
        print_run_command(snum, 
                          lp_printcommand(snum), NULL,
                          "%s", p,
-                         "%f", p);
+                         "%J", jobname,
+                         "%f", p,
+                         NULL);
 
        chdir(wd);
 
@@ -711,23 +961,13 @@ BOOL print_job_end(int jobid)
        /* force update the database */
        print_cache_flush(snum);
        
-       return True;
-}
+       /* Send a printer notify message */
 
+       printer_name = PRINTERNAME(snum);
 
-/****************************************************************************
-check if the print queue has been updated recently enough
-****************************************************************************/
-static BOOL print_cache_expired(int snum)
-{
-       fstring key;
-       time_t t2, t = time(NULL);
-       slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum));
-       t2 = tdb_get_int(tdb, key);
-       if (t2 == ((time_t)-1) || (t - t2) >= lp_lpqcachetime()) {
-               return True;
-       }
-       return False;
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /* utility fn to enumerate the print queue */
@@ -744,14 +984,14 @@ static int traverse_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *
        /* maybe it isn't for this queue */
        if (ts->snum != print_queue_snum(pjob.qname)) return 0;
 
-       ts->queue = Realloc(ts->queue,sizeof(print_queue_struct)*(ts->qcount+1));
-       if (!ts->queue) return -1;
+       if (ts->qcount >= ts->maxcount) return 0;
+
        i = ts->qcount;
 
        ts->queue[i].job = jobid;
        ts->queue[i].size = pjob.size;
        ts->queue[i].status = pjob.status;
-       ts->queue[i].priority = 0;
+       ts->queue[i].priority = 1;
        ts->queue[i].time = pjob.starttime;
        fstrcpy(ts->queue[i].user, pjob.user);
        fstrcpy(ts->queue[i].file, pjob.jobname);
@@ -761,6 +1001,45 @@ static int traverse_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *
        return 0;
 }
 
+struct traverse_count_struct {
+       int snum, count;
+};
+
+/* utility fn to count the number of entries in the print queue */
+static int traverse_count_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+       struct traverse_count_struct *ts = (struct traverse_count_struct *)state;
+       struct printjob pjob;
+       int jobid;
+
+       if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+       memcpy(&jobid, key.dptr, sizeof(jobid));
+       memcpy(&pjob,  data.dptr, sizeof(pjob));
+
+       /* maybe it isn't for this queue */
+       if (ts->snum != print_queue_snum(pjob.qname)) return 0;
+
+       ts->count++;
+
+       return 0;
+}
+
+/* Sort print jobs by submittal time */
+
+static int printjob_comp(print_queue_struct *j1, print_queue_struct *j2)
+{
+       /* Silly cases */
+
+       if (!j1 && !j2) return 0;
+       if (!j1) return -1;
+       if (!j2) return 1;
+
+       /* Sort on job start time */
+
+       if (j1->time == j2->time) return 0;
+       return (j1->time > j2->time) ? 1 : -1;
+}
+
 /****************************************************************************
 get a printer queue listing
 ****************************************************************************/
@@ -769,20 +1048,19 @@ int print_queue_status(int snum,
                       print_status_struct *status)
 {
        struct traverse_struct tstruct;
+       struct traverse_count_struct tsc;
        fstring keystr;
        TDB_DATA data, key;
 
        /* make sure the database is up to date */
        if (print_cache_expired(snum)) print_queue_update(snum);
-       
-       /* fill in the queue */
-       tstruct.queue = NULL;
-       tstruct.qcount = 0;
-       tstruct.snum = snum;
-
-       tdb_traverse(tdb, traverse_fn_queue, (void *)&tstruct);
 
-       /* also fetch the queue status */
+       *queue = NULL;
+       
+       /*
+        * Fetch the queue status.  We must do this first, as there may
+        * be no jobs in the queue.
+        */
        ZERO_STRUCTP(status);
        slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
        key.dptr = keystr;
@@ -795,6 +1073,41 @@ int print_queue_status(int snum,
                free(data.dptr);
        }
 
+       /*
+        * Now, fetch the print queue information.  We first count the number
+        * of entries, and then only retrieve the queue if necessary.
+        */
+       tsc.count = 0;
+       tsc.snum = snum;
+       
+       tdb_traverse(tdb, traverse_count_fn_queue, (void *)&tsc);
+
+       if (tsc.count == 0)
+               return 0;
+
+       /* Allocate the queue size. */
+       if ((tstruct.queue = (print_queue_struct *)
+            malloc(sizeof(print_queue_struct)*tsc.count))
+                               == NULL)
+               return 0;
+
+       /*
+        * Fill in the queue.
+        * We need maxcount as the queue size may have changed between
+        * the two calls to tdb_traverse.
+        */
+       tstruct.qcount = 0;
+       tstruct.maxcount = tsc.count;
+       tstruct.snum = snum;
+
+       tdb_traverse(tdb, traverse_fn_queue, (void *)&tstruct);
+
+       /* Sort the queue by submission time otherwise they are displayed
+          in hash order. */
+
+       qsort(tstruct.queue, tstruct.qcount, sizeof(print_queue_struct),
+             QSORT_CAST(printjob_comp));
+
        *queue = tstruct.queue;
        return tstruct.qcount;
 }
@@ -814,52 +1127,96 @@ int print_queue_snum(char *qname)
 /****************************************************************************
  pause a queue
 ****************************************************************************/
-BOOL print_queue_pause(int snum)
+BOOL print_queue_pause(struct current_user *user, int snum, int *errcode)
 {
-       int ret = print_run_command(snum, 
-                                   lp_queuepausecommand(snum), NULL,
-                                   NULL, NULL,
-                                   NULL, NULL);
+       char *printer_name;
+       int ret;
+       
+       if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       ret = print_run_command(snum, lp_queuepausecommand(snum), NULL, 
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
 
        /* force update the database */
        print_cache_flush(snum);
 
-       return ret == 0;
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
  resume a queue
 ****************************************************************************/
-BOOL print_queue_resume(int snum)
+BOOL print_queue_resume(struct current_user *user, int snum, int *errcode)
 {
-       int ret = print_run_command(snum, 
-                                   lp_queueresumecommand(snum), NULL,
-                                   NULL, NULL,
-                                   NULL, NULL);
+       char *printer_name;
+       int ret;
+
+       if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       ret = print_run_command(snum, lp_queueresumecommand(snum), NULL, 
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
 
        /* force update the database */
        print_cache_flush(snum);
 
-       return ret == 0;
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
  purge a queue - implemented by deleting all jobs that we can delete
 ****************************************************************************/
-BOOL print_queue_purge(int snum)
+BOOL print_queue_purge(struct current_user *user, int snum, int *errcode)
 {
        print_queue_struct *queue;
        print_status_struct status;
+       char *printer_name;
        int njobs, i;
 
        njobs = print_queue_status(snum, &queue, &status);
        for (i=0;i<njobs;i++) {
-               print_job_delete1(queue[i].job);
+               if (print_access_check(user, snum, 
+                                      PRINTER_ACCESS_ADMINISTER)) {
+                       print_job_delete1(queue[i].job);
+               }
        }
 
        print_cache_flush(snum);
+       safe_free(queue);
 
-       return True;
-}
+       /* Send a printer notify message */
 
+       printer_name = PRINTERNAME(snum);
 
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
+}
+#undef OLD_NTDOMAIN