14a5d661aSToomas Soome /* $NetBSD: tftp.c,v 1.4 1997/09/17 16:57:07 drochner Exp $ */
24a5d661aSToomas Soome
34a5d661aSToomas Soome /*
44a5d661aSToomas Soome * Copyright (c) 1996
54a5d661aSToomas Soome * Matthias Drochner. All rights reserved.
64a5d661aSToomas Soome *
74a5d661aSToomas Soome * Redistribution and use in source and binary forms, with or without
84a5d661aSToomas Soome * modification, are permitted provided that the following conditions
94a5d661aSToomas Soome * are met:
104a5d661aSToomas Soome * 1. Redistributions of source code must retain the above copyright
114a5d661aSToomas Soome * notice, this list of conditions and the following disclaimer.
124a5d661aSToomas Soome * 2. Redistributions in binary form must reproduce the above copyright
134a5d661aSToomas Soome * notice, this list of conditions and the following disclaimer in the
144a5d661aSToomas Soome * documentation and/or other materials provided with the distribution.
154a5d661aSToomas Soome * 3. All advertising materials mentioning features or use of this software
164a5d661aSToomas Soome * must display the following acknowledgement:
174a5d661aSToomas Soome * This product includes software developed for the NetBSD Project
184a5d661aSToomas Soome * by Matthias Drochner.
194a5d661aSToomas Soome * 4. The name of the author may not be used to endorse or promote products
204a5d661aSToomas Soome * derived from this software without specific prior written permission.
214a5d661aSToomas Soome *
224a5d661aSToomas Soome * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
234a5d661aSToomas Soome * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
244a5d661aSToomas Soome * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
254a5d661aSToomas Soome * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
264a5d661aSToomas Soome * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
274a5d661aSToomas Soome * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
284a5d661aSToomas Soome * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
294a5d661aSToomas Soome * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
304a5d661aSToomas Soome * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
314a5d661aSToomas Soome * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
324a5d661aSToomas Soome */
334a5d661aSToomas Soome
344a5d661aSToomas Soome #include <sys/cdefs.h>
354a5d661aSToomas Soome
364a5d661aSToomas Soome /*
374a5d661aSToomas Soome * Simple TFTP implementation for libsa.
384a5d661aSToomas Soome * Assumes:
394a5d661aSToomas Soome * - socket descriptor (int) at open_file->f_devdata
404a5d661aSToomas Soome * - server host IP in global servip
414a5d661aSToomas Soome * Restrictions:
424a5d661aSToomas Soome * - read only
434a5d661aSToomas Soome * - lseek only with SEEK_SET or SEEK_CUR
444a5d661aSToomas Soome * - no big time differences between transfers (<tftp timeout)
454a5d661aSToomas Soome */
464a5d661aSToomas Soome
474a5d661aSToomas Soome #include <sys/types.h>
484a5d661aSToomas Soome #include <sys/stat.h>
494a5d661aSToomas Soome #include <netinet/in.h>
504a5d661aSToomas Soome #include <netinet/udp.h>
514a5d661aSToomas Soome #include <netinet/in_systm.h>
524a5d661aSToomas Soome #include <arpa/tftp.h>
534a5d661aSToomas Soome
544a5d661aSToomas Soome #include <string.h>
554a5d661aSToomas Soome
564a5d661aSToomas Soome #include "stand.h"
574a5d661aSToomas Soome #include "net.h"
584a5d661aSToomas Soome #include "netif.h"
594a5d661aSToomas Soome
604a5d661aSToomas Soome #include "tftp.h"
614a5d661aSToomas Soome
624a5d661aSToomas Soome struct tftp_handle;
634a5d661aSToomas Soome
644a5d661aSToomas Soome static int tftp_open(const char *path, struct open_file *f);
654a5d661aSToomas Soome static int tftp_close(struct open_file *f);
664a5d661aSToomas Soome static int tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len);
674a5d661aSToomas Soome static int tftp_read(struct open_file *f, void *buf, size_t size, size_t *resid);
684a5d661aSToomas Soome static int tftp_write(struct open_file *f, void *buf, size_t size, size_t *resid);
694a5d661aSToomas Soome static off_t tftp_seek(struct open_file *f, off_t offset, int where);
704a5d661aSToomas Soome static int tftp_set_blksize(struct tftp_handle *h, const char *str);
714a5d661aSToomas Soome static int tftp_stat(struct open_file *f, struct stat *sb);
724a5d661aSToomas Soome static ssize_t sendrecv_tftp(struct tftp_handle *h,
734a5d661aSToomas Soome ssize_t (*sproc)(struct iodesc *, void *, size_t),
744a5d661aSToomas Soome void *sbuf, size_t ssize,
757b2a1233SToomas Soome ssize_t (*rproc)(struct tftp_handle *h, void **, void **, time_t, unsigned short *),
767b2a1233SToomas Soome void **, void **, unsigned short *rtype);
774a5d661aSToomas Soome
784a5d661aSToomas Soome struct fs_ops tftp_fsops = {
794a5d661aSToomas Soome "tftp",
804a5d661aSToomas Soome tftp_open,
814a5d661aSToomas Soome tftp_close,
824a5d661aSToomas Soome tftp_read,
834a5d661aSToomas Soome tftp_write,
844a5d661aSToomas Soome tftp_seek,
854a5d661aSToomas Soome tftp_stat,
864a5d661aSToomas Soome null_readdir
874a5d661aSToomas Soome };
884a5d661aSToomas Soome
894a5d661aSToomas Soome extern struct in_addr servip;
904a5d661aSToomas Soome
914a5d661aSToomas Soome static int tftpport = 2000;
924a5d661aSToomas Soome static int is_open = 0;
934a5d661aSToomas Soome
944a5d661aSToomas Soome /*
954a5d661aSToomas Soome * The legacy TFTP_BLKSIZE value was SEGSIZE(512).
964a5d661aSToomas Soome * TFTP_REQUESTED_BLKSIZE of 1428 is (Ethernet MTU, less the TFTP, UDP and
974a5d661aSToomas Soome * IP header lengths).
984a5d661aSToomas Soome */
994a5d661aSToomas Soome #define TFTP_REQUESTED_BLKSIZE 1428
1004a5d661aSToomas Soome
1014a5d661aSToomas Soome /*
1024a5d661aSToomas Soome * Choose a blksize big enough so we can test with Ethernet
1034a5d661aSToomas Soome * Jumbo frames in the future.
1044a5d661aSToomas Soome */
1054a5d661aSToomas Soome #define TFTP_MAX_BLKSIZE 9008
1064a5d661aSToomas Soome
1074a5d661aSToomas Soome struct tftp_handle {
1084a5d661aSToomas Soome struct iodesc *iodesc;
1094a5d661aSToomas Soome int currblock; /* contents of lastdata */
1104a5d661aSToomas Soome int islastblock; /* flag */
1114a5d661aSToomas Soome int validsize;
1124a5d661aSToomas Soome int off;
1134a5d661aSToomas Soome char *path; /* saved for re-requests */
1144a5d661aSToomas Soome unsigned int tftp_blksize;
1154a5d661aSToomas Soome unsigned long tftp_tsize;
1167b2a1233SToomas Soome void *pkt;
1177b2a1233SToomas Soome struct tftphdr *tftp_hdr;
1184a5d661aSToomas Soome };
1194a5d661aSToomas Soome
1204a5d661aSToomas Soome #define TFTP_MAX_ERRCODE EOPTNEG
1214a5d661aSToomas Soome static const int tftperrors[TFTP_MAX_ERRCODE + 1] = {
1224a5d661aSToomas Soome 0, /* ??? */
1234a5d661aSToomas Soome ENOENT,
1244a5d661aSToomas Soome EPERM,
1254a5d661aSToomas Soome ENOSPC,
1264a5d661aSToomas Soome EINVAL, /* ??? */
1274a5d661aSToomas Soome EINVAL, /* ??? */
1284a5d661aSToomas Soome EEXIST,
1294a5d661aSToomas Soome EINVAL, /* ??? */
1304a5d661aSToomas Soome EINVAL, /* Option negotiation failed. */
1314a5d661aSToomas Soome };
1324a5d661aSToomas Soome
1334a5d661aSToomas Soome static int tftp_getnextblock(struct tftp_handle *h);
1344a5d661aSToomas Soome
1354a5d661aSToomas Soome /* send error message back. */
1364a5d661aSToomas Soome static void
tftp_senderr(struct tftp_handle * h,u_short errcode,const char * msg)1374a5d661aSToomas Soome tftp_senderr(struct tftp_handle *h, u_short errcode, const char *msg)
1384a5d661aSToomas Soome {
1394a5d661aSToomas Soome struct {
1404a5d661aSToomas Soome u_char header[HEADER_SIZE];
1414a5d661aSToomas Soome struct tftphdr t;
1424a5d661aSToomas Soome u_char space[63]; /* +1 from t */
1434a5d661aSToomas Soome } __packed __aligned(4) wbuf;
1444a5d661aSToomas Soome char *wtail;
1454a5d661aSToomas Soome int len;
1464a5d661aSToomas Soome
1474a5d661aSToomas Soome len = strlen(msg);
1484a5d661aSToomas Soome if (len > sizeof(wbuf.space))
1494a5d661aSToomas Soome len = sizeof(wbuf.space);
1504a5d661aSToomas Soome
1514a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) ERROR);
1524a5d661aSToomas Soome wbuf.t.th_code = htons(errcode);
1534a5d661aSToomas Soome
1544a5d661aSToomas Soome wtail = wbuf.t.th_msg;
1554a5d661aSToomas Soome bcopy(msg, wtail, len);
1564a5d661aSToomas Soome wtail[len] = '\0';
1574a5d661aSToomas Soome wtail += len + 1;
1584a5d661aSToomas Soome
1594a5d661aSToomas Soome sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t);
1604a5d661aSToomas Soome }
1614a5d661aSToomas Soome
1624a5d661aSToomas Soome static void
tftp_sendack(struct tftp_handle * h)1634a5d661aSToomas Soome tftp_sendack(struct tftp_handle *h)
1644a5d661aSToomas Soome {
1654a5d661aSToomas Soome struct {
1664a5d661aSToomas Soome u_char header[HEADER_SIZE];
1674a5d661aSToomas Soome struct tftphdr t;
1684a5d661aSToomas Soome } __packed __aligned(4) wbuf;
1694a5d661aSToomas Soome char *wtail;
1704a5d661aSToomas Soome
1714a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) ACK);
1724a5d661aSToomas Soome wtail = (char *) &wbuf.t.th_block;
1734a5d661aSToomas Soome wbuf.t.th_block = htons((u_short) h->currblock);
1744a5d661aSToomas Soome wtail += 2;
1754a5d661aSToomas Soome
1764a5d661aSToomas Soome sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t);
1774a5d661aSToomas Soome }
1784a5d661aSToomas Soome
1794a5d661aSToomas Soome static ssize_t
recvtftp(struct tftp_handle * h,void ** pkt,void ** payload,time_t tleft,unsigned short * rtype)1807b2a1233SToomas Soome recvtftp(struct tftp_handle *h, void **pkt, void **payload, time_t tleft,
1814a5d661aSToomas Soome unsigned short *rtype)
1824a5d661aSToomas Soome {
1834a5d661aSToomas Soome struct iodesc *d = h->iodesc;
1844a5d661aSToomas Soome struct tftphdr *t;
1857b2a1233SToomas Soome void *ptr = NULL;
1867b2a1233SToomas Soome ssize_t len;
1874a5d661aSToomas Soome
1884a5d661aSToomas Soome errno = 0;
1894a5d661aSToomas Soome
1907b2a1233SToomas Soome len = readudp(d, &ptr, (void **)&t, tleft);
1914a5d661aSToomas Soome
1927b2a1233SToomas Soome if (len < 4) {
1937b2a1233SToomas Soome free(ptr);
1944a5d661aSToomas Soome return (-1);
1957b2a1233SToomas Soome }
1964a5d661aSToomas Soome
1974a5d661aSToomas Soome *rtype = ntohs(t->th_opcode);
1984a5d661aSToomas Soome switch (ntohs(t->th_opcode)) {
1994a5d661aSToomas Soome case DATA: {
2004a5d661aSToomas Soome int got;
2014a5d661aSToomas Soome
2024a5d661aSToomas Soome if (htons(t->th_block) != (u_short) d->xid) {
2034a5d661aSToomas Soome /*
2044a5d661aSToomas Soome * Expected block?
2054a5d661aSToomas Soome */
2067b2a1233SToomas Soome free(ptr);
2074a5d661aSToomas Soome return (-1);
2084a5d661aSToomas Soome }
2094a5d661aSToomas Soome if (d->xid == 1) {
2104a5d661aSToomas Soome /*
2114a5d661aSToomas Soome * First data packet from new port.
2124a5d661aSToomas Soome */
2134a5d661aSToomas Soome struct udphdr *uh;
2147b2a1233SToomas Soome uh = (struct udphdr *) t - 1;
2154a5d661aSToomas Soome d->destport = uh->uh_sport;
2164a5d661aSToomas Soome } /* else check uh_sport has not changed??? */
2174a5d661aSToomas Soome got = len - (t->th_data - (char *)t);
2187b2a1233SToomas Soome *pkt = ptr;
2197b2a1233SToomas Soome *payload = t;
2207b2a1233SToomas Soome return (got);
2214a5d661aSToomas Soome }
2224a5d661aSToomas Soome case ERROR:
2234a5d661aSToomas Soome if ((unsigned) ntohs(t->th_code) > TFTP_MAX_ERRCODE) {
2244a5d661aSToomas Soome printf("illegal tftp error %d\n", ntohs(t->th_code));
2254a5d661aSToomas Soome errno = EIO;
2264a5d661aSToomas Soome } else {
2274a5d661aSToomas Soome #ifdef TFTP_DEBUG
2284a5d661aSToomas Soome printf("tftp-error %d\n", ntohs(t->th_code));
2294a5d661aSToomas Soome #endif
2304a5d661aSToomas Soome errno = tftperrors[ntohs(t->th_code)];
2314a5d661aSToomas Soome }
2327b2a1233SToomas Soome free(ptr);
2334a5d661aSToomas Soome return (-1);
2344a5d661aSToomas Soome case OACK: {
2354a5d661aSToomas Soome struct udphdr *uh;
2364a5d661aSToomas Soome int tftp_oack_len;
2374a5d661aSToomas Soome
2384a5d661aSToomas Soome /*
2394a5d661aSToomas Soome * Unexpected OACK. TFTP transfer already in progress.
2404a5d661aSToomas Soome * Drop the pkt.
2414a5d661aSToomas Soome */
2424a5d661aSToomas Soome if (d->xid != 1) {
2437b2a1233SToomas Soome free(ptr);
2444a5d661aSToomas Soome return (-1);
2454a5d661aSToomas Soome }
2464a5d661aSToomas Soome
2474a5d661aSToomas Soome /*
2484a5d661aSToomas Soome * Remember which port this OACK came from, because we need
2494a5d661aSToomas Soome * to send the ACK or errors back to it.
2504a5d661aSToomas Soome */
2517b2a1233SToomas Soome uh = (struct udphdr *) t - 1;
2524a5d661aSToomas Soome d->destport = uh->uh_sport;
2534a5d661aSToomas Soome
2544a5d661aSToomas Soome /* Parse options ACK-ed by the server. */
2554a5d661aSToomas Soome tftp_oack_len = len - sizeof(t->th_opcode);
2564a5d661aSToomas Soome if (tftp_parse_oack(h, t->th_u.tu_stuff, tftp_oack_len) != 0) {
2574a5d661aSToomas Soome tftp_senderr(h, EOPTNEG, "Malformed OACK");
2584a5d661aSToomas Soome errno = EIO;
2597b2a1233SToomas Soome free(ptr);
2604a5d661aSToomas Soome return (-1);
2614a5d661aSToomas Soome }
2627b2a1233SToomas Soome *pkt = ptr;
2637b2a1233SToomas Soome *payload = t;
2644a5d661aSToomas Soome return (0);
2654a5d661aSToomas Soome }
2664a5d661aSToomas Soome default:
2674a5d661aSToomas Soome #ifdef TFTP_DEBUG
2684a5d661aSToomas Soome printf("tftp type %d not handled\n", ntohs(t->th_opcode));
2694a5d661aSToomas Soome #endif
2707b2a1233SToomas Soome free(ptr);
2714a5d661aSToomas Soome return (-1);
2724a5d661aSToomas Soome }
2734a5d661aSToomas Soome }
2744a5d661aSToomas Soome
2754a5d661aSToomas Soome /* send request, expect first block (or error) */
2764a5d661aSToomas Soome static int
tftp_makereq(struct tftp_handle * h)2774a5d661aSToomas Soome tftp_makereq(struct tftp_handle *h)
2784a5d661aSToomas Soome {
2794a5d661aSToomas Soome struct {
2804a5d661aSToomas Soome u_char header[HEADER_SIZE];
2814a5d661aSToomas Soome struct tftphdr t;
2824a5d661aSToomas Soome u_char space[FNAME_SIZE + 6];
2834a5d661aSToomas Soome } __packed __aligned(4) wbuf;
2844a5d661aSToomas Soome char *wtail;
2854a5d661aSToomas Soome int l;
2864a5d661aSToomas Soome ssize_t res;
2877b2a1233SToomas Soome void *pkt;
2884a5d661aSToomas Soome struct tftphdr *t;
2894a5d661aSToomas Soome char *tftp_blksize = NULL;
2904a5d661aSToomas Soome int blksize_l;
2914a5d661aSToomas Soome unsigned short rtype = 0;
2924a5d661aSToomas Soome
2934a5d661aSToomas Soome /*
2944a5d661aSToomas Soome * Allow overriding default TFTP block size by setting
2954a5d661aSToomas Soome * a tftp.blksize environment variable.
2964a5d661aSToomas Soome */
2974a5d661aSToomas Soome if ((tftp_blksize = getenv("tftp.blksize")) != NULL) {
2984a5d661aSToomas Soome tftp_set_blksize(h, tftp_blksize);
2994a5d661aSToomas Soome }
3004a5d661aSToomas Soome
3014a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) RRQ);
3024a5d661aSToomas Soome wtail = wbuf.t.th_stuff;
3034a5d661aSToomas Soome l = strlen(h->path);
3044a5d661aSToomas Soome #ifdef TFTP_PREPEND_PATH
3054a5d661aSToomas Soome if (l > FNAME_SIZE - (sizeof(TFTP_PREPEND_PATH) - 1))
3064a5d661aSToomas Soome return (ENAMETOOLONG);
3074a5d661aSToomas Soome bcopy(TFTP_PREPEND_PATH, wtail, sizeof(TFTP_PREPEND_PATH) - 1);
3084a5d661aSToomas Soome wtail += sizeof(TFTP_PREPEND_PATH) - 1;
3094a5d661aSToomas Soome #else
3104a5d661aSToomas Soome if (l > FNAME_SIZE)
3114a5d661aSToomas Soome return (ENAMETOOLONG);
3124a5d661aSToomas Soome #endif
3134a5d661aSToomas Soome bcopy(h->path, wtail, l + 1);
3144a5d661aSToomas Soome wtail += l + 1;
3154a5d661aSToomas Soome bcopy("octet", wtail, 6);
3164a5d661aSToomas Soome wtail += 6;
3174a5d661aSToomas Soome bcopy("blksize", wtail, 8);
3184a5d661aSToomas Soome wtail += 8;
3194a5d661aSToomas Soome blksize_l = sprintf(wtail, "%d", h->tftp_blksize);
3204a5d661aSToomas Soome wtail += blksize_l + 1;
3214a5d661aSToomas Soome bcopy("tsize", wtail, 6);
3224a5d661aSToomas Soome wtail += 6;
3234a5d661aSToomas Soome bcopy("0", wtail, 2);
3244a5d661aSToomas Soome wtail += 2;
3254a5d661aSToomas Soome
3264a5d661aSToomas Soome /* h->iodesc->myport = htons(--tftpport); */
3274a5d661aSToomas Soome h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
3284a5d661aSToomas Soome h->iodesc->destport = htons(IPPORT_TFTP);
3294a5d661aSToomas Soome h->iodesc->xid = 1; /* expected block */
3304a5d661aSToomas Soome
3314a5d661aSToomas Soome h->currblock = 0;
3324a5d661aSToomas Soome h->islastblock = 0;
3334a5d661aSToomas Soome h->validsize = 0;
3344a5d661aSToomas Soome
3357b2a1233SToomas Soome pkt = NULL;
3364a5d661aSToomas Soome res = sendrecv_tftp(h, &sendudp, &wbuf.t, wtail - (char *) &wbuf.t,
3377b2a1233SToomas Soome &recvtftp, &pkt, (void **)&t, &rtype);
3387b2a1233SToomas Soome if (res == -1) {
3397b2a1233SToomas Soome free(pkt);
3407b2a1233SToomas Soome return (errno);
3417b2a1233SToomas Soome }
3427b2a1233SToomas Soome
3437b2a1233SToomas Soome free(h->pkt);
3447b2a1233SToomas Soome h->pkt = pkt;
3457b2a1233SToomas Soome h->tftp_hdr = t;
3464a5d661aSToomas Soome
3474a5d661aSToomas Soome if (rtype == OACK)
3484a5d661aSToomas Soome return (tftp_getnextblock(h));
3494a5d661aSToomas Soome
3504a5d661aSToomas Soome /* Server ignored our blksize request, revert to TFTP default. */
3514a5d661aSToomas Soome h->tftp_blksize = SEGSIZE;
3524a5d661aSToomas Soome
3534a5d661aSToomas Soome switch (rtype) {
3544a5d661aSToomas Soome case DATA: {
3554a5d661aSToomas Soome h->currblock = 1;
3564a5d661aSToomas Soome h->validsize = res;
3574a5d661aSToomas Soome h->islastblock = 0;
3584a5d661aSToomas Soome if (res < h->tftp_blksize) {
3594a5d661aSToomas Soome h->islastblock = 1; /* very short file */
3604a5d661aSToomas Soome tftp_sendack(h);
3614a5d661aSToomas Soome }
3624a5d661aSToomas Soome return (0);
3634a5d661aSToomas Soome }
3644a5d661aSToomas Soome case ERROR:
3654a5d661aSToomas Soome default:
3664a5d661aSToomas Soome return (errno);
3674a5d661aSToomas Soome }
3684a5d661aSToomas Soome
3694a5d661aSToomas Soome }
3704a5d661aSToomas Soome
3714a5d661aSToomas Soome /* ack block, expect next */
3724a5d661aSToomas Soome static int
tftp_getnextblock(struct tftp_handle * h)3734a5d661aSToomas Soome tftp_getnextblock(struct tftp_handle *h)
3744a5d661aSToomas Soome {
3754a5d661aSToomas Soome struct {
3764a5d661aSToomas Soome u_char header[HEADER_SIZE];
3774a5d661aSToomas Soome struct tftphdr t;
3784a5d661aSToomas Soome } __packed __aligned(4) wbuf;
3794a5d661aSToomas Soome char *wtail;
3804a5d661aSToomas Soome int res;
3817b2a1233SToomas Soome void *pkt;
3824a5d661aSToomas Soome struct tftphdr *t;
3834a5d661aSToomas Soome unsigned short rtype = 0;
3844a5d661aSToomas Soome wbuf.t.th_opcode = htons((u_short) ACK);
3854a5d661aSToomas Soome wtail = (char *) &wbuf.t.th_block;
3864a5d661aSToomas Soome wbuf.t.th_block = htons((u_short) h->currblock);
3874a5d661aSToomas Soome wtail += 2;
3884a5d661aSToomas Soome
3894a5d661aSToomas Soome h->iodesc->xid = h->currblock + 1; /* expected block */
3904a5d661aSToomas Soome
3917b2a1233SToomas Soome pkt = NULL;
3924a5d661aSToomas Soome res = sendrecv_tftp(h, &sendudp, &wbuf.t, wtail - (char *) &wbuf.t,
3937b2a1233SToomas Soome &recvtftp, &pkt, (void **)&t, &rtype);
3944a5d661aSToomas Soome
3957b2a1233SToomas Soome if (res == -1) { /* 0 is OK! */
3967b2a1233SToomas Soome free(pkt);
3974a5d661aSToomas Soome return (errno);
3987b2a1233SToomas Soome }
3994a5d661aSToomas Soome
4007b2a1233SToomas Soome free(h->pkt);
4017b2a1233SToomas Soome h->pkt = pkt;
4027b2a1233SToomas Soome h->tftp_hdr = t;
4034a5d661aSToomas Soome h->currblock++;
4044a5d661aSToomas Soome h->validsize = res;
4054a5d661aSToomas Soome if (res < h->tftp_blksize)
4064a5d661aSToomas Soome h->islastblock = 1; /* EOF */
4074a5d661aSToomas Soome
4084a5d661aSToomas Soome if (h->islastblock == 1) {
4094a5d661aSToomas Soome /* Send an ACK for the last block */
4104a5d661aSToomas Soome wbuf.t.th_block = htons((u_short) h->currblock);
4114a5d661aSToomas Soome sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
4124a5d661aSToomas Soome }
4134a5d661aSToomas Soome
4144a5d661aSToomas Soome return (0);
4154a5d661aSToomas Soome }
4164a5d661aSToomas Soome
4174a5d661aSToomas Soome static int
tftp_open(const char * path,struct open_file * f)4184a5d661aSToomas Soome tftp_open(const char *path, struct open_file *f)
4194a5d661aSToomas Soome {
4204a5d661aSToomas Soome struct tftp_handle *tftpfile;
4214a5d661aSToomas Soome struct iodesc *io;
4224a5d661aSToomas Soome int res;
4234a5d661aSToomas Soome size_t pathsize;
4244a5d661aSToomas Soome const char *extraslash;
4254a5d661aSToomas Soome
42618609d04SToomas Soome if (netproto != NET_TFTP)
42718609d04SToomas Soome return (EINVAL);
42818609d04SToomas Soome
4297b2a1233SToomas Soome if (f->f_dev->dv_type != DEVT_NET)
4304a5d661aSToomas Soome return (EINVAL);
4314a5d661aSToomas Soome
4324a5d661aSToomas Soome if (is_open)
4334a5d661aSToomas Soome return (EBUSY);
4344a5d661aSToomas Soome
4354a5d661aSToomas Soome tftpfile = (struct tftp_handle *) malloc(sizeof(*tftpfile));
4364a5d661aSToomas Soome if (!tftpfile)
4374a5d661aSToomas Soome return (ENOMEM);
4384a5d661aSToomas Soome
4394a5d661aSToomas Soome memset(tftpfile, 0, sizeof(*tftpfile));
4404a5d661aSToomas Soome tftpfile->tftp_blksize = TFTP_REQUESTED_BLKSIZE;
4414a5d661aSToomas Soome tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata));
4424a5d661aSToomas Soome if (io == NULL)
4434a5d661aSToomas Soome return (EINVAL);
4444a5d661aSToomas Soome
4454a5d661aSToomas Soome io->destip = servip;
4464a5d661aSToomas Soome tftpfile->off = 0;
4474a5d661aSToomas Soome pathsize = (strlen(rootpath) + 1 + strlen(path) + 1) * sizeof(char);
4484a5d661aSToomas Soome tftpfile->path = malloc(pathsize);
4494a5d661aSToomas Soome if (tftpfile->path == NULL) {
4504a5d661aSToomas Soome free(tftpfile);
4514a5d661aSToomas Soome return(ENOMEM);
4524a5d661aSToomas Soome }
4534a5d661aSToomas Soome if (rootpath[strlen(rootpath) - 1] == '/' || path[0] == '/')
4544a5d661aSToomas Soome extraslash = "";
4554a5d661aSToomas Soome else
4564a5d661aSToomas Soome extraslash = "/";
4574a5d661aSToomas Soome res = snprintf(tftpfile->path, pathsize, "%s%s%s",
4584a5d661aSToomas Soome rootpath, extraslash, path);
4594a5d661aSToomas Soome if (res < 0 || res > pathsize) {
4604a5d661aSToomas Soome free(tftpfile->path);
4614a5d661aSToomas Soome free(tftpfile);
4624a5d661aSToomas Soome return(ENOMEM);
4634a5d661aSToomas Soome }
4644a5d661aSToomas Soome
4654a5d661aSToomas Soome res = tftp_makereq(tftpfile);
4664a5d661aSToomas Soome
4674a5d661aSToomas Soome if (res) {
4684a5d661aSToomas Soome free(tftpfile->path);
4697b2a1233SToomas Soome free(tftpfile->pkt);
4704a5d661aSToomas Soome free(tftpfile);
4714a5d661aSToomas Soome return (res);
4724a5d661aSToomas Soome }
4734a5d661aSToomas Soome f->f_fsdata = (void *) tftpfile;
4744a5d661aSToomas Soome is_open = 1;
4754a5d661aSToomas Soome return (0);
4764a5d661aSToomas Soome }
4774a5d661aSToomas Soome
4784a5d661aSToomas Soome static int
tftp_read(struct open_file * f,void * addr,size_t size,size_t * resid)4794a5d661aSToomas Soome tftp_read(struct open_file *f, void *addr, size_t size,
4804a5d661aSToomas Soome size_t *resid /* out */)
4814a5d661aSToomas Soome {
4824a5d661aSToomas Soome struct tftp_handle *tftpfile;
4834a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata;
4844a5d661aSToomas Soome
4854a5d661aSToomas Soome while (size > 0) {
4864a5d661aSToomas Soome int needblock, count;
4874a5d661aSToomas Soome
4884a5d661aSToomas Soome twiddle(32);
4894a5d661aSToomas Soome
4904a5d661aSToomas Soome needblock = tftpfile->off / tftpfile->tftp_blksize + 1;
4914a5d661aSToomas Soome
4924a5d661aSToomas Soome if (tftpfile->currblock > needblock) { /* seek backwards */
4934a5d661aSToomas Soome tftp_senderr(tftpfile, 0, "No error: read aborted");
4944a5d661aSToomas Soome tftp_makereq(tftpfile); /* no error check, it worked
4954a5d661aSToomas Soome * for open */
4964a5d661aSToomas Soome }
4974a5d661aSToomas Soome
4984a5d661aSToomas Soome while (tftpfile->currblock < needblock) {
4994a5d661aSToomas Soome int res;
5004a5d661aSToomas Soome
5014a5d661aSToomas Soome res = tftp_getnextblock(tftpfile);
5024a5d661aSToomas Soome if (res) { /* no answer */
5034a5d661aSToomas Soome #ifdef TFTP_DEBUG
5044a5d661aSToomas Soome printf("tftp: read error\n");
5054a5d661aSToomas Soome #endif
5064a5d661aSToomas Soome return (res);
5074a5d661aSToomas Soome }
5084a5d661aSToomas Soome if (tftpfile->islastblock)
5094a5d661aSToomas Soome break;
5104a5d661aSToomas Soome }
5114a5d661aSToomas Soome
5124a5d661aSToomas Soome if (tftpfile->currblock == needblock) {
5134a5d661aSToomas Soome int offinblock, inbuffer;
5144a5d661aSToomas Soome
5154a5d661aSToomas Soome offinblock = tftpfile->off % tftpfile->tftp_blksize;
5164a5d661aSToomas Soome
5174a5d661aSToomas Soome inbuffer = tftpfile->validsize - offinblock;
5184a5d661aSToomas Soome if (inbuffer < 0) {
5194a5d661aSToomas Soome #ifdef TFTP_DEBUG
5204a5d661aSToomas Soome printf("tftp: invalid offset %d\n",
5214a5d661aSToomas Soome tftpfile->off);
5224a5d661aSToomas Soome #endif
5234a5d661aSToomas Soome return (EINVAL);
5244a5d661aSToomas Soome }
5254a5d661aSToomas Soome count = (size < inbuffer ? size : inbuffer);
5267b2a1233SToomas Soome bcopy(tftpfile->tftp_hdr->th_data + offinblock,
5274a5d661aSToomas Soome addr, count);
5284a5d661aSToomas Soome
5294a5d661aSToomas Soome addr = (char *)addr + count;
5304a5d661aSToomas Soome tftpfile->off += count;
5314a5d661aSToomas Soome size -= count;
5324a5d661aSToomas Soome
5334a5d661aSToomas Soome if ((tftpfile->islastblock) && (count == inbuffer))
5344a5d661aSToomas Soome break; /* EOF */
5354a5d661aSToomas Soome } else {
5364a5d661aSToomas Soome #ifdef TFTP_DEBUG
5374a5d661aSToomas Soome printf("tftp: block %d not found\n", needblock);
5384a5d661aSToomas Soome #endif
5394a5d661aSToomas Soome return (EINVAL);
5404a5d661aSToomas Soome }
5414a5d661aSToomas Soome
5424a5d661aSToomas Soome }
5434a5d661aSToomas Soome
5444a5d661aSToomas Soome if (resid)
5454a5d661aSToomas Soome *resid = size;
5464a5d661aSToomas Soome return (0);
5474a5d661aSToomas Soome }
5484a5d661aSToomas Soome
5494a5d661aSToomas Soome static int
tftp_close(struct open_file * f)5504a5d661aSToomas Soome tftp_close(struct open_file *f)
5514a5d661aSToomas Soome {
5524a5d661aSToomas Soome struct tftp_handle *tftpfile;
5534a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata;
5544a5d661aSToomas Soome
5554a5d661aSToomas Soome /* let it time out ... */
5564a5d661aSToomas Soome
5574a5d661aSToomas Soome if (tftpfile) {
5584a5d661aSToomas Soome free(tftpfile->path);
5597b2a1233SToomas Soome free(tftpfile->pkt);
5604a5d661aSToomas Soome free(tftpfile);
5614a5d661aSToomas Soome }
5624a5d661aSToomas Soome is_open = 0;
5634a5d661aSToomas Soome return (0);
5644a5d661aSToomas Soome }
5654a5d661aSToomas Soome
5664a5d661aSToomas Soome static int
tftp_write(struct open_file * f __unused,void * start __unused,size_t size __unused,size_t * resid __unused)5674a5d661aSToomas Soome tftp_write(struct open_file *f __unused, void *start __unused, size_t size __unused,
5684a5d661aSToomas Soome size_t *resid __unused /* out */)
5694a5d661aSToomas Soome {
5704a5d661aSToomas Soome return (EROFS);
5714a5d661aSToomas Soome }
5724a5d661aSToomas Soome
5734a5d661aSToomas Soome static int
tftp_stat(struct open_file * f,struct stat * sb)5744a5d661aSToomas Soome tftp_stat(struct open_file *f, struct stat *sb)
5754a5d661aSToomas Soome {
5764a5d661aSToomas Soome struct tftp_handle *tftpfile;
5774a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata;
5784a5d661aSToomas Soome
5794a5d661aSToomas Soome sb->st_mode = 0444 | S_IFREG;
5804a5d661aSToomas Soome sb->st_nlink = 1;
5814a5d661aSToomas Soome sb->st_uid = 0;
5824a5d661aSToomas Soome sb->st_gid = 0;
5834a5d661aSToomas Soome sb->st_size = (off_t) tftpfile->tftp_tsize;
5844a5d661aSToomas Soome return (0);
5854a5d661aSToomas Soome }
5864a5d661aSToomas Soome
5874a5d661aSToomas Soome static off_t
tftp_seek(struct open_file * f,off_t offset,int where)5884a5d661aSToomas Soome tftp_seek(struct open_file *f, off_t offset, int where)
5894a5d661aSToomas Soome {
5904a5d661aSToomas Soome struct tftp_handle *tftpfile;
5914a5d661aSToomas Soome tftpfile = (struct tftp_handle *) f->f_fsdata;
5924a5d661aSToomas Soome
5934a5d661aSToomas Soome switch (where) {
5944a5d661aSToomas Soome case SEEK_SET:
5954a5d661aSToomas Soome tftpfile->off = offset;
5964a5d661aSToomas Soome break;
5974a5d661aSToomas Soome case SEEK_CUR:
5984a5d661aSToomas Soome tftpfile->off += offset;
5994a5d661aSToomas Soome break;
6004a5d661aSToomas Soome default:
6014a5d661aSToomas Soome errno = EOFFSET;
6024a5d661aSToomas Soome return (-1);
6034a5d661aSToomas Soome }
6044a5d661aSToomas Soome return (tftpfile->off);
6054a5d661aSToomas Soome }
6064a5d661aSToomas Soome
6074a5d661aSToomas Soome static ssize_t
sendrecv_tftp(struct tftp_handle * h,ssize_t (* sproc)(struct iodesc *,void *,size_t),void * sbuf,size_t ssize,ssize_t (* rproc)(struct tftp_handle *,void **,void **,time_t,unsigned short *),void ** pkt,void ** payload,unsigned short * rtype)6084a5d661aSToomas Soome sendrecv_tftp(struct tftp_handle *h,
6094a5d661aSToomas Soome ssize_t (*sproc)(struct iodesc *, void *, size_t),
6104a5d661aSToomas Soome void *sbuf, size_t ssize,
6117b2a1233SToomas Soome ssize_t (*rproc)(struct tftp_handle *, void **, void **, time_t,
6127b2a1233SToomas Soome unsigned short *),
6137b2a1233SToomas Soome void **pkt, void **payload, unsigned short *rtype)
6144a5d661aSToomas Soome {
6154a5d661aSToomas Soome struct iodesc *d = h->iodesc;
6164a5d661aSToomas Soome ssize_t cc;
6174a5d661aSToomas Soome time_t t, t1, tleft;
6184a5d661aSToomas Soome
6194a5d661aSToomas Soome #ifdef TFTP_DEBUG
6204a5d661aSToomas Soome if (debug)
6214a5d661aSToomas Soome printf("sendrecv: called\n");
6224a5d661aSToomas Soome #endif
6234a5d661aSToomas Soome
6244a5d661aSToomas Soome tleft = MINTMO;
6254a5d661aSToomas Soome t = t1 = getsecs();
6264a5d661aSToomas Soome for (;;) {
6274a5d661aSToomas Soome if ((getsecs() - t) > MAXTMO) {
6284a5d661aSToomas Soome errno = ETIMEDOUT;
6294a5d661aSToomas Soome return -1;
6304a5d661aSToomas Soome }
6314a5d661aSToomas Soome
6324a5d661aSToomas Soome cc = (*sproc)(d, sbuf, ssize);
6334a5d661aSToomas Soome if (cc != -1 && cc < ssize)
6344a5d661aSToomas Soome panic("sendrecv: short write! (%zd < %zu)",
6354a5d661aSToomas Soome cc, ssize);
6364a5d661aSToomas Soome
6374a5d661aSToomas Soome if (cc == -1) {
6384a5d661aSToomas Soome /* Error on transmit; wait before retrying */
6394a5d661aSToomas Soome while ((getsecs() - t1) < tleft);
640*a01cd540SToomas Soome t1 = getsecs();
6414a5d661aSToomas Soome continue;
6424a5d661aSToomas Soome }
6434a5d661aSToomas Soome
644*a01cd540SToomas Soome t = t1 = getsecs();
6454a5d661aSToomas Soome recvnext:
646*a01cd540SToomas Soome if ((getsecs() - t) > MAXTMO) {
647*a01cd540SToomas Soome errno = ETIMEDOUT;
648*a01cd540SToomas Soome return (-1);
649*a01cd540SToomas Soome }
6504a5d661aSToomas Soome /* Try to get a packet and process it. */
6517b2a1233SToomas Soome cc = (*rproc)(h, pkt, payload, tleft, rtype);
6524a5d661aSToomas Soome /* Return on data, EOF or real error. */
653*a01cd540SToomas Soome if (cc != -1 || (errno != 0 && errno != ETIMEDOUT))
6544a5d661aSToomas Soome return (cc);
6554a5d661aSToomas Soome if ((getsecs() - t1) < tleft) {
6564a5d661aSToomas Soome goto recvnext;
6574a5d661aSToomas Soome }
6584a5d661aSToomas Soome
6594a5d661aSToomas Soome /* Timed out or didn't get the packet we're waiting for */
6604a5d661aSToomas Soome tleft += MINTMO;
6614a5d661aSToomas Soome if (tleft > (2 * MINTMO)) {
6624a5d661aSToomas Soome tleft = (2 * MINTMO);
6634a5d661aSToomas Soome }
6644a5d661aSToomas Soome t1 = getsecs();
6654a5d661aSToomas Soome }
6664a5d661aSToomas Soome }
6674a5d661aSToomas Soome
6684a5d661aSToomas Soome static int
tftp_set_blksize(struct tftp_handle * h,const char * str)6694a5d661aSToomas Soome tftp_set_blksize(struct tftp_handle *h, const char *str)
6704a5d661aSToomas Soome {
6714a5d661aSToomas Soome char *endptr;
6724a5d661aSToomas Soome int new_blksize;
6734a5d661aSToomas Soome int ret = 0;
6744a5d661aSToomas Soome
6754a5d661aSToomas Soome if (h == NULL || str == NULL)
6764a5d661aSToomas Soome return (ret);
6774a5d661aSToomas Soome
6784a5d661aSToomas Soome new_blksize =
6794a5d661aSToomas Soome (unsigned int)strtol(str, &endptr, 0);
6804a5d661aSToomas Soome
6814a5d661aSToomas Soome /*
6824a5d661aSToomas Soome * Only accept blksize value if it is numeric.
6834a5d661aSToomas Soome * RFC2348 specifies that acceptable values are 8-65464.
6844a5d661aSToomas Soome * Let's choose a limit less than MAXRSPACE.
6854a5d661aSToomas Soome */
6864a5d661aSToomas Soome if (*endptr == '\0' && new_blksize >= 8
6874a5d661aSToomas Soome && new_blksize <= TFTP_MAX_BLKSIZE) {
6884a5d661aSToomas Soome h->tftp_blksize = new_blksize;
6894a5d661aSToomas Soome ret = 1;
6904a5d661aSToomas Soome }
6914a5d661aSToomas Soome
6924a5d661aSToomas Soome return (ret);
6934a5d661aSToomas Soome }
6944a5d661aSToomas Soome
6954a5d661aSToomas Soome /*
6964a5d661aSToomas Soome * In RFC2347, the TFTP Option Acknowledgement package (OACK)
6974a5d661aSToomas Soome * is used to acknowledge a client's option negotiation request.
6984a5d661aSToomas Soome * The format of an OACK packet is:
6994a5d661aSToomas Soome * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
7004a5d661aSToomas Soome * | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 |
7014a5d661aSToomas Soome * +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
7024a5d661aSToomas Soome *
7034a5d661aSToomas Soome * opc
7044a5d661aSToomas Soome * The opcode field contains a 6, for Option Acknowledgment.
7054a5d661aSToomas Soome *
7064a5d661aSToomas Soome * opt1
7074a5d661aSToomas Soome * The first option acknowledgment, copied from the original
7084a5d661aSToomas Soome * request.
7094a5d661aSToomas Soome *
7104a5d661aSToomas Soome * value1
7114a5d661aSToomas Soome * The acknowledged value associated with the first option. If
7124a5d661aSToomas Soome * and how this value may differ from the original request is
7134a5d661aSToomas Soome * detailed in the specification for the option.
7144a5d661aSToomas Soome *
7154a5d661aSToomas Soome * optN, valueN
7164a5d661aSToomas Soome * The final option/value acknowledgment pair.
7174a5d661aSToomas Soome */
7184a5d661aSToomas Soome static int
tftp_parse_oack(struct tftp_handle * h,char * buf,size_t len)7194a5d661aSToomas Soome tftp_parse_oack(struct tftp_handle *h, char *buf, size_t len)
7204a5d661aSToomas Soome {
7214a5d661aSToomas Soome /*
7224a5d661aSToomas Soome * We parse the OACK strings into an array
7234a5d661aSToomas Soome * of name-value pairs.
7244a5d661aSToomas Soome */
7254a5d661aSToomas Soome char *tftp_options[128] = { 0 };
7264a5d661aSToomas Soome char *val = buf;
7274a5d661aSToomas Soome int i = 0;
7284a5d661aSToomas Soome int option_idx = 0;
7294a5d661aSToomas Soome int blksize_is_set = 0;
7304a5d661aSToomas Soome int tsize = 0;
7314a5d661aSToomas Soome
7324a5d661aSToomas Soome unsigned int orig_blksize;
7334a5d661aSToomas Soome
7344a5d661aSToomas Soome while (option_idx < 128 && i < len) {
7354a5d661aSToomas Soome if (buf[i] == '\0') {
7364a5d661aSToomas Soome if (&buf[i] > val) {
7374a5d661aSToomas Soome tftp_options[option_idx] = val;
7384a5d661aSToomas Soome val = &buf[i] + 1;
7394a5d661aSToomas Soome ++option_idx;
7404a5d661aSToomas Soome }
7414a5d661aSToomas Soome }
7424a5d661aSToomas Soome ++i;
7434a5d661aSToomas Soome }
7444a5d661aSToomas Soome
7454a5d661aSToomas Soome /* Save the block size we requested for sanity check later. */
7464a5d661aSToomas Soome orig_blksize = h->tftp_blksize;
7474a5d661aSToomas Soome
7484a5d661aSToomas Soome /*
7494a5d661aSToomas Soome * Parse individual TFTP options.
7504a5d661aSToomas Soome * * "blksize" is specified in RFC2348.
7514a5d661aSToomas Soome * * "tsize" is specified in RFC2349.
7524a5d661aSToomas Soome */
7534a5d661aSToomas Soome for (i = 0; i < option_idx; i += 2) {
7544a5d661aSToomas Soome if (strcasecmp(tftp_options[i], "blksize") == 0) {
7554a5d661aSToomas Soome if (i + 1 < option_idx)
7564a5d661aSToomas Soome blksize_is_set =
7574a5d661aSToomas Soome tftp_set_blksize(h, tftp_options[i + 1]);
7584a5d661aSToomas Soome } else if (strcasecmp(tftp_options[i], "tsize") == 0) {
7594a5d661aSToomas Soome if (i + 1 < option_idx)
7604a5d661aSToomas Soome tsize = strtol(tftp_options[i + 1], (char **)NULL, 10);
7614a5d661aSToomas Soome if (tsize != 0)
7624a5d661aSToomas Soome h->tftp_tsize = tsize;
7634a5d661aSToomas Soome } else {
7644a5d661aSToomas Soome /* Do not allow any options we did not expect to be ACKed. */
7654a5d661aSToomas Soome printf("unexpected tftp option '%s'\n", tftp_options[i]);
7664a5d661aSToomas Soome return (-1);
7674a5d661aSToomas Soome }
7684a5d661aSToomas Soome }
7694a5d661aSToomas Soome
7704a5d661aSToomas Soome if (!blksize_is_set) {
7714a5d661aSToomas Soome /*
7724a5d661aSToomas Soome * If TFTP blksize was not set, try defaulting
7734a5d661aSToomas Soome * to the legacy TFTP blksize of SEGSIZE(512)
7744a5d661aSToomas Soome */
7754a5d661aSToomas Soome h->tftp_blksize = SEGSIZE;
7764a5d661aSToomas Soome } else if (h->tftp_blksize > orig_blksize) {
7774a5d661aSToomas Soome /*
7784a5d661aSToomas Soome * Server should not be proposing block sizes that
7794a5d661aSToomas Soome * exceed what we said we can handle.
7804a5d661aSToomas Soome */
7814a5d661aSToomas Soome printf("unexpected blksize %u\n", h->tftp_blksize);
7824a5d661aSToomas Soome return (-1);
7834a5d661aSToomas Soome }
7844a5d661aSToomas Soome
7854a5d661aSToomas Soome #ifdef TFTP_DEBUG
7864a5d661aSToomas Soome printf("tftp_blksize: %u\n", h->tftp_blksize);
7874a5d661aSToomas Soome printf("tftp_tsize: %lu\n", h->tftp_tsize);
7884a5d661aSToomas Soome #endif
7894a5d661aSToomas Soome return 0;
7904a5d661aSToomas Soome }
791