static ssize_t recvtftp(struct tftp_handle *h, void *pkt, ssize_t len, time_t tleft, unsigned short *rtype) { struct iodesc *d = h->iodesc; struct tftphdr *t; errno = 0; len = readudp(d, pkt, len, tleft); if (len < 4) return (-1); t = (struct tftphdr *) pkt; *rtype = ntohs(t->th_opcode); switch (ntohs(t->th_opcode)) { case DATA: { int got; if (htons(t->th_block) != d->xid) { /* * Expected block? */ return (-1); } if (d->xid == 1) { /* * First data packet from new port. */ struct udphdr *uh; uh = (struct udphdr *) pkt - 1; d->destport = uh->uh_sport; } /* else check uh_sport has not changed??? */ got = len - (t->th_data - (char *) t); return got; } case ERROR: if ((unsigned) ntohs(t->th_code) > TFTP_MAX_ERRCODE) { printf("illegal tftp error %d\n", ntohs(t->th_code)); errno = EIO; } else { #ifdef TFTP_DEBUG printf("tftp-error %d\n", ntohs(t->th_code)); #endif errno = tftperrors[ntohs(t->th_code)]; } return (-1); case OACK: { struct udphdr *uh; int tftp_oack_len; /* * Unexpected OACK. TFTP transfer already in progress. * Drop the pkt. */ if (d->xid != 1) { return (-1); } /* * Remember which port this OACK came from, because we need * to send the ACK or errors back to it. */ uh = (struct udphdr *) pkt - 1; d->destport = uh->uh_sport; /* Parse options ACK-ed by the server. */ tftp_oack_len = len - sizeof(t->th_opcode); if (tftp_parse_oack(h, t->th_u.tu_stuff, tftp_oack_len) != 0) { tftp_senderr(h, EOPTNEG, "Malformed OACK"); errno = EIO; return (-1); } return (0); } default: #ifdef TFTP_DEBUG printf("tftp type %d not handled\n", ntohs(t->th_opcode)); #endif return (-1); } }
static void tftp_handler(void *ctx, char *packet, unsigned len) { struct file_priv *priv = ctx; uint16_t proto; uint16_t *s; char *pkt = net_eth_to_udp_payload(packet); struct udphdr *udp = net_eth_to_udphdr(packet); len = net_eth_to_udplen(packet); if (len < 2) return; len -= 2; s = (uint16_t *)pkt; proto = *s++; pkt = (unsigned char *)s; debug("%s: proto 0x%04x\n", __func__, proto); switch (ntohs(proto)) { case TFTP_RRQ: case TFTP_WRQ: default: break; case TFTP_ACK: if (!priv->push) break; priv->block = ntohs(*(uint16_t *)pkt); if (priv->block != priv->last_block) { debug("ack %d != %d\n", priv->block, priv->last_block); break; } priv->block++; tftp_timer_reset(priv); if (priv->state == STATE_LAST) { priv->state = STATE_DONE; break; } priv->tftp_con->udp->uh_dport = udp->uh_sport; priv->state = STATE_WDATA; break; case TFTP_OACK: tftp_parse_oack(priv, pkt, len); priv->server_port = ntohs(udp->uh_sport); priv->tftp_con->udp->uh_dport = udp->uh_sport; if (priv->push) { /* send first block */ priv->state = STATE_WDATA; priv->block = 1; } else { /* send ACK */ priv->state = STATE_OACK; priv->block = 0; tftp_send(priv); } break; case TFTP_DATA: if (len < 2) return; len -= 2; priv->block = ntohs(*(uint16_t *)pkt); if (priv->state == STATE_RRQ || priv->state == STATE_OACK) { /* first block received */ priv->state = STATE_RDATA; priv->tftp_con->udp->uh_dport = udp->uh_sport; priv->server_port = ntohs(udp->uh_sport); priv->last_block = 0; if (priv->block != 1) { /* Assertion */ printf("error: First block is not block 1 (%d)\n", priv->block); priv->err = -EINVAL; priv->state = STATE_DONE; break; } } if (priv->block == priv->last_block) /* Same block again; ignore it. */ break; priv->last_block = priv->block; tftp_timer_reset(priv); kfifo_put(priv->fifo, pkt + 2, len); if (len < priv->blocksize) { tftp_send(priv); priv->err = 0; priv->state = STATE_DONE; } break; case TFTP_ERROR: debug("\nTFTP error: '%s' (%d)\n", pkt + 2, ntohs(*(uint16_t *)pkt)); switch (ntohs(*(uint16_t *)pkt)) { case 1: priv->err = -ENOENT; break; case 2: priv->err = -EACCES; break; default: priv->err = -EINVAL; break; } priv->state = STATE_DONE; break; } }