"Fossies" - the Fresh Open Source Software Archive

Member "cfengine-3.15.4/cf-serverd/server_common.c" (7 Jun 2021, 58347 Bytes) of package /linux/misc/cfengine-3.15.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "server_common.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.15.3_vs_3.15.4.

    1 /*
    2   Copyright 2019 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. */
   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 
   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 
   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 
   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 
  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. */
  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()). */
  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 
  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 
  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 
  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 
  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 
  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 
  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_BUFSIZE, "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_BUFSIZE, "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_BUFSIZE);
  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_BUFSIZE,
  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_BUFSIZE);
  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 
  861 bool CompareLocalHash(const char *filename, const char digest[EVP_MAX_MD_SIZE + 1],
  862                       char sendbuffer[CFD_FALSE_SIZE])
  863 {
  864     nt_static_assert(CFD_FALSE_SIZE == (strlen(CFD_FALSE) + 1));
  865     nt_static_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 
  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 
  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 
  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 
  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 
 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  */
 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  */
 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  */
 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. */
 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  */
 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  */
 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  */
 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 
 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  */
 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  */
 1573 bool DoExec2(const EvalContext *ctx,
 1574              ServerConnectionState *conn,
 1575              char *exec_args,
 1576              char *sendbuf, size_t sendbuf_size)
 1577 {
 1578     assert(conn != NULL);
 1579 
 1580     /* STEP 0: Verify cfruncommand was successfully configured. */
 1581     if (NULL_OR_EMPTY(CFRUNCOMMAND))
 1582     {
 1583         Log(LOG_LEVEL_INFO, "EXEC denied due to empty cfruncommand");
 1584         RefuseAccess(conn, "EXEC");
 1585         return false;
 1586     }
 1587 
 1588     /* STEP 1: Resolve and check permissions of CFRUNCOMMAND's arg0. IT is
 1589      *         done now and not at configuration time, as the file stat may
 1590      *         have changed since then. */
 1591     {
 1592         char arg0[PATH_MAX];
 1593         if (CommandArg0_bound(arg0, CFRUNCOMMAND, sizeof(arg0)) == (size_t) -1 ||
 1594             PreprocessRequestPath(arg0, sizeof(arg0))           == (size_t) -1)
 1595         {
 1596             Log(LOG_LEVEL_INFO, "EXEC failed, invalid cfruncommand arg0");
 1597             RefuseAccess(conn, "EXEC");
 1598             return false;
 1599         }
 1600 
 1601         /* Check body server access_rules, whether arg0 is authorized. */
 1602 
 1603         /* TODO EXEC should not just use paths_acl access control, but
 1604          * specific "exec_path" ACL. Then different command execution could be
 1605          * allowed per host, and the host could even set argv[0] in his EXEC
 1606          * request, rather than only the arguments. */
 1607 
 1608         if (acl_CheckPath(paths_acl, arg0,
 1609                           conn->ipaddr, conn->revdns,
 1610                           KeyPrintableHash(conn->conn_info->remote_key))
 1611             == false)
 1612         {
 1613             Log(LOG_LEVEL_INFO, "EXEC denied due to ACL for file: %s", arg0);
 1614             RefuseAccess(conn, "EXEC");
 1615             return false;
 1616         }
 1617     }
 1618 
 1619     /* STEP 2: Check body server control "allowusers" */
 1620     if (!AllowedUser(conn->username))
 1621     {
 1622         Log(LOG_LEVEL_INFO, "EXEC denied due to not allowed user: %s",
 1623             conn->username);
 1624         RefuseAccess(conn, "EXEC");
 1625         return false;
 1626     }
 1627 
 1628     /* STEP 3: This matches cf-runagent -s class1,class2 against classes
 1629      *         set during cf-serverd's policy evaluation. */
 1630 
 1631     if (!MatchClasses(ctx, conn))
 1632     {
 1633         snprintf(sendbuf, sendbuf_size,
 1634                  "EXEC denied due to failed class match (check cf-serverd verbose output)");
 1635         Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1636         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1637         return true;
 1638     }
 1639 
 1640 
 1641     /* STEP 4: Parse and authorise the EXEC arguments, which will be used as
 1642      *         arguments to CFRUNCOMMAND. Currently we only accept
 1643      *         [ -D classlist ] and [ -b bundlesequence ] arguments. */
 1644 
 1645     char   cmdbuf[CF_BUFSIZE] = "";
 1646     size_t cmdbuf_len         = 0;
 1647 
 1648     assert(sizeof(CFRUNCOMMAND) <= sizeof(cmdbuf));
 1649 
 1650     StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len, CFRUNCOMMAND, 0);
 1651 
 1652     exec_args += strspn(exec_args,  " \t");                  /* skip spaces */
 1653     while (exec_args[0] != '\0')
 1654     {
 1655         if (strncmp(exec_args, "-D", 2) == 0)
 1656         {
 1657             exec_args += 2;
 1658 
 1659             char *classlist = exec_args;
 1660             size_t classlist_len = 0;
 1661             bool allow = AuthorizeDelimitedArgs(conn, roles_acl,
 1662                                                 &classlist, &classlist_len);
 1663             if (!allow)
 1664             {
 1665                 snprintf(sendbuf, sendbuf_size,
 1666                          "EXEC denied role activation (check cf-serverd verbose output)");
 1667                 Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1668                 SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1669                 return true;
 1670             }
 1671 
 1672             if (classlist_len > 0)
 1673             {
 1674                 /* Append "-D classlist" to cfruncommand. */
 1675                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
 1676                        " -D ", 0);
 1677                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
 1678                        classlist, classlist_len);
 1679             }
 1680 
 1681             exec_args = classlist + classlist_len;
 1682         }
 1683         else if (strncmp(exec_args, "-b", 2) == 0)
 1684         {
 1685             exec_args += 2;
 1686 
 1687             char *bundlesequence = exec_args;
 1688             size_t bundlesequence_len = 0;
 1689 
 1690             bool allow = AuthorizeDelimitedArgs(conn, bundles_acl,
 1691                                                 &bundlesequence,
 1692                                                 &bundlesequence_len);
 1693             if (!allow)
 1694             {
 1695                 snprintf(sendbuf, sendbuf_size,
 1696                          "EXEC denied bundle activation (check cf-serverd verbose output)");
 1697                 Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1698                 SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1699                 return true;
 1700             }
 1701 
 1702             if (bundlesequence_len > 0)
 1703             {
 1704                 /* Append "--bundlesequence bundlesequence" to cfruncommand. */
 1705                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
 1706                        " --bundlesequence ", 0);
 1707                 StrCat(cmdbuf, sizeof(cmdbuf), &cmdbuf_len,
 1708                        bundlesequence, bundlesequence_len);
 1709             }
 1710 
 1711             exec_args = bundlesequence + bundlesequence_len;
 1712         }
 1713         else                                        /* disallowed parameter */
 1714         {
 1715             snprintf(sendbuf, sendbuf_size,
 1716                      "EXEC denied: invalid arguments: %s",
 1717                      exec_args);
 1718             Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1719             SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1720             return true;
 1721         }
 1722 
 1723         exec_args += strspn(exec_args,  " \t");              /* skip spaces */
 1724     }
 1725 
 1726     if (cmdbuf_len >= sizeof(cmdbuf))
 1727     {
 1728         snprintf(sendbuf, sendbuf_size,
 1729                  "EXEC denied: too long (%zu B) command: %s",
 1730                  cmdbuf_len, cmdbuf);
 1731         Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1732         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1733         return false;
 1734     }
 1735 
 1736     /* STEP 5: RUN CFRUNCOMMAND. */
 1737 
 1738     snprintf(sendbuf, sendbuf_size,
 1739              "cf-serverd executing cfruncommand: %s\n",
 1740              cmdbuf);
 1741     SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1742     Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1743 
 1744     FILE *pp = cf_popen(cmdbuf, "r", true);
 1745     if (pp == NULL)
 1746     {
 1747         snprintf(sendbuf, sendbuf_size,
 1748                  "Unable to run '%s' (pipe: %s)",
 1749                  cmdbuf, GetErrorStr());
 1750         Log(LOG_LEVEL_INFO, "%s", sendbuf);
 1751         SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE);
 1752         return false;
 1753     }
 1754 
 1755     size_t line_size = CF_BUFSIZE;
 1756     char *line = xmalloc(line_size);
 1757     while (true)
 1758     {
 1759         ssize_t res = CfReadLine(&line, &line_size, pp);
 1760         if (res == -1)
 1761         {
 1762             if (!feof(pp))
 1763             {
 1764                 /* Error reading, discard all unconsumed input before
 1765                  * aborting - linux-specific! */
 1766                 fflush(pp);
 1767             }
 1768             break;
 1769         }
 1770 
 1771         /* NOTICE: we can't SendTransaction() overlong strings, and we need to
 1772          * prepend and append to the string. */
 1773         size_t line_len = strlen(line);
 1774         if (line_len >= sendbuf_size - 5)
 1775         {
 1776             line[sendbuf_size - 5] = '\0';
 1777         }
 1778 
 1779         /* Prefixing output with "> " and postfixing with '\n' is new
 1780          * behaviour as of 3.7.0. Prefixing happens to avoid zero-length
 1781          * transaction packet. */
 1782         /* Old cf-runagent versions do not append a newline, so we must do
 1783          * it here. New ones do though, so TODO deprecate. */
 1784         xsnprintf(sendbuf, sendbuf_size, "> %s\n", line);
 1785         if (SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE) == -1)
 1786         {
 1787             Log(LOG_LEVEL_INFO,
 1788                 "Sending failed, aborting EXEC (send: %s)",
 1789                 GetErrorStr());
 1790             break;
 1791         }
 1792     }
 1793     free(line);
 1794     int exit_code = cf_pclose(pp);
 1795     if (exit_code >= 0)
 1796     {
 1797         xsnprintf(sendbuf, sendbuf_size, "(exit code: %d)\n", exit_code);
 1798         if (SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE) == -1)
 1799         {
 1800             Log(LOG_LEVEL_INFO, "Failed to send exit code from EXEC agent run");
 1801         }
 1802     }
 1803 
 1804     return true;
 1805 }