Exemple #1
0
int BusyWithClassicConnection(EvalContext *ctx, ServerConnectionState *conn)
{
    time_t tloc, trem = 0;
    char recvbuffer[CF_BUFSIZE + CF_BUFEXT], check[CF_BUFSIZE];
    char sendbuffer[CF_BUFSIZE] = { 0 };
    char filename[CF_BUFSIZE], buffer[CF_BUFSIZE], args[CF_BUFSIZE], out[CF_BUFSIZE];
    long time_no_see = 0;
    unsigned int len = 0;
    int drift, plainlen, received, encrypted = 0;
    ServerFileGetState get_args;
    Item *classes;

    memset(recvbuffer, 0, CF_BUFSIZE + CF_BUFEXT);
    memset(&get_args, 0, sizeof(get_args));

    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())
    {
        return false;
    }

    ProtocolCommandClassic command = GetCommandClassic(recvbuffer);

    switch (command)
    {
    /* Plain text authentication; this MUST be the first command client
       using classic protocol is sending. */
    case PROTOCOL_COMMAND_AUTH_PLAIN:
        SetConnectionData(conn, (char *) (recvbuffer + strlen("CAUTH ")));

        if (conn->username == NULL || IsUserNameValid(conn->username) == false)
        {
            Log(LOG_LEVEL_INFO, "Client is sending wrong username: '******'", conn->username);
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        /* This is used only for forcing correct state of state machine while
           connecting and authenticating user using classic protocol. */
        conn->user_data_set = true;

        return true;

    /* This MUST be exactly second command client using classic protocol is sending.
       This is where key agreement takes place. */
    case PROTOCOL_COMMAND_AUTH_SECURE:
        /* First command was ommited by client; this is protocol violation. */
        if (!conn->user_data_set)
        {
            Log(LOG_LEVEL_INFO, "Client is not verified; rejecting connection");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        conn->rsa_auth = AuthenticationDialogue(conn, recvbuffer, received);
        if (!conn->rsa_auth)
        {
            Log(LOG_LEVEL_INFO, "Auth dialogue error");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        return true;
    default:
        break;
    }

    /* At this point we should have both user_data_set and rsa_auth set to perform any operation.
       We can check only for second one as without first it won't be set up. */
    if (!conn->rsa_auth)
    {
        Log(LOG_LEVEL_INFO, "Server refusal due to no RSA authentication [command: %d]", command);
        RefuseAccess(conn, recvbuffer);
        return false;
    }

    /* We have to have key at this point. */
    assert(conn->session_key);

    /* At this point we can safely do next switch and make sure user is authenticated. */
    switch (command)
    {
    case PROTOCOL_COMMAND_EXEC:
        memset(args, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "EXEC %255[^\n]", args);

        if (!AllowedUser(conn->username))
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to non-allowed user");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        if (!AccessControl(ctx, CommandArg0(CFRUNCOMMAND), conn, false))
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to denied access to requested object");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        if (!MatchClasses(ctx, conn))
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to failed class/context match");
            Terminate(conn->conn_info);
            return false;
        }

        DoExec(ctx, conn, args);
        Terminate(conn->conn_info);
        return false;

    case PROTOCOL_COMMAND_VERSION:
        snprintf(sendbuffer, sizeof(sendbuffer), "OK: %s", Version());
        SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
        return conn->user_data_set;

    case PROTOCOL_COMMAND_GET:
        memset(filename, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename);

        if ((get_args.buf_size < 0) || (get_args.buf_size > CF_BUFSIZE))
        {
            Log(LOG_LEVEL_INFO, "GET buffer out of bounds");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        if (!AccessControl(ctx, filename, conn, false))
        {
            Log(LOG_LEVEL_INFO, "Access denied to get object");
            RefuseAccess(conn, recvbuffer);
            return true;
        }

        memset(sendbuffer, 0, sizeof(sendbuffer));

        if (get_args.buf_size >= CF_BUFSIZE)
        {
            get_args.buf_size = 2048;
        }

        get_args.connect = conn;
        get_args.encrypt = false;
        get_args.replybuff = sendbuffer;
        get_args.replyfile = filename;

        CfGetFile(&get_args);

        return true;

    case PROTOCOL_COMMAND_GET_SECURE:
        memset(buffer, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "SGET %u %d", &len, &(get_args.buf_size));

        if (received != len + CF_PROTO_OFFSET)
        {
            Log(LOG_LEVEL_VERBOSE, "Protocol error SGET");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        plainlen = DecryptString(conn->encryption_type, recvbuffer + CF_PROTO_OFFSET, buffer, conn->session_key, len);

        cfscanf(buffer, strlen("GET"), strlen("dummykey"), check, sendbuffer, filename);

        if (strcmp(check, "GET") != 0)
        {
            Log(LOG_LEVEL_INFO, "SGET/GET problem");
            RefuseAccess(conn, recvbuffer);
            return true;
        }

        if ((get_args.buf_size < 0) || (get_args.buf_size > 8192))
        {
            Log(LOG_LEVEL_INFO, "SGET bounding error");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        if (get_args.buf_size >= CF_BUFSIZE)
        {
            get_args.buf_size = 2048;
        }

        Log(LOG_LEVEL_DEBUG, "Confirm decryption, and thus validity of caller");
        Log(LOG_LEVEL_DEBUG, "SGET '%s' with blocksize %d", filename, get_args.buf_size);

        if (!AccessControl(ctx, filename, conn, true))
        {
            Log(LOG_LEVEL_INFO, "Access control error");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        memset(sendbuffer, 0, sizeof(sendbuffer));

        get_args.connect = conn;
        get_args.encrypt = true;
        get_args.replybuff = sendbuffer;
        get_args.replyfile = filename;

        CfEncryptGetFile(&get_args);
        return true;

    case PROTOCOL_COMMAND_OPENDIR_SECURE:
        memset(buffer, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "SOPENDIR %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_VERBOSE, "Protocol error OPENDIR: %d", len);
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);

        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);

        if (strncmp(recvbuffer, "OPENDIR", 7) != 0)
        {
            Log(LOG_LEVEL_INFO, "Opendir failed to decrypt");
            RefuseAccess(conn, recvbuffer);
            return true;
        }

        memset(filename, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "OPENDIR %[^\n]", filename);

        if (!AccessControl(ctx, filename, conn, true))        /* opendir don't care about privacy */
        {
            Log(LOG_LEVEL_INFO, "Access error");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        CfSecOpenDirectory(conn, sendbuffer, filename);
        return true;

    case PROTOCOL_COMMAND_OPENDIR:
        memset(filename, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "OPENDIR %[^\n]", filename);

        if (!AccessControl(ctx, filename, conn, true))        /* opendir don't care about privacy */
        {
            Log(LOG_LEVEL_INFO, "DIR access error");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        CfOpenDirectory(conn, sendbuffer, filename);
        return true;

    case PROTOCOL_COMMAND_SYNC_SECURE:
        memset(buffer, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "SSYNCH %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_VERBOSE, "Protocol error SSYNCH: %d", len);
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);

        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);

        if (plainlen < 0)
        {
            DebugBinOut((char *) conn->session_key, 32, "Session key");
            Log(LOG_LEVEL_ERR, "Bad decrypt (%d)", len);
        }

        if (strncmp(recvbuffer, "SYNCH", 5) != 0)
        {
            Log(LOG_LEVEL_INFO, "No synch");
            RefuseAccess(conn, recvbuffer);
            return true;
        }

        /* roll through, no break */

    case PROTOCOL_COMMAND_SYNC:
        memset(filename, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename);

        trem = (time_t) time_no_see;

        if ((time_no_see == 0) || (filename[0] == '\0'))
        {
            break;
        }

        if ((tloc = time((time_t *) NULL)) == -1)
        {
            Log(LOG_LEVEL_INFO, "Couldn't read system clock. (time: %s)", GetErrorStr());
            SendTransaction(conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE);
            return true;
        }

        drift = (int) (tloc - trem);

        if (!AccessControl(ctx, filename, conn, true))
        {
            Log(LOG_LEVEL_INFO, "Access control in sync");
            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);
            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_SECURE:
        sscanf(recvbuffer, "SMD5 %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_INFO, "Decryption error");
            RefuseAccess(conn, recvbuffer);
            return true;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);
        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);

        if (strncmp(recvbuffer, "MD5", 3) != 0)
        {
            Log(LOG_LEVEL_INFO, "MD5 protocol error");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        /* roll through, no break */

    case PROTOCOL_COMMAND_MD5:
        CompareLocalHash(conn, sendbuffer, recvbuffer);
        return true;

    case PROTOCOL_COMMAND_VAR_SECURE:
        sscanf(recvbuffer, "SVAR %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_INFO, "Decrypt error SVAR");
            RefuseAccess(conn, "decrypt error SVAR");
            return true;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);
        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);
        encrypted = true;

        if (strncmp(recvbuffer, "VAR", 3) != 0)
        {
            Log(LOG_LEVEL_INFO, "VAR protocol defect");
            RefuseAccess(conn, "decryption failure");
            return false;
        }

        /* roll through, no break */

    case PROTOCOL_COMMAND_VAR:
        if (!LiteralAccessControl(ctx, recvbuffer, conn, encrypted))
        {
            Log(LOG_LEVEL_INFO, "Literal access failure");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted);
        return true;

    case PROTOCOL_COMMAND_CONTEXT_SECURE:
        sscanf(recvbuffer, "SCONTEXT %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_INFO, "Decrypt error SCONTEXT, len,received = %d,%d", len, received);
            RefuseAccess(conn, "decrypt error SCONTEXT");
            return true;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);
        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);
        encrypted = true;

        if (strncmp(recvbuffer, "CONTEXT", 7) != 0)
        {
            Log(LOG_LEVEL_INFO, "CONTEXT protocol defect...");
            RefuseAccess(conn, "Decryption failed?");
            return false;
        }

        /* roll through, no break */

    case PROTOCOL_COMMAND_CONTEXT:
        if ((classes = ContextAccessControl(ctx, recvbuffer, conn, encrypted)) == NULL)
        {
            Log(LOG_LEVEL_INFO, "Context access failure on %s", recvbuffer);
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        ReplyServerContext(conn, encrypted, classes);
        return true;

    case PROTOCOL_COMMAND_QUERY_SECURE:
        sscanf(recvbuffer, "SQUERY %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_INFO, "Decrypt error SQUERY");
            RefuseAccess(conn, "decrypt error SQUERY");
            return true;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);
        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);

        if (strncmp(recvbuffer, "QUERY", 5) != 0)
        {
            Log(LOG_LEVEL_INFO, "QUERY protocol defect");
            RefuseAccess(conn, "decryption failure");
            return false;
        }

        if (!LiteralAccessControl(ctx, recvbuffer, conn, true))
        {
            Log(LOG_LEVEL_INFO, "Query access failure");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        if (GetServerQuery(conn, recvbuffer, true))       /* always encrypt */
        {
            return true;
        }

        break;

    case PROTOCOL_COMMAND_CALL_ME_BACK:
        sscanf(recvbuffer, "SCALLBACK %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_INFO, "Decrypt error CALL_ME_BACK");
            RefuseAccess(conn, "decrypt error CALL_ME_BACK");
            return true;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);
        plainlen = DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);

        if (strncmp(recvbuffer, "CALL_ME_BACK collect_calls", strlen("CALL_ME_BACK collect_calls")) != 0)
        {
            Log(LOG_LEVEL_INFO, "CALL_ME_BACK protocol defect");
            RefuseAccess(conn, "decryption failure");
            return false;
        }

        if (!LiteralAccessControl(ctx, recvbuffer, conn, true))
        {
            Log(LOG_LEVEL_INFO, "Query access failure");
            RefuseAccess(conn, recvbuffer);
            return false;
        }

        if (ReceiveCollectCall(conn))
        {
            return true;
        }

    case PROTOCOL_COMMAND_AUTH_PLAIN:
    case PROTOCOL_COMMAND_AUTH_SECURE:
    case PROTOCOL_COMMAND_AUTH:
    case PROTOCOL_COMMAND_CONTEXTS:
    case PROTOCOL_COMMAND_BAD:
        Log(LOG_LEVEL_WARNING, "Unexpected protocol command");
    }

    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;
}
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;
}
Exemple #3
0
void CfGetFile(ServerFileGetState *args)
{
    int fd;
    off_t n_read, total = 0, sendlen = 0, count = 0;
    char sendbuffer[CF_BUFSIZE + 256], filename[CF_BUFSIZE];
    struct stat sb;
    int blocksize = 2048;

    ConnectionInfo *conn_info = &(args->connect)->conn_info;

    TranslatePath(filename, args->replyfile);

    stat(filename, &sb);

    Log(LOG_LEVEL_DEBUG, "CfGetFile('%s'), size = %" PRIdMAX, filename, (intmax_t) sb.st_size);

/* Now check to see if we have remote permission */

    if (!TransferRights(filename, args, &sb))
    {
        RefuseAccess(args->connect, args->buf_size, "");
        snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
        if (conn_info->type == CF_PROTOCOL_CLASSIC)
        {
            SendSocketStream(conn_info->sd, sendbuffer, args->buf_size);
        }
        else if (conn_info->type == CF_PROTOCOL_TLS)
        {
            TLSSend(conn_info->ssl, sendbuffer, args->buf_size);
        }
        return;
    }

/* File transfer */

    if ((fd = open(filename, O_RDONLY)) == -1)
    {
        Log(LOG_LEVEL_ERR, "Open error of file '%s'. (open: %s)",
            filename, GetErrorStr());
        snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
        if (conn_info->type == CF_PROTOCOL_CLASSIC)
        {
            SendSocketStream(conn_info->sd, sendbuffer, args->buf_size);
        }
        else if (conn_info->type == CF_PROTOCOL_TLS)
        {
            TLSSend(conn_info->ssl, sendbuffer, args->buf_size);
        }
    }
    else
    {
        int div = 3;

        if (sb.st_size > 10485760L) /* File larger than 10 MB, checks every 64kB */
        {
            div = 32;
        }

        while (true)
        {
            memset(sendbuffer, 0, CF_BUFSIZE);

            Log(LOG_LEVEL_DEBUG, "Now reading from disk...");

            if ((n_read = read(fd, sendbuffer, blocksize)) == -1)
            {
                Log(LOG_LEVEL_ERR, "Read failed in GetFile. (read: %s)", GetErrorStr());
                break;
            }

            if (n_read == 0)
            {
                break;
            }
            else
            {
                off_t savedlen = sb.st_size;

                /* check the file is not changing at source */

                if (count++ % div == 0)   /* Don't do this too often */
                {
                    if (stat(filename, &sb))
                    {
                        Log(LOG_LEVEL_ERR, "Cannot stat file '%s'. (stat: %s)",
                            filename, GetErrorStr());
                        break;
                    }
                }

                if (sb.st_size != savedlen)
                {
                    snprintf(sendbuffer, CF_BUFSIZE, "%s%s: %s", CF_CHANGEDSTR1, CF_CHANGEDSTR2, filename);

                    if (conn_info->type == CF_PROTOCOL_CLASSIC)
                    {
                        if (SendSocketStream(conn_info->sd, sendbuffer, blocksize) == -1)
                        {
                            Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
                        }
                    }
                    else if (conn_info->type == CF_PROTOCOL_TLS)
                    {
                        if (TLSSend(conn_info->ssl, sendbuffer, blocksize) == -1)
                        {
                            Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
                        }
                    }

                    Log(LOG_LEVEL_DEBUG, "Aborting transfer after %" PRIdMAX ": file is changing rapidly at source.", (intmax_t)total);
                    break;
                }

                if ((savedlen - total) / blocksize > 0)
                {
                    sendlen = blocksize;
                }
                else if (savedlen != 0)
                {
                    sendlen = (savedlen - total);
                }
            }

            total += n_read;

            if (conn_info->type == CF_PROTOCOL_CLASSIC)
            {
                if (SendSocketStream(conn_info->sd, sendbuffer, sendlen) == -1)
                {
                    Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
                    break;
                }
            }
            else if (conn_info->type == CF_PROTOCOL_TLS)
            {
                if (TLSSend(conn_info->ssl, sendbuffer, sendlen) == -1)
                {
                    Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
                    break;
                }
            }
        }

        close(fd);
    }
}
Exemple #4
0
void CfEncryptGetFile(ServerFileGetState *args)
/* Because the stream doesn't end for each file, we need to know the
   exact number of bytes transmitted, which might change during
   encryption, hence we need to handle this with transactions */
{
    int fd, n_read, cipherlen, finlen;
    off_t total = 0, count = 0;
    char sendbuffer[CF_BUFSIZE + 256], out[CF_BUFSIZE], filename[CF_BUFSIZE];
    unsigned char iv[32] =
        { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
    int blocksize = CF_BUFSIZE - 4 * CF_INBAND_OFFSET;
    EVP_CIPHER_CTX ctx;
    char *key, enctype;
    struct stat sb;
    ConnectionInfo *conn_info = &args->connect->conn_info;

    key = (args->connect)->session_key;
    enctype = (args->connect)->encryption_type;

    TranslatePath(filename, args->replyfile);

    stat(filename, &sb);

    Log(LOG_LEVEL_DEBUG, "CfEncryptGetFile('%s'), size = %" PRIdMAX, filename, (intmax_t) sb.st_size);

/* Now check to see if we have remote permission */

    if (!TransferRights(filename, args, &sb))
    {
        RefuseAccess(args->connect, args->buf_size, "");
        FailedTransfer(conn_info);
    }

    EVP_CIPHER_CTX_init(&ctx);

    if ((fd = open(filename, O_RDONLY)) == -1)
    {
        Log(LOG_LEVEL_ERR, "Open error of file '%s'. (open: %s)", filename, GetErrorStr());
        FailedTransfer(conn_info);
    }
    else
    {
        int div = 3;

        if (sb.st_size > 10485760L) /* File larger than 10 MB, checks every 64kB */
        {
            div = 32;
        }

        while (true)
        {
            memset(sendbuffer, 0, CF_BUFSIZE);

            if ((n_read = read(fd, sendbuffer, blocksize)) == -1)
            {
                Log(LOG_LEVEL_ERR, "Read failed in EncryptGetFile. (read: %s)", GetErrorStr());
                break;
            }

            off_t savedlen = sb.st_size;

            if (count++ % div == 0)       /* Don't do this too often */
            {
                Log(LOG_LEVEL_DEBUG, "Restatting '%s' - size %d", filename, n_read);
                if (stat(filename, &sb))
                {
                    Log(LOG_LEVEL_ERR, "Cannot stat file '%s' (stat: %s)",
                            filename, GetErrorStr());
                    break;
                }
            }

            if (sb.st_size != savedlen)
            {
                AbortTransfer(conn_info, filename);
                break;
            }

            total += n_read;

            if (n_read > 0)
            {
                EVP_EncryptInit_ex(&ctx, CfengineCipher(enctype), NULL, key, iv);

                if (!EVP_EncryptUpdate(&ctx, out, &cipherlen, sendbuffer, n_read))
                {
                    FailedTransfer(conn_info);
                    EVP_CIPHER_CTX_cleanup(&ctx);
                    close(fd);
                    return;
                }

                if (!EVP_EncryptFinal_ex(&ctx, out + cipherlen, &finlen))
                {
                    FailedTransfer(conn_info);
                    EVP_CIPHER_CTX_cleanup(&ctx);
                    close(fd);
                    return;
                }
            }

            if (total >= savedlen)
            {
                if (SendTransaction(conn_info, out, cipherlen + finlen, CF_DONE) == -1)
                {
                    Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
                    EVP_CIPHER_CTX_cleanup(&ctx);
                    close(fd);
                    return;
                }
                break;
            }
            else
            {
                if (SendTransaction(conn_info, out, cipherlen + finlen, CF_MORE) == -1)
                {
                    Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
                    close(fd);
                    EVP_CIPHER_CTX_cleanup(&ctx);
                    return;
                }
            }
        }
    }

    EVP_CIPHER_CTX_cleanup(&ctx);
    close(fd);
}
Exemple #5
0
bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
{
    time_t tloc, trem = 0;
    char recvbuffer[CF_BUFSIZE + CF_BUFEXT], sendbuffer[CF_BUFSIZE];
    char filename[CF_BUFSIZE], args[CF_BUFSIZE], out[CF_BUFSIZE];
    long time_no_see = 0;
    unsigned int len = 0;
    int drift, received;
    ServerFileGetState get_args;
    Item *classes;

    /* We never double encrypt within the TLS layer */
    const int encrypted = 0;


    memset(recvbuffer, 0, CF_BUFSIZE + CF_BUFEXT);
    memset(&get_args, 0, sizeof(get_args));

    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())
    {
        return false;
    }

    switch (GetCommandNew(recvbuffer))
    {
    case PROTOCOL_COMMAND_EXEC:
        memset(args, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "EXEC %255[^\n]", args);

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to incorrect identity");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!AllowedUser(conn->username))
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to non-allowed user");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!conn->rsa_auth)
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to no RSA authentication");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!AccessControl(ctx, CommandArg0(CFRUNCOMMAND), conn, false))
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to denied access to requested object");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!MatchClasses(ctx, conn))
        {
            Log(LOG_LEVEL_INFO, "Server refusal due to failed class/context match");
            Terminate(&conn->conn_info);
            return false;
        }

        DoExec(ctx, conn, args);
        Terminate(&conn->conn_info);
        return false;

    case PROTOCOL_COMMAND_VERSION:

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
        }

        snprintf(conn->output, CF_BUFSIZE, "OK: %s", Version());
        SendTransaction(&conn->conn_info, conn->output, 0, CF_DONE);
        return conn->id_verified;

    case PROTOCOL_COMMAND_GET:

        memset(filename, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename);

        if ((get_args.buf_size < 0) || (get_args.buf_size > CF_BUFSIZE))
        {
            Log(LOG_LEVEL_INFO, "GET buffer out of bounds");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!AccessControl(ctx, filename, conn, false))
        {
            Log(LOG_LEVEL_INFO, "Access denied to get object");
            RefuseAccess(conn, 0, recvbuffer);
            return true;
        }

        memset(sendbuffer, 0, CF_BUFSIZE);

        if (get_args.buf_size >= CF_BUFSIZE)
        {
            get_args.buf_size = 2048;
        }

        get_args.connect = 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, CF_BUFSIZE);
        sscanf(recvbuffer, "OPENDIR %[^\n]", filename);

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (!AccessControl(ctx, filename, conn, true))        /* opendir don't care about privacy */
        {
            Log(LOG_LEVEL_INFO, "DIR access error");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        CfOpenDirectory(conn, sendbuffer, filename);
        return true;

    case PROTOCOL_COMMAND_SYNC:

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        memset(filename, 0, CF_BUFSIZE);
        sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename);

        trem = (time_t) time_no_see;

        if ((time_no_see == 0) || (filename[0] == '\0'))
        {
            break;
        }

        if ((tloc = time((time_t *) NULL)) == -1)
        {
            sprintf(conn->output, "Couldn't read system clock\n");
            Log(LOG_LEVEL_INFO, "Couldn't read system clock. (time: %s)", GetErrorStr());
            SendTransaction(&conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE);
            return true;
        }

        drift = (int) (tloc - trem);

        if (!AccessControl(ctx, filename, conn, true))
        {
            Log(LOG_LEVEL_VERBOSE, "AccessControl: access denied");
            RefuseAccess(conn, 0, recvbuffer);
            return true;
        }

        if (DENYBADCLOCKS && (drift * drift > CLOCK_DRIFT * CLOCK_DRIFT))
        {
            snprintf(conn->output, CF_BUFSIZE - 1, "BAD: Clocks are too far unsynchronized %ld/%ld\n", (long) tloc,
                     (long) trem);
            SendTransaction(&conn->conn_info, conn->output, 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:

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return true;
        }

        CompareLocalHash(conn, sendbuffer, recvbuffer);
        return true;

    case PROTOCOL_COMMAND_VAR:

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return true;
        }

        if (!LiteralAccessControl(ctx, recvbuffer, conn, encrypted))
        {
            Log(LOG_LEVEL_INFO, "Literal access failure");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted);
        return true;

    case PROTOCOL_COMMAND_CONTEXT:

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, "Context probe");
            return true;
        }

        if ((classes = ContextAccessControl(ctx, recvbuffer, conn, encrypted)) == NULL)
        {
            Log(LOG_LEVEL_INFO, "Context access failure on %s", recvbuffer);
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        ReplyServerContext(conn, encrypted, classes);
        return true;

    case PROTOCOL_COMMAND_QUERY:

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return true;
        }

        if (!LiteralAccessControl(ctx, recvbuffer, conn, encrypted))
        {
            Log(LOG_LEVEL_INFO, "Query access failure");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }

        if (GetServerQuery(conn, recvbuffer, encrypted))
        {
            return true;
        }

        break;

    case PROTOCOL_COMMAND_CALL_ME_BACK:

        sscanf(recvbuffer, "SCALLBACK %u", &len);

        if ((len >= sizeof(out)) || (received != (len + CF_PROTO_OFFSET)))
        {
            Log(LOG_LEVEL_INFO, "Decrypt error CALL_ME_BACK");
            RefuseAccess(conn, 0, "decrypt error CALL_ME_BACK");
            return true;
        }

        memcpy(out, recvbuffer + CF_PROTO_OFFSET, len);
        DecryptString(conn->encryption_type, out, recvbuffer, conn->session_key, len);

        if (strncmp(recvbuffer, "CALL_ME_BACK collect_calls", strlen("CALL_ME_BACK collect_calls")) != 0)
        {
            Log(LOG_LEVEL_INFO, "CALL_ME_BACK protocol defect");
            RefuseAccess(conn, 0, "decryption failure");
            return false;
        }

        if (!conn->id_verified)
        {
            Log(LOG_LEVEL_INFO, "ID not verified");
            RefuseAccess(conn, 0, recvbuffer);
            return true;
        }

        if (!LiteralAccessControl(ctx, recvbuffer, conn, true))
        {
            Log(LOG_LEVEL_INFO, "Query access failure");
            RefuseAccess(conn, 0, recvbuffer);
            return false;
        }
        return ReceiveCollectCall(conn);

    case PROTOCOL_COMMAND_BAD:
        Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer);
    }

    sprintf(sendbuffer, "BAD: Request denied\n");
    SendTransaction(&conn->conn_info, sendbuffer, 0, CF_DONE);
    Log(LOG_LEVEL_INFO, "Closing connection, due to request: '%s'", recvbuffer);
    return false;
}
Exemple #6
0
/**
 * 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;
}