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 static const int CF_NOSIZE = -1;
27 
28 
29 #include <server_common.h>
30 
31 #include <item_lib.h>                                 /* ItemList2CSV_bound */
32 #include <string_lib.h>                              /* ToLower,StrCatDelim */
33 #include <regex.h>                                    /* StringMatchFull */
34 #include <crypto.h>                                   /* EncryptString */
35 #include <files_names.h>
36 #include <files_interfaces.h>
37 #include <hash.h>
38 #include <file_lib.h>
39 #include <eval_context.h>
40 #include <dir.h>
41 #include <conversion.h>
42 #include <matching.h>                        /* IsRegexItemIn */
43 #include <pipes.h>
44 #include <classic.h>                  /* SendSocketStream */
45 #include <net.h>                      /* SendTransaction,ReceiveTransaction */
46 #include <openssl/err.h>                                   /* ERR_get_error */
47 #include <protocol.h>                                  /* ProtocolIsKnown() */
48 #include <tls_generic.h>              /* TLSSend */
49 #include <rlist.h>
50 #include <cf-serverd-enterprise-stubs.h>
51 #include <connection_info.h>
52 #include <misc_lib.h>                              /* UnexpectedError */
53 #include <cf-windows-functions.h>                  /* NovaWin_UserNameToSid */
54 #include <mutex.h>                                 /* ThreadLock */
55 #include <stat_cache.h>                            /* struct Stat */
56 #include "server_access.h"
57 
58 
59 /* NOTE: Always Log(LOG_LEVEL_INFO) before calling RefuseAccess(), so that
60  * some clue is printed in the cf-serverd logs. */
RefuseAccess(ServerConnectionState * conn,char * errmesg)61 void RefuseAccess(ServerConnectionState *conn, char *errmesg)
62 {
63     SendTransaction(conn->conn_info, CF_FAILEDSTR, 0, CF_DONE);
64 
65     /* TODO remove logging, it's done elsewhere. */
66     Log(LOG_LEVEL_VERBOSE, "REFUSAL to user='%s' of request: %s",
67         NULL_OR_EMPTY(conn->username) ? "?" : conn->username,
68         errmesg);
69 }
70 
IsUserNameValid(const char * username)71 bool IsUserNameValid(const char *username)
72 {
73     /* Add whatever characters are considered invalid in username */
74     const char *invalid_username_characters = "\\/";
75 
76     if (strpbrk(username, invalid_username_characters) == NULL)
77     {
78         return true;
79     }
80 
81     return false;
82 }
83 
AllowedUser(char * user)84 bool AllowedUser(char *user)
85 {
86     if (IsItemIn(SERVER_ACCESS.allowuserlist, user))
87     {
88         Log(LOG_LEVEL_DEBUG, "User %s granted connection privileges", user);
89         return true;
90     }
91 
92     Log(LOG_LEVEL_DEBUG, "User %s is not allowed on this server", user);
93     return false;
94 }
95 
ListPersistentClasses()96 Item *ListPersistentClasses()
97 {
98     Log(LOG_LEVEL_VERBOSE, "Scanning for all persistent classes");
99 
100     CF_DB *dbp;
101     CF_DBC *dbcp;
102 
103     if (!OpenDB(&dbp, dbid_state))
104     {
105         char *db_path = DBIdToPath(dbid_state);
106         Log(LOG_LEVEL_ERR, "Unable to open persistent classes database '%s'", db_path);
107         free(db_path);
108         return NULL;
109     }
110 
111     if (!NewDBCursor(dbp, &dbcp))
112     {
113         char *db_path = DBIdToPath(dbid_state);
114         Log(LOG_LEVEL_ERR, "Unable to get cursor for persistent classes database '%s'", db_path);
115         free(db_path);
116         CloseDB(dbp);
117         return NULL;
118     }
119 
120     const PersistentClassInfo *value;
121     int ksize, vsize;
122     char *key;
123     size_t count = 0;
124     time_t now = time(NULL);
125     Item *persistent_classes = NULL;
126     while (NextDB(dbcp, &key, &ksize, (void **)&value, &vsize))
127     {
128         if (now > value->expires)
129         {
130             Log(LOG_LEVEL_DEBUG,
131                 "Persistent class %s expired, removing from database", key);
132             DBCursorDeleteEntry(dbcp);
133         }
134         else
135         {
136             count++;
137             PrependItem(&persistent_classes, key, NULL);
138         }
139     }
140 
141     DeleteDBCursor(dbcp);
142     CloseDB(dbp);
143 
144     if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE)
145     {
146         char logbuf[CF_BUFSIZE];
147         ItemList2CSV_bound(persistent_classes, logbuf, sizeof(logbuf), ' ');
148         Log(LOG_LEVEL_VERBOSE,
149             "Found %zu valid persistent classes in state database: %s",
150             count, logbuf);
151     }
152 
153     return persistent_classes;
154 }
155 
156 
ReplyNothing(ServerConnectionState * conn)157 static void ReplyNothing(ServerConnectionState *conn)
158 {
159     char buffer[CF_BUFSIZE];
160 
161     snprintf(buffer, CF_BUFSIZE, "Hello %s (%s), nothing relevant to do here...\n\n", conn->hostname, conn->ipaddr);
162 
163     if (SendTransaction(conn->conn_info, buffer, 0, CF_DONE) == -1)
164     {
165         Log(LOG_LEVEL_ERR, "Unable to send transaction. (send: %s)", GetErrorStr());
166     }
167 }
168 
169 /* Used only in EXEC protocol command, to check if any of the received classes
170  * is defined in the server. */
MatchClasses(const EvalContext * ctx,ServerConnectionState * conn)171 bool MatchClasses(const EvalContext *ctx, ServerConnectionState *conn)
172 {
173     char recvbuffer[CF_BUFSIZE];
174     Item *classlist = NULL, *ip;
175     int count = 0;
176 
177     while (true && (count < 10))        /* arbitrary check to avoid infinite loop, DoS attack */
178     {
179         count++;
180 
181         if (ReceiveTransaction(conn->conn_info, recvbuffer, NULL) == -1)
182         {
183             Log(LOG_LEVEL_VERBOSE, "Unable to read data from network. (ReceiveTransaction: %s)", GetErrorStr());
184             return false;
185         }
186 
187         if (strncmp(recvbuffer, CFD_TERMINATOR, strlen(CFD_TERMINATOR)) == 0)
188         {
189             Log(LOG_LEVEL_DEBUG, "Got CFD_TERMINATOR");
190             if (count == 1)
191             {
192                 /* This is the common case, that cf-runagent had no
193                    "-s class1,class2" argument. */
194                 Log(LOG_LEVEL_DEBUG, "No classes were sent, assuming no restrictions...");
195                 return true;
196             }
197 
198             break;
199         }
200 
201         Log(LOG_LEVEL_DEBUG, "Got class buffer: %s", recvbuffer);
202 
203         classlist = SplitStringAsItemList(recvbuffer, ' ');
204 
205         for (ip = classlist; ip != NULL; ip = ip->next)
206         {
207             Log(LOG_LEVEL_VERBOSE, "Checking whether class %s can be identified as me...", ip->name);
208 
209             if (IsDefinedClass(ctx, ip->name))
210             {
211                 Log(LOG_LEVEL_DEBUG, "Class '%s' matched, accepting...", ip->name);
212                 DeleteItemList(classlist);
213                 return true;
214             }
215 
216             {
217                 /* What the heck are we doing here? */
218                 /* Hmmm so we iterate over all classes to see if the regex
219                  * received (ip->name) matches (StringMatchFull) to any local
220                  * class (expr)... SLOW! Change the spec! Don't accept
221                  * regexes! How many will be affected if a specific class has
222                  * to be set to run command, instead of matching a pattern?
223                  * It's safer anyway... */
224                 ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
225                 Class *cls = NULL;
226                 while ((cls = ClassTableIteratorNext(iter)))
227                 {
228                     char *expr = ClassRefToString(cls->ns, cls->name);
229                     /* FIXME: review this strcmp. Moved out from StringMatch */
230                     bool match = (strcmp(ip->name, expr) == 0 ||
231                                   StringMatchFull(ip->name, expr));
232                     free(expr);
233                     if (match)
234                     {
235                         Log(LOG_LEVEL_DEBUG, "Class matched regular expression '%s', accepting...", ip->name);
236                         DeleteItemList(classlist);
237                         return true;
238                     }
239                 }
240                 ClassTableIteratorDestroy(iter);
241             }
242 
243             if (strncmp(ip->name, CFD_TERMINATOR, strlen(CFD_TERMINATOR)) == 0)
244             {
245                 Log(LOG_LEVEL_VERBOSE, "No classes matched, rejecting....");
246                 ReplyNothing(conn);
247                 DeleteItemList(classlist);
248                 return false;
249             }
250         }
251     }
252 
253     ReplyNothing(conn);
254     Log(LOG_LEVEL_VERBOSE, "No classes matched, rejecting....");
255     DeleteItemList(classlist);
256     return false;
257 }
258 
259 /* TODO deprecate this function, only a simple SendTransaction(CFD_TERMINATOR)
260  * should be enough, without even error printing (it's already done in
261  * SendTransaction()). */
Terminate(ConnectionInfo * connection)262 void Terminate(ConnectionInfo *connection)
263 {
264     /* We send a trailing NULL in this transaction packet. TODO WHY? */
265     if (SendTransaction(connection, CFD_TERMINATOR,
266                         strlen(CFD_TERMINATOR) + 1, CF_DONE) == -1)
267     {
268         Log(LOG_LEVEL_VERBOSE, "Unable to reply with terminator. (send: %s)",
269             GetErrorStr());
270     }
271 }
272 
TransferRights(const ServerConnectionState * conn,const char * filename,const struct stat * sb)273 static bool TransferRights(
274     const ServerConnectionState *conn,
275     const char *filename,
276     const struct stat *sb)
277 {
278     Log(LOG_LEVEL_DEBUG, "Checking ownership of file: %s", filename);
279 
280     /* Don't do any check if connected user claims to be "root" or if
281      * "maproot" in access_rules contains the connecting IP address. */
282     if ((conn->uid == 0) || (conn->maproot))
283     {
284         Log(LOG_LEVEL_DEBUG, "Access granted because %s",
285             (conn->uid == 0) ? "remote user is root"
286                              : "of maproot");
287         return true;                                      /* access granted */
288     }
289 
290 #ifdef __MINGW32__
291 
292     SECURITY_DESCRIPTOR *secDesc;
293     SID *ownerSid;
294 
295     if (GetNamedSecurityInfo(
296             filename, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION,
297             (PSID *) &ownerSid, NULL, NULL, NULL, (void **) &secDesc)
298         != ERROR_SUCCESS)
299     {
300         Log(LOG_LEVEL_ERR,
301             "Could not retrieve owner of file '%s' "
302             "(GetNamedSecurityInfo: %s)",
303             filename, GetErrorStr());
304         return false;
305     }
306 
307     LocalFree(secDesc);
308 
309     if (!IsValidSid(conn->sid) ||
310         !EqualSid(ownerSid, conn->sid))
311     {
312         /* If "maproot" we've already granted access. */
313         assert(!conn->maproot);
314 
315         Log(LOG_LEVEL_INFO,
316             "Remote user '%s' is not the owner of the file, access denied, "
317             "consider maproot", conn->username);
318 
319         return false;
320     }
321 
322     Log(LOG_LEVEL_DEBUG,
323         "User '%s' is the owner of the file, access granted",
324         conn->username);
325 
326 #else                                         /* UNIX systems - common path */
327 
328     if (sb->st_uid != conn->uid)                   /* does not own the file */
329     {
330         if (!(sb->st_mode & S_IROTH))            /* file not world readable */
331         {
332             Log(LOG_LEVEL_INFO,
333                 "Remote user '%s' is not owner of the file, access denied, "
334                 "consider maproot or making file world-readable",
335                 conn->username);
336             return false;
337         }
338         else
339         {
340             Log(LOG_LEVEL_DEBUG,
341                 "Remote user '%s' is not the owner of the file, "
342                 "but file is world readable, access granted",
343                 conn->username);                 /* access granted */
344         }
345     }
346     else
347     {
348         Log(LOG_LEVEL_DEBUG,
349             "User '%s' is the owner of the file, access granted",
350             conn->username);                     /* access granted */
351     }
352 
353     /* ADMIT ACCESS, to summarise the following condition is now true: */
354 
355     /* Remote user is root, where "user" is just a string in the protocol, he
356      * might claim whatever he wants but will be able to login only if the
357      * user-key.pub key is found, */
358     assert((conn->uid == 0) ||
359     /* OR remote IP has maproot in the file's access_rules, */
360            (conn->maproot == true) ||
361     /* OR file is owned by the same username the user claimed - useless or
362      * even dangerous outside NIS, KERBEROS or LDAP authenticated domains,  */
363            (sb->st_uid == conn->uid) ||
364     /* OR file is readable by everyone */
365            (sb->st_mode & S_IROTH));
366 
367 #endif
368 
369     return true;
370 }
371 
AbortTransfer(ConnectionInfo * connection,char * filename)372 static void AbortTransfer(ConnectionInfo *connection, char *filename)
373 {
374     Log(LOG_LEVEL_VERBOSE, "Aborting transfer of file due to source changes");
375 
376     char sendbuffer[CF_BUFSIZE];
377     snprintf(sendbuffer, CF_BUFSIZE, "%s%s: %s",
378              CF_CHANGEDSTR1, CF_CHANGEDSTR2, filename);
379 
380     if (SendTransaction(connection, sendbuffer, 0, CF_DONE) == -1)
381     {
382         Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)",
383             GetErrorStr());
384     }
385 }
386 
FailedTransfer(ConnectionInfo * connection)387 static void FailedTransfer(ConnectionInfo *connection)
388 {
389     Log(LOG_LEVEL_VERBOSE, "Transfer failure");
390 
391     char sendbuffer[CF_BUFSIZE];
392 
393     snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
394 
395     if (SendTransaction(connection, sendbuffer, 0, CF_DONE) == -1)
396     {
397         Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)",
398             GetErrorStr());
399     }
400 }
401 
CfGetFile(ServerFileGetState * args)402 void CfGetFile(ServerFileGetState *args)
403 {
404     int fd;
405     off_t n_read, total = 0, sendlen = 0, count = 0;
406     char sendbuffer[CF_BUFSIZE + 256], filename[CF_BUFSIZE - 128];
407     struct stat sb;
408     int blocksize = 2048;
409 
410     ConnectionInfo *conn_info = args->conn->conn_info;
411 
412     TranslatePath(args->replyfile, filename, sizeof(filename));
413 
414     stat(filename, &sb);
415 
416     Log(LOG_LEVEL_DEBUG, "CfGetFile('%s'), size = %jd",
417         filename, (intmax_t) sb.st_size);
418 
419 /* Now check to see if we have remote permission */
420 
421     if (!TransferRights(args->conn, filename, &sb))
422     {
423         Log(LOG_LEVEL_INFO, "REFUSE access to file: %s", filename);
424         RefuseAccess(args->conn, args->replyfile);
425         snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
426 
427         const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
428         assert(ProtocolIsKnown(version));
429         if (ProtocolIsClassic(version))
430         {
431             SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, args->buf_size);
432         }
433         else if (ProtocolIsTLS(version))
434         {
435             TLSSend(ConnectionInfoSSL(conn_info), sendbuffer, args->buf_size);
436         }
437         return;
438     }
439 
440 /* File transfer */
441 
442     if ((fd = safe_open(filename, O_RDONLY)) == -1)
443     {
444         Log(LOG_LEVEL_ERR, "Open error of file '%s'. (open: %s)",
445             filename, GetErrorStr());
446         snprintf(sendbuffer, CF_BUFSIZE, "%s", CF_FAILEDSTR);
447 
448         const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
449         assert(ProtocolIsKnown(version));
450         if (ProtocolIsClassic(version))
451         {
452             SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, args->buf_size);
453         }
454         else if (ProtocolIsTLS(version))
455         {
456             TLSSend(ConnectionInfoSSL(conn_info), sendbuffer, args->buf_size);
457         }
458     }
459     else
460     {
461         int div = 3;
462 
463         if (sb.st_size > 10485760L) /* File larger than 10 MB, checks every 64kB */
464         {
465             div = 32;
466         }
467 
468         while (true)
469         {
470             memset(sendbuffer, 0, CF_BUFSIZE);
471 
472             Log(LOG_LEVEL_DEBUG, "Now reading from disk...");
473 
474             if ((n_read = read(fd, sendbuffer, blocksize)) == -1)
475             {
476                 Log(LOG_LEVEL_ERR, "Read failed in GetFile. (read: %s)", GetErrorStr());
477                 break;
478             }
479 
480             if (n_read == 0)
481             {
482                 break;
483             }
484             else
485             {
486                 off_t savedlen = sb.st_size;
487 
488                 /* check the file is not changing at source */
489 
490                 if (count++ % div == 0)   /* Don't do this too often */
491                 {
492                     if (stat(filename, &sb))
493                     {
494                         Log(LOG_LEVEL_ERR, "Cannot stat file '%s'. (stat: %s)",
495                             filename, GetErrorStr());
496                         break;
497                     }
498                 }
499 
500                 if (sb.st_size != savedlen)
501                 {
502                     snprintf(sendbuffer, CF_BUFSIZE, "%s%s: %s", CF_CHANGEDSTR1, CF_CHANGEDSTR2, filename);
503 
504                     const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
505 
506                     if (ProtocolIsClassic(version))
507                     {
508                         if (SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, blocksize) == -1)
509                         {
510                             Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
511                         }
512                     }
513                     else if (ProtocolIsTLS(version))
514                     {
515                         if (TLSSend(ConnectionInfoSSL(conn_info), sendbuffer, blocksize) == -1)
516                         {
517                             Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
518                         }
519                     }
520 
521                     Log(LOG_LEVEL_DEBUG,
522                         "Aborting transfer after %jd: file is changing rapidly at source.",
523                         (intmax_t) total);
524                     break;
525                 }
526 
527                 if ((savedlen - total) / blocksize > 0)
528                 {
529                     sendlen = blocksize;
530                 }
531                 else if (savedlen != 0)
532                 {
533                     sendlen = (savedlen - total);
534                 }
535             }
536 
537             total += n_read;
538 
539             const ProtocolVersion version = ConnectionInfoProtocolVersion(conn_info);
540 
541             if (ProtocolIsClassic(version))
542             {
543                 if (SendSocketStream(ConnectionInfoSocket(conn_info), sendbuffer, sendlen) == -1)
544                 {
545                     Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
546                     break;
547                 }
548             }
549             else if (ProtocolIsTLS(version))
550             {
551                 if (TLSSend(ConnectionInfoSSL(conn_info), sendbuffer, sendlen) == -1)
552                 {
553                     Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
554                     break;
555                 }
556             }
557         }
558 
559         close(fd);
560     }
561 }
562 
CfEncryptGetFile(ServerFileGetState * args)563 void CfEncryptGetFile(ServerFileGetState *args)
564 /* Because the stream doesn't end for each file, we need to know the
565    exact number of bytes transmitted, which might change during
566    encryption, hence we need to handle this with transactions */
567 {
568     int fd, n_read, cipherlen = 0, finlen = 0;
569     off_t total = 0, count = 0;
570     char filename[CF_BUFSIZE];
571     unsigned char sendbuffer[CF_BUFSIZE + 256];
572     unsigned char out[CF_BUFSIZE];
573     unsigned char iv[32] =
574         { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
575     int blocksize = CF_BUFSIZE - 4 * CF_INBAND_OFFSET;
576     struct stat sb;
577     ConnectionInfo *conn_info = args->conn->conn_info;
578 
579     const unsigned char *const key = args->conn->session_key;
580     const char enctype = args->conn->encryption_type;
581 
582     TranslatePath(args->replyfile, filename, sizeof(filename));
583 
584     stat(filename, &sb);
585 
586     Log(LOG_LEVEL_DEBUG, "CfEncryptGetFile('%s'), size = %jd",
587         filename, (intmax_t) sb.st_size);
588 
589 /* Now check to see if we have remote permission */
590 
591     if (!TransferRights(args->conn, filename, &sb))
592     {
593         Log(LOG_LEVEL_INFO, "REFUSE access to file: %s", filename);
594         RefuseAccess(args->conn, args->replyfile);
595         FailedTransfer(conn_info);
596         return;
597     }
598 
599     EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
600     if (ctx == NULL)
601     {
602         Log(LOG_LEVEL_ERR, "Failed to allocate cipher: %s",
603             TLSErrorString(ERR_get_error()));
604         return;
605     }
606 
607     if ((fd = safe_open(filename, O_RDONLY)) == -1)
608     {
609         Log(LOG_LEVEL_ERR, "Open error of file '%s'. (open: %s)", filename, GetErrorStr());
610         FailedTransfer(conn_info);
611     }
612     else
613     {
614         int div = 3;
615 
616         if (sb.st_size > 10485760L) /* File larger than 10 MB, checks every 64kB */
617         {
618             div = 32;
619         }
620 
621         while (true)
622         {
623             memset(sendbuffer, 0, CF_BUFSIZE);
624 
625             if ((n_read = read(fd, sendbuffer, blocksize)) == -1)
626             {
627                 Log(LOG_LEVEL_ERR, "Read failed in EncryptGetFile. (read: %s)", GetErrorStr());
628                 break;
629             }
630 
631             off_t savedlen = sb.st_size;
632 
633             if (count++ % div == 0)       /* Don't do this too often */
634             {
635                 Log(LOG_LEVEL_DEBUG, "Restatting '%s' - size %d", filename, n_read);
636                 if (stat(filename, &sb))
637                 {
638                     Log(LOG_LEVEL_ERR, "Cannot stat file '%s' (stat: %s)",
639                             filename, GetErrorStr());
640                     break;
641                 }
642             }
643 
644             if (sb.st_size != savedlen)
645             {
646                 AbortTransfer(conn_info, filename);
647                 break;
648             }
649 
650             total += n_read;
651 
652             if (n_read > 0)
653             {
654                 EVP_EncryptInit_ex(ctx, CfengineCipher(enctype), NULL, key, iv);
655 
656                 if (!EVP_EncryptUpdate(ctx, out, &cipherlen, sendbuffer, n_read))
657                 {
658                     FailedTransfer(conn_info);
659                     EVP_CIPHER_CTX_free(ctx);
660                     close(fd);
661                     return;
662                 }
663 
664                 if (!EVP_EncryptFinal_ex(ctx, out + cipherlen, &finlen))
665                 {
666                     FailedTransfer(conn_info);
667                     EVP_CIPHER_CTX_free(ctx);
668                     close(fd);
669                     return;
670                 }
671             }
672 
673             if (total >= savedlen)
674             {
675                 if (SendTransaction(conn_info, (const char *) out, cipherlen + finlen, CF_DONE) == -1)
676                 {
677                     Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
678                     EVP_CIPHER_CTX_free(ctx);
679                     close(fd);
680                     return;
681                 }
682                 break;
683             }
684             else
685             {
686                 if (SendTransaction(conn_info, (const char *) out, cipherlen + finlen, CF_MORE) == -1)
687                 {
688                     Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
689                     close(fd);
690                     EVP_CIPHER_CTX_free(ctx);
691                     return;
692                 }
693             }
694         }
695     }
696 
697     EVP_CIPHER_CTX_free(ctx);
698     close(fd);
699 }
700 
StatFile(ServerConnectionState * conn,char * sendbuffer,char * ofilename)701 int StatFile(ServerConnectionState *conn, char *sendbuffer, char *ofilename)
702 /* Because we do not know the size or structure of remote datatypes,*/
703 /* the simplest way to transfer the data is to convert them into */
704 /* plain text and interpret them on the other side. */
705 {
706     Stat cfst;
707     struct stat statbuf, statlinkbuf;
708     char linkbuf[CF_BUFSIZE], filename[CF_BUFSIZE - 128];
709     int islink = false;
710 
711     TranslatePath(ofilename, filename, sizeof(filename));
712 
713     memset(&cfst, 0, sizeof(Stat));
714 
715     if (strlen(ReadLastNode(filename)) > CF_MAXLINKSIZE)
716     {
717         snprintf(sendbuffer, CF_MSGSIZE, "BAD: Filename suspiciously long [%s]", filename);
718         Log(LOG_LEVEL_ERR, "%s", sendbuffer);
719         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
720         return -1;
721     }
722 
723     if (lstat(filename, &statbuf) == -1)
724     {
725         snprintf(sendbuffer, CF_MSGSIZE, "BAD: unable to stat file %s", filename);
726         Log(LOG_LEVEL_VERBOSE, "%s. (lstat: %s)", sendbuffer, GetErrorStr());
727         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
728         return -1;
729     }
730 
731     cfst.cf_readlink = NULL;
732     cfst.cf_lmode = 0;
733     cfst.cf_nlink = CF_NOSIZE;
734 
735     memset(linkbuf, 0, CF_BUFSIZE);
736 
737 #ifndef __MINGW32__                   // windows doesn't support symbolic links
738     if (S_ISLNK(statbuf.st_mode))
739     {
740         islink = true;
741         cfst.cf_type = FILE_TYPE_LINK; /* pointless - overwritten */
742         cfst.cf_lmode = statbuf.st_mode & 07777;
743         cfst.cf_nlink = statbuf.st_nlink;
744 
745         if (readlink(filename, linkbuf, CF_BUFSIZE - 1) == -1)
746         {
747             strcpy(sendbuffer, "BAD: unable to read link");
748             Log(LOG_LEVEL_ERR, "%s. (readlink: %s)", sendbuffer, GetErrorStr());
749             SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
750             return -1;
751         }
752 
753         Log(LOG_LEVEL_DEBUG, "readlink '%s'", linkbuf);
754 
755         cfst.cf_readlink = linkbuf;
756     }
757 
758     if (islink && (stat(filename, &statlinkbuf) != -1))       /* linktype=copy used by agent */
759     {
760         Log(LOG_LEVEL_DEBUG, "Getting size of link deref '%s'", linkbuf);
761         statbuf.st_size = statlinkbuf.st_size;
762         statbuf.st_mode = statlinkbuf.st_mode;
763         statbuf.st_uid = statlinkbuf.st_uid;
764         statbuf.st_gid = statlinkbuf.st_gid;
765         statbuf.st_mtime = statlinkbuf.st_mtime;
766         statbuf.st_ctime = statlinkbuf.st_ctime;
767     }
768 
769 #endif /* !__MINGW32__ */
770 
771     if (S_ISDIR(statbuf.st_mode))
772     {
773         cfst.cf_type = FILE_TYPE_DIR;
774     }
775 
776     if (S_ISREG(statbuf.st_mode))
777     {
778         cfst.cf_type = FILE_TYPE_REGULAR;
779     }
780 
781     if (S_ISSOCK(statbuf.st_mode))
782     {
783         cfst.cf_type = FILE_TYPE_SOCK;
784     }
785 
786     if (S_ISCHR(statbuf.st_mode))
787     {
788         cfst.cf_type = FILE_TYPE_CHAR_;
789     }
790 
791     if (S_ISBLK(statbuf.st_mode))
792     {
793         cfst.cf_type = FILE_TYPE_BLOCK;
794     }
795 
796     if (S_ISFIFO(statbuf.st_mode))
797     {
798         cfst.cf_type = FILE_TYPE_FIFO;
799     }
800 
801     cfst.cf_mode = statbuf.st_mode & 07777;
802     cfst.cf_uid = statbuf.st_uid & 0xFFFFFFFF;
803     cfst.cf_gid = statbuf.st_gid & 0xFFFFFFFF;
804     cfst.cf_size = statbuf.st_size;
805     cfst.cf_atime = statbuf.st_atime;
806     cfst.cf_mtime = statbuf.st_mtime;
807     cfst.cf_ctime = statbuf.st_ctime;
808     cfst.cf_ino = statbuf.st_ino;
809     cfst.cf_dev = statbuf.st_dev;
810     cfst.cf_readlink = linkbuf;
811 
812     if (cfst.cf_nlink == CF_NOSIZE)
813     {
814         cfst.cf_nlink = statbuf.st_nlink;
815     }
816 
817     /* Is file sparse? */
818     if (statbuf.st_size > ST_NBYTES(statbuf))
819     {
820         cfst.cf_makeholes = 1;  /* must have a hole to get checksum right */
821     }
822     else
823     {
824         cfst.cf_makeholes = 0;
825     }
826 
827     memset(sendbuffer, 0, CF_MSGSIZE);
828 
829     /* send as plain text */
830 
831     Log(LOG_LEVEL_DEBUG, "OK: type = %d, mode = %jo, lmode = %jo, "
832         "uid = %ju, gid = %ju, size = %jd, atime=%jd, mtime = %jd",
833         cfst.cf_type, (uintmax_t) cfst.cf_mode, (uintmax_t) cfst.cf_lmode,
834         (uintmax_t) cfst.cf_uid, (uintmax_t) cfst.cf_gid, (intmax_t) cfst.cf_size,
835         (intmax_t) cfst.cf_atime, (intmax_t) cfst.cf_mtime);
836 
837     snprintf(sendbuffer, CF_MSGSIZE,
838              "OK: %d %ju %ju %ju %ju %jd %jd %jd %jd %d %d %d %jd",
839              cfst.cf_type, (uintmax_t) cfst.cf_mode, (uintmax_t) cfst.cf_lmode,
840              (uintmax_t) cfst.cf_uid, (uintmax_t) cfst.cf_gid,   (intmax_t) cfst.cf_size,
841              (intmax_t) cfst.cf_atime, (intmax_t) cfst.cf_mtime, (intmax_t) cfst.cf_ctime,
842              cfst.cf_makeholes, cfst.cf_ino, cfst.cf_nlink, (intmax_t) cfst.cf_dev);
843 
844     SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
845 
846     memset(sendbuffer, 0, CF_MSGSIZE);
847 
848     if (cfst.cf_readlink != NULL)
849     {
850         strcpy(sendbuffer, "OK:");
851         strcat(sendbuffer, cfst.cf_readlink);
852     }
853     else
854     {
855         strcpy(sendbuffer, "OK:");
856     }
857 
858     SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
859     return 0;
860 }
861 
CompareLocalHash(const char * filename,const unsigned char digest[EVP_MAX_MD_SIZE+1],char sendbuffer[CFD_FALSE_SIZE])862 bool CompareLocalHash(const char *filename, const unsigned char digest[EVP_MAX_MD_SIZE + 1],
863                       char sendbuffer[CFD_FALSE_SIZE])
864 {
865     nt_static_assert(CFD_FALSE_SIZE == (strlen(CFD_FALSE) + 1));
866     nt_static_assert(strlen(CFD_FALSE) >= strlen(CFD_TRUE));
867     char translated_filename[CF_BUFSIZE] = { 0 };
868     TranslatePath(filename, translated_filename, sizeof(translated_filename));
869 
870     unsigned char file_digest[EVP_MAX_MD_SIZE + 1] = { 0 };
871     /* TODO connection might timeout if this takes long! */
872     HashFile(translated_filename, file_digest, CF_DEFAULT_DIGEST, false);
873 
874     if (HashesMatch(digest, file_digest, CF_DEFAULT_DIGEST))
875     {
876         strcpy(sendbuffer, CFD_FALSE);
877         Log(LOG_LEVEL_DEBUG, "Hashes matched ok");
878         return true;
879     }
880     else
881     {
882         strcpy(sendbuffer, CFD_TRUE);
883         Log(LOG_LEVEL_DEBUG, "Hashes didn't match");
884         return false;
885     }
886 }
887 
GetServerLiteral(EvalContext * ctx,ServerConnectionState * conn,char * sendbuffer,char * recvbuffer,int encrypted)888 void GetServerLiteral(EvalContext *ctx, ServerConnectionState *conn, char *sendbuffer, char *recvbuffer, int encrypted)
889 {
890     char handle[CF_BUFSIZE], out[CF_BUFSIZE];
891     int cipherlen;
892 
893     sscanf(recvbuffer, "VAR %255[^\n]", handle);
894 
895     if (ReturnLiteralData(ctx, handle, out))
896     {
897         memset(sendbuffer, 0, CF_BUFSIZE);
898         snprintf(sendbuffer, CF_BUFSIZE, "%s", out);
899     }
900     else
901     {
902         memset(sendbuffer, 0, CF_BUFSIZE);
903         snprintf(sendbuffer, CF_BUFSIZE, "BAD: Not found");
904     }
905 
906     if (encrypted)
907     {
908         cipherlen = EncryptString(out, sizeof(out),
909                                   sendbuffer, strlen(sendbuffer) + 1,
910                                   conn->encryption_type, conn->session_key);
911         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
912     }
913     else
914     {
915         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
916     }
917 }
918 
GetServerQuery(ServerConnectionState * conn,char * recvbuffer,int encrypt)919 bool GetServerQuery(ServerConnectionState *conn, char *recvbuffer, int encrypt)
920 {
921     char query[CF_BUFSIZE];
922 
923     query[0] = '\0';
924     sscanf(recvbuffer, "QUERY %255[^\n]", query);
925 
926     if (strlen(query) == 0)
927     {
928         return false;
929     }
930 
931     return ReturnQueryData(conn, query, encrypt);
932 }
933 
ReplyServerContext(ServerConnectionState * conn,int encrypted,Item * classes)934 void ReplyServerContext(ServerConnectionState *conn, int encrypted, Item *classes)
935 {
936     char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET];
937 
938     size_t ret = ItemList2CSV_bound(classes,
939                                     sendbuffer, sizeof(sendbuffer), ',');
940     if (ret >= sizeof(sendbuffer))
941     {
942         Log(LOG_LEVEL_ERR, "Overflow: classes don't fit in send buffer");
943     }
944 
945     DeleteItemList(classes);
946 
947     if (encrypted)
948     {
949         char out[CF_BUFSIZE];
950         int cipherlen = EncryptString(out, sizeof(out),
951                                       sendbuffer, strlen(sendbuffer) + 1,
952                                       conn->encryption_type, conn->session_key);
953         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
954     }
955     else
956     {
957         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
958     }
959 }
960 
CfOpenDirectory(ServerConnectionState * conn,char * sendbuffer,char * oldDirname)961 int CfOpenDirectory(ServerConnectionState *conn, char *sendbuffer, char *oldDirname)
962 {
963     Dir *dirh;
964     const struct dirent *dirp;
965     int offset;
966     char dirname[CF_BUFSIZE - 128];
967 
968     TranslatePath(oldDirname, dirname, sizeof(dirname));
969 
970     if (!IsAbsoluteFileName(dirname))
971     {
972         strcpy(sendbuffer, "BAD: request to access a non-absolute filename");
973         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
974         return -1;
975     }
976 
977     if ((dirh = DirOpen(dirname)) == NULL)
978     {
979         Log(LOG_LEVEL_INFO, "Couldn't open directory '%s' (DirOpen:%s)",
980             dirname, GetErrorStr());
981         snprintf(sendbuffer, CF_BUFSIZE, "BAD: cfengine, couldn't open dir %s", dirname);
982         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
983         return -1;
984     }
985 
986 /* Pack names for transmission */
987 
988     offset = 0;
989     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
990     {
991         /* Always leave MAXLINKSIZE bytes for CFD_TERMINATOR. Why??? */
992         if (strlen(dirp->d_name) + 1 + offset >= CF_BUFSIZE - CF_MAXLINKSIZE)
993         {
994             /* Double '\0' indicates end of packet. */
995             sendbuffer[offset] = '\0';
996             SendTransaction(conn->conn_info, sendbuffer, offset + 1, CF_MORE);
997 
998             offset = 0;                                       /* new packet */
999         }
1000 
1001         /* TODO fix copying names greater than 256. */
1002         strlcpy(sendbuffer + offset, dirp->d_name, CF_MAXLINKSIZE);
1003         offset += strlen(dirp->d_name) + 1;                  /* +1 for '\0' */
1004     }
1005 
1006     strcpy(sendbuffer + offset, CFD_TERMINATOR);
1007     offset += strlen(CFD_TERMINATOR) + 1;                    /* +1 for '\0' */
1008     /* Double '\0' indicates end of packet. */
1009     sendbuffer[offset] = '\0';
1010     SendTransaction(conn->conn_info, sendbuffer, offset + 1, CF_DONE);
1011 
1012     DirClose(dirh);
1013     return 0;
1014 }
1015 
1016 /**************************************************************/
1017 
CfSecOpenDirectory(ServerConnectionState * conn,char * sendbuffer,char * dirname)1018 int CfSecOpenDirectory(ServerConnectionState *conn, char *sendbuffer, char *dirname)
1019 {
1020     Dir *dirh;
1021     const struct dirent *dirp;
1022     int offset, cipherlen;
1023     char out[CF_BUFSIZE];
1024 
1025     if (!IsAbsoluteFileName(dirname))
1026     {
1027         strcpy(sendbuffer, "BAD: request to access a non-absolute filename");
1028         cipherlen = EncryptString(out, sizeof(out),
1029                                   sendbuffer, strlen(sendbuffer) + 1,
1030                                   conn->encryption_type, conn->session_key);
1031         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
1032         return -1;
1033     }
1034 
1035     if ((dirh = DirOpen(dirname)) == NULL)
1036     {
1037         Log(LOG_LEVEL_VERBOSE, "Couldn't open dir %s", dirname);
1038         snprintf(sendbuffer, CF_BUFSIZE, "BAD: cfengine, couldn't open dir %s", dirname);
1039         cipherlen = EncryptString(out, sizeof(out),
1040                                   sendbuffer, strlen(sendbuffer) + 1,
1041                                   conn->encryption_type, conn->session_key);
1042         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
1043         return -1;
1044     }
1045 
1046 /* Pack names for transmission */
1047 
1048     memset(sendbuffer, 0, CF_BUFSIZE);
1049 
1050     offset = 0;
1051 
1052     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
1053     {
1054         if (strlen(dirp->d_name) + 1 + offset >= CF_BUFSIZE - CF_MAXLINKSIZE)
1055         {
1056             cipherlen = EncryptString(out, sizeof(out),
1057                                       sendbuffer, offset + 1,
1058                                       conn->encryption_type, conn->session_key);
1059             SendTransaction(conn->conn_info, out, cipherlen, CF_MORE);
1060             offset = 0;
1061             memset(sendbuffer, 0, CF_BUFSIZE);
1062             memset(out, 0, CF_BUFSIZE);
1063         }
1064 
1065         strlcpy(sendbuffer + offset, dirp->d_name, CF_MAXLINKSIZE);
1066         /* + zero byte separator */
1067         offset += strlen(dirp->d_name) + 1;
1068     }
1069 
1070     strcpy(sendbuffer + offset, CFD_TERMINATOR);
1071 
1072     cipherlen =
1073         EncryptString(out, sizeof(out),
1074                       sendbuffer, offset + 2 + strlen(CFD_TERMINATOR),
1075                       conn->encryption_type, conn->session_key);
1076     SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
1077     DirClose(dirh);
1078     return 0;
1079 }
1080 
1081 
1082 /********************* MISC UTILITY FUNCTIONS *************************/
1083 
1084 
1085 /**
1086  * Search and replace occurrences of #find1, #find2, #find3, with
1087  * #repl1, #repl2, #repl3 respectively.
1088  *
1089  *   "$(connection.ip)" from "191.168.0.1"
1090  *   "$(connection.hostname)" from "blah.cfengine.com",
1091  *   "$(connection.key)" from "SHA=asdfghjkl"
1092  *
1093  * @return the output length of #buf, (size_t) -1 if overflow would occur,
1094  *         or 0 if no replacement happened and #buf was not touched.
1095  *
1096  * @TODO change the function to more generic interface accepting arbitrary
1097  *       find/replace pairs.
1098  */
ReplaceSpecialVariables(char * buf,size_t buf_size,const char * find1,const char * repl1,const char * find2,const char * repl2,const char * find3,const char * repl3)1099 size_t ReplaceSpecialVariables(char *buf, size_t buf_size,
1100                                const char *find1, const char *repl1,
1101                                const char *find2, const char *repl2,
1102                                const char *find3, const char *repl3)
1103 {
1104     size_t ret = 0;
1105 
1106     if ((find1 != NULL) && (find1[0] != '\0') &&
1107         (repl1 != NULL) && (repl1[0] != '\0'))
1108     {
1109         size_t ret2 = StringReplace(buf, buf_size, find1, repl1);
1110         ret = MAX(ret, ret2);           /* size_t is unsigned, thus -1 wins */
1111     }
1112     if ((ret != (size_t) -1) &&
1113         (find2 != NULL) && (find2[0] != '\0') &&
1114         (repl2 != NULL) && (repl2[0] != '\0'))
1115     {
1116         size_t ret2 = StringReplace(buf, buf_size, find2, repl2);
1117         ret = MAX(ret, ret2);
1118     }
1119     if ((ret != (size_t) -1) &&
1120         (find3 != NULL) && (find3[0] != '\0') &&
1121         (repl3 != NULL) && (repl3[0] != '\0'))
1122     {
1123         size_t ret2 = StringReplace(buf, buf_size, find3, repl3);
1124         ret = MAX(ret, ret2);
1125     }
1126 
1127     /* Zero is returned only if all of the above were zero. */
1128     return ret;
1129 }
1130 
1131 
1132 /**
1133  * Remove trailing FILE_SEPARATOR, unless we're referring to root dir: '/' or 'a:\'
1134  */
PathRemoveTrailingSlash(char * s,size_t s_len)1135 bool PathRemoveTrailingSlash(char *s, size_t s_len)
1136 {
1137     char *first_separator = strchr(s, FILE_SEPARATOR);
1138 
1139     if (first_separator != NULL &&
1140          s[s_len-1] == FILE_SEPARATOR &&
1141         &s[s_len-1] != first_separator)
1142     {
1143         s[s_len-1] = '\0';
1144         return true;
1145     }
1146 
1147     return false;
1148 }
1149 
1150 /**
1151  * Append a trailing FILE_SEPARATOR if it's not there.
1152  */
PathAppendTrailingSlash(char * s,size_t s_len)1153 bool PathAppendTrailingSlash(char *s, size_t s_len)
1154 {
1155     if (s_len > 0 && s[s_len-1] != FILE_SEPARATOR)
1156     {
1157         s[s_len] = FILE_SEPARATOR;
1158         s[s_len+1] = '\0';
1159         return true;
1160     }
1161 
1162     return false;
1163 }
1164 
1165 /* We use this instead of IsAbsoluteFileName() which also checks for
1166  * quotes. There is no meaning in receiving quoted strings over the
1167  * network. */
PathIsAbsolute(const char * s)1168 static bool PathIsAbsolute(const char *s)
1169 {
1170     bool result = false;
1171 
1172 #if defined(__MINGW32__)
1173     if (isalpha(s[0]) && (s[1] == ':') && (s[2] == FILE_SEPARATOR))
1174     {
1175         result = true;                                          /* A:\ */
1176     }
1177     else                                                        /* \\ */
1178     {
1179         result = (s[0] == FILE_SEPARATOR && s[1] == FILE_SEPARATOR);
1180     }
1181 #else
1182     if (s[0] == FILE_SEPARATOR)                                 /* / */
1183     {
1184         result = true;
1185     }
1186 #endif
1187 
1188     return result;
1189 }
1190 
1191 /**
1192  * If #path is relative, expand the first part accorting to #shortcuts, doing
1193  * any replacements of special variables "$(connection.*)" on the way, with
1194  * the provided #ipaddr, #hostname, #key.
1195  *
1196  * @return the length of the new string or 0 if no replace took place. -1 in
1197  * case of overflow.
1198  */
ShortcutsExpand(char * path,size_t path_size,const StringMap * shortcuts,const char * ipaddr,const char * hostname,const char * key)1199 size_t ShortcutsExpand(char *path, size_t path_size,
1200                        const StringMap *shortcuts,
1201                        const char *ipaddr, const char *hostname,
1202                        const char *key)
1203 {
1204     char dst[path_size];
1205     size_t path_len = strlen(path);
1206 
1207     if (path_len == 0)
1208     {
1209         UnexpectedError("ShortcutsExpand: 0 length string!");
1210         return (size_t) -1;
1211     }
1212 
1213     if (!PathIsAbsolute(path))
1214     {
1215         char *separ = strchr(path, FILE_SEPARATOR);
1216         size_t first_part_len;
1217         if (separ != NULL)
1218         {
1219             first_part_len = separ - path;
1220             assert(first_part_len < path_len);
1221         }
1222         else
1223         {
1224             first_part_len = path_len;
1225         }
1226         size_t second_part_len = path_len - first_part_len;
1227 
1228         /* '\0'-terminate first_part, do StringMapGet(), undo '\0'-term */
1229         char separ_char = path[first_part_len];
1230         path[first_part_len] = '\0';
1231         char *replacement = StringMapGet(shortcuts, path);
1232         path[first_part_len] = separ_char;
1233 
1234         /* Either the first_part ends with separator, or its all the string */
1235         assert(separ_char == FILE_SEPARATOR ||
1236                separ_char == '\0');
1237 
1238         if (replacement != NULL)                 /* we found a shortcut */
1239         {
1240             size_t replacement_len = strlen(replacement);
1241             if (replacement_len + 1 > path_size)
1242             {
1243                 goto err_too_long;
1244             }
1245 
1246             /* Replacement path for shortcut was found, but it may contain
1247              * special variables such as $(connection.ip), that we also need
1248              * to expand. */
1249             /* TODO if StrAnyStr(replacement, "$(connection.ip)", "$(connection.hostname)", "$(connection.key)") */
1250             char replacement_expanded[path_size];
1251             memcpy(replacement_expanded, replacement, replacement_len + 1);
1252 
1253             size_t ret =
1254                 ReplaceSpecialVariables(replacement_expanded, sizeof(replacement_expanded),
1255                                         "$(connection.ip)", ipaddr,
1256                                         "$(connection.hostname)", hostname,
1257                                         "$(connection.key)", key);
1258 
1259             size_t replacement_expanded_len;
1260             /* (ret == -1) is checked later. */
1261             if (ret == 0)                        /* No expansion took place */
1262             {
1263                 replacement_expanded_len = replacement_len;
1264             }
1265             else
1266             {
1267                 replacement_expanded_len = ret;
1268             }
1269 
1270             size_t dst_len = replacement_expanded_len + second_part_len;
1271             if (ret == (size_t) -1 || dst_len + 1 > path_size)
1272             {
1273                 goto err_too_long;
1274             }
1275 
1276             /* Assemble final result. */
1277             memcpy(dst, replacement_expanded, replacement_expanded_len);
1278             /* Second part may be empty, then this only copies '\0'. */
1279             memcpy(&dst[replacement_expanded_len], &path[first_part_len],
1280                    second_part_len + 1);
1281 
1282             Log(LOG_LEVEL_DEBUG,
1283                 "ShortcutsExpand: Path '%s' became: %s",
1284                 path, dst);
1285 
1286             /* Copy back to path. */
1287             memcpy(path, dst, dst_len + 1);
1288             return dst_len;
1289         }
1290     }
1291 
1292     /* No expansion took place, either because path was absolute, or because
1293      * no shortcut was found. */
1294     return 0;
1295 
1296   err_too_long:
1297     Log(LOG_LEVEL_INFO, "Path too long after shortcut expansion!");
1298     return (size_t) -1;
1299 }
1300 
1301 /**
1302  * Canonicalize a path, ensure it is absolute, and resolve all symlinks.
1303  * In detail:
1304  *
1305  * 1. MinGW: Translate to windows-compatible: slashes to FILE_SEPARATOR
1306  *           and uppercase to lowercase.
1307  * 2. Ensure the path is absolute.
1308  * 3. Resolve symlinks, resolve '.' and '..' and remove double '/'
1309  *    WARNING this will currently fail if file does not exist,
1310  *    returning -1 and setting errno==ENOENT!
1311  *
1312  * @note trailing slash is left as is if it's there.
1313  * @note #reqpath is written in place (if success was returned). It is always
1314  *       an absolute path.
1315  * @note #reqpath is invalid to be of zero length.
1316  * @note #reqpath_size must be at least PATH_MAX.
1317  *
1318  * @return the length of #reqpath after preprocessing. In case of error
1319  *         return (size_t) -1.
1320  */
PreprocessRequestPath(char * reqpath,size_t reqpath_size)1321 size_t PreprocessRequestPath(char *reqpath, size_t reqpath_size)
1322 {
1323     errno = 0;             /* on return, errno might be set from realpath() */
1324     char dst[reqpath_size];
1325     size_t reqpath_len = strlen(reqpath);
1326 
1327     if (reqpath_len == 0)
1328     {
1329         UnexpectedError("PreprocessRequestPath: 0 length string!");
1330         return (size_t) -1;
1331     }
1332 
1333     /* Translate all slashes to backslashes on Windows so that all the rest
1334      * of work is done using FILE_SEPARATOR. THIS HAS TO BE FIRST. */
1335     #if defined(__MINGW32__)
1336     {
1337         char *p = reqpath;
1338         while ((p = strchr(p, '/')) != NULL)
1339         {
1340             *p = FILE_SEPARATOR;
1341         }
1342         /* Also convert everything to lowercase. */
1343         ToLowerStrInplace(reqpath);
1344     }
1345     #endif
1346 
1347     if (!PathIsAbsolute(reqpath))
1348     {
1349         Log(LOG_LEVEL_INFO, "Relative paths are not allowed: %s", reqpath);
1350         return (size_t) -1;
1351     }
1352 
1353     /* TODO replace realpath with Solaris' resolvepath(), in all
1354      * platforms. That one does not check for existence, just resolves
1355      * symlinks and canonicalises. Ideally we would want the following:
1356      *
1357      * PathResolve(dst, src, dst_size, basedir);
1358      *
1359      * - It prepends basedir if path relative (could be the shortcut)
1360      * - It compresses double '/', '..', '.'
1361      * - It follows each component of the path replacing symlinks
1362      * - errno = ENOENT if path component does not exist, but keeps
1363      *   compressing path anyway.
1364      * - Leaves trailing slash as it was passed to it.
1365      *   OR appends it depending on last component ISDIR.
1366      */
1367 
1368     assert(sizeof(dst) >= PATH_MAX);               /* needed for realpath() */
1369     char *p = realpath(reqpath, dst);
1370     if (p == NULL)
1371     {
1372         /* TODO If path does not exist try to canonicalise only directory. INSECURE?*/
1373         /* if (errno == ENOENT) */
1374         /* { */
1375 
1376         /* } */
1377         struct stat statbuf;
1378         if ((lstat(reqpath, &statbuf) == 0) && S_ISLNK(statbuf.st_mode))
1379         {
1380             Log(LOG_LEVEL_VERBOSE, "Requested file is a dead symbolic link (filename: %s)", reqpath);
1381             strlcpy(dst, reqpath, CF_BUFSIZE);
1382         }
1383         else
1384         {
1385             Log(LOG_LEVEL_INFO,
1386                 "Failed to canonicalise filename '%s' (realpath: %s)",
1387                 reqpath, GetErrorStr());
1388             return (size_t) -1;
1389         }
1390     }
1391 
1392     size_t dst_len = strlen(dst);
1393 
1394     /* Some realpath()s remove trailing '/' even for dirs! Put it back if
1395      * original request had it. */
1396     if (reqpath[reqpath_len - 1] == FILE_SEPARATOR &&
1397         dst[dst_len - 1]         != FILE_SEPARATOR)
1398     {
1399         if (dst_len + 2 > sizeof(dst))
1400         {
1401             Log(LOG_LEVEL_INFO, "Error, path too long: %s", reqpath);
1402             return (size_t) -1;
1403         }
1404 
1405         PathAppendTrailingSlash(dst, dst_len);
1406         dst_len++;
1407     }
1408 
1409     memcpy(reqpath, dst, dst_len + 1);
1410     reqpath_len = dst_len;
1411 
1412     return reqpath_len;
1413 }
1414 
1415 
1416 /**
1417  * Set conn->uid (and conn->sid on Windows).
1418  */
SetConnIdentity(ServerConnectionState * conn,const char * username)1419 void SetConnIdentity(ServerConnectionState *conn, const char *username)
1420 {
1421     size_t username_len = strlen(username);
1422 
1423     conn->uid = CF_UNKNOWN_OWNER;
1424     conn->username[0] = '\0';
1425 
1426     if (username_len < sizeof(conn->username))
1427     {
1428         memcpy(conn->username, username, username_len + 1);
1429     }
1430 
1431     bool is_root = strcmp(conn->username, "root") == 0;
1432     if (is_root)
1433     {
1434         /* If the remote user identifies himself as root, even on Windows
1435          * cf-serverd must grant access to all files. uid==0 is checked later
1436          * in TranferRights() for that. */
1437         conn->uid = 0;
1438     }
1439 
1440 #ifdef __MINGW32__            /* NT uses security identifier instead of uid */
1441 
1442     if (!NovaWin_UserNameToSid(conn->username, (SID *) conn->sid,
1443                                CF_MAXSIDSIZE, !is_root))
1444     {
1445         memset(conn->sid, 0, CF_MAXSIDSIZE);  /* is invalid sid - discarded */
1446     }
1447 
1448 #else                                                 /* UNIX - common path */
1449 
1450     if (conn->uid == CF_UNKNOWN_OWNER)      /* skip looking up UID for root */
1451     {
1452         static pthread_mutex_t pwnam_mtx = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
1453         struct passwd *pw = NULL;
1454 
1455         ThreadLock(&pwnam_mtx);
1456         /* TODO Redmine#7643: looking up the UID is expensive and should
1457          * not be needed, since today's agent machine VS hub most probably
1458          * do not share the accounts. */
1459         pw = getpwnam(conn->username);
1460         if (pw != NULL)
1461         {
1462             conn->uid = pw->pw_uid;
1463         }
1464         ThreadUnlock(&pwnam_mtx);
1465     }
1466 
1467 #endif
1468 }
1469 
1470 
CharsetAcceptable(const char * s,size_t s_len)1471 static bool CharsetAcceptable(const char *s, size_t s_len)
1472 {
1473     const char *ACCEPT =
1474         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:";
1475     size_t acceptable_chars = strspn(s, ACCEPT);
1476     if (s_len == 0)
1477     {
1478         s_len = strlen(s);
1479     }
1480 
1481     if (acceptable_chars < s_len)
1482     {
1483         Log(LOG_LEVEL_INFO,
1484             "llegal character in column %zu of: %s",
1485             acceptable_chars, s);
1486         return false;
1487     }
1488 
1489     return true;
1490 }
1491 
1492 
1493 /**
1494  * @param #args_start is a comma separated list of words, which may be
1495  *                    prefixed with spaces and suffixed with spaces and other
1496  *                    words. Example: " asd,fgh,jk blah". In this example the
1497  *                    list has 3 words, and "blah" is not one of them.
1498  *
1499  * Both #args_start and #args_len are in-out parameters.
1500  * At the end of execution #args_start returns the real start of the list, and
1501  * #args_len the real length.
1502  */
AuthorizeDelimitedArgs(const ServerConnectionState * conn,struct acl * acl,char ** args_start,size_t * args_len)1503 static bool AuthorizeDelimitedArgs(const ServerConnectionState *conn,
1504                                    struct acl *acl,
1505                                    char **args_start, size_t *args_len)
1506 {
1507     char *s;
1508     size_t s_len, skip;
1509 
1510     assert(args_start != NULL);
1511     assert(args_len != NULL);
1512 
1513     /* Give the name s and s_len purely for ease of use. */
1514     s_len = *args_len;
1515     s     = *args_start;
1516     /* Skip spaces in the beginning of argument list. */
1517     skip  = strspn(s, " \t");
1518     s    += skip;
1519 
1520     if (s_len == 0)                        /* if end was not given, find it */
1521     {
1522         s_len = strcspn(s, " \t");
1523     }
1524     else                                                /* if end was given */
1525     {
1526         s_len = (skip <= s_len) ? (s_len - skip) : 0;
1527     }
1528 
1529     /* Admit, unless any token fails to be authorised. */
1530     bool admit = true;
1531     if (s_len > 0)
1532     {
1533         const char tmp_c = s[s_len];
1534         s[s_len] = '\0';
1535 
1536         /* Iterate over comma-separated list. */
1537 
1538         char *token = &s[0];
1539         while (token < &s[s_len] && admit)
1540         {
1541             char *token_end = strchrnul(token, ',');
1542 
1543             const char tmp_sep = *token_end;
1544             *token_end = '\0';
1545 
1546             if (!CharsetAcceptable(token, 0) ||
1547                 !acl_CheckRegex(acl, token,
1548                                 conn->ipaddr, conn->revdns,
1549                                 KeyPrintableHash(conn->conn_info->remote_key),
1550                                 conn->username))
1551             {
1552                 Log(LOG_LEVEL_INFO, "Access denied to: %s", token);
1553                 admit = false;                              /* EARLY RETURN */
1554             }
1555 
1556             *token_end = tmp_sep;
1557             token      = token_end + 1;
1558         }
1559 
1560         s[s_len] = tmp_c;
1561     }
1562 
1563     *args_start = s;
1564     *args_len   = s_len;
1565     return admit;
1566 }
1567 
1568 
1569 /**
1570  * @return #true if the connection should remain open for next requests, or
1571  *         #false if the server should actively close it - for example when
1572  *         protocol errors have occurred.
1573  */
DoExec2(const EvalContext * ctx,ServerConnectionState * conn,char * exec_args,char * sendbuf,size_t sendbuf_size)1574 bool DoExec2(const EvalContext *ctx,
1575              ServerConnectionState *conn,
1576              char *exec_args,
1577              char *sendbuf, size_t sendbuf_size)
1578 {
1579     assert(conn != NULL);
1580 
1581     /* STEP 0: Verify cfruncommand was successfully configured. */
1582     if (NULL_OR_EMPTY(CFRUNCOMMAND))
1583     {
1584         Log(LOG_LEVEL_INFO, "EXEC denied due to empty cfruncommand");
1585         RefuseAccess(conn, "EXEC");
1586         return false;
1587     }
1588 
1589     /* STEP 1: Resolve and check permissions of CFRUNCOMMAND's arg0. IT is
1590      *         done now and not at configuration time, as the file stat may
1591      *         have changed since then. */
1592     {
1593         char arg0[PATH_MAX];
1594         if (CommandArg0_bound(arg0, CFRUNCOMMAND, sizeof(arg0)) == (size_t) -1 ||
1595             PreprocessRequestPath(arg0, sizeof(arg0))           == (size_t) -1)
1596         {
1597             Log(LOG_LEVEL_INFO, "EXEC failed, invalid cfruncommand arg0");
1598             RefuseAccess(conn, "EXEC");
1599             return false;
1600         }
1601 
1602         /* Check body server access_rules, whether arg0 is authorized. */
1603 
1604         /* TODO EXEC should not just use paths_acl access control, but
1605          * specific "exec_path" ACL. Then different command execution could be
1606          * allowed per host, and the host could even set argv[0] in his EXEC
1607          * request, rather than only the arguments. */
1608 
1609         if (acl_CheckPath(paths_acl, arg0,
1610                           conn->ipaddr, conn->revdns,
1611                           KeyPrintableHash(conn->conn_info->remote_key))
1612             == false)
1613         {
1614             Log(LOG_LEVEL_INFO, "EXEC denied due to ACL for file: %s", arg0);
1615             RefuseAccess(conn, "EXEC");
1616             return false;
1617         }
1618     }
1619 
1620     /* STEP 2: Check body server control "allowusers" */
1621     if (!AllowedUser(conn->username))
1622     {
1623         Log(LOG_LEVEL_INFO, "EXEC denied due to not allowed user: %s",
1624             conn->username);
1625         RefuseAccess(conn, "EXEC");
1626         return false;
1627     }
1628 
1629     /* STEP 3: This matches cf-runagent -s class1,class2 against classes
1630      *         set during cf-serverd's policy evaluation. */
1631 
1632     if (!MatchClasses(ctx, conn))
1633     {
1634         snprintf(sendbuf, sendbuf_size,
1635                  "EXEC denied due to failed class match (check cf-serverd verbose output)");
1636         Log(LOG_LEVEL_INFO, "%s", sendbuf);
1637         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1638         return true;
1639     }
1640 
1641 
1642     /* STEP 4: Parse and authorise the EXEC arguments, which will be used as
1643      *         arguments to CFRUNCOMMAND. Currently we only accept
1644      *         [ -D classlist ] and [ -b bundlesequence ] arguments. */
1645 
1646     char   cmdbuf[CF_BUFSIZE] = "";
1647     size_t cmdbuf_len         = 0;
1648 
1649     nt_static_assert(sizeof(CFRUNCOMMAND) <= sizeof(cmdbuf));
1650 
1651     StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len, CFRUNCOMMAND, 0);
1652 
1653     exec_args += strspn(exec_args,  " \t");                  /* skip spaces */
1654     while (exec_args[0] != '\0')
1655     {
1656         if (strncmp(exec_args, "-D", 2) == 0)
1657         {
1658             exec_args += 2;
1659 
1660             char *classlist = exec_args;
1661             size_t classlist_len = 0;
1662             bool allow = AuthorizeDelimitedArgs(conn, roles_acl,
1663                                                 &classlist, &classlist_len);
1664             if (!allow)
1665             {
1666                 snprintf(sendbuf, sendbuf_size,
1667                          "EXEC denied role activation (check cf-serverd verbose output)");
1668                 Log(LOG_LEVEL_INFO, "%s", sendbuf);
1669                 SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1670                 return true;
1671             }
1672 
1673             if (classlist_len > 0)
1674             {
1675                 /* Append "-D classlist" to cfruncommand. */
1676                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1677                        " -D ", 0);
1678                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1679                        classlist, classlist_len);
1680             }
1681 
1682             exec_args = classlist + classlist_len;
1683         }
1684         else if (strncmp(exec_args, "-b", 2) == 0)
1685         {
1686             exec_args += 2;
1687 
1688             char *bundlesequence = exec_args;
1689             size_t bundlesequence_len = 0;
1690 
1691             bool allow = AuthorizeDelimitedArgs(conn, bundles_acl,
1692                                                 &bundlesequence,
1693                                                 &bundlesequence_len);
1694             if (!allow)
1695             {
1696                 snprintf(sendbuf, sendbuf_size,
1697                          "EXEC denied bundle activation (check cf-serverd verbose output)");
1698                 Log(LOG_LEVEL_INFO, "%s", sendbuf);
1699                 SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1700                 return true;
1701             }
1702 
1703             if (bundlesequence_len > 0)
1704             {
1705                 /* Append "--bundlesequence bundlesequence" to cfruncommand. */
1706                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1707                        " --bundlesequence ", 0);
1708                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1709                        bundlesequence, bundlesequence_len);
1710             }
1711 
1712             exec_args = bundlesequence + bundlesequence_len;
1713         }
1714         else                                        /* disallowed parameter */
1715         {
1716             snprintf(sendbuf, sendbuf_size,
1717                      "EXEC denied: invalid arguments: %s",
1718                      exec_args);
1719             Log(LOG_LEVEL_INFO, "%s", sendbuf);
1720             SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1721             return true;
1722         }
1723 
1724         exec_args += strspn(exec_args,  " \t");              /* skip spaces */
1725     }
1726 
1727     if (cmdbuf_len >= sizeof(cmdbuf))
1728     {
1729         snprintf(sendbuf, sendbuf_size,
1730                  "EXEC denied: too long (%zu B) command: %s",
1731                  cmdbuf_len, cmdbuf);
1732         Log(LOG_LEVEL_INFO, "%s", sendbuf);
1733         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1734         return false;
1735     }
1736 
1737     /* STEP 5: RUN CFRUNCOMMAND. */
1738 
1739     snprintf(sendbuf, sendbuf_size,
1740              "cf-serverd executing cfruncommand: %s\n",
1741              cmdbuf);
1742     SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1743     Log(LOG_LEVEL_INFO, "%s", sendbuf);
1744 
1745     FILE *pp = cf_popen(cmdbuf, "r", true);
1746     if (pp == NULL)
1747     {
1748         snprintf(sendbuf, sendbuf_size,
1749                  "Unable to run '%s' (pipe: %s)",
1750                  cmdbuf, GetErrorStr());
1751         Log(LOG_LEVEL_INFO, "%s", sendbuf);
1752         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1753         return false;
1754     }
1755 
1756     size_t line_size = CF_BUFSIZE;
1757     char *line = xmalloc(line_size);
1758     while (true)
1759     {
1760         ssize_t res = CfReadLine(&line, &line_size, pp);
1761         if (res == -1)
1762         {
1763             if (!feof(pp))
1764             {
1765                 /* Error reading, discard all unconsumed input before
1766                  * aborting - linux-specific! */
1767                 fflush(pp);
1768             }
1769             break;
1770         }
1771 
1772         /* NOTICE: we can't SendTransaction() overlong strings, and we need to
1773          * prepend and append to the string. */
1774         size_t line_len = strlen(line);
1775         if (line_len >= sendbuf_size - 5)
1776         {
1777             line[sendbuf_size - 5] = '\0';
1778         }
1779 
1780         /* Prefixing output with "> " and postfixing with '\n' is new
1781          * behaviour as of 3.7.0. Prefixing happens to avoid zero-length
1782          * transaction packet. */
1783         /* Old cf-runagent versions do not append a newline, so we must do
1784          * it here. New ones do though, so TODO deprecate. */
1785         xsnprintf(sendbuf, sendbuf_size, "> %s\n", line);
1786         if (SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE) == -1)
1787         {
1788             Log(LOG_LEVEL_INFO,
1789                 "Sending failed, aborting EXEC (send: %s)",
1790                 GetErrorStr());
1791             break;
1792         }
1793     }
1794     free(line);
1795     int exit_code = cf_pclose(pp);
1796     if (exit_code >= 0)
1797     {
1798         xsnprintf(sendbuf, sendbuf_size, "(exit code: %d)\n", exit_code);
1799         if (SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE) == -1)
1800         {
1801             Log(LOG_LEVEL_INFO, "Failed to send exit code from EXEC agent run");
1802         }
1803     }
1804 
1805     return true;
1806 }
1807