Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cf-net/cf-net.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ static const char *const HINTS[] =
"Enable basic information output",
"Minimum TLS version to use",
"TLS ciphers to use (comma-separated list)",
"Specify CFEngine protocol to use. Possible values: 'classic', 'tls', 'cookie', 'filestream', 'latest' (default)",
"Specify CFEngine protocol to use. Possible values: 'classic', 'tls', 'cookie', 'filestream', 'leech2', 'latest' (default)",
"Print rsync performance statistics to stderr",
NULL
};
Expand Down
9 changes: 9 additions & 0 deletions cf-serverd/cf-serverd-enterprise-stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#include <server.h>
#include <cf-serverd-functions.h>
#include <patch_stream.h> /* PatchStreamRefuse */
#include <connection_info.h> /* ConnectionInfoSSL */

ENTERPRISE_VOID_FUNC_3ARG_DEFINE_STUB(void, RegisterLiteralServerData,
ARG_UNUSED EvalContext *, ctx,
Expand Down Expand Up @@ -63,6 +65,13 @@ ENTERPRISE_FUNC_1ARG_DEFINE_STUB(bool, ReturnCookies, ARG_UNUSED ServerConnectio
return false;
}

ENTERPRISE_FUNC_2ARG_DEFINE_STUB(bool, ServeLeech2Patch, ServerConnectionState *, conn, ARG_UNUSED const char *, last_hash)
{
assert(conn != NULL);
Log(LOG_LEVEL_VERBOSE, "Serving leech2 patches is only available in CFEngine Enterprise");
return PatchStreamRefuse(ConnectionInfoSSL(conn->conn_info));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}

ENTERPRISE_FUNC_3ARG_DEFINE_STUB(bool, ReturnQueryData, ARG_UNUSED ServerConnectionState *, conn, ARG_UNUSED char *, menu, ARG_UNUSED int, encrypt)
{
return false;
Expand Down
1 change: 1 addition & 0 deletions cf-serverd/cf-serverd-enterprise-stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ typedef void (*ServerEntryPointFunction)(EvalContext *ctx, char *ipaddr, Connect
ENTERPRISE_FUNC_1ARG_DECLARE(bool, ReceiveCollectCall, ServerConnectionState *, conn);

ENTERPRISE_FUNC_1ARG_DECLARE(bool, ReturnCookies, ServerConnectionState *, conn);
ENTERPRISE_FUNC_2ARG_DECLARE(bool, ServeLeech2Patch, ServerConnectionState *, conn, const char *, last_hash);
ENTERPRISE_FUNC_3ARG_DECLARE(bool, ReturnQueryData, ServerConnectionState *, conn, char *, menu, int, encrypt);
ENTERPRISE_FUNC_2ARG_DECLARE(bool, CFTestD_ReturnQueryData, ServerConnectionState *, conn, char *, menu);

Expand Down
68 changes: 58 additions & 10 deletions cf-serverd/server_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@
#include <lastseen.h> /* LastSaw1 */
#include <net.h> /* SendTransaction,ReceiveTransaction */
#include <tls_generic.h> /* TLSSend */
#include <patch_stream.h> /* PatchStreamRefuse */
#include <cf-serverd-enterprise-stubs.h>
#include <connection_info.h>
#include <regex.h> /* StringMatchFull */
#include <known_dirs.h>
#include <file_lib.h> /* IsDirReal */
#include <string_lib.h>

#include "server_access.h" /* access_CheckResource, acl_CheckExact */

Expand Down Expand Up @@ -706,7 +708,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
char *args = &recvbuffer[EXEC_len];
args += strspn(args, " \t"); /* bypass spaces */

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Received:", "EXEC", args);

bool b = DoExec2(ctx, conn, args,
Expand Down Expand Up @@ -736,7 +738,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
goto protocol_error;
}

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Received:", "GET", filename);

/* TODO batch all the following in one function since it's very
Expand All @@ -760,7 +762,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)

PathRemoveTrailingSlash(filename, strlen(filename));

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Translated to:", "GET", filename);

if (acl_CheckPath(paths_acl, filename,
Expand Down Expand Up @@ -799,7 +801,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
goto protocol_error;
}

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Received:", "OPENDIR", filename);

/* sizeof()-1 because we need one extra byte for
Expand All @@ -823,7 +825,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
/* OPENDIR *must* be directory. */
PathAppendTrailingSlash(filename, strlen(filename));

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Translated to:", "OPENDIR", filename);

if (acl_CheckPath(paths_acl, filename,
Expand Down Expand Up @@ -863,7 +865,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
time_t trem = (time_t) time_no_see;
int drift = (int) (tloc - trem);

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Received:", "STAT", filename);

/* sizeof()-1 because we need one extra byte for
Expand Down Expand Up @@ -893,7 +895,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
PathRemoveTrailingSlash(filename, strlen(filename));
}

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Translated to:", "STAT", filename);

if (acl_CheckPath(paths_acl, filename,
Expand Down Expand Up @@ -931,7 +933,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
goto protocol_error;
}

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Received:", "MD5", filename);

/* TODO batch all the following in one function since it's very
Expand All @@ -955,7 +957,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)

PathRemoveTrailingSlash(filename, strlen(filename));

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Translated to:", "MD5", filename);

if (acl_CheckPath(paths_acl, filename,
Expand Down Expand Up @@ -1013,7 +1015,7 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)
goto protocol_error;
}

Log(LOG_LEVEL_VERBOSE, "%14s %7s %s",
Log(LOG_LEVEL_VERBOSE, "%14s %8s %s",
"Received:", "CONTEXT", client_regex);

/* WARNING: this comes from legacy code and must be killed if we care
Expand Down Expand Up @@ -1116,6 +1118,52 @@ bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn)

break;
}
case PROTOCOL_COMMAND_GETPATCH:
{
if (ConnectionInfoProtocolVersion(conn->conn_info) < CF_PROTOCOL_LEECH2)
{
goto protocol_error;
}

char last_hash[41];
int ret = sscanf(recvbuffer, "GETPATCH %40s", last_hash);
if (ret != 1)
{
goto protocol_error;
}

Log(LOG_LEVEL_VERBOSE, "%14s %8s %.7s...",
"Received:", "GETPATCH", last_hash);

if (!StringIsSHA1Hex(last_hash))
{
goto protocol_error;
}

const char *hostkey = KeyPrintableHash(
ConnectionInfoKey(conn->conn_info));
const bool access_to_query_delta = acl_CheckExact(
query_acl, "delta", conn->ipaddr, conn->revdns, hostkey);

if (!access_to_query_delta)
{
Log(LOG_LEVEL_INFO, "access denied to GETPATCH: %s", recvbuffer);
/* Refuse using the stream protocol, as opposed to RefuseAccess(),
* because the client expects stream protocol messages after
* sending the GETPATCH request.
*
* Access denied is not fatal to the connection, so keep it open on
* success. However, if sending the refusal failed, it means that
* the connection is broken (already logged), so close it by
* returning false. */
return PatchStreamRefuse(ConnectionInfoSSL(conn->conn_info));
}

/* Keep the connection open on success. If serving the patch stream
* failed, it means that the connection is broken (already logged), so
* close it by returning false */
return ServeLeech2Patch(conn, last_hash);
}
case PROTOCOL_COMMAND_CALL_ME_BACK:
/* Server side, handing the collect call off to cf-hub. */

Expand Down
2 changes: 2 additions & 0 deletions cf-serverd/server_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ typedef enum
PROTOCOL_COMMAND_QUERY,
PROTOCOL_COMMAND_CALL_ME_BACK,
PROTOCOL_COMMAND_COOKIE,
PROTOCOL_COMMAND_GETPATCH,
PROTOCOL_COMMAND_BAD
} ProtocolCommandNew;

Expand All @@ -62,6 +63,7 @@ static const char *const PROTOCOL_NEW[PROTOCOL_COMMAND_BAD + 1] =
"QUERY",
"SCALLBACK",
"COOKIE",
"GETPATCH",
NULL
};

Expand Down
22 changes: 22 additions & 0 deletions cf-testd/cf-testd.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <mutex.h> // ThreadLock
#include <net.h>
#include <openssl/err.h> // ERR_get_error
#include <patch_stream.h> // PatchStreamServe
#include <policy_server.h> // PolicyServerReadFile
#include <printsize.h> // PRINTSIZE
#include <server_access.h> // acl_Free
Expand Down Expand Up @@ -345,6 +346,8 @@ static bool CFTestD_ProtocolError(
static bool CFTestD_BusyLoop(
ServerConnectionState *conn, CFTestD_Config *config)
{
assert(conn != NULL);

char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = "";
char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET] = "";

Expand Down Expand Up @@ -405,6 +408,25 @@ static bool CFTestD_BusyLoop(

break;
}
case PROTOCOL_COMMAND_GETPATCH:
{
char last_hash[64];
int ret = sscanf(recvbuffer, "GETPATCH %63s", last_hash);
if (ret != 1)
{
break;
}

/* cf-testd has no leech2 state; serve an empty patch so that the
* hub's GETPATCH request succeeds. */
Log(LOG_LEVEL_INFO, "Serving empty leech2 patch");
if (PatchStreamServe(ConnectionInfoSSL(conn->conn_info), "", 0))
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
{
return true;
}

break;
}
case PROTOCOL_COMMAND_BAD:
default:
Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer);
Expand Down
1 change: 1 addition & 0 deletions libcfnet/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ libcfnet_la_SOURCES = \
key.c key.h \
misc.c \
net.c net.h \
patch_stream.c patch_stream.h \
policy_server.c policy_server.h \
protocol.c protocol.h \
protocol_version.c protocol_version.h \
Expand Down
113 changes: 113 additions & 0 deletions libcfnet/patch_stream.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
Copyright 2026 Northern.tech AS

This file is part of CFEngine 3 - written and maintained by Northern.tech AS.

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; version 3.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA

To the extent this program is licensed as part of the Enterprise
versions of CFEngine, the applicable Commercial Open Source License
(COSL) may apply to this file if you as a licensee so wish it. See
included file COSL.txt.
*/

#include <platform.h>

#include <patch_stream.h>

#include <stream_protocol.h>
#include <logging.h>
#include <alloc.h>

bool PatchStreamRefuse(SSL *conn)
{
assert(conn != NULL);

/* The client sends nothing after the request line, so there is no need
* to flush the stream before sending the error message. */
return ProtocolSendError(conn, false, ERROR_MSG_UNSPECIFIED_SERVER_REFUSAL);
}

bool PatchStreamServe(SSL *conn, const void *data, size_t len)
{
assert(conn != NULL);
assert(data != NULL || len == 0);

const char *buf = data;
size_t offset = 0;
bool eof = false;

while (!eof)
{
Comment thread
larsewi marked this conversation as resolved.
size_t chunk = len - offset;
if (chunk > PROTOCOL_MESSAGE_SIZE)
{
chunk = PROTOCOL_MESSAGE_SIZE;
}
eof = (offset + chunk == len);

if (!ProtocolSendMessage(conn, buf + offset, chunk, eof))
{
/* Error is already logged in ProtocolSendMessage() */
return false;
}
offset += chunk;
}

return true;
}

bool PatchStreamFetch(SSL *conn, char **data, size_t *len)
{
assert(conn != NULL);
assert(data != NULL);
assert(len != NULL);

char *buf = NULL;
size_t buf_len = 0;

char msg[PROTOCOL_MESSAGE_SIZE];
size_t msg_len;
bool eof = false;

while (!eof)
{
if (!ProtocolRecvMessage(conn, msg, &msg_len, &eof))
{
/* Error is already logged in ProtocolRecvMessage() */
free(buf);
return false;
}

if (msg_len > 0)
Comment thread
larsewi marked this conversation as resolved.
{
if (msg_len > PATCH_STREAM_MAX_SIZE - buf_len)
{
Log(LOG_LEVEL_ERR,
"Refusing to fetch patch: size exceeds maximum of %d bytes",
PATCH_STREAM_MAX_SIZE);
free(buf);
return false;
}

buf = xrealloc(buf, buf_len + msg_len);
memcpy(buf + buf_len, msg, msg_len);
buf_len += msg_len;
}
}

*data = buf;
*len = buf_len;
return true;
}
Loading
Loading