registry.ha (3642B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use net; 5 use net::dns; 6 use unix::hosts; 7 8 // Returned if the address parameter was invalid, for example if it specifies an 9 // invalid port number. 10 export type invalid_address = !void; 11 12 // Returned if the service parameter does not name a service known to the 13 // system. 14 export type unknown_service = !void; 15 16 // Errors which can occur from dial. 17 export type error = !(invalid_address | unknown_service | net::error | dns::error 18 | hosts::error); 19 20 // Converts an [[error]] to a human-readable string. The result may be 21 // statically allocated. 22 export fn strerror(err: error) const str = { 23 // TODO: These could be better 24 match (err) { 25 case invalid_address => 26 return "Attempted to dial an invalid address"; 27 case unknown_service => 28 return "Unknown service"; 29 case let err: net::error => 30 return net::strerror(err); 31 case let err: dns::error => 32 return dns::strerror(err); 33 case let err: hosts::error => 34 return hosts::strerror(err); 35 }; 36 }; 37 38 // A dialer is a function which implements dial for a specific protocol. 39 export type dialer = fn(addr: str, service: str) (net::socket | error); 40 41 type protocol = struct { 42 name: str, 43 dial: *dialer, 44 }; 45 46 type service = struct { 47 proto: str, 48 name: str, 49 alias: []str, 50 port: u16, 51 }; 52 53 let default_protocols: [_]protocol = [ 54 protocol { name = "tcp", dial = &dial_tcp }, 55 protocol { name = "udp", dial = &dial_udp }, 56 ]; 57 58 let default_services: [_]service = [ 59 service { proto = "tcp", name = "ssh", alias = [], port = 22 }, 60 service { proto = "tcp", name = "smtp", alias = ["mail"], port = 25 }, 61 service { proto = "tcp", name = "domain", alias = ["dns"], port = 53 }, 62 service { proto = "tcp", name = "http", alias = ["www"], port = 80 }, 63 service { proto = "tcp", name = "imap2", alias = ["imap"], port = 143 }, 64 service { proto = "tcp", name = "https", alias = [], port = 443 }, 65 service { proto = "tcp", name = "submission", alias = [], port = 587 }, 66 service { proto = "tcp", name = "imaps", alias = [], port = 993 }, 67 service { proto = "udp", name = "domain", alias = ["dns"], port = 53 }, 68 service { proto = "udp", name = "ntp", alias = [], port = 123 }, 69 ]; 70 71 let protocols: []protocol = []; 72 let services: []service = []; 73 74 @fini fn fini() void = { 75 free(protocols); 76 free(services); 77 }; 78 79 // Registers a new transport-level protocol (e.g. TCP) with the dialer. The name 80 // should be statically allocated. 81 export fn registerproto(name: str, dial: *dialer) void = { 82 append(protocols, protocol { 83 name = name, 84 dial = dial, 85 }); 86 }; 87 88 // Registers a new application-level service (e.g. SSH) with the dialer. Note 89 // that the purpose of services is simply to establish the default outgoing 90 // port for TCP and UDP connections. The name and alias list should be 91 // statically allocated. 92 export fn registersvc( 93 proto: str, 94 name: str, 95 alias: []str, 96 port: u16, 97 ) void = { 98 append(services, service { 99 proto = proto, 100 name = name, 101 alias = alias, 102 port = port, 103 }); 104 }; 105 106 fn lookup_service(proto: str, service: str) (u16 | void) = { 107 for (let i = 0z; i < len(default_services); i += 1) { 108 const serv = &default_services[i]; 109 if (service_match(serv, proto, service)) { 110 return serv.port; 111 }; 112 }; 113 114 for (let i = 0z; i < len(services); i += 1) { 115 const serv = &services[i]; 116 if (service_match(serv, proto, service)) { 117 return serv.port; 118 }; 119 }; 120 }; 121 122 fn service_match(candidate: *service, proto: str, service: str) bool = { 123 if (candidate.name == service) { 124 return true; 125 }; 126 for (let j = 0z; j < len(candidate.alias); j += 1) { 127 if (candidate.alias[j] == service) { 128 return true; 129 }; 130 }; 131 return false; 132 };