1: # include <stdio.h>
   2: # include <pwd.h>
   3: # include <signal.h>
   4: # include <sys/file.h>
   5: # include "dlvrmail.h"
   6: # ifdef LOG
   7: # include <log.h>
   8: # endif LOG
   9: 
  10: static char SccsId[] = "@(#)deliver.c	2.5	2/5/81";
  11: 
  12: /*
  13: **  DELIVER -- Deliver a message to a particular address.
  14: **
  15: **	Algorithm:
  16: **		Compute receiving network (i.e., mailer), host, & user.
  17: **		If local, see if this is really a program name.
  18: **		Build argument for the mailer.
  19: **		Create pipe through edit fcn if appropriate.
  20: **		Fork.
  21: **			Child: call mailer
  22: **		Parent: call editfcn if specified.
  23: **		Wait for mailer to finish.
  24: **		Interpret exit status.
  25: **
  26: **	Parameters:
  27: **		to -- the address to deliver the message to.
  28: **		editfcn -- if non-NULL, we want to call this function
  29: **			to output the letter (instead of just out-
  30: **			putting it raw).
  31: **
  32: **	Returns:
  33: **		zero -- successfully delivered.
  34: **		else -- some failure, see ExitStat for more info.
  35: **
  36: **	Side Effects:
  37: **		The standard input is passed off to someone.
  38: **
  39: **	WARNING:
  40: **		The standard input is shared amongst all children,
  41: **		including the file pointer.  It is critical that the
  42: **		parent waits for the child to finish before forking
  43: **		another child.
  44: **
  45: **	Called By:
  46: **		main
  47: **		savemail
  48: **
  49: **	Files:
  50: **		standard input -- must be opened to the message to
  51: **			deliver.
  52: */
  53: 
  54: deliver(to, editfcn)
  55:     addrq *to;
  56:     int (*editfcn)();
  57: {
  58:     register struct mailer *m;
  59:     char *host;
  60:     char *user;
  61:     extern struct passwd *getpwnam();
  62:     char **pvp;
  63:     extern char **buildargv();
  64:     auto int st;
  65:     register int i;
  66:     register char *p;
  67:     int pid;
  68:     int pvect[2];
  69:     extern FILE *fdopen();
  70:     extern int errno;
  71:     FILE *mfile;
  72:     extern putheader();
  73:     extern pipesig();
  74:     extern bool GotHdr;
  75:     extern char *index();
  76: 
  77:     /*
  78: 	**  Compute receiving mailer, host, and to addreses.
  79: 	**	Do some initialization first.  To is the to address
  80: 	**	for error messages.
  81: 	*/
  82: 
  83:     To = to->q_paddr;
  84:     m = to->q_mailer;
  85:     user = to->q_user;
  86:     host = to->q_host;
  87:     Errors = 0;
  88:     errno = 0;
  89: # ifdef DEBUG
  90:     if (Debug)
  91:         printf("deliver(%s [%d, `%s', `%s'])\n", To, m, host, user);
  92: # endif DEBUG
  93: 
  94:     /*
  95: 	**  Remove quote bits from user/host.
  96: 	*/
  97: 
  98:     for (p = user; (*p++ &= 0177) != '\0'; )
  99:         continue;
 100:     if (host != NULL)
 101:         for (p = host; (*p++ &= 0177) != '\0'; )
 102:             continue;
 103: 
 104:     /*
 105: 	**  Strip quote bits from names if the mailer wants it.
 106: 	*/
 107: 
 108:     if (flagset(M_STRIPQ, m->m_flags))
 109:     {
 110:         stripquotes(user);
 111:         stripquotes(host);
 112:     }
 113: 
 114:     /*
 115: 	**  See if this user name is "special".
 116: 	**	If the user is a program, diddle with the mailer spec.
 117: 	**	If the user name has a slash in it, assume that this
 118: 	**		is a file -- send it off without further ado.
 119: 	**		Note that this means that editfcn's will not
 120: 	**		be applied to the message.
 121: 	*/
 122: 
 123:     if (m == &Mailer[0])
 124:     {
 125:         if (*user == '|')
 126:         {
 127:             user++;
 128:             m = &Mailer[1];
 129:         }
 130:         else
 131:         {
 132:             if (index(user, '/') != NULL)
 133:             {
 134:                 i = mailfile(user);
 135:                 giveresponse(i, TRUE, m);
 136:                 return (i);
 137:             }
 138:         }
 139:     }
 140: 
 141:     /*
 142: 	**  See if the user exists.
 143: 	**	Strictly, this is only needed to print a pretty
 144: 	**	error message.
 145: 	**
 146: 	**	>>>>>>>>>> This clause assumes that the local mailer
 147: 	**	>> NOTE >> cannot do any further aliasing; that
 148: 	**	>>>>>>>>>> function is subsumed by delivermail.
 149: 	*/
 150: 
 151:     if (m == &Mailer[0])
 152:     {
 153:         if (getpwnam(user) == NULL)
 154:         {
 155:             giveresponse(EX_NOUSER, TRUE, m);
 156:             return (EX_NOUSER);
 157:         }
 158:     }
 159: 
 160:     /*
 161: 	**  If the mailer wants a From line, insert a new editfcn.
 162: 	*/
 163: 
 164:     if (flagset(M_HDR, m->m_flags) && editfcn == NULL && (!GotHdr || flagset(M_FHDR, m->m_flags)))
 165:         editfcn = putheader;
 166: 
 167:     /*
 168: 	**  Call the mailer.
 169: 	**	The argument vector gets built, pipes through 'editfcn'
 170: 	**	are created as necessary, and we fork & exec as
 171: 	**	appropriate.  In the parent, we call 'editfcn'.
 172: 	*/
 173: 
 174:     pvp = buildargv(m->m_argv, m->m_flags, host, user, From.q_paddr);
 175:     if (pvp == NULL)
 176:     {
 177:         usrerr("name too long");
 178:         return (-1);
 179:     }
 180:     rewind(stdin);
 181: 
 182:     /* create a pipe if we will need one */
 183:     if (editfcn != NULL && pipe(pvect) < 0)
 184:     {
 185:         syserr("pipe");
 186:         return (-1);
 187:     }
 188: # ifdef VFORK
 189:     pid = vfork();
 190: # else
 191:     pid = fork();
 192: # endif
 193:     if (pid < 0)
 194:     {
 195:         syserr("Cannot fork");
 196:         if (editfcn != NULL)
 197:         {
 198:             close(pvect[0]);
 199:             close(pvect[1]);
 200:         }
 201:         return (-1);
 202:     }
 203:     else if (pid == 0)
 204:     {
 205:         /* child -- set up input & exec mailer */
 206:         /* make diagnostic output be standard output */
 207:         close(2);
 208:         dup(1);
 209:         signal(SIGINT, SIG_IGN);
 210:         if (editfcn != NULL)
 211:         {
 212:             close(0);
 213:             if (dup(pvect[0]) < 0)
 214:             {
 215:                 syserr("Cannot dup to zero!");
 216:                 _exit(EX_OSERR);
 217:             }
 218:             close(pvect[0]);
 219:             close(pvect[1]);
 220:         }
 221:         if (!flagset(M_RESTR, m->m_flags))
 222:             setuid(getuid());
 223: # ifdef LOG
 224:         closelog();
 225: # endif LOG
 226: # ifndef VFORK
 227:         /*
 228: 		 * We have to be careful with vfork - we can't mung up the
 229: 		 * memory but we don't want the mailer to inherit any extra
 230: 		 * open files.  Chances are the mailer won't
 231: 		 * care about an extra file, but then again you never know.
 232: 		 * Actually, we would like to close(fileno(pwf)), but it's
 233: 		 * declared static so we can't.  But if we fclose(pwf), which
 234: 		 * is what endpwent does, it closes it in the parent too and
 235: 		 * the next getpwnam will be slower.  If you have a weird mailer
 236: 		 * that chokes on the extra file you should do the endpwent().
 237: 		 */
 238:         endpwent();
 239: # endif
 240:         execv(m->m_mailer, pvp);
 241:         /* syserr fails because log is closed */
 242:         /* syserr("Cannot exec %s", m->m_mailer); */
 243:         printf("Cannot exec %s\n", m->m_mailer);
 244:         fflush(stdout);
 245:         _exit(EX_UNAVAILABLE);
 246:     }
 247: 
 248:     /* arrange to write out header message if error */
 249:     if (editfcn != NULL)
 250:     {
 251:         close(pvect[0]);
 252:         signal(SIGPIPE, pipesig);
 253:         mfile = fdopen(pvect[1], "w");
 254:         (*editfcn)(mfile);
 255:         fclose(mfile);
 256:     }
 257: 
 258:     /*
 259: 	**  Wait for child to die and report status.
 260: 	**	We should never get fatal errors (e.g., segmentation
 261: 	**	violation), so we report those specially.  For other
 262: 	**	errors, we choose a status message (into statmsg),
 263: 	**	and if it represents an error, we print it.
 264: 	*/
 265: 
 266:     while ((i = wait(&st)) > 0 && i != pid)
 267:         continue;
 268:     if (i < 0)
 269:     {
 270:         syserr("wait");
 271:         return (-1);
 272:     }
 273:     if ((st & 0377) != 0)
 274:     {
 275:         syserr("%s: stat %o", pvp[0], st);
 276:         ExitStat = EX_UNAVAILABLE;
 277:         return (-1);
 278:     }
 279:     i = (st >> 8) & 0377;
 280:     giveresponse(i, TRUE, m);
 281:     return (i);
 282: }
 283: /*
 284: **  GIVERESPONSE -- Interpret an error response from a mailer
 285: **
 286: **	Parameters:
 287: **		stat -- the status code from the mailer (high byte
 288: **			only; core dumps must have been taken care of
 289: **			already).
 290: **		force -- if set, force an error message output, even
 291: **			if the mailer seems to like to print its own
 292: **			messages.
 293: **		m -- the mailer descriptor for this mailer.
 294: **
 295: **	Returns:
 296: **		none.
 297: **
 298: **	Side Effects:
 299: **		Errors may be incremented.
 300: **		ExitStat may be set.
 301: **
 302: **	Called By:
 303: **		deliver
 304: */
 305: 
 306: giveresponse(stat, force, m)
 307:     int stat;
 308:     int force;
 309:     register struct mailer *m;
 310: {
 311:     register char *statmsg;
 312:     extern char *SysExMsg[];
 313:     register int i;
 314:     extern int N_SysEx;
 315:     extern long MsgSize;
 316:     char buf[30];
 317: 
 318:     i = stat - EX__BASE;
 319:     if (i < 0 || i > N_SysEx)
 320:         statmsg = NULL;
 321:     else
 322:         statmsg = SysExMsg[i];
 323:     if (stat == 0)
 324:         statmsg = "ok";
 325:     else
 326:     {
 327:         Errors++;
 328:         if (statmsg == NULL && m->m_badstat != 0)
 329:         {
 330:             stat = m->m_badstat;
 331:             i = stat - EX__BASE;
 332: # ifdef DEBUG
 333:             if (i < 0 || i >= N_SysEx)
 334:                 syserr("Bad m_badstat %d", stat);
 335:             else
 336: # endif DEBUG
 337:             statmsg = SysExMsg[i];
 338:         }
 339:         if (statmsg == NULL)
 340:             usrerr("unknown mailer response %d", stat);
 341:         else if (force || !flagset(M_QUIET, m->m_flags))
 342:             usrerr("%s", statmsg);
 343:     }
 344: 
 345:     /*
 346: 	**  Final cleanup.
 347: 	**	Log a record of the transaction.  Compute the new
 348: 	**	ExitStat -- if we already had an error, stick with
 349: 	**	that.
 350: 	*/
 351: 
 352:     if (statmsg == NULL)
 353:     {
 354:         sprintf(buf, "error %d", stat);
 355:         statmsg = buf;
 356:     }
 357: 
 358: # ifdef LOG
 359:     logmsg(LOG_INFO, "%s->%s: %ld: %s", From.q_paddr, To, MsgSize, statmsg);
 360: # endif LOG
 361:     setstat(stat);
 362:     return (stat);
 363: }
 364: /*
 365: **  PUTHEADER -- insert the From header into some mail
 366: **
 367: **	For mailers such as 'msgs' that want the header inserted
 368: **	into the mail, this edit filter inserts the From line and
 369: **	then passes the rest of the message through.
 370: **
 371: **	Parameters:
 372: **		fp -- the file pointer for the output.
 373: **
 374: **	Returns:
 375: **		none
 376: **
 377: **	Side Effects:
 378: **		Puts a "From" line in UNIX format, and then
 379: **			outputs the rest of the message.
 380: **
 381: **	Called By:
 382: **		deliver
 383: */
 384: 
 385: putheader(fp)
 386:     register FILE *fp;
 387: {
 388:     char buf[MAXLINE + 1];
 389:     long tim;
 390:     extern char *ctime();
 391:     register char *p;
 392:     extern char *index();
 393: 
 394:     /* output the header part */
 395:     fgets(buf, sizeof buf, stdin);
 396:     if (strncmp(buf, "From ", 5) != 0 || (p = index(&buf[5], ' ')) == NULL)
 397:     {
 398:         time(&tim);
 399:         fprintf(fp, "From %s %s", From.q_paddr, ctime(&tim));
 400:         fputs(buf, fp);
 401:     }
 402:     else
 403:         fprintf(fp, "From %s %s", From.q_paddr, &p[1]);
 404: 
 405:     /* output the body */
 406:     while (!ferror(fp) && fgets(buf, sizeof buf, stdin) != NULL)
 407:         fputs(buf, fp);
 408:     if (ferror(fp))
 409:     {
 410:         syserr("putheader: write error");
 411:         setstat(EX_IOERR);
 412:     }
 413: }
 414: /*
 415: **  PIPESIG -- Handle broken pipe signals
 416: **
 417: **	This just logs an error.
 418: **
 419: **	Parameters:
 420: **		none
 421: **
 422: **	Returns:
 423: **		none
 424: **
 425: **	Side Effects:
 426: **		logs an error message.
 427: */
 428: 
 429: pipesig()
 430: {
 431:     syserr("Broken pipe");
 432:     signal(SIGPIPE, SIG_IGN);
 433: }
 434: /*
 435: **  SENDTO -- Designate a send list.
 436: **
 437: **	The parameter is a comma-separated list of people to send to.
 438: **	This routine arranges to send to all of them.
 439: **
 440: **	Parameters:
 441: **		list -- the send list.
 442: **		copyf -- the copy flag; passed to parse.
 443: **
 444: **	Returns:
 445: **		none
 446: **
 447: **	Side Effects:
 448: **		none.
 449: **
 450: **	Called By:
 451: **		main
 452: **		alias
 453: */
 454: 
 455: sendto(list, copyf)
 456:     char *list;
 457:     int copyf;
 458: {
 459:     register char *p;
 460:     register char *q;
 461:     register char c;
 462:     addrq *a;
 463:     extern addrq *parse();
 464:     bool more;
 465: 
 466:     /* more keeps track of what the previous delimiter was */
 467:     more = TRUE;
 468:     for (p = list; more; )
 469:     {
 470:         /* find the end of this address */
 471:         q = p;
 472:         while ((c = *p++) != '\0' && c != ',' && c != '\n')
 473:             continue;
 474:         more = c != '\0';
 475:         *--p = '\0';
 476:         if (more)
 477:             p++;
 478: 
 479:         /* parse the address */
 480:         if ((a = parse(q, (addrq *) NULL, copyf)) == NULL)
 481:             continue;
 482: 
 483:         /* arrange to send to this person */
 484:         recipient(a, &SendQ);
 485:     }
 486:     To = NULL;
 487: }
 488: /*
 489: **  RECIPIENT -- Designate a message recipient
 490: **
 491: **	Saves the named person for future mailing.
 492: **
 493: **	Designates a person as a recipient.  This routine
 494: **	does the initial parsing, and checks to see if
 495: **	this person has already received the mail.
 496: **	It also supresses local network names and turns them into
 497: **	local names.
 498: **
 499: **	Parameters:
 500: **		a -- the (preparsed) address header for the recipient.
 501: **		targetq -- the queue to add the name to.
 502: **
 503: **	Returns:
 504: **		none.
 505: **
 506: **	Side Effects:
 507: **		none.
 508: **
 509: **	Called By:
 510: **		sendto
 511: **		main
 512: */
 513: 
 514: recipient(a, targetq)
 515:     register addrq *a;
 516:     addrq *targetq;
 517: {
 518:     register addrq *q;
 519:     register struct mailer *m;
 520:     register char **pvp;
 521:     extern char *xalloc();
 522:     extern bool forward();
 523:     extern int errno;
 524:     extern bool sameaddr();
 525: 
 526:     To = a->q_paddr;
 527:     m = a->q_mailer;
 528:     errno = 0;
 529: # ifdef DEBUG
 530:     if (Debug)
 531:         printf("recipient(%s)\n", To);
 532: # endif DEBUG
 533: 
 534:     /*
 535: 	**  Look up this person in the recipient list.  If they
 536: 	**  are there already, return, otherwise continue.
 537: 	*/
 538: 
 539:     if (!ForceMail)
 540:     {
 541:         for (q = &SendQ; (q = nxtinq(q)) != NULL; )
 542:             if (sameaddr(q, a, FALSE))
 543:             {
 544: # ifdef DEBUG
 545:                 if (Debug)
 546:                     printf("(%s in SendQ)\n", a->q_paddr);
 547: # endif DEBUG
 548:                 return;
 549:             }
 550:         for (q = &AliasQ; (q = nxtinq(q)) != NULL; )
 551:             if (sameaddr(q, a, FALSE))
 552:             {
 553: # ifdef DEBUG
 554:                 if (Debug)
 555:                     printf("(%s in AliasQ)\n", a->q_paddr);
 556: # endif DEBUG
 557:                 return;
 558:             }
 559:     }
 560: 
 561:     /*
 562: 	**  See if the user wants hir mail forwarded.
 563: 	**	`Forward' must do the forwarding recursively.
 564: 	*/
 565: 
 566:     if (m == &Mailer[0] && !NoAlias && targetq == &SendQ && forward(a))
 567:         return;
 568: 
 569:     /*
 570: 	**  Put the user onto the target queue.
 571: 	*/
 572: 
 573:     if (targetq != NULL)
 574:     {
 575:         putonq(a, targetq);
 576:     }
 577: 
 578:     return;
 579: }
 580: /*
 581: **  BUILDARGV -- Build an argument vector for a mail server.
 582: **
 583: **	Using a template defined in config.c, an argv is built.
 584: **	The format of the template is already a vector.  The
 585: **	items of this vector are copied, unless a dollar sign
 586: **	is encountered.  In this case, the next character
 587: **	specifies something else to copy in.  These can be
 588: **		$f	The from address.
 589: **		$h	The host.
 590: **		$u	The user.
 591: **		$c	The hop count.
 592: **	The vector is built in a local buffer.  A pointer to
 593: **	the static argv is returned.
 594: **
 595: **	Parameters:
 596: **		tmplt -- a template for an argument vector.
 597: **		flags -- the flags for this server.
 598: **		host -- the host name to send to.
 599: **		user -- the user name to send to.
 600: **		from -- the person this mail is from.
 601: **
 602: **	Returns:
 603: **		A pointer to an argv.
 604: **
 605: **	Side Effects:
 606: **		none
 607: **
 608: **	WARNING:
 609: **		Since the argv is staticly allocated, any subsequent
 610: **		calls will clobber the old argv.
 611: **
 612: **	Called By:
 613: **		deliver
 614: */
 615: 
 616: char **
 617: buildargv(tmplt, flags, host, user, from)
 618:     char **tmplt;
 619:     int flags;
 620:     char *host;
 621:     char *user;
 622:     char *from;
 623: {
 624:     register char *p;
 625:     register char *q;
 626:     static char *pv[MAXPV+1];
 627:     char **pvp;
 628:     char **mvp;
 629:     static char buf[512];
 630:     register char *bp;
 631:     char pbuf[30];
 632: 
 633:     /*
 634: 	**  Do initial argv setup.
 635: 	**	Insert the mailer name.  Notice that $x expansion is
 636: 	**	NOT done on the mailer name.  Then, if the mailer has
 637: 	**	a picky -f flag, we insert it as appropriate.  This
 638: 	**	code does not check for 'pv' overflow; this places a
 639: 	**	manifest lower limit of 4 for MAXPV.
 640: 	*/
 641: 
 642:     pvp = pv;
 643:     bp = buf;
 644: 
 645:     *pvp++ = tmplt[0];
 646: 
 647:     /* insert -f or -r flag as appropriate */
 648:     if (flagset(M_FOPT|M_ROPT, flags) && FromFlag)
 649:     {
 650:         if (flagset(M_FOPT, flags))
 651:             *pvp++ = "-f";
 652:         else
 653:             *pvp++ = "-r";
 654:         *pvp++ = From.q_paddr;
 655:     }
 656: 
 657:     /*
 658: 	**  Build the rest of argv.
 659: 	**	For each prototype parameter, the prototype is
 660: 	**	scanned character at a time.  If a dollar-sign is
 661: 	**	found, 'q' is set to the appropriate expansion,
 662: 	**	otherwise it is null.  Then either the string
 663: 	**	pointed to by q, or the original character, is
 664: 	**	interpolated into the buffer.  Buffer overflow is
 665: 	**	checked.
 666: 	*/
 667: 
 668:     for (mvp = tmplt; (p = *++mvp) != NULL; )
 669:     {
 670:         if (pvp >= &pv[MAXPV])
 671:         {
 672:             syserr("Too many parameters to %s", pv[0]);
 673:             return (NULL);
 674:         }
 675:         *pvp++ = bp;
 676:         for (; *p != '\0'; p++)
 677:         {
 678:             /* q will be the interpolated quantity */
 679:             q = NULL;
 680:             if (*p == '$')
 681:             {
 682:                 switch (*++p)
 683:                 {
 684:                   case 'f': /* from person */
 685:                     q = from;
 686:                     break;
 687: 
 688:                   case 'u': /* user */
 689:                     q = user;
 690:                     break;
 691: 
 692:                   case 'h': /* host */
 693:                     q = host;
 694:                     break;
 695: 
 696:                   case 'c': /* hop count */
 697:                     sprintf(pbuf, "%d", HopCount);
 698:                     q = pbuf;
 699:                     break;
 700:                 }
 701:             }
 702: 
 703:             /*
 704: 			**  Interpolate q or output one character
 705: 			**	Strip quote bits as we proceed.....
 706: 			*/
 707: 
 708:             if (q != NULL)
 709:             {
 710:                 while (bp < &buf[sizeof buf - 1] && (*bp++ = *q++) != '\0')
 711:                     continue;
 712:                 bp--;
 713:             }
 714:             else if (bp < &buf[sizeof buf - 1])
 715:                 *bp++ = *p;
 716:         }
 717:         *bp++ = '\0';
 718:         if (bp >= &buf[sizeof buf - 1])
 719:             return (NULL);
 720:     }
 721:     *pvp = NULL;
 722: 
 723: # ifdef DEBUG
 724:     if (Debug)
 725:     {
 726:         printf("Interpolated argv is:\n");
 727:         for (mvp = pv; *mvp != NULL; mvp++)
 728:             printf("\t%s\n", *mvp);
 729:     }
 730: # endif DEBUG
 731: 
 732:     return (pv);
 733: }
 734: /*
 735: **  MAILFILE -- Send a message to a file.
 736: **
 737: **	Parameters:
 738: **		filename -- the name of the file to send to.
 739: **
 740: **	Returns:
 741: **		The exit code associated with the operation.
 742: **
 743: **	Side Effects:
 744: **		none.
 745: **
 746: **	Called By:
 747: **		deliver
 748: */
 749: 
 750: mailfile(filename)
 751:     char *filename;
 752: {
 753:     char buf[MAXLINE];
 754:     register FILE *f;
 755:     auto long tim;
 756:     extern char *ctime();
 757: 
 758:     if (access(filename, FACCESS_WRITE) < 0)
 759:         return (EX_CANTCREAT);
 760:     f = fopen(filename, "a");
 761:     if (f == NULL)
 762:         return (EX_CANTCREAT);
 763: 
 764:     /* output the timestamp */
 765:     time(&tim);
 766:     fprintf(f, "From %s %s", From.q_paddr, ctime(&tim));
 767:     rewind(stdin);
 768:     while (fgets(buf, sizeof buf, stdin) != NULL)
 769:     {
 770:         fputs(buf, f);
 771:         if (ferror(f))
 772:         {
 773:             fclose(f);
 774:             return (EX_IOERR);
 775:         }
 776:     }
 777:     fputs("\n", f);
 778:     fclose(f);
 779:     return (EX_OK);
 780: }

Defined functions

buildargv defined in line 616; used 2 times
giveresponse defined in line 306; used 3 times
mailfile defined in line 750; used 1 times
pipesig defined in line 429; used 2 times
putheader defined in line 385; used 2 times
recipient defined in line 514; used 3 times
sendto defined in line 455; used 4 times

Defined variables

SccsId defined in line 10; never used
Last modified: 1983-06-28
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 1240
Valid CSS Valid XHTML 1.0 Strict