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