/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/tftp.c
1 394 строки
30 KB
Simon Josefsson
maint: Run 'make update-copyright'
20 янв 2026, 11:50
Не верифицирован
20 янв 2026, 11:50
1ce61cd
Код
Авторство
О чём код?
/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see `http://www.gnu.org/licenses/'. */ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Many bug fixes are from Jim Guyton <guyton@rand-unix> */ #include <config.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/file.h> #include <sys/time.h> #include <time.h> #include <netinet/in.h> #include <arpa/tftp.h> #include <arpa/inet.h> #include <errno.h> #include <error.h> #include <fcntl.h> #include <netdb.h> #include <setjmp.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #ifdef HAVE_LOCALE_H # include <locale.h> #endif #if defined HAVE_IDN2_H && defined HAVE_IDN2 # include <idn2.h> #elif defined HAVE_IDNA_H # include <idna.h> #endif #include <argp.h> #include <attribute.h> #include <libinetutils.h> #include "xalloc.h" #include "progname.h" #include "tftpsubs.h" /* Some systems define PKTSIZE in <arpa/tftp.h>. */ #ifndef PKTSIZE # define PKTSIZE SEGSIZE+4 #endif char ackbuf[PKTSIZE]; int timeout; jmp_buf timeoutbuf; static void nak (int); 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); #define TIMEOUT 5 /* secs between rexmt's */ static int rexmtval = TIMEOUT; static int maxtimeout = 5 * TIMEOUT; static struct sockaddr_storage peeraddr; /* filled in by main */ static socklen_t peerlen; static int f = -1; /* the opened socket */ static int port; /* Port number in host byte order of the server. */ static int trace; static int verbose; static int connected; static int fromatty; char mode[32]; char line[200]; int margc; #define TFTP_MAX_ARGS 20 char *margv[TFTP_MAX_ARGS]; char *prompt = "tftp"; jmp_buf toplevel; void intr (int signo); void get (int, char **); void help (int, char **); void modecmd (int, char **); void put (int, char **); void quit (int, char **); void setascii (int, char **); void setbinary (int, char **); void setpeer (int, char **); void setrexmt (int, char **); void settimeout (int, char **); void settrace (int, char **); void setverbose (int, char **); void status (int, char **); static void command (void); static void getusage (char *); static void makeargv (void); static void putusage (char *); static void settftpmode (char *); static in_port_t get_port (struct sockaddr_storage *); static void set_port (struct sockaddr_storage *, in_port_t); #define HELPINDENT (sizeof("connect")) struct cmd { char *name; char *help; void (*handler) (int, char **); }; char vhelp[] = "toggle verbose mode"; char thelp[] = "toggle packet tracing"; char chelp[] = "connect to remote tftp"; char qhelp[] = "exit tftp"; char hhelp[] = "print help information"; char shelp[] = "send file"; char rhelp[] = "receive file"; char mhelp[] = "set file transfer mode"; char sthelp[] = "show current status"; char xhelp[] = "set per-packet retransmission timeout"; char ihelp[] = "set total retransmission timeout"; char ashelp[] = "set mode to netascii"; char bnhelp[] = "set mode to octet"; struct cmd cmdtab[] = { {"connect", chelp, setpeer}, {"mode", mhelp, modecmd}, {"put", shelp, put}, {"get", rhelp, get}, {"quit", qhelp, quit}, {"verbose", vhelp, setverbose}, {"trace", thelp, settrace}, {"status", sthelp, status}, {"binary", bnhelp, setbinary}, {"ascii", ashelp, setascii}, {"rexmt", xhelp, setrexmt}, {"timeout", ihelp, settimeout}, {"?", hhelp, help}, {NULL, NULL, NULL} }; struct cmd *getcmd (char *name); char *tail (char *filename); const char args_doc[] = "[HOST [PORT]]"; const char doc[] = "Trivial file transfer protocol client"; static struct argp_option argp_options[] = { {"verbose", 'v', NULL, 0, "verbose output", 1}, {NULL, 0, NULL, 0, NULL, 0} }; char *hostport_argv[3] = { "connect" }; int hostport_argc = 1; static in_port_t get_port (struct sockaddr_storage *ss) { switch (ss->ss_family) { case AF_INET6: return ntohs (((struct sockaddr_in6 *) ss)->sin6_port); break; case AF_INET: default: return ntohs (((struct sockaddr_in *) ss)->sin_port); break; } } static void set_port (struct sockaddr_storage *ss, in_port_t port) { switch (ss->ss_family) { case AF_INET6: ((struct sockaddr_in6 *) ss)->sin6_port = htons (port); break; case AF_INET: default: ((struct sockaddr_in *) ss)->sin_port = htons (port); break; } } void recvfile (int, char *, char *); void tftp_sendfile (int, char *, char *); static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case 'v': /* Verbose. */ verbose++; break; case ARGP_KEY_ARG: if (state->arg_num >= 2 || hostport_argc >= 3) /* Too many arguments. */ argp_usage (state); hostport_argv[hostport_argc++] = arg; break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { argp_options, parse_opt, args_doc, doc, NULL, NULL, NULL }; int main (int argc, char *argv[]) { struct servent *sp; set_program_name (argv[0]); #ifdef HAVE_SETLOCALE setlocale (LC_ALL, ""); #endif iu_argp_init ("tftp", default_program_authors); argp_parse (&argp, argc, argv, 0, NULL, NULL); /* Initiate a default port. */ sp = getservbyname ("tftp", "udp"); if (sp == 0) port = 69; else port = ntohs (sp->s_port); fromatty = isatty (STDIN_FILENO); strcpy (mode, "netascii"); signal (SIGINT, intr); if (hostport_argc > 1) { if (setjmp (toplevel) != 0) exit (EXIT_SUCCESS); setpeer (hostport_argc, hostport_argv); } if (setjmp (toplevel) != 0) putchar ('\n'); command (); } char *hostname; #define RESOLVE_OK 0 #define RESOLVE_FAIL -1 /* Resolve NAME. Fill in peeraddr, hostname and set connected on success. Return value: RESOLVE_OK success RESOLVE_FAIL error */ static int resolve_name (char *name) { int err; char *rname; struct sockaddr_storage ss; struct addrinfo hints, *ai, *aiptr; #if defined HAVE_IDN || defined HAVE_IDN2 err = idna_to_ascii_lz (name, &rname, 0); if (err) { fprintf (stderr, "tftp: %s: %s\n", name, idna_strerror (err)); return RESOLVE_FAIL; } #else /* !HAVE_IDN && !HAVE_IDN2 */ rname = name; #endif memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_CANONNAME; #ifdef AI_IDN hints.ai_flags |= AI_IDN; #endif #ifdef AI_CANONIDN hints.ai_flags |= AI_CANONIDN; #endif err = getaddrinfo (rname, "tftp", &hints, &aiptr); if (err) { fprintf (stderr, "tftp: %s: %s\n", rname, gai_strerror (err)); return RESOLVE_FAIL; } #if defined HAVE_IDN || defined HAVE_IDN2 free (rname); #endif if (f >= 0) { close (f); f = -1; } for (ai = aiptr; ai; ai = ai->ai_next) { f = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (f < 0) continue; memset (&ss, 0, sizeof (ss)); ss.ss_family = ai->ai_family; #if HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN ss.ss_len = ai->ai_addrlen; #endif if (bind (f, (struct sockaddr *) &ss, ai->ai_addrlen)) { close (f); f = -1; continue; } /* Successfully resolved hostname. */ peerlen = ai->ai_addrlen; memcpy (&peeraddr, ai->ai_addr, ai->ai_addrlen); connected = 1; free (hostname); if (ai->ai_canonname) hostname = xstrdup (ai->ai_canonname); else hostname = xstrdup ("<dummy>"); break; } freeaddrinfo (aiptr); if (ai == NULL) return RESOLVE_FAIL; else return RESOLVE_OK; } /* Prompt for more arguments from the user with PROMPT, putting the results into ARGC & ARGV, with an initial argument of ARG0. Global variables LINE, MARGC, and MARGV are changed. */ static void get_args (char *arg0, char *prompt, int *argc, char ***argv) { size_t arg0_len = strlen (arg0); strcpy (line, arg0); strcat (line, " "); printf ("%s", prompt); if (fgets (line + arg0_len + 1, sizeof line - arg0_len - 1, stdin)) { makeargv (); *argc = margc; *argv = margv; } else { *argv[0] = arg0; *argc = 1; /* Will produce a usage printout. */ } } void setpeer (int argc, char *argv[]) { if (argc < 2) get_args ("Connect", "(to) ", &argc, &argv); if (argc < 2 || argc > 3) { printf ("usage: %s host-name [port]\n", argv[0]); return; } switch (resolve_name (argv[1])) { case RESOLVE_OK: break; case RESOLVE_FAIL: return; } if (argc == 3) { /* Take a user-specified port number. */ port = atoi (argv[2]); if (port <= 0) { printf ("%s: bad port number\n", argv[2]); connected = 0; return; } } else { /* Use the standard TFTP port. */ struct servent *sp; sp = getservbyname ("tftp", "udp"); if (sp == 0) error (EXIT_FAILURE, 0, "udp/tftp: unknown service\n"); port = ntohs (sp->s_port); } connected = 1; } struct modes { char *m_name; char *m_mode; } modes[] = { {"ascii", "netascii"}, {"netascii", "netascii"}, {"binary", "octet"}, {"image", "octet"}, {"octet", "octet"}, {0, 0} }; void modecmd (int argc, char *argv[]) { struct modes *p; char *sep; if (argc < 2) { printf ("Using %s mode to transfer files.\n", mode); return; } if (argc == 2) { for (p = modes; p->m_name; p++) if (strcmp (argv[1], p->m_name) == 0) break; if (p->m_name) { settftpmode (p->m_mode); return; } printf ("%s: unknown mode\n", argv[1]); /* drop through and print usage message */ } printf ("usage: %s [", argv[0]); sep = " "; for (p = modes; p->m_name; p++) { printf ("%s%s", sep, p->m_name); if (*sep == ' ') sep = " | "; } printf (" ]\n"); return; } void setbinary (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[]) { settftpmode ("octet"); } void setascii (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[]) { settftpmode ("netascii"); } static void settftpmode (char *newmode) { strcpy (mode, newmode); if (verbose) printf ("mode set to %s\n", mode); } /* * Send file(s). */ void put (int argc, char *argv[]) { int fd; int n; char *cp, *targ; if (argc < 2) get_args ("send", "(file) ", &argc, &argv); if (argc < 2) { putusage (argv[0]); return; } targ = argv[argc - 1]; if (strchr (argv[argc - 1], ':')) { char *cp; for (n = 1; n < argc - 1; n++) if (strchr (argv[n], ':')) { putusage (argv[0]); return; } cp = argv[argc - 1]; /* Is host string escaped using square brackets? */ if (cp[0] == '[') { /* Locate far end. */ cp = strchr (cp, ']'); if (cp == NULL) return; /* Unpaired bracket. Fail! */ /* Erase far end and capture valid host string. */ *cp = 0; targ = cp + 1; /* Position beyond bracket. */ cp = argv[argc - 1] + 1; /* Verify presence of a colon. Else backup * in order to accept a strange file name. */ if (targ[0] == ':') ++targ; else { targ = argv[argc - 1]; cp = NULL; /* Invalidate host name string. */ } } else { targ = strchr (cp, ':'); *targ++ = 0; /* Test whether host string lacks content. * Then the host string will be ignored. */ if (strlen (cp) == 0) cp = NULL; /* Invalidate host name string. */ } /* If a remote host was stated, resolve it! */ if (cp != NULL && resolve_name (cp) != RESOLVE_OK) return; } if (!connected) { printf ("No target machine specified.\n"); return; } if (argc < 4) { cp = argc == 2 ? tail (targ) : argv[1]; fd = open (cp, O_RDONLY); if (fd < 0) { fprintf (stderr, "tftp: "); perror (cp); return; } if (verbose) printf ("putting %s to %s:%s [%s]\n", cp, hostname, targ, mode); set_port (&peeraddr, port); tftp_sendfile (fd, targ, mode); return; } /* this assumes the target is a directory */ /* on a remote unix system. hmmmm. */ cp = strchr (targ, '\0'); *cp++ = '/'; for (n = 1; n < argc - 1; n++) { strcpy (cp, tail (argv[n])); fd = open (argv[n], O_RDONLY); if (fd < 0) { fprintf (stderr, "tftp: "); perror (argv[n]); continue; } if (verbose) printf ("putting %s to %s:%s [%s]\n", argv[n], hostname, targ, mode); set_port (&peeraddr, port); tftp_sendfile (fd, targ, mode); } } static void putusage (char *s) { printf ("usage: %s file ... host:target, or\n", s); printf (" %s file ... target (when already connected)\n", s); } /* * Receive file(s). */ void get (int argc, char *argv[]) { int fd; int n; char *cp; char *src; if (argc < 2) get_args ("get", "(files) ", &argc, &argv); if (argc < 2) { getusage (argv[0]); return; } if (!connected) { for (n = 1; n < argc; n++) if (strchr (argv[n], ':') == 0) { getusage (argv[0]); return; } } for (n = 1; n < argc; n++) { src = strchr (argv[n], ':'); if (src == NULL) src = argv[n]; else if (src == argv[n]) { /* Degenerate case: silently drop initial colon. * No usable host name. */ ++src; } else { /* Parse the host name string; remove square brackets. */ cp = argv[n]; if (cp[0] == '[') { cp = strchr (argv[n], ']'); if (cp) { /* Calculate host string and source file name. */ src = cp + 1; *cp = 0; if (*src == ':') ++src; cp = argv[n] + 1; } } else { /* No escaping; break string at first colon. */ *src++ = 0; } if (cp != NULL && resolve_name (cp) != RESOLVE_OK) continue; } if (argc < 4) { cp = argc == 3 ? argv[2] : tail (src); fd = creat (cp, 0644); if (fd < 0) { fprintf (stderr, "tftp: "); perror (cp); return; } if (verbose) printf ("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); set_port (&peeraddr, port); recvfile (fd, src, mode); break; } cp = tail (src); /* new .. jdg */ fd = creat (cp, 0644); if (fd < 0) { fprintf (stderr, "tftp: "); perror (cp); continue; } if (verbose) printf ("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); set_port (&peeraddr, port); recvfile (fd, src, mode); } } static void getusage (char *s) { printf ("usage: %s host:file host:file ... file, or\n", s); printf (" %s file file ... file if connected\n", s); } void setrexmt (int argc, char *argv[]) { int t; if (argc < 2) get_args ("Rexmt-timeout", "(value) ", &argc, &argv); if (argc != 2) { printf ("usage: %s value\n", argv[0]); return; } t = atoi (argv[1]); if (t < 0) printf ("%s: bad value\n", argv[1]); else rexmtval = t; } void settimeout (int argc, char *argv[]) { int t; if (argc < 2) get_args ("Maximum-timeout", "(value) ", &argc, &argv); if (argc != 2) { printf ("usage: %s value\n", argv[0]); return; } t = atoi (argv[1]); if (t < 0) printf ("%s: bad value\n", argv[1]); else maxtimeout = t; } void status (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[]) { if (connected) printf ("Connected to %s.\n", hostname); else printf ("Not connected.\n"); printf ("Mode: %s Verbose: %s Tracing: %s\n", mode, verbose ? "on" : "off", trace ? "on" : "off"); printf ("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n", rexmtval, maxtimeout); } void intr (int signo MAYBE_UNUSED) { signal (SIGALRM, SIG_IGN); alarm (0); longjmp (toplevel, -1); } char * tail (char *filename) { char *s; while (*filename) { s = strrchr (filename, '/'); if (s == NULL) break; if (s[1]) return (s + 1); *s = '\0'; } return filename; } /* * Command parser. */ static void command (void) { struct cmd *c; for (;;) { if (fromatty) printf ("%s> ", prompt); if (fgets (line, sizeof line, stdin) == 0) { if (feof (stdin)) exit (EXIT_SUCCESS); else continue; } if (line[0] == 0) continue; makeargv (); if (margc == 0) continue; c = getcmd (margv[0]); if (c == (struct cmd *) -1) { printf ("?Ambiguous command\n"); continue; } if (c == 0) { printf ("?Invalid command\n"); continue; } (*c->handler) (margc, margv); } } struct cmd * getcmd (char *name) { char *p, *q; struct cmd *c, *found; int nmatches, longest; longest = 0; nmatches = 0; found = 0; for (c = cmdtab; (p = c->name) != NULL; c++) { for (q = name; *q == *p++; q++) if (*q == 0) /* exact match? */ return (c); if (!*q) { /* the name was a prefix */ if (q - name > longest) { longest = q - name; nmatches = 1; found = c; } else if (q - name == longest) nmatches++; } } if (nmatches > 1) return (struct cmd *) -1; return found; } /* * Slice a string up into argc/argv. */ static void makeargv (void) { char *cp; char **argp = margv; margc = 0; for (cp = line; *cp;) { while (isspace (*cp)) cp++; if (*cp == '\0') break; if (margc + 1 >= TFTP_MAX_ARGS) { fprintf (stderr, "Ignoring excess arguments.\n"); break; } *argp++ = cp; margc += 1; while (*cp != '\0' && !isspace (*cp)) cp++; if (*cp == '\0') break; *cp++ = '\0'; } *argp++ = 0; } void quit (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[]) { exit (EXIT_SUCCESS); } /* * Help command. */ void help (int argc, char *argv[]) { struct cmd *c; if (argc == 1) { printf ("Commands may be abbreviated. Commands are:\n\n"); for (c = cmdtab; c->name; c++) printf ("%-*s\t%s\n", (int) HELPINDENT, c->name, c->help); return; } while (--argc > 0) { char *arg; arg = *++argv; c = getcmd (arg); if (c == (struct cmd *) -1) printf ("?Ambiguous help command %s\n", arg); else if (c == (struct cmd *) 0) printf ("?Invalid help command %s\n", arg); else printf ("%s\n", c->help); } } void settrace (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[]) { trace = !trace; printf ("Packet tracing %s.\n", trace ? "on" : "off"); } void setverbose (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[]) { verbose = !verbose; printf ("Verbose mode %s.\n", verbose ? "on" : "off"); } /* * Send the requested file. */ void tftp_sendfile (int fd, char *name, char *mode) { struct tftphdr *ap; /* data and ack packets */ struct tftphdr *r_init (void), *dp; int n; volatile int block, size, convert; volatile unsigned long amount; struct sockaddr_storage from; socklen_t fromlen; FILE *file; 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; 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); break; } dp->th_opcode = htons ((unsigned short) DATA); dp->th_block = htons ((unsigned short) block); } timeout = 0; setjmp (timeoutbuf); send_data: if (trace) tpacket ("sent", dp, size + 4); n = sendto (f, (const char *) dp, size + 4, 0, (struct sockaddr *) &peeraddr, peerlen); if (n != size + 4) { perror ("tftp: sendto"); goto abort; } 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) { perror ("tftp: recvfrom"); goto abort; } set_port (&peeraddr, get_port (&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); 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; } } 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, char *name, char *mode) { struct tftphdr *ap; struct tftphdr *dp, *w_init (void); int n; volatile int block, size, firsttrip; volatile unsigned long amount; struct sockaddr_storage from; socklen_t fromlen; FILE *file; volatile int convert; /* true if converting crlf -> lf */ startclock (); dp = w_init (); ap = (struct tftphdr *) ackbuf; file = fdopen (fd, "w"); convert = !strcmp (mode, "netascii"); block = 1; firsttrip = 1; amount = 0; signal (SIGALRM, timer); do { if (firsttrip) { size = makerequest (RRQ, name, ap, mode); firsttrip = 0; } else { ap->th_opcode = htons ((unsigned short) ACK); ap->th_block = htons ((unsigned short) (block)); size = 4; block++; } timeout = 0; setjmp (timeoutbuf); send_ack: if (trace) tpacket ("sent", ap, size); if (sendto (f, ackbuf, size, 0, (struct sockaddr *) &peeraddr, peerlen) != size) { alarm (0); perror ("tftp: sendto"); goto abort; } write_behind (file, convert); for (;;) { alarm (rexmtval); do { fromlen = sizeof (from); n = recvfrom (f, (char *) dp, PKTSIZE, 0, (struct sockaddr *) &from, &fromlen); } while (n <= 0); alarm (0); if (n < 0) { perror ("tftp: recvfrom"); goto abort; } set_port (&peeraddr, get_port (&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); 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); break; } amount += size; } while (size == SEGSIZE); abort: /* ok to ack, since user */ ap->th_opcode = htons ((unsigned short) ACK); /* has seen err msg */ ap->th_block = htons ((unsigned short) block); sendto (f, ackbuf, 4, 0, (struct sockaddr *) &peeraddr, peerlen); write_behind (file, convert); /* flush last buffer */ fclose (file); stopclock (); if (amount > 0) printstats ("Received", amount); } static int makerequest (int request, const char *name, struct tftphdr *tp, const char *mode) { char *cp; size_t arglen, len; tp->th_opcode = htons ((unsigned short) request); #if HAVE_STRUCT_TFTPHDR_TH_U /* * GNU and BSD essentially, i.e. modulo a macro, define * 'tftphdr.th_stuff' as a character array of length * naught with GNU, and one with BSD! * * When compiling with stack protectors, like during * hardened builds in Debian, every useful file name * will overflow that limit. However, our code ensures * '*tp' to be of length PKTSIZE. Assigning CP via an * offset calculation avoids this issue. */ cp = (char *) tp + (tp->th_stuff - (char *) tp); #else cp = (char *) &(tp->th_stuff); #endif /* Available space for naming the target file. */ len = PKTSIZE - sizeof (struct tftphdr) - sizeof ("netascii"); arglen = strlen (name); strncpy (cp, name, len); cp += (arglen < len) ? arglen : len; *cp++ = '\0'; /* Mode is either "netascii" or "octet", so is always fits * based on our choice of LEN. */ strcpy (cp, mode); cp += strlen (mode); *cp++ = '\0'; return cp - (char *) tp; } 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. */ static void nak (int error) { struct errmsg *pe; struct tftphdr *tp; int length; tp = (struct tftphdr *) ackbuf; tp->th_opcode = htons ((unsigned short) ERROR); tp->th_code = htons ((unsigned 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; } length = strlen (pe->e_msg) + 4; memcpy (tp->th_msg, pe->e_msg, length - 3); if (trace) tpacket ("sent", tp, length); if (sendto (f, ackbuf, length, 0, (struct sockaddr *) &peeraddr, peerlen) != length) perror ("nak"); } static void tpacket (const char *s, struct tftphdr *tp, int n) { static char *opcodes[] = { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" }; char *cp, *file; unsigned 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; #if HAVE_STRUCT_TFTPHDR_TH_U file = cp = tp->th_stuff; #else file = cp = (char *) &(tp->th_stuff); #endif cp = strchr (cp, '\0'); printf ("<file=%s, mode=%s>\n", file, cp + 1); break; case DATA: printf ("<block=%d, %d bytes>\n", ntohs (tp->th_block), n - 4); break; case ACK: printf ("<block=%d>\n", ntohs (tp->th_block)); break; case ERROR: printf ("<code=%d, msg=%s>\n", ntohs (tp->th_code), tp->th_msg); break; } } struct timeval tstart; struct timeval tstop; static void startclock (void) { gettimeofday (&tstart, NULL); } static void stopclock (void) { gettimeofday (&tstop, NULL); } 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 %d bytes in %.1f seconds", direction, (int) amount, delta); if (verbose) printf (" [%.0f bits/sec]", (amount * 8.) / delta); putchar ('\n'); } static void timer (int sig MAYBE_UNUSED) { timeout += rexmtval; if (timeout >= maxtimeout) { printf ("Transfer timed out.\n"); longjmp (toplevel, -1); } longjmp (timeoutbuf, 1); }