static int CheckStoreKey(ServerConnectionState *conn, RSA *key) { RSA *savedkey; const char *udigest = KeyPrintableHash(ConnectionInfoKey(conn->conn_info)); assert(udigest != NULL); if ((savedkey = HavePublicKey(conn->username, MapAddress(conn->ipaddr), udigest))) { Log(LOG_LEVEL_VERBOSE, "A public key was already known from %s/%s - no trust required", conn->hostname, conn->ipaddr); if ((BN_cmp(savedkey->e, key->e) == 0) && (BN_cmp(savedkey->n, key->n) == 0)) { Log(LOG_LEVEL_VERBOSE, "The public key identity was confirmed as %s@%s", conn->username, conn->hostname); SendTransaction(conn->conn_info, "OK: key accepted", 0, CF_DONE); RSA_free(savedkey); return true; } } /* Finally, if we're still here then the key is new (not in ppkeys * directory): Allow access only if host is listed in "trustkeysfrom" body * server control option. */ if ((SV.trustkeylist != NULL) && (IsMatchItemIn(SV.trustkeylist, MapAddress(conn->ipaddr)))) { Log(LOG_LEVEL_VERBOSE, "Host %s/%s was found in the list of hosts to trust", conn->hostname, conn->ipaddr); SendTransaction(conn->conn_info, "OK: unknown key was accepted on trust", 0, CF_DONE); SavePublicKey(conn->username, udigest, key); return true; } else { Log(LOG_LEVEL_VERBOSE, "No previous key found, and unable to accept this one on trust"); SendTransaction(conn->conn_info, "BAD: key could not be accepted on trust", 0, CF_DONE); return false; } }
void ServerEntryPoint(EvalContext *ctx, char *ipaddr, ConnectionInfo *info) { char intime[64]; time_t now; Log(LOG_LEVEL_VERBOSE, "Obtained IP address of '%s' on socket %d from accept", ipaddr, ConnectionInfoSocket(info)); if ((SV.nonattackerlist) && (!IsMatchItemIn(SV.nonattackerlist, MapAddress(ipaddr)))) { Log(LOG_LEVEL_ERR, "Not allowing connection from non-authorized IP '%s'", ipaddr); cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } if (IsMatchItemIn(SV.attackerlist, MapAddress(ipaddr))) { Log(LOG_LEVEL_ERR, "Denying connection from non-authorized IP '%s'", ipaddr); cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } if ((now = time((time_t *) NULL)) == -1) { now = 0; } PurgeOldConnections(&SV.connectionlist, now); if (!IsMatchItemIn(SV.multiconnlist, MapAddress(ipaddr))) { if (!ThreadLock(cft_count)) { return; } if (IsItemIn(SV.connectionlist, MapAddress(ipaddr))) { ThreadUnlock(cft_count); Log(LOG_LEVEL_ERR, "Denying repeated connection from '%s'", ipaddr); cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } ThreadUnlock(cft_count); } if (SV.logconns) { Log(LOG_LEVEL_INFO, "Accepting connection from %s", ipaddr); } else { Log(LOG_LEVEL_INFO, "Accepting connection from %s", ipaddr); } snprintf(intime, 63, "%d", (int) now); if (!ThreadLock(cft_count)) { cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } PrependItem(&SV.connectionlist, MapAddress(ipaddr), intime); if (!ThreadUnlock(cft_count)) { cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } SpawnConnection(ctx, ipaddr, info); }
/* Checks the "varadmit" legacy ACL. */ static Item *ContextAccessControl(EvalContext *ctx, char *in, ServerConnectionState *conn, int encrypt) { Auth *ap; int access = false; char client_regex[CF_BUFSIZE]; Item *ip, *matches = NULL; int ret = sscanf(in, "CONTEXT %255[^\n]", client_regex); Item *persistent_classes = ListPersistentClasses(); if (ret != 1 || persistent_classes == NULL) { return NULL; } for (ip = persistent_classes; ip != NULL; ip = ip->next) { /* Does the class match the regex that the agent requested? */ if (StringMatchFull(client_regex, ip->name)) { for (ap = SV.varadmit; ap != NULL; ap = ap->next) { /* Does the class match any of the regex in ACLs? */ if (StringMatchFull(ap->path, ip->name)) { Log(LOG_LEVEL_VERBOSE, "Found a matching rule in access list (%s in %s)", ip->name, ap->path); if (ap->classpattern == false) { Log(LOG_LEVEL_ERR, "Context %s requires a literal server item...cannot set variable directly by path", ap->path); access = false; continue; } if ((!encrypt) && (ap->encrypt == true)) { Log(LOG_LEVEL_ERR, "Context %s requires encrypt connection...will not serve", ip->name); access = false; break; } else { Log(LOG_LEVEL_DEBUG, "Checking whether to map root privileges"); if ((IsMatchItemIn(ap->maproot, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->maproot, conn->hostname))) { conn->maproot = true; Log(LOG_LEVEL_VERBOSE, "Mapping root privileges"); } else { Log(LOG_LEVEL_VERBOSE, "No root privileges granted"); } if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = true; Log(LOG_LEVEL_DEBUG, "Access privileges - match found"); } } } } for (ap = SV.vardeny; ap != NULL; ap = ap->next) { if (strcmp(ap->path, ip->name) == 0) { if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = false; Log(LOG_LEVEL_VERBOSE, "Host %s explicitly denied access to context %s", conn->hostname, ip->name); break; } } } if (access) { Log(LOG_LEVEL_VERBOSE, "Host %s granted access to context '%s'", conn->hostname, ip->name); AppendItem(&matches, ip->name, NULL); if (encrypt && LOGENCRYPT) { /* Log files that were marked as requiring encryption */ Log(LOG_LEVEL_INFO, "Host %s granted access to context '%s'", conn->hostname, ip->name); } } else { Log(LOG_LEVEL_VERBOSE, "Host %s denied access to context '%s'", conn->hostname, ip->name); } } } DeleteItemList(persistent_classes); return matches; }
static int AccessControl(EvalContext *ctx, const char *req_path, ServerConnectionState *conn, int encrypt) { int access = false; char transrequest[CF_BUFSIZE]; struct stat statbuf; char translated_req_path[CF_BUFSIZE]; char transpath[CF_BUFSIZE]; /* * /var/cfengine -> $workdir translation. */ TranslatePath(translated_req_path, req_path); if (ResolveFilename(translated_req_path, transrequest)) { Log(LOG_LEVEL_VERBOSE, "Filename %s is resolved to %s", translated_req_path, transrequest); } else { Log(LOG_LEVEL_INFO, "Couldn't resolve (realpath: %s) filename: %s", GetErrorStr(), translated_req_path); return false; /* can't continue without transrequest */ } if (lstat(transrequest, &statbuf) == -1) { Log(LOG_LEVEL_INFO, "Couldn't stat (lstat: %s) filename: %s", GetErrorStr(), transrequest); return false; } Log(LOG_LEVEL_DEBUG, "AccessControl, match (%s,%s) encrypt request = %d", transrequest, conn->hostname, encrypt); if (SV.admit == NULL) { Log(LOG_LEVEL_INFO, "cf-serverd access list is empty, no files are visible"); return false; } conn->maproot = false; for (Auth *ap = SV.admit; ap != NULL; ap = ap->next) { int res = false; Log(LOG_LEVEL_DEBUG, "Examining rule in access list (%s,%s)", transrequest, ap->path); /* TODO MapName when constructing this list. */ strncpy(transpath, ap->path, CF_BUFSIZE - 1); MapName(transpath); /* If everything is allowed */ if ((strcmp(transpath, FILE_SEPARATOR_STR) == 0) || /* or if transpath is a parent directory of transrequest */ (strlen(transrequest) > strlen(transpath) && strncmp(transpath, transrequest, strlen(transpath)) == 0 && transrequest[strlen(transpath)] == FILE_SEPARATOR) || /* or if it's an exact match */ (strcmp(transpath, transrequest) == 0)) { res = true; } /* Exact match means single file to admit */ if (strcmp(transpath, transrequest) == 0) { res = true; } if (res) { Log(LOG_LEVEL_VERBOSE, "Found a matching rule in access list (%s in %s)", transrequest, transpath); if (stat(transpath, &statbuf) == -1) { Log(LOG_LEVEL_INFO, "Warning cannot stat file object %s in admit/grant, or access list refers to dangling link", transpath); continue; } if ((!encrypt) && (ap->encrypt == true)) { Log(LOG_LEVEL_ERR, "File %s requires encrypt connection...will not serve", transpath); access = false; } else { Log(LOG_LEVEL_DEBUG, "Checking whether to map root privileges.."); if ((IsMatchItemIn(ap->maproot, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->maproot, conn->hostname))) { conn->maproot = true; Log(LOG_LEVEL_VERBOSE, "Mapping root privileges to access non-root files"); } if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = true; Log(LOG_LEVEL_DEBUG, "Access granted to host: %s", conn->ipaddr); } } break; } } for (Auth *dp = SV.deny; dp != NULL; dp = dp->next) { strncpy(transpath, dp->path, CF_BUFSIZE - 1); MapName(transpath); /* If everything is denied */ if ((strcmp(transpath, FILE_SEPARATOR_STR) == 0) || /* or if transpath is a parent directory of transrequest */ (strlen(transrequest) > strlen(transpath) && strncmp(transpath, transrequest, strlen(transpath)) == 0 && transrequest[strlen(transpath)] == FILE_SEPARATOR) || /* or if it's an exact match */ (strcmp(transpath, transrequest) == 0)) { if ((IsMatchItemIn(dp->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, dp->accesslist, conn->hostname))) { access = false; Log(LOG_LEVEL_INFO, "Host '%s' in deny list, explicitly denying access to '%s'", conn->ipaddr, transrequest); break; } } } if (access) { Log(LOG_LEVEL_VERBOSE, "Host %s granted access to %s", conn->hostname, req_path); if (encrypt && LOGENCRYPT) { /* Log files that were marked as requiring encryption */ Log(LOG_LEVEL_INFO, "Host %s granted access to %s", conn->hostname, req_path); } } else { Log(LOG_LEVEL_INFO, "Host %s denied access to %s", conn->hostname, req_path); } return access; }
/* Checks the "varadmit" legacy ACL. */ static int LiteralAccessControl(EvalContext *ctx, char *in, ServerConnectionState *conn, int encrypt) { Auth *ap; int access = false; char name[CF_BUFSIZE]; name[0] = '\0'; if (strncmp(in, "VAR", 3) == 0) { sscanf(in, "VAR %255[^\n]", name); } else if (strncmp(in, "CALL_ME_BACK", strlen("CALL_ME_BACK")) == 0) { sscanf(in, "CALL_ME_BACK %255[^\n]", name); } else { sscanf(in, "QUERY %128s", name); } conn->maproot = false; for (ap = SV.varadmit; ap != NULL; ap = ap->next) { Log(LOG_LEVEL_VERBOSE, "Examining rule in access list (%s,%s)?", name, ap->path); if (strcmp(ap->path, name) == 0) /* exact match */ { Log(LOG_LEVEL_VERBOSE, "Found a matching rule in access list (%s in %s)", name, ap->path); if ((!ap->literal) && (!ap->variable)) { Log(LOG_LEVEL_ERR, "Variable/query '%s' requires a literal server item...cannot set variable directly by path", ap->path); access = false; break; } if ((!encrypt) && (ap->encrypt == true)) { Log(LOG_LEVEL_ERR, "Variable %s requires encrypt connection...will not serve", name); access = false; break; } else { Log(LOG_LEVEL_DEBUG, "Checking whether to map root privileges"); if ((IsMatchItemIn(ap->maproot, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->maproot, conn->hostname))) { conn->maproot = true; Log(LOG_LEVEL_VERBOSE, "Mapping root privileges"); } else { Log(LOG_LEVEL_VERBOSE, "No root privileges granted"); } if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = true; Log(LOG_LEVEL_DEBUG, "Access privileges - match found"); } } } } for (ap = SV.vardeny; ap != NULL; ap = ap->next) { if (strcmp(ap->path, name) == 0) { if ((IsMatchItemIn(ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = false; Log(LOG_LEVEL_VERBOSE, "Host %s explicitly denied access to %s", conn->hostname, name); break; } } } if (access) { Log(LOG_LEVEL_VERBOSE, "Host %s granted access to literal '%s'", conn->hostname, name); if (encrypt && LOGENCRYPT) { /* Log files that were marked as requiring encryption */ Log(LOG_LEVEL_INFO, "Host %s granted access to literal '%s'", conn->hostname, name); } } else { Log(LOG_LEVEL_VERBOSE, "Host %s denied access to literal '%s'", conn->hostname, name); } return access; }
static int AuthorizeRoles(EvalContext *ctx, ServerConnectionState *conn, char *args) { char *sp; Auth *ap; char userid1[CF_MAXVARSIZE], userid2[CF_MAXVARSIZE]; Rlist *rp, *defines = NULL; int permitted = false; snprintf(userid1, CF_MAXVARSIZE, "%s@%s", conn->username, conn->hostname); snprintf(userid2, CF_MAXVARSIZE, "%s@%s", conn->username, conn->ipaddr); Log(LOG_LEVEL_VERBOSE, "Checking authorized roles in %s", args); if (strncmp(args, "--define", strlen("--define")) == 0) { sp = args + strlen("--define"); } else { sp = args + strlen("-D"); } while (*sp == ' ') { sp++; } defines = RlistFromSplitRegex(ctx, sp, "[,:;]", 99, false); /* For each user-defined class attempt, check RBAC */ for (rp = defines; rp != NULL; rp = rp->next) { Log(LOG_LEVEL_VERBOSE, "Verifying %s", RlistScalarValue(rp)); for (ap = SV.roles; ap != NULL; ap = ap->next) { if (FullTextMatch(ctx, ap->path, RlistScalarValue(rp))) { /* We have a pattern covering this class - so are we allowed to activate it? */ if ((IsMatchItemIn(ctx, ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname)) || (IsRegexItemIn(ctx, ap->accesslist, userid1)) || (IsRegexItemIn(ctx, ap->accesslist, userid2)) || (IsRegexItemIn(ctx, ap->accesslist, conn->username))) { Log(LOG_LEVEL_VERBOSE, "Attempt to define role/class %s is permitted", RlistScalarValue(rp)); permitted = true; } else { Log(LOG_LEVEL_VERBOSE, "Attempt to define role/class %s is denied", RlistScalarValue(rp)); RlistDestroy(defines); return false; } } } } if (permitted) { Log(LOG_LEVEL_VERBOSE, "Role activation allowed"); } else { Log(LOG_LEVEL_VERBOSE, "Role activation disallowed - abort execution"); } RlistDestroy(defines); return permitted; }
/** * @brief Accept a TLS connection and authenticate and identify. * @note Various fields in #conn are set, like username and keyhash. */ int ServerTLSSessionEstablish(ServerConnectionState *conn) { int ret; if (ConnectionInfoConnectionStatus(conn->conn_info) != CF_CONNECTION_ESTABLISHED) { assert(ConnectionInfoSSL(conn->conn_info) == NULL); SSL *ssl = SSL_new(SSLSERVERCONTEXT); if (ssl == NULL) { Log(LOG_LEVEL_ERR, "SSL_new: %s", TLSErrorString(ERR_get_error())); return -1; } ConnectionInfoSetSSL(conn->conn_info, ssl); /* Pass conn_info inside the ssl struct for TLSVerifyCallback(). */ SSL_set_ex_data(ssl, CONNECTIONINFO_SSL_IDX, conn->conn_info); /* Now we are letting OpenSSL take over the open socket. */ SSL_set_fd(ssl, ConnectionInfoSocket(conn->conn_info)); ret = SSL_accept(ssl); if (ret <= 0) { TLSLogError(ssl, LOG_LEVEL_ERR, "Failed to accept TLS connection", ret); return -1; } Log(LOG_LEVEL_VERBOSE, "TLS cipher negotiated: %s, %s", SSL_get_cipher_name(ssl), SSL_get_cipher_version(ssl)); Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust..."); /* Send/Receive "CFE_v%d" version string, agree on version, receive identity (username) of peer. */ char username[sizeof(conn->username)] = ""; bool b = ServerIdentificationDialog(conn->conn_info, username, sizeof(username)); if (b != true) { return -1; } /* We *now* (maybe a bit late) verify the key that the client sent us in * the TLS handshake, since we need the username to do so. TODO in the * future store keys irrelevant of username, so that we can match them * before IDENTIFY. */ ret = TLSVerifyPeer(conn->conn_info, conn->ipaddr, username); if (ret == -1) /* error */ { return -1; } if (ret == 1) /* trusted key */ { Log(LOG_LEVEL_VERBOSE, "%s: Client is TRUSTED, public key MATCHES stored one.", KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); } if (ret == 0) /* untrusted key */ { if ((SV.trustkeylist != NULL) && (IsMatchItemIn(SV.trustkeylist, conn->ipaddr))) { Log(LOG_LEVEL_VERBOSE, "Peer was found in \"trustkeysfrom\" list"); Log(LOG_LEVEL_NOTICE, "Trusting new key: %s", KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); SavePublicKey(username, KeyPrintableHash(conn->conn_info->remote_key), KeyRSA(ConnectionInfoKey(conn->conn_info))); } else { Log(LOG_LEVEL_NOTICE, "TRUST FAILED, peer presented an untrusted key, dropping connection!"); Log(LOG_LEVEL_VERBOSE, "Add peer to \"trustkeysfrom\" if you really want to start trusting this new key."); return -1; } } /* All checks succeeded, set conn->uid (conn->sid for Windows) * according to the received USERNAME identity. */ SetConnIdentity(conn, username); /* No CAUTH, SAUTH in non-classic protocol. */ conn->user_data_set = 1; conn->rsa_auth = 1; LastSaw1(conn->ipaddr, KeyPrintableHash(ConnectionInfoKey(conn->conn_info)), LAST_SEEN_ROLE_ACCEPT); ServerSendWelcome(conn); } return 1; }
Item *ContextAccessControl(EvalContext *ctx, char *in, ServerConnectionState *conn, int encrypt) { Auth *ap; int access = false; char client_regex[CF_BUFSIZE]; CF_DB *dbp; CF_DBC *dbcp; int ksize, vsize; char *key; void *value; time_t now = time(NULL); CfState q; Item *ip, *matches = NULL, *candidates = NULL; sscanf(in, "CONTEXT %255[^\n]", client_regex); if (!OpenDB(&dbp, dbid_state)) { return NULL; } if (!NewDBCursor(dbp, &dbcp)) { Log(LOG_LEVEL_INFO, "Unable to scan persistence cache"); CloseDB(dbp); return NULL; } while (NextDB(dbcp, &key, &ksize, &value, &vsize)) { memcpy((void *) &q, value, sizeof(CfState)); if (now > q.expires) { Log(LOG_LEVEL_VERBOSE, " Persistent class %s expired", key); DBCursorDeleteEntry(dbcp); } else { if (FullTextMatch(ctx, client_regex, key)) { Log(LOG_LEVEL_VERBOSE, " - Found key %s...", key); AppendItem(&candidates, key, NULL); } } } DeleteDBCursor(dbcp); CloseDB(dbp); for (ip = candidates; ip != NULL; ip = ip->next) { for (ap = SV.varadmit; ap != NULL; ap = ap->next) { int res = false; if (FullTextMatch(ctx, ap->path, ip->name)) { res = true; } if (res) { Log(LOG_LEVEL_VERBOSE, "Found a matching rule in access list (%s in %s)", ip->name, ap->path); if (ap->classpattern == false) { Log(LOG_LEVEL_ERR, "Context %s requires a literal server item...cannot set variable directly by path", ap->path); access = false; continue; } if ((!encrypt) && (ap->encrypt == true)) { Log(LOG_LEVEL_ERR, "Context %s requires encrypt connection...will not serve", ip->name); access = false; break; } else { Log(LOG_LEVEL_DEBUG, "Checking whether to map root privileges"); if ((IsMatchItemIn(ctx, ap->maproot, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->maproot, conn->hostname))) { conn->maproot = true; Log(LOG_LEVEL_VERBOSE, "Mapping root privileges"); } else { Log(LOG_LEVEL_VERBOSE, "No root privileges granted"); } if ((IsMatchItemIn(ctx, ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = true; Log(LOG_LEVEL_DEBUG, "Access privileges - match found"); } } } } for (ap = SV.vardeny; ap != NULL; ap = ap->next) { if (strcmp(ap->path, ip->name) == 0) { if ((IsMatchItemIn(ctx, ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = false; Log(LOG_LEVEL_VERBOSE, "Host %s explicitly denied access to context %s", conn->hostname, ip->name); break; } } } if (access) { Log(LOG_LEVEL_VERBOSE, "Host %s granted access to context '%s'", conn->hostname, ip->name); AppendItem(&matches, ip->name, NULL); if (encrypt && LOGENCRYPT) { /* Log files that were marked as requiring encryption */ Log(LOG_LEVEL_INFO, "Host %s granted access to context '%s'", conn->hostname, ip->name); } } else { Log(LOG_LEVEL_VERBOSE, "Host %s denied access to context '%s'", conn->hostname, ip->name); } } DeleteItemList(candidates); return matches; }
int AccessControl(EvalContext *ctx, const char *req_path, ServerConnectionState *conn, int encrypt) { Auth *ap; int access = false; char transrequest[CF_BUFSIZE]; struct stat statbuf; char translated_req_path[CF_BUFSIZE]; char transpath[CF_BUFSIZE]; /* * /var/cfengine -> $workdir translation. */ TranslatePath(translated_req_path, req_path); if (ResolveFilename(translated_req_path, transrequest)) { Log(LOG_LEVEL_VERBOSE, "Filename %s is resolved to %s", translated_req_path, transrequest); } else { Log(LOG_LEVEL_INFO, "Couldn't resolve (realpath: %s) filename: %s", GetErrorStr(), translated_req_path); } if (lstat(transrequest, &statbuf) == -1) { Log(LOG_LEVEL_INFO, "Couldn't stat (lstat: %s) filename: %s", GetErrorStr(), transrequest); return false; } Log(LOG_LEVEL_DEBUG, "AccessControl, match (%s,%s) encrypt request = %d", transrequest, conn->hostname, encrypt); if (SV.admit == NULL) { Log(LOG_LEVEL_INFO, "cf-serverd access list is empty, no files are visible"); return false; } conn->maproot = false; for (ap = SV.admit; ap != NULL; ap = ap->next) { int res = false; Log(LOG_LEVEL_DEBUG, "Examining rule in access list (%s,%s)", transrequest, ap->path); strncpy(transpath, ap->path, CF_BUFSIZE - 1); MapName(transpath); if ((strlen(transrequest) > strlen(transpath)) && (strncmp(transpath, transrequest, strlen(transpath)) == 0) && (transrequest[strlen(transpath)] == FILE_SEPARATOR)) { res = true; /* Substring means must be a / to link, else just a substring og filename */ } /* Exact match means single file to admit */ if (strcmp(transpath, transrequest) == 0) { res = true; } if (strcmp(transpath, "/") == 0) { res = true; } if (res) { Log(LOG_LEVEL_VERBOSE, "Found a matching rule in access list (%s in %s)", transrequest, transpath); if (stat(transpath, &statbuf) == -1) { Log(LOG_LEVEL_INFO, "Warning cannot stat file object %s in admit/grant, or access list refers to dangling link\n", transpath); continue; } if ((!encrypt) && (ap->encrypt == true)) { Log(LOG_LEVEL_ERR, "File %s requires encrypt connection...will not serve", transpath); access = false; } else { Log(LOG_LEVEL_DEBUG, "Checking whether to map root privileges.."); if ((IsMatchItemIn(ctx, ap->maproot, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->maproot, conn->hostname))) { conn->maproot = true; Log(LOG_LEVEL_VERBOSE, "Mapping root privileges to access non-root files"); } if ((IsMatchItemIn(ctx, ap->accesslist, MapAddress(conn->ipaddr))) || (IsRegexItemIn(ctx, ap->accesslist, conn->hostname))) { access = true; Log(LOG_LEVEL_DEBUG, "Access privileges - match found"); } } break; } } if (strncmp(transpath, transrequest, strlen(transpath)) == 0) { for (ap = SV.deny; ap != NULL; ap = ap->next) { if (IsRegexItemIn(ctx, ap->accesslist, conn->hostname)) { access = false; Log(LOG_LEVEL_INFO, "Host %s explicitly denied access to %s", conn->hostname, transrequest); break; } } } if (access) { Log(LOG_LEVEL_VERBOSE, "Host %s granted access to %s", conn->hostname, req_path); if (encrypt && LOGENCRYPT) { /* Log files that were marked as requiring encryption */ Log(LOG_LEVEL_INFO, "Host %s granted access to %s", conn->hostname, req_path); } } else { Log(LOG_LEVEL_INFO, "Host %s denied access to %s", conn->hostname, req_path); } if (!conn->rsa_auth) { Log(LOG_LEVEL_INFO, "Cannot map root access without RSA authentication"); conn->maproot = false; /* only public files accessible */ } return access; }
/** * @brief Accept a TLS connection and authenticate and identify. * @note Various fields in #conn are set, like username and keyhash. */ int ServerTLSSessionEstablish(ServerConnectionState *conn) { int ret; conn->conn_info.ssl = SSL_new(SSLSERVERCONTEXT); if (conn->conn_info.ssl == NULL) { Log(LOG_LEVEL_ERR, "SSL_new: %s", ERR_reason_error_string(ERR_get_error())); return -1; } /* Now we are letting OpenSSL take over the open socket. */ SSL_set_fd(conn->conn_info.ssl, conn->conn_info.sd); ret = SSL_accept(conn->conn_info.ssl); if (ret <= 0) { TLSLogError(conn->conn_info.ssl, LOG_LEVEL_ERR, "Connection handshake", ret); return -1; } Log(LOG_LEVEL_VERBOSE, "TLS cipher negotiated: %s, %s", SSL_get_cipher_name(conn->conn_info.ssl), SSL_get_cipher_version(conn->conn_info.ssl)); Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust..."); /* Send/Receive "CFE_v%d" version string and agree on version. */ ret = ServerNegotiateProtocol(&conn->conn_info); if (ret <= 0) { return -1; } /* Receive IDENTITY USER=asdf plain string. */ ret = ServerIdentifyClient(&conn->conn_info, conn->username, sizeof(conn->username)); if (ret != 1) { return -1; } /* We *now* (maybe a bit late) verify the key that the client sent us in * the TLS handshake, since we need the username to do so. TODO in the * future store keys irrelevant of username, so that we can match them * before IDENTIFY. */ ret = TLSVerifyPeer(&conn->conn_info, conn->ipaddr, conn->username); if (ret == -1) /* error */ { return -1; } if (ret == 1) /* trusted key */ { Log(LOG_LEVEL_VERBOSE, "%s: Client is TRUSTED, public key MATCHES stored one.", conn->conn_info.remote_keyhash_str); } if (ret == 0) /* untrusted key */ { Log(LOG_LEVEL_WARNING, "%s: Client's public key is UNKNOWN!", conn->conn_info.remote_keyhash_str); if ((SV.trustkeylist != NULL) && (IsMatchItemIn(conn->ctx, SV.trustkeylist, MapAddress(conn->ipaddr)))) { Log(LOG_LEVEL_VERBOSE, "Host %s was found in the \"trustkeysfrom\" list", conn->ipaddr); Log(LOG_LEVEL_WARNING, "%s: Explicitly trusting this key from now on.", conn->conn_info.remote_keyhash_str); conn->trust = true; SavePublicKey("root", conn->conn_info.remote_keyhash_str, conn->conn_info.remote_key); } else { Log(LOG_LEVEL_ERR, "TRUST FAILED, WARNING: possible MAN IN THE MIDDLE attack, dropping connection!"); Log(LOG_LEVEL_ERR, "Open server's ACL if you really want to start trusting this new key."); return -1; } } /* skipping CAUTH */ conn->id_verified = 1; /* skipping SAUTH, allow access to read-only files */ conn->rsa_auth = 1; LastSaw1(conn->ipaddr, conn->conn_info.remote_keyhash_str, LAST_SEEN_ROLE_ACCEPT); ServerSendWelcome(conn); return 1; }
void ServerEntryPoint(EvalContext *ctx, const char *ipaddr, ConnectionInfo *info) { time_t now; Log(LOG_LEVEL_VERBOSE, "Obtained IP address of '%s' on socket %d from accept", ipaddr, ConnectionInfoSocket(info)); /* TODO change nonattackerlist, attackerlist and especially connectionlist * to binary searched lists, or remove them from the main thread! */ if ((SV.nonattackerlist) && (!IsMatchItemIn(SV.nonattackerlist, ipaddr))) { Log(LOG_LEVEL_ERR, "Remote host '%s' not in allowconnects, denying connection", ipaddr); cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } if (IsMatchItemIn(SV.attackerlist, ipaddr)) { Log(LOG_LEVEL_ERR, "Remote host '%s' is in denyconnects, denying connection", ipaddr); cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } if ((now = time(NULL)) == -1) { now = 0; } PurgeOldConnections(&SV.connectionlist, now); if (!IsMatchItemIn(SV.multiconnlist, ipaddr)) { if (!ThreadLock(cft_count)) { cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } if (IsItemIn(SV.connectionlist, ipaddr)) { ThreadUnlock(cft_count); Log(LOG_LEVEL_ERR, "Remote host '%s' is not in allowallconnects, denying second simultaneous connection", ipaddr); cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } ThreadUnlock(cft_count); } char intime[PRINTSIZE(now)]; snprintf(intime, sizeof(intime), "%jd", (intmax_t) now); if (!ThreadLock(cft_count)) { cf_closesocket(ConnectionInfoSocket(info)); ConnectionInfoDestroy(&info); return; } PrependItem(&SV.connectionlist, ipaddr, intime); ThreadUnlock(cft_count); SpawnConnection(ctx, ipaddr, info); }
static void *HandleConnection(void *c) { ServerConnectionState *conn = c; int ret; /* Set logging prefix to be the IP address for all of thread's lifetime. */ /* This stack-allocated struct should be valid for all the lifetime of the * thread. Just make sure that after calling DeleteConn() (which frees * ipaddr), you exit the thread right away. */ LoggingPrivContext log_ctx = { .log_hook = LogHook, .param = conn->ipaddr }; LoggingPrivSetContext(&log_ctx); Log(LOG_LEVEL_INFO, "Accepting connection"); /* We test if number of active threads is greater than max, if so we deny connection, if it happened too many times within a short timeframe then we kill ourself.TODO this test should be done *before* spawning the thread. */ ret = ThreadLock(cft_server_children); if (!ret) { Log(LOG_LEVEL_ERR, "Unable to thread-lock, closing connection!"); goto ret2; } else if (ACTIVE_THREADS > CFD_MAXPROCESSES) { if (TRIES > MAXTRIES) { /* This happens when no thread was freed while we had to drop 5 * (or maxconnections/3) consecutive connections, because none of * the existing threads finished. */ Log(LOG_LEVEL_CRIT, "Server seems to be paralyzed. DOS attack? " "Committing apoptosis..."); ThreadUnlock(cft_server_children); FatalError(conn->ctx, "Terminating"); } TRIES++; Log(LOG_LEVEL_ERR, "Too many threads (%d > %d), dropping connection! " "Increase server maxconnections?", ACTIVE_THREADS, CFD_MAXPROCESSES); ThreadUnlock(cft_server_children); goto ret2; } ACTIVE_THREADS++; TRIES = 0; ThreadUnlock(cft_server_children); DisableSendDelays(ConnectionInfoSocket(conn->conn_info)); /* 20 times the connect() timeout should be enough to avoid MD5 * computation timeouts on big files on old slow Solaris 8 machines. */ SetReceiveTimeout(ConnectionInfoSocket(conn->conn_info), CONNTIMEOUT * 20 * 1000); if (ConnectionInfoConnectionStatus(conn->conn_info) != CF_CONNECTION_ESTABLISHED) { /* Decide the protocol used. */ ret = ServerTLSPeek(conn->conn_info); if (ret == -1) { goto ret1; } } ProtocolVersion protocol_version = ConnectionInfoProtocolVersion(conn->conn_info); if (protocol_version == CF_PROTOCOL_LATEST) { ret = ServerTLSSessionEstablish(conn); if (ret == -1) { goto ret1; } } else if (protocol_version < CF_PROTOCOL_LATEST && protocol_version > CF_PROTOCOL_UNDEFINED) { /* This connection is legacy protocol. Do we allow it? */ if (SV.allowlegacyconnects != NULL && /* By default we do */ !IsMatchItemIn(SV.allowlegacyconnects, conn->ipaddr)) { Log(LOG_LEVEL_INFO, "Connection is not using latest protocol, denying"); goto ret1; } } else { UnexpectedError("HandleConnection: ProtocolVersion %d!", ConnectionInfoProtocolVersion(conn->conn_info)); goto ret1; } /* ========================= MAIN LOOPS ========================= */ if (protocol_version >= CF_PROTOCOL_TLS) { /* New protocol does DNS reverse look up of the connected * IP address, to check hostname access_rules. */ if (NEED_REVERSE_LOOKUP) { ret = getnameinfo((const struct sockaddr *) &conn->conn_info->ss, conn->conn_info->ss_len, conn->revdns, sizeof(conn->revdns), NULL, 0, NI_NAMEREQD); if (ret != 0) { Log(LOG_LEVEL_INFO, "Reverse lookup failed (getnameinfo: %s)!", gai_strerror(ret)); } else { Log(LOG_LEVEL_INFO, "Hostname (reverse looked up): %s", conn->revdns); } } while (BusyWithNewProtocol(conn->ctx, conn)) { } } else if (protocol_version == CF_PROTOCOL_CLASSIC) { while (BusyWithClassicConnection(conn->ctx, conn)) { } } /* ============================================================ */ Log(LOG_LEVEL_INFO, "Connection closed, terminating thread"); ret1: ThreadLock(cft_server_children); ACTIVE_THREADS--; ThreadUnlock(cft_server_children); ret2: DeleteConn(conn); return NULL; } /***************************************************************/ /* Toolkit/Class: conn */ /***************************************************************/ static ServerConnectionState *NewConn(EvalContext *ctx, ConnectionInfo *info) { ServerConnectionState *conn = NULL; struct sockaddr_storage addr; socklen_t size = sizeof(addr); if (getsockname(ConnectionInfoSocket(info), (struct sockaddr *)&addr, &size) == -1) { Log(LOG_LEVEL_ERR, "Could not obtain socket address. (getsockname: '%s')", GetErrorStr()); return NULL; } conn = xcalloc(1, sizeof(*conn)); conn->ctx = ctx; conn->conn_info = info; conn->user_data_set = false; conn->rsa_auth = false; conn->hostname[0] = '\0'; conn->ipaddr[0] = '\0'; conn->username[0] = '\0'; conn->session_key = NULL; conn->encryption_type = 'c'; conn->maproot = false; /* Only public files (chmod o+r) accessible */ conn->revdns[0] = '\0'; Log(LOG_LEVEL_DEBUG, "New socket %d", ConnectionInfoSocket(info)); return conn; }