diff options
Diffstat (limited to 'source3/printing')
-rw-r--r-- | source3/printing/load.c | 64 | ||||
-rw-r--r-- | source3/printing/lpq_parse.c | 1152 | ||||
-rw-r--r-- | source3/printing/notify.c | 571 | ||||
-rw-r--r-- | source3/printing/nt_printing.c | 5892 | ||||
-rw-r--r-- | source3/printing/pcap.c | 263 | ||||
-rw-r--r-- | source3/printing/print_aix.c | 123 | ||||
-rw-r--r-- | source3/printing/print_cups.c | 1347 | ||||
-rw-r--r-- | source3/printing/print_generic.c | 301 | ||||
-rw-r--r-- | source3/printing/print_iprint.c | 1240 | ||||
-rw-r--r-- | source3/printing/print_svid.c | 127 | ||||
-rw-r--r-- | source3/printing/printfsp.c | 122 | ||||
-rw-r--r-- | source3/printing/printing.c | 2888 | ||||
-rw-r--r-- | source3/printing/printing_db.c | 215 |
13 files changed, 14305 insertions, 0 deletions
diff --git a/source3/printing/load.c b/source3/printing/load.c new file mode 100644 index 0000000000..23144d5a95 --- /dev/null +++ b/source3/printing/load.c @@ -0,0 +1,64 @@ +/* + 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" + + +/*************************************************************************** +auto-load some homes and printer services +***************************************************************************/ +static void add_auto_printers(void) +{ + const char *p; + int pnum = lp_servicenumber(PRINTERS_NAME); + char *str; + char *saveptr; + + if (pnum < 0) + return; + + if ((str = SMB_STRDUP(lp_auto_services())) == 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 (pcap_printername_ok(p)) + lp_add_printer(p, pnum); + } + + SAFE_FREE(str); +} + +/*************************************************************************** +load automatic printer services +***************************************************************************/ +void load_printers(void) +{ + if (!pcap_cache_loaded()) + pcap_cache_reload(); + + add_auto_printers(); + + /* load all printcap printers */ + if (lp_load_printers() && lp_servicenumber(PRINTERS_NAME) >= 0) + pcap_printer_fn(lp_add_one_printer); +} diff --git a/source3/printing/lpq_parse.c b/source3/printing/lpq_parse.c new file mode 100644 index 0000000000..addf2d14aa --- /dev/null +++ b/source3/printing/lpq_parse.c @@ -0,0 +1,1152 @@ +/* + 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" + +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->job = 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 ){ + t->tm_hour = atoi(time_string); + t->tm_min = atoi(time_string+3); + t->tm_sec = atoi(time_string+6); + } else { + 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->job = atoi(tokarr[LPRNG_JOBTOK]); + buf->size = atoi(tokarr[LPRNG_TOTALTOK]); + + if (strequal(tokarr[LPRNG_RANKTOK],"active")) { + buf->status = LPQ_PRINTING; + } else if (strequal(tokarr[LPRNG_RANKTOK],"done")) { + buf->status = LPQ_PRINTED; + } else if (isdigit((int)*tokarr[LPRNG_RANKTOK])) { + buf->status = LPQ_QUEUED; + } else { + buf->status = LPQ_PAUSED; + } + + buf->priority = *tokarr[LPRNG_PRIOTOK] -'A'; + + buf->time = LPRng_time(tokarr[LPRNG_TIMETOK]); + + fstrcpy(buf->fs_user,tokarr[LPRNG_USERTOK]); + + /* The '@hostname' prevents windows from displaying the printing icon + * for the current user on the taskbar. Plop in a null. + */ + + if ((ptr = strchr_m(buf->fs_user,'@')) != NULL) { + *ptr = '\0'; + } + + fstrcpy(buf->fs_file,tokarr[LPRNG_FILETOK]); + + if ((LPRNG_FILETOK + 1) != LPRNG_TOTALTOK) { + int i; + + for (i = (LPRNG_FILETOK + 1); i < LPRNG_TOTALTOK; i++) { + /* FIXME: Using fstrcat rather than other means is a bit + * inefficient; this might be a problem for enormous queues with + * many fields. */ + fstrcat(buf->fs_file, " "); + fstrcat(buf->fs_file, tokarr[i]); + } + /* Ensure null termination. */ + buf->fs_file[sizeof(buf->fs_file)-1] = '\0'; + } + + TALLOC_FREE(frame); + return True; +} + +/******************************************************************* +parse lpq on an aix system + +Queue Dev Status Job Files User PP % Blks Cp Rnk +------- ----- --------- --- ------------------ ---------- ---- -- ----- --- --- +lazer lazer READY +lazer lazer RUNNING 537 6297doc.A kvintus@IE 0 10 2445 1 1 + QUEUED 538 C.ps root@IEDVB 124 1 2 + QUEUED 539 E.ps root@IEDVB 28 1 3 + QUEUED 540 L.ps root@IEDVB 172 1 4 + QUEUED 541 P.ps root@IEDVB 22 1 5 +********************************************************************/ + +static bool parse_lpq_aix(char *line,print_queue_struct *buf,bool first) +{ + char *tok[11]; + int count=0; + const char *cline = line; + TALLOC_CTX *frame = talloc_stackframe(); + + /* handle the case of "(standard input)" as a filename */ + string_sub(line,"standard input","STDIN",0); + all_string_sub(line,"(","\"",0); + all_string_sub(line,")","\"",0); + + for (count=0; count<10 && + next_token_talloc(frame,&cline,&tok[count],NULL); count++) { + ; + } + + /* we must get 6 tokens */ + if (count < 10) { + if ((count == 7) && ((strcmp(tok[0],"QUEUED") == 0) || (strcmp(tok[0],"HELD") == 0))) { + /* the 2nd and 5th columns must be integer */ + if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[4])) { + TALLOC_FREE(frame); + return False; + } + buf->size = atoi(tok[4]) * 1024; + /* if the fname contains a space then use STDIN */ + if (strchr_m(tok[2],' ')) { + tok[2] = talloc_strdup(frame,"STDIN"); + if (!tok[2]) { + TALLOC_FREE(frame); + return false; + } + } + + /* only take the last part of the filename */ + { + char *p = strrchr_m(tok[2],'/'); + if (p) { + tok[2] = p+1; + } + } + + buf->job = 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->job = 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->job = 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->job = 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->job = 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) { + fstrcpy(tmp,p+1); + fstrcpy(tok[6],tmp); + } + } + + buf->job = 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->job = 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->job = 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->job = 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; + strlower_m(line); + + 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; + } + } + /* fallthrough */ + 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; + } + } + /* fallthrough */ + 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 0000000000..23df17c389 --- /dev/null +++ b/source3/printing/notify.c @@ -0,0 +1,571 @@ +/* + 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" + +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 *buf; + size_t buflen; +} *notify_queue_head = NULL; + + +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_print_ok(snum)) + return -1; + return snum; +} + +/******************************************************************* + Used to decide if we need a short select timeout. +*******************************************************************/ + +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 *buf = NULL; + size_t buflen = 0, len; + +again: + len = 0; + + /* Pack header */ + + len += tdb_pack(buf + len, buflen - len, "f", msg->printer); + + len += tdb_pack(buf + len, buflen - len, "ddddddd", + (uint32)q->tv.tv_sec, (uint32)q->tv.tv_usec, + msg->type, msg->field, msg->id, msg->len, msg->flags); + + /* Pack data */ + + if (msg->len == 0) + len += tdb_pack(buf + len, buflen - len, "dd", + msg->notify.value[0], msg->notify.value[1]); + else + len += tdb_pack(buf + len, buflen - len, "B", + msg->len, msg->notify.data); + + if (buflen != len) { + buf = (uint8 *)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 *)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; +} + +/********************************************************************** + 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(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_TOTAL_BYTES + || msg->field == JOB_NOTIFY_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_P(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_P(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, struct notify_queue *); + num_messages++; +} + +static void send_notify_field_values(const char *sharename, uint32 type, + uint32 field, uint32 id, uint32 value1, + uint32 value2, uint32 flags) +{ + struct spoolss_notify_msg *msg; + + if (lp_disable_spoolss()) + return; + + if (!create_send_ctx()) + return; + + msg = TALLOC_P(send_ctx, struct spoolss_notify_msg); + if (!msg) + return; + + ZERO_STRUCTP(msg); + + 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(msg); +} + +static void send_notify_field_buffer(const char *sharename, uint32 type, + uint32 field, uint32 id, uint32 len, + const char *buffer) +{ + struct spoolss_notify_msg *msg; + + if (lp_disable_spoolss()) + return; + + if (!create_send_ctx()) + return; + + msg = TALLOC_P(send_ctx, struct spoolss_notify_msg); + if (!msg) + return; + + ZERO_STRUCTP(msg); + + fstrcpy(msg->printer, sharename); + msg->type = type; + msg->field = field; + msg->id = id; + msg->len = len; + msg->notify.data = CONST_DISCARD(char *,buffer); + + send_spoolss_notify2_msg(msg); +} + +/* Send a message that the printer status has changed */ + +void notify_printer_status_byname(const char *sharename, uint32 status) +{ + /* Printer status stored in value1 */ + + send_notify_field_values(sharename, PRINTER_NOTIFY_TYPE, + PRINTER_NOTIFY_STATUS, 0, + status, 0, 0); +} + +void notify_printer_status(int snum, uint32 status) +{ + const char *sharename = SERVICE(snum); + + if (sharename) + notify_printer_status_byname(sharename, status); +} + +void notify_job_status_byname(const char *sharename, uint32 jobid, uint32 status, + uint32 flags) +{ + /* Job id stored in id field, status in value1 */ + + send_notify_field_values(sharename, JOB_NOTIFY_TYPE, + JOB_NOTIFY_STATUS, jobid, + status, 0, flags); +} + +void notify_job_status(const char *sharename, uint32 jobid, uint32 status) +{ + notify_job_status_byname(sharename, jobid, status, 0); +} + +void notify_job_total_bytes(const char *sharename, uint32 jobid, + uint32 size) +{ + /* Job id stored in id field, status in value1 */ + + send_notify_field_values(sharename, JOB_NOTIFY_TYPE, + JOB_NOTIFY_TOTAL_BYTES, jobid, + size, 0, 0); +} + +void notify_job_total_pages(const char *sharename, uint32 jobid, + uint32 pages) +{ + /* Job id stored in id field, status in value1 */ + + send_notify_field_values(sharename, JOB_NOTIFY_TYPE, + JOB_NOTIFY_TOTAL_PAGES, jobid, + pages, 0, 0); +} + +void notify_job_username(const char *sharename, uint32 jobid, char *name) +{ + send_notify_field_buffer( + sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_USER_NAME, + jobid, strlen(name) + 1, name); +} + +void notify_job_name(const char *sharename, uint32 jobid, char *name) +{ + send_notify_field_buffer( + sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_DOCUMENT, + jobid, strlen(name) + 1, name); +} + +void notify_job_submitted(const char *sharename, uint32 jobid, + time_t submitted) +{ + send_notify_field_buffer( + sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_SUBMITTED, + jobid, sizeof(submitted), (char *)&submitted); +} + +void notify_printer_driver(int snum, char *driver_name) +{ + const char *sharename = SERVICE(snum); + + send_notify_field_buffer( + sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_DRIVER_NAME, + snum, strlen(driver_name) + 1, driver_name); +} + +void notify_printer_comment(int snum, char *comment) +{ + const char *sharename = SERVICE(snum); + + send_notify_field_buffer( + sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_COMMENT, + snum, strlen(comment) + 1, comment); +} + +void notify_printer_sharename(int snum, char *share_name) +{ + const char *sharename = SERVICE(snum); + + send_notify_field_buffer( + sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_SHARE_NAME, + snum, strlen(share_name) + 1, share_name); +} + +void notify_printer_printername(int snum, char *printername) +{ + const char *sharename = SERVICE(snum); + + send_notify_field_buffer( + sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_PRINTER_NAME, + snum, strlen(printername) + 1, printername); +} + +void notify_printer_port(int snum, char *port_name) +{ + const char *sharename = SERVICE(snum); + + send_notify_field_buffer( + sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_PORT_NAME, + snum, strlen(port_name) + 1, port_name); +} + +void notify_printer_location(int snum, char *location) +{ + const char *sharename = SERVICE(snum); + + send_notify_field_buffer( + sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_LOCATION, + snum, strlen(location) + 1, location); +} + +void notify_printer_byname( const char *printername, uint32 change, const char *value ) +{ + int snum = print_queue_snum(printername); + int type = PRINTER_NOTIFY_TYPE; + + if ( snum == -1 ) + return; + + send_notify_field_buffer( 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. +****************************************************************************/ + +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) == -1) { + 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/nt_printing.c b/source3/printing/nt_printing.c new file mode 100644 index 0000000000..ded985c260 --- /dev/null +++ b/source3/printing/nt_printing.c @@ -0,0 +1,5892 @@ +/* + * 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" + +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 */ + +#define FORMS_PREFIX "FORMS/" +#define DRIVERS_PREFIX "DRIVERS/" +#define DRIVER_INIT_PREFIX "DRIVER_INIT/" +#define PRINTERS_PREFIX "PRINTERS/" +#define SECDESC_PREFIX "SECDESC/" +#define GLOBAL_C_SETPRINTER "GLOBALS/c_setprinter" + +#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 */ + +/* Map generic permissions to printer object specific permissions */ + +const struct generic_mapping printer_generic_mapping = { + PRINTER_READ, + PRINTER_WRITE, + PRINTER_EXECUTE, + PRINTER_ALL_ACCESS +}; + +const struct standard_mapping printer_std_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 +}; + +const struct generic_mapping printserver_std_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 +}; + +/* We need one default form to support our default printer. Msoft adds the +forms it wants and in the ORDER it wants them (note: DEVMODE papersize is an +array index). Letter is always first, so (for the current code) additions +always put things in the correct order. */ +static const nt_forms_struct default_forms[] = { + {"Letter",0x1,0x34b5c,0x44368,0x0,0x0,0x34b5c,0x44368}, + {"Letter Small",0x1,0x34b5c,0x44368,0x0,0x0,0x34b5c,0x44368}, + {"Tabloid",0x1,0x44368,0x696b8,0x0,0x0,0x44368,0x696b8}, + {"Ledger",0x1,0x696b8,0x44368,0x0,0x0,0x696b8,0x44368}, + {"Legal",0x1,0x34b5c,0x56d10,0x0,0x0,0x34b5c,0x56d10}, + {"Statement",0x1,0x221b4,0x34b5c,0x0,0x0,0x221b4,0x34b5c}, + {"Executive",0x1,0x2cf56,0x411cc,0x0,0x0,0x2cf56,0x411cc}, + {"A3",0x1,0x48828,0x668a0,0x0,0x0,0x48828,0x668a0}, + {"A4",0x1,0x33450,0x48828,0x0,0x0,0x33450,0x48828}, + {"A4 Small",0x1,0x33450,0x48828,0x0,0x0,0x33450,0x48828}, + {"A5",0x1,0x24220,0x33450,0x0,0x0,0x24220,0x33450}, + {"B4 (JIS)",0x1,0x3ebe8,0x58de0,0x0,0x0,0x3ebe8,0x58de0}, + {"B5 (JIS)",0x1,0x2c6f0,0x3ebe8,0x0,0x0,0x2c6f0,0x3ebe8}, + {"Folio",0x1,0x34b5c,0x509d8,0x0,0x0,0x34b5c,0x509d8}, + {"Quarto",0x1,0x347d8,0x43238,0x0,0x0,0x347d8,0x43238}, + {"10x14",0x1,0x3e030,0x56d10,0x0,0x0,0x3e030,0x56d10}, + {"11x17",0x1,0x44368,0x696b8,0x0,0x0,0x44368,0x696b8}, + {"Note",0x1,0x34b5c,0x44368,0x0,0x0,0x34b5c,0x44368}, + {"Envelope #9",0x1,0x18079,0x37091,0x0,0x0,0x18079,0x37091}, + {"Envelope #10",0x1,0x19947,0x3ae94,0x0,0x0,0x19947,0x3ae94}, + {"Envelope #11",0x1,0x1be7c,0x40565,0x0,0x0,0x1be7c,0x40565}, + {"Envelope #12",0x1,0x1d74a,0x44368,0x0,0x0,0x1d74a,0x44368}, + {"Envelope #14",0x1,0x1f018,0x47504,0x0,0x0,0x1f018,0x47504}, + {"C size sheet",0x1,0x696b8,0x886d0,0x0,0x0,0x696b8,0x886d0}, + {"D size sheet",0x1,0x886d0,0xd2d70,0x0,0x0,0x886d0,0xd2d70}, + {"E size sheet",0x1,0xd2d70,0x110da0,0x0,0x0,0xd2d70,0x110da0}, + {"Envelope DL",0x1,0x1adb0,0x35b60,0x0,0x0,0x1adb0,0x35b60}, + {"Envelope C5",0x1,0x278d0,0x37e88,0x0,0x0,0x278d0,0x37e88}, + {"Envelope C3",0x1,0x4f1a0,0x6fd10,0x0,0x0,0x4f1a0,0x6fd10}, + {"Envelope C4",0x1,0x37e88,0x4f1a0,0x0,0x0,0x37e88,0x4f1a0}, + {"Envelope C6",0x1,0x1bd50,0x278d0,0x0,0x0,0x1bd50,0x278d0}, + {"Envelope C65",0x1,0x1bd50,0x37e88,0x0,0x0,0x1bd50,0x37e88}, + {"Envelope B4",0x1,0x3d090,0x562e8,0x0,0x0,0x3d090,0x562e8}, + {"Envelope B5",0x1,0x2af80,0x3d090,0x0,0x0,0x2af80,0x3d090}, + {"Envelope B6",0x1,0x2af80,0x1e848,0x0,0x0,0x2af80,0x1e848}, + {"Envelope",0x1,0x1adb0,0x38270,0x0,0x0,0x1adb0,0x38270}, + {"Envelope Monarch",0x1,0x18079,0x2e824,0x0,0x0,0x18079,0x2e824}, + {"6 3/4 Envelope",0x1,0x167ab,0x284ec,0x0,0x0,0x167ab,0x284ec}, + {"US Std Fanfold",0x1,0x5c3e1,0x44368,0x0,0x0,0x5c3e1,0x44368}, + {"German Std Fanfold",0x1,0x34b5c,0x4a6a0,0x0,0x0,0x34b5c,0x4a6a0}, + {"German Legal Fanfold",0x1,0x34b5c,0x509d8,0x0,0x0,0x34b5c,0x509d8}, + {"B4 (ISO)",0x1,0x3d090,0x562e8,0x0,0x0,0x3d090,0x562e8}, + {"Japanese Postcard",0x1,0x186a0,0x24220,0x0,0x0,0x186a0,0x24220}, + {"9x11",0x1,0x37cf8,0x44368,0x0,0x0,0x37cf8,0x44368}, + {"10x11",0x1,0x3e030,0x44368,0x0,0x0,0x3e030,0x44368}, + {"15x11",0x1,0x5d048,0x44368,0x0,0x0,0x5d048,0x44368}, + {"Envelope Invite",0x1,0x35b60,0x35b60,0x0,0x0,0x35b60,0x35b60}, + {"Reserved48",0x1,0x1,0x1,0x0,0x0,0x1,0x1}, + {"Reserved49",0x1,0x1,0x1,0x0,0x0,0x1,0x1}, + {"Letter Extra",0x1,0x3ae94,0x4a6a0,0x0,0x0,0x3ae94,0x4a6a0}, + {"Legal Extra",0x1,0x3ae94,0x5d048,0x0,0x0,0x3ae94,0x5d048}, + {"Tabloid Extra",0x1,0x4a6a0,0x6f9f0,0x0,0x0,0x4a6a0,0x6f9f0}, + {"A4 Extra",0x1,0x397c2,0x4eb16,0x0,0x0,0x397c2,0x4eb16}, + {"Letter Transverse",0x1,0x34b5c,0x44368,0x0,0x0,0x34b5c,0x44368}, + {"A4 Transverse",0x1,0x33450,0x48828,0x0,0x0,0x33450,0x48828}, + {"Letter Extra Transverse",0x1,0x3ae94,0x4a6a0,0x0,0x0,0x3ae94,0x4a6a0}, + {"Super A",0x1,0x376b8,0x56ea0,0x0,0x0,0x376b8,0x56ea0}, + {"Super B",0x1,0x4a768,0x76e58,0x0,0x0,0x4a768,0x76e58}, + {"Letter Plus",0x1,0x34b5c,0x4eb16,0x0,0x0,0x34b5c,0x4eb16}, + {"A4 Plus",0x1,0x33450,0x50910,0x0,0x0,0x33450,0x50910}, + {"A5 Transverse",0x1,0x24220,0x33450,0x0,0x0,0x24220,0x33450}, + {"B5 (JIS) Transverse",0x1,0x2c6f0,0x3ebe8,0x0,0x0,0x2c6f0,0x3ebe8}, + {"A3 Extra",0x1,0x4e9d0,0x6ca48,0x0,0x0,0x4e9d0,0x6ca48}, + {"A5 Extra",0x1,0x2a7b0,0x395f8,0x0,0x0,0x2a7b0,0x395f8}, + {"B5 (ISO) Extra",0x1,0x31128,0x43620,0x0,0x0,0x31128,0x43620}, + {"A2",0x1,0x668a0,0x91050,0x0,0x0,0x668a0,0x91050}, + {"A3 Transverse",0x1,0x48828,0x668a0,0x0,0x0,0x48828,0x668a0}, + {"A3 Extra Transverse",0x1,0x4e9d0,0x6ca48,0x0,0x0,0x4e9d0,0x6ca48}, + {"Japanese Double Postcard",0x1,0x30d40,0x24220,0x0,0x0,0x30d40,0x24220}, + {"A6",0x1,0x19a28,0x24220,0x0,0x0,0x19a28,0x24220}, + {"Japanese Envelope Kaku #2",0x1,0x3a980,0x510e0,0x0,0x0,0x3a980,0x510e0}, + {"Japanese Envelope Kaku #3",0x1,0x34bc0,0x43a08,0x0,0x0,0x34bc0,0x43a08}, + {"Japanese Envelope Chou #3",0x1,0x1d4c0,0x395f8,0x0,0x0,0x1d4c0,0x395f8}, + {"Japanese Envelope Chou #4",0x1,0x15f90,0x320c8,0x0,0x0,0x15f90,0x320c8}, + {"Letter Rotated",0x1,0x44368,0x34b5c,0x0,0x0,0x44368,0x34b5c}, + {"A3 Rotated",0x1,0x668a0,0x48828,0x0,0x0,0x668a0,0x48828}, + {"A4 Rotated",0x1,0x48828,0x33450,0x0,0x0,0x48828,0x33450}, + {"A5 Rotated",0x1,0x33450,0x24220,0x0,0x0,0x33450,0x24220}, + {"B4 (JIS) Rotated",0x1,0x58de0,0x3ebe8,0x0,0x0,0x58de0,0x3ebe8}, + {"B5 (JIS) Rotated",0x1,0x3ebe8,0x2c6f0,0x0,0x0,0x3ebe8,0x2c6f0}, + {"Japanese Postcard Rotated",0x1,0x24220,0x186a0,0x0,0x0,0x24220,0x186a0}, + {"Double Japan Postcard Rotated",0x1,0x24220,0x30d40,0x0,0x0,0x24220,0x30d40}, + {"A6 Rotated",0x1,0x24220,0x19a28,0x0,0x0,0x24220,0x19a28}, + {"Japan Envelope Kaku #2 Rotated",0x1,0x510e0,0x3a980,0x0,0x0,0x510e0,0x3a980}, + {"Japan Envelope Kaku #3 Rotated",0x1,0x43a08,0x34bc0,0x0,0x0,0x43a08, 0x34bc0}, + {"Japan Envelope Chou #3 Rotated",0x1,0x395f8,0x1d4c0,0x0,0x0,0x395f8,0x1d4c0}, + {"Japan Envelope Chou #4 Rotated",0x1,0x320c8,0x15f90,0x0,0x0,0x320c8,0x15f90}, + {"B6 (JIS)",0x1,0x1f400,0x2c6f0,0x0,0x0,0x1f400,0x2c6f0}, + {"B6 (JIS) Rotated",0x1,0x2c6f0,0x1f400,0x0,0x0,0x2c6f0,0x1f400}, + {"12x11",0x1,0x4a724,0x443e1,0x0,0x0,0x4a724,0x443e1}, + {"Japan Envelope You #4",0x1,0x19a28,0x395f8,0x0,0x0,0x19a28,0x395f8}, + {"Japan Envelope You #4 Rotated",0x1,0x395f8,0x19a28,0x0,0x0,0x395f8,0x19a28}, + {"PRC 16K",0x1,0x2de60,0x3f7a0,0x0,0x0,0x2de60,0x3f7a0}, + {"PRC 32K",0x1,0x1fbd0,0x2cec0,0x0,0x0,0x1fbd0,0x2cec0}, + {"PRC 32K(Big)",0x1,0x222e0,0x318f8,0x0,0x0,0x222e0,0x318f8}, + {"PRC Envelope #1",0x1,0x18e70,0x28488,0x0,0x0,0x18e70,0x28488}, + {"PRC Envelope #2",0x1,0x18e70,0x2af80,0x0,0x0,0x18e70,0x2af80}, + {"PRC Envelope #3",0x1,0x1e848,0x2af80,0x0,0x0,0x1e848,0x2af80}, + {"PRC Envelope #4",0x1,0x1adb0,0x32c80,0x0,0x0,0x1adb0,0x32c80}, + {"PRC Envelope #5",0x1,0x1adb0,0x35b60,0x0,0x0,0x1adb0,0x35b60}, + {"PRC Envelope #6",0x1,0x1d4c0,0x38270,0x0,0x0,0x1d4c0,0x38270}, + {"PRC Envelope #7",0x1,0x27100,0x38270,0x0,0x0,0x27100,0x38270}, + {"PRC Envelope #8",0x1,0x1d4c0,0x4b708,0x0,0x0,0x1d4c0,0x4b708}, + {"PRC Envelope #9",0x1,0x37e88,0x4f1a0,0x0,0x0,0x37e88,0x4f1a0}, + {"PRC Envelope #10",0x1,0x4f1a0,0x6fd10,0x0,0x0,0x4f1a0,0x6fd10}, + {"PRC 16K Rotated",0x1,0x3f7a0,0x2de60,0x0,0x0,0x3f7a0,0x2de60}, + {"PRC 32K Rotated",0x1,0x2cec0,0x1fbd0,0x0,0x0,0x2cec0,0x1fbd0}, + {"PRC 32K(Big) Rotated",0x1,0x318f8,0x222e0,0x0,0x0,0x318f8,0x222e0}, + {"PRC Envelope #1 Rotated",0x1,0x28488,0x18e70,0x0,0x0,0x28488,0x18e70}, + {"PRC Envelope #2 Rotated",0x1,0x2af80,0x18e70,0x0,0x0,0x2af80,0x18e70}, + {"PRC Envelope #3 Rotated",0x1,0x2af80,0x1e848,0x0,0x0,0x2af80,0x1e848}, + {"PRC Envelope #4 Rotated",0x1,0x32c80,0x1adb0,0x0,0x0,0x32c80,0x1adb0}, + {"PRC Envelope #5 Rotated",0x1,0x35b60,0x1adb0,0x0,0x0,0x35b60,0x1adb0}, + {"PRC Envelope #6 Rotated",0x1,0x38270,0x1d4c0,0x0,0x0,0x38270,0x1d4c0}, + {"PRC Envelope #7 Rotated",0x1,0x38270,0x27100,0x0,0x0,0x38270,0x27100}, + {"PRC Envelope #8 Rotated",0x1,0x4b708,0x1d4c0,0x0,0x0,0x4b708,0x1d4c0}, + {"PRC Envelope #9 Rotated",0x1,0x4f1a0,0x37e88,0x0,0x0,0x4f1a0,0x37e88}, + {"PRC Envelope #10 Rotated",0x1,0x6fd10,0x4f1a0,0x0,0x0,0x6fd10,0x4f1a0} +}; + +struct table_node { + const char *long_archi; + const char *short_archi; + int version; +}; + +#define SPL_ARCH_WIN40 "WIN40" +#define SPL_ARCH_W32X86 "W32X86" +#define SPL_ARCH_W32MIPS "W32MIPS" +#define SPL_ARCH_W32ALPHA "W32ALPHA" +#define SPL_ARCH_W32PPC "W32PPC" +#define SPL_ARCH_IA64 "IA64" +#define SPL_ARCH_X64 "x64" + +static const struct table_node archi_table[]= { + + {"Windows 4.0", SPL_ARCH_WIN40, 0 }, + {"Windows NT x86", SPL_ARCH_W32X86, 2 }, + {"Windows NT R4000", SPL_ARCH_W32MIPS, 2 }, + {"Windows NT Alpha_AXP", SPL_ARCH_W32ALPHA, 2 }, + {"Windows NT PowerPC", SPL_ARCH_W32PPC, 2 }, + {"Windows IA64", SPL_ARCH_IA64, 3 }, + {"Windows x64", SPL_ARCH_X64, 3 }, + {NULL, "", -1 } +}; + + +/**************************************************************************** + 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); + 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 ); + strlower_m(share); + + keystr = talloc_asprintf(ctx, "%s%s", SECDESC_PREFIX, share); + key = string_term_tdb_data(keystr ? keystr : ""); + + return key; +} + +/**************************************************************************** +****************************************************************************/ + +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), safe_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 ) +{ + prs_struct ps; + SEC_DESC_BUF *sd_orig = NULL; + SEC_DESC_BUF *sd_new, *sd_store; + SEC_DESC *sec, *new_sec; + TALLOC_CTX *ctx = state; + int result, i; + uint32 sd_size; + 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 */ + + ZERO_STRUCT( ps ); + + prs_init_empty( &ps, ctx, UNMARSHALL ); + prs_give_memory( &ps, (char *)data.dptr, data.dsize, False ); + + if ( !sec_io_desc_buf( "sec_desc_upg_fn", &sd_orig, &ps, 1 ) ) { + /* 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 ); + prs_mem_free( &ps ); + return 0; + } + + if (!sd_orig) { + prs_mem_free( &ps ); + return 0; + } + sec = sd_orig->sd; + + /* is this even valid? */ + + if ( !sec->dacl ) { + prs_mem_free( &ps ); + 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; + + default: /* no change */ + break; + } + } + + /* create a new SEC_DESC with the appropriate owner and group SIDs */ + + new_sec = make_sec_desc( ctx, SEC_DESC_REVISION, SEC_DESC_SELF_RELATIVE, + &global_sid_Builtin_Administrators, + &global_sid_Builtin_Administrators, + NULL, NULL, &size_new_sec ); + if (!new_sec) { + prs_mem_free( &ps ); + return 0; + } + sd_new = make_sec_desc_buf( ctx, size_new_sec, new_sec ); + if (!sd_new) { + prs_mem_free( &ps ); + return 0; + } + + if ( !(sd_store = sec_desc_merge( ctx, sd_new, sd_orig )) ) { + DEBUG(0,("sec_desc_upg_fn: Failed to update sec_desc for %s\n", key.dptr )); + prs_mem_free( &ps ); + return 0; + } + + prs_mem_free( &ps ); + + /* store it back */ + + sd_size = ndr_size_security_descriptor(sd_store->sd, 0) + + sizeof(SEC_DESC_BUF); + if ( !prs_init(&ps, sd_size, ctx, MARSHALL) ) { + DEBUG(0,("sec_desc_upg_fn: Failed to allocate prs memory for %s\n", key.dptr )); + return 0; + } + + if ( !sec_io_desc_buf( "sec_desc_upg_fn", &sd_store, &ps, 1 ) ) { + DEBUG(0,("sec_desc_upg_fn: Failed to parse new sec_desc for %s\n", key.dptr )); + prs_mem_free( &ps ); + return 0; + } + + data.dptr = (uint8 *)prs_data_p( &ps ); + data.dsize = sd_size; + + result = tdb_store( tdb_printers, key, data, TDB_REPLACE ); + + prs_mem_free( &ps ); + + /* 0 to continue and non-zero to stop traversal */ + + return (result == -1); +} + +/******************************************************************* +*******************************************************************/ + +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 != -1 ); +} + +/******************************************************************* + 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; +} + +/******************************************************************* +*******************************************************************/ + +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 != -1 ); +} + +/**************************************************************************** + Open the NT printing tdbs. Done once before fork(). +****************************************************************************/ + +bool nt_printing_init(struct messaging_context *msg_ctx) +{ + const char *vstring = "INFO/version"; + WERROR win_rc; + int32 vers_id; + + if ( tdb_drivers && tdb_printers && tdb_forms ) + return True; + + if (tdb_drivers) + tdb_close(tdb_drivers); + tdb_drivers = tdb_open_log(state_path("ntdrivers.tdb"), 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + if (!tdb_drivers) { + DEBUG(0,("nt_printing_init: Failed to open nt drivers database %s (%s)\n", + state_path("ntdrivers.tdb"), strerror(errno) )); + return False; + } + + if (tdb_printers) + tdb_close(tdb_printers); + tdb_printers = tdb_open_log(state_path("ntprinters.tdb"), 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + if (!tdb_printers) { + DEBUG(0,("nt_printing_init: Failed to open nt printers database %s (%s)\n", + state_path("ntprinters.tdb"), strerror(errno) )); + return False; + } + + if (tdb_forms) + tdb_close(tdb_forms); + tdb_forms = tdb_open_log(state_path("ntforms.tdb"), 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + if (!tdb_forms) { + DEBUG(0,("nt_printing_init: Failed to open nt forms database %s (%s)\n", + state_path("ntforms.tdb"), strerror(errno) )); + return False; + } + + /* handle a 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()) + return False; + 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() ) + return False; + 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() ) + return False; + 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)); + return False; + } + } + + update_c_setprinter(True); + + /* + * register callback to handle updating printers as new + * drivers are installed + */ + + messaging_register(msg_ctx, NULL, MSG_PRINTER_DRVUPGRADE, + do_drv_upgrade_printer); + + /* + * register callback to handle updating printer data + * when a driver is initialized + */ + + messaging_register(msg_ctx, NULL, MSG_PRINTERDATA_INIT_RESET, + reset_all_printerdata); + + /* of course, none of the message callbacks matter if you don't + tell messages.c that you interested in receiving PRINT_GENERAL + msgs. This is done in claim_connection() */ + + + if ( lp_security() == SEC_ADS ) { + win_rc = check_published_printers(); + if (!W_ERROR_IS_OK(win_rc)) + DEBUG(0, ("nt_printing_init: error checking published printers: %s\n", dos_errstr(win_rc))); + } + + return True; +} + +/******************************************************************* + Function to allow filename parsing "the old way". +********************************************************************/ + +static char *driver_unix_convert(connection_struct *conn, + const char *old_name, + SMB_STRUCT_STAT *pst) +{ + TALLOC_CTX *ctx = talloc_tos(); + char *name = talloc_strdup(ctx, old_name); + char *new_name = NULL; + + if (!name) { + return NULL; + } + unix_format(name); + name = unix_clean_name(ctx, name); + if (!name) { + return NULL; + } + trim_string(name,"/","/"); + unix_convert(ctx,conn, name, false, &new_name, NULL, pst); + return new_name; +} + +/******************************************************************* + tdb traversal function for counting printers. +********************************************************************/ + +static int traverse_counting_printers(TDB_CONTEXT *t, TDB_DATA key, + TDB_DATA data, void *context) +{ + int *printer_count = (int*)context; + + if (memcmp(PRINTERS_PREFIX, key.dptr, sizeof(PRINTERS_PREFIX)-1) == 0) { + (*printer_count)++; + DEBUG(10,("traverse_counting_printers: printer = [%s] printer_count = %d\n", key.dptr, *printer_count)); + } + + return 0; +} + +/******************************************************************* + Update the spooler global c_setprinter. This variable is initialized + when the parent smbd starts with the number of existing printers. It + is monotonically increased by the current number of printers *after* + each add or delete printer RPC. Only Microsoft knows why... JRR020119 +********************************************************************/ + +uint32 update_c_setprinter(bool initialize) +{ + int32 c_setprinter; + int32 printer_count = 0; + + tdb_lock_bystring(tdb_printers, GLOBAL_C_SETPRINTER); + + /* Traverse the tdb, counting the printers */ + tdb_traverse(tdb_printers, traverse_counting_printers, (void *)&printer_count); + + /* If initializing, set c_setprinter to current printers count + * otherwise, bump it by the current printer count + */ + if (!initialize) + c_setprinter = tdb_fetch_int32(tdb_printers, GLOBAL_C_SETPRINTER) + printer_count; + else + c_setprinter = printer_count; + + DEBUG(10,("update_c_setprinter: c_setprinter = %u\n", (unsigned int)c_setprinter)); + tdb_store_int32(tdb_printers, GLOBAL_C_SETPRINTER, c_setprinter); + + tdb_unlock_bystring(tdb_printers, GLOBAL_C_SETPRINTER); + + return (uint32)c_setprinter; +} + +/******************************************************************* + Get the spooler global c_setprinter, accounting for initialization. +********************************************************************/ + +uint32 get_c_setprinter(void) +{ + int32 c_setprinter = tdb_fetch_int32(tdb_printers, GLOBAL_C_SETPRINTER); + + if (c_setprinter == (int32)-1) + c_setprinter = update_c_setprinter(True); + + DEBUG(10,("get_c_setprinter: c_setprinter = %d\n", c_setprinter)); + + return (uint32)c_setprinter; +} + +/**************************************************************************** + Get builtin form struct list. +****************************************************************************/ + +int get_builtin_ntforms(nt_forms_struct **list) +{ + *list = (nt_forms_struct *)memdup(&default_forms[0], sizeof(default_forms)); + if (!*list) { + return 0; + } + return sizeof(default_forms) / sizeof(default_forms[0]); +} + +/**************************************************************************** + get a builtin form struct +****************************************************************************/ + +bool get_a_builtin_ntform(UNISTR2 *uni_formname,nt_forms_struct *form) +{ + int i,count; + fstring form_name; + unistr2_to_ascii(form_name, uni_formname, sizeof(form_name)); + DEBUGADD(6,("Looking for builtin form %s \n", form_name)); + count = sizeof(default_forms) / sizeof(default_forms[0]); + for (i=0;i<count;i++) { + if (strequal(form_name,default_forms[i].name)) { + DEBUGADD(6,("Found builtin form %s \n", form_name)); + memcpy(form,&default_forms[i],sizeof(*form)); + break; + } + } + + return (i !=count); +} + +/**************************************************************************** + get a form struct list. +****************************************************************************/ + +int get_ntforms(nt_forms_struct **list) +{ + TDB_DATA kbuf, newkey, dbuf; + nt_forms_struct form; + int ret; + int i; + int n = 0; + + *list = NULL; + + for (kbuf = tdb_firstkey(tdb_forms); + kbuf.dptr; + newkey = tdb_nextkey(tdb_forms, kbuf), safe_free(kbuf.dptr), kbuf=newkey) + { + if (strncmp((const char *)kbuf.dptr, FORMS_PREFIX, strlen(FORMS_PREFIX)) != 0) + continue; + + dbuf = tdb_fetch(tdb_forms, kbuf); + if (!dbuf.dptr) + continue; + + fstrcpy(form.name, (const char *)kbuf.dptr+strlen(FORMS_PREFIX)); + ret = tdb_unpack(dbuf.dptr, dbuf.dsize, "dddddddd", + &i, &form.flag, &form.width, &form.length, &form.left, + &form.top, &form.right, &form.bottom); + SAFE_FREE(dbuf.dptr); + if (ret != dbuf.dsize) + continue; + + *list = SMB_REALLOC_ARRAY(*list, nt_forms_struct, n+1); + if (!*list) { + DEBUG(0,("get_ntforms: Realloc fail.\n")); + return 0; + } + (*list)[n] = form; + n++; + } + + + return n; +} + +/**************************************************************************** +write a form struct list +****************************************************************************/ + +int write_ntforms(nt_forms_struct **list, int number) +{ + TALLOC_CTX *ctx = talloc_tos(); + char *buf = NULL; + char *key = NULL; + int len; + TDB_DATA dbuf; + int i; + + for (i=0;i<number;i++) { + /* save index, so list is rebuilt in correct order */ + len = tdb_pack(NULL, 0, "dddddddd", + i, (*list)[i].flag, (*list)[i].width, (*list)[i].length, + (*list)[i].left, (*list)[i].top, (*list)[i].right, + (*list)[i].bottom); + if (!len) { + continue; + } + buf = TALLOC_ARRAY(ctx, char, len); + if (!buf) { + return 0; + } + len = tdb_pack((uint8 *)buf, len, "dddddddd", + i, (*list)[i].flag, (*list)[i].width, (*list)[i].length, + (*list)[i].left, (*list)[i].top, (*list)[i].right, + (*list)[i].bottom); + key = talloc_asprintf(ctx, "%s%s", FORMS_PREFIX, (*list)[i].name); + if (!key) { + return 0; + } + dbuf.dsize = len; + dbuf.dptr = (uint8 *)buf; + if (tdb_store_bystring(tdb_forms, key, dbuf, TDB_REPLACE) != 0) { + TALLOC_FREE(key); + TALLOC_FREE(buf); + break; + } + TALLOC_FREE(key); + TALLOC_FREE(buf); + } + + return i; +} + +/**************************************************************************** +add a form struct at the end of the list +****************************************************************************/ +bool add_a_form(nt_forms_struct **list, const FORM *form, int *count) +{ + int n=0; + bool update; + fstring form_name; + + /* + * NT tries to add forms even when + * they are already in the base + * only update the values if already present + */ + + update=False; + + unistr2_to_ascii(form_name, &form->name, sizeof(form_name)); + for (n=0; n<*count; n++) { + if ( strequal((*list)[n].name, form_name) ) { + update=True; + break; + } + } + + if (update==False) { + if((*list=SMB_REALLOC_ARRAY(*list, nt_forms_struct, n+1)) == NULL) { + DEBUG(0,("add_a_form: failed to enlarge forms list!\n")); + return False; + } + unistr2_to_ascii((*list)[n].name, &form->name, sizeof((*list)[n].name)); + (*count)++; + } + + (*list)[n].flag=form->flags; + (*list)[n].width=form->size_x; + (*list)[n].length=form->size_y; + (*list)[n].left=form->left; + (*list)[n].top=form->top; + (*list)[n].right=form->right; + (*list)[n].bottom=form->bottom; + + DEBUG(6,("add_a_form: Successfully %s form [%s]\n", + update ? "updated" : "added", form_name)); + + return True; +} + +/**************************************************************************** + Delete a named form struct. +****************************************************************************/ + +bool delete_a_form(nt_forms_struct **list, UNISTR2 *del_name, int *count, WERROR *ret) +{ + char *key = NULL; + int n=0; + fstring form_name; + + *ret = WERR_OK; + + unistr2_to_ascii(form_name, del_name, sizeof(form_name)); + + for (n=0; n<*count; n++) { + if (!strncmp((*list)[n].name, form_name, strlen(form_name))) { + DEBUG(103, ("delete_a_form, [%s] in list\n", form_name)); + break; + } + } + + if (n == *count) { + DEBUG(10,("delete_a_form, [%s] not found\n", form_name)); + *ret = WERR_INVALID_PARAM; + return False; + } + + if (asprintf(&key, "%s%s", FORMS_PREFIX, (*list)[n].name) < 0) { + *ret = WERR_NOMEM; + return false; + } + if (tdb_delete_bystring(tdb_forms, key) != 0) { + SAFE_FREE(key); + *ret = WERR_NOMEM; + return False; + } + SAFE_FREE(key); + return true; +} + +/**************************************************************************** + Update a form struct. +****************************************************************************/ + +void update_a_form(nt_forms_struct **list, const FORM *form, int count) +{ + int n=0; + fstring form_name; + unistr2_to_ascii(form_name, &(form->name), sizeof(form_name)); + + DEBUG(106, ("[%s]\n", form_name)); + for (n=0; n<count; n++) { + DEBUGADD(106, ("n [%d]:[%s]\n", n, (*list)[n].name)); + if (!strncmp((*list)[n].name, form_name, strlen(form_name))) + break; + } + + if (n==count) return; + + (*list)[n].flag=form->flags; + (*list)[n].width=form->size_x; + (*list)[n].length=form->size_y; + (*list)[n].left=form->left; + (*list)[n].top=form->top; + (*list)[n].right=form->right; + (*list)[n].bottom=form->bottom; +} + +/**************************************************************************** + Get the nt drivers list. + Traverse the database and look-up the matching names. +****************************************************************************/ +int get_ntdrivers(fstring **list, const char *architecture, uint32 version) +{ + int total=0; + const char *short_archi; + char *key = NULL; + TDB_DATA kbuf, newkey; + + short_archi = get_short_archi(architecture); + if (!short_archi) { + return 0; + } + + if (asprintf(&key, "%s%s/%d/", DRIVERS_PREFIX, + short_archi, version) < 0) { + return 0; + } + + for (kbuf = tdb_firstkey(tdb_drivers); + kbuf.dptr; + newkey = tdb_nextkey(tdb_drivers, kbuf), safe_free(kbuf.dptr), kbuf=newkey) { + + if (strncmp((const char *)kbuf.dptr, key, strlen(key)) != 0) + continue; + + if((*list = SMB_REALLOC_ARRAY(*list, fstring, total+1)) == NULL) { + DEBUG(0,("get_ntdrivers: failed to enlarge list!\n")); + SAFE_FREE(key); + return -1; + } + + fstrcpy((*list)[total], (const char *)kbuf.dptr+strlen(key)); + total++; + } + + SAFE_FREE(key); + return(total); +} + +/**************************************************************************** + 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 dependant directory\n")); + do { + i++; + } while ( (archi_table[i].long_archi!=NULL ) && + StrCaseCmp(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; +} + +/**************************************************************************** + 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 *major, uint32 *minor) +{ + int i; + char *buf = NULL; + ssize_t byte_count; + + if ((buf=(char *)SMB_MALLOC(DOS_HEADER_SIZE)) == NULL) { + DEBUG(0,("get_file_version: PE file [%s] DOS Header malloc failed bytes = %d\n", + fname, DOS_HEADER_SIZE)); + goto error_exit; + } + + if ((byte_count = vfs_read_data(fsp, buf, DOS_HEADER_SIZE)) < DOS_HEADER_SIZE) { + DEBUG(3,("get_file_version: 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) { + DEBUG(6,("get_file_version: 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 */ + if (SMB_VFS_LSEEK(fsp, SVAL(buf,DOS_HEADER_LFANEW_OFFSET), SEEK_SET) == (SMB_OFF_T)-1) { + DEBUG(3,("get_file_version: File [%s] too short, errno = %d\n", + fname, errno)); + /* Assume this isn't an error... the file just looks sort of like a PE/NE file */ + goto no_version_info; + } + + /* Note: DOS_HEADER_SIZE and NE_HEADER_SIZE are incidentally same */ + if ((byte_count = vfs_read_data(fsp, buf, NE_HEADER_SIZE)) < NE_HEADER_SIZE) { + DEBUG(3,("get_file_version: 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) { + unsigned int num_sections; + unsigned int section_table_bytes; + + /* Just skip over optional header to get to section table */ + if (SMB_VFS_LSEEK(fsp, + SVAL(buf,PE_HEADER_OPTIONAL_HEADER_SIZE)-(NE_HEADER_SIZE-PE_HEADER_SIZE), + SEEK_CUR) == (SMB_OFF_T)-1) { + DEBUG(3,("get_file_version: File [%s] Windows optional header too short, errno = %d\n", + fname, errno)); + goto error_exit; + } + + /* get the section table */ + num_sections = SVAL(buf,PE_HEADER_NUMBER_OF_SECTIONS); + section_table_bytes = num_sections * PE_HEADER_SECT_HEADER_SIZE; + if (section_table_bytes == 0) + goto error_exit; + + SAFE_FREE(buf); + if ((buf=(char *)SMB_MALLOC(section_table_bytes)) == NULL) { + DEBUG(0,("get_file_version: PE file [%s] section table malloc failed bytes = %d\n", + fname, section_table_bytes)); + goto error_exit; + } + + if ((byte_count = vfs_read_data(fsp, buf, section_table_bytes)) < section_table_bytes) { + DEBUG(3,("get_file_version: PE file [%s] Section header too short, bytes read = %lu\n", + fname, (unsigned long)byte_count)); + goto error_exit; + } + + /* 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 error_exit; + + SAFE_FREE(buf); + if ((buf=(char *)SMB_MALLOC(section_bytes)) == NULL) { + DEBUG(0,("get_file_version: PE file [%s] version malloc failed bytes = %d\n", + fname, section_bytes)); + goto error_exit; + } + + /* Seek to the start of the .rsrc section info */ + if (SMB_VFS_LSEEK(fsp, section_pos, SEEK_SET) == (SMB_OFF_T)-1) { + DEBUG(3,("get_file_version: PE file [%s] too short for section info, errno = %d\n", + fname, errno)); + goto error_exit; + } + + if ((byte_count = vfs_read_data(fsp, buf, section_bytes)) < section_bytes) { + DEBUG(3,("get_file_version: PE file [%s] .rsrc section too short, bytes read = %lu\n", + fname, (unsigned long)byte_count)); + goto error_exit; + } + + if (section_bytes < VS_VERSION_INFO_UNICODE_SIZE) + goto error_exit; + + for (i=0; i<section_bytes-VS_VERSION_INFO_UNICODE_SIZE; i++) { + /* Scan for 1st 3 unicoded bytes followed by word aligned magic value */ + if (buf[i] == 'V' && buf[i+1] == '\0' && buf[i+2] == 'S') { + /* Align to next long address */ + int pos = (i + sizeof(VS_SIGNATURE)*2 + 3) & 0xfffffffc; + + if (IVAL(buf,pos) == VS_MAGIC_VALUE) { + *major = IVAL(buf,pos+VS_MAJOR_OFFSET); + *minor = IVAL(buf,pos+VS_MINOR_OFFSET); + + DEBUG(6,("get_file_version: PE file [%s] Version = %08x:%08x (%d.%d.%d.%d)\n", + fname, *major, *minor, + (*major>>16)&0xffff, *major&0xffff, + (*minor>>16)&0xffff, *minor&0xffff)); + SAFE_FREE(buf); + return 1; + } + } + } + } + } + + /* Version info not found, fall back to origin date/time */ + DEBUG(10,("get_file_version: PE file [%s] has no version info\n", fname)); + SAFE_FREE(buf); + return 0; + + } else if (SVAL(buf,NE_HEADER_SIGNATURE_OFFSET) == NE_HEADER_SIGNATURE) { + if (CVAL(buf,NE_HEADER_TARGET_OS_OFFSET) != NE_HEADER_TARGOS_WIN ) { + DEBUG(3,("get_file_version: 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 somthing + * else besides a NE file, but it unlikely at this point. */ + goto error_exit; + } + + /* Allocate a bit more space to speed up things */ + SAFE_FREE(buf); + if ((buf=(char *)SMB_MALLOC(VS_NE_BUF_SIZE)) == NULL) { + DEBUG(0,("get_file_version: NE file [%s] malloc failed bytes = %d\n", + fname, PE_HEADER_SIZE)); + goto error_exit; + } + + /* 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 */ + while((byte_count = vfs_read_data(fsp, buf, VS_NE_BUF_SIZE)) > 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') continue; + + /* Potential match data crosses buf boundry, move it to beginning + * of buf, and fill the buf with as much as it will hold. */ + if (i>byte_count-VS_VERSION_INFO_SIZE) { + int bc; + + memcpy(buf, &buf[i], byte_count-i); + if ((bc = vfs_read_data(fsp, &buf[byte_count-i], VS_NE_BUF_SIZE- + (byte_count-i))) < 0) { + + DEBUG(0,("get_file_version: NE file [%s] Read error, errno=%d\n", + fname, errno)); + goto error_exit; + } + + byte_count = bc + (byte_count - i); + 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 */ + int skip = -(SMB_VFS_LSEEK(fsp, 0, SEEK_CUR) - (byte_count - i) + + sizeof(VS_SIGNATURE)) & 3; + if (IVAL(buf,i+sizeof(VS_SIGNATURE)+skip) != 0xfeef04bd) continue; + + *major = IVAL(buf,i+sizeof(VS_SIGNATURE)+skip+VS_MAJOR_OFFSET); + *minor = IVAL(buf,i+sizeof(VS_SIGNATURE)+skip+VS_MINOR_OFFSET); + DEBUG(6,("get_file_version: NE file [%s] Version = %08x:%08x (%d.%d.%d.%d)\n", + fname, *major, *minor, + (*major>>16)&0xffff, *major&0xffff, + (*minor>>16)&0xffff, *minor&0xffff)); + SAFE_FREE(buf); + return 1; + } + } + } + + /* Version info not found, fall back to origin date/time */ + DEBUG(0,("get_file_version: NE file [%s] Version info not found\n", fname)); + SAFE_FREE(buf); + return 0; + + } else + /* Assume this isn't an error... the file just looks sort of like a PE/NE file */ + DEBUG(3,("get_file_version: File [%s] unknown file format, signature = 0x%x\n", + fname, IVAL(buf,PE_HEADER_SIGNATURE_OFFSET))); + + 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; + char *filepath = NULL; + + uint32 new_major; + uint32 new_minor; + time_t new_create_time; + + uint32 old_major; + uint32 old_minor; + time_t old_create_time; + + files_struct *fsp = NULL; + SMB_STRUCT_STAT st; + SMB_STRUCT_STAT stat_buf; + + NTSTATUS status; + + SET_STAT_INVALID(st); + SET_STAT_INVALID(stat_buf); + new_create_time = (time_t)0; + old_create_time = (time_t)0; + + /* Get file version info (if available) for previous file (if it exists) */ + filepath = driver_unix_convert(conn,old_file,&stat_buf); + if (!filepath) { + goto error_exit; + } + + status = open_file_ntcreate(conn, NULL, filepath, &stat_buf, + FILE_GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, + FILE_OPEN, + 0, + FILE_ATTRIBUTE_NORMAL, + INTERNAL_OPEN_ONLY, + NULL, &fsp); + + 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", + filepath, errno)); + return 1; + + } else { + int 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 = st.st_mtime; + DEBUGADD(6,("file_version_is_newer: mod time = %ld sec\n", old_create_time)); + } + } + close_file(fsp, NORMAL_CLOSE); + + /* Get file version info (if available) for new file */ + filepath = driver_unix_convert(conn,new_file,&stat_buf); + if (!filepath) { + goto error_exit; + } + + status = open_file_ntcreate(conn, NULL, filepath, &stat_buf, + FILE_GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, + FILE_OPEN, + 0, + FILE_ATTRIBUTE_NORMAL, + INTERNAL_OPEN_ONLY, + NULL, &fsp); + + 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", + filepath, errno)); + goto error_exit; + + } else { + int 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 = st.st_mtime; + DEBUGADD(6,("file_version_is_newer: mod time = %ld sec\n", new_create_time)); + } + } + close_file(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)); + return 1; + } + else { + DEBUG(6,("file_version_is_newer: Leaving [%s] unchanged\n", old_file)); + return 0; + } + + } 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)); + return 1; + } + else { + DEBUG(6,("file_version_is_newer: Leaving [%s] unchanged\n", old_file)); + return 0; + } + } + + error_exit: + if(fsp) + close_file(fsp, NORMAL_CLOSE); + return -1; +} + +/**************************************************************************** +Determine the correct cVersion associated with an architecture and driver +****************************************************************************/ +static uint32 get_correct_cversion(const char *architecture, fstring driverpath_in, + struct current_user *user, WERROR *perr) +{ + int cversion; + NTSTATUS nt_status; + char *driverpath = NULL; + DATA_BLOB null_pw; + fstring res_type; + files_struct *fsp = NULL; + SMB_STRUCT_STAT st; + connection_struct *conn; + NTSTATUS status; + + SET_STAT_INVALID(st); + + *perr = WERR_INVALID_PARAM; + + /* 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; + return 0; + } + + /* If architecture is Windows x64, the version is always 3. */ + if (strcmp(architecture, SPL_ARCH_X64) == 0) { + DEBUG(10,("get_correct_cversion: Driver is x64, cversion = 3\n")); + *perr = WERR_OK; + return 3; + } + + /* + * Connect to the print$ share under the same account as the user connected + * to the rpc pipe. Note we must still be root to do this. + */ + + /* Null password is ok - we are already an authenticated user... */ + null_pw = data_blob_null; + fstrcpy(res_type, "A:"); + become_root(); + conn = make_connection_with_chdir("print$", null_pw, res_type, user->vuid, &nt_status); + unbecome_root(); + + if (conn == NULL) { + DEBUG(0,("get_correct_cversion: Unable to connect\n")); + *perr = ntstatus_to_werror(nt_status); + return -1; + } + + /* We are temporarily becoming the connection user. */ + if (!become_user(conn, user->vuid)) { + DEBUG(0,("get_correct_cversion: Can't become user!\n")); + *perr = WERR_ACCESS_DENIED; + return -1; + } + + /* Open the driver file (Portable Executable format) and determine the + * deriver the cversion. */ + driverpath = talloc_asprintf(talloc_tos(), + "%s/%s", + architecture, + driverpath_in); + if (!driverpath) { + *perr = WERR_NOMEM; + goto error_exit; + } + + driverpath = driver_unix_convert(conn,driverpath,&st); + if (!driverpath) { + *perr = WERR_NOMEM; + goto error_exit; + } + + if (!vfs_file_exist(conn, driverpath, &st)) { + *perr = WERR_BADFILE; + goto error_exit; + } + + status = open_file_ntcreate(conn, NULL, driverpath, &st, + FILE_GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, + FILE_OPEN, + 0, + FILE_ATTRIBUTE_NORMAL, + INTERNAL_OPEN_ONLY, + NULL, &fsp); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3,("get_correct_cversion: Can't open file [%s], errno = %d\n", + driverpath, errno)); + *perr = WERR_ACCESS_DENIED; + goto error_exit; + } else { + uint32 major; + uint32 minor; + int ret = get_file_version(fsp, driverpath, &major, &minor); + if (ret == -1) goto error_exit; + + if (!ret) { + DEBUG(6,("get_correct_cversion: Version info not found [%s]\n", driverpath)); + goto error_exit; + } + + /* + * This is a Microsoft'ism. See references in MSDN to VER_FILEVERSION + * for more details. Version in this case is not just the version of the + * file, but the version in the sense of kernal mode (2) vs. user mode + * (3) drivers. Other bits of the version fields are the version info. + * JRR 010716 + */ + cversion = major & 0x0000ffff; + switch (cversion) { + case 2: /* WinNT drivers */ + case 3: /* Win2K drivers */ + break; + + default: + DEBUG(6,("get_correct_cversion: cversion invalid [%s] cversion = %d\n", + driverpath, cversion)); + goto error_exit; + } + + DEBUG(10,("get_correct_cversion: Version info found [%s] major = 0x%x minor = 0x%x\n", + driverpath, major, minor)); + } + + DEBUG(10,("get_correct_cversion: Driver file [%s] cversion = %d\n", + driverpath, cversion)); + + close_file(fsp, NORMAL_CLOSE); + close_cnum(conn, user->vuid); + unbecome_user(); + *perr = WERR_OK; + return cversion; + + + error_exit: + + if(fsp) + close_file(fsp, NORMAL_CLOSE); + + close_cnum(conn, user->vuid); + unbecome_user(); + return -1; +} + +/**************************************************************************** +****************************************************************************/ +static WERROR clean_up_driver_struct_level_3(NT_PRINTER_DRIVER_INFO_LEVEL_3 *driver, + struct current_user *user) +{ + const char *architecture; + fstring new_name; + char *p; + int i; + WERROR err; + + /* clean up the driver name. + * we can get .\driver.dll + * or worse c:\windows\system\driver.dll ! + */ + /* using an intermediate string to not have overlaping memcpy()'s */ + if ((p = strrchr(driver->driverpath,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->driverpath, new_name); + } + + if ((p = strrchr(driver->datafile,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->datafile, new_name); + } + + if ((p = strrchr(driver->configfile,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->configfile, new_name); + } + + if ((p = strrchr(driver->helpfile,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->helpfile, new_name); + } + + if (driver->dependentfiles) { + for (i=0; *driver->dependentfiles[i]; i++) { + if ((p = strrchr(driver->dependentfiles[i],'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->dependentfiles[i], new_name); + } + } + } + + architecture = get_short_archi(driver->environment); + if (!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 + */ + if ((driver->cversion = get_correct_cversion( architecture, driver->driverpath, user, &err)) == -1) + return err; + + return WERR_OK; +} + +/**************************************************************************** +****************************************************************************/ +static WERROR clean_up_driver_struct_level_6(NT_PRINTER_DRIVER_INFO_LEVEL_6 *driver, struct current_user *user) +{ + const char *architecture; + fstring new_name; + char *p; + int i; + WERROR err; + + /* clean up the driver name. + * we can get .\driver.dll + * or worse c:\windows\system\driver.dll ! + */ + /* using an intermediate string to not have overlaping memcpy()'s */ + if ((p = strrchr(driver->driverpath,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->driverpath, new_name); + } + + if ((p = strrchr(driver->datafile,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->datafile, new_name); + } + + if ((p = strrchr(driver->configfile,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->configfile, new_name); + } + + if ((p = strrchr(driver->helpfile,'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->helpfile, new_name); + } + + if (driver->dependentfiles) { + for (i=0; *driver->dependentfiles[i]; i++) { + if ((p = strrchr(driver->dependentfiles[i],'\\')) != NULL) { + fstrcpy(new_name, p+1); + fstrcpy(driver->dependentfiles[i], new_name); + } + } + } + + architecture = get_short_archi(driver->environment); + if (!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 + */ + + if ((driver->version = get_correct_cversion(architecture, driver->driverpath, user, &err)) == -1) + return err; + + return WERR_OK; +} + +/**************************************************************************** +****************************************************************************/ +WERROR clean_up_driver_struct(NT_PRINTER_DRIVER_INFO_LEVEL driver_abstract, + uint32 level, struct current_user *user) +{ + switch (level) { + case 3: + { + NT_PRINTER_DRIVER_INFO_LEVEL_3 *driver; + driver=driver_abstract.info_3; + return clean_up_driver_struct_level_3(driver, user); + } + case 6: + { + NT_PRINTER_DRIVER_INFO_LEVEL_6 *driver; + driver=driver_abstract.info_6; + return clean_up_driver_struct_level_6(driver, user); + } + default: + return WERR_INVALID_PARAM; + } +} + +/**************************************************************************** + This function sucks and should be replaced. JRA. +****************************************************************************/ + +static void convert_level_6_to_level3(NT_PRINTER_DRIVER_INFO_LEVEL_3 *dst, NT_PRINTER_DRIVER_INFO_LEVEL_6 *src) +{ + dst->cversion = src->version; + + fstrcpy( dst->name, src->name); + fstrcpy( dst->environment, src->environment); + fstrcpy( dst->driverpath, src->driverpath); + fstrcpy( dst->datafile, src->datafile); + fstrcpy( dst->configfile, src->configfile); + fstrcpy( dst->helpfile, src->helpfile); + fstrcpy( dst->monitorname, src->monitorname); + fstrcpy( dst->defaultdatatype, src->defaultdatatype); + dst->dependentfiles = src->dependentfiles; +} + +#if 0 /* Debugging function */ + +static char* ffmt(unsigned char *c){ + int i; + static char ffmt_str[17]; + + for (i=0; i<16; i++) { + if ((c[i] < ' ') || (c[i] > '~')) + ffmt_str[i]='.'; + else + ffmt_str[i]=c[i]; + } + ffmt_str[16]='\0'; + return ffmt_str; +} + +#endif + +/**************************************************************************** +****************************************************************************/ +WERROR move_driver_to_download_area(NT_PRINTER_DRIVER_INFO_LEVEL driver_abstract, uint32 level, + struct current_user *user, WERROR *perr) +{ + NT_PRINTER_DRIVER_INFO_LEVEL_3 *driver; + NT_PRINTER_DRIVER_INFO_LEVEL_3 converted_driver; + const char *architecture; + char *new_dir = NULL; + char *old_name = NULL; + char *new_name = NULL; + DATA_BLOB null_pw; + connection_struct *conn; + NTSTATUS nt_status; + fstring res_type; + SMB_STRUCT_STAT st; + int i; + TALLOC_CTX *ctx = talloc_tos(); + int ver = 0; + + *perr = WERR_OK; + + if (level==3) + driver=driver_abstract.info_3; + else if (level==6) { + convert_level_6_to_level3(&converted_driver, driver_abstract.info_6); + driver = &converted_driver; + } else { + DEBUG(0,("move_driver_to_download_area: Unknown info level (%u)\n", (unsigned int)level )); + return WERR_UNKNOWN_LEVEL; + } + + architecture = get_short_archi(driver->environment); + if (!architecture) { + return WERR_UNKNOWN_PRINTER_DRIVER; + } + + /* + * Connect to the print$ share under the same account as the user connected to the rpc pipe. + * Note we must be root to do this. + */ + + null_pw = data_blob_null; + fstrcpy(res_type, "A:"); + become_root(); + conn = make_connection_with_chdir("print$", null_pw, res_type, user->vuid, &nt_status); + unbecome_root(); + + if (conn == NULL) { + DEBUG(0,("move_driver_to_download_area: Unable to connect\n")); + *perr = ntstatus_to_werror(nt_status); + return WERR_NO_SUCH_SHARE; + } + + /* + * Save who we are - we are temporarily becoming the connection user. + */ + + if (!become_user(conn, conn->vuid)) { + DEBUG(0,("move_driver_to_download_area: Can't become user!\n")); + return WERR_ACCESS_DENIED; + } + + /* WE ARE NOW RUNNING AS USER conn->vuid !!!!! */ + + /* + * make the directories version and version\driver_name + * under the architecture directory. + */ + DEBUG(5,("Creating first directory\n")); + new_dir = talloc_asprintf(ctx, + "%s/%d", + architecture, + driver->cversion); + if (!new_dir) { + *perr = WERR_NOMEM; + goto err_exit; + } + new_dir = driver_unix_convert(conn,new_dir,&st); + if (!new_dir) { + *perr = WERR_NOMEM; + goto err_exit; + } + + create_directory(conn, NULL, new_dir); + + /* 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\cversion\, 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\cversion\filexxx.yyy. + * Otherwise, delete the file. + * + * If a file is not moved to archi\cversion\ 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\cversion\, 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->driverpath && strlen(driver->driverpath)) { + new_name = talloc_asprintf(ctx, + "%s/%s", + architecture, + driver->driverpath); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + old_name = talloc_asprintf(ctx, + "%s/%s", + new_dir, + driver->driverpath); + if (!old_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + + if (ver != -1 && (ver=file_version_is_newer(conn, new_name, old_name)) > 0) { + new_name = driver_unix_convert(conn,new_name,&st); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if ( !NT_STATUS_IS_OK(copy_file(ctx,conn, new_name, old_name, OPENX_FILE_EXISTS_TRUNCATE| + OPENX_FILE_CREATE_IF_NOT_EXIST, 0, False))) { + DEBUG(0,("move_driver_to_download_area: Unable to rename [%s] to [%s]\n", + new_name, old_name)); + *perr = WERR_ACCESS_DENIED; + ver = -1; + } + } + } + + if (driver->datafile && strlen(driver->datafile)) { + if (!strequal(driver->datafile, driver->driverpath)) { + new_name = talloc_asprintf(ctx, + "%s/%s", + architecture, + driver->datafile); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + old_name = talloc_asprintf(ctx, + "%s/%s", + new_dir, + driver->datafile); + if (!old_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if (ver != -1 && (ver=file_version_is_newer(conn, new_name, old_name)) > 0) { + new_name = driver_unix_convert(conn,new_name,&st); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if ( !NT_STATUS_IS_OK(copy_file(ctx,conn, new_name, old_name, OPENX_FILE_EXISTS_TRUNCATE| + OPENX_FILE_CREATE_IF_NOT_EXIST, 0, False))) { + DEBUG(0,("move_driver_to_download_area: Unable to rename [%s] to [%s]\n", + new_name, old_name)); + *perr = WERR_ACCESS_DENIED; + ver = -1; + } + } + } + } + + if (driver->configfile && strlen(driver->configfile)) { + if (!strequal(driver->configfile, driver->driverpath) && + !strequal(driver->configfile, driver->datafile)) { + new_name = talloc_asprintf(ctx, + "%s/%s", + architecture, + driver->configfile); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + old_name = talloc_asprintf(ctx, + "%s/%s", + new_dir, + driver->configfile); + if (!old_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if (ver != -1 && (ver=file_version_is_newer(conn, new_name, old_name)) > 0) { + new_name = driver_unix_convert(conn,new_name,&st); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if ( !NT_STATUS_IS_OK(copy_file(ctx,conn, new_name, old_name, OPENX_FILE_EXISTS_TRUNCATE| + OPENX_FILE_CREATE_IF_NOT_EXIST, 0, False))) { + DEBUG(0,("move_driver_to_download_area: Unable to rename [%s] to [%s]\n", + new_name, old_name)); + *perr = WERR_ACCESS_DENIED; + ver = -1; + } + } + } + } + + if (driver->helpfile && strlen(driver->helpfile)) { + if (!strequal(driver->helpfile, driver->driverpath) && + !strequal(driver->helpfile, driver->datafile) && + !strequal(driver->helpfile, driver->configfile)) { + new_name = talloc_asprintf(ctx, + "%s/%s", + architecture, + driver->helpfile); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + old_name = talloc_asprintf(ctx, + "%s/%s", + new_dir, + driver->helpfile); + if (!old_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if (ver != -1 && (ver=file_version_is_newer(conn, new_name, old_name)) > 0) { + new_name = driver_unix_convert(conn,new_name,&st); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if ( !NT_STATUS_IS_OK(copy_file(ctx,conn, new_name, old_name, OPENX_FILE_EXISTS_TRUNCATE| + OPENX_FILE_CREATE_IF_NOT_EXIST, 0, False))) { + DEBUG(0,("move_driver_to_download_area: Unable to rename [%s] to [%s]\n", + new_name, old_name)); + *perr = WERR_ACCESS_DENIED; + ver = -1; + } + } + } + } + + if (driver->dependentfiles) { + for (i=0; *driver->dependentfiles[i]; i++) { + if (!strequal(driver->dependentfiles[i], driver->driverpath) && + !strequal(driver->dependentfiles[i], driver->datafile) && + !strequal(driver->dependentfiles[i], driver->configfile) && + !strequal(driver->dependentfiles[i], driver->helpfile)) { + int j; + for (j=0; j < i; j++) { + if (strequal(driver->dependentfiles[i], driver->dependentfiles[j])) { + goto NextDriver; + } + } + + new_name = talloc_asprintf(ctx, + "%s/%s", + architecture, + driver->dependentfiles[i]); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + old_name = talloc_asprintf(ctx, + "%s/%s", + new_dir, + driver->dependentfiles[i]); + if (!old_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if (ver != -1 && (ver=file_version_is_newer(conn, new_name, old_name)) > 0) { + new_name = driver_unix_convert(conn,new_name,&st); + if (!new_name) { + *perr = WERR_NOMEM; + goto err_exit; + } + if ( !NT_STATUS_IS_OK(copy_file(ctx,conn, new_name, old_name, + OPENX_FILE_EXISTS_TRUNCATE| + OPENX_FILE_CREATE_IF_NOT_EXIST, 0, False))) { + DEBUG(0,("move_driver_to_download_area: Unable to rename [%s] to [%s]\n", + new_name, old_name)); + *perr = WERR_ACCESS_DENIED; + ver = -1; + } + } + } + NextDriver: ; + } + } + + err_exit: + + close_cnum(conn, user->vuid); + unbecome_user(); + + if (W_ERROR_EQUAL(*perr, WERR_OK)) { + return WERR_OK; + } + if (ver == -1) { + return WERR_UNKNOWN_PRINTER_DRIVER; + } + return (*perr); +} + +/**************************************************************************** +****************************************************************************/ + +static uint32 add_a_printer_driver_3(NT_PRINTER_DRIVER_INFO_LEVEL_3 *driver) +{ + TALLOC_CTX *ctx = talloc_tos(); + int len, buflen; + const char *architecture; + char *directory = NULL; + fstring temp_name; + char *key = NULL; + uint8 *buf; + int i, ret; + TDB_DATA dbuf; + + architecture = get_short_archi(driver->environment); + if (!architecture) { + return (uint32)-1; + } + + /* The names are relative. We store them in the form: \print$\arch\version\driver.xxx + * \\server is added in the rpc server layer. + * It does make sense to NOT store the server's name in the printer TDB. + */ + + directory = talloc_asprintf(ctx, "\\print$\\%s\\%d\\", + architecture, driver->cversion); + if (!directory) { + return (uint32)-1; + } + + /* .inf files do not always list a file for each of the four standard files. + * Don't prepend a path to a null filename, or client claims: + * "The server on which the printer resides does not have a suitable + * <printer driver name> printer driver installed. Click OK if you + * wish to install the driver on your local machine." + */ + if (strlen(driver->driverpath)) { + fstrcpy(temp_name, driver->driverpath); + slprintf(driver->driverpath, sizeof(driver->driverpath)-1, "%s%s", directory, temp_name); + } + + if (strlen(driver->datafile)) { + fstrcpy(temp_name, driver->datafile); + slprintf(driver->datafile, sizeof(driver->datafile)-1, "%s%s", directory, temp_name); + } + + if (strlen(driver->configfile)) { + fstrcpy(temp_name, driver->configfile); + slprintf(driver->configfile, sizeof(driver->configfile)-1, "%s%s", directory, temp_name); + } + + if (strlen(driver->helpfile)) { + fstrcpy(temp_name, driver->helpfile); + slprintf(driver->helpfile, sizeof(driver->helpfile)-1, "%s%s", directory, temp_name); + } + + if (driver->dependentfiles) { + for (i=0; *driver->dependentfiles[i]; i++) { + fstrcpy(temp_name, driver->dependentfiles[i]); + slprintf(driver->dependentfiles[i], sizeof(driver->dependentfiles[i])-1, "%s%s", directory, temp_name); + } + } + + key = talloc_asprintf(ctx, "%s%s/%d/%s", DRIVERS_PREFIX, + architecture, driver->cversion, driver->name); + if (!key) { + return (uint32)-1; + } + + DEBUG(5,("add_a_printer_driver_3: Adding driver with key %s\n", key )); + + buf = NULL; + len = buflen = 0; + + again: + len = 0; + len += tdb_pack(buf+len, buflen-len, "dffffffff", + driver->cversion, + driver->name, + driver->environment, + driver->driverpath, + driver->datafile, + driver->configfile, + driver->helpfile, + driver->monitorname, + driver->defaultdatatype); + + if (driver->dependentfiles) { + for (i=0; *driver->dependentfiles[i]; i++) { + len += tdb_pack(buf+len, buflen-len, "f", + driver->dependentfiles[i]); + } + } + + if (len != buflen) { + buf = (uint8 *)SMB_REALLOC(buf, len); + if (!buf) { + DEBUG(0,("add_a_printer_driver_3: failed to enlarge buffer\n!")); + ret = -1; + goto done; + } + buflen = len; + goto again; + } + + dbuf.dptr = buf; + dbuf.dsize = len; + + ret = tdb_store_bystring(tdb_drivers, key, dbuf, TDB_REPLACE); + +done: + if (ret) + DEBUG(0,("add_a_printer_driver_3: Adding driver with key %s failed.\n", key )); + + SAFE_FREE(buf); + return ret; +} + +/**************************************************************************** +****************************************************************************/ +static uint32 add_a_printer_driver_6(NT_PRINTER_DRIVER_INFO_LEVEL_6 *driver) +{ + NT_PRINTER_DRIVER_INFO_LEVEL_3 info3; + + ZERO_STRUCT(info3); + info3.cversion = driver->version; + fstrcpy(info3.name,driver->name); + fstrcpy(info3.environment,driver->environment); + fstrcpy(info3.driverpath,driver->driverpath); + fstrcpy(info3.datafile,driver->datafile); + fstrcpy(info3.configfile,driver->configfile); + fstrcpy(info3.helpfile,driver->helpfile); + fstrcpy(info3.monitorname,driver->monitorname); + fstrcpy(info3.defaultdatatype,driver->defaultdatatype); + info3.dependentfiles = driver->dependentfiles; + + return add_a_printer_driver_3(&info3); +} + + +/**************************************************************************** +****************************************************************************/ +static WERROR get_a_printer_driver_3_default(NT_PRINTER_DRIVER_INFO_LEVEL_3 **info_ptr, const char *driver, const char *arch) +{ + NT_PRINTER_DRIVER_INFO_LEVEL_3 info; + + ZERO_STRUCT(info); + + fstrcpy(info.name, driver); + fstrcpy(info.defaultdatatype, "RAW"); + + fstrcpy(info.driverpath, ""); + fstrcpy(info.datafile, ""); + fstrcpy(info.configfile, ""); + fstrcpy(info.helpfile, ""); + + if ((info.dependentfiles= SMB_MALLOC_ARRAY(fstring, 2)) == NULL) + return WERR_NOMEM; + + memset(info.dependentfiles, '\0', 2*sizeof(fstring)); + fstrcpy(info.dependentfiles[0], ""); + + *info_ptr = (NT_PRINTER_DRIVER_INFO_LEVEL_3 *)memdup(&info, sizeof(info)); + if (!*info_ptr) { + SAFE_FREE(info.dependentfiles); + return WERR_NOMEM; + } + + return WERR_OK; +} + +/**************************************************************************** +****************************************************************************/ +static WERROR get_a_printer_driver_3(NT_PRINTER_DRIVER_INFO_LEVEL_3 **info_ptr, fstring drivername, const char *arch, uint32 version) +{ + NT_PRINTER_DRIVER_INFO_LEVEL_3 driver; + TDB_DATA dbuf; + const char *architecture; + int len = 0; + int i; + char *key = NULL; + + ZERO_STRUCT(driver); + + architecture = get_short_archi(arch); + if ( !architecture ) { + return WERR_UNKNOWN_PRINTER_DRIVER; + } + + /* Windows 4.0 (i.e. win9x) should always use a version of 0 */ + + if ( strcmp( architecture, SPL_ARCH_WIN40 ) == 0 ) + version = 0; + + DEBUG(8,("get_a_printer_driver_3: [%s%s/%d/%s]\n", DRIVERS_PREFIX, architecture, version, drivername)); + + if (asprintf(&key, "%s%s/%d/%s", DRIVERS_PREFIX, + architecture, version, drivername) < 0) { + return WERR_NOMEM; + } + + dbuf = tdb_fetch_bystring(tdb_drivers, key); + if (!dbuf.dptr) { + SAFE_FREE(key); + return WERR_UNKNOWN_PRINTER_DRIVER; + } + + len += tdb_unpack(dbuf.dptr, dbuf.dsize, "dffffffff", + &driver.cversion, + driver.name, + driver.environment, + driver.driverpath, + driver.datafile, + driver.configfile, + driver.helpfile, + driver.monitorname, + driver.defaultdatatype); + + i=0; + while (len < dbuf.dsize) { + driver.dependentfiles = SMB_REALLOC_ARRAY(driver.dependentfiles, fstring, i+2); + if ( !driver.dependentfiles ) { + DEBUG(0,("get_a_printer_driver_3: failed to enlarge buffer!\n")); + break; + } + + len += tdb_unpack(dbuf.dptr+len, dbuf.dsize-len, "f", + &driver.dependentfiles[i]); + i++; + } + + if ( driver.dependentfiles ) + fstrcpy( driver.dependentfiles[i], "" ); + + SAFE_FREE(dbuf.dptr); + SAFE_FREE(key); + + if (len != dbuf.dsize) { + SAFE_FREE(driver.dependentfiles); + + return get_a_printer_driver_3_default(info_ptr, drivername, arch); + } + + *info_ptr = (NT_PRINTER_DRIVER_INFO_LEVEL_3 *)memdup(&driver, sizeof(driver)); + if (!*info_ptr) { + SAFE_FREE(driver.dependentfiles); + return WERR_NOMEM; + } + + return WERR_OK; +} + +/**************************************************************************** + Debugging function, dump at level 6 the struct in the logs. +****************************************************************************/ + +static uint32 dump_a_printer_driver(NT_PRINTER_DRIVER_INFO_LEVEL driver, uint32 level) +{ + uint32 result; + NT_PRINTER_DRIVER_INFO_LEVEL_3 *info3; + int i; + + DEBUG(20,("Dumping printer driver at level [%d]\n", level)); + + switch (level) + { + case 3: + { + if (driver.info_3 == NULL) + result=5; + else { + info3=driver.info_3; + + DEBUGADD(20,("version:[%d]\n", info3->cversion)); + DEBUGADD(20,("name:[%s]\n", info3->name)); + DEBUGADD(20,("environment:[%s]\n", info3->environment)); + DEBUGADD(20,("driverpath:[%s]\n", info3->driverpath)); + DEBUGADD(20,("datafile:[%s]\n", info3->datafile)); + DEBUGADD(20,("configfile:[%s]\n", info3->configfile)); + DEBUGADD(20,("helpfile:[%s]\n", info3->helpfile)); + DEBUGADD(20,("monitorname:[%s]\n", info3->monitorname)); + DEBUGADD(20,("defaultdatatype:[%s]\n", info3->defaultdatatype)); + + for (i=0; info3->dependentfiles && + *info3->dependentfiles[i]; i++) { + DEBUGADD(20,("dependentfile:[%s]\n", + info3->dependentfiles[i])); + } + result=0; + } + break; + } + default: + DEBUGADD(20,("dump_a_printer_driver: Level %u not implemented\n", (unsigned int)level)); + result=1; + break; + } + + return result; +} + +/**************************************************************************** +****************************************************************************/ +int pack_devicemode(NT_DEVICEMODE *nt_devmode, uint8 *buf, int buflen) +{ + int len = 0; + + len += tdb_pack(buf+len, buflen-len, "p", nt_devmode); + + if (!nt_devmode) + return len; + + len += tdb_pack(buf+len, buflen-len, "ffwwwwwwwwwwwwwwwwwwddddddddddddddp", + nt_devmode->devicename, + nt_devmode->formname, + + nt_devmode->specversion, + nt_devmode->driverversion, + nt_devmode->size, + nt_devmode->driverextra, + nt_devmode->orientation, + nt_devmode->papersize, + nt_devmode->paperlength, + nt_devmode->paperwidth, + nt_devmode->scale, + nt_devmode->copies, + nt_devmode->defaultsource, + nt_devmode->printquality, + nt_devmode->color, + nt_devmode->duplex, + nt_devmode->yresolution, + nt_devmode->ttoption, + nt_devmode->collate, + nt_devmode->logpixels, + + nt_devmode->fields, + nt_devmode->bitsperpel, + nt_devmode->pelswidth, + nt_devmode->pelsheight, + nt_devmode->displayflags, + nt_devmode->displayfrequency, + nt_devmode->icmmethod, + nt_devmode->icmintent, + nt_devmode->mediatype, + nt_devmode->dithertype, + nt_devmode->reserved1, + nt_devmode->reserved2, + nt_devmode->panningwidth, + nt_devmode->panningheight, + nt_devmode->nt_dev_private); + + if (nt_devmode->nt_dev_private) { + len += tdb_pack(buf+len, buflen-len, "B", + nt_devmode->driverextra, + nt_devmode->nt_dev_private); + } + + DEBUG(8,("Packed devicemode [%s]\n", nt_devmode->formname)); + + return len; +} + +/**************************************************************************** + Pack all values in all printer keys + ***************************************************************************/ + +static int pack_values(NT_PRINTER_DATA *data, uint8 *buf, int buflen) +{ + int len = 0; + int i, j; + REGISTRY_VALUE *val; + REGVAL_CTR *val_ctr; + char *path = NULL; + int num_values; + + if ( !data ) + return 0; + + /* loop over all keys */ + + for ( i=0; i<data->num_keys; i++ ) { + val_ctr = data->keys[i].values; + num_values = regval_ctr_numvals( val_ctr ); + + /* pack the keyname followed by a empty value */ + + len += tdb_pack(buf+len, buflen-len, "pPdB", + &data->keys[i].name, + data->keys[i].name, + REG_NONE, + 0, + NULL); + + /* now loop over all values */ + + for ( j=0; j<num_values; j++ ) { + /* pathname should be stored as <key>\<value> */ + + val = regval_ctr_specific_value( val_ctr, j ); + if (asprintf(&path, "%s\\%s", + data->keys[i].name, + regval_name(val)) < 0) { + return -1; + } + + len += tdb_pack(buf+len, buflen-len, "pPdB", + val, + path, + regval_type(val), + regval_size(val), + regval_data_p(val) ); + + DEBUG(8,("specific: [%s], len: %d\n", regval_name(val), regval_size(val))); + SAFE_FREE(path); + } + + } + + /* terminator */ + + len += tdb_pack(buf+len, buflen-len, "p", NULL); + + return len; +} + + +/**************************************************************************** + Delete a printer - this just deletes the printer info file, any open + handles are not affected. +****************************************************************************/ + +uint32 del_a_printer(const char *sharename) +{ + TDB_DATA kbuf; + char *printdb_path = NULL; + TALLOC_CTX *ctx = talloc_tos(); + + kbuf = make_printer_tdbkey(ctx, sharename); + tdb_delete(tdb_printers, kbuf); + + kbuf= make_printers_secdesc_tdbkey(ctx, sharename); + tdb_delete(tdb_printers, kbuf); + + close_all_print_db(); + + if (geteuid() == 0) { + if (asprintf(&printdb_path, "%s%s.tdb", + lock_path("printing/"), + sharename) < 0) { + return (uint32)-1; + } + unlink(printdb_path); + SAFE_FREE(printdb_path); + } + + return 0; +} + +/**************************************************************************** +****************************************************************************/ +static WERROR update_a_printer_2(NT_PRINTER_INFO_LEVEL_2 *info) +{ + uint8 *buf; + int buflen, len; + int retlen; + WERROR ret; + TDB_DATA kbuf, dbuf; + + /* + * in addprinter: no servername and the printer is the name + * in setprinter: servername is \\server + * and printer is \\server\\printer + * + * Samba manages only local printers. + * we currently don't support things like i + * path=\\other_server\printer + * + * We only store the printername, not \\server\printername + */ + + if ( info->servername[0] != '\0' ) { + trim_string(info->printername, info->servername, NULL); + trim_char(info->printername, '\\', '\0'); + info->servername[0]='\0'; + } + + /* + * JFM: one day I'll forget. + * below that's info->portname because that's the SAMBA sharename + * and I made NT 'thinks' it's the portname + * the info->sharename is the thing you can name when you add a printer + * that's the short-name when you create shared printer for 95/98 + * So I've made a limitation in SAMBA: you can only have 1 printer model + * behind a SAMBA share. + */ + + buf = NULL; + buflen = 0; + + again: + len = 0; + len += tdb_pack(buf+len, buflen-len, "dddddddddddfffffPfffff", + info->attributes, + info->priority, + info->default_priority, + info->starttime, + info->untiltime, + info->status, + info->cjobs, + info->averageppm, + info->changeid, + info->c_setprinter, + info->setuptime, + info->servername, + info->printername, + info->sharename, + info->portname, + info->drivername, + info->comment, + info->location, + info->sepfile, + info->printprocessor, + info->datatype, + info->parameters); + + len += pack_devicemode(info->devmode, buf+len, buflen-len); + retlen = pack_values( info->data, buf+len, buflen-len ); + if (retlen == -1) { + ret = WERR_NOMEM; + goto done; + } + len += retlen; + + if (buflen != len) { + buf = (uint8 *)SMB_REALLOC(buf, len); + if (!buf) { + DEBUG(0,("update_a_printer_2: failed to enlarge buffer!\n")); + ret = WERR_NOMEM; + goto done; + } + buflen = len; + goto again; + } + + kbuf = make_printer_tdbkey(talloc_tos(), info->sharename ); + + dbuf.dptr = buf; + dbuf.dsize = len; + + ret = (tdb_store(tdb_printers, kbuf, dbuf, TDB_REPLACE) == 0? WERR_OK : WERR_NOMEM); + +done: + if (!W_ERROR_IS_OK(ret)) + DEBUG(8, ("error updating printer to tdb on disk\n")); + + SAFE_FREE(buf); + + DEBUG(8,("packed printer [%s] with driver [%s] portname=[%s] len=%d\n", + info->sharename, info->drivername, info->portname, len)); + + return ret; +} + + +/**************************************************************************** + Malloc and return an NT devicemode. +****************************************************************************/ + +NT_DEVICEMODE *construct_nt_devicemode(const fstring default_devicename) +{ + + char adevice[MAXDEVICENAME]; + NT_DEVICEMODE *nt_devmode = SMB_MALLOC_P(NT_DEVICEMODE); + + if (nt_devmode == NULL) { + DEBUG(0,("construct_nt_devicemode: malloc fail.\n")); + return NULL; + } + + ZERO_STRUCTP(nt_devmode); + + slprintf(adevice, sizeof(adevice), "%s", default_devicename); + fstrcpy(nt_devmode->devicename, adevice); + + fstrcpy(nt_devmode->formname, "Letter"); + + nt_devmode->specversion = 0x0401; + nt_devmode->driverversion = 0x0400; + nt_devmode->size = 0x00DC; + nt_devmode->driverextra = 0x0000; + nt_devmode->fields = FORMNAME | TTOPTION | PRINTQUALITY | + DEFAULTSOURCE | COPIES | SCALE | + PAPERSIZE | ORIENTATION; + nt_devmode->orientation = 1; + nt_devmode->papersize = PAPER_LETTER; + nt_devmode->paperlength = 0; + nt_devmode->paperwidth = 0; + nt_devmode->scale = 0x64; + nt_devmode->copies = 1; + nt_devmode->defaultsource = BIN_FORMSOURCE; + nt_devmode->printquality = RES_HIGH; /* 0x0258 */ + nt_devmode->color = COLOR_MONOCHROME; + nt_devmode->duplex = DUP_SIMPLEX; + nt_devmode->yresolution = 0; + nt_devmode->ttoption = TT_SUBDEV; + nt_devmode->collate = COLLATE_FALSE; + nt_devmode->icmmethod = 0; + nt_devmode->icmintent = 0; + nt_devmode->mediatype = 0; + nt_devmode->dithertype = 0; + + /* non utilisés par un driver d'imprimante */ + nt_devmode->logpixels = 0; + nt_devmode->bitsperpel = 0; + nt_devmode->pelswidth = 0; + nt_devmode->pelsheight = 0; + nt_devmode->displayflags = 0; + nt_devmode->displayfrequency = 0; + nt_devmode->reserved1 = 0; + nt_devmode->reserved2 = 0; + nt_devmode->panningwidth = 0; + nt_devmode->panningheight = 0; + + nt_devmode->nt_dev_private = NULL; + return nt_devmode; +} + +/**************************************************************************** + Deepcopy an NT devicemode. +****************************************************************************/ + +NT_DEVICEMODE *dup_nt_devicemode(NT_DEVICEMODE *nt_devicemode) +{ + NT_DEVICEMODE *new_nt_devicemode = NULL; + + if ( !nt_devicemode ) + return NULL; + + if ((new_nt_devicemode = (NT_DEVICEMODE *)memdup(nt_devicemode, sizeof(NT_DEVICEMODE))) == NULL) { + DEBUG(0,("dup_nt_devicemode: malloc fail.\n")); + return NULL; + } + + new_nt_devicemode->nt_dev_private = NULL; + if (nt_devicemode->nt_dev_private != NULL) { + if ((new_nt_devicemode->nt_dev_private = (uint8 *)memdup(nt_devicemode->nt_dev_private, nt_devicemode->driverextra)) == NULL) { + SAFE_FREE(new_nt_devicemode); + DEBUG(0,("dup_nt_devicemode: malloc fail.\n")); + return NULL; + } + } + + return new_nt_devicemode; +} + +/**************************************************************************** + Clean up and deallocate a (maybe partially) allocated NT_DEVICEMODE. +****************************************************************************/ + +void free_nt_devicemode(NT_DEVICEMODE **devmode_ptr) +{ + NT_DEVICEMODE *nt_devmode = *devmode_ptr; + + if(nt_devmode == NULL) + return; + + DEBUG(106,("free_nt_devicemode: deleting DEVMODE\n")); + + SAFE_FREE(nt_devmode->nt_dev_private); + SAFE_FREE(*devmode_ptr); +} + +/**************************************************************************** + Clean up and deallocate a (maybe partially) allocated NT_PRINTER_INFO_LEVEL_2. +****************************************************************************/ + +static void free_nt_printer_info_level_2(NT_PRINTER_INFO_LEVEL_2 **info_ptr) +{ + NT_PRINTER_INFO_LEVEL_2 *info = *info_ptr; + + if ( !info ) + return; + + free_nt_devicemode(&info->devmode); + + TALLOC_FREE( *info_ptr ); +} + + +/**************************************************************************** +****************************************************************************/ +int unpack_devicemode(NT_DEVICEMODE **nt_devmode, const uint8 *buf, int buflen) +{ + int len = 0; + int extra_len = 0; + NT_DEVICEMODE devmode; + + ZERO_STRUCT(devmode); + + len += tdb_unpack(buf+len, buflen-len, "p", nt_devmode); + + if (!*nt_devmode) return len; + + len += tdb_unpack(buf+len, buflen-len, "ffwwwwwwwwwwwwwwwwwwddddddddddddddp", + devmode.devicename, + devmode.formname, + + &devmode.specversion, + &devmode.driverversion, + &devmode.size, + &devmode.driverextra, + &devmode.orientation, + &devmode.papersize, + &devmode.paperlength, + &devmode.paperwidth, + &devmode.scale, + &devmode.copies, + &devmode.defaultsource, + &devmode.printquality, + &devmode.color, + &devmode.duplex, + &devmode.yresolution, + &devmode.ttoption, + &devmode.collate, + &devmode.logpixels, + + &devmode.fields, + &devmode.bitsperpel, + &devmode.pelswidth, + &devmode.pelsheight, + &devmode.displayflags, + &devmode.displayfrequency, + &devmode.icmmethod, + &devmode.icmintent, + &devmode.mediatype, + &devmode.dithertype, + &devmode.reserved1, + &devmode.reserved2, + &devmode.panningwidth, + &devmode.panningheight, + &devmode.nt_dev_private); + + if (devmode.nt_dev_private) { + /* the len in tdb_unpack is an int value and + * devmode.driverextra is only a short + */ + len += tdb_unpack(buf+len, buflen-len, "B", &extra_len, &devmode.nt_dev_private); + devmode.driverextra=(uint16)extra_len; + + /* check to catch an invalid TDB entry so we don't segfault */ + if (devmode.driverextra == 0) { + devmode.nt_dev_private = NULL; + } + } + + *nt_devmode = (NT_DEVICEMODE *)memdup(&devmode, sizeof(devmode)); + if (!*nt_devmode) { + SAFE_FREE(devmode.nt_dev_private); + return -1; + } + + DEBUG(8,("Unpacked devicemode [%s](%s)\n", devmode.devicename, devmode.formname)); + if (devmode.nt_dev_private) + DEBUG(8,("with a private section of %d bytes\n", devmode.driverextra)); + + return len; +} + +/**************************************************************************** + Allocate and initialize a new slot. +***************************************************************************/ + +int add_new_printer_key( NT_PRINTER_DATA *data, const char *name ) +{ + NT_PRINTER_KEY *d; + int key_index; + + if ( !name || !data ) + return -1; + + /* allocate another slot in the NT_PRINTER_KEY array */ + + if ( !(d = TALLOC_REALLOC_ARRAY( data, data->keys, NT_PRINTER_KEY, data->num_keys+1)) ) { + DEBUG(0,("add_new_printer_key: Realloc() failed!\n")); + return -1; + } + + data->keys = d; + + key_index = data->num_keys; + + /* initialze new key */ + + data->keys[key_index].name = talloc_strdup( data, name ); + + if ( !(data->keys[key_index].values = TALLOC_ZERO_P( data, REGVAL_CTR )) ) + return -1; + + data->num_keys++; + + DEBUG(10,("add_new_printer_key: Inserted new data key [%s]\n", name )); + + return key_index; +} + +/**************************************************************************** + search for a registry key name in the existing printer data + ***************************************************************************/ + +int delete_printer_key( NT_PRINTER_DATA *data, const char *name ) +{ + int i; + + for ( i=0; i<data->num_keys; i++ ) { + if ( strequal( data->keys[i].name, name ) ) { + + /* cleanup memory */ + + TALLOC_FREE( data->keys[i].name ); + TALLOC_FREE( data->keys[i].values ); + + /* if not the end of the array, move remaining elements down one slot */ + + data->num_keys--; + if ( data->num_keys && (i < data->num_keys) ) + memmove( &data->keys[i], &data->keys[i+1], sizeof(NT_PRINTER_KEY)*(data->num_keys-i) ); + + break; + } + } + + + return data->num_keys; +} + +/**************************************************************************** + search for a registry key name in the existing printer data + ***************************************************************************/ + +int lookup_printerkey( NT_PRINTER_DATA *data, const char *name ) +{ + int key_index = -1; + int i; + + if ( !data || !name ) + return -1; + + DEBUG(12,("lookup_printerkey: Looking for [%s]\n", name)); + + /* loop over all existing keys */ + + for ( i=0; i<data->num_keys; i++ ) { + if ( strequal(data->keys[i].name, name) ) { + DEBUG(12,("lookup_printerkey: Found [%s]!\n", name)); + key_index = i; + break; + + } + } + + return key_index; +} + +/**************************************************************************** + ***************************************************************************/ + +int get_printer_subkeys( NT_PRINTER_DATA *data, const char* key, fstring **subkeys ) +{ + int i, j; + int key_len; + int num_subkeys = 0; + char *p; + fstring *subkeys_ptr = NULL; + fstring subkeyname; + + *subkeys = NULL; + + if ( !data ) + return 0; + + if ( !key ) + return -1; + + /* special case of asking for the top level printer data registry key names */ + + if ( strlen(key) == 0 ) { + for ( i=0; i<data->num_keys; i++ ) { + + /* found a match, so allocate space and copy the name */ + + if ( !(subkeys_ptr = SMB_REALLOC_ARRAY( subkeys_ptr, fstring, num_subkeys+2)) ) { + DEBUG(0,("get_printer_subkeys: Realloc failed for [%d] entries!\n", + num_subkeys+1)); + return -1; + } + + fstrcpy( subkeys_ptr[num_subkeys], data->keys[i].name ); + num_subkeys++; + } + + goto done; + } + + /* asking for the subkeys of some key */ + /* subkey paths are stored in the key name using '\' as the delimiter */ + + for ( i=0; i<data->num_keys; i++ ) { + if ( StrnCaseCmp(data->keys[i].name, key, strlen(key)) == 0 ) { + + /* if we found the exact key, then break */ + key_len = strlen( key ); + if ( strlen(data->keys[i].name) == key_len ) + break; + + /* get subkey path */ + + p = data->keys[i].name + key_len; + if ( *p == '\\' ) + p++; + fstrcpy( subkeyname, p ); + if ( (p = strchr( subkeyname, '\\' )) ) + *p = '\0'; + + /* don't add a key more than once */ + + for ( j=0; j<num_subkeys; j++ ) { + if ( strequal( subkeys_ptr[j], subkeyname ) ) + break; + } + + if ( j != num_subkeys ) + continue; + + /* found a match, so allocate space and copy the name */ + + if ( !(subkeys_ptr = SMB_REALLOC_ARRAY( subkeys_ptr, fstring, num_subkeys+2)) ) { + DEBUG(0,("get_printer_subkeys: Realloc failed for [%d] entries!\n", + num_subkeys+1)); + return 0; + } + + fstrcpy( subkeys_ptr[num_subkeys], subkeyname ); + num_subkeys++; + } + + } + + /* return error if the key was not found */ + + if ( i == data->num_keys ) { + SAFE_FREE(subkeys_ptr); + return -1; + } + +done: + /* tag off the end */ + + if (num_subkeys) + fstrcpy(subkeys_ptr[num_subkeys], "" ); + + *subkeys = subkeys_ptr; + + return num_subkeys; +} + +#ifdef HAVE_ADS +static void map_sz_into_ctr(REGVAL_CTR *ctr, const char *val_name, + const char *sz) +{ + smb_ucs2_t conv_str[1024]; + size_t str_size; + + regval_ctr_delvalue(ctr, val_name); + str_size = push_ucs2(NULL, conv_str, sz, sizeof(conv_str), + STR_TERMINATE | STR_NOALIGN); + regval_ctr_addvalue(ctr, val_name, REG_SZ, + (char *) conv_str, str_size); +} + +static void map_dword_into_ctr(REGVAL_CTR *ctr, const char *val_name, + uint32 dword) +{ + regval_ctr_delvalue(ctr, val_name); + regval_ctr_addvalue(ctr, val_name, REG_DWORD, + (char *) &dword, sizeof(dword)); +} + +static void map_bool_into_ctr(REGVAL_CTR *ctr, const char *val_name, + bool b) +{ + uint8 bin_bool = (b ? 1 : 0); + regval_ctr_delvalue(ctr, val_name); + regval_ctr_addvalue(ctr, val_name, REG_BINARY, + (char *) &bin_bool, sizeof(bin_bool)); +} + +static void map_single_multi_sz_into_ctr(REGVAL_CTR *ctr, const char *val_name, + const char *multi_sz) +{ + smb_ucs2_t *conv_strs = NULL; + size_t str_size; + + /* a multi-sz has to have a null string terminator, i.e., the last + string must be followed by two nulls */ + str_size = strlen(multi_sz) + 2; + conv_strs = SMB_CALLOC_ARRAY(smb_ucs2_t, str_size); + if (!conv_strs) { + return; + } + + /* Change to byte units. */ + str_size *= sizeof(smb_ucs2_t); + push_ucs2(NULL, conv_strs, multi_sz, str_size, + STR_TERMINATE | STR_NOALIGN); + + regval_ctr_delvalue(ctr, val_name); + regval_ctr_addvalue(ctr, val_name, REG_MULTI_SZ, + (char *) conv_strs, str_size); + safe_free(conv_strs); + +} + +/**************************************************************************** + * Map the NT_PRINTER_INFO_LEVEL_2 data into DsSpooler keys for publishing. + * + * @param info2 NT_PRINTER_INFO_LEVEL_2 describing printer - gets modified + * @return bool indicating success or failure + ***************************************************************************/ + +static bool map_nt_printer_info2_to_dsspooler(NT_PRINTER_INFO_LEVEL_2 *info2) +{ + REGVAL_CTR *ctr = NULL; + fstring longname; + const char *dnssuffix; + char *allocated_string = NULL; + const char *ascii_str; + int i; + + if ((i = lookup_printerkey(info2->data, SPOOL_DSSPOOLER_KEY)) < 0) + i = add_new_printer_key(info2->data, SPOOL_DSSPOOLER_KEY); + ctr = info2->data->keys[i].values; + + map_sz_into_ctr(ctr, SPOOL_REG_PRINTERNAME, info2->sharename); + map_sz_into_ctr(ctr, SPOOL_REG_SHORTSERVERNAME, global_myname()); + + /* we make the assumption that the netbios name is the same + as the DNS name sinc ethe former will be what we used to + join the domain */ + + dnssuffix = get_mydnsdomname(talloc_tos()); + if (dnssuffix && *dnssuffix) { + fstr_sprintf( longname, "%s.%s", global_myname(), dnssuffix ); + } else { + fstrcpy( longname, global_myname() ); + } + + map_sz_into_ctr(ctr, SPOOL_REG_SERVERNAME, longname); + + asprintf(&allocated_string, "\\\\%s\\%s", longname, info2->sharename); + map_sz_into_ctr(ctr, SPOOL_REG_UNCNAME, allocated_string); + SAFE_FREE(allocated_string); + + map_dword_into_ctr(ctr, SPOOL_REG_VERSIONNUMBER, 4); + map_sz_into_ctr(ctr, SPOOL_REG_DRIVERNAME, info2->drivername); + map_sz_into_ctr(ctr, SPOOL_REG_LOCATION, info2->location); + map_sz_into_ctr(ctr, SPOOL_REG_DESCRIPTION, info2->comment); + map_single_multi_sz_into_ctr(ctr, SPOOL_REG_PORTNAME, info2->portname); + map_sz_into_ctr(ctr, SPOOL_REG_PRINTSEPARATORFILE, info2->sepfile); + map_dword_into_ctr(ctr, SPOOL_REG_PRINTSTARTTIME, info2->starttime); + map_dword_into_ctr(ctr, SPOOL_REG_PRINTENDTIME, info2->untiltime); + map_dword_into_ctr(ctr, SPOOL_REG_PRIORITY, info2->priority); + + map_bool_into_ctr(ctr, SPOOL_REG_PRINTKEEPPRINTEDJOBS, + (info2->attributes & + PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS)); + + switch (info2->attributes & 0x3) { + case 0: + ascii_str = SPOOL_REGVAL_PRINTWHILESPOOLING; + break; + case 1: + ascii_str = SPOOL_REGVAL_PRINTAFTERSPOOLED; + break; + case 2: + ascii_str = SPOOL_REGVAL_PRINTDIRECT; + break; + default: + ascii_str = "unknown"; + } + map_sz_into_ctr(ctr, SPOOL_REG_PRINTSPOOLING, ascii_str); + + return True; +} + +/***************************************************************** + ****************************************************************/ + +static void store_printer_guid(NT_PRINTER_INFO_LEVEL_2 *info2, + struct GUID guid) +{ + int i; + REGVAL_CTR *ctr=NULL; + UNISTR2 unistr_guid; + + /* find the DsSpooler key */ + if ((i = lookup_printerkey(info2->data, SPOOL_DSSPOOLER_KEY)) < 0) + i = add_new_printer_key(info2->data, SPOOL_DSSPOOLER_KEY); + ctr = info2->data->keys[i].values; + + regval_ctr_delvalue(ctr, "objectGUID"); + + /* We used to store this as a REG_BINARY but that causes + Vista to whine */ + + ZERO_STRUCT( unistr_guid ); + + init_unistr2( &unistr_guid, smb_uuid_string(talloc_tos(), guid), + UNI_STR_TERMINATE ); + + regval_ctr_addvalue(ctr, "objectGUID", REG_SZ, + (char *)unistr_guid.buffer, + unistr_guid.uni_max_len*2); + +} + +static WERROR nt_printer_publish_ads(ADS_STRUCT *ads, + NT_PRINTER_INFO_LEVEL *printer) +{ + ADS_STATUS ads_rc; + LDAPMessage *res; + char *prt_dn = NULL, *srv_dn, *srv_cn_0, *srv_cn_escaped, *sharename_escaped; + char *srv_dn_utf8, **srv_cn_utf8; + TALLOC_CTX *ctx; + ADS_MODLIST mods; + const char *attrs[] = {"objectGUID", NULL}; + struct GUID guid; + WERROR win_rc = WERR_OK; + size_t converted_size; + + DEBUG(5, ("publishing printer %s\n", printer->info_2->printername)); + + /* figure out where to publish */ + ads_find_machine_acct(ads, &res, global_myname()); + + /* 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); + if (!srv_dn_utf8) { + ads_destroy(&ads); + return WERR_SERVER_UNAVAILABLE; + } + ads_msgfree(ads, res); + srv_cn_utf8 = ldap_explode_dn(srv_dn_utf8, 1); + if (!srv_cn_utf8) { + ldap_memfree(srv_dn_utf8); + ads_destroy(&ads); + return WERR_SERVER_UNAVAILABLE; + } + /* Now convert to CH_UNIX. */ + if (!pull_utf8_allocate(&srv_dn, srv_dn_utf8, &converted_size)) { + ldap_memfree(srv_dn_utf8); + ldap_memfree(srv_cn_utf8); + ads_destroy(&ads); + return WERR_SERVER_UNAVAILABLE; + } + if (!pull_utf8_allocate(&srv_cn_0, srv_cn_utf8[0], &converted_size)) { + ldap_memfree(srv_dn_utf8); + ldap_memfree(srv_cn_utf8); + ads_destroy(&ads); + SAFE_FREE(srv_dn); + return WERR_SERVER_UNAVAILABLE; + } + + ldap_memfree(srv_dn_utf8); + ldap_memfree(srv_cn_utf8); + + srv_cn_escaped = escape_rdn_val_string_alloc(srv_cn_0); + if (!srv_cn_escaped) { + SAFE_FREE(srv_cn_0); + ldap_memfree(srv_dn_utf8); + ads_destroy(&ads); + return WERR_SERVER_UNAVAILABLE; + } + sharename_escaped = escape_rdn_val_string_alloc(printer->info_2->sharename); + if (!sharename_escaped) { + SAFE_FREE(srv_cn_escaped); + SAFE_FREE(srv_cn_0); + ldap_memfree(srv_dn_utf8); + ads_destroy(&ads); + return WERR_SERVER_UNAVAILABLE; + } + + + asprintf(&prt_dn, "cn=%s-%s,%s", srv_cn_escaped, sharename_escaped, srv_dn); + + SAFE_FREE(srv_dn); + SAFE_FREE(srv_cn_0); + SAFE_FREE(srv_cn_escaped); + SAFE_FREE(sharename_escaped); + + /* build the ads mods */ + ctx = talloc_init("nt_printer_publish_ads"); + if (ctx == NULL) { + SAFE_FREE(prt_dn); + return WERR_NOMEM; + } + + mods = ads_init_mods(ctx); + + if (mods == NULL) { + SAFE_FREE(prt_dn); + talloc_destroy(ctx); + return WERR_NOMEM; + } + + get_local_printer_publishing_data(ctx, &mods, printer->info_2->data); + ads_mod_str(ctx, &mods, SPOOL_REG_PRINTERNAME, + printer->info_2->sharename); + + /* publish it */ + ads_rc = ads_mod_printer_entry(ads, prt_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, prt_dn, ctx, &mods); + } + + if (!ADS_ERR_OK(ads_rc)) + DEBUG(3, ("error publishing %s: %s\n", printer->info_2->sharename, ads_errstr(ads_rc))); + + talloc_destroy(ctx); + + /* retreive the guid and store it locally */ + if (ADS_ERR_OK(ads_search_dn(ads, &res, prt_dn, attrs))) { + ZERO_STRUCT(guid); + ads_pull_guid(ads, res, &guid); + ads_msgfree(ads, res); + store_printer_guid(printer->info_2, guid); + win_rc = mod_a_printer(printer, 2); + } + + SAFE_FREE(prt_dn); + return win_rc; +} + +static WERROR nt_printer_unpublish_ads(ADS_STRUCT *ads, + NT_PRINTER_INFO_LEVEL *printer) +{ + ADS_STATUS ads_rc; + LDAPMessage *res; + char *prt_dn = NULL; + + DEBUG(5, ("unpublishing printer %s\n", printer->info_2->printername)); + + /* remove the printer from the directory */ + ads_rc = ads_find_printer_on_server(ads, &res, + printer->info_2->sharename, global_myname()); + + if (ADS_ERR_OK(ads_rc) && ads_count_replies(ads, res)) { + prt_dn = ads_get_dn(ads, res); + if (!prt_dn) { + ads_msgfree(ads, res); + return WERR_NOMEM; + } + ads_rc = ads_del_dn(ads, prt_dn); + ads_memfree(ads, prt_dn); + } + + ads_msgfree(ads, res); + return WERR_OK; +} + +/**************************************************************************** + * Publish a printer in the directory + * + * @param snum describing printer service + * @return WERROR indicating status of publishing + ***************************************************************************/ + +WERROR nt_printer_publish(Printer_entry *print_hnd, int snum, int action) +{ + ADS_STATUS ads_rc; + ADS_STRUCT *ads = NULL; + NT_PRINTER_INFO_LEVEL *printer = NULL; + WERROR win_rc; + + win_rc = get_a_printer(print_hnd, &printer, 2, lp_servicename(snum)); + if (!W_ERROR_IS_OK(win_rc)) + goto done; + + switch (action) { + case SPOOL_DS_PUBLISH: + case SPOOL_DS_UPDATE: + /* set the DsSpooler info and attributes */ + if (!(map_nt_printer_info2_to_dsspooler(printer->info_2))) { + win_rc = WERR_NOMEM; + goto done; + } + + printer->info_2->attributes |= PRINTER_ATTRIBUTE_PUBLISHED; + break; + case SPOOL_DS_UNPUBLISH: + printer->info_2->attributes ^= PRINTER_ATTRIBUTE_PUBLISHED; + break; + default: + win_rc = WERR_NOT_SUPPORTED; + goto done; + } + + win_rc = mod_a_printer(printer, 2); + if (!W_ERROR_IS_OK(win_rc)) { + DEBUG(3, ("err %d saving data\n", W_ERROR_V(win_rc))); + goto done; + } + + ads = ads_init(lp_realm(), lp_workgroup(), NULL); + if (!ads) { + DEBUG(3, ("ads_init() failed\n")); + win_rc = WERR_SERVER_UNAVAILABLE; + goto done; + } + setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1); + SAFE_FREE(ads->auth.password); + ads->auth.password = secrets_fetch_machine_password(lp_workgroup(), + NULL, NULL); + + /* 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 SPOOL_DS_PUBLISH: + case SPOOL_DS_UPDATE: + win_rc = nt_printer_publish_ads(ads, printer); + break; + case SPOOL_DS_UNPUBLISH: + win_rc = nt_printer_unpublish_ads(ads, printer); + break; + } + +done: + free_a_printer(&printer, 2); + ads_destroy(&ads); + return win_rc; +} + +WERROR check_published_printers(void) +{ + ADS_STATUS ads_rc; + ADS_STRUCT *ads = NULL; + int snum; + int n_services = lp_numservices(); + NT_PRINTER_INFO_LEVEL *printer = NULL; + + ads = ads_init(lp_realm(), lp_workgroup(), NULL); + if (!ads) { + DEBUG(3, ("ads_init() failed\n")); + return WERR_SERVER_UNAVAILABLE; + } + setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1); + SAFE_FREE(ads->auth.password); + ads->auth.password = secrets_fetch_machine_password(lp_workgroup(), + NULL, NULL); + + /* 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))); + ads_destroy(&ads); + ads_kdestroy("MEMORY:prtpub_cache"); + return WERR_ACCESS_DENIED; + } + + for (snum = 0; snum < n_services; snum++) { + if (!(lp_snum_ok(snum) && lp_print_ok(snum))) + continue; + + if (W_ERROR_IS_OK(get_a_printer(NULL, &printer, 2, + lp_servicename(snum))) && + (printer->info_2->attributes & PRINTER_ATTRIBUTE_PUBLISHED)) + nt_printer_publish_ads(ads, printer); + + free_a_printer(&printer, 2); + } + + ads_destroy(&ads); + ads_kdestroy("MEMORY:prtpub_cache"); + return WERR_OK; +} + +bool is_printer_published(Printer_entry *print_hnd, int snum, + struct GUID *guid) +{ + NT_PRINTER_INFO_LEVEL *printer = NULL; + REGVAL_CTR *ctr; + REGISTRY_VALUE *guid_val; + WERROR win_rc; + int i; + bool ret = False; + + win_rc = get_a_printer(print_hnd, &printer, 2, lp_servicename(snum)); + + if (!W_ERROR_IS_OK(win_rc) || + !(printer->info_2->attributes & PRINTER_ATTRIBUTE_PUBLISHED) || + ((i = lookup_printerkey(printer->info_2->data, SPOOL_DSSPOOLER_KEY)) < 0) || + !(ctr = printer->info_2->data->keys[i].values) || + !(guid_val = regval_ctr_getvalue(ctr, "objectGUID"))) + { + free_a_printer(&printer, 2); + return False; + } + + /* fetching printer guids really ought to be a separate function. */ + + if ( guid ) { + fstring guid_str; + + /* We used to store the guid as REG_BINARY, then swapped + to REG_SZ for Vista compatibility so check for both */ + + switch ( regval_type(guid_val) ){ + case REG_SZ: + rpcstr_pull( guid_str, regval_data_p(guid_val), + sizeof(guid_str)-1, -1, STR_TERMINATE ); + ret = smb_string_to_uuid( guid_str, guid ); + break; + case REG_BINARY: + if ( regval_size(guid_val) != sizeof(struct GUID) ) { + ret = False; + break; + } + memcpy(guid, regval_data_p(guid_val), sizeof(struct GUID)); + break; + default: + DEBUG(0,("is_printer_published: GUID value stored as " + "invaluid type (%d)\n", regval_type(guid_val) )); + break; + } + } + + free_a_printer(&printer, 2); + return ret; +} +#else +WERROR nt_printer_publish(Printer_entry *print_hnd, int snum, int action) +{ + return WERR_OK; +} + +WERROR check_published_printers(void) +{ + return WERR_OK; +} + +bool is_printer_published(Printer_entry *print_hnd, int snum, + struct GUID *guid) +{ + return False; +} +#endif /* HAVE_ADS */ + +/**************************************************************************** + ***************************************************************************/ + +WERROR delete_all_printer_data( NT_PRINTER_INFO_LEVEL_2 *p2, const char *key ) +{ + NT_PRINTER_DATA *data; + int i; + int removed_keys = 0; + int empty_slot; + + data = p2->data; + empty_slot = data->num_keys; + + if ( !key ) + return WERR_INVALID_PARAM; + + /* remove all keys */ + + if ( !strlen(key) ) { + + TALLOC_FREE( data ); + + p2->data = NULL; + + DEBUG(8,("delete_all_printer_data: Removed all Printer Data from printer [%s]\n", + p2->printername )); + + return WERR_OK; + } + + /* remove a specific key (and all subkeys) */ + + for ( i=0; i<data->num_keys; i++ ) { + if ( StrnCaseCmp( data->keys[i].name, key, strlen(key)) == 0 ) { + DEBUG(8,("delete_all_printer_data: Removed all Printer Data from key [%s]\n", + data->keys[i].name)); + + TALLOC_FREE( data->keys[i].name ); + TALLOC_FREE( data->keys[i].values ); + + /* mark the slot as empty */ + + ZERO_STRUCTP( &data->keys[i] ); + } + } + + /* find the first empty slot */ + + for ( i=0; i<data->num_keys; i++ ) { + if ( !data->keys[i].name ) { + empty_slot = i; + removed_keys++; + break; + } + } + + if ( i == data->num_keys ) + /* nothing was removed */ + return WERR_INVALID_PARAM; + + /* move everything down */ + + for ( i=empty_slot+1; i<data->num_keys; i++ ) { + if ( data->keys[i].name ) { + memcpy( &data->keys[empty_slot], &data->keys[i], sizeof(NT_PRINTER_KEY) ); + ZERO_STRUCTP( &data->keys[i] ); + empty_slot++; + removed_keys++; + } + } + + /* update count */ + + data->num_keys -= removed_keys; + + /* sanity check to see if anything is left */ + + if ( !data->num_keys ) { + DEBUG(8,("delete_all_printer_data: No keys left for printer [%s]\n", p2->printername )); + + SAFE_FREE( data->keys ); + ZERO_STRUCTP( data ); + } + + return WERR_OK; +} + +/**************************************************************************** + ***************************************************************************/ + +WERROR delete_printer_data( NT_PRINTER_INFO_LEVEL_2 *p2, const char *key, const char *value ) +{ + WERROR result = WERR_OK; + int key_index; + + /* we must have names on non-zero length */ + + if ( !key || !*key|| !value || !*value ) + return WERR_INVALID_NAME; + + /* find the printer key first */ + + key_index = lookup_printerkey( p2->data, key ); + if ( key_index == -1 ) + return WERR_OK; + + /* make sure the value exists so we can return the correct error code */ + + if ( !regval_ctr_getvalue( p2->data->keys[key_index].values, value ) ) + return WERR_BADFILE; + + regval_ctr_delvalue( p2->data->keys[key_index].values, value ); + + DEBUG(8,("delete_printer_data: Removed key => [%s], value => [%s]\n", + key, value )); + + return result; +} + +/**************************************************************************** + ***************************************************************************/ + +WERROR add_printer_data( NT_PRINTER_INFO_LEVEL_2 *p2, const char *key, const char *value, + uint32 type, uint8 *data, int real_len ) +{ + WERROR result = WERR_OK; + int key_index; + + /* we must have names on non-zero length */ + + if ( !key || !*key|| !value || !*value ) + return WERR_INVALID_NAME; + + /* find the printer key first */ + + key_index = lookup_printerkey( p2->data, key ); + if ( key_index == -1 ) + key_index = add_new_printer_key( p2->data, key ); + + if ( key_index == -1 ) + return WERR_NOMEM; + + regval_ctr_addvalue( p2->data->keys[key_index].values, value, + type, (const char *)data, real_len ); + + DEBUG(8,("add_printer_data: Added key => [%s], value => [%s], type=> [%d], size => [%d]\n", + key, value, type, real_len )); + + return result; +} + +/**************************************************************************** + ***************************************************************************/ + +REGISTRY_VALUE* get_printer_data( NT_PRINTER_INFO_LEVEL_2 *p2, const char *key, const char *value ) +{ + int key_index; + + if ( (key_index = lookup_printerkey( p2->data, key )) == -1 ) + return NULL; + + DEBUG(8,("get_printer_data: Attempting to lookup key => [%s], value => [%s]\n", + key, value )); + + return regval_ctr_getvalue( p2->data->keys[key_index].values, value ); +} + +/**************************************************************************** + Unpack a list of registry values frem the TDB + ***************************************************************************/ + +static int unpack_values(NT_PRINTER_DATA *printer_data, const uint8 *buf, int buflen) +{ + int len = 0; + uint32 type; + fstring string; + const char *valuename = NULL; + const char *keyname = NULL; + char *str; + int size; + uint8 *data_p; + REGISTRY_VALUE *regval_p; + int key_index; + + /* add the "PrinterDriverData" key first for performance reasons */ + + add_new_printer_key( printer_data, SPOOL_PRINTERDATA_KEY ); + + /* loop and unpack the rest of the registry values */ + + while ( True ) { + + /* check to see if there are any more registry values */ + + regval_p = NULL; + len += tdb_unpack(buf+len, buflen-len, "p", ®val_p); + if ( !regval_p ) + break; + + /* unpack the next regval */ + + len += tdb_unpack(buf+len, buflen-len, "fdB", + string, + &type, + &size, + &data_p); + + /* lookup for subkey names which have a type of REG_NONE */ + /* there's no data with this entry */ + + if ( type == REG_NONE ) { + if ( (key_index=lookup_printerkey( printer_data, string)) == -1 ) + add_new_printer_key( printer_data, string ); + continue; + } + + /* + * break of the keyname from the value name. + * Valuenames can have embedded '\'s so be careful. + * only support one level of keys. See the + * "Konica Fiery S300 50C-K v1.1. enu" 2k driver. + * -- jerry + */ + + str = strchr_m( string, '\\'); + + /* Put in "PrinterDriverData" is no key specified */ + + if ( !str ) { + keyname = SPOOL_PRINTERDATA_KEY; + valuename = string; + } + else { + *str = '\0'; + keyname = string; + valuename = str+1; + } + + /* see if we need a new key */ + + if ( (key_index=lookup_printerkey( printer_data, keyname )) == -1 ) + key_index = add_new_printer_key( printer_data, keyname ); + + if ( key_index == -1 ) { + DEBUG(0,("unpack_values: Failed to allocate a new key [%s]!\n", + keyname)); + break; + } + + DEBUG(8,("specific: [%s:%s], len: %d\n", keyname, valuename, size)); + + /* Vista doesn't like unknown REG_BINARY values in DsSpooler. + Thanks to Martin Zielinski for the hint. */ + + if ( type == REG_BINARY && + strequal( keyname, SPOOL_DSSPOOLER_KEY ) && + strequal( valuename, "objectGUID" ) ) + { + struct GUID guid; + UNISTR2 unistr_guid; + + ZERO_STRUCT( unistr_guid ); + + /* convert the GUID to a UNICODE string */ + + memcpy( &guid, data_p, sizeof(struct GUID) ); + + init_unistr2( &unistr_guid, + smb_uuid_string(talloc_tos(), guid), + UNI_STR_TERMINATE ); + + regval_ctr_addvalue( printer_data->keys[key_index].values, + valuename, REG_SZ, + (const char *)unistr_guid.buffer, + unistr_guid.uni_str_len*2 ); + + } else { + /* add the value */ + + regval_ctr_addvalue( printer_data->keys[key_index].values, + valuename, type, (const char *)data_p, + size ); + } + + SAFE_FREE(data_p); /* 'B' option to tdbpack does a malloc() */ + + } + + return len; +} + +/**************************************************************************** + ***************************************************************************/ + +static char *last_from; +static char *last_to; + +static const char *get_last_from(void) +{ + if (!last_from) { + return ""; + } + return last_from; +} + +static const char *get_last_to(void) +{ + if (!last_to) { + return ""; + } + return last_to; +} + +static bool set_last_from_to(const char *from, const char *to) +{ + char *orig_from = last_from; + char *orig_to = last_to; + + last_from = SMB_STRDUP(from); + last_to = SMB_STRDUP(to); + + SAFE_FREE(orig_from); + SAFE_FREE(orig_to); + + if (!last_from || !last_to) { + SAFE_FREE(last_from); + SAFE_FREE(last_to); + return false; + } + return true; +} + +static void map_to_os2_driver(fstring drivername) +{ + char *mapfile = lp_os2_driver_map(); + char **lines = NULL; + int numlines = 0; + int i; + + if (!strlen(drivername)) + return; + + if (!*mapfile) + return; + + if (strequal(drivername,get_last_from())) { + DEBUG(3,("Mapped Windows driver %s to OS/2 driver %s\n", + drivername,get_last_to())); + fstrcpy(drivername,get_last_to()); + return; + } + + lines = file_lines_load(mapfile, &numlines,0); + if (numlines == 0 || lines == NULL) { + DEBUG(0,("No entries in OS/2 driver map %s\n",mapfile)); + SAFE_FREE(lines); + return; + } + + 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) + continue; + + *os2_name++ = 0; + + while (isspace(*nt_name)) + nt_name++; + + if (!*nt_name || 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 os2 driver%s\n",drivername,os2_name)); + set_last_from_to(drivername,os2_name); + fstrcpy(drivername,os2_name); + file_lines_free(lines); + return; + } + } + + file_lines_free(lines); +} + +/**************************************************************************** + Get a default printer info 2 struct. +****************************************************************************/ + +static WERROR get_a_printer_2_default(NT_PRINTER_INFO_LEVEL_2 *info, + const char *servername, + const char* sharename, + bool get_loc_com) +{ + int snum = lp_servicenumber(sharename); + + slprintf(info->servername, sizeof(info->servername)-1, "\\\\%s", servername); + slprintf(info->printername, sizeof(info->printername)-1, "\\\\%s\\%s", + servername, sharename); + fstrcpy(info->sharename, sharename); + fstrcpy(info->portname, SAMBA_PRINTER_PORT_NAME); + + /* by setting the driver name to an empty string, a local NT admin + can now run the **local** APW to install a local printer driver + for a Samba shared printer in 2.2. Without this, drivers **must** be + installed on the Samba server for NT clients --jerry */ +#if 0 /* JERRY --do not uncomment-- */ + if (!*info->drivername) + fstrcpy(info->drivername, "NO DRIVER AVAILABLE FOR THIS PRINTER"); +#endif + + + DEBUG(10,("get_a_printer_2_default: driver name set to [%s]\n", info->drivername)); + + strlcpy(info->comment, "", sizeof(info->comment)); + fstrcpy(info->printprocessor, "winprint"); + fstrcpy(info->datatype, "RAW"); + +#ifdef HAVE_CUPS + if (get_loc_com && (enum printing_types)lp_printing(snum) == PRINT_CUPS ) { + /* Pull the location and comment strings from cups if we don't + already have one */ + if ( !strlen(info->location) || !strlen(info->comment) ) + cups_pull_comment_location( info ); + } +#endif + + info->attributes = PRINTER_ATTRIBUTE_SAMBA; + + info->starttime = 0; /* Minutes since 12:00am GMT */ + info->untiltime = 0; /* Minutes since 12:00am GMT */ + info->priority = 1; + info->default_priority = 1; + info->setuptime = (uint32)time(NULL); + + /* + * I changed this as I think it is better to have a generic + * DEVMODE than to crash Win2k explorer.exe --jerry + * See the HP Deskjet 990c Win2k drivers for an example. + * + * However the default devmode appears to cause problems + * with the HP CLJ 8500 PCL driver. Hence the addition of + * the "default devmode" parameter --jerry 22/01/2002 + */ + + if (lp_default_devmode(snum)) { + if ((info->devmode = construct_nt_devicemode(info->printername)) == NULL) { + goto fail; + } + } else { + info->devmode = NULL; + } + + if (!nt_printing_getsec(info, sharename, &info->secdesc_buf)) { + goto fail; + } + + return WERR_OK; + +fail: + if (info->devmode) + free_nt_devicemode(&info->devmode); + + return WERR_ACCESS_DENIED; +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR get_a_printer_2(NT_PRINTER_INFO_LEVEL_2 *info, + const char *servername, + const char *sharename, + bool get_loc_com) +{ + int len = 0; + int snum = lp_servicenumber(sharename); + TDB_DATA kbuf, dbuf; + fstring printername; + char adevice[MAXDEVICENAME]; + char *comment = NULL; + + kbuf = make_printer_tdbkey(talloc_tos(), sharename); + + dbuf = tdb_fetch(tdb_printers, kbuf); + if (!dbuf.dptr) { + return get_a_printer_2_default(info, servername, + sharename, get_loc_com); + } + + len += tdb_unpack(dbuf.dptr+len, dbuf.dsize-len, "dddddddddddfffffPfffff", + &info->attributes, + &info->priority, + &info->default_priority, + &info->starttime, + &info->untiltime, + &info->status, + &info->cjobs, + &info->averageppm, + &info->changeid, + &info->c_setprinter, + &info->setuptime, + info->servername, + info->printername, + info->sharename, + info->portname, + info->drivername, + &comment, + info->location, + info->sepfile, + info->printprocessor, + info->datatype, + info->parameters); + + if (comment) { + strlcpy(info->comment, comment, sizeof(info->comment)); + SAFE_FREE(comment); + } + + /* Samba has to have shared raw drivers. */ + info->attributes |= PRINTER_ATTRIBUTE_SAMBA; + info->attributes &= ~PRINTER_ATTRIBUTE_NOT_SAMBA; + + /* Restore the stripped strings. */ + slprintf(info->servername, sizeof(info->servername)-1, "\\\\%s", servername); + + if ( lp_force_printername(snum) ) { + slprintf(printername, sizeof(printername)-1, "\\\\%s\\%s", servername, sharename ); + } else { + slprintf(printername, sizeof(printername)-1, "\\\\%s\\%s", servername, info->printername); + } + + fstrcpy(info->printername, printername); + +#ifdef HAVE_CUPS + if (get_loc_com && (enum printing_types)lp_printing(snum) == PRINT_CUPS ) { + /* Pull the location and comment strings from cups if we don't + already have one */ + if ( !strlen(info->location) || !strlen(info->comment) ) + cups_pull_comment_location( info ); + } +#endif + + len += unpack_devicemode(&info->devmode,dbuf.dptr+len, dbuf.dsize-len); + + /* + * Some client drivers freak out if there is a NULL devmode + * (probably the driver is not checking before accessing + * the devmode pointer) --jerry + * + * See comments in get_a_printer_2_default() + */ + + if (lp_default_devmode(snum) && !info->devmode) { + DEBUG(8,("get_a_printer_2: Constructing a default device mode for [%s]\n", + printername)); + info->devmode = construct_nt_devicemode(printername); + } + + slprintf( adevice, sizeof(adevice), "%s", info->printername ); + if (info->devmode) { + fstrcpy(info->devmode->devicename, adevice); + } + + if ( !(info->data = TALLOC_ZERO_P( info, NT_PRINTER_DATA )) ) { + DEBUG(0,("unpack_values: talloc() failed!\n")); + SAFE_FREE(dbuf.dptr); + return WERR_NOMEM; + } + len += unpack_values( info->data, dbuf.dptr+len, dbuf.dsize-len ); + + /* This will get the current RPC talloc context, but we should be + passing this as a parameter... fixme... JRA ! */ + + if (!nt_printing_getsec(info, sharename, &info->secdesc_buf)) { + SAFE_FREE(dbuf.dptr); + return WERR_NOMEM; + } + + /* Fix for OS/2 drivers. */ + + if (get_remote_arch() == RA_OS2) { + map_to_os2_driver(info->drivername); + } + + SAFE_FREE(dbuf.dptr); + + DEBUG(9,("Unpacked printer [%s] name [%s] running driver [%s]\n", + sharename, info->printername, info->drivername)); + + return WERR_OK; +} + +/**************************************************************************** + Debugging function, dump at level 6 the struct in the logs. +****************************************************************************/ +static uint32 dump_a_printer(NT_PRINTER_INFO_LEVEL *printer, uint32 level) +{ + uint32 result; + NT_PRINTER_INFO_LEVEL_2 *info2; + + DEBUG(106,("Dumping printer at level [%d]\n", level)); + + switch (level) { + case 2: + { + if (printer->info_2 == NULL) + result=5; + else + { + info2=printer->info_2; + + DEBUGADD(106,("attributes:[%d]\n", info2->attributes)); + DEBUGADD(106,("priority:[%d]\n", info2->priority)); + DEBUGADD(106,("default_priority:[%d]\n", info2->default_priority)); + DEBUGADD(106,("starttime:[%d]\n", info2->starttime)); + DEBUGADD(106,("untiltime:[%d]\n", info2->untiltime)); + DEBUGADD(106,("status:[%d]\n", info2->status)); + DEBUGADD(106,("cjobs:[%d]\n", info2->cjobs)); + DEBUGADD(106,("averageppm:[%d]\n", info2->averageppm)); + DEBUGADD(106,("changeid:[%d]\n", info2->changeid)); + DEBUGADD(106,("c_setprinter:[%d]\n", info2->c_setprinter)); + DEBUGADD(106,("setuptime:[%d]\n", info2->setuptime)); + + DEBUGADD(106,("servername:[%s]\n", info2->servername)); + DEBUGADD(106,("printername:[%s]\n", info2->printername)); + DEBUGADD(106,("sharename:[%s]\n", info2->sharename)); + DEBUGADD(106,("portname:[%s]\n", info2->portname)); + DEBUGADD(106,("drivername:[%s]\n", info2->drivername)); + DEBUGADD(106,("comment:[%s]\n", info2->comment)); + DEBUGADD(106,("location:[%s]\n", info2->location)); + DEBUGADD(106,("sepfile:[%s]\n", info2->sepfile)); + DEBUGADD(106,("printprocessor:[%s]\n", info2->printprocessor)); + DEBUGADD(106,("datatype:[%s]\n", info2->datatype)); + DEBUGADD(106,("parameters:[%s]\n", info2->parameters)); + result=0; + } + break; + } + default: + DEBUGADD(106,("dump_a_printer: Level %u not implemented\n", (unsigned int)level )); + result=1; + break; + } + + return result; +} + +/**************************************************************************** + Update the changeid time. + This is SO NASTY as some drivers need this to change, others need it + static. This value will change every second, and I must hope that this + is enough..... DON'T CHANGE THIS CODE WITHOUT A TEST MATRIX THE SIZE OF + UTAH ! JRA. +****************************************************************************/ + +static uint32 rev_changeid(void) +{ + struct timeval tv; + + get_process_uptime(&tv); + +#if 1 /* JERRY */ + /* Return changeid as msec since spooler restart */ + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +#else + /* + * This setting seems to work well but is too untested + * to replace the above calculation. Left in for experiementation + * of the reader --jerry (Tue Mar 12 09:15:05 CST 2002) + */ + return tv.tv_sec * 10 + tv.tv_usec / 100000; +#endif +} + + +/* + * The function below are the high level ones. + * only those ones must be called from the spoolss code. + * JFM. + */ + +/**************************************************************************** + Modify a printer. This is called from SETPRINTERDATA/DELETEPRINTERDATA. +****************************************************************************/ + +WERROR mod_a_printer(NT_PRINTER_INFO_LEVEL *printer, uint32 level) +{ + WERROR result; + + dump_a_printer(printer, level); + + switch (level) { + case 2: + { + /* + * Update the changestamp. Emperical tests show that the + * ChangeID is always updated,but c_setprinter is + * global spooler variable (not per printer). + */ + + /* ChangeID **must** be increasing over the lifetime + of client's spoolss service in order for the + client's cache to show updates */ + + printer->info_2->changeid = rev_changeid(); + + /* + * Because one day someone will ask: + * NT->NT An admin connection to a remote + * printer show changes imeediately in + * the properities dialog + * + * A non-admin connection will only show the + * changes after viewing the properites page + * 2 times. Seems to be related to a + * race condition in the client between the spooler + * updating the local cache and the Explorer.exe GUI + * actually displaying the properties. + * + * This is fixed in Win2k. admin/non-admin + * connections both display changes immediately. + * + * 14/12/01 --jerry + */ + + result=update_a_printer_2(printer->info_2); + break; + } + default: + result=WERR_UNKNOWN_LEVEL; + break; + } + + return result; +} + +/**************************************************************************** + Initialize printer devmode & data with previously saved driver init values. +****************************************************************************/ + +static bool set_driver_init_2( NT_PRINTER_INFO_LEVEL_2 *info_ptr ) +{ + int len = 0; + char *key = NULL; + TDB_DATA dbuf; + NT_PRINTER_INFO_LEVEL_2 info; + + + ZERO_STRUCT(info); + + /* + * Delete any printer data 'values' already set. When called for driver + * replace, there will generally be some, but during an add printer, there + * should not be any (if there are delete them). + */ + + if ( info_ptr->data ) + delete_all_printer_data( info_ptr, "" ); + + if (asprintf(&key, "%s%s", DRIVER_INIT_PREFIX, + info_ptr->drivername) < 0) { + return false; + } + + dbuf = tdb_fetch_bystring(tdb_drivers, key); + if (!dbuf.dptr) { + /* + * When changing to a driver that has no init info in the tdb, remove + * the previous drivers init info and leave the new on blank. + */ + free_nt_devicemode(&info_ptr->devmode); + SAFE_FREE(key); + return false; + } + + SAFE_FREE(key); + /* + * Get the saved DEVMODE.. + */ + + len += unpack_devicemode(&info.devmode,dbuf.dptr+len, dbuf.dsize-len); + + /* + * The saved DEVMODE contains the devicename from the printer used during + * the initialization save. Change it to reflect the new printer. + */ + + if ( info.devmode ) { + ZERO_STRUCT(info.devmode->devicename); + fstrcpy(info.devmode->devicename, info_ptr->printername); + } + + /* + * NT/2k does not change out the entire DeviceMode of a printer + * when changing the driver. Only the driverextra, private, & + * driverversion fields. --jerry (Thu Mar 14 08:58:43 CST 2002) + * + * Later examination revealed that Windows NT/2k does reset the + * the printer's device mode, bit **only** when you change a + * property of the device mode such as the page orientation. + * --jerry + */ + + + /* Bind the saved DEVMODE to the new the printer */ + + free_nt_devicemode(&info_ptr->devmode); + info_ptr->devmode = info.devmode; + + DEBUG(10,("set_driver_init_2: Set printer [%s] init %s DEVMODE for driver [%s]\n", + info_ptr->printername, info_ptr->devmode?"VALID":"NULL", info_ptr->drivername)); + + /* Add the printer data 'values' to the new printer */ + + if ( !(info_ptr->data = TALLOC_ZERO_P( info_ptr, NT_PRINTER_DATA )) ) { + DEBUG(0,("set_driver_init_2: talloc() failed!\n")); + return False; + } + + len += unpack_values( info_ptr->data, dbuf.dptr+len, dbuf.dsize-len ); + + SAFE_FREE(dbuf.dptr); + + return true; +} + +/**************************************************************************** + Initialize printer devmode & data with previously saved driver init values. + When a printer is created using AddPrinter, the drivername bound to the + printer is used to lookup previously saved driver initialization info, which + is bound to the new printer. +****************************************************************************/ + +bool set_driver_init(NT_PRINTER_INFO_LEVEL *printer, uint32 level) +{ + bool result = False; + + switch (level) { + case 2: + result = set_driver_init_2(printer->info_2); + break; + + default: + DEBUG(0,("set_driver_init: Programmer's error! Unknown driver_init level [%d]\n", + level)); + break; + } + + return result; +} + +/**************************************************************************** + Delete driver init data stored for a specified driver +****************************************************************************/ + +bool del_driver_init(char *drivername) +{ + char *key; + bool ret; + + if (!drivername || !*drivername) { + DEBUG(3,("del_driver_init: No drivername specified!\n")); + return false; + } + + if (asprintf(&key, "%s%s", DRIVER_INIT_PREFIX, drivername) < 0) { + return false; + } + + DEBUG(6,("del_driver_init: Removing driver init data for [%s]\n", + drivername)); + + ret = (tdb_delete_bystring(tdb_drivers, key) == 0); + SAFE_FREE(key); + return ret; +} + +/**************************************************************************** + Pack up the DEVMODE and values for a printer into a 'driver init' entry + in the tdb. Note: this is different from the driver entry and the printer + entry. There should be a single driver init entry for each driver regardless + of whether it was installed from NT or 2K. Technically, they should be + different, but they work out to the same struct. +****************************************************************************/ + +static uint32 update_driver_init_2(NT_PRINTER_INFO_LEVEL_2 *info) +{ + char *key = NULL; + uint8 *buf; + int buflen, len, ret; + int retlen; + TDB_DATA dbuf; + + buf = NULL; + buflen = 0; + + again: + len = 0; + len += pack_devicemode(info->devmode, buf+len, buflen-len); + + retlen = pack_values( info->data, buf+len, buflen-len ); + if (retlen == -1) { + ret = -1; + goto done; + } + len += retlen; + + if (buflen < len) { + buf = (uint8 *)SMB_REALLOC(buf, len); + if (!buf) { + DEBUG(0, ("update_driver_init_2: failed to enlarge buffer!\n")); + ret = -1; + goto done; + } + buflen = len; + goto again; + } + + SAFE_FREE(key); + if (asprintf(&key, "%s%s", DRIVER_INIT_PREFIX, info->drivername) < 0) { + ret = (uint32)-1; + goto done; + } + + dbuf.dptr = buf; + dbuf.dsize = len; + + ret = tdb_store_bystring(tdb_drivers, key, dbuf, TDB_REPLACE); + +done: + if (ret == -1) + DEBUG(8, ("update_driver_init_2: error updating printer init to tdb on disk\n")); + + SAFE_FREE(buf); + + DEBUG(10,("update_driver_init_2: Saved printer [%s] init DEVMODE & values for driver [%s]\n", + info->sharename, info->drivername)); + + return ret; +} + +/**************************************************************************** + Update (i.e. save) the driver init info (DEVMODE and values) for a printer +****************************************************************************/ + +static uint32 update_driver_init(NT_PRINTER_INFO_LEVEL *printer, uint32 level) +{ + uint32 result; + + dump_a_printer(printer, level); + + switch (level) { + case 2: + result = update_driver_init_2(printer->info_2); + break; + default: + result = 1; + break; + } + + return result; +} + +/**************************************************************************** + Convert the printer data value, a REG_BINARY array, into an initialization + DEVMODE. Note: the array must be parsed as if it was a DEVMODE in an rpc... + got to keep the endians happy :). +****************************************************************************/ + +static bool convert_driver_init( TALLOC_CTX *ctx, NT_DEVICEMODE *nt_devmode, uint8 *data, uint32 data_len ) +{ + bool result = False; + prs_struct ps; + DEVICEMODE devmode; + + ZERO_STRUCT(devmode); + + prs_init_empty(&ps, ctx, UNMARSHALL); + ps.data_p = (char *)data; + ps.buffer_size = data_len; + + if (spoolss_io_devmode("phantom DEVMODE", &ps, 0, &devmode)) + result = convert_devicemode("", &devmode, &nt_devmode); + else + DEBUG(10,("convert_driver_init: error parsing DEVMODE\n")); + + return result; +} + +/**************************************************************************** + Set the DRIVER_INIT info in the tdb. Requires Win32 client code that: + + 1. Use the driver's config DLL to this UNC printername and: + a. Call DrvPrintEvent with PRINTER_EVENT_INITIALIZE + b. Call DrvConvertDevMode with CDM_DRIVER_DEFAULT to get default DEVMODE + 2. Call SetPrinterData with the 'magic' key and the DEVMODE as data. + + The last step triggers saving the "driver initialization" information for + this printer into the tdb. Later, new printers that use this driver will + have this initialization information bound to them. This simulates the + driver initialization, as if it had run on the Samba server (as it would + have done on NT). + + The Win32 client side code requirement sucks! But until we can run arbitrary + Win32 printer driver code on any Unix that Samba runs on, we are stuck with it. + + It would have been easier to use SetPrinter because all the UNMARSHALLING of + the DEVMODE is done there, but 2K/XP clients do not set the DEVMODE... think + about it and you will realize why. JRR 010720 +****************************************************************************/ + +static WERROR save_driver_init_2(NT_PRINTER_INFO_LEVEL *printer, uint8 *data, uint32 data_len ) +{ + WERROR status = WERR_OK; + TALLOC_CTX *ctx = NULL; + NT_DEVICEMODE *nt_devmode = NULL; + NT_DEVICEMODE *tmp_devmode = printer->info_2->devmode; + + /* + * When the DEVMODE is already set on the printer, don't try to unpack it. + */ + DEBUG(8,("save_driver_init_2: Enter...\n")); + + if ( !printer->info_2->devmode && data_len ) { + /* + * Set devmode on printer info, so entire printer initialization can be + * saved to tdb. + */ + + if ((ctx = talloc_init("save_driver_init_2")) == NULL) + return WERR_NOMEM; + + if ((nt_devmode = SMB_MALLOC_P(NT_DEVICEMODE)) == NULL) { + status = WERR_NOMEM; + goto done; + } + + ZERO_STRUCTP(nt_devmode); + + /* + * The DEVMODE is held in the 'data' component of the param in raw binary. + * Convert it to to a devmode structure + */ + if ( !convert_driver_init( ctx, nt_devmode, data, data_len )) { + DEBUG(10,("save_driver_init_2: error converting DEVMODE\n")); + status = WERR_INVALID_PARAM; + goto done; + } + + printer->info_2->devmode = nt_devmode; + } + + /* + * Pack up and add (or update) the DEVMODE and any current printer data to + * a 'driver init' element in the tdb + * + */ + + if ( update_driver_init(printer, 2) != 0 ) { + DEBUG(10,("save_driver_init_2: error updating DEVMODE\n")); + status = WERR_NOMEM; + goto done; + } + + /* + * If driver initialization info was successfully saved, set the current + * printer to match it. This allows initialization of the current printer + * as well as the driver. + */ + status = mod_a_printer(printer, 2); + if (!W_ERROR_IS_OK(status)) { + DEBUG(10,("save_driver_init_2: error setting DEVMODE on printer [%s]\n", + printer->info_2->printername)); + } + + done: + talloc_destroy(ctx); + free_nt_devicemode( &nt_devmode ); + + printer->info_2->devmode = tmp_devmode; + + return status; +} + +/**************************************************************************** + Update the driver init info (DEVMODE and specifics) for a printer +****************************************************************************/ + +WERROR save_driver_init(NT_PRINTER_INFO_LEVEL *printer, uint32 level, uint8 *data, uint32 data_len) +{ + WERROR status = WERR_OK; + + switch (level) { + case 2: + status = save_driver_init_2( printer, data, data_len ); + break; + default: + status = WERR_UNKNOWN_LEVEL; + break; + } + + return status; +} + +/**************************************************************************** + Get a NT_PRINTER_INFO_LEVEL struct. It returns malloced memory. + + Previously the code had a memory allocation problem because it always + used the TALLOC_CTX from the Printer_entry*. This context lasts + as a long as the original handle is open. So if the client made a lot + of getprinter[data]() calls, the memory usage would climb. Now we use + a short lived TALLOC_CTX for printer_info_2 objects returned. We + still use the Printer_entry->ctx for maintaining the cache copy though + since that object must live as long as the handle by definition. + --jerry + +****************************************************************************/ + +static WERROR get_a_printer_internal( Printer_entry *print_hnd, NT_PRINTER_INFO_LEVEL **pp_printer, uint32 level, + const char *sharename, bool get_loc_com) +{ + WERROR result; + fstring servername; + + DEBUG(10,("get_a_printer: [%s] level %u\n", sharename, (unsigned int)level)); + + if ( !(*pp_printer = TALLOC_ZERO_P(NULL, NT_PRINTER_INFO_LEVEL)) ) { + DEBUG(0,("get_a_printer: talloc() fail.\n")); + return WERR_NOMEM; + } + + switch (level) { + case 2: + if ( !((*pp_printer)->info_2 = TALLOC_ZERO_P(*pp_printer, NT_PRINTER_INFO_LEVEL_2)) ) { + DEBUG(0,("get_a_printer: talloc() fail.\n")); + TALLOC_FREE( *pp_printer ); + return WERR_NOMEM; + } + + if ( print_hnd ) + fstrcpy( servername, print_hnd->servername ); + else { + fstrcpy( servername, "%L" ); + standard_sub_basic( "", "", servername, + sizeof(servername)-1 ); + } + + result = get_a_printer_2( (*pp_printer)->info_2, + servername, sharename, get_loc_com); + + /* we have a new printer now. Save it with this handle */ + + if ( !W_ERROR_IS_OK(result) ) { + TALLOC_FREE( *pp_printer ); + DEBUG(10,("get_a_printer: [%s] level %u returning %s\n", + sharename, (unsigned int)level, dos_errstr(result))); + return result; + } + + dump_a_printer( *pp_printer, level); + + break; + + default: + TALLOC_FREE( *pp_printer ); + return WERR_UNKNOWN_LEVEL; + } + + return WERR_OK; +} + +WERROR get_a_printer( Printer_entry *print_hnd, + NT_PRINTER_INFO_LEVEL **pp_printer, + uint32 level, + const char *sharename) +{ + return get_a_printer_internal(print_hnd, pp_printer, level, + sharename, true); +} + +WERROR get_a_printer_search( Printer_entry *print_hnd, + NT_PRINTER_INFO_LEVEL **pp_printer, + uint32 level, + const char *sharename) +{ + return get_a_printer_internal(print_hnd, pp_printer, level, + sharename, false); +} + +/**************************************************************************** + Deletes a NT_PRINTER_INFO_LEVEL struct. +****************************************************************************/ + +uint32 free_a_printer(NT_PRINTER_INFO_LEVEL **pp_printer, uint32 level) +{ + NT_PRINTER_INFO_LEVEL *printer = *pp_printer; + + if ( !printer ) + return 0; + + switch (level) { + case 2: + if ( printer->info_2 ) + free_nt_printer_info_level_2(&printer->info_2); + break; + + default: + DEBUG(0,("free_a_printer: unknown level! [%d]\n", level )); + return 1; + } + + TALLOC_FREE(*pp_printer); + + return 0; +} + +/**************************************************************************** +****************************************************************************/ +uint32 add_a_printer_driver(NT_PRINTER_DRIVER_INFO_LEVEL driver, uint32 level) +{ + uint32 result; + DEBUG(104,("adding a printer at level [%d]\n", level)); + dump_a_printer_driver(driver, level); + + switch (level) { + case 3: + result=add_a_printer_driver_3(driver.info_3); + break; + + case 6: + result=add_a_printer_driver_6(driver.info_6); + break; + + default: + result=1; + break; + } + + return result; +} +/**************************************************************************** +****************************************************************************/ + +WERROR get_a_printer_driver(NT_PRINTER_DRIVER_INFO_LEVEL *driver, uint32 level, + fstring drivername, const char *architecture, uint32 version) +{ + WERROR result; + + switch (level) { + case 3: + /* Sometime we just want any version of the driver */ + + if ( version == DRIVER_ANY_VERSION ) { + /* look for Win2k first and then for NT4 */ + result = get_a_printer_driver_3(&driver->info_3, drivername, + architecture, 3); + + if ( !W_ERROR_IS_OK(result) ) { + result = get_a_printer_driver_3( &driver->info_3, + drivername, architecture, 2 ); + } + } else { + result = get_a_printer_driver_3(&driver->info_3, drivername, + architecture, version); + } + break; + + default: + result=W_ERROR(1); + break; + } + + if (W_ERROR_IS_OK(result)) + dump_a_printer_driver(*driver, level); + + return result; +} + +/**************************************************************************** +****************************************************************************/ +uint32 free_a_printer_driver(NT_PRINTER_DRIVER_INFO_LEVEL driver, uint32 level) +{ + uint32 result; + + switch (level) { + case 3: + { + NT_PRINTER_DRIVER_INFO_LEVEL_3 *info3; + if (driver.info_3 != NULL) + { + info3=driver.info_3; + SAFE_FREE(info3->dependentfiles); + ZERO_STRUCTP(info3); + SAFE_FREE(info3); + result=0; + } else { + result=4; + } + break; + } + case 6: + { + NT_PRINTER_DRIVER_INFO_LEVEL_6 *info6; + if (driver.info_6 != NULL) { + info6=driver.info_6; + SAFE_FREE(info6->dependentfiles); + SAFE_FREE(info6->previousnames); + ZERO_STRUCTP(info6); + SAFE_FREE(info6); + result=0; + } else { + result=4; + } + break; + } + default: + result=1; + break; + } + return result; +} + + +/**************************************************************************** + Determine whether or not a particular driver is currently assigned + to a printer +****************************************************************************/ + +bool printer_driver_in_use ( NT_PRINTER_DRIVER_INFO_LEVEL_3 *info_3 ) +{ + int snum; + int n_services = lp_numservices(); + NT_PRINTER_INFO_LEVEL *printer = NULL; + bool in_use = False; + + if ( !info_3 ) + 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_print_ok(snum) ) ) + continue; + + if ( !W_ERROR_IS_OK(get_a_printer(NULL, &printer, 2, lp_servicename(snum))) ) + continue; + + if ( strequal(info_3->name, printer->info_2->drivername) ) + in_use = True; + + free_a_printer( &printer, 2 ); + } + + DEBUG(10,("printer_driver_in_use: Completed search through ntprinters.tdb...\n")); + + if ( in_use ) { + NT_PRINTER_DRIVER_INFO_LEVEL d; + WERROR werr; + + DEBUG(5,("printer_driver_in_use: driver \"%s\" is currently in use\n", info_3->name)); + + /* we can still remove the driver if there is one of + "Windows NT x86" version 2 or 3 left */ + + if ( !strequal( "Windows NT x86", info_3->environment ) ) { + werr = get_a_printer_driver( &d, 3, info_3->name, "Windows NT x86", DRIVER_ANY_VERSION ); + } + else { + switch ( info_3->cversion ) { + case 2: + werr = get_a_printer_driver( &d, 3, info_3->name, "Windows NT x86", 3 ); + break; + case 3: + werr = get_a_printer_driver( &d, 3, info_3->name, "Windows NT x86", 2 ); + break; + default: + DEBUG(0,("printer_driver_in_use: ERROR! unknown driver version (%d)\n", + info_3->cversion)); + werr = WERR_UNKNOWN_PRINTER_DRIVER; + break; + } + } + + /* now check the error code */ + + if ( W_ERROR_IS_OK(werr) ) { + /* it's ok to remove the driver, we have other architctures left */ + in_use = False; + free_a_printer_driver( d, 3 ); + } + } + + /* 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( char* file, NT_PRINTER_DRIVER_INFO_LEVEL_3 *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->driverpath) ) + return True; + + if ( strequal(file, info->datafile) ) + return True; + + if ( strequal(file, info->configfile) ) + return True; + + if ( strequal(file, info->helpfile) ) + return True; + + /* see of there are any dependent files to examine */ + + if ( !info->dependentfiles ) + return False; + + while ( *info->dependentfiles[i] ) { + if ( strequal(file, info->dependentfiles[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( fstring files[], int idx ) +{ + + /* bump everything down a slot */ + + while( *files[idx+1] ) { + fstrcpy( files[idx], files[idx+1] ); + idx++; + } + + *files[idx] = '\0'; + + return; +} + +/********************************************************************** + Check if any of the files used by src are also used by drv + *********************************************************************/ + +static bool trim_overlap_drv_files( NT_PRINTER_DRIVER_INFO_LEVEL_3 *src, + NT_PRINTER_DRIVER_INFO_LEVEL_3 *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->driverpath, drv) ) { + in_use = True; + DEBUG(10,("Removing driverfile [%s] from list\n", src->driverpath)); + fstrcpy( src->driverpath, "" ); + } + + if ( drv_file_in_use(src->datafile, drv) ) { + in_use = True; + DEBUG(10,("Removing datafile [%s] from list\n", src->datafile)); + fstrcpy( src->datafile, "" ); + } + + if ( drv_file_in_use(src->configfile, drv) ) { + in_use = True; + DEBUG(10,("Removing configfile [%s] from list\n", src->configfile)); + fstrcpy( src->configfile, "" ); + } + + if ( drv_file_in_use(src->helpfile, drv) ) { + in_use = True; + DEBUG(10,("Removing helpfile [%s] from list\n", src->helpfile)); + fstrcpy( src->helpfile, "" ); + } + + /* are there any dependentfiles to examine? */ + + if ( !src->dependentfiles ) + return in_use; + + while ( *src->dependentfiles[i] ) { + if ( drv_file_in_use(src->dependentfiles[i], drv) ) { + in_use = True; + DEBUG(10,("Removing [%s] from dependent file list\n", src->dependentfiles[i])); + trim_dependent_file( src->dependentfiles, 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 ( NT_PRINTER_DRIVER_INFO_LEVEL_3 *info ) +{ + int i; + int ndrivers; + uint32 version; + fstring *list = NULL; + NT_PRINTER_DRIVER_INFO_LEVEL driver; + bool in_use = false; + + if ( !info ) + return False; + + version = info->cversion; + + /* loop over all driver versions */ + + DEBUG(5,("printer_driver_files_in_use: Beginning search through ntdrivers.tdb...\n")); + + /* get the list of drivers */ + + list = NULL; + ndrivers = get_ntdrivers(&list, info->environment, version); + + DEBUGADD(4,("we have:[%d] drivers in environment [%s] and version [%d]\n", + ndrivers, info->environment, version)); + + /* check each driver for overlap in files */ + + for (i=0; i<ndrivers; i++) { + DEBUGADD(5,("\tdriver: [%s]\n", list[i])); + + ZERO_STRUCT(driver); + + if ( !W_ERROR_IS_OK(get_a_printer_driver(&driver, 3, list[i], info->environment, version)) ) { + SAFE_FREE(list); + 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->name, driver.info_3->name) ) { + if ( trim_overlap_drv_files(info, driver.info_3) ) { + /* mz: Do not instantly return - + * we need to ensure this file isn't + * also in use by other drivers. */ + in_use = true; + } + } + + free_a_printer_driver(driver, 3); + } + + SAFE_FREE(list); + + DEBUG(5,("printer_driver_files_in_use: Completed search through ntdrivers.tdb...\n")); + + driver.info_3 = info; + + if ( DEBUGLEVEL >= 20 ) + dump_a_printer_driver( driver, 3 ); + + return in_use; +} + +/**************************************************************************** + Actually delete the driver files. Make sure that + printer_driver_files_in_use() return False before calling + this. +****************************************************************************/ + +static bool delete_driver_files( NT_PRINTER_DRIVER_INFO_LEVEL_3 *info_3, struct current_user *user ) +{ + int i = 0; + char *s; + const char *file; + connection_struct *conn; + DATA_BLOB null_pw; + NTSTATUS nt_status; + fstring res_type; + SMB_STRUCT_STAT st; + + if ( !info_3 ) + return False; + + DEBUG(6,("delete_driver_files: deleting driver [%s] - version [%d]\n", info_3->name, info_3->cversion)); + + /* + * Connect to the print$ share under the same account as the + * user connected to the rpc pipe. Note we must be root to + * do this. + */ + + null_pw = data_blob_null; + fstrcpy(res_type, "A:"); + become_root(); + conn = make_connection_with_chdir( "print$", null_pw, res_type, user->vuid, &nt_status ); + unbecome_root(); + + if ( !conn ) { + DEBUG(0,("delete_driver_files: Unable to connect\n")); + return False; + } + + if ( !CAN_WRITE(conn) ) { + DEBUG(3,("delete_driver_files: Cannot delete print driver when [print$] is read-only\n")); + return False; + } + + /* Save who we are - we are temporarily becoming the connection user. */ + + if ( !become_user(conn, conn->vuid) ) { + DEBUG(0,("delete_driver_files: Can't become user!\n")); + return False; + } + + /* now delete the files; must strip the '\print$' string from + fron of path */ + + if ( *info_3->driverpath ) { + if ( (s = strchr( &info_3->driverpath[1], '\\' )) != NULL ) { + file = s; + driver_unix_convert(conn,file,&st); + DEBUG(10,("deleting driverfile [%s]\n", s)); + unlink_internals(conn, NULL, 0, file, False); + } + } + + if ( *info_3->configfile ) { + if ( (s = strchr( &info_3->configfile[1], '\\' )) != NULL ) { + file = s; + driver_unix_convert(conn,file,&st); + DEBUG(10,("deleting configfile [%s]\n", s)); + unlink_internals(conn, NULL, 0, file, False); + } + } + + if ( *info_3->datafile ) { + if ( (s = strchr( &info_3->datafile[1], '\\' )) != NULL ) { + file = s; + driver_unix_convert(conn,file,&st); + DEBUG(10,("deleting datafile [%s]\n", s)); + unlink_internals(conn, NULL, 0, file, False); + } + } + + if ( *info_3->helpfile ) { + if ( (s = strchr( &info_3->helpfile[1], '\\' )) != NULL ) { + file = s; + driver_unix_convert(conn,file,&st); + DEBUG(10,("deleting helpfile [%s]\n", s)); + unlink_internals(conn, NULL, 0, file, False); + } + } + + /* check if we are done removing files */ + + if ( info_3->dependentfiles ) { + while ( info_3->dependentfiles[i][0] ) { + char *p; + + /* bypass the "\print$" portion of the path */ + + if ( (p = strchr( info_3->dependentfiles[i]+1, '\\' )) != NULL ) { + file = p; + driver_unix_convert(conn,file,&st); + DEBUG(10,("deleting dependent file [%s]\n", file)); + unlink_internals(conn, NULL, 0, file, False); + } + + i++; + } + } + + unbecome_user(); + + return true; +} + +/**************************************************************************** + Remove a printer driver from the TDB. This assumes that the the driver was + previously looked up. + ***************************************************************************/ + +WERROR delete_printer_driver( NT_PRINTER_DRIVER_INFO_LEVEL_3 *info_3, struct current_user *user, + uint32 version, bool delete_files ) +{ + char *key = NULL; + const char *arch; + TDB_DATA dbuf; + NT_PRINTER_DRIVER_INFO_LEVEL ctr; + + /* delete the tdb data first */ + + arch = get_short_archi(info_3->environment); + if (!arch) { + return WERR_UNKNOWN_PRINTER_DRIVER; + } + if (asprintf(&key, "%s%s/%d/%s", DRIVERS_PREFIX, + arch, version, info_3->name) < 0) { + return WERR_NOMEM; + } + + DEBUG(5,("delete_printer_driver: key = [%s] delete_files = %s\n", + key, delete_files ? "TRUE" : "FALSE" )); + + ctr.info_3 = info_3; + dump_a_printer_driver( ctr, 3 ); + + /* check if the driver actually exists for this environment */ + + dbuf = tdb_fetch_bystring( tdb_drivers, key ); + if ( !dbuf.dptr ) { + DEBUG(8,("delete_printer_driver: Driver unknown [%s]\n", key)); + SAFE_FREE(key); + return WERR_UNKNOWN_PRINTER_DRIVER; + } + + SAFE_FREE( dbuf.dptr ); + + /* ok... the driver exists so the delete should return success */ + + if (tdb_delete_bystring(tdb_drivers, key) == -1) { + DEBUG (0,("delete_printer_driver: fail to delete %s!\n", key)); + SAFE_FREE(key); + return WERR_ACCESS_DENIED; + } + + /* + * now delete any associated files if delete_files == True + * even if this part failes, we return succes because the + * driver doesn not exist any more + */ + + if ( delete_files ) + delete_driver_files( info_3, user ); + + DEBUG(5,("delete_printer_driver: driver delete successful [%s]\n", key)); + SAFE_FREE(key); + + return WERR_OK; +} + +/**************************************************************************** + Store a security desc for a printer. +****************************************************************************/ + +WERROR nt_printing_setsec(const char *sharename, SEC_DESC_BUF *secdesc_ctr) +{ + SEC_DESC_BUF *new_secdesc_ctr = NULL; + SEC_DESC_BUF *old_secdesc_ctr = NULL; + prs_struct ps; + bool prs_init_done = false; + TALLOC_CTX *mem_ctx = NULL; + TDB_DATA kbuf; + WERROR status; + + mem_ctx = talloc_init("nt_printing_setsec"); + if (mem_ctx == NULL) + return WERR_NOMEM; + + /* The old owner and group sids of the security descriptor are not + present when new ACEs are added or removed by changing printer + permissions through NT. If they are NULL in the new security + descriptor then copy them over from the old one. */ + + if (!secdesc_ctr->sd->owner_sid || !secdesc_ctr->sd->group_sid) { + DOM_SID *owner_sid, *group_sid; + SEC_ACL *dacl, *sacl; + SEC_DESC *psd = NULL; + size_t size; + + if (!nt_printing_getsec(mem_ctx, sharename, &old_secdesc_ctr)) { + status = WERR_NOMEM; + goto out; + } + + /* Pick out correct owner and group sids */ + + owner_sid = secdesc_ctr->sd->owner_sid ? + secdesc_ctr->sd->owner_sid : + old_secdesc_ctr->sd->owner_sid; + + group_sid = secdesc_ctr->sd->group_sid ? + secdesc_ctr->sd->group_sid : + old_secdesc_ctr->sd->group_sid; + + dacl = secdesc_ctr->sd->dacl ? + secdesc_ctr->sd->dacl : + old_secdesc_ctr->sd->dacl; + + sacl = secdesc_ctr->sd->sacl ? + secdesc_ctr->sd->sacl : + old_secdesc_ctr->sd->sacl; + + /* Make a deep copy of the security descriptor */ + + psd = make_sec_desc(mem_ctx, secdesc_ctr->sd->revision, secdesc_ctr->sd->type, + owner_sid, group_sid, + sacl, + dacl, + &size); + + if (!psd) { + status = WERR_NOMEM; + goto out; + } + + new_secdesc_ctr = make_sec_desc_buf(mem_ctx, size, psd); + } + + if (!new_secdesc_ctr) { + new_secdesc_ctr = secdesc_ctr; + } + + /* Store the security descriptor in a tdb */ + + if (!prs_init(&ps, + (uint32)ndr_size_security_descriptor(new_secdesc_ctr->sd, 0) + + sizeof(SEC_DESC_BUF), mem_ctx, MARSHALL) ) { + status = WERR_NOMEM; + goto out; + } + + + prs_init_done = true; + + if (!sec_io_desc_buf("nt_printing_setsec", &new_secdesc_ctr, + &ps, 1)) { + status = WERR_BADFUNC; + goto out; + } + + kbuf = make_printers_secdesc_tdbkey(mem_ctx, sharename ); + + if (tdb_prs_store(tdb_printers, kbuf, &ps)==0) { + status = WERR_OK; + } else { + DEBUG(1,("Failed to store secdesc for %s\n", sharename)); + status = WERR_BADFUNC; + } + + /* Free malloc'ed memory */ + + out: + + if (prs_init_done) { + prs_mem_free(&ps); + } + if (mem_ctx) + talloc_destroy(mem_ctx); + return status; +} + +/**************************************************************************** + Construct a default security descriptor buffer for a printer. +****************************************************************************/ + +static SEC_DESC_BUF *construct_default_printer_sdb(TALLOC_CTX *ctx) +{ + SEC_ACE ace[5]; /* max number of ace entries */ + int i = 0; + SEC_ACCESS sa; + SEC_ACL *psa = NULL; + SEC_DESC_BUF *sdb = NULL; + SEC_DESC *psd = NULL; + DOM_SID adm_sid; + size_t sd_size; + + /* Create an ACE where Everyone is allowed to print */ + + init_sec_access(&sa, PRINTER_ACE_PRINT); + init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED, + sa, SEC_ACE_FLAG_CONTAINER_INHERIT); + + /* Add the domain admins group if we are a DC */ + + if ( IS_DC ) { + DOM_SID domadmins_sid; + + sid_copy(&domadmins_sid, get_global_sam_sid()); + sid_append_rid(&domadmins_sid, DOMAIN_GROUP_RID_ADMINS); + + init_sec_access(&sa, PRINTER_ACE_FULL_CONTROL); + init_sec_ace(&ace[i++], &domadmins_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, sa, + SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY); + init_sec_ace(&ace[i++], &domadmins_sid, SEC_ACE_TYPE_ACCESS_ALLOWED, + sa, SEC_ACE_FLAG_CONTAINER_INHERIT); + } + else if (secrets_fetch_domain_sid(lp_workgroup(), &adm_sid)) { + sid_append_rid(&adm_sid, DOMAIN_USER_RID_ADMIN); + + init_sec_access(&sa, PRINTER_ACE_FULL_CONTROL); + init_sec_ace(&ace[i++], &adm_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, sa, + SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY); + init_sec_ace(&ace[i++], &adm_sid, SEC_ACE_TYPE_ACCESS_ALLOWED, + sa, SEC_ACE_FLAG_CONTAINER_INHERIT); + } + + /* add BUILTIN\Administrators as FULL CONTROL */ + + init_sec_access(&sa, PRINTER_ACE_FULL_CONTROL); + init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators, + SEC_ACE_TYPE_ACCESS_ALLOWED, sa, + SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY); + init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators, + SEC_ACE_TYPE_ACCESS_ALLOWED, + sa, SEC_ACE_FLAG_CONTAINER_INHERIT); + + /* Make the security descriptor owned by the BUILTIN\Administrators */ + + /* The ACL revision number in rpc_secdesc.h differs from the one + created by NT when setting ACE entries in printer + descriptors. NT4 complains about the property being edited by a + NT5 machine. */ + + if ((psa = make_sec_acl(ctx, NT4_ACL_REVISION, i, ace)) != NULL) { + psd = make_sec_desc(ctx, SEC_DESC_REVISION, SEC_DESC_SELF_RELATIVE, + &global_sid_Builtin_Administrators, + &global_sid_Builtin_Administrators, + NULL, psa, &sd_size); + } + + if (!psd) { + DEBUG(0,("construct_default_printer_sd: Failed to make SEC_DESC.\n")); + return NULL; + } + + sdb = make_sec_desc_buf(ctx, sd_size, psd); + + DEBUG(4,("construct_default_printer_sdb: size = %u.\n", + (unsigned int)sd_size)); + + return sdb; +} + +/**************************************************************************** + Get a security desc for a printer. +****************************************************************************/ + +bool nt_printing_getsec(TALLOC_CTX *ctx, const char *sharename, SEC_DESC_BUF **secdesc_ctr) +{ + prs_struct ps; + TDB_DATA kbuf; + char *temp; + + if (strlen(sharename) > 2 && (temp = strchr(sharename + 2, '\\'))) { + sharename = temp + 1; + } + + ZERO_STRUCT(ps); + + /* Fetch security descriptor from tdb */ + + kbuf = make_printers_secdesc_tdbkey(ctx, sharename ); + + if (tdb_prs_fetch(tdb_printers, kbuf, &ps, ctx)!=0 || + !sec_io_desc_buf("nt_printing_getsec", secdesc_ctr, &ps, 1)) { + + prs_mem_free(&ps); + + DEBUG(4,("using default secdesc for %s\n", sharename)); + + if (!(*secdesc_ctr = construct_default_printer_sdb(ctx))) { + return False; + } + + /* Save default security descriptor for later */ + + if (!prs_init(&ps, (uint32)ndr_size_security_descriptor((*secdesc_ctr)->sd, 0) + + sizeof(SEC_DESC_BUF), ctx, MARSHALL)) + return False; + + if (sec_io_desc_buf("nt_printing_getsec", secdesc_ctr, &ps, 1)) { + tdb_prs_store(tdb_printers, kbuf, &ps); + } + + prs_mem_free(&ps); + + return True; + } + + prs_mem_free(&ps); + + /* If security descriptor is owned by S-1-1-0 and winbindd is up, + this security descriptor has been created when winbindd was + down. Take ownership of security descriptor. */ + + if (sid_equal((*secdesc_ctr)->sd->owner_sid, &global_sid_World)) { + DOM_SID owner_sid; + + /* Change sd owner to workgroup administrator */ + + if (secrets_fetch_domain_sid(lp_workgroup(), &owner_sid)) { + SEC_DESC_BUF *new_secdesc_ctr = NULL; + SEC_DESC *psd = NULL; + size_t size; + + /* Create new sd */ + + sid_append_rid(&owner_sid, DOMAIN_USER_RID_ADMIN); + + psd = make_sec_desc(ctx, (*secdesc_ctr)->sd->revision, (*secdesc_ctr)->sd->type, + &owner_sid, + (*secdesc_ctr)->sd->group_sid, + (*secdesc_ctr)->sd->sacl, + (*secdesc_ctr)->sd->dacl, + &size); + + if (!psd) { + return False; + } + + new_secdesc_ctr = make_sec_desc_buf(ctx, size, psd); + if (!new_secdesc_ctr) { + return False; + } + + /* Swap with other one */ + + *secdesc_ctr = new_secdesc_ctr; + + /* Set it */ + + nt_printing_setsec(sharename, *secdesc_ctr); + } + } + + if (DEBUGLEVEL >= 10) { + SEC_ACL *the_acl = (*secdesc_ctr)->sd->dacl; + int i; + + DEBUG(10, ("secdesc_ctr for %s has %d aces:\n", + sharename, the_acl->num_aces)); + + for (i = 0; i < the_acl->num_aces; i++) { + DEBUG(10, ("%s %d %d 0x%08x\n", + sid_string_dbg(&the_acl->aces[i].trustee), + the_acl->aces[i].type, the_acl->aces[i].flags, + the_acl->aces[i].access_mask)); + } + } + + return True; +} + +/* 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 existant struct +*/ + +/* + A printer and a printer driver are 2 different things. + NT manages them separatelly, Samba does the same. + Why ? Simply because it's easier and it makes sense ! + + Now explanation: You have 3 printers behind your samba server, + 2 of them are the same make and model (laser A and B). But laser B + has an 3000 sheet feeder and laser A doesn't such an option. + Your third printer is an old dot-matrix model for the accounting :-). + + If the /usr/local/samba/lib directory (default dir), you will have + 5 files to describe all of this. + + 3 files for the printers (1 by printer): + NTprinter_laser A + NTprinter_laser B + NTprinter_accounting + 2 files for the drivers (1 for the laser and 1 for the dot matrix) + NTdriver_printer model X + NTdriver_printer model Y + +jfm: I should use this comment for the text file to explain + same thing for the forms BTW. + Je devrais mettre mes commentaires en francais, ca serait mieux :-) + +*/ + +/* Convert generic access rights to printer object specific access rights. + It turns out that NT4 security descriptors use generic access rights and + NT5 the object specific ones. */ + +void map_printer_permissions(SEC_DESC *sd) +{ + int 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(SEC_DESC *sd) +{ + int 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 ans 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) + + ****************************************************************************/ +bool print_access_check(struct auth_serversupplied_info *server_info, int snum, + int access_type) +{ + SEC_DESC_BUF *secdesc = NULL; + uint32 access_granted; + NTSTATUS status; + bool result; + const char *pname; + TALLOC_CTX *mem_ctx = NULL; + SE_PRIV se_printop = SE_PRINT_OPERATOR; + + /* If user is NULL then use the current_user structure */ + + /* Always allow root or SE_PRINT_OPERATROR to do anything */ + + if (server_info->utok.uid == 0 + || user_has_privileges(server_info->ptok, &se_printop ) ) { + return True; + } + + /* Get printer name */ + + pname = PRINTERNAME(snum); + + if (!pname || !*pname) { + errno = EACCES; + return False; + } + + /* Get printer security descriptor */ + + if(!(mem_ctx = talloc_init("print_access_check"))) { + errno = ENOMEM; + return False; + } + + if (!nt_printing_getsec(mem_ctx, pname, &secdesc)) { + talloc_destroy(mem_ctx); + errno = ENOMEM; + return False; + } + + if (access_type == JOB_ACCESS_ADMINISTER) { + SEC_DESC_BUF *parent_secdesc = secdesc; + + /* Create a child security descriptor to check permissions + against. This is because print jobs are child objects + objects of a printer. */ + + secdesc = se_create_child_secdesc(mem_ctx, parent_secdesc->sd, False); + + if (!secdesc) { + talloc_destroy(mem_ctx); + errno = ENOMEM; + return False; + } + + map_job_permissions(secdesc->sd); + } else { + map_printer_permissions(secdesc->sd); + } + + /* Check access */ + result = se_access_check(secdesc->sd, server_info->ptok, access_type, + &access_granted, &status); + + DEBUG(4, ("access check was %s\n", result ? "SUCCESS" : "FAILURE")); + + /* see if we need to try the printer admin list */ + + if ((access_granted == 0) && + (token_contains_name_in_list(uidtoname(server_info->utok.uid), + NULL, NULL, server_info->ptok, + lp_printer_admin(snum)))) { + talloc_destroy(mem_ctx); + return True; + } + + talloc_destroy(mem_ctx); + + if (!result) { + errno = EACCES; + } + + return result; +} + +/**************************************************************************** + Check the time parameters allow a print operation. +*****************************************************************************/ + +bool print_time_access_check(const char *servicename) +{ + NT_PRINTER_INFO_LEVEL *printer = NULL; + bool ok = False; + time_t now = time(NULL); + struct tm *t; + uint32 mins; + + if (!W_ERROR_IS_OK(get_a_printer(NULL, &printer, 2, servicename))) + return False; + + if (printer->info_2->starttime == 0 && printer->info_2->untiltime == 0) + ok = True; + + t = gmtime(&now); + mins = (uint32)t->tm_hour*60 + (uint32)t->tm_min; + + if (mins >= printer->info_2->starttime && mins <= printer->info_2->untiltime) + ok = True; + + free_a_printer(&printer, 2); + + if (!ok) + errno = EACCES; + + return ok; +} + +/**************************************************************************** + Fill in the servername sent in the _spoolss_open_printer_ex() call +****************************************************************************/ + +char* get_server_name( Printer_entry *printer ) +{ + return printer->servername; +} + + diff --git a/source3/printing/pcap.c b/source3/printing/pcap.c new file mode 100644 index 0000000000..30cb254a29 --- /dev/null +++ b/source3/printing/pcap.c @@ -0,0 +1,263 @@ +/* + 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. + * + * 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" + + +typedef struct pcap_cache { + char *name; + char *comment; + struct pcap_cache *next; +} pcap_cache_t; + +static pcap_cache_t *pcap_cache = NULL; + +bool pcap_cache_add(const char *name, const char *comment) +{ + pcap_cache_t *p; + + if (name == NULL || ((p = SMB_MALLOC_P(pcap_cache_t)) == NULL)) + return False; + + p->name = SMB_STRDUP(name); + p->comment = (comment && *comment) ? SMB_STRDUP(comment) : NULL; + + p->next = pcap_cache; + pcap_cache = p; + + return True; +} + +static void pcap_cache_destroy(pcap_cache_t *cache) +{ + pcap_cache_t *p, *next; + + for (p = cache; p != NULL; p = next) { + next = p->next; + + SAFE_FREE(p->name); + SAFE_FREE(p->comment); + SAFE_FREE(p); + } +} + +bool pcap_cache_loaded(void) +{ + return (pcap_cache != NULL); +} + +void pcap_cache_reload(void) +{ + const char *pcap_name = lp_printcapname(); + bool pcap_reloaded = False; + pcap_cache_t *tmp_cache = NULL; + XFILE *pcap_file; + char *pcap_line; + + DEBUG(3, ("reloading printcap cache\n")); + + /* only go looking if no printcap name supplied */ + if (pcap_name == NULL || *pcap_name == 0) { + DEBUG(0, ("No printcap file name configured!\n")); + return; + } + + tmp_cache = pcap_cache; + pcap_cache = NULL; + +#ifdef HAVE_CUPS + if (strequal(pcap_name, "cups")) { + pcap_reloaded = cups_cache_reload(); + goto done; + } +#endif + +#ifdef HAVE_IPRINT + if (strequal(pcap_name, "iprint")) { + pcap_reloaded = iprint_cache_reload(); + goto done; + } +#endif + +#if defined(SYSV) || defined(HPUX) + if (strequal(pcap_name, "lpstat")) { + pcap_reloaded = sysv_cache_reload(); + goto done; + } +#endif + +#ifdef AIX + if (strstr_m(pcap_name, "/qconfig") != NULL) { + pcap_reloaded = aix_cache_reload(); + goto done; + } +#endif + + /* handle standard printcap - moved from pcap_printer_fn() */ + + if ((pcap_file = x_fopen(pcap_name, O_RDONLY, 0)) == NULL) { + DEBUG(0, ("Unable to open printcap file %s for read!\n", pcap_name)); + goto done; + } + + for (; (pcap_line = fgets_slash(NULL, 1024, pcap_file)) != NULL; safe_free(pcap_line)) { + char name[MAXPRINTERLEN+1]; + char comment[62]; + char *p, *q; + + if (*pcap_line == '#' || *pcap_line == 0) + 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 (*name = *comment = 0, p = pcap_line; p != NULL; p = q) { + bool has_punctuation; + + if ((q = strchr_m(p, '|')) != NULL) + *q++ = 0; + + has_punctuation = (strchr_m(p, ' ') || + strchr_m(p, '\t') || + strchr_m(p, '(') || + strchr_m(p, ')')); + + if (strlen(p) > strlen(comment) && has_punctuation) { + strlcpy(comment, p, sizeof(comment)); + continue; + } + + if (strlen(p) <= MAXPRINTERLEN && + strlen(p) > strlen(name) && !has_punctuation) { + if (!*comment) { + strlcpy(comment, name, sizeof(comment)); + } + strlcpy(name, p, sizeof(name)); + continue; + } + + if (!strchr_m(comment, ' ') && + strlen(p) > strlen(comment)) { + strlcpy(comment, p, sizeof(comment)); + continue; + } + } + + comment[60] = 0; + name[MAXPRINTERLEN] = 0; + + if (*name && !pcap_cache_add(name, comment)) { + x_fclose(pcap_file); + goto done; + } + } + + x_fclose(pcap_file); + pcap_reloaded = True; + +done: + DEBUG(3, ("reload status: %s\n", (pcap_reloaded) ? "ok" : "error")); + + if (pcap_reloaded) + pcap_cache_destroy(tmp_cache); + else { + pcap_cache_destroy(pcap_cache); + pcap_cache = tmp_cache; + } + + return; +} + + +bool pcap_printername_ok(const char *printername) +{ + pcap_cache_t *p; + + for (p = pcap_cache; p != NULL; p = p->next) + if (strequal(p->name, printername)) + return True; + + return False; +} + +/*************************************************************************** +run a function on each printer name in the printcap file. The function is +passed the primary name and the comment (if possible). Note the fn() takes +strings in DOS codepage. This means the xxx_printer_fn() calls must be fixed +to return DOS codepage. FIXME !! JRA. + +XXX: I'm not sure if this comment still applies.. Anyone? -Rob +***************************************************************************/ +void pcap_printer_fn(void (*fn)(char *, char *)) +{ + pcap_cache_t *p; + + for (p = pcap_cache; p != NULL; p = p->next) + fn(p->name, p->comment); + + return; +} diff --git a/source3/printing/print_aix.c b/source3/printing/print_aix.c new file mode 100644 index 0000000000..57590cc39e --- /dev/null +++ b/source3/printing/print_aix.c @@ -0,0 +1,123 @@ +/* + 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" + +#ifdef AIX +bool aix_cache_reload(void) +{ + int iEtat; + XFILE *pfile; + char *line = NULL, *p; + char *name = NULL; + TALLOC_CTX *ctx = talloc_init("aix_cache_reload"); + + if (!ctx) { + return false; + } + + DEBUG(5, ("reloading aix printcap cache\n")); + + if ((pfile = x_fopen(lp_printcapname(), O_RDONLY, 0)) == 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>: */ + for (;(line = fgets_slash(NULL, 1024, pfile)); safe_free(line)) { + if (*line == '*' || *line == 0) + continue; + + switch (iEtat) { + case 0: /* locate an entry */ + if (*line == '\t' || *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) { + safe_free(line); + x_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; + if (!pcap_cache_add(name, NULL)) { + safe_free(line); + x_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; + if (!pcap_cache_add(name, NULL)) { + safe_free(line); + x_fclose(pfile); + TALLOC_FREE(ctx); + return false; + } + continue; + } + break; + } + } + + x_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 0000000000..593c5c7a1f --- /dev/null +++ b/source3/printing/print_cups.c @@ -0,0 +1,1347 @@ +/* + * Support code for the Common UNIX Printing System ("CUPS") + * + * Copyright 1999-2003 by Michael R Sweet. + * + * 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" + +#ifdef HAVE_CUPS +#include <cups/cups.h> +#include <cups/language.h> + +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(void) +{ + http_t *http; + char *server, *p; + int port; + + if (lp_cups_server() != NULL && strlen(lp_cups_server()) > 0) { + server = smb_xstrdup(lp_cups_server()); + } else { + server = smb_xstrdup(cupsServer()); + } + + 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)); + + if ((http = httpConnect(server, port)) == NULL) { + DEBUG(0,("Unable to connect to CUPS server %s:%d - %s\n", + server, port, strerror(errno))); + SAFE_FREE(server); + return NULL; + } + + SAFE_FREE(server); + return http; +} + +bool cups_cache_reload(void) +{ + 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 *name, /* printer-name attribute */ + *info; /* printer-info attribute */ + static const char *requested[] =/* Requested attributes */ + { + "printer-name", + "printer-info" + }; + bool ret = False; + + DEBUG(5, ("reloading cups printcap cache\n")); + + /* + * Make sure we don't ask for passwords... + */ + + cupsSetPasswordCB(cups_passwd_cb); + + /* + * Try to connect to the server... + */ + + if ((http = cups_connect()) == NULL) { + goto out; + } + + /* + * Build a CUPS_GET_PRINTERS request, which requires the following + * attributes: + * + * attributes-charset + * attributes-natural-language + * requested-attributes + */ + + request = ippNew(); + + request->request.op.operation_id = CUPS_GET_PRINTERS; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + 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); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/")) == NULL) { + DEBUG(0,("Unable to get printer list - %s\n", + ippErrorString(cupsLastError()))); + goto out; + } + + for (attr = response->attrs; attr != NULL;) { + /* + * Skip leading attributes until we hit a printer... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Pull the needed attributes from this printer... + */ + + name = NULL; + info = NULL; + + while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER) { + if (strcmp(attr->name, "printer-name") == 0 && + attr->value_tag == IPP_TAG_NAME) + name = attr->values[0].string.text; + + if (strcmp(attr->name, "printer-info") == 0 && + attr->value_tag == IPP_TAG_TEXT) + info = attr->values[0].string.text; + + attr = attr->next; + } + + /* + * See if we have everything needed... + */ + + if (name == NULL) + break; + + if (!pcap_cache_add(name, info)) { + 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(); + + request->request.op.operation_id = CUPS_GET_CLASSES; + request->request.op.request_id = 1; + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + 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); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/")) == NULL) { + DEBUG(0,("Unable to get printer list - %s\n", + ippErrorString(cupsLastError()))); + goto out; + } + + for (attr = response->attrs; attr != NULL;) { + /* + * Skip leading attributes until we hit a printer... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Pull the needed attributes from this printer... + */ + + name = NULL; + info = NULL; + + while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER) { + if (strcmp(attr->name, "printer-name") == 0 && + attr->value_tag == IPP_TAG_NAME) + name = attr->values[0].string.text; + + if (strcmp(attr->name, "printer-info") == 0 && + attr->value_tag == IPP_TAG_TEXT) + info = attr->values[0].string.text; + + attr = attr->next; + } + + /* + * See if we have everything needed... + */ + + if (name == NULL) + break; + + if (!pcap_cache_add(name, info)) { + goto out; + } + } + + ret = True; + + out: + if (response) + ippDelete(response); + + if (language) + cupsLangFree(language); + + if (http) + httpClose(http); + + return ret; +} + + +/* + * 'cups_job_delete()' - Delete a job. + */ + +static int cups_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 */ + + + 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()) == 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(); + + request->request.op.operation_id = IPP_CANCEL_JOB; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/jobs/%d", pjob->sysjob); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", + NULL, pjob->user); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) { + if (response->request.status.status_code >= 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; +} + + +/* + * 'cups_job_pause()' - Pause a job. + */ + +static int cups_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 */ + + + 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()) == 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(); + + request->request.op.operation_id = IPP_HOLD_JOB; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/jobs/%d", pjob->sysjob); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", + NULL, pjob->user); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) { + if (response->request.status.status_code >= 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; +} + + +/* + * 'cups_job_resume()' - Resume a paused job. + */ + +static int cups_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 */ + + + 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()) == 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(); + + request->request.op.operation_id = IPP_RELEASE_JOB; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/jobs/%d", pjob->sysjob); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", + NULL, pjob->user); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) { + if (response->request.status.status_code >= 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; +} + + +/* + * 'cups_job_submit()' - Submit a job for printing. + */ + +static int cups_job_submit(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 */ + const char *clientname = NULL; /* hostname of client for job-originating-host attribute */ + char *new_jobname = NULL; + int num_options = 0; + cups_option_t *options = NULL; + char addr[INET6_ADDRSTRLEN]; + + DEBUG(5,("cups_job_submit(%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()) == 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(); + + request->request.op.operation_id = IPP_PRINT_JOB; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/printers/%s", + PRINTERNAME(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); + + clientname = client_name(get_client_fd()); + if (strcmp(clientname, "UNKNOWN") == 0) { + clientname = client_addr(get_client_fd(),addr,sizeof(addr)); + } + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "job-originating-host-name", NULL, + clientname); + + if (asprintf(&new_jobname,"%s%.8u %s", PRINT_SPOOL_PREFIX, + (unsigned int)pjob->smbjob, pjob->jobname) < 0) { + goto out; + } + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, + new_jobname); + + /* + * add any options defined in smb.conf + */ + + num_options = 0; + options = NULL; + num_options = cupsParseOptions(lp_cups_options(snum), num_options, &options); + + if ( num_options ) + cupsEncodeOptions(request, num_options, options); + + /* + * Do the request and get back a response... + */ + + slprintf(uri, sizeof(uri) - 1, "/printers/%s", PRINTERNAME(snum)); + + if ((response = cupsDoFileRequest(http, request, uri, pjob->filename)) != NULL) { + if (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to print file to %s - %s\n", PRINTERNAME(snum), + ippErrorString(cupsLastError()))); + } else { + ret = 0; + } + } else { + DEBUG(0,("Unable to print file to `%s' - %s\n", PRINTERNAME(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); + + SAFE_FREE(new_jobname); + + 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) +{ + 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 */ + 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 */ + 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" + }; + + *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,("cups_queue_get(%s, %p, %p)\n", printername, q, status)); + + /* + * Make sure we don't ask for passwords... + */ + + cupsSetPasswordCB(cups_passwd_cb); + + /* + * Try to connect to the server... + */ + + if ((http = cups_connect()) == NULL) { + goto out; + } + + /* + * Generate the printer URI... + */ + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/printers/%s", printername); + + /* + * Build an IPP_GET_JOBS request, which requires the following + * attributes: + * + * attributes-charset + * attributes-natural-language + * requested-attributes + * printer-uri + */ + + request = ippNew(); + + request->request.op.operation_id = IPP_GET_JOBS; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + 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(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 (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to get jobs for %s - %s\n", uri, + ippErrorString(response->request.status.status_code))); + goto out; + } + + /* + * Process the jobs... + */ + + qcount = 0; + qalloc = 0; + queue = NULL; + + for (attr = response->attrs; attr != NULL; attr = attr->next) { + /* + * Skip leading attributes until we hit a job... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_JOB) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Allocate memory as needed... + */ + if (qcount >= qalloc) { + qalloc += 16; + + queue = SMB_REALLOC_ARRAY(queue, print_queue_struct, qalloc); + + if (queue == NULL) { + DEBUG(0,("cups_queue_get: Not enough memory!")); + qcount = 0; + goto out; + } + } + + temp = queue + qcount; + memset(temp, 0, sizeof(print_queue_struct)); + + /* + * Pull the needed attributes from this job... + */ + + job_id = 0; + job_priority = 50; + job_status = IPP_JOB_PENDING; + job_time = 0; + job_k_octets = 0; + user_name = NULL; + job_name = NULL; + + while (attr != NULL && attr->group_tag == IPP_TAG_JOB) { + if (attr->name == NULL) { + attr = attr->next; + break; + } + + if (strcmp(attr->name, "job-id") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_id = attr->values[0].integer; + + if (strcmp(attr->name, "job-k-octets") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_k_octets = attr->values[0].integer; + + if (strcmp(attr->name, "job-priority") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_priority = attr->values[0].integer; + + if (strcmp(attr->name, "job-state") == 0 && + attr->value_tag == IPP_TAG_ENUM) + job_status = (ipp_jstate_t)(attr->values[0].integer); + + if (strcmp(attr->name, "time-at-creation") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_time = attr->values[0].integer; + + if (strcmp(attr->name, "job-name") == 0 && + attr->value_tag == IPP_TAG_NAME) + job_name = attr->values[0].string.text; + + if (strcmp(attr->name, "job-originating-user-name") == 0 && + attr->value_tag == IPP_TAG_NAME) + user_name = attr->values[0].string.text; + + attr = attr->next; + } + + /* + * See if we have everything needed... + */ + + if (user_name == NULL || job_name == NULL || job_id == 0) { + if (attr == NULL) + break; + else + continue; + } + + temp->job = 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; + } + + 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(); + + request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; + request->request.op.request_id = 1; + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + 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()))); + *q = queue; + goto out; + } + + if (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to get printer status for %s - %s\n", printername, + ippErrorString(response->request.status.status_code))); + *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 (attr->values[0].integer == 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, attr->values[0].string.text); + + /* + * Return the job queue... + */ + + *q = queue; + + out: + if (response) + ippDelete(response); + + if (language) + cupsLangFree(language); + + if (http) + httpClose(http); + + return qcount; +} + + +/* + * 'cups_queue_pause()' - Pause a print queue. + */ + +static int cups_queue_pause(int snum) +{ + 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 */ + + + 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()) == 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(); + + request->request.op.operation_id = IPP_PAUSE_PRINTER; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/printers/%s", + PRINTERNAME(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, current_user_info.unix_name); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/admin/")) != NULL) { + if (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to pause printer %s - %s\n", PRINTERNAME(snum), + ippErrorString(cupsLastError()))); + } else { + ret = 0; + } + } else { + DEBUG(0,("Unable to pause printer %s - %s\n", PRINTERNAME(snum), + ippErrorString(cupsLastError()))); + } + + out: + if (response) + ippDelete(response); + + if (language) + cupsLangFree(language); + + if (http) + httpClose(http); + + return ret; +} + + +/* + * 'cups_queue_resume()' - Restart a print queue. + */ + +static int cups_queue_resume(int snum) +{ + 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 */ + + + 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()) == 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(); + + request->request.op.operation_id = IPP_RESUME_PRINTER; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://localhost/printers/%s", + PRINTERNAME(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, current_user_info.unix_name); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/admin/")) != NULL) { + if (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to resume printer %s - %s\n", PRINTERNAME(snum), + ippErrorString(cupsLastError()))); + } else { + ret = 0; + } + } else { + DEBUG(0,("Unable to resume printer %s - %s\n", PRINTERNAME(snum), + ippErrorString(cupsLastError()))); + } + + out: + if (response) + ippDelete(response); + + if (language) + cupsLangFree(language); + + if (http) + httpClose(http); + + 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, +}; + +bool cups_pull_comment_location(NT_PRINTER_INFO_LEVEL_2 *printer) +{ + 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 *name, /* printer-name attribute */ + *info, /* printer-info attribute */ + *location; /* printer-location attribute */ + char uri[HTTP_MAX_URI]; + static const char *requested[] =/* Requested attributes */ + { + "printer-name", + "printer-info", + "printer-location" + }; + bool ret = False; + + DEBUG(5, ("pulling %s location\n", printer->sharename)); + + /* + * Make sure we don't ask for passwords... + */ + + cupsSetPasswordCB(cups_passwd_cb); + + /* + * Try to connect to the server... + */ + + if ((http = cups_connect()) == NULL) { + goto out; + } + + request = ippNew(); + + request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; + request->request.op.request_id = 1; + + language = cupsLangDefault(); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", NULL, cupsLangEncoding(language)); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", NULL, language->language); + + slprintf(uri, sizeof(uri) - 1, "ipp://%s/printers/%s", + lp_cups_server(), printer->sharename); + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, uri); + + ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requested-attributes", + (sizeof(requested) / sizeof(requested[0])), + NULL, requested); + + /* + * Do the request and get back a response... + */ + + if ((response = cupsDoRequest(http, request, "/")) == NULL) { + DEBUG(0,("Unable to get printer attributes - %s\n", + ippErrorString(cupsLastError()))); + goto out; + } + + for (attr = response->attrs; attr != NULL;) { + /* + * Skip leading attributes until we hit a printer... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Pull the needed attributes from this printer... + */ + + name = NULL; + info = NULL; + location = NULL; + + while ( attr && (attr->group_tag == IPP_TAG_PRINTER) ) { + /* Grab the comment if we don't have one */ + if ( (strcmp(attr->name, "printer-info") == 0) + && (attr->value_tag == IPP_TAG_TEXT) + && !strlen(printer->comment) ) + { + DEBUG(5,("cups_pull_comment_location: Using cups comment: %s\n", + attr->values[0].string.text)); + strlcpy(printer->comment, + attr->values[0].string.text, + sizeof(printer->comment)); + } + + /* Grab the location if we don't have one */ + if ( (strcmp(attr->name, "printer-location") == 0) + && (attr->value_tag == IPP_TAG_TEXT) + && !strlen(printer->location) ) + { + DEBUG(5,("cups_pull_comment_location: Using cups location: %s\n", + attr->values[0].string.text)); + fstrcpy(printer->location,attr->values[0].string.text); + } + + attr = attr->next; + } + + /* + * See if we have everything needed... + */ + + if (name == NULL) + break; + + } + + ret = True; + + out: + if (response) + ippDelete(response); + + if (language) + cupsLangFree(language); + + if (http) + httpClose(http); + + return ret; +} + +#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 0000000000..2a324fdd5c --- /dev/null +++ b/source3/printing/print_generic.c @@ -0,0 +1,301 @@ +/* + 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" + +extern struct current_user current_user; +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, ...) +{ + 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; + } + + 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; + } + + if (do_sub && snum != -1) { + syscmd = talloc_sub_advanced(ctx, + lp_servicename(snum), + current_user_info.unix_name, + "", + current_user.ut.gid, + get_current_username(), + current_user_info.domain, + syscmd); + if (!syscmd) { + return -1; + } + } + + ret = smbrun_no_sanitize(syscmd,outfd); + + 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 */ + slprintf(jobstr, sizeof(jobstr)-1, "%d", pjob->sysjob); + return print_run_command( -1, sharename, False, lprm_command, NULL, + "%j", jobstr, + "%T", http_timestring(pjob->starttime), + NULL); +} + +/**************************************************************************** +pause a job +****************************************************************************/ +static int generic_job_pause(int snum, struct printjob *pjob) +{ + fstring jobstr; + + /* need to pause the spooled entry */ + slprintf(jobstr, sizeof(jobstr)-1, "%d", pjob->sysjob); + return print_run_command(snum, PRINTERNAME(snum), True, + lp_lppausecommand(snum), NULL, + "%j", jobstr, + NULL); +} + +/**************************************************************************** +resume a job +****************************************************************************/ +static int generic_job_resume(int snum, struct printjob *pjob) +{ + fstring jobstr; + + /* need to pause the spooled entry */ + slprintf(jobstr, sizeof(jobstr)-1, "%d", pjob->sysjob); + return print_run_command(snum, PRINTERNAME(snum), True, + lp_lpresumecommand(snum), NULL, + "%j", jobstr, + NULL); +} + +/**************************************************************************** + Submit a file for printing - called from print_job_end() +****************************************************************************/ + +static int generic_job_submit(int snum, struct printjob *pjob) +{ + int ret = -1; + 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; + + /* we print from the directory path to give the best chance of + parsing the lpq output */ + current_directory = TALLOC_ARRAY(ctx, + char, + PATH_MAX+1); + if (!current_directory) { + return -1; + } + wd = sys_getwd(current_directory); + if (!wd) { + 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; + } + slprintf(job_page_count, sizeof(job_page_count)-1, "%d", pjob->page_count); + slprintf(job_size, sizeof(job_size)-1, "%lu", (unsigned long)pjob->size); + + /* send it to the system spooler */ + ret = print_run_command(snum, PRINTERNAME(snum), True, + lp_printcommand(snum), NULL, + "%s", p, + "%J", jobname, + "%f", p, + "%z", job_size, + "%c", job_page_count, + NULL); + + out: + + chdir(wd); + TALLOC_FREE(current_directory); + return ret; +} + + +/**************************************************************************** +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 rigt 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); + 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) { + file_lines_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++; + } + } + } + + file_lines_free(qlines); + *q = queue; + return qcount; +} + +/**************************************************************************** + pause a queue +****************************************************************************/ +static int generic_queue_pause(int snum) +{ + return print_run_command(snum, PRINTERNAME(snum), True, lp_queuepausecommand(snum), NULL, NULL); +} + +/**************************************************************************** + resume a queue +****************************************************************************/ +static int generic_queue_resume(int snum) +{ + return print_run_command(snum, PRINTERNAME(snum), True, lp_queueresumecommand(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 0000000000..b038f8d1fe --- /dev/null +++ b/source3/printing/print_iprint.c @@ -0,0 +1,1240 @@ +/* + * 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" + +#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 + +/* + * '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) +{ + if ((lp_iprint_server() != NULL) && (strlen(lp_iprint_server()) > 0)) { + DEBUG(10, ("iprint server explicitly set to %s\n", + lp_iprint_server())); + return lp_iprint_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(); + + request->request.op.operation_id = (ipp_op_t)OPERATION_NOVELL_MGMT; + request->request.op.request_id = 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) || + (response->request.status.status_code >= IPP_OK_CONFLICT)) + goto out; + + if (((attr = ippFindAttribute(response, "server-version", + IPP_TAG_STRING)) != NULL)) { + if ((ver = strstr(attr->values[0].string.text, + 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(attr->values[0].string.text, + 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, + char* url) +{ + ipp_t *request = NULL, /* IPP Request */ + *response = NULL; /* IPP Response */ + ipp_attribute_t *attr; /* Current attribute */ + cups_lang_t *language = NULL; /* Default language */ + char *name, /* printer-name attribute */ + *info, /* printer-info attribute */ + smb_enabled, /* smb-enabled attribute */ + secure; /* security-enabled attrib. */ + + 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(); + + request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; + request->request.op.request_id = 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 = response->attrs; attr != NULL;) { + /* + * Skip leading attributes until we hit a printer... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Pull the needed attributes from this printer... + */ + + name = NULL; + info = NULL; + smb_enabled= 1; + secure = 0; + + while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER) { + if (strcmp(attr->name, "printer-name") == 0 && + attr->value_tag == IPP_TAG_NAME) + name = attr->values[0].string.text; + + if (strcmp(attr->name, "printer-info") == 0 && + (attr->value_tag == IPP_TAG_TEXT || + attr->value_tag == IPP_TAG_TEXTLANG)) + info = attr->values[0].string.text; + + /* + * 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(attr->name, "smb-enabled") && + ((attr->value_tag == IPP_TAG_INTEGER && + !attr->values[0].integer) || + (attr->value_tag == IPP_TAG_BOOLEAN && + !attr->values[0].boolean))) + 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(attr->name, "security-enabled") && + ((attr->value_tag == IPP_TAG_INTEGER && + attr->values[0].integer) || + (attr->value_tag == IPP_TAG_BOOLEAN && + attr->values[0].boolean))) + secure = 1; + + attr = attr->next; + } + + /* + * 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(name, info); + } + + out: + if (response) + ippDelete(response); + return(0); +} + +bool iprint_cache_reload(void) +{ + 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; + + 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... + */ + + if ((http = httpConnect(iprint_server(), ippPort())) == 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(); + + request->request.op.operation_id = + (ipp_op_t)OPERATION_NOVELL_LIST_PRINTERS; + request->request.op.request_id = 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 = response->attrs; attr != NULL;) { + /* + * Skip leading attributes until we hit a printer... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Pull the needed attributes from this printer... + */ + + while (attr != NULL && attr->group_tag == IPP_TAG_PRINTER) + { + if (strcmp(attr->name, "printer-name") == 0 && + (attr->value_tag == IPP_TAG_URI || + attr->value_tag == IPP_TAG_NAME || + attr->value_tag == IPP_TAG_TEXT || + attr->value_tag == IPP_TAG_NAMELANG || + attr->value_tag == IPP_TAG_TEXTLANG)) + { + for (i = 0; i<attr->num_values; i++) + { + char *url = attr->values[i].string.text; + if (!url || !strlen(url)) + continue; + iprint_cache_add_printer(http, i+2, url); + } + } + attr = attr->next; + } + } + + ret = True; + + 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... + */ + + if ((http = httpConnect(iprint_server(), ippPort())) == 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(); + + request->request.op.operation_id = IPP_CANCEL_JOB; + request->request.op.request_id = 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 (response->request.status.status_code >= 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 */ + + + 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... + */ + + if ((http = httpConnect(iprint_server(), ippPort())) == 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(); + + request->request.op.operation_id = IPP_HOLD_JOB; + request->request.op.request_id = 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(), PRINTERNAME(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", PRINTERNAME(snum)); + + if ((response = cupsDoRequest(http, request, httpPath)) != NULL) { + if (response->request.status.status_code >= 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 */ + + + 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... + */ + + if ((http = httpConnect(iprint_server(), ippPort())) == 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(); + + request->request.op.operation_id = IPP_RELEASE_JOB; + request->request.op.request_id = 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(), PRINTERNAME(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", PRINTERNAME(snum)); + + if ((response = cupsDoRequest(http, request, httpPath)) != NULL) { + if (response->request.status.status_code >= 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) +{ + 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 char *clientname = NULL; /* hostname of client for job-originating-host attribute */ + char addr[INET6_ADDRSTRLEN]; + + 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... + */ + + if ((http = httpConnect(iprint_server(), ippPort())) == 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(); + + request->request.op.operation_id = IPP_PRINT_JOB; + request->request.op.request_id = 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(), PRINTERNAME(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); + + clientname = client_name(get_client_fd()); + if (strcmp(clientname, "UNKNOWN") == 0) { + clientname = client_addr(get_client_fd(),addr,sizeof(addr)); + } + + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "job-originating-host-name", NULL, + clientname); + + 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", PRINTERNAME(snum)); + + if ((response = cupsDoFileRequest(http, request, uri, pjob->filename)) != NULL) { + if (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to print file to %s - %s\n", PRINTERNAME(snum), + ippErrorString(cupsLastError()))); + } else { + ret = 0; + } + } else { + DEBUG(0,("Unable to print file to `%s' - %s\n", PRINTERNAME(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 && attr->group_tag == IPP_TAG_JOB) + { + pjob->sysjob = attr->values[0].integer; + } + } + + 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_current_time = 0; /* printer's current time */ + time_t printer_up_time = 0; /* printer's uptime */ + ipp_jstate_t job_status; /* job-status attribute */ + int job_priority; /* job-priority attribute */ + static const char *jattrs[] = /* Requested job attributes */ + { + "job-id", + "job-k-octets", + "job-name", + "job-originating-user-name", + "job-priority", + "job-state", + "time-at-creation", + }; + static const char *pattrs[] = /* Requested printer attributes */ + { + "printer-state", + "printer-state-message", + "printer-current-time", + "printer-up-time" + }; + + *q = NULL; + + /* HACK ALERT!!! The porblem with support the 'printer name' + option is that we key the tdb off the sharename. So we will + overload the lpq_command string to pass in the printername + (which is basically what we do for non-cups printers ... using + the lpq_command to get the queue listing). */ + + fstrcpy( printername, lpq_command ); + + DEBUG(5,("iprint_queue_get(%s, %p, %p)\n", printername, q, status)); + + /* + * Make sure we don't ask for passwords... + */ + + cupsSetPasswordCB(iprint_passwd_cb); + + /* + * Try to connect to the server... + */ + + if ((http = httpConnect(iprint_server(), ippPort())) == NULL) { + DEBUG(0,("Unable to connect to iPrint server %s - %s\n", + iprint_server(), strerror(errno))); + goto out; + } + + /* + * Generate the printer URI and the service URI that goes with it... + */ + + slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(), printername); + slprintf(serviceUri, sizeof(serviceUri) - 1, "ipp://%s/ipp/", iprint_server()); + + /* + * For Linux iPrint servers from OES SP1 on, the iPrint server + * uses Unix time for job start times unless it detects the iPrint + * client in an http User-Agent header. (This was done to accomodate + * CUPS broken behavior. According to RFC 2911, section 4.3.14, job + * start times are supposed to be relative to how long the printer has + * been up.) Since libcups doesn't allow us to set that header before + * the request is sent, this ugly hack allows us to detect the server + * version and decide how to interpret the job time. + */ + if (iprint_get_server_version(http, serviceUri) >= + NOVELL_SERVER_VERSION_OES_SP1) + jobUseUnixTime = 1; + + request = ippNew(); + + request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; + request->request.op.request_id = 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 (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to get printer status for %s - %s\n", printername, + ippErrorString(response->request.status.status_code))); + *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 (attr->values[0].integer == 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, attr->values[0].string.text); + + if ((attr = ippFindAttribute(response, "printer-current-time", + IPP_TAG_DATE)) != NULL) + printer_current_time = ippDateToTime(attr->values[0].date); + + if ((attr = ippFindAttribute(response, "printer-up-time", + IPP_TAG_INTEGER)) != NULL) + printer_up_time = attr->values[0].integer; + + 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(); + + request->request.op.operation_id = IPP_GET_JOBS; + request->request.op.request_id = 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 (response->request.status.status_code >= IPP_OK_CONFLICT) { + DEBUG(0,("Unable to get jobs for %s - %s\n", uri, + ippErrorString(response->request.status.status_code))); + goto out; + } + + /* + * Process the jobs... + */ + + qcount = 0; + qalloc = 0; + queue = NULL; + + for (attr = response->attrs; attr != NULL; attr = attr->next) { + /* + * Skip leading attributes until we hit a job... + */ + + while (attr != NULL && attr->group_tag != IPP_TAG_JOB) + attr = attr->next; + + if (attr == NULL) + break; + + /* + * Allocate memory as needed... + */ + if (qcount >= qalloc) { + qalloc += 16; + + queue = SMB_REALLOC_ARRAY(queue, print_queue_struct, qalloc); + + if (queue == NULL) { + DEBUG(0,("iprint_queue_get: Not enough memory!")); + qcount = 0; + goto out; + } + } + + temp = queue + qcount; + memset(temp, 0, sizeof(print_queue_struct)); + + /* + * Pull the needed attributes from this job... + */ + + job_id = 0; + job_priority = 50; + job_status = IPP_JOB_PENDING; + job_time = 0; + job_k_octets = 0; + user_name = NULL; + job_name = NULL; + + while (attr != NULL && attr->group_tag == IPP_TAG_JOB) { + if (attr->name == NULL) { + attr = attr->next; + break; + } + + if (strcmp(attr->name, "job-id") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_id = attr->values[0].integer; + + if (strcmp(attr->name, "job-k-octets") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_k_octets = attr->values[0].integer; + + if (strcmp(attr->name, "job-priority") == 0 && + attr->value_tag == IPP_TAG_INTEGER) + job_priority = attr->values[0].integer; + + if (strcmp(attr->name, "job-state") == 0 && + attr->value_tag == IPP_TAG_ENUM) + job_status = (ipp_jstate_t)(attr->values[0].integer); + + if (strcmp(attr->name, "time-at-creation") == 0 && + attr->value_tag == 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 = attr->values[0].integer; + else + job_time = time(NULL) - printer_up_time + attr->values[0].integer; + } + + if (strcmp(attr->name, "job-name") == 0 && + (attr->value_tag == IPP_TAG_NAMELANG || + attr->value_tag == IPP_TAG_NAME)) + job_name = attr->values[0].string.text; + + if (strcmp(attr->name, "job-originating-user-name") == 0 && + (attr->value_tag == IPP_TAG_NAMELANG || + attr->value_tag == IPP_TAG_NAME)) + user_name = attr->values[0].string.text; + + attr = attr->next; + } + + /* + * See if we have everything needed... + */ + + if (user_name == NULL || job_name == NULL || job_id == 0) { + if (attr == NULL) + break; + else + continue; + } + + temp->job = 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_svid.c b/source3/printing/print_svid.c new file mode 100644 index 0000000000..7e91d3a677 --- /dev/null +++ b/source3/printing/print_svid.c @@ -0,0 +1,127 @@ +/* + * 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" + +#if defined(SYSV) || defined(HPUX) +bool sysv_cache_reload(void) +{ + char **lines; + int i; + +#if defined(HPUX) + DEBUG(5, ("reloading hpux printcap cache\n")); +#else + DEBUG(5, ("reloading sysv printcap cache\n")); +#endif + + if ((lines = file_lines_pload("/usr/bin/lpstat -v", NULL)) == 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; + scheduler = file_lines_pload("/usr/bin/lpstat -r", NULL); + if(!strcmp(*scheduler,"scheduler is running")){ + DEBUG(3,("No Printers found!!!\n")); + file_lines_free(scheduler); + return True; + } + else{ + DEBUG(3,("Scheduler is not running!!!\n")); + file_lines_free(scheduler); + return False; + } +#else + DEBUG(3,("No Printers found!!!\n")); + return False; +#endif + } + + 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(name, NULL)) { + file_lines_free(lines); + return False; + } + } + + file_lines_free(lines); + return True; +} + +#else +/* this keeps fussy compilers happy */ + void print_svid_dummy(void); + void print_svid_dummy(void) {} +#endif diff --git a/source3/printing/printfsp.c b/source3/printing/printfsp.c new file mode 100644 index 0000000000..c6749226fd --- /dev/null +++ b/source3/printing/printfsp.c @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + printing backend routines for smbd - using files_struct rather + than only snum + 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" + +/*************************************************************************** +open a print file and setup a fsp for it. This is a wrapper around +print_job_start(). +***************************************************************************/ + +NTSTATUS print_fsp_open(connection_struct *conn, const char *fname, + uint16_t current_vuid, files_struct **result) +{ + int jobid; + SMB_STRUCT_STAT sbuf; + files_struct *fsp; + fstring name; + NTSTATUS status; + + status = file_new(conn, &fsp); + if(!NT_STATUS_IS_OK(status)) { + return status; + } + + fstrcpy( name, "Remote Downlevel Document"); + if (fname) { + const char *p = strrchr(fname, '/'); + fstrcat(name, " "); + if (!p) { + p = fname; + } + fstrcat(name, p); + } + + jobid = print_job_start(conn->server_info, SNUM(conn), name, NULL); + if (jobid == -1) { + status = map_nt_error_from_unix(errno); + file_free(fsp); + return status; + } + + /* Convert to RAP id. */ + fsp->rap_print_jobid = pjobid_to_rap(lp_const_servicename(SNUM(conn)), jobid); + if (fsp->rap_print_jobid == 0) { + /* We need to delete the entry in the tdb. */ + pjob_delete(lp_const_servicename(SNUM(conn)), jobid); + file_free(fsp); + return NT_STATUS_ACCESS_DENIED; /* No errno around here */ + } + + /* setup a full fsp */ + fsp->fh->fd = print_job_fd(lp_const_servicename(SNUM(conn)),jobid); + GetTimeOfDay(&fsp->open_time); + fsp->vuid = current_vuid; + fsp->fh->pos = -1; + fsp->can_lock = True; + fsp->can_read = False; + fsp->access_mask = FILE_GENERIC_WRITE; + fsp->can_write = True; + fsp->print_file = True; + fsp->modified = False; + fsp->oplock_type = NO_OPLOCK; + fsp->sent_oplock_break = NO_BREAK_SENT; + fsp->is_directory = False; + string_set(&fsp->fsp_name,print_job_fname(lp_const_servicename(SNUM(conn)),jobid)); + fsp->wcp = NULL; + SMB_VFS_FSTAT(fsp, &sbuf); + fsp->mode = sbuf.st_mode; + fsp->file_id = vfs_file_id_from_sbuf(conn, &sbuf); + + conn->num_files_open++; + + *result = fsp; + return NT_STATUS_OK; +} + +/**************************************************************************** + Print a file - called on closing the file. +****************************************************************************/ + +void print_fsp_end(files_struct *fsp, enum file_close_type close_type) +{ + uint32 jobid; + fstring sharename; + + if (fsp->fh->private_options & FILE_DELETE_ON_CLOSE) { + /* + * Truncate the job. print_job_end will take + * care of deleting it for us. JRA. + */ + sys_ftruncate(fsp->fh->fd, 0); + } + + if (fsp->fsp_name) { + string_free(&fsp->fsp_name); + } + + if (!rap_to_pjobid(fsp->rap_print_jobid, sharename, &jobid)) { + DEBUG(3,("print_fsp_end: Unable to convert RAP jobid %u to print jobid.\n", + (unsigned int)fsp->rap_print_jobid )); + return; + } + + print_job_end(SNUM(fsp->conn),jobid, close_type); +} diff --git a/source3/printing/printing.c b/source3/printing/printing.c new file mode 100644 index 0000000000..1016e6183d --- /dev/null +++ b/source3/printing/printing.c @@ -0,0 +1,2888 @@ +/* + 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 "printing.h" + +extern SIG_ATOMIC_T got_sig_term; +extern SIG_ATOMIC_T reload_after_sighup; +extern struct current_user current_user; +extern userdom_struct current_user_info; + +/* Current printer interface */ +static bool remove_from_jobs_changed(const char* sharename, uint32 jobid); + +/* + 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. +*/ + +static TDB_CONTEXT *rap_tdb; +static uint16 next_rap_jobid; +struct rap_jobid_key { + fstring sharename; + uint32 jobid; +}; + +/*************************************************************************** + Nightmare. LANMAN jobid's are 16 bit numbers..... We must map them to 32 + bit RPC jobids.... JRA. +***************************************************************************/ + +uint16 pjobid_to_rap(const char* sharename, uint32 jobid) +{ + uint16 rap_jobid; + TDB_DATA data, key; + struct rap_jobid_key jinfo; + uint8 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 *)&jinfo; + key.dsize = sizeof(jinfo); + + data = tdb_fetch(rap_tdb, key); + if (data.dptr && data.dsize == sizeof(uint16)) { + 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 rap_jobid, fstring sharename, uint32 *pjobid) +{ + TDB_DATA data, key; + uint8 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; + 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; +} + +static void rap_jobid_delete(const char* sharename, uint32 jobid) +{ + TDB_DATA key, data; + uint16 rap_jobid; + struct rap_jobid_key jinfo; + uint8 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 *)&jinfo; + key.dsize = sizeof(jinfo); + + data = tdb_fetch(rap_tdb, key); + if (!data.dptr || (data.dsize != sizeof(uint16))) { + 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); +} + +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; + + unlink(lock_path("printing.tdb")); + mkdir(lock_path("printing"),0755); + + /* handle a Samba upgrade */ + + for (snum = 0; snum < services; snum++) { + struct tdb_print_db *pdb; + if (!lp_print_ok(snum)) + continue; + + pdb = get_print_db_byname(lp_const_servicename(snum)); + if (!pdb) + continue; + if (tdb_lock_bystring(pdb->tdb, sversion) == -1) { + 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 jobid, uint32 *tmp) +{ + TDB_DATA ret; + + SIVAL(tmp, 0, jobid); + ret.dptr = (uint8 *)tmp; + ret.dsize = sizeof(*tmp); + return ret; +} + +/*********************************************************************** + unpack a pjob from a tdb buffer +***********************************************************************/ + +int unpack_pjob( uint8 *buf, int buflen, struct printjob *pjob ) +{ + int len = 0; + int used; + uint32 pjpid, pjsysjob, pjfd, pjstarttime, pjstatus; + uint32 pjsize, pjpage_count, pjspooled, pjsmbjob; + + if ( !buf || !pjob ) + return -1; + + len += tdb_unpack(buf+len, buflen-len, "dddddddddffff", + &pjpid, + &pjsysjob, + &pjfd, + &pjstarttime, + &pjstatus, + &pjsize, + &pjpage_count, + &pjspooled, + &pjsmbjob, + pjob->filename, + pjob->jobname, + pjob->user, + pjob->queuename); + + if ( len == -1 ) + return -1; + + if ( (used = unpack_devicemode(&pjob->nt_devmode, buf+len, buflen-len)) == -1 ) + return -1; + + len += used; + + pjob->pid = pjpid; + 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(const char *sharename, uint32 jobid) +{ + static 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", (unsigned int)jobid )); + return NULL; + } + + if ( pjob.nt_devmode ) { + free_nt_devicemode( &pjob.nt_devmode ); + } + + ZERO_STRUCT( pjob ); + + if ( unpack_pjob( ret.dptr, ret.dsize, &pjob ) == -1 ) { + DEBUG(10,("print_job_find: failed to unpack jobid %u.\n", (unsigned int)jobid )); + SAFE_FREE(ret.dptr); + return NULL; + } + + SAFE_FREE(ret.dptr); + + DEBUG(10,("print_job_find: returning system job %d for jobid %u.\n", + (int)pjob.sysjob, (unsigned int)jobid )); + + return &pjob; +} + +/* Convert a unix jobid to a smb jobid */ + +struct unixjob_traverse_state { + int sysjob; + uint32 sysjob_to_jobid_value; +}; + +static int unixjob_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key, + TDB_DATA data, void *private_data) +{ + struct printjob *pjob; + struct unixjob_traverse_state *state = + (struct unixjob_traverse_state *)private_data; + + if (!data.dptr || data.dsize == 0) + return 0; + + pjob = (struct printjob *)data.dptr; + if (key.dsize != sizeof(uint32)) + return 0; + + if (state->sysjob == pjob->sysjob) { + uint32 jobid = IVAL(key.dptr,0); + + state->sysjob_to_jobid_value = jobid; + return 1; + } + + return 0; +} + +/**************************************************************************** + 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 sysjob_to_jobid(int unix_jobid) +{ + int services = lp_numservices(); + int snum; + struct unixjob_traverse_state state; + + state.sysjob = unix_jobid; + state.sysjob_to_jobid_value = (uint32)-1; + + for (snum = 0; snum < services; snum++) { + struct tdb_print_db *pdb; + if (!lp_print_ok(snum)) + continue; + pdb = get_print_db_byname(lp_const_servicename(snum)); + if (!pdb) { + continue; + } + tdb_traverse(pdb->tdb, unixjob_traverse_fn, &state); + release_print_db(pdb); + if (state.sysjob_to_jobid_value != (uint32)-1) + return state.sysjob_to_jobid_value; + } + return (uint32)-1; +} + +/**************************************************************************** + Send notifications based on what has changed after a pjob_store. +****************************************************************************/ + +static const struct { + uint32 lpq_status; + uint32 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 }, + { LPQ_USER_INTERVENTION, JOB_STATUS_USER_INTERVENTION }, + { -1, 0 } +}; + +/* Convert a lpq status value stored in printing.tdb into the + appropriate win32 API constant. */ + +static uint32 map_to_spoolss_status(uint32 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; +} + +static void pjob_store_notify(const char* sharename, uint32 jobid, struct printjob *old_data, + struct printjob *new_data) +{ + bool new_job = False; + + if (!old_data) + new_job = True; + + /* Job attributes that can't be changed. We only send + notification for these on a new job. */ + + /* 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(sharename, jobid, new_data->starttime); + notify_job_username(sharename, jobid, new_data->user); + } + + if (new_job || !strequal(old_data->jobname, new_data->jobname)) + notify_job_name(sharename, jobid, new_data->jobname); + + /* Job attributes of a new job or attributes that can be + modified. */ + + if (new_job || !strequal(old_data->jobname, new_data->jobname)) + notify_job_name(sharename, jobid, new_data->jobname); + + if (new_job || old_data->status != new_data->status) + notify_job_status(sharename, jobid, map_to_spoolss_status(new_data->status)); + + if (new_job || old_data->size != new_data->size) + notify_job_total_bytes(sharename, jobid, new_data->size); + + if (new_job || old_data->page_count != new_data->page_count) + notify_job_total_pages(sharename, jobid, new_data->page_count); +} + +/**************************************************************************** + Store a job structure back to the database. +****************************************************************************/ + +static bool pjob_store(const char* sharename, uint32 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 *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, "dddddddddffff", + (uint32)pjob->pid, + (uint32)pjob->sysjob, + (uint32)pjob->fd, + (uint32)pjob->starttime, + (uint32)pjob->status, + (uint32)pjob->size, + (uint32)pjob->page_count, + (uint32)pjob->spooled, + (uint32)pjob->smbjob, + pjob->filename, + pjob->jobname, + pjob->user, + pjob->queuename); + + len += pack_devicemode(pjob->nt_devmode, buf+len, buflen-len); + + if (buflen != len) { + buf = (uint8 *)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); + + release_print_db(pdb); + + /* Send notify updates for what has changed */ + + if ( ret ) { + struct printjob old_pjob; + + if ( old_data.dsize ) + { + if ( unpack_pjob( old_data.dptr, old_data.dsize, &old_pjob ) != -1 ) + { + pjob_store_notify( sharename, jobid, &old_pjob , pjob ); + free_nt_devicemode( &old_pjob.nt_devmode ); + } + } + else { + /* new job */ + pjob_store_notify( sharename, jobid, NULL, pjob ); + } + } + +done: + SAFE_FREE( old_data.dptr ); + SAFE_FREE( buf ); + + return ret; +} + +/**************************************************************************** + Remove a job structure from the database. +****************************************************************************/ + +void pjob_delete(const char* sharename, uint32 jobid) +{ + uint32_t tmp; + struct printjob *pjob; + uint32 job_status = 0; + struct tdb_print_db *pdb; + + pdb = get_print_db_byname( sharename ); + + if (!pdb) + return; + + pjob = print_job_find( sharename, jobid ); + + if (!pjob) { + DEBUG(5, ("pjob_delete: we were asked to delete nonexistent job %u\n", + (unsigned int)jobid)); + release_print_db(pdb); + return; + } + + /* 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(sharename, jobid, job_status); + + /* Remove from printing.tdb */ + + tdb_delete(pdb->tdb, print_key(jobid, &tmp)); + remove_from_jobs_changed(sharename, jobid); + release_print_db( pdb ); + rap_jobid_delete(sharename, jobid); +} + +/**************************************************************************** + Parse a file name from the system spooler to generate a jobid. +****************************************************************************/ + +static uint32 print_parse_jobid(char *fname) +{ + int jobid; + + if (strncmp(fname,PRINT_SPOOL_PREFIX,strlen(PRINT_SPOOL_PREFIX)) != 0) + return (uint32)-1; + fname += strlen(PRINT_SPOOL_PREFIX); + + jobid = atoi(fname); + if (jobid <= 0) + return (uint32)-1; + + return (uint32)jobid; +} + +/**************************************************************************** + List a unix job in the print database. +****************************************************************************/ + +static void print_unix_job(const char *sharename, print_queue_struct *q, uint32 jobid) +{ + struct printjob pj, *old_pj; + + if (jobid == (uint32)-1) + jobid = q->job + UNIX_JOB_START; + + /* Preserve the timestamp on an existing unix print job */ + + old_pj = print_job_find(sharename, jobid); + + ZERO_STRUCT(pj); + + pj.pid = (pid_t)-1; + pj.sysjob = q->job; + 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(sharename, jobid, &pj); +} + + +struct traverse_struct { + print_queue_struct *queue; + int qcount, snum, maxcount, total_jobs; + const char *sharename; + time_t lpq_time; + const char *lprm_command; + struct printif *print_if; +}; + +/**************************************************************************** + 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 jobid; + int i = 0; + + if ( key.dsize != sizeof(jobid) ) + return 0; + + jobid = IVAL(key.dptr, 0); + if ( unpack_pjob( data.dptr, data.dsize, &pjob ) == -1 ) + return 0; + free_nt_devicemode( &pjob.nt_devmode ); + + + if (!pjob.smbjob) { + /* remove a unix job if it isn't in the system queue any more */ + + for (i=0;i<ts->qcount;i++) { + uint32 u_jobid = (ts->queue[i].job + UNIX_JOB_START); + if (jobid == u_jobid) + break; + } + if (i == ts->qcount) { + DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !smbjob\n", + (unsigned int)jobid )); + pjob_delete(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->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++) { + uint32 curr_jobid; + + if ( pjob.status == LPQ_DELETED ) + continue; + + curr_jobid = print_parse_jobid(ts->queue[i].fs_file); + + if (jobid == curr_jobid) { + + /* 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->sharename, jobid, &pjob); + } + else { + /* if we deleted the job, the remove the tdb record */ + pjob_delete(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->sharename, jobid); + } else + ts->total_jobs++; + return 0; + } + + /* Save the pjob attributes we will store. + FIXME!!! This is the only place where queue->job + represents the SMB jobid --jerry */ + + ts->queue[i].job = jobid; + 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 = sys_getpid(); + uint8 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: %s updating 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_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)queue[i].job, + (uint32)queue[i].size, + (uint32)queue[i].page_count, + (uint32)queue[i].status, + (uint32)queue[i].priority, + (uint32)queue[i].time, + queue[i].fs_user, + queue[i].fs_file); + } + + if ((data.dptr = (uint8 *)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)queue[i].job, + (uint32)queue[i].size, + (uint32)queue[i].page_count, + (uint32)queue[i].status, + (uint32)queue[i].priority, + (uint32)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_changed_data(struct tdb_print_db *pdb) +{ + TDB_DATA data; + + ZERO_STRUCT(data); + + data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_changed")); + if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0)) { + SAFE_FREE(data.dptr); + ZERO_STRUCT(data); + } + + return data; +} + +static void check_job_changed(const char *sharename, TDB_DATA data, uint32 jobid) +{ + unsigned int i; + unsigned int job_count = data.dsize / 4; + + for (i = 0; i < job_count; i++) { + uint32 ch_jobid; + + ch_jobid = IVAL(data.dptr, i*4); + if (ch_jobid == jobid) + remove_from_jobs_changed(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_lpqcachetime() + || last_qscan_time > (time_now + MAX_CACHE_VALID_TIME)) + { + uint32 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_lpqcachetime() )); + + /* 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 cahe for a printer queue +****************************************************************************/ + +static void print_queue_update_internal( const char *sharename, + struct printif *current_printif, + char *lpq_command, char *lprm_command ) +{ + int 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); + + if (!pdb) { + 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); + + DEBUG(3, ("print_queue_update_internal: %d 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. */ + + qsort(queue, qcount, sizeof(print_queue_struct), + QSORT_CAST(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_changed_data(pdb); + + for (i=0; i<qcount; i++) { + uint32 jobid = print_parse_jobid(queue[i].fs_file); + + if (jobid == (uint32)-1) { + /* assume its a unix print job */ + print_unix_job(sharename, &queue[i], jobid); + continue; + } + + /* we have an active SMB print job - update its status */ + pjob = print_job_find(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. */ + print_unix_job(sharename, &queue[i], jobid); + continue; + } + + pjob->sysjob = queue[i].job; + + /* don't reset the status on jobs to be deleted */ + + if ( pjob->status != LPQ_DELETING ) + pjob->status = queue[i].status; + + pjob_store(sharename, jobid, pjob); + + check_job_changed(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; + + 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); + + DEBUG(10,("print_queue_update_internal: printer %s INFO/total_jobs = %d\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) + DEBUG(10,("print_queue_update_internal: queue status change %d jobs -> %d 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 *)&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)time(NULL)); + + /* clear the msg pending record for this queue */ + + snprintf(keystr, sizeof(keystr), "MSG_PENDING/%s", sharename); + + if ( !tdb_store_uint32( pdb->tdb, keystr, 0 ) ) { + /* log a message but continue on */ + + DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n", + sharename)); + } + + release_print_db( pdb ); + + return; +} + +/**************************************************************************** + Update the internal database from the system print queue for a queue. + obtain a lock on the print queue before proceeding (needed when mutiple + smbd processes maytry to update the lpq cache concurrently). +****************************************************************************/ + +static void print_queue_update_with_lock( 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) == -1) { + 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( 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 +****************************************************************************/ +static 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 *)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(sharename, + get_printer_fns_from_type((enum printing_types)printing_type), + lpqcommand, lprmcommand ); + + SAFE_FREE(lpqcommand); + SAFE_FREE(lprmcommand); + return; +} + +static pid_t background_lpq_updater_pid = -1; + +/**************************************************************************** +main thread of the background lpq updater +****************************************************************************/ +void start_background_queue(void) +{ + DEBUG(3,("start_background_queue: Starting background LPQ thread\n")); + background_lpq_updater_pid = sys_fork(); + + if (background_lpq_updater_pid == -1) { + DEBUG(5,("start_background_queue: background LPQ thread failed to start. %s\n", strerror(errno) )); + exit(1); + } + + if(background_lpq_updater_pid == 0) { + /* Child. */ + DEBUG(5,("start_background_queue: background LPQ thread started\n")); + + if (!reinit_after_fork(smbd_messaging_context(), true)) { + DEBUG(0,("reinit_after_fork() failed\n")); + smb_panic("reinit_after_fork() failed"); + } + + claim_connection( NULL, "smbd lpq backend", + FLAG_MSG_GENERAL|FLAG_MSG_SMBD|FLAG_MSG_PRINT_GENERAL); + + if (!locking_init()) { + exit(1); + } + + messaging_register(smbd_messaging_context(), NULL, + MSG_PRINTER_UPDATE, print_queue_receive); + + DEBUG(5,("start_background_queue: background LPQ thread waiting for messages\n")); + while (1) { + pause(); + + /* check for some essential signals first */ + + if (got_sig_term) { + exit_server_cleanly(NULL); + } + + if (reload_after_sighup) { + change_to_root_user(); + DEBUG(1,("Reloading services after SIGHUP\n")); + reload_services(False); + reload_after_sighup = 0; + } + + /* now check for messages */ + + DEBUG(10,("start_background_queue: background LPQ thread got a message\n")); + message_dispatch(smbd_messaging_context()); + + /* process any pending print change notify messages */ + + print_notify_send_messages(smbd_messaging_context(), + 0); + } + } +} + +/**************************************************************************** +update the internal database from the system print queue for a queue +****************************************************************************/ + +static void print_queue_update(int snum, bool force) +{ + fstring key; + fstring sharename; + char *lpqcommand = NULL; + char *lprmcommand = NULL; + uint8 *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(); + + fstrcpy( sharename, lp_const_servicename(snum)); + + /* don't strip out characters like '$' from the printername */ + + lpqcommand = talloc_string_sub2(ctx, + lp_lpqcommand(snum), + "%p", + PRINTERNAME(snum), + false, false, false); + if (!lpqcommand) { + return; + } + lpqcommand = talloc_sub_advanced(ctx, + lp_servicename(snum), + current_user_info.unix_name, + "", + current_user.ut.gid, + get_current_username(), + current_user_info.domain, + lpqcommand); + if (!lpqcommand) { + return; + } + + lprmcommand = talloc_string_sub2(ctx, + lp_lprmcommand(snum), + "%p", + PRINTERNAME(snum), + false, false, false); + if (!lprmcommand) { + return; + } + lprmcommand = talloc_sub_advanced(ctx, + lp_servicename(snum), + current_user_info.unix_name, + "", + current_user.ut.gid, + get_current_username(), + current_user_info.domain, + lprmcommand); + if (!lprmcommand) { + return; + } + + /* + * Make sure that the background queue process exists. + * Otherwise just do the update ourselves + */ + + if ( force || background_lpq_updater_pid == -1 ) { + DEBUG(4,("print_queue_update: updating queue [%s] myself\n", sharename)); + current_printif = get_printer_fns( snum ); + print_queue_update_with_lock( 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, 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 */ + + messaging_send_buf(smbd_messaging_context(), + pid_to_procid(background_lpq_updater_pid), + MSG_PRINTER_UPDATE, (uint8 *)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 mypid = (uint32)sys_getpid(); + bool ret = False; + size_t i; + + /* if (snum == -1), then the change notify request was + on a print server handle and we need to register on + all print queus */ + + if (snum == -1) + { + int num_services = lp_numservices(); + int idx; + + for ( idx=0; idx<num_services; idx++ ) { + if (lp_snum_ok(idx) && lp_print_ok(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) == -1) { + 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 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 *)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) == -1) { + 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 mypid = (uint32)sys_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_print_ok(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) == -1) { + 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 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) == -1) { + 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 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; +} + +/**************************************************************************** + Give the fd used for a jobid. +****************************************************************************/ + +int print_job_fd(const char* sharename, uint32 jobid) +{ + struct printjob *pjob = print_job_find(sharename, jobid); + if (!pjob) + return -1; + /* don't allow another process to get this info - it is meaningless */ + if (pjob->pid != sys_getpid()) + return -1; + return pjob->fd; +} + +/**************************************************************************** + Give the filename used for a jobid. + Only valid for the process doing the spooling and when the job + has not been spooled. +****************************************************************************/ + +char *print_job_fname(const char* sharename, uint32 jobid) +{ + struct printjob *pjob = print_job_find(sharename, jobid); + if (!pjob || pjob->spooled || pjob->pid != sys_getpid()) + return NULL; + return pjob->filename; +} + + +/**************************************************************************** + Give the filename used for a jobid. + Only valid for the process doing the spooling and when the job + has not been spooled. +****************************************************************************/ + +NT_DEVICEMODE *print_job_devmode(const char* sharename, uint32 jobid) +{ + struct printjob *pjob = print_job_find(sharename, jobid); + + if ( !pjob ) + return NULL; + + return pjob->nt_devmode; +} + +/**************************************************************************** + Set the place in the queue for a job. +****************************************************************************/ + +bool print_job_set_place(const char *sharename, uint32 jobid, int place) +{ + DEBUG(2,("print_job_set_place not implemented yet\n")); + return False; +} + +/**************************************************************************** + Set the name of a job. Only possible for owner. +****************************************************************************/ + +bool print_job_set_name(const char *sharename, uint32 jobid, char *name) +{ + struct printjob *pjob; + + pjob = print_job_find(sharename, jobid); + if (!pjob || pjob->pid != sys_getpid()) + return False; + + fstrcpy(pjob->jobname, name); + return pjob_store(sharename, jobid, pjob); +} + +/*************************************************************************** + Remove a jobid from the 'jobs changed' list. +***************************************************************************/ + +static bool remove_from_jobs_changed(const char* sharename, uint32 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("INFO/jobs_changed"); + + if (tdb_chainlock_with_timeout(pdb->tdb, key, 5) == -1) + 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 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) == -1) + goto out; + break; + } + } + + ret = True; + out: + + if (gotlock) + tdb_chainunlock(pdb->tdb, key); + SAFE_FREE(data.dptr); + release_print_db(pdb); + if (ret) + DEBUG(10,("remove_from_jobs_changed: removed jobid %u\n", (unsigned int)jobid )); + else + DEBUG(10,("remove_from_jobs_changed: Failed to remove jobid %u\n", (unsigned int)jobid )); + return ret; +} + +/**************************************************************************** + Delete a print job - don't update queue. +****************************************************************************/ + +static bool print_job_delete1(int snum, uint32 jobid) +{ + const char* sharename = lp_const_servicename(snum); + struct printjob *pjob = print_job_find(sharename, jobid); + int result = 0; + struct printif *current_printif = get_printer_fns( snum ); + + if (!pjob) + return False; + + /* + * If already deleting just return. + */ + + if (pjob->status == LPQ_DELETING) + return True; + + /* Hrm - we need to be able to cope with deleting a job before it + has reached the spooler. Just mark it as LPQ_DELETING and + let the print_queue_update() code rmeove the record */ + + + if (pjob->sysjob == -1) { + DEBUG(5, ("attempt to delete job %u not seen by lpr\n", (unsigned int)jobid)); + } + + /* Set the tdb entry to be deleting. */ + + pjob->status = LPQ_DELETING; + pjob_store(sharename, jobid, pjob); + + if (pjob->spooled && pjob->sysjob != -1) + { + result = (*(current_printif->job_delete))( + PRINTERNAME(snum), + lp_lprmcommand(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) + return False; + pjob_delete(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_changed( sharename, jobid ); + + return (result == 0); +} + +/**************************************************************************** + Return true if the current user owns the print job. +****************************************************************************/ + +static bool is_owner(struct auth_serversupplied_info *server_info, + const char *servicename, + uint32 jobid) +{ + struct printjob *pjob = print_job_find(servicename, jobid); + + if (!pjob || !server_info) + return False; + + return strequal(pjob->user, server_info->sanitized_username); +} + +/**************************************************************************** + Delete a print job. +****************************************************************************/ + +bool print_job_delete(struct auth_serversupplied_info *server_info, int snum, + uint32 jobid, WERROR *errcode) +{ + const char* sharename = lp_const_servicename( snum ); + struct printjob *pjob; + bool owner; + char *fname; + + *errcode = WERR_OK; + + owner = is_owner(server_info, lp_const_servicename(snum), jobid); + + /* Check access against security descriptor or whether the user + owns their job. */ + + if (!owner && + !print_access_check(server_info, snum, JOB_ACCESS_ADMINISTER)) { + DEBUG(3, ("delete denied by security descriptor\n")); + *errcode = WERR_ACCESS_DENIED; + + /* BEGIN_ADMIN_LOG */ + sys_adminlog( LOG_ERR, + "Permission denied-- user not allowed to delete, \ +pause, or resume print job. User name: %s. Printer name: %s.", + uidtoname(server_info->utok.uid), + PRINTERNAME(snum) ); + /* END_ADMIN_LOG */ + + return False; + } + + /* + * 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. + */ + + if ( (fname = print_job_fname( sharename, jobid )) != NULL ) + { + /* remove the spool file */ + DEBUG(10,("print_job_delete: Removing spool file [%s]\n", fname )); + if ( unlink( fname ) == -1 ) { + *errcode = map_werror_from_unix(errno); + return False; + } + } + + if (!print_job_delete1(snum, jobid)) { + *errcode = WERR_ACCESS_DENIED; + return False; + } + + /* force update the database and say the delete failed if the + job still exists */ + + print_queue_update(snum, True); + + pjob = print_job_find(sharename, jobid); + if ( pjob && (pjob->status != LPQ_DELETING) ) + *errcode = WERR_ACCESS_DENIED; + + return (pjob == NULL ); +} + +/**************************************************************************** + Pause a job. +****************************************************************************/ + +bool print_job_pause(struct auth_serversupplied_info *server_info, int snum, + uint32 jobid, WERROR *errcode) +{ + const char* sharename = lp_const_servicename(snum); + struct printjob *pjob; + int ret = -1; + struct printif *current_printif = get_printer_fns( snum ); + + pjob = print_job_find(sharename, jobid); + + if (!pjob || !server_info) { + DEBUG(10, ("print_job_pause: no pjob or user for jobid %u\n", + (unsigned int)jobid )); + return False; + } + + 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 )); + return False; + } + + if (!is_owner(server_info, lp_const_servicename(snum), jobid) && + !print_access_check(server_info, snum, JOB_ACCESS_ADMINISTER)) { + DEBUG(3, ("pause denied by security descriptor\n")); + + /* BEGIN_ADMIN_LOG */ + sys_adminlog( LOG_ERR, + "Permission denied-- user not allowed to delete, \ +pause, or resume print job. User name: %s. Printer name: %s.", + uidtoname(server_info->utok.uid), + PRINTERNAME(snum) ); + /* END_ADMIN_LOG */ + + *errcode = WERR_ACCESS_DENIED; + return False; + } + + /* need to pause the spooled entry */ + ret = (*(current_printif->job_pause))(snum, pjob); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* force update the database */ + print_cache_flush(lp_const_servicename(snum)); + + /* Send a printer notify message */ + + notify_job_status(sharename, jobid, JOB_STATUS_PAUSED); + + /* how do we tell if this succeeded? */ + + return True; +} + +/**************************************************************************** + Resume a job. +****************************************************************************/ + +bool print_job_resume(struct auth_serversupplied_info *server_info, int snum, + uint32 jobid, WERROR *errcode) +{ + const char *sharename = lp_const_servicename(snum); + struct printjob *pjob; + int ret; + struct printif *current_printif = get_printer_fns( snum ); + + pjob = print_job_find(sharename, jobid); + + if (!pjob || !server_info) { + DEBUG(10, ("print_job_resume: no pjob or user for jobid %u\n", + (unsigned int)jobid )); + return False; + } + + 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 )); + return False; + } + + if (!is_owner(server_info, lp_const_servicename(snum), jobid) && + !print_access_check(server_info, snum, JOB_ACCESS_ADMINISTER)) { + DEBUG(3, ("resume denied by security descriptor\n")); + *errcode = WERR_ACCESS_DENIED; + + /* BEGIN_ADMIN_LOG */ + sys_adminlog( LOG_ERR, + "Permission denied-- user not allowed to delete, \ +pause, or resume print job. User name: %s. Printer name: %s.", + uidtoname(server_info->utok.uid), + PRINTERNAME(snum) ); + /* END_ADMIN_LOG */ + return False; + } + + ret = (*(current_printif->job_resume))(snum, pjob); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* force update the database */ + print_cache_flush(lp_const_servicename(snum)); + + /* Send a printer notify message */ + + notify_job_status(sharename, jobid, JOB_STATUS_QUEUED); + + return True; +} + +/**************************************************************************** + Write to a print file. +****************************************************************************/ + +ssize_t print_job_write(int snum, uint32 jobid, const char *buf, SMB_OFF_T pos, size_t size) +{ + const char* sharename = lp_const_servicename(snum); + int return_code; + struct printjob *pjob; + + pjob = print_job_find(sharename, jobid); + + if (!pjob) + return -1; + /* don't allow another process to get this info - it is meaningless */ + if (pjob->pid != sys_getpid()) + return -1; + + return_code = write_data_at_offset(pjob->fd, buf, size, pos); + + if (return_code>0) { + pjob->size += size; + pjob_store(sharename, jobid, pjob); + } + 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(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(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 bool allocate_print_jobid(struct tdb_print_db *pdb, int snum, const char *sharename, uint32 *pjobid) +{ + int i; + uint32 jobid; + + *pjobid = (uint32)-1; + + for (i = 0; i < 3; i++) { + /* Lock the database - only wait 20 seconds. */ + if (tdb_lock_bystring_with_timeout(pdb->tdb, "INFO/nextjob", 20) == -1) { + DEBUG(0,("allocate_print_jobid: failed to lock printing database %s\n", sharename)); + return False; + } + + if (!tdb_fetch_uint32(pdb->tdb, "INFO/nextjob", &jobid)) { + if (tdb_error(pdb->tdb) != TDB_ERR_NOEXIST) { + DEBUG(0, ("allocate_print_jobid: failed to fetch INFO/nextjob for print queue %s\n", + sharename)); + return False; + } + jobid = 0; + } + + jobid = NEXT_JOBID(jobid); + + if (tdb_store_int32(pdb->tdb, "INFO/nextjob", jobid)==-1) { + DEBUG(3, ("allocate_print_jobid: failed to store INFO/nextjob.\n")); + tdb_unlock_bystring(pdb->tdb, "INFO/nextjob"); + return False; + } + + /* We've finished with the INFO/nextjob lock. */ + tdb_unlock_bystring(pdb->tdb, "INFO/nextjob"); + + if (!print_job_exists(sharename, jobid)) + break; + } + + if (i > 2) { + DEBUG(0, ("allocate_print_jobid: failed to allocate a print job for queue %s\n", + sharename)); + /* Probably full... */ + errno = ENOSPC; + return False; + } + + /* Store a dummy placeholder. */ + { + uint32_t tmp; + TDB_DATA dum; + dum.dptr = NULL; + dum.dsize = 0; + if (tdb_store(pdb->tdb, print_key(jobid, &tmp), dum, + TDB_INSERT) == -1) { + DEBUG(3, ("allocate_print_jobid: jobid (%d) failed to store placeholder.\n", + jobid )); + return False; + } + } + + *pjobid = jobid; + return True; +} + +/*************************************************************************** + Append a jobid to the 'jobs changed' list. +***************************************************************************/ + +static bool add_to_jobs_changed(struct tdb_print_db *pdb, uint32 jobid) +{ + TDB_DATA data; + uint32 store_jobid; + + SIVAL(&store_jobid, 0, jobid); + data.dptr = (uint8 *)&store_jobid; + data.dsize = 4; + + DEBUG(10,("add_to_jobs_changed: Added jobid %u\n", (unsigned int)jobid )); + + return (tdb_append(pdb->tdb, string_tdb_data("INFO/jobs_changed"), + data) == 0); +} + +/*************************************************************************** + Start spooling a job - return the jobid. +***************************************************************************/ + +uint32 print_job_start(struct auth_serversupplied_info *server_info, int snum, + char *jobname, NT_DEVICEMODE *nt_devmode ) +{ + uint32 jobid; + char *path; + struct printjob pjob; + const char *sharename = lp_const_servicename(snum); + struct tdb_print_db *pdb = get_print_db_byname(sharename); + int njobs; + + errno = 0; + + if (!pdb) + return (uint32)-1; + + if (!print_access_check(server_info, snum, PRINTER_ACCESS_USE)) { + DEBUG(3, ("print_job_start: job start denied by security descriptor\n")); + release_print_db(pdb); + return (uint32)-1; + } + + if (!print_time_access_check(lp_servicename(snum))) { + DEBUG(3, ("print_job_start: job start denied by time check\n")); + release_print_db(pdb); + return (uint32)-1; + } + + path = lp_pathname(snum); + + /* see if we have sufficient disk space */ + if (lp_minprintspace(snum)) { + SMB_BIG_UINT dspace, dsize; + if (sys_fsusage(path, &dspace, &dsize) == 0 && + dspace < 2*(SMB_BIG_UINT)lp_minprintspace(snum)) { + DEBUG(3, ("print_job_start: disk space check failed.\n")); + release_print_db(pdb); + errno = ENOSPC; + return (uint32)-1; + } + } + + /* for autoloaded printers, check that the printcap entry still exists */ + if (lp_autoloaded(snum) && !pcap_printername_ok(lp_const_servicename(snum))) { + DEBUG(3, ("print_job_start: printer name %s check failed.\n", lp_const_servicename(snum) )); + release_print_db(pdb); + errno = ENOENT; + return (uint32)-1; + } + + /* Insure the maximum queue size is not violated */ + if ((njobs = print_queue_length(snum,NULL)) > lp_maxprintjobs(snum)) { + DEBUG(3, ("print_job_start: Queue %s number of jobs (%d) larger than max printjobs per queue (%d).\n", + sharename, njobs, lp_maxprintjobs(snum) )); + release_print_db(pdb); + errno = ENOSPC; + return (uint32)-1; + } + + DEBUG(10,("print_job_start: Queue %s number of jobs (%d), max printjobs = %d\n", + sharename, njobs, lp_maxprintjobs(snum) )); + + if (!allocate_print_jobid(pdb, snum, sharename, &jobid)) + goto fail; + + /* create the database entry */ + + ZERO_STRUCT(pjob); + + pjob.pid = sys_getpid(); + pjob.sysjob = -1; + pjob.fd = -1; + pjob.starttime = time(NULL); + pjob.status = LPQ_SPOOLING; + pjob.size = 0; + pjob.spooled = False; + pjob.smbjob = True; + pjob.nt_devmode = nt_devmode; + + fstrcpy(pjob.jobname, jobname); + + fstrcpy(pjob.user, lp_printjob_username(snum)); + standard_sub_advanced(sharename, server_info->sanitized_username, + path, server_info->utok.gid, + server_info->sanitized_username, + pdb_get_domain(server_info->sam_account), + pjob.user, sizeof(pjob.user)-1); + /* ensure NULL termination */ + pjob.user[sizeof(pjob.user)-1] = '\0'; + + fstrcpy(pjob.queuename, lp_const_servicename(snum)); + + /* we have a job entry - now create the spool file */ + slprintf(pjob.filename, sizeof(pjob.filename)-1, "%s/%s%.8u.XXXXXX", + path, PRINT_SPOOL_PREFIX, (unsigned int)jobid); + pjob.fd = smb_mkstemp(pjob.filename); + + if (pjob.fd == -1) { + if (errno == EACCES) { + /* Common setup error, force a report. */ + DEBUG(0, ("print_job_start: insufficient permissions \ +to open spool file %s.\n", pjob.filename)); + } else { + /* Normal case, report at level 3 and above. */ + DEBUG(3, ("print_job_start: can't open spool file %s,\n", pjob.filename)); + DEBUGADD(3, ("errno = %d (%s).\n", errno, strerror(errno))); + } + goto fail; + } + + pjob_store(sharename, jobid, &pjob); + + /* Update the 'jobs changed' entry used by print_queue_status. */ + add_to_jobs_changed(pdb, 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); + + return jobid; + + fail: + if (jobid != -1) + pjob_delete(sharename, jobid); + + release_print_db(pdb); + + DEBUG(3, ("print_job_start: returning fail. Error = %s\n", strerror(errno) )); + return (uint32)-1; +} + +/**************************************************************************** + Update the number of pages spooled to jobid +****************************************************************************/ + +void print_job_endpage(int snum, uint32 jobid) +{ + const char* sharename = lp_const_servicename(snum); + struct printjob *pjob; + + pjob = print_job_find(sharename, jobid); + if (!pjob) + return; + /* don't allow another process to get this info - it is meaningless */ + if (pjob->pid != sys_getpid()) + return; + + pjob->page_count++; + pjob_store(sharename, jobid, pjob); +} + +/**************************************************************************** + 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. +****************************************************************************/ + +bool print_job_end(int snum, uint32 jobid, enum file_close_type close_type) +{ + const char* sharename = lp_const_servicename(snum); + struct printjob *pjob; + int ret; + SMB_STRUCT_STAT sbuf; + struct printif *current_printif = get_printer_fns( snum ); + + pjob = print_job_find(sharename, jobid); + + if (!pjob) + return False; + + if (pjob->spooled || pjob->pid != sys_getpid()) + return False; + + if ((close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) && + (sys_fstat(pjob->fd, &sbuf) == 0)) { + pjob->size = sbuf.st_size; + close(pjob->fd); + pjob->fd = -1; + } else { + + /* + * Not a normal close or we couldn't stat the job file, + * so something has gone wrong. Cleanup. + */ + close(pjob->fd); + pjob->fd = -1; + DEBUG(3,("print_job_end: failed to stat file for jobid %d\n", jobid )); + 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(sharename, jobid); + return True; + } + + pjob->smbjob = jobid; + + ret = (*(current_printif->job_submit))(snum, pjob); + + if (ret) + goto fail; + + /* The print job has been successfully handed over to the back-end */ + + pjob->spooled = True; + pjob->status = LPQ_QUEUED; + pjob_store(sharename, jobid, pjob); + + /* make sure the database is up to date */ + if (print_cache_expired(lp_const_servicename(snum), True)) + print_queue_update(snum, False); + + return True; + +fail: + + /* The print job was not successfully started. Cleanup */ + /* Still need to add proper error return propagation! 010122:JRR */ + unlink(pjob->filename); + pjob_delete(sharename, jobid); + return False; +} + +/**************************************************************************** + Get a snapshot of jobs in the system without traversing. +****************************************************************************/ + +static bool get_stored_queue_info(struct tdb_print_db *pdb, int snum, int *pcount, print_queue_struct **ppqueue) +{ + TDB_DATA data, cgdata; + print_queue_struct *queue = NULL; + uint32 qcount = 0; + uint32 extra_count = 0; + int total_count = 0; + size_t len = 0; + uint32 i; + int max_reported_jobs = lp_max_reported_jobs(snum); + bool ret = False; + const char* sharename = lp_servicename(snum); + + /* make sure the database is up to date */ + if (print_cache_expired(lp_const_servicename(snum), True)) + print_queue_update(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 changed jobs list. */ + cgdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_changed")); + if (cgdata.dptr != NULL && (cgdata.dsize % 4 == 0)) + extra_count = cgdata.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 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].job = 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 in the changed jobids. */ + for( i = 0; i < extra_count; i++) { + uint32 jobid; + struct printjob *pjob; + + jobid = IVAL(cgdata.dptr, i*4); + DEBUG(5,("get_stored_queue_info: changed job = %u\n", (unsigned int)jobid)); + pjob = print_job_find(lp_const_servicename(snum), jobid); + if (!pjob) { + DEBUG(5,("get_stored_queue_info: failed to find changed job = %u\n", (unsigned int)jobid)); + remove_from_jobs_changed(sharename, jobid); + continue; + } + + queue[total_count].job = jobid; + 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++; + } + + /* Sort the queue by submission time otherwise they are displayed + in hash order. */ + + qsort(queue, total_count, sizeof(print_queue_struct), QSORT_CAST(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); + 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(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(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(pdb, snum, &count, ppqueue)) { + release_print_db(pdb); + return 0; + } + + release_print_db(pdb); + return count; +} + +/**************************************************************************** + Pause a queue. +****************************************************************************/ + +bool print_queue_pause(struct auth_serversupplied_info *server_info, int snum, + WERROR *errcode) +{ + int ret; + struct printif *current_printif = get_printer_fns( snum ); + + if (!print_access_check(server_info, snum, + PRINTER_ACCESS_ADMINISTER)) { + *errcode = WERR_ACCESS_DENIED; + return False; + } + + + become_root(); + + ret = (*(current_printif->queue_pause))(snum); + + unbecome_root(); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* force update the database */ + print_cache_flush(lp_const_servicename(snum)); + + /* Send a printer notify message */ + + notify_printer_status(snum, PRINTER_STATUS_PAUSED); + + return True; +} + +/**************************************************************************** + Resume a queue. +****************************************************************************/ + +bool print_queue_resume(struct auth_serversupplied_info *server_info, int snum, + WERROR *errcode) +{ + int ret; + struct printif *current_printif = get_printer_fns( snum ); + + if (!print_access_check(server_info, snum, + PRINTER_ACCESS_ADMINISTER)) { + *errcode = WERR_ACCESS_DENIED; + return False; + } + + become_root(); + + ret = (*(current_printif->queue_resume))(snum); + + unbecome_root(); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* make sure the database is up to date */ + if (print_cache_expired(lp_const_servicename(snum), True)) + print_queue_update(snum, True); + + /* Send a printer notify message */ + + notify_printer_status(snum, PRINTER_STATUS_OK); + + return True; +} + +/**************************************************************************** + Purge a queue - implemented by deleting all jobs that we can delete. +****************************************************************************/ + +bool print_queue_purge(struct auth_serversupplied_info *server_info, int snum, + WERROR *errcode) +{ + 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(snum, True); + + can_job_admin = print_access_check(server_info, snum, + JOB_ACCESS_ADMINISTER); + njobs = print_queue_status(snum, &queue, &status); + + if ( can_job_admin ) + become_root(); + + for (i=0;i<njobs;i++) { + bool owner = is_owner(server_info, lp_const_servicename(snum), + queue[i].job); + + if (owner || can_job_admin) { + print_job_delete1(snum, queue[i].job); + } + } + + if ( can_job_admin ) + unbecome_root(); + + /* update the cache */ + print_queue_update( snum, True ); + + SAFE_FREE(queue); + + return True; +} diff --git a/source3/printing/printing_db.c b/source3/printing/printing_db.c new file mode 100644 index 0000000000..94319f96d0 --- /dev/null +++ b/source3/printing/printing_db.c @@ -0,0 +1,215 @@ +/* + 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 "printing.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; + int num_open = 0; + char *printdb_path = NULL; + bool done_become_root = False; + + 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(print_db_head->tdb)) { + DEBUG(0,("get_print_db: Failed to close tdb for printer %s\n", + print_db_head->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); + } + + if (asprintf(&printdb_path, "%s%s.tdb", + lock_path("printing/"), + printername) < 0) { + DLIST_REMOVE(print_db_head, p); + SAFE_FREE(p); + return NULL; + } + + if (geteuid() != 0) { + 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(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 == sys_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; +} + + |