Ejemplo n.º 1
0
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);
}
Ejemplo n.º 2
0
/*
 * Transfer file
 */
static fetchIO *
ftp_transfer(conn_t *conn, const char *oper, const char *file, const char *op_arg,
    int mode, off_t offset, const char *flags)
{
	union anonymous {
		struct sockaddr_storage ss;
		struct sockaddr sa;
		struct sockaddr_in6 sin6;
		struct sockaddr_in sin4;
	} u;
	const char *bindaddr;
	const char *filename;
	int filenamelen, type;
	int pasv, verbose;
	int e, sd = -1;
	socklen_t l;
	char *s;
	fetchIO *df;

	/* check flags */
	pasv = !CHECK_FLAG('a');
	verbose = CHECK_FLAG('v');

	/* passive mode */
	if (!pasv)
		pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
		    strncasecmp(s, "no", 2) != 0);

	/* isolate filename */
	filename = ftp_filename(file, &filenamelen, &type, op_arg != NULL);

	/* set transfer mode and data type */
	if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK)
		goto ouch;

	/* find our own address, bind, and listen */
	l = sizeof(u.ss);
	if (getsockname(conn->sd, &u.sa, &l) == -1)
		goto sysouch;
	if (u.ss.ss_family == AF_INET6)
		unmappedaddr(&u.sin6, &l);

retry_mode:

	/* open data socket */
	if ((sd = socket(u.ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		fetch_syserr();
		return (NULL);
	}

	if (pasv) {
		unsigned char addr[64];
		char *ln, *p;
		unsigned int i;
		int port;

		/* send PASV command */
		if (verbose)
			fetch_info("setting passive mode");
		switch (u.ss.ss_family) {
		case AF_INET:
			if ((e = ftp_cmd(conn, "PASV\r\n")) != FTP_PASSIVE_MODE)
				goto ouch;
			break;
		case AF_INET6:
			if ((e = ftp_cmd(conn, "EPSV\r\n")) != FTP_EPASSIVE_MODE) {
				if (e == -1)
					goto ouch;
				if ((e = ftp_cmd(conn, "LPSV\r\n")) !=
				    FTP_LPASSIVE_MODE)
					goto ouch;
			}
			break;
		default:
			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
			goto ouch;
		}

		/*
		 * Find address and port number. The reply to the PASV command
		 * is IMHO the one and only weak point in the FTP protocol.
		 */
		ln = conn->buf;
		switch (e) {
		case FTP_PASSIVE_MODE:
		case FTP_LPASSIVE_MODE:
			for (p = ln + 3; *p && !isdigit((unsigned char)*p); p++)
				/* nothing */ ;
			if (!*p) {
				e = FTP_PROTOCOL_ERROR;
				goto ouch;
			}
			l = (e == FTP_PASSIVE_MODE ? 6 : 21);
			for (i = 0; *p && i < l; i++, p++)
				addr[i] = strtol(p, &p, 10);
			if (i < l) {
				e = FTP_PROTOCOL_ERROR;
				goto ouch;
			}
			break;
		case FTP_EPASSIVE_MODE:
			for (p = ln + 3; *p && *p != '('; p++)
				/* nothing */ ;
			if (!*p) {
				e = FTP_PROTOCOL_ERROR;
				goto ouch;
			}
			++p;
			if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
				&port, &addr[3]) != 5 ||
			    addr[0] != addr[1] ||
			    addr[0] != addr[2] || addr[0] != addr[3]) {
				e = FTP_PROTOCOL_ERROR;
				goto ouch;
			}
			break;
		case FTP_SYNTAX_ERROR:
			if (verbose)
				fetch_info("passive mode failed");
			/* Close socket and retry with passive mode. */
			pasv = 0;
			close(sd);
			sd = -1;
			goto retry_mode;
		}

		/* seek to required offset */
		if (offset)
			if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
				goto sysouch;

		/* construct sockaddr for data socket */
		l = sizeof(u.ss);
		if (getpeername(conn->sd, &u.sa, &l) == -1)
			goto sysouch;
		if (u.ss.ss_family == AF_INET6)
			unmappedaddr(&u.sin6, &l);
		switch (u.ss.ss_family) {
		case AF_INET6:
			if (e == FTP_EPASSIVE_MODE)
				u.sin6.sin6_port = htons(port);
			else {
				memcpy(&u.sin6.sin6_addr, addr + 2, 16);
				memcpy(&u.sin6.sin6_port, addr + 19, 2);
			}
			break;
		case AF_INET:
			if (e == FTP_EPASSIVE_MODE)
				u.sin4.sin_port = htons(port);
			else {
				memcpy(&u.sin4.sin_addr, addr, 4);
				memcpy(&u.sin4.sin_port, addr + 4, 2);
			}
			break;
		default:
			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
			break;
		}

		/* connect to data port */
		if (verbose)
			fetch_info("opening data connection");
		bindaddr = getenv("FETCH_BIND_ADDRESS");
		if (bindaddr != NULL && *bindaddr != '\0' &&
		    fetch_bind(sd, u.ss.ss_family, bindaddr) != 0)
			goto sysouch;
		if (connect(sd, &u.sa, l) == -1)
			goto sysouch;

		/* make the server initiate the transfer */
		if (verbose)
			fetch_info("initiating transfer");
		if (op_arg)
			e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
		else
			e = ftp_cmd(conn, "%s %.*s\r\n", oper,
			    (int)filenamelen, filename);
		if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
			goto ouch;

	} else {
		uint32_t a;
		uint16_t p;
#if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE)
		int arg;
		int low = CHECK_FLAG('l');
#endif
		int d;
		char hname[INET6_ADDRSTRLEN];

		switch (u.ss.ss_family) {
		case AF_INET6:
			u.sin6.sin6_port = 0;
#ifdef IPV6_PORTRANGE
			arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
			if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
				(char *)&arg, sizeof(arg)) == -1)
				goto sysouch;
