Skip to content

Commit 4bbaeeb

Browse files
New network protocol version v4 - filestream
Ticket: ENT-12414 Changelog: Title Signed-off-by: Lars Erik Wik <[email protected]> Co-authored-by: Craig Comstock <[email protected]>
1 parent a7b50e8 commit 4bbaeeb

File tree

8 files changed

+118
-18
lines changed

8 files changed

+118
-18
lines changed

cf-agent/verify_files_utils.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1551,7 +1551,7 @@ bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, con
15511551
return false;
15521552
}
15531553

1554-
if (!CopyRegularFileNet(source, ToChangesPath(new),
1554+
if (!CopyRegularFileNet(source, dest, ToChangesPath(new),
15551555
sstat->st_size, attr->copy.encrypt, conn, sstat->st_mode))
15561556
{
15571557
RecordFailure(ctx, pp, attr, "Failed to copy file '%s' from '%s'",

cf-serverd/server_common.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static const int CF_NOSIZE = -1;
5454
#include <mutex.h> /* ThreadLock */
5555
#include <stat_cache.h> /* struct Stat */
5656
#include <unix.h> /* GetUserID() */
57+
#include <file_stream.h>
5758
#include "server_access.h"
5859

5960

@@ -402,6 +403,9 @@ static void FailedTransfer(ConnectionInfo *connection)
402403

403404
void CfGetFile(ServerFileGetState *args)
404405
{
406+
assert(args != NULL);
407+
assert(args->conn != NULL);
408+
405409
int fd;
406410
off_t n_read, total = 0, sendlen = 0, count = 0;
407411
char sendbuffer[CF_BUFSIZE + 256], filename[CF_BUFSIZE - 128];
@@ -421,12 +425,23 @@ void CfGetFile(ServerFileGetState *args)
421425

422426
if (!TransferRights(args->conn, filename, &sb))
423427
{
428+
const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
429+
assert(ProtocolIsKnown(version));
430+
424431
Log(LOG_LEVEL_INFO, "REFUSE access to file: %s", filename);
432+
433+
if (ProtocolSupportsFileStream(version)) {
434+
Log(LOG_LEVEL_VERBOSE, "REFUSAL to user='%s' of request: %s",
435+
NULL_OR_EMPTY(args->conn->username) ? "?" : args->conn->username,
436+
args->replyfile);
437+
FileStreamRefuse(args->conn->conn_info->ssl);
438+
return;
439+
}
440+
/* Else then handle older protocols */
441+
425442
RefuseAccess(args->conn, args->replyfile);
426443
snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
427444

428-
const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
429-
assert(ProtocolIsKnown(version));
430445
if (ProtocolIsClassic(version))
431446
{
432447
SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, args->buf_size);
@@ -440,6 +455,12 @@ void CfGetFile(ServerFileGetState *args)
440455

441456
/* File transfer */
442457

458+
const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
459+
if (ProtocolSupportsFileStream(version)) {
460+
FileStreamServe(conn_info->ssl, filename);
461+
return;
462+
}
463+
443464
if ((fd = safe_open(filename, O_RDONLY)) == -1)
444465
{
445466
Log(LOG_LEVEL_ERR, "Open error of file '%s'. (open: %s)",

libcfnet/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Names of protocol versions:
1515
1. `"classic"` - Legacy, pre-TLS, protocol. Not enabled or allowed by default.
1616
2. `"tls"` - TLS Protocol using OpenSSL. Encrypted and 2-way authentication.
1717
3. `"cookie"` - TLS Protocol with cookie command for duplicate host detection.
18+
3. `"filestream"` - Introduces a new streaming API for get file request (powered by librsync).
1819

1920
Wanted protocol version can be specified from policy:
2021

@@ -59,3 +60,30 @@ Both server and client will then set `conn_info->protocol` to `2`, and use proto
5960
There is currently no way to require a specific version number (only allow / disallow version 1).
6061
This is because version 2 and 3 are practically identical.
6162
Downgrade from version 3 to 2 happens seamlessly, but crucially, it doesn't downgrade to version 1 inside the TLS code.
63+
64+
## Commands
65+
66+
### `GET <FILENAME>` (protocol v4)
67+
68+
The following is a description of the `GET <FILENAME>` command, modified in
69+
protocol version v4 (introduced in CFEngine 3.25).
70+
71+
The initial motivation for creating a new protocol version `filestream` was
72+
due to a race condition found in the `GET <FILENAME>` request. It relied on the
73+
file size aquired by `STAT <FILENAME>`. However, if the file size increased
74+
between the two requests, the client would think that the remaining data at the
75+
offset of the aquired file size is a new protocol header. This situation would lead
76+
to undefined behaviour. Hence, we needed a new protocol to send files. Instead
77+
of reinventing the wheel, we decided to use librsync which utilizes the RSYNC
78+
protocol to transmit files.
79+
80+
The server implementation is found in function
81+
[CfGet()](../cf-serverd/server_common.c). Client impementations are found in
82+
[CopyRegularFileNet()](client_code.c) and [ProtocolGet()](protocol.c)
83+
84+
Similar to before, the client issues a `GET <FILENAME>` request. However,
85+
instead of continuing to execute the old protocol, the client immediately calls
86+
`FileStreamFetch()` from the "File Stream API". Upon receiving such a request,
87+
the server calls either `FileStreamRefuse()` (to refuse the request) or
88+
`FileStreamServe()` (to comply with the request). The internal workings of the
89+
File Stream API is well explained in [file_stream.h](file_stream.h).

libcfnet/client_code.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include <misc_lib.h> /* ProgrammingError */
4646
#include <printsize.h> /* PRINTSIZE */
4747
#include <lastseen.h> /* LastSaw */
48+
#include <file_stream.h>
4849

4950

5051
#define CFENGINE_SERVICE "cfengine"
@@ -749,9 +750,11 @@ static void FlushFileStream(int sd, int toget)
749750

750751
/* TODO finalise socket or TLS session in all cases that this function fails
751752
* and the transaction protocol is out of sync. */
752-
bool CopyRegularFileNet(const char *source, const char *dest, off_t size,
753+
bool CopyRegularFileNet(const char *source, const char *basis, const char *dest, off_t size,
753754
bool encrypt, AgentConnection *conn, mode_t mode)
754755
{
756+
assert(conn != NULL);
757+
755758
char *buf, workbuf[CF_BUFSIZE], cfchangedstr[265];
756759
const int buf_size = 2048;
757760

@@ -774,23 +777,12 @@ bool CopyRegularFileNet(const char *source, const char *dest, off_t size,
774777

775778
unlink(dest); /* To avoid link attacks */
776779

777-
int dd = safe_open_create_perms(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, mode);
778-
if (dd == -1)
779-
{
780-
Log(LOG_LEVEL_ERR,
781-
"Copy from server '%s' to destination '%s' failed (open: %s)",
782-
conn->this_server, dest, GetErrorStr());
783-
unlink(dest);
784-
return false;
785-
}
786-
787780
workbuf[0] = '\0';
788781
int tosend = snprintf(workbuf, CF_BUFSIZE, "GET %d %s", buf_size, source);
789782
if (tosend <= 0 || tosend >= CF_BUFSIZE)
790783
{
791784
Log(LOG_LEVEL_ERR, "Failed to compose GET command for file %s",
792785
source);
793-
close(dd);
794786
return false;
795787
}
796788

@@ -799,7 +791,21 @@ bool CopyRegularFileNet(const char *source, const char *dest, off_t size,
799791
if (SendTransaction(conn->conn_info, workbuf, tosend, CF_DONE) == -1)
800792
{
801793
Log(LOG_LEVEL_ERR, "Couldn't send GET command");
802-
close(dd);
794+
return false;
795+
}
796+
797+
const ProtocolVersion version = ConnectionInfoProtocolVersion(conn->conn_info);
798+
if (ProtocolSupportsFileStream(version)) {
799+
return FileStreamFetch(conn->conn_info->ssl, basis, dest, mode);
800+
}
801+
802+
int dd = safe_open_create_perms(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY, mode);
803+
if (dd == -1)
804+
{
805+
Log(LOG_LEVEL_ERR,
806+
"Copy from server '%s' to destination '%s' failed (open: %s)",
807+
conn->this_server, dest, GetErrorStr());
808+
unlink(dest);
803809
return false;
804810
}
805811

libcfnet/client_code.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ AgentConnection *ServerConnection(const char *server, const char *port, const Rl
4747
void DisconnectServer(AgentConnection *conn);
4848

4949
bool CompareHashNet(const char *file1, const char *file2, bool encrypt, AgentConnection *conn);
50-
bool CopyRegularFileNet(const char *source, const char *dest, off_t size,
50+
bool CopyRegularFileNet(const char *source, const char *basis, const char *dest, off_t size,
5151
bool encrypt, AgentConnection *conn, mode_t mode);
5252
Item *RemoteDirList(const char *dirname, bool encrypt, AgentConnection *conn);
5353

libcfnet/protocol.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <stat_cache.h>
3232
#include <string_lib.h>
3333
#include <tls_generic.h>
34+
#include <file_stream.h>
3435

3536
Seq *ProtocolOpenDir(AgentConnection *conn, const char *path)
3637
{
@@ -124,6 +125,38 @@ bool ProtocolGet(AgentConnection *conn, const char *remote_path,
124125
return false;
125126
}
126127

128+
/* Use file stream API if it is available */
129+
const ProtocolVersion version = ConnectionInfoProtocolVersion(conn->conn_info);
130+
if (ProtocolSupportsFileStream(version))
131+
{
132+
fclose(file_ptr);
133+
134+
char dest[PATH_MAX];
135+
ret = snprintf(dest, sizeof(dest), "%s.cfnew", local_path);
136+
if (ret < 0 || (size_t)ret >= sizeof(dest))
137+
{
138+
Log(LOG_LEVEL_ERR, "Truncation error: Path too long (%d >= %zu)", ret, sizeof(dest));
139+
return false;
140+
}
141+
142+
if (!FileStreamFetch(conn->conn_info->ssl, local_path, dest, perms))
143+
{
144+
/* Error is already logged */
145+
return false;
146+
}
147+
148+
Log(LOG_LEVEL_VERBOSE, "Replacing file '%s' with '%s'...", dest, local_path);
149+
if (rename(dest, local_path) == -1)
150+
{
151+
Log(LOG_LEVEL_ERR, "Failed to replace destination file '%s' with basis file '%s': %s", dest, local_path, GetErrorStr());
152+
return false;
153+
}
154+
155+
return true;
156+
}
157+
158+
/* Otherwise, use old protocol */
159+
127160
char cfchangedstr[sizeof(CF_CHANGEDSTR1 CF_CHANGEDSTR2)];
128161
snprintf(cfchangedstr, sizeof(cfchangedstr), "%s%s",
129162
CF_CHANGEDSTR1, CF_CHANGEDSTR2);

libcfnet/protocol_version.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ ProtocolVersion ParseProtocolVersionPolicy(const char *const s)
3333
{
3434
return CF_PROTOCOL_COOKIE;
3535
}
36+
else if (StringEqual(s, "4") || StringEqual(s, "filestream"))
37+
{
38+
return CF_PROTOCOL_FILESTREAM;
39+
}
3640
else if (StringEqual(s, "latest"))
3741
{
3842
return CF_PROTOCOL_LATEST;

libcfnet/protocol_version.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ typedef enum
3939
/* --- Greater versions use TLS as secure communications layer --- */
4040
CF_PROTOCOL_TLS = 2,
4141
CF_PROTOCOL_COOKIE = 3,
42+
CF_PROTOCOL_FILESTREAM = 4,
4243
} ProtocolVersion;
4344

4445
/* We use CF_PROTOCOL_LATEST as the default for new connections. */
45-
#define CF_PROTOCOL_LATEST CF_PROTOCOL_COOKIE
46+
#define CF_PROTOCOL_LATEST CF_PROTOCOL_FILESTREAM
4647

4748
static inline const char *ProtocolVersionString(const ProtocolVersion p)
4849
{
@@ -54,6 +55,8 @@ static inline const char *ProtocolVersionString(const ProtocolVersion p)
5455
return "tls";
5556
case CF_PROTOCOL_CLASSIC:
5657
return "classic";
58+
case CF_PROTOCOL_FILESTREAM:
59+
return "filestream";
5760
default:
5861
return "undefined";
5962
}
@@ -84,6 +87,11 @@ static inline bool ProtocolIsClassic(const ProtocolVersion p)
8487
return (p == CF_PROTOCOL_CLASSIC);
8588
}
8689

90+
static inline bool ProtocolSupportsFileStream(const ProtocolVersion p)
91+
{
92+
return (p >= CF_PROTOCOL_FILESTREAM);
93+
}
94+
8795
static inline bool ProtocolTerminateCSV(const ProtocolVersion p)
8896
{
8997
return (p < CF_PROTOCOL_COOKIE);

0 commit comments

Comments
 (0)