1 /* $NetBSD: tftp-proxy.c,v 1.2 2008/06/18 09:06:26 yamt Exp $ */
2 /* $OpenBSD: tftp-proxy.c,v 1.2 2006/12/20 03:33:38 joel Exp $
4 * Copyright (c) 2005 DLS Internet Services
5 * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/types.h>
32 #include <sys/ioctl.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <arpa/tftp.h>
39 #include <sys/socket.h>
41 #include <net/pfvar.h>
52 #define CHROOT_DIR "/var/chroot/tftp-proxy"
53 #define NOPRIV_USER "_proxy"
55 #define PF_NAT_PROXY_PORT_LOW 50001
56 #define PF_NAT_PROXY_PORT_HIGH 65535
58 #define DEFTRANSWAIT 2
61 #define PKTSIZE SEGSIZE+4
64 #ifndef IPPORT_HIFIRSTAUTO
65 #define IPPORT_HIFIRSTAUTO IPPORT_ANONMIN
66 #endif /* IPPORT_HIFIRSTAUTO */
68 #ifndef IPPORT_HILASTAUTO
69 #define IPPORT_HILASTAUTO IPPORT_ANONMAX
70 #endif /* IPPORT_HILASTAUTO */
72 const char *opcode(int);
73 const char *sock_ntop(struct sockaddr
*);
74 u_int16_t
pick_proxy_port(void);
75 static void usage(void);
77 extern char *__progname
;
78 char ntop_buf
[NTOP_BUFS
][INET6_ADDRSTRLEN
];
82 main(int argc
, char *argv
[])
84 int c
, fd
= 0, on
= 1, out_fd
= 0, peer
, reqsize
= 0;
85 int transwait
= DEFTRANSWAIT
;
96 struct sockaddr_storage from
, proxy
, server
, proxy_to_server
, s_in
;
97 struct sockaddr_in sock_out
;
101 openlog(__progname
, LOG_PID
| LOG_NDELAY
, LOG_DAEMON
);
103 while ((c
= getopt(argc
, argv
, "vw:")) != -1)
109 transwait
= strtoll(optarg
, &p
, 10);
111 syslog(LOG_ERR
, "invalid -w value");
121 init_filter(NULL
, verbose
);
125 pw
= getpwnam(NOPRIV_USER
);
127 syslog(LOG_ERR
, "no such user %s: %m", NOPRIV_USER
);
130 if (chroot(CHROOT_DIR
) || chdir("/")) {
131 syslog(LOG_ERR
, "chroot %s: %m", CHROOT_DIR
);
135 if (setgroups(1, &pw
->pw_gid
) ||
136 setgid(pw
->pw_gid
) ||
137 setuid(pw
->pw_uid
)) {
138 syslog(LOG_ERR
, "can't revoke privs: %m");
142 if (setgroups(1, &pw
->pw_gid
) ||
143 setresgid(pw
->pw_gid
, pw
->pw_gid
, pw
->pw_gid
) ||
144 setresuid(pw
->pw_uid
, pw
->pw_uid
, pw
->pw_uid
)) {
145 syslog(LOG_ERR
, "can't revoke privs: %m");
148 #endif /* !__NetBSD__ */
150 /* non-blocking io */
151 if (ioctl(fd
, FIONBIO
, &on
) < 0) {
152 syslog(LOG_ERR
, "ioctl(FIONBIO): %m");
156 if (setsockopt(fd
, IPPROTO_IP
, IP_RECVDSTADDR
, &on
, sizeof(on
)) == -1) {
157 syslog(LOG_ERR
, "setsockopt(IP_RECVDSTADDR): %m");
162 if (getsockname(fd
, (struct sockaddr
*)&s_in
, &j
) == -1) {
163 syslog(LOG_ERR
, "getsockname: %m");
167 bindport
= ((struct sockaddr_in
*)&s_in
)->sin_port
;
169 /* req will be pushed back out at the end, unchanged */
171 if ((reqsize
= recvfrom(fd
, req
, sizeof(req
), MSG_PEEK
,
172 (struct sockaddr
*)&from
, &j
)) < 0) {
173 syslog(LOG_ERR
, "recvfrom: %m");
177 bzero(&msg
, sizeof(msg
));
179 iov
.iov_len
= sizeof(req
);
180 msg
.msg_name
= &from
;
181 msg
.msg_namelen
= sizeof(from
);
185 cbuflen
= CMSG_SPACE(sizeof(struct sockaddr_storage
));
186 if ((cbuf
= malloc(cbuflen
)) == NULL
) {
187 syslog(LOG_ERR
, "malloc: %m");
191 msg
.msg_control
= cbuf
;
192 msg
.msg_controllen
= cbuflen
;
194 if (recvmsg(fd
, &msg
, 0) < 0) {
195 syslog(LOG_ERR
, "recvmsg: %m");
202 peer
= socket(from
.ss_family
, SOCK_DGRAM
, 0);
204 syslog(LOG_ERR
, "socket: %m");
207 memset(&s_in
, 0, sizeof(s_in
));
208 s_in
.ss_family
= from
.ss_family
;
209 s_in
.ss_len
= from
.ss_len
;
211 /* get local address if possible */
212 for (cmsg
= CMSG_FIRSTHDR(&msg
); cmsg
!= NULL
;
213 cmsg
= CMSG_NXTHDR(&msg
, cmsg
)) {
214 if (cmsg
->cmsg_level
== IPPROTO_IP
&&
215 cmsg
->cmsg_type
== IP_RECVDSTADDR
) {
216 memcpy(&((struct sockaddr_in
*)&s_in
)->sin_addr
,
217 CMSG_DATA(cmsg
), sizeof(struct in_addr
));
222 if (bind(peer
, (struct sockaddr
*)&s_in
, s_in
.ss_len
) < 0) {
223 syslog(LOG_ERR
, "bind: %m");
226 if (connect(peer
, (struct sockaddr
*)&from
, from
.ss_len
) < 0) {
227 syslog(LOG_ERR
, "connect: %m");
231 tp
= (struct tftphdr
*)req
;
232 if (!(ntohs(tp
->th_opcode
) == RRQ
|| ntohs(tp
->th_opcode
) == WRQ
)) {
233 /* not a tftp request, bail */
235 syslog(LOG_WARNING
, "not a valid tftp request");
238 /* exit 0 so inetd doesn't log anything */
242 j
= sizeof(struct sockaddr_storage
);
243 if (getsockname(fd
, (struct sockaddr
*)&proxy
, &j
) == -1) {
244 syslog(LOG_ERR
, "getsockname: %m");
248 ((struct sockaddr_in
*)&proxy
)->sin_port
= bindport
;
250 /* find the un-rdr'd server and port the client wanted */
251 if (server_lookup((struct sockaddr
*)&from
,
252 (struct sockaddr
*)&proxy
, (struct sockaddr
*)&server
,
254 syslog(LOG_ERR
, "pf connection lookup failed (no rdr?)");
258 /* establish a new outbound connection to the remote server */
259 if ((out_fd
= socket(((struct sockaddr
*)&from
)->sa_family
,
260 SOCK_DGRAM
, IPPROTO_UDP
)) < 0) {
261 syslog(LOG_ERR
, "couldn't create new socket");
265 bzero((char *)&sock_out
, sizeof(sock_out
));
266 sock_out
.sin_family
= from
.ss_family
;
267 sock_out
.sin_port
= htons(pick_proxy_port());
268 if (bind(out_fd
, (struct sockaddr
*)&sock_out
, sizeof(sock_out
)) < 0) {
269 syslog(LOG_ERR
, "couldn't bind to new socket: %m");
273 if (connect(out_fd
, (struct sockaddr
*)&server
,
274 ((struct sockaddr
*)&server
)->sa_len
) < 0 && errno
!= EINPROGRESS
) {
275 syslog(LOG_ERR
, "couldn't connect to remote server: %m");
279 j
= sizeof(struct sockaddr_storage
);
280 if ((getsockname(out_fd
, (struct sockaddr
*)&proxy_to_server
,
282 syslog(LOG_ERR
, "getsockname: %m");
287 syslog(LOG_INFO
, "%s:%d -> %s:%d/%s:%d -> %s:%d \"%s %s\"",
288 sock_ntop((struct sockaddr
*)&from
),
289 ntohs(((struct sockaddr_in
*)&from
)->sin_port
),
290 sock_ntop((struct sockaddr
*)&proxy
),
291 ntohs(((struct sockaddr_in
*)&proxy
)->sin_port
),
292 sock_ntop((struct sockaddr
*)&proxy_to_server
),
293 ntohs(((struct sockaddr_in
*)&proxy_to_server
)->sin_port
),
294 sock_ntop((struct sockaddr
*)&server
),
295 ntohs(((struct sockaddr_in
*)&server
)->sin_port
),
296 opcode(ntohs(tp
->th_opcode
)),
299 /* get ready to add rdr and pass rules */
300 if (prepare_commit(1) == -1) {
301 syslog(LOG_ERR
, "couldn't prepare pf commit");
305 /* rdr from server to us on our random port -> client on its port */
306 if (add_rdr(1, (struct sockaddr
*)&server
,
307 (struct sockaddr
*)&proxy_to_server
, ntohs(sock_out
.sin_port
),
308 (struct sockaddr
*)&from
,
309 ntohs(((struct sockaddr_in
*)&from
)->sin_port
),
310 IPPROTO_UDP
) == -1) {
311 syslog(LOG_ERR
, "couldn't add rdr");
315 /* explicitly allow the packets to return back to the client (which pf
316 * will see post-rdr) */
317 if (add_filter(1, PF_IN
, (struct sockaddr
*)&server
,
318 (struct sockaddr
*)&from
,
319 ntohs(((struct sockaddr_in
*)&from
)->sin_port
),
320 IPPROTO_UDP
) == -1) {
321 syslog(LOG_ERR
, "couldn't add pass in");
324 if (add_filter(1, PF_OUT
, (struct sockaddr
*)&server
,
325 (struct sockaddr
*)&from
,
326 ntohs(((struct sockaddr_in
*)&from
)->sin_port
),
327 IPPROTO_UDP
) == -1) {
328 syslog(LOG_ERR
, "couldn't add pass out");
332 /* and just in case, to pass out from us to the server */
333 if (add_filter(1, PF_OUT
, (struct sockaddr
*)&proxy_to_server
,
334 (struct sockaddr
*)&server
,
335 ntohs(((struct sockaddr_in
*)&server
)->sin_port
),
336 IPPROTO_UDP
) == -1) {
337 syslog(LOG_ERR
, "couldn't add pass out");
341 if (do_commit() == -1) {
342 syslog(LOG_ERR
, "couldn't commit pf rules");
346 /* forward the initial tftp request and start the insanity */
347 if (send(out_fd
, tp
, reqsize
, 0) < 0) {
348 syslog(LOG_ERR
, "couldn't forward tftp packet: %m");
352 /* allow the transfer to start to establish a state */
355 /* delete our rdr rule and clean up */
369 (void)snprintf(str
, sizeof(str
), "RRQ");
372 (void)snprintf(str
, sizeof(str
), "WRQ");
375 (void)snprintf(str
, sizeof(str
), "(%d)", code
);
383 sock_ntop(struct sockaddr
*sa
)
387 /* Cycle to next buffer. */
388 n
= (n
+ 1) % NTOP_BUFS
;
389 ntop_buf
[n
][0] = '\0';
391 if (sa
->sa_family
== AF_INET
) {
392 struct sockaddr_in
*sin
= (struct sockaddr_in
*)sa
;
394 return (inet_ntop(AF_INET
, &sin
->sin_addr
, ntop_buf
[n
],
395 sizeof ntop_buf
[0]));
398 if (sa
->sa_family
== AF_INET6
) {
399 struct sockaddr_in6
*sin6
= (struct sockaddr_in6
*)sa
;
401 return (inet_ntop(AF_INET6
, &sin6
->sin6_addr
, ntop_buf
[n
],
402 sizeof ntop_buf
[0]));
409 pick_proxy_port(void)
411 return (IPPORT_HIFIRSTAUTO
+ (arc4random() %
412 (IPPORT_HILASTAUTO
- IPPORT_HIFIRSTAUTO
)));
418 syslog(LOG_ERR
, "usage: %s [-v] [-w transwait]", __progname
);