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 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];
407     struct stat sb;
408     int blocksize = 2048;
409 
410     ConnectionInfo *conn_info = args->conn->conn_info;
411 
412     TranslatePath(filename, args->replyfile);
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 sendbuffer[CF_BUFSIZE + 256], out[CF_BUFSIZE], filename[CF_BUFSIZE];
571     unsigned char iv[32] =
572         { 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 };
573     int blocksize = CF_BUFSIZE - 4 * CF_INBAND_OFFSET;
574     char *key, enctype;
575     struct stat sb;
576     ConnectionInfo *conn_info = args->conn->conn_info;
577 
578     key = args->conn->session_key;
579     enctype = args->conn->encryption_type;
580 
581     TranslatePath(filename, args->replyfile);
582 
583     stat(filename, &sb);
584 
585     Log(LOG_LEVEL_DEBUG, "CfEncryptGetFile('%s'), size = %jd",
586         filename, (intmax_t) sb.st_size);
587 
588 /* Now check to see if we have remote permission */
589 
590     if (!TransferRights(args->conn, filename, &sb))
591     {
592         Log(LOG_LEVEL_INFO, "REFUSE access to file: %s", filename);
593         RefuseAccess(args->conn, args->replyfile);
594         FailedTransfer(conn_info);
595         return;
596     }
597 
598     EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
599     if (ctx == NULL)
600     {
601         Log(LOG_LEVEL_ERR, "Failed to allocate cipher: %s",
602             TLSErrorString(ERR_get_error()));
603         return;
604     }
605 
606     if ((fd = safe_open(filename, O_RDONLY)) == -1)
607     {
608         Log(LOG_LEVEL_ERR, "Open error of file '%s'. (open: %s)", filename, GetErrorStr());
609         FailedTransfer(conn_info);
610     }
611     else
612     {
613         int div = 3;
614 
615         if (sb.st_size > 10485760L) /* File larger than 10 MB, checks every 64kB */
616         {
617             div = 32;
618         }
619 
620         while (true)
621         {
622             memset(sendbuffer, 0, CF_BUFSIZE);
623 
624             if ((n_read = read(fd, sendbuffer, blocksize)) == -1)
625             {
626                 Log(LOG_LEVEL_ERR, "Read failed in EncryptGetFile. (read: %s)", GetErrorStr());
627                 break;
628             }
629 
630             off_t savedlen = sb.st_size;
631 
632             if (count++ % div == 0)       /* Don't do this too often */
633             {
634                 Log(LOG_LEVEL_DEBUG, "Restatting '%s' - size %d", filename, n_read);
635                 if (stat(filename, &sb))
636                 {
637                     Log(LOG_LEVEL_ERR, "Cannot stat file '%s' (stat: %s)",
638                             filename, GetErrorStr());
639                     break;
640                 }
641             }
642 
643             if (sb.st_size != savedlen)
644             {
645                 AbortTransfer(conn_info, filename);
646                 break;
647             }
648 
649             total += n_read;
650 
651             if (n_read > 0)
652             {
653                 EVP_EncryptInit_ex(ctx, CfengineCipher(enctype), NULL, key, iv);
654 
655                 if (!EVP_EncryptUpdate(ctx, out, &cipherlen, sendbuffer, n_read))
656                 {
657                     FailedTransfer(conn_info);
658                     EVP_CIPHER_CTX_free(ctx);
659                     close(fd);
660                     return;
661                 }
662 
663                 if (!EVP_EncryptFinal_ex(ctx, out + cipherlen, &finlen))
664                 {
665                     FailedTransfer(conn_info);
666                     EVP_CIPHER_CTX_free(ctx);
667                     close(fd);
668                     return;
669                 }
670             }
671 
672             if (total >= savedlen)
673             {
674                 if (SendTransaction(conn_info, out, cipherlen + finlen, CF_DONE) == -1)
675                 {
676                     Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
677                     EVP_CIPHER_CTX_free(ctx);
678                     close(fd);
679                     return;
680                 }
681                 break;
682             }
683             else
684             {
685                 if (SendTransaction(conn_info, out, cipherlen + finlen, CF_MORE) == -1)
686                 {
687                     Log(LOG_LEVEL_VERBOSE, "Send failed in GetFile. (send: %s)", GetErrorStr());
688                     close(fd);
689                     EVP_CIPHER_CTX_free(ctx);
690                     return;
691                 }
692             }
693         }
694     }
695 
696     EVP_CIPHER_CTX_free(ctx);
697     close(fd);
698 }
699 
StatFile(ServerConnectionState * conn,char * sendbuffer,char * ofilename)700 int StatFile(ServerConnectionState *conn, char *sendbuffer, char *ofilename)
701 /* Because we do not know the size or structure of remote datatypes,*/
702 /* the simplest way to transfer the data is to convert them into */
703 /* plain text and interpret them on the other side. */
704 {
705     Stat cfst;
706     struct stat statbuf, statlinkbuf;
707     char linkbuf[CF_BUFSIZE], filename[CF_BUFSIZE];
708     int islink = false;
709 
710     TranslatePath(filename, ofilename);
711 
712     memset(&cfst, 0, sizeof(Stat));
713 
714     if (strlen(ReadLastNode(filename)) > CF_MAXLINKSIZE)
715     {
716         snprintf(sendbuffer, CF_MSGSIZE, "BAD: Filename suspiciously long [%s]", filename);
717         Log(LOG_LEVEL_ERR, "%s", sendbuffer);
718         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
719         return -1;
720     }
721 
722     if (lstat(filename, &statbuf) == -1)
723     {
724         snprintf(sendbuffer, CF_MSGSIZE, "BAD: unable to stat file %s", filename);
725         Log(LOG_LEVEL_VERBOSE, "%s. (lstat: %s)", sendbuffer, GetErrorStr());
726         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
727         return -1;
728     }
729 
730     cfst.cf_readlink = NULL;
731     cfst.cf_lmode = 0;
732     cfst.cf_nlink = CF_NOSIZE;
733 
734     memset(linkbuf, 0, CF_BUFSIZE);
735 
736 #ifndef __MINGW32__                   // windows doesn't support symbolic links
737     if (S_ISLNK(statbuf.st_mode))
738     {
739         islink = true;
740         cfst.cf_type = FILE_TYPE_LINK; /* pointless - overwritten */
741         cfst.cf_lmode = statbuf.st_mode & 07777;
742         cfst.cf_nlink = statbuf.st_nlink;
743 
744         if (readlink(filename, linkbuf, CF_BUFSIZE - 1) == -1)
745         {
746             strcpy(sendbuffer, "BAD: unable to read link");
747             Log(LOG_LEVEL_ERR, "%s. (readlink: %s)", sendbuffer, GetErrorStr());
748             SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
749             return -1;
750         }
751 
752         Log(LOG_LEVEL_DEBUG, "readlink '%s'", linkbuf);
753 
754         cfst.cf_readlink = linkbuf;
755     }
756 
757     if (islink && (stat(filename, &statlinkbuf) != -1))       /* linktype=copy used by agent */
758     {
759         Log(LOG_LEVEL_DEBUG, "Getting size of link deref '%s'", linkbuf);
760         statbuf.st_size = statlinkbuf.st_size;
761         statbuf.st_mode = statlinkbuf.st_mode;
762         statbuf.st_uid = statlinkbuf.st_uid;
763         statbuf.st_gid = statlinkbuf.st_gid;
764         statbuf.st_mtime = statlinkbuf.st_mtime;
765         statbuf.st_ctime = statlinkbuf.st_ctime;
766     }
767 
768 #endif /* !__MINGW32__ */
769 
770     if (S_ISDIR(statbuf.st_mode))
771     {
772         cfst.cf_type = FILE_TYPE_DIR;
773     }
774 
775     if (S_ISREG(statbuf.st_mode))
776     {
777         cfst.cf_type = FILE_TYPE_REGULAR;
778     }
779 
780     if (S_ISSOCK(statbuf.st_mode))
781     {
782         cfst.cf_type = FILE_TYPE_SOCK;
783     }
784 
785     if (S_ISCHR(statbuf.st_mode))
786     {
787         cfst.cf_type = FILE_TYPE_CHAR_;
788     }
789 
790     if (S_ISBLK(statbuf.st_mode))
791     {
792         cfst.cf_type = FILE_TYPE_BLOCK;
793     }
794 
795     if (S_ISFIFO(statbuf.st_mode))
796     {
797         cfst.cf_type = FILE_TYPE_FIFO;
798     }
799 
800     cfst.cf_mode = statbuf.st_mode & 07777;
801     cfst.cf_uid = statbuf.st_uid & 0xFFFFFFFF;
802     cfst.cf_gid = statbuf.st_gid & 0xFFFFFFFF;
803     cfst.cf_size = statbuf.st_size;
804     cfst.cf_atime = statbuf.st_atime;
805     cfst.cf_mtime = statbuf.st_mtime;
806     cfst.cf_ctime = statbuf.st_ctime;
807     cfst.cf_ino = statbuf.st_ino;
808     cfst.cf_dev = statbuf.st_dev;
809     cfst.cf_readlink = linkbuf;
810 
811     if (cfst.cf_nlink == CF_NOSIZE)
812     {
813         cfst.cf_nlink = statbuf.st_nlink;
814     }
815 
816     /* Is file sparse? */
817     if (statbuf.st_size > ST_NBYTES(statbuf))
818     {
819         cfst.cf_makeholes = 1;  /* must have a hole to get checksum right */
820     }
821     else
822     {
823         cfst.cf_makeholes = 0;
824     }
825 
826     memset(sendbuffer, 0, CF_MSGSIZE);
827 
828     /* send as plain text */
829 
830     Log(LOG_LEVEL_DEBUG, "OK: type = %d, mode = %jo, lmode = %jo, "
831         "uid = %ju, gid = %ju, size = %jd, atime=%jd, mtime = %jd",
832         cfst.cf_type, (uintmax_t) cfst.cf_mode, (uintmax_t) cfst.cf_lmode,
833         (uintmax_t) cfst.cf_uid, (uintmax_t) cfst.cf_gid, (intmax_t) cfst.cf_size,
834         (intmax_t) cfst.cf_atime, (intmax_t) cfst.cf_mtime);
835 
836     snprintf(sendbuffer, CF_MSGSIZE,
837              "OK: %d %ju %ju %ju %ju %jd %jd %jd %jd %d %d %d %jd",
838              cfst.cf_type, (uintmax_t) cfst.cf_mode, (uintmax_t) cfst.cf_lmode,
839              (uintmax_t) cfst.cf_uid, (uintmax_t) cfst.cf_gid,   (intmax_t) cfst.cf_size,
840              (intmax_t) cfst.cf_atime, (intmax_t) cfst.cf_mtime, (intmax_t) cfst.cf_ctime,
841              cfst.cf_makeholes, cfst.cf_ino, cfst.cf_nlink, (intmax_t) cfst.cf_dev);
842 
843     SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
844 
845     memset(sendbuffer, 0, CF_MSGSIZE);
846 
847     if (cfst.cf_readlink != NULL)
848     {
849         strcpy(sendbuffer, "OK:");
850         strcat(sendbuffer, cfst.cf_readlink);
851     }
852     else
853     {
854         strcpy(sendbuffer, "OK:");
855     }
856 
857     SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
858     return 0;
859 }
860 
CompareLocalHash(const char * filename,const char digest[EVP_MAX_MD_SIZE+1],char sendbuffer[CFD_FALSE_SIZE])861 bool CompareLocalHash(const char *filename, const char digest[EVP_MAX_MD_SIZE + 1],
862                       char sendbuffer[CFD_FALSE_SIZE])
863 {
864     assert(CFD_FALSE_SIZE == (strlen(CFD_FALSE) + 1));
865     assert(strlen(CFD_FALSE) >= strlen(CFD_TRUE));
866     char translated_filename[CF_BUFSIZE] = { 0 };
867     TranslatePath(translated_filename, filename);
868 
869     unsigned char file_digest[EVP_MAX_MD_SIZE + 1] = { 0 };
870     /* TODO connection might timeout if this takes long! */
871     HashFile(translated_filename, file_digest, CF_DEFAULT_DIGEST, false);
872 
873     if (HashesMatch(digest, file_digest, CF_DEFAULT_DIGEST))
874     {
875         strcpy(sendbuffer, CFD_FALSE);
876         Log(LOG_LEVEL_DEBUG, "Hashes matched ok");
877         return true;
878     }
879     else
880     {
881         strcpy(sendbuffer, CFD_TRUE);
882         Log(LOG_LEVEL_DEBUG, "Hashes didn't match");
883         return false;
884     }
885 }
886 
GetServerLiteral(EvalContext * ctx,ServerConnectionState * conn,char * sendbuffer,char * recvbuffer,int encrypted)887 void GetServerLiteral(EvalContext *ctx, ServerConnectionState *conn, char *sendbuffer, char *recvbuffer, int encrypted)
888 {
889     char handle[CF_BUFSIZE], out[CF_BUFSIZE];
890     int cipherlen;
891 
892     sscanf(recvbuffer, "VAR %255[^\n]", handle);
893 
894     if (ReturnLiteralData(ctx, handle, out))
895     {
896         memset(sendbuffer, 0, CF_BUFSIZE);
897         snprintf(sendbuffer, CF_BUFSIZE - 1, "%s", out);
898     }
899     else
900     {
901         memset(sendbuffer, 0, CF_BUFSIZE);
902         snprintf(sendbuffer, CF_BUFSIZE - 1, "BAD: Not found");
903     }
904 
905     if (encrypted)
906     {
907         cipherlen = EncryptString(out, sizeof(out),
908                                   sendbuffer, strlen(sendbuffer) + 1,
909                                   conn->encryption_type, conn->session_key);
910         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
911     }
912     else
913     {
914         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
915     }
916 }
917 
GetServerQuery(ServerConnectionState * conn,char * recvbuffer,int encrypt)918 bool GetServerQuery(ServerConnectionState *conn, char *recvbuffer, int encrypt)
919 {
920     char query[CF_BUFSIZE];
921 
922     query[0] = '\0';
923     sscanf(recvbuffer, "QUERY %255[^\n]", query);
924 
925     if (strlen(query) == 0)
926     {
927         return false;
928     }
929 
930     return ReturnQueryData(conn, query, encrypt);
931 }
932 
ReplyServerContext(ServerConnectionState * conn,int encrypted,Item * classes)933 void ReplyServerContext(ServerConnectionState *conn, int encrypted, Item *classes)
934 {
935     char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET];
936 
937     size_t ret = ItemList2CSV_bound(classes,
938                                     sendbuffer, sizeof(sendbuffer), ',');
939     if (ret >= sizeof(sendbuffer))
940     {
941         Log(LOG_LEVEL_ERR, "Overflow: classes don't fit in send buffer");
942     }
943 
944     DeleteItemList(classes);
945 
946     if (encrypted)
947     {
948         char out[CF_BUFSIZE];
949         int cipherlen = EncryptString(out, sizeof(out),
950                                       sendbuffer, strlen(sendbuffer) + 1,
951                                       conn->encryption_type, conn->session_key);
952         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
953     }
954     else
955     {
956         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
957     }
958 }
959 
CfOpenDirectory(ServerConnectionState * conn,char * sendbuffer,char * oldDirname)960 int CfOpenDirectory(ServerConnectionState *conn, char *sendbuffer, char *oldDirname)
961 {
962     Dir *dirh;
963     const struct dirent *dirp;
964     int offset;
965     char dirname[CF_BUFSIZE];
966 
967     TranslatePath(dirname, oldDirname);
968 
969     if (!IsAbsoluteFileName(dirname))
970     {
971         strcpy(sendbuffer, "BAD: request to access a non-absolute filename");
972         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
973         return -1;
974     }
975 
976     if ((dirh = DirOpen(dirname)) == NULL)
977     {
978         Log(LOG_LEVEL_INFO, "Couldn't open directory '%s' (DirOpen:%s)",
979             dirname, GetErrorStr());
980         snprintf(sendbuffer, CF_BUFSIZE, "BAD: cfengine, couldn't open dir %s", dirname);
981         SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
982         return -1;
983     }
984 
985 /* Pack names for transmission */
986 
987     offset = 0;
988     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
989     {
990         /* Always leave MAXLINKSIZE bytes for CFD_TERMINATOR. Why??? */
991         if (strlen(dirp->d_name) + 1 + offset >= CF_BUFSIZE - CF_MAXLINKSIZE)
992         {
993             /* Double '\0' indicates end of packet. */
994             sendbuffer[offset] = '\0';
995             SendTransaction(conn->conn_info, sendbuffer, offset + 1, CF_MORE);
996 
997             offset = 0;                                       /* new packet */
998         }
999 
1000         /* TODO fix copying names greater than 256. */
1001         strlcpy(sendbuffer + offset, dirp->d_name, CF_MAXLINKSIZE);
1002         offset += strlen(dirp->d_name) + 1;                  /* +1 for '\0' */
1003     }
1004 
1005     strcpy(sendbuffer + offset, CFD_TERMINATOR);
1006     offset += strlen(CFD_TERMINATOR) + 1;                    /* +1 for '\0' */
1007     /* Double '\0' indicates end of packet. */
1008     sendbuffer[offset] = '\0';
1009     SendTransaction(conn->conn_info, sendbuffer, offset + 1, CF_DONE);
1010 
1011     DirClose(dirh);
1012     return 0;
1013 }
1014 
1015 /**************************************************************/
1016 
CfSecOpenDirectory(ServerConnectionState * conn,char * sendbuffer,char * dirname)1017 int CfSecOpenDirectory(ServerConnectionState *conn, char *sendbuffer, char *dirname)
1018 {
1019     Dir *dirh;
1020     const struct dirent *dirp;
1021     int offset, cipherlen;
1022     char out[CF_BUFSIZE];
1023 
1024     if (!IsAbsoluteFileName(dirname))
1025     {
1026         strcpy(sendbuffer, "BAD: request to access a non-absolute filename");
1027         cipherlen = EncryptString(out, sizeof(out),
1028                                   sendbuffer, strlen(sendbuffer) + 1,
1029                                   conn->encryption_type, conn->session_key);
1030         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
1031         return -1;
1032     }
1033 
1034     if ((dirh = DirOpen(dirname)) == NULL)
1035     {
1036         Log(LOG_LEVEL_VERBOSE, "Couldn't open dir %s", dirname);
1037         snprintf(sendbuffer, CF_BUFSIZE, "BAD: cfengine, couldn't open dir %s", dirname);
1038         cipherlen = EncryptString(out, sizeof(out),
1039                                   sendbuffer, strlen(sendbuffer) + 1,
1040                                   conn->encryption_type, conn->session_key);
1041         SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
1042         return -1;
1043     }
1044 
1045 /* Pack names for transmission */
1046 
1047     memset(sendbuffer, 0, CF_BUFSIZE);
1048 
1049     offset = 0;
1050 
1051     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
1052     {
1053         if (strlen(dirp->d_name) + 1 + offset >= CF_BUFSIZE - CF_MAXLINKSIZE)
1054         {
1055             cipherlen = EncryptString(out, sizeof(out),
1056                                       sendbuffer, offset + 1,
1057                                       conn->encryption_type, conn->session_key);
1058             SendTransaction(conn->conn_info, out, cipherlen, CF_MORE);
1059             offset = 0;
1060             memset(sendbuffer, 0, CF_BUFSIZE);
1061             memset(out, 0, CF_BUFSIZE);
1062         }
1063 
1064         strlcpy(sendbuffer + offset, dirp->d_name, CF_MAXLINKSIZE);
1065         /* + zero byte separator */
1066         offset += strlen(dirp->d_name) + 1;
1067     }
1068 
1069     strcpy(sendbuffer + offset, CFD_TERMINATOR);
1070 
1071     cipherlen =
1072         EncryptString(out, sizeof(out),
1073                       sendbuffer, offset + 2 + strlen(CFD_TERMINATOR),
1074                       conn->encryption_type, conn->session_key);
1075     SendTransaction(conn->conn_info, out, cipherlen, CF_DONE);
1076     DirClose(dirh);
1077     return 0;
1078 }
1079 
1080 
1081 /********************* MISC UTILITY FUNCTIONS *************************/
1082 
1083 
1084 /**
1085  * Search and replace occurrences of #find1, #find2, #find3, with
1086  * #repl1, #repl2, #repl3 respectively.
1087  *
1088  *   "$(connection.ip)" from "191.168.0.1"
1089  *   "$(connection.hostname)" from "blah.cfengine.com",
1090  *   "$(connection.key)" from "SHA=asdfghjkl"
1091  *
1092  * @return the output length of #buf, (size_t) -1 if overflow would occur,
1093  *         or 0 if no replacement happened and #buf was not touched.
1094  *
1095  * @TODO change the function to more generic interface accepting arbitrary
1096  *       find/replace pairs.
1097  */
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)1098 size_t ReplaceSpecialVariables(char *buf, size_t buf_size,
1099                                const char *find1, const char *repl1,
1100                                const char *find2, const char *repl2,
1101                                const char *find3, const char *repl3)
1102 {
1103     size_t ret = 0;
1104 
1105     if ((find1 != NULL) && (find1[0] != '\0') &&
1106         (repl1 != NULL) && (repl1[0] != '\0'))
1107     {
1108         size_t ret2 = StringReplace(buf, buf_size, find1, repl1);
1109         ret = MAX(ret, ret2);           /* size_t is unsigned, thus -1 wins */
1110     }
1111     if ((ret != (size_t) -1) &&
1112         (find2 != NULL) && (find2[0] != '\0') &&
1113         (repl2 != NULL) && (repl2[0] != '\0'))
1114     {
1115         size_t ret2 = StringReplace(buf, buf_size, find2, repl2);
1116         ret = MAX(ret, ret2);
1117     }
1118     if ((ret != (size_t) -1) &&
1119         (find3 != NULL) && (find3[0] != '\0') &&
1120         (repl3 != NULL) && (repl3[0] != '\0'))
1121     {
1122         size_t ret2 = StringReplace(buf, buf_size, find3, repl3);
1123         ret = MAX(ret, ret2);
1124     }
1125 
1126     /* Zero is returned only if all of the above were zero. */
1127     return ret;
1128 }
1129 
1130 
1131 /**
1132  * Remove trailing FILE_SEPARATOR, unless we're referring to root dir: '/' or 'a:\'
1133  */
PathRemoveTrailingSlash(char * s,size_t s_len)1134 bool PathRemoveTrailingSlash(char *s, size_t s_len)
1135 {
1136     char *first_separator = strchr(s, FILE_SEPARATOR);
1137 
1138     if (first_separator != NULL &&
1139          s[s_len-1] == FILE_SEPARATOR &&
1140         &s[s_len-1] != first_separator)
1141     {
1142         s[s_len-1] = '\0';
1143         return true;
1144     }
1145 
1146     return false;
1147 }
1148 
1149 /**
1150  * Append a trailing FILE_SEPARATOR if it's not there.
1151  */
PathAppendTrailingSlash(char * s,size_t s_len)1152 bool PathAppendTrailingSlash(char *s, size_t s_len)
1153 {
1154     if (s_len > 0 && s[s_len-1] != FILE_SEPARATOR)
1155     {
1156         s[s_len] = FILE_SEPARATOR;
1157         s[s_len+1] = '\0';
1158         return true;
1159     }
1160 
1161     return false;
1162 }
1163 
1164 /* We use this instead of IsAbsoluteFileName() which also checks for
1165  * quotes. There is no meaning in receiving quoted strings over the
1166  * network. */
PathIsAbsolute(const char * s)1167 static bool PathIsAbsolute(const char *s)
1168 {
1169     bool result = false;
1170 
1171 #if defined(__MINGW32__)
1172     if (isalpha(s[0]) && (s[1] == ':') && (s[2] == FILE_SEPARATOR))
1173     {
1174         result = true;                                          /* A:\ */
1175     }
1176     else                                                        /* \\ */
1177     {
1178         result = (s[0] == FILE_SEPARATOR && s[1] == FILE_SEPARATOR);
1179     }
1180 #else
1181     if (s[0] == FILE_SEPARATOR)                                 /* / */
1182     {
1183         result = true;
1184     }
1185 #endif
1186 
1187     return result;
1188 }
1189 
1190 /**
1191  * If #path is relative, expand the first part accorting to #shortcuts, doing
1192  * any replacements of special variables "$(connection.*)" on the way, with
1193  * the provided #ipaddr, #hostname, #key.
1194  *
1195  * @return the length of the new string or 0 if no replace took place. -1 in
1196  * case of overflow.
1197  */
ShortcutsExpand(char * path,size_t path_size,const StringMap * shortcuts,const char * ipaddr,const char * hostname,const char * key)1198 size_t ShortcutsExpand(char *path, size_t path_size,
1199                        const StringMap *shortcuts,
1200                        const char *ipaddr, const char *hostname,
1201                        const char *key)
1202 {
1203     char dst[path_size];
1204     size_t path_len = strlen(path);
1205 
1206     if (path_len == 0)
1207     {
1208         UnexpectedError("ShortcutsExpand: 0 length string!");
1209         return (size_t) -1;
1210     }
1211 
1212     if (!PathIsAbsolute(path))
1213     {
1214         char *separ = strchr(path, FILE_SEPARATOR);
1215         size_t first_part_len;
1216         if (separ != NULL)
1217         {
1218             first_part_len = separ - path;
1219             assert(first_part_len < path_len);
1220         }
1221         else
1222         {
1223             first_part_len = path_len;
1224         }
1225         size_t second_part_len = path_len - first_part_len;
1226 
1227         /* '\0'-terminate first_part, do StringMapGet(), undo '\0'-term */
1228         char separ_char = path[first_part_len];
1229         path[first_part_len] = '\0';
1230         char *replacement = StringMapGet(shortcuts, path);
1231         path[first_part_len] = separ_char;
1232 
1233         /* Either the first_part ends with separator, or its all the string */
1234         assert(separ_char == FILE_SEPARATOR ||
1235                separ_char == '\0');
1236 
1237         if (replacement != NULL)                 /* we found a shortcut */
1238         {
1239             size_t replacement_len = strlen(replacement);
1240             if (replacement_len + 1 > path_size)
1241             {
1242                 goto err_too_long;
1243             }
1244 
1245             /* Replacement path for shortcut was found, but it may contain
1246              * special variables such as $(connection.ip), that we also need
1247              * to expand. */
1248             /* TODO if StrAnyStr(replacement, "$(connection.ip)", "$(connection.hostname)", "$(connection.key)") */
1249             char replacement_expanded[path_size];
1250             memcpy(replacement_expanded, replacement, replacement_len + 1);
1251 
1252             size_t ret =
1253                 ReplaceSpecialVariables(replacement_expanded, sizeof(replacement_expanded),
1254                                         "$(connection.ip)", ipaddr,
1255                                         "$(connection.hostname)", hostname,
1256                                         "$(connection.key)", key);
1257 
1258             size_t replacement_expanded_len;
1259             /* (ret == -1) is checked later. */
1260             if (ret == 0)                        /* No expansion took place */
1261             {
1262                 replacement_expanded_len = replacement_len;
1263             }
1264             else
1265             {
1266                 replacement_expanded_len = ret;
1267             }
1268 
1269             size_t dst_len = replacement_expanded_len + second_part_len;
1270             if (ret == (size_t) -1 || dst_len + 1 > path_size)
1271             {
1272                 goto err_too_long;
1273             }
1274 
1275             /* Assemble final result. */
1276             memcpy(dst, replacement_expanded, replacement_expanded_len);
1277             /* Second part may be empty, then this only copies '\0'. */
1278             memcpy(&dst[replacement_expanded_len], &path[first_part_len],
1279                    second_part_len + 1);
1280 
1281             Log(LOG_LEVEL_DEBUG,
1282                 "ShortcutsExpand: Path '%s' became: %s",
1283                 path, dst);
1284 
1285             /* Copy back to path. */
1286             memcpy(path, dst, dst_len + 1);
1287             return dst_len;
1288         }
1289     }
1290 
1291     /* No expansion took place, either because path was absolute, or because
1292      * no shortcut was found. */
1293     return 0;
1294 
1295   err_too_long:
1296     Log(LOG_LEVEL_INFO, "Path too long after shortcut expansion!");
1297     return (size_t) -1;
1298 }
1299 
1300 /**
1301  * Canonicalize a path, ensure it is absolute, and resolve all symlinks.
1302  * In detail:
1303  *
1304  * 1. MinGW: Translate to windows-compatible: slashes to FILE_SEPARATOR
1305  *           and uppercase to lowercase.
1306  * 2. Ensure the path is absolute.
1307  * 3. Resolve symlinks, resolve '.' and '..' and remove double '/'
1308  *    WARNING this will currently fail if file does not exist,
1309  *    returning -1 and setting errno==ENOENT!
1310  *
1311  * @note trailing slash is left as is if it's there.
1312  * @note #reqpath is written in place (if success was returned). It is always
1313  *       an absolute path.
1314  * @note #reqpath is invalid to be of zero length.
1315  * @note #reqpath_size must be at least PATH_MAX.
1316  *
1317  * @return the length of #reqpath after preprocessing. In case of error
1318  *         return (size_t) -1.
1319  */
PreprocessRequestPath(char * reqpath,size_t reqpath_size)1320 size_t PreprocessRequestPath(char *reqpath, size_t reqpath_size)
1321 {
1322     errno = 0;             /* on return, errno might be set from realpath() */
1323     char dst[reqpath_size];
1324     size_t reqpath_len = strlen(reqpath);
1325 
1326     if (reqpath_len == 0)
1327     {
1328         UnexpectedError("PreprocessRequestPath: 0 length string!");
1329         return (size_t) -1;
1330     }
1331 
1332     /* Translate all slashes to backslashes on Windows so that all the rest
1333      * of work is done using FILE_SEPARATOR. THIS HAS TO BE FIRST. */
1334     #if defined(__MINGW32__)
1335     {
1336         char *p = reqpath;
1337         while ((p = strchr(p, '/')) != NULL)
1338         {
1339             *p = FILE_SEPARATOR;
1340         }
1341         /* Also convert everything to lowercase. */
1342         ToLowerStrInplace(reqpath);
1343     }
1344     #endif
1345 
1346     if (!PathIsAbsolute(reqpath))
1347     {
1348         Log(LOG_LEVEL_INFO, "Relative paths are not allowed: %s", reqpath);
1349         return (size_t) -1;
1350     }
1351 
1352     /* TODO replace realpath with Solaris' resolvepath(), in all
1353      * platforms. That one does not check for existence, just resolves
1354      * symlinks and canonicalises. Ideally we would want the following:
1355      *
1356      * PathResolve(dst, src, dst_size, basedir);
1357      *
1358      * - It prepends basedir if path relative (could be the shortcut)
1359      * - It compresses double '/', '..', '.'
1360      * - It follows each component of the path replacing symlinks
1361      * - errno = ENOENT if path component does not exist, but keeps
1362      *   compressing path anyway.
1363      * - Leaves trailing slash as it was passed to it.
1364      *   OR appends it depending on last component ISDIR.
1365      */
1366 
1367     assert(sizeof(dst) >= PATH_MAX);               /* needed for realpath() */
1368     char *p = realpath(reqpath, dst);
1369     if (p == NULL)
1370     {
1371         /* TODO If path does not exist try to canonicalise only directory. INSECURE?*/
1372         /* if (errno == ENOENT) */
1373         /* { */
1374 
1375         /* } */
1376         struct stat statbuf;
1377         if ((lstat(reqpath, &statbuf) == 0) && S_ISLNK(statbuf.st_mode))
1378         {
1379             Log(LOG_LEVEL_VERBOSE, "Requested file is a dead symbolic link (filename: %s)", reqpath);
1380             strlcpy(dst, reqpath, CF_BUFSIZE);
1381         }
1382         else
1383         {
1384             Log(LOG_LEVEL_INFO,
1385                 "Failed to canonicalise filename '%s' (realpath: %s)",
1386                 reqpath, GetErrorStr());
1387             return (size_t) -1;
1388         }
1389     }
1390 
1391     size_t dst_len = strlen(dst);
1392 
1393     /* Some realpath()s remove trailing '/' even for dirs! Put it back if
1394      * original request had it. */
1395     if (reqpath[reqpath_len - 1] == FILE_SEPARATOR &&
1396         dst[dst_len - 1]         != FILE_SEPARATOR)
1397     {
1398         if (dst_len + 2 > sizeof(dst))
1399         {
1400             Log(LOG_LEVEL_INFO, "Error, path too long: %s", reqpath);
1401             return (size_t) -1;
1402         }
1403 
1404         PathAppendTrailingSlash(dst, dst_len);
1405         dst_len++;
1406     }
1407 
1408     memcpy(reqpath, dst, dst_len + 1);
1409     reqpath_len = dst_len;
1410 
1411     return reqpath_len;
1412 }
1413 
1414 
1415 /**
1416  * Set conn->uid (and conn->sid on Windows).
1417  */
SetConnIdentity(ServerConnectionState * conn,const char * username)1418 void SetConnIdentity(ServerConnectionState *conn, const char *username)
1419 {
1420     size_t username_len = strlen(username);
1421 
1422     conn->uid = CF_UNKNOWN_OWNER;
1423     conn->username[0] = '\0';
1424 
1425     if (username_len < sizeof(conn->username))
1426     {
1427         memcpy(conn->username, username, username_len + 1);
1428     }
1429 
1430     bool is_root = strcmp(conn->username, "root") == 0;
1431     if (is_root)
1432     {
1433         /* If the remote user identifies himself as root, even on Windows
1434          * cf-serverd must grant access to all files. uid==0 is checked later
1435          * in TranferRights() for that. */
1436         conn->uid = 0;
1437     }
1438 
1439 #ifdef __MINGW32__            /* NT uses security identifier instead of uid */
1440 
1441     if (!NovaWin_UserNameToSid(conn->username, (SID *) conn->sid,
1442                                CF_MAXSIDSIZE, !is_root))
1443     {
1444         memset(conn->sid, 0, CF_MAXSIDSIZE);  /* is invalid sid - discarded */
1445     }
1446 
1447 #else                                                 /* UNIX - common path */
1448 
1449     if (conn->uid == CF_UNKNOWN_OWNER)      /* skip looking up UID for root */
1450     {
1451         static pthread_mutex_t pwnam_mtx = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
1452         struct passwd *pw = NULL;
1453 
1454         ThreadLock(&pwnam_mtx);
1455         /* TODO Redmine#7643: looking up the UID is expensive and should
1456          * not be needed, since today's agent machine VS hub most probably
1457          * do not share the accounts. */
1458         pw = getpwnam(conn->username);
1459         if (pw != NULL)
1460         {
1461             conn->uid = pw->pw_uid;
1462         }
1463         ThreadUnlock(&pwnam_mtx);
1464     }
1465 
1466 #endif
1467 }
1468 
1469 
CharsetAcceptable(const char * s,size_t s_len)1470 static bool CharsetAcceptable(const char *s, size_t s_len)
1471 {
1472     const char *ACCEPT =
1473         "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:";
1474     size_t acceptable_chars = strspn(s, ACCEPT);
1475     if (s_len == 0)
1476     {
1477         s_len = strlen(s);
1478     }
1479 
1480     if (acceptable_chars < s_len)
1481     {
1482         Log(LOG_LEVEL_INFO,
1483             "llegal character in column %zu of: %s",
1484             acceptable_chars, s);
1485         return false;
1486     }
1487 
1488     return true;
1489 }
1490 
1491 
1492 /**
1493  * @param #args_start is a comma separated list of words, which may be
1494  *                    prefixed with spaces and suffixed with spaces and other
1495  *                    words. Example: " asd,fgh,jk blah". In this example the
1496  *                    list has 3 words, and "blah" is not one of them.
1497  *
1498  * Both #args_start and #args_len are in-out parameters.
1499  * At the end of execution #args_start returns the real start of the list, and
1500  * #args_len the real length.
1501  */
AuthorizeDelimitedArgs(const ServerConnectionState * conn,struct acl * acl,char ** args_start,size_t * args_len)1502 static bool AuthorizeDelimitedArgs(const ServerConnectionState *conn,
1503                                    struct acl *acl,
1504                                    char **args_start, size_t *args_len)
1505 {
1506     char *s;
1507     size_t s_len, skip;
1508 
1509     assert(args_start != NULL);
1510     assert(args_len != NULL);
1511 
1512     /* Give the name s and s_len purely for ease of use. */
1513     s_len = *args_len;
1514     s     = *args_start;
1515     /* Skip spaces in the beginning of argument list. */
1516     skip  = strspn(s, " \t");
1517     s    += skip;
1518 
1519     if (s_len == 0)                        /* if end was not given, find it */
1520     {
1521         s_len = strcspn(s, " \t");
1522     }
1523     else                                                /* if end was given */
1524     {
1525         s_len = (skip <= s_len) ? (s_len - skip) : 0;
1526     }
1527 
1528     /* Admit, unless any token fails to be authorised. */
1529     bool admit = true;
1530     if (s_len > 0)
1531     {
1532         const char tmp_c = s[s_len];
1533         s[s_len] = '\0';
1534 
1535         /* Iterate over comma-separated list. */
1536 
1537         char *token = &s[0];
1538         while (token < &s[s_len] && admit)
1539         {
1540             char *token_end = strchrnul(token, ',');
1541 
1542             const char tmp_sep = *token_end;
1543             *token_end = '\0';
1544 
1545             if (!CharsetAcceptable(token, 0) ||
1546                 !acl_CheckRegex(acl, token,
1547                                 conn->ipaddr, conn->revdns,
1548                                 KeyPrintableHash(conn->conn_info->remote_key),
1549                                 conn->username))
1550             {
1551                 Log(LOG_LEVEL_INFO, "Access denied to: %s", token);
1552                 admit = false;                              /* EARLY RETURN */
1553             }
1554 
1555             *token_end = tmp_sep;
1556             token      = token_end + 1;
1557         }
1558 
1559         s[s_len] = tmp_c;
1560     }
1561 
1562     *args_start = s;
1563     *args_len   = s_len;
1564     return admit;
1565 }
1566 
1567 
1568 /**
1569  * @return #true if the connection should remain open for next requests, or
1570  *         #false if the server should actively close it - for example when
1571  *         protocol errors have occurred.
1572  */
DoExec2(const EvalContext * ctx,ServerConnectionState * conn,char * exec_args,char * sendbuf,size_t sendbuf_size)1573 bool DoExec2(const EvalContext *ctx,
1574              ServerConnectionState *conn,
1575              char *exec_args,
1576              char *sendbuf, size_t sendbuf_size)
1577 {
1578     /* STEP 0: Verify cfruncommand was successfully configured. */
1579     if (NULL_OR_EMPTY(CFRUNCOMMAND))
1580     {
1581         Log(LOG_LEVEL_INFO, "EXEC denied due to empty cfruncommand");
1582         RefuseAccess(conn, "EXEC");
1583         return false;
1584     }
1585 
1586     /* STEP 1: Resolve and check permissions of CFRUNCOMMAND's arg0. IT is
1587      *         done now and not at configuration time, as the file stat may
1588      *         have changed since then. */
1589     {
1590         char arg0[PATH_MAX];
1591         if (CommandArg0_bound(arg0, CFRUNCOMMAND, sizeof(arg0)) == (size_t) -1 ||
1592             PreprocessRequestPath(arg0, sizeof(arg0))           == (size_t) -1)
1593         {
1594             Log(LOG_LEVEL_INFO, "EXEC failed, invalid cfruncommand arg0");
1595             RefuseAccess(conn, "EXEC");
1596             return false;
1597         }
1598 
1599         /* Check body server access_rules, whether arg0 is authorized. */
1600 
1601         /* TODO EXEC should not just use paths_acl access control, but
1602          * specific "exec_path" ACL. Then different command execution could be
1603          * allowed per host, and the host could even set argv[0] in his EXEC
1604          * request, rather than only the arguments. */
1605 
1606         if (acl_CheckPath(paths_acl, arg0,
1607                           conn->ipaddr, conn->revdns,
1608                           KeyPrintableHash(conn->conn_info->remote_key))
1609             == false)
1610         {
1611             Log(LOG_LEVEL_INFO, "EXEC denied due to ACL for file: %s", arg0);
1612             RefuseAccess(conn, "EXEC");
1613             return false;
1614         }
1615     }
1616 
1617     /* STEP 2: Check body server control "allowusers" */
1618     if (!AllowedUser(conn->username))
1619     {
1620         Log(LOG_LEVEL_INFO, "EXEC denied due to not allowed user: %s",
1621             conn->username);
1622         RefuseAccess(conn, "EXEC");
1623         return false;
1624     }
1625 
1626     /* STEP 3: This matches cf-runagent -s class1,class2 against classes
1627      *         set during cf-serverd's policy evaluation. */
1628 
1629     if (!MatchClasses(ctx, conn))
1630     {
1631         snprintf(sendbuf, sendbuf_size,
1632                  "EXEC denied due to failed class match (check cf-serverd verbose output)");
1633         Log(LOG_LEVEL_INFO, "%s", sendbuf);
1634         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1635         return true;
1636     }
1637 
1638 
1639     /* STEP 4: Parse and authorise the EXEC arguments, which will be used as
1640      *         arguments to CFRUNCOMMAND. Currently we only accept
1641      *         [ -D classlist ] and [ -b bundlesequence ] arguments. */
1642 
1643     char   cmdbuf[CF_BUFSIZE] = "";
1644     size_t cmdbuf_len         = 0;
1645 
1646     assert(sizeof(CFRUNCOMMAND) <= sizeof(cmdbuf));
1647 
1648     StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len, CFRUNCOMMAND, 0);
1649 
1650     exec_args += strspn(exec_args,  " \t");                  /* skip spaces */
1651     while (exec_args[0] != '\0')
1652     {
1653         if (strncmp(exec_args, "-D", 2) == 0)
1654         {
1655             exec_args += 2;
1656 
1657             char *classlist = exec_args;
1658             size_t classlist_len = 0;
1659             bool allow = AuthorizeDelimitedArgs(conn, roles_acl,
1660                                                 &classlist, &classlist_len);
1661             if (!allow)
1662             {
1663                 snprintf(sendbuf, sendbuf_size,
1664                          "EXEC denied role activation (check cf-serverd verbose output)");
1665                 Log(LOG_LEVEL_INFO, "%s", sendbuf);
1666                 SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1667                 return true;
1668             }
1669 
1670             if (classlist_len > 0)
1671             {
1672                 /* Append "-D classlist" to cfruncommand. */
1673                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1674                        " -D ", 0);
1675                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1676                        classlist, classlist_len);
1677             }
1678 
1679             exec_args = classlist + classlist_len;
1680         }
1681         else if (strncmp(exec_args, "-b", 2) == 0)
1682         {
1683             exec_args += 2;
1684 
1685             char *bundlesequence = exec_args;
1686             size_t bundlesequence_len = 0;
1687 
1688             bool allow = AuthorizeDelimitedArgs(conn, bundles_acl,
1689                                                 &bundlesequence,
1690                                                 &bundlesequence_len);
1691             if (!allow)
1692             {
1693                 snprintf(sendbuf, sendbuf_size,
1694                          "EXEC denied bundle activation (check cf-serverd verbose output)");
1695                 Log(LOG_LEVEL_INFO, "%s", sendbuf);
1696                 SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1697                 return true;
1698             }
1699 
1700             if (bundlesequence_len > 0)
1701             {
1702                 /* Append "--bundlesequence bundlesequence" to cfruncommand. */
1703                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1704                        " --bundlesequence ", 0);
1705                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
1706                        bundlesequence, bundlesequence_len);
1707             }
1708 
1709             exec_args = bundlesequence + bundlesequence_len;
1710         }
1711         else                                        /* disallowed parameter */
1712         {
1713             snprintf(sendbuf, sendbuf_size,
1714                      "EXEC denied: invalid arguments: %s",
1715                      exec_args);
1716             Log(LOG_LEVEL_INFO, "%s", sendbuf);
1717             SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1718             return true;
1719         }
1720 
1721         exec_args += strspn(exec_args,  " \t");              /* skip spaces */
1722     }
1723 
1724     if (cmdbuf_len >= sizeof(cmdbuf))
1725     {
1726         snprintf(sendbuf, sendbuf_size,
1727                  "EXEC denied: too long (%zu B) command: %s",
1728                  cmdbuf_len, cmdbuf);
1729         Log(LOG_LEVEL_INFO, "%s", sendbuf);
1730         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1731         return false;
1732     }
1733 
1734     /* STEP 5: RUN CFRUNCOMMAND. */
1735 
1736     snprintf(sendbuf, sendbuf_size,
1737              "cf-serverd executing cfruncommand: %s\n",
1738              cmdbuf);
1739     SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1740     Log(LOG_LEVEL_INFO, "%s", sendbuf);
1741 
1742     FILE *pp = cf_popen(cmdbuf, "r", true);
1743     if (pp == NULL)
1744     {
1745         snprintf(sendbuf, sendbuf_size,
1746                  "Unable to run '%s' (pipe: %s)",
1747                  cmdbuf, GetErrorStr());
1748         Log(LOG_LEVEL_INFO, "%s", sendbuf);
1749         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
1750         return false;
1751     }
1752 
1753     size_t line_size = CF_BUFSIZE;
1754     char *line = xmalloc(line_size);
1755     while (true)
1756     {
1757         ssize_t res = CfReadLine(&line, &line_size, pp);
1758         if (res == -1)
1759         {
1760             if (!feof(pp))
1761             {
1762                 /* Error reading, discard all unconsumed input before
1763                  * aborting - linux-specific! */
1764                 fflush(pp);
1765             }
1766             break;
1767         }
1768 
1769         /* NOTICE: we can't SendTransaction() overlong strings, and we need to
1770          * prepend and append to the string. */
1771         size_t line_len = strlen(line);
1772         if (line_len >= sendbuf_size - 5)
1773         {
1774             line[sendbuf_size - 5] = '\0';
1775         }
1776 
1777         /* Prefixing output with "> " and postfixing with '\n' is new
1778          * behaviour as of 3.7.0. Prefixing happens to avoid zero-length
1779          * transaction packet. */
1780         /* Old cf-runagent versions do not append a newline, so we must do
1781          * it here. New ones do though, so TODO deprecate. */
1782         xsnprintf(sendbuf, sendbuf_size, "> %s\n", line);
1783         if (SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE) == -1)
1784         {
1785             Log(LOG_LEVEL_INFO,
1786                 "Sending failed, aborting EXEC (send: %s)",
1787                 GetErrorStr());
1788             break;
1789         }
1790     }
1791     free(line);
1792     cf_pclose(pp);
1793 
1794     return true;
1795 }
1796