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