1 /* cups-pdf.c -- CUPS Backend (version 3.0.1, 2017-02-24)
2 08.02.2003, Volker C. Behr
3 volker@cups-pdf.de
4 http://www.cups-pdf.de
5
6 This code may be freely distributed as long as this header
7 is preserved.
8
9 This code is distributed under the GPL.
10 (http://www.gnu.org/copyleft/gpl.html)
11
12 ---------------------------------------------------------------------------
13
14 Copyright (C) 2003-2017 Volker C. Behr
15
16 This program is free software; you can redistribute it and/or
17 modify it under the terms of the GNU General Public License
18 as published by the Free Software Foundation; either version 2
19 of the License, or (at your option) any later version.
20
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29
30 ---------------------------------------------------------------------------
31
32 If you want to redistribute modified sources/binaries this header
33 has to be preserved and all modifications should be clearly
34 indicated.
35 In case you want to include this code into your own programs
36 I would appreciate your feedback via email.
37
38
39 HISTORY: see ChangeLog in the parent directory of the source archive
40 */
41
42 #include <time.h>
43 #include <errno.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <fcntl.h>
47 #include <string.h>
48 #include <ctype.h>
49 #include <unistd.h>
50 #include <stddef.h>
51 #include <pwd.h>
52 #include <grp.h>
53 #include <stdarg.h>
54 #include <dirent.h>
55 #include <sys/types.h>
56 #include <sys/stat.h>
57 #include <sys/wait.h>
58
59 #include <cups/cups.h>
60 #include <cups/ppd.h>
61 #include <cups/backend.h>
62
63 #include "cups-pdf.h"
64
65
66 static FILE *logfp=NULL;
67
68
log_event(short type,const char * message,...)69 static void log_event(short type, const char *message, ...) {
70 time_t secs;
71 int error=errno;
72 char ctype[8], *timestring;
73 cp_string logbuffer;
74 va_list ap;
75
76 if ((logfp != NULL) && (type & Conf_LogType)) {
77 (void) time(&secs);
78 timestring=ctime(&secs);
79 timestring[strlen(timestring)-1]='\0';
80
81 if (type == CPERROR)
82 snprintf(ctype, 8, "ERROR");
83 else if (type == CPSTATUS)
84 snprintf(ctype, 8, "STATUS");
85 else
86 snprintf(ctype, 8, "DEBUG");
87
88 va_start(ap, message);
89 vsnprintf(logbuffer, BUFSIZE, message, ap);
90 va_end(ap);
91
92 fprintf(logfp,"%s [%s] %s\n", timestring, ctype, logbuffer);
93 if ((Conf_LogType & CPDEBUG) && (type == CPERROR) && error)
94 fprintf(logfp,"%s [DEBUG] ERRNO: %d (%s)\n", timestring, error, strerror(error));
95
96 (void) fflush(logfp);
97 }
98
99 return;
100 }
101
create_dir(char * dirname,int nolog)102 static int create_dir(char *dirname, int nolog) {
103 struct stat fstatus;
104 char buffer[BUFSIZE],*delim;
105 int i;
106
107 while ((i=strlen(dirname))>1 && dirname[i-1]=='/')
108 dirname[i-1]='\0';
109 if (stat(dirname, &fstatus) || !S_ISDIR(fstatus.st_mode)) {
110 strncpy(buffer,dirname,BUFSIZE);
111 delim=strrchr(buffer,'/');
112 if (delim!=buffer)
113 delim[0]='\0';
114 else
115 delim[1]='\0';
116 if (create_dir(buffer,nolog)!=0)
117 return 1;
118 (void) stat(buffer, &fstatus);
119 if (mkdir(dirname,fstatus.st_mode)!=0) {
120 if (!nolog)
121 log_event(CPERROR, "failed to create directory: %s", dirname);
122 return 1;
123 }
124 else
125 if (!nolog)
126 log_event(CPSTATUS, "directory created: %s", dirname);
127 if (chown(dirname,fstatus.st_uid,fstatus.st_gid)!=0)
128 if (!nolog)
129 log_event(CPDEBUG, "failed to set owner on directory: %s (non fatal)", dirname);
130 }
131 return 0;
132 }
133
_assign_value(int security,char * key,char * value)134 static int _assign_value(int security, char *key, char *value) {
135 int tmp;
136 int option;
137
138 for (option=0; option<END_OF_OPTIONS; option++) {
139 if (!strcasecmp(key, configData[option].key_name))
140 break;
141 }
142
143 if (option == END_OF_OPTIONS) {
144 return 0;
145 }
146
147 if (!(security & configData[option].security) && !(Conf_AllowUnsafeOptions)) {
148 log_event(CPERROR, "Unsafe option not allowed: %s", key);
149 return 0;
150 }
151
152 switch(option) {
153 case AnonDirName:
154 strncpy(Conf_AnonDirName, value, BUFSIZE);
155 break;
156 case AnonUser:
157 strncpy(Conf_AnonUser, value, BUFSIZE);
158 break;
159 case GhostScript:
160 strncpy(Conf_GhostScript, value, BUFSIZE);
161 break;
162 case GSCall:
163 strncpy(Conf_GSCall, value, BUFSIZE);
164 break;
165 case Grp:
166 strncpy(Conf_Grp, value, BUFSIZE);
167 break;
168 case GSTmp:
169 snprintf(Conf_GSTmp, BUFSIZE, "%s%s", "TMPDIR=", value);
170 break;
171 case Log:
172 strncpy(Conf_Log, value, BUFSIZE);
173 break;
174 case PDFVer:
175 strncpy(Conf_PDFVer, value, BUFSIZE);
176 break;
177 case PostProcessing:
178 strncpy(Conf_PostProcessing, value, BUFSIZE);
179 break;
180 case Out:
181 strncpy(Conf_Out, value, BUFSIZE);
182 break;
183 case Spool:
184 strncpy(Conf_Spool, value, BUFSIZE);
185 break;
186 case UserPrefix:
187 strncpy(Conf_UserPrefix, value, BUFSIZE);
188 break;
189 case RemovePrefix:
190 strncpy(Conf_RemovePrefix, value, BUFSIZE);
191 break;
192 case Cut:
193 tmp=atoi(value);
194 Conf_Cut=(tmp>=-1)?tmp:-1;
195 break;
196 case Truncate:
197 tmp=atoi(value);
198 Conf_Truncate=(tmp>=8)?tmp:8;
199 break;
200 case DirPrefix:
201 tmp=atoi(value);
202 Conf_DirPrefix=(tmp)?1:0;
203 break;
204 case Label:
205 tmp=atoi(value);
206 Conf_Label=(tmp>2)?2:((tmp<0)?0:tmp);
207 break;
208 case LogType:
209 tmp=atoi(value);
210 Conf_LogType=(tmp>7)?7:((tmp<0)?0:tmp);
211 break;
212 case LowerCase:
213 tmp=atoi(value);
214 Conf_LowerCase=(tmp)?1:0;
215 break;
216 case TitlePref:
217 tmp=atoi(value);
218 Conf_TitlePref=(tmp)?1:0;
219 break;
220 case DecodeHexStrings:
221 tmp=atoi(value);
222 Conf_DecodeHexStrings=(tmp)?1:0;
223 break;
224 case FixNewlines:
225 tmp=atoi(value);
226 Conf_FixNewlines=(tmp)?1:0;
227 break;
228 case AnonUMask:
229 tmp=(int)strtol(value,NULL,8);
230 Conf_AnonUMask=(mode_t)tmp;
231 break;
232 case UserUMask:
233 tmp=(int)strtol(value,NULL,8);
234 Conf_UserUMask=(mode_t)tmp;
235 break;
236 default:
237 log_event(CPERROR, "Program error: option not treated: %s = %s\n", key, value);
238 return 0;
239 }
240 return 1;
241 }
242
read_config_file(char * filename)243 static void read_config_file(char *filename) {
244 FILE *fp=NULL;
245 struct stat fstatus;
246 cp_string buffer, key, value;
247
248 if ((strlen(filename) > 1) && (!stat(filename, &fstatus)) &&
249 (S_ISREG(fstatus.st_mode) || S_ISLNK(fstatus.st_mode))) {
250 fp=fopen(filename,"r");
251 }
252 if (fp==NULL) {
253 log_event(CPERROR, "Cannot open config: %s", filename);
254 return;
255 }
256
257 while (fgets(buffer, BUFSIZE, fp) != NULL) {
258 key[0]='\0';
259 value[0]='\0';
260 if (sscanf(buffer,"%s %[^\n]",key,value)) {
261 if (!strlen(key) || !strncmp(key,"#",1))
262 continue;
263 _assign_value(SEC_CONF, key, value);
264 }
265 }
266
267 (void) fclose(fp);
268 return;
269 }
270
read_config_ppd()271 static void read_config_ppd() {
272 ppd_option_t *option;
273 ppd_file_t *ppd_file;
274 char * ppd_name;
275
276 ppd_name = getenv("PPD");
277 if (ppd_name == NULL) {
278 log_event(CPERROR, "Could not retrieve PPD name");
279 return;
280 }
281 ppd_file = ppdOpenFile(ppd_name);
282 if (ppd_file == NULL) {
283 log_event(CPERROR, "Could not open PPD file: %s", ppd_name);
284 return;
285 }
286 ppdMarkDefaults(ppd_file);
287
288 option = ppdFirstOption(ppd_file);
289 while (option != NULL) {
290 _assign_value(SEC_PPD, option->keyword, option->defchoice);
291 option = ppdNextOption(ppd_file);
292 }
293 ppdClose(ppd_file);
294
295 return;
296 }
297
read_config_options(const char * lpoptions)298 static void read_config_options(const char *lpoptions) {
299 int i;
300 int num_options;
301 cups_option_t *options;
302 cups_option_t *option;
303
304 num_options = cupsParseOptions(lpoptions, 0, &options);
305
306 for (i = 0, option = options; i < num_options; i ++, option ++) {
307
308 /* replace all _ by " " in value */
309 int j;
310 for (j=0; option->value[j]!= '\0'; j++) {
311 if (option->value[j] == '_') {
312 option->value[j] = ' ';
313 }
314 }
315 _assign_value(SEC_LPOPT, option->name, option->value);
316 }
317 return;
318 }
319
dump_configuration()320 static void dump_configuration() {
321 if (Conf_LogType & CPDEBUG) {
322 log_event(CPDEBUG, "*** Final Configuration ***");
323 log_event(CPDEBUG, "AnonDirName = \"%s\"", Conf_AnonDirName);
324 log_event(CPDEBUG, "AnonUser = \"%s\"", Conf_AnonUser);
325 log_event(CPDEBUG, "GhostScript = \"%s\"", Conf_GhostScript);
326 log_event(CPDEBUG, "GSCall = \"%s\"", Conf_GSCall);
327 log_event(CPDEBUG, "Grp = \"%s\"", Conf_Grp);
328 log_event(CPDEBUG, "GSTmp = \"%s\"", Conf_GSTmp);
329 log_event(CPDEBUG, "Log = \"%s\"", Conf_Log);
330 log_event(CPDEBUG, "PDFVer = \"%s\"", Conf_PDFVer);
331 log_event(CPDEBUG, "PostProcessing = \"%s\"", Conf_PostProcessing);
332 log_event(CPDEBUG, "Out = \"%s\"", Conf_Out);
333 log_event(CPDEBUG, "Spool = \"%s\"", Conf_Spool);
334 log_event(CPDEBUG, "UserPrefix = \"%s\"", Conf_UserPrefix);
335 log_event(CPDEBUG, "RemovePrefix = \"%s\"", Conf_RemovePrefix);
336 log_event(CPDEBUG, "OutExtension = \"%s\"", Conf_OutExtension);
337 log_event(CPDEBUG, "Cut = %d", Conf_Cut);
338 log_event(CPDEBUG, "Truncate = %d", Conf_Truncate);
339 log_event(CPDEBUG, "DirPrefix = %d", Conf_DirPrefix);
340 log_event(CPDEBUG, "Label = %d", Conf_Label);
341 log_event(CPDEBUG, "LogType = %d", Conf_LogType);
342 log_event(CPDEBUG, "LowerCase = %d", Conf_LowerCase);
343 log_event(CPDEBUG, "TitlePref = %d", Conf_TitlePref);
344 log_event(CPDEBUG, "DecodeHexStrings = %d", Conf_DecodeHexStrings);
345 log_event(CPDEBUG, "FixNewlines = %d", Conf_FixNewlines);
346 log_event(CPDEBUG, "AllowUnsafeOptions = %d", Conf_AllowUnsafeOptions);
347 log_event(CPDEBUG, "AnonUMask = %04o", Conf_AnonUMask);
348 log_event(CPDEBUG, "UserUMask = %04o", Conf_UserUMask);
349 log_event(CPDEBUG, "*** End of Configuration ***");
350 }
351 return;
352 }
353
init(char * argv[])354 static int init(char *argv[]) {
355 struct stat fstatus;
356 struct group *group;
357 cp_string filename;
358 int grpstat;
359 const char *uri=cupsBackendDeviceURI(argv);
360
361 if ((uri != NULL) && (strncmp(uri, "cups-pdf:/", 10) == 0) && strlen(uri) > 10) {
362 uri = uri + 10;
363 sprintf(filename, "%s/cups-pdf-%s.conf", CP_CONFIG_PATH, uri);
364 }
365 else {
366 sprintf(filename, "%s/cups-pdf.conf", CP_CONFIG_PATH);
367 }
368 read_config_file(filename);
369
370 read_config_ppd();
371
372 read_config_options(argv[5]);
373
374 (void) umask(0077);
375
376 group=getgrnam(Conf_Grp);
377 grpstat=setgid(group->gr_gid);
378
379 if (strlen(Conf_Log)) {
380 if (stat(Conf_Log, &fstatus) || !S_ISDIR(fstatus.st_mode)) {
381 if (create_dir(Conf_Log, 1))
382 return 1;
383 if (chmod(Conf_Log, 0700))
384 return 1;
385 }
386 snprintf(filename, BUFSIZE, "%s/%s%s%s", Conf_Log, "cups-pdf-", getenv("PRINTER"), "_log");
387 logfp=fopen(filename, "a");
388 }
389
390 dump_configuration();
391
392 if (!group) {
393 log_event(CPERROR, "Grp not found: %s", Conf_Grp);
394 return 1;
395 }
396 else if (grpstat) {
397 log_event(CPERROR, "failed to set new gid: %s", Conf_Grp);
398 return 1;
399 }
400 else
401 log_event(CPDEBUG, "set new gid: %s", Conf_Grp);
402
403 (void) umask(0022);
404
405 if (stat(Conf_Spool, &fstatus) || !S_ISDIR(fstatus.st_mode)) {
406 if (create_dir(Conf_Spool, 0)) {
407 log_event(CPERROR, "failed to create spool directory: %s", Conf_Spool);
408 return 1;
409 }
410 if (chmod(Conf_Spool, 0751)) {
411 log_event(CPERROR, "failed to set mode on spool directory: %s", Conf_Spool);
412 return 1;
413 }
414 if (chown(Conf_Spool, -1, group->gr_gid))
415 log_event(CPERROR, "failed to set group id %s on spool directory: %s (non fatal)", Conf_Grp, Conf_Spool);
416 log_event(CPSTATUS, "spool directory created: %s", Conf_Spool);
417 }
418
419 (void) umask(0077);
420 return 0;
421 }
422
announce_printers()423 static void announce_printers() {
424 DIR *dir;
425 struct dirent *config_ent;
426 int len;
427 cp_string setup;
428
429 printf("file cups-pdf:/ \"Virtual PDF Printer\" \"CUPS-PDF\" \"MFG:Generic;MDL:CUPS-PDF Printer;DES:Generic CUPS-PDF Printer;CLS:PRINTER;CMD:POSTSCRIPT;\"\n");
430
431 if ((dir = opendir(CP_CONFIG_PATH)) != NULL) {
432 while ((config_ent = readdir(dir)) != NULL) {
433 len = strlen(config_ent->d_name);
434 if ((strncmp(config_ent->d_name, "cups-pdf-", 9) == 0) &&
435 (len > 14 && strcmp(config_ent->d_name + len - 5, ".conf") == 0)) {
436 strncpy(setup, config_ent->d_name + 9, BUFSIZE>len-14 ? len-14 : BUFSIZE);
437 setup[BUFSIZE>len-14 ? len-14 : BUFSIZE - 1] = '\0';
438 printf("file cups-pdf:/%s \"Virtual %s Printer\" \"CUPS-PDF\" \"MFG:Generic;MDL:CUPS-PDF Printer;DES:Generic CUPS-PDF Printer;CLS:PRINTER;CMD:POSTSCRIPT;\"\n", setup, setup);
439 }
440 }
441 closedir(dir);
442 }
443 return;
444 }
445
preparedirname(struct passwd * passwd,char * uname)446 static char *preparedirname(struct passwd *passwd, char *uname) {
447 int size;
448 char bufin[BUFSIZE], bufout[BUFSIZE], *needle, *cptr;
449
450 needle=strstr(uname, Conf_RemovePrefix);
451 if ((int)strlen(uname)>(size=strlen(Conf_RemovePrefix)))
452 uname=uname+size;
453
454 strncpy(bufin, Conf_Out, BUFSIZE);
455 do {
456 needle=strstr(bufin, "${HOME}");
457 if (needle == NULL)
458 break;
459 needle[0]='\0';
460 cptr=needle+7;
461 snprintf(bufout, BUFSIZE, "%s%s%s", bufin, passwd->pw_dir, cptr);
462 strncpy(bufin, bufout, BUFSIZE);
463 } while (needle != NULL);
464 do {
465 needle=strstr(bufin, "${USER}");
466 if (needle == NULL)
467 break;
468 needle[0]='\0';
469 cptr=needle+7;
470 if (!Conf_DirPrefix)
471 snprintf(bufout, BUFSIZE, "%s%s%s", bufin, uname, cptr);
472 else
473 snprintf(bufout, BUFSIZE, "%s%s%s", bufin, passwd->pw_name, cptr);
474 strncpy(bufin, bufout, BUFSIZE);
475 } while (needle != NULL);
476 size=strlen(bufin)+1;
477 cptr=calloc(size, sizeof(char));
478 if (cptr == NULL)
479 return NULL;
480 snprintf(cptr,size,"%s",bufin);
481 return cptr;
482 }
483
prepareuser(struct passwd * passwd,char * dirname)484 static int prepareuser(struct passwd *passwd, char *dirname) {
485 struct stat fstatus;
486
487 (void) umask(0000);
488 if (stat(dirname, &fstatus) || !S_ISDIR(fstatus.st_mode)) {
489 if (!strcmp(passwd->pw_name, Conf_AnonUser)) {
490 if (create_dir(dirname, 0)) {
491 log_event(CPERROR, "failed to create anonymous output directory: %s", dirname);
492 return 1;
493 }
494 if (chmod(dirname, (mode_t)(0777&~Conf_AnonUMask))) {
495 log_event(CPERROR, "failed to set mode on anonymous output directory: %s", dirname);
496 return 1;
497 }
498 log_event(CPDEBUG, "anonymous output directory created: %s", dirname);
499 }
500 else {
501 if (create_dir(dirname, 0)) {
502 log_event(CPERROR, "failed to create user output directory: %s", dirname);
503 return 1;
504 }
505 if (chmod(dirname, (mode_t)(0777&~Conf_UserUMask))) {
506 log_event(CPERROR, "failed to set mode on user output directory: %s", dirname);
507 return 1;
508 }
509 log_event(CPDEBUG, "user output directory created: %s", dirname);
510 }
511 if (chown(dirname, passwd->pw_uid, passwd->pw_gid)) {
512 log_event(CPERROR, "failed to set owner for output directory: %s", passwd->pw_name);
513 return 1;
514 }
515 log_event(CPDEBUG, "owner set for output directory: %s", passwd->pw_name);
516 }
517 (void) umask(0077);
518 return 0;
519 }
520
521 /* no validation is done here, please use is_ps_hex_string for that */
decode_ps_hex_string(char * string)522 static void decode_ps_hex_string(char *string) {
523 char *src_ptr, *dst_ptr;
524 int is_lower_digit; /* 0 - higher digit, 1 - lower digit */
525 char number, digit;
526
527 dst_ptr=string; /* we should always be behind src_ptr,
528 so it's safe to write over original string */
529 number=(char)0;
530 is_lower_digit=0;
531 for (src_ptr=string+1;*src_ptr != '>';src_ptr++) { /* begin after start marker */
532 if (*src_ptr == ' ' || *src_ptr == '\t' ) { /* skip whitespace */
533 continue;
534 }
535 if (*src_ptr >= 'a') { /* assuming 0 < A < a */
536 digit=*src_ptr-'a'+(char)10;
537 }
538 else if (*src_ptr >= 'A') {
539 digit=*src_ptr-'A'+(char)10;
540 }
541 else {
542 digit=*src_ptr-'0';
543 }
544 if (is_lower_digit) {
545 number|=digit;
546 *dst_ptr=number; /* write character */
547 dst_ptr++;
548 is_lower_digit=0;
549 }
550 else { /* higher digit */
551 number=digit<<4;
552 is_lower_digit=1;
553 }
554 }
555 if (is_lower_digit) { /* write character with lower digit = 0,
556 as per PostScript Language Reference */
557 *dst_ptr=number;
558 dst_ptr++;
559 /* is_lower_digit=0; */
560 }
561 *dst_ptr=0; /* finish him! */
562 return;
563 }
564
is_ps_hex_string(char * string)565 static int is_ps_hex_string(char *string) {
566 int got_end_marker=0;
567 char *ptr;
568
569 if (string[0] != '<') { /* if has no start marker */
570 log_event(CPDEBUG, "not a hex string, has no start marker: %s", string);
571 return 0; /* not hex string, obviously */
572 }
573 for (ptr=string+1;*ptr;ptr++) { /* begin after start marker */
574 if (got_end_marker) { /* got end marker and still something left */
575 log_event(CPDEBUG, "not a hex string, trailing characters after end marker: %s", ptr);
576 return 0; /* that's bad! */
577 }
578 else if (*ptr == '>') { /* here it is! */
579 got_end_marker=1;
580 log_event(CPDEBUG, "got an end marker in the hex string, expecting 0-termination: %s", ptr);
581 }
582 else if ( !(
583 isxdigit(*ptr) ||
584 *ptr == ' ' ||
585 *ptr == '\t'
586 ) ) {
587 log_event(CPDEBUG, "not a hex string, invalid character: %s", ptr);
588 return 0; /* that's bad, too */
589 }
590 }
591 return got_end_marker;
592 }
593
alternate_replace_string(char * string)594 static void alternate_replace_string(char *string) {
595 unsigned int i;
596
597 log_event(CPDEBUG, "removing alternate special characters from title: %s", string);
598 for (i=0;i<(unsigned int)strlen(string);i++)
599 if ( isascii(string[i]) && /* leaving non-ascii characters intact */
600 (!isalnum(string[i])) &&
601 string[i] != '-' && string[i] != '+' && string[i] != '.')
602 string[i]='_';
603 return;
604 }
605
replace_string(char * string)606 static void replace_string(char *string) {
607 unsigned int i;
608
609 log_event(CPDEBUG, "removing special characters from title: %s", string);
610 for (i=0;i<(unsigned int)strlen(string);i++)
611 if ( ( string[i] < '0' || string[i] > '9' ) &&
612 ( string[i] < 'A' || string[i] > 'Z' ) &&
613 ( string[i] < 'a' || string[i] > 'z' ) &&
614 string[i] != '-' && string[i] != '+' && string[i] != '.')
615 string[i]='_';
616 return;
617 }
618
preparetitle(char * title)619 static int preparetitle(char *title) {
620 char *cut;
621 int i;
622
623 if (title != NULL) {
624 if (Conf_DecodeHexStrings) {
625 log_event(CPSTATUS, "***Experimental Option: DecodeHexStrings");
626 log_event(CPDEBUG, "checking for hex strings: %s", title);
627 if (is_ps_hex_string(title))
628 decode_ps_hex_string(title);
629 log_event(CPDEBUG, "calling alternate_replace_string");
630 alternate_replace_string(title);
631 }
632 else {
633 replace_string(title);
634 }
635 i=strlen(title);
636 if (i>1) {
637 while (title[--i]=='_');
638 if (i<strlen(title)-1) {
639 log_event(CPDEBUG, "removing trailing _ from title: %s", title);
640 title[i+1]='\0';
641 }
642 i=0;
643 while (title[i++]=='_');
644 if (i>1) {
645 log_event(CPDEBUG, "removing leading _ from title: %s", title);
646 memmove(title, title+i-1, strlen(title)-i+2);
647 }
648 }
649 while (strlen(title)>2 && title[0]=='(' && title[strlen(title)-1]==')') {
650 log_event(CPDEBUG, "removing enclosing parentheses () from full title: %s", title);
651 title[strlen(title)-1]='\0';
652 memmove(title, title+1, strlen(title));
653 }
654 }
655 cut=strrchr(title, '/');
656 if (cut != NULL) {
657 log_event(CPDEBUG, "removing slashes from full title: %s", title);
658 memmove(title, cut+1, strlen(cut+1)+1);
659 }
660 cut=strrchr(title, '\\');
661 if (cut != NULL) {
662 log_event(CPDEBUG, "removing backslashes from full title: %s", title);
663 memmove(title, cut+1, strlen(cut+1)+1);
664 }
665 cut=strrchr(title, '.');
666 if ((cut != NULL) && ((int)strlen(cut) <= Conf_Cut+1) && (cut != title)) {
667 log_event(CPDEBUG, "removing file name extension: %s", cut);
668 cut[0]='\0';
669 }
670 if (strlen(title)>Conf_Truncate) {
671 title[Conf_Truncate]='\0';
672 log_event(CPDEBUG, "truncating title: %s", title);
673 }
674 return strcmp(title, "");
675 }
676
fgets2(char * fbuffer,int fbufsize,FILE * ffpsrc)677 static char *fgets2(char *fbuffer, int fbufsize, FILE *ffpsrc) {
678 /* like fgets() but linedelimiters are 0x0A, 0x0C, 0x0D (LF, FF, CR). */
679 int c, pos;
680 char *result;
681
682 if (!Conf_FixNewlines)
683 return fgets(fbuffer, fbufsize, ffpsrc);
684
685 result=NULL;
686 pos=0;
687
688 while (pos < fbufsize) { /* pos in [0..fbufsize-1] */
689 c=fgetc(ffpsrc); /* converts CR/LF to LF in some OSses */
690 if (c == EOF) /* EOF _or_ error */
691 break;
692 fbuffer[pos++]=c;
693 if (c == 0x0A || c == 0x0C || c == 0x0D) /* line is at an end */
694 break;
695 }
696
697 if (pos > 0 && !ferror(ffpsrc)) { /* at least one char read and no error */
698 fbuffer[pos]='\0';
699 result=fbuffer;
700 }
701
702 return result;
703 }
704
preparespoolfile(FILE * fpsrc,char * spoolfile,char * title,char * cmdtitle,int job,struct passwd * passwd)705 static int preparespoolfile(FILE *fpsrc, char *spoolfile, char *title, char *cmdtitle,
706 int job, struct passwd *passwd) {
707 cp_string buffer;
708 int rec_depth,is_title=0;
709 FILE *fpdest;
710
711 if (fpsrc == NULL) {
712 log_event(CPERROR, "failed to open source stream");
713 return 1;
714 }
715 log_event(CPDEBUG, "source stream ready");
716 fpdest=fopen(spoolfile, "w");
717 if (fpdest == NULL) {
718 log_event(CPERROR, "failed to open spoolfile: %s", spoolfile);
719 (void) fclose(fpsrc);
720 return 1;
721 }
722 log_event(CPDEBUG, "destination stream ready: %s", spoolfile);
723 if (chown(spoolfile, passwd->pw_uid, -1)) {
724 log_event(CPERROR, "failed to set owner for spoolfile: %s", spoolfile);
725 return 1;
726 }
727 log_event(CPDEBUG, "owner set for spoolfile: %s", spoolfile);
728 rec_depth=0;
729 if (Conf_FixNewlines)
730 log_event(CPSTATUS, "***Experimental Option: FixNewlines");
731 else
732 log_event(CPDEBUG, "using traditional fgets");
733 while (fgets2(buffer, BUFSIZE, fpsrc) != NULL) {
734 if (!strncmp(buffer, "%!", 2) && strncmp(buffer, "%!PS-AdobeFont", 14)) {
735 log_event(CPDEBUG, "found beginning of postscript code: %s", buffer);
736 break;
737 }
738 }
739 log_event(CPDEBUG, "now extracting postscript code");
740 (void) fputs(buffer, fpdest);
741 while (fgets2(buffer, BUFSIZE, fpsrc) != NULL) {
742 (void) fputs(buffer, fpdest);
743 if (!is_title && !rec_depth)
744 if (sscanf(buffer, "%%%%Title: %"TBUFSIZE"c", title)==1) {
745 log_event(CPDEBUG, "found title in ps code: %s", title);
746 is_title=1;
747 }
748 if (!strncmp(buffer, "%!", 2)) {
749 log_event(CPDEBUG, "found embedded (e)ps code: %s", buffer);
750 rec_depth++;
751 }
752 else if (!strncmp(buffer, "%%EOF", 5)) {
753 if (!rec_depth) {
754 log_event(CPDEBUG, "found end of postscript code: %s", buffer);
755 break;
756 }
757 else {
758 log_event(CPDEBUG, "found end of embedded (e)ps code: %s", buffer);
759 rec_depth--;
760 }
761 }
762 }
763 (void) fclose(fpdest);
764 (void) fclose(fpsrc);
765 log_event(CPDEBUG, "all data written to spoolfile: %s", spoolfile);
766
767 if (cmdtitle == NULL || !strcmp(cmdtitle, "(stdin)"))
768 buffer[0]='\0';
769 else
770 strncpy(buffer, cmdtitle, BUFSIZE);
771 if (title == NULL || !strcmp(title, "((stdin))"))
772 title[0]='\0';
773
774 if (Conf_TitlePref) {
775 log_event(CPDEBUG, "trying to use commandline title: %s", buffer);
776 if (!preparetitle(buffer)) {
777 log_event(CPDEBUG, "empty commandline title, using PS title: %s", title);
778 if (!preparetitle(title))
779 log_event(CPDEBUG, "empty PS title");
780 }
781 else
782 snprintf(title, BUFSIZE, "%s", buffer);
783 }
784 else {
785 log_event(CPDEBUG, "trying to use PS title: %s", title);
786 if (!preparetitle(title)) {
787 log_event(CPDEBUG, "empty PS title, using commandline title: %s", buffer);
788 if (!preparetitle(buffer))
789 log_event(CPDEBUG, "empty commandline title");
790 else
791 snprintf(title, BUFSIZE, "%s", buffer);
792 }
793 }
794
795 if (!strcmp(title, "")) {
796 if (Conf_Label == 2)
797 snprintf(title, BUFSIZE, "untitled_document-job_%i", job);
798 else
799 snprintf(title, BUFSIZE, "job_%i-untitled_document", job);
800 log_event(CPDEBUG, "no title found - using default value: %s", title);
801 }
802 else {
803 if (Conf_Label) {
804 strcpy(buffer, title);
805 if (Conf_Label == 2)
806 snprintf(title, BUFSIZE, "%s-job_%i", buffer, job);
807 else
808 snprintf(title, BUFSIZE, "job_%i-%s", job, buffer);
809 }
810 log_event(CPDEBUG, "title successfully retrieved: %s", title);
811 }
812 return 0;
813 }
814
main(int argc,char * argv[])815 int main(int argc, char *argv[]) {
816 char *user, *dirname, *spoolfile, *outfile, *gscall, *ppcall;
817 cp_string title;
818 int size;
819 mode_t mode;
820 struct passwd *passwd;
821 gid_t *groups;
822 int ngroups;
823 pid_t pid;
824
825 if (setuid(0)) {
826 (void) fputs("CUPS-PDF cannot be called without root privileges!\n", stderr);
827 return 0;
828 }
829
830 if (argc==1) {
831 announce_printers();
832 return 0;
833 }
834 if (argc<6 || argc>7) {
835 (void) fputs("Usage: cups-pdf job-id user title copies options [file]\n", stderr);
836 return 0;
837 }
838
839 if (init(argv))
840 return 5;
841 log_event(CPDEBUG, "initialization finished: %s", CPVERSION);
842
843 size=strlen(Conf_UserPrefix)+strlen(argv[2])+1;
844 user=calloc(size, sizeof(char));
845 if (user == NULL) {
846 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
847 return 5;
848 }
849 snprintf(user, size, "%s%s", Conf_UserPrefix, argv[2]);
850 passwd=getpwnam(user);
851 if (passwd == NULL && Conf_LowerCase) {
852 log_event(CPDEBUG, "unknown user: %s", user);
853 for (size=0;size<(int) strlen(argv[2]);size++)
854 argv[2][size]=tolower(argv[2][size]);
855 log_event(CPDEBUG, "trying lower case user name: %s", argv[2]);
856 size=strlen(Conf_UserPrefix)+strlen(argv[2])+1;
857 snprintf(user, size, "%s%s", Conf_UserPrefix, argv[2]);
858 passwd=getpwnam(user);
859 }
860 if (passwd == NULL) {
861 if (strlen(Conf_AnonUser)) {
862 passwd=getpwnam(Conf_AnonUser);
863 if (passwd == NULL) {
864 log_event(CPERROR, "username for anonymous access unknown: %s", Conf_AnonUser);
865 free(user);
866 if (logfp!=NULL)
867 (void) fclose(logfp);
868 return 5;
869 }
870 log_event(CPDEBUG, "unknown user: %s", user);
871 size=strlen(Conf_AnonDirName)+4;
872 dirname=calloc(size, sizeof(char));
873 if (dirname == NULL) {
874 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
875 free(user);
876 if (logfp!=NULL)
877 (void) fclose(logfp);
878 return 5;
879 }
880 snprintf(dirname, size, "%s", Conf_AnonDirName);
881 while (strlen(dirname) && ((dirname[strlen(dirname)-1] == '\n') ||
882 (dirname[strlen(dirname)-1] == '\r')))
883 dirname[strlen(dirname)-1]='\0';
884 log_event(CPDEBUG, "output directory name generated: %s", dirname);
885 }
886 else {
887 log_event(CPSTATUS, "anonymous access denied: %s", user);
888 free(user);
889 if (logfp!=NULL)
890 (void) fclose(logfp);
891 return 0;
892 }
893 mode=(mode_t)(0666&~Conf_AnonUMask);
894 }
895 else {
896 log_event(CPDEBUG, "user identified: %s", passwd->pw_name);
897 if ((dirname=preparedirname(passwd, argv[2])) == NULL) {
898 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
899 free(user);
900 if (logfp!=NULL)
901 (void) fclose(logfp);
902 return 5;
903 }
904 while (strlen(dirname) && ((dirname[strlen(dirname)-1] == '\n') ||
905 (dirname[strlen(dirname)-1] == '\r')))
906 dirname[strlen(dirname)-1]='\0';
907 log_event(CPDEBUG, "output directory name generated: %s", dirname);
908 mode=(mode_t)(0666&~Conf_UserUMask);
909 }
910 ngroups=32;
911 groups=calloc(ngroups, sizeof(gid_t));
912 if (groups == NULL) {
913 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
914 free(user);
915 if (logfp!=NULL)
916 (void) fclose(logfp);
917 return 5;
918 }
919 size=getgrouplist(user, passwd->pw_gid, groups, &ngroups);
920 if (size == -1) {
921 free(groups);
922 groups=calloc(ngroups, sizeof(gid_t));
923 size=getgrouplist(user, passwd->pw_gid, groups, &ngroups);
924 }
925 if (size < 0) {
926 log_event(CPERROR, "getgrouplist failed");
927 free(user);
928 free(groups);
929 if (logfp!=NULL)
930 (void) fclose(logfp);
931 return 5;
932 }
933 free(user);
934 if (prepareuser(passwd, dirname)) {
935 free(groups);
936 free(dirname);
937 if (logfp!=NULL)
938 (void) fclose(logfp);
939 return 5;
940 }
941 log_event(CPDEBUG, "user information prepared");
942
943 size=strlen(Conf_Spool)+22;
944 spoolfile=calloc(size, sizeof(char));
945 if (spoolfile == NULL) {
946 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
947 free(groups);
948 free(dirname);
949 if (logfp!=NULL)
950 (void) fclose(logfp);
951 return 5;
952 }
953 snprintf(spoolfile, size, "%s/cups2pdf-%i", Conf_Spool, (int) getpid());
954 log_event(CPDEBUG, "spoolfile name created: %s", spoolfile);
955
956 if (argc == 6) {
957 if (preparespoolfile(stdin, spoolfile, title, argv[3], atoi(argv[1]), passwd)) {
958 free(groups);
959 free(dirname);
960 free(spoolfile);
961 if (logfp!=NULL)
962 (void) fclose(logfp);
963 return 5;
964 }
965 log_event(CPDEBUG, "input data read from stdin");
966 }
967 else {
968 if (preparespoolfile(fopen(argv[6], "r"), spoolfile, title, argv[3], atoi(argv[1]), passwd)) {
969 free(groups);
970 free(dirname);
971 free(spoolfile);
972 if (logfp!=NULL)
973 (void) fclose(logfp);
974 return 5;
975 }
976 log_event(CPDEBUG, "input data read from file: %s", argv[6]);
977 }
978
979 size=strlen(dirname)+strlen(title)+strlen(Conf_OutExtension)+3;
980 outfile=calloc(size, sizeof(char));
981 if (outfile == NULL) {
982 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
983 if (unlink(spoolfile))
984 log_event(CPERROR, "failed to unlink spoolfile during clean-up: %s", spoolfile);
985 free(groups);
986 free(dirname);
987 free(spoolfile);
988 if (logfp!=NULL)
989 (void) fclose(logfp);
990 return 5;
991 }
992 if (strlen(Conf_OutExtension))
993 snprintf(outfile, size, "%s/%s.%s", dirname, title, Conf_OutExtension);
994 else
995 snprintf(outfile, size, "%s/%s", dirname, title);
996 log_event(CPDEBUG, "output filename created: %s", outfile);
997
998 size=strlen(Conf_GSCall)+strlen(Conf_GhostScript)+strlen(Conf_PDFVer)+strlen(outfile)+strlen(spoolfile)+6;
999 gscall=calloc(size, sizeof(char));
1000 if (gscall == NULL) {
1001 (void) fputs("CUPS-PDF: failed to allocate memory\n", stderr);
1002 if (unlink(spoolfile))
1003 log_event(CPERROR, "failed to unlink spoolfile during clean-up: %s", spoolfile);
1004 free(groups);
1005 free(dirname);
1006 free(spoolfile);
1007 free(outfile);
1008 if (logfp!=NULL)
1009 (void) fclose(logfp);
1010 return 5;
1011 }
1012 snprintf(gscall, size, Conf_GSCall, Conf_GhostScript, Conf_PDFVer, outfile, spoolfile);
1013 log_event(CPDEBUG, "ghostscript commandline built: %s", gscall);
1014
1015 (void) unlink(outfile);
1016 log_event(CPDEBUG, "output file unlinked: %s", outfile);
1017
1018 if (putenv(Conf_GSTmp)) {
1019 log_event(CPERROR, "insufficient space in environment to set TMPDIR: %s", Conf_GSTmp);
1020 if (unlink(spoolfile))
1021 log_event(CPERROR, "failed to unlink spoolfile during clean-up: %s", spoolfile);
1022 free(groups);
1023 free(dirname);
1024 free(spoolfile);
1025 free(outfile);
1026 free(gscall);
1027 if (logfp!=NULL)
1028 (void) fclose(logfp);
1029 return 5;
1030 }
1031 log_event(CPDEBUG, "TMPDIR set for GhostScript: %s", getenv("TMPDIR"));
1032
1033 pid=fork();
1034
1035 if (!pid) {
1036 log_event(CPDEBUG, "entering child process");
1037
1038 if (setgid(passwd->pw_gid))
1039 log_event(CPERROR, "failed to set GID for current user");
1040 else
1041 log_event(CPDEBUG, "GID set for current user");
1042 if (setgroups(ngroups, groups))
1043 log_event(CPERROR, "failed to set supplementary groups for current user");
1044 else
1045 log_event(CPDEBUG, "supplementary groups set for current user");
1046 if (setuid(passwd->pw_uid))
1047 log_event(CPERROR, "failed to set UID for current user: %s", passwd->pw_name);
1048 else
1049 log_event(CPDEBUG, "UID set for current user: %s", passwd->pw_name);
1050
1051 (void) umask(0077);
1052 size=system(gscall);
1053 log_event(CPDEBUG, "ghostscript has finished: %d", size);
1054 if (chmod(outfile, mode))
1055 log_event(CPERROR, "failed to set file mode for PDF file: %s (non fatal)", outfile);
1056 else
1057 log_event(CPDEBUG, "file mode set for user output: %s", outfile);
1058
1059 if (strlen(Conf_PostProcessing)) {
1060 size=strlen(Conf_PostProcessing)+strlen(outfile)+strlen(passwd->pw_name)+strlen(argv[2])+4;
1061 ppcall=calloc(size, sizeof(char));
1062 if (ppcall == NULL)
1063 log_event(CPERROR, "failed to allocate memory for postprocessing (non fatal)");
1064 else {
1065 snprintf(ppcall, size, "%s %s %s %s", Conf_PostProcessing, outfile, passwd->pw_name, argv[2]);
1066 log_event(CPDEBUG, "postprocessing commandline built: %s", ppcall);
1067 size=system(ppcall);
1068 snprintf(title,BUFSIZE,"%d",size);
1069 log_event(CPDEBUG, "postprocessing has finished: %s", title);
1070 free(ppcall);
1071 }
1072 }
1073 else
1074 log_event(CPDEBUG, "no postprocessing");
1075
1076 return 0;
1077 }
1078 log_event(CPDEBUG, "waiting for child to exit");
1079 (void) waitpid(pid,NULL,0);
1080
1081 if (unlink(spoolfile))
1082 log_event(CPERROR, "failed to unlink spoolfile: %s (non fatal)", spoolfile);
1083 else
1084 log_event(CPDEBUG, "spoolfile unlinked: %s", spoolfile);
1085
1086 free(groups);
1087 free(dirname);
1088 free(spoolfile);
1089 free(outfile);
1090 free(gscall);
1091
1092 log_event(CPDEBUG, "all memory has been freed");
1093
1094 log_event(CPSTATUS, "PDF creation successfully finished for %s", passwd->pw_name);
1095
1096 if (logfp!=NULL)
1097 (void) fclose(logfp);
1098 return 0;
1099 }
1100