|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. ! 3: * ! 4: * @APPLE_LICENSE_HEADER_START@ ! 5: * ! 6: * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights ! 7: * Reserved. This file contains Original Code and/or Modifications of ! 8: * Original Code as defined in and that are subject to the Apple Public ! 9: * Source License Version 1.1 (the "License"). You may not use this file ! 10: * except in compliance with the License. Please obtain a copy of the ! 11: * License at http://www.apple.com/publicsource and read it before using ! 12: * this file. ! 13: * ! 14: * The Original Code and all software distributed under the License are ! 15: * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER ! 16: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, ! 17: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, ! 18: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the ! 19: * License for the specific language governing rights and limitations ! 20: * under the License. ! 21: * ! 22: * @APPLE_LICENSE_HEADER_END@ ! 23: */ ! 24: ! 25: /* ! 26: * Copyright (c) 1988, 1989, 1997, 1998 Apple Computer, Inc. ! 27: * ! 28: * The information contained herein is subject to change without ! 29: * notice and should not be construed as a commitment by Apple ! 30: * Computer, Inc. Apple Computer, Inc. assumes no responsibility ! 31: * for any errors that may appear. ! 32: * ! 33: * Confidential and Proprietary to Apple Computer, Inc. ! 34: */ ! 35: ! 36: ! 37: #include <sysglue.h> ! 38: ! 39: /* reaching for DDP and NBP headers in the datagram */ ! 40: #define DATA_DDP(mp) ((at_ddp_t *)(gbuf_rptr(mp))) ! 41: #define DATA_NBP(mp) ((at_nbp_t *)((DATA_DDP(mp))->data)) ! 42: ! 43: /* Get to the nve_entry_t part ofthe buffer */ ! 44: #define NVE_ENTRY(mp) (nve_entry_t *)(gbuf_rptr(mp)) ! 45: ! 46: #include <string.h> ! 47: #include <at/appletalk.h> ! 48: #include <at/ddp.h> ! 49: #include <at/nbp.h> ! 50: #include <at/zip.h> ! 51: #include <rtmp.h> ! 52: #include <lap.h> ! 53: #include <at/elap.h> /* router */ ! 54: #include <at/at_lap.h> ! 55: #include <routing_tables.h> /* router */ ! 56: #include <at_elap.h> /* router */ ! 57: #include <at_ddp.h> /* for FROM_US */ ! 58: #include <at_snmp.h> ! 59: ! 60: #ifndef MIN ! 61: #define MIN(a,b) ((a)>(b)?(b):(a)) ! 62: #endif ! 63: ! 64: #include "nbp.h" ! 65: ! 66: #define errno nbperrno ! 67: #define NBP_DEBUG 0 ! 68: ! 69: /* externs */ ! 70: extern at_state_t at_state, *at_statep; ! 71: extern at_if_t *ifID_table[]; ! 72: ! 73: ! 74: /* statics */ ! 75: static nve_entry_t name_registry = {&name_registry, &name_registry}; ! 76: static at_nvestr_t this_zone; ! 77: static int Extended_net; ! 78: static int errno; ! 79: static gbuf_t *lzones=0; /* head of local zones list */ ! 80: static int lzonecnt=0; /* # zones stored in lzones */ ! 81: static u_int hzonehash=0; /* hash val of home zone */ ! 82: atlock_t nve_lock; ! 83: static int nve_lock_pri; ! 84: ! 85: static int nbp_lkup_reply(nbp_req_t *, nve_entry_t *); ! 86: static int nbp_strcmp(at_nvestr_t *, at_nvestr_t *, u_char); ! 87: static int nbp_setup_resp(nbp_req_t *, int); ! 88: static int nbp_send_resp(nbp_req_t *); ! 89: static int nbp_insert_entry(nve_entry_t *); ! 90: static int nbp_enum_gen(nve_entry_t *); ! 91: ! 92: /* macros */ ! 93: #define NBP2ENTITY(nbp) &nbp->tuple[0].en_u.en_se.entity ! 94: #define NVE_LOCK nve_lock ! 95: ! 96: /* prototypes */ ! 97: at_nvestr_t *getRTRLocalZone(if_zone_t *); ! 98: at_nvestr_t *getSPLocalZone(int); ! 99: at_nvestr_t *getLocalZone(int); ! 100: int nbp_add_multicast( at_nvestr_t *, at_if_t *); ! 101: int isZoneLocal(at_nvestr_t *); ! 102: u_int nbp_strhash (at_nvestr_t *); ! 103: ! 104: int sethzonehash(elapp) ! 105: elap_specifics_t *elapp; ! 106: { ! 107: if (elapp->cfg.zonename.len) { ! 108: hzonehash = nbp_strhash(&elapp->cfg.zonename); ! 109: } ! 110: } ! 111: ! 112: ! 113: int nbp_init () ! 114: { ! 115: void nbp_input(); ! 116: at_ddp_cfg_t ddp_cfg; ! 117: ! 118: /* Initialize Extended_net */ ! 119: ddp_get_cfg (&ddp_cfg, 0); ! 120: if (ddp_cfg.flags & AT_IFF_LOCALTALK) ! 121: Extended_net = 0; ! 122: else ! 123: Extended_net = 1; ! 124: ! 125: ATLOCKINIT(nve_lock); ! 126: return ((int)nbp_input); ! 127: } ! 128: ! 129: void nbp_shutdown() ! 130: { ! 131: /* delete all NVE's and release buffers */ ! 132: register nve_entry_t *nve_entry, *next_nve; ! 133: extern void nbp_delete_entry(); ! 134: ! 135: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 136: for (nve_entry = name_registry.fwd, next_nve = nve_entry->fwd; ! 137: nve_entry != &name_registry; ! 138: nve_entry = next_nve, next_nve = nve_entry->fwd) { ! 139: nbp_delete_entry(nve_entry); ! 140: } ! 141: ATENABLE(nve_lock_pri,NVE_LOCK); ! 142: ! 143: if (lzones) { ! 144: gbuf_freem(lzones); ! 145: lzones = NULL; ! 146: } ! 147: } /* nbp_shutdown */ ! 148: ! 149: void nbp_input (m, ifID) ! 150: register gbuf_t *m; ! 151: register at_if_t *ifID; ! 152: { ! 153: void zip_handler(), nbp_handler(), rtmp_handler(); ! 154: ! 155: switch (gbuf_type(m)) { ! 156: ! 157: /* *** fix this later: this code may no longer be needed *** */ ! 158: case MSG_ERROR : ! 159: if (*gbuf_rptr(m) == ESHUTDOWN) { ! 160: /* DDP shutting down, clean up the name registry */ ! 161: nbp_shutdown(); ! 162: } ! 163: break; ! 164: /* *** end code that may no longer be needed *** */ ! 165: ! 166: case MSG_DATA : ! 167: switch (((at_ddp_t *)(DATA_DDP(m)))->type) { ! 168: case NBP_DDP_TYPE : ! 169: nbp_handler (m, ifID); ! 170: return; ! 171: case ZIP_DDP_TYPE : ! 172: dPrintf(D_M_NBP_LOW, D_L_WARNING, ! 173: ("nbp_input: calling zip_handler\n")); ! 174: zip_handler (m); ! 175: break; ! 176: case RTMP_DDP_TYPE : /* applicable only in case of LocalTalk */ ! 177: dPrintf(D_M_NBP_LOW,D_L_WARNING, ! 178: ("nbp_input: calling rtmp_handler\n")); ! 179: rtmp_handler (m); ! 180: break; ! 181: } ! 182: break; ! 183: default : ! 184: dPrintf(D_M_NBP, D_L_WARNING, ! 185: ("nbp_input: default_handler type=%d\n", gbuf_type(m))); ! 186: break; ! 187: } ! 188: gbuf_freem(m); ! 189: } ! 190: ! 191: static ! 192: u_char *nbp2zone(nbp, maxp) ! 193: at_nbp_t *nbp; ! 194: u_char *maxp; ! 195: { ! 196: ! 197: u_char *p; ! 198: ! 199: p = (u_char*)NBP2ENTITY(nbp); /* p -> object */ ! 200: if (p >= maxp) return NULL; ! 201: p += (*p +1); /* p -> type */ ! 202: if (p >= maxp) return NULL; ! 203: p += (*p +1); /* p -> zone */ ! 204: if (p >= maxp) return NULL; ! 205: if ((p + *p) >= maxp) return NULL; ! 206: return(p); ! 207: } ! 208: ! 209: static void nbp_handler (m, ifID) ! 210: register gbuf_t *m; ! 211: register at_if_t *ifID; ! 212: ! 213: { ! 214: register at_nbp_t *nbp = DATA_NBP(m); ! 215: register nve_entry_t *nve_entry, *next_nve; ! 216: register RT_entry *rt; ! 217: register int ddpSent = FALSE; /* true if we re-sent this pkt (don't free) */ ! 218: struct etalk_addr mcastAddr; ! 219: nbp_req_t nbp_req; ! 220: nve_entry_t *nbp_search_nve(); ! 221: void nbp_delete_entry(); ! 222: at_ddp_t *ddp; ! 223: u_char *p; ! 224: ! 225: /* Some initializations */ ! 226: nbp_req.response = NULL; ! 227: nbp_req.request = m; ! 228: nbp_req.space_unused = nbp_req.flags = 0; ! 229: ! 230: ! 231: dPrintf(D_M_NBP_LOW, D_L_USR1, ! 232: ("nbp_handler control:%d tuplecount:%d id:%d\n", ! 233: nbp->control, nbp->tuple_count, nbp->at_nbp_id)); ! 234: switch (nbp->control) { ! 235: case NBP_LKUP : ! 236: { ! 237: at_net_al dst_net; ! 238: ddp = DATA_DDP(m); ! 239: dst_net = NET_VALUE(ddp->dst_net); ! 240: dPrintf(D_M_NBP_LOW, D_L_USR2, (" LKUP %s\n", ! 241: ifID != ifID_table[0] ? "non-home" : "home")); ! 242: if ( ROUTING_MODE && (NET_VALUE(ddp->dst_net) != 0) ! 243: && ((dst_net < ifID->ifThisCableStart) ! 244: || (dst_net > ifID->ifThisCableEnd)) ) { ! 245: routing_needed(m, ifID, TRUE); ! 246: ddpSent = TRUE; ! 247: break; ! 248: } ! 249: } ! 250: ! 251: if (nbp_validate_n_hash (&nbp_req, TRUE, FALSE) == 0) { ! 252: nbp_req.func = nbp_lkup_reply; ! 253: (void) nbp_search_nve(&nbp_req, ifID); ! 254: if (nbp_req.response) { ! 255: nbp_send_resp(&nbp_req); ! 256: } ! 257: } ! 258: #ifdef NBP_DEBUG ! 259: { ! 260: char zone[35],object[35],type[35]; ! 261: strncpy(zone,nbp_req.nve.zone.str, nbp_req.nve.zone.len); ! 262: strncpy(object,nbp_req.nve.object.str, nbp_req.nve.object.len); ! 263: strncpy(type,nbp_req.nve.type.str, nbp_req.nve.type.len); ! 264: object[nbp_req.nve.object.len] = '\0'; ! 265: zone[nbp_req.nve.zone.len] = '\0'; ! 266: type[nbp_req.nve.type.len] = '\0'; ! 267: if (ifID != ifID_table[0]) ! 268: dPrintf(D_M_NBP_LOW,D_L_USR2, ! 269: ("nbp_LKUP for:%s:%s@%s", object, type, zone)); ! 270: } ! 271: #endif /* NBP_DEBUG */ ! 272: ! 273: break; ! 274: case NBP_REGISTER : ! 275: /* Whatever the outcome of this operation, we need to return ! 276: * a response to the requester; so set up the response buffers ! 277: */ ! 278: #ifdef NBP_DEBUG ! 279: { ! 280: at_net_al dst_net,src_net; ! 281: int src_node, dst_node, src_skt, dst_skt; ! 282: ddp = DATA_DDP(m); ! 283: dst_net = NET_VALUE(ddp->dst_net); ! 284: src_net = NET_VALUE(ddp->src_net); ! 285: src_node = ddp->src_node; ! 286: dst_node = ddp->dst_node; ! 287: src_skt = ddp->src_socket; ! 288: dst_skt = ddp->dst_socket; ! 289: dPrintf(D_M_NBP_LOW,D_L_USR2, ! 290: ("nbp reg ddp src:%d.%d.%d ",src_net,src_node,src_skt)); ! 291: dPrintf(D_M_NBP_LOW,D_L_USR2, ! 292: ("dst: %d.%d.%d\n",dst_net,dst_node,dst_skt)); ! 293: } ! 294: #endif /* NBP_DEBUG */ ! 295: (void) nbp_setup_resp(&nbp_req, 0); ! 296: if (nbp_validate_n_hash (&nbp_req, FALSE, TRUE) != 0) { ! 297: /* bad tuple... send an error message */ ! 298: goto bad; ! 299: } ! 300: ! 301: ! 302: ! 303: nbp_req.func = NULL; ! 304: if ((nve_entry = nbp_search_nve(&nbp_req, NULL)) != NULL) { ! 305: /* We have an entry for the same name in the registry. ! 306: * This is either a duplicate request (client ! 307: * retransmission) or another request. If this is a ! 308: * retransmitted request (ie it's for the same socket), ! 309: * then return ok; else return error. ! 310: */ ! 311: if (*(int *)&nve_entry->address == ! 312: *(int *)&nbp_req.nve.address) { ! 313: /* Send ok response */ ! 314: DATA_NBP(nbp_req.response)->tuple_count = 0; ! 315: nbp_send_resp(&nbp_req); ! 316: break; ! 317: } else { ! 318: errno = EADDRNOTAVAIL; ! 319: goto bad; ! 320: } ! 321: } else { ! 322: /* Normal case; no tuple found for this name, so insert ! 323: * this tuple in the registry and return ok response. ! 324: */ ! 325: #ifdef NBP_DEBUG ! 326: { ! 327: char str[35]; ! 328: char object[35]; ! 329: strncpy(str,nbp_req.nve.zone.str,nbp_req.nve.zone.len); ! 330: str[nbp_req.nve.zone.len] = '\0'; ! 331: strncpy(object,nbp_req.nve.object.str,nbp_req.nve.object.len); ! 332: object[nbp_req.nve.object.len] = '\0'; ! 333: dPrintf(D_M_NBP_LOW, D_L_USR4, ! 334: ("nbp_register for zone %s (len:%d) obj:%s (len:%d)\n", ! 335: str, nbp_req.nve.zone.len, object, ! 336: nbp_req.nve.object.len)); ! 337: } ! 338: #endif /* NBP_DEBUG */ ! 339: ! 340: if ((nbp_enum_gen(&nbp_req.nve) != 0) || ! 341: nbp_insert_entry(&nbp_req.nve) != 0) ! 342: goto bad; ! 343: else { ! 344: /* Send okay response */ ! 345: DATA_NBP(nbp_req.response)->tuple_count = 0; ! 346: nbp_send_resp(&nbp_req); ! 347: break; ! 348: } ! 349: } ! 350: bad: ! 351: DATA_NBP(nbp_req.response)->tuple_count = errno; ! 352: nbp_send_resp(&nbp_req); ! 353: break; ! 354: case NBP_DELETE : ! 355: /* Whatever the outcome of this operation, we need to return ! 356: * a response to the requester; so set up the response buffers ! 357: */ ! 358: (void) nbp_setup_resp(&nbp_req, 0); ! 359: if (nbp_validate_n_hash (&nbp_req, FALSE, TRUE) != 0) { ! 360: /* bad tuple... send an error message */ ! 361: DATA_NBP(nbp_req.response)->tuple_count = errno; ! 362: nbp_send_resp(&nbp_req); ! 363: break; ! 364: } ! 365: ! 366: nbp_req.func = NULL; ! 367: if (MULTIHOME_MODE && ! 368: nbp_req.nve.zone.len == 1 && ! 369: nbp_req.nve.zone.str[0] == '*' ! 370: ) { /* if mhome & *, remove nve from all default zones */ ! 371: ! 372: int found = FALSE; /* if any found & deleted */ ! 373: at_if_t **ifID; ! 374: ! 375: for (ifID=ifID_table; *ifID; ifID++) { ! 376: nbp_req.nve.zone = (*ifID)->ifZoneName; ! 377: if ((nve_entry = nbp_search_nve(&nbp_req, NULL)) == NULL) ! 378: continue; ! 379: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 380: nbp_delete_entry(nve_entry); ! 381: ATENABLE(nve_lock_pri,NVE_LOCK); ! 382: found = TRUE; ! 383: } ! 384: nbp_req.nve.zone.len = 1; ! 385: nbp_req.nve.zone.str[0] = '*'; ! 386: if (found) ! 387: DATA_NBP(nbp_req.response)->tuple_count = 0; ! 388: else ! 389: DATA_NBP(nbp_req.response)->tuple_count = EADDRNOTAVAIL; ! 390: nbp_send_resp(&nbp_req); ! 391: break; ! 392: } else { ! 393: if ((nve_entry = nbp_search_nve(&nbp_req, NULL)) == NULL) { ! 394: /* Can't find the tuple we're looking for, send error*/ ! 395: DATA_NBP(nbp_req.response)->tuple_count = EADDRNOTAVAIL; ! 396: nbp_send_resp(&nbp_req); ! 397: } else { ! 398: /* Normal case; tuple found for this name, so delete ! 399: * the entry from the registry and return ok response. ! 400: */ ! 401: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 402: nbp_delete_entry(nve_entry); ! 403: ATENABLE(nve_lock_pri,NVE_LOCK); ! 404: DATA_NBP(nbp_req.response)->tuple_count = 0; ! 405: nbp_send_resp(&nbp_req); ! 406: } ! 407: } ! 408: break; ! 409: case NBP_CLOSE_NOTE : ! 410: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 411: for (nve_entry = name_registry.fwd, next_nve = nve_entry->fwd; ! 412: nve_entry != &name_registry; ! 413: nve_entry = next_nve, next_nve = nve_entry->fwd) { ! 414: if ( DATA_DDP(m)->src_socket == nve_entry->address.socket && ! 415: (*(int *)gbuf_wptr(m) == nve_entry->pid) && ! 416: ot_ddp_check_socket(nve_entry->address.socket, ! 417: nve_entry->pid) < 2) { ! 418: nbp_delete_entry(nve_entry); ! 419: } ! 420: } ! 421: ATENABLE(nve_lock_pri,NVE_LOCK); ! 422: break; ! 423: case NBP_FWDRQ: ! 424: { ! 425: register int zhome=0; /* true if home zone == destination zone */ ! 426: register int zno, i; ! 427: register gbuf_t *m2; ! 428: register error_found =0; ! 429: register at_if_t *ifIDorig; ! 430: ! 431: if (MULTIHOME_MODE || !ROUTING_MODE) /* for routers only! */ ! 432: break; ! 433: ! 434: ddp = DATA_DDP(m); ! 435: ! 436: ifIDorig = ifID; ! 437: ifID= NULL; ! 438: for (i = 0 ; i < RT_MAXENTRY; i++) { ! 439: rt = &RT_table[i]; ! 440: if ((rt->EntryState & RTE_STATE_PERMANENT) && ! 441: NET_VALUE(ddp->dst_net) >= rt->NetStart && ! 442: NET_VALUE(ddp->dst_net) <= rt->NetStop ! 443: ) { ! 444: /* sanity check */ ! 445: if (rt->NetPort >= IF_TOTAL_MAX) { ! 446: dPrintf(D_M_NBP,D_L_ERROR, ! 447: ("nbp_handler:FWDREQ: bad port# from RT_table\n")); ! 448: error_found = TRUE; ! 449: break; ! 450: } ! 451: ifID = ifID_table[rt->NetPort]; ! 452: if (!ifID || !IFID_VALID(ifID)) { ! 453: dPrintf(D_M_NBP,D_L_ERROR, ! 454: ("nbp_handler:FWDREQ: ifID %s\n", ! 455: !ifID ? "not found" : "invalid")); ! 456: error_found = TRUE; ! 457: break; ! 458: } ! 459: if (ifID->ifState == LAP_OFFLINE) { ! 460: dPrintf(D_M_NBP,D_L_ERROR, ! 461: ("nbp_handler:FWDREQ: ifID offline (port %d)\n", ! 462: rt->NetPort)); ! 463: error_found = TRUE; ! 464: break; ! 465: } ! 466: break; ! 467: } ! 468: } ! 469: if (error_found) /* the port is not correct */ ! 470: break; ! 471: ! 472: if (!ifID) { /* this packet is not for us, let the routing engine handle it */ ! 473: routing_needed(m, ifIDorig, TRUE); ! 474: ddpSent= TRUE; ! 475: break; ! 476: } ! 477: ! 478: /* ! 479: * At this point, we have a valid Forward request for one of our ! 480: * direclty connected port. Convert it to a NBP Lookup ! 481: */ ! 482: ! 483: nbp->control = NBP_LKUP; ! 484: NET_ASSIGN(ddp->dst_net, 0); ! 485: ddp->dst_node = 255; ! 486: ! 487: ! 488: /*### LD 01/18/94 Check if the dest is also the home zone. */ ! 489: ! 490: p = nbp2zone(nbp, gbuf_wptr(m)); ! 491: if ((p == NULL) || !(zno = zt_find_zname(p))) { ! 492: dPrintf(D_M_NBP,D_L_WARNING, ! 493: ("nbp_handler: FWDRQ:zone not found\n")); ! 494: break; ! 495: } ! 496: if (isZoneLocal((at_nvestr_t*)p)) ! 497: zhome = TRUE; /* one of our ports is in destination zone */ ! 498: if (!zt_get_zmcast(ifID, p, &mcastAddr)) { ! 499: dPrintf(D_M_NBP,D_L_ERROR, ! 500: ("nbp_handler: FDWREQ:zt_get_zmcast error\n")); ! 501: break; ! 502: } ! 503: ! 504: ! 505: if (zhome) { /*### LD 01/18/95 In case our home is here, call back nbp */ ! 506: ! 507: if (!(m2 = (gbuf_t *)gbuf_copym((gbuf_t *)m))) { ! 508: dPrintf(D_M_NBP,D_L_ERROR, ! 509: ("nbp_handler: FWDRQ:gbuf_copym failed\n")); ! 510: break; ! 511: } ! 512: ! 513: ddp = DATA_DDP(m2); ! 514: nbp = DATA_NBP(m2); ! 515: nbp->control = NBP_LKUP; ! 516: NET_ASSIGN(ddp->dst_net, 0); ! 517: ddp->dst_node = 255; ! 518: dPrintf(D_M_NBP,D_L_INFO, ! 519: ("nbp_handler: FWDRQ:loop back for us\n")); ! 520: nbp_handler(m2, ifID_table[IFID_HOME]); ! 521: } ! 522: ! 523: if ((ifID->ifType == IFTYPE_FDDITALK) ! 524: || (ifID->ifType == IFTYPE_TOKENTALK)) ! 525: ddp_bit_reverse(&mcastAddr); ! 526: ddp_router_output(m, ifID, ET_ADDR,NULL,NULL, &mcastAddr); ! 527: ddpSent = TRUE; ! 528: } ! 529: break; ! 530: ! 531: case NBP_BRRQ: ! 532: { ! 533: register int zno; /* zone table entry numb */ ! 534: register int ztind; /* zone bitmap index into RT_entry */ ! 535: register int ztbit; /* zone bit to check within above index */ ! 536: register int zhome=0; /* true if home zone == destination zone */ ! 537: register int i; ! 538: register gbuf_t *m2, *m3; ! 539: register int fromUs = FALSE; ! 540: register at_socket ourSkt; /* originating skt */ ! 541: ! 542: ddp = DATA_DDP(m); /* for router & MH local only */ ! 543: if ((MULTIHOME_MODE && !FROM_US(ddp))&& !ROUTING_MODE) { ! 544: dPrintf(D_M_NBP,D_L_USR2, ! 545: ("nbp_handler: BRREQ:non router or MH local\n")); ! 546: ! 547: break; ! 548: } ! 549: p = nbp2zone(nbp, gbuf_wptr(m)); ! 550: if ((p == NULL) || !(zno = zt_find_zname(p))) { ! 551: break; ! 552: } ! 553: if (MULTIHOME_MODE && ifID->ifRouterState == NO_ROUTER) { ! 554: ((at_nvestr_t*)p)->len = 1; ! 555: ((at_nvestr_t*)p)->str[0] = '*'; ! 556: } ! 557: if (isZoneLocal((at_nvestr_t*)p)) { ! 558: zhome = TRUE; /* one of our ports is in destination zone */ ! 559: } ! 560: if (FROM_US(ddp)){ /* save, before we munge it */ ! 561: fromUs = TRUE; ! 562: ourSkt = ddp->src_socket; ! 563: dPrintf(D_M_NBP,D_L_USR2, ! 564: ("nbp_handler:BRRQ from us net:%d\n", ! 565: (int)NET_VALUE(ddp->src_net))); ! 566: } ! 567: /* from ZT_CLR_ZMAP */ ! 568: i = zno - 1; ! 569: ztind = i >> 3; ! 570: ztbit = 0x80 >> (i % 8); ! 571: for (i=0,rt=RT_table; i<RT_MAXENTRY; i++,rt++) { ! 572: if (!(rt->ZoneBitMap[ztind] & ztbit)) /* if zone not in route, skip*/ ! 573: continue; ! 574: /* dPrintf(D_M_NBP, D_L_USR3, ! 575: ("nbp_handler: BRREQ: port:%d, entry %d\n", ! 576: rt->NetPort, i)); ! 577: */ ! 578: ! 579: ifID = ifID_table[rt->NetPort]; ! 580: if (!ifID || !IFID_VALID(ifID)) { ! 581: dPrintf(D_M_NBP, D_L_ERROR, ! 582: ("nbp_handler:BRRQ: ifID %s\n", ! 583: !ifID ? "not found" : "invalid")); ! 584: break; ! 585: } ! 586: ! 587: ddp = DATA_DDP(m); ! 588: ddp->src_node = ifID->ifThisNode.atalk_node; ! 589: NET_NET(ddp->src_net, ifID->ifThisNode.atalk_net); ! 590: ddp->src_socket = NBP_SOCKET; ! 591: if (!(m2 = (gbuf_t *)gbuf_copym((gbuf_t *)m))) { ! 592: dPrintf(D_M_NBP,D_L_ERROR, ! 593: ("nbp_handler: BRREQ:gbuf_copym failed\n")); ! 594: break; ! 595: } ! 596: ! 597: ddp = DATA_DDP(m2); ! 598: nbp = DATA_NBP(m2); ! 599: /* nbp->tuple[0].enu_addr.socket = NBP_SOCKET; */ ! 600: if (MULTIHOME_MODE && fromUs ) { ! 601: /* set the return address of the lookup to that of the ! 602: interface it's going out on so that replies come back ! 603: on that net */ ! 604: dPrintf(D_M_NBP,D_L_USR3, ! 605: ("nbp_handler: BRREQ: src changed to %d.%d.%d\n", ! 606: NET_VALUE(ifID->ifThisNode.atalk_net), ! 607: ifID->ifThisNode.atalk_node, ourSkt)); ! 608: NET_NET(nbp->tuple[0].enu_addr.net,ifID->ifThisNode.atalk_net); ! 609: nbp->tuple[0].enu_addr.node = ifID->ifThisNode.atalk_node; ! 610: nbp->tuple[0].enu_addr.socket = ourSkt; ! 611: ddp->src_socket = NBP_SOCKET; ! 612: } ! 613: else ! 614: dPrintf(D_M_NBP, D_L_USR3, ! 615: ("nbp_handler: BRREQ: not from us\n")); ! 616: ! 617: dPrintf(D_M_NBP, D_L_USR3, ! 618: ("nbp_handler dist:%d\n", rt->NetDist)); ! 619: if (rt->NetDist == 0) { /* if direct connect, *we* do the LKUP */ ! 620: nbp->control = NBP_LKUP; ! 621: NET_ASSIGN(ddp->dst_net, 0); ! 622: ddp->dst_node = 255; ! 623: if (!zt_get_zmcast(ifID, p, &mcastAddr)) { ! 624: dPrintf(D_M_NBP,D_L_ERROR, ! 625: ("nbp_handler: BRRQ:zt_get_zmcast error\n")); ! 626: break; ! 627: } ! 628: if ((ifID->ifType == IFTYPE_FDDITALK) ! 629: || (ifID->ifType == IFTYPE_TOKENTALK)) ! 630: ddp_bit_reverse(&mcastAddr); ! 631: ddp_router_output(m2, ifID, ET_ADDR, NULL, NULL, &mcastAddr); ! 632: } ! 633: else { /* else fwd to router */ ! 634: ddp->dst_node = 0; ! 635: if (rt->NetStart == 0) /* if Ltalk */ ! 636: NET_ASSIGN(ddp->dst_net, rt->NetStop); ! 637: else ! 638: NET_ASSIGN(ddp->dst_net, rt->NetStart); ! 639: nbp->control = NBP_FWDRQ; ! 640: ddp_router_output(m2, ifID, AT_ADDR, rt->NextIRNet, rt->NextIRNode, NULL); ! 641: } ! 642: } ! 643: if (!zhome) ! 644: break; ! 645: ! 646: if (!(m3 = (gbuf_t *)gbuf_copym((gbuf_t *)m))) { ! 647: dPrintf(D_M_NBP,D_L_ERROR, ! 648: ("nbp_handler: BRREQ:gbuf_copym failed\n")); ! 649: break; ! 650: } ! 651: ! 652: ddp = DATA_DDP(m3); ! 653: nbp = DATA_NBP(m3); ! 654: ! 655: nbp->control = NBP_LKUP; ! 656: NET_ASSIGN(ddp->dst_net, 0); ! 657: ddp->dst_node = 255; ! 658: dPrintf(D_M_NBP,D_L_INFO, ! 659: ("nbp_handler: BRRQ:loop back for us\n")); ! 660: nbp_handler(m3, ifID_table[IFID_HOME]); ! 661: break; ! 662: } ! 663: ! 664: case NBP_LKUP_REPLY: ! 665: ! 666: if (MULTIHOME_MODE || !ROUTING_MODE) /* for routers only! */ ! 667: break; ! 668: ! 669: ddp = DATA_DDP(m); ! 670: ! 671: dPrintf(D_M_NBP,D_L_WARNING, ! 672: ("nbp_handler: routing needed for LKUP_REPLY: from %d.%d\n", ! 673: NET_VALUE(ddp->src_net), ddp->src_node)); ! 674: routing_needed(m, ifID, TRUE); ! 675: ddpSent = TRUE; ! 676: break; ! 677: ! 678: default : ! 679: ! 680: dPrintf(D_M_NBP,D_L_ERROR, ! 681: ("nbp_handler: unhandled pkt: type:%d\n", nbp->control)); ! 682: ! 683: routing_needed(m, ifID, TRUE); ! 684: ddpSent = TRUE; ! 685: break; ! 686: } /* switch control */ ! 687: if (!ddpSent) ! 688: gbuf_freem(m); ! 689: return; ! 690: } ! 691: ! 692: ! 693: static void zip_handler (m) ! 694: gbuf_t *m; ! 695: { ! 696: at_ddp_t *ddp = DATA_DDP(m); ! 697: register at_zip_t *zip = ZIP_ZIP(ddp); ! 698: register char old_zone_len; ! 699: register char new_zone_len; ! 700: register char mcast_len; ! 701: char *new_zone; ! 702: ! 703: switch (zip->command) { ! 704: case ZIP_NOTIFY : ! 705: /* ZipNotify packet forwarded by ZIP module... ! 706: * change the zone name this_zone. ! 707: */ ! 708: old_zone_len = zip->data[4]; ! 709: mcast_len = zip->data[4+old_zone_len+1]; ! 710: new_zone_len = zip->data[4+old_zone_len+1+mcast_len+1]; ! 711: new_zone = &zip->data[4+old_zone_len+1+mcast_len+1]; ! 712: bcopy(new_zone, &this_zone, new_zone_len + 1); ! 713: break; ! 714: default : ! 715: /* ignore all the packets we don't know about */ ! 716: break; ! 717: } /* switch command */ ! 718: return; ! 719: } ! 720: ! 721: ! 722: static void rtmp_handler (m) ! 723: gbuf_t *m; ! 724: { ! 725: at_ddp_t *ddp = DATA_DDP(m); ! 726: at_rtmp *rtmp = (at_rtmp *)ddp->data; ! 727: at_net new_net; ! 728: register nve_entry_t *nve_entry; ! 729: ! 730: NET_NET(new_net, rtmp->at_rtmp_this_net); ! 731: for (nve_entry = name_registry.fwd; nve_entry != &name_registry ; ! 732: nve_entry = nve_entry->fwd) ! 733: NET_NET(nve_entry->address.net, new_net); ! 734: } ! 735: ! 736: ! 737: static int nbp_validate_n_hash (nbp_req, wild_ok, checkLocal) ! 738: register nbp_req_t *nbp_req; ! 739: register int wild_ok; ! 740: register int checkLocal; /* if true check if local zone */ ! 741: { ! 742: register at_nvestr_t *object, *type, *zone; ! 743: at_nbptuple_t *tuple; ! 744: register int i, part_wild; ! 745: u_int nbp_strhash(); ! 746: ! 747: tuple = DATA_NBP(nbp_req->request)->tuple; ! 748: nbp_req->flags = 0; ! 749: #ifdef COMMENTED_OUT ! 750: { ! 751: int net,node,skt; ! 752: net = NET_VALUE(tuple->enu_addr.net); ! 753: node = tuple->enu_addr.node; ! 754: skt = tuple->enu_addr.socket; ! 755: dPrintf(D_M_NBP_LOW,D_L_USR4, ! 756: ("nbp_validate: tuple addr:%d:%d:%d\n",net,node,skt)); ! 757: } ! 758: #endif /* COMMENTED_OUT */ ! 759: ! 760: object = (at_nvestr_t *)tuple->enu_name; ! 761: type = (at_nvestr_t *)(&object->str[object->len]); ! 762: zone = (at_nvestr_t *)(&type->str[type->len]); ! 763: ! 764: if (object->len > NBP_NVE_STR_SIZE || type->len > NBP_NVE_STR_SIZE || ! 765: zone->len > NBP_NVE_STR_SIZE) { ! 766: errno = EINVAL; ! 767: return (-1); ! 768: } ! 769: ! 770: #ifdef NBP_DEBUG ! 771: { ! 772: char xzone[35],xobject[35],xtype[35]; ! 773: strncpy(xzone,zone->str, zone->len); ! 774: strncpy(xobject,object->str, object->len); ! 775: strncpy(xtype,type->str, type->len); ! 776: xobject[object->len] = '\0'; ! 777: xzone[zone->len] = '\0'; ! 778: xtype[type->len] = '\0'; ! 779: dPrintf(D_M_NBP_LOW, D_L_USR4, ! 780: ("nbp_validate: looking for %s:%s@%s\n", ! 781: xobject, xtype, xzone)); ! 782: } ! 783: #endif /* NBP_DEBUG */ ! 784: /* Is this request for our zone ?? */ ! 785: nbp_req->nve.zone.len = zone->len; ! 786: nbp_req->nve.zone_hash = 0; ! 787: bcopy(zone->str,nbp_req->nve.zone.str, zone->len); ! 788: if (checkLocal && !isZoneLocal(zone)) { ! 789: char str[35]; ! 790: strncpy(str,zone->str,zone->len); ! 791: str[zone->len] = '\0'; ! 792: dPrintf(D_M_NBP_LOW,D_L_WARNING, ! 793: ("nbp_val_n_hash bad zone: %s\n", str)); ! 794: errno = EINVAL; ! 795: return(-1); ! 796: } ! 797: if (Extended_net) { ! 798: /* EtherTalk... ! 799: * The zone name in the request must match either "*" or ! 800: * our zone name (this_zone). Zero length zone field is the ! 801: * same as "*". ! 802: */ ! 803: if (zone->len != 0) { ! 804: if (!(zone->len == 1 && zone->str[0] == '*')) { ! 805: nbp_req->nve.zone_hash = ! 806: nbp_strhash(& nbp_req->nve.zone); ! 807: } ! 808: #ifdef COMMENTED_OUT ! 809: /* we allow multi-zone registration now, so ! 810: the actual zone name will be checked in ! 811: nbp_search_nve() */ ! 812: if (nbp_strcmp(zone, &this_zone, NULL) != 0) { ! 813: errno = EINVAL; ! 814: return (-1); ! 815: } ! 816: #endif /* COMMENTED_OUT */ ! 817: } ! 818: } else { ! 819: /* LocalTalk... ! 820: * "this_zone" variable will not have been initialized, since ! 821: * there's no interaction between NBP and ZIP for LocalTalk. ! 822: * We are assuming that the zone name field in a lookup ! 823: * request packet will always contain a legal value. ! 824: * One check that could be performed here (but is not) is : ! 825: * make sure that if there's no router around on the ! 826: * net, the zone name in the lookup request must be "*". If ! 827: * it's anything else, reject the packet. ! 828: */ ! 829: } ! 830: ! 831: nbp_req->nve.address = tuple->enu_addr; ! 832: nbp_req->nve.object.len = object->len; ! 833: nbp_req->nve.object_hash = 0; ! 834: if (object->len == 1 && (object->str[0] == NBP_ORD_WILDCARD || ! 835: object->str[0] == NBP_SPL_WILDCARD)) { ! 836: if (wild_ok) ! 837: nbp_req->flags |= NBP_WILD_OBJECT; ! 838: else { ! 839: errno = EINVAL; ! 840: return (-1); ! 841: } ! 842: } else{ ! 843: for (i = part_wild = 0; (unsigned) i<object->len; i++) { ! 844: if (object->str[i] == NBP_SPL_WILDCARD) ! 845: if (wild_ok) ! 846: if (part_wild) { ! 847: errno = EINVAL; ! 848: return (-1); ! 849: } else ! 850: part_wild++; ! 851: else { ! 852: errno = EINVAL; ! 853: return (-1); ! 854: } ! 855: nbp_req->nve.object.str[i] = object->str[i]; ! 856: } ! 857: if (!part_wild) ! 858: nbp_req->nve.object_hash = ! 859: nbp_strhash(&nbp_req->nve.object); ! 860: } ! 861: ! 862: nbp_req->nve.type.len = type->len; ! 863: nbp_req->nve.type_hash = 0; ! 864: if (type->len == 1 && (type->str[0] == NBP_ORD_WILDCARD || ! 865: type->str[0] == NBP_SPL_WILDCARD)) { ! 866: if (wild_ok) ! 867: nbp_req->flags |= NBP_WILD_TYPE; ! 868: else { ! 869: errno = EINVAL; ! 870: return (-1); ! 871: } ! 872: } else { ! 873: for (i = part_wild = 0; (unsigned) i<type->len; i++) { ! 874: if (type->str[i] == NBP_SPL_WILDCARD) ! 875: if (wild_ok) ! 876: if (part_wild) { ! 877: errno = EINVAL; ! 878: return (-1); ! 879: } else ! 880: part_wild++; ! 881: else { ! 882: errno = EINVAL; ! 883: return (-1); ! 884: } ! 885: nbp_req->nve.type.str[i] = type->str[i]; ! 886: } ! 887: if (!part_wild) ! 888: nbp_req->nve.type_hash = ! 889: nbp_strhash(&nbp_req->nve.type); ! 890: } ! 891: #ifdef NBP_DEBUG ! 892: { ! 893: char zone[35],object[35],type[35]; ! 894: strncpy(zone,nbp_req->nve.zone.str, nbp_req->nve.zone.len); ! 895: strncpy(object,nbp_req->nve.object.str, nbp_req->nve.object.len); ! 896: strncpy(type,nbp_req->nve.type.str, nbp_req->nve.type.len); ! 897: object[nbp_req->nve.object.len] = '\0'; ! 898: zone[nbp_req->nve.zone.len] = '\0'; ! 899: type[nbp_req->nve.type.len] = '\0'; ! 900: dPrintf(D_M_NBP_LOW,D_L_USR4, ! 901: ("nbp_validate: after hash: %s:%s@%s\n", ! 902: object, type, zone)); ! 903: } ! 904: #endif /* NBP_DEBUG */ ! 905: return(0); ! 906: } ! 907: ! 908: ! 909: /* Upshifts in place */ ! 910: static void nbp_upshift (str, count) ! 911: register u_char *str; ! 912: register int count; ! 913: { ! 914: register int i, j; ! 915: register u_char ch; ! 916: static unsigned char lower_case[] = ! 917: {0x8a, 0x8c, 0x8d, 0x8e, 0x96, 0x9a, 0x9f, 0xbe, ! 918: 0xbf, 0xcf, 0x9b, 0x8b, 0x88, 0}; ! 919: static unsigned char upper_case[] = ! 920: {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0xae, ! 921: 0xaf, 0xce, 0xcd, 0xcc, 0xcb, 0}; ! 922: ! 923: for (j=0 ; j<count ; j++) { ! 924: ch = str[j]; ! 925: if (ch >= 'a' && ch <= 'z') ! 926: str[j] = ch + 'A' - 'a'; ! 927: else if (ch & 0x80) ! 928: for (i=0; lower_case[i]; i++) ! 929: if (ch == lower_case[i]) ! 930: str[j] = upper_case[i]; ! 931: } ! 932: } ! 933: ! 934: ! 935: static u_int nbp_strhash (nvestr) ! 936: register at_nvestr_t *nvestr; ! 937: { ! 938: /* upshift while hashing */ ! 939: register u_int hash = 0; ! 940: register int i, len; ! 941: union { ! 942: u_char h_4char[4]; ! 943: int h_int; ! 944: } un; ! 945: ! 946: for (i=0; (unsigned) i < nvestr->len; i+=sizeof(int)) { ! 947: len = MIN((nvestr->len-i), sizeof(int)); ! 948: if (len == sizeof(int)) ! 949: bcopy(&(nvestr->str[i]), &un, sizeof(un)); ! 950: else { ! 951: un.h_int = -1; ! 952: for ( ; (unsigned) i<nvestr->len; i++) ! 953: un.h_4char[i % sizeof(int)] = nvestr->str[i]; ! 954: } ! 955: nbp_upshift (un.h_4char, len); ! 956: hash ^= un.h_int; ! 957: } ! 958: ! 959: return (hash); ! 960: } ! 961: ! 962: ! 963: static nve_entry_t *nbp_search_nve (nbp_req, ifID) ! 964: register nbp_req_t *nbp_req; ! 965: register at_if_t *ifID; /* NULL ok */ ! 966: { ! 967: register nve_entry_t *nve_entry; ! 968: ! 969: #ifdef NBP_DEBUG ! 970: { ! 971: char zone[35],object[35],type[35]; ! 972: strncpy(zone,nbp_req->nve.zone.str, nbp_req->nve.zone.len); ! 973: strncpy(object,nbp_req->nve.object.str, nbp_req->nve.object.len); ! 974: strncpy(type,nbp_req->nve.type.str, nbp_req->nve.type.len); ! 975: object[nbp_req->nve.object.len] = '\0'; ! 976: zone[nbp_req->nve.zone.len] = '\0'; ! 977: type[nbp_req->nve.type.len] = '\0'; ! 978: dPrintf(D_M_NBP_LOW, D_L_USR4, ! 979: ("nbp_search: looking for %s:%s@%s resp:0x%x\n",object,type,zone, ! 980: (u_int) nbp_req->response)); ! 981: } ! 982: #endif /* NBP_DEBUG */ ! 983: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 984: for (nve_entry = name_registry.fwd; nve_entry != &name_registry ; ! 985: nve_entry = nve_entry->fwd) { ! 986: ! 987: if ((nbp_req->nve.zone_hash) && ! 988: ((nbp_req->nve.zone_hash != ! 989: nve_entry->zone_hash) && ! 990: (nbp_req->nve.zone_hash != hzonehash) ! 991: ) ! 992: ) { ! 993: dPrintf(D_M_NBP_LOW,D_L_USR4, ! 994: ("nbp_search: no match for zone, req hash:%x\n", ! 995: nbp_req->nve.zone_hash)); ! 996: continue; ! 997: } ! 998: else { /* for this entry's zone OR no zone in request or entry */ ! 999: /* only in singleport mode (!MULTIPORT_MODE) with empty PRAM ! 1000: can an entry have '*' for it's zone ! 1001: */ ! 1002: at_nvestr_t *ezone=&nve_entry->zone; ! 1003: at_nvestr_t *rzone=&nbp_req->nve.zone; ! 1004: /* if (!(rzone->len == 1 && rzone->str[0] == '*')) { */ ! 1005: if (!DEFAULT_ZONE(rzone) && !DEFAULT_ZONE(ezone)) { ! 1006: if (nbp_strcmp (rzone, ezone, 0) != 0) ! 1007: continue; ! 1008: } ! 1009: else { ! 1010: if (MULTIHOME_MODE && ifID && ! 1011: NET_NOTEQ(nve_entry->address.net, ifID->ifThisNode.atalk_net)) { ! 1012: dPrintf(D_M_NBP, D_L_USR4, ! 1013: ("nbp search ifID (%d) & req net (%d) not eq\n", ! 1014: NET_VALUE(nve_entry->address.net), ! 1015: NET_VALUE(ifID->ifThisNode.atalk_net))); ! 1016: continue; ! 1017: } ! 1018: dPrintf(D_M_NBP, D_L_USR4, ! 1019: ("nbp search ifID (%d) & req net (%d) equal\n", ! 1020: NET_VALUE(nve_entry->address.net), ! 1021: NET_VALUE(ifID->ifThisNode.atalk_net))); ! 1022: } ! 1023: ! 1024: } ! 1025: if (!(nbp_req->flags & NBP_WILD_OBJECT)) { ! 1026: if ((nbp_req->nve.object_hash) && ! 1027: (nbp_req->nve.object_hash != ! 1028: nve_entry->object_hash)) ! 1029: continue; ! 1030: else { ! 1031: if (nbp_strcmp (&nbp_req->nve.object, ! 1032: &nve_entry->object, ! 1033: NBP_SPL_WILDCARD) != 0) ! 1034: continue; ! 1035: } ! 1036: } ! 1037: ! 1038: ! 1039: if (!(nbp_req->flags & NBP_WILD_TYPE)) { ! 1040: if ((nbp_req->nve.type_hash) && ! 1041: (nbp_req->nve.type_hash !=nve_entry->type_hash)) ! 1042: continue; ! 1043: else { ! 1044: if (nbp_strcmp (&nbp_req->nve.type, ! 1045: &nve_entry->type, ! 1046: NBP_SPL_WILDCARD) != 0) ! 1047: continue; ! 1048: } ! 1049: } ! 1050: ! 1051: /* Found a match! */ ! 1052: #ifdef NBP_DEBUG ! 1053: { ! 1054: char zone[35],object[35],type[35]; ! 1055: int net; ! 1056: net = NET_VALUE(nve_entry->address.net); ! 1057: strncpy(zone,nbp_req->nve.zone.str, nbp_req->nve.zone.len); ! 1058: strncpy(object,nbp_req->nve.object.str, nbp_req->nve.object.len); ! 1059: strncpy(type,nbp_req->nve.type.str, nbp_req->nve.type.len); ! 1060: object[nbp_req->nve.object.len] = '\0'; ! 1061: zone[nbp_req->nve.zone.len] = '\0'; ! 1062: type[nbp_req->nve.type.len] = '\0'; ! 1063: dPrintf(D_M_NBP_LOW, D_L_USR2, ! 1064: ("nbp_search: found %s:%s@%s net:%d\n", ! 1065: object, type, zone, net)); ! 1066: } ! 1067: #endif /* NBP_DEBUG */ ! 1068: if (nbp_req->func != NULL) { ! 1069: if ((*(nbp_req->func))(nbp_req, nve_entry) != 0) { ! 1070: /* errno expected to be set by func */ ! 1071: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1072: return (NULL); ! 1073: } ! 1074: } else { ! 1075: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1076: return (nve_entry); ! 1077: } ! 1078: } ! 1079: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1080: ! 1081: errno = 0; ! 1082: return (NULL); ! 1083: } ! 1084: ! 1085: ! 1086: static int nbp_lkup_reply (nbp_req, nve_entry) ! 1087: register nbp_req_t *nbp_req; ! 1088: register nve_entry_t *nve_entry; ! 1089: { ! 1090: register at_nbptuple_t *tuple; ! 1091: register int tuple_size, buf_len; ! 1092: register int obj_len, type_len; ! 1093: ! 1094: /* size of the current tuple we want to write... */ ! 1095: tuple_size = nve_entry->object.len + 1 + /* object */ ! 1096: nve_entry->type.len + 1 + /* type */ ! 1097: 2 + /* zone */ ! 1098: sizeof (at_inet_t) + 1; /* addr + enum */ ! 1099: ! 1100: buf_len = ((nbp_req->flags & NBP_WILD_MASK) ? DDP_DATA_SIZE:tuple_size); ! 1101: if (nbp_req->response == NULL) { ! 1102: if (nbp_setup_resp (nbp_req, buf_len) != 0) ! 1103: /* errno expected to be set by nbp_setup_resp() */ ! 1104: return (-1); ! 1105: } ! 1106: ! 1107: if ((nbp_req->space_unused < tuple_size) || ! 1108: (DATA_NBP(nbp_req->response)->tuple_count == NBP_TUPLE_MAX)) { ! 1109: if (nbp_send_resp (nbp_req) != 0) ! 1110: return (-1); ! 1111: if (nbp_setup_resp (nbp_req, buf_len) != 0) ! 1112: return (-1); ! 1113: } ! 1114: ! 1115: /* At this point, we have a response buffer that can accommodate the ! 1116: * tuple we want to write. Write it! ! 1117: */ ! 1118: tuple = (at_nbptuple_t *)gbuf_wptr(nbp_req->response); ! 1119: tuple->enu_addr = nve_entry->address; ! 1120: tuple->enu_enum = nve_entry->enumerator; ! 1121: obj_len = nve_entry->object.len + 1; ! 1122: bcopy(&nve_entry->object, &tuple->enu_name[0], obj_len); ! 1123: type_len = nve_entry->type.len + 1; ! 1124: bcopy(&nve_entry->type, &tuple->enu_name[obj_len], type_len); ! 1125: tuple->enu_name[obj_len + type_len] = 1; ! 1126: tuple->enu_name[obj_len + type_len + 1] = '*'; ! 1127: ! 1128: nbp_req->space_unused -= tuple_size; ! 1129: gbuf_winc(nbp_req->response, tuple_size); ! 1130: ! 1131: /* increment the tuple count in header by 1 */ ! 1132: DATA_NBP(nbp_req->response)->tuple_count++; ! 1133: ! 1134: return (0); ! 1135: } ! 1136: ! 1137: ! 1138: static int nbp_strcmp (str1, str2, embedded_wildcard) ! 1139: register at_nvestr_t *str1, *str2; ! 1140: register u_char embedded_wildcard; /* If str1 may contain a character ! 1141: * that's to be treated as an ! 1142: * embedded wildcard, this character ! 1143: * is it. Making this special case ! 1144: * since for zone names, squiggly ! 1145: * equal is not to be treated as a ! 1146: * wildcard. ! 1147: */ ! 1148: { ! 1149: u_char ch1,ch2; ! 1150: register int i1, i2; ! 1151: register int reverse = 0; ! 1152: register int left_index; ! 1153: ! 1154: /* Embedded wildcard, if any, could only be in the first string (str1). ! 1155: * returns 0 if two strings are equal (modulo case), -1 otherwise ! 1156: */ ! 1157: ! 1158: if (str1->len == 0 || str2->len == 0) { ! 1159: return (-1); ! 1160: } ! 1161: ! 1162: /* Wildcards are not allowed in str2. ! 1163: * ! 1164: * If str1 could potentially contain an embedded wildcard, since the ! 1165: * embedded wildcard matches ZERO OR MORE characters, str1 can not be ! 1166: * more than 1 character longer than str2. ! 1167: * ! 1168: * If str1 is not supposed to have embedded wildcards, the two strs ! 1169: * must be of equal length. ! 1170: */ ! 1171: if ((embedded_wildcard && (str2->len < (unsigned) (str1->len-1))) || ! 1172: (!embedded_wildcard && (str2->len != str1->len))) { ! 1173: return (-1); ! 1174: } ! 1175: ! 1176: for (i1 = i2 = left_index = 0; (unsigned) i1 < str1->len ;) { ! 1177: ch1 = str1->str[i1]; ! 1178: ch2 = str2->str[i2]; ! 1179: ! 1180: if (embedded_wildcard && (ch1==embedded_wildcard)) { ! 1181: /* hit the embedded wild card... start comparing from ! 1182: * the other end of the string. ! 1183: */ ! 1184: reverse++; ! 1185: /* But, if embedded wildcard was the last character of ! 1186: * the string, the two strings match, so return okay. ! 1187: */ ! 1188: if (i1 == str1->len-1) { ! 1189: return (0); ! 1190: } ! 1191: ! 1192: i1 = str1->len - 1; ! 1193: i2 = str2->len - 1; ! 1194: ! 1195: continue; ! 1196: } ! 1197: ! 1198: nbp_upshift(&ch1, 1); ! 1199: nbp_upshift(&ch2, 1); ! 1200: ! 1201: if (ch1 != ch2) { ! 1202: return (-1); ! 1203: } ! 1204: ! 1205: if (reverse) { ! 1206: i1--; i2--; ! 1207: if (i1 == left_index) { ! 1208: return (0); ! 1209: } ! 1210: } else { ! 1211: i1++; i2++; left_index++; ! 1212: } ! 1213: } ! 1214: return (0); ! 1215: } ! 1216: ! 1217: ! 1218: static void nbp_setup_hdr (nbp_req) ! 1219: register nbp_req_t *nbp_req; ! 1220: { ! 1221: register at_ddp_t *ddp; ! 1222: register at_nbp_t *nbp; ! 1223: ! 1224: ddp = DATA_DDP(nbp_req->response); ! 1225: nbp = DATA_NBP(nbp_req->response); ! 1226: ! 1227: ddp->type = NBP_DDP_TYPE; ! 1228: UAS_ASSIGN(ddp->checksum, 0); ! 1229: ddp->unused = ddp->hopcount = 0; ! 1230: ! 1231: switch(DATA_NBP(nbp_req->request)->control) { ! 1232: case NBP_LKUP : ! 1233: ddp->dst_socket = nbp_req->nve.address.socket; ! 1234: ddp->dst_node = nbp_req->nve.address.node; ! 1235: NET_NET(ddp->dst_net, nbp_req->nve.address.net); ! 1236: nbp->control = NBP_LKUP_REPLY; ! 1237: break; ! 1238: case NBP_REGISTER : ! 1239: case NBP_DELETE : ! 1240: ddp->dst_socket = DATA_DDP(nbp_req->request)->src_socket; ! 1241: ddp->dst_node = DATA_DDP(nbp_req->request)->src_node; ! 1242: NET_NET(ddp->dst_net, DATA_DDP(nbp_req->request)->src_net); ! 1243: nbp->control = NBP_STATUS_REPLY; ! 1244: break; ! 1245: } ! 1246: nbp->at_nbp_id = DATA_NBP(nbp_req->request)->at_nbp_id; ! 1247: return; ! 1248: } ! 1249: ! 1250: ! 1251: static int nbp_setup_resp (nbp_req, tuples_size) ! 1252: register nbp_req_t *nbp_req; ! 1253: register int tuples_size; ! 1254: { ! 1255: int buf_size = tuples_size + DDP_X_HDR_SIZE + NBP_HDR_SIZE; ! 1256: nbp_req->response = gbuf_alloc(AT_WR_OFFSET+buf_size, PRI_MED); ! 1257: if (nbp_req->response == NULL) { ! 1258: errno = ENOBUFS; ! 1259: return(-1); ! 1260: } ! 1261: gbuf_rinc(nbp_req->response, AT_WR_OFFSET); ! 1262: gbuf_wset(nbp_req->response, DDP_X_HDR_SIZE + NBP_HDR_SIZE); ! 1263: nbp_setup_hdr(nbp_req); ! 1264: ! 1265: DATA_NBP(nbp_req->response)->tuple_count = 0; ! 1266: nbp_req->space_unused = tuples_size; ! 1267: ! 1268: return (0); ! 1269: } /* nbp_setup_resp */ ! 1270: ! 1271: ! 1272: static int nbp_send_resp (nbp_req) ! 1273: register nbp_req_t *nbp_req; ! 1274: { ! 1275: int status; ! 1276: ! 1277: status = ddp_output(&nbp_req->response, (at_socket)NBP_SOCKET, NULL); ! 1278: ! 1279: if (status) ! 1280: gbuf_freem(nbp_req->response); ! 1281: nbp_req->response = NULL; ! 1282: errno = status; ! 1283: return(errno?-1:0); ! 1284: } ! 1285: ! 1286: static int nbp_insert_entry (nve_entry) ! 1287: nve_entry_t *nve_entry; ! 1288: { ! 1289: gbuf_t *tag; ! 1290: nve_entry_t *new_entry; ! 1291: int i; ! 1292: at_if_t *ifID; ! 1293: at_nvestr_t *zone; ! 1294: int multiZone = 0; /* true if we should do multiple registrations */ ! 1295: int defaultZone = 0; /* true if zone is '*' */ ! 1296: ! 1297: /* Got an nve entry on hand.... allocate a buffer, copy the entry ! 1298: * on to it and stick it in the registry. ! 1299: */ ! 1300: if (nve_entry->zone.str[0] == '*') ! 1301: defaultZone=TRUE; ! 1302: if (defaultZone && MULTIHOME_MODE) { ! 1303: multiZone = TRUE; ! 1304: } ! 1305: for (ifID = ifID_table[0],i=0; ifID; ifID=ifID_table[++i]) { ! 1306: if (!defaultZone && MULTIPORT_MODE ) { ! 1307: /* if registering for a specific zone and we have multiple ! 1308: I/F's running, find the first I/F containing the ! 1309: requested zone ! 1310: */ ! 1311: int zno; ! 1312: char ifs_in_zone[IF_TOTAL_MAX]; ! 1313: if (!(zno = zt_find_zname(&nve_entry->zone))) { ! 1314: errno = ENOENT; ! 1315: return(-1); ! 1316: } ! 1317: getIfUsage(zno-1, ifs_in_zone); ! 1318: for(; ifID; ifID=ifID_table[++i]) ! 1319: if (ifs_in_zone[i]) ! 1320: break; ! 1321: if (!ifID) { ! 1322: errno = ENOENT; ! 1323: return(-1); ! 1324: } ! 1325: } ! 1326: ! 1327: if ((tag = gbuf_alloc(sizeof(nve_entry_t), PRI_HI)) == NULL){ ! 1328: errno = ENOBUFS; ! 1329: return (-1); ! 1330: } ! 1331: gbuf_wset(tag, sizeof(nve_entry_t)); ! 1332: new_entry = (nve_entry_t *)gbuf_rptr(tag); ! 1333: bcopy(nve_entry, new_entry, sizeof(nve_entry_t)); ! 1334: NET_NET(new_entry->address.net, ifID->ifThisNode.atalk_net); ! 1335: new_entry->address.node = ifID->ifThisNode.atalk_node; ! 1336: if (multiZone ){ ! 1337: new_entry->zone = ZT_table[ifID->ifDefZone-1].Zone; ! 1338: new_entry->zone_hash = nbp_strhash(&new_entry->zone); ! 1339: } ! 1340: ! 1341: if (defaultZone && !multiZone ) { ! 1342: /* put actual zone name in entry instead of "*" */ ! 1343: /* if single port mode and no zone name, then a router ! 1344: is down, so use pram zone name hint from elap cfg */ ! 1345: if (!MULTIPORT_MODE && ifID->ifZoneName.str[0] == '*') { ! 1346: elap_specifics_t *elapp; ! 1347: elapp = (elap_specifics_t*)(ifID_table[IFID_HOME]->ifLapp); ! 1348: zone = &elapp->cfg.zonename; ! 1349: } ! 1350: else { ! 1351: zone = &ifID_table[IFID_HOME]->ifZoneName; ! 1352: } ! 1353: new_entry->zone = *zone; ! 1354: if ( new_entry->zone.len == 0 ) { ! 1355: new_entry->zone.str[0] = '*'; ! 1356: new_entry->zone.len = 1; ! 1357: } ! 1358: new_entry->zone_hash = nbp_strhash(&new_entry->zone); ! 1359: } ! 1360: new_entry->tag = tag; ! 1361: #ifdef NEXT ! 1362: /* Fixes PR 2243737: didn't deregister nbp entity properly */ ! 1363: new_entry->pid = current_proc()->p_pid; ! 1364: #else ! 1365: new_entry->pid = getpid(); ! 1366: #endif ! 1367: ! 1368: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 1369: ddp_insque(new_entry, name_registry.fwd); ! 1370: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1371: at_statep->flags |= AT_ST_NBP_CHANGED; ! 1372: #ifdef NBP_DEBUG ! 1373: { ! 1374: char zone[35],object[35],type[35]; ! 1375: strncpy(zone,new_entry->zone.str, new_entry->zone.len); ! 1376: strncpy(object,new_entry->object.str, new_entry->object.len); ! 1377: strncpy(type,new_entry->type.str, new_entry->type.len); ! 1378: object[new_entry->object.len] = '\0'; ! 1379: zone[new_entry->zone.len] = '\0'; ! 1380: type[new_entry->type.len] = '\0'; ! 1381: dPrintf(D_M_NBP_LOW, D_L_USR4, ! 1382: ("nbp_insert:adding %s:%s@%s i/f:%s ", ! 1383: object, type, zone, ifID->ifName)); ! 1384: dPrintf(D_M_NBP_LOW, D_L_USR4, ! 1385: ("addr:%d.%d\n", ! 1386: NET_VALUE(new_entry->address.net), ! 1387: new_entry->address.node)); ! 1388: } ! 1389: #endif /* NBP_DEBUG */ ! 1390: nbp_add_multicast(&new_entry->zone, ifID); ! 1391: if (!multiZone) ! 1392: break; ! 1393: } ! 1394: return (0); ! 1395: } ! 1396: ! 1397: nbp_add_multicast(zone, ifID) ! 1398: at_nvestr_t *zone; ! 1399: at_if_t *ifID; ! 1400: { ! 1401: char data[ETHERNET_ADDR_LEN]; ! 1402: int i; ! 1403: ! 1404: if (zone->str[0] == '*') ! 1405: return; ! 1406: { ! 1407: char str[35]; ! 1408: strncpy(str,zone->str,zone->len); ! 1409: str[zone->len] = '\0'; ! 1410: dPrintf(D_M_NBP_LOW, D_L_USR3, ! 1411: ("nbp_add_multi getting mc for %s\n", str)); ! 1412: } ! 1413: zt_get_zmcast(ifID, zone, data); ! 1414: if ((ifID->ifType == IFTYPE_FDDITALK) ! 1415: || (ifID->ifType == IFTYPE_TOKENTALK)) ! 1416: ddp_bit_reverse(data); ! 1417: dPrintf(D_M_NBP_LOW,D_L_USR3, ! 1418: ("nbp_add_multi adding 0x%x%x port:%d ifID:0x%x if:%s\n", ! 1419: *(unsigned*)data, (*(unsigned *)(data+2))&0x0000ffff, ! 1420: i, (u_int) ifID, ifID->ifName)); ! 1421: ! 1422: elap_control(ifID, ELAP_REG_ZONE_MCAST, data); ! 1423: } ! 1424: ! 1425: ! 1426: static void nbp_delete_entry (nve_entry) ! 1427: nve_entry_t *nve_entry; ! 1428: { ! 1429: ddp_remque(nve_entry); ! 1430: gbuf_freem(nve_entry->tag); ! 1431: at_statep->flags |= AT_ST_NBP_CHANGED; ! 1432: } ! 1433: ! 1434: ! 1435: static int nbp_enum_gen (nve_entry) ! 1436: register nve_entry_t *nve_entry; ! 1437: { ! 1438: register int new_enum = 0; ! 1439: register nve_entry_t *ne; ! 1440: ! 1441: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 1442: re_do: ! 1443: for (ne = name_registry.fwd; ne != &name_registry; ne = ne->fwd) { ! 1444: if ((*(int *)&ne->address == *(int *)&nve_entry->address) && ! 1445: (ne->enumerator == new_enum)) { ! 1446: if (new_enum == 255) { ! 1447: errno = EADDRNOTAVAIL; ! 1448: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1449: return (-1); ! 1450: } else { ! 1451: new_enum++; ! 1452: goto re_do; ! 1453: } ! 1454: } ! 1455: } ! 1456: ! 1457: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1458: nve_entry->enumerator = new_enum; ! 1459: return (0); ! 1460: } ! 1461: ! 1462: ! 1463: getNbpTableSize() ! 1464: ! 1465: /* for SNMP, returns size in # of entries */ ! 1466: { ! 1467: register nve_entry_t *nve; ! 1468: register int i=0; ! 1469: ! 1470: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 1471: for (nve = name_registry.fwd; nve != &name_registry; nve = nve->fwd, i++) ! 1472: ; ! 1473: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1474: return(i); ! 1475: } ! 1476: ! 1477: getNbpTable(p,s,c) ! 1478: snmpNbpEntry_t *p; ! 1479: int s; /* starting entry */ ! 1480: int c; /* # entries to copy */ ! 1481: ! 1482: /* for SNMP, returns section of nbp table */ ! 1483: ! 1484: { ! 1485: register nve_entry_t *nve; ! 1486: register int i=0; ! 1487: static int nextNo=0; /* entry that *next points to */ ! 1488: static nve_entry_t *next = (nve_entry_t*)NULL; ! 1489: ! 1490: if (s && next && nextNo == s) { ! 1491: nve = next; ! 1492: i = nextNo; ! 1493: } ! 1494: else ! 1495: nve = name_registry.fwd; ! 1496: ! 1497: ATDISABLE(nve_lock_pri,NVE_LOCK); ! 1498: for ( ; nve != &name_registry && c ; nve = nve->fwd, p++,i++) { ! 1499: if (i>= s) { ! 1500: p->nbpe_object = nve->object; ! 1501: p->nbpe_type = nve->type; ! 1502: c--; ! 1503: } ! 1504: } ! 1505: ATENABLE(nve_lock_pri,NVE_LOCK); ! 1506: if (nve != &name_registry) { ! 1507: next = nve; ! 1508: nextNo = i; ! 1509: }else { ! 1510: next = (nve_entry_t*)NULL; ! 1511: nextNo = 0; ! 1512: } ! 1513: } ! 1514: ! 1515: ! 1516: #define ZONES_PER_BLK 31 /* 31 fits within a 1k blk) */ ! 1517: #define ZONE_BLK_SIZE ZONES_PER_BLK * sizeof(at_nvestr_t) ! 1518: ! 1519: setLocalZones(newzones, size) ! 1520: at_nvestr_t *newzones; ! 1521: int size; ! 1522: /* updates list of zones which are local to all active ports ! 1523: missing zones are not deleted, only missing zones are added. ! 1524: */ ! 1525: { ! 1526: int bytesread=0; /* #bytes read from tuple */ ! 1527: int i=0, dupe; ! 1528: gbuf_t *m; ! 1529: at_nvestr_t *pnve, *pnew = newzones; ! 1530: ! 1531: if (!lzones) { ! 1532: if(!(lzones = gbuf_alloc(ZONE_BLK_SIZE, PRI_MED))) ! 1533: return(ENOBUFS); ! 1534: gbuf_wset(lzones,0); ! 1535: } ! 1536: while (bytesread < size) { /* for each new zone */ ! 1537: { ! 1538: char str[35]; ! 1539: strncpy(str,pnew->str,pnew->len); ! 1540: str[pnew->len] = '\0'; ! 1541: } ! 1542: m = lzones; ! 1543: pnve = (at_nvestr_t*)gbuf_rptr(m); ! 1544: dupe = 0; ! 1545: for (i=0; i<lzonecnt && !dupe; i++,pnve++) { ! 1546: if (i && !(i%ZONES_PER_BLK)) ! 1547: if (gbuf_cont(m)) { ! 1548: m = gbuf_cont(m); ! 1549: pnve = (at_nvestr_t*)gbuf_rptr(m); ! 1550: } ! 1551: else ! 1552: break; ! 1553: if (pnew->len != pnve->len) ! 1554: continue; ! 1555: if ((pnew->len > 33) || (pnew->len <0) ) { ! 1556: return(0); ! 1557: } ! 1558: if (!strncmp(pnew->str, pnve->str, pnew->len)) { ! 1559: dupe=1; ! 1560: continue; ! 1561: } ! 1562: } ! 1563: if (!dupe) { ! 1564: /* add new zone */ ! 1565: if (lzonecnt && !(lzonecnt%ZONES_PER_BLK)) { ! 1566: if(!(gbuf_cont(m) = gbuf_alloc(ZONE_BLK_SIZE, PRI_MED))) ! 1567: return(ENOBUFS); ! 1568: gbuf_wset(gbuf_cont(m),0); ! 1569: pnve = (at_nvestr_t*)gbuf_rptr(gbuf_cont(m)); ! 1570: } ! 1571: strncpy(pnve->str,pnew->str,pnew->len); ! 1572: pnve->len = pnew->len; ! 1573: lzonecnt++; ! 1574: } ! 1575: bytesread += (pnew->len+1); ! 1576: pnew = (at_nvestr_t*) (((char *)pnew) + pnew->len + 1); ! 1577: } ! 1578: /* showLocalZones1(); */ ! 1579: return(0); ! 1580: } ! 1581: ! 1582: /********** ! 1583: showLocalZones1() ! 1584: { ! 1585: int i; ! 1586: at_nvestr_t *pnve; ! 1587: gbuf_t *m; ! 1588: char str[35]; ! 1589: ! 1590: for (i=0; ; i++) { ! 1591: if (!(pnve = getLocalZone(i))) { ! 1592: break; ! 1593: } ! 1594: strncpy(str,pnve->str,pnve->len); ! 1595: str[pnve->len] = '\0'; ! 1596: } ! 1597: } ! 1598: ! 1599: *********/ ! 1600: ! 1601: isZoneLocal(zone) ! 1602: at_nvestr_t *zone; ! 1603: { ! 1604: at_nvestr_t *pnve; ! 1605: int i; ! 1606: if ((zone->len == 1 && zone->str[0] == '*') || zone->len == 0) ! 1607: return(1); ! 1608: for (i=0; ; i++) { ! 1609: if (!(pnve = getLocalZone(i))) ! 1610: break; ! 1611: if (!nbp_strcmp(pnve,zone,0)) ! 1612: return(1); ! 1613: } ! 1614: return(0); ! 1615: } ! 1616: ! 1617: ! 1618: #define NULL_PNVESTR (at_nvestr_t *) 0 ! 1619: ! 1620: at_nvestr_t *getLocalZone(zno) ! 1621: int zno; /* zone number in virtual list to ! 1622: return, 0 for first zone */ ! 1623: /* returns pointer to a new local zone number zno, ! 1624: returns null when no zones left. ! 1625: */ ! 1626: { ! 1627: if_zone_t ifz; ! 1628: ifz.ifzn.zone = zno; ! 1629: if (ROUTING_MODE || MULTIHOME_MODE) ! 1630: return(getRTRLocalZone(&ifz)); ! 1631: else ! 1632: return(getSPLocalZone(zno)); ! 1633: } ! 1634: ! 1635: ! 1636: at_nvestr_t *getSPLocalZone(zno) ! 1637: int zno; /* zone number in virtual list to ! 1638: return, 0 for first zone */ ! 1639: /* single port mode version */ ! 1640: { ! 1641: int curz=0; /* current zone */ ! 1642: gbuf_t *m; ! 1643: at_nvestr_t *pnve; ! 1644: ! 1645: if (lzones) { ! 1646: m = lzones; ! 1647: pnve = (at_nvestr_t*)gbuf_rptr(m); ! 1648: } ! 1649: else ! 1650: return(NULL_PNVESTR); ! 1651: if ( zno>=lzonecnt ) ! 1652: return(NULL_PNVESTR); ! 1653: for (curz=0; curz<zno; curz++,pnve++ ) { ! 1654: if ( curz<lzonecnt ) { ! 1655: if (curz && !(curz%ZONES_PER_BLK) ) { ! 1656: if (gbuf_cont(m)) { ! 1657: m = gbuf_cont(m); ! 1658: pnve = (at_nvestr_t*)gbuf_rptr(m); ! 1659: } ! 1660: else { ! 1661: return(NULL_PNVESTR); ! 1662: } ! 1663: } ! 1664: if ((pnve->len > NBP_NVE_STR_SIZE) || (pnve->len <0) ) { ! 1665: return(NULL_PNVESTR); ! 1666: } ! 1667: } ! 1668: else ! 1669: return(NULL_PNVESTR); ! 1670: } ! 1671: return(pnve); ! 1672: } ! 1673: ! 1674: ! 1675: ! 1676:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.