summaryrefslogtreecommitdiffstats
path: root/usr.bin/tftp/tftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/tftp/tftp.c')
-rw-r--r--usr.bin/tftp/tftp.c576
1 files changed, 180 insertions, 396 deletions
diff --git a/usr.bin/tftp/tftp.c b/usr.bin/tftp/tftp.c
index 4300902..29ba2b2 100644
--- a/usr.bin/tftp/tftp.c
+++ b/usr.bin/tftp/tftp.c
@@ -45,446 +45,230 @@ __FBSDID("$FreeBSD$");
/*
* TFTP User Program -- Protocol Machines
*/
-#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/time.h>
+#include <sys/stat.h>
#include <netinet/in.h>
-#include <arpa/inet.h>
#include <arpa/tftp.h>
#include <err.h>
-#include <errno.h>
-#include <setjmp.h>
-#include <signal.h>
+#include <netdb.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
-#include <unistd.h>
-#include <netdb.h>
+#include <syslog.h>
-#include "extern.h"
-#include "tftpsubs.h"
-
-extern struct sockaddr_storage peeraddr; /* filled in by main */
-extern int f; /* the opened socket */
-extern int trace;
-extern int verbose;
-extern int rexmtval;
-extern int maxtimeout;
-extern volatile int txrx_error;
-
-#define PKTSIZE SEGSIZE+4
-char ackbuf[PKTSIZE];
-int timeout;
-jmp_buf toplevel;
-jmp_buf timeoutbuf;
-
-static void nak(int, const struct sockaddr *);
-static int makerequest(int, const char *, struct tftphdr *, const char *);
-static void printstats(const char *, unsigned long);
-static void startclock(void);
-static void stopclock(void);
-static void timer(int);
-static void tpacket(const char *, struct tftphdr *, int);
-static int cmpport(const struct sockaddr *, const struct sockaddr *);
+#include "tftp.h"
+#include "tftp-file.h"
+#include "tftp-utils.h"
+#include "tftp-io.h"
+#include "tftp-transfer.h"
+#include "tftp-options.h"
/*
* Send the requested file.
*/
void
-xmitfile(int fd, const char *name, const char *mode)
+xmitfile(int peer, char *port, int fd, char *name, char *mode)
{
- struct tftphdr *ap; /* data and ack packets */
- struct tftphdr *dp;
- int n;
- volatile unsigned short block;
- volatile int size, convert;
- volatile unsigned long amount;
- struct sockaddr_storage from;
- socklen_t fromlen;
- FILE *file;
- struct sockaddr_storage peer;
+ struct tftphdr *rp;
+ int n, i;
+ uint16_t block;
+ uint32_t amount;
struct sockaddr_storage serv; /* valid server port number */
+ char recvbuffer[MAXPKTSIZE];
+ struct tftp_stats tftp_stats;
+
+ stats_init(&tftp_stats);
- startclock(); /* start stat's clock */
- dp = r_init(); /* reset fillbuf/read-ahead code */
- ap = (struct tftphdr *)ackbuf;
- file = fdopen(fd, "r");
- convert = !strcmp(mode, "netascii");
- block = 0;
- amount = 0;
- memcpy(&peer, &peeraddr, peeraddr.ss_len);
memset(&serv, 0, sizeof(serv));
+ rp = (struct tftphdr *)recvbuffer;
+
+ if (port == NULL) {
+ struct servent *se;
+ se = getservbyname("tftp", "udp");
+ ((struct sockaddr_in *)&peer_sock)->sin_port = se->s_port;
+ } else
+ ((struct sockaddr_in *)&peer_sock)->sin_port =
+ htons(atoi(port));
+
+ for (i = 0; i < 12; i++) {
+ struct sockaddr_storage from;
+
+ /* Tell the other side what we want to do */
+ if (debug&DEBUG_SIMPLE)
+ printf("Sending %s\n", name);
+
+ n = send_wrq(peer, name, mode);
+ if (n > 0) {
+ printf("Cannot send WRQ packet\n");
+ return;
+ }
- signal(SIGALRM, timer);
- do {
- if (block == 0)
- size = makerequest(WRQ, name, dp, mode) - 4;
- else {
- /* size = read(fd, dp->th_data, SEGSIZE); */
- size = readit(file, &dp, convert);
- if (size < 0) {
- nak(errno + 100, (struct sockaddr *)&peer);
- break;
- }
- dp->th_opcode = htons((u_short)DATA);
- dp->th_block = htons((u_short)block);
+ /*
+ * The first packet we receive has the new destination port
+ * we have to send the next packets to.
+ */
+ n = receive_packet(peer, recvbuffer,
+ MAXPKTSIZE, &from, timeoutpacket);
+
+ /* We got some data! */
+ if (n >= 0) {
+ ((struct sockaddr_in *)&peer_sock)->sin_port =
+ ((struct sockaddr_in *)&from)->sin_port;
+ break;
}
- timeout = 0;
- (void) setjmp(timeoutbuf);
-send_data:
- if (trace)
- tpacket("sent", dp, size + 4);
- n = sendto(f, dp, size + 4, 0,
- (struct sockaddr *)&peer, peer.ss_len);
- if (n != size + 4) {
- warn("sendto");
- txrx_error = 1;
- goto abort;
+
+ /* This should be retried */
+ if (n == RP_TIMEOUT) {
+ printf("Try %d, didn't receive answer from remote.\n",
+ i + 1);
+ continue;
}
- read_ahead(file, convert);
- for ( ; ; ) {
- alarm(rexmtval);
- do {
- fromlen = sizeof(from);
- n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
- (struct sockaddr *)&from, &fromlen);
- } while (n <= 0);
- alarm(0);
- if (n < 0) {
- warn("recvfrom");
- txrx_error = 1;
- goto abort;
- }
- if (!serv.ss_family)
- serv = from;
- else if (!cmpport((struct sockaddr *)&serv,
- (struct sockaddr *)&from)) {
- warn("server port mismatch");
- txrx_error = 1;
- goto abort;
- }
- peer = from;
- if (trace)
- tpacket("received", ap, n);
- /* should verify packet came from server */
- ap->th_opcode = ntohs(ap->th_opcode);
- ap->th_block = ntohs(ap->th_block);
- if (ap->th_opcode == ERROR) {
- printf("Error code %d: %s\n", ap->th_code,
- ap->th_msg);
- txrx_error = 1;
- goto abort;
- }
- if (ap->th_opcode == ACK) {
- int j;
-
- if (ap->th_block == block) {
- break;
- }
- /* On an error, try to synchronize
- * both sides.
- */
- j = synchnet(f);
- if (j && trace) {
- printf("discarded %d packets\n",
- j);
- }
- if (ap->th_block == (block-1)) {
- goto send_data;
- }
- }
+
+ /* Everything else is fatal */
+ break;
+ }
+ if (i == 12) {
+ printf("Transfer timed out.\n");
+ return;
+ }
+ if (rp->th_opcode == ERROR) {
+ printf("Got ERROR, aborted\n");
+ return;
+ }
+
+ /*
+ * If the first packet is an OACK instead of an ACK packet,
+ * handle it different.
+ */
+ if (rp->th_opcode == OACK) {
+ if (!options_rfc_enabled) {
+ printf("Got OACK while options are not enabled!\n");
+ send_error(peer, EBADOP);
+ return;
}
- if (block > 0)
- amount += size;
- block++;
- } while (size == SEGSIZE || block == 1);
-abort:
- fclose(file);
- stopclock();
- if (amount > 0)
- printstats("Sent", amount);
-}
-/*
- * Receive a file.
- */
-void
-recvfile(int fd, const char *name, const char *mode)
-{
- struct tftphdr *ap;
- struct tftphdr *dp;
- int n;
- volatile unsigned short block;
- volatile int size, firsttrip;
- volatile unsigned long amount;
- struct sockaddr_storage from;
- socklen_t fromlen;
- FILE *file;
- volatile int convert; /* true if converting crlf -> lf */
- struct sockaddr_storage peer;
- struct sockaddr_storage serv; /* valid server port number */
+ parse_options(peer, rp->th_stuff, n + 2);
+ }
+
+ if (read_init(fd, NULL, mode) < 0) {
+ warn("read_init()");
+ return;
+ }
- startclock();
- dp = w_init();
- ap = (struct tftphdr *)ackbuf;
- file = fdopen(fd, "w");
- convert = !strcmp(mode, "netascii");
block = 1;
- firsttrip = 1;
- amount = 0;
- memcpy(&peer, &peeraddr, peeraddr.ss_len);
- memset(&serv, 0, sizeof(serv));
+ tftp_send(peer, &block, &tftp_stats);
- signal(SIGALRM, timer);
- do {
- if (firsttrip) {
- size = makerequest(RRQ, name, ap, mode);
- firsttrip = 0;
- } else {
- ap->th_opcode = htons((u_short)ACK);
- ap->th_block = htons((u_short)(block));
- size = 4;
- block++;
- }
- timeout = 0;
- (void) setjmp(timeoutbuf);
-send_ack:
- if (trace)
- tpacket("sent", ap, size);
- if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&peer,
- peer.ss_len) != size) {
- alarm(0);
- warn("sendto");
- txrx_error = 1;
- goto abort;
- }
- write_behind(file, convert);
- for ( ; ; ) {
- alarm(rexmtval);
- do {
- fromlen = sizeof(from);
- n = recvfrom(f, dp, PKTSIZE, 0,
- (struct sockaddr *)&from, &fromlen);
- } while (n <= 0);
- alarm(0);
- if (n < 0) {
- warn("recvfrom");
- txrx_error = 1;
- goto abort;
- }
- if (!serv.ss_family)
- serv = from;
- else if (!cmpport((struct sockaddr *)&serv,
- (struct sockaddr *)&from)) {
- warn("server port mismatch");
- txrx_error = 1;
- goto abort;
- }
- peer = from;
- if (trace)
- tpacket("received", dp, n);
- /* should verify client address */
- dp->th_opcode = ntohs(dp->th_opcode);
- dp->th_block = ntohs(dp->th_block);
- if (dp->th_opcode == ERROR) {
- printf("Error code %d: %s\n", dp->th_code,
- dp->th_msg);
- txrx_error = 1;
- goto abort;
- }
- if (dp->th_opcode == DATA) {
- int j;
-
- if (dp->th_block == block) {
- break; /* have next packet */
- }
- /* On an error, try to synchronize
- * both sides.
- */
- j = synchnet(f);
- if (j && trace) {
- printf("discarded %d packets\n", j);
- }
- if (dp->th_block == (block-1)) {
- goto send_ack; /* resend ack */
- }
- }
- }
- /* size = write(fd, dp->th_data, n - 4); */
- size = writeit(file, &dp, n - 4, convert);
- if (size < 0) {
- nak(errno + 100, (struct sockaddr *)&peer);
- break;
- }
- amount += size;
- } while (size == SEGSIZE);
-abort: /* ok to ack, since user */
- ap->th_opcode = htons((u_short)ACK); /* has seen err msg */
- ap->th_block = htons((u_short)block);
- (void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peer,
- peer.ss_len);
- write_behind(file, convert); /* flush last buffer */
- fclose(file);
- stopclock();
+ read_close();
if (amount > 0)
- printstats("Received", amount);
-}
+ printstats("Sent", verbose, &tftp_stats);
-static int
-makerequest(int request, const char *name, struct tftphdr *tp, const char *mode)
-{
- char *cp;
-
- tp->th_opcode = htons((u_short)request);
- cp = tp->th_stuff;
- strcpy(cp, name);
- cp += strlen(name);
- *cp++ = '\0';
- strcpy(cp, mode);
- cp += strlen(mode);
- *cp++ = '\0';
- return (cp - (char *)tp);
+ txrx_error = 1;
}
-struct errmsg {
- int e_code;
- const char *e_msg;
-} errmsgs[] = {
- { EUNDEF, "Undefined error code" },
- { ENOTFOUND, "File not found" },
- { EACCESS, "Access violation" },
- { ENOSPACE, "Disk full or allocation exceeded" },
- { EBADOP, "Illegal TFTP operation" },
- { EBADID, "Unknown transfer ID" },
- { EEXISTS, "File already exists" },
- { ENOUSER, "No such user" },
- { -1, 0 }
-};
-
/*
- * Send a nak packet (error message).
- * Error code passed in is one of the
- * standard TFTP codes, or a UNIX errno
- * offset by 100.
+ * Receive a file.
*/
-static void
-nak(int error, const struct sockaddr *peer)
-{
- struct errmsg *pe;
- struct tftphdr *tp;
- int length;
-
- tp = (struct tftphdr *)ackbuf;
- tp->th_opcode = htons((u_short)ERROR);
- tp->th_code = htons((u_short)error);
- for (pe = errmsgs; pe->e_code >= 0; pe++)
- if (pe->e_code == error)
- break;
- if (pe->e_code < 0) {
- pe->e_msg = strerror(error - 100);
- tp->th_code = EUNDEF;
- }
- strcpy(tp->th_msg, pe->e_msg);
- length = strlen(pe->e_msg) + 4;
- if (trace)
- tpacket("sent", tp, length);
- if (sendto(f, ackbuf, length, 0, peer, peer->sa_len) != length)
- warn("nak");
-}
-
-static void
-tpacket(const char *s, struct tftphdr *tp, int n)
+void
+recvfile(int peer, char *port, int fd, char *name, char *mode)
{
- static const char *opcodes[] =
- { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
- char *cp, *file;
- u_short op = ntohs(tp->th_opcode);
-
- if (op < RRQ || op > ERROR)
- printf("%s opcode=%x ", s, op);
- else
- printf("%s %s ", s, opcodes[op]);
- switch (op) {
-
- case RRQ:
- case WRQ:
- n -= 2;
- file = cp = tp->th_stuff;
- cp = index(cp, '\0');
- printf("<file=%s, mode=%s>\n", file, cp + 1);
- break;
+ struct tftphdr *rp;
+ uint16_t block;
+ char recvbuffer[MAXPKTSIZE];
+ int n, i;
+ struct tftp_stats tftp_stats;
+
+ stats_init(&tftp_stats);
+
+ rp = (struct tftphdr *)recvbuffer;
+
+ if (port == NULL) {
+ struct servent *se;
+ se = getservbyname("tftp", "udp");
+ ((struct sockaddr_in *)&peer_sock)->sin_port = se->s_port;
+ } else
+ ((struct sockaddr_in *)&peer_sock)->sin_port =
+ htons(atoi(port));
+
+ for (i = 0; i < 12; i++) {
+ struct sockaddr_storage from;
+
+ /* Tell the other side what we want to do */
+ if (debug&DEBUG_SIMPLE)
+ printf("Requesting %s\n", name);
+
+ n = send_rrq(peer, name, mode);
+ if (n > 0) {
+ printf("Cannot send RRQ packet\n");
+ return;
+ }
- case DATA:
- printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
- break;
+ /*
+ * The first packet we receive has the new destination port
+ * we have to send the next packets to.
+ */
+ n = receive_packet(peer, recvbuffer,
+ MAXPKTSIZE, &from, timeoutpacket);
+
+ /* We got something useful! */
+ if (n >= 0) {
+ ((struct sockaddr_in *)&peer_sock)->sin_port =
+ ((struct sockaddr_in *)&from)->sin_port;
+ break;
+ }
- case ACK:
- printf("<block=%d>\n", ntohs(tp->th_block));
- break;
+ /* We should retry if this happens */
+ if (n == RP_TIMEOUT) {
+ printf("Try %d, didn't receive answer from remote.\n",
+ i + 1);
+ continue;
+ }
- case ERROR:
- printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
+ /* Otherwise it is a fatal error */
break;
}
-}
-
-struct timeval tstart;
-struct timeval tstop;
-
-static void
-startclock(void)
-{
- (void)gettimeofday(&tstart, NULL);
-}
-
-static void
-stopclock(void)
-{
+ if (rp->th_opcode == ERROR) {
+ tftp_log(LOG_ERR, "Error code %d: %s", rp->th_code, rp->th_msg);
+ return;
+ }
- (void)gettimeofday(&tstop, NULL);
-}
+ if (write_init(fd, NULL, mode) < 0) {
+ warn("write_init");
+ return;
+ }
-static void
-printstats(const char *direction, unsigned long amount)
-{
- double delta;
- /* compute delta in 1/10's second units */
- delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
- ((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
- delta = delta/10.; /* back to seconds */
- printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
- if (verbose)
- printf(" [%.0f bits/sec]", (amount*8.)/delta);
- putchar('\n');
-}
+ stats_init(&tftp_stats);
+
+ /*
+ * If the first packet is an OACK packet instead of an DATA packet,
+ * handle it different.
+ */
+ if (rp->th_opcode == OACK) {
+ if (!options_rfc_enabled) {
+ printf("Got OACK while options are not enabled!\n");
+ send_error(peer, EBADOP);
+ return;
+ }
-static void
-timer(int sig __unused)
-{
+ parse_options(peer, rp->th_stuff, n + 2);
- timeout += rexmtval;
- if (timeout >= maxtimeout) {
- printf("Transfer timed out.\n");
- longjmp(toplevel, -1);
+ n = send_ack(peer, 0);
+ if (n > 0) {
+ printf("Cannot send ACK on OACK.\n");
+ return;
+ }
+ block = 0;
+ tftp_receive(peer, &block, &tftp_stats, NULL, 0);
+ } else {
+ block = 1;
+ tftp_receive(peer, &block, &tftp_stats, rp, n);
}
- txrx_error = 1;
- longjmp(timeoutbuf, 1);
-}
-
-static int
-cmpport(const struct sockaddr *sa, const struct sockaddr *sb)
-{
- char a[NI_MAXSERV], b[NI_MAXSERV];
-
- if (getnameinfo(sa, sa->sa_len, NULL, 0, a, sizeof(a), NI_NUMERICSERV))
- return 0;
- if (getnameinfo(sb, sb->sa_len, NULL, 0, b, sizeof(b), NI_NUMERICSERV))
- return 0;
- if (strcmp(a, b) != 0)
- return 0;
- return 1;
+ write_close();
+ if (tftp_stats.amount > 0)
+ printstats("Received", verbose, &tftp_stats);
+ return;
}
OpenPOWER on IntegriCloud