1: /*
   2:  * Copyright (c) 1982, 1986 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:  *	@(#)uipc_usrreq.c	7.1 (Berkeley) 6/5/86
   7:  */
   8: 
   9: #include "param.h"
  10: #include "dir.h"
  11: #include "user.h"
  12: #include "mbuf.h"
  13: #include "domain.h"
  14: #include "protosw.h"
  15: #include "socket.h"
  16: #include "socketvar.h"
  17: #include "unpcb.h"
  18: #include "un.h"
  19: #include "inode.h"
  20: #include "file.h"
  21: #include "stat.h"
  22: 
  23: /*
  24:  * Unix communications domain.
  25:  *
  26:  * TODO:
  27:  *	SEQPACKET, RDM
  28:  *	rethink name space problems
  29:  *	need a proper out-of-band
  30:  */
  31: struct  sockaddr sun_noname = { AF_UNIX };
  32: ino_t   unp_ino;            /* prototype for fake inode numbers */
  33: 
  34: /*ARGSUSED*/
  35: uipc_usrreq(so, req, m, nam, rights)
  36:     struct socket *so;
  37:     int req;
  38:     struct mbuf *m, *nam, *rights;
  39: {
  40:     struct unpcb *unp = sotounpcb(so);
  41:     register struct socket *so2;
  42:     int error = 0;
  43: 
  44:     if (req == PRU_CONTROL)
  45:         return (EOPNOTSUPP);
  46:     if (req != PRU_SEND && rights && rights->m_len) {
  47:         error = EOPNOTSUPP;
  48:         goto release;
  49:     }
  50:     if (unp == 0 && req != PRU_ATTACH) {
  51:         error = EINVAL;
  52:         goto release;
  53:     }
  54:     switch (req) {
  55: 
  56:     case PRU_ATTACH:
  57:         if (unp) {
  58:             error = EISCONN;
  59:             break;
  60:         }
  61:         error = unp_attach(so);
  62:         break;
  63: 
  64:     case PRU_DETACH:
  65:         unp_detach(unp);
  66:         break;
  67: 
  68:     case PRU_BIND:
  69:         error = unp_bind(unp, nam);
  70:         break;
  71: 
  72:     case PRU_LISTEN:
  73:         if (unp->unp_inode == 0)
  74:             error = EINVAL;
  75:         break;
  76: 
  77:     case PRU_CONNECT:
  78:         error = unp_connect(so, nam);
  79:         break;
  80: 
  81:     case PRU_CONNECT2:
  82:         error = unp_connect2(so, (struct socket *)nam);
  83:         break;
  84: 
  85:     case PRU_DISCONNECT:
  86:         unp_disconnect(unp);
  87:         break;
  88: 
  89:     case PRU_ACCEPT:
  90:         /*
  91: 		 * Pass back name of connected socket,
  92: 		 * if it was bound and we are still connected
  93: 		 * (our peer may have closed already!).
  94: 		 */
  95:         if (unp->unp_conn && unp->unp_conn->unp_addr) {
  96:             nam->m_len = unp->unp_conn->unp_addr->m_len;
  97:             bcopy(mtod(unp->unp_conn->unp_addr, caddr_t),
  98:                 mtod(nam, caddr_t), (unsigned)nam->m_len);
  99:         } else {
 100:             nam->m_len = sizeof(sun_noname);
 101:             *(mtod(nam, struct sockaddr *)) = sun_noname;
 102:         }
 103:         break;
 104: 
 105:     case PRU_SHUTDOWN:
 106:         socantsendmore(so);
 107:         unp_usrclosed(unp);
 108:         break;
 109: 
 110:     case PRU_RCVD:
 111:         switch (so->so_type) {
 112: 
 113:         case SOCK_DGRAM:
 114:             panic("uipc 1");
 115:             /*NOTREACHED*/
 116: 
 117:         case SOCK_STREAM:
 118: #define rcv (&so->so_rcv)
 119: #define snd (&so2->so_snd)
 120:             if (unp->unp_conn == 0)
 121:                 break;
 122:             so2 = unp->unp_conn->unp_socket;
 123:             /*
 124: 			 * Adjust backpressure on sender
 125: 			 * and wakeup any waiting to write.
 126: 			 */
 127:             snd->sb_mbmax += unp->unp_mbcnt - rcv->sb_mbcnt;
 128:             unp->unp_mbcnt = rcv->sb_mbcnt;
 129:             snd->sb_hiwat += unp->unp_cc - rcv->sb_cc;
 130:             unp->unp_cc = rcv->sb_cc;
 131:             sowwakeup(so2);
 132: #undef snd
 133: #undef rcv
 134:             break;
 135: 
 136:         default:
 137:             panic("uipc 2");
 138:         }
 139:         break;
 140: 
 141:     case PRU_SEND:
 142:         if (rights) {
 143:             error = unp_internalize(rights);
 144:             if (error)
 145:                 break;
 146:         }
 147:         switch (so->so_type) {
 148: 
 149:         case SOCK_DGRAM: {
 150:             struct sockaddr *from;
 151: 
 152:             if (nam) {
 153:                 if (unp->unp_conn) {
 154:                     error = EISCONN;
 155:                     break;
 156:                 }
 157:                 error = unp_connect(so, nam);
 158:                 if (error)
 159:                     break;
 160:             } else {
 161:                 if (unp->unp_conn == 0) {
 162:                     error = ENOTCONN;
 163:                     break;
 164:                 }
 165:             }
 166:             so2 = unp->unp_conn->unp_socket;
 167:             if (unp->unp_addr)
 168:                 from = mtod(unp->unp_addr, struct sockaddr *);
 169:             else
 170:                 from = &sun_noname;
 171:             if (sbspace(&so2->so_rcv) > 0 &&
 172:                 sbappendaddr(&so2->so_rcv, from, m, rights)) {
 173:                 sorwakeup(so2);
 174:                 m = 0;
 175:             } else
 176:                 error = ENOBUFS;
 177:             if (nam)
 178:                 unp_disconnect(unp);
 179:             break;
 180:         }
 181: 
 182:         case SOCK_STREAM:
 183: #define rcv (&so2->so_rcv)
 184: #define snd (&so->so_snd)
 185:             if (so->so_state & SS_CANTSENDMORE) {
 186:                 error = EPIPE;
 187:                 break;
 188:             }
 189:             if (unp->unp_conn == 0)
 190:                 panic("uipc 3");
 191:             so2 = unp->unp_conn->unp_socket;
 192:             /*
 193: 			 * Send to paired receive port, and then reduce
 194: 			 * send buffer hiwater marks to maintain backpressure.
 195: 			 * Wake up readers.
 196: 			 */
 197:             if (rights)
 198:                 (void)sbappendrights(rcv, m, rights);
 199:             else
 200:                 sbappend(rcv, m);
 201:             snd->sb_mbmax -=
 202:                 rcv->sb_mbcnt - unp->unp_conn->unp_mbcnt;
 203:             unp->unp_conn->unp_mbcnt = rcv->sb_mbcnt;
 204:             snd->sb_hiwat -= rcv->sb_cc - unp->unp_conn->unp_cc;
 205:             unp->unp_conn->unp_cc = rcv->sb_cc;
 206:             sorwakeup(so2);
 207:             m = 0;
 208: #undef snd
 209: #undef rcv
 210:             break;
 211: 
 212:         default:
 213:             panic("uipc 4");
 214:         }
 215:         break;
 216: 
 217:     case PRU_ABORT:
 218:         unp_drop(unp, ECONNABORTED);
 219:         break;
 220: 
 221:     case PRU_SENSE:
 222:         ((struct stat *) m)->st_blksize = so->so_snd.sb_hiwat;
 223:         if (so->so_type == SOCK_STREAM && unp->unp_conn != 0) {
 224:             so2 = unp->unp_conn->unp_socket;
 225:             ((struct stat *) m)->st_blksize += so2->so_rcv.sb_cc;
 226:         }
 227:         ((struct stat *) m)->st_dev = NODEV;
 228:         if (unp->unp_ino == 0)
 229:             unp->unp_ino = unp_ino++;
 230:         ((struct stat *) m)->st_ino = unp->unp_ino;
 231:         return (0);
 232: 
 233:     case PRU_RCVOOB:
 234:         return (EOPNOTSUPP);
 235: 
 236:     case PRU_SENDOOB:
 237:         error = EOPNOTSUPP;
 238:         break;
 239: 
 240:     case PRU_SOCKADDR:
 241:         break;
 242: 
 243:     case PRU_PEERADDR:
 244:         if (unp->unp_conn && unp->unp_conn->unp_addr) {
 245:             nam->m_len = unp->unp_conn->unp_addr->m_len;
 246:             bcopy(mtod(unp->unp_conn->unp_addr, caddr_t),
 247:                 mtod(m, caddr_t), (unsigned)m->m_len);
 248:         }
 249:         break;
 250: 
 251:     case PRU_SLOWTIMO:
 252:         break;
 253: 
 254:     default:
 255:         panic("piusrreq");
 256:     }
 257: release:
 258:     if (m)
 259:         m_freem(m);
 260:     return (error);
 261: }
 262: 
 263: /*
 264:  * Both send and receive buffers are allocated PIPSIZ bytes of buffering
 265:  * for stream sockets, although the total for sender and receiver is
 266:  * actually only PIPSIZ.
 267:  * Datagram sockets really use the sendspace as the maximum datagram size,
 268:  * and don't really want to reserve the sendspace.  Their recvspace should
 269:  * be large enough for at least one max-size datagram plus address.
 270:  */
 271: #define PIPSIZ  4096
 272: int unpst_sendspace = PIPSIZ;
 273: int unpst_recvspace = PIPSIZ;
 274: int unpdg_sendspace = 2*1024;   /* really max datagram size */
 275: int unpdg_recvspace = 4*1024;
 276: 
 277: int unp_rights;         /* file descriptors in flight */
 278: 
 279: unp_attach(so)
 280:     struct socket *so;
 281: {
 282:     register struct mbuf *m;
 283:     register struct unpcb *unp;
 284:     int error;
 285: 
 286:     switch (so->so_type) {
 287: 
 288:     case SOCK_STREAM:
 289:         error = soreserve(so, unpst_sendspace, unpst_recvspace);
 290:         break;
 291: 
 292:     case SOCK_DGRAM:
 293:         error = soreserve(so, unpdg_sendspace, unpdg_recvspace);
 294:         break;
 295:     }
 296:     if (error)
 297:         return (error);
 298:     m = m_getclr(M_DONTWAIT, MT_PCB);
 299:     if (m == NULL)
 300:         return (ENOBUFS);
 301:     unp = mtod(m, struct unpcb *);
 302:     so->so_pcb = (caddr_t)unp;
 303:     unp->unp_socket = so;
 304:     return (0);
 305: }
 306: 
 307: unp_detach(unp)
 308:     register struct unpcb *unp;
 309: {
 310: 
 311:     if (unp->unp_inode) {
 312:         unp->unp_inode->i_socket = 0;
 313:         irele(unp->unp_inode);
 314:         unp->unp_inode = 0;
 315:     }
 316:     if (unp->unp_conn)
 317:         unp_disconnect(unp);
 318:     while (unp->unp_refs)
 319:         unp_drop(unp->unp_refs, ECONNRESET);
 320:     soisdisconnected(unp->unp_socket);
 321:     unp->unp_socket->so_pcb = 0;
 322:     m_freem(unp->unp_addr);
 323:     (void) m_free(dtom(unp));
 324:     if (unp_rights)
 325:         unp_gc();
 326: }
 327: 
 328: unp_bind(unp, nam)
 329:     struct unpcb *unp;
 330:     struct mbuf *nam;
 331: {
 332:     struct sockaddr_un *soun = mtod(nam, struct sockaddr_un *);
 333:     register struct inode *ip;
 334:     register struct nameidata *ndp = &u.u_nd;
 335:     int error;
 336: 
 337:     ndp->ni_dirp = soun->sun_path;
 338:     if (unp->unp_inode != NULL || nam->m_len == MLEN)
 339:         return (EINVAL);
 340:     *(mtod(nam, caddr_t) + nam->m_len) = 0;
 341: /* SHOULD BE ABLE TO ADOPT EXISTING AND wakeup() ALA FIFO's */
 342:     ndp->ni_nameiop = CREATE | FOLLOW;
 343:     ndp->ni_segflg = UIO_SYSSPACE;
 344:     ip = namei(ndp);
 345:     if (ip) {
 346:         iput(ip);
 347:         return (EADDRINUSE);
 348:     }
 349:     if (error = u.u_error) {
 350:         u.u_error = 0;          /* XXX */
 351:         return (error);
 352:     }
 353:     ip = maknode(IFSOCK | 0777, ndp);
 354:     if (ip == NULL) {
 355:         error = u.u_error;      /* XXX */
 356:         u.u_error = 0;          /* XXX */
 357:         return (error);
 358:     }
 359:     ip->i_socket = unp->unp_socket;
 360:     unp->unp_inode = ip;
 361:     unp->unp_addr = m_copy(nam, 0, (int)M_COPYALL);
 362:     iunlock(ip);            /* but keep reference */
 363:     return (0);
 364: }
 365: 
 366: unp_connect(so, nam)
 367:     struct socket *so;
 368:     struct mbuf *nam;
 369: {
 370:     register struct sockaddr_un *soun = mtod(nam, struct sockaddr_un *);
 371:     register struct inode *ip;
 372:     int error;
 373:     register struct socket *so2;
 374:     register struct nameidata *ndp = &u.u_nd;
 375: 
 376:     ndp->ni_dirp = soun->sun_path;
 377:     if (nam->m_len + (nam->m_off - MMINOFF) == MLEN)
 378:         return (EMSGSIZE);
 379:     *(mtod(nam, caddr_t) + nam->m_len) = 0;
 380:     ndp->ni_nameiop = LOOKUP | FOLLOW;
 381:     ndp->ni_segflg = UIO_SYSSPACE;
 382:     ip = namei(ndp);
 383:     if (ip == 0) {
 384:         error = u.u_error;
 385:         u.u_error = 0;
 386:         return (error);     /* XXX */
 387:     }
 388:     if (access(ip, IWRITE)) {
 389:         error = u.u_error;
 390:         u.u_error = 0;      /* XXX */
 391:         goto bad;
 392:     }
 393:     if ((ip->i_mode&IFMT) != IFSOCK) {
 394:         error = ENOTSOCK;
 395:         goto bad;
 396:     }
 397:     so2 = ip->i_socket;
 398:     if (so2 == 0) {
 399:         error = ECONNREFUSED;
 400:         goto bad;
 401:     }
 402:     if (so->so_type != so2->so_type) {
 403:         error = EPROTOTYPE;
 404:         goto bad;
 405:     }
 406:     if (so->so_proto->pr_flags & PR_CONNREQUIRED &&
 407:         ((so2->so_options&SO_ACCEPTCONN) == 0 ||
 408:          (so2 = sonewconn(so2)) == 0)) {
 409:         error = ECONNREFUSED;
 410:         goto bad;
 411:     }
 412:     error = unp_connect2(so, so2);
 413: bad:
 414:     iput(ip);
 415:     return (error);
 416: }
 417: 
 418: unp_connect2(so, so2)
 419:     register struct socket *so;
 420:     register struct socket *so2;
 421: {
 422:     register struct unpcb *unp = sotounpcb(so);
 423:     register struct unpcb *unp2;
 424: 
 425:     if (so2->so_type != so->so_type)
 426:         return (EPROTOTYPE);
 427:     unp2 = sotounpcb(so2);
 428:     unp->unp_conn = unp2;
 429:     switch (so->so_type) {
 430: 
 431:     case SOCK_DGRAM:
 432:         unp->unp_nextref = unp2->unp_refs;
 433:         unp2->unp_refs = unp;
 434:         soisconnected(so);
 435:         break;
 436: 
 437:     case SOCK_STREAM:
 438:         unp2->unp_conn = unp;
 439:         soisconnected(so2);
 440:         soisconnected(so);
 441:         break;
 442: 
 443:     default:
 444:         panic("unp_connect2");
 445:     }
 446:     return (0);
 447: }
 448: 
 449: unp_disconnect(unp)
 450:     struct unpcb *unp;
 451: {
 452:     register struct unpcb *unp2 = unp->unp_conn;
 453: 
 454:     if (unp2 == 0)
 455:         return;
 456:     unp->unp_conn = 0;
 457:     switch (unp->unp_socket->so_type) {
 458: 
 459:     case SOCK_DGRAM:
 460:         if (unp2->unp_refs == unp)
 461:             unp2->unp_refs = unp->unp_nextref;
 462:         else {
 463:             unp2 = unp2->unp_refs;
 464:             for (;;) {
 465:                 if (unp2 == 0)
 466:                     panic("unp_disconnect");
 467:                 if (unp2->unp_nextref == unp)
 468:                     break;
 469:                 unp2 = unp2->unp_nextref;
 470:             }
 471:             unp2->unp_nextref = unp->unp_nextref;
 472:         }
 473:         unp->unp_nextref = 0;
 474:         unp->unp_socket->so_state &= ~SS_ISCONNECTED;
 475:         break;
 476: 
 477:     case SOCK_STREAM:
 478:         soisdisconnected(unp->unp_socket);
 479:         unp2->unp_conn = 0;
 480:         soisdisconnected(unp2->unp_socket);
 481:         break;
 482:     }
 483: }
 484: 
 485: #ifdef notdef
 486: unp_abort(unp)
 487:     struct unpcb *unp;
 488: {
 489: 
 490:     unp_detach(unp);
 491: }
 492: #endif
 493: 
 494: /*ARGSUSED*/
 495: unp_usrclosed(unp)
 496:     struct unpcb *unp;
 497: {
 498: 
 499: }
 500: 
 501: unp_drop(unp, errno)
 502:     struct unpcb *unp;
 503:     int errno;
 504: {
 505:     struct socket *so = unp->unp_socket;
 506: 
 507:     so->so_error = errno;
 508:     unp_disconnect(unp);
 509:     if (so->so_head) {
 510:         so->so_pcb = (caddr_t) 0;
 511:         m_freem(unp->unp_addr);
 512:         (void) m_free(dtom(unp));
 513:         sofree(so);
 514:     }
 515: }
 516: 
 517: #ifdef notdef
 518: unp_drain()
 519: {
 520: 
 521: }
 522: #endif
 523: 
 524: unp_externalize(rights)
 525:     struct mbuf *rights;
 526: {
 527:     int newfds = rights->m_len / sizeof (int);
 528:     register int i;
 529:     register struct file **rp = mtod(rights, struct file **);
 530:     register struct file *fp;
 531:     int f;
 532: 
 533:     if (newfds > ufavail()) {
 534:         for (i = 0; i < newfds; i++) {
 535:             fp = *rp;
 536:             unp_discard(fp);
 537:             *rp++ = 0;
 538:         }
 539:         return (EMSGSIZE);
 540:     }
 541:     for (i = 0; i < newfds; i++) {
 542:         f = ufalloc(0);
 543:         if (f < 0)
 544:             panic("unp_externalize");
 545:         fp = *rp;
 546:         u.u_ofile[f] = fp;
 547:         fp->f_msgcount--;
 548:         unp_rights--;
 549:         *(int *)rp++ = f;
 550:     }
 551:     return (0);
 552: }
 553: 
 554: unp_internalize(rights)
 555:     struct mbuf *rights;
 556: {
 557:     register struct file **rp;
 558:     int oldfds = rights->m_len / sizeof (int);
 559:     register int i;
 560:     register struct file *fp;
 561: 
 562:     rp = mtod(rights, struct file **);
 563:     for (i = 0; i < oldfds; i++)
 564:         if (getf(*(int *)rp++) == 0)
 565:             return (EBADF);
 566:     rp = mtod(rights, struct file **);
 567:     for (i = 0; i < oldfds; i++) {
 568:         fp = getf(*(int *)rp);
 569:         *rp++ = fp;
 570:         fp->f_count++;
 571:         fp->f_msgcount++;
 572:         unp_rights++;
 573:     }
 574:     return (0);
 575: }
 576: 
 577: int unp_defer, unp_gcing;
 578: int unp_mark();
 579: extern  struct domain unixdomain;
 580: 
 581: unp_gc()
 582: {
 583:     register struct file *fp;
 584:     register struct socket *so;
 585: 
 586:     if (unp_gcing)
 587:         return;
 588:     unp_gcing = 1;
 589: restart:
 590:     unp_defer = 0;
 591:     for (fp = file; fp < fileNFILE; fp++)
 592:         fp->f_flag &= ~(FMARK|FDEFER);
 593:     do {
 594:         for (fp = file; fp < fileNFILE; fp++) {
 595:             if (fp->f_count == 0)
 596:                 continue;
 597:             if (fp->f_flag & FDEFER) {
 598:                 fp->f_flag &= ~FDEFER;
 599:                 unp_defer--;
 600:             } else {
 601:                 if (fp->f_flag & FMARK)
 602:                     continue;
 603:                 if (fp->f_count == fp->f_msgcount)
 604:                     continue;
 605:                 fp->f_flag |= FMARK;
 606:             }
 607:             if (fp->f_type != DTYPE_SOCKET)
 608:                 continue;
 609:             so = (struct socket *)fp->f_data;
 610:             if (so->so_proto->pr_domain != &unixdomain ||
 611:                 (so->so_proto->pr_flags&PR_RIGHTS) == 0)
 612:                 continue;
 613:             if (so->so_rcv.sb_flags & SB_LOCK) {
 614:                 sbwait(&so->so_rcv);
 615:                 goto restart;
 616:             }
 617:             unp_scan(so->so_rcv.sb_mb, unp_mark);
 618:         }
 619:     } while (unp_defer);
 620:     for (fp = file; fp < fileNFILE; fp++) {
 621:         if (fp->f_count == 0)
 622:             continue;
 623:         if (fp->f_count == fp->f_msgcount && (fp->f_flag & FMARK) == 0)
 624:             while (fp->f_msgcount)
 625:                 unp_discard(fp);
 626:     }
 627:     unp_gcing = 0;
 628: }
 629: 
 630: unp_dispose(m)
 631:     struct mbuf *m;
 632: {
 633:     int unp_discard();
 634: 
 635:     if (m)
 636:         unp_scan(m, unp_discard);
 637: }
 638: 
 639: unp_scan(m0, op)
 640:     register struct mbuf *m0;
 641:     int (*op)();
 642: {
 643:     register struct mbuf *m;
 644:     register struct file **rp;
 645:     register int i;
 646:     int qfds;
 647: 
 648:     while (m0) {
 649:         for (m = m0; m; m = m->m_next)
 650:             if (m->m_type == MT_RIGHTS && m->m_len) {
 651:                 qfds = m->m_len / sizeof (struct file *);
 652:                 rp = mtod(m, struct file **);
 653:                 for (i = 0; i < qfds; i++)
 654:                     (*op)(*rp++);
 655:                 break;      /* XXX, but saves time */
 656:             }
 657:         m0 = m0->m_act;
 658:     }
 659: }
 660: 
 661: unp_mark(fp)
 662:     struct file *fp;
 663: {
 664: 
 665:     if (fp->f_flag & FMARK)
 666:         return;
 667:     unp_defer++;
 668:     fp->f_flag |= (FMARK|FDEFER);
 669: }
 670: 
 671: unp_discard(fp)
 672:     struct file *fp;
 673: {
 674: 
 675:     fp->f_msgcount--;
 676:     unp_rights--;
 677:     closef(fp);
 678: }

