1 #ident "$Id: faxq-helper.c,v 4.16 2005/04/10 20:47:43 gert Exp $ Copyright (c) Gert Doering"
2 
3 /* faxq-helper.c
4  *
5  * this is a suid helper process that is used for the unprivileged
6  * fax queue client programs (faxspool, faxq, faxrm) to access the
7  * /var/spool/fax/outgoing/... fax queue ($OUT).
8  *
9  * it is NOT suid "root" but suid "FAX_OUT_USER" (usually "fax") as
10  * defined in the top level mgetty Makefile
11  *
12  * there are 5 commands:
13  *
14  * faxq-helper new
15  *       user permission check (fax.allow + fax.deny)
16  *       return a new job ID (F000123) and create "$OUT/.inF000123.$uid/"
17  *
18  * faxq-helper input $ID $filename
19  *       validate $filename
20  *       open $OUT/.in$ID.uid/$filename (O_EXCL)
21  *       copy stdin to file
22  *
23  * faxq-helper activate $ID
24  *       take prototype JOB file from stdin,
25  *	     check that "pages ..." does not reference any non-local
26  *	     or non-existing files
27  *	     check that "user ..." contains the correct value
28  *	 create JOB
29  *       move $OUT/.inF000134.$uid/ to $OUT/F000134/ -> faxrunq will "see" it
30  *
31  * faxq-helper remove $ID
32  *       check that $OUT/$ID/ exists and belongs to the calling user
33  *       lock JOB
34  *       rm -r $OUT/$ID/ directory tree
35  *
36  * faxq-helper requeue $ID
37  *       check that $OUT/$ID/ exists and belongs to the calling user
38  *       move $JOB.error to $JOB to reactivate job
39  *       touch $queue_changed
40  *
41  * some checks are done globally for all commands
42  *       faxq-helper must be suid fax, and fax user must exist in passwd
43  *       $FAX_SPOOL_OUT must exist
44  *       if $OUT is world- or group-writeable, or not owned by 'fax', issue
45  *        warning (but go on) - this could be legal, but it's off-spec
46  *
47  * Note: right now, this needs an ANSI C compiler, and might not be
48  *       as portable as the remaining mgetty code.  Send diffs :-)
49  *
50  * $Log: faxq-helper.c,v $
51  * Revision 4.16  2005/04/10 20:47:43  gert
52  * make do_sanitize() work on a copy of the input line
53  * (strtok() modifies the input string, leading to corrupt JOB files)
54  *
55  */
56 
57 #include <stdio.h>
58 #include <unistd.h>
59 #include <stdlib.h>
60 #include <ctype.h>
61 #include <errno.h>
62 #include <string.h>
63 #include <pwd.h>
64 #include <fcntl.h>
65 #include <sys/types.h>
66 #include <sys/stat.h>
67 #include <time.h>
68 #include <dirent.h>
69 
70 #include <stdarg.h>
71 
72 /* globals used by all the routines */
73 char * program_name;
74 int    real_user_id;		/* numeric user ID of caller */
75 char * real_user_name;		/* user name of caller */
76 
77 int    fax_out_uid;		/* user ID to chown() fax jobs to */
78 int    fax_out_gid;		/* group ID ... */
79 
80 #define	ROOT_UID	0	/* root's user ID - override checks */
81 
82 #define FAX_SEQ_FILE	".Sequence"
83 #define FAX_SEQ_LOCK	"LCK..seq"
84 
85 #define MAXJIDLEN	20	/* maximum length of acceptable job ID */
86 
87 #ifndef MAXPATHLEN
88 # define MAXPATHLEN 2048
89 #endif
90 
error_and_exit(char * string)91 void error_and_exit( char * string )
92 {
93     fprintf( stderr, "%s: %s\n", program_name, string );
94     exit(1);
95 }
96 
97 /* generic error messager - just to increase readability of the code below
98  */
eout(const char * fmt,...)99 void eout(const char *fmt, ...)
100 {
101     va_list ap;
102     va_start(ap, fmt);
103     fprintf( stderr, "%s: ", program_name );
104     vfprintf( stderr, fmt, ap);
105     va_end(ap);
106 }
107 
108 /* validate format of job id: "Fnnnnnnn"
109  */
validate_job_id(char * JobID)110 int validate_job_id( char * JobID )
111 {
112     int rc = 0;
113     char * p = JobID;
114 
115     if ( *p++ != 'F' ) rc = -1;
116     while( *p != '\0' && rc == 0 )
117     {
118 	if ( ! isdigit( *p ) ) rc = -1;
119 	p++;
120     }
121     if ( strlen( JobID ) > MAXJIDLEN ) rc = -1;
122 
123     if ( rc<0 ) eout( "invalid JobID: '%s'\n", JobID );
124 
125     return rc;
126 }
127 
128 /* verify that a given path name is a directory, and is owned by "fax"
129  */
validate_dir(char * dir)130 int validate_dir( char * dir )
131 {
132 struct stat stb;
133 
134     if ( lstat( dir, &stb ) < 0 )
135     {
136 	eout( "can't stat '%s': %s\n", dir, strerror(errno) );
137 	return -1;
138     }
139     if ( !S_ISDIR( stb.st_mode ) )
140     {
141 	eout( "%s is no directory!\n", dir );
142 	return -1;
143     }
144     if ( stb.st_uid != fax_out_uid )
145     {
146 	eout( "job directory '%s' is not owned by '%s' (%d), abort\n",
147 	      dir, FAX_OUT_USER, stb.st_uid );
148 	return -1;
149     }
150     return 0;
151 }
152 
153 /* lookup user name in ASCII file
154  *
155  * each line consists of a single user name only
156  * if a line is too long (>100 characters) it's silently ignored because
157  * it wouldn't match anyway
158  *
159  * return values: -1 = no such file / error, 0 = not found in file, 1 = found
160  *
161  * TODO: "man faxspool" claims that "user<blank>otherstuff" is fine
162  *       -> either adapt code, or rework documentation
163  */
164 
find_user_in_file(char * file,char * u)165 int find_user_in_file( char * file, char * u )
166 {
167 FILE * fp;
168 char buf[100];
169 
170     fp = fopen( file, "r" );
171     if ( fp == NULL )
172     {
173 	if ( errno != ENOENT )
174 	    eout( "can't open '%s' for reading: %s\n", file, strerror(errno));
175 	return -1;
176     }
177 
178     while( fgets( buf, sizeof(buf)-1, fp ) != NULL )
179     {
180 	int l = strlen(buf);
181 
182 	/* lines that are too long are just ignored */
183 	if ( l>0 && buf[l-1] == '\n' )
184 	{
185 	    buf[l-1]='\0';
186 	    if ( strcmp( buf, u ) == 0 )
187 			{ fclose( fp ); return 1; }
188 	}
189     }
190 
191     fclose(fp);
192     return 0;
193 }
194 
195 /* check whether user may use fax service
196  *  - if fax.allow exists, and user is listed -> OK
197  *  - if fax.deny exists, and user is not listed -> OK
198  *  - if neither exists, and user is root -> OK
199  */
check_fax_allow_deny(char * u)200 int check_fax_allow_deny( char * u )
201 {
202 int rc;
203 
204     rc = find_user_in_file( FAX_ALLOW, u );
205     if ( rc == 1 ) return 0;
206     if ( rc == 0 )
207     {
208 	eout( "Sorry, %s, you are not allowed to use the fax service.\n", u );
209 	return -1;
210     }
211 
212     rc = find_user_in_file( FAX_DENY, u );
213     if ( rc == 0 ) return 0;
214     if ( rc == 1 )
215     {
216 	eout( "Sorry, %s, you are not allowed to use the fax service.\n", u );
217 	return -1;
218     }
219 
220     if ( strcmp( u, "root" ) == 0 ) return 0;
221 
222     eout( "Neither fax.allow nor fax.deny exist,\n"
223 	  "so only 'root' may use the fax service. Sorry.\n" );
224     return -1;
225 }
226 
227 /* create next sequence number
228  *   - if sequence file doesn't exist -> create, and return "1"
229  *   - if locks/ subdir doesn't exist, create
230  *   - lock file by creating hard link
231  *   - read current sequence number, add 1, write back
232  *   - unlock
233  */
get_next_seq(void)234 long get_next_seq(void)
235 {
236 char buf[100];
237 int try = 0;
238 long s = -1;
239 int fd, l;
240 
241 again:
242     if ( link( FAX_SEQ_FILE, FAX_SEQ_LOCK ) < 0 )
243     {
244 	if ( errno == EEXIST )		/* lock exists */
245 	{
246 	    if ( ++try < 3 )
247 	    {
248 		eout( "sequence file locked (try %d)...\n", try );
249 		sleep( rand()%3 +1 );
250 		goto again;
251 	    }
252 	    eout( "can't lock sequence file, give up\n" );
253 	    return -1;
254 	}
255 
256 	if ( errno == ENOENT )		/* sequence file does not exist */
257 	{
258 	    eout( "sequence file does not exist, creating...\n" );
259 	    fd = creat( FAX_SEQ_FILE, 0644 );
260 	    if ( fd < 0 )
261 	    {
262 		eout( "can't create sequence file '%s': %s\n",
263 		      FAX_SEQ_FILE, strerror(errno) );
264 		return -1;
265 	    }
266 	    write( fd, "000000\n", 7 );
267 	    close( fd );
268 	    goto again;
269 	}
270 
271 	eout( "can't lock sequence file: %s\n", strerror(errno) );
272 	return -1;
273     }
274 
275     /* sequence file is locked, now read current sequence */
276     fd = open( FAX_SEQ_FILE, O_RDWR );
277     if ( fd < 0 )
278     {
279 	eout( "can't open '%s' read/write: %s\n",
280               FAX_SEQ_FILE, strerror(errno) );
281 	goto unlock_and_out;
282     }
283 
284     l = read( fd, buf, sizeof(buf)-1 );
285     if ( l >= 0 ) buf[l] = '\0';
286 
287     if ( l < 0 || l >= sizeof(buf)-1 || ! isdigit( buf[0] ) )
288     {
289 	eout( "sequence file '%s' corrupt\n", FAX_SEQ_FILE );
290 	goto close_and_out;
291     }
292 
293     s = atol( buf ) + 1;
294     sprintf( buf, "%0*ld\n", l-1, s );
295 
296     if ( lseek( fd, 0, SEEK_SET ) != 0 )
297     {
298 	eout( "can't rewind sequence file: %s\n", strerror(errno) );
299 	goto close_and_out;
300     }
301 
302     l = strlen(buf);
303     if ( write( fd, buf, l ) != l )
304     {
305 	eout( "can't write all %d bytes to %s: %s\n",
306               l, FAX_SEQ_FILE, strerror(errno) );
307     }
308 
309 close_and_out:
310     close(fd);
311 
312 unlock_and_out:
313     unlink( FAX_SEQ_LOCK );
314     return s;
315 }
316 
317 /* create a new job
318  *   - check user permissions (fax.allow/fax.deny)
319  *   - get next sequence number
320  *   - create directory (prefixed with ".in", suffixed with user ID)
321  *   - print job ID to stdout
322  */
do_new(void)323 int do_new( void )
324 {
325 long seq;
326 char dirbuf[100];
327 
328     /* check if user may use fax service (fax.allow/fax.deny files) */
329     if ( check_fax_allow_deny( real_user_name ) < 0 ) return -1;
330 
331     /* get next sequence number (including locking) */
332     seq = get_next_seq();
333 
334     if ( seq <= 0 ) return -1;
335 
336     sprintf( dirbuf, ".inF%06ld.%d", seq, real_user_id );
337 
338     if ( mkdir(dirbuf, 0700) < 0 )
339     {
340 	eout( "can't create directory '%s': %s\n", dirbuf, strerror(errno) );
341 	return -1;
342     }
343 
344     /* print file name (without ".in") to stdout */
345     printf( "F%06ld\n", seq );
346     return 0;
347 }
348 
349 /* do_input
350  *  validate job ID and input file name
351  *  files with "/" are only allowed in one special case (.source-files/)
352  *  read file from stdin, write to $OUT/.in$JID.uid/$filename
353  */
do_input(char * JID,char * outfilename)354 int do_input( char * JID, char * outfilename )
355 {
356 char * p;
357 char dir1[MAXJIDLEN+20];
358 char pathbuf[200], buf[4096];
359 int fd, r, w;
360 
361     if ( isatty(fileno(stdin)) )
362 	fprintf(stderr, "NOTICE: reading input from stdin, end with ctrl-D\n");
363 
364     sprintf( dir1, ".in%s.%d", JID, real_user_id );
365     if ( validate_dir( dir1 ) < 0 ) return -1;
366 
367     p = outfilename;
368     if ( strncmp( outfilename, ".source-files/", 14 ) == 0 )
369     {
370 	p+=14;
371 	sprintf( pathbuf, "%s/.source-files", dir1 );
372 	if ( mkdir( pathbuf, 0755 ) < 0 && errno != EEXIST )
373 	{
374 	    eout( "can't mkdir '%s': %s\n", pathbuf, strerror(errno));
375 	    return -1;
376 	}
377     }
378 
379     while( *p != '\0' )
380     {
381 	if ( *p == '/' || *p == '\\' || isspace(*p) || !isprint(*p) )
382 	{
383 	    eout( "invalid char. '%c' in file name '%s', abort\n",
384 		  *p, outfilename );
385 	    return -1;
386 	}
387 	p++;
388     }
389 
390     if ( strlen( dir1 ) + strlen( outfilename ) >= sizeof(pathbuf) -3 )
391     {
392 	eout( "'%s/%s': file name too long\n" ); return -1;
393     }
394 
395     sprintf( pathbuf, "%s/%s", dir1, outfilename );
396 
397     fd = open( pathbuf, O_WRONLY | O_CREAT | O_EXCL, 0644 );
398     if ( fd < 0 )
399     {
400 	eout( "can't open '%s' for writing: %s\n", pathbuf, strerror(errno));
401 	return -1;
402     }
403 
404     while( ( r = read( fileno(stdin), buf, sizeof(buf) ) ) > 0 )
405     {
406 	w = write( fd, buf, r );
407 	if ( w != r )
408 	{
409 	    eout( "can't write all %d bytes to %s: %s\n",
410 		  r, pathbuf, strerror(errno) );
411 	    break;
412 	}
413     }
414 
415     if ( r != 0 )	/* read or write error */
416     {
417 	if ( r < 0 )
418 	    eout( "error reading from stdin: %s\n", strerror(errno));
419 	close(fd);
420 	unlink(pathbuf);
421 	return -1;
422     }
423 
424     close(fd);
425     return 0;
426 }
427 
428 /* do a "rm -rf <dir>"
429  * TODO: check for ownership?
430  */
recursive_rm(char * dir)431 int recursive_rm( char * dir )
432 {
433 char pathbuf[MAXPATHLEN];
434 DIR * dirp;
435 struct dirent * de;
436 struct stat stb;
437 int rc = 0;
438 
439     if ( ( dirp = opendir( dir ) ) == NULL )
440     {
441 	eout( "can't read directory '%s': %s\n", dir, strerror(errno));
442 	return -1;
443     }
444 
445     while( ( de = readdir( dirp ) ) != NULL )
446     {
447 	if ( strcmp( de->d_name, "." ) == 0 ||
448 	     strcmp( de->d_name, ".." ) == 0 )
449 	{
450 	    continue;
451 	}
452 	if ( strlen( dir ) + strlen( de->d_name ) > sizeof(pathbuf) -5 )
453 	{
454 	    eout( "file path too long: %s/%s\n", dir, de->d_name );
455 	    rc--;
456 	    continue;
457 	}
458 	sprintf( pathbuf, "%s/%s", dir, de->d_name );
459 
460 	/* fprintf( stderr, "debug2: '%s'\n", pathbuf ); */
461 
462 	if ( lstat( pathbuf, &stb ) < 0 )
463 	{
464 	    eout( "can't stat '%s': %s\n", pathbuf, strerror(errno));
465 	    rc--;
466 	    continue;
467 	}
468 
469 	/* directories are followed, everything else is removed */
470 	if ( S_ISDIR( stb.st_mode ) )
471 	{
472 	    rc += recursive_rm( pathbuf );
473 	}
474 	else
475 	{
476 	    if ( unlink( pathbuf ) < 0 )
477 	    {
478 		eout( "can't unlink '%s': %s\n", pathbuf, strerror( errno ));
479 		rc--;
480 	    }
481 	}
482     }
483     closedir( dirp );
484 
485     if ( rmdir( dir ) < 0 )
486     {
487 	eout( "can't rmdir '%s': %s\n", dir, strerror(errno));
488 	rc--;
489     }
490 
491     return rc;
492 }
493 
494 
495 /* make sure that all path names in the following list (separated by
496  * whitespace) are local to this directory, exist, and are regular files
497  */
do_sanitize_page_files(char * dir,char * filelist)498 int do_sanitize_page_files( char * dir, char * filelist )
499 {
500 char * p, * copy, tmp[300];
501 struct stat stb;
502 int n=0;
503 int l;
504 
505     l = strlen(filelist);
506     if ( l == 0 ) return 0;		/* empty list is OK */
507 
508     copy = malloc( l+1 );
509     if ( copy == NULL )
510     {
511 	eout( "in do_sanitize: cannot malloc() %d bytes, abort\n", l ); return -1;
512     }
513     memcpy( copy, filelist, l+1 );
514 
515     p = strtok( copy, " \t\n" );
516 
517     while( p != NULL )
518     {
519 	if ( strchr( p, '/' ) != NULL )
520 	{
521 	    eout( "non-local file name: '%s', abort\n", p ); return -1;
522 	}
523 
524 	if ( strlen( dir ) + strlen( p ) + 3 >= sizeof(tmp) )
525 	{
526 	    eout( "file name '%s' too long, abort\n", p ); return -1;
527 	}
528 	sprintf( tmp, "%s/%s", dir, p );
529 
530 	if ( lstat( tmp, &stb ) < 0 )
531 	{
532 	    eout( "can't stat file '%s': %s\n", tmp, strerror(errno) );
533 	    return -1;
534 	}
535 
536 	if ( !S_ISREG( stb.st_mode ) )
537 	{
538 	    eout( "'%s' is not a regular file, abort\n", tmp ); return -1;
539 	}
540 
541 	n++;
542 	p = strtok( NULL, " \t\n" );
543     }
544 
545     return n;
546 }
547 
548 /* Activate "pending" fax job
549  *
550  */
do_activate(char * JID)551 int do_activate( char * JID )
552 {
553 char dir1[MAXJIDLEN+20];
554 char buf[1000], *p, *q;
555 int fd;
556 int user_seen = 0;
557 
558     if ( isatty(fileno(stdin)) )
559 	fprintf(stderr, "NOTICE: reading input from stdin, end with ctrl-D\n");
560 
561     sprintf( dir1, ".in%s.%d", JID, real_user_id );
562     if ( validate_dir( dir1 ) < 0 ) return -1;
563 
564     sprintf( buf, "%s/JOB", dir1 );
565 
566     /* the JOB file has to be world-readable, relax umask */
567     umask( 0022 );
568 
569 /* TODO: check if this portably catches symlinks to non-existant files! */
570     if ( ( fd = open( buf, O_WRONLY | O_CREAT | O_EXCL, 0644 ) ) < 0 )
571     {
572 	eout( "can't create JOB file '%s': %s\n", buf, strerror(errno) );
573 	recursive_rm(dir1);
574 	return -1;
575     }
576 
577     /* read queue metadata from stdin, sanitize, write to JOB fd */
578     while( ( p = fgets( buf, sizeof(buf)-1, stdin ) ) != NULL )
579     {
580 	int l = strlen(buf);
581 
582 	if ( l >= sizeof(buf)-2 )
583 	{
584 	    eout( "input line too long\n" ); break;
585 	}
586 
587 	if ( l>0 && buf[l-1] == '\n' ) buf[--l]='\0';
588 
589 	if ( strncmp(buf, "user ", 5) == 0 )
590 	{
591 	    user_seen=1;
592 	    if ( real_user_id != ROOT_UID &&
593 		 strcmp( buf+5, real_user_name ) != 0 )
594 	    {
595 		eout( "user name mismatch (%s <-> %s)\n", buf+5, real_user_name );
596 		break;
597 	    }
598 	}
599 	if ( strncmp(buf, "pages", 5 ) == 0 &&
600 	     do_sanitize_page_files( dir1, buf+5 ) < 0 )
601 	{
602 	    eout( "bad input files specified\n" );
603 	    break;
604 	}
605 
606 	/* replace all quote characters, backslash and ';' by '_' */
607 	for( q = buf; *q != '\0'; q++ )
608 	{
609 	    if ( *q == '\'' || *q == '"' || *q == '`' ||
610 		 *q == '\\' || *q == ';' )
611 				    { *q = '_'; }
612 	}
613 
614         /* and write to JOB file... */
615 	buf[l++] = '\n';
616 	if ( write( fd, buf, l ) != l )
617 	{
618 	    eout( "can't write line to JOB file: %s\n", strerror(errno) );
619 	    break;
620 	}
621     }
622 
623     if ( p != NULL )	/* loop aborted */
624     {
625         close(fd); recursive_rm(dir1); return -1;
626     }
627 
628     if ( !user_seen )		/* no "user ..." line in JOB? */
629     {
630 	sprintf( buf, "user %.100s\n", real_user_name );
631 	write( fd, buf, strlen(buf) );
632     }
633     close(fd);
634 
635     /* now make directory world-readable & move to final place */
636     if ( chmod( dir1, 0755 ) < 0 )
637     {
638 	eout( "can't chmod '%s' to 0755: %s\n", dir1, strerror(errno));
639 	recursive_rm(dir1); return -1;
640     }
641 
642     if ( rename( dir1, JID ) < 0 )
643     {
644 	eout( "can't rename '%s' to '%s': %s\n", dir1, JID, strerror(errno));
645 	recursive_rm(dir1); return -1;
646     }
647 
648     return 0;
649 }
650 
651 
652 /* helper function for do_remove and do_requeue
653  *  - check whether /$JID/ exists at all (and is a directory)
654  *  - lock $JID/$jobfile, if present
655  *  - check $JID/$jobfile for "user xxx" and compare with caller uid
656  *
657  * TODO: permit "root" override
658  */
check_user_perms(char * JID,char * jobfile)659 int check_user_perms( char * JID, char * jobfile )
660 {
661 struct stat stb;
662 char buf[1000], *p;
663 FILE * fp;
664 char jfile[MAXJIDLEN+30], lfile[MAXJIDLEN+30];
665 
666     if ( lstat( JID, &stb ) < 0 ||
667 	 !S_ISDIR( stb.st_mode ) ||
668 	 stb.st_uid != fax_out_uid )
669     {
670 	eout( "'%s' is not a directory or has wrong owner\n", JID );
671 	return -1;
672     }
673 
674     sprintf( jfile, "%s/%s", JID, jobfile );
675     sprintf( lfile, "%s/%s", JID, "JOB.locked" );
676 
677     if ( link( jfile, lfile ) < 0 )
678     {
679 	if ( errno == EEXIST )
680 	{
681 	    eout( "%s already locked\n", jfile ); return -1;
682 	}
683 	if ( errno == ENOENT )
684 	{
685 	    return -2;		/* signal "file not found" to caller */
686 	}
687 
688 	eout( "can't lock JOB file: %s\n", strerror(errno) ); return -1;
689     }
690 
691     if ( ( fp = fopen( jfile, "r" ) ) == NULL )
692     {
693 	eout( "can't open '%s' for reading: %s\n", jfile, strerror(errno) );
694 	unlink( lfile );
695 	return -1;
696     }
697 
698     while( ( p = fgets( buf, sizeof(buf)-1, fp ) ) != NULL )
699     {
700 	int l = strlen(buf);
701 
702 	if ( l >= sizeof(buf)-2 )
703 	{
704 	    eout( "input line too long\n" );
705 	    unlink( lfile );
706 	    fclose(fp);
707 	    return -1;
708 	}
709 
710 	if ( l>0 && buf[l-1] == '\n' ) buf[--l]='\0';
711 
712 	if ( strncmp(buf, "user ", 5) == 0 )
713 	{
714 	    if ( real_user_id != ROOT_UID &&
715 		 strcmp( buf+5, real_user_name ) != 0 )
716 	    {
717 		fprintf( stderr, "%s: not your job, can't do anything (%s <-> %s)\n", jfile, buf+5, real_user_name );
718 		unlink( lfile );
719 		fclose(fp);
720 		return -1;
721 	    }
722 	}
723     }
724 
725     fclose(fp);
726     return 0;		/* lock file purposely kept in place! */
727 }
728 
do_remove(char * JID)729 int do_remove( char * JID )
730 {
731 int rc;
732 
733     rc = check_user_perms( JID, "JOB" );
734     if ( rc == -2 )				/* not found */
735 	rc = check_user_perms( JID, "JOB.error" );
736     if ( rc == -2 )				/* not found */
737 	rc = check_user_perms( JID, "JOB.suspended" );
738     if ( rc == -2 )				/* not found */
739 	rc = check_user_perms( JID, "JOB.done" );
740 
741     if ( rc < 0 )				/* check failed */
742     {
743 	if ( rc == -2 )				/* still not found */
744 	    eout( "no JOB file in %s/ - can't verify permissions\n", JID );
745 	return -1;
746     }
747 
748     rc = recursive_rm( JID );
749 
750     if ( rc < 0 )
751     {
752 	eout( "could not remove %d files or subdirectories\n", -rc );
753 	return -1;
754     }
755     return 0;
756 }
757 
do_requeue(char * JID)758 int do_requeue( char * JID )
759 {
760 int rc, fd;
761 char file1[MAXJIDLEN+30], file2[MAXJIDLEN+30];
762 char buf[100];
763 time_t ti;
764 
765     rc = check_user_perms( JID, "JOB.suspended" );
766     if ( rc == -2 )
767     {
768 	eout( "no %s/JOB.suspended found, do nothing\n", JID );
769 	return -1;
770     }
771     if ( rc < 0 ) { return -1; }
772 
773     /* JOB.suspended found, and user permissions OK */
774 
775     sprintf( file1, "%s/JOB.suspended", JID );
776     sprintf( file2, "%s/JOB", JID );
777 
778     if ( ( fd = open( file1, O_WRONLY | O_APPEND ) ) < 0 )
779     {
780 	eout( "can't open '%s' to append status line: %s\n",
781 	      file1, strerror(errno));
782 	return -1;
783     }
784 
785     time(&ti);
786     sprintf( buf, "Status %.40s", ctime(&ti) );
787     sprintf( &buf[strlen(buf)-1], " - reactivated by %.16s\n", real_user_name);
788 
789     rc = strlen(buf);
790     if ( write( fd, buf, rc ) != rc )
791     {
792 	eout( "can't write all %d bytes to '%s': %s\n",
793 	       rc, file1, strerror(errno) );
794 	close( fd );
795 	return -1;
796     }
797     close( fd );
798 
799     if ( rename( file1, file2 ) < 0 )
800     {
801 	eout( "can't rename '%s' to '%s': %s\n",
802 	      file1, file2, strerror(errno) );
803 	return -1;
804     }
805 
806     sprintf( file1, "%s/JOB.locked", JID );
807 
808     if ( unlink( file1 ) < 0 )
809     {
810 	eout( "can't unlink '%s': %s\n", file1, strerror(errno) );
811     }
812 
813     /* signal to faxrunqd that queue needs re-reading */
814     fd = open( ".queue-changed", O_WRONLY | O_CREAT | O_EXCL, 0644 );
815     if ( fd < 0 )
816     {
817 	if ( errno != EEXIST )
818 	    eout( "can't create '.queue-changed' file: %s\n", strerror(errno));
819     }
820     else
821 	close(fd);
822 
823     return 0;
824 }
825 
main(int argc,char ** argv)826 int main( int argc, char ** argv )
827 {
828     struct passwd * pw; 		/* for user name */
829     struct stat stb;
830 
831     program_name = strrchr( argv[0], '/' );
832     if ( program_name != NULL ) program_name++;
833 		           else program_name = argv[0];
834 
835     if ( argc < 2 )
836 	{ error_and_exit( "keyword missing" ); }
837 
838     /* common things to check and prepare */
839 
840     /* make sure people do not play umask tricks on us - the only
841      * bits that are accepted in a user umask are "044" - permit/prevent
842      * read access by group/other.  Write access is always denied.
843      */
844     umask( ( umask(0) & 0044 ) | 0022 );
845 
846     /* get numeric uid/gid for fax user */
847     pw = getpwnam( FAX_OUT_USER );
848     if ( pw == NULL )
849     {
850 	eout( "can't get user ID for user '%s', abort!\n", FAX_OUT_USER );
851 	exit(3);
852     }
853     fax_out_uid = pw->pw_uid;
854     fax_out_gid = pw->pw_gid;
855 
856     /* effective user ID is root, real user ID is still the caller's */
857     if ( geteuid() != fax_out_uid )
858     {
859 	eout( "must be set-uid '%s'\n", FAX_OUT_USER );
860 	exit(3);
861     }
862     real_user_id = getuid();
863     pw = getpwuid( real_user_id );
864     if ( pw == NULL || pw->pw_name == NULL )
865     {
866 	eout( "you don't exist, go away (uid=%d)!\n", real_user_id );
867 	exit(3);
868     }
869     real_user_name = pw->pw_name;
870 
871     /* spool directory has to exist, and should be owned by 'fax' */
872     if ( chdir( FAX_SPOOL_OUT ) < 0 )
873     {
874 	eout( "can't chdir to %s: %s\n", FAX_SPOOL_OUT, strerror(errno) );
875 	exit(2);
876     }
877     if ( stat( ".", &stb ) < 0 )
878     {
879 	eout( "can't stat %s: %s\n", FAX_SPOOL_OUT, strerror(errno) );
880 	exit(2);
881     }
882     if ( ( stb.st_mode & 0022 ) > 0 )
883     {
884 	eout( "WARNING: %s is group- or world-writeable\n", FAX_SPOOL_OUT);
885     }
886     if ( stb.st_uid != fax_out_uid )
887     {
888 	eout( "WARNING: %s should be owned by user '%s'\n",
889 	      FAX_SPOOL_OUT, FAX_OUT_USER );
890     }
891 
892     /* now parse arguments and go to specific functions */
893     if ( argc == 2 && strcmp( argv[1], "new" ) == 0 )
894     {
895 	exit( do_new() <0? 10: 0);
896     }
897     if ( argc == 4 && strcmp( argv[1], "input" ) == 0 )
898     {
899 	/* second parameter is job ID, 3rd is file name */
900 	char * job_id = argv[2];
901 	char * file_name = argv[3];
902 	if ( validate_job_id( job_id ) <0 ) exit(1);
903 
904 	exit( do_input( job_id, file_name ) <0? 10: 0);
905     }
906     if ( argc == 3 )
907     {
908 	/* second parameter is common for all commands: job ID */
909 	char * job_id = argv[2];
910 	if ( validate_job_id( job_id ) <0 ) exit(1);
911 
912     	if( strcmp( argv[1], "activate" ) == 0 )
913 	{
914 	    exit( do_activate( job_id ) <0? 10: 0);
915 	}
916 	if ( strcmp( argv[1], "remove" ) == 0 )
917 	{
918 	    exit( do_remove( job_id ) <0? 10: 0);
919 	}
920 	if ( strcmp( argv[1], "requeue" ) == 0 )
921 	{
922 	    exit( do_requeue( job_id ) <0? 10: 0);
923 	}
924     }
925 
926     error_and_exit( "invalid keyword or wrong number of parameters" );
927     return 0;
928 }
929