[hackers] [sbase] Add TFTP client as specified by RFC 1350 || sin

From: <git_AT_suckless.org>
Date: Fri, 14 Aug 2015 14:11:22 +0200 (CEST)

commit 1fa942a0ee2a6c02d3c48ae385b828682348c5d1
Author: sin <sin_AT_2f30.org>
AuthorDate: Fri Aug 14 12:58:02 2015 +0100
Commit: sin <sin_AT_2f30.org>
CommitDate: Fri Aug 14 13:11:16 2015 +0100

    Add TFTP client as specified by RFC 1350
    
    This client does not support the netascii mode. The default mode
    is octet/binary and should be sufficient.
    
    One thing left to do is to check the source port of the server
    to make sure it doesn't change. If it does, we should ignore the
    packet and send an error back without disturbing an existing
    transfer.

diff --git a/Makefile b/Makefile
index 7194ade..d5d230b 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -139,6 +139,7 @@ BIN =\
         tar\
         tee\
         test\
+ tftp\
         time\
         touch\
         tr\
diff --git a/README b/README
index 330e699..5c5491d 100644
--- a/README
+++ b/README
_AT_@ -78,6 +78,7 @@ The following tools are implemented:
 =*|x tar .
 =*|o tee .
 =*|o test .
+=* x tftp .
 =*|o time .
 =*|o touch .
 #*|o tr .
