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.
This commit is contained in:
parent
d6d8c98345
commit
1fa942a0ee
1
README
1
README
@ -78,6 +78,7 @@ The following tools are implemented:
|
||||
=*|x tar .
|
||||
=*|o tee .
|
||||
=*|o test .
|
||||
=* x tftp .
|
||||
=*|o time .
|
||||
=*|o touch .
|
||||
#*|o tr .
|
||||
|
29
tftp.1
Normal file
29
tftp.1
Normal file
@ -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
|
308
tftp.c
Normal file
308
tftp.c
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user