summaryrefslogtreecommitdiffstats
path: root/source3/printing
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /source3/printing
parentInitial commit. (diff)
downloadsamba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz
samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/printing')
-rw-r--r--source3/printing/load.c109
-rw-r--r--source3/printing/load.h28
-rw-r--r--source3/printing/lpq_parse.c1164
-rw-r--r--source3/printing/notify.c693
-rw-r--r--source3/printing/notify.h87
-rw-r--r--source3/printing/nt_printing.c2419
-rw-r--r--source3/printing/nt_printing_ads.c908
-rw-r--r--source3/printing/nt_printing_migrate.c386
-rw-r--r--source3/printing/nt_printing_migrate.h47
-rw-r--r--source3/printing/nt_printing_migrate_internal.c272
-rw-r--r--source3/printing/nt_printing_migrate_internal.h26
-rw-r--r--source3/printing/nt_printing_os2.c167
-rw-r--r--source3/printing/nt_printing_os2.h22
-rw-r--r--source3/printing/nt_printing_tdb.c498
-rw-r--r--source3/printing/nt_printing_tdb.h28
-rw-r--r--source3/printing/pcap.c213
-rw-r--r--source3/printing/pcap.h69
-rw-r--r--source3/printing/print_aix.c140
-rw-r--r--source3/printing/print_cups.c1749
-rw-r--r--source3/printing/print_generic.c358
-rw-r--r--source3/printing/print_iprint.c1367
-rw-r--r--source3/printing/print_standard.c157
-rw-r--r--source3/printing/print_svid.c148
-rw-r--r--source3/printing/printer_list.c460
-rw-r--r--source3/printing/printer_list.h105
-rw-r--r--source3/printing/printing.c3266
-rw-r--r--source3/printing/printing_db.c228
-rw-r--r--source3/printing/printspoolss.c405
-rw-r--r--source3/printing/queue_process.c451
-rw-r--r--source3/printing/queue_process.h44
-rw-r--r--source3/printing/rap_jobid.c164
-rw-r--r--source3/printing/rap_jobid.h29
-rw-r--r--source3/printing/samba-bgqd.c359
-rw-r--r--source3/printing/tests/README.vlp19
-rw-r--r--source3/printing/tests/vlp.c446
35 files changed, 17031 insertions, 0 deletions
diff --git a/source3/printing/load.c b/source3/printing/load.c
new file mode 100644
index 0000000..ea5154d
--- /dev/null
+++ b/source3/printing/load.c
@@ -0,0 +1,109 @@
+/*
+ Unix SMB/CIFS implementation.
+ load printer lists
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/load.h"
+#include "lib/param/loadparm.h"
+
+/***************************************************************************
+auto-load some homes and printer services
+***************************************************************************/
+static void add_auto_printers(void)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *p;
+ int pnum = lp_servicenumber(PRINTERS_NAME);
+ char *str;
+ char *saveptr;
+ char *auto_serv = NULL;
+
+ if (pnum < 0)
+ if (process_registry_service(PRINTERS_NAME))
+ pnum = lp_servicenumber(PRINTERS_NAME);
+
+ if (pnum < 0)
+ return;
+
+ auto_serv = lp_auto_services(talloc_tos(), lp_sub);
+ str = SMB_STRDUP(auto_serv);
+ TALLOC_FREE(auto_serv);
+ if (str == NULL) {
+ return;
+ }
+
+ for (p = strtok_r(str, LIST_SEP, &saveptr); p;
+ p = strtok_r(NULL, LIST_SEP, &saveptr)) {
+ if (lp_servicenumber(p) >= 0)
+ continue;
+
+ if (printer_list_printername_exists(p))
+ lp_add_printer(p, pnum);
+ }
+
+ SAFE_FREE(str);
+}
+
+/***************************************************************************
+load automatic printer services from pre-populated pcap cache
+***************************************************************************/
+void load_printers(void)
+{
+ NTSTATUS status;
+
+ if (!pcap_cache_loaded(NULL)) {
+ return;
+ }
+
+ add_auto_printers();
+
+ if (!lp_load_printers()) {
+ return;
+ }
+
+ /*
+ * Do not add printers from pcap, if we don't have a [printers] share.
+ */
+ if (lp_servicenumber(PRINTERS_NAME) < 0) {
+ return;
+ }
+
+ status = printer_list_read_run_fn(lp_add_one_printer, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("printer_list_read_run_fn failed: %s\n",
+ nt_errstr(status));
+ }
+}
+
+bool pcap_cache_loaded(time_t *_last_change)
+{
+ NTSTATUS status;
+ time_t last;
+
+ status = printer_list_get_last_refresh(&last);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ if (_last_change != NULL) {
+ *_last_change = last;
+ }
+ return true;
+}
diff --git a/source3/printing/load.h b/source3/printing/load.h
new file mode 100644
index 0000000..5a37769
--- /dev/null
+++ b/source3/printing/load.h
@@ -0,0 +1,28 @@
+/*
+ Unix SMB/CIFS implementation.
+ load printer lists
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PRINTING_LOAD_H_
+#define _PRINTING_LOAD_H_
+
+/* The following definitions come from printing/load.c */
+
+bool pcap_cache_loaded(time_t *_last_change);
+void load_printers(void);
+
+#endif /* _PRINTING_LOAD_H_ */
diff --git a/source3/printing/lpq_parse.c b/source3/printing/lpq_parse.c
new file mode 100644
index 0000000..335bc7f
--- /dev/null
+++ b/source3/printing/lpq_parse.c
@@ -0,0 +1,1164 @@
+/*
+ Unix SMB/CIFS implementation.
+ lpq parsing routines
+ Copyright (C) Andrew Tridgell 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "lib/util/string_wrappers.h"
+
+static const char *Months[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Err"};
+
+
+/*******************************************************************
+ Process time fields
+********************************************************************/
+
+static time_t EntryTime(char *tok[], int ptr, int count, int minimum)
+{
+ time_t jobtime,jobtime1;
+
+ jobtime = time(NULL); /* default case: take current time */
+ if (count >= minimum) {
+ struct tm *t;
+ int i, day, hour, min, sec;
+
+ for (i=0; i<13; i++) {
+ if (!strncmp(tok[ptr], Months[i],3)) {
+ break; /* Find month */
+ }
+ }
+
+ if (i<12) {
+ fstring c;
+ t = localtime(&jobtime);
+ if (!t) {
+ return (time_t)-1;
+ }
+ day = atoi(tok[ptr+1]);
+ fstrcpy(c,tok[ptr+2]);
+ *(c+2)=0;
+ hour = atoi(c);
+ *(c+5)=0;
+ min = atoi(c+3);
+ if(*(c+6) != 0) {
+ sec = atoi(c+6);
+ } else {
+ sec=0;
+ }
+
+ if ((t->tm_mon < i)|| ((t->tm_mon == i)&&
+ ((t->tm_mday < day)||
+ ((t->tm_mday == day)&&
+ (t->tm_hour*60+t->tm_min < hour*60+min))))) {
+ t->tm_year--; /* last year's print job */
+ }
+
+ t->tm_mon = i;
+ t->tm_mday = day;
+ t->tm_hour = hour;
+ t->tm_min = min;
+ t->tm_sec = sec;
+ jobtime1 = mktime(t);
+ if (jobtime1 != (time_t)-1) {
+ jobtime = jobtime1;
+ }
+ }
+ }
+ return jobtime;
+}
+
+/****************************************************************************
+parse a lpq line
+
+here is an example of lpq output under bsd
+
+Warning: no daemon present
+Rank Owner Job Files Total Size
+1st tridge 148 README 8096 bytes
+
+here is an example of lpq output under osf/1
+
+Warning: no daemon present
+Rank Pri Owner Job Files Total Size
+1st 0 tridge 148 README 8096 bytes
+
+
+<allan@umich.edu> June 30, 1998.
+Modified to handle file names with spaces, like the parse_lpq_lprng code
+further below.
+****************************************************************************/
+
+static bool parse_lpq_bsd(char *line,print_queue_struct *buf,bool first)
+{
+#ifdef OSF1
+#define RANKTOK 0
+#define PRIOTOK 1
+#define USERTOK 2
+#define JOBTOK 3
+#define FILETOK 4
+#define TOTALTOK (count - 2)
+#define NTOK 6
+#define MAXTOK 128
+#else /* OSF1 */
+#define RANKTOK 0
+#define USERTOK 1
+#define JOBTOK 2
+#define FILETOK 3
+#define TOTALTOK (count - 2)
+#define NTOK 5
+#define MAXTOK 128
+#endif /* OSF1 */
+
+ char *tok[MAXTOK];
+ int count = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *line2 = NULL;
+ char *saveptr;
+
+ line2 = talloc_strdup(ctx, line);
+ if (!line2) {
+ return false;
+ }
+
+#ifdef OSF1
+ {
+ size_t length;
+ length = strlen(line2);
+ if (line2[length-3] == ':') {
+ return False;
+ }
+ }
+#endif /* OSF1 */
+
+ /* FIXME: Use next_token_talloc rather than strtok! */
+ tok[0] = strtok_r(line2," \t", &saveptr);
+ count++;
+
+ while ((count < MAXTOK)
+ && ((tok[count] = strtok_r(NULL, " \t", &saveptr)) != NULL)) {
+ count++;
+ }
+
+ /* we must get at least NTOK tokens */
+ if (count < NTOK) {
+ return False;
+ }
+
+ /* the Job and Total columns must be integer */
+ if (!isdigit((int)*tok[JOBTOK]) || !isdigit((int)*tok[TOTALTOK])) {
+ return False;
+ }
+
+ buf->sysjob = atoi(tok[JOBTOK]);
+ buf->size = atoi(tok[TOTALTOK]);
+ buf->status = strequal(tok[RANKTOK],"active")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[USERTOK]);
+ fstrcpy(buf->fs_file,tok[FILETOK]);
+
+ if ((FILETOK + 1) != TOTALTOK) {
+ int i;
+
+ for (i = (FILETOK + 1); i < TOTALTOK; i++) {
+ /* FIXME: Using fstrcat rather than other means is a bit
+ * inefficient; this might be a problem for enormous queues with
+ * many fields. */
+ fstrcat(buf->fs_file, " ");
+ fstrcat(buf->fs_file, tok[i]);
+ }
+ /* Ensure null termination. */
+ buf->fs_file[sizeof(buf->fs_file)-1] = '\0';
+ }
+
+#ifdef PRIOTOK
+ buf->priority = atoi(tok[PRIOTOK]);
+#else
+ buf->priority = 1;
+#endif
+ return True;
+}
+
+/*
+<magnus@hum.auc.dk>
+LPRng_time modifies the current date by inserting the hour and minute from
+the lpq output. The lpq time looks like "23:15:07"
+
+<allan@umich.edu> June 30, 1998.
+Modified to work with the re-written parse_lpq_lprng routine.
+
+<J.P.M.v.Itegem@tue.nl> Dec 17,1999
+Modified to work with lprng 3.16
+With lprng 3.16 The lpq time looks like
+ "23:15:07"
+ "23:15:07.100"
+ "1999-12-16-23:15:07"
+ "1999-12-16-23:15:07.100"
+
+*/
+static time_t LPRng_time(char *time_string)
+{
+ time_t jobtime;
+ struct tm *t;
+
+ jobtime = time(NULL); /* default case: take current time */
+ t = localtime(&jobtime);
+ if (!t) {
+ return (time_t)-1;
+ }
+
+ if ( atoi(time_string) < 24 ){
+ if (strlen(time_string) < 7) {
+ return (time_t)-1;
+ }
+ t->tm_hour = atoi(time_string);
+ t->tm_min = atoi(time_string+3);
+ t->tm_sec = atoi(time_string+6);
+ } else {
+ if (strlen(time_string) < 18) {
+ return (time_t)-1;
+ }
+ t->tm_year = atoi(time_string)-1900;
+ t->tm_mon = atoi(time_string+5)-1;
+ t->tm_mday = atoi(time_string+8);
+ t->tm_hour = atoi(time_string+11);
+ t->tm_min = atoi(time_string+14);
+ t->tm_sec = atoi(time_string+17);
+ }
+ jobtime = mktime(t);
+
+ return jobtime;
+}
+
+/****************************************************************************
+ parse a lprng lpq line
+ <allan@umich.edu> June 30, 1998.
+ Re-wrote this to handle file names with spaces, multiple file names on one
+ lpq line, etc;
+
+****************************************************************************/
+
+static bool parse_lpq_lprng(char *line,print_queue_struct *buf,bool first)
+{
+#define LPRNG_RANKTOK 0
+#define LPRNG_USERTOK 1
+#define LPRNG_PRIOTOK 2
+#define LPRNG_JOBTOK 3
+#define LPRNG_FILETOK 4
+#define LPRNG_TOTALTOK (num_tok - 2)
+#define LPRNG_TIMETOK (num_tok - 1)
+#define LPRNG_NTOK 7
+#define LPRNG_MAXTOK 128 /* PFMA just to keep us from running away. */
+
+ char *tokarr[LPRNG_MAXTOK];
+ const char *cptr;
+ char *ptr;
+ int num_tok = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ cptr = line;
+ while((num_tok < LPRNG_MAXTOK) && next_token_talloc(frame, &cptr,
+ &tokarr[num_tok], " \t")) {
+ num_tok++;
+ }
+
+ /* We must get at least LPRNG_NTOK tokens. */
+ if (num_tok < LPRNG_NTOK) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ if (!isdigit((int)*tokarr[LPRNG_JOBTOK]) || !isdigit((int)*tokarr[LPRNG_TOTALTOK])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ buf->sysjob = atoi(tokarr[LPRNG_JOBTOK]);
+ buf->size = atoi(tokarr[LPRNG_TOTALTOK]);
+
+ if (strequal(tokarr[LPRNG_RANKTOK],"active")) {
+ buf->status = LPQ_PRINTING;
+ } else if (strequal(tokarr[LPRNG_RANKTOK],"done")) {
+ buf->status = LPQ_PRINTED;
+ } else if (isdigit((int)*tokarr[LPRNG_RANKTOK])) {
+ buf->status = LPQ_QUEUED;
+ } else {
+ buf->status = LPQ_PAUSED;
+ }
+
+ buf->priority = *tokarr[LPRNG_PRIOTOK] -'A';
+
+ buf->time = LPRng_time(tokarr[LPRNG_TIMETOK]);
+
+ fstrcpy(buf->fs_user,tokarr[LPRNG_USERTOK]);
+
+ /* The '@hostname' prevents windows from displaying the printing icon
+ * for the current user on the taskbar. Plop in a null.
+ */
+
+ if ((ptr = strchr_m(buf->fs_user,'@')) != NULL) {
+ *ptr = '\0';
+ }
+
+ fstrcpy(buf->fs_file,tokarr[LPRNG_FILETOK]);
+
+ if ((LPRNG_FILETOK + 1) != LPRNG_TOTALTOK) {
+ int i;
+
+ for (i = (LPRNG_FILETOK + 1); i < LPRNG_TOTALTOK; i++) {
+ /* FIXME: Using fstrcat rather than other means is a bit
+ * inefficient; this might be a problem for enormous queues with
+ * many fields. */
+ fstrcat(buf->fs_file, " ");
+ fstrcat(buf->fs_file, tokarr[i]);
+ }
+ /* Ensure null termination. */
+ buf->fs_file[sizeof(buf->fs_file)-1] = '\0';
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/*******************************************************************
+parse lpq on an aix system
+
+Queue Dev Status Job Files User PP % Blks Cp Rnk
+------- ----- --------- --- ------------------ ---------- ---- -- ----- --- ---
+lazer lazer READY
+lazer lazer RUNNING 537 6297doc.A kvintus@IE 0 10 2445 1 1
+ QUEUED 538 C.ps root@IEDVB 124 1 2
+ QUEUED 539 E.ps root@IEDVB 28 1 3
+ QUEUED 540 L.ps root@IEDVB 172 1 4
+ QUEUED 541 P.ps root@IEDVB 22 1 5
+********************************************************************/
+
+static bool parse_lpq_aix(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[11];
+ int count=0;
+ const char *cline = line;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* handle the case of "(standard input)" as a filename */
+ string_sub(line,"standard input","STDIN",0);
+ all_string_sub(line,"(","\"",0);
+ all_string_sub(line,")","\"",0);
+
+ for (count=0; count<10 &&
+ next_token_talloc(frame,&cline,&tok[count],NULL); count++) {
+ ;
+ }
+
+ /* we must get 6 tokens */
+ if (count < 10) {
+ if ((count == 7) && ((strcmp(tok[0],"QUEUED") == 0) || (strcmp(tok[0],"HELD") == 0))) {
+ /* the 2nd and 5th columns must be integer */
+ if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[4])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ buf->size = atoi(tok[4]) * 1024;
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[2],' ')) {
+ tok[2] = talloc_strdup(frame,"STDIN");
+ if (!tok[2]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ /* only take the last part of the filename */
+ {
+ char *p = strrchr_m(tok[2],'/');
+ if (p) {
+ tok[2] = p+1;
+ }
+ }
+
+ buf->sysjob = atoi(tok[1]);
+ buf->status = strequal(tok[0],"HELD")?LPQ_PAUSED:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[3]);
+ fstrcpy(buf->fs_file,tok[2]);
+ } else {
+ DEBUG(6,("parse_lpq_aix count=%d\n", count));
+ TALLOC_FREE(frame);
+ return False;
+ }
+ } else {
+ /* the 4th and 9th columns must be integer */
+ if (!isdigit((int)*tok[3]) || !isdigit((int)*tok[8])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ buf->size = atoi(tok[8]) * 1024;
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[4],' ')) {
+ tok[4] = talloc_strdup(frame,"STDIN");
+ if (!tok[4]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ /* only take the last part of the filename */
+ {
+ char *p = strrchr_m(tok[4],'/');
+ if (p) {
+ tok[4] = p+1;
+ }
+ }
+
+ buf->sysjob = atoi(tok[3]);
+ buf->status = strequal(tok[2],"RUNNING")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[5]);
+ fstrcpy(buf->fs_file,tok[4]);
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/****************************************************************************
+parse a lpq line
+here is an example of lpq output under hpux; note there's no space after -o !
+$> lpstat -oljplus
+ljplus-2153 user priority 0 Jan 19 08:14 on ljplus
+ util.c 125697 bytes
+ server.c 110712 bytes
+ljplus-2154 user priority 0 Jan 19 08:14 from client
+ (standard input) 7551 bytes
+****************************************************************************/
+
+static bool parse_lpq_hpux(char *line, print_queue_struct *buf, bool first)
+{
+ /* must read two lines to process, therefore keep some values static */
+ static bool header_line_ok=False, base_prio_reset=False;
+ static char *jobuser;
+ static int jobid;
+ static int jobprio;
+ static time_t jobtime;
+ static int jobstat=LPQ_QUEUED;
+ /* to store minimum priority to print, lpstat command should be invoked
+ with -p option first, to work */
+ static int base_prio;
+ int count;
+ char htab = '\011';
+ const char *cline = line;
+ char *tok[12];
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* If a line begins with a horizontal TAB, it is a subline type */
+
+ if (line[0] == htab) { /* subline */
+ /* check if it contains the base priority */
+ if (!strncmp(line,"\tfence priority : ",18)) {
+ base_prio=atoi(&line[18]);
+ DEBUG(4, ("fence priority set at %d\n", base_prio));
+ }
+
+ if (!header_line_ok) {
+ TALLOC_FREE(frame);
+ return False; /* incorrect header line */
+ }
+
+ /* handle the case of "(standard input)" as a filename */
+ string_sub(line,"standard input","STDIN",0);
+ all_string_sub(line,"(","\"",0);
+ all_string_sub(line,")","\"",0);
+
+ for (count=0; count<2 &&
+ next_token_talloc(frame, &cline, &tok[count],NULL);
+ count++) {
+ ;
+ }
+ /* we must get 2 tokens */
+ if (count < 2) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 2nd column must be integer */
+ if (!isdigit((int)*tok[1])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[0],' ')) {
+ tok[0] = talloc_strdup(frame, "STDIN");
+ if (!tok[0]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ buf->size = atoi(tok[1]);
+ fstrcpy(buf->fs_file,tok[0]);
+
+ /* fill things from header line */
+ buf->time = jobtime;
+ buf->sysjob = jobid;
+ buf->status = jobstat;
+ buf->priority = jobprio;
+ if (jobuser) {
+ fstrcpy(buf->fs_user,jobuser);
+ } else {
+ buf->fs_user[0] = '\0';
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+ } else { /* header line */
+ header_line_ok=False; /* reset it */
+ if (first) {
+ if (!base_prio_reset) {
+ base_prio=0; /* reset it */
+ base_prio_reset=True;
+ }
+ } else if (base_prio) {
+ base_prio_reset=False;
+ }
+
+ /* handle the dash in the job id */
+ string_sub(line,"-"," ",0);
+
+ for (count=0; count<12 &&
+ next_token_talloc(frame, &cline, &tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 8 tokens */
+ if (count < 8) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* first token must be printer name (cannot check ?) */
+ /* the 2nd, 5th & 7th column must be integer */
+ if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[4]) || !isdigit((int)*tok[6])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ jobid = atoi(tok[1]);
+ SAFE_FREE(jobuser);
+ jobuser = SMB_STRDUP(tok[2]);
+ jobprio = atoi(tok[4]);
+
+ /* process time */
+ jobtime=EntryTime(tok, 5, count, 8);
+ if (jobprio < base_prio) {
+ jobstat = LPQ_PAUSED;
+ DEBUG (4, ("job %d is paused: prio %d < %d; jobstat=%d\n",
+ jobid, jobprio, base_prio, jobstat));
+ } else {
+ jobstat = LPQ_QUEUED;
+ if ((count >8) && (((strequal(tok[8],"on")) ||
+ ((strequal(tok[8],"from")) &&
+ ((count > 10)&&(strequal(tok[10],"on"))))))) {
+ jobstat = LPQ_PRINTING;
+ }
+ }
+
+ header_line_ok=True; /* information is correct */
+ TALLOC_FREE(frame);
+ return False; /* need subline info to include into queuelist */
+ }
+}
+
+/****************************************************************************
+parse a lpstat line
+
+here is an example of "lpstat -o dcslw" output under sysv
+
+dcslw-896 tridge 4712 Dec 20 10:30:30 on dcslw
+dcslw-897 tridge 4712 Dec 20 10:30:30 being held
+
+****************************************************************************/
+
+static bool parse_lpq_sysv(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[9];
+ int count=0;
+ char *p;
+ const char *cline = line;
+ TALLOC_CTX *frame = NULL;
+
+ /*
+ * Handle the dash in the job id, but make sure that we skip over
+ * the printer name in case we have a dash in that.
+ * Patch from Dom.Mitchell@palmerharvey.co.uk.
+ */
+
+ /*
+ * Move to the first space.
+ */
+ for (p = line ; !isspace(*p) && *p; p++) {
+ ;
+ }
+
+ /*
+ * Back up until the last '-' character or
+ * start of line.
+ */
+ for (; (p >= line) && (*p != '-'); p--) {
+ ;
+ }
+
+ if((p >= line) && (*p == '-')) {
+ *p = ' ';
+ }
+
+ frame = talloc_stackframe();
+ for (count=0; count<9 &&
+ next_token_talloc(frame, &cline, &tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 7 tokens */
+ if (count < 7) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 2nd and 4th, 6th columns must be integer */
+ if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[3])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ if (!isdigit((int)*tok[5])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* if the user contains a ! then trim the first part of it */
+ if ((p=strchr_m(tok[2],'!'))) {
+ tok[2] = p+1;
+ }
+
+ buf->sysjob = atoi(tok[1]);
+ buf->size = atoi(tok[3]);
+ if (count > 7 && strequal(tok[7],"on")) {
+ buf->status = LPQ_PRINTING;
+ } else if (count > 8 && strequal(tok[7],"being") && strequal(tok[8],"held")) {
+ buf->status = LPQ_PAUSED;
+ } else {
+ buf->status = LPQ_QUEUED;
+ }
+ buf->priority = 0;
+ buf->time = EntryTime(tok, 4, count, 7);
+ fstrcpy(buf->fs_user,tok[2]);
+ fstrcpy(buf->fs_file,tok[2]);
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/****************************************************************************
+parse a lpq line
+
+here is an example of lpq output under qnx
+Spooler: /qnx/spooler, on node 1
+Printer: txt (ready)
+0000: root [job #1 ] active 1146 bytes /etc/profile
+0001: root [job #2 ] ready 2378 bytes /etc/install
+0002: root [job #3 ] ready 1146 bytes -- standard input --
+****************************************************************************/
+
+static bool parse_lpq_qnx(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[7];
+ int count=0;
+ const char *cline = line;
+ TALLOC_CTX *frame = NULL;
+
+ DEBUG(4,("antes [%s]\n", line));
+
+ /* handle the case of "-- standard input --" as a filename */
+ string_sub(line,"standard input","STDIN",0);
+ DEBUG(4,("despues [%s]\n", line));
+ all_string_sub(line,"-- ","\"",0);
+ all_string_sub(line," --","\"",0);
+ DEBUG(4,("despues 1 [%s]\n", line));
+
+ string_sub(line,"[job #","",0);
+ string_sub(line,"]","",0);
+ DEBUG(4,("despues 2 [%s]\n", line));
+
+ frame = talloc_stackframe();
+ for (count=0; count<7 &&
+ next_token_talloc(frame,&cline,&tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 7 tokens */
+ if (count < 7) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 3rd and 5th columns must be integer */
+ if (!isdigit((int)*tok[2]) || !isdigit((int)*tok[4])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* only take the last part of the filename */
+ {
+ char *p = strrchr_m(tok[6],'/');
+ if (p) {
+ tok[6] = p+1;
+ }
+ }
+
+ buf->sysjob = atoi(tok[2]);
+ buf->size = atoi(tok[4]);
+ buf->status = strequal(tok[3],"active")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[1]);
+ fstrcpy(buf->fs_file,tok[6]);
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/****************************************************************************
+ parse a lpq line for the plp printing system
+ Bertrand Wallrich <Bertrand.Wallrich@loria.fr>
+
+redone by tridge. Here is a sample queue:
+
+Local Printer 'lp2' (fjall):
+ Printing (started at Jun 15 13:33:58, attempt 1).
+ Rank Owner Pr Opt Job Host Files Size Date
+ active tridge X - 6 fjall /etc/hosts 739 Jun 15 13:33
+ 3rd tridge X - 7 fjall /etc/hosts 739 Jun 15 13:33
+
+****************************************************************************/
+
+static bool parse_lpq_plp(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[11];
+ int count=0;
+ const char *cline = line;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* handle the case of "(standard input)" as a filename */
+ string_sub(line,"stdin","STDIN",0);
+ all_string_sub(line,"(","\"",0);
+ all_string_sub(line,")","\"",0);
+
+ for (count=0; count<11 &&
+ next_token_talloc(frame,&cline,&tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 11 tokens */
+ if (count < 11) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the first must be "active" or begin with an integer */
+ if (strcmp(tok[0],"active") && !isdigit((int)tok[0][0])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 5th and 8th must be integer */
+ if (!isdigit((int)*tok[4]) || !isdigit((int)*tok[7])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[6],' ')) {
+ tok[6] = talloc_strdup(frame, "STDIN");
+ if (!tok[6]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ /* only take the last part of the filename */
+ {
+ fstring tmp;
+ char *p = strrchr_m(tok[6],'/');
+ if (p) {
+ size_t len = strlen(tok[6])+1;
+ fstrcpy(tmp,p+1);
+ strlcpy(tok[6],tmp, len);
+ }
+ }
+
+ buf->sysjob = atoi(tok[4]);
+
+ buf->size = atoi(tok[7]);
+ if (strchr_m(tok[7],'K')) {
+ buf->size *= 1024;
+ }
+ if (strchr_m(tok[7],'M')) {
+ buf->size *= 1024*1024;
+ }
+
+ buf->status = strequal(tok[0],"active")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[1]);
+ fstrcpy(buf->fs_file,tok[6]);
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/*******************************************************************
+parse lpq on an NT system
+
+ Windows 2000 LPD Server
+ Printer \\10.0.0.2\NP17PCL (Paused)
+
+Owner Status Jobname Job-Id Size Pages Priority
+----------------------------------------------------------------------------
+root (9.99. Printing /usr/lib/rhs/rhs-pr 3 625 0 1
+root (9.99. Paused /usr/lib/rhs/rhs-pr 4 625 0 1
+jmcd Waiting Re: Samba Open Sour 26 32476 1 1
+
+********************************************************************/
+
+static bool parse_lpq_nt(char *line,print_queue_struct *buf,bool first)
+{
+#define LPRNT_OWNSIZ 11
+#define LPRNT_STATSIZ 9
+#define LPRNT_JOBSIZ 19
+#define LPRNT_IDSIZ 6
+#define LPRNT_SIZSIZ 9
+ typedef struct {
+ char owner[LPRNT_OWNSIZ];
+ char space1;
+ char status[LPRNT_STATSIZ];
+ char space2;
+ char jobname[LPRNT_JOBSIZ];
+ char space3;
+ char jobid[LPRNT_IDSIZ];
+ char space4;
+ char size[LPRNT_SIZSIZ];
+ char terminator;
+ } nt_lpq_line;
+
+ char parse_line_char[sizeof(nt_lpq_line)];
+ nt_lpq_line *parse_line = (nt_lpq_line *)parse_line_char;
+#define LPRNT_PRINTING "Printing"
+#define LPRNT_WAITING "Waiting"
+#define LPRNT_PAUSED "Paused"
+
+ memset(parse_line_char, '\0', sizeof(parse_line_char));
+ strncpy(parse_line_char, line, sizeof(parse_line_char) -1);
+
+ if (strlen(parse_line_char) != sizeof(parse_line_char) - 1) {
+ return False;
+ }
+
+ /* Just want the first word in the owner field - the username */
+ if (strchr_m(parse_line->owner, ' ')) {
+ *(strchr_m(parse_line->owner, ' ')) = '\0';
+ } else {
+ parse_line->space1 = '\0';
+ }
+
+ /* Make sure we have an owner */
+ if (!strlen(parse_line->owner)) {
+ return False;
+ }
+
+ /* Make sure the status is valid */
+ parse_line->space2 = '\0';
+ trim_char(parse_line->status, '\0', ' ');
+ if (!strequal(parse_line->status, LPRNT_PRINTING) &&
+ !strequal(parse_line->status, LPRNT_PAUSED) &&
+ !strequal(parse_line->status, LPRNT_WAITING)) {
+ return False;
+ }
+
+ parse_line->space3 = '\0';
+ trim_char(parse_line->jobname, '\0', ' ');
+
+ buf->sysjob = atoi(parse_line->jobid);
+ buf->priority = 0;
+ buf->size = atoi(parse_line->size);
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user, parse_line->owner);
+ fstrcpy(buf->fs_file, parse_line->jobname);
+ if (strequal(parse_line->status, LPRNT_PRINTING)) {
+ buf->status = LPQ_PRINTING;
+ } else if (strequal(parse_line->status, LPRNT_PAUSED)) {
+ buf->status = LPQ_PAUSED;
+ } else {
+ buf->status = LPQ_QUEUED;
+ }
+
+ return True;
+}
+
+/*******************************************************************
+parse lpq on an OS2 system
+
+JobID File Name Rank Size Status Comment
+----- --------------- ------ -------- ------------ ------------
+ 3 Control 1 68 Queued root@psflinu
+ 4 /etc/motd 2 11666 Queued root@psflinu
+
+********************************************************************/
+
+static bool parse_lpq_os2(char *line,print_queue_struct *buf,bool first)
+{
+#define LPROS2_IDSIZ 5
+#define LPROS2_JOBSIZ 15
+#define LPROS2_SIZSIZ 8
+#define LPROS2_STATSIZ 12
+#define LPROS2_OWNSIZ 12
+ typedef struct {
+ char jobid[LPROS2_IDSIZ];
+ char space1[2];
+ char jobname[LPROS2_JOBSIZ];
+ char space2[14];
+ char size[LPROS2_SIZSIZ];
+ char space3[4];
+ char status[LPROS2_STATSIZ];
+ char space4[4];
+ char owner[LPROS2_OWNSIZ];
+ char terminator;
+ } os2_lpq_line;
+
+ char parse_line_char[sizeof(os2_lpq_line)];
+ os2_lpq_line *parse_line = (os2_lpq_line *)parse_line_char;
+#define LPROS2_PRINTING "Printing"
+#define LPROS2_WAITING "Queued"
+#define LPROS2_PAUSED "Paused"
+
+ memset(parse_line_char, '\0', sizeof(parse_line_char));
+ strncpy(parse_line_char, line, sizeof(parse_line_char) -1);
+
+ if (strlen(parse_line_char) != sizeof(parse_line_char) - 1) {
+ return False;
+ }
+
+ /* Get the jobid */
+ buf->sysjob = atoi(parse_line->jobid);
+
+ /* Get the job name */
+ parse_line->space2[0] = '\0';
+ trim_char(parse_line->jobname, '\0', ' ');
+ fstrcpy(buf->fs_file, parse_line->jobname);
+
+ buf->priority = 0;
+ buf->size = atoi(parse_line->size);
+ buf->time = time(NULL);
+
+ /* Make sure we have an owner */
+ if (!strlen(parse_line->owner)) {
+ return False;
+ }
+
+ /* Make sure we have a valid status */
+ parse_line->space4[0] = '\0';
+ trim_char(parse_line->status, '\0', ' ');
+ if (!strequal(parse_line->status, LPROS2_PRINTING) &&
+ !strequal(parse_line->status, LPROS2_PAUSED) &&
+ !strequal(parse_line->status, LPROS2_WAITING)) {
+ return False;
+ }
+
+ fstrcpy(buf->fs_user, parse_line->owner);
+ if (strequal(parse_line->status, LPROS2_PRINTING)) {
+ buf->status = LPQ_PRINTING;
+ } else if (strequal(parse_line->status, LPROS2_PAUSED)) {
+ buf->status = LPQ_PAUSED;
+ } else {
+ buf->status = LPQ_QUEUED;
+ }
+
+ return True;
+}
+
+static const char *stat0_strings[] = { "enabled", "online", "idle", "no entries", "free", "ready", NULL };
+static const char *stat1_strings[] = { "offline", "disabled", "down", "off", "waiting", "no daemon", NULL };
+static const char *stat2_strings[] = { "jam", "paper", "error", "responding", "not accepting", "not running", "turned off", NULL };
+
+#ifdef DEVELOPER
+
+/****************************************************************************
+parse a vlp line
+****************************************************************************/
+
+static bool parse_lpq_vlp(char *line,print_queue_struct *buf,bool first)
+{
+ int toknum = 0;
+ char *tok;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *cline = line;
+
+ /* First line is printer status */
+
+ if (!isdigit(line[0])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* Parse a print job entry */
+
+ while(next_token_talloc(frame, &cline, &tok, NULL)) {
+ switch (toknum) {
+ case 0:
+ buf->sysjob = atoi(tok);
+ break;
+ case 1:
+ buf->size = atoi(tok);
+ break;
+ case 2:
+ buf->status = atoi(tok);
+ break;
+ case 3:
+ buf->time = atoi(tok);
+ break;
+ case 4:
+ fstrcpy(buf->fs_user, tok);
+ break;
+ case 5:
+ fstrcpy(buf->fs_file, tok);
+ break;
+ }
+ toknum++;
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+#endif /* DEVELOPER */
+
+/****************************************************************************
+parse a lpq line. Choose printing style
+****************************************************************************/
+
+bool parse_lpq_entry(enum printing_types printing_type,char *line,
+ print_queue_struct *buf,
+ print_status_struct *status,bool first)
+{
+ bool ret;
+
+ switch (printing_type) {
+ case PRINT_SYSV:
+ ret = parse_lpq_sysv(line,buf,first);
+ break;
+ case PRINT_AIX:
+ ret = parse_lpq_aix(line,buf,first);
+ break;
+ case PRINT_HPUX:
+ ret = parse_lpq_hpux(line,buf,first);
+ break;
+ case PRINT_QNX:
+ ret = parse_lpq_qnx(line,buf,first);
+ break;
+ case PRINT_LPRNG:
+ ret = parse_lpq_lprng(line,buf,first);
+ break;
+ case PRINT_PLP:
+ ret = parse_lpq_plp(line,buf,first);
+ break;
+ case PRINT_LPRNT:
+ ret = parse_lpq_nt(line,buf,first);
+ break;
+ case PRINT_LPROS2:
+ ret = parse_lpq_os2(line,buf,first);
+ break;
+#ifdef DEVELOPER
+ case PRINT_VLP:
+ case PRINT_TEST:
+ ret = parse_lpq_vlp(line,buf,first);
+ break;
+#endif /* DEVELOPER */
+ default:
+ ret = parse_lpq_bsd(line,buf,first);
+ break;
+ }
+
+ /* We don't want the newline in the status message. */
+ {
+ char *p = strchr_m(line,'\n');
+ if (p) {
+ *p = 0;
+ }
+ }
+
+ /* in the LPRNG case, we skip lines starting by a space.*/
+ if (!ret && (printing_type==PRINT_LPRNG) ) {
+ if (line[0]==' ') {
+ return ret;
+ }
+ }
+
+ if (status && !ret) {
+ /* a few simple checks to see if the line might be a
+ printer status line:
+ handle them so that most severe condition is shown */
+ int i;
+ if (!strlower_m(line)) {
+ return false;
+ }
+
+ switch (status->status) {
+ case LPSTAT_OK:
+ for (i=0; stat0_strings[i]; i++) {
+ if (strstr_m(line,stat0_strings[i])) {
+ fstrcpy(status->message,line);
+ status->status=LPSTAT_OK;
+ return ret;
+ }
+ }
+ FALL_THROUGH;
+ case LPSTAT_STOPPED:
+ for (i=0; stat1_strings[i]; i++) {
+ if (strstr_m(line,stat1_strings[i])) {
+ fstrcpy(status->message,line);
+ status->status=LPSTAT_STOPPED;
+ return ret;
+ }
+ }
+ FALL_THROUGH;
+ case LPSTAT_ERROR:
+ for (i=0; stat2_strings[i]; i++) {
+ if (strstr_m(line,stat2_strings[i])) {
+ fstrcpy(status->message,line);
+ status->status=LPSTAT_ERROR;
+ return ret;
+ }
+ }
+ break;
+ }
+ }
+
+ return ret;
+}
+
diff --git a/source3/printing/notify.c b/source3/printing/notify.c
new file mode 100644
index 0000000..8312b0b
--- /dev/null
+++ b/source3/printing/notify.c
@@ -0,0 +1,693 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Tim Potter, 2002
+ Copyright (C) Gerald Carter, 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "../librpc/gen_ndr/spoolss.h"
+#include "nt_printing.h"
+#include "printing/notify.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+static TALLOC_CTX *send_ctx;
+
+static unsigned int num_messages;
+
+static struct notify_queue {
+ struct notify_queue *next, *prev;
+ struct spoolss_notify_msg *msg;
+ struct timeval tv;
+ uint8_t *buf;
+ size_t buflen;
+} *notify_queue_head = NULL;
+
+static struct tevent_timer *notify_event;
+
+static bool print_notify_pid_list(const char *printername, TALLOC_CTX *mem_ctx,
+ size_t *p_num_pids, pid_t **pp_pid_list);
+
+static bool create_send_ctx(void)
+{
+ if (!send_ctx)
+ send_ctx = talloc_init("print notify queue");
+
+ if (!send_ctx)
+ return False;
+
+ return True;
+}
+
+/****************************************************************************
+ Turn a queue name into a snum.
+****************************************************************************/
+
+int print_queue_snum(const char *qname)
+{
+ int snum = lp_servicenumber(qname);
+ if (snum == -1 || !lp_printable(snum))
+ return -1;
+ return snum;
+}
+
+/*******************************************************************
+ Used to decide if we need a short select timeout.
+*******************************************************************/
+
+static bool print_notify_messages_pending(void)
+{
+ return (notify_queue_head != NULL);
+}
+
+/*******************************************************************
+ Flatten data into a message.
+*******************************************************************/
+
+static bool flatten_message(struct notify_queue *q)
+{
+ struct spoolss_notify_msg *msg = q->msg;
+ uint8_t *buf = NULL;
+ size_t buflen = 0, len;
+
+again:
+ len = 0;
+
+ /* Pack header */
+
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "f", msg->printer);
+
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "ddddddd",
+ (uint32_t)q->tv.tv_sec, (uint32_t)q->tv.tv_usec,
+ msg->type, msg->field, msg->id, msg->len, msg->flags);
+
+ /* Pack data */
+
+ if (msg->len == 0)
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "dd",
+ msg->notify.value[0], msg->notify.value[1]);
+ else
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "B",
+ msg->len, msg->notify.data);
+
+ if (buflen != len) {
+ buf = (uint8_t *)TALLOC_REALLOC(send_ctx, buf, len);
+ if (!buf)
+ return False;
+ buflen = len;
+ goto again;
+ }
+
+ q->buf = buf;
+ q->buflen = buflen;
+
+ return True;
+}
+
+/*******************************************************************
+ Send the batched messages - on a per-printer basis.
+*******************************************************************/
+
+static void print_notify_send_messages_to_printer(struct messaging_context *msg_ctx,
+ const char *printer,
+ unsigned int timeout)
+{
+ char *buf;
+ struct notify_queue *pq, *pq_next;
+ size_t msg_count = 0, offset = 0;
+ size_t num_pids = 0;
+ size_t i;
+ pid_t *pid_list = NULL;
+ struct timeval end_time = timeval_zero();
+
+ /* Count the space needed to send the messages. */
+ for (pq = notify_queue_head; pq; pq = pq->next) {
+ if (strequal(printer, pq->msg->printer)) {
+ if (!flatten_message(pq)) {
+ DEBUG(0,("print_notify_send_messages: Out of memory\n"));
+ talloc_free_children(send_ctx);
+ num_messages = 0;
+ return;
+ }
+ offset += (pq->buflen + 4);
+ msg_count++;
+ }
+ }
+ offset += 4; /* For count. */
+
+ buf = (char *)TALLOC(send_ctx, offset);
+ if (!buf) {
+ DEBUG(0,("print_notify_send_messages: Out of memory\n"));
+ talloc_free_children(send_ctx);
+ num_messages = 0;
+ return;
+ }
+
+ offset = 0;
+ SIVAL(buf,offset,msg_count);
+ offset += 4;
+ for (pq = notify_queue_head; pq; pq = pq_next) {
+ pq_next = pq->next;
+
+ if (strequal(printer, pq->msg->printer)) {
+ SIVAL(buf,offset,pq->buflen);
+ offset += 4;
+ memcpy(buf + offset, pq->buf, pq->buflen);
+ offset += pq->buflen;
+
+ /* Remove from list. */
+ DLIST_REMOVE(notify_queue_head, pq);
+ }
+ }
+
+ DEBUG(5, ("print_notify_send_messages_to_printer: sending %lu print notify message%s to printer %s\n",
+ (unsigned long)msg_count, msg_count != 1 ? "s" : "", printer));
+
+ /*
+ * Get the list of PID's to send to.
+ */
+
+ if (!print_notify_pid_list(printer, send_ctx, &num_pids, &pid_list))
+ return;
+
+ if (timeout != 0) {
+ end_time = timeval_current_ofs(timeout, 0);
+ }
+
+ for (i = 0; i < num_pids; i++) {
+ messaging_send_buf(msg_ctx,
+ pid_to_procid(pid_list[i]),
+ MSG_PRINTER_NOTIFY2 | MSG_FLAG_LOWPRIORITY,
+ (uint8_t *)buf, offset);
+
+ if ((timeout != 0) && timeval_expired(&end_time)) {
+ break;
+ }
+ }
+}
+
+/*******************************************************************
+ Actually send the batched messages.
+*******************************************************************/
+
+void print_notify_send_messages(struct messaging_context *msg_ctx,
+ unsigned int timeout)
+{
+ if (!print_notify_messages_pending())
+ return;
+
+ if (!create_send_ctx())
+ return;
+
+ while (print_notify_messages_pending())
+ print_notify_send_messages_to_printer(
+ msg_ctx, notify_queue_head->msg->printer, timeout);
+
+ talloc_free_children(send_ctx);
+ num_messages = 0;
+}
+
+/*******************************************************************
+ Event handler to send the messages.
+*******************************************************************/
+
+static void print_notify_event_send_messages(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct messaging_context *msg_ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+ /* Remove this timed event handler. */
+ TALLOC_FREE(notify_event);
+
+ change_to_root_user();
+ print_notify_send_messages(msg_ctx, 0);
+}
+
+/**********************************************************************
+ deep copy a SPOOLSS_NOTIFY_MSG structure using a TALLOC_CTX
+ *********************************************************************/
+
+static bool copy_notify2_msg( SPOOLSS_NOTIFY_MSG *to, SPOOLSS_NOTIFY_MSG *from )
+{
+
+ if ( !to || !from )
+ return False;
+
+ memcpy( to, from, sizeof(SPOOLSS_NOTIFY_MSG) );
+
+ if ( from->len ) {
+ to->notify.data = (char *)talloc_memdup(send_ctx, from->notify.data, from->len );
+ if ( !to->notify.data ) {
+ DEBUG(0,("copy_notify2_msg: talloc_memdup() of size [%d] failed!\n", from->len ));
+ return False;
+ }
+ }
+
+
+ return True;
+}
+
+/*******************************************************************
+ Batch up print notify messages.
+*******************************************************************/
+
+static void send_spoolss_notify2_msg(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ SPOOLSS_NOTIFY_MSG *msg)
+{
+ struct notify_queue *pnqueue, *tmp_ptr;
+
+ /*
+ * Ensure we only have one job total_bytes and job total_pages for
+ * each job. There is no point in sending multiple messages that match
+ * as they will just cause flickering updates in the client.
+ */
+
+ if ((num_messages < 100) && (msg->type == JOB_NOTIFY_TYPE)
+ && (msg->field == JOB_NOTIFY_FIELD_TOTAL_BYTES
+ || msg->field == JOB_NOTIFY_FIELD_TOTAL_PAGES ))
+ {
+
+ for (tmp_ptr = notify_queue_head; tmp_ptr; tmp_ptr = tmp_ptr->next)
+ {
+ if (tmp_ptr->msg->type == msg->type &&
+ tmp_ptr->msg->field == msg->field &&
+ tmp_ptr->msg->id == msg->id &&
+ tmp_ptr->msg->flags == msg->flags &&
+ strequal(tmp_ptr->msg->printer, msg->printer)) {
+
+ DEBUG(5,("send_spoolss_notify2_msg: replacing message 0x%02x/0x%02x for "
+ "printer %s in notify_queue\n", msg->type, msg->field, msg->printer));
+
+ tmp_ptr->msg = msg;
+ return;
+ }
+ }
+ }
+
+ /* Store the message on the pending queue. */
+
+ pnqueue = talloc(send_ctx, struct notify_queue);
+ if (!pnqueue) {
+ DEBUG(0,("send_spoolss_notify2_msg: Out of memory.\n"));
+ return;
+ }
+
+ /* allocate a new msg structure and copy the fields */
+
+ if ( !(pnqueue->msg = talloc(send_ctx, SPOOLSS_NOTIFY_MSG)) ) {
+ DEBUG(0,("send_spoolss_notify2_msg: talloc() of size [%lu] failed!\n",
+ (unsigned long)sizeof(SPOOLSS_NOTIFY_MSG)));
+ return;
+ }
+ copy_notify2_msg(pnqueue->msg, msg);
+ GetTimeOfDay(&pnqueue->tv);
+ pnqueue->buf = NULL;
+ pnqueue->buflen = 0;
+
+ DEBUG(5, ("send_spoolss_notify2_msg: appending message 0x%02x/0x%02x for printer %s \
+to notify_queue_head\n", msg->type, msg->field, msg->printer));
+
+ /*
+ * Note we add to the end of the list to ensure
+ * the messages are sent in the order they were received. JRA.
+ */
+
+ DLIST_ADD_END(notify_queue_head, pnqueue);
+ num_messages++;
+
+ if ((notify_event == NULL) && (ev != NULL)) {
+ /* Add an event for 1 second's time to send this queue. */
+ notify_event = tevent_add_timer(
+ ev, NULL, timeval_current_ofs(1,0),
+ print_notify_event_send_messages, msg_ctx);
+ }
+
+}
+
+static void send_notify_field_values(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t type,
+ uint32_t field, uint32_t id, uint32_t value1,
+ uint32_t value2, uint32_t flags)
+{
+ struct spoolss_notify_msg *msg;
+
+ if (lp_disable_spoolss())
+ return;
+
+ if (!create_send_ctx())
+ return;
+
+ msg = talloc_zero(send_ctx, struct spoolss_notify_msg);
+ if (!msg)
+ return;
+
+ fstrcpy(msg->printer, sharename);
+ msg->type = type;
+ msg->field = field;
+ msg->id = id;
+ msg->notify.value[0] = value1;
+ msg->notify.value[1] = value2;
+ msg->flags = flags;
+
+ send_spoolss_notify2_msg(ev, msg_ctx, msg);
+}
+
+static void send_notify_field_buffer(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t type,
+ uint32_t field, uint32_t id, uint32_t len,
+ const char *buffer)
+{
+ struct spoolss_notify_msg *msg;
+
+ if (lp_disable_spoolss())
+ return;
+
+ if (!create_send_ctx())
+ return;
+
+ msg = talloc_zero(send_ctx, struct spoolss_notify_msg);
+ if (!msg)
+ return;
+
+ fstrcpy(msg->printer, sharename);
+ msg->type = type;
+ msg->field = field;
+ msg->id = id;
+ msg->len = len;
+ msg->notify.data = discard_const_p(char, buffer);
+
+ send_spoolss_notify2_msg(ev, msg_ctx, msg);
+}
+
+/* Send a message that the printer status has changed */
+
+void notify_printer_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t status)
+{
+ /* Printer status stored in value1 */
+
+ int snum = print_queue_snum(sharename);
+
+ send_notify_field_values(ev, msg_ctx, sharename, PRINTER_NOTIFY_TYPE,
+ PRINTER_NOTIFY_FIELD_STATUS, snum,
+ status, 0, 0);
+}
+
+void notify_printer_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t status)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ if (sharename)
+ notify_printer_status_byname(ev, msg_ctx, sharename, status);
+}
+
+void notify_job_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t status,
+ uint32_t flags)
+{
+ /* Job id stored in id field, status in value1 */
+
+ send_notify_field_values(ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE,
+ JOB_NOTIFY_FIELD_STATUS, jobid,
+ status, 0, flags);
+}
+
+void notify_job_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, uint32_t status)
+{
+ notify_job_status_byname(ev, msg_ctx, sharename, jobid, status, 0);
+}
+
+void notify_job_total_bytes(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t size)
+{
+ /* Job id stored in id field, status in value1 */
+
+ send_notify_field_values(ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE,
+ JOB_NOTIFY_FIELD_TOTAL_BYTES, jobid,
+ size, 0, 0);
+}
+
+void notify_job_total_pages(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t pages)
+{
+ /* Job id stored in id field, status in value1 */
+
+ send_notify_field_values(ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE,
+ JOB_NOTIFY_FIELD_TOTAL_PAGES, jobid,
+ pages, 0, 0);
+}
+
+void notify_job_username(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name)
+{
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_USER_NAME,
+ jobid, strlen(name) + 1, name);
+}
+
+void notify_job_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name)
+{
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DOCUMENT,
+ jobid, strlen(name) + 1, name);
+}
+
+void notify_job_submitted(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ time_t submitted)
+{
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_SUBMITTED,
+ jobid, sizeof(submitted), (char *)&submitted);
+}
+
+void notify_printer_driver(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *driver_name)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DRIVER_NAME,
+ snum, strlen(driver_name) + 1, driver_name);
+}
+
+void notify_printer_comment(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *comment)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_COMMENT,
+ snum, strlen(comment) + 1, comment);
+}
+
+void notify_printer_sharename(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *share_name)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SHARE_NAME,
+ snum, strlen(share_name) + 1, share_name);
+}
+
+void notify_printer_printername(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *printername)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRINTER_NAME,
+ snum, strlen(printername) + 1, printername);
+}
+
+void notify_printer_port(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *port_name)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PORT_NAME,
+ snum, strlen(port_name) + 1, port_name);
+}
+
+void notify_printer_location(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *location)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_LOCATION,
+ snum, strlen(location) + 1, location);
+}
+
+void notify_printer_sepfile(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *sepfile)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SEPFILE,
+ snum, strlen(sepfile) + 1, sepfile);
+}
+
+
+void notify_printer_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *printername, uint32_t change,
+ const char *value)
+{
+ int snum = print_queue_snum(printername);
+ int type = PRINTER_NOTIFY_TYPE;
+
+ if ( snum == -1 )
+ return;
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ printername, type, change, snum, strlen(value)+1, value );
+}
+
+
+/****************************************************************************
+ Return a malloced list of pid_t's that are interested in getting update
+ messages on this print queue. Used in printing/notify to send the messages.
+****************************************************************************/
+
+static bool print_notify_pid_list(const char *printername, TALLOC_CTX *mem_ctx,
+ size_t *p_num_pids, pid_t **pp_pid_list)
+{
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ TDB_DATA data;
+ bool ret = True;
+ size_t i, num_pids, offset;
+ pid_t *pid_list;
+
+ *p_num_pids = 0;
+ *pp_pid_list = NULL;
+
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+
+ if (tdb_read_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_pid_list: Failed to lock printer %s database\n",
+ printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ if (!data.dptr) {
+ ret = True;
+ goto done;
+ }
+
+ num_pids = data.dsize / 8;
+
+ if (num_pids) {
+ if ((pid_list = talloc_array(mem_ctx, pid_t, num_pids)) == NULL) {
+ ret = False;
+ goto done;
+ }
+ } else {
+ pid_list = NULL;
+ }
+
+ for( i = 0, offset = 0; i < num_pids; offset += 8, i++)
+ pid_list[i] = (pid_t)IVAL(data.dptr, offset);
+
+ *pp_pid_list = pid_list;
+ *p_num_pids = num_pids;
+
+ ret = True;
+
+ done:
+
+ tdb_read_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
diff --git a/source3/printing/notify.h b/source3/printing/notify.h
new file mode 100644
index 0000000..427bbf0
--- /dev/null
+++ b/source3/printing/notify.h
@@ -0,0 +1,87 @@
+#ifndef _PRINTING_NOTIFY_H_
+#define _PRINTING_NOTIFY_H_
+
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Tim Potter, 2002
+ Copyright (C) Gerald Carter, 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* The following definitions come from printing/notify.c */
+
+int print_queue_snum(const char *qname);
+void print_notify_send_messages(struct messaging_context *msg_ctx,
+ unsigned int timeout);
+void notify_printer_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t status);
+void notify_printer_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t status);
+void notify_job_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t status,
+ uint32_t flags);
+void notify_job_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, uint32_t status);
+void notify_job_total_bytes(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t size);
+void notify_job_total_pages(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t pages);
+void notify_job_username(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name);
+void notify_job_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name);
+void notify_job_submitted(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ time_t submitted);
+void notify_printer_driver(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *driver_name);
+void notify_printer_comment(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *comment);
+void notify_printer_sharename(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *share_name);
+void notify_printer_printername(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *printername);
+void notify_printer_port(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *port_name);
+void notify_printer_location(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *location);
+void notify_printer_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *printername, uint32_t change,
+ const char *value);
+void notify_printer_sepfile(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *sepfile);
+#endif
diff --git a/source3/printing/nt_printing.c b/source3/printing/nt_printing.c
new file mode 100644
index 0000000..4b4d12f
--- /dev/null
+++ b/source3/printing/nt_printing.c
@@ -0,0 +1,2419 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing/nt_printing_tdb.h"
+#include "printing/queue_process.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "rpc_server/spoolss/srv_spoolss_util.h"
+#include "nt_printing.h"
+#include "secrets.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "messages.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+
+/* Map generic permissions to printer object specific permissions */
+
+const struct generic_mapping printer_generic_mapping = {
+ PRINTER_READ,
+ PRINTER_WRITE,
+ PRINTER_EXECUTE,
+ PRINTER_ALL_ACCESS
+};
+
+/* Map generic permissions to print server object specific permissions */
+
+const struct generic_mapping printserver_generic_mapping = {
+ SERVER_READ,
+ SERVER_WRITE,
+ SERVER_EXECUTE,
+ SERVER_ALL_ACCESS
+};
+
+/* Map generic permissions to job object specific permissions */
+
+const struct generic_mapping job_generic_mapping = {
+ JOB_READ,
+ JOB_WRITE,
+ JOB_EXECUTE,
+ JOB_ALL_ACCESS
+};
+
+static bool print_driver_directories_init(void)
+{
+ int service;
+ size_t i;
+ char *driver_path;
+ bool ok;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ const char *dir_list[] = {
+ "W32X86/PCC",
+ "x64/PCC",
+ "ARM64",
+ "color"
+ };
+
+ service = lp_servicenumber("print$");
+ if (service < 0) {
+ /* We don't have a print$ share */
+ DEBUG(5, ("No print$ share has been configured.\n"));
+ talloc_free(mem_ctx);
+ return true;
+ }
+
+ driver_path = lp_path(mem_ctx, lp_sub, service);
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1, ("Failed to create printer driver directory %s\n",
+ driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ for (i = 0; archi_table[i].long_archi != NULL; i++) {
+ const char *arch_path;
+
+ arch_path = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ driver_path,
+ archi_table[i].short_archi);
+ if (arch_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(arch_path, 0755);
+ if (!ok) {
+ DEBUG(1, ("Failed to create printer driver "
+ "architecture directory %s\n",
+ arch_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dir_list); i++) {
+ const char *path;
+
+ path = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ driver_path,
+ dir_list[i]);
+ if (path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(path, 0755);
+ if (!ok) {
+ DEBUG(1, ("Failed to create printer driver "
+ "architecture directory %s\n",
+ path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+ }
+
+ driver_path = state_path(talloc_tos(), "DriverStore");
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1,("failed to create path %s\n", driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ driver_path = state_path(talloc_tos(), "DriverStore/FileRepository");
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1,("failed to create path %s\n", driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ driver_path = state_path(talloc_tos(), "DriverStore/Temp");
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1,("failed to create path %s\n", driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ talloc_free(mem_ctx);
+ return true;
+}
+
+/****************************************************************************
+ Forward a MSG_PRINTER_DRVUPGRADE message from another smbd to the
+ background lpq updater.
+****************************************************************************/
+
+static void forward_drv_upgrade_printer_msg(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ send_to_bgqd(msg, msg_type, data->data, data->length);
+}
+
+/****************************************************************************
+ Open the NT printing tdbs. Done once before fork().
+****************************************************************************/
+
+bool nt_printing_init(struct messaging_context *msg_ctx)
+{
+ WERROR win_rc;
+
+ if (!print_driver_directories_init()) {
+ return false;
+ }
+
+ if (!nt_printing_tdb_upgrade()) {
+ return false;
+ }
+
+ /*
+ * register callback to handle updating printers as new
+ * drivers are installed. Forwards to background lpq updater.
+ */
+ messaging_register(msg_ctx, NULL, MSG_PRINTER_DRVUPGRADE,
+ forward_drv_upgrade_printer_msg);
+
+ if ( lp_security() == SEC_ADS ) {
+ win_rc = check_published_printers(msg_ctx);
+ if (!W_ERROR_IS_OK(win_rc))
+ DEBUG(0, ("nt_printing_init: error checking published printers: %s\n", win_errstr(win_rc)));
+ }
+
+ return true;
+}
+
+/*******************************************************************
+ Function to allow filename parsing "the old way".
+********************************************************************/
+
+static NTSTATUS driver_unix_convert(connection_struct *conn,
+ const char *old_name,
+ struct files_struct **pdirfsp,
+ struct smb_filename **psmb_fname)
+{
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name = talloc_strdup(ctx, old_name);
+
+ if (!name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ unix_format(name);
+ name = unix_clean_name(ctx, name);
+ if (!name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ trim_string(name,"/","/");
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ 0, /* ucf_flags */
+ 0, /* twrp */
+ pdirfsp,
+ psmb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Function to do the mapping between the long architecture name and
+ the short one.
+****************************************************************************/
+
+const char *get_short_archi(const char *long_archi)
+{
+ int i=-1;
+
+ DEBUG(107,("Getting architecture dependent directory\n"));
+ do {
+ i++;
+ } while ( (archi_table[i].long_archi!=NULL ) &&
+ strcasecmp_m(long_archi, archi_table[i].long_archi) );
+
+ if (archi_table[i].long_archi==NULL) {
+ DEBUGADD(10,("Unknown architecture [%s] !\n", long_archi));
+ return NULL;
+ }
+
+ /* this might be client code - but shouldn't this be an fstrcpy etc? */
+
+ DEBUGADD(108,("index: [%d]\n", i));
+ DEBUGADD(108,("long architecture: [%s]\n", archi_table[i].long_archi));
+ DEBUGADD(108,("short architecture: [%s]\n", archi_table[i].short_archi));
+
+ return archi_table[i].short_archi;
+}
+
+/****************************************************************************
+ Read data from fsp on the vfs.
+****************************************************************************/
+
+static ssize_t printing_pread_data(files_struct *fsp,
+ char *buf,
+ off_t *poff,
+ size_t byte_count)
+{
+ size_t total=0;
+ off_t in_pos = *poff;
+
+ /* Don't allow integer wrap on read. */
+ if (in_pos + byte_count < in_pos) {
+ return -1;
+ }
+
+ while (total < byte_count) {
+ ssize_t ret = read_file(fsp,
+ buf + total,
+ in_pos,
+ byte_count - total);
+
+ if (ret == 0) {
+ *poff = in_pos;
+ return total;
+ }
+ if (ret == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ return -1;
+ }
+ }
+ in_pos += ret;
+ total += ret;
+ }
+ *poff = in_pos;
+ return (ssize_t)total;
+}
+
+/****************************************************************************
+ Detect the major and minor version of a PE file.
+ Returns:
+
+ 1 if file is a PE file and we got version numbers,
+ 0 if this file is a PE file and we couldn't get the version numbers,
+ -1 on error.
+
+ NB. buf is passed into and freed inside this function. This is a
+ bad API design, but fixing this is a task for another day.
+****************************************************************************/
+
+static int handle_pe_file(files_struct *fsp,
+ off_t in_pos,
+ char *fname,
+ char *buf,
+ uint32_t *major,
+ uint32_t *minor)
+{
+ unsigned int i;
+ unsigned int num_sections;
+ unsigned int section_table_bytes;
+ ssize_t byte_count;
+ off_t rel_pos;
+ int ret = -1;
+
+ /* Just skip over optional header to get to section table */
+ rel_pos = SVAL(buf,PE_HEADER_OPTIONAL_HEADER_SIZE)-
+ (NE_HEADER_SIZE-PE_HEADER_SIZE);
+
+ if (in_pos + rel_pos < in_pos) {
+ /* Integer wrap. */
+ goto out;
+ }
+ in_pos = rel_pos + in_pos;
+
+ /* get the section table */
+ num_sections = SVAL(buf,PE_HEADER_NUMBER_OF_SECTIONS);
+
+ if (num_sections >= (UINT_MAX / PE_HEADER_SECT_HEADER_SIZE)) {
+ /* Integer wrap. */
+ goto out;
+ }
+
+ section_table_bytes = num_sections * PE_HEADER_SECT_HEADER_SIZE;
+ if (section_table_bytes == 0) {
+ goto out;
+ }
+
+ SAFE_FREE(buf);
+ buf = (char *)SMB_MALLOC(section_table_bytes);
+ if (buf == NULL) {
+ DBG_ERR("PE file [%s] section table malloc "
+ "failed bytes = %d\n",
+ fname,
+ section_table_bytes);
+ goto out;
+ }
+
+ byte_count = printing_pread_data(fsp, buf, &in_pos, section_table_bytes);
+ if (byte_count < section_table_bytes) {
+ DBG_NOTICE("PE file [%s] Section header too short, "
+ "bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ goto out;
+ }
+
+ /*
+ * Iterate the section table looking for
+ * the resource section ".rsrc"
+ */
+ for (i = 0; i < num_sections; i++) {
+ int sec_offset = i * PE_HEADER_SECT_HEADER_SIZE;
+
+ if (strcmp(".rsrc",
+ &buf[sec_offset+ PE_HEADER_SECT_NAME_OFFSET]) == 0) {
+ unsigned int section_pos = IVAL(buf,
+ sec_offset+
+ PE_HEADER_SECT_PTR_DATA_OFFSET);
+ unsigned int section_bytes = IVAL(buf,
+ sec_offset+
+ PE_HEADER_SECT_SIZE_DATA_OFFSET);
+
+ if (section_bytes == 0) {
+ goto out;
+ }
+
+ SAFE_FREE(buf);
+ buf=(char *)SMB_MALLOC(section_bytes);
+ if (buf == NULL) {
+ DBG_ERR("PE file [%s] version malloc "
+ "failed bytes = %d\n",
+ fname,
+ section_bytes);
+ goto out;
+ }
+
+ /*
+ * Read from the start of the .rsrc
+ * section info
+ */
+ in_pos = section_pos;
+
+ byte_count = printing_pread_data(fsp,
+ buf,
+ &in_pos,
+ section_bytes);
+ if (byte_count < section_bytes) {
+ DBG_NOTICE("PE file "
+ "[%s] .rsrc section too short, "
+ "bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ goto out;
+ }
+
+ if (section_bytes < VS_VERSION_INFO_UNICODE_SIZE) {
+ goto out;
+ }
+
+ for (i=0;
+ i< section_bytes - VS_VERSION_INFO_UNICODE_SIZE;
+ i++) {
+ /*
+ * Scan for 1st 3 unicoded bytes
+ * followed by word aligned magic
+ * value.
+ */
+ int mpos;
+ bool magic_match = false;
+
+ if (buf[i] == 'V' &&
+ buf[i+1] == '\0' &&
+ buf[i+2] == 'S') {
+ magic_match = true;
+ }
+
+ if (magic_match == false) {
+ continue;
+ }
+
+ /* Align to next long address */
+ mpos = (i + sizeof(VS_SIGNATURE)*2 +
+ 3) & 0xfffffffc;
+
+ if (IVAL(buf,mpos) == VS_MAGIC_VALUE) {
+ *major = IVAL(buf,
+ mpos+ VS_MAJOR_OFFSET);
+ *minor = IVAL(buf,
+ mpos+ VS_MINOR_OFFSET);
+
+ DBG_INFO("PE file [%s] Version = "
+ "%08x:%08x (%d.%d.%d.%d)\n",
+ fname,
+ *major,
+ *minor,
+ (*major>>16)&0xffff,
+ *major&0xffff,
+ (*minor>>16)&0xffff,
+ *minor&0xffff);
+ ret = 1;
+ goto out;
+ }
+ }
+ }
+ }
+
+ /* Version info not found, fall back to origin date/time */
+ DBG_DEBUG("PE file [%s] has no version info\n", fname);
+ ret = 0;
+
+ out:
+
+ SAFE_FREE(buf);
+ return ret;
+}
+
+/****************************************************************************
+ Detect the major and minor version of an NE file.
+ Returns:
+
+ 1 if file is an NE file and we got version numbers,
+ 0 if this file is an NE file and we couldn't get the version numbers,
+ -1 on error.
+
+ NB. buf is passed into and freed inside this function. This is a
+ bad API design, but fixing this is a task for another day.
+****************************************************************************/
+
+static int handle_ne_file(files_struct *fsp,
+ off_t in_pos,
+ char *fname,
+ char *buf,
+ uint32_t *major,
+ uint32_t *minor)
+{
+ unsigned int i;
+ ssize_t byte_count;
+ int ret = -1;
+
+ if (CVAL(buf,NE_HEADER_TARGET_OS_OFFSET) != NE_HEADER_TARGOS_WIN ) {
+ DBG_NOTICE("NE file [%s] wrong target OS = 0x%x\n",
+ fname,
+ CVAL(buf,NE_HEADER_TARGET_OS_OFFSET));
+ /*
+ * At this point, we assume the file is in error.
+ * It still could be something else besides a NE file,
+ * but it unlikely at this point.
+ */
+ goto out;
+ }
+
+ /* Allocate a bit more space to speed up things */
+ SAFE_FREE(buf);
+ buf=(char *)SMB_MALLOC(VS_NE_BUF_SIZE);
+ if (buf == NULL) {
+ DBG_ERR("NE file [%s] malloc failed bytes = %d\n",
+ fname,
+ PE_HEADER_SIZE);
+ goto out;
+ }
+
+ /*
+ * This is a HACK! I got tired of trying to sort through the
+ * messy 'NE' file format. If anyone wants to clean this up
+ * please have at it, but this works. 'NE' files will
+ * eventually fade away. JRR
+ */
+ byte_count = printing_pread_data(fsp, buf, &in_pos, VS_NE_BUF_SIZE);
+ while (byte_count > 0) {
+ /*
+ * Cover case that should not occur in a well
+ * formed 'NE' .dll file
+ */
+ if (byte_count-VS_VERSION_INFO_SIZE <= 0) {
+ break;
+ }
+
+ for(i=0; i<byte_count; i++) {
+ /*
+ * Fast skip past data that can't
+ * possibly match
+ */
+ if (buf[i] != 'V') {
+ byte_count = printing_pread_data(fsp,
+ buf,
+ &in_pos,
+ VS_NE_BUF_SIZE);
+ continue;
+ }
+
+ /*
+ * Potential match data crosses buf boundry,
+ * move it to beginning of buf, and fill the
+ * buf with as much as it will hold.
+ */
+ if (i>byte_count-VS_VERSION_INFO_SIZE) {
+ ssize_t amount_read;
+ ssize_t amount_unused = byte_count-i;
+
+ memmove(buf, &buf[i], amount_unused);
+ amount_read = printing_pread_data(fsp,
+ &buf[amount_unused],
+ &in_pos,
+ VS_NE_BUF_SIZE- amount_unused);
+ if (amount_read < 0) {
+ DBG_ERR("NE file [%s] Read "
+ "error, errno=%d\n",
+ fname,
+ errno);
+ goto out;
+ }
+
+ if (amount_read + amount_unused <
+ amount_read) {
+ /* Check for integer wrap. */
+ break;
+ }
+
+ byte_count = amount_read +
+ amount_unused;
+ if (byte_count < VS_VERSION_INFO_SIZE) {
+ break;
+ }
+
+ i = 0;
+ }
+
+ /*
+ * Check that the full signature string and
+ * the magic number that follows exist (not
+ * a perfect solution, but the chances that this
+ * occurs in code is, well, remote. Yes I know
+ * I'm comparing the 'V' twice, as it is
+ * simpler to read the code.
+ */
+ if (strcmp(&buf[i], VS_SIGNATURE) == 0) {
+ /*
+ * Compute skip alignment to next
+ * long address.
+ */
+ off_t cpos = in_pos;
+ int skip = -(cpos - (byte_count - i) +
+ sizeof(VS_SIGNATURE)) & 3;
+ if (IVAL(buf,
+ i+sizeof(VS_SIGNATURE)+skip)
+ != 0xfeef04bd) {
+ byte_count = printing_pread_data(fsp,
+ buf,
+ &in_pos,
+ VS_NE_BUF_SIZE);
+ continue;
+ }
+
+ *major = IVAL(buf,
+ i+sizeof(VS_SIGNATURE)+
+ skip+VS_MAJOR_OFFSET);
+ *minor = IVAL(buf,
+ i+sizeof(VS_SIGNATURE)+
+ skip+VS_MINOR_OFFSET);
+ DBG_INFO("NE file [%s] Version "
+ "= %08x:%08x (%d.%d.%d.%d)\n",
+ fname,
+ *major,
+ *minor,
+ (*major>>16)&0xffff,
+ *major&0xffff,
+ (*minor>>16)&0xffff,
+ *minor&0xffff);
+ ret = 1;
+ goto out;
+ }
+ }
+ }
+
+ /* Version info not found, fall back to origin date/time */
+ DBG_ERR("NE file [%s] Version info not found\n", fname);
+ ret = 0;
+
+ out:
+
+ SAFE_FREE(buf);
+ return ret;
+}
+
+/****************************************************************************
+ Version information in Microsoft files is held in a VS_VERSION_INFO structure.
+ There are two case to be covered here: PE (Portable Executable) and NE (New
+ Executable) files. Both files support the same INFO structure, but PE files
+ store the signature in unicode, and NE files store it as !unicode.
+ returns -1 on error, 1 on version info found, and 0 on no version info found.
+****************************************************************************/
+
+static int get_file_version(files_struct *fsp,
+ char *fname,
+ uint32_t *major,
+ uint32_t *minor)
+{
+ char *buf = NULL;
+ ssize_t byte_count;
+ off_t in_pos = fh_get_pos(fsp->fh);
+
+ buf=(char *)SMB_MALLOC(DOS_HEADER_SIZE);
+ if (buf == NULL) {
+ DBG_ERR("PE file [%s] DOS Header malloc failed bytes = %d\n",
+ fname,
+ DOS_HEADER_SIZE);
+ goto error_exit;
+ }
+
+ byte_count = printing_pread_data(fsp, buf, &in_pos, DOS_HEADER_SIZE);
+ if (byte_count < DOS_HEADER_SIZE) {
+ DBG_NOTICE("File [%s] DOS header too short, bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ goto no_version_info;
+ }
+
+ /* Is this really a DOS header? */
+ if (SVAL(buf,DOS_HEADER_MAGIC_OFFSET) != DOS_HEADER_MAGIC) {
+ DBG_INFO("File [%s] bad DOS magic = 0x%x\n",
+ fname,
+ SVAL(buf,DOS_HEADER_MAGIC_OFFSET));
+ goto no_version_info;
+ }
+
+ /*
+ * Skip OEM header (if any) and the
+ * DOS stub to start of Windows header.
+ */
+ in_pos = SVAL(buf,DOS_HEADER_LFANEW_OFFSET);
+
+ /* Note: DOS_HEADER_SIZE and NE_HEADER_SIZE are incidentally same */
+ byte_count = printing_pread_data(fsp, buf, &in_pos, NE_HEADER_SIZE);
+ if (byte_count < NE_HEADER_SIZE) {
+ DBG_NOTICE("File [%s] Windows header too short, "
+ "bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ /*
+ * Assume this isn't an error...
+ * the file just looks sort of like a PE/NE file
+ */
+ goto no_version_info;
+ }
+
+ /*
+ * The header may be a PE (Portable Executable)
+ * or an NE (New Executable).
+ */
+ if (IVAL(buf,PE_HEADER_SIGNATURE_OFFSET) == PE_HEADER_SIGNATURE) {
+ return handle_pe_file(fsp,
+ in_pos,
+ fname,
+ buf,
+ major,
+ minor);
+ } else if (SVAL(buf,NE_HEADER_SIGNATURE_OFFSET) ==
+ NE_HEADER_SIGNATURE) {
+ return handle_ne_file(fsp,
+ in_pos,
+ fname,
+ buf,
+ major,
+ minor);
+ } else {
+ /*
+ * Assume this isn't an error... the file just
+ * looks sort of like a PE/NE file.
+ */
+ DBG_NOTICE("File [%s] unknown file format, signature = 0x%x\n",
+ fname,
+ IVAL(buf,PE_HEADER_SIGNATURE_OFFSET));
+ /* Fallthrough into no_version_info: */
+ }
+
+ no_version_info:
+ SAFE_FREE(buf);
+ return 0;
+
+ error_exit:
+ SAFE_FREE(buf);
+ return -1;
+}
+
+/****************************************************************************
+Drivers for Microsoft systems contain multiple files. Often, multiple drivers
+share one or more files. During the MS installation process files are checked
+to insure that only a newer version of a shared file is installed over an
+older version. There are several possibilities for this comparison. If there
+is no previous version, the new one is newer (obviously). If either file is
+missing the version info structure, compare the creation date (on Unix use
+the modification date). Otherwise chose the numerically larger version number.
+****************************************************************************/
+
+static int file_version_is_newer(connection_struct *conn, fstring new_file, fstring old_file)
+{
+ bool use_version = true;
+
+ uint32_t new_major;
+ uint32_t new_minor;
+ time_t new_create_time;
+
+ uint32_t old_major;
+ uint32_t old_minor;
+ time_t old_create_time;
+
+ struct smb_filename *smb_fname = NULL;
+ files_struct *fsp = NULL;
+ struct files_struct *dirfsp = NULL;
+ SMB_STRUCT_STAT st;
+
+ NTSTATUS status;
+ int ret;
+
+ SET_STAT_INVALID(st);
+ new_create_time = (time_t)0;
+ old_create_time = (time_t)0;
+
+ /* Get file version info (if available) for previous file (if it exists) */
+ status = driver_unix_convert(conn, old_file, &dirfsp, &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error_exit;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = 1;
+ goto done;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Old file not found, so by definition new file is in fact newer */
+ DEBUG(10,("file_version_is_newer: Can't open old file [%s], "
+ "errno = %d\n", smb_fname_str_dbg(smb_fname),
+ errno));
+ ret = 1;
+ goto done;
+
+ }
+
+ ret = get_file_version(fsp, old_file, &old_major, &old_minor);
+ if (ret == -1) {
+ goto error_exit;
+ }
+
+ if (!ret) {
+ DEBUG(6,("file_version_is_newer: Version info not found [%s], "
+ "use mod time\n",
+ old_file));
+ use_version = false;
+ if (SMB_VFS_FSTAT(fsp, &st) == -1) {
+ goto error_exit;
+ }
+ old_create_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ DEBUGADD(6,("file_version_is_newer: mod time = %ld sec\n",
+ (long)old_create_time));
+ }
+
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+
+ /* Get file version info (if available) for new file */
+ status = driver_unix_convert(conn, new_file, &dirfsp, &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error_exit;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Can't open new file [%s], errno = %d\n",
+ smb_fname_str_dbg(smb_fname), errno);
+ goto error_exit;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* New file not found, this shouldn't occur if the caller did its job */
+ DEBUG(3,("file_version_is_newer: Can't open new file [%s], "
+ "errno = %d\n", smb_fname_str_dbg(smb_fname), errno));
+ goto error_exit;
+
+ }
+
+ ret = get_file_version(fsp, new_file, &new_major, &new_minor);
+ if (ret == -1) {
+ goto error_exit;
+ }
+
+ if (!ret) {
+ DEBUG(6,("file_version_is_newer: Version info not found [%s], "
+ "use mod time\n",
+ new_file));
+ use_version = false;
+ if (SMB_VFS_FSTAT(fsp, &st) == -1) {
+ goto error_exit;
+ }
+ new_create_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ DEBUGADD(6,("file_version_is_newer: mod time = %ld sec\n",
+ (long)new_create_time));
+ }
+
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+
+ if (use_version && (new_major != old_major || new_minor != old_minor)) {
+ /* Compare versions and choose the larger version number */
+ if (new_major > old_major ||
+ (new_major == old_major && new_minor > old_minor)) {
+
+ DEBUG(6,("file_version_is_newer: Replacing [%s] with [%s]\n", old_file, new_file));
+ ret = 1;
+ goto done;
+ }
+ else {
+ DEBUG(6,("file_version_is_newer: Leaving [%s] unchanged\n", old_file));
+ ret = 0;
+ goto done;
+ }
+
+ } else {
+ /* Compare modification time/dates and choose the newest time/date */
+ if (new_create_time > old_create_time) {
+ DEBUG(6,("file_version_is_newer: Replacing [%s] with [%s]\n", old_file, new_file));
+ ret = 1;
+ goto done;
+ }
+ else {
+ DEBUG(6,("file_version_is_newer: Leaving [%s] unchanged\n", old_file));
+ ret = 0;
+ goto done;
+ }
+ }
+
+ error_exit:
+ if(fsp)
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ ret = -1;
+ done:
+ TALLOC_FREE(smb_fname);
+ return ret;
+}
+
+/****************************************************************************
+Determine the correct cVersion associated with an architecture and driver
+****************************************************************************/
+static uint32_t get_correct_cversion(const struct auth_session_info *session_info,
+ const char *architecture,
+ const char *driverpath_in,
+ const char *driver_directory,
+ WERROR *perr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int cversion = -1;
+ NTSTATUS nt_status;
+ struct smb_filename *smb_fname = NULL;
+ files_struct *fsp = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ char *printdollar = NULL;
+ char *printdollar_path = NULL;
+ char *working_dir = NULL;
+ int printdollar_snum;
+ uint32_t major, minor;
+ int ret;
+
+ *perr = WERR_INVALID_PARAMETER;
+
+ /* If architecture is Windows 95/98/ME, the version is always 0. */
+ if (strcmp(architecture, SPL_ARCH_WIN40) == 0) {
+ DEBUG(10,("get_correct_cversion: Driver is Win9x, cversion = 0\n"));
+ *perr = WERR_OK;
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /* If architecture is Windows x64, the version is always 3. */
+ if (strcmp(architecture, SPL_ARCH_X64) == 0 ||
+ strcmp(architecture, SPL_ARCH_ARM64) == 0) {
+ DBG_DEBUG("get_correct_cversion: this architecture must be, cversion = 3\n");
+ *perr = WERR_OK;
+ TALLOC_FREE(frame);
+ return 3;
+ }
+
+ printdollar_snum = find_service(frame, "print$", &printdollar);
+ if (!printdollar) {
+ *perr = WERR_NOT_ENOUGH_MEMORY;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ if (printdollar_snum == -1) {
+ *perr = WERR_BAD_NET_NAME;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ printdollar_path = lp_path(frame, lp_sub, printdollar_snum);
+ if (printdollar_path == NULL) {
+ *perr = WERR_NOT_ENOUGH_MEMORY;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ working_dir = talloc_asprintf(frame,
+ "%s/%s",
+ printdollar_path,
+ architecture);
+ /*
+ * If the driver has been uploaded into a temorpary driver
+ * directory, switch to the driver directory.
+ */
+ if (driver_directory != NULL) {
+ working_dir = talloc_asprintf(frame, "%s/%s/%s",
+ printdollar_path,
+ architecture,
+ driver_directory);
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ printdollar_snum,
+ working_dir,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("get_correct_cversion: create_conn_struct "
+ "returned %s\n", nt_errstr(nt_status)));
+ *perr = ntstatus_to_werror(nt_status);
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ conn = c->conn;
+
+ nt_status = set_conn_force_user_group(conn, printdollar_snum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ *perr = ntstatus_to_werror(nt_status);
+ goto error_free_conn;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ *perr = WERR_ACCESS_DENIED;
+ goto error_free_conn;
+ }
+
+ /*
+ * We switch to the directory where the driver files are located,
+ * so only work on the file names
+ */
+ nt_status = driver_unix_convert(conn,
+ driverpath_in,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ *perr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+
+ nt_status = vfs_file_exist(conn, smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("get_correct_cversion: vfs_file_exist failed\n"));
+ *perr = WERR_FILE_NOT_FOUND;
+ goto error_exit;
+ }
+
+ nt_status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_NOTICE("Can't open file [%s]: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(nt_status));
+ *perr = WERR_ACCESS_DENIED;
+ goto error_exit;
+ }
+
+ nt_status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* private_flags */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("get_correct_cversion: Can't open file [%s], errno = "
+ "%d\n", smb_fname_str_dbg(smb_fname), errno));
+ *perr = WERR_ACCESS_DENIED;
+ goto error_exit;
+ }
+
+ ret = get_file_version(fsp, smb_fname->base_name, &major, &minor);
+ if (ret == -1) {
+ *perr = WERR_INVALID_PARAMETER;
+ goto error_exit;
+ } else if (!ret) {
+ DEBUG(6,("get_correct_cversion: Version info not "
+ "found [%s]\n",
+ smb_fname_str_dbg(smb_fname)));
+ *perr = WERR_INVALID_PARAMETER;
+ goto error_exit;
+ }
+
+ /*
+ * This is a Microsoft'ism. See references in MSDN to VER_FILEVERSION
+ * for more details. Version in this case is not just the version of the
+ * file, but the version in the sense of kernal mode (2) vs. user mode
+ * (3) drivers. Other bits of the version fields are the version info.
+ * JRR 010716
+ */
+ cversion = major & 0x0000ffff;
+ switch (cversion) {
+ case 2: /* WinNT drivers */
+ case 3: /* Win2K drivers */
+ break;
+
+ default:
+ DEBUG(6,("get_correct_cversion: cversion "
+ "invalid [%s] cversion = %d\n",
+ smb_fname_str_dbg(smb_fname),
+ cversion));
+ goto error_exit;
+ }
+
+ DEBUG(10,("get_correct_cversion: Version info found [%s] major"
+ " = 0x%x minor = 0x%x\n",
+ smb_fname_str_dbg(smb_fname), major, minor));
+
+ DEBUG(10,("get_correct_cversion: Driver file [%s] cversion = %d\n",
+ smb_fname_str_dbg(smb_fname), cversion));
+ *perr = WERR_OK;
+
+ error_exit:
+ unbecome_user_without_service();
+ error_free_conn:
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ if (!W_ERROR_IS_OK(*perr)) {
+ cversion = -1;
+ }
+
+ TALLOC_FREE(frame);
+ return cversion;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+#define strip_driver_path(_mem_ctx, _element) do { \
+ if (_element && ((_p = strrchr((_element), '\\')) != NULL)) { \
+ (_element) = talloc_asprintf((_mem_ctx), "%s", _p+1); \
+ W_ERROR_HAVE_NO_MEMORY((_element)); \
+ } \
+} while (0);
+
+static WERROR clean_up_driver_struct_level(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ const char *architecture,
+ const char **driver_path,
+ const char **data_file,
+ const char **config_file,
+ const char **help_file,
+ struct spoolss_StringArray *dependent_files,
+ enum spoolss_DriverOSVersion *version,
+ uint32_t flags,
+ const char **driver_directory)
+{
+ const char *short_architecture;
+ int i;
+ WERROR err;
+ char *_p;
+
+ if (!*driver_path || !*data_file) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!strequal(architecture, SPOOLSS_ARCHITECTURE_4_0) && !*config_file) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (flags & APD_COPY_FROM_DIRECTORY) {
+ char *path;
+ char *q;
+
+ /*
+ * driver_path is set to:
+ *
+ * \\PRINTSRV\print$\x64\{279245b0-a8bd-4431-bf6f-baee92ac15c0}\pscript5.dll
+ */
+ path = talloc_strdup(mem_ctx, *driver_path);
+ if (path == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Remove pscript5.dll */
+ q = strrchr_m(path, '\\');
+ if (q == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ *q = '\0';
+
+ /* Get \{279245b0-a8bd-4431-bf6f-baee92ac15c0} */
+ q = strrchr_m(path, '\\');
+ if (q == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Set driver_directory to:
+ *
+ * {279245b0-a8bd-4431-bf6f-baee92ac15c0}
+ *
+ * This is the directory where all the files have been uploaded
+ */
+ *driver_directory = q + 1;
+ }
+
+ /* clean up the driver name.
+ * we can get .\driver.dll
+ * or worse c:\windows\system\driver.dll !
+ */
+ /* using an intermediate string to not have overlaping memcpy()'s */
+
+ strip_driver_path(mem_ctx, *driver_path);
+ strip_driver_path(mem_ctx, *data_file);
+ if (*config_file) {
+ strip_driver_path(mem_ctx, *config_file);
+ }
+ if (help_file) {
+ strip_driver_path(mem_ctx, *help_file);
+ }
+
+ if (dependent_files && dependent_files->string) {
+ for (i=0; dependent_files->string[i]; i++) {
+ strip_driver_path(mem_ctx, dependent_files->string[i]);
+ }
+ }
+
+ short_architecture = get_short_archi(architecture);
+ if (!short_architecture) {
+ return WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ /* jfm:7/16/2000 the client always sends the cversion=0.
+ * The server should check which version the driver is by reading
+ * the PE header of driver->driverpath.
+ *
+ * For Windows 95/98 the version is 0 (so the value sent is correct)
+ * For Windows NT (the architecture doesn't matter)
+ * NT 3.1: cversion=0
+ * NT 3.5/3.51: cversion=1
+ * NT 4: cversion=2
+ * NT2K: cversion=3
+ */
+
+ *version = get_correct_cversion(session_info,
+ short_architecture,
+ *driver_path,
+ *driver_directory,
+ &err);
+ if (*version == -1) {
+ return err;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+WERROR clean_up_driver_struct(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ const struct spoolss_AddDriverInfoCtr *r,
+ uint32_t flags,
+ const char **driver_directory)
+{
+ switch (r->level) {
+ case 3:
+ return clean_up_driver_struct_level(mem_ctx, session_info,
+ r->info.info3->architecture,
+ &r->info.info3->driver_path,
+ &r->info.info3->data_file,
+ &r->info.info3->config_file,
+ &r->info.info3->help_file,
+ r->info.info3->dependent_files,
+ &r->info.info3->version,
+ flags,
+ driver_directory);
+ case 6:
+ return clean_up_driver_struct_level(mem_ctx, session_info,
+ r->info.info6->architecture,
+ &r->info.info6->driver_path,
+ &r->info.info6->data_file,
+ &r->info.info6->config_file,
+ &r->info.info6->help_file,
+ r->info.info6->dependent_files,
+ &r->info.info6->version,
+ flags,
+ driver_directory);
+ case 8:
+ return clean_up_driver_struct_level(mem_ctx, session_info,
+ r->info.info8->architecture,
+ &r->info.info8->driver_path,
+ &r->info.info8->data_file,
+ &r->info.info8->config_file,
+ &r->info.info8->help_file,
+ r->info.info8->dependent_files,
+ &r->info.info8->version,
+ flags,
+ driver_directory);
+ default:
+ return WERR_NOT_SUPPORTED;
+ }
+}
+
+/****************************************************************************
+ This function sucks and should be replaced. JRA.
+****************************************************************************/
+
+static void convert_level_6_to_level3(struct spoolss_AddDriverInfo3 *dst,
+ const struct spoolss_AddDriverInfo6 *src)
+{
+ dst->version = src->version;
+
+ dst->driver_name = src->driver_name;
+ dst->architecture = src->architecture;
+ dst->driver_path = src->driver_path;
+ dst->data_file = src->data_file;
+ dst->config_file = src->config_file;
+ dst->help_file = src->help_file;
+ dst->monitor_name = src->monitor_name;
+ dst->default_datatype = src->default_datatype;
+ dst->_ndr_size_dependent_files = src->_ndr_size_dependent_files;
+ dst->dependent_files = src->dependent_files;
+}
+
+static void convert_level_8_to_level3(struct spoolss_AddDriverInfo3 *dst,
+ const struct spoolss_AddDriverInfo8 *src)
+{
+ dst->version = src->version;
+
+ dst->driver_name = src->driver_name;
+ dst->architecture = src->architecture;
+ dst->driver_path = src->driver_path;
+ dst->data_file = src->data_file;
+ dst->config_file = src->config_file;
+ dst->help_file = src->help_file;
+ dst->monitor_name = src->monitor_name;
+ dst->default_datatype = src->default_datatype;
+ dst->_ndr_size_dependent_files = src->_ndr_size_dependent_files;
+ dst->dependent_files = src->dependent_files;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR move_driver_file_to_download_area(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *driver_file,
+ const char *short_architecture,
+ uint32_t driver_version,
+ uint32_t version,
+ const char *driver_directory)
+{
+ struct smb_filename *smb_fname_old = NULL;
+ struct smb_filename *smb_fname_new = NULL;
+ char *old_name = NULL;
+ char *new_name = NULL;
+ NTSTATUS status;
+ WERROR ret;
+
+ if (driver_directory != NULL) {
+ old_name = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ short_architecture,
+ driver_directory,
+ driver_file);
+ } else {
+ old_name = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ short_architecture,
+ driver_file);
+ }
+ if (old_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ new_name = talloc_asprintf(mem_ctx, "%s/%d/%s",
+ short_architecture, driver_version, driver_file);
+ if (new_name == NULL) {
+ TALLOC_FREE(old_name);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (version != -1 && (version = file_version_is_newer(conn, old_name, new_name)) > 0) {
+ struct files_struct *dirfsp = NULL;
+
+ status = driver_unix_convert(conn,
+ old_name,
+ &dirfsp,
+ &smb_fname_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ /* Setup a synthetic smb_filename struct */
+ smb_fname_new = talloc_zero(mem_ctx, struct smb_filename);
+ if (!smb_fname_new) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ smb_fname_new->base_name = new_name;
+
+ DEBUG(10,("move_driver_file_to_download_area: copying '%s' to "
+ "'%s'\n", smb_fname_old->base_name,
+ smb_fname_new->base_name));
+
+ status = copy_file(mem_ctx, conn, smb_fname_old, smb_fname_new,
+ FILE_OVERWRITE_IF);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("move_driver_file_to_download_area: Unable "
+ "to rename [%s] to [%s]: %s\n",
+ smb_fname_old->base_name, new_name,
+ nt_errstr(status)));
+ ret = WERR_APP_INIT_FAILURE;
+ goto out;
+ }
+ }
+
+ ret = WERR_OK;
+ out:
+ TALLOC_FREE(smb_fname_old);
+ TALLOC_FREE(smb_fname_new);
+ return ret;
+}
+
+WERROR move_driver_to_download_area(const struct auth_session_info *session_info,
+ const struct spoolss_AddDriverInfoCtr *r,
+ const char *driver_directory)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct spoolss_AddDriverInfo3 *driver;
+ struct spoolss_AddDriverInfo3 converted_driver;
+ const char *short_architecture;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ char *new_dir = NULL;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ NTSTATUS nt_status;
+ int i;
+ int ver = 0;
+ char *printdollar = NULL;
+ int printdollar_snum;
+ WERROR err = WERR_OK;
+
+ switch (r->level) {
+ case 3:
+ driver = r->info.info3;
+ break;
+ case 6:
+ convert_level_6_to_level3(&converted_driver, r->info.info6);
+ driver = &converted_driver;
+ break;
+ case 8:
+ convert_level_8_to_level3(&converted_driver, r->info.info8);
+ driver = &converted_driver;
+ break;
+ default:
+ DEBUG(0,("move_driver_to_download_area: Unknown info level (%u)\n", (unsigned int)r->level));
+ TALLOC_FREE(frame);
+ return WERR_INVALID_LEVEL;
+ }
+
+ short_architecture = get_short_archi(driver->architecture);
+ if (!short_architecture) {
+ TALLOC_FREE(frame);
+ return WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ printdollar_snum = find_service(frame, "print$", &printdollar);
+ if (!printdollar) {
+ TALLOC_FREE(frame);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (printdollar_snum == -1) {
+ TALLOC_FREE(frame);
+ return WERR_BAD_NET_NAME;
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ printdollar_snum,
+ lp_path(frame, lp_sub, printdollar_snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("move_driver_to_download_area: create_conn_struct "
+ "returned %s\n", nt_errstr(nt_status)));
+ err = ntstatus_to_werror(nt_status);
+ TALLOC_FREE(frame);
+ return err;
+ }
+ conn = c->conn;
+
+ nt_status = set_conn_force_user_group(conn, printdollar_snum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ err = ntstatus_to_werror(nt_status);
+ goto err_free_conn;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ err = WERR_ACCESS_DENIED;
+ goto err_free_conn;
+ }
+
+ new_dir = talloc_asprintf(frame,
+ "%s/%d",
+ short_architecture,
+ driver->version);
+ if (!new_dir) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto err_exit;
+ }
+ nt_status = driver_unix_convert(conn, new_dir, &dirfsp, &smb_dname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto err_exit;
+ }
+
+ DEBUG(5,("Creating first directory: %s\n", smb_dname->base_name));
+
+ nt_status = create_directory(conn, NULL, dirfsp, smb_dname);
+ if (!NT_STATUS_IS_OK(nt_status)
+ && !NT_STATUS_EQUAL(nt_status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ DEBUG(0, ("failed to create driver destination directory: %s\n",
+ nt_errstr(nt_status)));
+ err = ntstatus_to_werror(nt_status);
+ goto err_exit;
+ }
+
+ /* For each driver file, archi\filexxx.yyy, if there is a duplicate file
+ * listed for this driver which has already been moved, skip it (note:
+ * drivers may list the same file name several times. Then check if the
+ * file already exists in archi\version\, if so, check that the version
+ * info (or time stamps if version info is unavailable) is newer (or the
+ * date is later). If it is, move it to archi\version\filexxx.yyy.
+ * Otherwise, delete the file.
+ *
+ * If a file is not moved to archi\version\ because of an error, all the
+ * rest of the 'unmoved' driver files are removed from archi\. If one or
+ * more of the driver's files was already moved to archi\version\, it
+ * potentially leaves the driver in a partially updated state. Version
+ * trauma will most likely occur if an client attempts to use any printer
+ * bound to the driver. Perhaps a rewrite to make sure the moves can be
+ * done is appropriate... later JRR
+ */
+
+ DEBUG(5,("Moving files now !\n"));
+
+ if (driver->driver_path && strlen(driver->driver_path)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->driver_path,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+
+ if (driver->data_file && strlen(driver->data_file)) {
+ if (!strequal(driver->data_file, driver->driver_path)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->data_file,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ }
+
+ if (driver->config_file && strlen(driver->config_file)) {
+ if (!strequal(driver->config_file, driver->driver_path) &&
+ !strequal(driver->config_file, driver->data_file)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->config_file,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ }
+
+ if (driver->help_file && strlen(driver->help_file)) {
+ if (!strequal(driver->help_file, driver->driver_path) &&
+ !strequal(driver->help_file, driver->data_file) &&
+ !strequal(driver->help_file, driver->config_file)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->help_file,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ }
+
+ if (driver->dependent_files && driver->dependent_files->string) {
+ for (i=0; driver->dependent_files->string[i]; i++) {
+ if (!strequal(driver->dependent_files->string[i], driver->driver_path) &&
+ !strequal(driver->dependent_files->string[i], driver->data_file) &&
+ !strequal(driver->dependent_files->string[i], driver->config_file) &&
+ !strequal(driver->dependent_files->string[i], driver->help_file)) {
+ int j;
+ for (j=0; j < i; j++) {
+ if (strequal(driver->dependent_files->string[i], driver->dependent_files->string[j])) {
+ goto NextDriver;
+ }
+ }
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->dependent_files->string[i],
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ NextDriver: ;
+ }
+ }
+
+ err = WERR_OK;
+ err_exit:
+ unbecome_user_without_service();
+ err_free_conn:
+ TALLOC_FREE(frame);
+ return err;
+}
+
+/****************************************************************************
+ Determine whether or not a particular driver is currently assigned
+ to a printer
+****************************************************************************/
+
+bool printer_driver_in_use(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const struct spoolss_DriverInfo8 *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ int n_services = lp_numservices();
+ bool in_use = false;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+
+ if (!r) {
+ return false;
+ }
+
+ DEBUG(10,("printer_driver_in_use: Beginning search through ntprinters.tdb...\n"));
+
+ /* loop through the printers.tdb and check for the drivername */
+
+ for (snum=0; snum<n_services && !in_use; snum++) {
+ if (!lp_snum_ok(snum) || !lp_printable(snum)) {
+ continue;
+ }
+
+ result = winreg_get_printer(mem_ctx, b,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ continue; /* skip */
+ }
+
+ if (strequal(r->driver_name, pinfo2->drivername)) {
+ in_use = true;
+ }
+
+ TALLOC_FREE(pinfo2);
+ }
+
+ DEBUG(10,("printer_driver_in_use: Completed search through ntprinters.tdb...\n"));
+
+ if ( in_use ) {
+ struct spoolss_DriverInfo8 *driver = NULL;
+ WERROR werr;
+
+ DEBUG(5,("printer_driver_in_use: driver \"%s\" is currently in use\n", r->driver_name));
+
+ /* we can still remove the driver if there is one of
+ "Windows NT x86" version 2 or 3 left */
+
+ if (strequal(SPOOLSS_ARCHITECTURE_NT_X86, r->architecture)) {
+ if (r->version == 2) {
+ werr = winreg_get_driver(mem_ctx, b,
+ r->architecture,
+ r->driver_name,
+ 3, &driver);
+ } else if (r->version == 3) {
+ werr = winreg_get_driver(mem_ctx, b,
+ r->architecture,
+ r->driver_name,
+ 2, &driver);
+ } else {
+ DBG_ERR("Unknown driver version (%d)\n",
+ r->version);
+ werr = WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+ } else if (strequal(SPOOLSS_ARCHITECTURE_x64, r->architecture)) {
+ werr = winreg_get_driver(mem_ctx, b,
+ SPOOLSS_ARCHITECTURE_NT_X86,
+ r->driver_name,
+ DRIVER_ANY_VERSION,
+ &driver);
+ } else {
+ DBG_ERR("Unknown driver architecture: %s\n",
+ r->architecture);
+ werr = WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ /* now check the error code */
+
+ if ( W_ERROR_IS_OK(werr) ) {
+ /* it's ok to remove the driver, we have other architctures left */
+ in_use = false;
+ talloc_free(driver);
+ }
+ }
+
+ /* report that the driver is not in use by default */
+
+ return in_use;
+}
+
+
+/**********************************************************************
+ Check to see if a ogiven file is in use by *info
+ *********************************************************************/
+
+static bool drv_file_in_use(const char *file, const struct spoolss_DriverInfo8 *info)
+{
+ int i = 0;
+
+ if ( !info )
+ return False;
+
+ /* mz: skip files that are in the list but already deleted */
+ if (!file || !file[0]) {
+ return false;
+ }
+
+ if (strequal(file, info->driver_path))
+ return True;
+
+ if (strequal(file, info->data_file))
+ return True;
+
+ if (strequal(file, info->config_file))
+ return True;
+
+ if (strequal(file, info->help_file))
+ return True;
+
+ /* see of there are any dependent files to examine */
+
+ if (!info->dependent_files)
+ return False;
+
+ while (info->dependent_files[i] && *info->dependent_files[i]) {
+ if (strequal(file, info->dependent_files[i]))
+ return True;
+ i++;
+ }
+
+ return False;
+
+}
+
+/**********************************************************************
+ Utility function to remove the dependent file pointed to by the
+ input parameter from the list
+ *********************************************************************/
+
+static void trim_dependent_file(TALLOC_CTX *mem_ctx, const char **files, int idx)
+{
+
+ /* bump everything down a slot */
+
+ while (files && files[idx+1]) {
+ files[idx] = talloc_strdup(mem_ctx, files[idx+1]);
+ idx++;
+ }
+
+ files[idx] = NULL;
+
+ return;
+}
+
+/**********************************************************************
+ Check if any of the files used by src are also used by drv
+ *********************************************************************/
+
+static bool trim_overlap_drv_files(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo8 *src,
+ const struct spoolss_DriverInfo8 *drv)
+{
+ bool in_use = False;
+ int i = 0;
+
+ if ( !src || !drv )
+ return False;
+
+ /* check each file. Remove it from the src structure if it overlaps */
+
+ if (drv_file_in_use(src->driver_path, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing driverfile [%s] from list\n", src->driver_path));
+ src->driver_path = talloc_strdup(mem_ctx, "");
+ if (!src->driver_path) { return false; }
+ }
+
+ if (drv_file_in_use(src->data_file, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing datafile [%s] from list\n", src->data_file));
+ src->data_file = talloc_strdup(mem_ctx, "");
+ if (!src->data_file) { return false; }
+ }
+
+ if (drv_file_in_use(src->config_file, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing configfile [%s] from list\n", src->config_file));
+ src->config_file = talloc_strdup(mem_ctx, "");
+ if (!src->config_file) { return false; }
+ }
+
+ if (drv_file_in_use(src->help_file, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing helpfile [%s] from list\n", src->help_file));
+ src->help_file = talloc_strdup(mem_ctx, "");
+ if (!src->help_file) { return false; }
+ }
+
+ /* are there any dependentfiles to examine? */
+
+ if (!src->dependent_files)
+ return in_use;
+
+ while (src->dependent_files[i] && *src->dependent_files[i]) {
+ if (drv_file_in_use(src->dependent_files[i], drv)) {
+ in_use = True;
+ DEBUG(10,("Removing [%s] from dependent file list\n", src->dependent_files[i]));
+ trim_dependent_file(mem_ctx, src->dependent_files, i);
+ } else
+ i++;
+ }
+
+ return in_use;
+}
+
+/****************************************************************************
+ Determine whether or not a particular driver files are currently being
+ used by any other driver.
+
+ Return value is True if any files were in use by other drivers
+ and False otherwise.
+
+ Upon return, *info has been modified to only contain the driver files
+ which are not in use
+
+ Fix from mz:
+
+ This needs to check all drivers to ensure that all files in use
+ have been removed from *info, not just the ones in the first
+ match.
+****************************************************************************/
+
+bool printer_driver_files_in_use(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_DriverInfo8 *info)
+{
+ uint32_t version;
+ struct spoolss_DriverInfo8 *driver;
+ bool in_use = false;
+ uint32_t i, num_drivers;
+ const char **drivers;
+ WERROR result;
+
+ if ( !info )
+ return False;
+
+ version = info->version;
+
+ /* loop over all driver versions */
+
+ DEBUG(5,("printer_driver_files_in_use: Beginning search of drivers...\n"));
+
+ /* get the list of drivers */
+
+ result = winreg_get_driver_list(mem_ctx, b,
+ info->architecture, version,
+ &num_drivers, &drivers);
+ if (!W_ERROR_IS_OK(result)) {
+ return true;
+ }
+
+ DEBUGADD(4, ("we have:[%d] drivers in environment [%s] and version [%d]\n",
+ num_drivers, info->architecture, version));
+
+ /* check each driver for overlap in files */
+
+ for (i = 0; i < num_drivers; i++) {
+ DEBUGADD(5,("\tdriver: [%s]\n", drivers[i]));
+
+ driver = NULL;
+
+ result = winreg_get_driver(mem_ctx, b,
+ info->architecture, drivers[i],
+ version, &driver);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(drivers);
+ return True;
+ }
+
+ /* check if d2 uses any files from d1 */
+ /* only if this is a different driver than the one being deleted */
+
+ if (!strequal(info->driver_name, driver->driver_name)) {
+ if (trim_overlap_drv_files(mem_ctx, info, driver)) {
+ /* mz: Do not instantly return -
+ * we need to ensure this file isn't
+ * also in use by other drivers. */
+ in_use = true;
+ }
+ }
+
+ talloc_free(driver);
+ }
+
+ talloc_free(drivers);
+
+ DEBUG(5,("printer_driver_files_in_use: Completed search of drivers...\n"));
+
+ return in_use;
+}
+
+static NTSTATUS driver_unlink_internals(connection_struct *conn,
+ const char *short_arch,
+ int vers,
+ const char *fname)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(conn);
+ struct smb_filename *smb_fname = NULL;
+ char *print_dlr_path;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ print_dlr_path = talloc_asprintf(tmp_ctx, "%s/%d/%s",
+ short_arch, vers, fname);
+ if (print_dlr_path == NULL) {
+ goto err_out;
+ }
+
+ status = synthetic_pathref(tmp_ctx,
+ conn->cwd_fsp,
+ print_dlr_path,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = unlink_internals(conn, NULL, 0, NULL, smb_fname);
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************************
+ Actually delete the driver files. Make sure that
+ printer_driver_files_in_use() return False before calling
+ this.
+****************************************************************************/
+
+bool delete_driver_files(const struct auth_session_info *session_info,
+ const struct spoolss_DriverInfo8 *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *short_arch;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ NTSTATUS nt_status;
+ char *printdollar = NULL;
+ int printdollar_snum;
+ bool ret = false;
+
+ if (!r) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ DEBUG(6,("delete_driver_files: deleting driver [%s] - version [%d]\n",
+ r->driver_name, r->version));
+
+ printdollar_snum = find_service(frame, "print$", &printdollar);
+ if (!printdollar) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ if (printdollar_snum == -1) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ printdollar_snum,
+ lp_path(frame, lp_sub, printdollar_snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("delete_driver_files: create_conn_struct "
+ "returned %s\n", nt_errstr(nt_status)));
+ TALLOC_FREE(frame);
+ return false;
+ }
+ conn = c->conn;
+
+ nt_status = set_conn_force_user_group(conn, printdollar_snum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ ret = false;
+ goto err_free_conn;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ ret = false;
+ goto err_free_conn;
+ }
+
+ if ( !CAN_WRITE(conn) ) {
+ DEBUG(3,("delete_driver_files: Cannot delete print driver when [print$] is read-only\n"));
+ ret = false;
+ goto err_out;
+ }
+
+ short_arch = get_short_archi(r->architecture);
+ if (short_arch == NULL) {
+ DEBUG(0, ("bad architecture %s\n", r->architecture));
+ ret = false;
+ goto err_out;
+ }
+
+ /* now delete the files */
+
+ if (r->driver_path && r->driver_path[0]) {
+ DEBUG(10,("deleting driverfile [%s]\n", r->driver_path));
+ driver_unlink_internals(conn, short_arch, r->version, r->driver_path);
+ }
+
+ if (r->config_file && r->config_file[0]) {
+ DEBUG(10,("deleting configfile [%s]\n", r->config_file));
+ driver_unlink_internals(conn, short_arch, r->version, r->config_file);
+ }
+
+ if (r->data_file && r->data_file[0]) {
+ DEBUG(10,("deleting datafile [%s]\n", r->data_file));
+ driver_unlink_internals(conn, short_arch, r->version, r->data_file);
+ }
+
+ if (r->help_file && r->help_file[0]) {
+ DEBUG(10,("deleting helpfile [%s]\n", r->help_file));
+ driver_unlink_internals(conn, short_arch, r->version, r->help_file);
+ }
+
+ if (r->dependent_files) {
+ int i = 0;
+ while (r->dependent_files[i] && r->dependent_files[i][0]) {
+ DEBUG(10,("deleting dependent file [%s]\n", r->dependent_files[i]));
+ driver_unlink_internals(conn, short_arch, r->version, r->dependent_files[i]);
+ i++;
+ }
+ }
+
+ ret = true;
+ err_out:
+ unbecome_user_without_service();
+ err_free_conn:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/* error code:
+ 0: everything OK
+ 1: level not implemented
+ 2: file doesn't exist
+ 3: can't allocate memory
+ 4: can't free memory
+ 5: non existent struct
+*/
+
+/*
+ A printer and a printer driver are 2 different things.
+ NT manages them separatelly, Samba does the same.
+ Why ? Simply because it's easier and it makes sense !
+
+ Now explanation: You have 3 printers behind your samba server,
+ 2 of them are the same make and model (laser A and B). But laser B
+ has an 3000 sheet feeder and laser A doesn't such an option.
+ Your third printer is an old dot-matrix model for the accounting :-).
+
+ If the /usr/local/samba/lib directory (default dir), you will have
+ 5 files to describe all of this.
+
+ 3 files for the printers (1 by printer):
+ NTprinter_laser A
+ NTprinter_laser B
+ NTprinter_accounting
+ 2 files for the drivers (1 for the laser and 1 for the dot matrix)
+ NTdriver_printer model X
+ NTdriver_printer model Y
+
+jfm: I should use this comment for the text file to explain
+ same thing for the forms BTW.
+ Je devrais mettre mes commentaires en francais, ca serait mieux :-)
+
+*/
+
+/* Convert generic access rights to printer object specific access rights.
+ It turns out that NT4 security descriptors use generic access rights and
+ NT5 the object specific ones. */
+
+void map_printer_permissions(struct security_descriptor *sd)
+{
+ uint32_t i;
+
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ se_map_generic(&sd->dacl->aces[i].access_mask,
+ &printer_generic_mapping);
+ }
+}
+
+void map_job_permissions(struct security_descriptor *sd)
+{
+ uint32_t i;
+
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ se_map_generic(&sd->dacl->aces[i].access_mask,
+ &job_generic_mapping);
+ }
+}
+
+
+/****************************************************************************
+ Check a user has permissions to perform the given operation. We use the
+ permission constants defined in include/rpc_spoolss.h to check the various
+ actions we perform when checking printer access.
+
+ PRINTER_ACCESS_ADMINISTER:
+ print_queue_pause, print_queue_resume, update_printer_sec,
+ update_printer, spoolss_addprinterex_level_2,
+ _spoolss_setprinterdata
+
+ PRINTER_ACCESS_USE:
+ print_job_start
+
+ JOB_ACCESS_ADMINISTER:
+ print_job_delete, print_job_pause, print_job_resume,
+ print_queue_purge
+
+ Try access control in the following order (for performance reasons):
+ 1) root and SE_PRINT_OPERATOR can do anything (easy check)
+ 2) check security descriptor (bit comparisons in memory)
+ 3) "printer admins" (may result in numerous calls to winbind)
+
+ ****************************************************************************/
+WERROR print_access_check(const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx, int snum,
+ int access_type)
+{
+ struct spoolss_security_descriptor *secdesc = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t access_granted;
+ size_t sd_size;
+ NTSTATUS status;
+ WERROR result;
+ const char *pname;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ /* If user is NULL then use the current_user structure */
+
+ /* Always allow root or SE_PRINT_OPERATROR to do anything */
+
+ if ((session_info->unix_token->uid == sec_initial_uid())
+ || security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ return WERR_OK;
+ }
+
+ /* Get printer name */
+
+ pname = lp_printername(talloc_tos(), lp_sub, snum);
+
+ if (!pname || !*pname) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Get printer security descriptor */
+
+ if(!(mem_ctx = talloc_init("print_access_check"))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_get_printer_secdesc_internal(mem_ctx,
+ get_session_info_system(),
+ msg_ctx,
+ pname,
+ &secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_destroy(mem_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (access_type == JOB_ACCESS_ADMINISTER) {
+ struct spoolss_security_descriptor *parent_secdesc = secdesc;
+
+ /* Create a child security descriptor to check permissions
+ against. This is because print jobs are child objects
+ objects of a printer. */
+ status = se_create_child_secdesc(mem_ctx,
+ &secdesc,
+ &sd_size,
+ parent_secdesc,
+ parent_secdesc->owner_sid,
+ parent_secdesc->group_sid,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_destroy(mem_ctx);
+ return ntstatus_to_werror(status);
+ }
+
+ map_job_permissions(secdesc);
+ } else {
+ map_printer_permissions(secdesc);
+ }
+
+ /* Check access */
+ status = se_access_check(secdesc, session_info->security_token, access_type,
+ &access_granted);
+
+ DEBUG(4, ("access check was %s\n", NT_STATUS_IS_OK(status) ? "SUCCESS" : "FAILURE"));
+
+ talloc_destroy(mem_ctx);
+
+ return ntstatus_to_werror(status);
+}
+
+/****************************************************************************
+ Check the time parameters allow a print operation.
+*****************************************************************************/
+
+bool print_time_access_check(const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servicename)
+{
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+ bool ok = False;
+ time_t now = time(NULL);
+ struct tm *t;
+ uint32_t mins;
+
+ result = winreg_get_printer_internal(NULL, session_info, msg_ctx,
+ servicename, &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ return False;
+ }
+
+ if (pinfo2->starttime == 0 && pinfo2->untiltime == 0) {
+ ok = True;
+ }
+
+ t = gmtime(&now);
+ mins = (uint32_t)t->tm_hour*60 + (uint32_t)t->tm_min;
+
+ if (mins >= pinfo2->starttime && mins <= pinfo2->untiltime) {
+ ok = True;
+ }
+
+ TALLOC_FREE(pinfo2);
+
+ if (!ok) {
+ errno = EACCES;
+ }
+
+ return ok;
+}
+
+void nt_printer_remove(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer)
+{
+ WERROR result;
+
+ result = winreg_delete_printer_key_internal(mem_ctx, session_info, msg_ctx,
+ printer, "");
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("nt_printer_remove: failed to remove printer %s: "
+ "%s\n", printer, win_errstr(result)));
+ }
+}
+
+void nt_printer_add(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer)
+{
+ WERROR result;
+
+ result = winreg_create_printer_internal(mem_ctx, session_info, msg_ctx,
+ printer);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("nt_printer_add: failed to add printer %s: %s\n",
+ printer, win_errstr(result)));
+ }
+}
diff --git a/source3/printing/nt_printing_ads.c b/source3/printing/nt_printing_ads.c
new file mode 100644
index 0000000..ff41baa
--- /dev/null
+++ b/source3/printing/nt_printing_ads.c
@@ -0,0 +1,908 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/spoolss.h"
+#include "rpc_server/spoolss/srv_spoolss_util.h"
+#include "nt_printing.h"
+#include "ads.h"
+#include "secrets.h"
+#include "krb5_env.h"
+#include "../libcli/registry/util_reg.h"
+#include "auth.h"
+#include "../librpc/ndr/libndr.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+
+#ifdef HAVE_ADS
+/*****************************************************************
+ ****************************************************************/
+
+WERROR nt_printer_guid_store(struct messaging_context *msg_ctx,
+ const char *printer, struct GUID guid)
+{
+ TALLOC_CTX *tmp_ctx;
+ const struct auth_session_info *session_info;
+ const char *guid_str;
+ DATA_BLOB blob;
+ WERROR result;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ DEBUG(0, ("Out of memory?!\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ session_info = get_session_info_system();
+ if (session_info == NULL) {
+ DEBUG(0, ("Could not get system session_info\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ guid_str = GUID_string(tmp_ctx, &guid);
+ if (!guid_str) {
+ DEBUG(0, ("Out of memory?!\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* We used to store this as a REG_BINARY but that causes
+ Vista to whine */
+
+ if (!push_reg_sz(tmp_ctx, &blob, guid_str)) {
+ DEBUG(0, ("Could not marshall string %s for objectGUID\n",
+ guid_str));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_set_printer_dataex_internal(tmp_ctx, session_info, msg_ctx,
+ printer,
+ SPOOL_DSSPOOLER_KEY, "objectGUID",
+ REG_SZ, blob.data, blob.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to store GUID for printer %s\n", printer));
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ talloc_free(tmp_ctx);
+
+ return result;
+}
+
+static WERROR nt_printer_dn_lookup(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char *printer,
+ char **pprinter_dn)
+{
+ char *printer_dn = NULL;
+ char *srv_dn = NULL;
+ char *srv_cn_0 = NULL;
+ char *srv_cn_escaped = NULL;
+ char *sharename_escaped = NULL;
+ char *srv_dn_utf8 = NULL;
+ char **srv_cn_utf8 = NULL;
+ size_t converted_size;
+ ADS_STATUS ads_status;
+ LDAPMessage *res;
+ WERROR result;
+ bool ok;
+
+ ads_status = ads_find_machine_acct(ads, &res, lp_netbios_name());
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(2, ("Failed to find machine account for %s\n",
+ lp_netbios_name()));
+ result = WERR_NOT_FOUND;
+ goto err_out;
+ }
+
+ /*
+ * We use ldap_get_dn here as we need the answer in utf8 to call
+ * ldap_explode_dn(). JRA.
+ */
+ srv_dn_utf8 = ldap_get_dn((LDAP *)ads->ldap.ld, (LDAPMessage *)res);
+ ads_msgfree(ads, res);
+ if (srv_dn_utf8 == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ srv_cn_utf8 = ldap_explode_dn(srv_dn_utf8, 1);
+ if (srv_cn_utf8 == NULL) {
+ ldap_memfree(srv_dn_utf8);
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ /* Now convert to CH_UNIX. */
+ ok = pull_utf8_talloc(mem_ctx, &srv_dn, srv_dn_utf8, &converted_size);
+ ldap_memfree(srv_dn_utf8);
+ if (!ok) {
+ ldap_memfree(srv_cn_utf8);
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ ok = pull_utf8_talloc(mem_ctx, &srv_cn_0, srv_cn_utf8[0], &converted_size);
+ ldap_memfree(srv_cn_utf8);
+ if (!ok) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ srv_cn_escaped = escape_rdn_val_string_alloc(srv_cn_0);
+ if (srv_cn_escaped == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ sharename_escaped = escape_rdn_val_string_alloc(printer);
+ if (sharename_escaped == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ printer_dn = talloc_asprintf(mem_ctx,
+ "cn=%s-%s,%s",
+ srv_cn_escaped,
+ sharename_escaped,
+ srv_dn);
+ if (printer_dn == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ *pprinter_dn = printer_dn;
+
+ result = WERR_OK;
+err_out:
+ SAFE_FREE(sharename_escaped);
+ SAFE_FREE(srv_cn_escaped);
+ TALLOC_FREE(srv_cn_0);
+ TALLOC_FREE(srv_dn);
+ return result;
+}
+
+static WERROR nt_printer_guid_retrieve_internal(ADS_STRUCT *ads,
+ const char *printer_dn,
+ struct GUID *pguid)
+{
+ ADS_STATUS ads_status;
+ LDAPMessage *res;
+ const char *attrs[] = {"objectGUID", NULL};
+ struct GUID guid;
+ bool ok;
+
+ ads_status = ads_search_dn(ads, &res, printer_dn, attrs);
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(2, ("Failed to retrieve GUID from DC - %s\n",
+ ads_errstr(ads_status)));
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ ZERO_STRUCT(guid);
+ ok = ads_pull_guid(ads, res, &guid);
+ ads_msgfree(ads, res);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *pguid = guid;
+
+ return WERR_OK;
+}
+
+WERROR nt_printer_guid_retrieve(TALLOC_CTX *mem_ctx, const char *printer,
+ struct GUID *pguid)
+{
+ ADS_STRUCT *ads = NULL;
+ char *old_krb5ccname = NULL;
+ char *printer_dn;
+ WERROR result;
+ ADS_STATUS ads_status;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ char *machine_password = NULL;
+
+ ads = ads_init(tmp_ctx,
+ lp_realm(),
+ lp_workgroup(),
+ NULL,
+ ADS_SASL_PLAIN);
+ if (ads == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto out;
+ }
+
+ old_krb5ccname = getenv(KRB5_ENV_CCNAME);
+ setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1);
+ TALLOC_FREE(ads->auth.password);
+ machine_password = secrets_fetch_machine_password(lp_workgroup(),
+ NULL, NULL);
+ if (machine_password != NULL) {
+ ads->auth.password = talloc_strdup(ads, machine_password);
+ SAFE_FREE(machine_password);
+ if (ads->auth.password == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ }
+
+ ads_status = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(3, ("ads_connect failed: %s\n", ads_errstr(ads_status)));
+ result = WERR_ACCESS_DENIED;
+ goto out;
+ }
+
+ result = nt_printer_dn_lookup(tmp_ctx, ads, printer, &printer_dn);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ result = nt_printer_guid_retrieve_internal(ads, printer_dn, pguid);
+out:
+ TALLOC_FREE(tmp_ctx);
+ ads_kdestroy("MEMORY:prtpub_cache");
+ unsetenv(KRB5_ENV_CCNAME);
+ if (old_krb5ccname != NULL) {
+ setenv(KRB5_ENV_CCNAME, old_krb5ccname, 0);
+ }
+
+ return result;
+}
+
+WERROR nt_printer_guid_get(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer, struct GUID *guid)
+{
+ TALLOC_CTX *tmp_ctx;
+ enum winreg_Type type;
+ DATA_BLOB blob;
+ uint32_t len;
+ NTSTATUS status;
+ WERROR result;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ DEBUG(0, ("out of memory?!\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_get_printer_dataex_internal(tmp_ctx, session_info,
+ msg_ctx, printer,
+ SPOOL_DSSPOOLER_KEY,
+ "objectGUID",
+ &type,
+ &blob.data,
+ &len);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to get GUID for printer %s\n", printer));
+ goto out_ctx_free;
+ }
+ blob.length = (size_t)len;
+
+ /* We used to store the guid as REG_BINARY, then swapped
+ to REG_SZ for Vista compatibility so check for both */
+
+ switch (type) {
+ case REG_SZ: {
+ bool ok;
+ const char *guid_str;
+ ok = pull_reg_sz(tmp_ctx, &blob, &guid_str);
+ if (!ok) {
+ DEBUG(0, ("Failed to unmarshall GUID for printer %s\n",
+ printer));
+ result = WERR_REGISTRY_CORRUPT;
+ goto out_ctx_free;
+ }
+ status = GUID_from_string(guid_str, guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("bad GUID for printer %s\n", printer));
+ result = ntstatus_to_werror(status);
+ goto out_ctx_free;
+ }
+ break;
+ }
+ case REG_BINARY:
+ if (blob.length != sizeof(struct GUID)) {
+ DEBUG(0, ("bad GUID for printer %s\n", printer));
+ result = WERR_REGISTRY_CORRUPT;
+ goto out_ctx_free;
+ }
+ memcpy(guid, blob.data, sizeof(struct GUID));
+ break;
+ default:
+ DEBUG(0,("GUID value stored as invalid type (%d)\n", type));
+ result = WERR_REGISTRY_CORRUPT;
+ goto out_ctx_free;
+ break;
+ }
+ result = WERR_OK;
+
+out_ctx_free:
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+static WERROR nt_printer_devmode_to_mods(TALLOC_CTX *ctx,
+ struct spoolss_DeviceMode *devmode,
+ ADS_MODLIST *mods)
+{
+ char *str = NULL;
+ ADS_STATUS status;
+
+ /*
+ the device mode fields bits allow us to make an educated guess if a
+ printer feature is supported. For sure a feature must be unsupported if
+ the fields bit is not set. Device Mode Extra Data and FeatureOptionPairs
+ might help to figure out more information here. Common attributes, that
+ we can't handle yet:
+ SPOOL_REG_PRINTBINNAMES - printBinNames
+ SPOOL_REG_PRINTMAXXEXTENT - printMaxXExtent
+ SPOOL_REG_PRINTMAXYEXTENT - printMaxYExtent
+ SPOOL_REG_PRINTMINXEXTENT - printMinXExtent
+ SPOOL_REG_PRINTMINYEXTENT - printMinYExtent
+ SPOOL_REG_PRINTSTAPLINGSUPPORTED - printStaplingSupported
+ SPOOL_REG_PRINTPAGESPERMINUTE - printPagesPerMinute
+ SPOOL_REG_PRINTRATE - printRate
+ SPOOL_REG_PRINTRATEUNIT - printRateUnit
+ SPOOL_REG_PRINTMEDIAREADY - printMediaReady
+ SPOOL_REG_PRINTMEDIASUPPORTED - printMediaSupported
+ SPOOL_REG_PRINTNUMBERUP - printNumberUp
+ SPOOL_REG_PRINTMAXCOPIES - printMaxCopies
+ */
+ if (devmode->fields & DEVMODE_COLOR) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLOR, "TRUE");
+ } else {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLOR, "FALSE");
+ }
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (devmode->fields & DEVMODE_DUPLEX) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTDUPLEXSUPPORTED, "TRUE");
+ } else {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTDUPLEXSUPPORTED, "FALSE");
+ }
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (devmode->fields & DEVMODE_COLLATE) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLLATE, "TRUE");
+ } else {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLLATE, "FALSE");
+ }
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* portrait mode is always supported, LANDSCAPE is optional */
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTORIENTATIONSSUPPORTED, "PORTRAIT");
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (devmode->fields & DEVMODE_ORIENTATION) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTORIENTATIONSSUPPORTED, "LANDSCAPE");
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ /* the driverVersion attribute in AD contains actually specversion */
+ str = talloc_asprintf(ctx, "%u", devmode->specversion);
+ if (str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strlen(str) != 0) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_DRIVERVERSION, str);
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ /* devmode->yresolution is a good candidate for printMaxResolutionSupported */
+ str = talloc_asprintf(ctx, "%u", devmode->yresolution);
+ if (str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strlen(str) != 0) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTMAXRESOLUTIONSUPPORTED, str);
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return WERR_OK;
+}
+
+
+
+static WERROR nt_printer_info_to_mods(TALLOC_CTX *ctx,
+ struct spoolss_PrinterInfo2 *info2,
+ ADS_MODLIST *mods)
+{
+ char *info_str;
+
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTERNAME, info2->sharename);
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSHARENAME, info2->sharename);
+ ads_mod_str(ctx, mods, SPOOL_REG_SHORTSERVERNAME, lp_netbios_name());
+ ads_mod_str(ctx, mods, SPOOL_REG_SERVERNAME, get_mydnsfullname());
+
+ info_str = talloc_asprintf(ctx, "\\\\%s\\%s",
+ get_mydnsfullname(), info2->sharename);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_UNCNAME, info_str);
+
+ info_str = talloc_asprintf(ctx, "%d", 4);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_VERSIONNUMBER, info_str);
+
+ /* empty strings in the mods list result in an attrubute error */
+ if (strlen(info2->drivername) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_DRIVERNAME, info2->drivername);
+ if (strlen(info2->location) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_LOCATION, info2->location);
+ if (strlen(info2->comment) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_DESCRIPTION, info2->comment);
+ if (strlen(info2->portname) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_PORTNAME, info2->portname);
+ if (strlen(info2->sepfile) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSEPARATORFILE, info2->sepfile);
+
+ info_str = talloc_asprintf(ctx, "%u", info2->starttime);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSTARTTIME, info_str);
+
+ info_str = talloc_asprintf(ctx, "%u", info2->untiltime);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTENDTIME, info_str);
+
+ info_str = talloc_asprintf(ctx, "%u", info2->priority);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_PRIORITY, info_str);
+
+ if (info2->attributes & PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS) {
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTKEEPPRINTEDJOBS, "TRUE");
+ } else {
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTKEEPPRINTEDJOBS, "FALSE");
+ }
+
+ switch (info2->attributes & 0x3) {
+ case 0:
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSPOOLING,
+ SPOOL_REGVAL_PRINTWHILESPOOLING);
+ break;
+ case 1:
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSPOOLING,
+ SPOOL_REGVAL_PRINTAFTERSPOOLED);
+ break;
+ case 2:
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSPOOLING,
+ SPOOL_REGVAL_PRINTDIRECT);
+ break;
+ default:
+ DEBUG(3, ("unsupported printer attributes %x\n",
+ info2->attributes));
+ }
+
+ if (info2->devmode != NULL) {
+ WERROR werr;
+ werr = nt_printer_devmode_to_mods(ctx, info2->devmode, mods);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR nt_printer_publish_ads(struct messaging_context *msg_ctx,
+ ADS_STRUCT *ads,
+ struct spoolss_PrinterInfo2 *pinfo2)
+{
+ ADS_STATUS ads_rc;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ ADS_MODLIST mods;
+ struct GUID guid;
+ WERROR win_rc = WERR_OK;
+ const char *printer = pinfo2->sharename;
+ char *printer_dn = NULL;
+
+ /* build the ads mods */
+ DEBUG(5, ("publishing printer %s\n", printer));
+
+ win_rc = nt_printer_dn_lookup(ctx, ads, printer, &printer_dn);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ DEBUG(2, ("Failed to create printer dn\n"));
+ TALLOC_FREE(ctx);
+ return win_rc;
+ }
+
+ mods = ads_init_mods(ctx);
+
+ if (mods == NULL) {
+ TALLOC_FREE(ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ win_rc = nt_printer_info_to_mods(ctx, pinfo2, &mods);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ TALLOC_FREE(ctx);
+ return win_rc;
+ }
+
+ /* publish it */
+ ads_rc = ads_mod_printer_entry(ads, printer_dn, ctx, &mods);
+ if (ads_rc.err.rc == LDAP_NO_SUCH_OBJECT) {
+ int i;
+ for (i=0; mods[i] != 0; i++)
+ ;
+ mods[i] = (LDAPMod *)-1;
+ ads_rc = ads_add_printer_entry(ads, printer_dn, ctx, &mods);
+ }
+
+ if (!ADS_ERR_OK(ads_rc)) {
+ DEBUG(3, ("error publishing %s: %s\n",
+ printer, ads_errstr(ads_rc)));
+ /* XXX failed to publish, so no guid to retrieve */
+ }
+
+ win_rc = nt_printer_guid_retrieve_internal(ads, printer_dn, &guid);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ TALLOC_FREE(ctx);
+ return win_rc;
+ }
+
+ win_rc = nt_printer_guid_store(msg_ctx, printer, guid);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ DEBUG(3, ("failed to store printer %s guid\n",
+ printer));
+ /* not catastrophic, retrieve on next use */
+ win_rc = WERR_OK;
+ }
+
+ TALLOC_FREE(ctx);
+
+ return win_rc;
+}
+
+static WERROR nt_printer_unpublish_ads(ADS_STRUCT *ads,
+ const char *printer)
+{
+ ADS_STATUS ads_rc;
+ LDAPMessage *res = NULL;
+ char *prt_dn = NULL;
+
+ DEBUG(5, ("unpublishing printer %s\n", printer));
+
+ /* remove the printer from the directory */
+ ads_rc = ads_find_printer_on_server(ads, &res,
+ printer, lp_netbios_name());
+
+ if (ADS_ERR_OK(ads_rc) && res && ads_count_replies(ads, res)) {
+ prt_dn = ads_get_dn(ads, talloc_tos(), res);
+ if (!prt_dn) {
+ ads_msgfree(ads, res);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_rc = ads_del_dn(ads, prt_dn);
+ TALLOC_FREE(prt_dn);
+ }
+
+ if (res) {
+ ads_msgfree(ads, res);
+ }
+ return WERR_OK;
+}
+
+/****************************************************************************
+ * Publish a printer in the directory
+ *
+ * @param mem_ctx memory context
+ * @param session_info session_info to access winreg pipe
+ * @param pinfo2 printer information
+ * @param action publish/unpublish action
+ * @return WERROR indicating status of publishing
+ ***************************************************************************/
+
+WERROR nt_printer_publish(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int action)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ uint32_t info2_mask = SPOOLSS_PRINTER_INFO_ATTRIBUTES;
+ struct spoolss_SetPrinterInfo2 *sinfo2;
+ ADS_STATUS ads_rc;
+ ADS_STRUCT *ads = NULL;
+ WERROR win_rc;
+ char *old_krb5ccname = NULL;
+ char *machine_password = NULL;
+
+ sinfo2 = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfo2);
+ if (!sinfo2) {
+ win_rc = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ switch (action) {
+ case DSPRINT_PUBLISH:
+ case DSPRINT_UPDATE:
+ pinfo2->attributes |= PRINTER_ATTRIBUTE_PUBLISHED;
+ break;
+ case DSPRINT_UNPUBLISH:
+ pinfo2->attributes &= (~PRINTER_ATTRIBUTE_PUBLISHED);
+ break;
+ default:
+ win_rc = WERR_NOT_SUPPORTED;
+ goto done;
+ }
+
+ sinfo2->attributes = pinfo2->attributes;
+
+ win_rc = winreg_update_printer_internal(tmp_ctx, session_info, msg_ctx,
+ pinfo2->sharename, info2_mask,
+ sinfo2, NULL, NULL);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ DBG_NOTICE("Failed to update data for printer [%s] - %s\n",
+ pinfo2->sharename,
+ win_errstr(win_rc));
+ goto done;
+ }
+
+ TALLOC_FREE(sinfo2);
+
+ ads = ads_init(tmp_ctx,
+ lp_realm(),
+ lp_workgroup(),
+ NULL,
+ ADS_SASL_PLAIN);
+ if (!ads) {
+ DEBUG(3, ("ads_init() failed\n"));
+ win_rc = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto done;
+ }
+ old_krb5ccname = getenv(KRB5_ENV_CCNAME);
+ setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1);
+ TALLOC_FREE(ads->auth.password);
+ machine_password = secrets_fetch_machine_password(lp_workgroup(),
+ NULL, NULL);
+ if (machine_password != NULL) {
+ ads->auth.password = talloc_strdup(ads, machine_password);
+ SAFE_FREE(machine_password);
+ if (ads->auth.password == NULL) {
+ win_rc = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ /* ads_connect() will find the DC for us */
+ ads_rc = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_rc)) {
+ DEBUG(3, ("ads_connect failed: %s\n", ads_errstr(ads_rc)));
+ win_rc = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ switch (action) {
+ case DSPRINT_PUBLISH:
+ case DSPRINT_UPDATE:
+ win_rc = nt_printer_publish_ads(msg_ctx, ads, pinfo2);
+ break;
+ case DSPRINT_UNPUBLISH:
+ win_rc = nt_printer_unpublish_ads(ads, pinfo2->sharename);
+ break;
+ }
+
+done:
+ ads_kdestroy("MEMORY:prtpub_cache");
+ unsetenv(KRB5_ENV_CCNAME);
+ if (old_krb5ccname) {
+ setenv(KRB5_ENV_CCNAME, old_krb5ccname, 0);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+
+ return win_rc;
+}
+
+WERROR check_published_printers(struct messaging_context *msg_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ ADS_STATUS ads_rc;
+ ADS_STRUCT *ads = NULL;
+ int snum;
+ int n_services = lp_numservices();
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct auth_session_info *session_info = NULL;
+ struct spoolss_PrinterInfo2 *pinfo2;
+ NTSTATUS status;
+ WERROR result;
+ char *old_krb5ccname = NULL;
+ char *machine_password = NULL;
+
+ ads = ads_init(tmp_ctx,
+ lp_realm(),
+ lp_workgroup(),
+ NULL,
+ ADS_SASL_PLAIN);
+ if (!ads) {
+ DEBUG(3, ("ads_init() failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return WERR_RPC_S_SERVER_UNAVAILABLE;
+ }
+ old_krb5ccname = getenv(KRB5_ENV_CCNAME);
+ setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1);
+ TALLOC_FREE(ads->auth.password);
+ machine_password = secrets_fetch_machine_password(lp_workgroup(),
+ NULL, NULL);
+ if (machine_password != NULL) {
+ ads->auth.password = talloc_strdup(ads, machine_password);
+ SAFE_FREE(machine_password);
+ if (ads->auth.password == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+ /* ads_connect() will find the DC for us */
+ ads_rc = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_rc)) {
+ DEBUG(3, ("ads_connect failed: %s\n", ads_errstr(ads_rc)));
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ status = make_session_info_system(tmp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("check_published_printers: "
+ "Could not create system session_info\n"));
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ for (snum = 0; snum < n_services; snum++) {
+ if (!lp_snum_ok(snum) || !lp_printable(snum)) {
+ continue;
+ }
+
+ result = winreg_get_printer_internal(tmp_ctx, session_info, msg_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ continue;
+ }
+
+ if (pinfo2->attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
+ nt_printer_publish_ads(msg_ctx, ads, pinfo2);
+ }
+
+ TALLOC_FREE(pinfo2);
+ }
+
+ result = WERR_OK;
+done:
+ ads_kdestroy("MEMORY:prtpub_cache");
+ unsetenv(KRB5_ENV_CCNAME);
+ if (old_krb5ccname) {
+ setenv(KRB5_ENV_CCNAME, old_krb5ccname, 0);
+ }
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+bool is_printer_published(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **info2)
+{
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+
+ result = winreg_printer_binding_handle(mem_ctx,
+ session_info,
+ msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ return false;
+ }
+
+ result = winreg_get_printer(mem_ctx, b,
+ printer, &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ return false;
+ }
+
+ if (!(pinfo2->attributes & PRINTER_ATTRIBUTE_PUBLISHED)) {
+ TALLOC_FREE(pinfo2);
+ return false;
+ }
+
+ if (info2) {
+ *info2 = talloc_move(mem_ctx, &pinfo2);
+ }
+ talloc_free(pinfo2);
+ return true;
+}
+#else
+WERROR nt_printer_guid_store(struct messaging_context *msg_ctx,
+ const char *printer, struct GUID guid)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR nt_printer_guid_retrieve(TALLOC_CTX *mem_ctx, const char *printer,
+ struct GUID *pguid)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR nt_printer_guid_get(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer, struct GUID *guid)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR nt_printer_publish(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int action)
+{
+ return WERR_OK;
+}
+
+WERROR check_published_printers(struct messaging_context *msg_ctx)
+{
+ return WERR_OK;
+}
+
+bool is_printer_published(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **info2)
+{
+ return False;
+}
+#endif /* HAVE_ADS */
diff --git a/source3/printing/nt_printing_migrate.c b/source3/printing/nt_printing_migrate.c
new file mode 100644
index 0000000..f56aa70
--- /dev/null
+++ b/source3/printing/nt_printing_migrate.c
@@ -0,0 +1,386 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ * Copyright (C) Bjoern Baumbach <bb@sernet.de> 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing/nt_printing_migrate.h"
+
+#include "rpc_client/rpc_client.h"
+#include "librpc/gen_ndr/ndr_ntprinting.h"
+#include "librpc/gen_ndr/ndr_spoolss_c.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+
+static const char *driver_file_basename(const char *file)
+{
+ const char *basefile;
+
+ basefile = strrchr(file, '\\');
+ if (basefile == NULL) {
+ basefile = file;
+ } else {
+ basefile++;
+ }
+
+ return basefile;
+}
+
+NTSTATUS printing_tdb_migrate_form(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct ntprinting_form r;
+ struct spoolss_AddFormInfo1 f1;
+ DATA_BLOB blob;
+ WERROR result;
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_form);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("Form pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Don't migrate builtin forms */
+ if (r.flag == SPOOLSS_FORM_BUILTIN) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(2, ("Migrating Form: %s\n", key_name));
+
+ f1.form_name = key_name;
+ f1.flags = r.flag;
+
+ f1.size.width = r.width;
+ f1.size.height = r.length;
+
+ f1.area.top = r.top;
+ f1.area.right = r.right;
+ f1.area.bottom = r.bottom;
+ f1.area.left = r.left;
+
+ result = winreg_printer_addform1(mem_ctx,
+ b,
+ &f1);
+ if (W_ERROR_EQUAL(result, WERR_FILE_EXISTS)) {
+ /* Don't migrate form if it already exists. */
+ result = WERR_OK;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS printing_tdb_migrate_driver(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct ntprinting_driver r;
+ struct spoolss_AddDriverInfoCtr d;
+ struct spoolss_AddDriverInfo3 d3;
+ struct spoolss_StringArray a;
+ DATA_BLOB blob;
+ WERROR result;
+ const char *driver_name;
+ uint32_t driver_version;
+ int i;
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ if (do_string_conversion) {
+ r.string_flags = LIBNDR_FLAG_STR_ASCII;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_driver);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("Driver pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(2, ("Migrating Printer Driver: %s\n", key_name));
+
+ ZERO_STRUCT(d3);
+ ZERO_STRUCT(a);
+
+ /* remove paths from file names */
+ if (r.dependent_files != NULL) {
+ for (i = 0 ; r.dependent_files[i] != NULL; i++) {
+ r.dependent_files[i] = driver_file_basename(r.dependent_files[i]);
+ }
+ }
+ a.string = r.dependent_files;
+
+ r.driverpath = driver_file_basename(r.driverpath);
+ r.configfile = driver_file_basename(r.configfile);
+ r.datafile = driver_file_basename(r.datafile);
+ r.helpfile = driver_file_basename(r.helpfile);
+
+ d3.architecture = r.environment;
+ d3.config_file = r.configfile;
+ d3.data_file = r.datafile;
+ d3.default_datatype = r.defaultdatatype;
+ d3.dependent_files = &a;
+ d3.driver_path = r.driverpath;
+ d3.help_file = r.helpfile;
+ d3.monitor_name = r.monitorname;
+ d3.driver_name = r.name;
+ d3.version = r.version;
+
+ d.level = 3;
+ d.info.info3 = &d3;
+
+ result = winreg_add_driver(mem_ctx,
+ b,
+ &d,
+ &driver_name,
+ &driver_version);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS printing_tdb_migrate_printer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct ntprinting_printer r;
+ struct spoolss_SetPrinterInfo2 info2;
+ struct spoolss_DeviceMode dm;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ WERROR result;
+ int j;
+ uint32_t info2_mask = (SPOOLSS_PRINTER_INFO_ALL)
+ & ~SPOOLSS_PRINTER_INFO_SECDESC;
+
+ if (strequal(key_name, "printers")) {
+ return NT_STATUS_OK;
+ }
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ if (do_string_conversion) {
+ r.info.string_flags = LIBNDR_FLAG_STR_ASCII;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t) ndr_pull_ntprinting_printer);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("printer pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(2, ("Migrating Printer: %s\n", key_name));
+
+ ZERO_STRUCT(devmode_ctr);
+
+ /* Create printer info level 2 */
+ ZERO_STRUCT(info2);
+
+ info2.attributes = r.info.attributes;
+ info2.averageppm = r.info.averageppm;
+ info2.cjobs = r.info.cjobs;
+ info2.comment = r.info.comment;
+ info2.datatype = r.info.datatype;
+ info2.defaultpriority = r.info.default_priority;
+ info2.drivername = r.info.drivername;
+ info2.location = r.info.location;
+ info2.parameters = r.info.parameters;
+ info2.portname = r.info.portname;
+ info2.printername = r.info.printername;
+ info2.printprocessor = r.info.printprocessor;
+ info2.priority = r.info.priority;
+ info2.sepfile = r.info.sepfile;
+ info2.sharename = r.info.sharename;
+ info2.starttime = r.info.starttime;
+ info2.status = r.info.status;
+ info2.untiltime = r.info.untiltime;
+
+ /* Create Device Mode */
+ if (r.devmode == NULL) {
+ info2_mask &= ~SPOOLSS_PRINTER_INFO_DEVMODE;
+ } else {
+ ZERO_STRUCT(dm);
+
+ dm.bitsperpel = r.devmode->bitsperpel;
+ dm.collate = r.devmode->collate;
+ dm.color = r.devmode->color;
+ dm.copies = r.devmode->copies;
+ dm.defaultsource = r.devmode->defaultsource;
+ dm.devicename = r.devmode->devicename;
+ dm.displayflags = r.devmode->displayflags;
+ dm.displayfrequency = r.devmode->displayfrequency;
+ dm.dithertype = r.devmode->dithertype;
+ dm.driverversion = r.devmode->driverversion;
+ dm.duplex = r.devmode->duplex;
+ dm.fields = r.devmode->fields;
+ dm.formname = r.devmode->formname;
+ dm.icmintent = r.devmode->icmintent;
+ dm.icmmethod = r.devmode->icmmethod;
+ dm.logpixels = r.devmode->logpixels;
+ dm.mediatype = r.devmode->mediatype;
+ dm.orientation = r.devmode->orientation;
+ dm.panningheight = r.devmode->pelsheight;
+ dm.panningwidth = r.devmode->panningwidth;
+ dm.paperlength = r.devmode->paperlength;
+ dm.papersize = r.devmode->papersize;
+ dm.paperwidth = r.devmode->paperwidth;
+ dm.pelsheight = r.devmode->pelsheight;
+ dm.pelswidth = r.devmode->pelswidth;
+ dm.printquality = r.devmode->printquality;
+ dm.size = r.devmode->size;
+ dm.scale = r.devmode->scale;
+ dm.specversion = r.devmode->specversion;
+ dm.ttoption = r.devmode->ttoption;
+ dm.yresolution = r.devmode->yresolution;
+
+ if (r.devmode->nt_dev_private != NULL) {
+ dm.driverextra_data.data = r.devmode->nt_dev_private->data;
+ dm.driverextra_data.length = r.devmode->nt_dev_private->length;
+ dm.__driverextra_length = r.devmode->nt_dev_private->length;
+ }
+
+ devmode_ctr.devmode = &dm;
+ }
+
+ result = winreg_update_printer(mem_ctx, b,
+ key_name,
+ info2_mask,
+ &info2,
+ &dm,
+ NULL);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("SetPrinter(%s) level 2 refused -- %s.\n",
+ key_name, win_errstr(result)));
+ status = werror_to_ntstatus(result);
+ goto done;
+ }
+
+ /* migrate printerdata */
+ for (j = 0; j < r.count; j++) {
+ char *valuename;
+ const char *keyname;
+
+ if (r.printer_data[j].type == REG_NONE) {
+ continue;
+ }
+
+ keyname = r.printer_data[j].name;
+ valuename = strchr(keyname, '\\');
+ if (valuename == NULL) {
+ continue;
+ } else {
+ valuename[0] = '\0';
+ valuename++;
+ }
+
+ result = winreg_set_printer_dataex(mem_ctx, b,
+ key_name,
+ keyname,
+ valuename,
+ r.printer_data[j].type,
+ r.printer_data[j].data.data,
+ r.printer_data[j].data.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("SetPrinterDataEx: printer [%s], keyname [%s], "
+ "valuename [%s] refused -- %s.\n",
+ key_name, keyname, valuename,
+ win_errstr(result)));
+ status = werror_to_ntstatus(result);
+ break;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ done:
+
+ return status;
+}
+
+NTSTATUS printing_tdb_migrate_secdesc(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct sec_desc_buf secdesc_ctr;
+ DATA_BLOB blob;
+ WERROR result;
+
+ if (strequal(key_name, "printers")) {
+ return NT_STATUS_OK;
+ }
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(secdesc_ctr);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &secdesc_ctr,
+ (ndr_pull_flags_fn_t)ndr_pull_sec_desc_buf);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("security descriptor pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(2, ("Migrating Security Descriptor: %s\n", key_name));
+
+ result = winreg_set_printer_secdesc(mem_ctx, b,
+ key_name,
+ secdesc_ctr.sd);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/printing/nt_printing_migrate.h b/source3/printing/nt_printing_migrate.h
new file mode 100644
index 0000000..0c9800d
--- /dev/null
+++ b/source3/printing/nt_printing_migrate.h
@@ -0,0 +1,47 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NT_PRINTING_MIGRATE_H_
+#define _NT_PRINTING_MIGRATE_H_
+
+NTSTATUS printing_tdb_migrate_form(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length);
+NTSTATUS printing_tdb_migrate_driver(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion);
+NTSTATUS printing_tdb_migrate_printer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion);
+NTSTATUS printing_tdb_migrate_secdesc(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length);
+
+#endif /* _NT_PRINTING_MIGRATE_H_ */
diff --git a/source3/printing/nt_printing_migrate_internal.c b/source3/printing/nt_printing_migrate_internal.c
new file mode 100644
index 0000000..8bcc2d4
--- /dev/null
+++ b/source3/printing/nt_printing_migrate_internal.c
@@ -0,0 +1,272 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/nt_printing_migrate.h"
+#include "printing/nt_printing_migrate_internal.h"
+
+#include "rpc_client/rpc_client.h"
+#include "librpc/gen_ndr/ndr_spoolss_c.h"
+#include "librpc/gen_ndr/ndr_winreg.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "auth.h"
+#include "util_tdb.h"
+
+#define FORMS_PREFIX "FORMS/"
+#define DRIVERS_PREFIX "DRIVERS/"
+#define PRINTERS_PREFIX "PRINTERS/"
+#define SECDESC_PREFIX "SECDESC/"
+
+static int rename_file_with_suffix(TALLOC_CTX *mem_ctx,
+ const char *path,
+ const char *suffix)
+{
+ int rc = -1;
+ char *dst_path;
+
+ dst_path = talloc_asprintf(mem_ctx, "%s%s", path, suffix);
+ if (dst_path == NULL) {
+ DEBUG(3, ("error out of memory\n"));
+ return rc;
+ }
+
+ rc = (rename(path, dst_path) != 0);
+
+ if (rc == 0) {
+ DEBUG(5, ("moved '%s' to '%s'\n", path, dst_path));
+ } else if (errno == ENOENT) {
+ DEBUG(3, ("file '%s' does not exist - so not moved\n", path));
+ rc = 0;
+ } else {
+ DEBUG(3, ("error renaming %s to %s: %s\n", path, dst_path,
+ strerror(errno)));
+ }
+
+ TALLOC_FREE(dst_path);
+ return rc;
+}
+
+static NTSTATUS migrate_internal(TALLOC_CTX *mem_ctx,
+ const char *tdb_path,
+ struct rpc_pipe_client *winreg_pipe)
+{
+ const char *backup_suffix = ".bak";
+ TDB_DATA kbuf, newkey, dbuf;
+ TDB_CONTEXT *tdb;
+ NTSTATUS status;
+ int rc;
+
+ tdb = tdb_open_log(tdb_path, 0, TDB_DEFAULT, O_RDONLY, 0600);
+ if (tdb == NULL && errno == ENOENT) {
+ /* if we have no printers database then migration is
+ considered successful */
+ DEBUG(4, ("No printers database to migrate in %s\n", tdb_path));
+ return NT_STATUS_OK;
+ }
+ if (tdb == NULL) {
+ DEBUG(2, ("Failed to open tdb file: %s\n", tdb_path));
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf = newkey)
+ {
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, FORMS_PREFIX, strlen(FORMS_PREFIX)) == 0) {
+ status = printing_tdb_migrate_form(mem_ctx,
+ winreg_pipe,
+ (const char *) kbuf.dptr + strlen(FORMS_PREFIX),
+ dbuf.dptr,
+ dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, DRIVERS_PREFIX, strlen(DRIVERS_PREFIX)) == 0) {
+ status = printing_tdb_migrate_driver(mem_ctx,
+ winreg_pipe,
+ (const char *) kbuf.dptr + strlen(DRIVERS_PREFIX),
+ dbuf.dptr,
+ dbuf.dsize,
+ false);
+ SAFE_FREE(dbuf.dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, PRINTERS_PREFIX, strlen(PRINTERS_PREFIX)) == 0) {
+ const char *printer_name = (const char *)(kbuf.dptr
+ + strlen(PRINTERS_PREFIX));
+ status = printing_tdb_migrate_printer(mem_ctx,
+ winreg_pipe,
+ printer_name,
+ dbuf.dptr,
+ dbuf.dsize,
+ false);
+ SAFE_FREE(dbuf.dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf = newkey)
+ {
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX)) == 0) {
+ const char *secdesc_name = (const char *)(kbuf.dptr
+ + strlen(SECDESC_PREFIX));
+ status = printing_tdb_migrate_secdesc(mem_ctx,
+ winreg_pipe,
+ secdesc_name,
+ dbuf.dptr,
+ dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ if (NT_STATUS_EQUAL(status, werror_to_ntstatus(WERR_FILE_NOT_FOUND))) {
+ DEBUG(2, ("Skipping secdesc migration for non-existent "
+ "printer: %s\n", secdesc_name));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ tdb_close(tdb);
+
+ rc = rename_file_with_suffix(mem_ctx, tdb_path, backup_suffix);
+ if (rc != 0) {
+ DEBUG(0, ("Error moving tdb to '%s%s'\n",
+ tdb_path, backup_suffix));
+ }
+
+ return NT_STATUS_OK;
+}
+
+bool nt_printing_tdb_migrate(struct messaging_context *msg_ctx)
+{
+ const char *drivers_path;
+ const char *printers_path;
+ const char *forms_path;
+ bool drivers_exists;
+ bool printers_exists;
+ bool forms_exists;
+ struct auth_session_info *session_info;
+ struct rpc_pipe_client *winreg_pipe = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+
+ /* paths talloced on new stackframe */
+ drivers_path = state_path(talloc_tos(), "ntdrivers.tdb");
+ printers_path = state_path(talloc_tos(), "ntprinters.tdb");
+ forms_path = state_path(talloc_tos(), "ntforms.tdb");
+ if ((drivers_path == NULL) || (printers_path == NULL)
+ || (forms_path == NULL)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ drivers_exists = file_exist(drivers_path);
+ printers_exists = file_exist(printers_path);
+ forms_exists = file_exist(forms_path);
+
+ if (!drivers_exists && !printers_exists && !forms_exists) {
+ talloc_free(tmp_ctx);
+ return true;
+ }
+
+ status = make_session_info_system(tmp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't create session_info: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ status = rpc_pipe_open_interface(tmp_ctx,
+ &ndr_table_winreg,
+ session_info,
+ NULL,
+ NULL,
+ msg_ctx,
+ &winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't open internal winreg pipe: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ if (drivers_exists) {
+ status = migrate_internal(tmp_ctx, drivers_path, winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't migrate drivers tdb file: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ if (printers_exists) {
+ status = migrate_internal(tmp_ctx, printers_path, winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't migrate printers tdb file: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ if (forms_exists) {
+ status = migrate_internal(tmp_ctx, forms_path, winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't migrate forms tdb file: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return true;
+}
diff --git a/source3/printing/nt_printing_migrate_internal.h b/source3/printing/nt_printing_migrate_internal.h
new file mode 100644
index 0000000..dfcf914
--- /dev/null
+++ b/source3/printing/nt_printing_migrate_internal.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NT_PRINTING_MIGRATE_INTERNAL_H_
+#define _NT_PRINTING_MIGRATE_INTERNAL_H_
+
+bool nt_printing_tdb_migrate(struct messaging_context *msg_ctx);
+
+#endif /* _NT_PRINTING_MIGRATE_INTERNAL_H_ */
diff --git a/source3/printing/nt_printing_os2.c b/source3/printing/nt_printing_os2.c
new file mode 100644
index 0000000..82b8248
--- /dev/null
+++ b/source3/printing/nt_printing_os2.c
@@ -0,0 +1,167 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing/nt_printing_os2.h"
+
+/****************************************************************************
+ ***************************************************************************/
+
+static char *win_driver;
+static char *os2_driver;
+
+static const char *get_win_driver(void)
+{
+ if (win_driver == NULL) {
+ return "";
+ }
+ return win_driver;
+}
+
+static const char *get_os2_driver(void)
+{
+ if (os2_driver == NULL) {
+ return "";
+ }
+ return os2_driver;
+}
+
+static bool set_driver_mapping(const char *from, const char *to)
+{
+ SAFE_FREE(win_driver);
+ SAFE_FREE(os2_driver);
+
+ win_driver = SMB_STRDUP(from);
+ os2_driver = SMB_STRDUP(to);
+
+ if (win_driver == NULL || os2_driver == NULL) {
+ SAFE_FREE(win_driver);
+ SAFE_FREE(os2_driver);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * @internal
+ *
+ * @brief Map a Windows driver to a OS/2 driver.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in,out] pdrivername The drivername of Windows to remap.
+ *
+ * @return WERR_OK on success, a corresponding WERROR on failure.
+ */
+WERROR spoolss_map_to_os2_driver(TALLOC_CTX *mem_ctx, const char **pdrivername)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *mapfile = lp_os2_driver_map(talloc_tos(), lp_sub);
+ char **lines = NULL;
+ const char *drivername;
+ int numlines = 0;
+ int i;
+
+ if (pdrivername == NULL || *pdrivername == NULL || *pdrivername[0] == '\0') {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ drivername = *pdrivername;
+
+ if (mapfile[0] == '\0') {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ if (strequal(drivername, get_win_driver())) {
+ DEBUG(3,("Mapped Windows driver %s to OS/2 driver %s\n",
+ drivername, get_os2_driver()));
+ drivername = talloc_strdup(mem_ctx, get_os2_driver());
+ if (drivername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *pdrivername = drivername;
+ return WERR_OK;
+ }
+
+ lines = file_lines_load(mapfile, &numlines, 0, NULL);
+ if (numlines == 0 || lines == NULL) {
+ DEBUG(0,("No entries in OS/2 driver map %s\n", mapfile));
+ TALLOC_FREE(lines);
+ return WERR_EMPTY;
+ }
+
+ DEBUG(4,("Scanning OS/2 driver map %s\n",mapfile));
+
+ for( i = 0; i < numlines; i++) {
+ char *nt_name = lines[i];
+ char *os2_name = strchr(nt_name, '=');
+
+ if (os2_name == NULL) {
+ continue;
+ }
+
+ *os2_name++ = '\0';
+
+ while (isspace(*nt_name)) {
+ nt_name++;
+ }
+
+ if (*nt_name == '\0' || strchr("#;", *nt_name)) {
+ continue;
+ }
+
+ {
+ int l = strlen(nt_name);
+ while (l && isspace(nt_name[l - 1])) {
+ nt_name[l - 1] = 0;
+ l--;
+ }
+ }
+
+ while (isspace(*os2_name)) {
+ os2_name++;
+ }
+
+ {
+ int l = strlen(os2_name);
+ while (l && isspace(os2_name[l-1])) {
+ os2_name[l-1] = 0;
+ l--;
+ }
+ }
+
+ if (strequal(nt_name, drivername)) {
+ DEBUG(3,("Mapped Windows driver %s to OS/2 driver %s\n",drivername,os2_name));
+ set_driver_mapping(drivername, os2_name);
+ drivername = talloc_strdup(mem_ctx, os2_name);
+ TALLOC_FREE(lines);
+ if (drivername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *pdrivername = drivername;
+ return WERR_OK;
+ }
+ }
+
+ TALLOC_FREE(lines);
+ return WERR_OK;
+}
diff --git a/source3/printing/nt_printing_os2.h b/source3/printing/nt_printing_os2.h
new file mode 100644
index 0000000..0ef07de
--- /dev/null
+++ b/source3/printing/nt_printing_os2.h
@@ -0,0 +1,22 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+WERROR spoolss_map_to_os2_driver(TALLOC_CTX *mem_ctx, const char **pdrivername);
diff --git a/source3/printing/nt_printing_tdb.c b/source3/printing/nt_printing_tdb.c
new file mode 100644
index 0000000..eddefe5
--- /dev/null
+++ b/source3/printing/nt_printing_tdb.c
@@ -0,0 +1,498 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (c) Andrew Tridgell 1992-2000,
+ * Copyright (c) Jean François Micouleau 1998-2000.
+ * Copyright (c) Gerald Carter 2002-2005.
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/nt_printing_tdb.h"
+#include "librpc/gen_ndr/spoolss.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+#define FORMS_PREFIX "FORMS/"
+#define DRIVERS_PREFIX "DRIVERS/"
+#define PRINTERS_PREFIX "PRINTERS/"
+#define SECDESC_PREFIX "SECDESC/"
+
+#define NTDRIVERS_DATABASE_VERSION_1 1
+#define NTDRIVERS_DATABASE_VERSION_2 2
+#define NTDRIVERS_DATABASE_VERSION_3 3 /* little endian version of v2 */
+#define NTDRIVERS_DATABASE_VERSION_4 4 /* fix generic bits in security descriptors */
+#define NTDRIVERS_DATABASE_VERSION_5 5 /* normalize keys in ntprinters.tdb */
+
+static TDB_CONTEXT *tdb_forms; /* used for forms files */
+static TDB_CONTEXT *tdb_drivers; /* used for driver files */
+static TDB_CONTEXT *tdb_printers; /* used for printers files */
+
+/****************************************************************************
+ generate a new TDB_DATA key for storing a printer
+****************************************************************************/
+
+static TDB_DATA make_printer_tdbkey(TALLOC_CTX *ctx, const char *sharename )
+{
+ fstring share;
+ char *keystr = NULL;
+ TDB_DATA key;
+
+ fstrcpy(share, sharename);
+ (void)strlower_m(share);
+
+ keystr = talloc_asprintf(ctx, "%s%s", PRINTERS_PREFIX, share);
+ key = string_term_tdb_data(keystr ? keystr : "");
+
+ return key;
+}
+
+/****************************************************************************
+ generate a new TDB_DATA key for storing a printer security descriptor
+****************************************************************************/
+
+static TDB_DATA make_printers_secdesc_tdbkey(TALLOC_CTX *ctx,
+ const char* sharename )
+{
+ fstring share;
+ char *keystr = NULL;
+ TDB_DATA key;
+
+ fstrcpy(share, sharename );
+ (void)strlower_m(share);
+
+ keystr = talloc_asprintf(ctx, "%s%s", SECDESC_PREFIX, share);
+ key = string_term_tdb_data(keystr ? keystr : "");
+
+ return key;
+}
+
+/****************************************************************************
+ Upgrade the tdb files to version 3
+****************************************************************************/
+
+static bool upgrade_to_version_3(void)
+{
+ TDB_DATA kbuf, newkey, dbuf;
+
+ DEBUG(0,("upgrade_to_version_3: upgrading print tdb's to version 3\n"));
+
+ for (kbuf = tdb_firstkey(tdb_drivers); kbuf.dptr;
+ newkey = tdb_nextkey(tdb_drivers, kbuf), free(kbuf.dptr), kbuf=newkey) {
+
+ dbuf = tdb_fetch(tdb_drivers, kbuf);
+
+ if (strncmp((const char *)kbuf.dptr, FORMS_PREFIX, strlen(FORMS_PREFIX)) == 0) {
+ DEBUG(0,("upgrade_to_version_3:moving form\n"));
+ if (tdb_store(tdb_forms, kbuf, dbuf, TDB_REPLACE) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to move form. Error (%s).\n", tdb_errorstr(tdb_forms)));
+ return False;
+ }
+ if (tdb_delete(tdb_drivers, kbuf) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to delete form. Error (%s)\n", tdb_errorstr(tdb_drivers)));
+ return False;
+ }
+ }
+
+ if (strncmp((const char *)kbuf.dptr, PRINTERS_PREFIX, strlen(PRINTERS_PREFIX)) == 0) {
+ DEBUG(0,("upgrade_to_version_3:moving printer\n"));
+ if (tdb_store(tdb_printers, kbuf, dbuf, TDB_REPLACE) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to move printer. Error (%s)\n", tdb_errorstr(tdb_printers)));
+ return False;
+ }
+ if (tdb_delete(tdb_drivers, kbuf) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to delete printer. Error (%s)\n", tdb_errorstr(tdb_drivers)));
+ return False;
+ }
+ }
+
+ if (strncmp((const char *)kbuf.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX)) == 0) {
+ DEBUG(0,("upgrade_to_version_3:moving secdesc\n"));
+ if (tdb_store(tdb_printers, kbuf, dbuf, TDB_REPLACE) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to move secdesc. Error (%s)\n", tdb_errorstr(tdb_printers)));
+ return False;
+ }
+ if (tdb_delete(tdb_drivers, kbuf) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to delete secdesc. Error (%s)\n", tdb_errorstr(tdb_drivers)));
+ return False;
+ }
+ }
+
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ return True;
+}
+
+/*******************************************************************
+ Fix an issue with security descriptors. Printer sec_desc must
+ use more than the generic bits that were previously used
+ in <= 3.0.14a. They must also have a owner and group SID assigned.
+ Otherwise, any printers than have been migrated to a Windows
+ host using printmig.exe will not be accessible.
+*******************************************************************/
+
+static int sec_desc_upg_fn( TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *state )
+{
+ NTSTATUS status;
+ struct sec_desc_buf *sd_orig = NULL;
+ struct sec_desc_buf *sd_new, *sd_store;
+ struct security_descriptor *sec, *new_sec;
+ TALLOC_CTX *ctx = state;
+ int result, i;
+ size_t size_new_sec;
+
+ if (!data.dptr || data.dsize == 0) {
+ return 0;
+ }
+
+ if ( strncmp((const char *) key.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX) ) != 0 ) {
+ return 0;
+ }
+
+ /* upgrade the security descriptor */
+
+ status = unmarshall_sec_desc_buf(ctx, data.dptr, data.dsize, &sd_orig);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* delete bad entries */
+ DEBUG(0,("sec_desc_upg_fn: Failed to parse original sec_desc for %si. Deleting....\n",
+ (const char *)key.dptr ));
+ tdb_delete( tdb_printers, key );
+ return 0;
+ }
+
+ if (!sd_orig) {
+ return 0;
+ }
+ sec = sd_orig->sd;
+
+ /* is this even valid? */
+
+ if ( !sec->dacl ) {
+ return 0;
+ }
+
+ /* update access masks */
+
+ for ( i=0; i<sec->dacl->num_aces; i++ ) {
+ switch ( sec->dacl->aces[i].access_mask ) {
+ case (GENERIC_READ_ACCESS | GENERIC_WRITE_ACCESS | GENERIC_EXECUTE_ACCESS):
+ sec->dacl->aces[i].access_mask = PRINTER_ACE_PRINT;
+ break;
+
+ case GENERIC_ALL_ACCESS:
+ sec->dacl->aces[i].access_mask = PRINTER_ACE_FULL_CONTROL;
+ break;
+
+ case READ_CONTROL_ACCESS:
+ sec->dacl->aces[i].access_mask = PRINTER_ACE_MANAGE_DOCUMENTS;
+
+ break;
+ default: /* no change */
+ break;
+ }
+ }
+
+ /* create a new struct security_descriptor with the appropriate owner and group SIDs */
+
+ new_sec = make_sec_desc( ctx, SD_REVISION, SEC_DESC_SELF_RELATIVE,
+ &global_sid_Builtin_Administrators,
+ &global_sid_Builtin_Administrators,
+ NULL, NULL, &size_new_sec );
+ if (!new_sec) {
+ return 0;
+ }
+ sd_new = make_sec_desc_buf( ctx, size_new_sec, new_sec );
+ if (!sd_new) {
+ return 0;
+ }
+
+ if ( !(sd_store = sec_desc_merge_buf( ctx, sd_new, sd_orig )) ) {
+ DEBUG(0,("sec_desc_upg_fn: Failed to update sec_desc for %s\n", key.dptr ));
+ return 0;
+ }
+
+ /* store it back */
+
+ status = marshall_sec_desc_buf(ctx, sd_store, &data.dptr, &data.dsize);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("sec_desc_upg_fn: Failed to parse new sec_desc for %s\n", key.dptr ));
+ return 0;
+ }
+
+ result = tdb_store( tdb_printers, key, data, TDB_REPLACE );
+
+ /* 0 to continue and non-zero to stop traversal */
+
+ return (result != 0);
+}
+
+/*******************************************************************
+ Upgrade the tdb files to version 4
+*******************************************************************/
+
+static bool upgrade_to_version_4(void)
+{
+ TALLOC_CTX *ctx;
+ int result;
+
+ DEBUG(0,("upgrade_to_version_4: upgrading printer security descriptors\n"));
+
+ if ( !(ctx = talloc_init( "upgrade_to_version_4" )) )
+ return False;
+
+ result = tdb_traverse( tdb_printers, sec_desc_upg_fn, ctx );
+
+ talloc_destroy( ctx );
+
+ return ( result >= 0 );
+}
+
+/*******************************************************************
+ Fix an issue with security descriptors. Printer sec_desc must
+ use more than the generic bits that were previously used
+ in <= 3.0.14a. They must also have a owner and group SID assigned.
+ Otherwise, any printers than have been migrated to a Windows
+ host using printmig.exe will not be accessible.
+*******************************************************************/
+
+static int normalize_printers_fn( TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *state )
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ TDB_DATA new_key;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ /* upgrade printer records and security descriptors */
+
+ if ( strncmp((const char *) key.dptr, PRINTERS_PREFIX, strlen(PRINTERS_PREFIX) ) == 0 ) {
+ new_key = make_printer_tdbkey(ctx, (const char *)key.dptr+strlen(PRINTERS_PREFIX) );
+ }
+ else if ( strncmp((const char *) key.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX) ) == 0 ) {
+ new_key = make_printers_secdesc_tdbkey(ctx, (const char *)key.dptr+strlen(SECDESC_PREFIX) );
+ }
+ else {
+ /* ignore this record */
+ return 0;
+ }
+
+ /* delete the original record and store under the normalized key */
+
+ if ( tdb_delete( the_tdb, key ) != 0 ) {
+ DEBUG(0,("normalize_printers_fn: tdb_delete for [%s] failed!\n",
+ key.dptr));
+ return 1;
+ }
+
+ if ( tdb_store( the_tdb, new_key, data, TDB_REPLACE) != 0 ) {
+ DEBUG(0,("normalize_printers_fn: failed to store new record for [%s]!\n",
+ key.dptr));
+ return 1;
+ }
+
+ return 0;
+}
+
+/*******************************************************************
+ Upgrade the tdb files to version 5
+*******************************************************************/
+
+static bool upgrade_to_version_5(void)
+{
+ TALLOC_CTX *ctx;
+ int result;
+
+ DEBUG(0,("upgrade_to_version_5: normalizing printer keys\n"));
+
+ if ( !(ctx = talloc_init( "upgrade_to_version_5" )) )
+ return False;
+
+ result = tdb_traverse( tdb_printers, normalize_printers_fn, NULL );
+
+ talloc_destroy( ctx );
+
+ return ( result >= 0 );
+}
+
+bool nt_printing_tdb_upgrade(void)
+{
+ char *drivers_path;
+ char *printers_path;
+ char *forms_path;
+ bool drivers_exists;
+ bool printers_exists;
+ bool forms_exists;
+ const char *vstring = "INFO/version";
+ int32_t vers_id;
+ bool ret;
+
+ drivers_path = state_path(talloc_tos(), "ntdrivers.tdb");
+ if (drivers_path == NULL) {
+ ret = false;
+ goto err_out;
+ }
+ printers_path = state_path(talloc_tos(), "ntprinters.tdb");
+ if (printers_path == NULL) {
+ ret = false;
+ goto err_drvdb_free;
+ }
+ forms_path = state_path(talloc_tos(), "ntforms.tdb");
+ if (forms_path == NULL) {
+ ret = false;
+ goto err_prdb_free;
+ }
+
+ drivers_exists = file_exist(drivers_path);
+ printers_exists = file_exist(printers_path);
+ forms_exists = file_exist(forms_path);
+
+ if (!drivers_exists && !printers_exists && !forms_exists) {
+ ret = true;
+ goto err_formsdb_free;
+ }
+
+ tdb_drivers = tdb_open_log(drivers_path,
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0600);
+ if (tdb_drivers == NULL) {
+ DEBUG(0,("nt_printing_init: Failed to open nt drivers "
+ "database %s (%s)\n",
+ drivers_path, strerror(errno)));
+ ret = false;
+ goto err_formsdb_free;
+ }
+
+ tdb_printers = tdb_open_log(printers_path,
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0600);
+ if (tdb_printers == NULL) {
+ DEBUG(0,("nt_printing_init: Failed to open nt printers "
+ "database %s (%s)\n",
+ printers_path, strerror(errno)));
+ ret = false;
+ goto err_drvdb_close;
+ }
+
+ tdb_forms = tdb_open_log(forms_path,
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0600);
+ if (tdb_forms == NULL) {
+ DEBUG(0,("nt_printing_init: Failed to open nt forms "
+ "database %s (%s)\n",
+ forms_path, strerror(errno)));
+ ret = false;
+ goto err_prdb_close;
+ }
+
+ /* Samba upgrade */
+ vers_id = tdb_fetch_int32(tdb_drivers, vstring);
+ if (vers_id == -1) {
+ DEBUG(10, ("Fresh database\n"));
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_5);
+ vers_id = NTDRIVERS_DATABASE_VERSION_5;
+ }
+
+ if (vers_id != NTDRIVERS_DATABASE_VERSION_5) {
+ if ((vers_id == NTDRIVERS_DATABASE_VERSION_1) ||
+ (IREV(vers_id) == NTDRIVERS_DATABASE_VERSION_1)) {
+ if (!upgrade_to_version_3()) {
+ ret = false;
+ goto err_formsdb_close;
+ }
+
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_3);
+ vers_id = NTDRIVERS_DATABASE_VERSION_3;
+ }
+
+ if ((vers_id == NTDRIVERS_DATABASE_VERSION_2) ||
+ (IREV(vers_id) == NTDRIVERS_DATABASE_VERSION_2)) {
+ /*
+ * Written on a bigendian machine with old fetch_int
+ * code. Save as le. The only upgrade between V2 and V3
+ * is to save the version in little-endian.
+ */
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_3);
+ vers_id = NTDRIVERS_DATABASE_VERSION_3;
+ }
+
+ if (vers_id == NTDRIVERS_DATABASE_VERSION_3) {
+ if (!upgrade_to_version_4()) {
+ ret = false;
+ goto err_formsdb_close;
+ }
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_4);
+ vers_id = NTDRIVERS_DATABASE_VERSION_4;
+ }
+
+ if (vers_id == NTDRIVERS_DATABASE_VERSION_4 ) {
+ if (!upgrade_to_version_5()) {
+ ret = false;
+ goto err_formsdb_close;
+ }
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_5);
+ vers_id = NTDRIVERS_DATABASE_VERSION_5;
+ }
+
+ if (vers_id != NTDRIVERS_DATABASE_VERSION_5) {
+ DEBUG(0,("nt_printing_init: Unknown printer database version [%d]\n", vers_id));
+ ret = false;
+ goto err_formsdb_close;
+ }
+ }
+ ret = true;
+
+err_formsdb_close:
+ if (tdb_forms) {
+ tdb_close(tdb_forms);
+ tdb_forms = NULL;
+ }
+err_prdb_close:
+ if (tdb_printers) {
+ tdb_close(tdb_printers);
+ tdb_printers = NULL;
+ }
+err_drvdb_close:
+ if (tdb_drivers) {
+ tdb_close(tdb_drivers);
+ tdb_drivers = NULL;
+ }
+err_formsdb_free:
+ talloc_free(forms_path);
+err_prdb_free:
+ talloc_free(printers_path);
+err_drvdb_free:
+ talloc_free(drivers_path);
+err_out:
+ return ret;
+}
diff --git a/source3/printing/nt_printing_tdb.h b/source3/printing/nt_printing_tdb.h
new file mode 100644
index 0000000..81e1813
--- /dev/null
+++ b/source3/printing/nt_printing_tdb.h
@@ -0,0 +1,28 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (c) Andrew Tridgell 1992-2000,
+ * Copyright (c) Jean François Micouleau 1998-2000.
+ * Copyright (c) Gerald Carter 2002-2005.
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NT_PRINTING_TDB_H_
+#define _NT_PRINTING_TDB_H_
+
+bool nt_printing_tdb_upgrade(void);
+
+#endif /* _NT_PRINTING_TDB_H_ */
diff --git a/source3/printing/pcap.c b/source3/printing/pcap.c
new file mode 100644
index 0000000..ec68d29
--- /dev/null
+++ b/source3/printing/pcap.c
@@ -0,0 +1,213 @@
+/*
+ Unix SMB/CIFS implementation.
+ printcap parsing
+ Copyright (C) Karl Auer 1993-1998
+
+ Re-working by Martin Kiff, 1994
+
+ Re-written again by Andrew Tridgell
+
+ Modified for SVID support by Norm Jacobs, 1997
+
+ Modified for CUPS support by Michael Sweet, 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Modified to call SVID/XPG4 support if printcap name is set to "lpstat"
+ * in smb.conf under Solaris.
+ *
+ * Modified to call CUPS support if printcap name is set to "cups"
+ * in smb.conf.
+ *
+ * Modified to call iPrint support if printcap name is set to "iprint"
+ * in smb.conf.
+ */
+
+#include "includes.h"
+#include "printing/pcap.h"
+#include "printer_list.h"
+
+struct pcap_cache {
+ char *name;
+ char *comment;
+ char *location;
+ struct pcap_cache *next;
+};
+
+bool pcap_cache_add_specific(struct pcap_cache **ppcache, const char *name, const char *comment, const char *location)
+{
+ struct pcap_cache *p;
+
+ if (name == NULL || ((p = SMB_MALLOC_P(struct pcap_cache)) == NULL))
+ return false;
+
+ p->name = SMB_STRDUP(name);
+ p->comment = (comment && *comment) ? SMB_STRDUP(comment) : NULL;
+ p->location = (location && *location) ? SMB_STRDUP(location) : NULL;
+
+ DEBUG(11,("pcap_cache_add_specific: Adding name %s info %s, location: %s\n",
+ p->name, p->comment ? p->comment : "",
+ p->location ? p->location : ""));
+
+ p->next = *ppcache;
+ *ppcache = p;
+
+ return true;
+}
+
+void pcap_cache_destroy_specific(struct pcap_cache **pp_cache)
+{
+ struct pcap_cache *p, *next;
+
+ for (p = *pp_cache; p != NULL; p = next) {
+ next = p->next;
+
+ SAFE_FREE(p->name);
+ SAFE_FREE(p->comment);
+ SAFE_FREE(p->location);
+ SAFE_FREE(p);
+ }
+ *pp_cache = NULL;
+}
+
+bool pcap_cache_replace(const struct pcap_cache *pcache)
+{
+ const struct pcap_cache *p;
+ NTSTATUS status;
+ time_t t = time_mono(NULL);
+
+ status = printer_list_mark_reload();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to mark printer list for reload!\n"));
+ return false;
+ }
+
+ for (p = pcache; p; p = p->next) {
+ status = printer_list_set_printer(talloc_tos(), p->name,
+ p->comment, p->location, t);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ }
+
+ status = printer_list_clean_old();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to cleanup printer list!\n"));
+ return false;
+ }
+
+ return true;
+}
+
+void pcap_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *))
+{
+ const char *pcap_name = lp_printcapname();
+ bool pcap_reloaded = False;
+ bool post_cache_fill_fn_handled = false;
+ struct pcap_cache *pcache = NULL;
+
+ DEBUG(3, ("reloading printcap cache\n"));
+
+ if (!lp_load_printers()) {
+ DBG_NOTICE("skipping reload - load printers disabled\n");
+ return;
+ }
+
+ /* only go looking if a printcap name is supplied */
+ if (pcap_name == NULL || *pcap_name == 0) {
+ DEBUG(0, ("No printcap file name configured!\n"));
+ return;
+ }
+
+#ifdef HAVE_CUPS
+ if (strequal(pcap_name, "cups")) {
+ pcap_reloaded = cups_cache_reload(ev, msg_ctx,
+ post_cache_fill_fn);
+ /*
+ * cups_cache_reload() is async and calls post_cache_fill_fn()
+ * on successful completion
+ */
+ post_cache_fill_fn_handled = true;
+ goto done;
+ }
+#endif
+
+#ifdef HAVE_IPRINT
+ if (strequal(pcap_name, "iprint")) {
+ pcap_reloaded = iprint_cache_reload(&pcache);
+ goto done;
+ }
+#endif
+
+#if defined(SYSV) || defined(HPUX)
+ if (strequal(pcap_name, "lpstat")) {
+ pcap_reloaded = sysv_cache_reload(&pcache);
+ goto done;
+ }
+#endif
+
+#ifdef AIX
+ if (strstr_m(pcap_name, "/qconfig") != NULL) {
+ pcap_reloaded = aix_cache_reload(&pcache);
+ goto done;
+ }
+#endif
+
+ pcap_reloaded = std_pcap_cache_reload(pcap_name, &pcache);
+
+/* Fix silly compiler warning about done not being used if none of the above
+ * ifdefs are used */
+#if defined(HAVE_CUPS) || defined(HAVE_IPRINT) || defined(SYSV) || defined(HPUX) || defined(AIX)
+done:
+#endif
+ DEBUG(3, ("reload status: %s\n", (pcap_reloaded) ? "ok" : "error"));
+
+ if ((pcap_reloaded) && (post_cache_fill_fn_handled == false)) {
+ /* cleanup old entries only if the operation was successful,
+ * otherwise keep around the old entries until we can
+ * successfully reload */
+
+ if (!pcap_cache_replace(pcache)) {
+ DEBUG(0, ("Failed to replace printer list!\n"));
+ }
+
+ if (post_cache_fill_fn != NULL) {
+ post_cache_fill_fn(ev, msg_ctx);
+ }
+ }
+ pcap_cache_destroy_specific(&pcache);
+
+ return;
+}
+
+/***************************************************************************
+run a function on each printer name in the printcap file.
+***************************************************************************/
+
+void pcap_printer_fn_specific(const struct pcap_cache *pc,
+ void (*fn)(const char *, const char *, const char *, void *),
+ void *pdata)
+{
+ const struct pcap_cache *p;
+
+ for (p = pc; p != NULL; p = p->next)
+ fn(p->name, p->comment, p->location, pdata);
+
+ return;
+}
diff --git a/source3/printing/pcap.h b/source3/printing/pcap.h
new file mode 100644
index 0000000..99a0a91
--- /dev/null
+++ b/source3/printing/pcap.h
@@ -0,0 +1,69 @@
+/*
+ Unix SMB/CIFS implementation.
+ printcap headers
+
+ Copyright (C) Karl Auer 1993-1998
+
+ Re-working by Martin Kiff, 1994
+
+ Re-written again by Andrew Tridgell
+
+ Modified for SVID support by Norm Jacobs, 1997
+
+ Modified for CUPS support by Michael Sweet, 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PRINTING_PCAP_H_
+#define _PRINTING_PCAP_H_
+
+struct pcap_cache;
+
+/* The following definitions come from printing/pcap.c */
+
+bool pcap_cache_add_specific(struct pcap_cache **ppcache, const char *name, const char *comment, const char *location);
+void pcap_cache_destroy_specific(struct pcap_cache **ppcache);
+bool pcap_cache_replace(const struct pcap_cache *cache);
+void pcap_printer_fn_specific(const struct pcap_cache *, void (*fn)(const char *, const char *, const char *, void *), void *);
+
+void pcap_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *));
+bool pcap_printername_ok(const char *printername);
+
+/* The following definitions come from printing/print_aix.c */
+
+bool aix_cache_reload(struct pcap_cache **_pcache);
+
+/* The following definitions come from printing/print_cups.c */
+
+bool cups_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *));
+
+/* The following definitions come from printing/print_iprint.c */
+
+bool iprint_cache_reload(struct pcap_cache **_pcache);
+
+/* The following definitions come from printing/print_svid.c */
+
+bool sysv_cache_reload(struct pcap_cache **_pcache);
+
+/* The following definitions come from printing/print_standard.c */
+bool std_pcap_cache_reload(const char *pcap_name, struct pcap_cache **_pcache);
+
+#endif /* _PRINTING_PCAP_H_ */
diff --git a/source3/printing/print_aix.c b/source3/printing/print_aix.c
new file mode 100644
index 0000000..d3836a4
--- /dev/null
+++ b/source3/printing/print_aix.c
@@ -0,0 +1,140 @@
+/*
+ AIX-specific printcap loading
+ Copyright (C) Jean-Pierre.Boulard@univ-rennes1.fr 1996
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This module implements AIX-specific printcap loading. Most of the code
+ * here was originally provided by Jean-Pierre.Boulard@univ-rennes1.fr in
+ * the Samba 1.9.14 release, and was formerly contained in pcap.c. It has
+ * been moved here and condensed as part of a larger effort to clean up and
+ * simplify the printcap code. -- Rob Foehl, 2004/12/06
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/pcap.h"
+
+#ifdef AIX
+bool aix_cache_reload(struct pcap_cache **_pcache)
+{
+ int iEtat;
+ FILE *pfile;
+ char *line = NULL, *p;
+ char *name = NULL;
+ struct pcap_cache *pcache = NULL;
+ TALLOC_CTX *ctx = talloc_init("aix_cache_reload");
+
+ if (!ctx) {
+ return false;
+ }
+
+ DEBUG(5, ("reloading aix printcap cache\n"));
+
+ if ((pfile = fopen(lp_printcapname(), "r")) == NULL) {
+ DEBUG(0,( "Unable to open qconfig file %s for read!\n", lp_printcapname()));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ iEtat = 0;
+ /* scan qconfig file for searching <printername>: */
+ while (line = fgets_slash(ctx, NULL, 1024, pfile)) {
+ bool ok;
+
+ if (*line == '*' || *line == 0) {
+ TALLOC_FREE(line);
+ continue;
+ }
+
+ switch (iEtat) {
+ case 0: /* locate an entry */
+ if (*line == '\t' || *line == ' ') {
+ TALLOC_FREE(line);
+ continue;
+ }
+
+ if ((p = strchr_m(line, ':'))) {
+ char *saveptr;
+ *p = '\0';
+ p = strtok_r(line, ":", &saveptr);
+ if (strcmp(p, "bsh") != 0) {
+ name = talloc_strdup(ctx, p);
+ if (!name) {
+ pcap_cache_destroy_specific(&pcache);
+ TALLOC_FREE(line);
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ iEtat = 1;
+ continue;
+ }
+ }
+ break;
+
+ case 1: /* scanning device stanza */
+ if (*line == '*' || *line == 0)
+ continue;
+
+ if (*line != '\t' && *line != ' ') {
+ /* name is found without stanza device */
+ /* probably a good printer ??? */
+ iEtat = 0;
+ ok = pcap_cache_add_specific(&pcache,
+ name, NULL, NULL);
+ if (!ok) {
+ pcap_cache_destroy_specific(&pcache);
+ TALLOC_FREE(line);
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ continue;
+ }
+
+ if (strstr_m(line, "backend")) {
+ /* it's a device, not a virtual printer */
+ iEtat = 0;
+ } else if (strstr_m(line, "device")) {
+ /* it's a good virtual printer */
+ iEtat = 0;
+ ok = pcap_cache_add_specific(&pcache,
+ name, NULL, NULL);
+ if (!ok) {
+ pcap_cache_destroy_specific(&pcache);
+ SAFE_FREE(line);
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ continue;
+ }
+ break;
+ }
+ }
+
+ *_pcache = pcache;
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return true;
+}
+
+#else
+/* this keeps fussy compilers happy */
+ void print_aix_dummy(void);
+ void print_aix_dummy(void) {}
+#endif /* AIX */
diff --git a/source3/printing/print_cups.c b/source3/printing/print_cups.c
new file mode 100644
index 0000000..0bbd7c8
--- /dev/null
+++ b/source3/printing/print_cups.c
@@ -0,0 +1,1749 @@
+/*
+ * Support code for the Common UNIX Printing System ("CUPS")
+ *
+ * Copyright 1999-2003 by Michael R Sweet.
+ * Copyright 2008 Jeremy Allison.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * JRA. Converted to utf8 pull/push.
+ */
+
+#include "includes.h"
+#include "printing.h"
+#include "printing/pcap.h"
+#include "librpc/gen_ndr/ndr_printcap.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_CUPS
+#include <cups/cups.h>
+#include <cups/language.h>
+#include <cups/http.h>
+
+/* CUPS prior to version 1.7 doesn't have HTTP_URI_STATUS_OK */
+#if (CUPS_VERSION_MAJOR == 1) && (CUPS_VERSION_MINOR < 7)
+#define HTTP_URI_STATUS_OK HTTP_URI_OK
+#endif
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetGroupTag(attr) attr->group_tag
+#define ippGetName(attr) attr->name
+#define ippGetValueTag(attr) attr->value_tag
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+
+static ipp_attribute_t *
+ippFirstAttribute(ipp_t *ipp)
+{
+ if (!ipp)
+ return (NULL);
+ return (ipp->current = ipp->attrs);
+}
+
+static ipp_attribute_t *
+ippNextAttribute(ipp_t *ipp)
+{
+ if (!ipp || !ipp->current)
+ return (NULL);
+ return (ipp->current = ipp->current->next);
+}
+
+static int ippSetOperation(ipp_t *ipp, ipp_op_t op)
+{
+ ipp->request.op.operation_id = op;
+ return (1);
+}
+
+static int ippSetRequestId(ipp_t *ipp, int request_id)
+{
+ ipp->request.any.request_id = request_id;
+ return (1);
+}
+#endif
+
+static SIG_ATOMIC_T gotalarm;
+
+/***************************************************************
+ Signal function to tell us we timed out.
+****************************************************************/
+
+static void gotalarm_sig(int signum)
+{
+ gotalarm = 1;
+}
+
+extern userdom_struct current_user_info;
+
+/*
+ * 'cups_passwd_cb()' - The CUPS password callback...
+ */
+
+static const char * /* O - Password or NULL */
+cups_passwd_cb(const char *prompt) /* I - Prompt */
+{
+ /*
+ * Always return NULL to indicate that no password is available...
+ */
+
+ return (NULL);
+}
+
+static http_t *cups_connect(TALLOC_CTX *frame)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ http_t *http = NULL;
+ char *server = NULL, *p = NULL;
+ int port;
+ int timeout = lp_cups_connection_timeout();
+ size_t size;
+
+ if (lp_cups_server(talloc_tos(), lp_sub) != NULL && strlen(lp_cups_server(talloc_tos(), lp_sub)) > 0) {
+ if (!push_utf8_talloc(frame, &server, lp_cups_server(talloc_tos(), lp_sub), &size)) {
+ return NULL;
+ }
+ } else {
+ server = talloc_strdup(frame,cupsServer());
+ }
+ if (!server) {
+ return NULL;
+ }
+
+ p = strchr(server, ':');
+ if (p) {
+ port = atoi(p+1);
+ *p = '\0';
+ } else {
+ port = ippPort();
+ }
+
+ DEBUG(10, ("connecting to cups server %s:%d\n",
+ server, port));
+
+ gotalarm = 0;
+
+ if (timeout) {
+ CatchSignal(SIGALRM, gotalarm_sig);
+ alarm(timeout);
+ }
+
+#if defined(HAVE_HTTPCONNECT2)
+ http = httpConnect2(server,
+ port,
+ NULL,
+ AF_UNSPEC,
+ lp_cups_encrypt() ?
+ HTTP_ENCRYPTION_ALWAYS :
+ HTTP_ENCRYPTION_IF_REQUESTED,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#elif defined(HAVE_HTTPCONNECTENCRYPT)
+ http = httpConnectEncrypt(server, port, lp_cups_encrypt());
+#else
+ http = httpConnect(server, port);
+#endif
+
+
+ CatchSignal(SIGALRM, SIG_IGN);
+ alarm(0);
+
+ if (http == NULL) {
+ DEBUG(3,("Unable to connect to CUPS server %s:%d - %s\n",
+ server, port, strerror(errno)));
+ }
+
+ return http;
+}
+
+static bool send_pcap_blob(DATA_BLOB *pcap_blob, int fd)
+{
+ size_t ret;
+
+ ret = sys_write(fd, &pcap_blob->length, sizeof(pcap_blob->length));
+ if (ret != sizeof(pcap_blob->length)) {
+ return false;
+ }
+
+ ret = sys_write(fd, pcap_blob->data, pcap_blob->length);
+ if (ret != pcap_blob->length) {
+ return false;
+ }
+
+ DEBUG(10, ("successfully sent blob of len %d\n", (int)ret));
+ return true;
+}
+
+static bool recv_pcap_blob(TALLOC_CTX *mem_ctx, int fd, DATA_BLOB *pcap_blob)
+{
+ size_t blob_len;
+ size_t ret;
+
+ ret = sys_read(fd, &blob_len, sizeof(blob_len));
+ if (ret != sizeof(blob_len)) {
+ return false;
+ }
+
+ *pcap_blob = data_blob_talloc_named(mem_ctx, NULL, blob_len,
+ "cups pcap");
+ if (pcap_blob->length != blob_len) {
+ return false;
+ }
+ ret = sys_read(fd, pcap_blob->data, blob_len);
+ if (ret != blob_len) {
+ talloc_free(pcap_blob->data);
+ return false;
+ }
+
+ DEBUG(10, ("successfully recvd blob of len %d\n", (int)ret));
+ return true;
+}
+
+static bool process_cups_printers_response(TALLOC_CTX *mem_ctx,
+ ipp_t *response,
+ struct pcap_data *pcap_data)
+{
+ ipp_attribute_t *attr;
+ char *name;
+ char *info;
+ char *location = NULL;
+ struct pcap_printer *printer;
+ bool ret_ok = false;
+
+ for (attr = ippFirstAttribute(response); attr != NULL;) {
+ /*
+ * Skip leading attributes until we hit a printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Pull the needed attributes from this printer...
+ */
+
+ name = NULL;
+ info = NULL;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
+ size_t size;
+ if (strcmp(ippGetName(attr), "printer-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME) {
+ if (!pull_utf8_talloc(mem_ctx,
+ &name,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto err_out;
+ }
+ }
+
+ if (strcmp(ippGetName(attr), "printer-info") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
+ if (!pull_utf8_talloc(mem_ctx,
+ &info,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto err_out;
+ }
+ }
+
+ if (strcmp(ippGetName(attr), "printer-location") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
+ if (!pull_utf8_talloc(mem_ctx,
+ &location,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto err_out;
+ }
+ }
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ */
+
+ if (name == NULL)
+ break;
+
+ if (pcap_data->count == 0) {
+ printer = talloc_array(mem_ctx, struct pcap_printer, 1);
+ } else {
+ printer = talloc_realloc(mem_ctx, pcap_data->printers,
+ struct pcap_printer,
+ pcap_data->count + 1);
+ }
+ if (printer == NULL) {
+ goto err_out;
+ }
+ pcap_data->printers = printer;
+ pcap_data->printers[pcap_data->count].name = name;
+ pcap_data->printers[pcap_data->count].info = info;
+ pcap_data->printers[pcap_data->count].location = location;
+ pcap_data->count++;
+ }
+
+ ret_ok = true;
+err_out:
+ return ret_ok;
+}
+
+/*
+ * request printer list from cups, send result back to up parent via fd.
+ * returns true if the (possibly failed) result was successfully sent to parent.
+ */
+static bool cups_cache_reload_async(int fd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pcap_data pcap_data;
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ static const char *requested[] =/* Requested attributes */
+ {
+ "printer-name",
+ "printer-info",
+ "printer-location"
+ };
+ bool ret = False;
+ enum ndr_err_code ndr_ret;
+ DATA_BLOB pcap_blob;
+
+ ZERO_STRUCT(pcap_data);
+ pcap_data.status = NT_STATUS_UNSUCCESSFUL;
+
+ DEBUG(5, ("reloading cups printcap cache\n"));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build a CUPS_GET_PRINTERS request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, CUPS_GET_PRINTERS);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requested-attributes",
+ (sizeof(requested) / sizeof(requested[0])),
+ NULL, requested);
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ ret = process_cups_printers_response(frame, response, &pcap_data);
+ if (!ret) {
+ DEBUG(0,("failed to process cups response\n"));
+ goto out;
+ }
+
+ ippDelete(response);
+ response = NULL;
+
+ /*
+ * Build a CUPS_GET_CLASSES request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, CUPS_GET_CLASSES);
+ ippSetRequestId(request, 1);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requested-attributes",
+ (sizeof(requested) / sizeof(requested[0])),
+ NULL, requested);
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ ret = process_cups_printers_response(frame, response, &pcap_data);
+ if (!ret) {
+ DEBUG(0,("failed to process cups response\n"));
+ goto out;
+ }
+
+ pcap_data.status = NT_STATUS_OK;
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ ret = false;
+ ndr_ret = ndr_push_struct_blob(&pcap_blob, frame, &pcap_data,
+ (ndr_push_flags_fn_t)ndr_push_pcap_data);
+ if (ndr_ret == NDR_ERR_SUCCESS) {
+ ret = send_pcap_blob(&pcap_blob, fd);
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static struct tevent_fd *cache_fd_event;
+
+static bool cups_pcap_load_async(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int *pfd)
+{
+ int fds[2];
+ pid_t pid;
+ NTSTATUS status;
+
+ *pfd = -1;
+
+ if (cache_fd_event) {
+ DEBUG(3,("cups_pcap_load_async: already waiting for "
+ "a refresh event\n" ));
+ return false;
+ }
+
+ DEBUG(5,("cups_pcap_load_async: asynchronously loading cups printers\n"));
+
+ if (pipe(fds) == -1) {
+ return false;
+ }
+
+ pid = fork();
+ if (pid == (pid_t)-1) {
+ DEBUG(10,("cups_pcap_load_async: fork failed %s\n",
+ strerror(errno) ));
+ close(fds[0]);
+ close(fds[1]);
+ return false;
+ }
+
+ if (pid) {
+ DEBUG(10,("cups_pcap_load_async: child pid = %u\n",
+ (unsigned int)pid ));
+ /* Parent. */
+ close(fds[1]);
+ *pfd = fds[0];
+ return true;
+ }
+
+ /* Child. */
+
+ close_all_print_db();
+
+ status = reinit_after_fork(msg_ctx, ev, true, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("cups_pcap_load_async: reinit_after_fork() failed\n"));
+ smb_panic("cups_pcap_load_async: reinit_after_fork() failed");
+ }
+
+ close(fds[0]);
+ cups_cache_reload_async(fds[1]);
+ close(fds[1]);
+ TALLOC_FREE(msg_ctx);
+ _exit(0);
+}
+
+struct cups_async_cb_args {
+ int pipe_fd;
+ struct tevent_context *event_ctx;
+ struct messaging_context *msg_ctx;
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *);
+};
+
+static void cups_async_callback(struct tevent_context *event_ctx,
+ struct tevent_fd *event,
+ uint16_t flags,
+ void *p)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cups_async_cb_args *cb_args = (struct cups_async_cb_args *)p;
+ struct pcap_cache *tmp_pcap_cache = NULL;
+ bool ret_ok;
+ struct pcap_data pcap_data;
+ DATA_BLOB pcap_blob;
+ enum ndr_err_code ndr_ret;
+ uint32_t i;
+
+ DEBUG(5,("cups_async_callback: callback received for printer data. "
+ "fd = %d\n", cb_args->pipe_fd));
+
+ ret_ok = recv_pcap_blob(frame, cb_args->pipe_fd, &pcap_blob);
+ if (!ret_ok) {
+ DEBUG(0,("failed to recv pcap blob\n"));
+ goto err_out;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(&pcap_blob, frame, &pcap_data,
+ (ndr_pull_flags_fn_t)ndr_pull_pcap_data);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ goto err_out;
+ }
+
+ if (!NT_STATUS_IS_OK(pcap_data.status)) {
+ DEBUG(3,("failed to retrieve printer list: %s\n",
+ nt_errstr(pcap_data.status)));
+ goto err_out;
+ }
+
+ for (i = 0; i < pcap_data.count; i++) {
+ ret_ok = pcap_cache_add_specific(&tmp_pcap_cache,
+ pcap_data.printers[i].name,
+ pcap_data.printers[i].info,
+ pcap_data.printers[i].location);
+ if (!ret_ok) {
+ DEBUG(0, ("failed to add to tmp pcap cache\n"));
+ goto err_out;
+ }
+ }
+
+ /* replace the system-wide pcap cache with a (possibly empty) new one */
+ ret_ok = pcap_cache_replace(tmp_pcap_cache);
+ if (!ret_ok) {
+ DEBUG(0, ("failed to replace pcap cache\n"));
+ } else if (cb_args->post_cache_fill_fn != NULL) {
+ /* Caller requested post cache fill callback */
+ cb_args->post_cache_fill_fn(cb_args->event_ctx,
+ cb_args->msg_ctx);
+ }
+err_out:
+ pcap_cache_destroy_specific(&tmp_pcap_cache);
+ TALLOC_FREE(frame);
+ TALLOC_FREE(cache_fd_event);
+ close(cb_args->pipe_fd);
+ TALLOC_FREE(cb_args);
+}
+
+bool cups_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *))
+{
+ struct cups_async_cb_args *cb_args;
+ int *p_pipe_fd;
+
+ cb_args = talloc(NULL, struct cups_async_cb_args);
+ if (cb_args == NULL) {
+ return false;
+ }
+
+ cb_args->post_cache_fill_fn = post_cache_fill_fn;
+ cb_args->event_ctx = ev;
+ cb_args->msg_ctx = msg_ctx;
+ p_pipe_fd = &cb_args->pipe_fd;
+ *p_pipe_fd = -1;
+
+ /* Set up an async refresh. */
+ if (!cups_pcap_load_async(ev, msg_ctx, p_pipe_fd)) {
+ talloc_free(cb_args);
+ return false;
+ }
+
+ DEBUG(10,("cups_cache_reload: async read on fd %d\n",
+ *p_pipe_fd ));
+
+ /* Trigger an event when the pipe can be read. */
+ cache_fd_event = tevent_add_fd(ev,
+ NULL, *p_pipe_fd,
+ TEVENT_FD_READ,
+ cups_async_callback,
+ (void *)cb_args);
+ if (!cache_fd_event) {
+ close(*p_pipe_fd);
+ TALLOC_FREE(cb_args);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * 'cups_job_delete()' - Delete a job.
+ */
+
+static int cups_job_delete(const char *sharename, const char *lprm_command, struct printjob *pjob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *user = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_job_delete(%s, %p (%d))\n", sharename, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_CANCEL_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * job-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_CANCEL_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/jobs/%d",
+ pjob->sysjob);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_job_pause()' - Pause a job.
+ */
+
+static int cups_job_pause(int snum, struct printjob *pjob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *user = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_job_pause(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_HOLD_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * job-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_HOLD_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/jobs/%d",
+ pjob->sysjob);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_job_resume()' - Resume a paused job.
+ */
+
+static int cups_job_resume(int snum, struct printjob *pjob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *user = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_job_resume(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_RELEASE_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * job-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_RELEASE_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/jobs/%d",
+ pjob->sysjob);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_job_submit()' - Submit a job for printing.
+ */
+
+static int cups_job_submit(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_cmd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr_job_id = NULL; /* IPP Attribute "job-id" */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ char *new_jobname = NULL;
+ int num_options = 0;
+ cups_option_t *options = NULL;
+ char *printername = NULL;
+ char *user = NULL;
+ char *jobname = NULL;
+ char *cupsoptions = NULL;
+ char *filename = NULL;
+ size_t size;
+
+ DEBUG(5,("cups_job_submit(%d, %p)\n", snum, pjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_PRINT_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ * [document-data]
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_PRINT_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ if (!push_utf8_talloc(frame, &printername,
+ lp_printername(talloc_tos(), lp_sub, snum),
+ &size)) {
+ goto out;
+ }
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "job-originating-host-name", NULL,
+ pjob->clientmachine);
+
+ if (!push_utf8_talloc(frame, &jobname, pjob->jobname, &size)) {
+ goto out;
+ }
+ new_jobname = talloc_asprintf(frame,
+ "%s%.8u %s", PRINT_SPOOL_PREFIX,
+ pjob->jobid, jobname);
+ if (new_jobname == NULL) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
+ new_jobname);
+
+ /*
+ * add any options defined in smb.conf
+ */
+
+ if (!push_utf8_talloc(frame, &cupsoptions,
+ lp_cups_options(talloc_tos(), lp_sub, snum), &size)) {
+ goto out;
+ }
+ num_options = 0;
+ options = NULL;
+ num_options = cupsParseOptions(cupsoptions, num_options, &options);
+
+ if ( num_options )
+ cupsEncodeOptions(request, num_options, options);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ if (!push_utf8_talloc(frame, &filename, pjob->filename, &size)) {
+ goto out;
+ }
+ if ((response = cupsDoFileRequest(http, request, uri, pjob->filename)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to print file to %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ attr_job_id = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
+ if(attr_job_id) {
+ pjob->sysjob = ippGetInteger(attr_job_id, 0);
+ DEBUG(5,("cups_job_submit: job-id %d\n", pjob->sysjob));
+ } else {
+ DEBUG(0,("Missing job-id attribute in IPP response"));
+ }
+ }
+ } else {
+ DEBUG(0,("Unable to print file to `%s' - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ if ( ret == 0 )
+ unlink(pjob->filename);
+ /* else print_job_end will do it for us */
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+
+ return ret;
+}
+
+/*
+ * 'cups_queue_get()' - Get all the jobs in the print queue.
+ */
+
+static int cups_queue_get(const char *sharename,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *printername = NULL;
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr = NULL; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ int qcount = 0, /* Number of active queue entries */
+ qalloc = 0; /* Number of queue entries allocated */
+ print_queue_struct *queue = NULL, /* Queue entries */
+ *temp; /* Temporary pointer for queue */
+ char *user_name = NULL, /* job-originating-user-name attribute */
+ *job_name = NULL; /* job-name attribute */
+ int job_id; /* job-id attribute */
+ int job_k_octets; /* job-k-octets attribute */
+ time_t job_time; /* time-at-creation attribute */
+ ipp_jstate_t job_status; /* job-status attribute */
+ int job_priority; /* job-priority attribute */
+ size_t size;
+ static const char *jattrs[] = /* Requested job attributes */
+ {
+ "job-id",
+ "job-k-octets",
+ "job-name",
+ "job-originating-user-name",
+ "job-priority",
+ "job-state",
+ "time-at-creation",
+ };
+ static const char *pattrs[] = /* Requested printer attributes */
+ {
+ "printer-state",
+ "printer-state-message"
+ };
+
+ *q = NULL;
+
+ /* HACK ALERT!!! The problem with support the 'printer name'
+ option is that we key the tdb off the sharename. So we will
+ overload the lpq_command string to pass in the printername
+ (which is basically what we do for non-cups printers ... using
+ the lpq_command to get the queue listing). */
+
+ if (!push_utf8_talloc(frame, &printername, lpq_command, &size)) {
+ goto out;
+ }
+ DEBUG(5,("cups_queue_get(%s, %p, %p)\n", lpq_command, q, status));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Generate the printer URI...
+ */
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_GET_JOBS request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ * printer-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_JOBS);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(jattrs) / sizeof(jattrs[0])),
+ NULL, jattrs);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(ippGetStatusCode(response))));
+ goto out;
+ }
+
+ /*
+ * Process the jobs...
+ */
+
+ qcount = 0;
+ qalloc = 0;
+ queue = NULL;
+
+ for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) {
+ /*
+ * Skip leading attributes until we hit a job...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_JOB)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Allocate memory as needed...
+ */
+ if (qcount >= qalloc) {
+ qalloc += 16;
+
+ queue = SMB_REALLOC_ARRAY(queue, print_queue_struct, qalloc);
+
+ if (queue == NULL) {
+ DEBUG(0,("cups_queue_get: Not enough memory!"));
+ qcount = 0;
+ goto out;
+ }
+ }
+
+ temp = queue + qcount;
+ memset(temp, 0, sizeof(print_queue_struct));
+
+ /*
+ * Pull the needed attributes from this job...
+ */
+
+ job_id = 0;
+ job_priority = 50;
+ job_status = IPP_JOB_PENDING;
+ job_time = 0;
+ job_k_octets = 0;
+ user_name = NULL;
+ job_name = NULL;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_JOB) {
+ if (ippGetName(attr) == NULL) {
+ attr = ippNextAttribute(response);
+ break;
+ }
+
+ if (strcmp(ippGetName(attr), "job-id") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_id = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-k-octets") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_k_octets = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-priority") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_priority = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-state") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_ENUM)
+ job_status = (ipp_jstate_t)ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "time-at-creation") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_time = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME) {
+ if (!pull_utf8_talloc(frame,
+ &job_name,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto out;
+ }
+ }
+
+ if (strcmp(ippGetName(attr), "job-originating-user-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME) {
+ if (!pull_utf8_talloc(frame,
+ &user_name,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto out;
+ }
+ }
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ */
+
+ if (user_name == NULL || job_name == NULL || job_id == 0) {
+ if (attr == NULL)
+ break;
+ else
+ continue;
+ }
+
+ temp->sysjob = job_id;
+ temp->size = job_k_octets * 1024;
+ temp->status = job_status == IPP_JOB_PENDING ? LPQ_QUEUED :
+ job_status == IPP_JOB_STOPPED ? LPQ_PAUSED :
+ job_status == IPP_JOB_HELD ? LPQ_PAUSED :
+ LPQ_PRINTING;
+ temp->priority = job_priority;
+ temp->time = job_time;
+ strlcpy(temp->fs_user, user_name, sizeof(temp->fs_user));
+ strlcpy(temp->fs_file, job_name, sizeof(temp->fs_file));
+
+ qcount ++;
+
+ if (attr == NULL)
+ break;
+ }
+
+ ippDelete(response);
+ response = NULL;
+
+ /*
+ * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the
+ * following attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ * printer-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_PRINTER_ATTRIBUTES);
+ ippSetRequestId(request, 1);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requested-attributes",
+ (sizeof(pattrs) / sizeof(pattrs[0])),
+ NULL, pattrs);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(ippGetStatusCode(response))));
+ goto out;
+ }
+
+ /*
+ * Get the current printer status and convert it to the SAMBA values.
+ */
+
+ if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) {
+ if (ippGetInteger(attr, 0) == IPP_PRINTER_STOPPED)
+ status->status = LPSTAT_STOPPED;
+ else
+ status->status = LPSTAT_OK;
+ }
+
+ if ((attr = ippFindAttribute(response, "printer-state-message",
+ IPP_TAG_TEXT)) != NULL) {
+ char *msg = NULL;
+ if (!pull_utf8_talloc(frame, &msg,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ SAFE_FREE(queue);
+ qcount = 0;
+ goto out;
+ }
+ fstrcpy(status->message, msg);
+ }
+
+ out:
+
+ /*
+ * Return the job queue...
+ */
+
+ *q = queue;
+
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return qcount;
+}
+
+
+/*
+ * 'cups_queue_pause()' - Pause a print queue.
+ */
+
+static int cups_queue_pause(int snum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *printername = NULL;
+ char *username = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_queue_pause(%d)\n", snum));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_PAUSE_PRINTER request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_PAUSE_PRINTER);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ if (!push_utf8_talloc(frame, &printername,
+ lp_printername(talloc_tos(), lp_sub, snum), &size)) {
+ goto out;
+ }
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &username, current_user_info.unix_name, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, username);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/admin/")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to pause printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to pause printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_queue_resume()' - Restart a print queue.
+ */
+
+static int cups_queue_resume(int snum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *printername = NULL;
+ char *username = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_queue_resume(%d)\n", snum));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_RESUME_PRINTER request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_RESUME_PRINTER);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ if (!push_utf8_talloc(frame, &printername, lp_printername(talloc_tos(), lp_sub, snum),
+ &size)) {
+ goto out;
+ }
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &username, current_user_info.unix_name, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, username);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/admin/")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to resume printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to resume printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*******************************************************************
+ * CUPS printing interface definitions...
+ ******************************************************************/
+
+struct printif cups_printif =
+{
+ PRINT_CUPS,
+ cups_queue_get,
+ cups_queue_pause,
+ cups_queue_resume,
+ cups_job_delete,
+ cups_job_pause,
+ cups_job_resume,
+ cups_job_submit,
+};
+
+#else
+ /* this keeps fussy compilers happy */
+ void print_cups_dummy(void);
+ void print_cups_dummy(void) {}
+#endif /* HAVE_CUPS */
diff --git a/source3/printing/print_generic.c b/source3/printing/print_generic.c
new file mode 100644
index 0000000..8798a4c
--- /dev/null
+++ b/source3/printing/print_generic.c
@@ -0,0 +1,358 @@
+/*
+ Unix SMB/CIFS implementation.
+ printing command routines
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "smbd/proto.h"
+#include "source3/lib/substitute.h"
+
+extern userdom_struct current_user_info;
+
+/****************************************************************************
+ 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, const char* printername, bool do_sub,
+ const char *command, int *outfd, ...)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *syscmd;
+ char *arg;
+ int ret;
+ TALLOC_CTX *ctx = talloc_tos();
+ va_list ap;
+ va_start(ap, outfd);
+
+ /* check for a valid system printername and valid command to run */
+
+ if ( !printername || !*printername ) {
+ va_end(ap);
+ return -1;
+ }
+
+ if (!command || !*command) {
+ va_end(ap);
+ return -1;
+ }
+
+ syscmd = talloc_strdup(ctx, command);
+ if (!syscmd) {
+ va_end(ap);
+ return -1;
+ }
+
+ DBG_DEBUG("Incoming command '%s'\n", syscmd);
+
+ while ((arg = va_arg(ap, char *))) {
+ char *value = va_arg(ap,char *);
+ syscmd = talloc_string_sub(ctx, syscmd, arg, value);
+ if (!syscmd) {
+ va_end(ap);
+ return -1;
+ }
+ }
+ va_end(ap);
+
+ syscmd = talloc_string_sub(ctx, syscmd, "%p", printername);
+ if (!syscmd) {
+ return -1;
+ }
+
+ syscmd = lpcfg_substituted_string(ctx, lp_sub, syscmd);
+ if (syscmd == NULL) {
+ return -1;
+ }
+
+ if (do_sub && snum != -1) {
+ syscmd = talloc_sub_advanced(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ syscmd);
+ if (!syscmd) {
+ return -1;
+ }
+ }
+
+ ret = smbrun_no_sanitize(syscmd, outfd, NULL);
+
+ DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+
+ return ret;
+}
+
+
+/****************************************************************************
+delete a print job
+****************************************************************************/
+static int generic_job_delete( const char *sharename, const char *lprm_command, struct printjob *pjob)
+{
+ fstring jobstr;
+
+ /* need to delete the spooled entry */
+ fstr_sprintf(jobstr, "%d", pjob->sysjob);
+ return print_run_command( -1, sharename, False, lprm_command, NULL,
+ "%j", jobstr,
+ "%T", http_timestring(talloc_tos(), pjob->starttime),
+ NULL);
+}
+
+/****************************************************************************
+pause a job
+****************************************************************************/
+static int generic_job_pause(int snum, struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ fstring jobstr;
+
+ /* need to pause the spooled entry */
+ fstr_sprintf(jobstr, "%d", pjob->sysjob);
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_lppause_command(snum), NULL,
+ "%j", jobstr,
+ NULL);
+}
+
+/****************************************************************************
+resume a job
+****************************************************************************/
+static int generic_job_resume(int snum, struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ fstring jobstr;
+
+ /* need to pause the spooled entry */
+ fstr_sprintf(jobstr, "%d", pjob->sysjob);
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_lpresume_command(snum), NULL,
+ "%j", jobstr,
+ NULL);
+}
+
+/****************************************************************************
+get the current list of queued jobs
+****************************************************************************/
+static int generic_queue_get(const char *printer_name,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status)
+{
+ char **qlines;
+ int fd;
+ int numlines, i, qcount;
+ print_queue_struct *queue = NULL;
+
+ /* never do substitution when running the 'lpq command' since we can't
+ get it right when using the background update daemon. Make the caller
+ do it before passing off the command string to us here. */
+
+ print_run_command(-1, printer_name, False, lpq_command, &fd, NULL);
+
+ if (fd == -1) {
+ DEBUG(5,("generic_queue_get: Can't read print queue status for printer %s\n",
+ printer_name ));
+ return 0;
+ }
+
+ numlines = 0;
+ qlines = fd_lines_load(fd, &numlines,0,NULL);
+ close(fd);
+
+ /* turn the lpq output into a series of job structures */
+ qcount = 0;
+ ZERO_STRUCTP(status);
+ if (numlines && qlines) {
+ queue = SMB_MALLOC_ARRAY(print_queue_struct, numlines+1);
+ if (!queue) {
+ TALLOC_FREE(qlines);
+ *q = NULL;
+ return 0;
+ }
+ memset(queue, '\0', sizeof(print_queue_struct)*(numlines+1));
+
+ for (i=0; i<numlines; i++) {
+ /* parse the line */
+ if (parse_lpq_entry(printing_type,qlines[i],
+ &queue[qcount],status,qcount==0)) {
+ qcount++;
+ }
+ }
+ }
+
+ TALLOC_FREE(qlines);
+ *q = queue;
+ return qcount;
+}
+
+/****************************************************************************
+ Submit a file for printing - called from print_job_end()
+****************************************************************************/
+
+static int generic_job_submit(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_cmd)
+{
+ int ret = -1;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *current_directory = NULL;
+ char *print_directory = NULL;
+ char *wd = NULL;
+ char *p = NULL;
+ char *jobname = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ fstring job_page_count, job_size;
+ print_queue_struct *q;
+ print_status_struct status;
+
+ /* we print from the directory path to give the best chance of
+ parsing the lpq output */
+ wd = sys_getwd();
+ if (!wd) {
+ return -1;
+ }
+
+ current_directory = talloc_strdup(ctx, wd);
+ SAFE_FREE(wd);
+
+ if (!current_directory) {
+ return -1;
+ }
+ print_directory = talloc_strdup(ctx, pjob->filename);
+ if (!print_directory) {
+ return -1;
+ }
+ p = strrchr_m(print_directory,'/');
+ if (!p) {
+ return -1;
+ }
+ *p++ = 0;
+
+ if (chdir(print_directory) != 0) {
+ return -1;
+ }
+
+ jobname = talloc_strdup(ctx, pjob->jobname);
+ if (!jobname) {
+ ret = -1;
+ goto out;
+ }
+ jobname = talloc_string_sub(ctx, jobname, "'", "_");
+ if (!jobname) {
+ ret = -1;
+ goto out;
+ }
+ fstr_sprintf(job_page_count, "%d", pjob->page_count);
+ fstr_sprintf(job_size, "%zu", pjob->size);
+
+ /* send it to the system spooler */
+ ret = print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_print_command(snum), NULL,
+ "%s", p,
+ "%J", jobname,
+ "%f", p,
+ "%z", job_size,
+ "%c", job_page_count,
+ NULL);
+ if (ret != 0) {
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * check the queue for the newly submitted job, this allows us to
+ * determine the backend job identifier (sysjob).
+ */
+ pjob->sysjob = -1;
+ ret = generic_queue_get(lp_printername(talloc_tos(), lp_sub, snum),
+ printing_type, lpq_cmd, &q, &status);
+ if (ret > 0) {
+ int i;
+ for (i = 0; i < ret; i++) {
+ if (strcmp(q[i].fs_file, p) == 0) {
+ pjob->sysjob = q[i].sysjob;
+ DEBUG(5, ("new job %u (%s) matches sysjob %d\n",
+ pjob->jobid, jobname, pjob->sysjob));
+ break;
+ }
+ }
+ SAFE_FREE(q);
+ ret = 0;
+ }
+ if (pjob->sysjob == -1) {
+ DEBUG(2, ("failed to get sysjob for job %u (%s), tracking as "
+ "Unix job\n", pjob->jobid, jobname));
+ }
+
+
+ out:
+
+ if (chdir(current_directory) == -1) {
+ smb_panic("chdir failed in generic_job_submit");
+ }
+ TALLOC_FREE(current_directory);
+ return ret;
+}
+
+/****************************************************************************
+ pause a queue
+****************************************************************************/
+static int generic_queue_pause(int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_queuepause_command(snum), NULL, NULL);
+}
+
+/****************************************************************************
+ resume a queue
+****************************************************************************/
+static int generic_queue_resume(int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_queueresume_command(snum), NULL, NULL);
+}
+
+/****************************************************************************
+ * Generic printing interface definitions...
+ ***************************************************************************/
+
+struct printif generic_printif =
+{
+ DEFAULT_PRINTING,
+ generic_queue_get,
+ generic_queue_pause,
+ generic_queue_resume,
+ generic_job_delete,
+ generic_job_pause,
+ generic_job_resume,
+ generic_job_submit,
+};
+
diff --git a/source3/printing/print_iprint.c b/source3/printing/print_iprint.c
new file mode 100644
index 0000000..f10dee0
--- /dev/null
+++ b/source3/printing/print_iprint.c
@@ -0,0 +1,1367 @@
+/*
+ * Support code for Novell iPrint using the Common UNIX Printing
+ * System ("CUPS") libraries
+ *
+ * Copyright 1999-2003 by Michael R Sweet.
+ * Portions Copyright 2005 by Joel J. Smith.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing.h"
+#include "printing/pcap.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_IPRINT
+#include <cups/cups.h>
+#include <cups/language.h>
+
+#define OPERATION_NOVELL_LIST_PRINTERS 0x401A
+#define OPERATION_NOVELL_MGMT 0x401C
+#define NOVELL_SERVER_SYSNAME "sysname="
+#define NOVELL_SERVER_SYSNAME_NETWARE "NetWare IA32"
+#define NOVELL_SERVER_VERSION_STRING "iprintserverversion="
+#define NOVELL_SERVER_VERSION_OES_SP1 33554432
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetCount(attr) attr->num_values
+#define ippGetGroupTag(attr) attr->group_tag
+#define ippGetName(attr) attr->name
+#define ippGetValueTag(attr) attr->value_tag
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetBoolean(attr, element) attr->values[element].boolean
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+
+static ipp_attribute_t *
+ippFirstAttribute(ipp_t *ipp)
+{
+ if (!ipp)
+ return (NULL);
+ return (ipp->current = ipp->attrs);
+}
+
+static ipp_attribute_t *
+ippNextAttribute(ipp_t *ipp)
+{
+ if (!ipp || !ipp->current)
+ return (NULL);
+ return (ipp->current = ipp->current->next);
+}
+
+static int ippSetOperation(ipp_t *ipp, ipp_op_t op)
+{
+ ipp->request.op.operation_id = op;
+ return (1);
+}
+
+static int ippSetRequestId(ipp_t *ipp, int request_id)
+{
+ ipp->request.any.request_id = request_id;
+ return (1);
+}
+#endif
+
+/*
+ * 'iprint_passwd_cb()' - The iPrint password callback...
+ */
+
+static const char * /* O - Password or NULL */
+iprint_passwd_cb(const char *prompt) /* I - Prompt */
+{
+ /*
+ * Always return NULL to indicate that no password is available...
+ */
+
+ return (NULL);
+}
+
+static const char *iprint_server(void)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *server = lp_iprint_server(talloc_tos(), lp_sub);
+
+ if ((server != NULL) && (strlen(server) > 0)) {
+ DEBUG(10, ("iprint server explicitly set to %s\n",
+ server));
+ return server;
+ }
+
+ DEBUG(10, ("iprint server left to default %s\n", cupsServer()));
+ return cupsServer();
+}
+
+/*
+ * Pass in an already connected http_t*
+ * Returns the server version if one can be found, multiplied by
+ * -1 for all NetWare versions. Returns 0 if a server version
+ * cannot be determined
+ */
+
+static int iprint_get_server_version(http_t *http, char* serviceUri)
+{
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char *ver; /* server version pointer */
+ char *vertmp; /* server version tmp pointer */
+ int serverVersion = 0; /* server version */
+ char *os; /* server os */
+ int osFlag = 0; /* 0 for NetWare, 1 for anything else */
+ char *temp; /* pointer for string manipulation */
+
+ /*
+ * Build an OPERATION_NOVELL_MGMT("get-server-version") request,
+ * which requires the following attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * operation-name
+ * service-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, (ipp_op_t)OPERATION_NOVELL_MGMT);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "service-uri", NULL, serviceUri);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "operation-name", NULL, "get-server-version");
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if (((response = cupsDoRequest(http, request, "/ipp/")) == NULL) ||
+ (ippGetStatusCode(response) >= IPP_OK_CONFLICT))
+ goto out;
+
+ if (((attr = ippFindAttribute(response, "server-version",
+ IPP_TAG_STRING)) != NULL)) {
+ if ((ver = strstr(ippGetString(attr, 0, NULL),
+ NOVELL_SERVER_VERSION_STRING)) != NULL) {
+ ver += strlen(NOVELL_SERVER_VERSION_STRING);
+ /*
+ * Strangely, libcups stores a IPP_TAG_STRING (octet
+ * string) as a null-terminated string with no length
+ * even though it could be binary data with nulls in
+ * it. Luckily, in this case the value is not binary.
+ */
+ serverVersion = strtol(ver, &vertmp, 10);
+
+ /* Check for not found, overflow or negative version */
+ if ((ver == vertmp) || (serverVersion < 0))
+ serverVersion = 0;
+ }
+
+ if ((os = strstr(ippGetString(attr, 0, NULL),
+ NOVELL_SERVER_SYSNAME)) != NULL) {
+ os += strlen(NOVELL_SERVER_SYSNAME);
+ if ((temp = strchr(os,'<')) != NULL)
+ *temp = '\0';
+ if (strcmp(os,NOVELL_SERVER_SYSNAME_NETWARE))
+ osFlag = 1; /* 1 for non-NetWare systems */
+ }
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (osFlag == 0)
+ serverVersion *= -1;
+
+ return serverVersion;
+}
+
+
+static int iprint_cache_add_printer(http_t *http,
+ int reqId,
+ const char *url,
+ struct pcap_cache **pcache)
+{
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ const char *name, /* printer-name attribute */
+ *info; /* printer-info attribute */
+ char smb_enabled, /* smb-enabled attribute */
+ secure; /* security-enabled attrib. */
+
+ const char *httpPath; /* path portion of the printer-uri */
+
+ static const char *pattrs[] = /* Requested printer attributes */
+ {
+ "printer-name",
+ "security-enabled",
+ "printer-info",
+ "smb-enabled"
+ };
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_PRINTER_ATTRIBUTES);
+ ippSetRequestId(request, reqId);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, url);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(pattrs) / sizeof(pattrs[0])),
+ NULL, pattrs);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((httpPath = strstr(url,"://")) == NULL ||
+ (httpPath = strchr(httpPath+3,'/')) == NULL)
+ {
+ ippDelete(request);
+ request = NULL;
+ goto out;
+ }
+
+ if ((response = cupsDoRequest(http, request, httpPath)) == NULL) {
+ ipp_status_t lastErr = cupsLastError();
+
+ /*
+ * Ignore printers that cannot be queried without credentials
+ */
+ if (lastErr == IPP_FORBIDDEN ||
+ lastErr == IPP_NOT_AUTHENTICATED ||
+ lastErr == IPP_NOT_AUTHORIZED)
+ goto out;
+
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(lastErr)));
+ goto out;
+ }
+
+ for (attr = ippFirstAttribute(response); attr != NULL;) {
+ /*
+ * Skip leading attributes until we hit a printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Pull the needed attributes from this printer...
+ */
+
+ name = NULL;
+ info = NULL;
+ smb_enabled= 1;
+ secure = 0;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
+ if (strcmp(ippGetName(attr), "printer-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME)
+ name = ippGetString(attr, 0, NULL);
+
+ if (strcmp(ippGetName(attr), "printer-info") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_TEXT ||
+ ippGetValueTag(attr) == IPP_TAG_TEXTLANG))
+ info = ippGetString(attr, 0, NULL);
+
+ /*
+ * If the smb-enabled attribute is present and the
+ * value is set to 0, don't show the printer.
+ * If the attribute is not present, assume that the
+ * printer should show up
+ */
+ if (!strcmp(ippGetName(attr), "smb-enabled") &&
+ ((ippGetValueTag(attr) == IPP_TAG_INTEGER &&
+ !ippGetInteger(attr, 0)) ||
+ (ippGetValueTag(attr) == IPP_TAG_BOOLEAN &&
+ !ippGetBoolean(attr, 0))))
+ smb_enabled = 0;
+
+ /*
+ * If the security-enabled attribute is present and the
+ * value is set to 1, don't show the printer.
+ * If the attribute is not present, assume that the
+ * printer should show up
+ */
+ if (!strcmp(ippGetName(attr), "security-enabled") &&
+ ((ippGetValueTag(attr) == IPP_TAG_INTEGER &&
+ ippGetInteger(attr, 0)) ||
+ (ippGetValueTag(attr) == IPP_TAG_BOOLEAN &&
+ ippGetBoolean(attr, 0))))
+ secure = 1;
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ * Make sure the printer is not a secure printer
+ * and make sure smb printing hasn't been explicitly
+ * disabled for the printer
+ */
+
+ if (name != NULL && !secure && smb_enabled)
+ pcap_cache_add_specific(pcache, name, info, NULL);
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+ return(0);
+}
+
+bool iprint_cache_reload(struct pcap_cache **_pcache)
+{
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ int i;
+ bool ret = false;
+ struct pcap_cache *pcache = NULL;
+
+ DEBUG(5, ("reloading iprint printcap cache\n"));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build a OPERATION_NOVELL_LIST_PRINTERS request, which requires the following attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, (ipp_op_t)OPERATION_NOVELL_LIST_PRINTERS);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "ipp-server", NULL, "ippSrvr");
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/ipp")) == NULL) {
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ for (attr = ippFirstAttribute(response); attr != NULL;) {
+ /*
+ * Skip leading attributes until we hit a printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Pull the needed attributes from this printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER)
+ {
+ if (strcmp(ippGetName(attr), "printer-name") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_URI ||
+ ippGetValueTag(attr) == IPP_TAG_NAME ||
+ ippGetValueTag(attr) == IPP_TAG_TEXT ||
+ ippGetValueTag(attr) == IPP_TAG_NAMELANG ||
+ ippGetValueTag(attr) == IPP_TAG_TEXTLANG))
+ {
+ for (i = 0; i<ippGetCount(attr); i++)
+ {
+ const char *url = ippGetString(attr, i, NULL);
+ if (!url || !strlen(url))
+ continue;
+ iprint_cache_add_printer(http, i+2, url,
+ &pcache);
+ }
+ }
+ attr = ippNextAttribute(response);
+ }
+ }
+
+ ret = true;
+ *_pcache = pcache;
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_delete()' - Delete a job.
+ */
+
+static int iprint_job_delete(const char *sharename, const char *lprm_command, struct printjob *pjob)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the printer-uri */
+
+
+ DEBUG(5,("iprint_job_delete(%s, %p (%d))\n", sharename, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_CANCEL_JOB request, which uses the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * job-id
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_CANCEL_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(), sharename);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", pjob->sysjob);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s", sharename);
+
+ if ((response = cupsDoRequest(http, request, httpPath)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_pause()' - Pause a job.
+ */
+
+static int iprint_job_pause(int snum, struct printjob *pjob)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the printer-uri */
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+
+ DEBUG(5,("iprint_job_pause(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_HOLD_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * job-id
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_HOLD_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(),
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", pjob->sysjob);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s",
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ if ((response = cupsDoRequest(http, request, httpPath)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_resume()' - Resume a paused job.
+ */
+
+static int iprint_job_resume(int snum, struct printjob *pjob)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the printer-uri */
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+
+ DEBUG(5,("iprint_job_resume(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_RELEASE_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * job-id
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_RELEASE_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(),
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", pjob->sysjob);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s",
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ if ((response = cupsDoRequest(http, request, httpPath)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_submit()' - Submit a job for printing.
+ */
+
+static int iprint_job_submit(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_cmd)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(5,("iprint_job_submit(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_PRINT_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ * [document-data]
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_PRINT_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(),
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "job-originating-host-name", NULL,
+ pjob->clientmachine);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
+ pjob->jobname);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(uri, sizeof(uri) - 1, "/ipp/%s", lp_printername(talloc_tos(), lp_sub, snum));
+
+ if ((response = cupsDoFileRequest(http, request, uri, pjob->filename)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to print file to %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to print file to `%s' - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ if ( ret == 0 )
+ unlink(pjob->filename);
+ /* else print_job_end will do it for us */
+
+ if ( ret == 0 ) {
+
+ attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
+ if (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_JOB)
+ {
+ pjob->sysjob = ippGetInteger(attr, 0);
+ }
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+/*
+ * 'iprint_queue_get()' - Get all the jobs in the print queue.
+ */
+
+static int iprint_queue_get(const char *sharename,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status)
+{
+ fstring printername;
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr = NULL; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char serviceUri[HTTP_MAX_URI]; /* service-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the uri */
+ int jobUseUnixTime = 0; /* Whether job times should
+ * be assumed to be Unix time */
+ int qcount = 0, /* Number of active queue entries */
+ qalloc = 0; /* Number of queue entries allocated */
+ print_queue_struct *queue = NULL, /* Queue entries */
+ *temp; /* Temporary pointer for queue */
+ const char *user_name, /* job-originating-user-name attribute */
+ *job_name; /* job-name attribute */
+ int job_id; /* job-id attribute */
+ int job_k_octets; /* job-k-octets attribute */
+ time_t job_time; /* time-at-creation attribute */
+ time_t printer_up_time = 0; /* printer's uptime */
+ ipp_jstate_t job_status; /* job-status attribute */
+ int job_priority; /* job-priority attribute */
+ static const char *jattrs[] = /* Requested job attributes */
+ {
+ "job-id",
+ "job-k-octets",
+ "job-name",
+ "job-originating-user-name",
+ "job-priority",
+ "job-state",
+ "time-at-creation",
+ };
+ static const char *pattrs[] = /* Requested printer attributes */
+ {
+ "printer-state",
+ "printer-state-message",
+ "printer-current-time",
+ "printer-up-time"
+ };
+
+ *q = NULL;
+
+ /* HACK ALERT!!! The porblem with support the 'printer name'
+ option is that we key the tdb off the sharename. So we will
+ overload the lpq_command string to pass in the printername
+ (which is basically what we do for non-cups printers ... using
+ the lpq_command to get the queue listing). */
+
+ fstrcpy( printername, lpq_command );
+
+ DEBUG(5,("iprint_queue_get(%s, %p, %p)\n", printername, q, status));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Generate the printer URI and the service URI that goes with it...
+ */
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(), printername);
+ slprintf(serviceUri, sizeof(serviceUri) - 1, "ipp://%s/ipp/", iprint_server());
+
+ /*
+ * For Linux iPrint servers from OES SP1 on, the iPrint server
+ * uses Unix time for job start times unless it detects the iPrint
+ * client in an http User-Agent header. (This was done to accomodate
+ * CUPS broken behavior. According to RFC 2911, section 4.3.14, job
+ * start times are supposed to be relative to how long the printer has
+ * been up.) Since libcups doesn't allow us to set that header before
+ * the request is sent, this ugly hack allows us to detect the server
+ * version and decide how to interpret the job time.
+ */
+ if (iprint_get_server_version(http, serviceUri) >=
+ NOVELL_SERVER_VERSION_OES_SP1)
+ jobUseUnixTime = 1;
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_PRINTER_ATTRIBUTES);
+ ippSetRequestId(request, 2);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(pattrs) / sizeof(pattrs[0])),
+ NULL, pattrs);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s", printername);
+
+ if ((response = cupsDoRequest(http, request, httpPath)) == NULL) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(cupsLastError())));
+ *q = queue;
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(ippGetStatusCode(response))));
+ *q = queue;
+ goto out;
+ }
+
+ /*
+ * Get the current printer status and convert it to the SAMBA values.
+ */
+
+ if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) {
+ if (ippGetInteger(attr, 0) == IPP_PRINTER_STOPPED)
+ status->status = LPSTAT_STOPPED;
+ else
+ status->status = LPSTAT_OK;
+ }
+
+ if ((attr = ippFindAttribute(response, "printer-state-message",
+ IPP_TAG_TEXT)) != NULL)
+ fstrcpy(status->message, ippGetString(attr, 0, NULL));
+
+ if ((attr = ippFindAttribute(response, "printer-up-time",
+ IPP_TAG_INTEGER)) != NULL)
+ printer_up_time = ippGetInteger(attr, 0);
+
+ ippDelete(response);
+ response = NULL;
+
+ /*
+ * Build an IPP_GET_JOBS request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ * printer-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_JOBS);
+ ippSetRequestId(request, 3);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(jattrs) / sizeof(jattrs[0])),
+ NULL, jattrs);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s", printername);
+
+ if ((response = cupsDoRequest(http, request, httpPath)) == NULL) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(ippGetStatusCode(response))));
+ goto out;
+ }
+
+ /*
+ * Process the jobs...
+ */
+
+ qcount = 0;
+ qalloc = 0;
+ queue = NULL;
+
+ for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) {
+ /*
+ * Skip leading attributes until we hit a job...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_JOB)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Allocate memory as needed...
+ */
+ if (qcount >= qalloc) {
+ qalloc += 16;
+
+ queue = SMB_REALLOC_ARRAY(queue, print_queue_struct, qalloc);
+
+ if (queue == NULL) {
+ DEBUG(0,("iprint_queue_get: Not enough memory!"));
+ qcount = 0;
+ goto out;
+ }
+ }
+
+ temp = queue + qcount;
+ memset(temp, 0, sizeof(print_queue_struct));
+
+ /*
+ * Pull the needed attributes from this job...
+ */
+
+ job_id = 0;
+ job_priority = 50;
+ job_status = IPP_JOB_PENDING;
+ job_time = 0;
+ job_k_octets = 0;
+ user_name = NULL;
+ job_name = NULL;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_JOB) {
+ if (ippGetName(attr) == NULL) {
+ attr = ippNextAttribute(response);
+ break;
+ }
+
+ if (strcmp(ippGetName(attr), "job-id") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_id = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-k-octets") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_k_octets = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-priority") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_priority = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-state") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_ENUM)
+ job_status = (ipp_jstate_t)ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "time-at-creation") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ {
+ /*
+ * If jobs times are in Unix time, the accuracy of the job
+ * start time depends upon the iPrint server's time being
+ * set correctly. Otherwise, the accuracy depends upon
+ * the Samba server's time being set correctly
+ */
+
+ if (jobUseUnixTime)
+ job_time = ippGetInteger(attr, 0);
+ else
+ job_time = time(NULL) - printer_up_time + ippGetInteger(attr, 0);
+ }
+
+ if (strcmp(ippGetName(attr), "job-name") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_NAMELANG ||
+ ippGetValueTag(attr) == IPP_TAG_NAME))
+ job_name = ippGetString(attr, 0, NULL);
+
+ if (strcmp(ippGetName(attr), "job-originating-user-name") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_NAMELANG ||
+ ippGetValueTag(attr) == IPP_TAG_NAME))
+ user_name = ippGetString(attr, 0, NULL);
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ */
+
+ if (user_name == NULL || job_name == NULL || job_id == 0) {
+ if (attr == NULL)
+ break;
+ else
+ continue;
+ }
+
+ temp->sysjob = job_id;
+ temp->size = job_k_octets * 1024;
+ temp->status = job_status == IPP_JOB_PENDING ? LPQ_QUEUED :
+ job_status == IPP_JOB_STOPPED ? LPQ_PAUSED :
+ job_status == IPP_JOB_HELD ? LPQ_PAUSED :
+ LPQ_PRINTING;
+ temp->priority = job_priority;
+ temp->time = job_time;
+ strncpy(temp->fs_user, user_name, sizeof(temp->fs_user) - 1);
+ strncpy(temp->fs_file, job_name, sizeof(temp->fs_file) - 1);
+
+ qcount ++;
+
+ if (attr == NULL)
+ break;
+ }
+
+ /*
+ * Return the job queue...
+ */
+
+ *q = queue;
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return qcount;
+}
+
+
+/*
+ * 'iprint_queue_pause()' - Pause a print queue.
+ */
+
+static int iprint_queue_pause(int snum)
+{
+ return(-1); /* Not supported without credentials */
+}
+
+
+/*
+ * 'iprint_queue_resume()' - Restart a print queue.
+ */
+
+static int iprint_queue_resume(int snum)
+{
+ return(-1); /* Not supported without credentials */
+}
+
+/*******************************************************************
+ * iPrint printing interface definitions...
+ ******************************************************************/
+
+struct printif iprint_printif =
+{
+ PRINT_IPRINT,
+ iprint_queue_get,
+ iprint_queue_pause,
+ iprint_queue_resume,
+ iprint_job_delete,
+ iprint_job_pause,
+ iprint_job_resume,
+ iprint_job_submit,
+};
+
+#else
+ /* this keeps fussy compilers happy */
+ void print_iprint_dummy(void);
+ void print_iprint_dummy(void) {}
+#endif /* HAVE_IPRINT */
diff --git a/source3/printing/print_standard.c b/source3/printing/print_standard.c
new file mode 100644
index 0000000..7cbdac7
--- /dev/null
+++ b/source3/printing/print_standard.c
@@ -0,0 +1,157 @@
+/*
+ Unix SMB/CIFS implementation.
+ printcap parsing
+ Copyright (C) Karl Auer 1993-1998
+
+ Re-working by Martin Kiff, 1994
+
+ Re-written again by Andrew Tridgell
+
+ Modified for SVID support by Norm Jacobs, 1997
+
+ Modified for CUPS support by Michael Sweet, 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This module contains code to parse and cache printcap data, possibly
+ * in concert with the CUPS/SYSV/AIX-specific code found elsewhere.
+ *
+ * The way this module looks at the printcap file is very simplistic.
+ * Only the local printcap file is inspected (no searching of NIS
+ * databases etc).
+ *
+ * There are assumed to be one or more printer names per record, held
+ * as a set of sub-fields separated by vertical bar symbols ('|') in the
+ * first field of the record. The field separator is assumed to be a colon
+ * ':' and the record separator a newline.
+ *
+ * Lines ending with a backspace '\' are assumed to flag that the following
+ * line is a continuation line so that a set of lines can be read as one
+ * printcap entry.
+ *
+ * A line stating with a hash '#' is assumed to be a comment and is ignored
+ * Comments are discarded before the record is strung together from the
+ * set of continuation lines.
+ *
+ * Opening a pipe for "lpc status" and reading that would probably
+ * be pretty effective. Code to do this already exists in the freely
+ * distributable PCNFS server code.
+ */
+
+/* printcap parsing specific code moved here from printing/pcap.c */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/pcap.h"
+
+/* handle standard printcap - moved from pcap_printer_fn() */
+bool std_pcap_cache_reload(const char *pcap_name, struct pcap_cache **_pcache)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ FILE *pcap_file;
+ char *pcap_line;
+ struct pcap_cache *pcache = NULL;
+ bool print_warning = false;
+
+ if ((pcap_file = fopen(pcap_name, "r")) == NULL) {
+ DEBUG(0, ("Unable to open printcap file %s for read!\n", pcap_name));
+ talloc_free(frame);
+ return false;
+ }
+
+ while ((pcap_line = fgets_slash(frame, NULL, 1024,
+ pcap_file)) != NULL) {
+ char *name = NULL;
+ char *comment = NULL;
+ char *p, *q;
+
+ if (*pcap_line == '#' || *pcap_line == 0) {
+ TALLOC_FREE(pcap_line);
+ continue;
+ }
+
+ /* now we have a real printer line - cut at the first : */
+ if ((p = strchr_m(pcap_line, ':')) != NULL)
+ *p = 0;
+
+ /*
+ * now find the most likely printer name and comment
+ * this is pure guesswork, but it's better than nothing
+ */
+ for (p = pcap_line; p != NULL; p = q) {
+ bool has_punctuation = false;
+
+ if ((q = strchr_m(p, '|')) != NULL)
+ *q++ = 0;
+
+ has_punctuation = (strchr_m(p, ' ') ||
+ strchr_m(p, '\t') ||
+ strchr_m(p, '"') ||
+ strchr_m(p, '\'') ||
+ strchr_m(p, ';') ||
+ strchr_m(p, ',') ||
+ strchr_m(p, '(') ||
+ strchr_m(p, ')'));
+
+ if (name == NULL && !has_punctuation) {
+ name = talloc_strdup(frame, p);
+ TALLOC_FREE(pcap_line);
+ continue;
+ }
+
+ if (has_punctuation) {
+ comment = talloc_strdup(frame, p);
+ TALLOC_FREE(pcap_line);
+ continue;
+ }
+ }
+
+ if (name != NULL) {
+ bool ok;
+
+ if (!print_warning && strlen(name) > MAXPRINTERLEN) {
+ print_warning = true;
+ }
+
+ ok = pcap_cache_add_specific(&pcache,
+ name,
+ comment,
+ NULL);
+ if (!ok) {
+ fclose(pcap_file);
+ pcap_cache_destroy_specific(&pcache);
+ talloc_free(frame);
+ return false;
+ }
+ }
+ TALLOC_FREE(name);
+ TALLOC_FREE(comment);
+ TALLOC_FREE(pcap_line);
+ }
+
+ if (print_warning) {
+ DBG_WARNING("WARNING: You have some printer names that are "
+ "longer than %u characters. These may not be "
+ "accessible to some older clients!\n",
+ (unsigned int)MAXPRINTERLEN);
+ }
+
+ fclose(pcap_file);
+ *_pcache = pcache;
+ talloc_free(frame);
+ return true;
+}
diff --git a/source3/printing/print_svid.c b/source3/printing/print_svid.c
new file mode 100644
index 0000000..4006323
--- /dev/null
+++ b/source3/printing/print_svid.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 1997-1998 by Norm Jacobs, Colorado Springs, Colorado, USA
+ * Copyright (C) 1997-1998 by Sun Microsystem, Inc.
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module implements support for gathering and comparing available
+ * printer information on a SVID or XPG4 compliant system. It does this
+ * through the use of the SVID/XPG4 command "lpstat(1)".
+ *
+ * The expectations is that execution of the command "lpstat -v" will
+ * generate responses in the form of:
+ *
+ * device for serial: /dev/term/b
+ * system for fax: server
+ * system for color: server (as printer chroma)
+ */
+
+
+#include "includes.h"
+#include "printing/pcap.h"
+#include "lib/util_file.h"
+
+#if defined(SYSV) || defined(HPUX)
+bool sysv_cache_reload(struct pcap_cache **_pcache)
+{
+ char **lines;
+ int i;
+ struct pcap_cache *pcache = NULL;
+ char **argl = NULL;
+
+#if defined(HPUX)
+ DEBUG(5, ("reloading hpux printcap cache\n"));
+#else
+ DEBUG(5, ("reloading sysv printcap cache\n"));
+#endif
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "/usr/bin/lpstat");
+ str_list_add_printf(&argl, "-v");
+ if (argl == NULL) {
+ return false;
+ }
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
+ if (lines == NULL) {
+#if defined(HPUX)
+
+ /*
+ * if "lpstat -v" is NULL then we check if schedular is running if it is
+ * that means no printers are added on the HP-UX system, if schedular is not
+ * running we display reload error.
+ */
+
+ char **scheduler;
+
+ argl[1] = talloc_strdup(argl, "-r");
+ if (argl[1] == NULL) {
+ TALLOC_FREE(argl);
+ return false;
+ }
+ scheduler = file_lines_ploadv(talloc_tos(), argl, NULL);
+ TALLOC_FREE(argl);
+ if(!strcmp(*scheduler,"scheduler is running")){
+ DEBUG(3,("No Printers found!!!\n"));
+ TALLOC_FREE(scheduler);
+ return True;
+ }
+ else{
+ DEBUG(3,("Scheduler is not running!!!\n"));
+ TALLOC_FREE(scheduler);
+ return False;
+ }
+#else
+ DEBUG(3,("No Printers found!!!\n"));
+ return False;
+#endif
+ }
+ TALLOC_FREE(argl);
+
+ for (i = 0; lines[i]; i++) {
+ char *name, *tmp;
+ char *buf = lines[i];
+
+ /* eat "system/device for " */
+ if (((tmp = strchr_m(buf, ' ')) == NULL) ||
+ ((tmp = strchr_m(++tmp, ' ')) == NULL))
+ continue;
+
+ /*
+ * In case we're only at the "for ".
+ */
+
+ if(!strncmp("for ", ++tmp, 4)) {
+ tmp=strchr_m(tmp, ' ');
+ tmp++;
+ }
+
+ /* Eat whitespace. */
+
+ while(*tmp == ' ')
+ ++tmp;
+
+ /*
+ * On HPUX there is an extra line that can be ignored.
+ * d.thibadeau 2001/08/09
+ */
+ if(!strncmp("remote to", tmp, 9))
+ continue;
+
+ name = tmp;
+
+ /* truncate the ": ..." */
+ if ((tmp = strchr_m(name, ':')) != NULL)
+ *tmp = '\0';
+
+ /* add it to the cache */
+ if (!pcap_cache_add_specific(&pcache, name, NULL, NULL)) {
+ TALLOC_FREE(lines);
+ pcap_cache_destroy_specific(&pcache);
+ return false;
+ }
+ }
+
+ TALLOC_FREE(lines);
+ *_pcache = pcache;
+ return true;
+}
+
+#else
+/* this keeps fussy compilers happy */
+ void print_svid_dummy(void);
+ void print_svid_dummy(void) {}
+#endif
diff --git a/source3/printing/printer_list.c b/source3/printing/printer_list.c
new file mode 100644
index 0000000..8ff75dc
--- /dev/null
+++ b/source3/printing/printer_list.c
@@ -0,0 +1,460 @@
+/*
+ Unix SMB/CIFS implementation.
+ Share Database of available printers.
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "printer_list.h"
+
+#define PL_KEY_PREFIX "PRINTERLIST/PRN/"
+#define PL_KEY_FORMAT PL_KEY_PREFIX"%s"
+#define PL_TIMESTAMP_KEY "PRINTERLIST/GLOBAL/LAST_REFRESH"
+#define PL_DATA_FORMAT "ddPPP"
+#define PL_TSTAMP_FORMAT "dd"
+
+static struct db_context *printerlist_db;
+
+static struct db_context *get_printer_list_db(void)
+{
+ char *db_path;
+
+ if (printerlist_db != NULL) {
+ return printerlist_db;
+ }
+
+ db_path = lock_path(talloc_tos(), "printer_list.tdb");
+ if (db_path == NULL) {
+ return NULL;
+ }
+
+ printerlist_db = db_open(NULL,
+ db_path,
+ 0,
+ TDB_DEFAULT|TDB_INCOMPATIBLE_HASH,
+ O_RDWR|O_CREAT,
+ 0644,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(db_path);
+ if (printerlist_db == NULL) {
+ DBG_ERR("Failed to open printer_list.tdb\n");
+ }
+ return printerlist_db;
+}
+
+NTSTATUS printer_list_get_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char **comment,
+ const char **location,
+ time_t *last_refresh)
+{
+ struct db_context *db;
+ char *key;
+ TDB_DATA data;
+ uint32_t time_h, time_l;
+ char *nstr = NULL;
+ char *cstr = NULL;
+ char *lstr = NULL;
+ NTSTATUS status;
+ int ret;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ key = talloc_asprintf(mem_ctx, PL_KEY_FORMAT, name);
+ if (!key) {
+ DEBUG(0, ("Failed to allocate key name!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dbwrap_fetch_bystring_upper(db, key, key, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(6, ("Failed to fetch record! "
+ "The printer database is empty?\n"));
+ goto done;
+ }
+
+ ret = tdb_unpack(data.dptr, data.dsize,
+ PL_DATA_FORMAT,
+ &time_h, &time_l, &nstr, &cstr, &lstr);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to un pack printer data"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ if (last_refresh) {
+ *last_refresh = (time_t)(((uint64_t)time_h << 32) + time_l);
+ }
+
+ if (comment) {
+ *comment = talloc_strdup(mem_ctx, cstr);
+ if (!*comment) {
+ DEBUG(1, ("Failed to strdup comment!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ if (location) {
+ *location = talloc_strdup(mem_ctx, lstr);
+ if (*location == NULL) {
+ DEBUG(1, ("Failed to strdup location!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(nstr);
+ SAFE_FREE(cstr);
+ SAFE_FREE(lstr);
+ TALLOC_FREE(key);
+ return status;
+}
+
+bool printer_list_printername_exists(const char *name)
+{
+ struct db_context *db = get_printer_list_db();
+ char *key = NULL;
+ bool ok;
+
+ if (db == NULL) {
+ return false;
+ }
+
+ key = talloc_asprintf_strupper_m(
+ talloc_tos(), PL_KEY_FORMAT, name);
+ if (key == NULL) {
+ return false;
+ }
+
+ ok = dbwrap_exists(db, string_term_tdb_data(key));
+ TALLOC_FREE(key);
+ return ok;
+}
+
+NTSTATUS printer_list_set_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *comment,
+ const char *location,
+ time_t last_refresh)
+{
+ struct db_context *db;
+ char *key;
+ TDB_DATA data;
+ uint64_t time_64;
+ uint32_t time_h, time_l;
+ NTSTATUS status;
+ int len;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ key = talloc_asprintf(mem_ctx, PL_KEY_FORMAT, name);
+ if (!key) {
+ DEBUG(0, ("Failed to allocate key name!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (comment == NULL) {
+ comment = "";
+ }
+
+ if (location == NULL) {
+ location = "";
+ }
+
+ time_64 = last_refresh;
+ time_l = time_64 & 0xFFFFFFFFL;
+ time_h = time_64 >> 32;
+
+ len = tdb_pack(NULL, 0,
+ PL_DATA_FORMAT,
+ time_h,
+ time_l,
+ name,
+ comment,
+ location);
+
+ data.dptr = talloc_array(key, uint8_t, len);
+ if (!data.dptr) {
+ DEBUG(0, ("Failed to allocate tdb data buffer!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ data.dsize = len;
+
+ len = tdb_pack(data.dptr, data.dsize,
+ PL_DATA_FORMAT,
+ time_h,
+ time_l,
+ name,
+ comment,
+ location);
+
+ status = dbwrap_store_bystring_upper(db, key, data, TDB_REPLACE);
+
+done:
+ TALLOC_FREE(key);
+ return status;
+}
+
+NTSTATUS printer_list_get_last_refresh(time_t *last_refresh)
+{
+ struct db_context *db;
+ TDB_DATA data;
+ uint32_t time_h, time_l;
+ NTSTATUS status;
+ int ret;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ZERO_STRUCT(data);
+
+ status = dbwrap_fetch_bystring(db, talloc_tos(), PL_TIMESTAMP_KEY, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to fetch record!\n"));
+ goto done;
+ }
+
+ ret = tdb_unpack(data.dptr, data.dsize,
+ PL_TSTAMP_FORMAT, &time_h, &time_l);
+ TALLOC_FREE(data.dptr);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to un pack printer data"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ *last_refresh = (time_t)(((uint64_t)time_h << 32) + time_l);
+ status = NT_STATUS_OK;
+
+done:
+ return status;
+}
+
+NTSTATUS printer_list_mark_reload(void)
+{
+ struct db_context *db;
+ TDB_DATA data;
+ uint32_t time_h, time_l;
+ time_t now = time_mono(NULL);
+ NTSTATUS status;
+ int len;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ time_l = ((uint64_t)now) & 0xFFFFFFFFL;
+ time_h = ((uint64_t)now) >> 32;
+
+ len = tdb_pack(NULL, 0, PL_TSTAMP_FORMAT, time_h, time_l);
+
+ data.dptr = talloc_array(talloc_tos(), uint8_t, len);
+ if (!data.dptr) {
+ DEBUG(0, ("Failed to allocate tdb data buffer!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ data.dsize = len;
+
+ len = tdb_pack(data.dptr, data.dsize,
+ PL_TSTAMP_FORMAT, time_h, time_l);
+
+ status = dbwrap_store_bystring(db, PL_TIMESTAMP_KEY,
+ data, TDB_REPLACE);
+
+done:
+ TALLOC_FREE(data.dptr);
+ return status;
+}
+
+typedef int (printer_list_trv_fn_t)(struct db_record *, void *);
+
+static NTSTATUS printer_list_traverse(printer_list_trv_fn_t *fn,
+ void *private_data,
+ bool read_only)
+{
+ struct db_context *db;
+ NTSTATUS status;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (read_only) {
+ status = dbwrap_traverse_read(db, fn, private_data, NULL);
+ } else {
+ status = dbwrap_traverse(db, fn, private_data, NULL);
+ }
+
+ return status;
+}
+
+struct printer_list_clean_state {
+ time_t last_refresh;
+ NTSTATUS status;
+};
+
+static int printer_list_clean_fn(struct db_record *rec, void *private_data)
+{
+ struct printer_list_clean_state *state =
+ (struct printer_list_clean_state *)private_data;
+ uint32_t time_h, time_l;
+ time_t refresh;
+ char *name;
+ char *comment;
+ char *location;
+ int ret;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+
+ /* skip anything that does not contain PL_DATA_FORMAT data */
+ if (strncmp((char *)key.dptr,
+ PL_KEY_PREFIX, sizeof(PL_KEY_PREFIX)-1)) {
+ return 0;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ ret = tdb_unpack(value.dptr, value.dsize,
+ PL_DATA_FORMAT, &time_h, &time_l, &name, &comment,
+ &location);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to un pack printer data"));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+
+ SAFE_FREE(name);
+ SAFE_FREE(comment);
+ SAFE_FREE(location);
+
+ refresh = (time_t)(((uint64_t)time_h << 32) + time_l);
+
+ if (refresh < state->last_refresh) {
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+NTSTATUS printer_list_clean_old(void)
+{
+ struct printer_list_clean_state state;
+ NTSTATUS status;
+
+ status = printer_list_get_last_refresh(&state.last_refresh);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ state.status = NT_STATUS_OK;
+
+ status = printer_list_traverse(printer_list_clean_fn, &state, false);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL) &&
+ !NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ }
+
+ return status;
+}
+
+struct printer_list_exec_state {
+ void (*fn)(const char *, const char *, const char *, void *);
+ void *private_data;
+ NTSTATUS status;
+};
+
+static int printer_list_exec_fn(struct db_record *rec, void *private_data)
+{
+ struct printer_list_exec_state *state =
+ (struct printer_list_exec_state *)private_data;
+ uint32_t time_h, time_l;
+ char *name;
+ char *comment;
+ char *location;
+ int ret;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+
+ /* always skip PL_TIMESTAMP_KEY key */
+ if (strequal((const char *)key.dptr, PL_TIMESTAMP_KEY)) {
+ return 0;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ ret = tdb_unpack(value.dptr, value.dsize,
+ PL_DATA_FORMAT, &time_h, &time_l, &name, &comment,
+ &location);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to un pack printer data"));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+
+ state->fn(name, comment, location, state->private_data);
+
+ SAFE_FREE(name);
+ SAFE_FREE(comment);
+ SAFE_FREE(location);
+ return 0;
+}
+
+NTSTATUS printer_list_read_run_fn(void (*fn)(const char *, const char *, const char *, void *),
+ void *private_data)
+{
+ struct printer_list_exec_state state;
+ NTSTATUS status;
+
+ state.fn = fn;
+ state.private_data = private_data;
+ state.status = NT_STATUS_OK;
+
+ status = printer_list_traverse(printer_list_exec_fn, &state, true);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL) &&
+ !NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ }
+
+ return status;
+}
diff --git a/source3/printing/printer_list.h b/source3/printing/printer_list.h
new file mode 100644
index 0000000..c687048
--- /dev/null
+++ b/source3/printing/printer_list.h
@@ -0,0 +1,105 @@
+/*
+ Unix SMB/CIFS implementation.
+ Share Database of available printers.
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PRINTER_LIST_H_
+#define _PRINTER_LIST_H_
+
+/**
+ * @brief Get the comment and the last refresh time from the printer list
+ * database.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] name The printer name to lookup.
+ *
+ * @param[out] comment A pointer to store the comment of the printer.
+ *
+ * @param[out] location A pointer to store the location of the printer.
+ *
+ * @param[out] last_refresh A pointer to store the last refresh time of the
+ * printer.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_get_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char **comment,
+ const char **location,
+ time_t *last_refresh);
+
+bool printer_list_printername_exists(const char *name);
+
+/**
+ * @brief Add a printer to the printer list database.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] name The printer name to store in the db.
+ *
+ * @param[in] comment The comment to store in the db.
+ *
+ * @param[in] location The location to store in the db.
+ *
+ * @param[in] last_refresh The last refresh time of the printer to store in
+ * the db.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_set_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *comment,
+ const char *location,
+ time_t last_refresh);
+
+/**
+ * @brief Get the time of the last refresh of the printer database.
+ *
+ * @param[out] last_refresh The last refresh time in the db.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_get_last_refresh(time_t *last_refresh);
+
+/**
+ * @brief Mark the database as reloaded.
+ *
+ * This sets the last refresh time to the current time. You can get the last
+ * reload/refresh time of the database with printer_list_get_last_refresh().
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_mark_reload(void);
+
+/**
+ * @brief Cleanup old entries in the database.
+ *
+ * Entries older than the last refresh times will be deleted.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_clean_old(void);
+
+NTSTATUS printer_list_read_run_fn(void (*fn)(const char *, const char *, const char *, void *),
+ void *private_data);
+#endif /* _PRINTER_LIST_H_ */
diff --git a/source3/printing/printing.c b/source3/printing/printing.c
new file mode 100644
index 0000000..dbe5a20
--- /dev/null
+++ b/source3/printing/printing.c
@@ -0,0 +1,3266 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/globals.h"
+#include "system/syslog.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "nt_printing.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "printing/notify.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/queue_process.h"
+#include "serverid.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+
+extern userdom_struct current_user_info;
+
+/* Current printer interface */
+static bool remove_from_jobs_added(const char* sharename, uint32_t jobid);
+
+static int get_queue_status(const char* sharename, print_status_struct *);
+
+/****************************************************************************
+ Initialise the printing backend. Called once at startup before the fork().
+****************************************************************************/
+
+bool print_backend_init(struct messaging_context *msg_ctx)
+{
+ const char *sversion = "INFO/version";
+ int services = lp_numservices();
+ int snum;
+ bool ok;
+ char *print_cache_path;
+
+ print_cache_path = cache_path(talloc_tos(), "printing");
+ if (print_cache_path == NULL) {
+ return false;
+ }
+ ok = directory_create_or_exist(print_cache_path, 0755);
+ TALLOC_FREE(print_cache_path);
+ if (!ok) {
+ return false;
+ }
+
+ /* handle a Samba upgrade */
+
+ for (snum = 0; snum < services; snum++) {
+ struct tdb_print_db *pdb;
+ if (!lp_printable(snum))
+ continue;
+
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (!pdb)
+ continue;
+ if (tdb_lock_bystring(pdb->tdb, sversion) != 0) {
+ DEBUG(0,("print_backend_init: Failed to open printer %s database\n", lp_const_servicename(snum) ));
+ release_print_db(pdb);
+ return False;
+ }
+ if (tdb_fetch_int32(pdb->tdb, sversion) != PRINT_DATABASE_VERSION) {
+ tdb_wipe_all(pdb->tdb);
+ tdb_store_int32(pdb->tdb, sversion, PRINT_DATABASE_VERSION);
+ }
+ tdb_unlock_bystring(pdb->tdb, sversion);
+ release_print_db(pdb);
+ }
+
+ close_all_print_db(); /* Don't leave any open. */
+
+ /* do NT print initialization... */
+ return nt_printing_init(msg_ctx);
+}
+
+/****************************************************************************
+ Shut down printing backend. Called once at shutdown to close the tdb.
+****************************************************************************/
+
+void printing_end(void)
+{
+ close_all_print_db(); /* Don't leave any open. */
+}
+
+/****************************************************************************
+ Retrieve the set of printing functions for a given service. This allows
+ us to set the printer function table based on the value of the 'printing'
+ service parameter.
+
+ Use the generic interface as the default and only use cups interface only
+ when asked for (and only when supported)
+****************************************************************************/
+
+static struct printif *get_printer_fns_from_type( enum printing_types type )
+{
+ struct printif *printer_fns = &generic_printif;
+
+#ifdef HAVE_CUPS
+ if ( type == PRINT_CUPS ) {
+ printer_fns = &cups_printif;
+ }
+#endif /* HAVE_CUPS */
+
+#ifdef HAVE_IPRINT
+ if ( type == PRINT_IPRINT ) {
+ printer_fns = &iprint_printif;
+ }
+#endif /* HAVE_IPRINT */
+
+ printer_fns->type = type;
+
+ return printer_fns;
+}
+
+static struct printif *get_printer_fns( int snum )
+{
+ return get_printer_fns_from_type( (enum printing_types)lp_printing(snum) );
+}
+
+
+/****************************************************************************
+ Useful function to generate a tdb key.
+****************************************************************************/
+
+static TDB_DATA print_key(uint32_t jobid, uint32_t *tmp)
+{
+ TDB_DATA ret;
+
+ SIVAL(tmp, 0, jobid);
+ ret.dptr = (uint8_t *)tmp;
+ ret.dsize = sizeof(*tmp);
+ return ret;
+}
+
+/****************************************************************************
+ Pack the devicemode to store it in a tdb.
+****************************************************************************/
+static int pack_devicemode(struct spoolss_DeviceMode *devmode, uint8_t *buf, int buflen)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = { .data = NULL };
+ int len = 0;
+
+ if (devmode) {
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(),
+ devmode,
+ (ndr_push_flags_fn_t)
+ ndr_push_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("pack_devicemode: "
+ "error encoding spoolss_DeviceMode\n"));
+ goto done;
+ }
+ }
+
+ len = tdb_pack(buf, buflen, "B", blob.length, blob.data);
+
+ if (devmode) {
+ DEBUG(8, ("Packed devicemode [%s]\n", devmode->formname));
+ }
+
+done:
+ return len;
+}
+
+/****************************************************************************
+ Unpack the devicemode to store it in a tdb.
+****************************************************************************/
+static int unpack_devicemode(TALLOC_CTX *mem_ctx,
+ const uint8_t *buf, int buflen,
+ struct spoolss_DeviceMode **devmode)
+{
+ struct spoolss_DeviceMode *dm;
+ enum ndr_err_code ndr_err;
+ char *data = NULL;
+ uint32_t data_len = 0;
+ DATA_BLOB blob;
+ int len = 0;
+
+ *devmode = NULL;
+
+ len = tdb_unpack(buf, buflen, "B", &data_len, &data);
+ if (!data) {
+ return len;
+ }
+
+ dm = talloc_zero(mem_ctx, struct spoolss_DeviceMode);
+ if (!dm) {
+ goto done;
+ }
+
+ blob = data_blob_const(data, data_len);
+
+ ndr_err = ndr_pull_struct_blob(&blob, dm, dm,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("unpack_devicemode: "
+ "error parsing spoolss_DeviceMode\n"));
+ goto done;
+ }
+
+ DEBUG(8, ("Unpacked devicemode [%s](%s)\n",
+ dm->devicename, dm->formname));
+ if (dm->driverextra_data.data) {
+ DEBUG(8, ("with a private section of %d bytes\n",
+ dm->__driverextra_length));
+ }
+
+ *devmode = dm;
+
+done:
+ SAFE_FREE(data);
+ return len;
+}
+
+/***********************************************************************
+ unpack a pjob from a tdb buffer
+***********************************************************************/
+
+static int unpack_pjob(TALLOC_CTX *mem_ctx, uint8_t *buf, int buflen,
+ struct printjob *pjob)
+{
+ int len = 0;
+ int used;
+ uint32_t pjpid, pjjobid, pjsysjob, pjfd, pjstarttime, pjstatus;
+ uint32_t pjsize, pjpage_count, pjspooled, pjsmbjob;
+
+ if (!buf || !pjob) {
+ return -1;
+ }
+
+ len += tdb_unpack(buf+len, buflen-len, "ddddddddddfffff",
+ &pjpid,
+ &pjjobid,
+ &pjsysjob,
+ &pjfd,
+ &pjstarttime,
+ &pjstatus,
+ &pjsize,
+ &pjpage_count,
+ &pjspooled,
+ &pjsmbjob,
+ pjob->filename,
+ pjob->jobname,
+ pjob->user,
+ pjob->clientmachine,
+ pjob->queuename);
+
+ if (len == -1) {
+ return -1;
+ }
+
+ used = unpack_devicemode(mem_ctx, buf+len, buflen-len, &pjob->devmode);
+ if (used == -1) {
+ return -1;
+ }
+
+ len += used;
+
+ pjob->pid = pjpid;
+ pjob->jobid = pjjobid;
+ pjob->sysjob = pjsysjob;
+ pjob->fd = pjfd;
+ pjob->starttime = pjstarttime;
+ pjob->status = pjstatus;
+ pjob->size = pjsize;
+ pjob->page_count = pjpage_count;
+ pjob->spooled = pjspooled;
+ pjob->smbjob = pjsmbjob;
+
+ return len;
+
+}
+
+/****************************************************************************
+ Useful function to find a print job in the database.
+****************************************************************************/
+
+static struct printjob *print_job_find(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid)
+{
+ struct printjob *pjob;
+ uint32_t tmp;
+ TDB_DATA ret;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ DEBUG(10,("print_job_find: looking up job %u for share %s\n",
+ (unsigned int)jobid, sharename ));
+
+ if (!pdb) {
+ return NULL;
+ }
+
+ ret = tdb_fetch(pdb->tdb, print_key(jobid, &tmp));
+ release_print_db(pdb);
+
+ if (!ret.dptr) {
+ DEBUG(10, ("print_job_find: failed to find jobid %u.\n",
+ jobid));
+ return NULL;
+ }
+
+ pjob = talloc_zero(mem_ctx, struct printjob);
+ if (pjob == NULL) {
+ goto err_out;
+ }
+
+ if (unpack_pjob(mem_ctx, ret.dptr, ret.dsize, pjob) == -1) {
+ DEBUG(10, ("failed to unpack jobid %u.\n", jobid));
+ talloc_free(pjob);
+ pjob = NULL;
+ goto err_out;
+ }
+
+ DEBUG(10,("print_job_find: returning system job %d for jobid %u.\n",
+ pjob->sysjob, jobid));
+ SMB_ASSERT(pjob->jobid == jobid);
+
+err_out:
+ SAFE_FREE(ret.dptr);
+ return pjob;
+}
+
+struct job_traverse_state {
+ int sysjob;
+ uint32_t jobid;
+};
+
+/* find spoolss jobid based on sysjob */
+static int sysjob_to_jobid_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *private_data)
+{
+ struct printjob *pjob;
+ struct job_traverse_state *state =
+ (struct job_traverse_state *)private_data;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ pjob = (struct printjob *)data.dptr;
+ if (key.dsize != sizeof(uint32_t))
+ return 0;
+
+ if (state->sysjob == pjob->sysjob) {
+ state->jobid = pjob->jobid;
+ return 1;
+ }
+
+ return 0;
+}
+
+uint32_t sysjob_to_jobid_pdb(struct tdb_print_db *pdb, int sysjob)
+{
+ struct job_traverse_state state;
+
+ state.sysjob = sysjob;
+ state.jobid = (uint32_t)-1;
+
+ tdb_traverse(pdb->tdb, sysjob_to_jobid_traverse_fn, &state);
+
+ return state.jobid;
+}
+
+/****************************************************************************
+ This is a *horribly expensive call as we have to iterate through all the
+ current printer tdb's. Don't do this often ! JRA.
+****************************************************************************/
+
+uint32_t sysjob_to_jobid(int unix_jobid)
+{
+ int services = lp_numservices();
+ int snum;
+ struct job_traverse_state state;
+
+ state.sysjob = unix_jobid;
+ state.jobid = (uint32_t)-1;
+
+ for (snum = 0; snum < services; snum++) {
+ struct tdb_print_db *pdb;
+ if (!lp_printable(snum))
+ continue;
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (!pdb) {
+ continue;
+ }
+ tdb_traverse(pdb->tdb, sysjob_to_jobid_traverse_fn, &state);
+ release_print_db(pdb);
+ if (state.jobid != (uint32_t)-1)
+ return state.jobid;
+ }
+ return (uint32_t)-1;
+}
+
+/* find sysjob based on spoolss jobid */
+static int jobid_to_sysjob_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *private_data)
+{
+ struct printjob *pjob;
+ struct job_traverse_state *state =
+ (struct job_traverse_state *)private_data;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ pjob = (struct printjob *)data.dptr;
+ if (key.dsize != sizeof(uint32_t))
+ return 0;
+
+ if (state->jobid == pjob->jobid) {
+ state->sysjob = pjob->sysjob;
+ return 1;
+ }
+
+ return 0;
+}
+
+int jobid_to_sysjob_pdb(struct tdb_print_db *pdb, uint32_t jobid)
+{
+ struct job_traverse_state state;
+
+ state.sysjob = -1;
+ state.jobid = jobid;
+
+ tdb_traverse(pdb->tdb, jobid_to_sysjob_traverse_fn, &state);
+
+ return state.sysjob;
+}
+
+/****************************************************************************
+ Send notifications based on what has changed after a pjob_store.
+****************************************************************************/
+
+static const struct {
+ uint32_t lpq_status;
+ uint32_t spoolss_status;
+} lpq_to_spoolss_status_map[] = {
+ { LPQ_QUEUED, JOB_STATUS_QUEUED },
+ { LPQ_PAUSED, JOB_STATUS_PAUSED },
+ { LPQ_SPOOLING, JOB_STATUS_SPOOLING },
+ { LPQ_PRINTING, JOB_STATUS_PRINTING },
+ { LPQ_DELETING, JOB_STATUS_DELETING },
+ { LPQ_OFFLINE, JOB_STATUS_OFFLINE },
+ { LPQ_PAPEROUT, JOB_STATUS_PAPEROUT },
+ { LPQ_PRINTED, JOB_STATUS_PRINTED },
+ { LPQ_DELETED, JOB_STATUS_DELETED },
+ { LPQ_BLOCKED, JOB_STATUS_BLOCKED_DEVQ },
+ { LPQ_USER_INTERVENTION, JOB_STATUS_USER_INTERVENTION },
+ { (uint32_t)-1, 0 }
+};
+
+/* Convert a lpq status value stored in printing.tdb into the
+ appropriate win32 API constant. */
+
+static uint32_t map_to_spoolss_status(uint32_t lpq_status)
+{
+ int i = 0;
+
+ while (lpq_to_spoolss_status_map[i].lpq_status != -1) {
+ if (lpq_to_spoolss_status_map[i].lpq_status == lpq_status)
+ return lpq_to_spoolss_status_map[i].spoolss_status;
+ i++;
+ }
+
+ return 0;
+}
+
+/***************************************************************************
+ Append a jobid to a list
+***************************************************************************/
+
+static bool add_to_jobs_list(
+ struct tdb_print_db *pdb, uint32_t jobid, const char *key)
+{
+ uint8_t store_jobid[sizeof(uint32_t)];
+ TDB_DATA data = {
+ .dptr = store_jobid, .dsize = sizeof(store_jobid)
+ };
+ int ret;
+
+ SIVAL(&store_jobid, 0, jobid);
+
+ DBG_DEBUG("Added jobid %"PRIu32" to %s\n", jobid, key);
+
+ ret = tdb_append(pdb->tdb, string_tdb_data(key), data);
+ return ret == 0;
+}
+
+/***************************************************************************
+ Remove a jobid from the 'jobs changed' list.
+***************************************************************************/
+
+static bool remove_from_jobs_list(
+ const char *keystr, const char *sharename, uint32_t jobid)
+{
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ TDB_DATA data, key;
+ size_t job_count, i;
+ bool ret = False;
+ bool gotlock = False;
+
+ if (!pdb) {
+ return False;
+ }
+
+ ZERO_STRUCT(data);
+
+ key = string_tdb_data(keystr);
+
+ if (tdb_chainlock_with_timeout(pdb->tdb, key, 5) != 0)
+ goto out;
+
+ gotlock = True;
+
+ data = tdb_fetch(pdb->tdb, key);
+
+ if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0))
+ goto out;
+
+ job_count = data.dsize / 4;
+ for (i = 0; i < job_count; i++) {
+ uint32_t ch_jobid;
+
+ ch_jobid = IVAL(data.dptr, i*4);
+ if (ch_jobid == jobid) {
+ if (i < job_count -1 )
+ memmove(data.dptr + (i*4), data.dptr + (i*4) + 4, (job_count - i - 1)*4 );
+ data.dsize -= 4;
+ if (tdb_store(pdb->tdb, key, data, TDB_REPLACE) != 0)
+ goto out;
+ break;
+ }
+ }
+
+ ret = True;
+ out:
+
+ if (gotlock)
+ tdb_chainunlock(pdb->tdb, key);
+ SAFE_FREE(data.dptr);
+ release_print_db(pdb);
+ if (ret)
+ DBG_DEBUG("removed jobid %"PRIu32"\n", jobid);
+ else
+ DBG_DEBUG("Failed to remove jobid %"PRIu32"\n", jobid);
+ return ret;
+}
+
+static bool remove_from_jobs_changed(const char* sharename, uint32_t jobid)
+{
+ bool ret = remove_from_jobs_list(
+ "INFO/jobs_changed", sharename, jobid);
+ return ret;
+}
+
+static void pjob_store_notify(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid,
+ struct printjob *old_data,
+ struct printjob *new_data,
+ bool *pchanged)
+{
+ bool new_job = false;
+ bool changed = false;
+
+ if (old_data == NULL) {
+ new_job = true;
+ }
+
+ /* ACHTUNG! Due to a bug in Samba's spoolss parsing of the
+ NOTIFY_INFO_DATA buffer, we *have* to send the job submission
+ time first or else we'll end up with potential alignment
+ errors. I don't think the systemtime should be spooled as
+ a string, but this gets us around that error.
+ --jerry (i'll feel dirty for this) */
+
+ if (new_job) {
+ notify_job_submitted(ev, msg_ctx,
+ sharename, jobid, new_data->starttime);
+ notify_job_username(ev, msg_ctx,
+ sharename, jobid, new_data->user);
+ notify_job_name(ev, msg_ctx,
+ sharename, jobid, new_data->jobname);
+ notify_job_status(ev, msg_ctx,
+ sharename, jobid, map_to_spoolss_status(new_data->status));
+ notify_job_total_bytes(ev, msg_ctx,
+ sharename, jobid, new_data->size);
+ notify_job_total_pages(ev, msg_ctx,
+ sharename, jobid, new_data->page_count);
+ } else {
+ if (!strequal(old_data->jobname, new_data->jobname)) {
+ notify_job_name(ev, msg_ctx, sharename,
+ jobid, new_data->jobname);
+ changed = true;
+ }
+
+ if (old_data->status != new_data->status) {
+ notify_job_status(ev, msg_ctx,
+ sharename, jobid,
+ map_to_spoolss_status(new_data->status));
+ }
+
+ if (old_data->size != new_data->size) {
+ notify_job_total_bytes(ev, msg_ctx,
+ sharename, jobid, new_data->size);
+ }
+
+ if (old_data->page_count != new_data->page_count) {
+ notify_job_total_pages(ev, msg_ctx,
+ sharename, jobid,
+ new_data->page_count);
+ }
+ }
+
+ *pchanged = changed;
+}
+
+/****************************************************************************
+ Store a job structure back to the database.
+****************************************************************************/
+
+static bool pjob_store(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid,
+ struct printjob *pjob)
+{
+ uint32_t tmp;
+ TDB_DATA old_data, new_data;
+ bool ret = False;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ uint8_t *buf = NULL;
+ int len, newlen, buflen;
+
+
+ if (!pdb)
+ return False;
+
+ /* Get old data */
+
+ old_data = tdb_fetch(pdb->tdb, print_key(jobid, &tmp));
+
+ /* Doh! Now we have to pack/unpack data since the NT_DEVICEMODE was added */
+
+ newlen = 0;
+
+ do {
+ len = 0;
+ buflen = newlen;
+ len += tdb_pack(buf+len, buflen-len, "ddddddddddfffff",
+ (uint32_t)pjob->pid,
+ (uint32_t)pjob->jobid,
+ (uint32_t)pjob->sysjob,
+ (uint32_t)pjob->fd,
+ (uint32_t)pjob->starttime,
+ (uint32_t)pjob->status,
+ (uint32_t)pjob->size,
+ (uint32_t)pjob->page_count,
+ (uint32_t)pjob->spooled,
+ (uint32_t)pjob->smbjob,
+ pjob->filename,
+ pjob->jobname,
+ pjob->user,
+ pjob->clientmachine,
+ pjob->queuename);
+
+ len += pack_devicemode(pjob->devmode, buf+len, buflen-len);
+
+ if (buflen != len) {
+ buf = (uint8_t *)SMB_REALLOC(buf, len);
+ if (!buf) {
+ DEBUG(0,("pjob_store: failed to enlarge buffer!\n"));
+ goto done;
+ }
+ newlen = len;
+ }
+ } while ( buflen != len );
+
+
+ /* Store new data */
+
+ new_data.dptr = buf;
+ new_data.dsize = len;
+ ret = (tdb_store(pdb->tdb, print_key(jobid, &tmp), new_data,
+ TDB_REPLACE) == 0);
+
+ /* Send notify updates for what has changed */
+
+ if (ret) {
+ bool changed = false;
+ struct printjob old_pjob;
+
+ if (old_data.dsize) {
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL)
+ goto done;
+
+ len = unpack_pjob(tmp_ctx, old_data.dptr,
+ old_data.dsize, &old_pjob);
+ if (len != -1 ) {
+ pjob_store_notify(ev,
+ msg_ctx,
+ sharename, jobid, &old_pjob,
+ pjob,
+ &changed);
+ if (changed) {
+ add_to_jobs_list(
+ pdb,
+ jobid,
+ "INFO/jobs_changed");
+ }
+ }
+ talloc_free(tmp_ctx);
+
+ } else {
+ /* new job */
+ pjob_store_notify(ev, msg_ctx,
+ sharename, jobid, NULL, pjob,
+ &changed);
+ }
+ }
+
+done:
+ release_print_db(pdb);
+ SAFE_FREE( old_data.dptr );
+ SAFE_FREE( buf );
+
+ return ret;
+}
+
+/****************************************************************************
+ Remove a job structure from the database.
+****************************************************************************/
+
+static void pjob_delete(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid)
+{
+ uint32_t tmp;
+ struct printjob *pjob;
+ uint32_t job_status = 0;
+ struct tdb_print_db *pdb;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ pdb = get_print_db_byname(sharename);
+ if (!pdb) {
+ goto err_out;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ DEBUG(5, ("we were asked to delete nonexistent job %u\n",
+ jobid));
+ goto err_release;
+ }
+
+ /* We must cycle through JOB_STATUS_DELETING and
+ JOB_STATUS_DELETED for the port monitor to delete the job
+ properly. */
+
+ job_status = JOB_STATUS_DELETING|JOB_STATUS_DELETED;
+ notify_job_status(ev, msg_ctx, sharename, jobid, job_status);
+
+ /* Remove from printing.tdb */
+
+ tdb_delete(pdb->tdb, print_key(jobid, &tmp));
+ remove_from_jobs_added(sharename, jobid);
+ rap_jobid_delete(sharename, jobid);
+err_release:
+ release_print_db(pdb);
+err_out:
+ talloc_free(tmp_ctx);
+}
+
+/****************************************************************************
+ List a unix job in the print database.
+****************************************************************************/
+
+static void print_unix_job(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, print_queue_struct *q,
+ uint32_t jobid)
+{
+ struct printjob pj, *old_pj;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ if (jobid == (uint32_t)-1) {
+ jobid = q->sysjob + UNIX_JOB_START;
+ }
+
+ /* Preserve the timestamp on an existing unix print job */
+
+ old_pj = print_job_find(tmp_ctx, sharename, jobid);
+
+ ZERO_STRUCT(pj);
+
+ pj.pid = (pid_t)-1;
+ pj.jobid = jobid;
+ pj.sysjob = q->sysjob;
+ pj.fd = -1;
+ pj.starttime = old_pj ? old_pj->starttime : q->time;
+ pj.status = q->status;
+ pj.size = q->size;
+ pj.spooled = True;
+ fstrcpy(pj.filename, old_pj ? old_pj->filename : "");
+ if (jobid < UNIX_JOB_START) {
+ pj.smbjob = True;
+ fstrcpy(pj.jobname, old_pj ? old_pj->jobname : "Remote Downlevel Document");
+ } else {
+ pj.smbjob = False;
+ fstrcpy(pj.jobname, old_pj ? old_pj->jobname : q->fs_file);
+ }
+ fstrcpy(pj.user, old_pj ? old_pj->user : q->fs_user);
+ fstrcpy(pj.queuename, old_pj ? old_pj->queuename : sharename );
+
+ pjob_store(ev, msg_ctx, sharename, jobid, &pj);
+ talloc_free(tmp_ctx);
+}
+
+
+struct traverse_struct {
+ print_queue_struct *queue;
+ size_t qcount, snum, maxcount, total_jobs;
+ const char *sharename;
+ time_t lpq_time;
+ const char *lprm_command;
+ struct printif *print_if;
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ TALLOC_CTX *mem_ctx;
+};
+
+/****************************************************************************
+ Utility fn to delete any jobs that are no longer active.
+****************************************************************************/
+
+static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+ struct traverse_struct *ts = (struct traverse_struct *)state;
+ struct printjob pjob;
+ uint32_t jobid;
+ size_t i = 0;
+
+ if ( key.dsize != sizeof(jobid) )
+ return 0;
+
+ if (unpack_pjob(ts->mem_ctx, data.dptr, data.dsize, &pjob) == -1)
+ return 0;
+ talloc_free(pjob.devmode);
+ jobid = pjob.jobid;
+
+ 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 (ts->queue[i].sysjob == pjob.sysjob) {
+ break;
+ }
+ }
+ if (i == ts->qcount) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !smbjob\n",
+ (unsigned int)jobid ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ return 0;
+ }
+
+ /* need to continue the the bottom of the function to
+ save the correct attributes */
+ }
+
+ /* maybe it hasn't been spooled yet */
+ if (!pjob.spooled) {
+ /* if a job is not spooled and the process doesn't
+ exist then kill it. This cleans up after smbd
+ deaths */
+ if (!process_exists_by_pid(pjob.pid)) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !process_exists (%u)\n",
+ (unsigned int)jobid, (unsigned int)pjob.pid ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ } else
+ ts->total_jobs++;
+ return 0;
+ }
+
+ /* this check only makes sense for jobs submitted from Windows clients */
+
+ if (pjob.smbjob) {
+ for (i=0;i<ts->qcount;i++) {
+ if ( pjob.status == LPQ_DELETED )
+ continue;
+
+ if (ts->queue[i].sysjob == pjob.sysjob) {
+
+ /* try to clean up any jobs that need to be deleted */
+
+ if ( pjob.status == LPQ_DELETING ) {
+ int result;
+
+ result = (*(ts->print_if->job_delete))(
+ ts->sharename, ts->lprm_command, &pjob );
+
+ if ( result != 0 ) {
+ /* if we can't delete, then reset the job status */
+ pjob.status = LPQ_QUEUED;
+ pjob_store(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid, &pjob);
+ }
+ else {
+ /* if we deleted the job, the remove the tdb record */
+ pjob_delete(ts->ev,
+ ts->msg_ctx,
+ ts->sharename, jobid);
+ pjob.status = LPQ_DELETED;
+ }
+
+ }
+
+ 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) {
+
+ /* 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.
+ Don't delete the job if it was submitted after the lpq_time. */
+
+ if (pjob.starttime < ts->lpq_time) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to pjob.starttime (%u) < ts->lpq_time (%u)\n",
+ (unsigned int)jobid,
+ (unsigned int)pjob.starttime,
+ (unsigned int)ts->lpq_time ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ } else
+ ts->total_jobs++;
+ return 0;
+ }
+
+ /* Save the pjob attributes we will store. */
+ ts->queue[i].sysjob = pjob.sysjob;
+ ts->queue[i].size = pjob.size;
+ ts->queue[i].page_count = pjob.page_count;
+ ts->queue[i].status = pjob.status;
+ ts->queue[i].priority = 1;
+ ts->queue[i].time = pjob.starttime;
+ fstrcpy(ts->queue[i].fs_user, pjob.user);
+ fstrcpy(ts->queue[i].fs_file, pjob.jobname);
+
+ ts->total_jobs++;
+
+ return 0;
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static void print_cache_flush(const char *sharename)
+{
+ fstring key;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return;
+ slprintf(key, sizeof(key)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, key, -1);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+ Check if someone already thinks they are doing the update.
+****************************************************************************/
+
+static pid_t get_updating_pid(const char *sharename)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+ pid_t updating_pid;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return (pid_t)-1;
+ slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ data = tdb_fetch(pdb->tdb, key);
+ release_print_db(pdb);
+ if (!data.dptr || data.dsize != sizeof(pid_t)) {
+ SAFE_FREE(data.dptr);
+ return (pid_t)-1;
+ }
+
+ updating_pid = IVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+
+ if (process_exists_by_pid(updating_pid))
+ return updating_pid;
+
+ return (pid_t)-1;
+}
+
+/****************************************************************************
+ Set the fact that we're doing the update, or have finished doing the update
+ in the tdb.
+****************************************************************************/
+
+static void set_updating_pid(const fstring sharename, bool updating)
+{
+ fstring keystr;
+ TDB_DATA key;
+ TDB_DATA data;
+ pid_t updating_pid = getpid();
+ uint8_t buffer[4];
+
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return;
+
+ slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ DEBUG(5, ("set_updating_pid: %supdating lpq cache for print share %s\n",
+ updating ? "" : "not ",
+ sharename ));
+
+ if ( !updating ) {
+ tdb_delete(pdb->tdb, key);
+ release_print_db(pdb);
+ return;
+ }
+
+ SIVAL( buffer, 0, updating_pid);
+ data.dptr = buffer;
+ data.dsize = 4; /* we always assume this is a 4 byte value */
+
+ tdb_store(pdb->tdb, key, data, TDB_REPLACE);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+ 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;
+}
+
+/****************************************************************************
+ Store the sorted queue representation for later portmon retrieval.
+ Skip deleted jobs
+****************************************************************************/
+
+static void store_queue_struct(struct tdb_print_db *pdb, struct traverse_struct *pts)
+{
+ TDB_DATA data;
+ int max_reported_jobs = lp_max_reported_print_jobs(pts->snum);
+ print_queue_struct *queue = pts->queue;
+ size_t len;
+ size_t i;
+ unsigned int qcount;
+
+ if (max_reported_jobs && (max_reported_jobs < pts->qcount))
+ pts->qcount = max_reported_jobs;
+ qcount = 0;
+
+ /* Work out the size. */
+ data.dsize = 0;
+ data.dsize += tdb_pack(NULL, 0, "d", qcount);
+
+ for (i = 0; i < pts->qcount; i++) {
+ if ( queue[i].status == LPQ_DELETED )
+ continue;
+
+ qcount++;
+ data.dsize += tdb_pack(NULL, 0, "ddddddff",
+ (uint32_t)queue[i].sysjob,
+ (uint32_t)queue[i].size,
+ (uint32_t)queue[i].page_count,
+ (uint32_t)queue[i].status,
+ (uint32_t)queue[i].priority,
+ (uint32_t)queue[i].time,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ }
+
+ if ((data.dptr = (uint8_t *)SMB_MALLOC(data.dsize)) == NULL)
+ return;
+
+ len = 0;
+ len += tdb_pack(data.dptr + len, data.dsize - len, "d", qcount);
+ for (i = 0; i < pts->qcount; i++) {
+ if ( queue[i].status == LPQ_DELETED )
+ continue;
+
+ len += tdb_pack(data.dptr + len, data.dsize - len, "ddddddff",
+ (uint32_t)queue[i].sysjob,
+ (uint32_t)queue[i].size,
+ (uint32_t)queue[i].page_count,
+ (uint32_t)queue[i].status,
+ (uint32_t)queue[i].priority,
+ (uint32_t)queue[i].time,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ }
+
+ tdb_store(pdb->tdb, string_tdb_data("INFO/linear_queue_array"), data,
+ TDB_REPLACE);
+ SAFE_FREE(data.dptr);
+ return;
+}
+
+static TDB_DATA get_jobs_added_data(struct tdb_print_db *pdb)
+{
+ TDB_DATA data;
+
+ ZERO_STRUCT(data);
+
+ data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_added"));
+ if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0)) {
+ SAFE_FREE(data.dptr);
+ ZERO_STRUCT(data);
+ }
+
+ return data;
+}
+
+static void check_job_added(const char *sharename, TDB_DATA data, uint32_t jobid)
+{
+ unsigned int i;
+ unsigned int job_count = data.dsize / 4;
+
+ for (i = 0; i < job_count; i++) {
+ uint32_t ch_jobid;
+
+ ch_jobid = IVAL(data.dptr, i*4);
+ if (ch_jobid == jobid)
+ remove_from_jobs_added(sharename, jobid);
+ }
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static bool print_cache_expired(const char *sharename, bool check_pending)
+{
+ fstring key;
+ time_t last_qscan_time, time_now = time(NULL);
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ bool result = False;
+
+ if (!pdb)
+ return False;
+
+ snprintf(key, sizeof(key), "CACHE/%s", sharename);
+ last_qscan_time = (time_t)tdb_fetch_int32(pdb->tdb, key);
+
+ /*
+ * Invalidate the queue for 3 reasons.
+ * (1). last queue scan time == -1.
+ * (2). Current time - last queue scan time > allowed cache time.
+ * (3). last queue scan time > current time + MAX_CACHE_VALID_TIME (1 hour by default).
+ * This last test picks up machines for which the clock has been moved
+ * forward, an lpq scan done and then the clock moved back. Otherwise
+ * that last lpq scan would stay around for a loooong loooong time... :-). JRA.
+ */
+
+ if (last_qscan_time == ((time_t)-1)
+ || (time_now - last_qscan_time) >= lp_lpq_cache_time()
+ || last_qscan_time > (time_now + MAX_CACHE_VALID_TIME))
+ {
+ uint32_t u;
+ time_t msg_pending_time;
+
+ DEBUG(4, ("print_cache_expired: cache expired for queue %s "
+ "(last_qscan_time = %d, time now = %d, qcachetime = %d)\n",
+ sharename, (int)last_qscan_time, (int)time_now,
+ (int)lp_lpq_cache_time() ));
+
+ /* check if another smbd has already sent a message to update the
+ queue. Give the pending message one minute to clear and
+ then send another message anyways. Make sure to check for
+ clocks that have been run forward and then back again. */
+
+ snprintf(key, sizeof(key), "MSG_PENDING/%s", sharename);
+
+ if ( check_pending
+ && tdb_fetch_uint32( pdb->tdb, key, &u )
+ && (msg_pending_time=u) > 0
+ && msg_pending_time <= time_now
+ && (time_now - msg_pending_time) < 60 )
+ {
+ DEBUG(4,("print_cache_expired: message already pending for %s. Accepting cache\n",
+ sharename));
+ goto done;
+ }
+
+ result = True;
+ }
+
+done:
+ release_print_db(pdb);
+ return result;
+}
+
+/****************************************************************************
+ main work for updating the lpq cache for a printer queue
+****************************************************************************/
+
+static void print_queue_update_internal(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct printif *current_printif,
+ char *lpq_command, char *lprm_command)
+{
+ size_t i, qcount;
+ print_queue_struct *queue = NULL;
+ print_status_struct status;
+ print_status_struct old_status;
+ struct printjob *pjob;
+ struct traverse_struct tstruct;
+ TDB_DATA data, key;
+ TDB_DATA jcdata;
+ fstring keystr, cachestr;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+
+ if ((pdb == NULL) || (tmp_ctx == NULL)) {
+ return;
+ }
+
+ DEBUG(5,("print_queue_update_internal: printer = %s, type = %d, lpq command = [%s]\n",
+ sharename, current_printif->type, lpq_command));
+
+ /*
+ * Update the cache time FIRST ! Stops others even
+ * attempting to get the lock and doing this
+ * if the lpq takes a long time.
+ */
+
+ slprintf(cachestr, sizeof(cachestr)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, cachestr, (int)time(NULL));
+
+ /* get the current queue using the appropriate interface */
+ ZERO_STRUCT(status);
+
+ qcount = (*(current_printif->queue_get))(sharename,
+ current_printif->type,
+ lpq_command, &queue, &status);
+
+ DBG_NOTICE("%zu job%s in queue for %s\n",
+ qcount,
+ (qcount != 1) ? "s" : "",
+ sharename);
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ TYPESAFE_QSORT(queue, qcount, printjob_comp);
+
+ /*
+ any job in the internal database that is marked as spooled
+ and doesn't exist in the system queue is considered finished
+ and removed from the database
+
+ any job in the system database but not in the internal database
+ is added as a unix job
+
+ fill in any system job numbers as we go
+ */
+ jcdata = get_jobs_added_data(pdb);
+
+ for (i=0; i<qcount; i++) {
+ uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ /* assume its a unix print job */
+ print_unix_job(ev, msg_ctx,
+ sharename, &queue[i], jobid);
+ continue;
+ }
+
+ /* we have an active SMB print job - update its status */
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ /* err, somethings wrong. Probably smbd was restarted
+ with jobs in the queue. All we can do is treat them
+ like unix jobs. Pity. */
+ DEBUG(1, ("queued print job %d not found in jobs list, "
+ "assuming unix job\n", jobid));
+ print_unix_job(ev, msg_ctx,
+ sharename, &queue[i], jobid);
+ continue;
+ }
+
+ /* don't reset the status on jobs to be deleted */
+
+ if ( pjob->status != LPQ_DELETING )
+ pjob->status = queue[i].status;
+
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+
+ check_job_added(sharename, jcdata, jobid);
+ }
+
+ SAFE_FREE(jcdata.dptr);
+
+ /* now delete any queued entries that don't appear in the
+ system queue */
+ tstruct.queue = queue;
+ tstruct.qcount = qcount;
+ tstruct.snum = -1;
+ tstruct.total_jobs = 0;
+ tstruct.lpq_time = time(NULL);
+ tstruct.sharename = sharename;
+ tstruct.lprm_command = lprm_command;
+ tstruct.print_if = current_printif;
+ tstruct.ev = ev;
+ tstruct.msg_ctx = msg_ctx;
+ tstruct.mem_ctx = tmp_ctx;
+
+ tdb_traverse(pdb->tdb, traverse_fn_delete, (void *)&tstruct);
+
+ /* Store the linearised queue, max jobs only. */
+ store_queue_struct(pdb, &tstruct);
+
+ SAFE_FREE(tstruct.queue);
+ talloc_free(tmp_ctx);
+
+ DBG_DEBUG("printer %s INFO, total_jobs = %zu\n",
+ sharename,
+ tstruct.total_jobs);
+
+ tdb_store_int32(pdb->tdb, "INFO/total_jobs", tstruct.total_jobs);
+
+ get_queue_status(sharename, &old_status);
+ if (old_status.qcount != qcount) {
+ DBG_DEBUG("Queue status change %zu jobs -> %zu jobs "
+ "for printer %s\n",
+ old_status.qcount,
+ qcount,
+ sharename);
+ }
+
+ /* store the new queue status structure */
+ slprintf(keystr, sizeof(keystr)-1, "STATUS/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ status.qcount = qcount;
+ data.dptr = (uint8_t *)&status;
+ data.dsize = sizeof(status);
+ tdb_store(pdb->tdb, key, data, TDB_REPLACE);
+
+ /*
+ * Update the cache time again. We want to do this call
+ * as little as possible...
+ */
+
+ slprintf(keystr, sizeof(keystr)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, keystr, (int32_t)time(NULL));
+
+ /* clear the msg pending record for this queue */
+
+ snprintf(keystr, sizeof(keystr), "MSG_PENDING/%s", sharename);
+
+ if ( !tdb_store_uint32( pdb->tdb, keystr, 0 ) ) {
+ /* log a message but continue on */
+
+ DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n",
+ sharename));
+ }
+
+ release_print_db( pdb );
+
+ return;
+}
+
+/****************************************************************************
+ Update the internal database from the system print queue for a queue.
+ obtain a lock on the print queue before proceeding (needed when mutiple
+ smbd processes maytry to update the lpq cache concurrently).
+****************************************************************************/
+
+static void print_queue_update_with_lock( struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct printif *current_printif,
+ char *lpq_command, char *lprm_command )
+{
+ fstring keystr;
+ struct tdb_print_db *pdb;
+
+ DEBUG(5,("print_queue_update_with_lock: printer share = %s\n", sharename));
+ pdb = get_print_db_byname(sharename);
+ if (!pdb)
+ return;
+
+ if ( !print_cache_expired(sharename, False) ) {
+ DEBUG(5,("print_queue_update_with_lock: print cache for %s is still ok\n", sharename));
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * Check to see if someone else is doing this update.
+ * This is essentially a mutex on the update.
+ */
+
+ if (get_updating_pid(sharename) != -1) {
+ release_print_db(pdb);
+ return;
+ }
+
+ /* Lock the queue for the database update */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", sharename);
+ /* Only wait 10 seconds for this. */
+ if (tdb_lock_bystring_with_timeout(pdb->tdb, keystr, 10) != 0) {
+ DEBUG(0,("print_queue_update_with_lock: Failed to lock printer %s database\n", sharename));
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * Ensure that no one else got in here.
+ * If the updating pid is still -1 then we are
+ * the winner.
+ */
+
+ if (get_updating_pid(sharename) != -1) {
+ /*
+ * Someone else is doing the update, exit.
+ */
+ tdb_unlock_bystring(pdb->tdb, keystr);
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * We're going to do the update ourselves.
+ */
+
+ /* Tell others we're doing the update. */
+ set_updating_pid(sharename, True);
+
+ /*
+ * Allow others to enter and notice we're doing
+ * the update.
+ */
+
+ tdb_unlock_bystring(pdb->tdb, keystr);
+
+ /* do the main work now */
+
+ print_queue_update_internal(ev, msg_ctx,
+ sharename, current_printif,
+ lpq_command, lprm_command);
+
+ /* Delete our pid from the db. */
+ set_updating_pid(sharename, False);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+this is the receive function of the background lpq updater
+****************************************************************************/
+void print_queue_receive(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ fstring sharename;
+ char *lpqcommand = NULL, *lprmcommand = NULL;
+ int printing_type;
+ size_t len;
+
+ len = tdb_unpack( (uint8_t *)data->data, data->length, "fdPP",
+ sharename,
+ &printing_type,
+ &lpqcommand,
+ &lprmcommand );
+
+ if ( len == -1 ) {
+ SAFE_FREE(lpqcommand);
+ SAFE_FREE(lprmcommand);
+ DEBUG(0,("print_queue_receive: Got invalid print queue update message\n"));
+ return;
+ }
+
+ print_queue_update_with_lock(global_event_context(), msg, sharename,
+ get_printer_fns_from_type((enum printing_types)printing_type),
+ lpqcommand, lprmcommand );
+
+ SAFE_FREE(lpqcommand);
+ SAFE_FREE(lprmcommand);
+ return;
+}
+
+/****************************************************************************
+update the internal database from the system print queue for a queue
+****************************************************************************/
+
+static void print_queue_update(struct messaging_context *msg_ctx,
+ int snum, bool force)
+{
+ char key[268];
+ fstring sharename;
+ char *lpqcommand = NULL;
+ char *lprmcommand = NULL;
+ uint8_t *buffer = NULL;
+ size_t len = 0;
+ size_t newlen;
+ struct tdb_print_db *pdb;
+ int type;
+ struct printif *current_printif;
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ fstrcpy( sharename, lp_const_servicename(snum));
+
+ /* don't strip out characters like '$' from the printername */
+
+ lpqcommand = talloc_string_sub2(ctx,
+ lp_lpq_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (!lpqcommand) {
+ return;
+ }
+ lpqcommand = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ current_user_info.domain,
+ lpqcommand);
+ if (!lpqcommand) {
+ return;
+ }
+
+ lprmcommand = talloc_string_sub2(ctx,
+ lp_lprm_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (!lprmcommand) {
+ return;
+ }
+ lprmcommand = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ current_user_info.domain,
+ lprmcommand);
+ if (!lprmcommand) {
+ return;
+ }
+
+ /*
+ * Make sure that the background queue process exists.
+ * Otherwise just do the update ourselves
+ */
+
+ if ( force || background_lpq_updater_pid == -1 ) {
+ DEBUG(4,("print_queue_update: updating queue [%s] myself\n", sharename));
+ current_printif = get_printer_fns( snum );
+ print_queue_update_with_lock(global_event_context(), msg_ctx,
+ sharename, current_printif,
+ lpqcommand, lprmcommand);
+
+ return;
+ }
+
+ type = lp_printing(snum);
+
+ /* get the length */
+
+ len = tdb_pack( NULL, 0, "fdPP",
+ sharename,
+ type,
+ lpqcommand,
+ lprmcommand );
+
+ buffer = SMB_XMALLOC_ARRAY( uint8_t, len );
+
+ /* now pack the buffer */
+ newlen = tdb_pack( buffer, len, "fdPP",
+ sharename,
+ type,
+ lpqcommand,
+ lprmcommand );
+
+ SMB_ASSERT( newlen == len );
+
+ DEBUG(10,("print_queue_update: Sending message -> printer = %s, "
+ "type = %d, lpq command = [%s] lprm command = [%s]\n",
+ sharename, type, lpqcommand, lprmcommand ));
+
+ /* here we set a msg pending record for other smbd processes
+ to throttle the number of duplicate print_queue_update msgs
+ sent. */
+
+ pdb = get_print_db_byname(sharename);
+ if (!pdb) {
+ SAFE_FREE(buffer);
+ return;
+ }
+
+ snprintf(key, sizeof(key), "MSG_PENDING/%s", sharename);
+
+ if ( !tdb_store_uint32( pdb->tdb, key, time(NULL) ) ) {
+ /* log a message but continue on */
+
+ DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n",
+ sharename));
+ }
+
+ release_print_db( pdb );
+
+ /* finally send the message */
+
+ send_to_bgqd(msg_ctx, MSG_PRINTER_UPDATE, (uint8_t *)buffer, len);
+
+ SAFE_FREE( buffer );
+
+ return;
+}
+
+/****************************************************************************
+ Create/Update an entry in the print tdb that will allow us to send notify
+ updates only to interested smbd's.
+****************************************************************************/
+
+bool print_notify_register_pid(int snum)
+{
+ TDB_DATA data;
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ const char *printername;
+ uint32_t mypid = (uint32_t)getpid();
+ bool ret = False;
+ size_t i;
+
+ /* if (snum == -1), then the change notify request was
+ on a print server handle and we need to register on
+ all print queus */
+
+ if (snum == -1)
+ {
+ int num_services = lp_numservices();
+ int idx;
+
+ for ( idx=0; idx<num_services; idx++ ) {
+ if (lp_snum_ok(idx) && lp_printable(idx) )
+ print_notify_register_pid(idx);
+ }
+
+ return True;
+ }
+ else /* register for a specific printer */
+ {
+ printername = lp_const_servicename(snum);
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+ }
+
+ if (tdb_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to lock printer %s\n",
+ printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ /* Add ourselves and increase the refcount. */
+
+ for (i = 0; i < data.dsize; i += 8) {
+ if (IVAL(data.dptr,i) == mypid) {
+ uint32_t new_refcount = IVAL(data.dptr, i+4) + 1;
+ SIVAL(data.dptr, i+4, new_refcount);
+ break;
+ }
+ }
+
+ if (i == data.dsize) {
+ /* We weren't in the list. Realloc. */
+ data.dptr = (uint8_t *)SMB_REALLOC(data.dptr, data.dsize + 8);
+ if (!data.dptr) {
+ DEBUG(0,("print_notify_register_pid: Relloc fail for printer %s\n",
+ printername));
+ goto done;
+ }
+ data.dsize += 8;
+ SIVAL(data.dptr,data.dsize - 8,mypid);
+ SIVAL(data.dptr,data.dsize - 4,1); /* Refcount. */
+ }
+
+ /* Store back the record. */
+ if (tdb_store_bystring(tdb, NOTIFY_PID_LIST_KEY, data, TDB_REPLACE) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to update pid \
+list for printer %s\n", printername));
+ goto done;
+ }
+
+ ret = True;
+
+ done:
+
+ tdb_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/****************************************************************************
+ Update an entry in the print tdb that will allow us to send notify
+ updates only to interested smbd's.
+****************************************************************************/
+
+bool print_notify_deregister_pid(int snum)
+{
+ TDB_DATA data;
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ const char *printername;
+ uint32_t mypid = (uint32_t)getpid();
+ size_t i;
+ bool ret = False;
+
+ /* if ( snum == -1 ), we are deregister a print server handle
+ which means to deregister on all print queues */
+
+ if (snum == -1)
+ {
+ int num_services = lp_numservices();
+ int idx;
+
+ for ( idx=0; idx<num_services; idx++ ) {
+ if ( lp_snum_ok(idx) && lp_printable(idx) )
+ print_notify_deregister_pid(idx);
+ }
+
+ return True;
+ }
+ else /* deregister a specific printer */
+ {
+ printername = lp_const_servicename(snum);
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+ }
+
+ if (tdb_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to lock \
+printer %s database\n", printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ /* Reduce refcount. Remove ourselves if zero. */
+
+ for (i = 0; i < data.dsize; ) {
+ if (IVAL(data.dptr,i) == mypid) {
+ uint32_t refcount = IVAL(data.dptr, i+4);
+
+ refcount--;
+
+ if (refcount == 0) {
+ if (data.dsize - i > 8)
+ memmove( &data.dptr[i], &data.dptr[i+8], data.dsize - i - 8);
+ data.dsize -= 8;
+ continue;
+ }
+ SIVAL(data.dptr, i+4, refcount);
+ }
+
+ i += 8;
+ }
+
+ if (data.dsize == 0)
+ SAFE_FREE(data.dptr);
+
+ /* Store back the record. */
+ if (tdb_store_bystring(tdb, NOTIFY_PID_LIST_KEY, data, TDB_REPLACE) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to update pid \
+list for printer %s\n", printername));
+ goto done;
+ }
+
+ ret = True;
+
+ done:
+
+ tdb_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/****************************************************************************
+ Check if a jobid is valid. It is valid if it exists in the database.
+****************************************************************************/
+
+bool print_job_exists(const char* sharename, uint32_t jobid)
+{
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ bool ret;
+ uint32_t tmp;
+
+ if (!pdb)
+ return False;
+ ret = tdb_exists(pdb->tdb, print_key(jobid, &tmp));
+ release_print_db(pdb);
+ return ret;
+}
+
+/****************************************************************************
+ Return the device mode asigned to a specific print job.
+ Only valid for the process doing the spooling and when the job
+ has not been spooled.
+****************************************************************************/
+
+struct spoolss_DeviceMode *print_job_devmode(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid)
+{
+ struct printjob *pjob = print_job_find(mem_ctx, sharename, jobid);
+ if (pjob == NULL) {
+ return NULL;
+ }
+
+ return pjob->devmode;
+}
+
+/****************************************************************************
+ Set the name of a job. Only possible for owner.
+****************************************************************************/
+
+bool print_job_set_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, const char *name)
+{
+ struct printjob *pjob;
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || pjob->pid != getpid()) {
+ ret = false;
+ goto err_out;
+ }
+
+ fstrcpy(pjob->jobname, name);
+ ret = pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Get the name of a job. Only possible for owner.
+****************************************************************************/
+
+bool print_job_get_name(TALLOC_CTX *mem_ctx, const char *sharename, uint32_t jobid, char **name)
+{
+ struct printjob *pjob;
+
+ pjob = print_job_find(mem_ctx, sharename, jobid);
+ if (!pjob || pjob->pid != getpid()) {
+ return false;
+ }
+
+ *name = pjob->jobname;
+ return true;
+}
+
+
+/***************************************************************************
+ Remove a jobid from the 'jobs added' list.
+***************************************************************************/
+
+static bool remove_from_jobs_added(const char* sharename, uint32_t jobid)
+{
+ bool ret = remove_from_jobs_list("INFO/jobs_added", sharename, jobid);
+ return ret;
+}
+
+/****************************************************************************
+ Delete a print job - don't update queue.
+****************************************************************************/
+
+static bool print_job_delete1(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int result = 0;
+ struct printif *current_printif = get_printer_fns( snum );
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ ret = false;
+ goto err_out;
+ }
+
+ /*
+ * If already deleting just return.
+ */
+
+ if (pjob->status == LPQ_DELETING) {
+ ret = true;
+ goto err_out;
+ }
+
+ /* Hrm - we need to be able to cope with deleting a job before it
+ has reached the spooler. Just mark it as LPQ_DELETING and
+ let the print_queue_update() code rmeove the record */
+
+
+ if (pjob->sysjob == -1) {
+ DEBUG(5, ("attempt to delete job %u not seen by lpr\n", (unsigned int)jobid));
+ }
+
+ /* Set the tdb entry to be deleting. */
+
+ pjob->status = LPQ_DELETING;
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+
+ if (pjob->spooled && pjob->sysjob != -1)
+ {
+ result = (*(current_printif->job_delete))(
+ lp_printername(talloc_tos(), lp_sub, snum),
+ lp_lprm_command(snum),
+ pjob);
+
+ /* Delete the tdb entry if the delete succeeded or the job hasn't
+ been spooled. */
+
+ if (result == 0) {
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ int njobs = 1;
+
+ if (!pdb) {
+ ret = false;
+ goto err_out;
+ }
+ pjob_delete(ev, msg_ctx, sharename, jobid);
+ /* Ensure we keep a rough count of the number of total jobs... */
+ tdb_change_int32_atomic(pdb->tdb, "INFO/total_jobs", &njobs, -1);
+ release_print_db(pdb);
+ }
+ }
+
+ remove_from_jobs_added( sharename, jobid );
+
+ ret = (result == 0);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Return true if the current user owns the print job.
+****************************************************************************/
+
+static bool is_owner(const struct auth_session_info *server_info,
+ const char *servicename,
+ uint32_t jobid)
+{
+ struct printjob *pjob;
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(server_info);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, servicename, jobid);
+ if (!pjob || !server_info) {
+ ret = false;
+ goto err_out;
+ }
+
+ ret = strequal(pjob->user, server_info->unix_info->sanitized_username);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Delete a print job.
+****************************************************************************/
+
+WERROR print_job_delete(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ bool owner;
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ owner = is_owner(server_info, lp_const_servicename(snum), jobid);
+
+ /* Check access against security descriptor or whether the user
+ owns their job. */
+
+ if (!owner &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job delete denied."
+ "User name: %s, Printer name: %s.",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /*
+ * get the spooled filename of the print job
+ * if this works, then the file has not been spooled
+ * to the underlying print system. Just delete the
+ * spool file & return.
+ */
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || pjob->spooled || pjob->pid != getpid()) {
+ DEBUG(10, ("Skipping spool file removal for job %u\n", jobid));
+ } else {
+ DEBUG(10, ("Removing spool file [%s]\n", pjob->filename));
+ if (unlink(pjob->filename) == -1) {
+ werr = map_werror_from_unix(errno);
+ goto err_out;
+ }
+ }
+
+ if (!print_job_delete1(global_event_context(), msg_ctx, snum, jobid)) {
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /* force update the database and say the delete failed if the
+ job still exists */
+
+ print_queue_update(msg_ctx, snum, True);
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (pjob && (pjob->status != LPQ_DELETING)) {
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+ werr = WERR_PRINTER_HAS_JOBS_QUEUED;
+
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Pause a job.
+****************************************************************************/
+
+WERROR print_job_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret = -1;
+ struct printif *current_printif = get_printer_fns( snum );
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || !server_info) {
+ DEBUG(10, ("print_job_pause: no pjob or user for jobid %u\n",
+ (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!pjob->spooled || pjob->sysjob == -1) {
+ DEBUG(10, ("print_job_pause: not spooled or bad sysjob = %d for jobid %u\n",
+ (int)pjob->sysjob, (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!is_owner(server_info, lp_const_servicename(snum), jobid) &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job pause denied."
+ "User name: %s, Printer name: %s.",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /* need to pause the spooled entry */
+ ret = (*(current_printif->job_pause))(snum, pjob);
+
+ if (ret != 0) {
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_job_status(global_event_context(), msg_ctx, sharename, jobid,
+ JOB_STATUS_PAUSED);
+
+ /* how do we tell if this succeeded? */
+ werr = WERR_OK;
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Resume a job.
+****************************************************************************/
+
+WERROR print_job_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char *sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL)
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || !server_info) {
+ DEBUG(10, ("print_job_resume: no pjob or user for jobid %u\n",
+ (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!pjob->spooled || pjob->sysjob == -1) {
+ DEBUG(10, ("print_job_resume: not spooled or bad sysjob = %d for jobid %u\n",
+ (int)pjob->sysjob, (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!is_owner(server_info, lp_const_servicename(snum), jobid) &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job resume denied."
+ "User name: %s, Printer name: %s.",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ ret = (*(current_printif->job_resume))(snum, pjob);
+
+ if (ret != 0) {
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_job_status(global_event_context(), msg_ctx, sharename, jobid,
+ JOB_STATUS_QUEUED);
+
+ werr = WERR_OK;
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Write to a print file.
+****************************************************************************/
+
+ssize_t print_job_write(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid, const char *buf, size_t size)
+{
+ const char* sharename = lp_const_servicename(snum);
+ ssize_t return_code;
+ struct printjob *pjob;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ /* don't allow another process to get this info - it is meaningless */
+ if (pjob->pid != getpid()) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ /* if SMBD is spooling this can't be allowed */
+ if (pjob->status == PJOB_SMBD_SPOOLING) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ return_code = write_data(pjob->fd, buf, size);
+ if (return_code > 0) {
+ pjob->size += size;
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+ }
+err_out:
+ talloc_free(tmp_ctx);
+ return return_code;
+}
+
+/****************************************************************************
+ Get the queue status - do not update if db is out of date.
+****************************************************************************/
+
+static int get_queue_status(const char* sharename, print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ int len;
+
+ if (status) {
+ ZERO_STRUCTP(status);
+ }
+
+ if (!pdb)
+ return 0;
+
+ if (status) {
+ fstr_sprintf(keystr, "STATUS/%s", sharename);
+ data = tdb_fetch(pdb->tdb, string_tdb_data(keystr));
+ if (data.dptr) {
+ if (data.dsize == sizeof(print_status_struct))
+ /* this memcpy is ok since the status struct was
+ not packed before storing it in the tdb */
+ memcpy(status, data.dptr, sizeof(print_status_struct));
+ SAFE_FREE(data.dptr);
+ }
+ }
+ len = tdb_fetch_int32(pdb->tdb, "INFO/total_jobs");
+ release_print_db(pdb);
+ return (len == -1 ? 0 : len);
+}
+
+/****************************************************************************
+ Determine the number of jobs in a queue.
+****************************************************************************/
+
+int print_queue_length(struct messaging_context *msg_ctx, int snum,
+ print_status_struct *pstatus)
+{
+ const char* sharename = lp_const_servicename( snum );
+ print_status_struct status;
+ int len;
+
+ ZERO_STRUCT( status );
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ /* also fetch the queue status */
+ memset(&status, 0, sizeof(status));
+ len = get_queue_status(sharename, &status);
+
+ if (pstatus)
+ *pstatus = status;
+
+ return len;
+}
+
+/***************************************************************************
+ Allocate a jobid. Hold the lock for as short a time as possible.
+***************************************************************************/
+
+static WERROR allocate_print_jobid(struct tdb_print_db *pdb, int snum,
+ const char *sharename, uint32_t *pjobid)
+{
+ int i;
+ uint32_t jobid;
+ enum TDB_ERROR terr;
+ int ret;
+
+ *pjobid = (uint32_t)-1;
+
+ for (i = 0; i < 3; i++) {
+ /* Lock the database - only wait 20 seconds. */
+ ret = tdb_lock_bystring_with_timeout(pdb->tdb,
+ "INFO/nextjob", 20);
+ if (ret != 0) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to lock printing database %s\n",
+ sharename));
+ terr = tdb_error(pdb->tdb);
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+
+ if (!tdb_fetch_uint32(pdb->tdb, "INFO/nextjob", &jobid)) {
+ terr = tdb_error(pdb->tdb);
+ if (terr != TDB_ERR_NOEXIST) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to fetch INFO/nextjob "
+ "for print queue %s\n", sharename));
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+ DEBUG(10, ("allocate_print_jobid: "
+ "No existing jobid in %s\n", sharename));
+ jobid = 0;
+ }
+
+ DEBUG(10, ("allocate_print_jobid: "
+ "Read jobid %u from %s\n", jobid, sharename));
+
+ jobid = NEXT_JOBID(jobid);
+
+ ret = tdb_store_int32(pdb->tdb, "INFO/nextjob", jobid);
+ if (ret != 0) {
+ terr = tdb_error(pdb->tdb);
+ DEBUG(3, ("allocate_print_jobid: "
+ "Failed to store INFO/nextjob.\n"));
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+
+ /* We've finished with the INFO/nextjob lock. */
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+
+ if (!print_job_exists(sharename, jobid)) {
+ break;
+ }
+ DEBUG(10, ("allocate_print_jobid: "
+ "Found jobid %u in %s\n", jobid, sharename));
+ }
+
+ if (i > 2) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to allocate a print job for queue %s\n",
+ sharename));
+ /* Probably full... */
+ return WERR_NO_SPOOL_SPACE;
+ }
+
+ /* Store a dummy placeholder. */
+ {
+ uint32_t tmp;
+ TDB_DATA dum;
+ dum.dptr = NULL;
+ dum.dsize = 0;
+ if (tdb_store(pdb->tdb, print_key(jobid, &tmp), dum,
+ TDB_INSERT) != 0) {
+ DEBUG(3, ("allocate_print_jobid: "
+ "jobid (%d) failed to store placeholder.\n",
+ jobid ));
+ terr = tdb_error(pdb->tdb);
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+ }
+
+ *pjobid = jobid;
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Do all checks needed to determine if we can start a job.
+***************************************************************************/
+
+static WERROR print_job_checks(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, int *njobs)
+{
+ const char *sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint64_t dspace, dsize;
+ uint64_t minspace;
+ int ret;
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_USE))) {
+ DEBUG(3, ("print_job_checks: "
+ "job start denied by security descriptor\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!print_time_access_check(server_info, msg_ctx, sharename)) {
+ DEBUG(3, ("print_job_checks: "
+ "job start denied by time check\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* see if we have sufficient disk space */
+ if (lp_min_print_space(snum)) {
+ minspace = lp_min_print_space(snum);
+ ret = sys_fsusage(lp_path(talloc_tos(), lp_sub, snum), &dspace, &dsize);
+ if (ret == 0 && dspace < 2*minspace) {
+ DEBUG(3, ("print_job_checks: "
+ "disk space check failed.\n"));
+ return WERR_NO_SPOOL_SPACE;
+ }
+ }
+
+ /* for autoloaded printers, check that the printcap entry still exists */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(sharename)) {
+ DEBUG(3, ("print_job_checks: printer name %s check failed.\n",
+ sharename));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Insure the maximum queue size is not violated */
+ *njobs = print_queue_length(msg_ctx, snum, NULL);
+ if (*njobs > lp_maxprintjobs(snum)) {
+ DEBUG(3, ("print_job_checks: Queue %s number of jobs (%d) "
+ "larger than max printjobs per queue (%d).\n",
+ sharename, *njobs, lp_maxprintjobs(snum)));
+ return WERR_NO_SPOOL_SPACE;
+ }
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Create a job file.
+***************************************************************************/
+
+static WERROR print_job_spool_file(int snum, uint32_t jobid,
+ const char *output_file,
+ struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ WERROR werr;
+ SMB_STRUCT_STAT st;
+ const char *path;
+ int len;
+ mode_t mask;
+
+ /* if this file is within the printer path, it means that smbd
+ * is spooling it and will pass us control when it is finished.
+ * Verify that the file name is ok, within path, and it is
+ * already already there */
+ if (output_file) {
+ path = lp_path(talloc_tos(), lp_sub, snum);
+ len = strlen(path);
+ if (strncmp(output_file, path, len) == 0 &&
+ (output_file[len - 1] == '/' || output_file[len] == '/')) {
+
+ /* verify path is not too long */
+ if (strlen(output_file) >= sizeof(pjob->filename)) {
+ return WERR_INVALID_NAME;
+ }
+
+ /* verify that the file exists */
+ if (sys_stat(output_file, &st, false) != 0) {
+ return WERR_INVALID_NAME;
+ }
+
+ fstrcpy(pjob->filename, output_file);
+
+ DEBUG(3, ("print_job_spool_file:"
+ "External spooling activated\n"));
+
+ /* we do not open the file until spooling is done */
+ pjob->fd = -1;
+ pjob->status = PJOB_SMBD_SPOOLING;
+
+ return WERR_OK;
+ }
+ }
+
+ slprintf(pjob->filename, sizeof(pjob->filename)-1,
+ "%s/%sXXXXXX", lp_path(talloc_tos(), lp_sub, snum),
+ PRINT_SPOOL_PREFIX);
+ mask = umask(S_IRWXO | S_IRWXG);
+ pjob->fd = mkstemp(pjob->filename);
+ umask(mask);
+
+ if (pjob->fd == -1) {
+ werr = map_werror_from_unix(errno);
+ if (W_ERROR_EQUAL(werr, WERR_ACCESS_DENIED)) {
+ /* Common setup error, force a report. */
+ DEBUG(0, ("print_job_spool_file: "
+ "insufficient permissions to open spool "
+ "file %s.\n", pjob->filename));
+ } else {
+ /* Normal case, report at level 3 and above. */
+ DEBUG(3, ("print_job_spool_file: "
+ "can't open spool file %s\n",
+ pjob->filename));
+ }
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Start spooling a job - return the jobid.
+***************************************************************************/
+
+WERROR print_job_start(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *clientmachine,
+ int snum, const char *docname, const char *filename,
+ struct spoolss_DeviceMode *devmode, uint32_t *_jobid)
+{
+ uint32_t jobid;
+ char *path = NULL, *userstr = NULL;
+ struct printjob pjob;
+ const char *sharename = lp_const_servicename(snum);
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int njobs;
+ WERROR werr;
+
+ if (!pdb) {
+ return WERR_INTERNAL_DB_CORRUPTION;
+ }
+
+ path = lp_path(talloc_tos(), lp_sub, snum);
+
+ werr = print_job_checks(server_info, msg_ctx, snum, &njobs);
+ if (!W_ERROR_IS_OK(werr)) {
+ release_print_db(pdb);
+ return werr;
+ }
+
+ DEBUG(10, ("print_job_start: "
+ "Queue %s number of jobs (%d), max printjobs = %d\n",
+ sharename, njobs, lp_maxprintjobs(snum)));
+
+ werr = allocate_print_jobid(pdb, snum, sharename, &jobid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* create the database entry */
+
+ ZERO_STRUCT(pjob);
+
+ pjob.pid = getpid();
+ pjob.jobid = jobid;
+ pjob.sysjob = -1;
+ pjob.fd = -1;
+ pjob.starttime = time(NULL);
+ pjob.status = LPQ_SPOOLING;
+ pjob.size = 0;
+ pjob.spooled = False;
+ pjob.smbjob = True;
+ pjob.devmode = devmode;
+
+ fstrcpy(pjob.jobname, docname);
+
+ fstrcpy(pjob.clientmachine, clientmachine);
+
+ userstr = talloc_sub_full(talloc_tos(),
+ sharename,
+ server_info->unix_info->sanitized_username,
+ path, server_info->unix_token->gid,
+ server_info->unix_info->sanitized_username,
+ server_info->info->domain_name,
+ lp_printjob_username(snum));
+ if (userstr == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ strlcpy(pjob.user, userstr, sizeof(pjob.user));
+ TALLOC_FREE(userstr);
+
+ fstrcpy(pjob.queuename, lp_const_servicename(snum));
+
+ /* we have a job entry - now create the spool file */
+ werr = print_job_spool_file(snum, jobid, filename, &pjob);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, &pjob);
+
+ /* Update the 'jobs added' entry used by print_queue_status. */
+ add_to_jobs_list(pdb, jobid, "INFO/jobs_added");
+
+ /* Ensure we keep a rough count of the number of total jobs... */
+ tdb_change_int32_atomic(pdb->tdb, "INFO/total_jobs", &njobs, 1);
+
+ release_print_db(pdb);
+
+ *_jobid = jobid;
+ return WERR_OK;
+
+fail:
+ if (jobid != -1) {
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+ }
+
+ release_print_db(pdb);
+
+ DEBUG(3, ("print_job_start: returning fail. "
+ "Error = %s\n", win_errstr(werr)));
+ return werr;
+}
+
+/****************************************************************************
+ Update the number of pages spooled to jobid
+****************************************************************************/
+
+void print_job_endpage(struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ struct printjob *pjob;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ goto err_out;
+ }
+ /* don't allow another process to get this info - it is meaningless */
+ if (pjob->pid != getpid()) {
+ goto err_out;
+ }
+
+ pjob->page_count++;
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, pjob);
+err_out:
+ talloc_free(tmp_ctx);
+}
+
+/****************************************************************************
+ Print a file - called on closing the file. This spools the job.
+ If normal close is false then we're tearing down the jobs - treat as an
+ error.
+****************************************************************************/
+
+NTSTATUS print_job_end(struct messaging_context *msg_ctx, int snum,
+ uint32_t jobid, enum file_close_type close_type)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret;
+ SMB_STRUCT_STAT sbuf;
+ struct printif *current_printif = get_printer_fns(snum);
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *lpq_cmd;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto err_out;
+ }
+
+ if (pjob->spooled || pjob->pid != getpid()) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ if (close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) {
+ if (pjob->status == PJOB_SMBD_SPOOLING) {
+ /* take over the file now, smbd is done */
+ if (sys_stat(pjob->filename, &sbuf, false) != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3, ("print_job_end: "
+ "stat file failed for jobid %d\n",
+ jobid));
+ goto fail;
+ }
+
+ pjob->status = LPQ_SPOOLING;
+
+ } else {
+
+ if ((sys_fstat(pjob->fd, &sbuf, false) != 0)) {
+ status = map_nt_error_from_unix(errno);
+ close(pjob->fd);
+ DEBUG(3, ("print_job_end: "
+ "stat file failed for jobid %d\n",
+ jobid));
+ goto fail;
+ }
+
+ close(pjob->fd);
+ }
+
+ pjob->size = sbuf.st_ex_size;
+ } else {
+
+ /*
+ * Not a normal close, something has gone wrong. Cleanup.
+ */
+ if (pjob->fd != -1) {
+ close(pjob->fd);
+ }
+ goto fail;
+ }
+
+ /* Technically, this is not quite right. If the printer has a separator
+ * page turned on, the NT spooler prints the separator page even if the
+ * print job is 0 bytes. 010215 JRR */
+ if (pjob->size == 0 || pjob->status == LPQ_DELETING) {
+ /* don't bother spooling empty files or something being deleted. */
+ DEBUG(5,("print_job_end: canceling spool of %s (%s)\n",
+ pjob->filename, pjob->size ? "deleted" : "zero length" ));
+ unlink(pjob->filename);
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+ return NT_STATUS_OK;
+ }
+
+ /* don't strip out characters like '$' from the printername */
+ lpq_cmd = talloc_string_sub2(tmp_ctx,
+ lp_lpq_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (lpq_cmd == NULL) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+ lpq_cmd = talloc_sub_full(tmp_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ current_user_info.domain,
+ lpq_cmd);
+ if (lpq_cmd == NULL) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+
+ ret = (*(current_printif->job_submit))(snum, pjob,
+ current_printif->type, lpq_cmd);
+ if (ret) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+
+ /* The print job has been successfully handed over to the back-end */
+
+ pjob->spooled = True;
+ pjob->status = LPQ_QUEUED;
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, pjob);
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ return NT_STATUS_OK;
+
+fail:
+
+ /* The print job was not successfully started. Cleanup */
+ /* Still need to add proper error return propagation! 010122:JRR */
+ pjob->fd = -1;
+ unlink(pjob->filename);
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************************
+ Get a snapshot of jobs in the system without traversing.
+****************************************************************************/
+
+static bool get_stored_queue_info(struct messaging_context *msg_ctx,
+ struct tdb_print_db *pdb, int snum,
+ int *pcount, print_queue_struct **ppqueue)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ TDB_DATA data, cgdata, jcdata;
+ print_queue_struct *queue = NULL;
+ uint32_t qcount = 0;
+ uint32_t extra_count = 0;
+ uint32_t changed_count = 0;
+ int total_count = 0;
+ size_t len = 0;
+ uint32_t i;
+ int max_reported_jobs = lp_max_reported_print_jobs(snum);
+ bool ret = false;
+ const char* sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ *pcount = 0;
+ *ppqueue = NULL;
+
+ ZERO_STRUCT(data);
+ ZERO_STRUCT(cgdata);
+
+ /* Get the stored queue data. */
+ data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/linear_queue_array"));
+
+ if (data.dptr && data.dsize >= sizeof(qcount))
+ len += tdb_unpack(data.dptr + len, data.dsize - len, "d", &qcount);
+
+ /* Get the added jobs list. */
+ cgdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_added"));
+ if (cgdata.dptr != NULL && (cgdata.dsize % 4 == 0))
+ extra_count = cgdata.dsize/4;
+
+ /* Get the changed jobs list. */
+ jcdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_changed"));
+ if (jcdata.dptr != NULL && (jcdata.dsize % 4 == 0))
+ changed_count = jcdata.dsize / 4;
+
+ DEBUG(5,("get_stored_queue_info: qcount = %u, extra_count = %u\n", (unsigned int)qcount, (unsigned int)extra_count));
+
+ /* Allocate the queue size. */
+ if (qcount == 0 && extra_count == 0)
+ goto out;
+
+ if ((queue = SMB_MALLOC_ARRAY(print_queue_struct, qcount + extra_count)) == NULL)
+ goto out;
+
+ /* Retrieve the linearised queue data. */
+
+ for(i = 0; i < qcount; i++) {
+ uint32_t qjob, qsize, qpage_count, qstatus, qpriority, qtime;
+ len += tdb_unpack(data.dptr + len, data.dsize - len, "ddddddff",
+ &qjob,
+ &qsize,
+ &qpage_count,
+ &qstatus,
+ &qpriority,
+ &qtime,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ queue[i].sysjob = qjob;
+ queue[i].size = qsize;
+ queue[i].page_count = qpage_count;
+ queue[i].status = qstatus;
+ queue[i].priority = qpriority;
+ queue[i].time = qtime;
+ }
+
+ total_count = qcount;
+
+ /* Add new jobids to the queue. */
+ for (i = 0; i < extra_count; i++) {
+ uint32_t jobid;
+ struct printjob *pjob;
+
+ jobid = IVAL(cgdata.dptr, i*4);
+ DEBUG(5,("get_stored_queue_info: added job = %u\n", (unsigned int)jobid));
+ pjob = print_job_find(tmp_ctx, lp_const_servicename(snum), jobid);
+ if (!pjob) {
+ DEBUG(5,("get_stored_queue_info: failed to find added job = %u\n", (unsigned int)jobid));
+ remove_from_jobs_added(sharename, jobid);
+ continue;
+ }
+
+ queue[total_count].sysjob = pjob->sysjob;
+ queue[total_count].size = pjob->size;
+ queue[total_count].page_count = pjob->page_count;
+ queue[total_count].status = pjob->status;
+ queue[total_count].priority = 1;
+ queue[total_count].time = pjob->starttime;
+ fstrcpy(queue[total_count].fs_user, pjob->user);
+ fstrcpy(queue[total_count].fs_file, pjob->jobname);
+ total_count++;
+ talloc_free(pjob);
+ }
+
+ /* Update the changed jobids. */
+ for (i = 0; i < changed_count; i++) {
+ uint32_t jobid = IVAL(jcdata.dptr, i * 4);
+ struct printjob *pjob;
+ uint32_t j;
+ bool found = false;
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (pjob == NULL) {
+ DEBUG(5,("get_stored_queue_info: failed to find "
+ "changed job = %u\n",
+ (unsigned int)jobid));
+ remove_from_jobs_changed(sharename, jobid);
+ continue;
+ }
+
+ for (j = 0; j < total_count; j++) {
+ if (queue[j].sysjob == pjob->sysjob) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ DEBUG(5,("get_stored_queue_info: changed job: %u\n",
+ (unsigned int)jobid));
+
+ queue[j].sysjob = pjob->sysjob;
+ queue[j].size = pjob->size;
+ queue[j].page_count = pjob->page_count;
+ queue[j].status = pjob->status;
+ queue[j].priority = 1;
+ queue[j].time = pjob->starttime;
+ fstrcpy(queue[j].fs_user, pjob->user);
+ fstrcpy(queue[j].fs_file, pjob->jobname);
+ talloc_free(pjob);
+
+ DEBUG(5,("updated queue[%u], jobid: %u, sysjob: %u, "
+ "jobname: %s\n",
+ (unsigned int)j, (unsigned int)jobid,
+ (unsigned int)queue[j].sysjob, pjob->jobname));
+ }
+
+ remove_from_jobs_changed(sharename, jobid);
+ }
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ TYPESAFE_QSORT(queue, total_count, printjob_comp);
+
+ DEBUG(5,("get_stored_queue_info: total_count = %u\n", (unsigned int)total_count));
+
+ if (max_reported_jobs && total_count > max_reported_jobs)
+ total_count = max_reported_jobs;
+
+ *ppqueue = queue;
+ *pcount = total_count;
+
+ ret = true;
+
+ out:
+
+ SAFE_FREE(data.dptr);
+ SAFE_FREE(cgdata.dptr);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Get a printer queue listing.
+ set queue = NULL and status = NULL if you just want to update the cache
+****************************************************************************/
+
+int print_queue_status(struct messaging_context *msg_ctx, int snum,
+ print_queue_struct **ppqueue,
+ print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+ const char *sharename;
+ struct tdb_print_db *pdb;
+ int count = 0;
+
+ /* make sure the database is up to date */
+
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ /* return if we are done */
+ if ( !ppqueue || !status )
+ return 0;
+
+ *ppqueue = NULL;
+ sharename = lp_const_servicename(snum);
+ pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return 0;
+
+ /*
+ * 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)-1, "STATUS/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ data = tdb_fetch(pdb->tdb, key);
+ if (data.dptr) {
+ if (data.dsize == sizeof(*status)) {
+ /* this memcpy is ok since the status struct was
+ not packed before storing it in the tdb */
+ memcpy(status, data.dptr, sizeof(*status));
+ }
+ SAFE_FREE(data.dptr);
+ }
+
+ /*
+ * Now, fetch the print queue information. We first count the number
+ * of entries, and then only retrieve the queue if necessary.
+ */
+
+ if (!get_stored_queue_info(msg_ctx, pdb, snum, &count, ppqueue)) {
+ release_print_db(pdb);
+ return 0;
+ }
+
+ release_print_db(pdb);
+ return count;
+}
+
+/****************************************************************************
+ Pause a queue.
+****************************************************************************/
+
+WERROR print_queue_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+
+ become_root();
+
+ ret = (*(current_printif->queue_pause))(snum);
+
+ unbecome_root();
+
+ if (ret != 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_printer_status(global_event_context(), msg_ctx, snum,
+ PRINTER_STATUS_PAUSED);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Resume a queue.
+****************************************************************************/
+
+WERROR print_queue_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ become_root();
+
+ ret = (*(current_printif->queue_resume))(snum);
+
+ unbecome_root();
+
+ if (ret != 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, True);
+
+ /* Send a printer notify message */
+
+ notify_printer_status(global_event_context(), msg_ctx, snum,
+ PRINTER_STATUS_OK);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Purge a queue - implemented by deleting all jobs that we can delete.
+****************************************************************************/
+
+WERROR print_queue_purge(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ print_queue_struct *queue;
+ print_status_struct status;
+ int njobs, i;
+ bool can_job_admin;
+
+ /* Force and update so the count is accurate (i.e. not a cached count) */
+ print_queue_update(msg_ctx, snum, True);
+
+ can_job_admin = W_ERROR_IS_OK(print_access_check(server_info,
+ msg_ctx,
+ snum,
+ JOB_ACCESS_ADMINISTER));
+ njobs = print_queue_status(msg_ctx, snum, &queue, &status);
+
+ if ( can_job_admin )
+ become_root();
+
+ for (i = 0; i < njobs; i++) {
+ struct tdb_print_db *pdb;
+ int jobid;
+ bool owner;
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (pdb == NULL) {
+ DEBUG(1, ("failed to find printdb for %s\n",
+ lp_const_servicename(snum)));
+ continue;
+ }
+ jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(2, ("jobid for system job %d not found\n",
+ queue[i].sysjob));
+ continue; /* unix job */
+ }
+ owner = is_owner(server_info, lp_const_servicename(snum),
+ jobid);
+
+ if (owner || can_job_admin) {
+ print_job_delete1(global_event_context(), msg_ctx,
+ snum, jobid);
+ }
+ }
+
+ if ( can_job_admin )
+ unbecome_root();
+
+ /* update the cache */
+ print_queue_update(msg_ctx, snum, True);
+
+ SAFE_FREE(queue);
+
+ return WERR_OK;
+}
diff --git a/source3/printing/printing_db.c b/source3/printing/printing_db.c
new file mode 100644
index 0000000..d54a39a
--- /dev/null
+++ b/source3/printing/printing_db.c
@@ -0,0 +1,228 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "system/filesys.h"
+#include "printing.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+static struct tdb_print_db *print_db_head;
+
+/****************************************************************************
+ Function to find or create the printer specific job tdb given a printername.
+ Limits the number of tdb's open to MAX_PRINT_DBS_OPEN.
+****************************************************************************/
+
+struct tdb_print_db *get_print_db_byname(const char *printername)
+{
+ struct tdb_print_db *p = NULL, *last_entry = NULL;
+ size_t num_open = 0;
+ char *printdb_path = NULL;
+ bool done_become_root = False;
+ char *print_cache_path;
+ int ret;
+
+ SMB_ASSERT(printername != NULL);
+
+ for (p = print_db_head, last_entry = print_db_head; p; p = p->next) {
+ /* Ensure the list terminates... JRA. */
+ SMB_ASSERT(p->next != print_db_head);
+
+ if (p->tdb && strequal(p->printer_name, printername)) {
+ DLIST_PROMOTE(print_db_head, p);
+ p->ref_count++;
+ return p;
+ }
+ num_open++;
+ last_entry = p;
+ }
+
+ /* Not found. */
+ if (num_open >= MAX_PRINT_DBS_OPEN) {
+ /* Try and recycle the last entry. */
+ if (print_db_head && last_entry) {
+ DLIST_PROMOTE(print_db_head, last_entry);
+ }
+
+ for (p = print_db_head; p; p = p->next) {
+ if (p->ref_count)
+ continue;
+ if (p->tdb) {
+ if (tdb_close(p->tdb)) {
+ DEBUG(0,("get_print_db: Failed to close tdb for printer %s\n",
+ p->printer_name ));
+ return NULL;
+ }
+ }
+ p->tdb = NULL;
+ p->ref_count = 0;
+ memset(p->printer_name, '\0', sizeof(p->printer_name));
+ break;
+ }
+ if (p && print_db_head) {
+ DLIST_PROMOTE(print_db_head, p);
+ p = print_db_head;
+ }
+ }
+
+ if (!p) {
+ /* Create one. */
+ p = SMB_MALLOC_P(struct tdb_print_db);
+ if (!p) {
+ DEBUG(0,("get_print_db: malloc fail !\n"));
+ return NULL;
+ }
+ ZERO_STRUCTP(p);
+ DLIST_ADD(print_db_head, p);
+ }
+
+ print_cache_path = cache_path(talloc_tos(), "printing/");
+ if (print_cache_path == NULL) {
+ DLIST_REMOVE(print_db_head, p);
+ SAFE_FREE(p);
+ return NULL;
+ }
+ ret = asprintf(&printdb_path, "%s%s.tdb",
+ print_cache_path, printername);
+ TALLOC_FREE(print_cache_path);
+ if (ret < 0) {
+ DLIST_REMOVE(print_db_head, p);
+ SAFE_FREE(p);
+ return NULL;
+ }
+
+ if (geteuid() != sec_initial_uid()) {
+ become_root();
+ done_become_root = True;
+ }
+
+ p->tdb = tdb_open_log(printdb_path, 5000, TDB_DEFAULT, O_RDWR|O_CREAT,
+ 0600);
+
+ if (done_become_root)
+ unbecome_root();
+
+ if (!p->tdb) {
+ DEBUG(0,("get_print_db: Failed to open printer backend database %s.\n",
+ printdb_path ));
+ DLIST_REMOVE(print_db_head, p);
+ SAFE_FREE(printdb_path);
+ SAFE_FREE(p);
+ return NULL;
+ }
+ SAFE_FREE(printdb_path);
+ fstrcpy(p->printer_name, printername);
+ p->ref_count++;
+ return p;
+}
+
+/***************************************************************************
+ Remove a reference count.
+****************************************************************************/
+
+void release_print_db( struct tdb_print_db *pdb)
+{
+ pdb->ref_count--;
+ SMB_ASSERT(pdb->ref_count >= 0);
+}
+
+/***************************************************************************
+ Close all open print db entries.
+****************************************************************************/
+
+void close_all_print_db(void)
+{
+ struct tdb_print_db *p = NULL, *next_p = NULL;
+
+ for (p = print_db_head; p; p = next_p) {
+ next_p = p->next;
+
+ if (p->tdb)
+ tdb_close(p->tdb);
+ DLIST_REMOVE(print_db_head, p);
+ ZERO_STRUCTP(p);
+ SAFE_FREE(p);
+ }
+}
+
+
+/****************************************************************************
+ Fetch and clean the pid_t record list for all pids interested in notify
+ messages. data needs freeing on exit.
+****************************************************************************/
+
+TDB_DATA get_printer_notify_pid_list(struct tdb_context *tdb, const char *printer_name, bool cleanlist)
+{
+ TDB_DATA data;
+ size_t i;
+
+ ZERO_STRUCT(data);
+
+ data = tdb_fetch_bystring( tdb, NOTIFY_PID_LIST_KEY );
+
+ if (!data.dptr) {
+ ZERO_STRUCT(data);
+ return data;
+ }
+
+ if (data.dsize % 8) {
+ DEBUG(0,("get_printer_notify_pid_list: Size of record for printer %s not a multiple of 8 !\n", printer_name ));
+ tdb_delete_bystring(tdb, NOTIFY_PID_LIST_KEY );
+ SAFE_FREE(data.dptr);
+ ZERO_STRUCT(data);
+ return data;
+ }
+
+ if (!cleanlist)
+ return data;
+
+ /*
+ * Weed out all dead entries.
+ */
+
+ for( i = 0; i < data.dsize; i += 8) {
+ pid_t pid = (pid_t)IVAL(data.dptr, i);
+
+ if (pid == getpid())
+ continue;
+
+ /* Entry is dead if process doesn't exist or refcount is zero. */
+
+ while ((i < data.dsize) && ((IVAL(data.dptr, i + 4) == 0) || !process_exists_by_pid(pid))) {
+
+ /* Refcount == zero is a logic error and should never happen. */
+ if (IVAL(data.dptr, i + 4) == 0) {
+ DEBUG(0,("get_printer_notify_pid_list: Refcount == 0 for pid = %u printer %s !\n",
+ (unsigned int)pid, printer_name ));
+ }
+
+ if (data.dsize - i > 8)
+ memmove( &data.dptr[i], &data.dptr[i+8], data.dsize - i - 8);
+ data.dsize -= 8;
+ }
+ }
+
+ return data;
+}
+
+
diff --git a/source3/printing/printspoolss.c b/source3/printing/printspoolss.c
new file mode 100644
index 0000000..31117a4
--- /dev/null
+++ b/source3/printing/printspoolss.c
@@ -0,0 +1,405 @@
+/*
+ Unix SMB/CIFS implementation.
+ Printing routines that bridge to spoolss
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "smbd/globals.h"
+#include "../libcli/security/security.h"
+#include "smbd/fd_handle.h"
+#include "source3/printing/rap_jobid.h"
+
+struct print_file_data {
+ char *svcname;
+ char *docname;
+ char *filename;
+ struct policy_handle handle;
+ uint32_t jobid;
+ uint16_t rap_jobid;
+};
+
+uint16_t print_spool_rap_jobid(struct print_file_data *print_file)
+{
+ if (print_file == NULL) {
+ return 0;
+ }
+
+ return print_file->rap_jobid;
+}
+
+void print_spool_terminate(struct connection_struct *conn,
+ struct print_file_data *print_file);
+
+/***************************************************************************
+ * Open a Document over spoolss
+ ***************************************************************************/
+
+#define DOCNAME_DEFAULT "Remote Downlevel Document"
+
+NTSTATUS print_spool_open(files_struct *fsp,
+ const char *fname,
+ uint64_t current_vuid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+ struct print_file_data *pf;
+ struct dcerpc_binding_handle *b = NULL;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct spoolss_DocumentInfoCtr info_ctr;
+ struct spoolss_DocumentInfo1 *info1;
+ int fd = -1;
+ WERROR werr;
+ mode_t mask;
+
+ tmp_ctx = talloc_new(fsp);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pf = talloc_zero(fsp, struct print_file_data);
+ if (!pf) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ pf->svcname = lp_servicename(pf, lp_sub, SNUM(fsp->conn));
+
+ /* the document name is derived from the file name.
+ * "Remote Downlevel Document" is added in front to
+ * mimic what windows does in this case */
+ pf->docname = talloc_strdup(pf, DOCNAME_DEFAULT);
+ if (!pf->docname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ if (fname) {
+ const char *p = strrchr(fname, '/');
+ if (!p) {
+ p = fname;
+ }
+ pf->docname = talloc_asprintf_append(pf->docname, " %s", p);
+ if (!pf->docname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ /*
+ * Ok, now we have to open an actual file.
+ * Here is the reason:
+ * We want to write the spool job to this file in
+ * smbd for scalability reason (and also because
+ * apparently window printer drivers can seek when
+ * spooling to a file).
+ * So we first create a file, and then we pass it
+ * to spoolss in output_file so it can monitor and
+ * take over once we call EndDocPrinter().
+ * Of course we will not start writing until
+ * StartDocPrinter() actually gives the ok.
+ * smbd spooler files do not include a print jobid
+ * path component, as the jobid is only known after
+ * calling StartDocPrinter().
+ */
+
+ pf->filename = talloc_asprintf(pf, "%s/%sXXXXXX",
+ lp_path(talloc_tos(),
+ lp_sub,
+ SNUM(fsp->conn)),
+ PRINT_SPOOL_PREFIX);
+ if (!pf->filename) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ errno = 0;
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(pf->filename);
+ umask(mask);
+ if (fd == -1) {
+ if (errno == EACCES) {
+ /* Common setup error, force a report. */
+ DEBUG(0, ("Insufficient permissions "
+ "to open spool file %s.\n",
+ pf->filename));
+ } else {
+ /* Normal case, report at level 3 and above. */
+ DEBUG(3, ("can't open spool file %s,\n",
+ pf->filename));
+ DEBUGADD(3, ("errno = %d (%s).\n",
+ errno, strerror(errno)));
+ }
+ status = map_nt_error_from_unix(errno);
+ goto done;
+ }
+
+ /* now open a document over spoolss so that it does
+ * all printer verification, and eventually assigns
+ * a job id */
+
+ status = rpc_pipe_open_interface(fsp->conn,
+ &ndr_table_spoolss,
+ fsp->conn->session_info,
+ fsp->conn->sconn->remote_address,
+ fsp->conn->sconn->local_address,
+ fsp->conn->sconn->msg_ctx,
+ &fsp->conn->spoolss_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ b = fsp->conn->spoolss_pipe->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, pf, pf->svcname,
+ "RAW", devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &pf->handle, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ info1 = talloc(tmp_ctx, struct spoolss_DocumentInfo1);
+ if (info1 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ info1->document_name = pf->docname;
+ info1->output_file = pf->filename;
+ info1->datatype = "RAW";
+
+ info_ctr.level = 1;
+ info_ctr.info.info1 = info1;
+
+ status = dcerpc_spoolss_StartDocPrinter(b, tmp_ctx,
+ &pf->handle,
+ &info_ctr,
+ &pf->jobid,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ /* Convert to RAP id. */
+ pf->rap_jobid = pjobid_to_rap(pf->svcname, pf->jobid);
+ if (pf->rap_jobid == 0) {
+ /* No errno around here */
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ /* setup a full fsp */
+ fsp->fsp_name = synthetic_smb_fname(fsp,
+ pf->filename,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (fsp->fsp_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (sys_fstat(fd, &fsp->fsp_name->st, false) != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto done;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(fsp->conn, &fsp->fsp_name->st);
+ fsp_set_fd(fsp, fd);
+
+ fsp->vuid = current_vuid;
+ fsp->fsp_flags.can_lock = false;
+ fsp->fsp_flags.can_read = false;
+ fsp->access_mask = FILE_GENERIC_WRITE;
+ fsp->fsp_flags.can_write = true;
+ fsp->fsp_flags.modified = false;
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = false;
+ fsp->fsp_flags.delete_on_close = false;
+
+ fsp->print_file = pf;
+
+ status = NT_STATUS_OK;
+done:
+ if (!NT_STATUS_IS_OK(status)) {
+ if (fd != -1) {
+ close(fd);
+ if (fsp->print_file) {
+ unlink(fsp->print_file->filename);
+ }
+ }
+ /* We need to delete the job from spoolss too */
+ if (pf && pf->jobid) {
+ print_spool_terminate(fsp->conn, pf);
+ }
+ }
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+int print_spool_write(files_struct *fsp,
+ const char *data, uint32_t size,
+ off_t offset, uint32_t *written)
+{
+ SMB_STRUCT_STAT st;
+ ssize_t n;
+ int ret;
+
+ *written = 0;
+
+ /* first of all stat file to find out if it is still there.
+ * spoolss may have deleted it to signal someone has killed
+ * the job through it's interface */
+
+ if (sys_fstat(fsp_get_io_fd(fsp), &st, false) != 0) {
+ ret = errno;
+ DEBUG(3, ("printfile_offset: sys_fstat failed on %s (%s)\n",
+ fsp_str_dbg(fsp), strerror(ret)));
+ return ret;
+ }
+
+ /* check if the file is unlinked, this will signal spoolss has
+ * killed it, just return an error and close the file */
+ if (st.st_ex_nlink == 0) {
+ close(fsp_get_io_fd(fsp));
+ return EBADF;
+ }
+
+ /* When print files go beyond 4GB, the 32-bit offset sent in
+ * old SMBwrite calls is relative to the current 4GB chunk
+ * we're writing to.
+ * Discovered by Sebastian Kloska <oncaphillis@snafu.de>.
+ */
+ if (offset < 0xffffffff00000000LL) {
+ offset = (st.st_ex_size & 0xffffffff00000000LL) + offset;
+ }
+
+ n = write_data_at_offset(fsp_get_io_fd(fsp), data, size, offset);
+ if (n == -1) {
+ ret = errno;
+ print_spool_terminate(fsp->conn, fsp->print_file);
+ } else {
+ *written = n;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+void print_spool_end(files_struct *fsp, enum file_close_type close_type)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (fsp->fsp_flags.delete_on_close) {
+ int ret;
+
+ /*
+ * Job was requested to be cancelled by setting
+ * delete on close so truncate the job file.
+ * print_job_end() which is called from
+ * _spoolss_EndDocPrinter() will take
+ * care of deleting it for us.
+ */
+ ret = ftruncate(fsp_get_io_fd(fsp), 0);
+ if (ret == -1) {
+ DBG_ERR("ftruncate failed: %s\n", strerror(errno));
+ }
+ }
+
+ b = fsp->conn->spoolss_pipe->binding_handle;
+
+ switch (close_type) {
+ case NORMAL_CLOSE:
+ case SHUTDOWN_CLOSE:
+ /* this also automatically calls spoolss_EndDocPrinter */
+ status = dcerpc_spoolss_ClosePrinter(b, fsp->print_file,
+ &fsp->print_file->handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(status = werror_to_ntstatus(werr))) {
+ DEBUG(3, ("Failed to close printer %s [%s]\n",
+ fsp->print_file->svcname, nt_errstr(status)));
+ }
+ break;
+ case ERROR_CLOSE:
+ print_spool_terminate(fsp->conn, fsp->print_file);
+ break;
+ }
+}
+
+
+void print_spool_terminate(struct connection_struct *conn,
+ struct print_file_data *print_file)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ rap_jobid_delete(print_file->svcname, print_file->jobid);
+
+ status = rpc_pipe_open_interface(conn,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &conn->spoolss_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("print_spool_terminate: "
+ "Failed to get spoolss pipe [%s]\n",
+ nt_errstr(status)));
+ return;
+ }
+ b = conn->spoolss_pipe->binding_handle;
+
+ status = dcerpc_spoolss_SetJob(b, print_file,
+ &print_file->handle,
+ print_file->jobid,
+ NULL, SPOOLSS_JOB_CONTROL_DELETE,
+ &werr);
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(status = werror_to_ntstatus(werr))) {
+ DEBUG(3, ("Failed to delete job %d [%s]\n",
+ print_file->jobid, nt_errstr(status)));
+ return;
+ }
+ status = dcerpc_spoolss_ClosePrinter(b, print_file,
+ &print_file->handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(status = werror_to_ntstatus(werr))) {
+ DEBUG(3, ("Failed to close printer %s [%s]\n",
+ print_file->svcname, nt_errstr(status)));
+ return;
+ }
+}
diff --git a/source3/printing/queue_process.c b/source3/printing/queue_process.c
new file mode 100644
index 0000000..6613e8f
--- /dev/null
+++ b/source3/printing/queue_process.c
@@ -0,0 +1,451 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+ Copyright (C) Simo Sorce 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include <spawn.h>
+#include "smbd/globals.h"
+#include "include/messages.h"
+#include "lib/util/util_process.h"
+#include "lib/util/sys_rw.h"
+#include "printing.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/queue_process.h"
+#include "locking/proto.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "rpc_server/rpc_config.h"
+#include "printing/load.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "auth.h"
+#include "nt_printing.h"
+#include "util_event.h"
+#include "lib/global_contexts.h"
+#include "lib/util/pidfile.h"
+
+/**
+ * @brief Purge stale printers and reload from pre-populated pcap cache.
+ *
+ * This function should normally only be called as a callback on a successful
+ * pcap_cache_reload().
+ *
+ * This function can cause DELETION of printers and drivers from our registry,
+ * so calling it on a failed pcap reload may REMOVE permanently all printers
+ * and drivers.
+ *
+ * @param[in] ev The event context.
+ *
+ * @param[in] msg_ctx The messaging context.
+ */
+static void delete_and_reload_printers_full(struct tevent_context *ev,
+ struct messaging_context *msg_ctx)
+{
+ struct auth_session_info *session_info = NULL;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int n_services;
+ int pnum;
+ int snum;
+ const char *pname;
+ const char *sname;
+ NTSTATUS status;
+
+ n_services = lp_numservices();
+ pnum = lp_servicenumber(PRINTERS_NAME);
+
+ status = make_session_info_system(talloc_tos(), &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("reload_printers: "
+ "Could not create system session_info\n"));
+ /* can't remove stale printers before we
+ * are fully initialized */
+ return;
+ }
+
+ /*
+ * Add default config for printers added to smb.conf file and remove
+ * stale printers
+ */
+ for (snum = 0; snum < n_services; snum++) {
+ /* avoid removing PRINTERS_NAME */
+ if (snum == pnum) {
+ continue;
+ }
+
+ /* skip no-printer services */
+ if (!snum_is_shared_printer(snum)) {
+ continue;
+ }
+
+ sname = lp_const_servicename(snum);
+ pname = lp_printername(session_info, lp_sub, snum);
+
+ /* check printer, but avoid removing non-autoloaded printers */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(pname)) {
+ DEBUG(3, ("removing stale printer %s\n", pname));
+
+ if (is_printer_published(session_info, session_info,
+ msg_ctx,
+ NULL,
+ lp_servicename(session_info,
+ lp_sub,
+ snum),
+ &pinfo2)) {
+ nt_printer_publish(session_info,
+ session_info,
+ msg_ctx,
+ pinfo2,
+ DSPRINT_UNPUBLISH);
+ TALLOC_FREE(pinfo2);
+ }
+ nt_printer_remove(session_info, session_info, msg_ctx,
+ pname);
+ } else {
+ DEBUG(8, ("Adding default registry entry for printer "
+ "[%s], if it doesn't exist.\n", sname));
+ nt_printer_add(session_info, session_info, msg_ctx,
+ sname);
+ }
+ }
+
+ /* finally, purge old snums */
+ delete_and_reload_printers();
+
+ TALLOC_FREE(session_info);
+}
+
+
+/****************************************************************************
+ Notify smbds of new printcap data
+**************************************************************************/
+static void reload_pcap_change_notify(struct tevent_context *ev,
+ struct messaging_context *msg_ctx)
+{
+ /*
+ * Reload the printers first in the background process so that
+ * newly added printers get default values created in the registry.
+ *
+ * This will block the process for some time (~1 sec per printer), but
+ * it doesn't block smbd's serving clients.
+ */
+ delete_and_reload_printers_full(ev, msg_ctx);
+
+ messaging_send_all(msg_ctx, MSG_PRINTER_PCAP, NULL, 0);
+}
+
+struct bq_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct idle_event *housekeep;
+ struct tevent_signal *sighup_handler;
+ struct tevent_signal *sigchld_handler;
+};
+
+static bool print_queue_housekeeping(const struct timeval *now, void *pvt)
+{
+ struct bq_state *state;
+
+ state = talloc_get_type_abort(pvt, struct bq_state);
+
+ DEBUG(5, ("print queue housekeeping\n"));
+ pcap_cache_reload(state->ev, state->msg, reload_pcap_change_notify);
+
+ return true;
+}
+
+static bool printing_subsystem_queue_tasks(struct bq_state *state)
+{
+ uint32_t housekeeping_period = lp_printcap_cache_time();
+
+ /* cancel any existing housekeeping event */
+ TALLOC_FREE(state->housekeep);
+
+ if ((housekeeping_period == 0) || !lp_load_printers()) {
+ DEBUG(4, ("background print queue housekeeping disabled\n"));
+ return true;
+ }
+
+ state->housekeep = event_add_idle(state->ev, NULL,
+ timeval_set(housekeeping_period, 0),
+ "print_queue_housekeeping",
+ print_queue_housekeeping, state);
+ if (state->housekeep == NULL) {
+ DEBUG(0,("Could not add print_queue_housekeeping event\n"));
+ return false;
+ }
+
+ return true;
+}
+
+static void bq_reopen_logs(char *logfile)
+{
+ if (logfile) {
+ lp_set_logfile(logfile);
+ }
+ reopen_logs();
+}
+
+static void bq_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *pvt)
+{
+ struct bq_state *state;
+
+ state = talloc_get_type_abort(pvt, struct bq_state);
+ change_to_root_user();
+
+ DEBUG(1, ("Reloading pcap cache after SIGHUP\n"));
+ pcap_cache_reload(state->ev, state->msg,
+ reload_pcap_change_notify);
+ printing_subsystem_queue_tasks(state);
+ bq_reopen_logs(NULL);
+}
+
+static void bq_sig_chld_handler(struct tevent_context *ev_ctx,
+ struct tevent_signal *se,
+ int signum, int count,
+ void *siginfo, void *pvt)
+{
+ int status;
+ pid_t pid;
+
+ do {
+ do {
+ pid = waitpid(-1, &status, WNOHANG);
+ } while ((pid == -1) && (errno == EINTR));
+
+ if (WIFEXITED(status)) {
+ DBG_INFO("Bq child process %d terminated with %d\n",
+ (int)pid,
+ WEXITSTATUS(status));
+ } else {
+ DBG_NOTICE("Bq child process %d terminated abnormally\n",
+ (int)pid);
+ }
+ } while (pid > 0);
+}
+
+static void bq_smb_conf_updated(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct bq_state *state;
+
+ state = talloc_get_type_abort(private_data, struct bq_state);
+
+ DEBUG(10,("smb_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ change_to_root_user();
+ pcap_cache_reload(state->ev, msg_ctx, reload_pcap_change_notify);
+ printing_subsystem_queue_tasks(state);
+}
+
+static int bq_state_destructor(struct bq_state *s)
+{
+ struct messaging_context *msg_ctx = s->msg;
+ TALLOC_FREE(s->sighup_handler);
+ TALLOC_FREE(s->sigchld_handler);
+ messaging_deregister(msg_ctx, MSG_PRINTER_DRVUPGRADE, NULL);
+ messaging_deregister(msg_ctx, MSG_PRINTER_UPDATE, NULL);
+ messaging_deregister(msg_ctx, MSG_SMB_CONF_UPDATED, s);
+ return 0;
+}
+
+struct bq_state *register_printing_bq_handlers(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx)
+{
+ struct bq_state *state = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ state = talloc_zero(mem_ctx, struct bq_state);
+ if (state == NULL) {
+ return NULL;
+ }
+ state->ev = messaging_tevent_context(msg_ctx);
+ state->msg = msg_ctx;
+
+ status = messaging_register(
+ msg_ctx, state, MSG_SMB_CONF_UPDATED, bq_smb_conf_updated);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ status = messaging_register(
+ msg_ctx, NULL, MSG_PRINTER_UPDATE, print_queue_receive);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail_dereg_smb_conf_updated;
+ }
+ status = messaging_register(
+ msg_ctx, NULL, MSG_PRINTER_DRVUPGRADE, do_drv_upgrade_printer);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail_dereg_printer_update;
+ }
+
+ state->sighup_handler = tevent_add_signal(
+ state->ev, state, SIGHUP, 0, bq_sig_hup_handler, state);
+ if (state->sighup_handler == NULL) {
+ goto fail_dereg_printer_drvupgrade;
+ }
+ state->sigchld_handler = tevent_add_signal(
+ state->ev, state, SIGCHLD, 0, bq_sig_chld_handler, NULL);
+ if (state->sigchld_handler == NULL) {
+ goto fail_free_handlers;
+ }
+
+ /* Initialize the printcap cache as soon as the daemon starts. */
+ pcap_cache_reload(state->ev, state->msg, reload_pcap_change_notify);
+
+ ok = printing_subsystem_queue_tasks(state);
+ if (!ok) {
+ goto fail_free_handlers;
+ }
+
+ talloc_set_destructor(state, bq_state_destructor);
+
+ return state;
+
+fail_free_handlers:
+ TALLOC_FREE(state->sighup_handler);
+ TALLOC_FREE(state->sigchld_handler);
+fail_dereg_printer_drvupgrade:
+ messaging_deregister(msg_ctx, MSG_PRINTER_DRVUPGRADE, NULL);
+fail_dereg_printer_update:
+ messaging_deregister(msg_ctx, MSG_PRINTER_UPDATE, NULL);
+fail_dereg_smb_conf_updated:
+ messaging_deregister(msg_ctx, MSG_SMB_CONF_UPDATED, state);
+fail:
+ TALLOC_FREE(state);
+ return NULL;
+}
+
+/****************************************************************************
+main thread of the background lpq updater
+****************************************************************************/
+pid_t start_background_queue(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ char *logfile)
+{
+ pid_t pid;
+ int ret;
+ ssize_t nread;
+ char **argv = NULL;
+ int ready_fds[2];
+
+ DEBUG(3,("start_background_queue: Starting background LPQ thread\n"));
+
+ ret = pipe(ready_fds);
+ if (ret == -1) {
+ return -1;
+ }
+
+ argv = str_list_make_empty(talloc_tos());
+ str_list_add_printf(
+ &argv, "%s/samba-bgqd", get_dyn_SAMBA_LIBEXECDIR());
+ str_list_add_printf(
+ &argv, "--ready-signal-fd=%d", ready_fds[1]);
+ str_list_add_printf(
+ &argv, "--parent-watch-fd=%d", 0);
+ str_list_add_printf(
+ &argv, "--debuglevel=%d", debuglevel_get_class(DBGC_RPC_SRV));
+ if (!is_default_dyn_CONFIGFILE()) {
+ str_list_add_printf(
+ &argv, "--configfile=%s", get_dyn_CONFIGFILE());
+ }
+ if (!is_default_dyn_LOGFILEBASE()) {
+ str_list_add_printf(
+ &argv, "--log-basename=%s", get_dyn_LOGFILEBASE());
+ }
+ str_list_add_printf(&argv, "-F");
+ if (argv == NULL) {
+ goto nomem;
+ }
+
+ ret = posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
+ if (ret == -1) {
+ goto fail;
+ }
+ TALLOC_FREE(argv);
+
+ close(ready_fds[1]);
+
+ nread = sys_read(ready_fds[0], &pid, sizeof(pid));
+ close(ready_fds[0]);
+ if (nread != sizeof(pid)) {
+ goto fail;
+ }
+
+ return pid;
+
+nomem:
+ errno = ENOMEM;
+fail:
+ {
+ int err = errno;
+ TALLOC_FREE(argv);
+ errno = err;
+ }
+
+ return -1;
+}
+
+
+/* Run before the parent forks */
+bool printing_subsystem_init(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ struct dcesrv_context *dce_ctx)
+{
+ pid_t pid = -1;
+
+ pid = start_background_queue(NULL, NULL, NULL);
+ if (pid == -1) {
+ return false;
+ }
+ background_lpq_updater_pid = pid;
+
+ if (!print_backend_init(msg_ctx)) {
+ return false;
+ }
+
+ return true;
+}
+
+void send_to_bgqd(struct messaging_context *msg_ctx,
+ uint32_t msg_type,
+ const uint8_t *buf,
+ size_t buflen)
+{
+ pid_t bgqd = pidfile_pid(lp_pid_directory(), "samba-bgqd");
+
+ if (bgqd == -1) {
+ return;
+ }
+ messaging_send_buf(
+ msg_ctx, pid_to_procid(bgqd), msg_type, buf, buflen);
+}
diff --git a/source3/printing/queue_process.h b/source3/printing/queue_process.h
new file mode 100644
index 0000000..93bc79f
--- /dev/null
+++ b/source3/printing/queue_process.h
@@ -0,0 +1,44 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+ Copyright (C) Simo Sorce 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SOURCE3_PRINTING_QUEUE_PROCESS_H_
+#define _SOURCE3_PRINTING_QUEUE_PROCESS_H_
+
+struct dcesrv_context;
+
+bool printing_subsystem_init(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ struct dcesrv_context *dce_ctx);
+pid_t start_background_queue(struct tevent_context *ev,
+ struct messaging_context *msg,
+ char *logfile);
+void send_to_bgqd(struct messaging_context *msg_ctx,
+ uint32_t msg_type,
+ const uint8_t *buf,
+ size_t buflen);
+
+struct bq_state;
+struct bq_state *register_printing_bq_handlers(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx);
+
+#endif /* _SOURCE3_PRINTING_QUEUE_PROCESS_H_ */
diff --git a/source3/printing/rap_jobid.c b/source3/printing/rap_jobid.c
new file mode 100644
index 0000000..7af3d47
--- /dev/null
+++ b/source3/printing/rap_jobid.c
@@ -0,0 +1,164 @@
+/*
+ * Maintain rap vs spoolss jobids
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ the printing backend revolves around a tdb database that stores the
+ SMB view of the print queue
+
+ The key for this database is a jobid - a internally generated number that
+ uniquely identifies a print job
+
+ reading the print queue involves two steps:
+ - possibly running lpq and updating the internal database from that
+ - reading entries from the database
+
+ jobids are assigned when a job starts spooling.
+*/
+
+#include "rap_jobid.h"
+#include <tdb.h>
+#include "source3/include/util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+static TDB_CONTEXT *rap_tdb;
+static uint16_t next_rap_jobid;
+struct rap_jobid_key {
+ fstring sharename;
+ uint32_t jobid;
+};
+
+/***************************************************************************
+ Nightmare. LANMAN jobid's are 16 bit numbers..... We must map them to 32
+ bit RPC jobids.... JRA.
+***************************************************************************/
+
+uint16_t pjobid_to_rap(const char* sharename, uint32_t jobid)
+{
+ uint16_t rap_jobid;
+ TDB_DATA data, key;
+ struct rap_jobid_key jinfo;
+ uint8_t buf[2];
+
+ DEBUG(10,("pjobid_to_rap: called.\n"));
+
+ if (!rap_tdb) {
+ /* Create the in-memory tdb. */
+ rap_tdb = tdb_open_log(NULL, 0, TDB_INTERNAL, (O_RDWR|O_CREAT), 0644);
+ if (!rap_tdb)
+ return 0;
+ }
+
+ ZERO_STRUCT( jinfo );
+ fstrcpy( jinfo.sharename, sharename );
+ jinfo.jobid = jobid;
+ key.dptr = (uint8_t *)&jinfo;
+ key.dsize = sizeof(jinfo);
+
+ data = tdb_fetch(rap_tdb, key);
+ if (data.dptr && data.dsize == sizeof(uint16_t)) {
+ rap_jobid = SVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+ DEBUG(10,("pjobid_to_rap: jobid %u maps to RAP jobid %u\n",
+ (unsigned int)jobid, (unsigned int)rap_jobid));
+ return rap_jobid;
+ }
+ SAFE_FREE(data.dptr);
+ /* Not found - create and store mapping. */
+ rap_jobid = ++next_rap_jobid;
+ if (rap_jobid == 0)
+ rap_jobid = ++next_rap_jobid;
+ SSVAL(buf,0,rap_jobid);
+ data.dptr = buf;
+ data.dsize = sizeof(rap_jobid);
+ tdb_store(rap_tdb, key, data, TDB_REPLACE);
+ tdb_store(rap_tdb, data, key, TDB_REPLACE);
+
+ DEBUG(10,("pjobid_to_rap: created jobid %u maps to RAP jobid %u\n",
+ (unsigned int)jobid, (unsigned int)rap_jobid));
+ return rap_jobid;
+}
+
+bool rap_to_pjobid(uint16_t rap_jobid, fstring sharename, uint32_t *pjobid)
+{
+ TDB_DATA data, key;
+ uint8_t buf[2];
+
+ DEBUG(10,("rap_to_pjobid called.\n"));
+
+ if (!rap_tdb)
+ return False;
+
+ SSVAL(buf,0,rap_jobid);
+ key.dptr = buf;
+ key.dsize = sizeof(rap_jobid);
+ data = tdb_fetch(rap_tdb, key);
+ if ( data.dptr && data.dsize == sizeof(struct rap_jobid_key) )
+ {
+ struct rap_jobid_key *jinfo = (struct rap_jobid_key*)data.dptr;
+ if (sharename != NULL) {
+ fstrcpy( sharename, jinfo->sharename );
+ }
+ *pjobid = jinfo->jobid;
+ DEBUG(10,("rap_to_pjobid: jobid %u maps to RAP jobid %u\n",
+ (unsigned int)*pjobid, (unsigned int)rap_jobid));
+ SAFE_FREE(data.dptr);
+ return True;
+ }
+
+ DEBUG(10,("rap_to_pjobid: Failed to lookup RAP jobid %u\n",
+ (unsigned int)rap_jobid));
+ SAFE_FREE(data.dptr);
+ return False;
+}
+
+void rap_jobid_delete(const char* sharename, uint32_t jobid)
+{
+ TDB_DATA key, data;
+ uint16_t rap_jobid;
+ struct rap_jobid_key jinfo;
+ uint8_t buf[2];
+
+ DEBUG(10,("rap_jobid_delete: called.\n"));
+
+ if (!rap_tdb)
+ return;
+
+ ZERO_STRUCT( jinfo );
+ fstrcpy( jinfo.sharename, sharename );
+ jinfo.jobid = jobid;
+ key.dptr = (uint8_t *)&jinfo;
+ key.dsize = sizeof(jinfo);
+
+ data = tdb_fetch(rap_tdb, key);
+ if (!data.dptr || (data.dsize != sizeof(uint16_t))) {
+ DEBUG(10,("rap_jobid_delete: cannot find jobid %u\n",
+ (unsigned int)jobid ));
+ SAFE_FREE(data.dptr);
+ return;
+ }
+
+ DEBUG(10,("rap_jobid_delete: deleting jobid %u\n",
+ (unsigned int)jobid ));
+
+ rap_jobid = SVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+ SSVAL(buf,0,rap_jobid);
+ data.dptr = buf;
+ data.dsize = sizeof(rap_jobid);
+ tdb_delete(rap_tdb, key);
+ tdb_delete(rap_tdb, data);
+}
diff --git a/source3/printing/rap_jobid.h b/source3/printing/rap_jobid.h
new file mode 100644
index 0000000..6325689
--- /dev/null
+++ b/source3/printing/rap_jobid.h
@@ -0,0 +1,29 @@
+/*
+ * Maintain rap vs spoolss jobids
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef __PRINTING_RAP_JOBID_H__
+#define __PRINTING_RAP_JOBID_H__
+
+#include "includes.h"
+
+uint16_t pjobid_to_rap(const char *sharename, uint32_t jobid);
+bool rap_to_pjobid(
+ uint16_t rap_jobid, fstring sharename, uint32_t *pjobid);
+void rap_jobid_delete(const char *sharename, uint32_t jobid);
+
+#endif
diff --git a/source3/printing/samba-bgqd.c b/source3/printing/samba-bgqd.c
new file mode 100644
index 0000000..59ed0cc
--- /dev/null
+++ b/source3/printing/samba-bgqd.c
@@ -0,0 +1,359 @@
+/*
+ * Printing background queue helper
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "source3/locking/share_mode_lock.h"
+#include "source3/param/loadparm.h"
+#include "source3/param/param_proto.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/cmdline/closefrom_except.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/debug.h"
+#include "lib/util/signal.h"
+#include "lib/util/fault.h"
+#include "lib/util/become_daemon.h"
+#include "lib/util/charset/charset.h"
+#include "lib/util/samba_util.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/pidfile.h"
+#include "lib/async_req/async_sock.h"
+#include "dynconfig/dynconfig.h"
+#include "source3/lib/global_contexts.h"
+#include "messages.h"
+#include "nsswitch/winbind_client.h"
+#include "source3/include/auth.h"
+#include "source3/lib/util_procid.h"
+#include "source3/auth/proto.h"
+#include "source3/printing/queue_process.h"
+#include "source3/lib/substitute.h"
+
+static void watch_handler(struct tevent_req *req)
+{
+ bool *pdone = tevent_req_callback_data_void(req);
+ *pdone = true;
+}
+
+static void bgqd_sig_term_handler(
+ struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ bool *pdone = private_data;
+ *pdone = true;
+}
+
+static bool ready_signal_filter(
+ struct messaging_rec *rec, void *private_data)
+{
+ pid_t pid = getpid();
+ ssize_t written;
+
+ if (rec->msg_type != MSG_DAEMON_READY_FD) {
+ return false;
+ }
+ if (rec->num_fds != 1) {
+ return false;
+ }
+
+ written = sys_write(rec->fds[0], &pid, sizeof(pid));
+ if (written != sizeof(pid)) {
+ DBG_ERR("Could not write pid: %s\n", strerror(errno));
+ }
+
+ return false;
+}
+
+static int samba_bgqd_pidfile_create(
+ struct messaging_context *msg_ctx,
+ const char *progname,
+ int ready_signal_fd)
+{
+ const char *piddir = lp_pid_directory();
+ size_t len = strlen(piddir) + strlen(progname) + 6;
+ char pidFile[len];
+ pid_t existing_pid;
+ int fd, ret;
+
+ snprintf(pidFile,
+ sizeof(pidFile),
+ "%s/%s.pid",
+ piddir, progname);
+
+ ret = pidfile_path_create(pidFile, &fd, &existing_pid);
+ if (ret == 0) {
+ struct tevent_req *ready_signal_req = NULL;
+
+ /*
+ * Listen for fd's sent via MSG_DAEMON_READY_FD:
+ * Multiple instances of this process might have raced
+ * for creating the pidfile. Make sure the parent does
+ * not suffer from this race, reply on behalf of the
+ * loser of this race.
+ */
+
+ ready_signal_req = messaging_filtered_read_send(
+ msg_ctx,
+ messaging_tevent_context(msg_ctx),
+ msg_ctx,
+ ready_signal_filter,
+ NULL);
+ if (ready_signal_req == NULL) {
+ DBG_DEBUG("messaging_filtered_read_send failed\n");
+ pidfile_unlink(piddir, progname);
+ pidfile_fd_close(fd);
+ return ENOMEM;
+ }
+
+ /* leak fd */
+ return 0;
+ }
+
+ if (ret != EAGAIN) {
+ DBG_DEBUG("pidfile_path_create() failed: %s\n",
+ strerror(ret));
+ return ret;
+ }
+
+ DBG_DEBUG("%s pid %d exists\n", progname, (int)existing_pid);
+
+ if (ready_signal_fd != -1) {
+ /*
+ * We lost the race for the pidfile, but someone else
+ * can report readiness on our behalf.
+ */
+ NTSTATUS status = messaging_send_iov(
+ msg_ctx,
+ pid_to_procid(existing_pid),
+ MSG_DAEMON_READY_FD,
+ NULL,
+ 0,
+ &ready_signal_fd,
+ 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not send ready_signal_fd: %s\n",
+ nt_errstr(status));
+ }
+ }
+
+ return EAGAIN;
+}
+
+int main(int argc, const char *argv[])
+{
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *progname = getprogname();
+ TALLOC_CTX *frame = NULL;
+ poptContext pc;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *watch_req = NULL;
+ struct tevent_signal *sigterm_handler = NULL;
+ struct bq_state *bq = NULL;
+ int log_stdout = 0;
+ int ready_signal_fd = -1;
+ int watch_fd = -1;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+ bool done = false;
+ int exitcode = 1;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+
+ /*
+ * File descriptor to write the PID of the helper
+ * process to
+ */
+ {
+ .longName = "ready-signal-fd",
+ .argInfo = POPT_ARG_INT,
+ .arg = &ready_signal_fd,
+ .descrip = "Fd to signal readiness to" ,
+ },
+
+ /*
+ * Read end of a pipe held open by the parent
+ * smbd. Exit this process when it becomes readable.
+ */
+ {
+ .longName = "parent-watch-fd",
+ .argInfo = POPT_ARG_INT,
+ .arg = &watch_fd,
+ .descrip = "Fd to watch for exiting",
+ },
+ POPT_TABLEEND
+ };
+
+ {
+ const char *fd_params[] = {
+ "ready-signal-fd", "parent-watch-fd",
+ };
+
+ closefrom_except_fd_params(
+ 3, ARRAY_SIZE(fd_params), fd_params, argc, argv);
+ }
+
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+ umask(0);
+ set_remote_machine_name("smbd-bgqd", true);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to setup cmdline parser!\n");
+ exit(ENOMEM);
+ }
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ pc = samba_popt_get_context(progname,
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to get popt context!\n");
+ exit(ENOMEM);
+ }
+
+ ret = poptGetNextOpt(pc);
+ if (ret < -1) {
+ fprintf(stderr, "invalid options: %s\n", poptStrerror(ret));
+ goto done;
+ }
+
+ poptFreeContext(pc);
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+
+ /* main process will notify systemd */
+ daemon_sd_notifications(false);
+
+ if (!cmdline_daemon_cfg->fork) {
+ daemon_status(progname, "Starting process ... ");
+ } else {
+ become_daemon(true,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+ }
+
+ BlockSignals(true, SIGPIPE);
+
+ smb_init_locale();
+ dump_core_setup(progname, lp_logfile(frame, lp_sub));
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ DBG_ERR("messaging_init() failed\n");
+ goto done;
+ }
+ ev = messaging_tevent_context(msg_ctx);
+
+ ret = samba_bgqd_pidfile_create(msg_ctx, progname, ready_signal_fd);
+ if (ret != 0) {
+ goto done;
+ }
+
+ if (watch_fd != -1) {
+ watch_req = wait_for_read_send(ev, ev, watch_fd, true);
+ if (watch_req == NULL) {
+ fprintf(stderr, "tevent_add_fd failed\n");
+ goto done;
+ }
+ tevent_req_set_callback(watch_req, watch_handler, &done);
+ }
+
+ (void)winbind_off();
+ ok = init_guest_session_info(frame);
+ (void)winbind_on();
+ if (!ok) {
+ DBG_ERR("init_guest_session_info failed\n");
+ goto done;
+ }
+
+ (void)winbind_off();
+ status = init_system_session_info(frame);
+ (void)winbind_on();
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_system_session_info failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ sigterm_handler = tevent_add_signal(
+ ev, frame, SIGTERM, 0, bgqd_sig_term_handler, &done);
+ if (sigterm_handler == NULL) {
+ DBG_ERR("Could not install SIGTERM handler\n");
+ goto done;
+ }
+
+ bq = register_printing_bq_handlers(frame, msg_ctx);
+ if (bq == NULL) {
+ DBG_ERR("Could not register bq handlers\n");
+ goto done;
+ }
+
+ ok = locking_init();
+ if (!ok) {
+ DBG_ERR("locking_init failed\n");
+ goto done;
+ }
+
+ if (ready_signal_fd != -1) {
+ pid_t pid = getpid();
+ ssize_t written;
+
+ written = sys_write(ready_signal_fd, &pid, sizeof(pid));
+ if (written != sizeof(pid)) {
+ DBG_ERR("Reporting readiness failed\n");
+ goto done;
+ }
+ close(ready_signal_fd);
+ ready_signal_fd = -1;
+ }
+
+ while (!done) {
+ TALLOC_CTX *tmp = talloc_stackframe();
+ ret = tevent_loop_once(ev);
+ TALLOC_FREE(tmp);
+ if (ret != 0) {
+ DBG_ERR("tevent_loop_once failed\n");
+ break;
+ }
+ }
+
+ exitcode = 0;
+done:
+ TALLOC_FREE(watch_req);
+ TALLOC_FREE(bq);
+ TALLOC_FREE(sigterm_handler);
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ return exitcode;
+}
diff --git a/source3/printing/tests/README.vlp b/source3/printing/tests/README.vlp
new file mode 100644
index 0000000..fc0b91a
--- /dev/null
+++ b/source3/printing/tests/README.vlp
@@ -0,0 +1,19 @@
+Virtual line printer test program (vlp)
+=======================================
+
+This can be useful for testing/debugging Samba print code. It gives you a
+virtual full-function printer.
+
+Setup
+
+Set up Samba to use vlp.
+ In your smb.conf file under [global], add the following option:
+ printing = vlp
+ and then add any number of print shares, without needing to make them
+ really exist.
+
+ [testprinter]
+ printable = yes
+
+ is all you need for the most basic virtual printer.
+
diff --git a/source3/printing/tests/vlp.c b/source3/printing/tests/vlp.c
new file mode 100644
index 0000000..d596c31
--- /dev/null
+++ b/source3/printing/tests/vlp.c
@@ -0,0 +1,446 @@
+/*
+ Unix SMB/Netbios implementation.
+
+ Virtual lp system for printer testing
+
+ Copyright (C) Tim Potter 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "util_tdb.h"
+
+#ifdef malloc
+#undef malloc
+#endif
+
+#define PRINT_FIRSTJOB 100
+
+static TDB_CONTEXT *tdb;
+
+struct vlp_job {
+ fstring owner;
+ int jobid;
+ fstring jobname;
+ int size;
+ int status;
+ time_t submit_time;
+ int deleted;
+};
+
+/* Print usage */
+
+static void usage(void)
+{
+ printf("Usage: vlp tdbfile=/tmp/vlp.tdb lpq|lprm|print|queuepause|queueresume|"
+ "lppause|lpresume [args]\n");
+}
+
+/* Return an array of vlp jobs that is the printer queue */
+
+static void get_job_list(char *printer, struct vlp_job **job_list,
+ int *num_jobs)
+{
+ fstring keystr;
+ TDB_DATA data;
+
+ slprintf(keystr, sizeof(keystr) - 1, "LPQ/%s", printer);
+ data = tdb_fetch_bystring(tdb, keystr);
+
+ *job_list = (struct vlp_job *)data.dptr;
+ *num_jobs = data.dsize / sizeof(struct vlp_job);
+}
+
+/* Store an array of vl jobs for the queue */
+
+static void set_job_list(char *printer, struct vlp_job *job_list,
+ int num_jobs)
+{
+ fstring keystr;
+ TDB_DATA data;
+
+ slprintf(keystr, sizeof(keystr) - 1, "LPQ/%s", printer);
+
+ data.dptr = (unsigned char *)job_list;
+ data.dsize = num_jobs * sizeof(struct vlp_job);
+ tdb_store_bystring(tdb, keystr, data, TDB_REPLACE);
+}
+
+/* Return the next job number for a printer */
+
+static int next_jobnum(char *printer)
+{
+ fstring keystr;
+ int jobnum;
+
+ slprintf(keystr, sizeof(keystr) - 1, "JOBNUM/%s", printer);
+
+ tdb_lock_bystring(tdb, keystr);
+
+ jobnum = tdb_fetch_int32(tdb, keystr);
+
+ /* Create next job index if none exists */
+
+ if (jobnum == -1) {
+ jobnum = PRINT_FIRSTJOB;
+ } else {
+ jobnum++;
+ }
+
+ tdb_store_int32(tdb, keystr, jobnum);
+
+ tdb_unlock_bystring(tdb, keystr);
+
+ return jobnum;
+}
+
+static void set_printer_status(char *printer, int status)
+{
+ fstring keystr;
+
+ slprintf(keystr, sizeof(keystr) - 1, "STATUS/%s", printer);
+ tdb_store_int32(tdb, keystr, status);
+}
+
+static int get_printer_status(char *printer)
+{
+ fstring keystr;
+ TDB_DATA data;
+
+ slprintf(keystr, sizeof(keystr) - 1, "STATUS/%s", printer);
+
+ data.dptr = (unsigned char *)keystr;
+ data.dsize = strlen(keystr) + 1;
+
+ if (!tdb_exists(tdb, data)) {
+ set_printer_status(printer, LPSTAT_OK);
+ return LPSTAT_OK;
+ }
+
+ return tdb_fetch_int32(tdb, keystr);
+}
+
+/* Display printer queue */
+
+static int lpq_command(int argc, char **argv)
+{
+ char *printer;
+ struct vlp_job *job_list = NULL;
+ int i, num_jobs, job_count = 0;
+
+ if (argc != 2) {
+ printf("Usage: lpq <printername>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+
+ /* Display printer status */
+
+ switch (get_printer_status(printer)) {
+ case LPSTAT_OK:
+ printf("enabled\n");
+ break;
+ case LPSTAT_STOPPED:
+ printf("disabled\n");
+ break;
+ case LPSTAT_ERROR:
+ default:
+ printf("error\n");
+ break;
+ }
+
+ /* Print queued documents */
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].deleted) continue;
+ printf("%d\t%d\t%d\t%ld\t%s\t%s\n", job_list[i].jobid,
+ job_list[i].size,
+ (i == 0 && job_list[i].status == LPQ_QUEUED) ?
+ LPQ_SPOOLING : job_list[i].status,
+ (long int)job_list[i].submit_time, job_list[i].owner,
+ job_list[i].jobname);
+ job_count++;
+ }
+
+ free(job_list);
+
+ return 0;
+}
+
+/* Remove a job */
+
+static int lprm_command(int argc, char **argv)
+{
+ char *printer;
+ int jobid, num_jobs, i;
+ struct vlp_job *job_list;
+
+ if (argc < 3) {
+ printf("Usage: lprm <printername> <jobid>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ jobid = atoi(argv[2]);
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].jobid == jobid) {
+ job_list[i].deleted = 1;
+ set_job_list(printer, job_list, num_jobs);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* print command = print-test %p %s */
+
+static int print_command(int argc, char **argv)
+{
+ char *printer;
+ fstring keystr;
+ struct passwd *pw;
+ TDB_DATA value, queue;
+ struct vlp_job job;
+
+ if (argc < 3) {
+ printf("Usage: print <printername> <jobname>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+
+ ZERO_STRUCT(job);
+
+ /* Create a job record */
+
+ slprintf(job.jobname, sizeof(job.jobname) - 1, "%s", argv[2]);
+
+ if (!(pw = getpwuid(geteuid()))) {
+ printf("getpwuid failed\n");
+ return 1;
+ }
+
+ slprintf(job.owner, sizeof(job.owner) - 1, "%s", pw->pw_name);
+
+ job.jobid = next_jobnum(printer);
+ job.size = 666;
+ job.submit_time = time(NULL);
+
+ /* Store job entry in queue */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LPQ/%s", printer);
+
+ value = tdb_fetch_bystring(tdb, keystr);
+
+ if (value.dptr) {
+
+ /* Add job to end of queue */
+
+ queue.dptr = (unsigned char *)malloc(value.dsize + sizeof(struct vlp_job));
+ if (!queue.dptr) return 1;
+
+ memcpy(queue.dptr, value.dptr, value.dsize);
+ memcpy(queue.dptr + value.dsize, &job, sizeof(struct vlp_job));
+
+ queue.dsize = value.dsize + sizeof(struct vlp_job);
+
+ tdb_store_bystring(tdb, keystr, queue, TDB_REPLACE);
+
+ free(queue.dptr);
+
+ } else {
+
+ /* Create new queue */
+ queue.dptr = (unsigned char *)&job;
+ queue.dsize = sizeof(struct vlp_job);
+
+ tdb_store_bystring(tdb, keystr, queue, TDB_REPLACE);
+ }
+
+ return 0;
+}
+
+/* Pause the queue */
+
+static int queuepause_command(int argc, char **argv)
+{
+ char *printer;
+
+ if (argc != 2) {
+ printf("Usage: queuepause <printername>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ set_printer_status(printer, LPSTAT_STOPPED);
+
+ return 0;
+}
+
+/* Resume the queue */
+
+static int queueresume_command(int argc, char **argv)
+{
+ char *printer;
+
+ if (argc != 2) {
+ printf("Usage: queueresume <printername>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ set_printer_status(printer, LPSTAT_OK);
+
+ return 0;
+}
+
+/* Pause a job */
+
+static int lppause_command(int argc, char **argv)
+{
+ struct vlp_job *job_list;
+ char *printer;
+ int jobid, num_jobs, i;
+
+ if (argc != 3) {
+ printf("Usage: lppause <printername> <jobid>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ jobid = atoi(argv[2]);
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].jobid == jobid) {
+ job_list[i].status = LPQ_PAUSED;
+ set_job_list(printer, job_list, num_jobs);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Resume a job */
+
+static int lpresume_command(int argc, char **argv)
+{
+ struct vlp_job *job_list;
+ char *printer;
+ int jobid, num_jobs, i;
+
+ if (argc != 3) {
+ printf("Usage: lpresume <printername> <jobid>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ jobid = atoi(argv[2]);
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].jobid == jobid) {
+ job_list[i].status = LPQ_QUEUED;
+ set_job_list(printer, job_list, num_jobs);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ /* Parameter check */
+ const char *printdb_path = NULL;
+
+ if (argc < 2) {
+ usage();
+ return 1;
+ }
+
+ if (strncmp(argv[1], "tdbfile", strlen("tdbfile")) != 0) {
+ usage();
+ return 1;
+ }
+
+ printdb_path = get_string_param(argv[1]);
+ if (!printdb_path) {
+ return 1;
+ }
+
+ /* FIXME: We should *never* open a tdb without logging! */
+ if (!(tdb = tdb_open(printdb_path, 0, 0, O_RDWR | O_CREAT,
+ 0666))) {
+ printf("%s: unable to open %s\n", argv[0], printdb_path);
+ return 1;
+ }
+
+ /* Ensure we are modes 666 */
+
+ chmod(printdb_path, 0666);
+
+ /* Do commands */
+ if (argc < 3) {
+ usage();
+ return 1;
+ }
+
+ if (strcmp(argv[2], "lpq") == 0) {
+ return lpq_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "lprm") == 0) {
+ return lprm_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "print") == 0) {
+ return print_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "queuepause") == 0) {
+ return queuepause_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "queueresume") == 0) {
+ return queueresume_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "lppause") == 0) {
+ return lppause_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "lpresume") == 0) {
+ return lpresume_command(argc - 2, &argv[2]);
+ }
+
+ /* Unknown command */
+
+ printf("%s: invalid command %s\n", argv[0], argv[1]);
+ return 1;
+}