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