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 #include <protocol.h>
26 
27 #include <client_code.h>
28 #include <client_protocol.h>
29 #include <definitions.h>
30 #include <net.h>
31 #include <stat_cache.h>
32 #include <string_lib.h>
33 #include <tls_generic.h>
34 
ProtocolOpenDir(AgentConnection * conn,const char * path)35 Seq *ProtocolOpenDir(AgentConnection *conn, const char *path)
36 {
37     assert(conn != NULL);
38     assert(path != NULL);
39 
40     char buf[CF_MSGSIZE] = {0};
41     int tosend = snprintf(buf, CF_MSGSIZE, "OPENDIR %s", path);
42     if (tosend < 0 || tosend >= CF_MSGSIZE)
43     {
44         return NULL;
45     }
46 
47     int ret = SendTransaction(conn->conn_info, buf, tosend, CF_DONE);
48     if (ret == -1)
49     {
50         return NULL;
51     }
52 
53     Seq *seq = SeqNew(0, free);
54 
55     int more = 1;
56     while (more != 0)
57     {
58         int len = ReceiveTransaction(conn->conn_info, buf, &more);
59         if (len == -1)
60         {
61             break;
62         }
63 
64         if (BadProtoReply(buf))
65         {
66             Log(LOG_LEVEL_ERR, "Protocol error: %s", buf);
67             SeqDestroy(seq);
68             return NULL;
69         }
70 
71         /*
72          * Iterates over each string in the received transaction and appends
73          * it to the Seq list, until it either finds the CFD_TERMINATOR
74          * string, or reaches the end of the message.
75          */
76         for (int i = 0; i < len && buf[i] != '\0'; i += strlen(buf + i) + 1)
77         {
78             if (StringEqualN(buf + i, CFD_TERMINATOR,
79                                  sizeof(CFD_TERMINATOR) - 1))
80             {
81                 more = 0;
82                 break;
83             }
84 
85             char *str = xstrdup(buf + i);
86             SeqAppend(seq, str);
87         }
88     }
89 
90     return seq;
91 }
92 
ProtocolGet(AgentConnection * conn,const char * remote_path,const char * local_path,const uint32_t file_size,int perms)93 bool ProtocolGet(AgentConnection *conn, const char *remote_path,
94                  const char *local_path, const uint32_t file_size, int perms)
95 {
96     assert(conn != NULL);
97     assert(remote_path != NULL);
98     assert(local_path != NULL);
99     assert(file_size != 0);
100 
101     perms = (perms == 0) ? CF_PERMS_DEFAULT : perms;
102 
103     unlink(local_path);
104     FILE *file_ptr = safe_fopen_create_perms(local_path, "wx", perms);
105     if (file_ptr == NULL)
106     {
107         Log(LOG_LEVEL_WARNING, "Failed to open file %s (fopen: %s)",
108             local_path, GetErrorStr());
109         return false;
110     }
111 
112     char buf[CF_MSGSIZE] = {0};
113     int to_send = snprintf(buf, CF_MSGSIZE, "GET %d %s",
114                            CF_MSGSIZE, remote_path);
115 
116 
117     int ret = SendTransaction(conn->conn_info, buf, to_send, CF_DONE);
118     if (ret == -1)
119     {
120         Log(LOG_LEVEL_WARNING, "Failed to send request for remote file %s:%s",
121             conn->this_server, remote_path);
122         unlink(local_path);
123         fclose(file_ptr);
124         return false;
125     }
126 
127     char cfchangedstr[sizeof(CF_CHANGEDSTR1 CF_CHANGEDSTR2)];
128     snprintf(cfchangedstr, sizeof(cfchangedstr), "%s%s",
129              CF_CHANGEDSTR1, CF_CHANGEDSTR2);
130 
131     bool success = true;
132     uint32_t received_bytes = 0;
133     while (received_bytes < file_size)
134     {
135         int len = TLSRecv(conn->conn_info->ssl, buf, CF_MSGSIZE);
136         if (len == -1)
137         {
138             Log(LOG_LEVEL_WARNING, "Failed to GET file %s:%s",
139                 conn->this_server, remote_path);
140             success = false;
141             break;
142         }
143         else if (len > CF_MSGSIZE)
144         {
145             Log(LOG_LEVEL_WARNING,
146                 "Incorrect length of incoming packet "
147                 "while retrieving %s:%s, %d > %d",
148                 conn->this_server, remote_path, len, CF_MSGSIZE);
149             success = false;
150             break;
151         }
152 
153         if (BadProtoReply(buf))
154         {
155             Log(LOG_LEVEL_ERR,
156                 "Error from server while retrieving file %s:%s: %s",
157                 conn->this_server, remote_path, buf);
158             success = false;
159             break;
160         }
161 
162         if (StringEqualN(buf, cfchangedstr, sizeof(cfchangedstr) - 1))
163         {
164             Log(LOG_LEVEL_ERR,
165                 "Remote file %s:%s changed during file transfer",
166                 conn->this_server, remote_path);
167             success = false;
168             break;
169         }
170 
171         ret = fwrite(buf, sizeof(char), len, file_ptr);
172         if (ret < 0)
173         {
174             Log(LOG_LEVEL_ERR,
175                 "Failed to write during retrieval of file %s:%s (fwrite: %s)",
176                 conn->this_server, remote_path, GetErrorStr());
177             success = false;
178             break;
179         }
180 
181         received_bytes += len;
182     }
183 
184     if (!success)
185     {
186         unlink(local_path);
187     }
188 
189     fclose(file_ptr);
190     return success;
191 }
192 
ProtocolStatGet(AgentConnection * conn,const char * remote_path,const char * local_path,int perms)193 bool ProtocolStatGet(AgentConnection *conn, const char *remote_path,
194                      const char *local_path, int perms)
195 {
196     assert(conn != NULL);
197     assert(remote_path != NULL);
198 
199     struct stat sb;
200     bool ret = ProtocolStat(conn, remote_path, &sb);
201     if (!ret)
202     {
203         Log(LOG_LEVEL_ERR,
204             "Failed to stat remote file %s:%s",
205             conn->this_server, remote_path);
206         return false;
207     }
208 
209     return ProtocolGet(conn, remote_path, local_path, sb.st_size, perms);
210 }
211 
ProtocolStat(AgentConnection * const conn,const char * const remote_path,struct stat * const stat_buf)212 bool ProtocolStat(AgentConnection *const conn, const char *const remote_path,
213                   struct stat *const stat_buf)
214 {
215     assert(conn != NULL);
216     assert(remote_path != NULL);
217     assert(stat_buf != NULL);
218 
219     time_t tloc = time(NULL);
220     if (tloc == (time_t) -1)
221     {
222         Log(LOG_LEVEL_WARNING,
223             "Couldn't read system clock, defaulting to 0 in case server "
224             "does not care about clock differences (time: %s)",
225             GetErrorStr());
226         tloc = 0;
227     }
228 
229     char buf[CF_BUFSIZE] = {0};
230     int to_send = snprintf(buf, CF_BUFSIZE, "SYNCH %jd STAT %s",
231                            (intmax_t) tloc, remote_path);
232 
233     int ret = SendTransaction(conn->conn_info, buf, to_send, CF_DONE);
234     if (ret == -1)
235     {
236         Log(LOG_LEVEL_WARNING,
237             "Could not send stat request for remote file %s:%s.",
238             conn->this_server, remote_path);
239         return false;
240     }
241 
242     int recvd_len = ReceiveTransaction(conn->conn_info, buf, NULL) == -1;
243     if (recvd_len == -1)
244     {
245         Log(LOG_LEVEL_WARNING,
246             "Receiving file statistics from %s failed!",
247             conn->this_server);
248         return false;
249     }
250 
251     if (BadProtoReply(buf))
252     {
253         Log(LOG_LEVEL_WARNING,
254             "Could not stat remote file %s:%s, response: %s",
255             conn->this_server, remote_path, buf);
256         return false;
257     }
258 
259     if (!OKProtoReply(buf))
260     {
261         Log(LOG_LEVEL_WARNING,
262             "Illegal response from server while statting %s:%s",
263             conn->this_server, remote_path);
264         return false;
265     }
266 
267     Stat cf_stat;
268     ret = StatParseResponse(buf, &cf_stat);
269     if (!ret)
270     {
271         Log(LOG_LEVEL_WARNING,
272             "Failed to parse the response from the server "
273             "while statting %s:%s",
274             conn->this_server, remote_path);
275         return false;
276     }
277 
278     mode_t file_type = FileTypeToMode(cf_stat.cf_type);
279     if (file_type == 0)
280     {
281         Log(LOG_LEVEL_VERBOSE,
282             "Invalid file type identifier for file %s:%s, %u",
283             conn->this_server, remote_path, cf_stat.cf_type);
284         return false;
285     }
286 
287     stat_buf->st_mode = file_type | cf_stat.cf_mode;
288     stat_buf->st_uid = cf_stat.cf_uid;
289     stat_buf->st_gid = cf_stat.cf_gid;
290     stat_buf->st_size = cf_stat.cf_size;
291     stat_buf->st_mtime = cf_stat.cf_mtime;
292     stat_buf->st_ctime = cf_stat.cf_ctime;
293     stat_buf->st_atime = cf_stat.cf_atime;
294     stat_buf->st_ino = cf_stat.cf_ino;
295     stat_buf->st_dev = cf_stat.cf_dev;
296     stat_buf->st_nlink = cf_stat.cf_nlink;
297 
298     // Receive link destination, but do nothing
299     ReceiveTransaction(conn->conn_info, buf, NULL);
300 
301     return true;
302 }
303