1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25
26 #include <platform.h>
27 #include <stat_cache.h>
28
29 #include <cfnet.h> /* AgentConnection */
30 #include <net.h> /* {Send,Receive}Transaction */
31 #include <client_protocol.h> /* BadProtoReply,OKProtoReply */
32 #include <alloc.h> /* xmemdup */
33 #include <logging.h> /* Log */
34 #include <crypto.h> /* EncryptString */
35 #include <misc_lib.h> /* ProgrammingError */
36
NewStatCache(Stat * data,AgentConnection * conn)37 static void NewStatCache(Stat *data, AgentConnection *conn)
38 {
39 Stat *sp = xmemdup(data, sizeof(Stat));
40 sp->next = conn->cache;
41 conn->cache = sp;
42 }
43
DestroyStatCache(Stat * data)44 void DestroyStatCache(Stat *data)
45 {
46 if (data != NULL)
47 {
48 free(data->cf_readlink);
49 free(data->cf_filename);
50 free(data->cf_server);
51 free(data);
52 }
53 }
54
55 /**
56 * @brief Find remote stat information for #file in cache and
57 * return it in #statbuf.
58 * @return 0 if found, 1 if not found, -1 in case of error.
59 */
StatFromCache(AgentConnection * conn,const char * file,struct stat * statbuf,const char * stattype)60 static int StatFromCache(AgentConnection *conn, const char *file,
61 struct stat *statbuf, const char *stattype)
62 {
63 for (Stat *sp = conn->cache; sp != NULL; sp = sp->next)
64 {
65 /* TODO differentiate ports etc in this stat cache! */
66
67 if (strcmp(conn->this_server, sp->cf_server) == 0 &&
68 strcmp(file, sp->cf_filename) == 0)
69 {
70 if (sp->cf_failed) /* cached failure from cfopendir */
71 {
72 errno = EPERM;
73 return -1;
74 }
75
76 if ((strcmp(stattype, "link") == 0) && (sp->cf_lmode != 0))
77 {
78 statbuf->st_mode = sp->cf_lmode;
79 }
80 else
81 {
82 statbuf->st_mode = sp->cf_mode;
83 }
84
85 statbuf->st_uid = sp->cf_uid;
86 statbuf->st_gid = sp->cf_gid;
87 statbuf->st_size = sp->cf_size;
88 statbuf->st_atime = sp->cf_atime;
89 statbuf->st_mtime = sp->cf_mtime;
90 statbuf->st_ctime = sp->cf_ctime;
91 statbuf->st_ino = sp->cf_ino;
92 statbuf->st_dev = sp->cf_dev;
93 statbuf->st_nlink = sp->cf_nlink;
94
95 return 0;
96 }
97 }
98
99 return 1; /* not found */
100 }
101
102 /**
103 * @param #stattype should be either "link" or "file". If a link, this reads
104 * readlink and sends it back in the same packet. It then
105 * caches the value for each copy command.
106 *
107 */
cf_remote_stat(AgentConnection * conn,bool encrypt,const char * file,struct stat * statbuf,const char * stattype)108 int cf_remote_stat(AgentConnection *conn, bool encrypt, const char *file,
109 struct stat *statbuf, const char *stattype)
110 {
111 assert(conn != NULL);
112 assert(file != NULL);
113 assert(statbuf != NULL);
114 assert(strcmp(stattype, "file") == 0 ||
115 strcmp(stattype, "link") == 0);
116
117 /* We encrypt only for CLASSIC protocol. The TLS protocol is always over
118 * encrypted layer, so it does not support encrypted (S*) commands. */
119 encrypt = encrypt && conn->conn_info->protocol == CF_PROTOCOL_CLASSIC;
120
121 if (strlen(file) > CF_BUFSIZE - 30)
122 {
123 Log(LOG_LEVEL_ERR, "Filename too long");
124 return -1;
125 }
126
127 int ret = StatFromCache(conn, file, statbuf, stattype);
128 if (ret == 0 || ret == -1) /* found or error */
129 {
130 return ret;
131 }
132
133 /* Not found in cache */
134
135 char recvbuffer[CF_BUFSIZE];
136 memset(recvbuffer, 0, CF_BUFSIZE);
137
138 time_t tloc = time(NULL);
139 if (tloc == (time_t) -1)
140 {
141 Log(LOG_LEVEL_ERR, "Couldn't read system clock (time: %s)",
142 GetErrorStr());
143 tloc = 0;
144 }
145
146 char sendbuffer[CF_BUFSIZE];
147 int tosend;
148 sendbuffer[0] = '\0';
149
150 if (encrypt)
151 {
152 if (conn->session_key == NULL)
153 {
154 Log(LOG_LEVEL_ERR,
155 "Cannot do encrypted copy without keys (use cf-key)");
156 return -1;
157 }
158
159 char in[CF_BUFSIZE], out[CF_BUFSIZE];
160
161 snprintf(in, CF_BUFSIZE - 1, "SYNCH %jd STAT %s",
162 (intmax_t) tloc, file);
163 int cipherlen = EncryptString(out, sizeof(out), in, strlen(in) + 1,
164 conn->encryption_type, conn->session_key);
165
166 tosend = cipherlen + CF_PROTO_OFFSET;
167
168 if (tosend < 0)
169 {
170 ProgrammingError("cf_remote_stat: tosend (%d) < 0", tosend);
171 }
172 else if((unsigned int) tosend > sizeof(sendbuffer))
173 {
174 ProgrammingError("cf_remote_stat: tosend (%d) > sendbuffer (%zd)",
175 tosend, sizeof(sendbuffer));
176 }
177
178 snprintf(sendbuffer, CF_BUFSIZE - 1, "SSYNCH %d", cipherlen);
179 memcpy(sendbuffer + CF_PROTO_OFFSET, out, cipherlen);
180 }
181 else
182 {
183 snprintf(sendbuffer, CF_BUFSIZE, "SYNCH %jd STAT %s",
184 (intmax_t) tloc, file);
185 tosend = strlen(sendbuffer);
186 }
187
188 if (SendTransaction(conn->conn_info, sendbuffer, tosend, CF_DONE) == -1)
189 {
190 Log(LOG_LEVEL_INFO,
191 "Transmission failed/refused talking to %.255s:%.255s. (stat: %s)",
192 conn->this_server, file, GetErrorStr());
193 return -1;
194 }
195
196 if (ReceiveTransaction(conn->conn_info, recvbuffer, NULL) == -1)
197 {
198 /* TODO mark connection in the cache as closed. */
199 return -1;
200 }
201
202 if (strstr(recvbuffer, "unsynchronized"))
203 {
204 Log(LOG_LEVEL_ERR,
205 "Clocks differ too much to do copy by date (security), server reported: %s",
206 recvbuffer + strlen("BAD: "));
207 return -1;
208 }
209
210 if (BadProtoReply(recvbuffer))
211 {
212 Log(LOG_LEVEL_VERBOSE, "Server returned error: %s",
213 recvbuffer + strlen("BAD: "));
214 errno = EPERM;
215 return -1;
216 }
217
218 if (!OKProtoReply(recvbuffer))
219 {
220 Log(LOG_LEVEL_ERR,
221 "Transmission refused or failed statting '%s', got '%s'",
222 file, recvbuffer);
223 errno = EPERM;
224 return -1;
225 }
226
227 Stat cfst;
228
229 ret = StatParseResponse(recvbuffer, &cfst);
230 if (!ret)
231 {
232 Log(LOG_LEVEL_ERR, "Cannot read STAT reply from '%s'",
233 conn->this_server);
234 return -1;
235 }
236
237 // If remote path is symbolic link, receive actual path here
238 int recv_len = ReceiveTransaction(conn->conn_info, recvbuffer, NULL);
239 if (recv_len == -1)
240 {
241 /* TODO mark connection in the cache as closed. */
242 return -1;
243 }
244
245 int ok_len = sizeof("OK:");
246 /* Received a link destination from server
247 (recv_len greater than OK response + NUL-byte) */
248 if (recv_len > ok_len)
249 {
250 // Read from after "OK:"
251 cfst.cf_readlink = xstrdup(recvbuffer + (ok_len - 1));
252 }
253 else
254 {
255 cfst.cf_readlink = NULL;
256 }
257
258 mode_t file_type = FileTypeToMode(cfst.cf_type);
259 if (file_type == 0)
260 {
261 Log(LOG_LEVEL_ERR, "Invalid file type identifier for file %s:%s, %u",
262 conn->this_server, file, cfst.cf_type);
263 return -1;
264 }
265
266 cfst.cf_mode |= file_type;
267
268 cfst.cf_filename = xstrdup(file);
269 cfst.cf_server = xstrdup(conn->this_server);
270 cfst.cf_failed = false;
271
272 if (cfst.cf_lmode != 0)
273 {
274 cfst.cf_lmode |= (mode_t) S_IFLNK;
275 }
276
277 NewStatCache(&cfst, conn);
278
279 if ((cfst.cf_lmode != 0) && (strcmp(stattype, "link") == 0))
280 {
281 statbuf->st_mode = cfst.cf_lmode;
282 }
283 else
284 {
285 statbuf->st_mode = cfst.cf_mode;
286 }
287
288 statbuf->st_uid = cfst.cf_uid;
289 statbuf->st_gid = cfst.cf_gid;
290 statbuf->st_size = cfst.cf_size;
291 statbuf->st_mtime = cfst.cf_mtime;
292 statbuf->st_ctime = cfst.cf_ctime;
293 statbuf->st_atime = cfst.cf_atime;
294 statbuf->st_ino = cfst.cf_ino;
295 statbuf->st_dev = cfst.cf_dev;
296 statbuf->st_nlink = cfst.cf_nlink;
297
298 return 0;
299 }
300
301 /*********************************************************************/
302
303 /* TODO only a server_name is not enough for stat'ing of files... */
StatCacheLookup(const AgentConnection * conn,const char * file_name,const char * server_name)304 const Stat *StatCacheLookup(const AgentConnection *conn, const char *file_name,
305 const char *server_name)
306 {
307 for (const Stat *sp = conn->cache; sp != NULL; sp = sp->next)
308 {
309 if (strcmp(server_name, sp->cf_server) == 0 &&
310 strcmp(file_name, sp->cf_filename) == 0)
311 {
312 return sp;
313 }
314 }
315
316 return NULL;
317 }
318
319 /*********************************************************************/
320
FileTypeToMode(const FileType type)321 mode_t FileTypeToMode(const FileType type)
322 {
323 /* TODO Match the order of the actual stat struct for easier mode */
324 int mode = 0;
325 switch (type)
326 {
327 case FILE_TYPE_REGULAR:
328 mode |= (mode_t) S_IFREG;
329 break;
330 case FILE_TYPE_DIR:
331 mode |= (mode_t) S_IFDIR;
332 break;
333 case FILE_TYPE_CHAR_:
334 mode |= (mode_t) S_IFCHR;
335 break;
336 case FILE_TYPE_FIFO:
337 mode |= (mode_t) S_IFIFO;
338 break;
339 case FILE_TYPE_SOCK:
340 mode |= (mode_t) S_IFSOCK;
341 break;
342 case FILE_TYPE_BLOCK:
343 mode |= (mode_t) S_IFBLK;
344 break;
345 case FILE_TYPE_LINK:
346 mode |= (mode_t) S_IFLNK;
347 break;
348 }
349
350 // mode is 0 if no file types matched
351 return mode;
352 }
353
354 /*********************************************************************/
355
StatParseResponse(const char * const buf,Stat * const statbuf)356 bool StatParseResponse(const char *const buf, Stat *const statbuf)
357 {
358 assert(buf != NULL);
359 assert(statbuf != NULL);
360
361 // use intmax_t here to provide enough space for large values coming over the protocol
362 intmax_t d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12 = 0, d13 = 0;
363 int res = sscanf(buf, "OK:"
364 " %1" PRIdMAX // 01 statbuf->cf_type
365 " %5" PRIdMAX // 02 statbuf->cf_mode
366 " %14" PRIdMAX // 03 statbuf->cf_lmode
367 " %14" PRIdMAX // 04 statbuf->cf_uid
368 " %14" PRIdMAX // 05 statbuf->cf_gid
369 " %18" PRIdMAX // 06 statbuf->cf_size
370 " %14" PRIdMAX // 07 statbuf->cf_atime
371 " %14" PRIdMAX // 08 statbuf->cf_mtime
372 " %14" PRIdMAX // 09 statbuf->cf_ctime
373 " %1" PRIdMAX // 10 statbuf->cf_makeholes
374 " %14" PRIdMAX // 11 statbuf->cf_ino
375 " %14" PRIdMAX // 12 statbuf->cf_nlink
376 " %18" PRIdMAX, // 13 statbuf->cf_dev
377 &d1, &d2, &d3, &d4, &d5, &d6, &d7,
378 &d8, &d9, &d10, &d11, &d12, &d13);
379 if (res < 13)
380 {
381 if (res >= 0)
382 {
383 Log(LOG_LEVEL_VERBOSE,
384 "STAT response parsing failed, only %d/13 elements parsed",
385 res);
386 }
387
388 return false;
389 }
390
391 statbuf->cf_type = (FileType) d1;
392 statbuf->cf_mode = (mode_t) d2;
393 statbuf->cf_lmode = (mode_t) d3;
394 statbuf->cf_uid = (uid_t) d4;
395 statbuf->cf_gid = (gid_t) d5;
396 statbuf->cf_size = (off_t) d6;
397 statbuf->cf_atime = (time_t) d7;
398 statbuf->cf_mtime = (time_t) d8;
399 statbuf->cf_ctime = (time_t) d9;
400 statbuf->cf_makeholes = (char) d10;
401 statbuf->cf_ino = d11;
402 statbuf->cf_nlink = d12;
403 statbuf->cf_dev = (dev_t)d13;
404
405 return true;
406 }
407