Commit message: red fixes (#468)

* red fixes

* remove magic number
CVE: CVE-2022-24950
CWE: CWE-362
Vulnerability description: A race condition exists in Eternal Terminal prior to version 6.2.0 that allows an authenticated attacker to hijack other users' SSH authorization socket, enabling the attacker to login to other systems as the targeted users. The bug is in UserTerminalRouter::getInfoForId().

Project: EternalTerminal
File: PortForwardHandler.cpp

void PortForwardHandler::addSourceSocketId(int socketId, int sourceFd) {
  for (auto& it : sourceHandlers) {
    if (it->hasUnassignedFd(sourceFd)) {
      it->addSocket(socketId, sourceFd);
      socketIdSourceHandlerMap[socketId] = it;
      return;
    }
  }
  STERROR << "Tried to add a socketId but the corresponding sourceFd is "
             "already dead: "
          << socketId << " " << sourceFd;
}

Project: EternalTerminal
File: LogHandler.cpp

string LogHandler::stderrToFile(const string &pathPrefix) {
  time_t rawtime;
  struct tm *timeinfo;
  char buffer[80];
  time(&rawtime);
  timeinfo = localtime(&rawtime);
  strftime(buffer, sizeof(buffer), "%Y-%m-%d_%I-%M", timeinfo);
  string current_time(buffer);
  string stderrFilename = pathPrefix + "_stderr_" + current_time;
  FILE *stderr_stream = freopen(stderrFilename.c_str(), "w", stderr);
  fs::permissions(
      stderrFilename,
      fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read,
      fs::perm_options::replace);
  if (!stderr_stream) {
    STFATAL << "Invalid filename " << stderrFilename;
  }
  setvbuf(stderr_stream, NULL, _IOLBF, BUFSIZ);  // set to line buffering
  return stderrFilename;
}

Project: EternalTerminal
File: PipeSocketHandler.cpp

PipeSocketHandler::PipeSocketHandler() {}

Project: EternalTerminal
File: PipeSocketHandler.cpp

int PipeSocketHandler::connect(const SocketEndpoint& endpoint) {
  lock_guard<std::recursive_mutex> mutexGuard(globalMutex);

  string pipePath = endpoint.name();
  sockaddr_un remote;

  int sockFd = ::socket(AF_UNIX, SOCK_STREAM, 0);
  FATAL_FAIL(sockFd);
  initSocket(sockFd);
  remote.sun_family = AF_UNIX;
  strncpy(remote.sun_path, pipePath.c_str(), sizeof(remote.sun_path));

  VLOG(3) << "Connecting to " << endpoint << " with fd " << sockFd;
  int result =
      ::connect(sockFd, (struct sockaddr*)&remote, sizeof(sockaddr_un));
  auto localErrno = GetErrno();
  if (result < 0 && localErrno != EINPROGRESS) {
    VLOG(3) << "Connection result: " << result << " (" << strerror(localErrno)
            << ")";
#ifdef WIN32
    ::shutdown(sockFd, SD_BOTH);
#else
    ::shutdown(sockFd, SHUT_RDWR);
#endif
#ifdef _MSC_VER
    FATAL_FAIL(::closesocket(sockFd));
#else
    FATAL_FAIL(::close(sockFd));
#endif
    sockFd = -1;
    SetErrno(localErrno);
    return sockFd;
  }

  fd_set fdset;
  FD_ZERO(&fdset);
  FD_SET(sockFd, &fdset);
  timeval tv;
  tv.tv_sec = 3; /* 3 second timeout */
  tv.tv_usec = 0;
  VLOG(4) << "Before selecting sockFd";
  select(sockFd + 1, NULL, &fdset, NULL, &tv);

  if (FD_ISSET(sockFd, &fdset)) {
    VLOG(4) << "sockFd " << sockFd << " is selected";
    int so_error;
    socklen_t len = sizeof so_error;

    FATAL_FAIL(
        ::getsockopt(sockFd, SOL_SOCKET, SO_ERROR, (char*)&so_error, &len));

    if (so_error == 0) {
      LOG(INFO) << "Connected to endpoint " << endpoint;
      // Initialize the socket again once it's blocking to make sure timeouts
      // are set
      initSocket(sockFd);

      // if we get here, we must have connected successfully
    } else {
      LOG(INFO) << "Error connecting to " << endpoint << ": " << so_error << " "
                << strerror(so_error);
#ifdef _MSC_VER
      FATAL_FAIL(::closesocket(sockFd));
#else
      FATAL_FAIL(::close(sockFd));
#endif
      sockFd = -1;
    }
  } else {
    auto localErrno = GetErrno();
    LOG(INFO) << "Error connecting to " << endpoint << ": " << localErrno << " "
              << strerror(localErrno);
#ifdef _MSC_VER
    FATAL_FAIL(::closesocket(sockFd));
#else
    FATAL_FAIL(::close(sockFd));
#endif
    sockFd = -1;
  }

  LOG(INFO) << sockFd << " is a good socket";
  if (sockFd >= 0) {
    addToActiveSockets(sockFd);
  }
  return sockFd;
}

Project: EternalTerminal
File: PipeSocketHandler.cpp

set<int> PipeSocketHandler::listen(const SocketEndpoint& endpoint) {
  lock_guard<std::recursive_mutex> guard(globalMutex);

  string pipePath = endpoint.name();
  if (pipeServerSockets.find(pipePath) != pipeServerSockets.end()) {
    throw runtime_error("Tried to listen twice on the same path");
  }

  sockaddr_un local;

  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  FATAL_FAIL(fd);
  initServerSocket(fd);
  local.sun_family = AF_UNIX; /* local is declared before socket() ^ */
  strncpy(local.sun_path, pipePath.c_str(), sizeof(local.sun_path));
  unlink(local.sun_path);

  FATAL_FAIL(::bind(fd, (struct sockaddr*)&local, sizeof(sockaddr_un)));
  ::listen(fd, 5);
#ifndef WIN32
  FATAL_FAIL(::chmod(local.sun_path, S_IRUSR | S_IWUSR | S_IXUSR));
#endif

  pipeServerSockets[pipePath] = set<int>({fd});
  return pipeServerSockets[pipePath];
}

Project: EternalTerminal
File: TerminalClient.cpp

TerminalClient::~TerminalClient() {
  connection->shutdown();
  console.reset();
  portForwardHandler.reset();
  connection.reset();
}

Project: EternalTerminal
File: PortForwardHandler.cpp

PortForwardDestinationResponse PortForwardHandler::createDestination(
    const PortForwardDestinationRequest& pfdr) {
  int fd = -1;
  bool isTcp = pfdr.destination().has_port();
  if (pfdr.destination().has_port()) {
    // Try ipv6 first
    SocketEndpoint ipv6Localhost;
    ipv6Localhost.set_name("::1");
    ipv6Localhost.set_port(pfdr.destination().port());

    fd = networkSocketHandler->connect(ipv6Localhost);
    if (fd == -1) {
      SocketEndpoint ipv4Localhost;
      ipv4Localhost.set_name("127.0.0.1");
      ipv4Localhost.set_port(pfdr.destination().port());
      // Try ipv4 next
      fd = networkSocketHandler->connect(ipv4Localhost);
    }
  } else {
    fd = pipeSocketHandler->connect(pfdr.destination());
  }
  PortForwardDestinationResponse pfdresponse;
  pfdresponse.set_clientfd(pfdr.fd());
  if (fd == -1) {
    pfdresponse.set_error(strerror(GetErrno()));
  } else {
    int socketId = rand();
    int attempts = 0;
    while (destinationHandlers.find(socketId) != destinationHandlers.end()) {
      socketId = rand();
      attempts++;
      if (attempts >= 100000) {
        pfdresponse.set_error("Could not find empty socket id");
        break;
      }
    }
    if (!pfdresponse.has_error()) {
      LOG(INFO) << "Created socket/fd pair: " << socketId << ' ' << fd;
      destinationHandlers[socketId] =
          shared_ptr<ForwardDestinationHandler>(new ForwardDestinationHandler(
              isTcp ? networkSocketHandler : pipeSocketHandler, fd, socketId));
      pfdresponse.set_socketid(socketId);
    }
  }
  return pfdresponse;
}

Project: EternalTerminal
File: PortForwardHandler.cpp

PortForwardSourceResponse PortForwardHandler::createSource(
    const PortForwardSourceRequest& pfsr, string* sourceName, uid_t userid,
    gid_t groupid) {
  try {
    if (pfsr.has_source() && sourceName) {
      throw runtime_error(
          "Do not set a source when forwarding named pipes with environment "
          "variables");
    }
    SocketEndpoint source;
    if (pfsr.has_source()) {
      source = pfsr.source();
      if (source.has_name()) {
        throw runtime_error(
            "Named socket tunneling is only allowed with temporary filenames.");
      }
    } else {
      // Make a random file to forward the pipe
      string sourcePattern =
          GetTempDirectory() + string("et_forward_sock_XXXXXX");
      string sourceDirectory = string(mkdtemp(&sourcePattern[0]));
      FATAL_FAIL(::chmod(sourceDirectory.c_str(), S_IRUSR | S_IWUSR | S_IXUSR));
      FATAL_FAIL(::chown(sourceDirectory.c_str(), userid, groupid));
      string sourcePath = string(sourceDirectory) + "/sock";

      source.set_name(sourcePath);
      if (sourceName == nullptr) {
        STFATAL
            << "Tried to create a pipe but without a place to put the name!";
      }
      *sourceName = sourcePath;
      LOG(INFO) << "Creating pipe at " << sourcePath;
    }
    if (pfsr.source().has_port()) {
      if (sourceName != nullptr) {
        STFATAL << "Tried to create a port forward but with a place to put "
                   "the name!";
      }
      auto handler = shared_ptr<ForwardSourceHandler>(new ForwardSourceHandler(
          networkSocketHandler, source, pfsr.destination()));
      sourceHandlers.push_back(handler);
      return PortForwardSourceResponse();
    } else {
      if (userid < 0 || groupid < 0) {
        STFATAL
            << "Tried to create a unix socket forward with no userid/groupid";
      }
      auto handler = shared_ptr<ForwardSourceHandler>(new ForwardSourceHandler(
          pipeSocketHandler, source, pfsr.destination()));
      FATAL_FAIL(::chmod(source.name().c_str(), S_IRUSR | S_IWUSR | S_IXUSR));
      FATAL_FAIL(::chown(source.name().c_str(), userid, groupid));
      sourceHandlers.push_back(handler);
      return PortForwardSourceResponse();
    }
  } catch (const std::runtime_error& ex) {
    PortForwardSourceResponse pfsr;
    pfsr.set_error(ex.what());
    return pfsr;
  }
}

Project: EternalTerminal
File: UserTerminalRouter.cpp

UserTerminalRouter::UserTerminalRouter(
    shared_ptr<PipeSocketHandler> _socketHandler,
    const SocketEndpoint &_routerEndpoint)
    : socketHandler(_socketHandler) {
  serverFd = *(socketHandler->listen(_routerEndpoint).begin());
  FATAL_FAIL(::chown(_routerEndpoint.name().c_str(), getuid(), getgid()));
  FATAL_FAIL(::chmod(_routerEndpoint.name().c_str(),
                     S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP |
                         S_IROTH | S_IWOTH | S_IXOTH));
}

Project: EternalTerminal
File: PipeSocketHandler.cpp

void PipeSocketHandler::stopListening(const SocketEndpoint& endpoint) {
  lock_guard<std::recursive_mutex> guard(globalMutex);

  string pipePath = endpoint.name();
  auto it = pipeServerSockets.find(pipePath);
  if (it == pipeServerSockets.end()) {
    STFATAL << "Tried to stop listening to a pipe that we weren't listening on:"
            << pipePath;
  }
  int sockFd = *(it->second.begin());
#ifdef _MSC_VER
  FATAL_FAIL(::closesocket(sockFd));
#else
  FATAL_FAIL(::close(sockFd));
#endif
}

Project: EternalTerminal
File: UserTerminalRouter.cpp

TerminalUserInfo UserTerminalRouter::getInfoForId(const string &id) {
  lock_guard<recursive_mutex> guard(routerMutex);
  auto it = idInfoMap.find(id);
  if (it == idInfoMap.end()) {
    STFATAL << " Tried to read from an id that no longer exists";
  }
  return it->second;
}

Project: EternalTerminal
File: LogHandler.cpp

el::Configurations LogHandler::setupLogHandler(int *argc, char ***argv) {
  // easylogging parse verbose arguments, see [Application Arguments]
  // in https://github.com/muflihun/easyloggingpp/blob/master/README.md
  START_EASYLOGGINGPP(*argc, *argv);

  // Easylogging configurations
  el::Configurations defaultConf;
  defaultConf.setToDefault();
  // doc says %thread_name, but %thread is the right one
  defaultConf.setGlobally(el::ConfigurationType::Format,
                          "[%level %datetime %thread %fbase:%line] %msg");
  defaultConf.setGlobally(el::ConfigurationType::Enabled, "true");
  defaultConf.setGlobally(el::ConfigurationType::SubsecondPrecision, "3");
  defaultConf.setGlobally(el::ConfigurationType::PerformanceTracking, "false");
  defaultConf.setGlobally(el::ConfigurationType::LogFlushThreshold, "1");
  defaultConf.set(el::Level::Verbose, el::ConfigurationType::Format,
                  "[%levshort%vlevel %datetime %thread %fbase:%line] %msg");
  return defaultConf;
}

Project: EternalTerminal
File: PortForwardHandler.cpp

void PortForwardHandler::handlePacket(const Packet& packet,
                                      shared_ptr<Connection> connection) {
  switch (TerminalPacketType(packet.getHeader())) {
    case TerminalPacketType::PORT_FORWARD_DATA: {
      PortForwardData pwd = stringToProto<PortForwardData>(packet.getPayload());
      if (pwd.sourcetodestination()) {
        VLOG(1) << "Got data for destination socket: " << pwd.socketid();
        auto it = destinationHandlers.find(pwd.socketid());
        if (it == destinationHandlers.end()) {
          LOG(WARNING) << "Got data for a socket id that has already closed: "
                       << pwd.socketid();
        } else {
          if (pwd.has_closed()) {
            LOG(INFO) << "Port forward socket closed: " << pwd.socketid();
            it->second->close();
            destinationHandlers.erase(it);
          } else if (pwd.has_error()) {
            // TODO: Probably need to do something better here
            LOG(INFO) << "Port forward socket errored: " << pwd.socketid();
            it->second->close();
            destinationHandlers.erase(it);
          } else {
            it->second->write(pwd.buffer());
          }
        }
      } else {
        if (pwd.has_closed()) {
          LOG(INFO) << "Port forward socket closed: " << pwd.socketid();
          closeSourceSocketId(pwd.socketid());
        } else if (pwd.has_error()) {
          LOG(INFO) << "Port forward socket errored: " << pwd.socketid();
          closeSourceSocketId(pwd.socketid());
        } else {
          VLOG(1) << "Got data for source socket: " << pwd.socketid();
          sendDataToSourceOnSocket(pwd.socketid(), pwd.buffer());
        }
      }
      break;
    }
    case TerminalPacketType::PORT_FORWARD_DESTINATION_REQUEST: {
      PortForwardDestinationRequest pfdr =
          stringToProto<PortForwardDestinationRequest>(packet.getPayload());
      LOG(INFO) << "Got new port destination request for "
                << pfdr.destination();
      PortForwardDestinationResponse pfdresponse = createDestination(pfdr);
      Packet sendPacket(
          uint8_t(TerminalPacketType::PORT_FORWARD_DESTINATION_RESPONSE),
          protoToString(pfdresponse));
      connection->writePacket(sendPacket);
      break;
    }
    case TerminalPacketType::PORT_FORWARD_DESTINATION_RESPONSE: {
      PortForwardDestinationResponse pfdr =
          stringToProto<PortForwardDestinationResponse>(packet.getPayload());
      if (pfdr.has_error()) {
        LOG(INFO) << "Could not connect to server through tunnel: "
                  << pfdr.error();
        closeSourceFd(pfdr.clientfd());
      } else {
        LOG(INFO) << "Received socket/fd map from server: " << pfdr.socketid()
                  << " " << pfdr.clientfd();
        addSourceSocketId(pfdr.socketid(), pfdr.clientfd());
      }
      break;
    }
    default: {
      STFATAL << "Unknown packet type: " << int(packet.getHeader());
    }
  }
}

Project: EternalTerminal
File: LogHandler.cpp

void LogHandler::setupStdoutLogger() {
  el::Logger *stdoutLogger = el::Loggers::getLogger("stdout");
  // Easylogging configurations
  el::Configurations stdoutConf;
  stdoutConf.setToDefault();
  // Values are always std::string
  stdoutConf.setGlobally(el::ConfigurationType::Format, "%msg");
  stdoutConf.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
  stdoutConf.setGlobally(el::ConfigurationType::ToFile, "false");
  el::Loggers::reconfigureLogger(stdoutLogger, stdoutConf);
}

Project: EternalTerminal
File: TerminalClient.cpp

vector<PortForwardSourceRequest> parseRangesToRequests(const string& input) {
  vector<PortForwardSourceRequest> pfsrs;
  auto j = split(input, ',');
  for (auto& pair : j) {
    vector<string> sourceDestination = split(pair, ':');
    try {
      if (sourceDestination[0].find_first_not_of("0123456789-") !=
              string::npos &&
          sourceDestination[1].find_first_not_of("0123456789-") !=
              string::npos) {
        PortForwardSourceRequest pfsr;
        pfsr.set_environmentvariable(sourceDestination[0]);
        pfsr.mutable_destination()->set_name(sourceDestination[1]);
        pfsrs.push_back(pfsr);
      } else if (sourceDestination[0].find('-') != string::npos &&
                 sourceDestination[1].find('-') != string::npos) {
        vector<string> sourcePortRange = split(sourceDestination[0], '-');
        int sourcePortStart = stoi(sourcePortRange[0]);
        int sourcePortEnd = stoi(sourcePortRange[1]);

        vector<string> destinationPortRange = split(sourceDestination[1], '-');
        int destinationPortStart = stoi(destinationPortRange[0]);
        int destinationPortEnd = stoi(destinationPortRange[1]);

        if (sourcePortEnd - sourcePortStart !=
            destinationPortEnd - destinationPortStart) {
          STFATAL << "source/destination port range mismatch";
          exit(1);
        } else {
          int portRangeLength = sourcePortEnd - sourcePortStart + 1;
          for (int i = 0; i < portRangeLength; ++i) {
            PortForwardSourceRequest pfsr;
            pfsr.mutable_source()->set_port(sourcePortStart + i);
            pfsr.mutable_destination()->set_port(destinationPortStart + i);
            pfsrs.push_back(pfsr);
          }
        }
      } else if (sourceDestination[0].find('-') != string::npos ||
                 sourceDestination[1].find('-') != string::npos) {
        STFATAL << "Invalid port range syntax: if source is range, "
                   "destination must be range";
      } else {
        PortForwardSourceRequest pfsr;
        pfsr.mutable_source()->set_port(stoi(sourceDestination[0]));
        pfsr.mutable_destination()->set_port(stoi(sourceDestination[1]));
        pfsrs.push_back(pfsr);
      }
    } catch (const std::logic_error& lr) {
      STFATAL << "Logic error: " << lr.what();
      exit(1);
    }
  }
  return pfsrs;
}

Project: EternalTerminal
File: PortForwardHandler.cpp

void PortForwardHandler::update(vector<PortForwardDestinationRequest>* requests,
                                vector<PortForwardData>* dataToSend) {
  for (auto& it : sourceHandlers) {
    it->update(dataToSend);
    int fd = it->listen();
    if (fd >= 0) {
      PortForwardDestinationRequest pfr;
      *(pfr.mutable_destination()) = it->getDestination();
      pfr.set_fd(fd);
      requests->push_back(pfr);
    }
  }

  for (auto& it : destinationHandlers) {
    it.second->update(dataToSend);
    if (it.second->getFd() == -1) {
      // Kill the handler and don't update the rest: we'll pick
      // them up later
      destinationHandlers.erase(it.first);
      break;
    }
  }
}

Project: EternalTerminal
File: LogHandler.cpp

void LogHandler::rolloutHandler(const char *filename, std::size_t size) {
  // SHOULD NOT LOG ANYTHING HERE BECAUSE LOG FILE IS CLOSED!
  // REMOVE OLD LOG
  remove(filename);
}

Project: EternalTerminal
File: UserTerminalRouter.cpp

IdKeyPair UserTerminalRouter::acceptNewConnection() {
  lock_guard<recursive_mutex> guard(routerMutex);
  LOG(INFO) << "Listening to id/key FIFO";
  int terminalFd = socketHandler->accept(serverFd);
  if (terminalFd < 0) {
    if (GetErrno() != EAGAIN && GetErrno() != EWOULDBLOCK) {
      FATAL_FAIL(-1);  // STFATAL with the error
    } else {
      return IdKeyPair({"", ""});  // Nothing to accept this time
    }
  }

  LOG(INFO) << "Connected";

  try {
    Packet packet;
    if (!socketHandler->readPacket(terminalFd, &packet)) {
      STFATAL << "Missing user info packet";
    }
    if (packet.getHeader() != TerminalPacketType::TERMINAL_USER_INFO) {
      STFATAL << "Got an invalid packet header: " << int(packet.getHeader());
    }
    TerminalUserInfo tui = stringToProto<TerminalUserInfo>(packet.getPayload());
    tui.set_fd(terminalFd);
    idInfoMap[tui.id()] = tui;
    return IdKeyPair({tui.id(), tui.passkey()});
  } catch (const std::runtime_error &re) {
    STFATAL << "Router can't talk to terminal: " << re.what();
  }

  STFATAL << "Should never get here";
  return IdKeyPair({"", ""});
}

Project: EternalTerminal
File: PortForwardHandler.cpp

void PortForwardHandler::closeSourceFd(int fd) {
  for (auto& it : sourceHandlers) {
    if (it->hasUnassignedFd(fd)) {
      it->closeUnassignedFd(fd);
      return;
    }
  }
  STERROR << "Tried to close an unassigned socket that didn't exist (maybe "
             "it was already removed?): "
          << fd;
}

Project: EternalTerminal
File: PortForwardHandler.cpp

void PortForwardHandler::closeSourceSocketId(int socketId) {
  auto it = socketIdSourceHandlerMap.find(socketId);
  if (it == socketIdSourceHandlerMap.end()) {
    STERROR << "Tried to close a socket id that doesn't exist";
    return;
  }
  it->second->closeSocket(socketId);
  socketIdSourceHandlerMap.erase(socketId);
}

Project: EternalTerminal
File: PipeSocketHandler.cpp

set<int> PipeSocketHandler::getEndpointFds(const SocketEndpoint& endpoint) {
  lock_guard<std::recursive_mutex> guard(globalMutex);

  string pipePath = endpoint.name();
  if (pipeServerSockets.find(pipePath) == pipeServerSockets.end()) {
    STFATAL << "Tried to getPipeFd on a pipe without calling listen() first: "
            << pipePath;
  }
  return pipeServerSockets[pipePath];
}

Project: EternalTerminal
File: LogHandler.cpp

void LogHandler::setupLogFile(el::Configurations *defaultConf, string filename,
                              string maxlogsize) {
  // Enable strict log file size check
  el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
  defaultConf->setGlobally(el::ConfigurationType::Filename, filename);
  defaultConf->setGlobally(el::ConfigurationType::ToFile, "true");
  defaultConf->setGlobally(el::ConfigurationType::MaxLogFileSize, maxlogsize);
}

Project: EternalTerminal
File: TerminalClient.cpp

void TerminalClient::run(const string& command) {
  if (console) {
    console->setup();
  }

// TE sends/receives data to/from the shell one char at a time.
#define BUF_SIZE (16 * 1024)
  char b[BUF_SIZE];

  time_t keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
  bool waitingOnKeepalive = false;

  if (command.length()) {
    LOG(INFO) << "Got command: " << command;
    et::TerminalBuffer tb;
    tb.set_buffer(command + "; exit\n");

    connection->writePacket(
        Packet(TerminalPacketType::TERMINAL_BUFFER, protoToString(tb)));
  }

  TerminalInfo lastTerminalInfo;

  if (!console.get()) {
    CLOG(INFO, "stdout") << "ET running, feel free to background..." << endl;
  }

  while (!connection->isShuttingDown()) {
    {
      lock_guard<recursive_mutex> guard(shutdownMutex);
      if (shuttingDown) {
        break;
      }
    }
    // Data structures needed for select() and
    // non-blocking I/O.
    fd_set rfd;
    timeval tv;

    FD_ZERO(&rfd);
    int maxfd = -1;
    int consoleFd = -1;
    if (console) {
      consoleFd = console->getFd();
      maxfd = consoleFd;
      FD_SET(consoleFd, &rfd);
    }
    int clientFd = connection->getSocketFd();
    if (clientFd > 0) {
      FD_SET(clientFd, &rfd);
      maxfd = max(maxfd, clientFd);
    }
    // TODO: set port forward sockets as well for performance reasons.
    tv.tv_sec = 0;
    tv.tv_usec = 10000;
    select(maxfd + 1, &rfd, NULL, NULL, &tv);

    try {
      if (console) {
        // Check for data to send.
        if (FD_ISSET(consoleFd, &rfd)) {
          // Read from stdin and write to our client that will then send it to
          // the server.
          VLOG(4) << "Got data from stdin";
#ifdef WIN32
          DWORD events;
          INPUT_RECORD buffer[128];
          HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
          PeekConsoleInput(handle, buffer, 128, &events);
          if (events > 0) {
            ReadConsoleInput(handle, buffer, 128, &events);
            string s;
            for (int keyEvent = 0; keyEvent < events; keyEvent++) {
              if (buffer[keyEvent].EventType == KEY_EVENT &&
                  buffer[keyEvent].Event.KeyEvent.bKeyDown) {
                char charPressed =
                    ((char)buffer[keyEvent].Event.KeyEvent.uChar.AsciiChar);
                if (charPressed) {
                  s += charPressed;
                }
              }
            }
            if (s.length()) {
              et::TerminalBuffer tb;
              tb.set_buffer(s);

              connection->writePacket(Packet(
                  TerminalPacketType::TERMINAL_BUFFER, protoToString(tb)));
              keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
            }
          }
#else
          if (console) {
            int rc = ::read(consoleFd, b, BUF_SIZE);
            FATAL_FAIL(rc);
            if (rc > 0) {
              // VLOG(1) << "Sending byte: " << int(b) << " " << char(b) << " "
              // << connection->getWriter()->getSequenceNumber();
              string s(b, rc);
              et::TerminalBuffer tb;
              tb.set_buffer(s);

              connection->writePacket(Packet(
                  TerminalPacketType::TERMINAL_BUFFER, protoToString(tb)));
              keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
            }
          }
#endif
        }
      }

      if (clientFd > 0 && FD_ISSET(clientFd, &rfd)) {
        VLOG(4) << "Clientfd is selected";
        while (connection->hasData()) {
          VLOG(4) << "connection has data";
          Packet packet;
          if (!connection->read(&packet)) {
            break;
          }
          uint8_t packetType = packet.getHeader();
          if (packetType == et::TerminalPacketType::PORT_FORWARD_DATA ||
              packetType ==
                  et::TerminalPacketType::PORT_FORWARD_DESTINATION_REQUEST ||
              packetType ==
                  et::TerminalPacketType::PORT_FORWARD_DESTINATION_RESPONSE) {
            keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
            VLOG(4) << "Got PF packet type " << packetType;
            portForwardHandler->handlePacket(packet, connection);
            continue;
          }
          switch (packetType) {
            case et::TerminalPacketType::TERMINAL_BUFFER: {
              if (console) {
                VLOG(3) << "Got terminal buffer";
                // Read from the server and write to our fake terminal
                et::TerminalBuffer tb =
                    stringToProto<et::TerminalBuffer>(packet.getPayload());
                const string& s = tb.buffer();
                // VLOG(5) << "Got message: " << s;
                // VLOG(1) << "Got byte: " << int(b) << " " << char(b) << " " <<
                // connection->getReader()->getSequenceNumber();
                keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
                console->write(s);
              }
              break;
            }
            case et::TerminalPacketType::KEEP_ALIVE:
              waitingOnKeepalive = false;
              // This will fill up log file quickly but is helpful for debugging
              // latency issues.
              LOG(INFO) << "Got a keepalive";
              break;
            default:
              STFATAL << "Unknown packet type: " << int(packetType);
          }
        }
      }

      if (clientFd > 0 && keepaliveTime < time(NULL)) {
        keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
        if (waitingOnKeepalive) {
          LOG(INFO) << "Missed a keepalive, killing connection.";
          connection->closeSocketAndMaybeReconnect();
          waitingOnKeepalive = false;
        } else {
          LOG(INFO) << "Writing keepalive packet";
          connection->writePacket(Packet(TerminalPacketType::KEEP_ALIVE, ""));
          waitingOnKeepalive = true;
        }
      }
      if (clientFd < 0) {
        // We are disconnected, so stop waiting for keepalive.
        waitingOnKeepalive = false;
      }

      if (console) {
        TerminalInfo ti = console->getTerminalInfo();

        if (ti != lastTerminalInfo) {
          LOG(INFO) << "Window size changed: row: " << ti.row()
                    << " column: " << ti.column() << " width: " << ti.width()
                    << " height: " << ti.height();
          lastTerminalInfo = ti;
          connection->writePacket(
              Packet(TerminalPacketType::TERMINAL_INFO, protoToString(ti)));
        }
      }

      vector<PortForwardDestinationRequest> requests;
      vector<PortForwardData> dataToSend;
      portForwardHandler->update(&requests, &dataToSend);
      for (auto& pfr : requests) {
        connection->writePacket(
            Packet(TerminalPacketType::PORT_FORWARD_DESTINATION_REQUEST,
                   protoToString(pfr)));
        VLOG(4) << "send PF request";
        keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
      }
      for (auto& pwd : dataToSend) {
        connection->writePacket(
            Packet(TerminalPacketType::PORT_FORWARD_DATA, protoToString(pwd)));
        VLOG(4) << "send PF data";
        keepaliveTime = time(NULL) + CLIENT_KEEP_ALIVE_DURATION;
      }
    } catch (const runtime_error& re) {
      STERROR << "Error: " << re.what();
      CLOG(INFO, "stdout") << "Connection closing because of error: "
                           << re.what() << endl;
      lock_guard<recursive_mutex> guard(shutdownMutex);
      shuttingDown = true;
    }
  }
  if (console) {
    console->teardown();
  }
  CLOG(INFO, "stdout") << "Session terminated" << endl;
}

Project: EternalTerminal
File: TerminalClient.cpp

TerminalClient::TerminalClient(shared_ptr<SocketHandler> _socketHandler,
                               shared_ptr<SocketHandler> _pipeSocketHandler,
                               const SocketEndpoint& _socketEndpoint,
                               const string& id, const string& passkey,
                               shared_ptr<Console> _console, bool jumphost,
                               const string& tunnels,
                               const string& reverseTunnels,
                               bool forwardSshAgent,
                               const string& identityAgent)
    : console(_console), shuttingDown(false) {
  portForwardHandler = shared_ptr<PortForwardHandler>(
      new PortForwardHandler(_socketHandler, _pipeSocketHandler));
  InitialPayload payload;
  payload.set_jumphost(jumphost);

  try {
    if (tunnels.length()) {
      auto pfsrs = parseRangesToRequests(tunnels);
      for (auto& pfsr : pfsrs) {
#ifdef WIN32
        STFATAL << "Source tunnel not supported on windows yet";
#else
        auto pfsresponse =
            portForwardHandler->createSource(pfsr, nullptr, -1, -1);
        if (pfsresponse.has_error()) {
          throw std::runtime_error(pfsresponse.error());
        }
#endif
      }
    }
    if (reverseTunnels.length()) {
      auto pfsrs = parseRangesToRequests(reverseTunnels);
      for (auto& pfsr : pfsrs) {
        *(payload.add_reversetunnels()) = pfsr;
      }
    }
    if (forwardSshAgent) {
      PortForwardSourceRequest pfsr;
      string authSock = "";
      if (identityAgent.length()) {
        authSock.assign(identityAgent);
      } else {
        auto authSockEnv = getenv("SSH_AUTH_SOCK");
        if (!authSockEnv) {
          CLOG(INFO, "stdout")
              << "Missing environment variable SSH_AUTH_SOCK.  Are you sure "
                 "you "
                 "ran ssh-agent first?"
              << endl;
          exit(1);
        }
        authSock.assign(authSockEnv);
      }
      if (authSock.length()) {
        pfsr.mutable_destination()->set_name(authSock);
        pfsr.set_environmentvariable("SSH_AUTH_SOCK");
        *(payload.add_reversetunnels()) = pfsr;
      }
    }
  } catch (const std::runtime_error& ex) {
    CLOG(INFO, "stdout") << "Error establishing port forward: " << ex.what()
                         << endl;
    exit(1);
  }

  connection = shared_ptr<ClientConnection>(
      new ClientConnection(_socketHandler, _socketEndpoint, id, passkey));

  int connectFailCount = 0;
  while (true) {
    try {
      bool fail = true;
      if (connection->connect()) {
        connection->writePacket(
            Packet(EtPacketType::INITIAL_PAYLOAD, protoToString(payload)));
        fd_set rfd;
        timeval tv;
        for (int a = 0; a < 3; a++) {
          FD_ZERO(&rfd);
          int clientFd = connection->getSocketFd();
          if (clientFd < 0) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            continue;
          }
          FD_SET(clientFd, &rfd);
          tv.tv_sec = 1;
          tv.tv_usec = 0;
          select(clientFd + 1, &rfd, NULL, NULL, &tv);
          if (FD_ISSET(clientFd, &rfd)) {
            Packet initialResponsePacket;
            if (connection->readPacket(&initialResponsePacket)) {
              if (initialResponsePacket.getHeader() !=
                  EtPacketType::INITIAL_RESPONSE) {
                CLOG(INFO, "stdout") << "Error: Missing initial response\n";
                STFATAL << "Missing initial response!";
              }
              auto initialResponse = stringToProto<InitialResponse>(
                  initialResponsePacket.getPayload());
              if (initialResponse.has_error()) {
                CLOG(INFO, "stdout") << "Error initializing connection: "
                                     << initialResponse.error() << endl;
                exit(1);
              }
              fail = false;
              break;
            }
          }
        }
      }
      if (fail) {
        LOG(WARNING) << "Connecting to server failed: Connect timeout";
        connectFailCount++;
        if (connectFailCount == 3) {
          throw std::runtime_error("Connect Timeout");
        }
      }
    } catch (const runtime_error& err) {
      LOG(INFO) << "Could not make initial connection to server";
      CLOG(INFO, "stdout") << "Could not make initial connection to "
                           << _socketEndpoint << ": " << err.what() << endl;
      exit(1);
    }

    TelemetryService::get()->logToDatadog("Connection Established",
                                          el::Level::Info, __FILE__, __LINE__);
    break;
  }
  VLOG(1) << "Client created with id: " << connection->getId();
};

Project: EternalTerminal
File: PortForwardHandler.cpp

PortForwardHandler::PortForwardHandler(
    shared_ptr<SocketHandler> _networkSocketHandler,
    shared_ptr<SocketHandler> _pipeSocketHandler)
    : networkSocketHandler(_networkSocketHandler),
      pipeSocketHandler(_pipeSocketHandler) {}

Project: EternalTerminal
File: PortForwardHandler.cpp

void PortForwardHandler::sendDataToSourceOnSocket(int socketId,
                                                  const string& data) {
  auto it = socketIdSourceHandlerMap.find(socketId);
  if (it == socketIdSourceHandlerMap.end()) {
    STERROR << "Tried to send data on a socket id that doesn't exist: "
            << socketId;
    return;
  }
  it->second->sendDataOnSocket(socketId, data);
}