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