1*4a5d661aSToomas Soome /* $NetBSD: tftp.c,v 1.4 1997/09/17 16:57:07 drochner Exp $ */ 2*4a5d661aSToomas Soome 3*4a5d661aSToomas Soome /* 4*4a5d661aSToomas Soome * Copyright (c) 1996 5*4a5d661aSToomas Soome * Matthias Drochner. All rights reserved. 6*4a5d661aSToomas Soome * 7*4a5d661aSToomas Soome * Redistribution and use in source and binary forms, with or without 8*4a5d661aSToomas Soome * modification, are permitted provided that the following conditions 9*4a5d661aSToomas Soome * are met: 10*4a5d661aSToomas Soome * 1. Redistributions of source code must retain the above copyright 11*4a5d661aSToomas Soome * notice, this list of conditions and the following disclaimer. 12*4a5d661aSToomas Soome * 2. Redistributions in binary form must reproduce the above copyright 13*4a5d661aSToomas Soome * notice, this list of conditions and the following disclaimer in the 14*4a5d661aSToomas Soome * documentation and/or other materials provided with the distribution. 15*4a5d661aSToomas Soome * 3. All advertising materials mentioning features or use of this software 16*4a5d661aSToomas Soome * must display the following acknowledgement: 17*4a5d661aSToomas Soome * This product includes software developed for the NetBSD Project 18*4a5d661aSToomas Soome * by Matthias Drochner. 19*4a5d661aSToomas Soome * 4. The name of the author may not be used to endorse or promote products 20*4a5d661aSToomas Soome * derived from this software without specific prior written permission. 21*4a5d661aSToomas Soome * 22*4a5d661aSToomas Soome * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23*4a5d661aSToomas Soome * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24*4a5d661aSToomas Soome * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25*4a5d661aSToomas Soome * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26*4a5d661aSToomas Soome * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27*4a5d661aSToomas Soome * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28*4a5d661aSToomas Soome * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29*4a5d661aSToomas Soome * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30*4a5d661aSToomas Soome * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31*4a5d661aSToomas Soome * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32*4a5d661aSToomas Soome */ 33*4a5d661aSToomas Soome 34*4a5d661aSToomas Soome #include <sys/cdefs.h> 35*4a5d661aSToomas Soome __FBSDID("$FreeBSD$"); 36*4a5d661aSToomas Soome 37*4a5d661aSToomas Soome /* 38*4a5d661aSToomas Soome * Simple TFTP implementation for libsa. 39*4a5d661aSToomas Soome * Assumes: 40*4a5d661aSToomas Soome * - socket descriptor (int) at open_file->f_devdata 41*4a5d661aSToomas Soome * - server host IP in global servip 42*4a5d661aSToomas Soome * Restrictions: 43*4a5d661aSToomas Soome * - read only 44*4a5d661aSToomas Soome * - lseek only with SEEK_SET or SEEK_CUR 45*4a5d661aSToomas Soome * - no big time differences between transfers (<tftp timeout) 46*4a5d661aSToomas Soome */ 47*4a5d661aSToomas Soome 48*4a5d661aSToomas Soome #include <sys/types.h> 49*4a5d661aSToomas Soome #include <sys/stat.h> 50*4a5d661aSToomas Soome #include <netinet/in.h> 51*4a5d661aSToomas Soome #include <netinet/udp.h> 52*4a5d661aSToomas Soome #include <netinet/in_systm.h> 53*4a5d661aSToomas Soome #include <arpa/tftp.h> 54*4a5d661aSToomas Soome 55*4a5d661aSToomas Soome #include <string.h> 56*4a5d661aSToomas Soome 57*4a5d661aSToomas Soome #include "stand.h" 58*4a5d661aSToomas Soome #include "net.h" 59*4a5d661aSToomas Soome #include "netif.h" 60*4a5d661aSToomas Soome 61*4a5d661aSToomas Soome #include "tftp.h" 62*4a5d661aSToomas Soome 63*4a5d661aSToomas Soome struct tftp_handle; 64*4a5d661aSToomas Soome 65*4a5d661aSToomas Soome static int tftp_open(const char *path, struct open_file *f); 66*4a5d661aSToomas Soome static int tftp_close(struct open_file *f); 67*4a5d661aSToomas Soome static int tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len); 68*4a5d661aSToomas Soome static int tftp_read(struct open_file *f, void *buf, size_t size, size_t *resid); 69*4a5d661aSToomas Soome static int tftp_write(struct open_file *f, void *buf, size_t size, size_t *resid); 70*4a5d661aSToomas Soome static off_t tftp_seek(struct open_file *f, off_t offset, int where); 71*4a5d661aSToomas Soome static int tftp_set_blksize(struct tftp_handle *h, const char *str); 72*4a5d661aSToomas Soome static int tftp_stat(struct open_file *f, struct stat *sb); 73*4a5d661aSToomas Soome static ssize_t sendrecv_tftp(struct tftp_handle *h, 74*4a5d661aSToomas Soome ssize_t (*sproc)(struct iodesc *, void *, size_t), 75*4a5d661aSToomas Soome void *sbuf, size_t ssize, 76*4a5d661aSToomas Soome ssize_t (*rproc)(struct tftp_handle *h, void *, ssize_t, time_t, unsigned short *), 77*4a5d661aSToomas Soome void *rbuf, size_t rsize, unsigned short *rtype); 78*4a5d661aSToomas Soome 79*4a5d661aSToomas Soome struct fs_ops tftp_fsops = { 80*4a5d661aSToomas Soome "tftp", 81*4a5d661aSToomas Soome tftp_open, 82*4a5d661aSToomas Soome tftp_close, 83*4a5d661aSToomas Soome tftp_read, 84*4a5d661aSToomas Soome tftp_write, 85*4a5d661aSToomas Soome tftp_seek, 86*4a5d661aSToomas Soome tftp_stat, 87*4a5d661aSToomas Soome null_readdir 88*4a5d661aSToomas Soome }; 89*4a5d661aSToomas Soome 90*4a5d661aSToomas Soome extern struct in_addr servip; 91*4a5d661aSToomas Soome 92*4a5d661aSToomas Soome static int tftpport = 2000; 93*4a5d661aSToomas Soome static int is_open = 0; 94*4a5d661aSToomas Soome 95*4a5d661aSToomas Soome /* 96*4a5d661aSToomas Soome * The legacy TFTP_BLKSIZE value was SEGSIZE(512). 97*4a5d661aSToomas Soome * TFTP_REQUESTED_BLKSIZE of 1428 is (Ethernet MTU, less the TFTP, UDP and 98*4a5d661aSToomas Soome * IP header lengths). 99*4a5d661aSToomas Soome */ 100*4a5d661aSToomas Soome #define TFTP_REQUESTED_BLKSIZE 1428 101*4a5d661aSToomas Soome 102*4a5d661aSToomas Soome /* 103*4a5d661aSToomas Soome * Choose a blksize big enough so we can test with Ethernet 104*4a5d661aSToomas Soome * Jumbo frames in the future. 105*4a5d661aSToomas Soome */ 106*4a5d661aSToomas Soome #define TFTP_MAX_BLKSIZE 9008 107*4a5d661aSToomas Soome 108*4a5d661aSToomas Soome struct tftp_handle { 109*4a5d661aSToomas Soome struct iodesc *iodesc; 110*4a5d661aSToomas Soome int currblock; /* contents of lastdata */ 111*4a5d661aSToomas Soome int islastblock; /* flag */ 112*4a5d661aSToomas Soome int validsize; 113*4a5d661aSToomas Soome int off; 114*4a5d661aSToomas Soome char *path; /* saved for re-requests */ 115*4a5d661aSToomas Soome unsigned int tftp_blksize; 116*4a5d661aSToomas Soome unsigned long tftp_tsize; 117*4a5d661aSToomas Soome struct { 118*4a5d661aSToomas Soome u_char header[HEADER_SIZE]; 119*4a5d661aSToomas Soome struct tftphdr t; 120*4a5d661aSToomas Soome u_char space[TFTP_MAX_BLKSIZE]; 121*4a5d661aSToomas Soome } __packed __aligned(4) lastdata; 122*4a5d661aSToomas Soome }; 123*4a5d661aSToomas Soome 124*4a5d661aSToomas Soome #define TFTP_MAX_ERRCODE EOPTNEG 125*4a5d661aSToomas Soome static const int tftperrors[TFTP_MAX_ERRCODE + 1] = { 126*4a5d661aSToomas Soome 0, /* ??? */ 127*4a5d661aSToomas Soome ENOENT, 128*4a5d661aSToomas Soome EPERM, 129*4a5d661aSToomas Soome ENOSPC, 130*4a5d661aSToomas Soome EINVAL, /* ??? */ 131*4a5d661aSToomas Soome EINVAL, /* ??? */ 132*4a5d661aSToomas Soome EEXIST, 133*4a5d661aSToomas Soome EINVAL, /* ??? */ 134*4a5d661aSToomas Soome EINVAL, /* Option negotiation failed. */ 135*4a5d661aSToomas Soome }; 136*4a5d661aSToomas Soome 137*4a5d661aSToomas Soome static int tftp_getnextblock(struct tftp_handle *h); 138*4a5d661aSToomas Soome 139*4a5d661aSToomas Soome /* send error message back. */ 140*4a5d661aSToomas Soome static void 141*4a5d661aSToomas Soome tftp_senderr(struct tftp_handle *h, u_short errcode, const char *msg) 142*4a5d661aSToomas Soome { 143*4a5d661aSToomas Soome struct { 144*4a5d661aSToomas Soome u_char header[HEADER_SIZE]; 145*4a5d661aSToomas Soome struct tftphdr t; 146*4a5d661aSToomas Soome u_char space[63]; /* +1 from t */ 147*4a5d661aSToomas Soome } __packed __aligned(4) wbuf; 148*4a5d661aSToomas Soome char *wtail; 149*4a5d661aSToomas Soome int len; 150*4a5d661aSToomas Soome 151*4a5d661aSToomas Soome len = strlen(msg); 152*4a5d661aSToomas Soome if (len > sizeof(wbuf.space)) 153*4a5d661aSToomas Soome len = sizeof(wbuf.space); 154*4a5d661aSToomas Soome 155*4a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) ERROR); 156*4a5d661aSToomas Soome wbuf.t.th_code = htons(errcode); 157*4a5d661aSToomas Soome 158*4a5d661aSToomas Soome wtail = wbuf.t.th_msg; 159*4a5d661aSToomas Soome bcopy(msg, wtail, len); 160*4a5d661aSToomas Soome wtail[len] = '\0'; 161*4a5d661aSToomas Soome wtail += len + 1; 162*4a5d661aSToomas Soome 163*4a5d661aSToomas Soome sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t); 164*4a5d661aSToomas Soome } 165*4a5d661aSToomas Soome 166*4a5d661aSToomas Soome static void 167*4a5d661aSToomas Soome tftp_sendack(struct tftp_handle *h) 168*4a5d661aSToomas Soome { 169*4a5d661aSToomas Soome struct { 170*4a5d661aSToomas Soome u_char header[HEADER_SIZE]; 171*4a5d661aSToomas Soome struct tftphdr t; 172*4a5d661aSToomas Soome } __packed __aligned(4) wbuf; 173*4a5d661aSToomas Soome char *wtail; 174*4a5d661aSToomas Soome 175*4a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) ACK); 176*4a5d661aSToomas Soome wtail = (char *) &wbuf.t.th_block; 177*4a5d661aSToomas Soome wbuf.t.th_block = htons((u_short) h->currblock); 178*4a5d661aSToomas Soome wtail += 2; 179*4a5d661aSToomas Soome 180*4a5d661aSToomas Soome sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t); 181*4a5d661aSToomas Soome } 182*4a5d661aSToomas Soome 183*4a5d661aSToomas Soome static ssize_t 184*4a5d661aSToomas Soome recvtftp(struct tftp_handle *h, void *pkt, ssize_t len, time_t tleft, 185*4a5d661aSToomas Soome unsigned short *rtype) 186*4a5d661aSToomas Soome { 187*4a5d661aSToomas Soome struct iodesc *d = h->iodesc; 188*4a5d661aSToomas Soome struct tftphdr *t; 189*4a5d661aSToomas Soome 190*4a5d661aSToomas Soome errno = 0; 191*4a5d661aSToomas Soome 192*4a5d661aSToomas Soome len = readudp(d, pkt, len, tleft); 193*4a5d661aSToomas Soome 194*4a5d661aSToomas Soome if (len < 4) 195*4a5d661aSToomas Soome return (-1); 196*4a5d661aSToomas Soome 197*4a5d661aSToomas Soome t = (struct tftphdr *) pkt; 198*4a5d661aSToomas Soome *rtype = ntohs(t->th_opcode); 199*4a5d661aSToomas Soome switch (ntohs(t->th_opcode)) { 200*4a5d661aSToomas Soome case DATA: { 201*4a5d661aSToomas Soome int got; 202*4a5d661aSToomas Soome 203*4a5d661aSToomas Soome if (htons(t->th_block) != (u_short) d->xid) { 204*4a5d661aSToomas Soome /* 205*4a5d661aSToomas Soome * Expected block? 206*4a5d661aSToomas Soome */ 207*4a5d661aSToomas Soome return (-1); 208*4a5d661aSToomas Soome } 209*4a5d661aSToomas Soome if (d->xid == 1) { 210*4a5d661aSToomas Soome /* 211*4a5d661aSToomas Soome * First data packet from new port. 212*4a5d661aSToomas Soome */ 213*4a5d661aSToomas Soome struct udphdr *uh; 214*4a5d661aSToomas Soome uh = (struct udphdr *) pkt - 1; 215*4a5d661aSToomas Soome d->destport = uh->uh_sport; 216*4a5d661aSToomas Soome } /* else check uh_sport has not changed??? */ 217*4a5d661aSToomas Soome got = len - (t->th_data - (char *) t); 218*4a5d661aSToomas Soome return got; 219*4a5d661aSToomas Soome } 220*4a5d661aSToomas Soome case ERROR: 221*4a5d661aSToomas Soome if ((unsigned) ntohs(t->th_code) > TFTP_MAX_ERRCODE) { 222*4a5d661aSToomas Soome printf("illegal tftp error %d\n", ntohs(t->th_code)); 223*4a5d661aSToomas Soome errno = EIO; 224*4a5d661aSToomas Soome } else { 225*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 226*4a5d661aSToomas Soome printf("tftp-error %d\n", ntohs(t->th_code)); 227*4a5d661aSToomas Soome #endif 228*4a5d661aSToomas Soome errno = tftperrors[ntohs(t->th_code)]; 229*4a5d661aSToomas Soome } 230*4a5d661aSToomas Soome return (-1); 231*4a5d661aSToomas Soome case OACK: { 232*4a5d661aSToomas Soome struct udphdr *uh; 233*4a5d661aSToomas Soome int tftp_oack_len; 234*4a5d661aSToomas Soome 235*4a5d661aSToomas Soome /* 236*4a5d661aSToomas Soome * Unexpected OACK. TFTP transfer already in progress. 237*4a5d661aSToomas Soome * Drop the pkt. 238*4a5d661aSToomas Soome */ 239*4a5d661aSToomas Soome if (d->xid != 1) { 240*4a5d661aSToomas Soome return (-1); 241*4a5d661aSToomas Soome } 242*4a5d661aSToomas Soome 243*4a5d661aSToomas Soome /* 244*4a5d661aSToomas Soome * Remember which port this OACK came from, because we need 245*4a5d661aSToomas Soome * to send the ACK or errors back to it. 246*4a5d661aSToomas Soome */ 247*4a5d661aSToomas Soome uh = (struct udphdr *) pkt - 1; 248*4a5d661aSToomas Soome d->destport = uh->uh_sport; 249*4a5d661aSToomas Soome 250*4a5d661aSToomas Soome /* Parse options ACK-ed by the server. */ 251*4a5d661aSToomas Soome tftp_oack_len = len - sizeof(t->th_opcode); 252*4a5d661aSToomas Soome if (tftp_parse_oack(h, t->th_u.tu_stuff, tftp_oack_len) != 0) { 253*4a5d661aSToomas Soome tftp_senderr(h, EOPTNEG, "Malformed OACK"); 254*4a5d661aSToomas Soome errno = EIO; 255*4a5d661aSToomas Soome return (-1); 256*4a5d661aSToomas Soome } 257*4a5d661aSToomas Soome return (0); 258*4a5d661aSToomas Soome } 259*4a5d661aSToomas Soome default: 260*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 261*4a5d661aSToomas Soome printf("tftp type %d not handled\n", ntohs(t->th_opcode)); 262*4a5d661aSToomas Soome #endif 263*4a5d661aSToomas Soome return (-1); 264*4a5d661aSToomas Soome } 265*4a5d661aSToomas Soome } 266*4a5d661aSToomas Soome 267*4a5d661aSToomas Soome /* send request, expect first block (or error) */ 268*4a5d661aSToomas Soome static int 269*4a5d661aSToomas Soome tftp_makereq(struct tftp_handle *h) 270*4a5d661aSToomas Soome { 271*4a5d661aSToomas Soome struct { 272*4a5d661aSToomas Soome u_char header[HEADER_SIZE]; 273*4a5d661aSToomas Soome struct tftphdr t; 274*4a5d661aSToomas Soome u_char space[FNAME_SIZE + 6]; 275*4a5d661aSToomas Soome } __packed __aligned(4) wbuf; 276*4a5d661aSToomas Soome char *wtail; 277*4a5d661aSToomas Soome int l; 278*4a5d661aSToomas Soome ssize_t res; 279*4a5d661aSToomas Soome struct tftphdr *t; 280*4a5d661aSToomas Soome char *tftp_blksize = NULL; 281*4a5d661aSToomas Soome int blksize_l; 282*4a5d661aSToomas Soome unsigned short rtype = 0; 283*4a5d661aSToomas Soome 284*4a5d661aSToomas Soome /* 285*4a5d661aSToomas Soome * Allow overriding default TFTP block size by setting 286*4a5d661aSToomas Soome * a tftp.blksize environment variable. 287*4a5d661aSToomas Soome */ 288*4a5d661aSToomas Soome if ((tftp_blksize = getenv("tftp.blksize")) != NULL) { 289*4a5d661aSToomas Soome tftp_set_blksize(h, tftp_blksize); 290*4a5d661aSToomas Soome } 291*4a5d661aSToomas Soome 292*4a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) RRQ); 293*4a5d661aSToomas Soome wtail = wbuf.t.th_stuff; 294*4a5d661aSToomas Soome l = strlen(h->path); 295*4a5d661aSToomas Soome #ifdef TFTP_PREPEND_PATH 296*4a5d661aSToomas Soome if (l > FNAME_SIZE - (sizeof(TFTP_PREPEND_PATH) - 1)) 297*4a5d661aSToomas Soome return (ENAMETOOLONG); 298*4a5d661aSToomas Soome bcopy(TFTP_PREPEND_PATH, wtail, sizeof(TFTP_PREPEND_PATH) - 1); 299*4a5d661aSToomas Soome wtail += sizeof(TFTP_PREPEND_PATH) - 1; 300*4a5d661aSToomas Soome #else 301*4a5d661aSToomas Soome if (l > FNAME_SIZE) 302*4a5d661aSToomas Soome return (ENAMETOOLONG); 303*4a5d661aSToomas Soome #endif 304*4a5d661aSToomas Soome bcopy(h->path, wtail, l + 1); 305*4a5d661aSToomas Soome wtail += l + 1; 306*4a5d661aSToomas Soome bcopy("octet", wtail, 6); 307*4a5d661aSToomas Soome wtail += 6; 308*4a5d661aSToomas Soome bcopy("blksize", wtail, 8); 309*4a5d661aSToomas Soome wtail += 8; 310*4a5d661aSToomas Soome blksize_l = sprintf(wtail, "%d", h->tftp_blksize); 311*4a5d661aSToomas Soome wtail += blksize_l + 1; 312*4a5d661aSToomas Soome bcopy("tsize", wtail, 6); 313*4a5d661aSToomas Soome wtail += 6; 314*4a5d661aSToomas Soome bcopy("0", wtail, 2); 315*4a5d661aSToomas Soome wtail += 2; 316*4a5d661aSToomas Soome 317*4a5d661aSToomas Soome t = &h->lastdata.t; 318*4a5d661aSToomas Soome 319*4a5d661aSToomas Soome /* h->iodesc->myport = htons(--tftpport); */ 320*4a5d661aSToomas Soome h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff)); 321*4a5d661aSToomas Soome h->iodesc->destport = htons(IPPORT_TFTP); 322*4a5d661aSToomas Soome h->iodesc->xid = 1; /* expected block */ 323*4a5d661aSToomas Soome 324*4a5d661aSToomas Soome h->currblock = 0; 325*4a5d661aSToomas Soome h->islastblock = 0; 326*4a5d661aSToomas Soome h->validsize = 0; 327*4a5d661aSToomas Soome 328*4a5d661aSToomas Soome res = sendrecv_tftp(h, &sendudp, &wbuf.t, wtail - (char *) &wbuf.t, 329*4a5d661aSToomas Soome &recvtftp, t, sizeof(*t) + h->tftp_blksize, &rtype); 330*4a5d661aSToomas Soome 331*4a5d661aSToomas Soome if (rtype == OACK) 332*4a5d661aSToomas Soome return (tftp_getnextblock(h)); 333*4a5d661aSToomas Soome 334*4a5d661aSToomas Soome /* Server ignored our blksize request, revert to TFTP default. */ 335*4a5d661aSToomas Soome h->tftp_blksize = SEGSIZE; 336*4a5d661aSToomas Soome 337*4a5d661aSToomas Soome switch (rtype) { 338*4a5d661aSToomas Soome case DATA: { 339*4a5d661aSToomas Soome h->currblock = 1; 340*4a5d661aSToomas Soome h->validsize = res; 341*4a5d661aSToomas Soome h->islastblock = 0; 342*4a5d661aSToomas Soome if (res < h->tftp_blksize) { 343*4a5d661aSToomas Soome h->islastblock = 1; /* very short file */ 344*4a5d661aSToomas Soome tftp_sendack(h); 345*4a5d661aSToomas Soome } 346*4a5d661aSToomas Soome return (0); 347*4a5d661aSToomas Soome } 348*4a5d661aSToomas Soome case ERROR: 349*4a5d661aSToomas Soome default: 350*4a5d661aSToomas Soome return (errno); 351*4a5d661aSToomas Soome } 352*4a5d661aSToomas Soome 353*4a5d661aSToomas Soome } 354*4a5d661aSToomas Soome 355*4a5d661aSToomas Soome /* ack block, expect next */ 356*4a5d661aSToomas Soome static int 357*4a5d661aSToomas Soome tftp_getnextblock(struct tftp_handle *h) 358*4a5d661aSToomas Soome { 359*4a5d661aSToomas Soome struct { 360*4a5d661aSToomas Soome u_char header[HEADER_SIZE]; 361*4a5d661aSToomas Soome struct tftphdr t; 362*4a5d661aSToomas Soome } __packed __aligned(4) wbuf; 363*4a5d661aSToomas Soome char *wtail; 364*4a5d661aSToomas Soome int res; 365*4a5d661aSToomas Soome struct tftphdr *t; 366*4a5d661aSToomas Soome unsigned short rtype = 0; 367*4a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) ACK); 368*4a5d661aSToomas Soome wtail = (char *) &wbuf.t.th_block; 369*4a5d661aSToomas Soome wbuf.t.th_block = htons((u_short) h->currblock); 370*4a5d661aSToomas Soome wtail += 2; 371*4a5d661aSToomas Soome 372*4a5d661aSToomas Soome t = &h->lastdata.t; 373*4a5d661aSToomas Soome 374*4a5d661aSToomas Soome h->iodesc->xid = h->currblock + 1; /* expected block */ 375*4a5d661aSToomas Soome 376*4a5d661aSToomas Soome res = sendrecv_tftp(h, &sendudp, &wbuf.t, wtail - (char *) &wbuf.t, 377*4a5d661aSToomas Soome &recvtftp, t, sizeof(*t) + h->tftp_blksize, &rtype); 378*4a5d661aSToomas Soome 379*4a5d661aSToomas Soome if (res == -1) /* 0 is OK! */ 380*4a5d661aSToomas Soome return (errno); 381*4a5d661aSToomas Soome 382*4a5d661aSToomas Soome h->currblock++; 383*4a5d661aSToomas Soome h->validsize = res; 384*4a5d661aSToomas Soome if (res < h->tftp_blksize) 385*4a5d661aSToomas Soome h->islastblock = 1; /* EOF */ 386*4a5d661aSToomas Soome 387*4a5d661aSToomas Soome if (h->islastblock == 1) { 388*4a5d661aSToomas Soome /* Send an ACK for the last block */ 389*4a5d661aSToomas Soome wbuf.t.th_block = htons((u_short) h->currblock); 390*4a5d661aSToomas Soome sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t); 391*4a5d661aSToomas Soome } 392*4a5d661aSToomas Soome 393*4a5d661aSToomas Soome return (0); 394*4a5d661aSToomas Soome } 395*4a5d661aSToomas Soome 396*4a5d661aSToomas Soome static int 397*4a5d661aSToomas Soome tftp_open(const char *path, struct open_file *f) 398*4a5d661aSToomas Soome { 399*4a5d661aSToomas Soome struct tftp_handle *tftpfile; 400*4a5d661aSToomas Soome struct iodesc *io; 401*4a5d661aSToomas Soome int res; 402*4a5d661aSToomas Soome size_t pathsize; 403*4a5d661aSToomas Soome const char *extraslash; 404*4a5d661aSToomas Soome 405*4a5d661aSToomas Soome if (strcmp(f->f_dev->dv_name, "net") != 0) { 406*4a5d661aSToomas Soome #ifdef __i386__ 407*4a5d661aSToomas Soome if (strcmp(f->f_dev->dv_name, "pxe") != 0) 408*4a5d661aSToomas Soome return (EINVAL); 409*4a5d661aSToomas Soome #else 410*4a5d661aSToomas Soome return (EINVAL); 411*4a5d661aSToomas Soome #endif 412*4a5d661aSToomas Soome } 413*4a5d661aSToomas Soome 414*4a5d661aSToomas Soome if (is_open) 415*4a5d661aSToomas Soome return (EBUSY); 416*4a5d661aSToomas Soome 417*4a5d661aSToomas Soome tftpfile = (struct tftp_handle *) malloc(sizeof(*tftpfile)); 418*4a5d661aSToomas Soome if (!tftpfile) 419*4a5d661aSToomas Soome return (ENOMEM); 420*4a5d661aSToomas Soome 421*4a5d661aSToomas Soome memset(tftpfile, 0, sizeof(*tftpfile)); 422*4a5d661aSToomas Soome tftpfile->tftp_blksize = TFTP_REQUESTED_BLKSIZE; 423*4a5d661aSToomas Soome tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata)); 424*4a5d661aSToomas Soome if (io == NULL) 425*4a5d661aSToomas Soome return (EINVAL); 426*4a5d661aSToomas Soome 427*4a5d661aSToomas Soome io->destip = servip; 428*4a5d661aSToomas Soome tftpfile->off = 0; 429*4a5d661aSToomas Soome pathsize = (strlen(rootpath) + 1 + strlen(path) + 1) * sizeof(char); 430*4a5d661aSToomas Soome tftpfile->path = malloc(pathsize); 431*4a5d661aSToomas Soome if (tftpfile->path == NULL) { 432*4a5d661aSToomas Soome free(tftpfile); 433*4a5d661aSToomas Soome return(ENOMEM); 434*4a5d661aSToomas Soome } 435*4a5d661aSToomas Soome if (rootpath[strlen(rootpath) - 1] == '/' || path[0] == '/') 436*4a5d661aSToomas Soome extraslash = ""; 437*4a5d661aSToomas Soome else 438*4a5d661aSToomas Soome extraslash = "/"; 439*4a5d661aSToomas Soome res = snprintf(tftpfile->path, pathsize, "%s%s%s", 440*4a5d661aSToomas Soome rootpath, extraslash, path); 441*4a5d661aSToomas Soome if (res < 0 || res > pathsize) { 442*4a5d661aSToomas Soome free(tftpfile->path); 443*4a5d661aSToomas Soome free(tftpfile); 444*4a5d661aSToomas Soome return(ENOMEM); 445*4a5d661aSToomas Soome } 446*4a5d661aSToomas Soome 447*4a5d661aSToomas Soome res = tftp_makereq(tftpfile); 448*4a5d661aSToomas Soome 449*4a5d661aSToomas Soome if (res) { 450*4a5d661aSToomas Soome free(tftpfile->path); 451*4a5d661aSToomas Soome free(tftpfile); 452*4a5d661aSToomas Soome return (res); 453*4a5d661aSToomas Soome } 454*4a5d661aSToomas Soome f->f_fsdata = (void *) tftpfile; 455*4a5d661aSToomas Soome is_open = 1; 456*4a5d661aSToomas Soome return (0); 457*4a5d661aSToomas Soome } 458*4a5d661aSToomas Soome 459*4a5d661aSToomas Soome static int 460*4a5d661aSToomas Soome tftp_read(struct open_file *f, void *addr, size_t size, 461*4a5d661aSToomas Soome size_t *resid /* out */) 462*4a5d661aSToomas Soome { 463*4a5d661aSToomas Soome struct tftp_handle *tftpfile; 464*4a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata; 465*4a5d661aSToomas Soome 466*4a5d661aSToomas Soome while (size > 0) { 467*4a5d661aSToomas Soome int needblock, count; 468*4a5d661aSToomas Soome 469*4a5d661aSToomas Soome twiddle(32); 470*4a5d661aSToomas Soome 471*4a5d661aSToomas Soome needblock = tftpfile->off / tftpfile->tftp_blksize + 1; 472*4a5d661aSToomas Soome 473*4a5d661aSToomas Soome if (tftpfile->currblock > needblock) { /* seek backwards */ 474*4a5d661aSToomas Soome tftp_senderr(tftpfile, 0, "No error: read aborted"); 475*4a5d661aSToomas Soome tftp_makereq(tftpfile); /* no error check, it worked 476*4a5d661aSToomas Soome * for open */ 477*4a5d661aSToomas Soome } 478*4a5d661aSToomas Soome 479*4a5d661aSToomas Soome while (tftpfile->currblock < needblock) { 480*4a5d661aSToomas Soome int res; 481*4a5d661aSToomas Soome 482*4a5d661aSToomas Soome res = tftp_getnextblock(tftpfile); 483*4a5d661aSToomas Soome if (res) { /* no answer */ 484*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 485*4a5d661aSToomas Soome printf("tftp: read error\n"); 486*4a5d661aSToomas Soome #endif 487*4a5d661aSToomas Soome return (res); 488*4a5d661aSToomas Soome } 489*4a5d661aSToomas Soome if (tftpfile->islastblock) 490*4a5d661aSToomas Soome break; 491*4a5d661aSToomas Soome } 492*4a5d661aSToomas Soome 493*4a5d661aSToomas Soome if (tftpfile->currblock == needblock) { 494*4a5d661aSToomas Soome int offinblock, inbuffer; 495*4a5d661aSToomas Soome 496*4a5d661aSToomas Soome offinblock = tftpfile->off % tftpfile->tftp_blksize; 497*4a5d661aSToomas Soome 498*4a5d661aSToomas Soome inbuffer = tftpfile->validsize - offinblock; 499*4a5d661aSToomas Soome if (inbuffer < 0) { 500*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 501*4a5d661aSToomas Soome printf("tftp: invalid offset %d\n", 502*4a5d661aSToomas Soome tftpfile->off); 503*4a5d661aSToomas Soome #endif 504*4a5d661aSToomas Soome return (EINVAL); 505*4a5d661aSToomas Soome } 506*4a5d661aSToomas Soome count = (size < inbuffer ? size : inbuffer); 507*4a5d661aSToomas Soome bcopy(tftpfile->lastdata.t.th_data + offinblock, 508*4a5d661aSToomas Soome addr, count); 509*4a5d661aSToomas Soome 510*4a5d661aSToomas Soome addr = (char *)addr + count; 511*4a5d661aSToomas Soome tftpfile->off += count; 512*4a5d661aSToomas Soome size -= count; 513*4a5d661aSToomas Soome 514*4a5d661aSToomas Soome if ((tftpfile->islastblock) && (count == inbuffer)) 515*4a5d661aSToomas Soome break; /* EOF */ 516*4a5d661aSToomas Soome } else { 517*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 518*4a5d661aSToomas Soome printf("tftp: block %d not found\n", needblock); 519*4a5d661aSToomas Soome #endif 520*4a5d661aSToomas Soome return (EINVAL); 521*4a5d661aSToomas Soome } 522*4a5d661aSToomas Soome 523*4a5d661aSToomas Soome } 524*4a5d661aSToomas Soome 525*4a5d661aSToomas Soome if (resid) 526*4a5d661aSToomas Soome *resid = size; 527*4a5d661aSToomas Soome return (0); 528*4a5d661aSToomas Soome } 529*4a5d661aSToomas Soome 530*4a5d661aSToomas Soome static int 531*4a5d661aSToomas Soome tftp_close(struct open_file *f) 532*4a5d661aSToomas Soome { 533*4a5d661aSToomas Soome struct tftp_handle *tftpfile; 534*4a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata; 535*4a5d661aSToomas Soome 536*4a5d661aSToomas Soome /* let it time out ... */ 537*4a5d661aSToomas Soome 538*4a5d661aSToomas Soome if (tftpfile) { 539*4a5d661aSToomas Soome free(tftpfile->path); 540*4a5d661aSToomas Soome free(tftpfile); 541*4a5d661aSToomas Soome } 542*4a5d661aSToomas Soome is_open = 0; 543*4a5d661aSToomas Soome return (0); 544*4a5d661aSToomas Soome } 545*4a5d661aSToomas Soome 546*4a5d661aSToomas Soome static int 547*4a5d661aSToomas Soome tftp_write(struct open_file *f __unused, void *start __unused, size_t size __unused, 548*4a5d661aSToomas Soome size_t *resid __unused /* out */) 549*4a5d661aSToomas Soome { 550*4a5d661aSToomas Soome return (EROFS); 551*4a5d661aSToomas Soome } 552*4a5d661aSToomas Soome 553*4a5d661aSToomas Soome static int 554*4a5d661aSToomas Soome tftp_stat(struct open_file *f, struct stat *sb) 555*4a5d661aSToomas Soome { 556*4a5d661aSToomas Soome struct tftp_handle *tftpfile; 557*4a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata; 558*4a5d661aSToomas Soome 559*4a5d661aSToomas Soome sb->st_mode = 0444 | S_IFREG; 560*4a5d661aSToomas Soome sb->st_nlink = 1; 561*4a5d661aSToomas Soome sb->st_uid = 0; 562*4a5d661aSToomas Soome sb->st_gid = 0; 563*4a5d661aSToomas Soome sb->st_size = (off_t) tftpfile->tftp_tsize; 564*4a5d661aSToomas Soome return (0); 565*4a5d661aSToomas Soome } 566*4a5d661aSToomas Soome 567*4a5d661aSToomas Soome static off_t 568*4a5d661aSToomas Soome tftp_seek(struct open_file *f, off_t offset, int where) 569*4a5d661aSToomas Soome { 570*4a5d661aSToomas Soome struct tftp_handle *tftpfile; 571*4a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata; 572*4a5d661aSToomas Soome 573*4a5d661aSToomas Soome switch (where) { 574*4a5d661aSToomas Soome case SEEK_SET: 575*4a5d661aSToomas Soome tftpfile->off = offset; 576*4a5d661aSToomas Soome break; 577*4a5d661aSToomas Soome case SEEK_CUR: 578*4a5d661aSToomas Soome tftpfile->off += offset; 579*4a5d661aSToomas Soome break; 580*4a5d661aSToomas Soome default: 581*4a5d661aSToomas Soome errno = EOFFSET; 582*4a5d661aSToomas Soome return (-1); 583*4a5d661aSToomas Soome } 584*4a5d661aSToomas Soome return (tftpfile->off); 585*4a5d661aSToomas Soome } 586*4a5d661aSToomas Soome 587*4a5d661aSToomas Soome static ssize_t 588*4a5d661aSToomas Soome sendrecv_tftp(struct tftp_handle *h, 589*4a5d661aSToomas Soome ssize_t (*sproc)(struct iodesc *, void *, size_t), 590*4a5d661aSToomas Soome void *sbuf, size_t ssize, 591*4a5d661aSToomas Soome ssize_t (*rproc)(struct tftp_handle *, void *, ssize_t, time_t, unsigned short *), 592*4a5d661aSToomas Soome void *rbuf, size_t rsize, unsigned short *rtype) 593*4a5d661aSToomas Soome { 594*4a5d661aSToomas Soome struct iodesc *d = h->iodesc; 595*4a5d661aSToomas Soome ssize_t cc; 596*4a5d661aSToomas Soome time_t t, t1, tleft; 597*4a5d661aSToomas Soome 598*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 599*4a5d661aSToomas Soome if (debug) 600*4a5d661aSToomas Soome printf("sendrecv: called\n"); 601*4a5d661aSToomas Soome #endif 602*4a5d661aSToomas Soome 603*4a5d661aSToomas Soome tleft = MINTMO; 604*4a5d661aSToomas Soome t = t1 = getsecs(); 605*4a5d661aSToomas Soome for (;;) { 606*4a5d661aSToomas Soome if ((getsecs() - t) > MAXTMO) { 607*4a5d661aSToomas Soome errno = ETIMEDOUT; 608*4a5d661aSToomas Soome return -1; 609*4a5d661aSToomas Soome } 610*4a5d661aSToomas Soome 611*4a5d661aSToomas Soome cc = (*sproc)(d, sbuf, ssize); 612*4a5d661aSToomas Soome if (cc != -1 && cc < ssize) 613*4a5d661aSToomas Soome panic("sendrecv: short write! (%zd < %zu)", 614*4a5d661aSToomas Soome cc, ssize); 615*4a5d661aSToomas Soome 616*4a5d661aSToomas Soome if (cc == -1) { 617*4a5d661aSToomas Soome /* Error on transmit; wait before retrying */ 618*4a5d661aSToomas Soome while ((getsecs() - t1) < tleft); 619*4a5d661aSToomas Soome continue; 620*4a5d661aSToomas Soome } 621*4a5d661aSToomas Soome 622*4a5d661aSToomas Soome recvnext: 623*4a5d661aSToomas Soome /* Try to get a packet and process it. */ 624*4a5d661aSToomas Soome cc = (*rproc)(h, rbuf, rsize, tleft, rtype); 625*4a5d661aSToomas Soome /* Return on data, EOF or real error. */ 626*4a5d661aSToomas Soome if (cc != -1 || errno != 0) 627*4a5d661aSToomas Soome return (cc); 628*4a5d661aSToomas Soome if ((getsecs() - t1) < tleft) { 629*4a5d661aSToomas Soome goto recvnext; 630*4a5d661aSToomas Soome } 631*4a5d661aSToomas Soome 632*4a5d661aSToomas Soome /* Timed out or didn't get the packet we're waiting for */ 633*4a5d661aSToomas Soome tleft += MINTMO; 634*4a5d661aSToomas Soome if (tleft > (2 * MINTMO)) { 635*4a5d661aSToomas Soome tleft = (2 * MINTMO); 636*4a5d661aSToomas Soome } 637*4a5d661aSToomas Soome t1 = getsecs(); 638*4a5d661aSToomas Soome } 639*4a5d661aSToomas Soome } 640*4a5d661aSToomas Soome 641*4a5d661aSToomas Soome static int 642*4a5d661aSToomas Soome tftp_set_blksize(struct tftp_handle *h, const char *str) 643*4a5d661aSToomas Soome { 644*4a5d661aSToomas Soome char *endptr; 645*4a5d661aSToomas Soome int new_blksize; 646*4a5d661aSToomas Soome int ret = 0; 647*4a5d661aSToomas Soome 648*4a5d661aSToomas Soome if (h == NULL || str == NULL) 649*4a5d661aSToomas Soome return (ret); 650*4a5d661aSToomas Soome 651*4a5d661aSToomas Soome new_blksize = 652*4a5d661aSToomas Soome (unsigned int)strtol(str, &endptr, 0); 653*4a5d661aSToomas Soome 654*4a5d661aSToomas Soome /* 655*4a5d661aSToomas Soome * Only accept blksize value if it is numeric. 656*4a5d661aSToomas Soome * RFC2348 specifies that acceptable values are 8-65464. 657*4a5d661aSToomas Soome * Let's choose a limit less than MAXRSPACE. 658*4a5d661aSToomas Soome */ 659*4a5d661aSToomas Soome if (*endptr == '\0' && new_blksize >= 8 660*4a5d661aSToomas Soome && new_blksize <= TFTP_MAX_BLKSIZE) { 661*4a5d661aSToomas Soome h->tftp_blksize = new_blksize; 662*4a5d661aSToomas Soome ret = 1; 663*4a5d661aSToomas Soome } 664*4a5d661aSToomas Soome 665*4a5d661aSToomas Soome return (ret); 666*4a5d661aSToomas Soome } 667*4a5d661aSToomas Soome 668*4a5d661aSToomas Soome /* 669*4a5d661aSToomas Soome * In RFC2347, the TFTP Option Acknowledgement package (OACK) 670*4a5d661aSToomas Soome * is used to acknowledge a client's option negotiation request. 671*4a5d661aSToomas Soome * The format of an OACK packet is: 672*4a5d661aSToomas Soome * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 673*4a5d661aSToomas Soome * | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | 674*4a5d661aSToomas Soome * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 675*4a5d661aSToomas Soome * 676*4a5d661aSToomas Soome * opc 677*4a5d661aSToomas Soome * The opcode field contains a 6, for Option Acknowledgment. 678*4a5d661aSToomas Soome * 679*4a5d661aSToomas Soome * opt1 680*4a5d661aSToomas Soome * The first option acknowledgment, copied from the original 681*4a5d661aSToomas Soome * request. 682*4a5d661aSToomas Soome * 683*4a5d661aSToomas Soome * value1 684*4a5d661aSToomas Soome * The acknowledged value associated with the first option. If 685*4a5d661aSToomas Soome * and how this value may differ from the original request is 686*4a5d661aSToomas Soome * detailed in the specification for the option. 687*4a5d661aSToomas Soome * 688*4a5d661aSToomas Soome * optN, valueN 689*4a5d661aSToomas Soome * The final option/value acknowledgment pair. 690*4a5d661aSToomas Soome */ 691*4a5d661aSToomas Soome static int 692*4a5d661aSToomas Soome tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len) 693*4a5d661aSToomas Soome { 694*4a5d661aSToomas Soome /* 695*4a5d661aSToomas Soome * We parse the OACK strings into an array 696*4a5d661aSToomas Soome * of name-value pairs. 697*4a5d661aSToomas Soome */ 698*4a5d661aSToomas Soome char *tftp_options[128] = { 0 }; 699*4a5d661aSToomas Soome char *val = buf; 700*4a5d661aSToomas Soome int i = 0; 701*4a5d661aSToomas Soome int option_idx = 0; 702*4a5d661aSToomas Soome int blksize_is_set = 0; 703*4a5d661aSToomas Soome int tsize = 0; 704*4a5d661aSToomas Soome 705*4a5d661aSToomas Soome unsigned int orig_blksize; 706*4a5d661aSToomas Soome 707*4a5d661aSToomas Soome while (option_idx < 128 && i < len) { 708*4a5d661aSToomas Soome if (buf[i] == '\0') { 709*4a5d661aSToomas Soome if (&buf[i] > val) { 710*4a5d661aSToomas Soome tftp_options[option_idx] = val; 711*4a5d661aSToomas Soome val = &buf[i] + 1; 712*4a5d661aSToomas Soome ++option_idx; 713*4a5d661aSToomas Soome } 714*4a5d661aSToomas Soome } 715*4a5d661aSToomas Soome ++i; 716*4a5d661aSToomas Soome } 717*4a5d661aSToomas Soome 718*4a5d661aSToomas Soome /* Save the block size we requested for sanity check later. */ 719*4a5d661aSToomas Soome orig_blksize = h->tftp_blksize; 720*4a5d661aSToomas Soome 721*4a5d661aSToomas Soome /* 722*4a5d661aSToomas Soome * Parse individual TFTP options. 723*4a5d661aSToomas Soome * * "blksize" is specified in RFC2348. 724*4a5d661aSToomas Soome * * "tsize" is specified in RFC2349. 725*4a5d661aSToomas Soome */ 726*4a5d661aSToomas Soome for (i = 0; i < option_idx; i += 2) { 727*4a5d661aSToomas Soome if (strcasecmp(tftp_options[i], "blksize") == 0) { 728*4a5d661aSToomas Soome if (i + 1 < option_idx) 729*4a5d661aSToomas Soome blksize_is_set = 730*4a5d661aSToomas Soome tftp_set_blksize(h, tftp_options[i + 1]); 731*4a5d661aSToomas Soome } else if (strcasecmp(tftp_options[i], "tsize") == 0) { 732*4a5d661aSToomas Soome if (i + 1 < option_idx) 733*4a5d661aSToomas Soome tsize = strtol(tftp_options[i + 1], (char **)NULL, 10); 734*4a5d661aSToomas Soome if (tsize != 0) 735*4a5d661aSToomas Soome h->tftp_tsize = tsize; 736*4a5d661aSToomas Soome } else { 737*4a5d661aSToomas Soome /* Do not allow any options we did not expect to be ACKed. */ 738*4a5d661aSToomas Soome printf("unexpected tftp option '%s'\n", tftp_options[i]); 739*4a5d661aSToomas Soome return (-1); 740*4a5d661aSToomas Soome } 741*4a5d661aSToomas Soome } 742*4a5d661aSToomas Soome 743*4a5d661aSToomas Soome if (!blksize_is_set) { 744*4a5d661aSToomas Soome /* 745*4a5d661aSToomas Soome * If TFTP blksize was not set, try defaulting 746*4a5d661aSToomas Soome * to the legacy TFTP blksize of SEGSIZE(512) 747*4a5d661aSToomas Soome */ 748*4a5d661aSToomas Soome h->tftp_blksize = SEGSIZE; 749*4a5d661aSToomas Soome } else if (h->tftp_blksize > orig_blksize) { 750*4a5d661aSToomas Soome /* 751*4a5d661aSToomas Soome * Server should not be proposing block sizes that 752*4a5d661aSToomas Soome * exceed what we said we can handle. 753*4a5d661aSToomas Soome */ 754*4a5d661aSToomas Soome printf("unexpected blksize %u\n", h->tftp_blksize); 755*4a5d661aSToomas Soome return (-1); 756*4a5d661aSToomas Soome } 757*4a5d661aSToomas Soome 758*4a5d661aSToomas Soome #ifdef TFTP_DEBUG 759*4a5d661aSToomas Soome printf("tftp_blksize: %u\n", h->tftp_blksize); 760*4a5d661aSToomas Soome printf("tftp_tsize: %lu\n", h->tftp_tsize); 761*4a5d661aSToomas Soome #endif 762*4a5d661aSToomas Soome return 0; 763*4a5d661aSToomas Soome } 764