/* Wait up to a minute for an in-coming connection. * * @param sd The listening socket or -1. * @retval > 0 In-coming connection. * @retval 0 No in-coming connection. * @retval -1 Error (other than interrupt). * @retval < -1 Interrupted while waiting. */ static int WaitForIncoming(int sd) { Log(LOG_LEVEL_DEBUG, "Waiting at incoming select..."); struct timeval timeout = { .tv_sec = 60 }; int signal_pipe = GetSignalPipe(); fd_set rset; FD_ZERO(&rset); FD_SET(signal_pipe, &rset); /* sd might be -1 if "listen" attribute in body server control is set * to off (enterprise feature for call-collected clients). */ if (sd != -1) { FD_SET(sd, &rset); } int result = select(MAX(sd, signal_pipe) + 1, &rset, NULL, NULL, &timeout); if (result == -1) { return (errno == EINTR) ? -2 : -1; } assert(result >= 0); /* Empty the signal pipe, it is there to only detect missed * signals in-between checking IsPendingTermination() and calling * select(). */ unsigned char buf; while (recv(signal_pipe, &buf, 1, 0) > 0) { /* skip */ } /* We have an incoming connection if select() marked sd as ready: */ if (sd != -1 && result > 0 && FD_ISSET(sd, &rset)) { return 1; } return 0; } /* Check for new policy just before spawning a thread. * * Server reconfiguration can only happen when no threads are active, * so this is a good time to do it; but we do still have to check for * running threads. */ static void PolicyUpdateIfSafe(EvalContext *ctx, Policy **policy, GenericAgentConfig *config) { if (ThreadLock(cft_server_children)) { int prior = COLLECT_INTERVAL; if (ACTIVE_THREADS == 0) { CheckFileChanges(ctx, policy, config); } ThreadUnlock(cft_server_children); /* Check for change in call-collect interval: */ if (prior != COLLECT_INTERVAL) { /* Start, stop or change schedule, as appropriate. */ CollectCallStart(COLLECT_INTERVAL); } } } /* Try to accept a connection; handle if we get one. */ static void AcceptAndHandle(EvalContext *ctx, int sd) { /* TODO embed ConnectionInfo into ServerConnectionState. */ ConnectionInfo *info = ConnectionInfoNew(); /* Uses xcalloc() */ info->ss_len = sizeof(info->ss); info->sd = accept(sd, (struct sockaddr *) &info->ss, &info->ss_len); if (info->sd == -1) { Log(LOG_LEVEL_INFO, "Error accepting connection (%s)", GetErrorStr()); ConnectionInfoDestroy(&info); return; } Log(LOG_LEVEL_DEBUG, "Socket descriptor returned from accept(): %d", info->sd); /* Just convert IP address to string, no DNS lookup. */ char ipaddr[CF_MAX_IP_LEN] = ""; getnameinfo((const struct sockaddr *) &info->ss, info->ss_len, ipaddr, sizeof(ipaddr), NULL, 0, NI_NUMERICHOST); /* IPv4 mapped addresses (e.g. "::ffff:192.168.1.2") are * hereby represented with their IPv4 counterpart. */ ServerEntryPoint(ctx, MapAddress(ipaddr), info); }
/** * @retval >0 Number of threads still working * @retval 0 All threads are done * @retval -1 Server didn't run */ int StartServer(EvalContext *ctx, Policy **policy, GenericAgentConfig *config) { InitSignals(); ServerTLSInitialize(); int sd = SetServerListenState(ctx, QUEUESIZE, SERVER_LISTEN, &InitServer); /* Necessary for our use of select() to work in WaitForIncoming(): */ assert(sd < sizeof(fd_set) * CHAR_BIT && GetSignalPipe() < sizeof(fd_set) * CHAR_BIT); Policy *server_cfengine_policy = PolicyNew(); CfLock thislock = AcquireServerLock(ctx, config, server_cfengine_policy); if (thislock.lock == NULL) { PolicyDestroy(server_cfengine_policy); if (sd >= 0) { cf_closesocket(sd); } return -1; } PrepareServer(sd); CollectCallStart(COLLECT_INTERVAL); while (!IsPendingTermination()) { CollectCallIfDue(ctx); int selected = WaitForIncoming(sd); Log(LOG_LEVEL_DEBUG, "select(): %d", selected); if (selected == -1) { Log(LOG_LEVEL_ERR, "Error while waiting for connections. (select: %s)", GetErrorStr()); break; } else if (selected >= 0) /* timeout or success */ { PolicyUpdateIfSafe(ctx, policy, config); /* Is there a new connection pending at our listening socket? */ if (selected > 0) { AcceptAndHandle(ctx, sd); } } /* else: interrupted, maybe pending termination. */ } Log(LOG_LEVEL_NOTICE, "Cleaning up and exiting..."); CollectCallStop(); if (sd != -1) { Log(LOG_LEVEL_VERBOSE, "Closing listening socket"); cf_closesocket(sd); /* Close listening socket */ } /* This is a graceful exit, give 2 seconds chance to threads. */ int threads_left = WaitOnThreads(); YieldCurrentLock(thislock); PolicyDestroy(server_cfengine_policy); return threads_left; }
void StartServer(EvalContext *ctx, Policy **policy, GenericAgentConfig *config) { int sd = -1; fd_set rset; int ret_val; CfLock thislock; time_t last_policy_reload = 0; extern int COLLECT_WINDOW; struct sockaddr_storage cin; socklen_t addrlen = sizeof(cin); MakeSignalPipe(); signal(SIGINT, HandleSignalsForDaemon); signal(SIGTERM, HandleSignalsForDaemon); signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); signal(SIGUSR1, HandleSignalsForDaemon); signal(SIGUSR2, HandleSignalsForDaemon); ServerTLSInitialize(); sd = SetServerListenState(ctx, QUEUESIZE, SERVER_LISTEN, &InitServer); TransactionContext tc = { .ifelapsed = 0, .expireafter = 1, }; Policy *server_cfengine_policy = PolicyNew(); Promise *pp = NULL; { Bundle *bp = PolicyAppendBundle(server_cfengine_policy, NamespaceDefault(), "server_cfengine_bundle", "agent", NULL, NULL); PromiseType *tp = BundleAppendPromiseType(bp, "server_cfengine"); pp = PromiseTypeAppendPromise(tp, config->input_file, (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL); } assert(pp); thislock = AcquireLock(ctx, pp->promiser, VUQNAME, CFSTARTTIME, tc, pp, false); if (thislock.lock == NULL) { PolicyDestroy(server_cfengine_policy); return; } if (sd != -1) { Log(LOG_LEVEL_VERBOSE, "Listening for connections ..."); } #ifdef __MINGW32__ if (!NO_FORK) { Log(LOG_LEVEL_VERBOSE, "Windows does not support starting processes in the background - starting in foreground"); } #else /* !__MINGW32__ */ if ((!NO_FORK) && (fork() != 0)) { _exit(EXIT_SUCCESS); } if (!NO_FORK) { ActAsDaemon(); } #endif /* !__MINGW32__ */ WritePID("cf-serverd.pid"); /* Andrew Stribblehill <*****@*****.**> -- close sd on exec */ #ifndef __MINGW32__ fcntl(sd, F_SETFD, FD_CLOEXEC); #endif CollectCallStart(COLLECT_INTERVAL); while (!IsPendingTermination()) { /* Note that this loop logic is single threaded, but ACTIVE_THREADS might still change in threads pertaining to service handling */ if (ThreadLock(cft_server_children)) { if (ACTIVE_THREADS == 0) { CheckFileChanges(ctx, policy, config, &last_policy_reload); } ThreadUnlock(cft_server_children); } // Check whether we have established peering with a hub if (CollectCallHasPending()) { int waiting_queue = 0; int new_client = CollectCallGetPending(&waiting_queue); if (waiting_queue > COLLECT_WINDOW) { Log(LOG_LEVEL_INFO, "Closing collect call because it would take" "longer than the allocated window [%d]", COLLECT_WINDOW); } ConnectionInfo *info = ConnectionInfoNew(); if (info) { ConnectionInfoSetSocket(info, new_client); ServerEntryPoint(ctx, POLICY_SERVER, info); CollectCallMarkProcessed(); } } else { /* check if listening is working */ if (sd != -1) { // Look for normal incoming service requests int signal_pipe = GetSignalPipe(); FD_ZERO(&rset); FD_SET(sd, &rset); FD_SET(signal_pipe, &rset); Log(LOG_LEVEL_DEBUG, "Waiting at incoming select..."); struct timeval timeout = { .tv_sec = 60, .tv_usec = 0 }; int max_fd = (sd > signal_pipe) ? (sd + 1) : (signal_pipe + 1); ret_val = select(max_fd, &rset, NULL, NULL, &timeout); // Empty the signal pipe. We don't need the values. unsigned char buf; while (recv(signal_pipe, &buf, 1, 0) > 0) {} if (ret_val == -1) /* Error received from call to select */ { if (errno == EINTR) { continue; } else { Log(LOG_LEVEL_ERR, "select failed. (select: %s)", GetErrorStr()); exit(1); } } else if (!ret_val) /* No data waiting, we must have timed out! */ { continue; } if (FD_ISSET(sd, &rset)) { int new_client = accept(sd, (struct sockaddr *)&cin, &addrlen); if (new_client == -1) { continue; } /* Just convert IP address to string, no DNS lookup. */ char ipaddr[CF_MAX_IP_LEN] = ""; getnameinfo((struct sockaddr *) &cin, addrlen, ipaddr, sizeof(ipaddr), NULL, 0, NI_NUMERICHOST); ConnectionInfo *info = ConnectionInfoNew(); if (info) { ConnectionInfoSetSocket(info, new_client); ServerEntryPoint(ctx, ipaddr, info); } } } } } CollectCallStop(); PolicyDestroy(server_cfengine_policy); } /*********************************************************************/ /* Level 2 */ /*********************************************************************/ int InitServer(size_t queue_size) { int sd = -1; if ((sd = OpenReceiverChannel()) == -1) { Log(LOG_LEVEL_ERR, "Unable to start server"); exit(EXIT_FAILURE); } if (listen(sd, queue_size) == -1) { Log(LOG_LEVEL_ERR, "listen failed. (listen: %s)", GetErrorStr()); exit(EXIT_FAILURE); } return sd; }