/* * Send an ACK packet (acknowledgement). */ int send_ack(int fp, uint16_t block) { struct tftphdr *tp; int size; char buf[MAXPKTSIZE]; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ACK for block %d", block); DROPPACKETn("send_ack", 0); tp = (struct tftphdr *)buf; size = sizeof(buf) - 2; tp->th_opcode = htons((u_short)ACK); tp->th_block = htons((u_short)block); size = 4; if (sendto(fp, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { tftp_log(LOG_INFO, "send_ack: %s", strerror(errno)); return (1); } return (0); }
/* * Send an ERROR packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */ void send_error(int peer, int error) { struct tftphdr *tp; int length; struct errmsg *pe; char buf[MAXPKTSIZE]; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ERROR %d", error); DROPPACKET("send_error"); tp = (struct tftphdr *)buf; 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; /* set 'undef' errorcode */ } strcpy(tp->th_msg, pe->e_msg); length = strlen(pe->e_msg); tp->th_msg[length] = '\0'; length += 5; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ERROR %d: %s", error, tp->th_msg); if (sendto(peer, buf, length, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) != length) tftp_log(LOG_ERR, "send_error: %s", strerror(errno)); }
static void tftp_recvfile(int peer, const char *mode) { uint16_t block; struct timeval now1, now2; struct tftp_stats ts; gettimeofday(&now1, NULL); if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Receiving file"); write_init(0, file, mode); block = 0; tftp_receive(peer, &block, &ts, NULL, 0); write_close(); gettimeofday(&now2, NULL); if (debug&DEBUG_SIMPLE) { double f; if (now1.tv_usec > now2.tv_usec) { now2.tv_usec += 1000000; now2.tv_sec--; } f = now2.tv_sec - now1.tv_sec + (now2.tv_usec - now1.tv_usec) / 100000.0; tftp_log(LOG_INFO, "Download of %jd bytes in %d blocks completed after %0.1f seconds\n", (intmax_t)ts.amount, block, f); } return; }
static int send_packet(int peer, uint16_t block, char *pkt, int size) { int i; int t = 1; for (i = 0; i < 12 ; i++) { DROPPACKETn("send_packet", 0); if (sendto(peer, pkt, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) == size) { if (i) tftp_log(LOG_ERR, "%s block %d, attempt %d successful", packettype(ntohs(((struct tftphdr *) (pkt))->th_opcode)), block, i); return (0); } tftp_log(LOG_ERR, "%s block %d, attempt %d failed (Error %d: %s)", packettype(ntohs(((struct tftphdr *)(pkt))->th_opcode)), block, i, errno, strerror(errno)); sleep(t); if (t < 32) t <<= 1; } tftp_log(LOG_ERR, "send_packet: %s", strerror(errno)); return (1); }
/* * Send an RRQ packet (write request). */ int send_rrq(int peer, char *filename, char *mode) { int n; struct tftphdr *tp; char *bp; char buf[MAXPKTSIZE]; int size; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending RRQ: filename: '%s', mode '%s'", filename, mode ); DROPPACKETn("send_rrq", 1); tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)RRQ); size = 2; bp = tp->th_stuff; strcpy(bp, filename); bp += strlen(filename); *bp = 0; bp++; size += strlen(filename) + 1; strcpy(bp, mode); bp += strlen(mode); *bp = 0; bp++; size += strlen(mode) + 1; if (options_rfc_enabled) { options[OPT_TSIZE].o_request = strdup("0"); size += make_options(peer, bp, sizeof(buf) - size); } n = sendto(peer, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len); if (n != size) { tftp_log(LOG_ERR, "send_rrq: %d %s", n, strerror(errno)); return (1); } return (0); }
int read_close(void) { if (fclose(file) != 0) { tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno)); return 1; } return 0; }
static void tftp_xmitfile(int peer, const char *mode) { uint16_t block; time_t now; struct tftp_stats ts; now = time(NULL); if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Transmitting file"); read_init(0, file, mode); block = 1; tftp_send(peer, &block, &ts); read_close(); if (debug&DEBUG_SIMPLE) tftp_log(LOG_INFO, "Sent %jd bytes in %jd seconds", (intmax_t)ts.amount, (intmax_t)time(NULL) - now); }
/* * Send an OACK packet (option acknowledgement). */ int send_oack(int peer) { struct tftphdr *tp; int size, i, n; char *bp; char buf[MAXPKTSIZE]; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending OACK"); DROPPACKETn("send_oack", 0); /* * Send back an options acknowledgement (only the ones with * a reply for) */ tp = (struct tftphdr *)buf; bp = buf + 2; size = sizeof(buf) - 2; tp->th_opcode = htons((u_short)OACK); for (i = 0; options[i].o_type != NULL; i++) { if (options[i].o_reply != NULL) { n = snprintf(bp, size, "%s%c%s", options[i].o_type, 0, options[i].o_reply); bp += n+1; size -= n+1; if (size < 0) { tftp_log(LOG_ERR, "oack: buffer overflow"); exit(1); } } } size = bp - buf; if (sendto(peer, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { tftp_log(LOG_INFO, "send_oack: %s", strerror(errno)); return (1); } return (0); }
static char * parse_header(int peer, char *recvbuffer, ssize_t size, char **filename, char **mode) { char *cp; int i; struct formats *pf; *mode = NULL; cp = recvbuffer; i = get_field(peer, recvbuffer, size); if (i >= PATH_MAX) { tftp_log(LOG_ERR, "Bad option - filename too long"); send_error(peer, EBADOP); exit(1); } *filename = recvbuffer; tftp_log(LOG_INFO, "Filename: '%s'", *filename); cp += i; i = get_field(peer, cp, size); *mode = cp; cp += i; /* Find the file transfer mode */ for (cp = *mode; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) if (strcmp(pf->f_mode, *mode) == 0) break; if (pf->f_mode == NULL) { tftp_log(LOG_ERR, "Bad option - Unknown transfer mode (%s)", *mode); send_error(peer, EBADOP); exit(1); } tftp_log(LOG_INFO, "Mode: '%s'", *mode); return (cp + 1); }
/* * WRQ - receive a file from the client */ void tftp_wrq(int peer, char *recvbuffer, ssize_t size) { char *cp; int has_options = 0, ecode; char *filename, *mode; char fnbuf[PATH_MAX]; cp = parse_header(peer, recvbuffer, size, &filename, &mode); size -= (cp - recvbuffer) + 1; strlcpy(fnbuf, filename, sizeof(fnbuf)); reduce_path(fnbuf); filename = fnbuf; if (size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else tftp_log(LOG_INFO, "Options found but not enabled"); } ecode = validate_access(peer, &filename, WRQ); if (ecode == 0) { if (has_options) send_oack(peer); else send_ack(peer, 0); } if (logging) { tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, filename, errtomsg(ecode)); } if (ecode) { send_error(peer, ecode); exit(1); } tftp_recvfile(peer, mode); exit(0); }
static size_t convert_from_net(char *buffer, size_t count) { size_t i, n; /* * Convert all CR/LF to LF and all CR,NUL to CR */ n = 0; for (i = 0; i < count; i++) { if (gotcr == 0) { convbuffer[n++] = buffer[i]; gotcr = (buffer[i] == '\r'); continue; } /* CR, NULL -> CR */ if (buffer[i] == '\0') { gotcr = 0; continue; } /* CR, LF -> LF */ if (buffer[i] == '\n') { if (n == 0) { if (ftell(file) != 0) { fseek(file, -1, SEEK_END); convbuffer[n++] = '\n'; } else { /* This shouldn't happen */ tftp_log(LOG_ERR, "Received LF as first character"); abort(); } } else convbuffer[n-1] = '\n'; gotcr = 0; continue; } /* Everything else just accept as is */ convbuffer[n++] = buffer[i]; gotcr = (buffer[i] == '\r'); continue; } return fwrite(convbuffer, 1, n, file); }
/* Get a field from a \0 separated string */ ssize_t get_field(int peer, char *buffer, ssize_t size) { char *cp = buffer; while (cp < buffer + size) { if (*cp == '\0') break; cp++; } if (*cp != '\0') { tftp_log(LOG_ERR, "Bad option - no trailing \\0 found"); send_error(peer, EBADOP); exit(1); } return (cp - buffer + 1); }
int write_init(int fd, FILE *f, const char *mode) { if (f == NULL) { file = fdopen(fd, "w"); if (file == NULL) { int en = errno; tftp_log(LOG_ERR, "fdopen() failed: %s", strerror(errno)); return en; } } else file = f; convert = !strcmp(mode, "netascii"); return 0; }
/* * Send a DATA packet */ int send_data(int peer, uint16_t block, char *data, int size) { char buf[MAXPKTSIZE]; struct tftphdr *pkt; int n; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending DATA packet %d of %d bytes", block, size); DROPPACKETn("send_data", 0); pkt = (struct tftphdr *)buf; pkt->th_opcode = htons((u_short)DATA); pkt->th_block = htons((u_short)block); memcpy(pkt->th_data, data, size); n = send_packet(peer, block, (char *)pkt, size + 4); return (n); }
int receive_packet(int peer, char *data, int size, struct sockaddr_storage *from, int thistimeout) { struct tftphdr *pkt; struct sockaddr_storage from_local; struct sockaddr_storage *pfrom; socklen_t fromlen; int n; static int waiting; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Waiting %d seconds for packet", timeoutpacket); pkt = (struct tftphdr *)data; waiting = 0; signal(SIGALRM, timeout); setjmp(timeoutbuf); alarm(thistimeout); if (waiting > 0) { alarm(0); return (RP_TIMEOUT); } if (waiting > 0) { tftp_log(LOG_ERR, "receive_packet: timeout"); alarm(0); return (RP_TIMEOUT); } waiting++; pfrom = (from == NULL) ? &from_local : from; fromlen = sizeof(*pfrom); n = recvfrom(peer, data, size, 0, (struct sockaddr *)pfrom, &fromlen); alarm(0); DROPPACKETn("receive_packet", RP_TIMEOUT); if (n < 0) { tftp_log(LOG_ERR, "receive_packet: timeout"); return (RP_TIMEOUT); } alarm(0); if (n < 0) { /* No idea what could have happened if it isn't a timeout */ tftp_log(LOG_ERR, "receive_packet: %s", strerror(errno)); return (RP_RECVFROM); } if (n < 4) { tftp_log(LOG_ERR, "receive_packet: packet too small (%d bytes)", n); return (RP_TOOSMALL); } pkt->th_opcode = ntohs((u_short)pkt->th_opcode); if (pkt->th_opcode == DATA || pkt->th_opcode == ACK) pkt->th_block = ntohs((u_short)pkt->th_block); if (pkt->th_opcode == DATA && n > pktsize) { tftp_log(LOG_ERR, "receive_packet: packet too big"); return (RP_TOOBIG); } if (((struct sockaddr_in *)(pfrom))->sin_addr.s_addr != ((struct sockaddr_in *)(&peer_sock))->sin_addr.s_addr) { tftp_log(LOG_ERR, "receive_packet: received packet from wrong source"); return (RP_WRONGSOURCE); } if (pkt->th_opcode == ERROR) { tftp_log(pkt->th_code == EUNDEF ? LOG_DEBUG : LOG_ERR, "Got ERROR packet: %s", pkt->th_msg); return (RP_ERROR); } if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Received %d bytes in a %s packet", n, packettype(pkt->th_opcode)); return n - 4; }
int main(int argc, char *argv[]) { struct tftphdr *tp; int peer; socklen_t peerlen, len; ssize_t n; int ch; char *chroot_dir = NULL; struct passwd *nobody; const char *chuser = "******"; char recvbuffer[MAXPKTSIZE]; int allow_ro = 1, allow_wo = 1; tzset(); /* syslog in localtime */ acting_as_client = 0; tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { switch (ch) { case 'c': ipchroot = 1; break; case 'C': ipchroot = 2; break; case 'd': if (atoi(optarg) != 0) debug += atoi(optarg); else debug |= debug_finds(optarg); break; case 'F': newfile_format = optarg; break; case 'l': logging = 1; break; case 'n': suppress_naks = 1; break; case 'o': options_rfc_enabled = 0; break; case 'O': options_extra_enabled = 0; break; case 'p': packetdroppercentage = atoi(optarg); tftp_log(LOG_INFO, "Randomly dropping %d out of 100 packets", packetdroppercentage); break; case 's': chroot_dir = optarg; break; case 'u': chuser = optarg; break; case 'U': mask = strtol(optarg, NULL, 0); break; case 'w': create_new = 1; break; case 'W': create_new = 1; increase_name = 1; break; default: tftp_log(LOG_WARNING, "ignoring unknown option -%c", ch); } } if (optind < argc) { struct dirlist *dirp; /* Get list of directory prefixes. Skip relative pathnames. */ for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; optind++) { if (argv[optind][0] == '/') { dirp->name = argv[optind]; dirp->len = strlen(dirp->name); dirp++; } } } else if (chroot_dir) { dirs->name = "/"; dirs->len = 1; } if (ipchroot > 0 && chroot_dir == NULL) { tftp_log(LOG_ERR, "-c requires -s"); exit(1); } umask(mask); { int on = 1; if (ioctl(0, FIONBIO, &on) < 0) { tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); exit(1); } } /* Find out who we are talking to and what we are going to do */ peerlen = sizeof(peer_sock); n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (n < 0) { tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); exit(1); } getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); /* * Now that we have read the message out of the UDP * socket, we fork and exit. Thus, inetd will go back * to listening to the tftp port, and the next request * to come in will start up a new instance of tftpd. * * We do this so that inetd can run tftpd in "wait" mode. * The problem with tftpd running in "nowait" mode is that * inetd may get one or more successful "selects" on the * tftp port before we do our receive, so more than one * instance of tftpd may be started up. Worse, if tftpd * break before doing the above "recvfrom", inetd would * spawn endless instances, clogging the system. */ { int i, pid; for (i = 1; i < 20; i++) { pid = fork(); if (pid < 0) { sleep(i); /* * flush out to most recently sent request. * * This may drop some request, but those * will be resent by the clients when * they timeout. The positive effect of * this flush is to (try to) prevent more * than one tftpd being started up to service * a single request from a single client. */ peerlen = sizeof peer_sock; i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (i > 0) { n = i; } } else { break; } } if (pid < 0) { tftp_log(LOG_ERR, "fork: %s", strerror(errno)); exit(1); } else if (pid != 0) { exit(0); } } /* * See if the client is allowed to talk to me. * (This needs to be done before the chroot()) */ { struct request_info req; request_init(&req, RQ_CLIENT_ADDR, peername, 0); request_set(&req, RQ_DAEMON, "tftpd", 0); if (hosts_access(&req) == 0) { if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Access denied by 'tftpd' entry " "in /etc/hosts.allow"); /* * Full access might be disabled, but maybe the * client is allowed to do read-only access. */ request_set(&req, RQ_DAEMON, "tftpd-ro", 0); allow_ro = hosts_access(&req); request_set(&req, RQ_DAEMON, "tftpd-wo", 0); allow_wo = hosts_access(&req); if (allow_ro == 0 && allow_wo == 0) { tftp_log(LOG_WARNING, "Unauthorized access from %s", peername); exit(1); } if (debug&DEBUG_ACCESS) { if (allow_ro) tftp_log(LOG_WARNING, "But allowed readonly access " "via 'tftpd-ro' entry"); if (allow_wo) tftp_log(LOG_WARNING, "But allowed writeonly access " "via 'tftpd-wo' entry"); } } else if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Full access allowed" "in /etc/hosts.allow"); } /* * Since we exit here, we should do that only after the above * recvfrom to keep inetd from constantly forking should there * be a problem. See the above comment about system clogging. */ if (chroot_dir) { if (ipchroot > 0) { char *tempchroot; struct stat sb; int statret; struct sockaddr_storage ss; char hbuf[NI_MAXHOST]; statret = -1; memcpy(&ss, &peer_sock, peer_sock.ss_len); unmappedaddr((struct sockaddr_in6 *)&ss); getnameinfo((struct sockaddr *)&ss, ss.ss_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); if (ipchroot == 2) statret = stat(tempchroot, &sb); if (ipchroot == 1 || (statret == 0 && (sb.st_mode & S_IFDIR))) chroot_dir = tempchroot; } /* Must get this before chroot because /etc might go away */ if ((nobody = getpwnam(chuser)) == NULL) { tftp_log(LOG_ERR, "%s: no such user", chuser); exit(1); } if (chroot(chroot_dir)) { tftp_log(LOG_ERR, "chroot: %s: %s", chroot_dir, strerror(errno)); exit(1); } chdir("/"); setgroups(1, &nobody->pw_gid); if (setuid(nobody->pw_uid) != 0) { tftp_log(LOG_ERR, "setuid failed"); exit(1); } } len = sizeof(me_sock); if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { switch (me_sock.ss_family) { case AF_INET: ((struct sockaddr_in *)&me_sock)->sin_port = 0; break; case AF_INET6: ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; break; default: /* unsupported */ break; } } else { memset(&me_sock, 0, sizeof(me_sock)); me_sock.ss_family = peer_sock.ss_family; me_sock.ss_len = peer_sock.ss_len; } close(0); close(1); peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); if (peer < 0) { tftp_log(LOG_ERR, "socket: %s", strerror(errno)); exit(1); } if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { tftp_log(LOG_ERR, "bind: %s", strerror(errno)); exit(1); } tp = (struct tftphdr *)recvbuffer; tp->th_opcode = ntohs(tp->th_opcode); if (tp->th_opcode == RRQ) { if (allow_ro) tftp_rrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s read access denied", peername); exit(1); } } if (tp->th_opcode == WRQ) { if (allow_wo) tftp_wrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s write access denied", peername); exit(1); } } exit(1); }
/* * RRQ - send a file to the client */ void tftp_rrq(int peer, char *recvbuffer, ssize_t size) { char *cp; int has_options = 0, ecode; char *filename, *mode; char fnbuf[PATH_MAX]; cp = parse_header(peer, recvbuffer, size, &filename, &mode); size -= (cp - recvbuffer) + 1; strcpy(fnbuf, filename); reduce_path(fnbuf); filename = fnbuf; if (size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else tftp_log(LOG_INFO, "Options found but not enabled"); } ecode = validate_access(peer, &filename, RRQ); if (ecode == 0) { if (has_options) { int n; char lrecvbuffer[MAXPKTSIZE]; struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; send_oack(peer); n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n < 0) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Aborting: %s", rp_strerror(n)); return; } if (rp->th_opcode != ACK) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Expected ACK, got %s on OACK", packettype(rp->th_opcode)); return; } } } if (logging) tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, filename, errtomsg(ecode)); if (ecode) { /* * Avoid storms of naks to a RRQ broadcast for a relative * bootfile pathname from a diskless Sun. */ if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) exit(0); send_error(peer, ecode); exit(1); } tftp_xmitfile(peer, mode); }
/* * Send a file via the TFTP data session. */ void tftp_send(int peer, uint16_t *block, struct tftp_stats *ts) { struct tftphdr *rp; int size, n_data, n_ack, try; uint16_t oldblock; char sendbuffer[MAXPKTSIZE]; char recvbuffer[MAXPKTSIZE]; rp = (struct tftphdr *)recvbuffer; *block = 1; ts->amount = 0; do { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Sending block %d", *block); size = read_file(sendbuffer, segsize); if (size < 0) { tftp_log(LOG_ERR, "read_file returned %d", size); send_error(peer, errno + 100); goto abort; } for (try = 0; ; try++) { n_data = send_data(peer, *block, sendbuffer, size); if (n_data > 0) { if (try == maxtimeouts) { tftp_log(LOG_ERR, "Cannot send DATA packet #%d, " "giving up", *block); return; } tftp_log(LOG_ERR, "Cannot send DATA packet #%d, trying again", *block); continue; } n_ack = receive_packet(peer, recvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n_ack < 0) { if (n_ack == RP_TIMEOUT) { if (try == maxtimeouts) { tftp_log(LOG_ERR, "Timeout #%d send ACK %d " "giving up", try, *block); return; } tftp_log(LOG_WARNING, "Timeout #%d on ACK %d", try, *block); continue; } /* Either read failure or ERROR packet */ if (debug&DEBUG_SIMPLE) tftp_log(LOG_ERR, "Aborting: %s", rp_strerror(n_ack)); goto abort; } if (rp->th_opcode == ACK) { ts->blocks++; if (rp->th_block == *block) { ts->amount += size; break; } /* Re-synchronize with the other side */ (void) synchnet(peer); if (rp->th_block == (*block - 1)) { ts->retries++; continue; } } } oldblock = *block; (*block)++; if (oldblock > *block) { if (options[OPT_ROLLOVER].o_request == NULL) { /* * "rollover" option not specified in * tftp client. Default to rolling block * counter to 0. */ *block = 0; } else { *block = atoi(options[OPT_ROLLOVER].o_request); } ts->rollovers++; } gettimeofday(&(ts->tstop), NULL); } while (size == segsize); abort: return; } /* * Receive a file via the TFTP data session. * * - It could be that the first block has already arrived while * trying to figure out if we were receiving options or not. In * that case it is passed to this function. */ void tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts, struct tftphdr *firstblock, size_t fb_size) { struct tftphdr *rp; uint16_t oldblock; int n_data, n_ack, writesize, i, retry; char recvbuffer[MAXPKTSIZE]; ts->amount = 0; if (firstblock != NULL) { writesize = write_file(firstblock->th_data, fb_size); ts->amount += writesize; for (i = 0; ; i++) { n_ack = send_ack(peer, *block); if (n_ack > 0) { if (i == maxtimeouts) { tftp_log(LOG_ERR, "Cannot send ACK packet #%d, " "giving up", *block); return; } tftp_log(LOG_ERR, "Cannot send ACK packet #%d, trying again", *block); continue; } break; } if (fb_size != segsize) { gettimeofday(&(ts->tstop), NULL); return; } } rp = (struct tftphdr *)recvbuffer; do { oldblock = *block; (*block)++; if (oldblock > *block) { if (options[OPT_ROLLOVER].o_request == NULL) { /* * "rollover" option not specified in * tftp client. Default to rolling block * counter to 0. */ *block = 0; } else { *block = atoi(options[OPT_ROLLOVER].o_request); } ts->rollovers++; } for (retry = 0; ; retry++) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Receiving DATA block %d", *block); n_data = receive_packet(peer, recvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n_data < 0) { if (retry == maxtimeouts) { tftp_log(LOG_ERR, "Timeout #%d on DATA block %d, " "giving up", retry, *block); return; } if (n_data == RP_TIMEOUT) { tftp_log(LOG_WARNING, "Timeout #%d on DATA block %d", retry, *block); send_ack(peer, oldblock); continue; } /* Either read failure or ERROR packet */ if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Aborting: %s", rp_strerror(n_data)); goto abort; } if (rp->th_opcode == DATA) { ts->blocks++; if (rp->th_block == *block) break; tftp_log(LOG_WARNING, "Expected DATA block %d, got block %d", *block, rp->th_block); /* Re-synchronize with the other side */ (void) synchnet(peer); if (rp->th_block == (*block-1)) { tftp_log(LOG_INFO, "Trying to sync"); *block = oldblock; ts->retries++; goto send_ack; /* rexmit */ } } else { tftp_log(LOG_WARNING, "Expected DATA block, got %s block", packettype(rp->th_opcode)); } } if (n_data > 0) { writesize = write_file(rp->th_data, n_data); ts->amount += writesize; if (writesize <= 0) { tftp_log(LOG_ERR, "write_file returned %d", writesize); if (writesize < 0) send_error(peer, errno + 100); else send_error(peer, ENOSPACE); goto abort; } } send_ack: for (i = 0; ; i++) { n_ack = send_ack(peer, *block); if (n_ack > 0) { if (i == maxtimeouts) { tftp_log(LOG_ERR, "Cannot send ACK packet #%d, " "giving up", *block); return; } tftp_log(LOG_ERR, "Cannot send ACK packet #%d, trying again", *block); continue; } break; } gettimeofday(&(ts->tstop), NULL); } while (n_data == segsize); /* Don't do late packet management for the client implementation */ if (acting_as_client) return; for (i = 0; ; i++) { n_data = receive_packet(peer, (char *)rp, pktsize, NULL, timeoutpacket); if (n_data <= 0) break; if (n_data > 0 && rp->th_opcode == DATA && /* and got a data block */ *block == rp->th_block) /* then my last ack was lost */ send_ack(peer, *block); /* resend final ack */ } abort: return; }
/* * Receive a file. */ void recvfile(int peer, char *port, int fd, char *name, char *mode) { 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; } /* * 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; } /* We should retry if this happens */ if (n == RP_TIMEOUT) { printf("Try %d, didn't receive answer from remote.\n", i + 1); continue; } /* Otherwise it is a fatal error */ break; } if (i == 12) { printf("Transfer timed out.\n"); return; } if (rp->th_opcode == ERROR) { tftp_log(LOG_ERR, "Error code %d: %s", rp->th_code, rp->th_msg); return; } if (write_init(fd, NULL, mode) < 0) { warn("write_init"); return; } /* * 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; } parse_options(peer, rp->th_stuff, n + 2); 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); } write_close(); if (tftp_stats.amount > 0) printstats("Received", verbose, &tftp_stats); return; }