#endif
			break;
		case AF_INET:
			u.sin4.sin_port = 0;
#ifdef IP_PORTRANGE
			arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
			if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
				(char *)&arg, sizeof(arg)) == -1)
				goto sysouch;
#endif
			break;
		}
		if (verbose)
			fetch_info("binding data socket");
		if (bind(sd, &u.sa, l) == -1)
			goto sysouch;
		if (listen(sd, 1) == -1)
			goto sysouch;

		/* find what port we're on and tell the server */
		if (getsockname(sd, &u.sa, &l) == -1)
			goto sysouch;
		switch (u.ss.ss_family) {
		case AF_INET:
			a = ntohl(u.sin4.sin_addr.s_addr);
			p = ntohs(u.sin4.sin_port);
			e = ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d\r\n",
			    (a >> 24) & 0xff, (a >> 16) & 0xff,
			    (a >> 8) & 0xff, a & 0xff,
			    (p >> 8) & 0xff, p & 0xff);
			break;
		case AF_INET6:
			e = -1;
			u.sin6.sin6_scope_id = 0;
			if (getnameinfo(&u.sa, l,
				hname, sizeof(hname),
				NULL, 0, NI_NUMERICHOST) == 0) {
				e = ftp_cmd(conn, "EPRT |%d|%s|%d|\r\n", 2, hname,
				    htons(u.sin6.sin6_port));
				if (e == -1)
					goto ouch;
			}
			if (e != FTP_OK) {
				unsigned char *ap = (void *)&u.sin6.sin6_addr.s6_addr;
				uint16_t port = ntohs(u.sin6.sin6_port);
				e = ftp_cmd(conn,
				    "LPRT %d,%d,%u,%u,%u,%u,%u,%u,%u,%u,"
				    "%u,%u,%u,%u,%u,%u,%u,%u,%d,%d,%d\r\n",
				    6, 16,
				    (unsigned)ap[0], (unsigned)ap[1],
				    (unsigned)ap[2], (unsigned)ap[3],
				    (unsigned)ap[4], (unsigned)ap[5],
				    (unsigned)ap[6], (unsigned)ap[7],
				    (unsigned)ap[8], (unsigned)ap[9],
				    (unsigned)ap[10], (unsigned)ap[11],
				    (unsigned)ap[12], (unsigned)ap[13],
				    (unsigned)ap[14], (unsigned)ap[15],
				    2, port >> 8, port & 0xff);
			}
			break;
		default:
			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
			goto ouch;
		}
		if (e != FTP_OK)
			goto ouch;

		/* seek to required offset */
		if (offset)
			if (ftp_cmd(conn, "REST %llu\r\n", (unsigned long long)offset) != FTP_FILE_OK)
				goto sysouch;

		/* make the server initiate the transfer */
		if (verbose)
			fetch_info("initiating transfer");
		if (op_arg)
			e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
		else
			e = ftp_cmd(conn, "%s %.*s\r\n", oper,
			    (int)filenamelen, filename);
		if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
			goto ouch;

		/* accept the incoming connection and go to town */
		if ((d = accept(sd, NULL, NULL)) == -1)
			goto sysouch;
		close(sd);
		sd = d;
	}
