X-Git-Url: http://git.meshlink.io/?a=blobdiff_plain;f=src%2Fmeshlink.c;h=e0c11942b3f9219c0560bb531bdf8b61a0a0099f;hb=7eb75de1abcdce12a17b096edc06c787ad00a8dc;hp=d9da584aadf9b75c6c254eac73317c5cf475225f;hpb=6c68a325b927c1d27a55930e92dcb4e32eb12432;p=meshlink diff --git a/src/meshlink.c b/src/meshlink.c index d9da584a..e0c11942 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -16,12 +16,432 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define VAR_SERVER 1 /* Should be in tinc.conf */ +#define VAR_HOST 2 /* Can be in host config file */ +#define VAR_MULTIPLE 4 /* Multiple statements allowed */ +#define VAR_OBSOLETE 8 /* Should not be used anymore */ +#define VAR_SAFE 16 /* Variable is safe when accepting invitations */ +typedef struct { + const char *name; + int type; +} var_t; #include "system.h" +#include +#include "crypto.h" +#include "ecdsagen.h" #include "meshlink_internal.h" +#include "node.h" #include "protocol.h" +#include "route.h" #include "xalloc.h" +#include "ed25519/sha512.h" + +static char meshlink_conf[PATH_MAX]; +static char hosts_dir[PATH_MAX]; + +static int sock; +static sptps_t sptps; +static char cookie[18], hash[18]; +static char *data = NULL; +static size_t thedatalen = 0; +static bool success = false; +static char line[4096]; +static char buffer[4096]; +static size_t blen = 0; + +const var_t variables[] = { + /* Server configuration */ + {"AddressFamily", VAR_SERVER}, + {"AutoConnect", VAR_SERVER | VAR_SAFE}, + {"BindToAddress", VAR_SERVER | VAR_MULTIPLE}, + {"BindToInterface", VAR_SERVER}, + {"Broadcast", VAR_SERVER | VAR_SAFE}, + {"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE}, + {"DecrementTTL", VAR_SERVER}, + {"Device", VAR_SERVER}, + {"DeviceType", VAR_SERVER}, + {"DirectOnly", VAR_SERVER}, + {"ECDSAPrivateKeyFile", VAR_SERVER}, + {"ExperimentalProtocol", VAR_SERVER}, + {"Forwarding", VAR_SERVER}, + {"GraphDumpFile", VAR_SERVER | VAR_OBSOLETE}, + {"Hostnames", VAR_SERVER}, + {"IffOneQueue", VAR_SERVER}, + {"Interface", VAR_SERVER}, + {"KeyExpire", VAR_SERVER}, + {"ListenAddress", VAR_SERVER | VAR_MULTIPLE}, + {"LocalDiscovery", VAR_SERVER}, + {"MACExpire", VAR_SERVER}, + {"MaxConnectionBurst", VAR_SERVER}, + {"MaxOutputBufferSize", VAR_SERVER}, + {"MaxTimeout", VAR_SERVER}, + {"Mode", VAR_SERVER | VAR_SAFE}, + {"Name", VAR_SERVER}, + {"PingInterval", VAR_SERVER}, + {"PingTimeout", VAR_SERVER}, + {"PriorityInheritance", VAR_SERVER}, + {"PrivateKey", VAR_SERVER | VAR_OBSOLETE}, + {"PrivateKeyFile", VAR_SERVER}, + {"ProcessPriority", VAR_SERVER}, + {"Proxy", VAR_SERVER}, + {"ReplayWindow", VAR_SERVER}, + {"ScriptsExtension", VAR_SERVER}, + {"ScriptsInterpreter", VAR_SERVER}, + {"StrictSubnets", VAR_SERVER}, + {"TunnelServer", VAR_SERVER}, + {"VDEGroup", VAR_SERVER}, + {"VDEPort", VAR_SERVER}, + /* Host configuration */ + {"Address", VAR_HOST | VAR_MULTIPLE}, + {"Cipher", VAR_SERVER | VAR_HOST}, + {"ClampMSS", VAR_SERVER | VAR_HOST}, + {"Compression", VAR_SERVER | VAR_HOST}, + {"Digest", VAR_SERVER | VAR_HOST}, + {"ECDSAPublicKey", VAR_HOST}, + {"ECDSAPublicKeyFile", VAR_SERVER | VAR_HOST}, + {"IndirectData", VAR_SERVER | VAR_HOST}, + {"MACLength", VAR_SERVER | VAR_HOST}, + {"PMTU", VAR_SERVER | VAR_HOST}, + {"PMTUDiscovery", VAR_SERVER | VAR_HOST}, + {"Port", VAR_HOST}, + {"PublicKey", VAR_HOST | VAR_OBSOLETE}, + {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"Subnet", VAR_HOST | VAR_MULTIPLE | VAR_SAFE}, + {"TCPOnly", VAR_SERVER | VAR_HOST}, + {"Weight", VAR_HOST | VAR_SAFE}, + {NULL, 0} +}; + +static char *get_line(const char **data) { + if(!data || !*data) + return NULL; + + if(!**data) { + *data = NULL; + return NULL; + } + + static char line[1024]; + const char *end = strchr(*data, '\n'); + size_t len = end ? end - *data : strlen(*data); + if(len >= sizeof line) { + fprintf(stderr, "Maximum line length exceeded!\n"); + return NULL; + } + if(len && !isprint(**data)) + abort(); + + memcpy(line, *data, len); + line[len] = 0; + + if(end) + *data = end + 1; + else + *data = NULL; + + return line; +} + +static char *get_value(const char *data, const char *var) { + char *line = get_line(&data); + if(!line) + return NULL; + + char *sep = line + strcspn(line, " \t="); + char *val = sep + strspn(sep, " \t"); + if(*val == '=') + val += 1 + strspn(val + 1, " \t"); + *sep = 0; + if(strcasecmp(line, var)) + return NULL; + return val; +} +static FILE *fopenmask(const char *filename, const char *mode, mode_t perms) { + mode_t mask = umask(0); + perms &= ~mask; + umask(~perms); + FILE *f = fopen(filename, mode); +#ifdef HAVE_FCHMOD + if((perms & 0444) && f) + fchmod(fileno(f), perms); +#endif + umask(mask); + return f; +} + +static bool finalize_join(meshlink_handle_t *mesh) { + char *name = xstrdup(get_value(data, "Name")); + if(!name) { + fprintf(stderr, "No Name found in invitation!\n"); + return false; + } + + if(!check_id(name)) { + fprintf(stderr, "Invalid Name found in invitation: %s!\n", name); + return false; + } + + if(mkdir(mesh->confbase, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno)); + return false; + } + + if(mkdir(hosts_dir, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); + return false; + } + + FILE *f = fopen(meshlink_conf, "w"); + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", meshlink_conf, strerror(errno)); + return false; + } + + fprintf(f, "Name = %s\n", name); + + char filename[PATH_MAX]; + snprintf(filename,PATH_MAX, "%s" SLASH "%s", hosts_dir, name); + FILE *fh = fopen(filename, "w"); + if(!fh) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + return false; + } + + // Filter first chunk on approved keywords, split between tinc.conf and hosts/Name + // Other chunks go unfiltered to their respective host config files + const char *p = data; + char *l, *value; + + while((l = get_line(&p))) { + // Ignore comments + if(*l == '#') + continue; + + // Split line into variable and value + int len = strcspn(l, "\t ="); + value = l + len; + value += strspn(value, "\t "); + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + l[len] = 0; + + // Is it a Name? + if(!strcasecmp(l, "Name")) + if(strcmp(value, name)) + break; + else + continue; + else if(!strcasecmp(l, "NetName")) + continue; + + // Check the list of known variables + bool found = false; + int i; + for(i = 0; variables[i].name; i++) { + if(strcasecmp(l, variables[i].name)) + continue; + found = true; + break; + } + + // Ignore unknown and unsafe variables + if(!found) { + fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l); + continue; + } else if(!(variables[i].type & VAR_SAFE)) { + fprintf(stderr, "Ignoring unsafe variable '%s' in invitation.\n", l); + continue; + } + + // Copy the safe variable to the right config file + fprintf(variables[i].type & VAR_HOST ? fh : f, "%s = %s\n", l, value); + } + + fclose(f); + + while(l && !strcasecmp(l, "Name")) { + if(!check_id(value)) { + fprintf(stderr, "Invalid Name found in invitation.\n"); + return false; + } + + if(!strcmp(value, name)) { + fprintf(stderr, "Secondary chunk would overwrite our own host config file.\n"); + return false; + } + + snprintf(filename,PATH_MAX, "%s" SLASH "%s", hosts_dir, value); + f = fopen(filename, "w"); + + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + return false; + } + + while((l = get_line(&p))) { + if(!strcmp(l, "#---------------------------------------------------------------#")) + continue; + int len = strcspn(l, "\t ="); + if(len == 4 && !strncasecmp(l, "Name", 4)) { + value = l + len; + value += strspn(value, "\t "); + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + l[len] = 0; + break; + } + + fputs(l, f); + fputc('\n', f); + } + + fclose(f); + } + + // Generate our key and send a copy to the server + ecdsa_t *key = ecdsa_generate(); + if(!key) + return false; + + char *b64key = ecdsa_get_base64_public_key(key); + if(!b64key) + return false; + + snprintf(filename,PATH_MAX, "%s" SLASH "ecdsa_key.priv", mesh->confbase); + f = fopenmask(filename, "w", 0600); + + if(!ecdsa_write_pem_private_key(key, f)) { + fprintf(stderr, "Error writing private key!\n"); + ecdsa_free(key); + fclose(f); + return false; + } + + fclose(f); + + fprintf(fh, "ECDSAPublicKey = %s\n", b64key); + + sptps_send_record(&sptps, 1, b64key, strlen(b64key)); + free(b64key); + + ecdsa_free(key); + + check_port(name); + + fprintf(stderr, "Configuration stored in: %s\n", mesh->confbase); + + return true; +} + +static bool invitation_send(void *handle, uint8_t type, const char *data, size_t len) { + while(len) { + int result = send(sock, data, len, 0); + if(result == -1 && errno == EINTR) + continue; + else if(result <= 0) + return false; + data += result; + len -= result; + } + return true; +} + +static bool invitation_receive(void *handle, uint8_t type, const char *msg, uint16_t len) { + switch(type) { + case SPTPS_HANDSHAKE: + return sptps_send_record(&sptps, 0, cookie, sizeof cookie); + + case 0: + data = xrealloc(data, thedatalen + len + 1); + memcpy(data + thedatalen, msg, len); + thedatalen += len; + data[thedatalen] = 0; + break; + + case 1: + return finalize_join(NULL);//TODO: wrong, we have to pass the mesh handler here, but how ? + + case 2: + fprintf(stderr, "Invitation succesfully accepted.\n"); + shutdown(sock, SHUT_RDWR); + success = true; + break; + + default: + return false; + } + + return true; +} + +static bool recvline(int fd, char *line, size_t len) { + char *newline = NULL; + + if(!fd) + abort(); + + while(!(newline = memchr(buffer, '\n', blen))) { + int result = recv(fd, buffer + blen, sizeof buffer - blen, 0); + if(result == -1 && errno == EINTR) + continue; + else if(result <= 0) + return false; + blen += result; + } + + if(newline - buffer >= len) + return false; + + len = newline - buffer; + + memcpy(line, buffer, len); + line[len] = 0; + memmove(buffer, newline + 1, blen - len - 1); + blen -= len + 1; + + return true; +} +static bool sendline(int fd, char *format, ...) { + static char buffer[4096]; + char *p = buffer; + int blen = 0; + va_list ap; + + va_start(ap, format); + blen = vsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + + if(blen < 1 || blen >= sizeof buffer) + return false; + + buffer[blen] = '\n'; + blen++; + + while(blen) { + int result = send(fd, p, blen, MSG_NOSIGNAL); + if(result == -1 && errno == EINTR) + continue; + else if(result <= 0) + return false; + p += result; + blen -= result; + } + + return true; +} +int rstrip(char *value) { + int len = strlen(value); + while(len && strchr("\t\r\n ", value[len - 1])) + value[--len] = 0; + return len; +} + static const char *errstr[] = { [MESHLINK_OK] = "No error", @@ -33,11 +453,153 @@ const char *meshlink_strerror(meshlink_errno_t errno) { return errstr[errno]; } -static meshlink_handle_t *meshlink_setup(meshlink_handle_t *mesh) { - return mesh; +static bool ecdsa_keygen(meshlink_handle_t *mesh) { + ecdsa_t *key; + FILE *f; + char pubname[PATH_MAX], privname[PATH_MAX]; + + fprintf(stderr, "Generating ECDSA keypair:\n"); + + if(!(key = ecdsa_generate())) { + fprintf(stderr, "Error during key generation!\n"); + return false; + } else + fprintf(stderr, "Done.\n"); + + snprintf(privname, sizeof privname, "%s" SLASH "ecdsa_key.priv", mesh->confbase); + f = fopen(privname, "w"); + + if(!f) + return false; + +#ifdef HAVE_FCHMOD + fchmod(fileno(f), 0600); +#endif + + if(!ecdsa_write_pem_private_key(key, f)) { + fprintf(stderr, "Error writing private key!\n"); + ecdsa_free(key); + fclose(f); + return false; + } + + fclose(f); + + + snprintf(pubname, sizeof pubname, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->name); + f = fopen(pubname, "a"); + + if(!f) + return false; + + char *pubkey = ecdsa_get_base64_public_key(key); + fprintf(f, "ECDSAPublicKey = %s\n", pubkey); + free(pubkey); + + fclose(f); + ecdsa_free(key); + + return true; +} + +static bool try_bind(int port) { + struct addrinfo *ai = NULL; + struct addrinfo hint = { + .ai_flags = AI_PASSIVE, + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + + char portstr[16]; + snprintf(portstr, sizeof portstr, "%d", port); + + if(getaddrinfo(NULL, portstr, &hint, &ai) || !ai) + return false; + + while(ai) { + int fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP); + if(!fd) + return false; + int result = bind(fd, ai->ai_addr, ai->ai_addrlen); + closesocket(fd); + if(result) + return false; + ai = ai->ai_next; + } + + return true; +} + +int check_port(meshlink_handle_t *mesh) { + if(try_bind(655)) + return 655; + + fprintf(stderr, "Warning: could not bind to port 655.\n"); + + for(int i = 0; i < 100; i++) { + int port = 0x1000 + (rand() & 0x7fff); + if(try_bind(port)) { + char filename[PATH_MAX]; + snprintf(filename, sizeof filename, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->name); + FILE *f = fopen(filename, "a"); + if(!f) { + fprintf(stderr, "Please change MeshLink's Port manually.\n"); + return 0; + } + + fprintf(f, "Port = %d\n", port); + fclose(f); + fprintf(stderr, "MeshLink will instead listen on port %d.\n", port); + return port; + } + } + + fprintf(stderr, "Please change MeshLink's Port manually.\n"); + return 0; +} + +static bool meshlink_setup(meshlink_handle_t *mesh) { + + if(mkdir(mesh->confbase, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno)); + return false; + } + + snprintf(hosts_dir, sizeof hosts_dir, "%s" SLASH "hosts", mesh->confbase); + + if(mkdir(hosts_dir, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); + return false; + } + + snprintf(meshlink_conf, sizeof meshlink_conf, "%s" SLASH "meshlink.conf", mesh->confbase); + + if(!access(meshlink_conf, F_OK)) { + fprintf(stderr, "Configuration file %s already exists!\n", meshlink_conf); + return false; + } + + FILE *f = fopen(meshlink_conf, "w"); + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", meshlink_conf, strerror(errno)); + return 1; + } + + fprintf(f, "Name = %s\n", mesh->name); + fclose(f); + + if(!ecdsa_keygen(mesh)) + return false; + + check_port(mesh); + + return true; } meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { + // Validate arguments provided by the application + if(!confbase || !*confbase) { fprintf(stderr, "No confbase given!\n"); return NULL; @@ -56,72 +618,84 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name) { meshlink_handle_t *mesh = xzalloc(sizeof *mesh); mesh->confbase = xstrdup(confbase); mesh->name = xstrdup(name); + event_loop_init(&mesh->loop); + mesh->loop.data = mesh; + + // TODO: should be set by a function. + mesh->debug_level = 5; + + // Check whether meshlink.conf already exists char filename[PATH_MAX]; snprintf(filename, sizeof filename, "%s" SLASH "meshlink.conf", confbase); - FILE *f = fopen(filename, "r"); + if(access(filename, R_OK)) { + if(errno == ENOENT) { + // If not, create it + meshlink_setup(mesh); + } else { + fprintf(stderr, "Cannot not read from %s: %s\n", filename, strerror(errno)); + return meshlink_close(mesh), NULL; + } + } - if(!f && errno == ENOENT) - return meshlink_setup(mesh); + // Read the configuration - if(!f) { - fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); - return meshlink_close(mesh), NULL; - } + init_configuration(&mesh->config); - char buf[1024] = ""; - if(!fgets(buf, sizeof buf, f)) { - fprintf(stderr, "Could not read line from %s: %s\n", filename, strerror(errno)); - fclose(f); + if(!read_server_config(mesh)) return meshlink_close(mesh), NULL; - } - fclose(f); + // Setup up everything + // TODO: we should not open listening sockets yet - size_t len = strlen(buf); - if(len && buf[len - 1] == '\n') - buf[--len] = 0; - if(len && buf[len - 1] == '\r') - buf[--len] = 0; - - if(strncmp(buf, "Name = ", 7) || !check_id(buf + 7)) { - fprintf(stderr, "Could not read Name from %s\n", filename); + if(!setup_network(mesh)) return meshlink_close(mesh), NULL; - } - if(strcmp(buf + 7, name)) { - fprintf(stderr, "Name in %s is %s, not the same as %s\n", filename, buf + 7, name); - free(mesh->name); - mesh->name = xstrdup(buf + 7); - } + return mesh; +} - snprintf(filename, sizeof filename, "%s" SLASH "ed25519_key.priv", mesh->confbase); - f = fopen(filename, "r"); - if(!f) { - fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); - return meshlink_close(mesh), NULL; - } +void *meshlink_main_loop(void *arg) { + meshlink_handle_t *mesh = arg; - mesh->self->ecdsa = ecdsa_read_pem_private_key(f); - fclose(f); + try_outgoing_connections(mesh); - if(!mesh->self->ecdsa) { - fprintf(stderr, "Could not read keypair!\n"); - return meshlink_close(mesh), NULL; - } + main_loop(mesh); - return mesh; + return NULL; } bool meshlink_start(meshlink_handle_t *mesh) { - return false; + // TODO: open listening sockets first + + // Start the main thread + + if(pthread_create(&mesh->thread, NULL, meshlink_main_loop, mesh) != 0) { + fprintf(stderr, "Could not start thread: %s\n", strerror(errno)); + memset(&mesh->thread, 0, sizeof mesh->thread); + return false; + } + + return true; } void meshlink_stop(meshlink_handle_t *mesh) { + // TODO: close the listening sockets to signal the main thread to shut down + + // Wait for the main thread to finish + + pthread_join(mesh->thread, NULL); } void meshlink_close(meshlink_handle_t *mesh) { + // Close and free all resources used. + + close_network_connections(mesh); + + logger(DEBUG_ALWAYS, LOG_NOTICE, "Terminating"); + + exit_configuration(&mesh->config); + event_loop_exit(&mesh->loop); } void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb) { @@ -138,10 +712,29 @@ void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, me } bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, unsigned int len) { + vpn_packet_t packet; + meshlink_packethdr_t *hdr = (meshlink_packethdr_t *)packet.data; + if (sizeof(meshlink_packethdr_t) + len > MAXSIZE) { + //log something + return false; + } + + packet.probe = false; + memset(hdr, 0, sizeof *hdr); + memcpy(hdr->destination, destination->name, sizeof hdr->destination); + memcpy(hdr->source, mesh->self->name, sizeof hdr->source); + + packet.len = sizeof *hdr + len; + memcpy(packet.data + sizeof *hdr, data, len); + + mesh->self->in_packets++; + mesh->self->in_bytes += packet.len; + route(mesh, mesh->self, &packet); return false; } meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) { + return (meshlink_node_t *)lookup_node(mesh, name); return NULL; } @@ -162,6 +755,154 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { } bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { + + + // Make sure confbase exists and is accessible. + if(mkdir(mesh->confbase, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno)); + return 1; + } + + if(access(mesh->confbase, R_OK | W_OK | X_OK)) { + fprintf(stderr, "No permission to write in directory %s: %s\n", mesh->confbase, strerror(errno)); + return 1; + } + + // TODO: Either remove or reintroduce netname in meshlink + // If a netname or explicit configuration directory is specified, check for an existing meshlink.conf. + //if((mesh->netname || confbasegiven) && !access(meshlink_conf, F_OK)) { + // fprintf(stderr, "Configuration file %s already exists!\n", meshlink_conf); + // return 1; + //} + + char *slash = strchr(invitation, '/'); + if(!slash) + goto invalid; + + *slash++ = 0; + + if(strlen(slash) != 48) + goto invalid; + + char *address = invitation; + char *port = NULL; + if(*address == '[') { + address++; + char *bracket = strchr(address, ']'); + if(!bracket) + goto invalid; + *bracket = 0; + if(bracket[1] == ':') + port = bracket + 2; + } else { + port = strchr(address, ':'); + if(port) + *port++ = 0; + } + + if(!mesh->myport || !*port) + port = "655"; + + if(!b64decode(slash, hash, 18) || !b64decode(slash + 24, cookie, 18)) + goto invalid; + + // Generate a throw-away key for the invitation. + ecdsa_t *key = ecdsa_generate(); + if(!key) + return 1; + + char *b64key = ecdsa_get_base64_public_key(key); + + // Connect to the meshlink daemon mentioned in the URL. + struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM); + if(!ai) + return 1; + + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if(sock <= 0) { + fprintf(stderr, "Could not open socket: %s\n", strerror(errno)); + return 1; + } + + if(connect(sock, ai->ai_addr, ai->ai_addrlen)) { + fprintf(stderr, "Could not connect to %s port %s: %s\n", address, port, strerror(errno)); + closesocket(sock); + return 1; + } + + fprintf(stderr, "Connected to %s port %s...\n", address, port); + + // Tell him we have an invitation, and give him our throw-away key. + int len = snprintf(invitation, sizeof invitation, "0 ?%s %d.%d\n", b64key, PROT_MAJOR, PROT_MINOR); + if(len <= 0 || len >= sizeof invitation) + abort(); + + if(!sendline(sock, "0 ?%s %d.%d", b64key, PROT_MAJOR, 1)) { + fprintf(stderr, "Error sending request to %s port %s: %s\n", address, port, strerror(errno)); + closesocket(sock); + return 1; + } + + char hisname[4096] = ""; + int code, hismajor, hisminor = 0; + + if(!recvline(sock, line, sizeof line) || sscanf(line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(sock, line, sizeof line) || !rstrip(line) || sscanf(line, "%d ", &code) != 1 || code != ACK || strlen(line) < 3) { + fprintf(stderr, "Cannot read greeting from peer\n"); + closesocket(sock); + return 1; + } + + // Check if the hash of the key he gave us matches the hash in the URL. + char *fingerprint = line + 2; + char hishash[64]; + if(!sha512(fingerprint, strlen(fingerprint), hishash)) { + fprintf(stderr, "Could not create hash\n%s\n", line + 2); + return 1; + } + if(memcmp(hishash, hash, 18)) { + fprintf(stderr, "Peer has an invalid key!\n%s\n", line + 2); + return 1; + + } + + ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint); + if(!hiskey) + return 1; + + // Start an SPTPS session + if(!sptps_start(&sptps, NULL, true, false, key, hiskey, "meshlink invitation", 15, invitation_send, invitation_receive)) + return 1; + + // Feed rest of input buffer to SPTPS + if(!sptps_receive_data(&sptps, buffer, blen)) + return 1; + + while((len = recv(sock, line, sizeof line, 0))) { + if(len < 0) { + if(errno == EINTR) + continue; + fprintf(stderr, "Error reading data from %s port %s: %s\n", address, port, strerror(errno)); + return 1; + } + + if(!sptps_receive_data(&sptps, line, len)) + return 1; + } + + sptps_stop(&sptps); + ecdsa_free(hiskey); + ecdsa_free(key); + closesocket(sock); + + if(!success) { + fprintf(stderr, "Connection closed by peer, invitation cancelled.\n"); + return false; + } + + return true; + +invalid: + fprintf(stderr, "Invalid invitation URL.\n"); return false; } @@ -176,3 +917,10 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) { } +static void __attribute__((constructor)) meshlink_init(void) { + crypto_init(); +} + +static void __attribute__((destructor)) meshlink_exit(void) { + crypto_exit(); +}