/** * Constructs a path leading to the chosen server settings directory. Used * internally by file_path_player() and file_path_server(). * @return * */ static StringBuffer *file_path_server_internal(void) { StringBuffer *sb; sb = stringbuffer_new(); stringbuffer_append_string(sb, "settings/"); SOFT_ASSERT_RC(selected_server != NULL, sb, "Selected server is NULL."); SOFT_ASSERT_RC(!string_isempty(selected_server->hostname), sb, "Selected server has empty hostname."); stringbuffer_append_printf(sb, "servers/%s-%d/", selected_server->hostname, selected_server->port); return sb; }
/** * Get absolute path to the specified relative path. This path will typically * point to the to client data directory (which is usually located in the user's * home/appdata directory), but depending on the specified mode, extra actions * may be performed. These ensure that if you're trying to access a file that * does not yet exist in the client data directory, it will be read from the * client installation directory instead (unless it's being appended to, in * which case it will be copied to the client data directory first). * * Generally, you should almost always use this when you need to construct a * path, or use one of the many @ref file_wrapper_functions. * @param fname * The file path. * @param mode * File mode. * @return * The absolute path. Must be freed. */ char *file_path(const char *path, const char *mode) { bool is_write, is_append; StringBuffer *sb; char version[MAX_BUF], client_path[HUGE_BUF], *new_path; HARD_ASSERT(path != NULL); HARD_ASSERT(mode != NULL); SOFT_ASSERT_RC(path[0] != '/', estrdup(path), "Path is already absolute: %s", path); sb = stringbuffer_new(); stringbuffer_append_printf(sb, "%s/.atrinik/%s/%s", get_config_dir(), package_get_version_partial(VS(version)), path); new_path = stringbuffer_sub(sb, 0, 0); is_write = is_append = false; if (strchr(mode, 'w') != NULL) { is_write = true; } else if (strchr(mode, '+') != NULL || strchr(mode, 'a') != NULL) { is_append = true; } if (is_write || is_append) { if (access(new_path, W_OK) != 0) { char *dirname; /* Ensure directories exist if we're going to use this path for * writing/appending. */ dirname = path_dirname(new_path); mkdir_recurse(dirname); efree(dirname); if (is_append) { get_data_dir_file(VS(client_path), path); copy_file(client_path, new_path); } } } else { if (access(new_path, R_OK) != 0) { get_data_dir_file(VS(client_path), path); stringbuffer_seek(sb, 0); stringbuffer_append_string(sb, client_path); } } efree(new_path); return stringbuffer_finish(sb); }
/** * Verify resolved address of a server against the server's metaserver * certificate. * * @param server * Server to verify. * @param host * Host address. * @return * True if the resolved address is equal to the one in the certificate, * false otherwise. */ bool metaserver_cert_verify_host (server_struct *server, const char *host) { HARD_ASSERT(server != NULL); HARD_ASSERT(host != NULL); /* No certificate, nothing to verify. */ if (server->cert_info == NULL) { return true; } struct sockaddr_storage addr; SOFT_ASSERT_RC(socket_host2addr(host, &addr), false, "Failed to convert host to IP address"); switch (addr.ss_family) { case AF_INET: if (strcmp(host, server->cert_info->ipv4_address) != 0) { LOG(ERROR, "!!! Certificate IPv4 address error: %s != %s !!!", host, server->cert_info->ipv4_address); return false; } break; case AF_INET6: if (strcmp(host, server->cert_info->ipv6_address) != 0) { LOG(ERROR, "!!! Certificate IPv6 address error: %s != %s !!!", host, server->cert_info->ipv6_address); return false; } break; default: LOG(ERROR, "!!! Unknown address family %u !!!", addr.ss_family); return false; } return true; }
/** * Parse the metaserver certificate information. * * @param server * Metaserver entry. * @return * True on success, false on failure. */ static bool parse_metaserver_cert (server_struct *server) { HARD_ASSERT(server != NULL); if (server->cert == NULL || server->cert_sig == NULL) { /* No certificate. */ return true; } /* Generate a SHA512 hash of the certificate's contents. */ unsigned char cert_digest[SHA512_DIGEST_LENGTH]; if (SHA512((unsigned char *) server->cert, strlen(server->cert), cert_digest) == NULL) { LOG(ERROR, "SHA512() failed: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } char cert_hash[SHA512_DIGEST_LENGTH * 2 + 1]; SOFT_ASSERT_RC(string_tohex(VS(cert_digest), VS(cert_hash), false) == sizeof(cert_hash) - 1, false, "string_tohex failed"); string_tolower(cert_hash); /* Verify the signature. */ if (!curl_verify(CURL_PKEY_TRUST_ULTIMATE, cert_hash, strlen(cert_hash), server->cert_sig, server->cert_sig_len)) { LOG(ERROR, "Failed to verify signature"); return false; } server_cert_info_t *info = ecalloc(1, sizeof(*info)); char buf[MAX_BUF]; size_t pos = 0; bool in_info = false; while (string_get_word(server->cert, &pos, '\n', VS(buf), 0)) { char *cp = buf; string_skip_whitespace(cp); string_strip_newline(cp); if (*cp == '\0') { continue; } if (strcmp(cp, cert_begin_str) == 0) { in_info = true; continue; } else if (!in_info) { continue; } else if (strcmp(cp, cert_end_str) == 0) { break; } char *cps[2]; if (string_split(cp, cps, arraysize(cps), ':') != arraysize(cps)) { LOG(ERROR, "Parsing error"); continue; } string_tolower(cps[0]); string_skip_whitespace(cps[1]); const char *key = cps[0]; const char *value = cps[1]; char **content = NULL; if (strcmp(key, "name") == 0) { content = &info->name; } else if (strcmp(key, "hostname") == 0) { content = &info->hostname; } else if (strcmp(key, "ipv4 address") == 0) { content = &info->ipv4_address; } else if (strcmp(key, "ipv6 address") == 0) { content = &info->ipv6_address; } else if (strcmp(key, "public key") == 0) { content = &info->pubkey; } else if (strcmp(key, "port") == 0) { info->port = atoi(value); } else if (strcmp(key, "crypto port") == 0) { info->port_crypto = atoi(value); } else { LOG(DEVEL, "Unrecognized key: %s", key); continue; } if (content != NULL) { StringBuffer *sb = stringbuffer_new(); if (*content != NULL) { stringbuffer_append_string(sb, *content); stringbuffer_append_char(sb, '\n'); efree(*content); } stringbuffer_append_string(sb, value); *content = stringbuffer_finish(sb); } } /* Ensure we got the data we need. */ if (info->name == NULL || info->hostname == NULL || info->pubkey == NULL || info->port_crypto <= 0 || (info->ipv4_address == NULL) != (info->ipv6_address == NULL)) { LOG(ERROR, "Certificate is missing required data."); goto error; } /* Ensure certificate attributes match the advertised ones. */ if (strcmp(info->hostname, server->hostname) != 0) { LOG(ERROR, "Certificate hostname does not match advertised hostname."); goto error; } if (strcmp(info->name, server->name) != 0) { LOG(ERROR, "Certificate name does not match advertised name."); goto error; } if (info->port != server->port) { LOG(ERROR, "Certificate port does not match advertised port."); goto error; } if (info->port_crypto != server->port_crypto) { LOG(ERROR, "Certificate crypto port does not match advertised crypto port."); goto error; } server->cert_info = info; return true; error: metaserver_cert_free(info); return false; }