diff --git a/tftp.1 b/tftp.1
new file mode 100644
index 0000000..6224381
--- /dev/null
+++ b/tftp.1
_AT_@ -0,0 +1,29 @@
+.Dd August 14, 2015
+.Dt TFTP 1
+.Os sbase
+.Sh NAME
+.Nm tftp
+.Nd trivial file transfer protocol client
+.Sh SYNOPSIS
+.Nm
+.Fl h Ar host
+.Op Fl p Ar port
+.Op Fl x | c
+.Ar file
+.Sh DESCRIPTION
+.Nm
+is a client that implements the trivial file transfer protocol over
+either IPv4 or IPv6 as specified in RFC 1350. It can be used to transfer
+files to and from remote machines.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl h Ar host
+Set the remote hostname.
+.It Fl p Ar port
+Set the remote port. It defaults to port 69.
+.It Fl x
+Extract a file from the server. This is the default
+if no flags are specified. Output goes to stdout.
+.It Fl c
+Create a file on the server. Input comes from stdin.
+.El
diff --git a/tftp.c b/tftp.c
new file mode 100644
index 0000000..4073700
--- /dev/null
+++ b/tftp.c
_AT_@ -0,0 +1,308 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+#define BLKSIZE 512
+#define HDRSIZE 4
+#define PKTSIZE (BLKSIZE + HDRSIZE)
+
+#define TIMEOUT_SEC 5
+/* transfer will time out after NRETRIES * TIMEOUT_SEC */
+#define NRETRIES 5
+
+#define RRQ 1
+#define WWQ 2
+#define DATA 3
+#define ACK 4
+#define ERR 5
+
+static char *errtext[] = {
+ "Undefined",
+ "File not found",
+ "Access violation",
+ "Disk full or allocation exceeded",
+ "Illegal TFTP operation",
+ "Unknown transfer ID",
+ "File already exists",
+ "No such user"
+};
+
+static struct sockaddr_storage to;
+static socklen_t tolen;
+static int timeout;
+static int state;
+static int s;
+
+static int
+packreq(unsigned char *buf, int op, char *path, char *mode)
+{
+ unsigned char *p = buf;
+
+ *p++ = op >> 8;
+ *p++ = op & 0xff;
+ if (strlen(path) + 1 > 256)
+ eprintf("filename too long\n");
+ memcpy(p, path, strlen(path) + 1);
+ p += strlen(path) + 1;
+ memcpy(p, mode, strlen(mode) + 1);
+ p += strlen(mode) + 1;
+ return p - buf;
+}
+
+static int
+packack(unsigned char *buf, int blkno)
+{
+ buf[0] = ACK >> 8;
+ buf[1] = ACK & 0xff;
+ buf[2] = blkno >> 8;
+ buf[3] = blkno & 0xff;
+ return 4;
+}
+
+static int
+packdata(unsigned char *buf, int blkno)
+{
+ buf[0] = DATA >> 8;
+ buf[1] = DATA & 0xff;
+ buf[2] = blkno >> 8;
+ buf[3] = blkno & 0xff;
+ return 4;
+}
+
+static int
+unpackop(unsigned char *buf)
+{
+ return (buf[0] << 8) | (buf[1] & 0xff);
+}
+
+static int
+unpackblkno(unsigned char *buf)
+{
+ return (buf[2] << 8) | (buf[3] & 0xff);
+}
+
+static int
+unpackerrc(unsigned char *buf)
+{
+ int errc;
+
+ errc = (buf[2] << 8) | (buf[3] & 0xff);
+ if (errc < 0 || errc >= LEN(errtext))
+ eprintf("bad error code: %d\n", errc);
+ return errc;
+}
+
+static int
+writepkt(unsigned char *buf, int len)
+{
+ int n;
+
+ n = sendto(s, buf, len, 0, (struct sockaddr *)&to,
+ tolen);
+ if (n < 0)
+ if (errno != EINTR)
+ eprintf("sendto:");
+ return n;
+}
+
+static int
+readpkt(unsigned char *buf, int len)
+{
+ int n;
+
+ n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to,
+ &tolen);
+ if (n < 0) {
+ if (errno != EINTR && errno != EWOULDBLOCK)
+ eprintf("recvfrom:");
+ timeout++;
+ if (timeout == NRETRIES)
+ eprintf("transfer timed out\n");
+ } else {
+ timeout = 0;
+ }
+ return n;
+}
+
+static void
+getfile(char *file)
+{
+ unsigned char buf[PKTSIZE];
+ int n, op, blkno, nextblkno = 1, done = 0;
+
+ state = RRQ;
+ for (;;) {
+ switch (state) {
+ case RRQ:
+ n = packreq(buf, RRQ, file, "octet");
+ writepkt(buf, n);
+ n = readpkt(buf, sizeof(buf));
+ if (n > 0) {
+ op = unpackop(buf);
+ if (op != DATA && op != ERR)
+ eprintf("bad opcode: %d\n", op);
+ state = op;
+ }
+ break;
+ case DATA:
+ n -= HDRSIZE;
+ if (n < 0)
+ eprintf("truncated packet\n");
+ blkno = unpackblkno(buf);
+ if (blkno == nextblkno) {
+ nextblkno++;
+ write(1, &buf[HDRSIZE], n);
+ }
+ if (n < BLKSIZE)
+ done = 1;
+ state = ACK;
+ break;
+ case ACK:
+ n = packack(buf, blkno);
+ writepkt(buf, n);
+ if (done)
+ return;
+ n = readpkt(buf, sizeof(buf));
+ if (n > 0) {
+ op = unpackop(buf);
+ if (op != DATA && op != ERR)
+ eprintf("bad opcode: %d\n", op);
+ state = op;
+ }
+ break;
+ case ERR:
+ eprintf("error: %s\n", errtext[unpackerrc(buf)]);
+ }
+ }
+}
+
+static void
+putfile(char *file)
+{
+ unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE];
+ int inb, outb, op, blkno, nextblkno = 0, done = 0;
+
+ state = WWQ;
+ for (;;) {
+ switch (state) {
+ case WWQ:
+ outb = packreq(outbuf, WWQ, file, "octet");
+ writepkt(outbuf, outb);
+ inb = readpkt(inbuf, sizeof(inbuf));
+ if (inb > 0) {
+ op = unpackop(inbuf);
+ if (op != ACK && op != ERR)
+ eprintf("bad opcode: %d\n", op);
+ state = op;
+ }
+ break;
+ case DATA:
+ if (blkno == nextblkno) {
+ nextblkno++;
+ packdata(outbuf, nextblkno);
+ outb = read(0, &outbuf[HDRSIZE], BLKSIZE);
+ if (outb < BLKSIZE)
+ done = 1;
+ }
+ writepkt(outbuf, outb + HDRSIZE);
+ inb = readpkt(inbuf, sizeof(inbuf));
+ if (inb > 0) {
+ op = unpackop(inbuf);
+ if (op != ACK && op != ERR)
+ eprintf("bad opcode: %d\n", op);
+ state = op;
+ }
+ break;
+ case ACK:
+ if (inb < HDRSIZE)
+ eprintf("truncated packet\n");
+ blkno = unpackblkno(inbuf);
+ if (blkno == nextblkno)
+ if (done)
+ return;
+ state = DATA;
+ break;
+ case ERR:
+ eprintf("error: %s\n", errtext[unpackerrc(inbuf)]);
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints, *res, *r;
+ struct timeval tv;
+ char *host = NULL, *port = "tftp";
+ void (*fn)(char *) = getfile;
+ int ret;
+
+ ARGBEGIN {
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'p':
+ port = EARGF(usage());
+ break;
+ case 'x':
+ fn = getfile;
+ break;
+ case 'c':
+ fn = putfile;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!host || !argc)
+ usage();
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ ret = getaddrinfo(host, port, &hints, &res);
+ if (ret)
+ eprintf("getaddrinfo: %s\n", gai_strerror(ret));
+
+ for (r = res; r; r = r->ai_next) {
+ if (r->ai_family != AF_INET &&
+ r->ai_family != AF_INET6)
+ continue;
+ s = socket(r->ai_family, r->ai_socktype,
+ r->ai_protocol);
+ if (s < 0)
+ continue;
+ break;
+ }
+ if (!r)
+ eprintf("cannot create socket\n");
+ memcpy(&to, r->ai_addr, r->ai_addrlen);
+ tolen = r->ai_addrlen;
+ freeaddrinfo(res);
+
+ tv.tv_sec = TIMEOUT_SEC;
+ tv.tv_usec = 0;
+ if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
+ eprintf("setsockopt:");
+
+ fn(argv[0]);
+ return 0;
+}
Received on Fri Aug 14 2015 - 14:11:22 CEST

This archive was generated by hypermail 2.3.0 : Fri Aug 14 2015 - 14:12:11 CEST