1: /*
   2:  * Copyright (c) 1983 Regents of the University of California.
   3:  * All rights reserved.  The Berkeley software License Agreement
   4:  * specifies the terms and conditions for redistribution.
   5:  */
   6: 
   7: #ifndef lint
   8: static char sccsid[] = "@(#)printjob.c	5.2 (Berkeley) 9/17/85";
   9: #endif not lint
  10: 
  11: /*
  12:  * printjob -- print jobs in the queue.
  13:  *
  14:  *	NOTE: the lock file is used to pass information to lpq and lprm.
  15:  *	it does not need to be removed because file locks are dynamic.
  16:  */
  17: 
  18: #include "lp.h"
  19: 
  20: #define DORETURN    0   /* absorb fork error */
  21: #define DOABORT     1   /* abort if dofork fails */
  22: 
  23: /*
  24:  * Error tokens
  25:  */
  26: #define REPRINT     -2
  27: #define ERROR       -1
  28: #define OK      0
  29: #define FATALERR    1
  30: #define NOACCT      2
  31: #define FILTERERR   3
  32: #define ACCESS      4
  33: 
  34: char    title[80];      /* ``pr'' title */
  35: FILE    *cfp;           /* control file */
  36: int pfd;            /* printer file descriptor */
  37: int ofd;            /* output filter file descriptor */
  38: int lfd;            /* lock file descriptor */
  39: int pid;            /* pid of lpd process */
  40: int prchild;        /* id of pr process */
  41: int child;          /* id of any filters */
  42: int ofilter;        /* id of output filter, if any */
  43: int tof;            /* true if at top of form */
  44: int remote;         /* true if sending files to remote */
  45: dev_t   fdev;           /* device of file pointed to by symlink */
  46: ino_t   fino;           /* inode of file pointed to by symlink */
  47: 
  48: char    fromhost[32];       /* user's host machine */
  49: char    logname[32];        /* user's login name */
  50: char    jobname[100];       /* job or file name */
  51: char    class[32];      /* classification field */
  52: char    width[10] = "-w";   /* page width in characters */
  53: char    length[10] = "-l";  /* page length in lines */
  54: char    pxwidth[10] = "-x"; /* page width in pixels */
  55: char    pxlength[10] = "-y";    /* page length in pixels */
  56: char    indent[10] = "-i0"; /* indentation size in characters */
  57: char    tmpfile[] = "errsXXXXXX"; /* file name for filter output */
  58: 
  59: printjob()
  60: {
  61:     struct stat stb;
  62:     register struct queue *q, **qp;
  63:     struct queue **queue;
  64:     register int i, nitems;
  65:     long pidoff;
  66:     int count = 0;
  67:     extern int abortpr();
  68: 
  69:     init();                 /* set up capabilities */
  70:     (void) write(1, "", 1);         /* ack that daemon is started */
  71:     (void) close(2);            /* set up log file */
  72:     if (open(LF, O_WRONLY|O_APPEND, 0664) < 0) {
  73:         syslog(LOG_ERR, "%s: %m", LF);
  74:         (void) open("/dev/null", O_WRONLY);
  75:     }
  76:     setgid(getegid());
  77:     pid = getpid();             /* for use with lprm */
  78:     setpgrp(0, pid);
  79:     signal(SIGHUP, abortpr);
  80:     signal(SIGINT, abortpr);
  81:     signal(SIGQUIT, abortpr);
  82:     signal(SIGTERM, abortpr);
  83: 
  84:     (void) mktemp(tmpfile);
  85: 
  86:     /*
  87: 	 * uses short form file names
  88: 	 */
  89:     if (chdir(SD) < 0) {
  90:         syslog(LOG_ERR, "%s: %m", SD);
  91:         exit(1);
  92:     }
  93:     if (stat(LO, &stb) == 0 && (stb.st_mode & 0100))
  94:         exit(0);        /* printing disabled */
  95:     lfd = open(LO, O_WRONLY|O_CREAT, 0644);
  96:     if (lfd < 0) {
  97:         syslog(LOG_ERR, "%s: %s: %m", printer, LO);
  98:         exit(1);
  99:     }
 100:     if (flock(lfd, LOCK_EX|LOCK_NB) < 0) {
 101:         if (errno == EWOULDBLOCK)   /* active deamon present */
 102:             exit(0);
 103:         syslog(LOG_ERR, "%s: %s: %m", printer, LO);
 104:         exit(1);
 105:     }
 106:     ftruncate(lfd, 0);
 107:     /*
 108: 	 * write process id for others to know
 109: 	 */
 110:     sprintf(line, "%u\n", pid);
 111:     pidoff = i = strlen(line);
 112:     if (write(lfd, line, i) != i) {
 113:         syslog(LOG_ERR, "%s: %s: %m", printer, LO);
 114:         exit(1);
 115:     }
 116:     /*
 117: 	 * search the spool directory for work and sort by queue order.
 118: 	 */
 119:     if ((nitems = getq(&queue)) < 0) {
 120:         syslog(LOG_ERR, "%s: can't scan %s", printer, SD);
 121:         exit(1);
 122:     }
 123:     if (nitems == 0)        /* no work to do */
 124:         exit(0);
 125:     if (stb.st_mode & 01) {     /* reset queue flag */
 126:         if (fchmod(lfd, stb.st_mode & 0776) < 0)
 127:             syslog(LOG_ERR, "%s: %s: %m", printer, LO);
 128:     }
 129:     openpr();           /* open printer or remote */
 130: again:
 131:     /*
 132: 	 * we found something to do now do it --
 133: 	 *    write the name of the current control file into the lock file
 134: 	 *    so the spool queue program can tell what we're working on
 135: 	 */
 136:     for (qp = queue; nitems--; free((char *) q)) {
 137:         q = *qp++;
 138:         if (stat(q->q_name, &stb) < 0)
 139:             continue;
 140:     restart:
 141:         (void) lseek(lfd, pidoff, 0);
 142:         (void) sprintf(line, "%s\n", q->q_name);
 143:         i = strlen(line);
 144:         if (write(lfd, line, i) != i)
 145:             syslog(LOG_ERR, "%s: %s: %m", printer, LO);
 146:         if (!remote)
 147:             i = printit(q->q_name);
 148:         else
 149:             i = sendit(q->q_name);
 150:         /*
 151: 		 * Check to see if we are supposed to stop printing or
 152: 		 * if we are to rebuild the queue.
 153: 		 */
 154:         if (fstat(lfd, &stb) == 0) {
 155:             /* stop printing before starting next job? */
 156:             if (stb.st_mode & 0100)
 157:                 goto done;
 158:             /* rebuild queue (after lpc topq) */
 159:             if (stb.st_mode & 01) {
 160:                 for (free((char *) q); nitems--; free((char *) q))
 161:                     q = *qp++;
 162:                 if (fchmod(lfd, stb.st_mode & 0776) < 0)
 163:                     syslog(LOG_WARNING, "%s: %s: %m",
 164:                         printer, LO);
 165:                 break;
 166:             }
 167:         }
 168:         if (i == OK)        /* file ok and printed */
 169:             count++;
 170:         else if (i == REPRINT) { /* try reprinting the job */
 171:             syslog(LOG_INFO, "restarting %s", printer);
 172:             if (ofilter > 0) {
 173:                 kill(ofilter, SIGCONT); /* to be sure */
 174:                 (void) close(ofd);
 175:                 while ((i = wait(0)) > 0 && i != ofilter)
 176:                     ;
 177:                 ofilter = 0;
 178:             }
 179:             (void) close(pfd);  /* close printer */
 180:             if (ftruncate(lfd, pidoff) < 0)
 181:                 syslog(LOG_WARNING, "%s: %s: %m", printer, LO);
 182:             openpr();       /* try to reopen printer */
 183:             goto restart;
 184:         }
 185:     }
 186:     free((char *) queue);
 187:     /*
 188: 	 * search the spool directory for more work.
 189: 	 */
 190:     if ((nitems = getq(&queue)) < 0) {
 191:         syslog(LOG_ERR, "%s: can't scan %s", printer, SD);
 192:         exit(1);
 193:     }
 194:     if (nitems == 0) {      /* no more work to do */
 195:     done:
 196:         if (count > 0) {    /* Files actually printed */
 197:             if (!SF && !tof)
 198:                 (void) write(ofd, FF, strlen(FF));
 199:             if (TR != NULL)     /* output trailer */
 200:                 (void) write(ofd, TR, strlen(TR));
 201:         }
 202:         (void) unlink(tmpfile);
 203:         exit(0);
 204:     }
 205:     goto again;
 206: }
 207: 
 208: char    fonts[4][50];   /* fonts for troff */
 209: 
 210: char ifonts[4][18] = {
 211:     "/usr/lib/vfont/R",
 212:     "/usr/lib/vfont/I",
 213:     "/usr/lib/vfont/B",
 214:     "/usr/lib/vfont/S"
 215: };
 216: 
 217: /*
 218:  * The remaining part is the reading of the control file (cf)
 219:  * and performing the various actions.
 220:  */
 221: printit(file)
 222:     char *file;
 223: {
 224:     register int i;
 225:     char *cp;
 226:     int bombed = OK;
 227: 
 228:     /*
 229: 	 * open control file; ignore if no longer there.
 230: 	 */
 231:     if ((cfp = fopen(file, "r")) == NULL) {
 232:         syslog(LOG_INFO, "%s: %s: %m", printer, file);
 233:         return(OK);
 234:     }
 235:     /*
 236: 	 * Reset troff fonts.
 237: 	 */
 238:     for (i = 0; i < 4; i++)
 239:         strcpy(fonts[i], ifonts[i]);
 240:     strcpy(width+2, "0");
 241:     strcpy(indent+2, "0");
 242: 
 243:     /*
 244: 	 *      read the control file for work to do
 245: 	 *
 246: 	 *      file format -- first character in the line is a command
 247: 	 *      rest of the line is the argument.
 248: 	 *      valid commands are:
 249: 	 *
 250: 	 *		S -- "stat info" for symbolic link protection
 251: 	 *		J -- "job name" on banner page
 252: 	 *		C -- "class name" on banner page
 253: 	 *              L -- "literal" user's name to print on banner
 254: 	 *		T -- "title" for pr
 255: 	 *		H -- "host name" of machine where lpr was done
 256: 	 *              P -- "person" user's login name
 257: 	 *              I -- "indent" amount to indent output
 258: 	 *              f -- "file name" name of text file to print
 259: 	 *		l -- "file name" text file with control chars
 260: 	 *		p -- "file name" text file to print with pr(1)
 261: 	 *		t -- "file name" troff(1) file to print
 262: 	 *		n -- "file name" ditroff(1) file to print
 263: 	 *		d -- "file name" dvi file to print
 264: 	 *		g -- "file name" plot(1G) file to print
 265: 	 *		v -- "file name" plain raster file to print
 266: 	 *		c -- "file name" cifplot file to print
 267: 	 *		1 -- "R font file" for troff
 268: 	 *		2 -- "I font file" for troff
 269: 	 *		3 -- "B font file" for troff
 270: 	 *		4 -- "S font file" for troff
 271: 	 *		N -- "name" of file (used by lpq)
 272: 	 *              U -- "unlink" name of file to remove
 273: 	 *                    (after we print it. (Pass 2 only)).
 274: 	 *		M -- "mail" to user when done printing
 275: 	 *
 276: 	 *      getline reads a line and expands tabs to blanks
 277: 	 */
 278: 
 279:     /* pass 1 */
 280: 
 281:     while (getline(cfp))
 282:         switch (line[0]) {
 283:         case 'H':
 284:             strcpy(fromhost, line+1);
 285:             if (class[0] == '\0')
 286:                 strncpy(class, line+1, sizeof(class)-1);
 287:             continue;
 288: 
 289:         case 'P':
 290:             strncpy(logname, line+1, sizeof(logname)-1);
 291:             if (RS) {           /* restricted */
 292:                 if (getpwnam(logname) == (struct passwd *)0) {
 293:                     bombed = NOACCT;
 294:                     sendmail(line+1, bombed);
 295:                     goto pass2;
 296:                 }
 297:             }
 298:             continue;
 299: 
 300:         case 'S':
 301:             cp = line+1;
 302:             i = 0;
 303:             while (*cp >= '0' && *cp <= '9')
 304:                 i = i * 10 + (*cp++ - '0');
 305:             fdev = i;
 306:             cp++;
 307:             i = 0;
 308:             while (*cp >= '0' && *cp <= '9')
 309:                 i = i * 10 + (*cp++ - '0');
 310:             fino = i;
 311:             continue;
 312: 
 313:         case 'J':
 314:             if (line[1] != '\0')
 315:                 strncpy(jobname, line+1, sizeof(jobname)-1);
 316:             else
 317:                 strcpy(jobname, " ");
 318:             continue;
 319: 
 320:         case 'C':
 321:             if (line[1] != '\0')
 322:                 strncpy(class, line+1, sizeof(class)-1);
 323:             else if (class[0] == '\0')
 324:                 gethostname(class, sizeof(class));
 325:             continue;
 326: 
 327:         case 'T':   /* header title for pr */
 328:             strncpy(title, line+1, sizeof(title)-1);
 329:             continue;
 330: 
 331:         case 'L':   /* identification line */
 332:             if (!SH && !HL)
 333:                 banner(line+1, jobname);
 334:             continue;
 335: 
 336:         case '1':   /* troff fonts */
 337:         case '2':
 338:         case '3':
 339:         case '4':
 340:             if (line[1] != '\0')
 341:                 strcpy(fonts[line[0]-'1'], line+1);
 342:             continue;
 343: 
 344:         case 'W':   /* page width */
 345:             strncpy(width+2, line+1, sizeof(width)-3);
 346:             continue;
 347: 
 348:         case 'I':   /* indent amount */
 349:             strncpy(indent+2, line+1, sizeof(indent)-3);
 350:             continue;
 351: 
 352:         default:    /* some file to print */
 353:             switch (i = print(line[0], line+1)) {
 354:             case ERROR:
 355:                 if (bombed == OK)
 356:                     bombed = FATALERR;
 357:                 break;
 358:             case REPRINT:
 359:                 (void) fclose(cfp);
 360:                 return(REPRINT);
 361:             case FILTERERR:
 362:             case ACCESS:
 363:                 bombed = i;
 364:                 sendmail(logname, bombed);
 365:             }
 366:             title[0] = '\0';
 367:             continue;
 368: 
 369:         case 'N':
 370:         case 'U':
 371:         case 'M':
 372:             continue;
 373:         }
 374: 
 375:     /* pass 2 */
 376: 
 377: pass2:
 378:     fseek(cfp, 0L, 0);
 379:     while (getline(cfp))
 380:         switch (line[0]) {
 381:         case 'L':   /* identification line */
 382:             if (!SH && HL)
 383:                 banner(line+1, jobname);
 384:             continue;
 385: 
 386:         case 'M':
 387:             if (bombed < NOACCT)    /* already sent if >= NOACCT */
 388:                 sendmail(line+1, bombed);
 389:             continue;
 390: 
 391:         case 'U':
 392:             (void) unlink(line+1);
 393:         }
 394:     /*
 395: 	 * clean-up in case another control file exists
 396: 	 */
 397:     (void) fclose(cfp);
 398:     (void) unlink(file);
 399:     return(bombed == OK ? OK : ERROR);
 400: }
 401: 
 402: /*
 403:  * Print a file.
 404:  * Set up the chain [ PR [ | {IF, OF} ] ] or {IF, RF, TF, NF, DF, CF, VF}.
 405:  * Return -1 if a non-recoverable error occured,
 406:  * 2 if the filter detected some errors (but printed the job anyway),
 407:  * 1 if we should try to reprint this job and
 408:  * 0 if all is well.
 409:  * Note: all filters take stdin as the file, stdout as the printer,
 410:  * stderr as the log file, and must not ignore SIGINT.
 411:  */
 412: print(format, file)
 413:     int format;
 414:     char *file;
 415: {
 416:     register int n;
 417:     register char *prog;
 418:     int fi, fo;
 419:     char *av[15], buf[BUFSIZ];
 420:     int pid, p[2], stopped = 0;
 421:     union wait status;
 422:     struct stat stb;
 423: 
 424:     if (lstat(file, &stb) < 0 || (fi = open(file, O_RDONLY)) < 0)
 425:         return(ERROR);
 426:     /*
 427: 	 * Check to see if data file is a symbolic link. If so, it should
 428: 	 * still point to the same file or someone is trying to print
 429: 	 * something he shouldn't.
 430: 	 */
 431:     if ((stb.st_mode & S_IFMT) == S_IFLNK && fstat(fi, &stb) == 0 &&
 432:         (stb.st_dev != fdev || stb.st_ino != fino))
 433:         return(ACCESS);
 434:     if (!SF && !tof) {      /* start on a fresh page */
 435:         (void) write(ofd, FF, strlen(FF));
 436:         tof = 1;
 437:     }
 438:     if (IF == NULL && (format == 'f' || format == 'l')) {
 439:         tof = 0;
 440:         while ((n = read(fi, buf, BUFSIZ)) > 0)
 441:             if (write(ofd, buf, n) != n) {
 442:                 (void) close(fi);
 443:                 return(REPRINT);
 444:             }
 445:         (void) close(fi);
 446:         return(OK);
 447:     }
 448:     switch (format) {
 449:     case 'p':   /* print file using 'pr' */
 450:         if (IF == NULL) {   /* use output filter */
 451:             prog = PR;
 452:             av[0] = "pr";
 453:             av[1] = width;
 454:             av[2] = length;
 455:             av[3] = "-h";
 456:             av[4] = *title ? title : " ";
 457:             av[5] = 0;
 458:             fo = ofd;
 459:             goto start;
 460:         }
 461:         pipe(p);
 462:         if ((prchild = dofork(DORETURN)) == 0) {    /* child */
 463:             dup2(fi, 0);        /* file is stdin */
 464:             dup2(p[1], 1);      /* pipe is stdout */
 465:             for (n = 3; n < NOFILE; n++)
 466:                 (void) close(n);
 467:             execl(PR, "pr", width, length, "-h", *title ? title : " ", 0);
 468:             syslog(LOG_ERR, "cannot execl %s", PR);
 469:             exit(2);
 470:         }
 471:         (void) close(p[1]);     /* close output side */
 472:         (void) close(fi);
 473:         if (prchild < 0) {
 474:             prchild = 0;
 475:             (void) close(p[0]);
 476:             return(ERROR);
 477:         }
 478:         fi = p[0];          /* use pipe for input */
 479:     case 'f':   /* print plain text file */
 480:         prog = IF;
 481:         av[1] = width;
 482:         av[2] = length;
 483:         av[3] = indent;
 484:         n = 4;
 485:         break;
 486:     case 'l':   /* like 'f' but pass control characters */
 487:         prog = IF;
 488:         av[1] = "-c";
 489:         av[2] = width;
 490:         av[3] = length;
 491:         av[4] = indent;
 492:         n = 5;
 493:         break;
 494:     case 'r':   /* print a fortran text file */
 495:         prog = RF;
 496:         av[1] = width;
 497:         av[2] = length;
 498:         n = 3;
 499:         break;
 500:     case 't':   /* print troff output */
 501:     case 'n':   /* print ditroff output */
 502:     case 'd':   /* print tex output */
 503:         (void) unlink(".railmag");
 504:         if ((fo = creat(".railmag", FILMOD)) < 0) {
 505:             syslog(LOG_ERR, "%s: cannot create .railmag", printer);
 506:             (void) unlink(".railmag");
 507:         } else {
 508:             for (n = 0; n < 4; n++) {
 509:                 if (fonts[n][0] != '/')
 510:                     (void) write(fo, "/usr/lib/vfont/", 15);
 511:                 (void) write(fo, fonts[n], strlen(fonts[n]));
 512:                 (void) write(fo, "\n", 1);
 513:             }
 514:             (void) close(fo);
 515:         }
 516:         prog = (format == 't') ? TF : (format == 'n') ? NF : DF;
 517:         av[1] = pxwidth;
 518:         av[2] = pxlength;
 519:         n = 3;
 520:         break;
 521:     case 'c':   /* print cifplot output */
 522:         prog = CF;
 523:         av[1] = pxwidth;
 524:         av[2] = pxlength;
 525:         n = 3;
 526:         break;
 527:     case 'g':   /* print plot(1G) output */
 528:         prog = GF;
 529:         av[1] = pxwidth;
 530:         av[2] = pxlength;
 531:         n = 3;
 532:         break;
 533:     case 'v':   /* print raster output */
 534:         prog = VF;
 535:         av[1] = pxwidth;
 536:         av[2] = pxlength;
 537:         n = 3;
 538:         break;
 539:     default:
 540:         (void) close(fi);
 541:         syslog(LOG_ERR, "%s: illegal format character '%c'",
 542:             printer, format);
 543:         return(ERROR);
 544:     }
 545:     if ((av[0] = rindex(prog, '/')) != NULL)
 546:         av[0]++;
 547:     else
 548:         av[0] = prog;
 549:     av[n++] = "-n";
 550:     av[n++] = logname;
 551:     av[n++] = "-h";
 552:     av[n++] = fromhost;
 553:     av[n++] = AF;
 554:     av[n] = 0;
 555:     fo = pfd;
 556:     if (ofilter > 0) {      /* stop output filter */
 557:         write(ofd, "\031\1", 2);
 558:         while ((pid = wait3(&status, WUNTRACED, 0)) > 0 && pid != ofilter)
 559:             ;
 560:         if (status.w_stopval != WSTOPPED) {
 561:             (void) close(fi);
 562:             syslog(LOG_WARNING, "%s: output filter died (%d)",
 563:                 printer, status.w_retcode);
 564:             return(REPRINT);
 565:         }
 566:         stopped++;
 567:     }
 568: start:
 569:     if ((child = dofork(DORETURN)) == 0) {  /* child */
 570:         dup2(fi, 0);
 571:         dup2(fo, 1);
 572:         n = open(tmpfile, O_WRONLY|O_CREAT|O_TRUNC, 0664);
 573:         if (n >= 0)
 574:             dup2(n, 2);
 575:         for (n = 3; n < NOFILE; n++)
 576:             (void) close(n);
 577:         execv(prog, av);
 578:         syslog(LOG_ERR, "cannot execv %s", prog);
 579:         exit(2);
 580:     }
 581:     (void) close(fi);
 582:     if (child < 0)
 583:         status.w_retcode = 100;
 584:     else
 585:         while ((pid = wait(&status)) > 0 && pid != child)
 586:             ;
 587:     child = 0;
 588:     prchild = 0;
 589:     if (stopped) {      /* restart output filter */
 590:         if (kill(ofilter, SIGCONT) < 0) {
 591:             syslog(LOG_ERR, "cannot restart output filter");
 592:             exit(1);
 593:         }
 594:     }
 595:     tof = 0;
 596:     if (!WIFEXITED(status)) {
 597:         syslog(LOG_WARNING, "%s: Daemon filter '%c' terminated (%d)",
 598:             printer, format, status.w_termsig);
 599:         return(ERROR);
 600:     }
 601:     switch (status.w_retcode) {
 602:     case 0:
 603:         tof = 1;
 604:         return(OK);
 605:     case 1:
 606:         return(REPRINT);
 607:     default:
 608:         syslog(LOG_WARNING, "%s: Daemon filter '%c' exited (%d)",
 609:             printer, format, status.w_retcode);
 610:     case 2:
 611:         return(ERROR);
 612:     }
 613: }
 614: 
 615: /*
 616:  * Send the daemon control file (cf) and any data files.
 617:  * Return -1 if a non-recoverable error occured, 1 if a recoverable error and
 618:  * 0 if all is well.
 619:  */
 620: sendit(file)
 621:     char *file;
 622: {
 623:     register int i, err = OK;
 624:     char *cp, last[BUFSIZ];
 625: 
 626:     /*
 627: 	 * open control file
 628: 	 */
 629:     if ((cfp = fopen(file, "r")) == NULL)
 630:         return(OK);
 631:     /*
 632: 	 *      read the control file for work to do
 633: 	 *
 634: 	 *      file format -- first character in the line is a command
 635: 	 *      rest of the line is the argument.
 636: 	 *      commands of interest are:
 637: 	 *
 638: 	 *            a-z -- "file name" name of file to print
 639: 	 *              U -- "unlink" name of file to remove
 640: 	 *                    (after we print it. (Pass 2 only)).
 641: 	 */
 642: 
 643:     /*
 644: 	 * pass 1
 645: 	 */
 646:     while (getline(cfp)) {
 647:     again:
 648:         if (line[0] == 'S') {
 649:             cp = line+1;
 650:             i = 0;
 651:             while (*cp >= '0' && *cp <= '9')
 652:                 i = i * 10 + (*cp++ - '0');
 653:             fdev = i;
 654:             cp++;
 655:             i = 0;
 656:             while (*cp >= '0' && *cp <= '9')
 657:                 i = i * 10 + (*cp++ - '0');
 658:             fino = i;
 659:             continue;
 660:         }
 661:         if (line[0] >= 'a' && line[0] <= 'z') {
 662:             strcpy(last, line);
 663:             while (i = getline(cfp))
 664:                 if (strcmp(last, line))
 665:                     break;
 666:             switch (sendfile('\3', last+1)) {
 667:             case OK:
 668:                 if (i)
 669:                     goto again;
 670:                 break;
 671:             case REPRINT:
 672:                 (void) fclose(cfp);
 673:                 return(REPRINT);
 674:             case ACCESS:
 675:                 sendmail(logname, ACCESS);
 676:             case ERROR:
 677:                 err = ERROR;
 678:             }
 679:             break;
 680:         }
 681:     }
 682:     if (err == OK && sendfile('\2', file) > 0) {
 683:         (void) fclose(cfp);
 684:         return(REPRINT);
 685:     }
 686:     /*
 687: 	 * pass 2
 688: 	 */
 689:     fseek(cfp, 0L, 0);
 690:     while (getline(cfp))
 691:         if (line[0] == 'U')
 692:             (void) unlink(line+1);
 693:     /*
 694: 	 * clean-up in case another control file exists
 695: 	 */
 696:     (void) fclose(cfp);
 697:     (void) unlink(file);
 698:     return(err);
 699: }
 700: 
 701: /*
 702:  * Send a data file to the remote machine and spool it.
 703:  * Return positive if we should try resending.
 704:  */
 705: sendfile(type, file)
 706:     char type, *file;
 707: {
 708:     register int f, i, amt;
 709:     struct stat stb;
 710:     char buf[BUFSIZ];
 711:     int sizerr, resp;
 712: 
 713:     if (lstat(file, &stb) < 0 || (f = open(file, O_RDONLY)) < 0)
 714:         return(ERROR);
 715:     /*
 716: 	 * Check to see if data file is a symbolic link. If so, it should
 717: 	 * still point to the same file or someone is trying to print something
 718: 	 * he shouldn't.
 719: 	 */
 720:     if ((stb.st_mode & S_IFMT) == S_IFLNK && fstat(f, &stb) == 0 &&
 721:         (stb.st_dev != fdev || stb.st_ino != fino))
 722:         return(ACCESS);
 723:     (void) sprintf(buf, "%c%d %s\n", type, stb.st_size, file);
 724:     amt = strlen(buf);
 725:     for (i = 0;  ; i++) {
 726:         if (write(pfd, buf, amt) != amt ||
 727:             (resp = response()) < 0 || resp == '\1') {
 728:             (void) close(f);
 729:             return(REPRINT);
 730:         } else if (resp == '\0')
 731:             break;
 732:         if (i == 0)
 733:             status("no space on remote; waiting for queue to drain");
 734:         if (i == 10)
 735:             syslog(LOG_ALERT, "%s: can't send to %s; queue full",
 736:                 printer, RM);
 737:         sleep(5 * 60);
 738:     }
 739:     if (i)
 740:         status("sending to %s", RM);
 741:     sizerr = 0;
 742:     for (i = 0; i < stb.st_size; i += BUFSIZ) {
 743:         amt = BUFSIZ;
 744:         if (i + amt > stb.st_size)
 745:             amt = stb.st_size - i;
 746:         if (sizerr == 0 && read(f, buf, amt) != amt)
 747:             sizerr = 1;
 748:         if (write(pfd, buf, amt) != amt) {
 749:             (void) close(f);
 750:             return(REPRINT);
 751:         }
 752:     }
 753:     (void) close(f);
 754:     if (sizerr) {
 755:         syslog(LOG_INFO, "%s: %s: changed size", printer, file);
 756:         /* tell recvjob to ignore this file */
 757:         (void) write(pfd, "\1", 1);
 758:         return(ERROR);
 759:     }
 760:     if (write(pfd, "", 1) != 1 || response())
 761:         return(REPRINT);
 762:     return(OK);
 763: }
 764: 
 765: /*
 766:  * Check to make sure there have been no errors and that both programs
 767:  * are in sync with eachother.
 768:  * Return non-zero if the connection was lost.
 769:  */
 770: response()
 771: {
 772:     char resp;
 773: 
 774:     if (read(pfd, &resp, 1) != 1) {
 775:         syslog(LOG_INFO, "%s: lost connection", printer);
 776:         return(-1);
 777:     }
 778:     return(resp);
 779: }
 780: 
 781: /*
 782:  * Banner printing stuff
 783:  */
 784: banner(name1, name2)
 785:     char *name1, *name2;
 786: {
 787:     time_t tvec;
 788:     extern char *ctime();
 789: 
 790:     time(&tvec);
 791:     if (!SF && !tof)
 792:         (void) write(ofd, FF, strlen(FF));
 793:     if (SB) {   /* short banner only */
 794:         if (class[0]) {
 795:             (void) write(ofd, class, strlen(class));
 796:             (void) write(ofd, ":", 1);
 797:         }
 798:         (void) write(ofd, name1, strlen(name1));
 799:         (void) write(ofd, "  Job: ", 7);
 800:         (void) write(ofd, name2, strlen(name2));
 801:         (void) write(ofd, "  Date: ", 8);
 802:         (void) write(ofd, ctime(&tvec), 24);
 803:         (void) write(ofd, "\n", 1);
 804:     } else {    /* normal banner */
 805:         (void) write(ofd, "\n\n\n", 3);
 806:         scan_out(ofd, name1, '\0');
 807:         (void) write(ofd, "\n\n", 2);
 808:         scan_out(ofd, name2, '\0');
 809:         if (class[0]) {
 810:             (void) write(ofd,"\n\n\n",3);
 811:             scan_out(ofd, class, '\0');
 812:         }
 813:         (void) write(ofd, "\n\n\n\n\t\t\t\t\tJob:  ", 15);
 814:         (void) write(ofd, name2, strlen(name2));
 815:         (void) write(ofd, "\n\t\t\t\t\tDate: ", 12);
 816:         (void) write(ofd, ctime(&tvec), 24);
 817:         (void) write(ofd, "\n", 1);
 818:     }
 819:     if (!SF)
 820:         (void) write(ofd, FF, strlen(FF));
 821:     tof = 1;
 822: }
 823: 
 824: char *
 825: scnline(key, p, c)
 826:     register char key, *p;
 827:     char c;
 828: {
 829:     register scnwidth;
 830: 
 831:     for (scnwidth = WIDTH; --scnwidth;) {
 832:         key <<= 1;
 833:         *p++ = key & 0200 ? c : BACKGND;
 834:     }
 835:     return (p);
 836: }
 837: 
 838: #define TRC(q)  (((q)-' ')&0177)
 839: 
 840: scan_out(scfd, scsp, dlm)
 841:     int scfd;
 842:     char *scsp, dlm;
 843: {
 844:     register char *strp;
 845:     register nchrs, j;
 846:     char outbuf[LINELEN+1], *sp, c, cc;
 847:     int d, scnhgt;
 848:     extern char scnkey[][HEIGHT];   /* in lpdchar.c */
 849: 
 850:     for (scnhgt = 0; scnhgt++ < HEIGHT+DROP; ) {
 851:         strp = &outbuf[0];
 852:         sp = scsp;
 853:         for (nchrs = 0; ; ) {
 854:             d = dropit(c = TRC(cc = *sp++));
 855:             if ((!d && scnhgt > HEIGHT) || (scnhgt <= DROP && d))
 856:                 for (j = WIDTH; --j;)
 857:                     *strp++ = BACKGND;
 858:             else
 859:                 strp = scnline(scnkey[c][scnhgt-1-d], strp, cc);
 860:             if (*sp == dlm || *sp == '\0' || nchrs++ >= PW/(WIDTH+1)-1)
 861:                 break;
 862:             *strp++ = BACKGND;
 863:             *strp++ = BACKGND;
 864:         }
 865:         while (*--strp == BACKGND && strp >= outbuf)
 866:             ;
 867:         strp++;
 868:         *strp++ = '\n';
 869:         (void) write(scfd, outbuf, strp-outbuf);
 870:     }
 871: }
 872: 
 873: dropit(c)
 874:     char c;
 875: {
 876:     switch(c) {
 877: 
 878:     case TRC('_'):
 879:     case TRC(';'):
 880:     case TRC(','):
 881:     case TRC('g'):
 882:     case TRC('j'):
 883:     case TRC('p'):
 884:     case TRC('q'):
 885:     case TRC('y'):
 886:         return (DROP);
 887: 
 888:     default:
 889:         return (0);
 890:     }
 891: }
 892: 
 893: /*
 894:  * sendmail ---
 895:  *   tell people about job completion
 896:  */
 897: sendmail(user, bombed)
 898:     char *user;
 899:     int bombed;
 900: {
 901:     register int i;
 902:     int p[2], s;
 903:     register char *cp;
 904:     char buf[100];
 905:     struct stat stb;
 906:     FILE *fp;
 907: 
 908:     pipe(p);
 909:     if ((s = dofork(DORETURN)) == 0) {      /* child */
 910:         dup2(p[0], 0);
 911:         for (i = 3; i < NOFILE; i++)
 912:             (void) close(i);
 913:         if ((cp = rindex(MAIL, '/')) != NULL)
 914:             cp++;
 915:         else
 916:             cp = MAIL;
 917:         sprintf(buf, "%s@%s", user, fromhost);
 918:         execl(MAIL, cp, buf, 0);
 919:         exit(0);
 920:     } else if (s > 0) {             /* parent */
 921:         dup2(p[1], 1);
 922:         printf("To: %s@%s\n", user, fromhost);
 923:         printf("Subject: printer job\n\n");
 924:         printf("Your printer job ");
 925:         if (*jobname)
 926:             printf("(%s) ", jobname);
 927:         switch (bombed) {
 928:         case OK:
 929:             printf("\ncompleted successfully\n");
 930:             break;
 931:         default:
 932:         case FATALERR:
 933:             printf("\ncould not be printed\n");
 934:             break;
 935:         case NOACCT:
 936:             printf("\ncould not be printed without an account on %s\n", host);
 937:             break;
 938:         case FILTERERR:
 939:             if (stat(tmpfile, &stb) < 0 || stb.st_size == 0 ||
 940:                 (fp = fopen(tmpfile, "r")) == NULL) {
 941:                 printf("\nwas printed but had some errors\n");
 942:                 break;
 943:             }
 944:             printf("\nwas printed but had the following errors:\n");
 945:             while ((i = getc(fp)) != EOF)
 946:                 putchar(i);
 947:             (void) fclose(fp);
 948:             break;
 949:         case ACCESS:
 950:             printf("\nwas not printed because it was not linked to the original file\n");
 951:         }
 952:         fflush(stdout);
 953:         (void) close(1);
 954:     }
 955:     (void) close(p[0]);
 956:     (void) close(p[1]);
 957:     wait(&s);
 958: }
 959: 
 960: /*
 961:  * dofork - fork with retries on failure
 962:  */
 963: dofork(action)
 964:     int action;
 965: {
 966:     register int i, pid;
 967: 
 968:     for (i = 0; i < 20; i++) {
 969:         if ((pid = fork()) < 0) {
 970:             sleep((unsigned)(i*i));
 971:             continue;
 972:         }
 973:         /*
 974: 		 * Child should run as daemon instead of root
 975: 		 */
 976:         if (pid == 0)
 977:             setuid(DU);
 978:         return(pid);
 979:     }
 980:     syslog(LOG_ERR, "can't fork");
 981: 
 982:     switch (action) {
 983:     case DORETURN:
 984:         return (-1);
 985:     default:
 986:         syslog(LOG_ERR, "bad action (%d) to dofork", action);
 987:         /*FALL THRU*/
 988:     case DOABORT:
 989:         exit(1);
 990:     }
 991:     /*NOTREACHED*/
 992: }
 993: 
 994: /*
 995:  * Kill child processes to abort current job.
 996:  */
 997: abortpr()
 998: {
 999:     (void) unlink(tmpfile);
1000:     kill(0, SIGINT);
1001:     if (ofilter > 0)
1002:         kill(ofilter, SIGCONT);
1003:     while (wait(0) > 0)
1004:         ;
1005:     exit(0);
1006: }
1007: 
1008: init()
1009: {
1010:     int status;
1011: 
1012:     if ((status = pgetent(line, printer)) < 0) {
1013:         syslog(LOG_ERR, "can't open printer description file");
1014:         exit(1);
1015:     } else if (status == 0) {
1016:         syslog(LOG_ERR, "unknown printer: %s", printer);
1017:         exit(1);
1018:     }
1019:     if ((LP = pgetstr("lp", &bp)) == NULL)
1020:         LP = DEFDEVLP;
1021:     if ((RP = pgetstr("rp", &bp)) == NULL)
1022:         RP = DEFLP;
1023:     if ((LO = pgetstr("lo", &bp)) == NULL)
1024:         LO = DEFLOCK;
1025:     if ((ST = pgetstr("st", &bp)) == NULL)
1026:         ST = DEFSTAT;
1027:     if ((LF = pgetstr("lf", &bp)) == NULL)
1028:         LF = DEFLOGF;
1029:     if ((SD = pgetstr("sd", &bp)) == NULL)
1030:         SD = DEFSPOOL;
1031:     if ((DU = pgetnum("du")) < 0)
1032:         DU = DEFUID;
1033:     if ((FF = pgetstr("ff", &bp)) == NULL)
1034:         FF = DEFFF;
1035:     if ((PW = pgetnum("pw")) < 0)
1036:         PW = DEFWIDTH;
1037:     sprintf(&width[2], "%d", PW);
1038:     if ((PL = pgetnum("pl")) < 0)
1039:         PL = DEFLENGTH;
1040:     sprintf(&length[2], "%d", PL);
1041:     if ((PX = pgetnum("px")) < 0)
1042:         PX = 0;
1043:     sprintf(&pxwidth[2], "%d", PX);
1044:     if ((PY = pgetnum("py")) < 0)
1045:         PY = 0;
1046:     sprintf(&pxlength[2], "%d", PY);
1047:     RM = pgetstr("rm", &bp);
1048:     /*
1049: 	 * Figure out whether the local machine is the same as the remote
1050: 	 * machine entry (if it exists).  If not, then ignore the local
1051: 	 * queue information.
1052: 	 */
1053:      if (RM != (char *) NULL) {
1054:         char name[256];
1055:         struct hostent *hp;
1056: 
1057:         /* get the standard network name of the local host */
1058:         gethostname(name, sizeof(name));
1059:         name[sizeof(name)-1] = '\0';
1060:         hp = gethostbyname(name);
1061:         if (hp == (struct hostent *) NULL) {
1062:             syslog(LOG_ERR,
1063:             "unable to get network name for local machine %s",
1064:             name);
1065:             goto localcheck_done;
1066:         } else strcpy(name, hp->h_name);
1067: 
1068:         /* get the standard network name of RM */
1069:         hp = gethostbyname(RM);
1070:         if (hp == (struct hostent *) NULL) {
1071:             syslog(LOG_ERR,
1072:             "unable to get hostname for remote machine %s", RM);
1073:             goto localcheck_done;
1074:         }
1075: 
1076:         /* if printer is not on local machine, ignore LP */
1077:         if (strcmp(name, hp->h_name) != 0) *LP = '\0';
1078:     }
1079: localcheck_done:
1080: 
1081:     AF = pgetstr("af", &bp);
1082:     OF = pgetstr("of", &bp);
1083:     IF = pgetstr("if", &bp);
1084:     RF = pgetstr("rf", &bp);
1085:     TF = pgetstr("tf", &bp);
1086:     NF = pgetstr("nf", &bp);
1087:     DF = pgetstr("df", &bp);
1088:     GF = pgetstr("gf", &bp);
1089:     VF = pgetstr("vf", &bp);
1090:     CF = pgetstr("cf", &bp);
1091:     TR = pgetstr("tr", &bp);
1092:     RS = pgetflag("rs");
1093:     SF = pgetflag("sf");
1094:     SH = pgetflag("sh");
1095:     SB = pgetflag("sb");
1096:     HL = pgetflag("hl");
1097:     RW = pgetflag("rw");
1098:     BR = pgetnum("br");
1099:     if ((FC = pgetnum("fc")) < 0)
1100:         FC = 0;
1101:     if ((FS = pgetnum("fs")) < 0)
1102:         FS = 0;
1103:     if ((XC = pgetnum("xc")) < 0)
1104:         XC = 0;
1105:     if ((XS = pgetnum("xs")) < 0)
1106:         XS = 0;
1107:     tof = !pgetflag("fo");
1108: }
1109: 
1110: /*
1111:  * Acquire line printer or remote connection.
1112:  */
1113: openpr()
1114: {
1115:     register int i, n;
1116:     int resp;
1117: 
1118:     if (*LP) {
1119:         for (i = 1; ; i = i < 32 ? i << 1 : i) {
1120:             pfd = open(LP, RW ? O_RDWR : O_WRONLY);
1121:             if (pfd >= 0)
1122:                 break;
1123:             if (errno == ENOENT) {
1124:                 syslog(LOG_ERR, "%s: %m", LP);
1125:                 exit(1);
1126:             }
1127:             if (i == 1)
1128:                 status("waiting for %s to become ready (offline ?)", printer);
1129:             sleep(i);
1130:         }
1131:         if (isatty(pfd))
1132:             setty();
1133:         status("%s is ready and printing", printer);
1134:     } else if (RM != NULL) {
1135:         for (i = 1; ; i = i < 256 ? i << 1 : i) {
1136:             resp = -1;
1137:             pfd = getport(RM);
1138:             if (pfd >= 0) {
1139:                 (void) sprintf(line, "\2%s\n", RP);
1140:                 n = strlen(line);
1141:                 if (write(pfd, line, n) == n &&
1142:                     (resp = response()) == '\0')
1143:                     break;
1144:                 (void) close(pfd);
1145:             }
1146:             if (i == 1) {
1147:                 if (resp < 0)
1148:                     status("waiting for %s to come up", RM);
1149:                 else {
1150:                     status("waiting for queue to be enabled on %s", RM);
1151:                     i = 256;
1152:                 }
1153:             }
1154:             sleep(i);
1155:         }
1156:         status("sending to %s", RM);
1157:         remote = 1;
1158:     } else {
1159:         syslog(LOG_ERR, "%s: no line printer device or host name",
1160:             printer);
1161:         exit(1);
1162:     }
1163:     /*
1164: 	 * Start up an output filter, if needed.
1165: 	 */
1166:     if (OF) {
1167:         int p[2];
1168:         char *cp;
1169: 
1170:         pipe(p);
1171:         if ((ofilter = dofork(DOABORT)) == 0) { /* child */
1172:             dup2(p[0], 0);      /* pipe is std in */
1173:             dup2(pfd, 1);       /* printer is std out */
1174:             for (i = 3; i < NOFILE; i++)
1175:                 (void) close(i);
1176:             if ((cp = rindex(OF, '/')) == NULL)
1177:                 cp = OF;
1178:             else
1179:                 cp++;
1180:             execl(OF, cp, width, length, 0);
1181:             syslog(LOG_ERR, "%s: %s: %m", printer, OF);
1182:             exit(1);
1183:         }
1184:         (void) close(p[0]);     /* close input side */
1185:         ofd = p[1];         /* use pipe for output */
1186:     } else {
1187:         ofd = pfd;
1188:         ofilter = 0;
1189:     }
1190: }
1191: 
1192: struct bauds {
1193:     int baud;
1194:     int speed;
1195: } bauds[] = {
1196:     50, B50,
1197:     75, B75,
1198:     110,    B110,
1199:     134,    B134,
1200:     150,    B150,
1201:     200,    B200,
1202:     300,    B300,
1203:     600,    B600,
1204:     1200,   B1200,
1205:     1800,   B1800,
1206:     2400,   B2400,
1207:     4800,   B4800,
1208:     9600,   B9600,
1209:     19200,  EXTA,
1210:     38400,  EXTB,
1211:     0,  0
1212: };
1213: 
1214: /*
1215:  * setup tty lines.
1216:  */
1217: setty()
1218: {
1219:     struct sgttyb ttybuf;
1220:     register struct bauds *bp;
1221: 
1222:     if (ioctl(pfd, TIOCEXCL, (char *)0) < 0) {
1223:         syslog(LOG_ERR, "%s: ioctl(TIOCEXCL): %m", printer);
1224:         exit(1);
1225:     }
1226:     if (ioctl(pfd, TIOCGETP, (char *)&ttybuf) < 0) {
1227:         syslog(LOG_ERR, "%s: ioctl(TIOCGETP): %m", printer);
1228:         exit(1);
1229:     }
1230:     if (BR > 0) {
1231:         for (bp = bauds; bp->baud; bp++)
1232:             if (BR == bp->baud)
1233:                 break;
1234:         if (!bp->baud) {
1235:             syslog(LOG_ERR, "%s: illegal baud rate %d", printer, BR);
1236:             exit(1);
1237:         }
1238:         ttybuf.sg_ispeed = ttybuf.sg_ospeed = bp->speed;
1239:     }
1240:     ttybuf.sg_flags &= ~FC;
1241:     ttybuf.sg_flags |= FS;
1242:     if (ioctl(pfd, TIOCSETP, (char *)&ttybuf) < 0) {
1243:         syslog(LOG_ERR, "%s: ioctl(TIOCSETP): %m", printer);
1244:         exit(1);
1245:     }
1246:     if (XC || XS) {
1247:         int ldisc = NTTYDISC;
1248: 
1249:         if (ioctl(pfd, TIOCSETD, &ldisc) < 0) {
1250:             syslog(LOG_ERR, "%s: ioctl(TIOCSETD): %m", printer);
1251:             exit(1);
1252:         }
1253:     }
1254:     if (XC) {
1255:         if (ioctl(pfd, TIOCLBIC, &XC) < 0) {
1256:             syslog(LOG_ERR, "%s: ioctl(TIOCLBIC): %m", printer);
1257:             exit(1);
1258:         }
1259:     }
1260:     if (XS) {
1261:         if (ioctl(pfd, TIOCLBIS, &XS) < 0) {
1262:             syslog(LOG_ERR, "%s: ioctl(TIOCLBIS): %m", printer);
1263:             exit(1);
1264:         }
1265:     }
1266: }
1267: 
1268: /*VARARGS1*/
1269: status(msg, a1, a2, a3)
1270:     char *msg;
1271: {
1272:     register int fd;
1273:     char buf[BUFSIZ];
1274: 
1275:     umask(0);
1276:     fd = open(ST, O_WRONLY|O_CREAT, 0664);
1277:     if (fd < 0 || flock(fd, LOCK_EX) < 0) {
1278:         syslog(LOG_ERR, "%s: %s: %m", printer, ST);
1279:         exit(1);
1280:     }
1281:     ftruncate(fd, 0);
1282:     sprintf(buf, msg, a1, a2, a3);
1283:     strcat(buf, "\n");
1284:     (void) write(fd, buf, strlen(buf));
1285:     (void) close(fd);
1286: }

