bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn) { /* The CF_BUFEXT extra space is there to ensure we're not reading out of * bounds in commands that carry extra binary arguments, like MD5. */ char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = { 0 }; char sendbuffer[CF_BUFSIZE] = { 0 }; char filename[CF_BUFSIZE + 1]; /* +1 for appending slash sometimes */ int received; ServerFileGetState get_args = { 0 }; /* We already encrypt because of the TLS layer, no need to encrypt more. */ const int encrypted = 0; /* Legacy stuff only for old protocol. */ assert(conn->rsa_auth == 1); assert(conn->user_data_set == 1); /* Receive up to CF_BUFSIZE - 1 bytes. */ received = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); if (received == -1 || received == 0) { return false; } if (strlen(recvbuffer) == 0) { Log(LOG_LEVEL_WARNING, "Got NULL transmission, skipping!"); return true; } /* Don't process request if we're signalled to exit. */ if (IsPendingTermination()) { Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection"); return false; } /* TODO break recvbuffer here: command, param1, param2 etc. */ switch (GetCommandNew(recvbuffer)) { case PROTOCOL_COMMAND_EXEC: { /* TODO check it is always file, never directory, no end with '/' */ char args[256]; int ret = sscanf(recvbuffer, "EXEC %255[^\n]", args); if (ret != 1) /* No arguments, use default args. */ { args[0] = '\0'; } if (!AllowedUser(conn->username)) { Log(LOG_LEVEL_INFO, "EXEC denied due to not allowed user: %s", conn->username); RefuseAccess(conn, recvbuffer); return true; } char arg0[PATH_MAX]; size_t zret = CommandArg0_bound(arg0, CFRUNCOMMAND, sizeof(arg0)); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(arg0, sizeof(arg0)); if (zret == (size_t) -1) { goto protocol_error; } /* TODO EXEC should not just use paths_acl access control, but * specific "path_exec" ACL. Then different command execution could be * allowed per host, and the host could even set argv[0] in his EXEC * request, rather than only the arguments. */ if (acl_CheckPath(paths_acl, arg0, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "EXEC denied due to ACL for file: %s", arg0); RefuseAccess(conn, recvbuffer); return true; } if (!MatchClasses(ctx, conn)) { Log(LOG_LEVEL_INFO, "EXEC denied due to failed class match"); Terminate(conn->conn_info); return true; } DoExec(ctx, conn, args); Terminate(conn->conn_info); return true; } case PROTOCOL_COMMAND_VERSION: snprintf(sendbuffer, sizeof(sendbuffer), "OK: %s", Version()); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; case PROTOCOL_COMMAND_GET: { int ret = sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename); if (ret != 2 || get_args.buf_size <= 0 || get_args.buf_size > CF_BUFSIZE) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "GET", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { goto protocol_error; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "GET", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to GET: %s", filename); RefuseAccess(conn, recvbuffer); return true; } memset(sendbuffer, 0, sizeof(sendbuffer)); if (get_args.buf_size >= CF_BUFSIZE) { get_args.buf_size = 2048; } /* TODO eliminate! */ get_args.conn = conn; get_args.encrypt = false; get_args.replybuff = sendbuffer; get_args.replyfile = filename; CfGetFile(&get_args); return true; } case PROTOCOL_COMMAND_OPENDIR: { memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "OPENDIR %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "OPENDIR", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { goto protocol_error; } /* OPENDIR *must* be directory. */ PathAppendTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "OPENDIR", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to OPENDIR: %s", filename); RefuseAccess(conn, recvbuffer); return true; } CfOpenDirectory(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_SYNCH: { long time_no_see = 0; memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename); if (ret != 2 || filename[0] == '\0') { goto protocol_error; } time_t tloc = time(NULL); if (tloc == -1) { /* Should never happen. */ Log(LOG_LEVEL_ERR, "Couldn't read system clock. (time: %s)", GetErrorStr()); SendTransaction(conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE); return true; } time_t trem = (time_t) time_no_see; int drift = (int) (tloc - trem); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "STAT", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } if (IsDirReal(filename) == 1) { PathAppendTrailingSlash(filename, strlen(filename)); } else { PathRemoveTrailingSlash(filename, strlen(filename)); } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "STAT", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to STAT: %s", filename); RefuseAccess(conn, recvbuffer); return true; } if (DENYBADCLOCKS && (drift * drift > CLOCK_DRIFT * CLOCK_DRIFT)) { snprintf(sendbuffer, sizeof(sendbuffer), "BAD: Clocks are too far unsynchronized %ld/%ld", (long) tloc, (long) trem); Log(LOG_LEVEL_INFO, "denybadclocks %s", sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } else { Log(LOG_LEVEL_DEBUG, "Clocks were off by %ld", (long) tloc - (long) trem); StatFile(conn, sendbuffer, filename); } return true; } case PROTOCOL_COMMAND_MD5: { int ret = sscanf(recvbuffer, "MD5 %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "MD5", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { goto protocol_error; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "MD5", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to file: %s", filename); RefuseAccess(conn, recvbuffer); return true; } assert(CF_DEFAULT_DIGEST_LEN <= EVP_MAX_MD_SIZE); unsigned char digest[EVP_MAX_MD_SIZE + 1]; assert(CF_BUFSIZE + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN <= sizeof(recvbuffer)); memcpy(digest, recvbuffer + strlen(recvbuffer) + CF_SMALL_OFFSET, CF_DEFAULT_DIGEST_LEN); CompareLocalHash(filename, digest, sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } case PROTOCOL_COMMAND_VAR: { char var[256]; int ret = sscanf(recvbuffer, "VAR %255[^\n]", var); if (ret != 1) { goto protocol_error; } /* TODO if this is literals_acl, then when should I check vars_acl? */ if (acl_CheckExact(literals_acl, var, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to variable: %s", var); RefuseAccess(conn, recvbuffer); return true; } GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted); return true; } case PROTOCOL_COMMAND_CONTEXT: { char client_regex[256]; int ret = sscanf(recvbuffer, "CONTEXT %255[^\n]", client_regex); if (ret != 1) { goto protocol_error; } /* WARNING: this comes from legacy code and must be killed if we care * about performance. We should not accept regular expressions from * the client, but this will break backwards compatibility. * * I replicated the code in raw form here to emphasize complexity, * it's the only *slow* command currently in the protocol. */ Item *persistent_classes = ListPersistentClasses(); Item *matched_classes = NULL; /* For all persistent classes */ for (Item *ip = persistent_classes; ip != NULL; ip = ip->next) { const char *class_name = ip->name; /* Does this class match the regex the client sent? */ if (StringMatchFull(client_regex, class_name)) { /* For all ACLs */ for (size_t i = 0; i < classes_acl->len; i++) { struct resource_acl *racl = &classes_acl->acls[i]; /* Does this ACL apply to this host? */ if (access_CheckResource(racl, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == true) { const char *allowed_classes_regex = classes_acl->resource_names->list[i]->str; /* Does this ACL admits access for this class to the * connected host? */ if (StringMatchFull(allowed_classes_regex, class_name)) { PrependItem(&matched_classes, class_name, NULL); } } } } } if (matched_classes == NULL) { Log(LOG_LEVEL_INFO, "No allowed classes for remoteclassesmatching: %s", client_regex); RefuseAccess(conn, recvbuffer); return true; } ReplyServerContext(conn, encrypted, matched_classes); return true; } case PROTOCOL_COMMAND_QUERY: { char query[256], name[128]; int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query); int ret2 = sscanf(recvbuffer, "QUERY %127s", name); if (ret1 != 1 || ret2 != 1) { goto protocol_error; } if (acl_CheckExact(query_acl, name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to query: %s", query); RefuseAccess(conn, recvbuffer); return true; } if (GetServerQuery(conn, recvbuffer, encrypted)) { return true; } break; } case PROTOCOL_COMMAND_CALL_ME_BACK: if (acl_CheckExact(query_acl, "collect_calls", conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to Call-Collect, check the ACL for class: collect_calls"); return false; } ReceiveCollectCall(conn); /* On success that returned true; otherwise, it did all * relevant Log()ging. Either way, it closed the connection, * so we're no longer busy with it: */ return false; case PROTOCOL_COMMAND_BAD: Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer); } /* We should only reach this point if something went really bad, and * close connection. In all other cases (like access denied) connection * shouldn't be closed. * TODO So we need this function to return more than true/false, because * now we return true even when access is denied! E.g. return -1 for * error, 0 on success, 1 on access denied. It can be an option if * connection will close on denial. */ protocol_error: strcpy(sendbuffer, "BAD: Request denied"); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); Log(LOG_LEVEL_INFO, "Closing connection due to request: %s", recvbuffer); return false; }
static void KeepFileAccessPromise(const EvalContext *ctx, const Promise *pp) { char path[PATH_MAX]; size_t path_len = strlen(pp->promiser); if (path_len > sizeof(path) - 1) { goto err_too_long; } memcpy(path, pp->promiser, path_len + 1); /* Resolve symlinks and canonicalise access_rules path. */ size_t ret2 = PreprocessRequestPath(path, sizeof(path)); if (ret2 == (size_t) -1) { if (errno != ENOENT) /* something went wrong */ { Log(LOG_LEVEL_ERR, "Failed to canonicalize path '%s' in access_rules, ignoring!", pp->promiser); return; } else /* file does not exist, it doesn't matter */ { Log(LOG_LEVEL_INFO, "Path does not exist, it's added as-is in access rules: %s", path); Log(LOG_LEVEL_INFO, "WARNING: this means that (not) having a trailing slash defines if it's (not) a directory!"); /* Legacy: convert trailing "/." to "/" */ if (path_len >= 2 && path[path_len - 1] == '.' && path[path_len - 2] == '/') { path[path_len - 1] = '\0'; path_len--; } } } else /* file exists, path canonicalised */ { /* If it's a directory append trailing '/' */ path_len = ret2; int is_dir = IsDirReal(path); if (is_dir == 1 && path[path_len - 1] != FILE_SEPARATOR) { if (path_len + 2 > sizeof(path)) { goto err_too_long; } PathAppendTrailingSlash(path, path_len); path_len++; } } size_t pos = acl_SortedInsert(&paths_acl, path); if (pos == (size_t) -1) { /* Should never happen, besides when allocation fails. */ Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr()); exit(255); } /* Legacy code */ if (path_len != 1) { DeleteSlash(path); } Auth *ap = GetOrCreateAuth(path, &SV.admit, &SV.admittail); Auth *dp = GetOrCreateAuth(path, &SV.deny, &SV.denytail); AccessPromise_AddAccessConstraints(ctx, pp, &paths_acl->acls[pos], ap, dp); return; err_too_long: Log(LOG_LEVEL_ERR, "Path '%s' in access_rules is too long (%zu > %d), ignoring!", pp->promiser, strlen(pp->promiser), PATH_MAX); return; }
/** * Currently this function returns false when we want the connection * closed, and true, when we want to proceed further with requests. * * @TODO So we need this function to return more than true/false, because now * we return true even when access is denied! E.g. return -1 for error, 0 on * success, 1 on access denied. It can be an option if connection will close * on denial. */ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn) { /* The CF_BUFEXT extra space is there to ensure we're not *reading* out of * bounds in commands that carry extra binary arguments, like MD5. */ char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = { 0 }; /* This size is the max we can SendTransaction(). */ char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET] = { 0 }; char filename[CF_BUFSIZE + 1]; /* +1 for appending slash sometimes */ ServerFileGetState get_args = { 0 }; /* We already encrypt because of the TLS layer, no need to encrypt more. */ const int encrypted = 0; /* Legacy stuff only for old protocol. */ assert(conn->rsa_auth == 1); assert(conn->user_data_set == 1); /* Receive up to CF_BUFSIZE - 1 bytes. */ const int received = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); if (received == -1) { /* Already Log()ged in case of error. */ return false; } if (received > CF_BUFSIZE - 1) { UnexpectedError("Received transaction of size %d", received); return false; } if (strlen(recvbuffer) == 0) { Log(LOG_LEVEL_WARNING, "Got NULL transmission (of size %d)", received); return true; } /* Don't process request if we're signalled to exit. */ if (IsPendingTermination()) { Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection"); return false; } /* TODO break recvbuffer here: command, param1, param2 etc. */ switch (GetCommandNew(recvbuffer)) { case PROTOCOL_COMMAND_EXEC: { const size_t EXEC_len = strlen(PROTOCOL_NEW[PROTOCOL_COMMAND_EXEC]); /* Assert recvbuffer starts with EXEC. */ assert(strncmp(PROTOCOL_NEW[PROTOCOL_COMMAND_EXEC], recvbuffer, EXEC_len) == 0); char *args = &recvbuffer[EXEC_len]; args += strspn(args, " \t"); /* bypass spaces */ Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "EXEC", args); bool b = DoExec2(ctx, conn, args, sendbuffer, sizeof(sendbuffer)); /* In the end we might keep the connection open (return true) to be * ready for next requests, but we must always send the TERMINATOR * string so that the client can close the connection at will. */ Terminate(conn->conn_info); return b; } case PROTOCOL_COMMAND_VERSION: snprintf(sendbuffer, sizeof(sendbuffer), "OK: %s", Version()); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; case PROTOCOL_COMMAND_GET: { int ret = sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename); if (ret != 2 || get_args.buf_size <= 0 || get_args.buf_size > CF_BUFSIZE) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "GET", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "GET", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to GET: %s", filename); RefuseAccess(conn, recvbuffer); return true; } memset(sendbuffer, 0, sizeof(sendbuffer)); if (get_args.buf_size >= CF_BUFSIZE) { get_args.buf_size = 2048; } /* TODO eliminate! */ get_args.conn = conn; get_args.encrypt = false; get_args.replybuff = sendbuffer; get_args.replyfile = filename; CfGetFile(&get_args); return true; } case PROTOCOL_COMMAND_OPENDIR: { memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "OPENDIR %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "OPENDIR", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } /* OPENDIR *must* be directory. */ PathAppendTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "OPENDIR", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to OPENDIR: %s", filename); RefuseAccess(conn, recvbuffer); return true; } CfOpenDirectory(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_SYNCH: { long time_no_see = 0; memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename); if (ret != 2 || filename[0] == '\0') { goto protocol_error; } time_t tloc = time(NULL); if (tloc == -1) { /* Should never happen. */ Log(LOG_LEVEL_ERR, "Couldn't read system clock. (time: %s)", GetErrorStr()); SendTransaction(conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE); return true; } time_t trem = (time_t) time_no_see; int drift = (int) (tloc - trem); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "STAT", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } if (IsDirReal(filename) == 1) { PathAppendTrailingSlash(filename, strlen(filename)); } else { PathRemoveTrailingSlash(filename, strlen(filename)); } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "STAT", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to STAT: %s", filename); RefuseAccess(conn, recvbuffer); return true; } Log(LOG_LEVEL_DEBUG, "Clocks were off by %ld", (long) tloc - (long) trem); if (DENYBADCLOCKS && (drift * drift > CLOCK_DRIFT * CLOCK_DRIFT)) { snprintf(sendbuffer, sizeof(sendbuffer), "BAD: Clocks are too far unsynchronized %ld/%ld", (long) tloc, (long) trem); Log(LOG_LEVEL_INFO, "denybadclocks %s", sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } StatFile(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_MD5: { int ret = sscanf(recvbuffer, "MD5 %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "MD5", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { RefuseAccess(conn, recvbuffer); return true; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "MD5", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to file: %s", filename); RefuseAccess(conn, recvbuffer); return true; } assert(CF_DEFAULT_DIGEST_LEN <= EVP_MAX_MD_SIZE); unsigned char digest[EVP_MAX_MD_SIZE + 1]; assert(CF_BUFSIZE + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN <= sizeof(recvbuffer)); memcpy(digest, recvbuffer + strlen(recvbuffer) + CF_SMALL_OFFSET, CF_DEFAULT_DIGEST_LEN); CompareLocalHash(filename, digest, sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } case PROTOCOL_COMMAND_VAR: { char var[256]; int ret = sscanf(recvbuffer, "VAR %255[^\n]", var); if (ret != 1) { goto protocol_error; } /* TODO if this is literals_acl, then when should I check vars_acl? */ if (acl_CheckExact(literals_acl, var, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to variable: %s", var); RefuseAccess(conn, recvbuffer); return true; } GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted); return true; } case PROTOCOL_COMMAND_CONTEXT: { char client_regex[256]; int ret = sscanf(recvbuffer, "CONTEXT %255[^\n]", client_regex); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "CONTEXT", client_regex); /* WARNING: this comes from legacy code and must be killed if we care * about performance. We should not accept regular expressions from * the client, but this will break backwards compatibility. * * I replicated the code in raw form here to emphasize complexity, * it's the only *slow* command currently in the protocol. */ Item *persistent_classes = ListPersistentClasses(); Item *matched_classes = NULL; /* For all persistent classes */ for (Item *ip = persistent_classes; ip != NULL; ip = ip->next) { const char *class_name = ip->name; /* Does this class match the regex the client sent? */ if (StringMatchFull(client_regex, class_name)) { /* Is this class allowed to be given to the specific * host, according to the regexes in the ACLs? */ if (acl_CheckRegex(classes_acl, class_name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info)), NULL) == true) { Log(LOG_LEVEL_DEBUG, "Access granted to class: %s", class_name); PrependItem(&matched_classes, class_name, NULL); } } } if (matched_classes == NULL) { Log(LOG_LEVEL_INFO, "No allowed classes for remoteclassesmatching: %s", client_regex); RefuseAccess(conn, recvbuffer); return true; } ReplyServerContext(conn, encrypted, matched_classes); return true; } case PROTOCOL_COMMAND_QUERY: { char query[256], name[128]; int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query); int ret2 = sscanf(recvbuffer, "QUERY %127s", name); if (ret1 != 1 || ret2 != 1) { goto protocol_error; } if (acl_CheckExact(query_acl, name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to query: %s", query); RefuseAccess(conn, recvbuffer); return true; } if (GetServerQuery(conn, recvbuffer, encrypted)) { return true; } break; } case PROTOCOL_COMMAND_CALL_ME_BACK: /* Server side, handing the collect call off to cf-hub. */ if (acl_CheckExact(query_acl, "collect_calls", conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to Call-Collect, check the ACL for class: collect_calls"); return false; } ReceiveCollectCall(conn); /* On success that returned true; otherwise, it did all * relevant Log()ging. Either way, we're no longer busy with * it and our caller can close the connection: */ return false; case PROTOCOL_COMMAND_BAD: Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer); } /* We should only reach this point if something went really bad, and * close connection. In all other cases (like access denied) connection * shouldn't be closed. */ protocol_error: strcpy(sendbuffer, "BAD: Request denied"); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); Log(LOG_LEVEL_INFO, "Closing connection due to illegal request: %s", recvbuffer); return false; }