diff options
Diffstat (limited to '')
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..8351402 --- /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 +------- ----- --------- --- ------------------ ---------- ---- -- ----- --- --- +laser laser READY +laser laser 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..7907fb4 --- /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 boundary, + * 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 kernel 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 overlapping 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 architectures 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 separately, 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..4d5991e --- /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); + ADS_TALLOC_CONST_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 attribute 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); + ADS_TALLOC_CONST_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); + ADS_TALLOC_CONST_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..d8ba9cc --- /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); + 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\n")); + } + } + } 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!\n")); + 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..2b2215e --- /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 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). */ + + 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 accommodate + * 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!\n")); + 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..7fd07ed --- /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 unpack printer data\n")); + 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 unpack printer data\n")); + 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 unpack printer data\n")); + 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 unpack printer data\n")); + 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..70b2b11 --- /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 multiple + 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(), + get_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(), + get_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 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_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 assigned 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 remove 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.\n", + 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.\n", + 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.\n", + 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 dummy = { + .dsize = 0, + }; + if (tdb_store(pdb->tdb, print_key(jobid, &tmp), dummy, + 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(), + get_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..94404f7 --- /dev/null +++ b/source3/printing/printspoolss.c @@ -0,0 +1,406 @@ +/* + 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->fsp_flags.is_fsa = true; + + 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..b19b21e --- /dev/null +++ b/source3/printing/tests/vlp.c @@ -0,0 +1,445 @@ +/* + 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; + + 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); + } + + 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; +} |