Defined functions

abortpr defined in line 997; used 5 times
banner defined in line 784; used 2 times
dofork defined in line 963; used 4 times
dropit defined in line 873; used 1 times
openpr defined in line 1113; used 2 times
print defined in line 412; used 2 times
printit defined in line 221; used 1 times
printjob defined in line 59; used 3 times
response defined in line 770; used 3 times
scan_out defined in line 840; used 3 times
scnline defined in line 824; used 1 times
sendfile defined in line 705; used 2 times
sendit defined in line 620; used 1 times
sendmail defined in line 897; used 4 times
setty defined in line 1217; used 1 times
status defined in line 1269; used 20 times

Defined variables

bauds defined in line 1195; used 1 times
child defined in line 41; used 4 times
class defined in line 51; used 13 times
fonts defined in line 208; used 5 times
fromhost defined in line 48; used 4 times
ifonts defined in line 210; used 1 times
indent defined in line 56; used 5 times
jobname defined in line 50; used 7 times
lfd defined in line 38; used 11 times
logname defined in line 49; used 6 times
ofd defined in line 37; used 30 times
ofilter defined in line 42; used 11 times
pfd defined in line 36; used 22 times
pid defined in line 39; used 12 times
prchild defined in line 40; used 4 times
pxlength defined in line 55; used 5 times
pxwidth defined in line 54; used 5 times
remote defined in line 44; used 2 times
sccsid defined in line 8; never used
title defined in line 34; used 7 times
tmpfile defined in line 57; used 7 times
tof defined in line 43; used 9 times
width defined in line 52; used 10 times

Defined struct's

bauds defined in line 1192; used 2 times

Defined macros

ACCESS defined in line 32; used 3 times
DOABORT defined in line 21; used 1 times
DORETURN defined in line 20; used 3 times
ERROR defined in line 27; used 9 times
FATALERR defined in line 29; used 1 times
FILTERERR defined in line 31; never used
NOACCT defined in line 30; used 2 times
OK defined in line 28; used 12 times
REPRINT defined in line 26; used 10 times
TRC defined in line 838; used 9 times
Last modified: 1986-01-11
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 4815
Valid CSS Valid XHTML 1.0 Strict