aboutsummaryrefslogtreecommitdiff
path: root/bbot/tftp.cxx
blob: 27d58a492c721f37bf68de6cac01ba055d5ddf37 (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
// file      : bbot/tftp.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
// license   : TBC; see accompanying LICENSE file

#include <bbot/tftp>

#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>

using namespace std;
using namespace butl;

namespace bbot
{
  tftp_server::
  tftp_server (const string& map)
  {
    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 (0);

    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)
  {
    tracer trace ("tftp_server::serve");

    int fd (fd_.get ());

    // Note: Linux updates the timeout value which we rely upon.
    //
    timeval timeout {static_cast<long> (sec), 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 = 0;
        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.
                "/build/tftp/");

        sec = static_cast<size_t> (timeout.tv_sec);
        return true;
      }
    }
  }
}