diff options
Diffstat (limited to 'usr.bin/tftp/tftp.c')
-rw-r--r-- | usr.bin/tftp/tftp.c | 576 |
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; } |