static void SetupOpenSSLThreadLocks(void) { const int num_locks = CRYPTO_num_locks(); assert(cf_openssl_locks == NULL); cf_openssl_locks = xmalloc(num_locks * sizeof(*cf_openssl_locks)); for (int i = 0; i < num_locks; i++) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); int ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); if (ret != 0) { Log(LOG_LEVEL_ERR, "Failed to use error-checking mutexes for openssl," " falling back to normal ones (pthread_mutexattr_settype: %s)", GetErrorStrFromCode(ret)); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); } ret = pthread_mutex_init(&cf_openssl_locks[i], &attr); if (ret != 0) { Log(LOG_LEVEL_CRIT, "Failed to use initialise mutexes for openssl" " (pthread_mutex_init: %s)!", GetErrorStrFromCode(ret)); } pthread_mutexattr_destroy(&attr); } #ifndef __MINGW32__ CRYPTO_set_id_callback((unsigned long (*)())ThreadId_callback); #endif CRYPTO_set_locking_callback((void (*)())OpenSSLLock_callback); }
static void OpenSSLLock_callback(int mode, int index, char *file, int line) { if (mode & CRYPTO_LOCK) { int ret = pthread_mutex_lock(&(cf_openssl_locks[index])); if (ret != 0) { Log(LOG_LEVEL_ERR, "OpenSSL locking failure at %s:%d! (pthread_mutex_lock: %s)", file, line, GetErrorStrFromCode(ret)); } } else { int ret = pthread_mutex_unlock(&(cf_openssl_locks[index])); if (ret != 0) { Log(LOG_LEVEL_ERR, "OpenSSL locking failure at %s:%d! (pthread_mutex_unlock: %s)", file, line, GetErrorStrFromCode(ret)); } } }
int cf_closesocket(int sd) { int res; #ifdef __MINGW32__ res = closesocket(sd); if (res == SOCKET_ERROR) { Log(LOG_LEVEL_VERBOSE, "Failed to close socket (closesocket: %s)", GetErrorStrFromCode(WSAGetLastError())); } #else res = close(sd); if (res == -1) { Log(LOG_LEVEL_VERBOSE, "Failed to close socket (close: %s)", GetErrorStr()); } #endif return res; }
/** * Generic *at function. * @param dirfd File descriptor pointing to directory to do lookup in. * AT_FDCWD constant means to look in current directory. * @param func Function to call while in the directory. * @param cleanup Function to call if we need to clean up because of a failed call. * @param data Private data for the supplied functions. */ int generic_at_function(int dirfd, int (*func)(void *data), void (*cleanup)(void *data), void *data) { int cwd; int mutex_err; int saved_errno; int fchdir_ret; mutex_err = pthread_mutex_lock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when locking CHDIR_LOCK. Should never happen. (pthread_mutex_lock: '%s')", GetErrorStrFromCode(mutex_err)); } if (dirfd != AT_FDCWD) { cwd = open(".", O_RDONLY); if (cwd < 0) { mutex_err = pthread_mutex_unlock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when unlocking CHDIR_LOCK. Should never happen. (pthread_mutex_unlock: '%s')", GetErrorStrFromCode(mutex_err)); } return -1; } if (fchdir(dirfd) < 0) { close(cwd); mutex_err = pthread_mutex_unlock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when unlocking CHDIR_LOCK. Should never happen. (pthread_mutex_unlock: '%s')", GetErrorStrFromCode(mutex_err)); } return -1; } } int result = func(data); saved_errno = errno; if (dirfd != AT_FDCWD) { fchdir_ret = fchdir(cwd); close(cwd); } mutex_err = pthread_mutex_unlock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when unlocking CHDIR_LOCK. Should never happen. (pthread_mutex_unlock: '%s')", GetErrorStrFromCode(mutex_err)); } if (dirfd != AT_FDCWD) { if (fchdir_ret < 0) { cleanup(data); Log(LOG_LEVEL_WARNING, "Could not return to original working directory in '%s'. " "Things may not behave as expected. (fchdir: '%s')", __FUNCTION__, GetErrorStr()); return -1; } } errno = saved_errno; return result; }
/** Tries to connect() to server #host, returns the socket descriptor and the IP address that succeeded in #txtaddr. @param #connect_timeout how long to wait for connect(), zero blocks forever @param #txtaddr If connected successfully return the IP connected in textual representation @return Connected socket descriptor or -1 in case of failure. */ int SocketConnect(const char *host, const char *port, unsigned int connect_timeout, bool force_ipv4, char *txtaddr, size_t txtaddr_size) { struct addrinfo *response = NULL, *ap; bool connected = false; int sd = -1; struct addrinfo query = { .ai_family = force_ipv4 ? AF_INET : AF_UNSPEC, .ai_socktype = SOCK_STREAM }; int ret = getaddrinfo(host, port, &query, &response); if (ret != 0) { Log(LOG_LEVEL_INFO, "Unable to find host '%s' service '%s' (%s)", host, port, gai_strerror(ret)); if (response != NULL) { freeaddrinfo(response); } return -1; } for (ap = response; !connected && ap != NULL; ap = ap->ai_next) { /* Convert address to string. */ getnameinfo(ap->ai_addr, ap->ai_addrlen, txtaddr, txtaddr_size, NULL, 0, NI_NUMERICHOST); Log(LOG_LEVEL_VERBOSE, "Connecting to host %s, port %s as address %s", host, port, txtaddr); sd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol); if (sd == -1) { Log(LOG_LEVEL_ERR, "Couldn't open a socket to '%s' (socket: %s)", txtaddr, GetErrorStr()); } else { /* Bind socket to specific interface, if requested. */ if (BINDINTERFACE[0] != '\0') { struct addrinfo query2 = { .ai_family = force_ipv4 ? AF_INET : AF_UNSPEC, .ai_socktype = SOCK_STREAM, /* returned address is for bind() */ .ai_flags = AI_PASSIVE }; struct addrinfo *response2 = NULL, *ap2; int ret2 = getaddrinfo(BINDINTERFACE, NULL, &query2, &response2); if (ret2 != 0) { Log(LOG_LEVEL_ERR, "Unable to lookup interface '%s' to bind. (getaddrinfo: %s)", BINDINTERFACE, gai_strerror(ret2)); if (response2 != NULL) { freeaddrinfo(response2); } assert(response); /* first getaddrinfo was successful */ freeaddrinfo(response); cf_closesocket(sd); return -1; } for (ap2 = response2; ap2 != NULL; ap2 = ap2->ai_next) { if (bind(sd, ap2->ai_addr, ap2->ai_addrlen) == 0) { break; } } if (ap2 == NULL) { Log(LOG_LEVEL_ERR, "Unable to bind to interface '%s'. (bind: %s)", BINDINTERFACE, GetErrorStr()); } assert(response2); /* second getaddrinfo was successful */ freeaddrinfo(response2); } connected = TryConnect(sd, connect_timeout * 1000, ap->ai_addr, ap->ai_addrlen); if (!connected) { Log(LOG_LEVEL_VERBOSE, "Unable to connect to address %s (%s)", txtaddr, GetErrorStr()); cf_closesocket(sd); sd = -1; } } } assert(response != NULL); /* first getaddrinfo was successful */ freeaddrinfo(response); if (connected) { Log(LOG_LEVEL_VERBOSE, "Connected to host %s address %s port %s", host, txtaddr, port); } else { Log(LOG_LEVEL_VERBOSE, "Unable to connect to host %s port %s", host, port); } return sd; } #if !defined(__MINGW32__) #if defined(__hpux) && defined(__GNUC__) #pragma GCC diagnostic ignored "-Wstrict-aliasing" // HP-UX GCC type-pun warning on FD_SET() macro: // While the "fd_set" type is defined in /usr/include/sys/_fd_macros.h as a // struct of an array of "long" values in accordance with the XPG4 standard's // requirements, the macros for the FD operations "pretend it is an array of // int32_t's so the binary layout is the same for both Narrow and Wide // processes," as described in _fd_macros.h. In the FD_SET, FD_CLR, and // FD_ISSET macros at line 101, the result is cast to an "__fd_mask *" type, // which is defined as int32_t at _fd_macros.h:82. // // This conflict between the "long fds_bits[]" array in the XPG4-compliant // fd_set structure, and the cast to an int32_t - not long - pointer in the // macros, causes a type-pun warning if -Wstrict-aliasing is enabled. // The warning is merely a side effect of HP-UX working as designed, // so it can be ignored. #endif /** * Tries to connect for #timeout_ms milliseconds. On success sets the recv() * timeout to #timeout_ms as well. * * @param #timeout_ms How long to wait for connect(), if zero wait forever. * @return true on success, false otherwise. **/ bool TryConnect(int sd, unsigned long timeout_ms, const struct sockaddr *sa, socklen_t sa_len) { assert(sa != NULL); if (sd >= FD_SETSIZE) { Log(LOG_LEVEL_ERR, "Open connections exceed FD_SETSIZE limit of %d", FD_SETSIZE); return false; } /* set non-blocking socket */ int arg = fcntl(sd, F_GETFL, NULL); int ret = fcntl(sd, F_SETFL, arg | O_NONBLOCK); if (ret == -1) { Log(LOG_LEVEL_ERR, "Failed to set socket to non-blocking mode (fcntl: %s)", GetErrorStr()); } ret = connect(sd, sa, sa_len); if (ret == -1) { if (errno != EINPROGRESS) { Log(LOG_LEVEL_INFO, "Failed to connect to server (connect: %s)", GetErrorStr()); return false; } int errcode; socklen_t opt_len = sizeof(errcode); fd_set myset; FD_ZERO(&myset); FD_SET(sd, &myset); Log(LOG_LEVEL_VERBOSE, "Waiting to connect..."); struct timeval tv, *tvp; if (timeout_ms > 0) { tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; tvp = &tv; } else { tvp = NULL; /* wait indefinitely */ } ret = select(sd + 1, NULL, &myset, NULL, tvp); if (ret == 0) { Log(LOG_LEVEL_INFO, "Timeout connecting to server"); return false; } if (ret == -1) { if (errno == EINTR) { Log(LOG_LEVEL_ERR, "Socket connect was interrupted by signal"); } else { Log(LOG_LEVEL_ERR, "Failure while connecting (select: %s)", GetErrorStr()); } return false; } ret = getsockopt(sd, SOL_SOCKET, SO_ERROR, (void *) &errcode, &opt_len); if (ret == -1) { Log(LOG_LEVEL_ERR, "Could not check connection status (getsockopt: %s)", GetErrorStr()); return false; } if (errcode != 0) { Log(LOG_LEVEL_INFO, "Failed to connect to server: %s", GetErrorStrFromCode(errcode)); return false; } } /* Connection succeeded, return to blocking mode. */ ret = fcntl(sd, F_SETFL, arg); if (ret == -1) { Log(LOG_LEVEL_ERR, "Failed to set socket back to blocking mode (fcntl: %s)", GetErrorStr()); } if (timeout_ms > 0) { SetReceiveTimeout(sd, timeout_ms); } return true; } #if defined(__hpux) && defined(__GNUC__) #pragma GCC diagnostic warning "-Wstrict-aliasing" #endif #endif /* !defined(__MINGW32__) */ /** * Set timeout for recv(), in milliseconds. * @param ms must be > 0. */ int SetReceiveTimeout(int fd, unsigned long ms) { assert(ms > 0); Log(LOG_LEVEL_VERBOSE, "Setting socket timeout to %lu seconds.", ms/1000); /* On windows SO_RCVTIMEO is set by a DWORD indicating the timeout in * milliseconds, on UNIX it's a struct timeval. */ #if !defined(__MINGW32__) struct timeval tv = { .tv_sec = ms / 1000, .tv_usec = (ms % 1000) * 1000 }; int ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); #else int ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &ms, sizeof(ms)); #endif if (ret != 0) { Log(LOG_LEVEL_INFO, "Failed to set socket timeout to %lu milliseconds.", ms); return -1; } return 0; }
DBPriv *DBPrivOpenDB(const char *dbpath, dbid id) { DBPriv *db = xcalloc(1, sizeof(DBPriv)); MDB_txn *txn = NULL; int rc; rc = pthread_key_create(&db->txn_key, &DestroyTransaction); if (rc) { Log(LOG_LEVEL_ERR, "Could not create transaction key. (pthread_key_create: '%s')", GetErrorStrFromCode(rc)); free(db); return NULL; } rc = mdb_env_create(&db->env); if (rc) { Log(LOG_LEVEL_ERR, "Could not create handle for database %s: %s", dbpath, mdb_strerror(rc)); goto err; } rc = mdb_env_set_mapsize(db->env, LMDB_MAXSIZE); if (rc) { Log(LOG_LEVEL_ERR, "Could not set mapsize for database %s: %s", dbpath, mdb_strerror(rc)); goto err; } if (DB_MAX_READERS > 0) { rc = mdb_env_set_maxreaders(db->env, DB_MAX_READERS); if (rc) { Log(LOG_LEVEL_ERR, "Could not set maxreaders for database %s: %s", dbpath, mdb_strerror(rc)); goto err; } } unsigned int open_flags = MDB_NOSUBDIR; if (id == dbid_locks || (GetAmPolicyHub() && id == dbid_lastseen)) { open_flags |= MDB_NOSYNC; } #ifdef __hpux /* * On HP-UX, a unified file cache was not introduced until version 11.31. * This means that on 11.23 there are separate file caches for mmap()'ed * files and open()'ed files. When these two are mixed, changes made using * one mode won't be immediately seen by the other mode, which is an * assumption LMDB is relying on. The MDB_WRITEMAP flag causes LMDB to use * mmap() only, so that we stay within one file cache. */ open_flags |= MDB_WRITEMAP; #endif rc = mdb_env_open(db->env, dbpath, open_flags, 0644); if (rc) { Log(LOG_LEVEL_ERR, "Could not open database %s: %s", dbpath, mdb_strerror(rc)); goto err; } if (DB_MAX_READERS > 0) { int max_readers; rc = mdb_env_get_maxreaders(db->env, &max_readers); if (rc) { Log(LOG_LEVEL_ERR, "Could not get maxreaders for database %s: %s", dbpath, mdb_strerror(rc)); goto err; } if (max_readers < DB_MAX_READERS) { // LMDB will only reinitialize maxreaders if no database handles are // open, including in other processes, which is how we might end up // here. Log(LOG_LEVEL_VERBOSE, "Failed to set LMDB max reader limit on database '%s', " "consider restarting CFEngine", dbpath); } } rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn); if (rc) { Log(LOG_LEVEL_ERR, "Could not open database txn %s: %s", dbpath, mdb_strerror(rc)); goto err; } rc = mdb_open(txn, NULL, 0, &db->dbi); if (rc) { Log(LOG_LEVEL_ERR, "Could not open database dbi %s: %s", dbpath, mdb_strerror(rc)); mdb_txn_abort(txn); goto err; } rc = mdb_txn_commit(txn); if (rc) { Log(LOG_LEVEL_ERR, "Could not commit database dbi %s: %s", dbpath, mdb_strerror(rc)); goto err; } return db; err: if (db->env) { mdb_env_close(db->env); } pthread_key_delete(db->txn_key); free(db); if (rc == MDB_INVALID) { return DB_PRIV_DATABASE_BROKEN; } return NULL; }