DIRECTORY Ascii USING [Digit], BasicTime USING [GetClockPulses, GMT, Now, Pulses, Period, PulsesToMicroseconds, Update], IO USING [Backup, EndOfStream, GetChar, PutFR, RIS, STREAM, Value], Rope USING [Concat, Equal, Fetch, Find, Length, ROPE, Substr], RopeList USING [EqualLists], IPConfig USING [specialDomains, rootServers, rootServerAddresses], IPDefs USING [Byte, DataBuffer, Datagram, DatagramRec, DByte, Address, InternetHeader, nullAddress], IPName, IPNameCache, IPNameSupport USING [AddressInList, MakeAddressInList, MakeRopeInList, LogRope, AliasSummaryLine, AddressSummaryLine, NameSummaryLine, ZoneSummaryLine, MXSummaryLine], IPNameUdp USING [AppendQuery, Class, DomainHeader, EndOfData, GetIPAddress, GetTtl, GetCardinal, GetTwoBytes, GetName, Type], IPRouter USING [BestAddress, SortAddresses], UDP USING [BodyRec, Create, default, Destroy, domain, Handle, minLength, Receive, Send]; IPNameImpl: CEDAR MONITOR IMPORTS Ascii, BasicTime, IO, Rope, RopeList, IPConfig, IPNameCache, IPNameSupport, IPNameUdp, IPRouter, UDP EXPORTS IPName = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; sequenceNumber: CARDINAL _ 0; ttlDefault: INT = 3777; mxTTL: INT = 77777; bogusTTL: INT = 77777; downTTL: INT = 1777; -- Less than half hour (less than time for sick host retry) AliasEntry: TYPE = IPNameCache.AliasEntry; NameEntry: TYPE = IPNameCache.NameEntry; MXEntry: TYPE = IPNameCache.MXEntry; BogusNameEntry: TYPE = IPNameCache.BogusNameEntry; DownNameEntry: TYPE = IPNameCache.DownNameEntry; AddressEntry: TYPE = IPNameCache.AddressEntry; BogusAddressEntry: TYPE = IPNameCache.BogusAddressEntry; DownAddressEntry: TYPE = IPNameCache.DownAddressEntry; ZoneEntry: TYPE = IPNameCache.ZoneEntry; rootServerAddresses: LIST OF IPDefs.Address _ IPConfig.rootServerAddresses; rootServers: LIST OF Rope.ROPE _ IPConfig.rootServers; LoadCacheFromName: PUBLIC PROC [name: Rope.ROPE, takeOld, needAddress: BOOL _ FALSE] RETURNS [state: IPName.NameState] = { entry, initEntry: NameEntry; alias, bogus, down, mx, aliasmx, gotAddress: BOOL _ FALSE; IF Rope.Length[name] > 0 AND Rope.Fetch[name, 0] = '[ THEN RETURN[nameOk]; IF Rope.Find[name, "."] = -1 THEN name _ Rope.Concat[name, ".ARPA"]; [initEntry, bogus, down, mx, alias] _ FetchNameEntry[name, takeOld]; IF bogus THEN RETURN[bogus]; IF ~needAddress THEN { SELECT TRUE FROM alias => RETURN[aliasOk]; mx => RETURN[mxOk]; initEntry # NIL => { initEntry.nameCacheLoad _ initEntry.nameCacheLoad.SUCC; RETURN[nameOk]}; ENDCASE; } ELSE { SELECT TRUE FROM alias => { aliasEntry: AliasEntry _ IPNameCache.AliasLookup[name]; [entry, bogus, down, aliasmx, alias] _ FetchNameEntry[aliasEntry.name, takeOld]; IF bogus THEN RETURN[bogus]; IF down THEN RETURN[down]; IF entry # NIL THEN { entry.nameCacheLoad _ entry.nameCacheLoad.SUCC; gotAddress _ TRUE}; IF aliasmx THEN { mxEntry: MXEntry _ IPNameCache.MXLookup[aliasEntry.name]; FOR list: LIST OF IPNameCache.MXHostInfo _ mxEntry.mxHosts, list.rest UNTIL list = NIL DO mxName: Rope.ROPE _ list.first.host; [entry, bogus, down, aliasmx, alias] _ FetchNameEntry[mxName, takeOld]; IF entry # NIL THEN { entry.nameCacheLoad _ entry.nameCacheLoad.SUCC; gotAddress _ TRUE }; ENDLOOP; }; IF gotAddress THEN RETURN[aliasOk]; }; mx => { mxEntry: MXEntry _ IPNameCache.MXLookup[name]; FOR list: LIST OF IPNameCache.MXHostInfo _ mxEntry.mxHosts, list.rest UNTIL list = NIL DO mxName: Rope.ROPE _ list.first.host; [entry, bogus, down, mx, alias] _ FetchNameEntry[mxName, takeOld]; IF entry # NIL THEN { entry.nameCacheLoad _ entry.nameCacheLoad.SUCC; gotAddress _ TRUE }; ENDLOOP; IF gotAddress THEN RETURN[mxOk]; }; initEntry # NIL => { initEntry.nameCacheLoad _ initEntry.nameCacheLoad.SUCC; RETURN[nameOk]}; ENDCASE; }; RETURN[down]; }; NameToAddress: PUBLIC PROC [name: Rope.ROPE, mail: BOOL _ FALSE] RETURNS [addresses: LIST OF IPDefs.Address_ NIL] = { entry, mxEntry: NameEntry _ NIL; bogus: BOOL _ FALSE; alias, down, mx, aliasmx: BOOL; length: INT _ Rope.Length[name]; IF length > 2 AND Rope.Fetch[name, 0] = '[ THEN { -- It's an address already address: IPDefs.Address; IF Tailed[name, ".ARPA"] THEN name _ StripTail[name, ".ARPA"]; address _ ParseAddress[IO.RIS[name] !AddressError => {bogus _ TRUE; CONTINUE};]; IF bogus THEN RETURN[NIL] ELSE RETURN [LIST[address]]; }; IF Rope.Find[name, "."] = -1 THEN name _ Rope.Concat[name, ".ARPA"]; [entry, bogus, down, mx, alias] _ FetchNameEntry[name, FALSE]; IF bogus THEN RETURN[NIL]; IF entry # NIL AND ~mail THEN { addresses _ AppendAddressList[addresses, entry.addresses]; entry.nameToAddress _ entry.nameToAddress.SUCC; RETURN[addresses]; }; IF alias THEN { aliasEntry: AliasEntry _ IPNameCache.AliasLookup[name]; [entry, , , aliasmx, ] _ FetchNameEntry[aliasEntry.name, FALSE]; IF entry # NIL AND ~mail THEN { addresses _ AppendAddressList[addresses, entry.addresses]; entry.nameToAddress _ entry.nameToAddress.SUCC; RETURN[addresses]; }; IF aliasmx AND mail THEN { mxHosts: LIST OF Rope.ROPE _ NameToMXHostList[aliasEntry.name]; FOR list: LIST OF Rope.ROPE _ mxHosts, list.rest UNTIL list = NIL DO mxName: Rope.ROPE _ list.first; [mxEntry, , , , ] _ FetchNameEntry[mxName, FALSE]; IF mxEntry # NIL THEN { addresses _ AppendAddressList[addresses, mxEntry.addresses]; mxEntry.nameToAddress _ mxEntry.nameToAddress.SUCC; }; ENDLOOP; }; }; IF mx AND mail THEN { mxHosts: LIST OF Rope.ROPE _ NameToMXHostList[name]; FOR list: LIST OF Rope.ROPE _ mxHosts, list.rest UNTIL list = NIL DO mxName: Rope.ROPE _ list.first; [mxEntry, , , , ] _ FetchNameEntry[mxName, FALSE]; IF mxEntry # NIL THEN { addresses _ AppendAddressList[addresses, mxEntry.addresses]; mxEntry.nameToAddress _ mxEntry.nameToAddress.SUCC; }; ENDLOOP; }; IF entry # NIL THEN { addresses _ AppendAddressList[addresses, entry.addresses]; entry.nameToAddress _ entry.nameToAddress.SUCC; }; RETURN[addresses]; }; AppendAddressList: PROC [oldList: LIST OF IPDefs.Address, addList: LIST OF IPDefs.Address] RETURNS[newList: LIST OF IPDefs.Address] = { orderedAddList: LIST OF IPDefs.Address _ IPRouter.SortAddresses[addList]; -- best address first newList _ oldList; FOR list: LIST OF IPDefs.Address _ orderedAddList, list.rest UNTIL list = NIL DO address: IPDefs.Address _ list.first; newList _ IPNameSupport.MakeAddressInList[newList, address]; ENDLOOP; }; NameToMXHostList: PUBLIC PROC [name: Rope.ROPE] RETURNS [hosts: LIST OF Rope.ROPE] = { entry: MXEntry; entry _ IPNameCache.MXLookup[name]; IF entry = NIL THEN RETURN[NIL]; RETURN[SortMXHosts[entry]]; }; SortMXHosts: PROC [entry: MXEntry] RETURNS [sorted: LIST OF Rope.ROPE _ NIL] = { bestPreference: INT _ INT.LAST; -- least is best prevBestPreference: INT _ INT.LAST; bestHost: ROPE _ NIL; IF entry = NIL THEN RETURN[NIL]; FOR list: LIST OF IPNameCache.MXHostInfo _ entry.mxHosts, list.rest UNTIL list = NIL DO IF list.first.preference < bestPreference THEN bestPreference _ list.first.preference; ENDLOOP; FOR list: LIST OF IPNameCache.MXHostInfo _ entry.mxHosts, list.rest UNTIL list = NIL DO IF list.first.preference = bestPreference THEN {sorted _ IPNameSupport.MakeRopeInList[sorted, list.first.host]}; ENDLOOP; DO prevBestPreference _ bestPreference; bestPreference _ INT.LAST; FOR list: LIST OF IPNameCache.MXHostInfo _ entry.mxHosts, list.rest UNTIL list = NIL DO IF list.first.preference < bestPreference AND list.first.preference > prevBestPreference THEN {bestPreference _ list.first.preference}; ENDLOOP; IF bestPreference = INT.LAST THEN EXIT; FOR list: LIST OF IPNameCache.MXHostInfo _ entry.mxHosts, list.rest UNTIL list = NIL DO IF list.first.preference = bestPreference THEN {sorted _ IPNameSupport.MakeRopeInList[sorted, list.first.host]}; ENDLOOP; ENDLOOP; RETURN[sorted]; }; Source: PUBLIC PROC [name: Rope.ROPE, type: IPName.ExternalSourceCache] RETURNS [server: Rope.ROPE _ NIL] = { SELECT type FROM nameCache => { entry: NameEntry _ IPNameCache.NameLookup[name]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; aliasCache=> { entry: AliasEntry _ IPNameCache.AliasLookup[name]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; mxCache => { entry: MXEntry _ IPNameCache.MXLookup[name]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; bogusNameCache => { entry: BogusNameEntry _ IPNameCache.BogusNameLookup[name]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; addressCache => { entry: AddressEntry _ IPNameCache.AddressLookup[NameToAddress[name].first]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; bogusAddressCache => { entry: BogusAddressEntry _ IPNameCache.BogusAddressLookup[NameToAddress[name].first]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; zoneCache => { entry: ZoneEntry _ IPNameCache.ZoneLookup[name]; IF entry = NIL THEN RETURN[NIL]; server _ AddressToName[entry.source]}; ENDCASE; }; AddressError: ERROR = CODE; ParseAddress: PROC [s: IO.STREAM] RETURNS [a: IPDefs.Address] = { ENABLE IO.EndOfStream => GOTO EndOfStream; c: CHAR; WHILE (c _ IO.GetChar[s])=' OR c='\t DO ENDLOOP; IF c = '[ THEN c _ IO.GetChar[s]; FOR i: CARDINAL IN [0..3] DO b: CARDINAL _ 0; IF ~Ascii.Digit[c] THEN ERROR AddressError; DO SELECT c FROM IN ['0..'9] => b _ b*10 + (c-'0); '. => IF i<3 THEN {c _ IO.GetChar[s]; EXIT} ELSE ERROR AddressError; '] => IF i=3 THEN EXIT ELSE ERROR AddressError; ENDCASE => IF i=3 THEN {IO.Backup[s, c]; EXIT} ELSE ERROR AddressError; c _ IO.GetChar[s]; ENDLOOP; IF b > IPDefs.Byte.LAST THEN ERROR AddressError; a[i] _ b; ENDLOOP; EXITS EndOfStream => ERROR AddressError; }; LoadCacheFromAddress: PUBLIC PROC [address: IPDefs.Address, takeOld: BOOL] RETURNS [state: IPName.AddressState] = { entry: AddressEntry; bogus, down: BOOL; IF address = IPDefs.nullAddress THEN RETURN[bogus]; [entry, bogus, down] _ FetchAddressEntry[address, takeOld]; IF bogus THEN RETURN[bogus]; IF down THEN RETURN[down]; IF entry = NIL THEN RETURN[down]; entry.addressCacheLoad _ entry.addressCacheLoad.SUCC; RETURN[addressOk]; }; NormalizeName: PUBLIC PROC [name: ROPE] RETURNS [ROPE] = { entry: NameEntry; bogus, down, mx, alias: BOOL; FOR list: LIST OF Rope.ROPE _ IPConfig.specialDomains, list.rest UNTIL list = NIL DO domain: Rope.ROPE _ list.first; IF DotTailed[name, domain] THEN RETURN[NIL]; ENDLOOP; IF Rope.Find[name, "."] = -1 THEN name _ Rope.Concat[name, ".ARPA"]; name _ FixupTail[name, ".AG"]; name _ FixupTail[name, ".ArpaGateway"]; name _ FixupTail[name, ".NotArpa"]; name _ FixupTail[name, ".ARPA.ARPA"]; -- Hardy 10.0 always adds .ArpaGateway IF Rope.Length[name] > 2 AND Rope.Fetch[name, 0] = '[ THEN { IF Tailed[name, ".ARPA"] THEN name _ StripTail[name, ".ARPA"]; RETURN [name]; }; [entry, bogus, down, mx, alias] _ FetchNameEntry[name, TRUE]; IF down THEN RETURN[name]; IF bogus THEN RETURN[NIL]; IF alias THEN RETURN[IPNameCache.AliasLookup[name].name]; IF entry # NIL THEN { entry.normalizeName _ entry.normalizeName.SUCC; RETURN[entry.name]; }; IF mx THEN RETURN[name]; RETURN[NIL]; }; AddressToName: PUBLIC PROC [address: IPDefs.Address] RETURNS [name: ROPE] = { entry: AddressEntry; bogus, down: BOOL; IF address = IPDefs.nullAddress THEN RETURN[NIL]; [entry, bogus, down] _ FetchAddressEntry[address, TRUE]; IF entry = NIL THEN RETURN[AddressToRope[address]]; entry.addressToName _ entry.addressToName.SUCC; name _ entry.names.first; FOR list: LIST OF ROPE _ entry.names.rest, list.rest UNTIL list = NIL DO name _ PickBestName[name, list.first]; ENDLOOP; RETURN; }; MyAddressToName: PROC [address: IPDefs.Address] RETURNS [name: ROPE] = { entry: AddressEntry; IF address = IPDefs.nullAddress THEN RETURN[NIL]; entry _ IPNameCache.AddressLookup[address]; IF entry # NIL THEN { entry.addressToName _ entry.addressToName.SUCC; name _ entry.names.first; FOR list: LIST OF ROPE _ entry.names.rest, list.rest UNTIL list = NIL DO name _ PickBestName[name, list.first]; ENDLOOP; }; RETURN[AddressToRope[address]]; }; AddressToRope: PUBLIC PROC [a: IPDefs.Address] RETURNS [result: ROPE] = { result _ IO.PutFR["[%g.%g.%g.%g]", [cardinal[a[0]]], [cardinal[a[1]]], [cardinal[a[2]]], [cardinal[a[3]]]]; }; FetchNameEntry: PROC [name: ROPE, takeOld: BOOL _ FALSE] RETURNS [entry: NameEntry _ NIL, bogus, down, mx, alias: BOOL_ FALSE] = { DoQuery: PROC [name: ROPE] RETURNS[down, bogus: BOOL _ FALSE] = { reply: BOOL _ FALSE; -- Do zone and mx queries if necessary to guarantee entries are in cache by the time n_a query happens [down, bogus] _ LoadZones[name]; IF down OR bogus THEN RETURN; reply _ Query["MX Query", name, mx]; IF IPNameCache.BogusNameLookup[name] # NIL THEN RETURN[FALSE, TRUE]; IF ~reply THEN { [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN; }; reply _ Query["Name Query", name, name]; IF ~reply THEN { [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN; }; }; [entry, bogus, down, alias, mx] _ LookInNameCache[name, takeOld]; IF mx OR alias OR bogus OR down THEN RETURN; IF entry # NIL THEN {IF entry.authoritative OR takeOld THEN RETURN; }; [down, bogus] _ DoQuery[name]; IF down OR bogus THEN RETURN; [entry, bogus, down, alias, mx] _ LookInNameCache[name, takeOld]; IF mx OR alias OR bogus OR down THEN RETURN; IF entry # NIL THEN {IF entry.authoritative OR takeOld THEN RETURN; }; IF takeOld THEN { [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN; }; CheckNewServers[name]; [down, bogus] _ DoQuery[name]; IF down OR bogus THEN RETURN; [entry, bogus, down, alias, mx] _ LookInNameCache[name, takeOld]; IF mx OR alias OR bogus OR down THEN RETURN; IF entry # NIL THEN {IF entry.authoritative OR takeOld THEN RETURN; }; [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN; }; loopStop: INT _ 10; -- shouldn't be recursion to this extent LoadZones: PROC [name: ROPE, queryAddress: IPDefs.Address _ IPDefs.nullAddress] RETURNS[down, bogus: BOOL_FALSE]= { reply: BOOL _ FALSE; loopCtr: INT _ 0; childZone: ROPE _ NIL; lastChildZone: ROPE _ NIL; zone: ZoneEntry _ BestZone[name]; IF zone = NIL THEN { topZone: ROPE _ TopLevelZone[name]; reply _ Query["Zone Query", topZone, newZone, queryAddress]; {bogusEntry: BogusNameEntry _ IPNameCache.BogusNameLookup[topZone]; IF bogusEntry # NIL THEN { InsertBogusName[bogusEntry.source, bogusTTL, bogusEntry.authoritative, name]; RETURN[FALSE, TRUE]}; }; IF ~reply OR BestZone[name] = NIL THEN { [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN}; }; DO zone _ BestZone[name]; reply _ FALSE; IF ~zone.authoritative THEN { reply _ Query["Zone Query", zone.zone, newZone, queryAddress]; IF ~reply THEN { [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN}; }; IF Tailed[name, "IN-ADDR.ARPA"] THEN childZone _ name ELSE { IF Rope.Equal[zone.zone, name, FALSE] THEN EXIT; IF Rope.Equal[lastChildZone, name, FALSE] THEN EXIT; childZone _ ChildZone[name, zone.zone]; IF childZone = NIL THEN EXIT; IF Rope.Equal[childZone, zone.zone, FALSE] THEN EXIT; IF Rope.Equal[childZone, lastChildZone, FALSE] THEN EXIT; IF Tailed[childZone, "ARPA"] THEN EXIT; }; reply _ Query["Zone Query", childZone, newZone, queryAddress]; {bogusEntry: BogusNameEntry _ IPNameCache.BogusNameLookup[childZone]; IF bogusEntry # NIL THEN { InsertBogusName[bogusEntry.source, bogusTTL, bogusEntry.authoritative, name]; RETURN[FALSE, TRUE]}; }; IF ~reply THEN { [] _ IPNameCache.UpdateDownName[name, downTTL]; down _ TRUE; RETURN}; lastChildZone _ childZone; IF SameZone[BestZone[name], zone] THEN EXIT; IF loopCtr = loopStop THEN EXIT; -- beware of bogus reponses causing infinite loop loopCtr _ loopCtr +1; ENDLOOP; }; SameZone: PROC[zone1, zone2: ZoneEntry] RETURNS [same: BOOL _ TRUE] = { IF zone1 = NIL AND zone2 = NIL THEN RETURN[TRUE]; IF zone1 = NIL OR zone2 = NIL THEN RETURN[FALSE]; IF zone1.source # zone2.source THEN RETURN[FALSE]; IF zone1.authoritative # zone2.authoritative THEN RETURN[FALSE]; IF ~Rope.Equal[zone1.zone, zone2.zone, FALSE] THEN RETURN[FALSE]; IF ~RopeList.EqualLists[zone1.servers, zone2.servers, FALSE] THEN RETURN[FALSE]; RETURN[TRUE]; }; LookInNameCache: PROC [name: ROPE, takeOld: BOOL] RETURNS [entry: NameEntry_ NIL, bogus: BOOL_ FALSE, down: BOOL_ FALSE, alias: BOOL_ FALSE, mx: BOOL_ FALSE] = { bogusEntry: BogusNameEntry; aliasEntry: AliasEntry; downEntry: DownNameEntry; mxEntry: MXEntry; entry _ IPNameCache.NameLookup[name]; IF entry # NIL THEN { IF ~takeOld THEN entry _ MaybeRejuvinateName[entry]; }; mxEntry _ IPNameCache.MXLookup[name]; IF mxEntry # NIL THEN { IF ~takeOld THEN mxEntry _ MaybeRejuvinateMX[mxEntry]; IF mxEntry = NIL THEN mx _ FALSE -- Can't rejuvinate an MX record ELSE { mx _ TRUE; mxEntry.nameToMX _ mxEntry.nameToMX.SUCC}; }; aliasEntry _ IPNameCache.AliasLookup[name]; IF aliasEntry # NIL THEN { IF ~takeOld THEN aliasEntry _ MaybeRejuvinateAlias[aliasEntry]; IF aliasEntry = NIL THEN alias _ FALSE -- Can't rejuvinate an Alias ELSE { alias _ TRUE; aliasEntry.aliasToName _ aliasEntry.aliasToName.SUCC}; }; bogusEntry _ IPNameCache.BogusNameLookup[name]; IF bogusEntry # NIL THEN { IF ~takeOld THEN bogusEntry _ MaybeRejuvinateBogusName[bogusEntry]; IF bogusEntry = NIL THEN bogus _ FALSE -- Can't rejuvinate a Bogus name ELSE { bogus _ TRUE; bogusEntry.nameToBogus _ bogusEntry.nameToBogus.SUCC}; }; downEntry _ IPNameCache.DownNameLookup[name]; IF downEntry # NIL THEN { IF ~takeOld THEN downEntry _ MaybeRejuvinateDownName[downEntry]; IF downEntry = NIL THEN down _ FALSE -- time to check name ELSE { down _ TRUE; downEntry.nameToDown _ downEntry.nameToDown.SUCC}; }; }; AddressToQueryRope: PROC [address: IPDefs.Address] RETURNS[rope: ROPE] = { rope _ IO.PutFR["%G.%G.%G.%G.IN-ADDR.ARPA", [integer[address[3]]], [integer[address[2]]], [integer[address[1]]], [integer[address[0]]]]; }; FetchAddressEntry: PROC [address: IPDefs.Address, takeOld: BOOL] RETURNS [entry: AddressEntry_NIL, bogus: BOOL _ FALSE, down: BOOL _ FALSE] = { servers, newServers: LIST OF IPDefs.Address _ NIL; reply: BOOL _ FALSE; addressRope: ROPE _ AddressToQueryRope[address]; entry _ IPNameCache.AddressLookup[address]; IF entry = NIL THEN { bogusEntry: BogusAddressEntry _ IPNameCache.BogusAddressLookup[address]; IF bogusEntry # NIL THEN { IF ~takeOld THEN { bogusEntry _ MaybeRejuvinateBogusAddress[bogusEntry]; IF bogusEntry = NIL THEN RETURN[NIL, FALSE, FALSE]; }; }; -- Can't rejuvinate a Bogus Address IF bogusEntry # NIL THEN { bogusEntry.addressToBogus _ bogusEntry.addressToBogus.SUCC; RETURN[NIL, TRUE, FALSE]; }; }; -- We are sure this address is bogus IF entry # NIL THEN { IF ~takeOld THEN { entry _ MaybeRejuvinateAddress[entry]; IF entry = NIL THEN RETURN[NIL, FALSE, FALSE]; }; }; -- Can't rejuvinate an Address IF entry # NIL THEN RETURN; -- We are sure this address is valid IF entry = NIL THEN { downEntry: DownAddressEntry _ IPNameCache.DownAddressLookup[address]; IF downEntry # NIL THEN downEntry _ MaybeRejuvinateDownAddress[downEntry]; IF downEntry = NIL THEN down _ FALSE; -- time to check name IF downEntry # NIL THEN { downEntry.addressToDown _ downEntry.addressToDown.SUCC; RETURN[NIL, FALSE, TRUE]; }; }; [down, bogus] _ LoadZones[addressRope, address]; IF bogus THEN RETURN; IF down THEN { [] _ IPNameCache.UpdateDownAddress[address, downTTL]; RETURN; }; reply _ Query["Address Query", addressRope, address, address]; IF ~reply THEN { [] _ IPNameCache.UpdateDownAddress[address, downTTL]; down _ TRUE; RETURN; }; entry _ IPNameCache.AddressLookup[address]; -- We are sure this address is valid }; FixupTail: PROC [old, tail: ROPE] RETURNS [new: ROPE] = { new _ old; IF Tailed[old, tail] THEN { new _ StripTail[old, tail]; new _ Rope.Concat[new, ".ARPA"]; }; }; DotTailed: PROC [body, tail: ROPE] RETURNS [match: BOOL] = { IF ~Tailed[body, tail] THEN RETURN[FALSE]; IF Rope.Fetch[body, Rope.Length[body]-Rope.Length[tail]-1] # '. THEN RETURN[FALSE]; RETURN[TRUE]; }; Tailed: PROC [body, tail: ROPE] RETURNS [match: BOOL] = { bodyLength: INT = Rope.Length[body]; tailLength: INT = Rope.Length[tail]; back: ROPE; IF bodyLength <= tailLength THEN RETURN[FALSE]; back _ Rope.Substr[body, bodyLength-tailLength, tailLength]; IF ~Rope.Equal[back, tail, FALSE] THEN RETURN[FALSE]; RETURN[TRUE]; }; StripTail: PROC [body, tail: ROPE] RETURNS [new: ROPE] = { bodyLength: INT = body.Length[]; tailLength: INT = tail.Length[]; RETURN[Rope.Substr[body, 0, bodyLength - tailLength]]}; PickBestName: PUBLIC PROC [a, b: ROPE] RETURNS [ROPE] = { IF a = NIL THEN RETURN[b]; IF b = NIL THEN RETURN[a]; IF ~Tailed[a, ".ARPA"] THEN RETURN[a]; IF ~Tailed[b, ".ARPA"] THEN RETURN[b]; IF Gateway[a] THEN RETURN[b]; IF Gateway[b] THEN RETURN[a]; IF Dots[a] < Dots[b] THEN RETURN[a]; RETURN[b]; }; Gateway: PROC [name: ROPE] RETURNS [match: BOOL] = { IF Tailed[name, "-GW.ARPA"] THEN RETURN[TRUE]; IF Tailed[name, "-GATEWAY.ARPA"] THEN RETURN[TRUE]; RETURN[FALSE]; }; Dots: PROC [name: ROPE] RETURNS [dots: INT _ 0] = { FOR i: INT IN [0..Rope.Length[name]) DO IF Rope.Fetch[name, i] = '. THEN dots _ dots+1; ENDLOOP; }; Class: TYPE = IPNameUdp.Class; DomainHeader: TYPE = IPNameUdp.DomainHeader; Type: TYPE = IPNameUdp.Type; FindServers: PROCEDURE [name: ROPE] RETURNS [answers: LIST OF IPDefs.Address _ NIL] = { bestZone: ZoneEntry _ BestZone[name]; nameEntry: NameEntry _ IPNameCache.NameLookup[name]; serverList: LIST OF ROPE _ NIL; IF nameEntry # NIL THEN IF nameEntry.authoritative = TRUE THEN answers _ IPNameSupport.MakeAddressInList[answers, nameEntry.source]; IF bestZone = NIL THEN serverList _ rootServers ELSE serverList _ bestZone.servers; FOR list: LIST OF ROPE _ serverList, list.rest UNTIL list = NIL DO serverName: ROPE _ list.first; server: NameEntry _ IPNameCache.NameLookup[serverName]; -- Beware of recursion best: IPDefs.Address; IF server = NIL THEN LOOP; IF server.addresses = NIL THEN LOOP; best _ IPRouter.BestAddress[server.addresses]; IF IPNameCache.DownServerLookup[best] # NIL THEN LOOP; answers _ IPNameSupport.MakeAddressInList[answers, best]; ENDLOOP; FOR list: LIST OF ROPE _ serverList, list.rest UNTIL list = NIL DO serverName: ROPE _ list.first; server: NameEntry _ IPNameCache.NameLookup[serverName]; -- Beware of recursion IF server = NIL THEN LOOP; FOR list: LIST OF IPDefs.Address _ server.addresses, list.rest UNTIL list = NIL DO IF IPNameCache.DownServerLookup[list.first] # NIL THEN LOOP; answers _ IPNameSupport.MakeAddressInList[answers, list.first]; ENDLOOP; ENDLOOP; IF answers # NIL THEN RETURN[answers]; IF bestZone # NIL THEN RETURN[NIL]; -- All domain servers down FOR list: LIST OF IPDefs.Address _ rootServerAddresses, list.rest UNTIL list = NIL DO IF IPNameCache.DownServerLookup[list.first] # NIL THEN LOOP; answers _ IPNameSupport.MakeAddressInList[answers, list.first]; ENDLOOP; RETURN[answers]; }; BestZone: PROCEDURE [name: ROPE] RETURNS[bestZone: ZoneEntry_NIL] = { zones: LIST OF ZoneEntry = IPNameCache.GetZones[]; bestZoneName: ROPE _ NIL; bestZoneLength: INT _ 0; FOR list: LIST OF ZoneEntry _ zones, list.rest UNTIL list = NIL DO zone: ZoneEntry _ list.first; zoneName: ROPE _ zone.zone; zoneLength: INT _ Rope.Length[zoneName]; IF zoneLength < bestZoneLength THEN LOOP; IF Rope.Equal[name, zoneName, FALSE] OR DotTailed[name, zoneName] THEN { bestZone _ zone; bestZoneLength _ zoneLength; }; ENDLOOP; }; CheckNewServers: PROCEDURE [name: ROPE] = { zone: ZoneEntry _ IPNameCache.GetZone[name]; IF zone = NIL THEN RETURN; FOR list: LIST OF ROPE _ zone.servers, list.rest UNTIL list = NIL DO serverName: ROPE _ list.first; entry: NameEntry _ IPNameCache.NameLookup[serverName]; IF entry # NIL AND entry.authoritative THEN LOOP; [] _ Query["New Server", serverName, newServer]; ENDLOOP; IF zone.authoritative THEN RETURN; [] _ Query["New Zone", zone.zone, newZone]; }; rejuvinationThreshold: INT = -60; MaybeRejuvinateName: PROCEDURE [entry: NameEntry] RETURNS [NameEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { IPNameCache.AddToCacheHits[entry.source, name]; RETURN[entry]; }; {bestZone: ZoneEntry _ BestZone[entry.name]; IF bestZone # NIL THEN [] _ MaybeRejuvinateZone[bestZone];}; [] _ Query["Name Rejuvenation", entry.name, nameRejuvination]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; MaybeRejuvinateAlias: PROCEDURE [entry: AliasEntry] RETURNS [AliasEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { IPNameCache.AddToCacheHits[entry.source, alias]; RETURN[entry]; }; [] _ Query["Alias Rejuvenation", entry.alias, aliasRejuvination]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; MaybeRejuvinateMX: PROCEDURE [entry: MXEntry] RETURNS [MXEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { IPNameCache.AddToCacheHits[entry.source, mx]; RETURN[entry]; }; [] _ Query["MX Rejuvenation", entry.name, mxRejuvination]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; MaybeRejuvinateBogusName: PROCEDURE [entry: BogusNameEntry] RETURNS [BogusNameEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { IPNameCache.AddToCacheHits[entry.source, bogusName]; RETURN[entry]; }; [] _ Query["Bogus Name Rejuvenation", entry.bogus, bogusNameRejuvination]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; MaybeRejuvinateDownName: PROCEDURE [entry: DownNameEntry] RETURNS [DownNameEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN RETURN[entry]; RETURN[NIL]; }; -- Time to check name again MaybeRejuvinateAddress: PROCEDURE [entry: AddressEntry] RETURNS [AddressEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { IPNameCache.AddToCacheHits[entry.source, address]; RETURN[entry]; }; [] _ Query["Address Rejuvenation", AddressToQueryRope[entry.address], addressRejuvination, entry.address]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; MaybeRejuvinateBogusAddress: PROCEDURE [entry: BogusAddressEntry] RETURNS [BogusAddressEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { IPNameCache.AddToCacheHits[entry.source, bogusAddress]; RETURN[entry]; }; [] _ Query["Bogus Address Rejuvenation", AddressToQueryRope[entry.bogus], bogusAddressRejuvination, entry.bogus]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; MaybeRejuvinateDownAddress: PROCEDURE [entry: DownAddressEntry] RETURNS [DownAddressEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN RETURN[entry]; RETURN[NIL]; }; MaybeRejuvinateZone: PROCEDURE [entry: ZoneEntry] RETURNS [ZoneEntry] = { now: BasicTime.GMT _ BasicTime.Now[]; ttl: INT _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold AND entry.authoritative THEN {IPNameCache.AddToCacheHits[entry.source, zone]; RETURN[entry]; }; [] _ Query["Zone Rejuvenation", entry.zone, zoneRejuvination]; ttl _ BasicTime.Period[from: now, to: entry.expires]; IF ttl > rejuvinationThreshold THEN { entry.rejuvinations _ entry.rejuvinations.SUCC; RETURN[entry]; }; entry.expires _ BasicTime.Update[now, ttlDefault]; -- Don't thrash RETURN[NIL]; }; nQueries: CARDINAL _ 3; -- Number of query tries with timeouts below baseTimeout: CARDINAL _ 5; -- 5, 10, 15 seconds Query: PROCEDURE [ what: ROPE, name: ROPE, type: IPNameCache.ProbeType, queryAddress: IPDefs.Address _ IPDefs.nullAddress] RETURNS[done: BOOL _ FALSE] = TRUSTED { packetStart, packetStop: BasicTime.Pulses; milliseconds: LONG CARDINAL; id: CARDINAL _ (sequenceNumber _ sequenceNumber.SUCC); servers: LIST OF IPDefs.Address _ FindServers[name]; FOR list: LIST OF IPDefs.Address _ servers, list.rest UNTIL done OR list = NIL DO server: IPDefs.Address _ list.first; handle: UDP.Handle; handle _ UDP.Create[him: server, remote: UDP.domain, local: UDP.default]; IPNameCache.AddToProbes[server, type]; BEGIN ENABLE ServerIsDown => { IPNameSupport.LogRope[IO.PutFR["%T ** ServerDown during %G for %G via %G.\n", [time[BasicTime.Now[]]], [rope[what]], [rope[name]], [rope[MyAddressToName[server]]]]]; CONTINUE; }; FOR i: CARDINAL IN [1..nQueries] UNTIL done DO dg: IPDefs.Datagram _ NEW [IPDefs.DatagramRec]; udp: LONG POINTER TO UDP.BodyRec _ LOOPHOLE[@dg.data]; domain: LONG POINTER TO DomainHeader _ LOOPHOLE[@udp.data]; qType: IPNameUdp.Type _ a; domain^ _ [id: id]; udp.length _ UDP.minLength + DomainHeader.SIZE*2; IF type = newZone OR type = zoneRejuvination THEN qType _ ns; IF type = mx OR type = mxRejuvination THEN qType _ mx; IF type = address THEN qType _ star; IPNameUdp.AppendQuery[udp, domain, name, qType, in]; UDP.Send[handle, dg, udp.length]; packetStart _ BasicTime.GetClockPulses[]; UNTIL done OR (dg _ UDP.Receive[handle, i*1000*baseTimeout]) = NIL DO packetStop _ BasicTime.GetClockPulses[]; milliseconds _ BasicTime.PulsesToMicroseconds[packetStop-packetStart]/1000; udp _ LOOPHOLE[@dg.data]; domain _ LOOPHOLE[@udp.data]; IF dg.inHdr.source # server THEN IPNameSupport.LogRope[IO.PutFR["Strange source address: expected %G, found %G = %G: \n", [rope[AddressToRope[server]]], [rope[AddressToRope[dg.inHdr.source]]], [rope[MyAddressToName[dg.inHdr.source]]]]]; done _ ProcessReply[udp, domain, dg.inHdr.source, queryAddress]; IPNameCache.AddToCounter[server, milliseconds]; IPNameSupport.LogRope[IO.PutFR["%G for %G took %G ms via %G.\n", [rope[what]], [rope[name]], [integer[milliseconds]], [rope[MyAddressToName[server]]]]]; dg _ NIL; ENDLOOP; IF ~done THEN IPNameCache.AddToLost[server]; ENDLOOP; IF ~done THEN {IPNameSupport.LogRope[IO.PutFR["%T ** No response to %G for %G via %G.\n", [time[BasicTime.Now[]]], [rope[what]], [rope[name]], [rope[MyAddressToName[server]]]]]; [] _ IPNameCache.UpdateDownServer[server, downTTL]; }; END; UDP.Destroy[handle]; ENDLOOP; IF ~done THEN { IPNameSupport.LogRope[IO.PutFR["%T ** No response to %G for %G from any server.\n", [time[BasicTime.Now[]]], [rope[what]], [rope[name]]]]; }; }; ServerIsDown: SIGNAL = CODE; LikelyValidReply: PROC [name: ROPE, source: IPDefs.Address, bogus: BOOL _TRUE] RETURNS[ok: BOOL] = { zone: ZoneEntry; servers: LIST OF ROPE _ NIL; addresses: LIST OF IPDefs.Address _ NIL; IF bogus AND IPNameCache.NameLookup[name] # NIL THEN RETURN[FALSE]; IF bogus AND IPNameCache.MXLookup[name] # NIL THEN RETURN[FALSE]; IF bogus AND IPNameCache.AliasLookup[name] # NIL THEN RETURN[FALSE]; zone _ IPNameCache.ZoneLookup[name]; IF zone = NIL THEN zone _ IPNameCache.ZoneLookup[BestZoneFromName[name]]; IF zone = NIL THEN RETURN[TRUE]; addresses _ ServerListToAddressList[zone.servers]; IF ~IPNameSupport.AddressInList[addresses, source] THEN RETURN[FALSE]; RETURN[TRUE]; }; ServerListToAddressList: PROC [servers: LIST OF ROPE] RETURNS[answers: LIST OF IPDefs.Address _ NIL] = { FOR list: LIST OF ROPE _ servers, list.rest UNTIL list = NIL DO serverName: ROPE _ list.first; serverEntry: NameEntry _ IPNameCache.NameLookup[serverName]; IF serverEntry = NIL THEN LOOP; FOR addresses: LIST OF IPDefs.Address _ serverEntry.addresses, addresses.rest UNTIL addresses = NIL DO answers _ IPNameSupport.MakeAddressInList[answers, addresses.first]; ENDLOOP; ENDLOOP; }; BestZoneFromName: PROC [name: ROPE] RETURNS[ROPE] = { startPos: INT _ 0; nameLength: INT; IF name = NIL THEN RETURN[NIL]; nameLength _ name.Length[]; FOR i: INT IN [0..nameLength) DO IF Rope.Fetch[name, i] = '. THEN {startPos _ i+1; EXIT;}; ENDLOOP; RETURN[Rope.Substr[name, startPos, nameLength-startPos]]; }; TopLevelZone: PROC [name: ROPE] RETURNS[ROPE] = { startPos: INT _ 0; nameLength: INT; IF name = NIL THEN RETURN[NIL]; nameLength _ name.Length[]; FOR i: INT DECREASING IN [0..nameLength) DO IF Rope.Fetch[name, i] = '. THEN {startPos _ i+1; EXIT;}; ENDLOOP; RETURN[Rope.Substr[name, startPos, nameLength]]; }; ChildZone: PROC [name: ROPE, parentZone: ROPE_ NIL] RETURNS[ROPE] = { startPos: INT _ 0; nameLength: INT; parentLength: INT; IF name = NIL THEN RETURN[NIL]; IF parentZone = NIL THEN RETURN[TopLevelZone[name]]; nameLength _ name.Length[]; parentLength _ parentZone.Length[]; FOR i: INT DECREASING IN [0..nameLength-parentLength-1) DO IF Rope.Fetch[name, i] = '. THEN {startPos _ i+1; EXIT;}; ENDLOOP; RETURN[Rope.Substr[name, startPos, nameLength]]; }; SelfMX: PROC [mxEntry: MXEntry, name: ROPE] RETURNS[yes: BOOL_ FALSE] = { IF mxEntry = NIL OR name = NIL THEN RETURN[FALSE]; FOR list: LIST OF IPNameCache.MXHostInfo _ mxEntry.mxHosts, list.rest UNTIL list = NIL DO IF Rope.Equal[list.first.host, name, FALSE] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; ProcessReply: PROC [ -- Smashes udp.length udp: LONG POINTER TO UDP.BodyRec, domain: LONG POINTER TO DomainHeader, source: IPDefs.Address, queryAddress: IPDefs.Address_ IPDefs.nullAddress] RETURNS[done: BOOLEAN_TRUE] = TRUSTED { ENABLE {UNWIND => NULL; IPNameUdp.EndOfData => GOTO Quit}; qname: ROPE; qtype: Type; qclass: Class; udp.length _ UDP.minLength + DomainHeader.SIZE*2; IF domain.qr # response THEN RETURN[FALSE]; IF domain.tc THEN RETURN[FALSE]; SELECT domain.rcode FROM ok => NULL; format => RETURN[TRUE]; nameNotFound => { name: ROPE _ IPNameUdp.GetName[udp]; IF LikelyValidReply[name, source] AND domain.aa THEN InsertBogusName[source, bogusTTL, domain.aa, name] ELSE RETURN[FALSE]; RETURN[TRUE]; }; serverFailed, notImplemented, refused => SIGNAL ServerIsDown; ENDCASE => ERROR; FOR i: INT IN [0..domain.qdCount) DO qname _ IPNameUdp.GetName[udp]; qtype _ IPNameUdp.GetTwoBytes[udp]; qclass _ IPNameUdp.GetTwoBytes[udp]; ENDLOOP; IF domain.anCount = 0 THEN { IF qtype = mx AND IPNameCache.ZoneLookup[qname] = NIL AND LikelyValidReply[qname, source, FALSE] THEN { SELECT TRUE FROM domain.nsCount # 0 => RETURN[FALSE]; -- server is confused ! Try another server. domain.nsCount = 0 => MergeMXInfo[source, mxTTL, domain.aa, qname, qname, 0]; ENDCASE; }; IF qtype = a AND IPNameCache.ZoneLookup[qname] # NIL AND LikelyValidReply[qname, source, TRUE] AND domain.aa THEN {InsertBogusName[source, bogusTTL, domain.aa, qname]; RETURN[TRUE];}; {mxEntry: MXEntry _ IPNameCache.MXLookup[qname]; IF qtype = a AND SelfMX[mxEntry, qname] AND LikelyValidReply[qname, source, FALSE] THEN RETURN[FALSE]; -- server is confused! Try another server. }; }; FOR i: INT IN [0..domain.anCount) DO name: ROPE _ IPNameUdp.GetName[udp]; type: Type _ IPNameUdp.GetTwoBytes[udp]; class: Class _ IPNameUdp.GetTwoBytes[udp]; ttl: INT _ IPNameUdp.GetTtl[udp]; rDataLength: CARDINAL _ IPNameUdp.GetTwoBytes[udp]; SELECT TRUE FROM type = a AND class = in => { address: IPDefs.Address _ IPNameUdp.GetIPAddress[udp]; MergeNameInfo[source, ttl, domain.aa, name, address]; }; type = cName AND class = in => { alias: ROPE _ name; real: ROPE _ IPNameUdp.GetName[udp]; MergeAliasInfo[source, ttl, domain.aa, real, alias]; }; type = mx AND class = in => { preference: INT _ IPNameUdp.GetCardinal[udp]; host: ROPE _ IPNameUdp.GetName[udp]; MergeMXInfo[source, ttl, domain.aa, name, host, preference]}; type = ns AND class = in => { zone: ROPE _ name; server: ROPE _ IPNameUdp.GetName[udp]; IF Rope.Length[zone] # 0 THEN MergeServerInfo[source, ttl, domain.aa, zone, server]; }; type = ptr AND class = in => { nameForAddress: ROPE _ IPNameUdp.GetName[udp]; MergeAddressInfo[source, ttl, domain.aa, queryAddress, nameForAddress]; }; ENDCASE => udp.length _ udp.length + rDataLength; ENDLOOP; FOR i: INT IN [0..domain.nsCount) DO name: ROPE _ IPNameUdp.GetName[udp]; type: Type _ IPNameUdp.GetTwoBytes[udp]; class: Class _ IPNameUdp.GetTwoBytes[udp]; ttl: INT _ IPNameUdp.GetTtl[udp]; rDataLength: CARDINAL _ IPNameUdp.GetTwoBytes[udp]; SELECT TRUE FROM type = ns AND class = in => { zone: ROPE _ name; server: ROPE _ IPNameUdp.GetName[udp]; IF Rope.Length[zone] # 0 THEN MergeServerInfo[source, ttl, FALSE, zone, server]; }; ENDCASE => udp.length _ udp.length + rDataLength; ENDLOOP; FOR i: INT IN [0..domain.arCount) DO name: ROPE _ IPNameUdp.GetName[udp]; type: Type _ IPNameUdp.GetTwoBytes[udp]; class: Class _ IPNameUdp.GetTwoBytes[udp]; ttl: INT _ IPNameUdp.GetTtl[udp]; rDataLength: CARDINAL _ IPNameUdp.GetTwoBytes[udp]; SELECT TRUE FROM type = a AND class = in => { address: IPDefs.Address _ IPNameUdp.GetIPAddress[udp]; MergeNameInfo[source, ttl, FALSE, name, address]; }; type = cName AND class = in => { alias: ROPE _ name; real: ROPE _ IPNameUdp.GetName[udp]; MergeAliasInfo[source, ttl, FALSE, real, alias]; }; type = mx AND class = in => { preference: INT _ IPNameUdp.GetCardinal[udp]; host: ROPE _ IPNameUdp.GetName[udp]; MergeMXInfo[source, ttl, FALSE, name, host, preference]}; type = ns AND class = in => { zone: ROPE _ name; server: ROPE _ IPNameUdp.GetName[udp]; IF Rope.Length[zone] # 0 THEN MergeServerInfo[source, ttl, FALSE, zone, server]; }; ENDCASE => udp.length _ udp.length + rDataLength; ENDLOOP; EXITS Quit => RETURN[FALSE]; }; MergeNameInfo: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, name: ROPE, address: IPDefs.Address] = { entry: NameEntry _ IPNameCache.UpdateName[name, source, ttl, authoritative]; IF entry = NIL THEN RETURN; IPNameCache.AddAddressToName[entry, address]; IPNameSupport.NameSummaryLine[entry, TRUE]; }; MergeAliasInfo: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, name, alias: ROPE] = { entry: AliasEntry _ IPNameCache.UpdateAlias[alias, source, ttl, authoritative]; IF entry = NIL THEN RETURN; IPNameCache.AddNameToAlias[entry, name]; IPNameSupport.AliasSummaryLine[entry, TRUE]; }; MergeAddressInfo: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, address: IPDefs.Address, name: ROPE] = { entry: AddressEntry _ IPNameCache.UpdateAddress[address, source, ttl, authoritative]; IF entry = NIL THEN RETURN; IPNameCache.AddNameToAddress[entry, name]; IPNameSupport.AddressSummaryLine[entry, TRUE]; }; MergeServerInfo: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, zone, server: ROPE] = { now: BasicTime.GMT _ BasicTime.Now[]; entry: ZoneEntry _ IPNameCache.UpdateZone[zone, source, ttl, authoritative]; IF entry = NIL THEN RETURN; IPNameCache.AddServer[entry, server]; IPNameSupport.ZoneSummaryLine[entry, TRUE]; }; MergeMXInfo: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, name, host: ROPE, preference: INT] = { now: BasicTime.GMT _ BasicTime.Now[]; entry: MXEntry _ IPNameCache.UpdateMX[name, source, ttl, authoritative]; IF entry = NIL THEN RETURN; IPNameCache.AddHostToMX[entry, [host, preference]]; IPNameSupport.MXSummaryLine[entry, TRUE]; }; ProcessBogusAddress: PROC [bogus: IPDefs.Address] = { InsertBogusAddress[IPDefs.nullAddress, bogusTTL, FALSE, bogus]; }; InsertBogusAddress: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, bogus: IPDefs.Address] = { [] _ IPNameCache.UpdateBogusAddress[bogus, source, ttl, authoritative]; NULL; }; ProcessBogusName: PROC [bogus: ROPE] = { InsertBogusName[IPDefs.nullAddress, ttlDefault, FALSE, bogus]; }; InsertBogusName: PROC [ source: IPDefs.Address, ttl: INT, authoritative: BOOL, bogus: ROPE] = { [] _ IPNameCache.UpdateBogusName[bogus, source, ttl, authoritative]; NULL; }; END.  IPNameImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Hal Murray September 11, 1985 7:18:37 pm PDT John Larson, October 7, 1987 9:50:14 pm PDT -- Default Time To Lives (end in 777 so it's obvious where these come from) takeOld TRUE will avoid cache refresh needAddress TRUE causes extended lookup, all addresses loaded Return a sorted list of addresses for the name or NIL if the name is not found. Case is ignored. name is allowed to be an Internet address constant of the form "[a.b.c.d]". Return sorted list of mail forwarding hosts if MX entry exists. NIL otherwise. This code should really check for self (Xerox.com) in the mx list and do the right thing ... (see rfc974) For now, I'm assuming only one mail path into Xerox. Returns name of server responsible for data. Returns NIL if not in cache. Get an internet address (optionally preceded by spaces and tabs and surrounded by [ ]) from the input stream. Raises AddressError for illegal syntax. takeOld Boolean TRUE forces expedited query [NIL, FALSE, FALSE, FALSE, FALSE] => Don't know [NIL, FALSE, FALSE] => Don't know mumble.COM is better than mumble.ARPA mumble.ARPA is better than mumble-GW.ARPA mumble.ARPA is better than mumble-GATEWAY.ARPA (MARYLAND) mumble.ARPA is better than mumble.jumble.ARPA (F.ISI.ARPA) Brew up the list of addresses to try: First pass: use best address from each server Second pass: try back doors Don't use down server addresses Root server addresses not loaded yet (startup) Servers sometimes hand out bogus nameNotFounds, so must be careful; Name exists, but no address => mx, or zone, or server is confused ! Root of world is wired into to code - avoid bogus info Root of world is wired into to code - avoid bogus info Root of world is wired into to code - avoid bogus info Κ5¦˜šœ™Icodešœ Οmœ1™Jšœ žœ˜Jšœ žœ4˜BJšœžœX˜dJšœ˜Jšœ ˜ Jšœžœ”˜§Jšœ žœn˜}Jšœ žœ˜,šžœžœO˜XJ˜——šΟn œžœž˜šž˜JšœžœMž˜d—Jšžœ ˜Jšž˜J˜Jšžœžœžœ˜šžœžœžœžœ˜J˜—Jšœžœ˜J˜J™KJšœ žœ ˜Jšœžœ˜Jšœ žœ˜Jšœ žœ Οc;˜R˜J˜—Jšœ žœ˜*Jšœ žœ˜(Jšœ žœ˜$Jšœžœ˜2Jšœžœ˜0Jšœžœ˜.Jšœžœ!˜8Jšœžœ ˜6šœ žœ˜(J˜—JšΟtΠkt‘’‘œ‘˜KJš ‘ ’‘’‘’‘œ‘˜6J˜J˜šŸœžœžœ žœžœžœžœ˜zIcode2šœ%™%Lšœ=™=Jšœ˜Jšœ-ž œ˜:Jšžœžœžœžœ ˜JJšžœžœ#˜DJ˜JšœD˜DJ˜Jšžœžœžœ˜šžœžœ˜šžœžœž˜Jšœ žœ ˜Jšœžœ˜šœ žœ˜Jšœ2žœ˜7Jšžœ ˜—Jšžœ˜—J˜—šžœ˜šžœžœž˜šœžœ˜ J˜7JšœP˜PJšžœžœžœ˜Jšžœžœžœ˜šžœ žœžœ˜Jšœ*žœ˜/Jšœ žœ˜—šžœ žœ˜Jšœ9˜9š žœžœžœ5žœžœž˜YJšœ žœ˜$JšœG˜Gšžœ žœžœ˜Jšœ*žœ˜/Jšœ žœ˜—Jšžœ˜—J˜—Jšžœ žœžœ ˜#J˜—šœžœ˜Jšœ.˜.š žœžœžœ5žœžœž˜YJšœ žœ˜$JšœB˜Bšžœ žœžœ˜Jšœ*žœ˜/Jšœ žœ˜—Jšžœ˜—Jšžœ žœžœ˜ J˜—šœ žœ˜Jšœ2žœ˜7Jšžœ ˜—Jšžœ˜—J˜J˜—Jšžœ˜ Jšœ˜J˜J˜—šœ2žœw™¬J˜—š Ÿ œžœžœ žœžœžœ˜@Jšžœ žœžœžœ˜4Jšœžœ˜ Jšœžœžœ˜Jšœžœ˜Jšœžœ˜ J˜šžœ žœžœ ˜LJšœ˜Jšžœžœ!˜>Jš œžœžœ!žœžœ˜PJšžœžœžœžœžœžœžœ˜9J˜—šžœžœ#˜DJ˜J˜—Jšœ7žœ˜>Jšžœžœžœžœ˜J˜šžœ žœžœžœ˜Jšœ:˜:Jšœ*žœ˜/Jšžœ ˜J˜—J˜J˜šžœžœ˜J˜7Jšœ9žœ˜@šžœ žœžœžœ˜Jšœ:˜:Jšœ*žœ˜/Jšžœ ˜J˜—šžœ žœžœ˜Jšœ žœžœžœ%˜?š žœžœžœžœžœžœž˜DJšœ žœ˜Jšœ+žœ˜2šžœ žœžœ˜Jšœ<˜Jšžœ ˜J˜J˜—Jšœ7žœ˜=Jšžœžœžœ˜Jšžœžœžœžœ˜Jšžœžœžœ%˜9šžœ žœžœ˜Jšœ*žœ˜/Jšžœ˜—Jšžœžœžœ˜Jšžœžœ˜ Jšœ˜J˜—š Ÿ œžœžœžœžœ˜MJšœ˜Jšœ žœ˜Jšžœžœžœžœ˜1Jšœ2žœ˜8Jšžœ žœžœžœ˜3Jšœ*žœ˜/Jšœ˜š žœžœžœžœžœžœž˜HJšœ&˜&Jšžœ˜—Jšžœ˜ J˜—šŸœžœžœžœ˜HJšœ˜Jšžœžœžœžœ˜1Jšœ+˜+šžœ žœžœ˜Jšœ*žœ˜/Jšœ˜š žœžœžœžœžœžœž˜HJšœ&˜&Jšžœ˜ ——Jšžœ˜"J˜—š Ÿ œžœžœžœ žœ˜Išœ žœ˜"JšœK˜K—J˜J˜J˜—š Ÿœžœžœ žœžœ˜9Jšœ+™+Jšžœžœžœžœ˜IJ˜š Ÿœžœžœžœžœžœ˜AJšœžœž˜Jš f˜fJšœ ˜ Jšžœžœžœžœ˜J˜Jšœ%˜%Jš žœ%žœžœžœžœžœ˜Dšžœžœ˜Jšœ/˜/Jšœžœ˜ Jšžœ˜Jšœ˜J˜—Jšœ(˜(šžœžœ˜Jšœ/˜/Jšœžœ˜ Jšžœ˜Jšœ˜—J˜—J˜J˜JšœA˜AJš žœžœžœžœžœžœ˜,š žœ žœžœžœžœž œ˜FJ˜—J˜Jšžœžœžœžœ˜J˜JšœA˜AJš žœžœžœžœžœžœ˜,š žœ žœžœžœžœž œ˜FJ˜—šžœžœ˜Jšœ/˜/Jšœžœ˜ Jšžœ˜Jšœ˜—J˜Jšœ˜J˜J˜Jšžœžœžœžœ˜J˜JšœA˜AJš žœžœžœžœžœžœ˜,Jš žœ žœžœžœžœž œ˜FJ˜Jšœ/˜/Jšœžœ˜ šžœ˜J˜—Jšœ˜J˜J˜—šœ žœ  (˜?J˜—š Ÿ œžœžœ5žœžœžœ˜sJšœžœž˜Jšœ žœ˜Jšœ žœžœ˜Jšœžœžœ˜Jšœ!˜!J˜šžœžœžœ˜Jšœ žœ˜#Jšœ<˜<šœC˜Cšžœžœžœ˜JšœM˜MJšžœžœžœ˜—J˜—J˜šžœžœžœžœ˜(Jšœ/˜/Jšœžœ˜ Jšžœ˜—Jšœ˜J˜—šžœ˜Jšœ˜Jšœžœ˜J˜šžœžœ˜Jšœ>˜>šžœžœ˜Jšœ/˜/Jšœžœ˜ Jšžœ˜—Jšœ˜—J˜Jšžœžœ˜5šžœ˜Jšžœžœžœžœ˜0Jšžœ!žœžœžœ˜4Jšœ'˜'Jšžœ žœžœžœ˜Jšžœ"žœžœžœ˜5Jšžœ&žœžœžœ˜;Jšžœžœžœ˜'Jšœ˜J˜—Jšœ>˜>šœE˜Ešžœžœžœ˜JšœM˜MJšžœžœžœ˜—J˜—J˜šžœžœ˜Jšœ/˜/Jšœžœ˜ Jšžœ˜J˜—Jšœ˜Jšžœ žœžœ˜,Jšžœžœžœ 1˜SJšœ˜Jšžœ˜J˜—Jšœ˜J˜—š Ÿœžœžœžœžœ˜GJšžœ žœžœ žœžœžœžœ˜1Jšžœ žœžœ žœžœžœžœ˜2Jšžœžœžœžœ˜2Jšžœ+žœžœžœ˜@Jš žœ%žœžœžœžœ˜AJš žœ4žœžœžœžœ˜PJšžœžœ˜ Jšœ˜—J˜J˜šŸœžœžœ žœ˜1Jšžœžœ žœžœžœžœ žœžœžœžœ˜oJš œžœžœž œžœ™/J™J˜J˜J˜J˜J˜J˜Jšœ%˜%šžœ žœžœ˜Jšžœ žœ$˜4Jšœ˜—J˜Jšœ%˜%šžœ žœžœ˜Jšžœ žœ&˜6Jšžœ žœžœž   ˜Bšžœ˜Jšœžœ˜ Jšœ$žœ˜*—Jšœ˜—J˜J˜J˜+šžœžœžœ˜Jšžœ žœ/˜?Jš žœžœžœž œ ˜Ešž˜Jšœžœ˜ Jšœ0žœ˜6—Jšœ˜J˜—J˜/šžœžœžœ˜Jšžœ žœ3˜CJš žœžœžœž œ  ˜Hšžœ˜Jšœžœ˜ Jšœ0žœ˜6—Jšœ˜J˜—J˜-šžœ žœžœ˜Jšžœ žœ0˜@Jš žœ žœžœž œ ˜=šžœ˜Jšœžœ˜ Jšœ,žœ˜2—J˜—Jšœ˜J˜—šŸœžœžœžœ˜JJšœžœ˜ˆJšœ˜J˜—šŸœžœ$žœ˜@Jš žœžœ žœžœžœžœ˜NJšœžœžœžœ™!Jšœžœžœžœ˜2Jšœžœžœ˜Jšœ žœ˜0J˜Jšœ+˜+J˜šžœ žœžœ˜JšœH˜Hšžœžœžœ˜šžœ žœ˜Jšœ5˜5Jšžœžœžœžœžœžœžœ  #˜]——šžœžœžœ˜Jšœ6žœ˜;Jš žœžœžœžœ  $˜D——šžœ žœžœ˜šžœ žœ˜Jšœ&˜&Jšžœ žœžœžœžœžœžœ  ˜S——Jš žœ žœžœžœ $˜@šžœ žœžœ˜JšœE˜EJšžœ žœžœ3˜JJš žœ žœžœžœ ˜>šžœ žœžœ˜Jšœ2žœ˜7Jšžœžœžœžœ˜J˜——J˜0Jšžœžœžœ˜J˜šžœžœ˜Jšœ5˜5Jšžœ˜ —J˜Jšœ>˜>šžœžœ˜Jšœ5˜5Jšœžœ˜ Jšžœ˜Jšœ˜—J˜Jšœ+˜+Jš $˜$šœ˜J˜——š Ÿ œžœ žœžœžœ˜9Jšœ ˜ šžœžœ˜Jšœ˜Jšœ&˜&J˜——š Ÿ œžœžœžœ žœ˜žœžœžœ˜SJšžœžœ˜J˜—š Ÿœžœžœžœ žœ˜9Jšœ žœ˜$Jšœ žœ˜$Jšœžœ˜ Jšžœžœžœžœ˜/Jšœ<˜J˜J™.š žœžœžœ1žœžœž˜UJšžœ,žœžœžœ˜˜>Jšœ5˜5J˜šžœžœ˜%Jšœ*žœ˜/Jšžœ ˜—J˜Jšœ3 ˜BJšžœžœ˜J˜—šŸœž œžœ˜LJšœžœ˜%Jšœžœ2˜:Jšžœžœ4žœ ˜hJšœA˜AJšœ5˜5Jšžœžœ-žœžœ ˜gJšœ3 ˜BJšžœžœ˜J˜J˜—šŸœž œžœ˜CJšœžœ˜%Jšœžœ2˜:Jšžœžœ1žœ ˜eJšœ:˜:Jšœ5˜5Jšžœžœ-žœžœ ˜gJšœ3 ˜BJšžœžœ˜J˜J˜—šŸœž œžœ˜XJšœžœ˜%Jšœžœ2˜:Jšžœžœ8žœ ˜lJšœJ˜JJšœ5˜5Jšžœžœ-žœžœ ˜gJšœ3 ˜BJšžœžœ˜J˜—šŸœž œžœ˜UJšœžœ˜%Jšœžœ2˜:Jšžœžœžœ˜2Jšžœžœ ˜,J˜—šŸœž œžœ˜RJšœžœ˜%Jšœžœ2˜:Jšžœžœ6žœ ˜jJšœj˜jJšœ5˜5Jšžœžœ-žœžœ ˜gJšœ3 ˜BJšžœžœ˜J˜—šŸœž œžœ˜aJšœžœ˜%Jšœžœ2˜:Jšžœžœ;žœ ˜oJšœq˜qJšœ5˜5Jšžœžœ-žœžœ ˜gJšœ3 ˜BJšžœžœ˜J˜—šŸœž œžœ˜^Jšœžœ˜%Jšœžœ2˜:Jšžœžœžœ˜2Jšžœžœ˜J˜—šŸœž œžœ˜IJšœžœ˜%Jšœžœ2˜:Jšžœžœžœ2žœ ˜~Jšœ>˜>Jšœ5˜5Jšžœžœ-žœžœ ˜gJšœ3 ˜BJšžœžœ˜J˜J˜Jšœ žœ /˜HJšœ žœ ˜1J˜J˜—šŸœž œ˜Jš œžœžœRžœžœžœžœ˜Jšœ*˜*Jšœžœžœ˜Jšœžœ$žœ˜6Jšœ žœžœ$˜4š žœžœžœ%žœžœžœž˜QJšœ$˜$Jšœžœ˜Jšœ žœžœžœ ˜IJšœ&˜&šžœžœ˜Jšœžœ˜₯Jšžœ˜ —š žœžœžœžœž˜.Jšœžœ˜/Jš œžœžœžœžœ žœ ˜6Jš œžœžœžœžœ ˜;J˜Jšœ˜Jšœ žœžœ˜1Jšžœžœžœ ˜=Jšžœ žœžœ ˜6Jšžœžœ˜$Jšœ4˜4Jšžœ˜!Jšœ)˜)J˜š žœžœžœ(žœž˜EJšœ(˜(JšœK˜KJšœžœ ˜Jšœ žœ ˜šžœž˜ Jšœžœ@˜XJšœ˜Jšœ'˜'Jšœ,˜,—Jšœ@˜@Jšœ/˜/Jšœžœ€˜˜Jšœžœ˜ Jšžœ˜—Jšžœžœ˜,Jšžœ˜—šžœžœžœŠ˜±Jšœ6˜6—Jšžœ˜Jšžœ˜Jšžœ˜—šžœžœ˜Jšœžœr˜ŠJ˜Jšœ˜—Jšœ˜J˜—JšŸ œžœžœ˜J˜šŸœžœžœ!žœžœžœžœ˜dJšœ˜Jš œ žœžœžœžœ˜Jšœ žœžœžœ˜(Jš žœžœ žœžœžœžœ˜CJš žœžœžœžœžœžœ˜AJš žœžœ!žœžœžœžœ˜DJšœ$˜$Jšžœžœžœ7˜IJšžœžœž œžœ˜ Jšœ2˜2Jšžœ1žœžœžœ˜FJšžœžœ˜ Jšœ˜J˜—šŸœžœ žœžœžœžœ žœžœžœ˜hš žœžœžœžœžœžœž˜?Jšœ žœ˜Jšœ=˜=Jšžœžœžœžœ˜š žœ žœžœ8žœ žœž˜fJšœD˜DJšžœ˜—Jšžœ˜—Jšœ˜—J˜š Ÿœžœžœžœžœ˜5Jšœ žœ˜Jšœ žœ˜Jš žœžœžœžœžœ˜Jšœ˜šžœžœžœž˜ Jšžœžœžœ˜9Jšžœ˜ —Jšžœ3˜9Jšœ˜J˜—š Ÿ œžœžœžœžœ˜1Jšœ žœ˜Jšœ žœ˜Jš žœžœžœžœžœ˜Jšœ˜šžœžœž œž˜+Jšžœžœžœ˜9Jšžœ˜ —Jšžœ*˜0Jšœ˜J˜—š Ÿ œžœžœž œžœžœ˜EJšœ žœ˜Jšœ žœ˜Jšœžœ˜Jš žœžœžœžœžœ˜Jšžœžœžœžœ˜4Jšœ˜Jšœ žœ˜#š žœžœž œžœ ž˜:Jšžœžœžœ˜9Jšžœ˜ —Jšžœ*˜0Jšœ˜J˜—š Ÿœžœžœžœžœžœ˜IJšžœ žœžœžœžœžœžœ˜2š žœžœžœ5žœžœž˜YJš žœ#žœžœžœžœ˜>Jšžœ˜—Jšžœžœ˜J˜Jšœ˜J˜J˜—J˜šŸ œžœ ˜*Jš œžœžœžœžœ ˜!Jšœžœžœžœ˜%Jš œJžœžœžœžœ˜qJšžœžœžœžœ˜;Jšœžœ˜ Jšœ ˜ Jšœ˜Jšœ žœžœ˜1Jšžœžœžœžœ˜+Jšžœ žœžœžœ˜ šžœž˜Jšœžœ˜ Jšœ žœžœ˜šœ˜Jšœžœ˜$JšœC™Cšžœ žœ žœ˜5Jšœ2˜2—Jšžœžœžœ˜Jšžœžœ˜—Jšœ)žœ˜=Jšžœžœ˜J˜—šžœžœžœžœ˜&Jšœ˜Jšœ#˜#Jšœ$˜$Jšžœ˜J˜—šžœžœ˜JšœC™CJ™š žœ žœ!žœžœ!žœžœ˜gšžœžœž˜Jšœžœžœ +˜QJšœM˜MJšžœ˜—šœ˜J˜——Jšžœ žœ!žœžœ!žœžœ žœ7˜¨Jšžœžœ˜J˜šœ0˜0Jšžœ žœžœ!žœžœžœžœ ,˜”J˜—Jšœ˜J˜—šžœžœžœž˜$Jšœžœ˜$Jšœ(˜(Jšœ*˜*Jšœžœ˜!Jšœ žœ˜3J˜šžœžœž˜šœ žœ˜Jšœ6˜6Jšœ8˜8—šœ žœ˜ Jšœžœ˜Jšœžœ˜$Jšœ7˜7—šœ žœ˜Jšœ žœ˜-Jšœžœ˜$Jšœ=˜=—šœ žœ˜Jšœžœ˜Jšœžœ˜&šžœž˜™6Jšœ9˜9———šœ žœ˜Jšœžœ˜.JšœJ˜J—Jšžœ*˜1—Jšžœ˜—šžœžœžœž˜$Jšœžœ˜$Jšœ(˜(Jšœ*˜*Jšœžœ˜!Jšœ žœ˜3šžœžœž˜šœ žœ˜Jšœžœ˜Jšœžœ˜&šžœž˜J™6Jšœžœ˜5——Jšžœ*˜1—Jšžœ˜—šžœžœžœž˜$Jšœžœ˜$Jšœ(˜(Jšœ*˜*Jšœžœ˜!Jšœ žœ˜3šžœžœž˜šœ žœ˜Jšœ6˜6Jšœžœ˜4šœ žœ˜ Jšœžœ˜Jšœžœ˜$Jšœžœ˜3—šœ žœ˜Jšœ žœ˜-Jšœžœ˜$Jšœžœ˜9—šœ žœ˜Jšœžœ˜Jšœžœ˜&šžœž˜™6Jšœžœ˜5————Jšžœ*˜1—Jšžœ˜—šžœ˜Jšœžœžœ˜—Jšœ˜J˜—šŸ œžœ˜Jšœžœžœžœ˜_JšœL˜LJšžœ žœžœžœ˜Jšœ-˜-Jšœ%žœ˜/—šŸœžœ˜Jšœžœžœžœ˜MJšœO˜OJšžœ žœžœžœ˜Jšœ(˜(Jšœ&žœ˜0—šŸœžœ˜Jšœžœžœ!žœ˜_JšœU˜UJšžœ žœžœžœ˜Jšœ*˜*Jšœ(žœ˜2—šŸœžœ˜Jšœžœžœžœ˜NJšœžœ˜%JšœL˜LJšžœ žœžœžœ˜Jšœ%˜%Jšœ%žœ˜/—šŸ œžœ˜Jš œžœžœžœžœ˜]Jšœžœ˜%JšœH˜HJšžœ žœžœžœ˜Jšœ3˜3Jšœ#žœ˜,J˜—šŸœžœ˜5Jšœ1žœ ˜B—J˜šŸœžœ˜Jšœžœžœ˜QJšœG˜GJšžœ˜ —J˜šŸœžœ žœ˜(Jšœ0žœ ˜A—J˜šŸœžœ˜Jšœžœžœ žœ˜GJšœD˜DJšžœ˜ J˜—Jšžœ˜J˜J˜J˜—J˜—…—žVΫ