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