aboutsummaryrefslogtreecommitdiff
path: root/bbot/agent/tftp.cxx
blob: 0df0d1bd7ef0c9d0ffda9897ca65d66d3ea5601b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// file      : bbot/agent/tftp.cxx -*- C++ -*-
// 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.
                "--verbosity", 1,        // Verbosity level.
                "--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;
      }
    }
  }
}