Defined functions

uipc_usrreq defined in line 35; used 3 times
unp_abort defined in line 486; never used
unp_attach defined in line 279; used 1 times
  • in line 61
unp_bind defined in line 328; used 1 times
  • in line 69
unp_connect defined in line 366; used 2 times
unp_connect2 defined in line 418; used 3 times
unp_detach defined in line 307; used 2 times
unp_discard defined in line 671; used 5 times
unp_disconnect defined in line 449; used 4 times
unp_dispose defined in line 630; used 2 times
unp_drain defined in line 518; never used
unp_drop defined in line 501; used 2 times
unp_externalize defined in line 524; used 2 times
unp_gc defined in line 581; used 1 times
unp_internalize defined in line 554; used 1 times
unp_mark defined in line 661; used 2 times
unp_scan defined in line 639; used 2 times
unp_usrclosed defined in line 495; used 1 times

Defined variables

sun_noname defined in line 31; used 3 times
unp_defer defined in line 577; used 4 times
unp_gcing defined in line 577; used 3 times
unp_ino defined in line 32; used 4 times
unp_rights defined in line 277; used 4 times
unpdg_recvspace defined in line 275; used 1 times
unpdg_sendspace defined in line 274; used 1 times
unpst_recvspace defined in line 273; used 1 times
unpst_sendspace defined in line 272; used 1 times

Defined macros

PIPSIZ defined in line 271; used 2 times
rcv defined in line 183; used 12 times
snd defined in line 184; used 6 times
Last modified: 1986-06-05
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 1872
Valid CSS Valid XHTML 1.0 Strict