Ejemplo n.º 3
0
int
main(int argc, char *argv[])
{
	struct tftphdr *tp;
	int n;
	int ch, on;
	struct sockaddr_storage me;
	int len;
	char *chroot_dir = NULL;
	struct passwd *nobody;
	const char *chuser = "******";

	openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
	while ((ch = getopt(argc, argv, "cClns:u:")) != -1) {
		switch (ch) {
		case 'c':
			ipchroot = 1;
			break;
		case 'C':
			ipchroot = 2;
			break;
		case 'l':
			logging = 1;
			break;
		case 'n':
			suppress_naks = 1;
			break;
		case 's':
			chroot_dir = optarg;
			break;
		case 'u':
			chuser = optarg;
			break;
		default:
			syslog(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 && chroot_dir == NULL) {
		syslog(LOG_ERR, "-c requires -s");
		exit(1);
	}

	on = 1;
	if (ioctl(0, FIONBIO, &on) < 0) {
		syslog(LOG_ERR, "ioctl(FIONBIO): %m");
		exit(1);
	}
	fromlen = sizeof (from);
	n = recvfrom(0, buf, sizeof (buf), 0,
	    (struct sockaddr *)&from, &fromlen);
	if (n < 0) {
		syslog(LOG_ERR, "recvfrom: %m");
		exit(1);
	}
	/*
	 * 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 pid;
		int i, j;

		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.
				 */
				j = sizeof from;
				i = recvfrom(0, buf, sizeof (buf), 0,
				    (struct sockaddr *)&from, &j);
				if (i > 0) {
					n = i;
					fromlen = j;
				}
		    } else {
				break;
		    }
		}
		if (pid < 0) {
			syslog(LOG_ERR, "fork: %m");
			exit(1);
		} else if (pid != 0) {
			exit(0);
		}
	}

	/*
	 * 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) {
			char *tempchroot;
			struct stat sb;
			int statret;
			struct sockaddr_storage ss;
			char hbuf[NI_MAXHOST];

			memcpy(&ss, &from, from.ss_len);
			unmappedaddr((struct sockaddr_in6 *)&ss);
			getnameinfo((struct sockaddr *)&ss, ss.ss_len,
				    hbuf, sizeof(hbuf), NULL, 0,
				    NI_NUMERICHOST | NI_WITHSCOPEID);
			asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf);
			statret = stat(tempchroot, &sb);
			if ((sb.st_mode & S_IFDIR) &&
			    (statret == 0 || (statret == -1 && ipchroot == 1)))
				chroot_dir = tempchroot;
		}
		/* Must get this before chroot because /etc might go away */
		if ((nobody = getpwnam(chuser)) == NULL) {
			syslog(LOG_ERR, "%s: no such user", chuser);
			exit(1);
		}
		if (chroot(chroot_dir)) {
			syslog(LOG_ERR, "chroot: %s: %m", chroot_dir);
			exit(1);
		}
		chdir( "/" );
		setuid(nobody->pw_uid);
		setgroups(1, &nobody->pw_gid);
	}

	len = sizeof(me);
	if (getsockname(0, (struct sockaddr *)&me, &len) == 0) {
		switch (me.ss_family) {
		case AF_INET:
			((struct sockaddr_in *)&me)->sin_port = 0;
			break;
		case AF_INET6:
			((struct sockaddr_in6 *)&me)->sin6_port = 0;
			break;
		default:
			/* unsupported */
			break;
		}
	} else {
		memset(&me, 0, sizeof(me));
		me.ss_family = from.ss_family;
		me.ss_len = from.ss_len;
	}
	alarm(0);
	close(0);
	close(1);
	peer = socket(from.ss_family, SOCK_DGRAM, 0);
	if (peer < 0) {
		syslog(LOG_ERR, "socket: %m");
		exit(1);
	}
	if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
		syslog(LOG_ERR, "bind: %m");
		exit(1);
	}
	if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
		syslog(LOG_ERR, "connect: %m");
		exit(1);
	}
	tp = (struct tftphdr *)buf;
	tp->th_opcode = ntohs(tp->th_opcode);
	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
		tftp(tp, n);
	exit(1);
}