1 /*
2 Copyright 2020 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 > sizeof(sendbuffer))
169 {
170 ProgrammingError("cf_remote_stat: tosend (%d) > sendbuffer (%zd)",
171 tosend, sizeof(sendbuffer));
172 }
173
174 snprintf(sendbuffer, CF_BUFSIZE - 1, "SSYNCH %d", cipherlen);
175 memcpy(sendbuffer + CF_PROTO_OFFSET, out, cipherlen);
176 }
177 else
178 {
179 snprintf(sendbuffer, CF_BUFSIZE, "SYNCH %jd STAT %s",
180 (intmax_t) tloc, file);
181 tosend = strlen(sendbuffer);
182 }
183
184 if (SendTransaction(conn->conn_info, sendbuffer, tosend, CF_DONE) == -1)
185 {
186 Log(LOG_LEVEL_INFO,
187 "Transmission failed/refused talking to %.255s:%.255s. (stat: %s)",
188 conn->this_server, file, GetErrorStr());
189 return -1;
190 }
191
192 if (ReceiveTransaction(conn->conn_info, recvbuffer, NULL) == -1)
193 {
194 /* TODO mark connection in the cache as closed. */
195 return -1;
196 }
197
198 if (strstr(recvbuffer, "unsynchronized"))
199 {
200 Log(LOG_LEVEL_ERR,
201 "Clocks differ too much to do copy by date (security), server reported: %s",
202 recvbuffer + strlen("BAD: "));
203 return -1;
204 }
205
206 if (BadProtoReply(recvbuffer))
207 {
208 Log(LOG_LEVEL_VERBOSE, "Server returned error: %s",
209 recvbuffer + strlen("BAD: "));
210 errno = EPERM;
211 return -1;
212 }
213
214 if (!OKProtoReply(recvbuffer))
215 {
216 Log(LOG_LEVEL_ERR,
217 "Transmission refused or failed statting '%s', got '%s'",
218 file, recvbuffer);
219 errno = EPERM;
220 return -1;
221 }
222
223 Stat cfst;
224
225 ret = StatParseResponse(recvbuffer, &cfst);
226 if (!ret)
227 {
228 Log(LOG_LEVEL_ERR, "Cannot read STAT reply from '%s'",
229 conn->this_server);
230 return -1;
231 }
232
233 // If remote path is symbolic link, receive actual path here
234 int recv_len = ReceiveTransaction(conn->conn_info, recvbuffer, NULL);
235 if (recv_len == -1)
236 {
237 /* TODO mark connection in the cache as closed. */
238 return -1;
239 }
240
241 int ok_len = sizeof("OK:");
242 /* Received a link destination from server
243 (recv_len greater than OK response + NUL-byte) */
244 if (recv_len > ok_len)
245 {
246 // Read from after "OK:"
247 cfst.cf_readlink = xstrdup(recvbuffer + (ok_len - 1));
248 }
249 else
250 {
251 cfst.cf_readlink = NULL;
252 }
253
254 mode_t file_type = FileTypeToMode(cfst.cf_type);
255 if (file_type == 0)
256 {
257 Log(LOG_LEVEL_ERR, "Invalid file type identifier for file %s:%s, %u",
258 conn->this_server, file, cfst.cf_type);
259 return -1;
260 }
261
262 cfst.cf_mode |= file_type;
263
264 cfst.cf_filename = xstrdup(file);
265 cfst.cf_server = xstrdup(conn->this_server);
266 cfst.cf_failed = false;
267
268 if (cfst.cf_lmode != 0)
269 {
270 cfst.cf_lmode |= (mode_t) S_IFLNK;
271 }
272
273 NewStatCache(&cfst, conn);
274
275 if ((cfst.cf_lmode != 0) && (strcmp(stattype, "link") == 0))
276 {
277 statbuf->st_mode = cfst.cf_lmode;
278 }
279 else
280 {
281 statbuf->st_mode = cfst.cf_mode;
282 }
283
284 statbuf->st_uid = cfst.cf_uid;
285 statbuf->st_gid = cfst.cf_gid;
286 statbuf->st_size = cfst.cf_size;
287 statbuf->st_mtime = cfst.cf_mtime;
288 statbuf->st_ctime = cfst.cf_ctime;
289 statbuf->st_atime = cfst.cf_atime;
290 statbuf->st_ino = cfst.cf_ino;
291 statbuf->st_dev = cfst.cf_dev;
292 statbuf->st_nlink = cfst.cf_nlink;
293
294 return 0;
295 }
296
297 /*********************************************************************/
298
299 /* 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)300 const Stat *StatCacheLookup(const AgentConnection *conn, const char *file_name,
301 const char *server_name)
302 {
303 for (const Stat *sp = conn->cache; sp != NULL; sp = sp->next)
304 {
305 if (strcmp(server_name, sp->cf_server) == 0 &&
306 strcmp(file_name, sp->cf_filename) == 0)
307 {
308 return sp;
309 }
310 }
311
312 return NULL;
313 }
314
315 /*********************************************************************/
316
FileTypeToMode(const FileType type)317 mode_t FileTypeToMode(const FileType type)
318 {
319 /* TODO Match the order of the actual stat struct for easier mode */
320 int mode = 0;
321 switch (type)
322 {
323 case FILE_TYPE_REGULAR:
324 mode |= (mode_t) S_IFREG;
325 break;
326 case FILE_TYPE_DIR:
327 mode |= (mode_t) S_IFDIR;
328 break;
329 case FILE_TYPE_CHAR_:
330 mode |= (mode_t) S_IFCHR;
331 break;
332 case FILE_TYPE_FIFO:
333 mode |= (mode_t) S_IFIFO;
334 break;
335 case FILE_TYPE_SOCK:
336 mode |= (mode_t) S_IFSOCK;
337 break;
338 case FILE_TYPE_BLOCK:
339 mode |= (mode_t) S_IFBLK;
340 break;
341 case FILE_TYPE_LINK:
342 mode |= (mode_t) S_IFLNK;
343 break;
344 }
345
346 // mode is 0 if no file types matched
347 return mode;
348 }
349
350 /*********************************************************************/
351
StatParseResponse(const char * const buf,Stat * const statbuf)352 bool StatParseResponse(const char *const buf, Stat *const statbuf)
353 {
354 assert(buf != NULL);
355 assert(statbuf != NULL);
356
357 // use intmax_t here to provide enough space for large values coming over the protocol
358 intmax_t d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12 = 0, d13 = 0;
359 int res = sscanf(buf, "OK:"
360 " %1" PRIdMAX // 01 statbuf->cf_type
361 " %5" PRIdMAX // 02 statbuf->cf_mode
362 " %14" PRIdMAX // 03 statbuf->cf_lmode
363 " %14" PRIdMAX // 04 statbuf->cf_uid
364 " %14" PRIdMAX // 05 statbuf->cf_gid
365 " %18" PRIdMAX // 06 statbuf->cf_size
366 " %14" PRIdMAX // 07 statbuf->cf_atime
367 " %14" PRIdMAX // 08 statbuf->cf_mtime
368 " %14" PRIdMAX // 09 statbuf->cf_ctime
369 " %1" PRIdMAX // 10 statbuf->cf_makeholes
370 " %14" PRIdMAX // 11 statbuf->cf_ino
371 " %14" PRIdMAX // 12 statbuf->cf_nlink
372 " %18" PRIdMAX, // 13 statbuf->cf_dev
373 &d1, &d2, &d3, &d4, &d5, &d6, &d7,
374 &d8, &d9, &d10, &d11, &d12, &d13);
375 if (res < 13)
376 {
377 if (res >= 0)
378 {
379 Log(LOG_LEVEL_VERBOSE,
380 "STAT response parsing failed, only %d/13 elements parsed",
381 res);
382 }
383
384 return false;
385 }
386
387 statbuf->cf_type = (FileType) d1;
388 statbuf->cf_mode = (mode_t) d2;
389 statbuf->cf_lmode = (mode_t) d3;
390 statbuf->cf_uid = (uid_t) d4;
391 statbuf->cf_gid = (gid_t) d5;
392 statbuf->cf_size = (off_t) d6;
393 statbuf->cf_atime = (time_t) d7;
394 statbuf->cf_mtime = (time_t) d8;
395 statbuf->cf_ctime = (time_t) d9;
396 statbuf->cf_makeholes = (char) d10;
397 statbuf->cf_ino = d11;
398 statbuf->cf_nlink = d12;
399 statbuf->cf_dev = (dev_t)d13;
400
401 return true;
402 }
403