master xplshn/aruu / cmd / net / tftp.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <sys/socket.h>
  4#include <sys/time.h>
  5#include <sys/types.h>
  6
  7#include <netdb.h>
  8#include <netinet/in.h>
  9
 10#include <errno.h>
 11#include <stdio.h>
 12#include <stdlib.h>
 13#include <string.h>
 14#include <unistd.h>
 15
 16#include "util.h"
 17
 18#define BLKSIZE 512
 19#define HDRSIZE 4
 20#define PKTSIZE (BLKSIZE + HDRSIZE)
 21
 22#define TIMEOUT_SEC 5
 23/* transfer will time out after NRETRIES * TIMEOUT_SEC */
 24#define NRETRIES 5
 25
 26#define RRQ  1
 27#define WWQ  2
 28#define DATA 3
 29#define ACK  4
 30#define ERR  5
 31
 32static char *errtext[] = {
 33    "Undefined",
 34    "File not found",
 35    "Access violation",
 36    "Disk full or allocation exceeded",
 37    "Illegal TFTP operation",
 38    "Unknown transfer ID",
 39    "File already exists",
 40    "No such user"
 41};
 42
 43static struct sockaddr_storage to;
 44static socklen_t               tolen;
 45static int                     timeout;
 46static int                     state;
 47static int                     s;
 48
 49static int
 50packreq(unsigned char *buf, int op, char *path, char *mode)
 51{
 52  unsigned char *p = buf;
 53
 54  *p++ = op >> 8;
 55  *p++ = op & 0xff;
 56  if (strlen(path) + 1 > 256)
 57    eprintf("filename too long\n");
 58  memcpy(p, path, strlen(path) + 1);
 59  p += strlen(path) + 1;
 60  memcpy(p, mode, strlen(mode) + 1);
 61  p += strlen(mode) + 1;
 62  return p - buf;
 63}
 64
 65static int
 66packack(unsigned char *buf, int blkno)
 67{
 68  buf[0] = ACK >> 8;
 69  buf[1] = ACK & 0xff;
 70  buf[2] = blkno >> 8;
 71  buf[3] = blkno & 0xff;
 72  return 4;
 73}
 74
 75static int
 76packdata(unsigned char *buf, int blkno)
 77{
 78  buf[0] = DATA >> 8;
 79  buf[1] = DATA & 0xff;
 80  buf[2] = blkno >> 8;
 81  buf[3] = blkno & 0xff;
 82  return 4;
 83}
 84
 85static int
 86unpackop(unsigned char *buf)
 87{
 88  return (buf[0] << 8) | (buf[1] & 0xff);
 89}
 90
 91static int
 92unpackblkno(unsigned char *buf)
 93{
 94  return (buf[2] << 8) | (buf[3] & 0xff);
 95}
 96
 97static int
 98unpackerrc(unsigned char *buf)
 99{
100  int errc;
101
102  errc = (buf[2] << 8) | (buf[3] & 0xff);
103  if (errc < 0 || (size_t)errc >= LEN(errtext))
104    eprintf("bad error code: %d\n", errc);
105  return errc;
106}
107
108static int
109writepkt(unsigned char *buf, int len)
110{
111  int n;
112
113  n = sendto(s, buf, len, 0, (struct sockaddr *)&to, tolen);
114  if (n < 0)
115    if (errno != EINTR)
116      eprintf("sendto:");
117  return n;
118}
119
120static int
121readpkt(unsigned char *buf, int len)
122{
123  int n;
124
125  n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to, &tolen);
126  if (n < 0) {
127    if (errno != EINTR && errno != EWOULDBLOCK)
128      eprintf("recvfrom:");
129    timeout++;
130    if (timeout == NRETRIES)
131      eprintf("transfer timed out\n");
132  } else {
133    timeout = 0;
134  }
135  return n;
136}
137
138static void
139getfile(char *file)
140{
141  unsigned char buf[PKTSIZE];
142  int           n, op, blkno, nextblkno = 1, done = 0;
143
144  state = RRQ;
145  for (;;) {
146    switch (state) {
147      case RRQ:
148        n = packreq(buf, RRQ, file, "octet");
149        writepkt(buf, n);
150        n = readpkt(buf, sizeof(buf));
151        if (n > 0) {
152          op = unpackop(buf);
153          if (op != DATA && op != ERR)
154            eprintf("bad opcode: %d\n", op);
155          state = op;
156        }
157        break;
158      case DATA:
159        n -= HDRSIZE;
160        if (n < 0)
161          eprintf("truncated packet\n");
162        blkno = unpackblkno(buf);
163        if (blkno == nextblkno) {
164          nextblkno++;
165          write(1, &buf[HDRSIZE], n);
166        }
167        if (n < BLKSIZE)
168          done = 1;
169        state = ACK;
170        break;
171      case ACK:
172        n = packack(buf, blkno);
173        writepkt(buf, n);
174        if (done)
175          return;
176        n = readpkt(buf, sizeof(buf));
177        if (n > 0) {
178          op = unpackop(buf);
179          if (op != DATA && op != ERR)
180            eprintf("bad opcode: %d\n", op);
181          state = op;
182        }
183        break;
184      case ERR:
185        eprintf("error: %s\n", errtext[unpackerrc(buf)]);
186    }
187  }
188}
189
190static void
191putfile(char *file)
192{
193  unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE];
194  int           inb, outb, op, blkno, nextblkno = 0, done = 0;
195
196  state = WWQ;
197  for (;;) {
198    switch (state) {
199      case WWQ:
200        outb = packreq(outbuf, WWQ, file, "octet");
201        writepkt(outbuf, outb);
202        inb = readpkt(inbuf, sizeof(inbuf));
203        if (inb > 0) {
204          op = unpackop(inbuf);
205          if (op != ACK && op != ERR)
206            eprintf("bad opcode: %d\n", op);
207          state = op;
208        }
209        break;
210      case DATA:
211        if (blkno == nextblkno) {
212          nextblkno++;
213          packdata(outbuf, nextblkno);
214          outb = read(0, &outbuf[HDRSIZE], BLKSIZE);
215          if (outb < BLKSIZE)
216            done = 1;
217        }
218        writepkt(outbuf, outb + HDRSIZE);
219        inb = readpkt(inbuf, sizeof(inbuf));
220        if (inb > 0) {
221          op = unpackop(inbuf);
222          if (op != ACK && op != ERR)
223            eprintf("bad opcode: %d\n", op);
224          state = op;
225        }
226        break;
227      case ACK:
228        if (inb < HDRSIZE)
229          eprintf("truncated packet\n");
230        blkno = unpackblkno(inbuf);
231        if (blkno == nextblkno)
232          if (done)
233            return;
234        state = DATA;
235        break;
236      case ERR:
237        eprintf("error: %s\n", errtext[unpackerrc(inbuf)]);
238    }
239  }
240}
241
242static void
243usage(void)
244{
245  eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0);
246}
247
248// ?man tftp: tftp client
249// ?man arguments: -h host file
250// ?man transfer files to and from a remote tftp server
251int
252main(int argc, char *argv[])
253{
254  struct addrinfo hints, *res, *r;
255  struct timeval  tv;
256  char           *host = NULL, *port = "tftp";
257  void (*fn)(char *) = getfile;
258  int ret;
259
260  ARGBEGIN
261  {
262    // ?man -h:str: suppress headers or print help
263    case 'h':
264      host = EARGF(usage());
265      break;
266    // ?man -p:str: preserve file attributes
267    case 'p':
268      port = EARGF(usage());
269      break;
270    // ?man -x: hex format or match whole lines
271    case 'x':
272      fn = getfile;
273      break;
274    // ?man -c: print count or perform stdout action
275    case 'c':
276      fn = putfile;
277      break;
278    default:
279      usage();
280  }
281  ARGEND
282
283  if (!host || !argc)
284    usage();
285
286  memset(&hints, 0, sizeof(hints));
287  hints.ai_family   = AF_UNSPEC;
288  hints.ai_socktype = SOCK_DGRAM;
289  hints.ai_protocol = IPPROTO_UDP;
290  ret               = getaddrinfo(host, port, &hints, &res);
291  if (ret)
292    eprintf("getaddrinfo: %s\n", gai_strerror(ret));
293
294  for (r = res; r; r = r->ai_next) {
295    if (r->ai_family != AF_INET && r->ai_family != AF_INET6)
296      continue;
297    s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
298    if (s < 0)
299      continue;
300    break;
301  }
302  if (!r)
303    eprintf("cannot create socket\n");
304  memcpy(&to, r->ai_addr, r->ai_addrlen);
305  tolen = r->ai_addrlen;
306  freeaddrinfo(res);
307
308  tv.tv_sec  = TIMEOUT_SEC;
309  tv.tv_usec = 0;
310  if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
311    eprintf("setsockopt:");
312
313  fn(argv[0]);
314  return 0;
315}