/* Compile server client functions Copyright (C) 2010-2018 Red Hat Inc. This file is part of systemtap, and is free software. You can redistribute it and/or modify it under the terms of the GNU General Public License (GPL); either version 2, or (at your option) any later version. */ // Completely disable the client if NSS is not available. #include "config.h" #if HAVE_NSS #include "session.h" #include "cscommon.h" #include "csclient.h" #include "client-nss.h" #include "util.h" #include "stap-probe.h" #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include #include #include #include } #if HAVE_AVAHI extern "C" { #include #include #include #include #include #include } #endif // HAVE_AVAHI extern "C" { #include #include #include #include #include #include #include #include } #include "nsscommon.h" using namespace std; static PRIPv6Addr ©Address (PRIPv6Addr &PRin6, const in6_addr &in6); static PRNetAddr ©NetAddr (PRNetAddr &x, const PRNetAddr &y); bool operator!= (const PRNetAddr &x, const PRNetAddr &y); bool operator== (const PRNetAddr &x, const PRNetAddr &y); // Information about compile servers. compile_server_info::compile_server_info () : port(0), fully_specified(false) { memset (& address, 0, sizeof (address)); } bool compile_server_info::empty () const { return (this->host_name.empty () && ! this->hasAddress () && certinfo.empty ()); } bool compile_server_info::hasAddress () const { return this->address.raw.family != 0; } unsigned short compile_server_info::setAddressPort (unsigned short port) { if (this->address.raw.family == PR_AF_INET) return this->address.inet.port = htons (port); if (this->address.raw.family == PR_AF_INET6) return this->address.ipv6.port = htons (port); assert (0); return 0; } bool compile_server_info::isComplete () const { return this->hasAddress () && port != 0; } string compile_server_info::host_specification () const { ostringstream host_spec; if (!this->isComplete()) return host_spec.str(); if (! this->host_name.empty ()) host_spec << this->host_name; else if (this->hasAddress()) { PRStatus prStatus; switch (this->address.raw.family) { case PR_AF_INET: case PR_AF_INET6: { #define MAX_NETADDR_SIZE 46 // from the NSPR API reference. char buf[MAX_NETADDR_SIZE]; prStatus = PR_NetAddrToString(&this->address, buf, sizeof (buf)); if (prStatus == PR_SUCCESS) { host_spec << buf; break; } } break; default: // Do nothing. break; } } if (! host_spec.str().empty()) host_spec << ":" << this->port; return host_spec.str(); } bool compile_server_info::operator== (const compile_server_info &that) const { // If one item or the other has only a name, and possibly a port // specified, then allow a match by name and port only. This is so // that the user can specify names which are returned by avahi, but // are not dns resolvable. Otherwise, we will ignore the host_name. if ((! this->hasAddress() && this->version.empty () && this->sysinfo.empty () && this->certinfo.empty ()) || (! that.hasAddress() && that.version.empty () && that.sysinfo.empty () && that.certinfo.empty ())) { if (this->host_name != that.host_name) return false; } // Compare the other fields only if they have both been set. if (this->hasAddress() && that.hasAddress() && this->address != that.address) return false; if (this->port != 0 && that.port != 0 && this->port != that.port) return false; if (! this->version.empty () && ! that.version.empty () && this->version != that.version) return false; if (! this->sysinfo.empty () && ! that.sysinfo.empty () && this->sysinfo != that.sysinfo) return false; if (! this->certinfo.empty () && ! that.certinfo.empty () && this->certinfo != that.certinfo) return false; if (! this->mok_fingerprints.empty () && ! that.mok_fingerprints.empty () && this->mok_fingerprints != that.mok_fingerprints) return false; return true; // They are equal } // Used to sort servers by preference for order of contact. The // preferred server is "less" than the other one. bool compile_server_info::operator< (const compile_server_info &that) const { // Prefer servers with a later (higher) version number. cs_protocol_version this_version (this->version.c_str ()); cs_protocol_version that_version (that.version.c_str ()); return that_version < this_version; } ostream &operator<< (ostream &s, const compile_server_info &i); ostream &operator<< (ostream &s, const vector &v); static void preferred_order (vector &servers) { // Sort the given list of servers into the preferred order for contacting. // Don't bother if there are less than 2 servers in the list. if (servers.size () < 2) return; // Sort the list using compile_server_info::operator< sort (servers.begin (), servers.end ()); } struct resolved_host // see also PR16326, PR16342 { string host_name; PRNetAddr address; resolved_host(string chost_name, PRNetAddr caddress): host_name(chost_name), address(caddress) {} }; struct compile_server_cache { vector default_servers; vector specified_servers; vector trusted_servers; vector signing_servers; vector online_servers; vector all_servers; map > resolved_hosts; }; // For filtering queries. enum compile_server_properties { compile_server_all = 0x1, compile_server_trusted = 0x2, compile_server_online = 0x4, compile_server_compatible = 0x8, compile_server_signer = 0x10, compile_server_specified = 0x20 }; // Static functions. static compile_server_cache* cscache(systemtap_session& s); static void query_server_status (systemtap_session &s, const string &status_string); static void get_server_info (systemtap_session &s, int pmask, vector &servers); static void get_default_server_info (systemtap_session &s, vector &servers); static void merge_server_info (const compile_server_info &source, compile_server_info &target); static void resolve_host (systemtap_session& s, compile_server_info &server, vector &servers); // ----------------------------------------------------- // NSS related code used by the compile server client // ----------------------------------------------------- static void add_server_trust (systemtap_session &s, const string &cert_db_path, vector &server_list); static void revoke_server_trust (systemtap_session &s, const string &cert_db_path, const vector &server_list); void get_server_info_from_db (systemtap_session &s, vector &servers, const string &cert_db_path); string global_client_cert_db_path () { return SYSCONFDIR "/systemtap/ssl/client"; } static string private_ssl_cert_db_path () { return local_client_cert_db_path (); } string global_ssl_cert_db_path () { return global_client_cert_db_path (); } string signing_cert_db_path () { return SYSCONFDIR "/systemtap/staprun"; } /* Connection state. */ typedef struct connectionState_t { const char *hostName; PRNetAddr addr; const char *infileName; const char *outfileName; const char *trustNewServerMode; } connectionState_t; /* Add the server's certificate to our database of trusted servers. */ SECStatus nss_trustNewServer (CERTCertificate *serverCert) { SECStatus secStatus; CERTCertTrust *trust = NULL; PK11SlotInfo *slot = NULL; /* Import the certificate. */ slot = PK11_GetInternalKeySlot(); const char *nickname = server_cert_nickname (); secStatus = PK11_ImportCert(slot, serverCert, CK_INVALID_HANDLE, nickname, PR_FALSE); if (secStatus != SECSuccess) goto done; /* Make it a trusted peer. */ trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust)); if (! trust) { secStatus = SECFailure; goto done; } secStatus = CERT_DecodeTrustString(trust, "P,P,P"); if (secStatus != SECSuccess) goto done; secStatus = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), serverCert, trust); done: if (slot) PK11_FreeSlot (slot); if (trust) PORT_Free(trust); return secStatus; } static bool isIPv6LinkLocal (const PRNetAddr &address) { // Link-local addresses are members of the address block fe80:: if (address.raw.family == PR_AF_INET6 && address.ipv6.ip.pr_s6_addr[0] == 0xfe && address.ipv6.ip.pr_s6_addr[1] == 0x80) return true; return false; } // Issue a status message for when a server's trust is already in place. static void trust_already_in_place ( const compile_server_info &server, const vector &server_list, const string cert_db_path, bool revoking ) { // What level of trust? string purpose; if (cert_db_path == signing_cert_db_path ()) purpose = _("as a module signer for all users"); else { purpose = _("as an SSL peer"); if (cert_db_path == global_ssl_cert_db_path ()) purpose += _(" for all users"); else purpose += _(" for the current user"); } // Issue a message for each server in the list with the same certificate. unsigned limit = server_list.size (); for (unsigned i = 0; i < limit; ++i) { if (server.certinfo != server_list[i].certinfo) continue; clog << server_list[i] << _(" is already "); if (revoking) clog << _("untrusted ") << purpose << endl; else clog << _("trusted ") << purpose << endl; } } // Add the given servers to the given database of trusted servers. static void add_server_trust ( systemtap_session &s, const string &cert_db_path, vector &server_list ) { // Get a list of servers already trusted. This opens the database, so do it // before we open it for our own purposes. vector already_trusted; get_server_info_from_db (s, already_trusted, cert_db_path); // Make sure the given path exists. if (create_dir (cert_db_path.c_str (), 0755) != 0) { clog << _F("Unable to find or create the client certificate database directory %s: ", cert_db_path.c_str()); perror (""); return; } // Must predeclare this because of jumps to cleanup: below. vector processed_certs; client_backend *backend = nss_get_client_backend (s); if (backend->initialize () != 0) return; // Make sure NSPR is initialized. Must be done before NSS is initialized s.NSPR_init (); // Initialize the NSS libraries -- read/write SECStatus secStatus = nssInit (cert_db_path.c_str (), 1/*readwrite*/); if (secStatus != SECSuccess) { // Message already issued. goto cleanup; } // Enable all cipher suites. // SSL_ClearSessionCache is required for the new settings to take effect. /* Some NSS versions don't do this correctly in NSS_SetDomesticPolicy. */ do { const PRUint16 *cipher; for (cipher = SSL_GetImplementedCiphers(); *cipher != 0; ++cipher) SSL_CipherPolicySet(*cipher, SSL_ALLOWED); } while (0); SSL_ClearSessionCache (); // Iterate over the servers to become trusted. Contact each one and // add it to the list of trusted servers if it is not already trusted. // trust_server_info() will issue any error messages. for (vector::iterator server = server_list.begin(); server != server_list.end (); ++server) { // Trust is based on certificates. We need only add trust in the // same certificate once. // // RHBZ 1075685: If the new server to be trusted is selected by address + port, // and there is no avahi assistance available, or the server is not known // to avahi, then its certificate serial number field will be empty. We // therefore have no basis for comparing it to the serial numbers on already-trusted // certificates. In this case, unconditionally contact the new server to obtain // its certificate. if (! server->certinfo.empty ()) { // We need not contact the server if it has already been processed. if (find (processed_certs.begin (), processed_certs.end (), server->certinfo) != processed_certs.end ()) continue; processed_certs.push_back (server->certinfo); // We need not contact the server if it is already trusted. if (find (already_trusted.begin (), already_trusted.end (), *server) != already_trusted.end ()) { if (s.verbose >= 2) trust_already_in_place (*server, server_list, cert_db_path, false/*revoking*/); continue; } } // At a minimum we need an ip_address along with a port // number in order to contact an nss server. if (s.http_servers.empty ()) { if (! server->hasAddress() || server->port == 0) continue; // Set the port within the address. server->setAddressPort (server->port); } int rc = backend->trust_server_info (*server); if (rc != NSS_SUCCESS) { // Notice no space before the '%s'? The compile_server_info // '<<' operator always outputs a space. clog << _F("Unable to connect to%s", lex_cast(*server).c_str()) << endl; nssError (); // Additional information: if the address is IPv6 and is link-local, then it must // have a scope_id. if (isIPv6LinkLocal (server->address) && server->address.ipv6.scope_id == 0) { clog << _(" The address is an IPv6 link-local address with no scope specifier.") << endl; } } } cleanup: // Shutdown NSS. // SSL_ClearSessionCache is required before shutdown for client applications. SSL_ClearSessionCache (); nssCleanup (cert_db_path.c_str (), NULL); // Make sure the database files are readable. glob_t globbuf; string filespec = cert_db_path + "/*.db"; if (s.verbose >= 3) clog << _F("Searching \"%s\"\n", filespec.c_str()); int r = glob (filespec.c_str (), 0, NULL, & globbuf); if (r != GLOB_NOSPACE && r != GLOB_ABORTED && r != GLOB_NOMATCH) { for (unsigned i = 0; i < globbuf.gl_pathc; ++i) { string filename = globbuf.gl_pathv[i]; if (s.verbose >= 3) clog << _(" found ") << filename << endl; if (chmod (filename.c_str (), 0644) != 0) { s.print_warning("Unable to change permissions on " + filename + ": "); perror (""); } } globfree(& globbuf); } } // Remove the given servers from the given database of trusted servers. static void revoke_server_trust ( systemtap_session &s, const string &cert_db_path, const vector &server_list ) { // Make sure the given path exists. if (! file_exists (cert_db_path)) { if (s.verbose >= 5) { clog << _F("Certificate database '%s' does not exist", cert_db_path.c_str()) << endl; for (vector::const_iterator server = server_list.begin(); server != server_list.end (); ++server) trust_already_in_place (*server, server_list, cert_db_path, true/*revoking*/); } return; } // Must predeclare these because of jumps to cleanup: below. CERTCertDBHandle *handle; PRArenaPool *tmpArena = NULL; CERTCertList *certs = NULL; CERTCertificate *db_cert; vector processed_certs; const char *nickname; SECStatus secStatus; // Make sure NSPR is initialized. Must be done before NSS is initialized s.NSPR_init (); // Initialize the NSS libraries -- read/write secStatus = nssInit (cert_db_path.c_str (), 1/*readwrite*/); if (secStatus != SECSuccess) { // Message already issued goto cleanup; } handle = CERT_GetDefaultCertDB(); // A memory pool to work in tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (! tmpArena) { clog << _("Out of memory:"); nssError (); goto cleanup; } // Iterate over the servers to become untrusted. nickname = server_cert_nickname (); for (vector::const_iterator server = server_list.begin(); server != server_list.end (); ++server) { // If the server's certificate serial number is unknown, then we can't // match it with one in the database. if (server->certinfo.empty ()) continue; // Trust is based on certificates. We need only revoke trust in the same // certificate once. if (find (processed_certs.begin (), processed_certs.end (), server->certinfo) != processed_certs.end ()) continue; processed_certs.push_back (server->certinfo); // Search the client-side database of trusted servers. db_cert = PK11_FindCertFromNickname (nickname, NULL); if (! db_cert) { // No trusted servers. Not an error, but issue a status message. if (s.verbose >= 2) trust_already_in_place (*server, server_list, cert_db_path, true/*revoking*/); continue; } // Here, we have one cert with the desired nickname. // Now, we will attempt to get a list of ALL certs // with the same subject name as the cert we have. That list // should contain, at a minimum, the one cert we have already found. // If the list of certs is empty (NULL), the libraries have failed. certs = CERT_CreateSubjectCertList (NULL, handle, & db_cert->derSubject, PR_Now (), PR_FALSE); CERT_DestroyCertificate (db_cert); if (! certs) { clog << _F("Unable to query certificate database %s: ", cert_db_path.c_str()) << endl; PORT_SetError (SEC_ERROR_LIBRARY_FAILURE); nssError (); goto cleanup; } // Find the certificate matching the one belonging to our server. CERTCertListNode *node; for (node = CERT_LIST_HEAD (certs); ! CERT_LIST_END (node, certs); node = CERT_LIST_NEXT (node)) { // The certificate we're working with. db_cert = node->cert; // Get the serial number. string serialNumber = get_cert_serial_number (db_cert); // Does the serial number match that of the current server? if (serialNumber != server->certinfo) continue; // goto next certificate // All is ok! Remove the certificate from the database. break; } // Loop over certificates in the database // Was a certificate matching the server found? */ if (CERT_LIST_END (node, certs)) { // Not found. Server is already untrusted. if (s.verbose >= 2) trust_already_in_place (*server, server_list, cert_db_path, true/*revoking*/); } else { secStatus = SEC_DeletePermCertificate (db_cert); if (secStatus != SECSuccess) { clog << _F("Unable to remove certificate from %s: ", cert_db_path.c_str()) << endl; nssError (); } } CERT_DestroyCertList (certs); certs = NULL; } // Loop over servers cleanup: assert(!certs); if (tmpArena) PORT_FreeArena (tmpArena, PR_FALSE); nssCleanup (cert_db_path.c_str (), NULL); } // Obtain information about servers from the certificates in the given database. void get_server_info_from_db ( systemtap_session &s, vector &servers, const string &cert_db_path ) { NSSInitContext *context; // Make sure the given path exists. if (! file_exists (cert_db_path)) { if (s.verbose >= 5) clog << _F("Certificate database '%s' does not exist.", cert_db_path.c_str()) << endl; return; } // Make sure NSPR is initialized. Must be done before NSS is initialized s.NSPR_init (); // Initialize the NSS libraries -- readonly context = nssInitContext (cert_db_path.c_str (), 0 /* readWrite */, 0 /* issueMessage */); if (context == NULL) { // Message already issued. return; } CERTCertList *certs = get_cert_list_from_db (server_cert_nickname ()); if (! certs) { if (s.verbose >= 5) clog << _F("No certificate found in database %s", cert_db_path.c_str ()) << endl; goto cleanup; } for (CERTCertListNode *node = CERT_LIST_HEAD (certs); ! CERT_LIST_END (node, certs); node = CERT_LIST_NEXT (node)) { compile_server_info server_info; // The certificate we're working with. CERTCertificate *db_cert = node->cert; if (get_host_name (db_cert, server_info.host_name) == false) continue; // Get the serial number. server_info.certinfo = get_cert_serial_number (db_cert); // Our results will at a minimum contain this server. nss_add_server_info (server_info, servers); // Augment the list by querying all online servers and keeping the ones // with the same cert serial number. vector online_servers; nss_get_or_keep_online_server_info (s, online_servers, false/*keep*/); nss_keep_server_info_with_cert_and_port (s, server_info, online_servers); nss_add_server_info (online_servers, servers); } cleanup: if (certs) CERT_DestroyCertList (certs); nssCleanup (cert_db_path.c_str (), context); } // Utility Functions. //----------------------------------------------------------------------- ostream &operator<< (ostream &s, const compile_server_info &i) { // Don't print empty information if (i.empty ()) return s; s << " host="; if (! i.host_name.empty ()) s << i.host_name; else s << "unknown"; s << " address="; if (i.hasAddress()) { PRStatus prStatus; switch (i.address.raw.family) { case PR_AF_INET: case PR_AF_INET6: { #define MAX_NETADDR_SIZE 46 // from the NSPR API reference. char buf[MAX_NETADDR_SIZE]; prStatus = PR_NetAddrToString(& i.address, buf, sizeof (buf)); if (prStatus == PR_SUCCESS) { s << buf; break; } } // Fall through default: s << "offline"; break; } } else s << "offline"; s << " port="; if (i.port != 0) s << i.port; else s << "unknown"; s << " sysinfo=\""; if (! i.sysinfo.empty ()) s << i.sysinfo << '"'; else s << "unknown\""; s << " version="; if (! i.version.empty ()) s << i.version; else s << "unknown"; s << " certinfo=\""; if (! i.certinfo.empty ()) s << i.certinfo << '"'; else s << "unknown\""; if (! i.mok_fingerprints.empty ()) { // FIXME: Yikes, this output is ugly. Perhaps the server output // needs a more structured approach. s << " mok_fingerprints=\""; vector::const_iterator it; for (it = i.mok_fingerprints.begin (); it != i.mok_fingerprints.end (); it++) { if (it != i.mok_fingerprints.begin ()) s << ", "; s << *it; } s << "\""; } return s; } ostream &operator<< (ostream &s, const vector &v) { // Indicate an empty list. if (v.size () == 0 || (v.size () == 1 && v[0].empty())) s << "No Servers" << endl; else { for (unsigned i = 0; i < v.size(); ++i) { // Don't print empty items. if (! v[i].empty()) s << v[i] << endl; } } return s; } PRNetAddr & copyNetAddr (PRNetAddr &x, const PRNetAddr &y) { PRUint32 saveScope = 0; // For IPv6 addresses, don't overwrite the scope_id of x unless x is uninitialized or it is 0. if (x.raw.family == PR_AF_INET6) saveScope = x.ipv6.scope_id; x = y; if (saveScope != 0) x.ipv6.scope_id = saveScope; return x; } bool operator== (const PRNetAddr &x, const PRNetAddr &y) { // Same address family? if (x.raw.family != y.raw.family) return false; switch (x.raw.family) { case PR_AF_INET6: // If both scope ids are set, compare them. if (x.ipv6.scope_id != 0 && y.ipv6.scope_id != 0 && x.ipv6.scope_id != y.ipv6.scope_id) return false; // not equal // Scope is not a factor. Compare the address bits return memcmp (& x.ipv6.ip, & y.ipv6.ip, sizeof(x.ipv6.ip)) == 0; case PR_AF_INET: return x.inet.ip == y.inet.ip; default: break; } return false; } bool operator!= (const PRNetAddr &x, const PRNetAddr &y) { return !(x == y); } static PRIPv6Addr & copyAddress (PRIPv6Addr &PRin6, const in6_addr &in6) { // The NSPR type is a typedef of struct in6_addr, but C++ won't let us copy it assert (sizeof (PRin6) == sizeof (in6)); memcpy (& PRin6, & in6, sizeof (PRin6)); return PRin6; } // Return the default server specification, used when none is given on the // command line. static string default_server_spec (const systemtap_session &s) { // If --privilege=X has been used, where X is not stapdev, // the default is online,trusted,compatible,signer // otherwise // the default is online,trusted,compatible // // Having said that, // 'online' and 'compatible' will only succeed if we have avahi // 'trusted' and 'signer' will only succeed if we have NSS // string working_string = "online,trusted,compatible"; if (! pr_contains (s.privilege, pr_stapdev)) working_string += ",signer"; return working_string; } static int server_spec_to_pmask (const string &server_spec) { // Construct a mask of the server properties that have been requested. // The available properties are: // trusted - servers which are trusted SSL peers. // online - online servers. // compatible - servers which compile for the current kernel release // and architecture. // signer - servers which are trusted module signers. // specified - servers which have been specified using --use-server=XXX. // If no servers have been specified, then this is // equivalent to --list-servers=trusted,online,compatible. // all - all trusted servers, trusted module signers, // servers currently online and specified servers. string working_spec = server_spec; vector properties; tokenize (working_spec, properties, ","); int pmask = 0; unsigned limit = properties.size (); for (unsigned i = 0; i < limit; ++i) { const string &property = properties[i]; // Tolerate (and ignore) empty properties. if (property.empty ()) continue; if (property == "all") { pmask |= compile_server_all; } else if (property == "specified") { pmask |= compile_server_specified; } else if (property == "trusted") { pmask |= compile_server_trusted; } else if (property == "online") { pmask |= compile_server_online; } else if (property == "compatible") { pmask |= compile_server_compatible; } else if (property == "signer") { pmask |= compile_server_signer; } else { // XXX PR13274 needs-session to use print_warning() clog << _F("WARNING: unsupported compile server property: %s", property.c_str()) << endl; } } return pmask; } void nss_client_query_server_status (systemtap_session &s) { unsigned limit = s.server_status_strings.size (); for (unsigned i = 0; i < limit; ++i) query_server_status (s, s.server_status_strings[i]); } static void query_server_status (systemtap_session &s, const string &status_string) { // If this string is empty, then the default is "specified" string working_string = status_string; if (working_string.empty ()) working_string = "specified"; // If the query is "specified" and no servers have been specified // (i.e. '--use-server' and/or '--use-http-server' not used or used // with no argument), then use the default query. // // TODO: This may not be necessary. The underlying queries should handle // "specified" properly. if (working_string == "specified" && (s.specified_servers.empty () || (s.specified_servers.size () == 1 && s.specified_servers[0].empty ())) && (s.http_servers.empty() || (s.http_servers.size () == 1 && s.http_servers[0].empty ()))) working_string = default_server_spec (s); int pmask = server_spec_to_pmask (working_string); // Now obtain a list of the servers which match the criteria. vector raw_servers; get_server_info (s, pmask, raw_servers); // Augment the listing with as much information as possible by adding // information from known servers. vector servers; nss_get_all_server_info (s, servers); nss_keep_common_server_info (raw_servers, servers); // Sort the list of servers into a preferred order. preferred_order (servers); // Print the server information. Skip the empty entry at the head of the list. clog << _F("Systemtap Compile Server Status for '%s'", working_string.c_str()) << endl; bool found = false; unsigned limit = servers.size (); for (unsigned i = 0; i < limit; ++i) { assert (! servers[i].empty ()); // Don't list servers with no cert information. They may not actually // exist. // TODO: Could try contacting the server and obtaining its cert if (servers[i].certinfo.empty ()) continue; clog << servers[i] << endl; found = true; } if (! found) clog << _("No servers found") << endl; } // Add or remove trust of the servers specified on the command line. void nss_client_manage_server_trust (systemtap_session &s) { // This function should do nothing if we don't have NSS. // Nothing to do if --trust-servers was not specified. if (s.server_trust_spec.empty ()) return; // Break up and analyze the trust specification. Recognized components are: // ssl - trust the specified servers as ssl peers // signer - trust the specified servers as module signers // revoke - revoke the requested trust // all-users - apply/revoke the requested trust for all users // no-prompt - don't prompt the user for confirmation vectorcomponents; tokenize (s.server_trust_spec, components, ","); bool ssl = false; bool signer = false; bool revoke = false; bool all_users = false; bool no_prompt = false; bool error = false; for (vector::const_iterator i = components.begin (); i != components.end (); ++i) { if (*i == "ssl") ssl = true; else if (*i == "signer") { if (geteuid () != 0) { clog << _("Only root can specify 'signer' on --trust-servers") << endl; error = true; } else signer = true; } else if (*i == "revoke") revoke = true; else if (*i == "all-users") { if (geteuid () != 0) { clog << _("Only root can specify 'all-users' on --trust-servers") << endl; error = true; } else all_users = true; } else if (*i == "no-prompt") no_prompt = true; else s.print_warning("Unrecognized server trust specification: " + *i); } if (error) return; // Make sure NSPR is initialized s.NSPR_init (); // Now obtain the list of specified servers. vector server_list; nss_get_specified_server_info (s, server_list, true/*no_default*/); // Did we identify any potential servers? unsigned limit = server_list.size (); if (limit == 0) { clog << _("No servers identified for trust") << endl; return; } // Create a string representing the request in English. // If neither 'ssl' or 'signer' was specified, the default is 'ssl'. if (! ssl && ! signer) ssl = true; ostringstream trustString; if (ssl) { trustString << _("as an SSL peer"); if (all_users) trustString << _(" for all users"); else trustString << _(" for the current user"); } if (signer) { if (ssl) trustString << _(" and "); trustString << _("as a module signer for all users"); } // Prompt the user to confirm what's about to happen. if (no_prompt) { if (revoke) clog << _("Revoking trust "); else clog << _("Adding trust "); } else { if (revoke) clog << _("Revoke trust "); else clog << _("Add trust "); } clog << _F("in the following servers %s", trustString.str().c_str()); if (! no_prompt) clog << '?'; clog << endl; for (unsigned i = 0; i < limit; ++i) clog << " " << server_list[i] << endl; if (! no_prompt) { clog << "[y/N] " << flush; // Only carry out the operation if the response is "yes" string response; cin >> response; if (response[0] != 'y' && response [0] != 'Y') { clog << _("Server trust unchanged") << endl; return; } } // Now add/revoke the requested trust. string cert_db_path; if (ssl) { if (all_users) cert_db_path = global_ssl_cert_db_path (); else cert_db_path = private_ssl_cert_db_path (); if (revoke) revoke_server_trust (s, cert_db_path, server_list); else add_server_trust (s, cert_db_path, server_list); } if (signer) { cert_db_path = signing_cert_db_path (); if (revoke) revoke_server_trust (s, cert_db_path, server_list); else add_server_trust (s, cert_db_path, server_list); } } static compile_server_cache* cscache(systemtap_session& s) { if (!s.server_cache) s.server_cache = new compile_server_cache(); return s.server_cache; } static void get_server_info ( systemtap_session &s, int pmask, vector &servers ) { // Get information on compile servers matching the requested criteria. // The order of queries is significant. Accumulating queries must go first // followed by accumulating/filtering queries. bool keep = false; if (((pmask & compile_server_all))) { nss_get_all_server_info (s, servers); keep = true; } // Add the specified servers, if requested if ((pmask & compile_server_specified)) { nss_get_specified_server_info (s, servers); keep = true; } // Now filter or accumulate the list depending on whether a query has // already been made. if ((pmask & compile_server_online)) { nss_get_or_keep_online_server_info (s, servers, keep); keep = true; } if ((pmask & compile_server_trusted)) { nss_get_or_keep_trusted_server_info (s, servers, keep); keep = true; } if ((pmask & compile_server_signer)) { nss_get_or_keep_signing_server_info (s, servers, keep); keep = true; } if ((pmask & compile_server_compatible)) { nss_get_or_keep_compatible_server_info (s, servers, keep); keep = true; } } // Get information about all online servers as well as servers trusted // as SSL peers and servers trusted as signers. void nss_get_all_server_info ( systemtap_session &s, vector &servers ) { // We only need to obtain this once per session. This is a good thing(tm) // since obtaining this information is expensive. vector& all_servers = cscache(s)->all_servers; if (all_servers.empty ()) { nss_get_or_keep_online_server_info (s, all_servers, false/*keep*/); nss_get_or_keep_trusted_server_info (s, all_servers, false/*keep*/); nss_get_or_keep_signing_server_info (s, all_servers, false/*keep*/); if (s.verbose >= 4) { clog << _("All known servers:") << endl; clog << all_servers; } } // Add the information, but not duplicates. nss_add_server_info (all_servers, servers); } static void get_default_server_info ( systemtap_session &s, vector &servers ) { if (s.verbose >= 3) clog << _("Using the default servers") << endl; // We only need to obtain this once per session. This is a good thing(tm) // since obtaining this information is expensive. vector& default_servers = cscache(s)->default_servers; if (default_servers.empty ()) { // Get the required information. // get_server_info will add an empty entry at the beginning to indicate // that the search has been performed, in case the search comes up empty. int pmask = server_spec_to_pmask (default_server_spec (s)); get_server_info (s, pmask, default_servers); if (s.verbose >= 3) { clog << _("Default servers are:") << endl; clog << default_servers; } } // Add the information, but not duplicates. nss_add_server_info (default_servers, servers); } static bool isPort (const char *pstr, compile_server_info &server_info) { errno = 0; char *estr; unsigned long p = strtoul (pstr, & estr, 10); if (errno != 0 || *estr != '\0' || p > USHRT_MAX) { clog << _F("Invalid port number specified: %s", pstr) << endl; return false; } server_info.port = p; server_info.fully_specified = true; return true; } static bool isIPv6 (const string &server, compile_server_info &server_info) { // An IPv6 address is 8 hex components separated by colons. // One contiguous block of zero segments in the address may be elided using ::. // An interface may be specified by appending %IF_NAME to the address (e.g. %eth0). // A port may be specified by enclosing the ip address in [] and adding :. // Allow a bracketed address without a port. assert (! server.empty()); string ip; string::size_type portIx; if (server[0] == '[') { string::size_type endBracket = server.find (']'); if (endBracket == string::npos) return false; // Not a valid IPv6 address // Extract the address. ip = server.substr (1, endBracket - 1); portIx = endBracket + 1; } else { ip = server; portIx = string::npos; } // Find out how many components there are. The maximum is 8 unsigned empty = 0; vector components; tokenize_full (ip, components, ":"); if (components.size() > 8) return false; // Not a valid IPv6 address // The components must be either hex values between 0 and 0xffff, or must be empty. // There can be only one empty component. string interface; for (unsigned i = 0; i < components.size(); ++i) { if (components[i].empty()) { if (++empty > 1) return false; // Not a valid IPv6 address } // If it's the final component, see if it specifies the interface. If so, strip it from the // component in order to simplify parsing. It still remains as part of the original ip address // string. if (i == components.size() - 1) { size_t ix = components[i].find ('%'); if (ix != string::npos) { interface = components[i].substr(ix); components[i] = components[i].substr(0, ix); } } // Skip leading zeroes. unsigned j; for (j = 0; j < components[i].size(); ++j) { if (components[i][j] != '0') break; } // Max of 4 hex digits if (components[i].size() - j > 4) return false; // Not a valid IPv6 address for (/**/; j < components[i].size(); ++j) { if (! isxdigit (components[i][j])) return false; // Not a valid IPv6 address } } // If there is no empty component, then there must be exactly 8 components. if (! empty && components.size() != 8) return false; // Not a valid IPv6 address // Try to convert the string to an address. PRStatus prStatus = PR_StringToNetAddr (ip.c_str(), & server_info.address); if (prStatus != PR_SUCCESS) return false; // Examine the optional port if (portIx != string::npos) { string port = server.substr (portIx); if (port.size() != 0) { if (port.size() < 2 || port[0] != ':') return false; // Not a valid Port port = port.substr (1); if (! isPort (port.c_str(), server_info)) return false; // not a valid port } } else server_info.port = 0; return true; // valid IPv6 address. } static bool isIPv4 (const string &server, compile_server_info &server_info) { // An IPv4 address is 4 decimal components separated by periods with an // additional optional decimal port separated from the address by a colon. assert (! server.empty()); // Find out how many components there are. The maximum is 8 vector components; tokenize (server, components, ":"); if (components.size() > 2) return false; // Not a valid IPv4 address // Separate the address from the port (if any). string addr; string port; if (components.size() <= 1) addr = server; else { addr = components[0]; port = components[1]; } // Separate the address components. // There must be exactly 4 components. components.clear (); tokenize (addr, components, "."); if (components.size() != 4) return false; // Not a valid IPv4 address // The components must be decimal values between 0 and 255. for (unsigned i = 0; i < components.size(); ++i) { if (components[i].empty()) return false; // Not a valid IPv4 address errno = 0; char *estr; long p = strtol (components[i].c_str(), & estr, 10); if (errno != 0 || *estr != '\0' || p < 0 || p > 255) return false; // Not a valid IPv4 address } // Try to convert the string to an address. PRStatus prStatus = PR_StringToNetAddr (addr.c_str(), & server_info.address); if (prStatus != PR_SUCCESS) return false; // Examine the optional port if (! port.empty ()) { if (! isPort (port.c_str(), server_info)) return false; // not a valid port } else server_info.port = 0; return true; // valid IPv4 address. } static bool isCertSerialNumber (const string &server, compile_server_info &server_info) { // This function assumes that we have already ruled out the server spec being an IPv6 address. // Certificate serial numbers are 5 fields separated by colons plus an optional 6th decimal // field specifying a port. assert (! server.empty()); string host = server; vector components; tokenize (host, components, ":"); switch (components.size ()) { case 6: if (! isPort (components.back().c_str(), server_info)) return false; // not a valid port host = host.substr (0, host.find_last_of (':')); // fall through case 5: server_info.certinfo = host; break; default: return false; // not a cert serial number } return true; // valid cert serial number and optional port } static bool isDomain (const string &server, compile_server_info &server_info) { // Accept one or two components separated by a colon. The first will be the domain name and // the second must a port number. assert (! server.empty()); string host = server; vector components; unsigned scheme_offset = 0; if (host.substr (0, 6) == "https:") scheme_offset = 1; tokenize (host, components, ":"); switch (components.size ()) { case 3: case 2: if (! isPort (components.back().c_str(), server_info)) return false; // not a valid port host = components[(0 + scheme_offset)].substr (scheme_offset * 2); // fall through case 1: server_info.host_name = host; break; default: return false; // not a valid domain name } return true; } void nss_get_specified_server_info ( systemtap_session &s, vector &servers, bool no_default ) { // We only need to obtain this once per session. This is a good thing(tm) // since obtaining this information is expensive. vector& specified_servers = cscache(s)->specified_servers; if (specified_servers.empty ()) { // Maintain an empty entry to indicate that this search has been // performed, in case the search comes up empty. specified_servers.push_back (compile_server_info ()); // If --use-server was not specified at all, then return info for the // default server list. if (s.specified_servers.empty () && s.http_servers.empty ()) { if (s.verbose >= 3) clog << _("No servers specified") << endl; if (! no_default) get_default_server_info (s, specified_servers); } else { // Iterate over the specified servers. For each // specification, add to the list of servers. vector &server_list = (! s.http_servers.empty () ? s.http_servers : s.specified_servers); client_backend *backend = nss_get_client_backend (s); if (backend->initialize () != 0) return; for (auto it = server_list.begin (); it != server_list.end (); ++it) { string &server = *it; // If no specific server(s) specified, then use the // default servers. if (server.empty ()) { if (s.verbose >= 3) clog << _("No servers specified") << endl; if (! no_default) get_default_server_info (s, specified_servers); continue; } // Determine what has been specified. Servers may be // specified by: // - domain{:port} // - certificate-serial-number{:port} // - IPv4-address{:port} // - IPv6-address{:port} // where items within {} are optional. // // Check for IPv6 addresses first. It reduces the amount // of checking necessary for certificate serial numbers. compile_server_info server_info; vector resolved_servers; if (isIPv6 (server, server_info) || isIPv4 (server, server_info) || isCertSerialNumber (server, server_info)) { // An address or cert serial number has been specified. // No resolution is needed. resolved_servers.push_back (server_info); } else if (isDomain (server, server_info)) { // Try to resolve the given name. resolve_host (s, server_info, resolved_servers); } else { clog << _F("Invalid server specification: %s", server.c_str()) << endl; continue; } // Now examine the server(s) identified and add them to // the list of specified servers. vector known_servers; vector new_servers; for (auto i = resolved_servers.begin(); i != resolved_servers.end(); ++i) { // If this item was fully specified, then just add it. if (i->fully_specified || ! s.http_servers.empty ()) { // In this instance, "fully specified" means // address and port. At this point we haven't // tried to contact the server to get online // information, certificate information, // etc. Certain server types need us to connect to // the server directly to get this information. // Assume http servers are fully specified if (backend) backend->fill_in_server_info (*i); nss_add_server_info (*i, new_servers); } else { // It was not fully specified, so we need additional info. Try // to get it by matching what we have against other known servers. if (known_servers.empty ()) nss_get_all_server_info (s, known_servers); // See if this server spec matches that of a known server vector matched_servers = known_servers; nss_keep_common_server_info (*i, matched_servers); // If this server spec matches one or more known servers, then add the // augmented info to the specified_servers. Otherwise, if this server // spec is complete, then add it directly. Otherwise this server spec // is incomplete. if (! matched_servers.empty()) nss_add_server_info (matched_servers, new_servers); else if (i->isComplete ()) nss_add_server_info (*i, new_servers); else if (s.verbose >= 3) clog << _("Incomplete server spec: ") << *i << endl; } } if (s.verbose >= 3) { clog << _F("Servers matching %s: ", server.c_str()) << endl; clog << new_servers; } // Add the newly identified servers to the list. if (! new_servers.empty()) nss_add_server_info (new_servers, specified_servers); } // Loop over --use-server options } // -- use-server specified if (s.verbose >= 2) { clog << _("All specified servers:") << endl; clog << specified_servers; } } // Server information is not cached // Add the information, but not duplicates. nss_add_server_info (specified_servers, servers); } void nss_get_or_keep_trusted_server_info ( systemtap_session &s, vector &servers, bool keep ) { // If we're filtering the list and it's already empty, then // there's nothing to do. if (keep && servers.empty ()) return; // We only need to obtain this once per session. This is a good thing(tm) // since obtaining this information is expensive. vector& trusted_servers = cscache(s)->trusted_servers; if (trusted_servers.empty ()) { // Maintain an empty entry to indicate that this search has been // performed, in case the search comes up empty. trusted_servers.push_back (compile_server_info ()); // Check the private database first. string cert_db_path = private_ssl_cert_db_path (); get_server_info_from_db (s, trusted_servers, cert_db_path); // Now check the global database. cert_db_path = global_ssl_cert_db_path (); get_server_info_from_db (s, trusted_servers, cert_db_path); if (s.verbose >= 5) { clog << _("All servers trusted as ssl peers:") << endl; clog << trusted_servers; } } // Server information is not cached if (keep) { // Filter the existing vector by keeping the information in common with // the trusted_server vector. nss_keep_common_server_info (trusted_servers, servers); } else { // Add the information, but not duplicates. nss_add_server_info (trusted_servers, servers); } } void nss_get_or_keep_signing_server_info ( systemtap_session &s, vector &servers, bool keep ) { // If we're filtering the list and it's already empty, then // there's nothing to do. if (keep && servers.empty ()) return; // We only need to obtain this once per session. This is a good thing(tm) // since obtaining this information is expensive. vector& signing_servers = cscache(s)->signing_servers; if (signing_servers.empty ()) { // Maintain an empty entry to indicate that this search has been // performed, in case the search comes up empty. signing_servers.push_back (compile_server_info ()); // For all users, check the global database. string cert_db_path = signing_cert_db_path (); get_server_info_from_db (s, signing_servers, cert_db_path); if (s.verbose >= 5) { clog << _("All servers trusted as module signers:") << endl; clog << signing_servers; } } // Server information is not cached if (keep) { // Filter the existing vector by keeping the information in common with // the signing_server vector. nss_keep_common_server_info (signing_servers, servers); } else { // Add the information, but not duplicates. nss_add_server_info (signing_servers, servers); } } void nss_get_or_keep_compatible_server_info ( systemtap_session &s, vector &servers, bool keep ) { #if HAVE_AVAHI // If we're filtering the list and it's already empty, then // there's nothing to do. if (keep && servers.empty ()) return; // Remove entries for servers incompatible with the host environment // from the given list of servers. // A compatible server compiles for the kernel release and architecture // of the host environment. // // Compatibility can only be determined for online servers. So, augment // and filter the information we have with information for online servers. vector online_servers; nss_get_or_keep_online_server_info (s, online_servers, false/*keep*/); if (keep) nss_keep_common_server_info (online_servers, servers); else nss_add_server_info (online_servers, servers); // Now look to see which ones are compatible. // The vector can change size as we go, so be careful!! for (unsigned i = 0; i < servers.size (); /**/) { // Retain empty entries. assert (! servers[i].empty ()); // Check the target of the server. if (servers[i].sysinfo != s.kernel_release + " " + s.architecture && servers[i].sysinfo != s.architecture) { // Target platform mismatch. servers.erase (servers.begin () + i); continue; } // If the client requires secure boot signing, make sure the // server has the right MOK. if (! s.mok_fingerprints.empty ()) { // This server has no MOKs. if (servers[i].mok_fingerprints.empty ()) { servers.erase (servers.begin () + i); continue; } // Make sure the server has at least one MOK in common with // the client. vector::const_iterator it; bool mok_found = false; for (it = s.mok_fingerprints.begin(); it != s.mok_fingerprints.end(); it++) { if (find(servers[i].mok_fingerprints.begin(), servers[i].mok_fingerprints.end(), *it) != servers[i].mok_fingerprints.end ()) { mok_found = true; break; } } // This server has no MOK in common with the client. if (! mok_found) { servers.erase (servers.begin () + i); continue; } } // The server is compatible. Leave it in the list. ++i; } #else // ! HAVE_AVAHI // Without Avahi, we can't obtain the target platform of the server. // Issue a warning. if (s.verbose >= 2) clog << _("Unable to detect server compatibility without avahi") << endl; if (keep) servers.clear (); #endif } void nss_keep_server_info_with_cert_and_port ( systemtap_session &, const compile_server_info &server, vector &servers ) { assert (! server.certinfo.empty ()); // Search the list of servers for ones matching the // serial number specified. // The vector can change size as we go, so be careful!! for (unsigned i = 0; i < servers.size (); /**/) { // Retain empty entries. if (servers[i].empty ()) { ++i; continue; } if (servers[i].certinfo == server.certinfo && (servers[i].port == 0 || server.port == 0 || servers[i].port == server.port)) { // If the server is not online, then use the specified // port, if any. if (servers[i].port == 0) { servers[i].port = server.port; servers[i].fully_specified = server.fully_specified; } ++i; continue; } // The item does not match. Delete it. servers.erase (servers.begin () + i); } } // Obtain missing host name or ip address, if any. Return 0 on success. static void resolve_host ( systemtap_session& s, compile_server_info &server, vector &resolved_servers ) { vector& cached_hosts = cscache(s)->resolved_hosts[server.host_name]; if (cached_hosts.empty ()) { // The server's host_name member is a string containing either a host name or an ip address. // Either is acceptable for lookup. const char *lookup_name = server.host_name.c_str(); if (s.verbose >= 6) clog << _F("Looking up %s", lookup_name) << endl; struct addrinfo hints; memset(& hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version struct addrinfo *addr_info = 0; int rc = getaddrinfo (lookup_name, NULL, & hints, & addr_info); // Failure to resolve will result in an appropriate message later, if other methods fail. if (rc != 0) { if (s.verbose >= 6) clog << _F("%s not found: %s", lookup_name, gai_strerror (rc)) << endl; } else { // Loop over the results collecting information. assert (addr_info); for (const struct addrinfo *ai = addr_info; ai != NULL; ai = ai->ai_next) { PRNetAddr new_address; // We support IPv4 and IPv6, Ignore other protocols, if (ai->ai_family == AF_INET) { // IPv4 Address struct sockaddr_in *ip = (struct sockaddr_in *)ai->ai_addr; new_address.inet.family = PR_AF_INET; new_address.inet.ip = ip->sin_addr.s_addr; } else if (ai->ai_family == AF_INET6) { // IPv6 Address struct sockaddr_in6 *ip = (struct sockaddr_in6 *)ai->ai_addr; new_address.ipv6.family = PR_AF_INET6; new_address.ipv6.scope_id = ip->sin6_scope_id; copyAddress (new_address.ipv6.ip, ip->sin6_addr); } else continue; // Try to obtain a host name. Otherwise, leave it empty. char hbuf[NI_MAXHOST]; int status = getnameinfo (ai->ai_addr, ai->ai_addrlen, hbuf, sizeof (hbuf), NULL, 0, NI_NAMEREQD | NI_IDN); if (status != 0) hbuf[0] = '\0'; cached_hosts.push_back(resolved_host(hbuf, new_address)); } } if (addr_info) freeaddrinfo (addr_info); // free the linked list } // If no addresses were resolved, then return the info we were given. if (cached_hosts.empty()) nss_add_server_info (server, resolved_servers); else { // We will add a new server for each address resolved vector new_servers; for (vector::const_iterator it = cached_hosts.begin(); it != cached_hosts.end(); ++it) { // Start with the info we were given compile_server_info new_server = server; // NB: do not overwrite port info if (it->address.raw.family == AF_INET) { new_server.address.inet.family = PR_AF_INET; new_server.address.inet.ip = it->address.inet.ip; } else // AF_INET6 { new_server.address.ipv6.family = PR_AF_INET6; new_server.address.ipv6.scope_id = it->address.ipv6.scope_id; new_server.address.ipv6.ip = it->address.ipv6.ip; } if (!it->host_name.empty()) { new_server.unresolved_host_name = new_server.host_name; new_server.host_name = it->host_name; } nss_add_server_info (new_server, new_servers); } if (s.verbose >= 6) { clog << _F("%s resolves to:", server.host_name.c_str()) << endl; clog << new_servers; } nss_add_server_info (new_servers, resolved_servers); } } #if HAVE_AVAHI // Avahi API Callbacks. //----------------------------------------------------------------------- struct browsing_context { AvahiSimplePoll *simple_poll; AvahiClient *client; vector *servers; }; // Get the value of the requested key from the Avahi string list. static string get_value_from_avahi_string_list (AvahiStringList *strlst, const string &key) { AvahiStringList *p = avahi_string_list_find (strlst, key.c_str ()); if (p == NULL) { // Key not found. return ""; } char *k, *v; int rc = avahi_string_list_get_pair(p, &k, &v, NULL); if (rc < 0 || v == NULL) { avahi_free (k); return ""; } string value = v; avahi_free (k); avahi_free (v); return value; } // Get a vector of values of the requested key from the Avahi string // list. This is for multiple values having the same key. static void get_values_from_avahi_string_list (AvahiStringList *strlst, const string &key, vector &value_vector) { AvahiStringList *p; value_vector.clear(); p = avahi_string_list_find (strlst, key.c_str ()); for (; p != NULL; p = avahi_string_list_get_next(p)) { char *k, *v; int rc = avahi_string_list_get_pair(p, &k, &v, NULL); if (rc < 0 || v == NULL) { avahi_free (k); break; } value_vector.push_back(v); avahi_free (k); avahi_free (v); } return; } extern "C" void resolve_callback( AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags /*flags*/, AVAHI_GCC_UNUSED void* userdata) { PRStatus prStatus; assert(r); const browsing_context *context = (browsing_context *)userdata; vector *servers = context->servers; // Called whenever a service has been resolved successfully or timed out. switch (event) { case AVAHI_RESOLVER_FAILURE: clog << _F("Failed to resolve service '%s' of type '%s' in domain '%s': %s", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))) << endl; break; case AVAHI_RESOLVER_FOUND: { compile_server_info info; // Decode the address. char a[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(a, sizeof(a), address); prStatus = PR_StringToNetAddr (a, & info.address); if (prStatus != PR_SUCCESS) { clog << _F("Invalid address '%s' from avahi", a) << endl; break; } // We support both IPv4 and IPv6. Ignore other protocols. if (protocol == AVAHI_PROTO_INET6) { info.address.ipv6.family = PR_AF_INET6; info.address.ipv6.scope_id = interface; info.port = port; } else if (protocol == AVAHI_PROTO_INET) { info.address.inet.family = PR_AF_INET; info.port = port; } else break; // Save the host name. info.host_name = host_name; // Save the text tags. info.sysinfo = get_value_from_avahi_string_list (txt, "sysinfo"); info.certinfo = get_value_from_avahi_string_list (txt, "certinfo"); info.version = get_value_from_avahi_string_list (txt, "version"); if (info.version.empty ()) info.version = "1.0"; // default version is 1.0 // The server might provide one or more MOK certificate's // info. get_values_from_avahi_string_list(txt, "mok_info", info.mok_fingerprints); // Add this server to the list of discovered servers. nss_add_server_info (info, *servers); break; } default: break; } avahi_service_resolver_free(r); } extern "C" void browse_callback( AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) { browsing_context *context = (browsing_context *)userdata; AvahiClient *c = context->client; AvahiSimplePoll *simple_poll = context->simple_poll; assert(b); // Called whenever a new services becomes available on the LAN or is removed from the LAN. switch (event) { case AVAHI_BROWSER_FAILURE: clog << _F("Avahi browse failed: %s", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))) << endl; avahi_simple_poll_quit(simple_poll); break; case AVAHI_BROWSER_NEW: // We ignore the returned resolver object. In the callback // function we free it. If the server is terminated before // the callback function is called the server will free // the resolver for us. if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolve_callback, context))) { clog << _F("Failed to resolve service '%s': %s", name, avahi_strerror(avahi_client_errno(c))) << endl; } break; case AVAHI_BROWSER_REMOVE: case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: break; } } extern "C" void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) { assert(c); browsing_context *context = (browsing_context *)userdata; AvahiSimplePoll *simple_poll = context->simple_poll; // Called whenever the client or server state changes. if (state == AVAHI_CLIENT_FAILURE) { clog << _F("Avahi Server connection failure: %s", avahi_strerror(avahi_client_errno(c))) << endl; avahi_simple_poll_quit(simple_poll); } } extern "C" void timeout_callback(AVAHI_GCC_UNUSED AvahiTimeout *e, AVAHI_GCC_UNUSED void *userdata) { browsing_context *context = (browsing_context *)userdata; AvahiSimplePoll *simple_poll = context->simple_poll; avahi_simple_poll_quit(simple_poll); } #endif // HAVE_AVAHI void nss_get_or_keep_online_server_info ( systemtap_session &s, vector &servers, bool keep ) { // If we're filtering the list and it's already empty, then // there's nothing to do. if (keep && servers.empty ()) return; // We only need to obtain this once per session. This is a good thing(tm) // since obtaining this information is expensive. vector& online_servers = cscache(s)->online_servers; if (online_servers.empty () && s.http_servers.empty ()) { // Maintain an empty entry to indicate that this search has been // performed, in case the search comes up empty. online_servers.push_back (compile_server_info ()); #if HAVE_AVAHI // Must predeclare these due to jumping on error to fail: vector avahi_servers; // Initialize. AvahiClient *client = NULL; AvahiServiceBrowser *sb = NULL; // Allocate main loop object. AvahiSimplePoll *simple_poll; if (!(simple_poll = avahi_simple_poll_new())) { clog << _("Failed to create Avahi simple poll object") << endl; goto fail; } browsing_context context; context.simple_poll = simple_poll; context.servers = & avahi_servers; // Allocate a new Avahi client int error; client = avahi_client_new (avahi_simple_poll_get (simple_poll), (AvahiClientFlags)0, client_callback, & context, & error); // Check whether creating the client object succeeded. if (! client) { clog << _F("Failed to create Avahi client: %s", avahi_strerror(error)) << endl; goto fail; } context.client = client; // Create the service browser. if (!(sb = avahi_service_browser_new (client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_stap._tcp", NULL, (AvahiLookupFlags)0, browse_callback, & context))) { clog << _F("Failed to create Avahi service browser: %s", avahi_strerror(avahi_client_errno(client))) << endl; goto fail; } // Timeout after 0.5 seconds. struct timeval tv; avahi_simple_poll_get(simple_poll)->timeout_new( avahi_simple_poll_get(simple_poll), avahi_elapse_time(&tv, 1000/2, 0), timeout_callback, & context); // Run the main loop. avahi_simple_poll_loop(simple_poll); if (s.verbose >= 6) { clog << _("Avahi reports the following servers online:") << endl; clog << avahi_servers; } // Merge with the list of servers, as obtained by avahi. nss_add_server_info (avahi_servers, online_servers); fail: // Cleanup. if (client) { // Also frees the service browser avahi_client_free(client); } if (simple_poll) avahi_simple_poll_free(simple_poll); #else // ! HAVE_AVAHI // Without Avahi, we can't detect online servers. Issue a warning. if (s.verbose >= 2) clog << _("Unable to detect online servers without avahi") << endl; #endif // ! HAVE_AVAHI if (s.verbose >= 5) { clog << _("All online servers:") << endl; clog << online_servers; } } // Server information is not cached. if (!s.http_servers.empty ()) { // http server does not depend on avahi, so discover which servers are online by // getting a list of potential servers and trying to connect to them vector& specified_servers = cscache(s)->specified_servers; nss_get_specified_server_info (s, specified_servers); for (vector::const_iterator i = specified_servers.begin (); i != specified_servers.end (); ++i) { client_backend *backend = nss_get_client_backend (s); if (backend) backend->fill_in_server_info ((compile_server_info&)*i); } } if (keep) { // Filter the existing vector by keeping the information in common with // the online_server vector. nss_keep_common_server_info (online_servers, servers); } else { // Add the information, but not duplicates. nss_add_server_info (online_servers, servers); } } // Add server info to a list, avoiding duplicates. Merge information from // two duplicate items. void nss_add_server_info ( const compile_server_info &info, vector& target ) { if (info.empty ()) return; bool found = false; for (vector::iterator i = target.begin (); i != target.end (); ++i) { if (info == *i) { // Duplicate. Merge the two items. merge_server_info (info, *i); found = true; } } if (! found) target.push_back (info); } // Add server info from one vector to another. void nss_add_server_info ( const vector &source, vector &target ) { for (vector::const_iterator i = source.begin (); i != source.end (); ++i) { nss_add_server_info (*i, target); } } // Filter the vector by keeping information in common with the item. void nss_keep_common_server_info ( const compile_server_info &info_to_keep, vector &filtered_info ) { assert (! info_to_keep.empty ()); // The vector may change size as we go. Be careful!! for (unsigned i = 0; i < filtered_info.size (); /**/) { // Retain empty entries. if (filtered_info[i].empty ()) { ++i; continue; } if (info_to_keep == filtered_info[i]) { merge_server_info (info_to_keep, filtered_info[i]); ++i; continue; } // The item does not match. Delete it. filtered_info.erase (filtered_info.begin () + i); continue; } } // Filter the second vector by keeping information in common with the first // vector. void nss_keep_common_server_info ( const vector &info_to_keep, vector &filtered_info ) { // The vector may change size as we go. Be careful!! for (unsigned i = 0; i < filtered_info.size (); /**/) { // Retain empty entries. if (filtered_info[i].empty ()) { ++i; continue; } bool found = false; for (unsigned j = 0; j < info_to_keep.size (); ++j) { if (filtered_info[i] == info_to_keep[j]) { merge_server_info (info_to_keep[j], filtered_info[i]); found = true; } } // If the item was not found. Delete it. Otherwise, advance to the next // item. if (found) ++i; else filtered_info.erase (filtered_info.begin () + i); } } // Merge two compile server info items. static void merge_server_info ( const compile_server_info &source, compile_server_info &target ) { // Copy the host name if the source has one. if (! source.host_name.empty()) target.host_name = source.host_name; // Copy the address unconditionally, if the source has an address, even if they are already // equal. The source address may be an IPv6 address with a scope_id that the target is missing. assert (! target.hasAddress () || ! source.hasAddress () || source.address == target.address); if (source.hasAddress ()) copyNetAddr (target.address, source.address); if (target.port == 0) { target.port = source.port; target.fully_specified = source.fully_specified; } if (target.sysinfo.empty ()) target.sysinfo = source.sysinfo; if (target.version.empty ()) target.version = source.version; if (target.certinfo.empty ()) target.certinfo = source.certinfo; } void nss_add_online_server_info (systemtap_session &s, const compile_server_info &info) { nss_add_server_info (info, cscache(s)->online_servers); } #endif // HAVE_NSS /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */