diff options
Diffstat (limited to 'bbot/agent/tftp.cxx')
-rw-r--r-- | bbot/agent/tftp.cxx | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/bbot/agent/tftp.cxx b/bbot/agent/tftp.cxx new file mode 100644 index 0000000..27c1577 --- /dev/null +++ b/bbot/agent/tftp.cxx @@ -0,0 +1,137 @@ +// file : bbot/agent/tftp.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : TBC; see accompanying LICENSE file + +#include <bbot/agent/tftp.hxx> + +#include <arpa/inet.h> // htonl() +#include <netinet/in.h> // sockaddr_in +#include <sys/socket.h> +#include <sys/select.h> + +#include <cstring> // memset() + +#include <bbot/agent/agent.hxx> + +using namespace std; +using namespace butl; + +namespace bbot +{ + tftp_server:: + tftp_server (const string& map, uint16_t port) + { + int fd (socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)); + + if (fd == -1) + throw_system_error (errno); + + fd_.reset (fd); + + // Bind to ephemeral port. + // + sockaddr_in addr; + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl (INADDR_ANY); + addr.sin_port = htons (port); + + // Not to confuse with std::bind(). + // + if (::bind (fd, + reinterpret_cast<sockaddr*> (&addr), + sizeof (sockaddr_in)) == -1) + throw_system_error (errno); + + // Create the map file. + // + map_ = auto_rmfile (path::temp_path ("bbot-agent-tftp-map")); + ofdstream ofs (map_.path ()); + ofs << map << endl; + ofs.close (); + } + + uint16_t tftp_server:: + port () const + { + sockaddr_in addr; + socklen_t size (sizeof (addr)); + + if (getsockname (fd_.get (), + reinterpret_cast<sockaddr*> (&addr), + &size) == -1) + throw_system_error (errno); + + assert (size == sizeof (addr)); + return ntohs (addr.sin_port); + } + + bool tftp_server:: + serve (size_t& sec, size_t inc) + { + tracer trace ("tftp_server::serve"); + + if (inc == 0 || inc > sec) + inc = sec; + + int fd (fd_.get ()); + + // Note: Linux updates the timeout value which we rely upon. + // + timeval timeout {static_cast<long> (inc), 0}; + + fd_set rd; + FD_ZERO (&rd); + + for (;;) + { + FD_SET (fd, &rd); + + int r (select (fd + 1, &rd, nullptr, nullptr, &timeout)); + + if (r == -1) + { + if (errno == EINTR) + continue; + + throw_system_error (errno); + } + else if (r == 0) // Timeout. + { + sec -= inc; + return false; + } + + if (FD_ISSET (fd, &rd)) + { + // The inetd "protocol" is to pass the socket as stdin/stdout file + // descriptors. + // + // Notes/issues: + // + // 1. Writes diagnostics to syslog. + // + run_io (trace, + fddup (fd), + fddup (fd), + 2, + "sudo", // Required for --secure (chroot). + "/usr/sbin/in.tftpd", // Standard installation location. + "--timeout", 1, // Wait for more requests. + "--permissive", // Use inherited umask. + "--create", // Allow creating new files (PUT). + "--map-file", map_.path (), // Path remapping rules. + "--user", uname, // Run as our effective user. + "--secure", // Chroot to data directory. + ops.tftp ()); + + // This is not really accurate since tftpd will, for example, serve + // an upload request until it is complete. But it's close anough for + // our needs. + // + sec -= (inc - static_cast<size_t> (timeout.tv_sec)); + return true; + } + } + } +} |