1*749ce5c9Sandvar /* $NetBSD: ftp.c,v 1.20 2024/02/09 20:55:15 andvar Exp $ */
2ecf55737Sitojun /* $KAME: ftp.c,v 1.23 2003/08/19 21:20:33 itojun Exp $ */
3e5db40b6Sitojun
4e5db40b6Sitojun /*
5e5db40b6Sitojun * Copyright (C) 1997 and 1998 WIDE Project.
6e5db40b6Sitojun * All rights reserved.
7e5db40b6Sitojun *
8e5db40b6Sitojun * Redistribution and use in source and binary forms, with or without
9e5db40b6Sitojun * modification, are permitted provided that the following conditions
10e5db40b6Sitojun * are met:
11e5db40b6Sitojun * 1. Redistributions of source code must retain the above copyright
12e5db40b6Sitojun * notice, this list of conditions and the following disclaimer.
13e5db40b6Sitojun * 2. Redistributions in binary form must reproduce the above copyright
14e5db40b6Sitojun * notice, this list of conditions and the following disclaimer in the
15e5db40b6Sitojun * documentation and/or other materials provided with the distribution.
16e5db40b6Sitojun * 3. Neither the name of the project nor the names of its contributors
17e5db40b6Sitojun * may be used to endorse or promote products derived from this software
18e5db40b6Sitojun * without specific prior written permission.
19e5db40b6Sitojun *
20e5db40b6Sitojun * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21e5db40b6Sitojun * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22e5db40b6Sitojun * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23e5db40b6Sitojun * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24e5db40b6Sitojun * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25e5db40b6Sitojun * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26e5db40b6Sitojun * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27e5db40b6Sitojun * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28e5db40b6Sitojun * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29e5db40b6Sitojun * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30e5db40b6Sitojun * SUCH DAMAGE.
31e5db40b6Sitojun */
32e5db40b6Sitojun
33e5db40b6Sitojun #include <sys/param.h>
34e5db40b6Sitojun #include <sys/types.h>
35e5db40b6Sitojun #include <sys/socket.h>
36e5db40b6Sitojun #include <sys/ioctl.h>
37e5db40b6Sitojun #include <sys/time.h>
38e5db40b6Sitojun
39e5db40b6Sitojun #include <stdio.h>
40e5db40b6Sitojun #include <stdlib.h>
41e5db40b6Sitojun #include <string.h>
42e5db40b6Sitojun #include <syslog.h>
43e5db40b6Sitojun #include <unistd.h>
44ecf55737Sitojun #include <poll.h>
45e5db40b6Sitojun #include <errno.h>
46e5db40b6Sitojun #include <ctype.h>
47e5db40b6Sitojun
48e5db40b6Sitojun #include <netinet/in.h>
49e5db40b6Sitojun #include <arpa/inet.h>
50e5db40b6Sitojun #include <netdb.h>
51e5db40b6Sitojun
52e5db40b6Sitojun #include "faithd.h"
53e5db40b6Sitojun
54e5db40b6Sitojun static char rbuf[MSS];
55e5db40b6Sitojun static int passivemode = 0;
56e5db40b6Sitojun static int wport4 = -1; /* listen() to active */
57e5db40b6Sitojun static int wport6 = -1; /* listen() to passive */
58e5db40b6Sitojun static int port4 = -1; /* active: inbound passive: outbound */
59e5db40b6Sitojun static int port6 = -1; /* active: outbound passive: inbound */
60e5db40b6Sitojun static struct sockaddr_storage data4; /* server data address */
61e5db40b6Sitojun static struct sockaddr_storage data6; /* client data address */
62e5db40b6Sitojun static int epsvall = 0;
63e5db40b6Sitojun
64e5db40b6Sitojun enum state { NONE, LPRT, EPRT, LPSV, EPSV };
65e5db40b6Sitojun
662579358aSchristos static int ftp_activeconn(void);
672579358aSchristos static int ftp_passiveconn(void);
682579358aSchristos static ssize_t ftp_copy(int, int);
692579358aSchristos static ssize_t ftp_copyresult(int, int, enum state);
702579358aSchristos static ssize_t ftp_copycommand(int, int, enum state *);
71e5db40b6Sitojun
72e5db40b6Sitojun void
ftp_relay(int ctl6,int ctl4)73e5db40b6Sitojun ftp_relay(int ctl6, int ctl4)
74e5db40b6Sitojun {
75ecf55737Sitojun struct pollfd pfd[6];
762579358aSchristos ssize_t error;
77e5db40b6Sitojun enum state state = NONE;
78e5db40b6Sitojun
79e5db40b6Sitojun syslog(LOG_INFO, "starting ftp control connection");
80e5db40b6Sitojun
81e5db40b6Sitojun for (;;) {
82ecf55737Sitojun pfd[0].fd = ctl4;
83ecf55737Sitojun pfd[0].events = POLLIN;
84ecf55737Sitojun pfd[1].fd = ctl6;
85ecf55737Sitojun pfd[1].events = POLLIN;
86bc0d6cddSitojun if (0 <= port4) {
87ecf55737Sitojun pfd[2].fd = port4;
88ecf55737Sitojun pfd[2].events = POLLIN;
89ecf55737Sitojun } else
90ecf55737Sitojun pfd[2].fd = -1;
91bc0d6cddSitojun if (0 <= port6) {
92ecf55737Sitojun pfd[3].fd = port6;
93ecf55737Sitojun pfd[3].events = POLLIN;
94ecf55737Sitojun } else
95ecf55737Sitojun pfd[3].fd = -1;
96e5db40b6Sitojun #if 0
97bc0d6cddSitojun if (0 <= wport4) {
98ecf55737Sitojun pfd[4].fd = wport4;
99ecf55737Sitojun pfd[4].events = POLLIN;
100ecf55737Sitojun } else
101ecf55737Sitojun pfd[4].fd = -1;
102bc0d6cddSitojun if (0 <= wport6) {
103ecf55737Sitojun pfd[5].fd = wport4;
104ecf55737Sitojun pfd[5].events = POLLIN;
105ecf55737Sitojun } else
106ecf55737Sitojun pfd[5].fd = -1;
107ecf55737Sitojun #else
108ecf55737Sitojun pfd[4].fd = pfd[5].fd = -1;
109ecf55737Sitojun pfd[4].events = pfd[5].events = 0;
110e5db40b6Sitojun #endif
1112579358aSchristos error = poll(pfd, (unsigned int)(sizeof(pfd) / sizeof(pfd[0])),
1122579358aSchristos FAITH_TIMEOUT * 1000);
113ecf55737Sitojun if (error == -1) {
114ecf55737Sitojun exit_failure("poll: %s", strerror(errno));
115ecf55737Sitojun }
116e5db40b6Sitojun else if (error == 0)
117e5db40b6Sitojun exit_failure("connection timeout");
118e5db40b6Sitojun
119e5db40b6Sitojun /*
120e5db40b6Sitojun * The order of the following checks does (slightly) matter.
121e5db40b6Sitojun * It is important to visit all checks (do not use "continue"),
122e5db40b6Sitojun * otherwise some of the pipe may become full and we cannot
123e5db40b6Sitojun * relay correctly.
124e5db40b6Sitojun */
125ecf55737Sitojun if (pfd[1].revents & POLLIN)
126ecf55737Sitojun {
127e5db40b6Sitojun /*
128e5db40b6Sitojun * copy control connection from the client.
129e5db40b6Sitojun * command translation is necessary.
130e5db40b6Sitojun */
131e5db40b6Sitojun error = ftp_copycommand(ctl6, ctl4, &state);
132e5db40b6Sitojun
13318446509Sitojun if (error < 0)
134e5db40b6Sitojun goto bad;
13518446509Sitojun else if (error == 0) {
1364c1a6c87Schristos (void)close(ctl4);
1374c1a6c87Schristos (void)close(ctl6);
138e5db40b6Sitojun exit_success("terminating ftp control connection");
139e5db40b6Sitojun /*NOTREACHED*/
140e5db40b6Sitojun }
141e5db40b6Sitojun }
142ecf55737Sitojun if (pfd[0].revents & POLLIN)
143ecf55737Sitojun {
144e5db40b6Sitojun /*
145e5db40b6Sitojun * copy control connection from the server
146e5db40b6Sitojun * translation of result code is necessary.
147e5db40b6Sitojun */
148e5db40b6Sitojun error = ftp_copyresult(ctl4, ctl6, state);
149e5db40b6Sitojun
15018446509Sitojun if (error < 0)
151e5db40b6Sitojun goto bad;
15218446509Sitojun else if (error == 0) {
1534c1a6c87Schristos (void)close(ctl4);
1544c1a6c87Schristos (void)close(ctl6);
155e5db40b6Sitojun exit_success("terminating ftp control connection");
156e5db40b6Sitojun /*NOTREACHED*/
157e5db40b6Sitojun }
158e5db40b6Sitojun }
159ecf55737Sitojun if (0 <= port4 && 0 <= port6 && (pfd[2].revents & POLLIN))
160ecf55737Sitojun {
161e5db40b6Sitojun /*
162e5db40b6Sitojun * copy data connection.
163e5db40b6Sitojun * no special treatment necessary.
164e5db40b6Sitojun */
165ecf55737Sitojun if (pfd[2].revents & POLLIN)
166e5db40b6Sitojun error = ftp_copy(port4, port6);
167e5db40b6Sitojun switch (error) {
168e5db40b6Sitojun case -1:
169e5db40b6Sitojun goto bad;
170e5db40b6Sitojun case 0:
17110dfada8Schristos if (port4 >= 0) {
1722579358aSchristos (void)close(port4);
17310dfada8Schristos port4 = -1;
17410dfada8Schristos }
17510dfada8Schristos if (port6 >= 0) {
1762579358aSchristos (void)close(port6);
17710dfada8Schristos port6 = -1;
17810dfada8Schristos }
179e5db40b6Sitojun syslog(LOG_INFO, "terminating data connection");
180e5db40b6Sitojun break;
181e5db40b6Sitojun default:
182e5db40b6Sitojun break;
183e5db40b6Sitojun }
184e5db40b6Sitojun }
185ecf55737Sitojun if (0 <= port4 && 0 <= port6 && (pfd[3].revents & POLLIN))
186ecf55737Sitojun {
187e5db40b6Sitojun /*
188e5db40b6Sitojun * copy data connection.
189e5db40b6Sitojun * no special treatment necessary.
190e5db40b6Sitojun */
191ecf55737Sitojun if (pfd[3].revents & POLLIN)
192e5db40b6Sitojun error = ftp_copy(port6, port4);
193e5db40b6Sitojun switch (error) {
194e5db40b6Sitojun case -1:
195e5db40b6Sitojun goto bad;
196e5db40b6Sitojun case 0:
19710dfada8Schristos if (port4 >= 0) {
1982579358aSchristos (void)close(port4);
19910dfada8Schristos port4 = -1;
20010dfada8Schristos }
20110dfada8Schristos if (port6 >= 0) {
2022579358aSchristos (void)close(port6);
20310dfada8Schristos port6 = -1;
20410dfada8Schristos }
205e5db40b6Sitojun syslog(LOG_INFO, "terminating data connection");
206e5db40b6Sitojun break;
207e5db40b6Sitojun default:
208e5db40b6Sitojun break;
209e5db40b6Sitojun }
210e5db40b6Sitojun }
211e5db40b6Sitojun #if 0
212ecf55737Sitojun if (wport4 && (pfd[4].revents & POLLIN))
213ecf55737Sitojun {
214e5db40b6Sitojun /*
215e5db40b6Sitojun * establish active data connection from the server.
216e5db40b6Sitojun */
217e5db40b6Sitojun ftp_activeconn();
218e5db40b6Sitojun }
219ecf55737Sitojun if (wport4 && (pfd[5].revents & POLLIN))
220ecf55737Sitojun {
221e5db40b6Sitojun /*
222e5db40b6Sitojun * establish passive data connection from the client.
223e5db40b6Sitojun */
224e5db40b6Sitojun ftp_passiveconn();
225e5db40b6Sitojun }
226e5db40b6Sitojun #endif
227e5db40b6Sitojun }
228e5db40b6Sitojun
229e5db40b6Sitojun bad:
230bc0d6cddSitojun exit_failure("%s", strerror(errno));
231e5db40b6Sitojun }
232e5db40b6Sitojun
233e5db40b6Sitojun static int
ftp_activeconn()234e5db40b6Sitojun ftp_activeconn()
235e5db40b6Sitojun {
23652c469ffSitojun socklen_t n;
237e5db40b6Sitojun int error;
238ecf55737Sitojun struct pollfd pfd[1];
2399e8f1055Sitojun struct sockaddr *sa;
240e5db40b6Sitojun
241e5db40b6Sitojun /* get active connection from server */
242ecf55737Sitojun pfd[0].fd = wport4;
243ecf55737Sitojun pfd[0].events = POLLIN;
244e5db40b6Sitojun n = sizeof(data4);
2452579358aSchristos if (poll(pfd, (unsigned int)(sizeof(pfd) / sizeof(pfd[0])),
2462579358aSchristos 120000) == 0 ||
2472579358aSchristos (port4 = accept(wport4, (void *)&data4, &n)) < 0)
248ecf55737Sitojun {
2494c1a6c87Schristos (void)close(wport4);
250e5db40b6Sitojun wport4 = -1;
251e5db40b6Sitojun syslog(LOG_INFO, "active mode data connection failed");
252e5db40b6Sitojun return -1;
253e5db40b6Sitojun }
254e5db40b6Sitojun
255e5db40b6Sitojun /* ask active connection to client */
2562579358aSchristos sa = (void *)&data6;
2579e8f1055Sitojun port6 = socket(sa->sa_family, SOCK_STREAM, 0);
258e5db40b6Sitojun if (port6 == -1) {
2594c1a6c87Schristos (void)close(port4);
2604c1a6c87Schristos (void)close(wport4);
261e5db40b6Sitojun port4 = wport4 = -1;
262e5db40b6Sitojun syslog(LOG_INFO, "active mode data connection failed");
263e5db40b6Sitojun return -1;
264e5db40b6Sitojun }
2652579358aSchristos error = connect(port6, sa, (socklen_t)sa->sa_len);
2663f183427Sitojun if (error < 0) {
2674c1a6c87Schristos (void)close(port6);
2684c1a6c87Schristos (void)close(port4);
2694c1a6c87Schristos (void)close(wport4);
270e5db40b6Sitojun port6 = port4 = wport4 = -1;
271e5db40b6Sitojun syslog(LOG_INFO, "active mode data connection failed");
272e5db40b6Sitojun return -1;
273e5db40b6Sitojun }
274e5db40b6Sitojun
275e5db40b6Sitojun syslog(LOG_INFO, "active mode data connection established");
276e5db40b6Sitojun return 0;
277e5db40b6Sitojun }
278e5db40b6Sitojun
279e5db40b6Sitojun static int
ftp_passiveconn()280e5db40b6Sitojun ftp_passiveconn()
281e5db40b6Sitojun {
28252c469ffSitojun socklen_t len;
283e5db40b6Sitojun int error;
284ecf55737Sitojun struct pollfd pfd[1];
2859e8f1055Sitojun struct sockaddr *sa;
286e5db40b6Sitojun
287e5db40b6Sitojun /* get passive connection from client */
288ecf55737Sitojun pfd[0].fd = wport6;
289ecf55737Sitojun pfd[0].events = POLLIN;
29052c469ffSitojun len = sizeof(data6);
2912579358aSchristos if (poll(pfd, (unsigned int)(sizeof(pfd) / sizeof(pfd[0])),
2922579358aSchristos 120000) == 0 ||
2932579358aSchristos (port6 = accept(wport6, (void *)&data6, &len)) < 0)
294ecf55737Sitojun {
2954c1a6c87Schristos (void)close(wport6);
296e5db40b6Sitojun wport6 = -1;
297e5db40b6Sitojun syslog(LOG_INFO, "passive mode data connection failed");
298e5db40b6Sitojun return -1;
299e5db40b6Sitojun }
300e5db40b6Sitojun
301e5db40b6Sitojun /* ask passive connection to server */
3022579358aSchristos sa = (void *)&data4;
3039e8f1055Sitojun port4 = socket(sa->sa_family, SOCK_STREAM, 0);
304e5db40b6Sitojun if (port4 == -1) {
3054c1a6c87Schristos (void)close(wport6);
3064c1a6c87Schristos (void)close(port6);
307e5db40b6Sitojun wport6 = port6 = -1;
308e5db40b6Sitojun syslog(LOG_INFO, "passive mode data connection failed");
309e5db40b6Sitojun return -1;
310e5db40b6Sitojun }
3112579358aSchristos error = connect(port4, sa, (socklen_t)sa->sa_len);
3123f183427Sitojun if (error < 0) {
3134c1a6c87Schristos (void)close(wport6);
3144c1a6c87Schristos (void)close(port4);
3154c1a6c87Schristos (void)close(port6);
316e5db40b6Sitojun wport6 = port4 = port6 = -1;
317e5db40b6Sitojun syslog(LOG_INFO, "passive mode data connection failed");
318e5db40b6Sitojun return -1;
319e5db40b6Sitojun }
320e5db40b6Sitojun
321e5db40b6Sitojun syslog(LOG_INFO, "passive mode data connection established");
322e5db40b6Sitojun return 0;
323e5db40b6Sitojun }
324e5db40b6Sitojun
3252579358aSchristos static ssize_t
ftp_copy(int src,int dst)326e5db40b6Sitojun ftp_copy(int src, int dst)
327e5db40b6Sitojun {
3282579358aSchristos int error, atmark;
3292579358aSchristos ssize_t n;
330e5db40b6Sitojun
331e5db40b6Sitojun /* OOB data handling */
332e5db40b6Sitojun error = ioctl(src, SIOCATMARK, &atmark);
333e5db40b6Sitojun if (error != -1 && atmark == 1) {
334e5db40b6Sitojun n = read(src, rbuf, 1);
335e5db40b6Sitojun if (n == -1)
336e5db40b6Sitojun goto bad;
3372579358aSchristos (void)send(dst, rbuf, (size_t)n, MSG_OOB);
338e5db40b6Sitojun #if 0
339e5db40b6Sitojun n = read(src, rbuf, sizeof(rbuf));
340e5db40b6Sitojun if (n == -1)
341e5db40b6Sitojun goto bad;
3422579358aSchristos (void)write(dst, rbuf, (size_t)n);
343e5db40b6Sitojun return n;
344e5db40b6Sitojun #endif
345e5db40b6Sitojun }
346e5db40b6Sitojun
347e5db40b6Sitojun n = read(src, rbuf, sizeof(rbuf));
348e5db40b6Sitojun switch (n) {
349e5db40b6Sitojun case -1:
350e5db40b6Sitojun case 0:
351e5db40b6Sitojun return n;
352e5db40b6Sitojun default:
3532579358aSchristos (void)write(dst, rbuf, (size_t)n);
354e5db40b6Sitojun return n;
355e5db40b6Sitojun }
356e5db40b6Sitojun
357e5db40b6Sitojun bad:
358bc0d6cddSitojun exit_failure("%s", strerror(errno));
359e5db40b6Sitojun /*NOTREACHED*/
360e5db40b6Sitojun return 0; /* to make gcc happy */
361e5db40b6Sitojun }
362e5db40b6Sitojun
3632579358aSchristos static ssize_t
ftp_copyresult(int src,int dst,enum state state)364e5db40b6Sitojun ftp_copyresult(int src, int dst, enum state state)
365e5db40b6Sitojun {
3662579358aSchristos int error, atmark;
3672579358aSchristos ssize_t n;
36852c469ffSitojun socklen_t len;
369e5db40b6Sitojun char *param;
370e5db40b6Sitojun int code;
371bc0d6cddSitojun char *a, *p;
372bc0d6cddSitojun int i;
373e5db40b6Sitojun
374e5db40b6Sitojun /* OOB data handling */
375e5db40b6Sitojun error = ioctl(src, SIOCATMARK, &atmark);
376e5db40b6Sitojun if (error != -1 && atmark == 1) {
377e5db40b6Sitojun n = read(src, rbuf, 1);
378e5db40b6Sitojun if (n == -1)
379e5db40b6Sitojun goto bad;
3802579358aSchristos (void)send(dst, rbuf, (size_t)n, MSG_OOB);
381e5db40b6Sitojun #if 0
382e5db40b6Sitojun n = read(src, rbuf, sizeof(rbuf));
383e5db40b6Sitojun if (n == -1)
384e5db40b6Sitojun goto bad;
3852579358aSchristos (void)write(dst, rbuf, (size_t)n);
386e5db40b6Sitojun return n;
387e5db40b6Sitojun #endif
388e5db40b6Sitojun }
389e5db40b6Sitojun
390e5db40b6Sitojun n = read(src, rbuf, sizeof(rbuf));
391e5db40b6Sitojun if (n <= 0)
392e5db40b6Sitojun return n;
393e5db40b6Sitojun rbuf[n] = '\0';
394e5db40b6Sitojun
395e5db40b6Sitojun /*
396e5db40b6Sitojun * parse argument
397e5db40b6Sitojun */
398e5db40b6Sitojun p = rbuf;
399e5db40b6Sitojun for (i = 0; i < 3; i++) {
400cfe7f80fSdsl if (!isdigit((unsigned char)*p)) {
401e5db40b6Sitojun /* invalid reply */
4022579358aSchristos (void)write(dst, rbuf, (size_t)n);
403e5db40b6Sitojun return n;
404e5db40b6Sitojun }
405e5db40b6Sitojun p++;
406e5db40b6Sitojun }
407cfe7f80fSdsl if (!isspace((unsigned char)*p)) {
408e5db40b6Sitojun /* invalid reply */
4092579358aSchristos (void)write(dst, rbuf, (size_t)n);
410e5db40b6Sitojun return n;
411e5db40b6Sitojun }
412e5db40b6Sitojun code = atoi(rbuf);
413e5db40b6Sitojun param = p;
414e5db40b6Sitojun /* param points to first non-command token, if any */
415cfe7f80fSdsl while (*param && isspace((unsigned char)*param))
416e5db40b6Sitojun param++;
417e5db40b6Sitojun if (!*param)
418e5db40b6Sitojun param = NULL;
419e5db40b6Sitojun
420e5db40b6Sitojun switch (state) {
421e5db40b6Sitojun case NONE:
422e5db40b6Sitojun if (!passivemode && rbuf[0] == '1') {
423e5db40b6Sitojun if (ftp_activeconn() < 0) {
4242d9ec4daSitojun n = snprintf(rbuf, sizeof(rbuf),
425*749ce5c9Sandvar "425 Cannot open data connection\r\n");
4266bc6e73bSlukem if (n < 0 || n >= (int)sizeof(rbuf))
427bc0d6cddSitojun n = 0;
428e5db40b6Sitojun }
429e5db40b6Sitojun }
430bc0d6cddSitojun if (n)
4312579358aSchristos (void)write(dst, rbuf, (size_t)n);
432e5db40b6Sitojun return n;
433e5db40b6Sitojun case LPRT:
434e5db40b6Sitojun case EPRT:
435e5db40b6Sitojun /* expecting "200 PORT command successful." */
436e5db40b6Sitojun if (code == 200) {
437e5db40b6Sitojun p = strstr(rbuf, "PORT");
438e5db40b6Sitojun if (p) {
439e5db40b6Sitojun p[0] = (state == LPRT) ? 'L' : 'E';
440e5db40b6Sitojun p[1] = 'P';
441e5db40b6Sitojun }
442e5db40b6Sitojun } else {
4434c1a6c87Schristos (void)close(wport4);
444e5db40b6Sitojun wport4 = -1;
445e5db40b6Sitojun }
4462579358aSchristos (void)write(dst, rbuf, (size_t)n);
447e5db40b6Sitojun return n;
448e5db40b6Sitojun case LPSV:
449e5db40b6Sitojun case EPSV:
450a5d0cbc5Sitojun /*
451a5d0cbc5Sitojun * expecting "227 Entering Passive Mode (x,x,x,x,x,x,x)"
452a5d0cbc5Sitojun * (in some cases result comes without paren)
453a5d0cbc5Sitojun */
454e5db40b6Sitojun if (code != 227) {
455e5db40b6Sitojun passivefail0:
4564c1a6c87Schristos (void)close(wport6);
457e5db40b6Sitojun wport6 = -1;
4582579358aSchristos (void)write(dst, rbuf, (size_t)n);
459e5db40b6Sitojun return n;
460e5db40b6Sitojun }
461e5db40b6Sitojun
462e5db40b6Sitojun {
463e5db40b6Sitojun unsigned int ho[4], po[2];
464e5db40b6Sitojun struct sockaddr_in *sin;
465e5db40b6Sitojun struct sockaddr_in6 *sin6;
466e5db40b6Sitojun u_short port;
467e5db40b6Sitojun
468e5db40b6Sitojun /*
469e5db40b6Sitojun * PASV result -> LPSV/EPSV result
470e5db40b6Sitojun */
471e5db40b6Sitojun p = param;
472cfe7f80fSdsl while (*p && *p != '(' && !isdigit((unsigned char)*p)) /*)*/
473e5db40b6Sitojun p++;
474e5db40b6Sitojun if (!*p)
475e5db40b6Sitojun goto passivefail0; /*XXX*/
476a5d0cbc5Sitojun if (*p == '(') /*)*/
477e5db40b6Sitojun p++;
478e5db40b6Sitojun n = sscanf(p, "%u,%u,%u,%u,%u,%u",
479e5db40b6Sitojun &ho[0], &ho[1], &ho[2], &ho[3], &po[0], &po[1]);
480e5db40b6Sitojun if (n != 6)
481e5db40b6Sitojun goto passivefail0; /*XXX*/
482e5db40b6Sitojun
483e5db40b6Sitojun /* keep PORT parameter */
484e5db40b6Sitojun memset(&data4, 0, sizeof(data4));
4852579358aSchristos sin = (void *)&data4;
486e5db40b6Sitojun sin->sin_len = sizeof(*sin);
487e5db40b6Sitojun sin->sin_family = AF_INET;
488e5db40b6Sitojun sin->sin_addr.s_addr = 0;
489e5db40b6Sitojun for (n = 0; n < 4; n++) {
4902579358aSchristos sin->sin_addr.s_addr |= htonl(((uint32_t)(ho[n] & 0xff)
4912579358aSchristos << (int)((3 - n) * 8)));
492e5db40b6Sitojun }
493e5db40b6Sitojun sin->sin_port = htons(((po[0] & 0xff) << 8) | (po[1] & 0xff));
494e5db40b6Sitojun
495e5db40b6Sitojun /* get ready for passive data connection */
496e5db40b6Sitojun memset(&data6, 0, sizeof(data6));
4972579358aSchristos sin6 = (void *)&data6;
498e5db40b6Sitojun sin6->sin6_len = sizeof(*sin6);
499e5db40b6Sitojun sin6->sin6_family = AF_INET6;
500e5db40b6Sitojun wport6 = socket(sin6->sin6_family, SOCK_STREAM, 0);
501e5db40b6Sitojun if (wport6 == -1) {
502e5db40b6Sitojun passivefail:
5032579358aSchristos return dprintf(src,
504e5db40b6Sitojun "500 could not translate from PASV\r\n");
505e5db40b6Sitojun }
506e5db40b6Sitojun #ifdef IPV6_FAITH
507e5db40b6Sitojun {
508e5db40b6Sitojun int on = 1;
509e5db40b6Sitojun error = setsockopt(wport6, IPPROTO_IPV6, IPV6_FAITH,
5102579358aSchristos &on, (socklen_t)sizeof(on));
511e5db40b6Sitojun if (error == -1)
512bc0d6cddSitojun exit_failure("setsockopt(IPV6_FAITH): %s", strerror(errno));
513e5db40b6Sitojun }
514e5db40b6Sitojun #endif
5152579358aSchristos error = bind(wport6, (void *)sin6, (socklen_t)sin6->sin6_len);
516e5db40b6Sitojun if (error == -1) {
5174c1a6c87Schristos (void)close(wport6);
518e5db40b6Sitojun wport6 = -1;
519e5db40b6Sitojun goto passivefail;
520e5db40b6Sitojun }
521e5db40b6Sitojun error = listen(wport6, 1);
522e5db40b6Sitojun if (error == -1) {
5234c1a6c87Schristos (void)close(wport6);
524e5db40b6Sitojun wport6 = -1;
525e5db40b6Sitojun goto passivefail;
526e5db40b6Sitojun }
527e5db40b6Sitojun
528e5db40b6Sitojun /* transmit LPSV or EPSV */
529e5db40b6Sitojun /*
530e5db40b6Sitojun * addr from dst, port from wport6
531e5db40b6Sitojun */
53252c469ffSitojun len = sizeof(data6);
5332579358aSchristos error = getsockname(wport6, (void *)&data6, &len);
534e5db40b6Sitojun if (error == -1) {
5354c1a6c87Schristos (void)close(wport6);
536e5db40b6Sitojun wport6 = -1;
537e5db40b6Sitojun goto passivefail;
538e5db40b6Sitojun }
5392579358aSchristos sin6 = (void *)&data6;
540e5db40b6Sitojun port = sin6->sin6_port;
541e5db40b6Sitojun
54252c469ffSitojun len = sizeof(data6);
5432579358aSchristos error = getsockname(dst, (void *)&data6, &len);
544e5db40b6Sitojun if (error == -1) {
5454c1a6c87Schristos (void)close(wport6);
546e5db40b6Sitojun wport6 = -1;
547e5db40b6Sitojun goto passivefail;
548e5db40b6Sitojun }
5492579358aSchristos sin6 = (void *)&data6;
550e5db40b6Sitojun sin6->sin6_port = port;
551e5db40b6Sitojun
552e5db40b6Sitojun if (state == LPSV) {
5532579358aSchristos a = (void *)&sin6->sin6_addr;
5542579358aSchristos p = (void *)&sin6->sin6_port;
5552579358aSchristos passivemode = 1;
5562579358aSchristos return dprintf(dst,
5572579358aSchristos "228 Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,"
5582579358aSchristos "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)\r\n",
559e5db40b6Sitojun 6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
560e5db40b6Sitojun UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
561e5db40b6Sitojun UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
562e5db40b6Sitojun UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
563e5db40b6Sitojun 2, UC(p[0]), UC(p[1]));
564e5db40b6Sitojun } else {
5652579358aSchristos passivemode = 1;
5662579358aSchristos return dprintf(dst,
567e5db40b6Sitojun "229 Entering Extended Passive Mode (|||%d|)\r\n",
568e5db40b6Sitojun ntohs(sin6->sin6_port));
569e5db40b6Sitojun }
570e5db40b6Sitojun }
571e5db40b6Sitojun }
572e5db40b6Sitojun
573e5db40b6Sitojun bad:
574bc0d6cddSitojun exit_failure("%s", strerror(errno));
575e5db40b6Sitojun /*NOTREACHED*/
576e5db40b6Sitojun return 0; /* to make gcc happy */
577e5db40b6Sitojun }
578e5db40b6Sitojun
5792579358aSchristos static ssize_t
ftp_copycommand(int src,int dst,enum state * state)580e5db40b6Sitojun ftp_copycommand(int src, int dst, enum state *state)
581e5db40b6Sitojun {
5822579358aSchristos int error, atmark;
5832579358aSchristos ssize_t n;
58452c469ffSitojun socklen_t len;
585e5db40b6Sitojun unsigned int af, hal, ho[16], pal, po[2];
586bc0d6cddSitojun char *a, *p, *q;
587e5db40b6Sitojun char cmd[5], *param;
588e5db40b6Sitojun struct sockaddr_in *sin;
589e5db40b6Sitojun struct sockaddr_in6 *sin6;
590e5db40b6Sitojun enum state nstate;
591e5db40b6Sitojun char ch;
592bc0d6cddSitojun int i;
593e5db40b6Sitojun
594e5db40b6Sitojun /* OOB data handling */
595e5db40b6Sitojun error = ioctl(src, SIOCATMARK, &atmark);
596e5db40b6Sitojun if (error != -1 && atmark == 1) {
597e5db40b6Sitojun n = read(src, rbuf, 1);
598e5db40b6Sitojun if (n == -1)
599e5db40b6Sitojun goto bad;
6002579358aSchristos (void)send(dst, rbuf, (size_t)n, MSG_OOB);
601e5db40b6Sitojun #if 0
602e5db40b6Sitojun n = read(src, rbuf, sizeof(rbuf));
603e5db40b6Sitojun if (n == -1)
604e5db40b6Sitojun goto bad;
6052579358aSchristos (void)write(dst, rbuf, (size_t)n);
606e5db40b6Sitojun return n;
607e5db40b6Sitojun #endif
608e5db40b6Sitojun }
609e5db40b6Sitojun
610e5db40b6Sitojun n = read(src, rbuf, sizeof(rbuf));
611e5db40b6Sitojun if (n <= 0)
612e5db40b6Sitojun return n;
613e5db40b6Sitojun rbuf[n] = '\0';
614e5db40b6Sitojun
615e5db40b6Sitojun if (n < 4) {
6162579358aSchristos (void)write(dst, rbuf, (size_t)n);
617e5db40b6Sitojun return n;
618e5db40b6Sitojun }
619e5db40b6Sitojun
620e5db40b6Sitojun /*
621e5db40b6Sitojun * parse argument
622e5db40b6Sitojun */
623e5db40b6Sitojun p = rbuf;
624e5db40b6Sitojun q = cmd;
625e5db40b6Sitojun for (i = 0; i < 4; i++) {
626cfe7f80fSdsl if (!isalpha((unsigned char)*p)) {
627e5db40b6Sitojun /* invalid command */
6282579358aSchristos (void)write(dst, rbuf, (size_t)n);
629e5db40b6Sitojun return n;
630e5db40b6Sitojun }
631cfe7f80fSdsl *q++ = islower((unsigned char)*p) ? toupper((unsigned char)*p) : *p;
632e5db40b6Sitojun p++;
633e5db40b6Sitojun }
634cfe7f80fSdsl if (!isspace((unsigned char)*p)) {
635e5db40b6Sitojun /* invalid command */
6362579358aSchristos (void)write(dst, rbuf, (size_t)n);
637e5db40b6Sitojun return n;
638e5db40b6Sitojun }
639e5db40b6Sitojun *q = '\0';
640e5db40b6Sitojun param = p;
641e5db40b6Sitojun /* param points to first non-command token, if any */
642cfe7f80fSdsl while (*param && isspace((unsigned char)*param))
643e5db40b6Sitojun param++;
644e5db40b6Sitojun if (!*param)
645e5db40b6Sitojun param = NULL;
646e5db40b6Sitojun
647e5db40b6Sitojun *state = NONE;
648e5db40b6Sitojun
649e5db40b6Sitojun if (strcmp(cmd, "LPRT") == 0 && param) {
650e5db40b6Sitojun /*
651e5db40b6Sitojun * LPRT -> PORT
652e5db40b6Sitojun */
653e5db40b6Sitojun nstate = LPRT;
654e5db40b6Sitojun
6554c1a6c87Schristos (void)close(wport4);
6564c1a6c87Schristos (void)close(wport6);
6574c1a6c87Schristos (void)close(port4);
6584c1a6c87Schristos (void)close(port6);
659e5db40b6Sitojun wport4 = wport6 = port4 = port6 = -1;
660e5db40b6Sitojun
661e5db40b6Sitojun if (epsvall) {
6622579358aSchristos return dprintf(src, "501 %s disallowed in EPSV ALL\r\n",
663e5db40b6Sitojun cmd);
664e5db40b6Sitojun }
665e5db40b6Sitojun
666e5db40b6Sitojun n = sscanf(param,
6672579358aSchristos "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,"
6682579358aSchristos "%u,%u,%u", &af, &hal, &ho[0], &ho[1], &ho[2], &ho[3],
669e5db40b6Sitojun &ho[4], &ho[5], &ho[6], &ho[7],
670e5db40b6Sitojun &ho[8], &ho[9], &ho[10], &ho[11],
671e5db40b6Sitojun &ho[12], &ho[13], &ho[14], &ho[15],
672e5db40b6Sitojun &pal, &po[0], &po[1]);
673e5db40b6Sitojun if (n != 21 || af != 6 || hal != 16|| pal != 2) {
6742579358aSchristos return dprintf(src,
675e5db40b6Sitojun "501 illegal parameter to LPRT\r\n");
676e5db40b6Sitojun }
677e5db40b6Sitojun
678e5db40b6Sitojun /* keep LPRT parameter */
679e5db40b6Sitojun memset(&data6, 0, sizeof(data6));
6802579358aSchristos sin6 = (void *)&data6;
681e5db40b6Sitojun sin6->sin6_len = sizeof(*sin6);
682e5db40b6Sitojun sin6->sin6_family = AF_INET6;
683e5db40b6Sitojun for (n = 0; n < 16; n++)
6849e8f1055Sitojun sin6->sin6_addr.s6_addr[n] = ho[n];
685e5db40b6Sitojun sin6->sin6_port = htons(((po[0] & 0xff) << 8) | (po[1] & 0xff));
686e5db40b6Sitojun
687e5db40b6Sitojun sendport:
688e5db40b6Sitojun /* get ready for active data connection */
68952c469ffSitojun len = sizeof(data4);
6902579358aSchristos error = getsockname(dst, (void *)&data4, &len);
691e5db40b6Sitojun if (error == -1) {
692e5db40b6Sitojun lprtfail:
6932579358aSchristos return dprintf(src,
694e5db40b6Sitojun "500 could not translate to PORT\r\n");
695e5db40b6Sitojun }
6962579358aSchristos if (((struct sockaddr *)(void *)&data4)->sa_family != AF_INET)
697e5db40b6Sitojun goto lprtfail;
6982579358aSchristos sin = (void *)&data4;
699e5db40b6Sitojun sin->sin_port = 0;
700e5db40b6Sitojun wport4 = socket(sin->sin_family, SOCK_STREAM, 0);
701e5db40b6Sitojun if (wport4 == -1)
702e5db40b6Sitojun goto lprtfail;
7032579358aSchristos error = bind(wport4, (void *)sin, (socklen_t)sin->sin_len);
704e5db40b6Sitojun if (error == -1) {
7054c1a6c87Schristos (void)close(wport4);
706e5db40b6Sitojun wport4 = -1;
707e5db40b6Sitojun goto lprtfail;
708e5db40b6Sitojun }
709e5db40b6Sitojun error = listen(wport4, 1);
710e5db40b6Sitojun if (error == -1) {
7114c1a6c87Schristos (void)close(wport4);
712e5db40b6Sitojun wport4 = -1;
713e5db40b6Sitojun goto lprtfail;
714e5db40b6Sitojun }
715e5db40b6Sitojun
716e5db40b6Sitojun /* transmit PORT */
71752c469ffSitojun len = sizeof(data4);
7182579358aSchristos error = getsockname(wport4, (void *)&data4, &len);
719e5db40b6Sitojun if (error == -1) {
7204c1a6c87Schristos (void)close(wport4);
721e5db40b6Sitojun wport4 = -1;
722e5db40b6Sitojun goto lprtfail;
723e5db40b6Sitojun }
7242579358aSchristos if (((struct sockaddr *)(void *)&data4)->sa_family != AF_INET) {
7254c1a6c87Schristos (void)close(wport4);
726e5db40b6Sitojun wport4 = -1;
727e5db40b6Sitojun goto lprtfail;
728e5db40b6Sitojun }
7292579358aSchristos sin = (void *)&data4;
7302579358aSchristos a = (void *)&sin->sin_addr;
7312579358aSchristos p = (void *)&sin->sin_port;
732e5db40b6Sitojun *state = nstate;
733e5db40b6Sitojun passivemode = 0;
7342579358aSchristos return dprintf(dst, "PORT %d,%d,%d,%d,%d,%d\r\n",
7352579358aSchristos UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
736e5db40b6Sitojun } else if (strcmp(cmd, "EPRT") == 0 && param) {
737e5db40b6Sitojun /*
738e5db40b6Sitojun * EPRT -> PORT
739e5db40b6Sitojun */
740e5db40b6Sitojun char *afp, *hostp, *portp;
741e5db40b6Sitojun struct addrinfo hints, *res;
742e5db40b6Sitojun
743e5db40b6Sitojun nstate = EPRT;
744e5db40b6Sitojun
7454c1a6c87Schristos (void)close(wport4);
7464c1a6c87Schristos (void)close(wport6);
7474c1a6c87Schristos (void)close(port4);
7484c1a6c87Schristos (void)close(port6);
749e5db40b6Sitojun wport4 = wport6 = port4 = port6 = -1;
750e5db40b6Sitojun
751e5db40b6Sitojun if (epsvall) {
7522579358aSchristos return dprintf(src, "501 %s disallowed in EPSV ALL\r\n",
753e5db40b6Sitojun cmd);
754e5db40b6Sitojun }
755e5db40b6Sitojun
756e5db40b6Sitojun p = param;
757e5db40b6Sitojun ch = *p++; /* boundary character */
758e5db40b6Sitojun afp = p;
759e5db40b6Sitojun while (*p && *p != ch)
760e5db40b6Sitojun p++;
761e5db40b6Sitojun if (!*p) {
762e5db40b6Sitojun eprtparamfail:
7632579358aSchristos return dprintf(src,
764e5db40b6Sitojun "501 illegal parameter to EPRT\r\n");
765e5db40b6Sitojun }
766e5db40b6Sitojun *p++ = '\0';
767e5db40b6Sitojun hostp = p;
768e5db40b6Sitojun while (*p && *p != ch)
769e5db40b6Sitojun p++;
770e5db40b6Sitojun if (!*p)
771e5db40b6Sitojun goto eprtparamfail;
772e5db40b6Sitojun *p++ = '\0';
773e5db40b6Sitojun portp = p;
774e5db40b6Sitojun while (*p && *p != ch)
775e5db40b6Sitojun p++;
776e5db40b6Sitojun if (!*p)
777e5db40b6Sitojun goto eprtparamfail;
778e5db40b6Sitojun *p++ = '\0';
779e5db40b6Sitojun
780e5db40b6Sitojun n = sscanf(afp, "%d", &af);
781e5db40b6Sitojun if (n != 1 || af != 2) {
7822579358aSchristos return dprintf(src,
783e5db40b6Sitojun "501 unsupported address family to EPRT\r\n");
784e5db40b6Sitojun }
785e5db40b6Sitojun memset(&hints, 0, sizeof(hints));
786e5db40b6Sitojun hints.ai_family = AF_UNSPEC;
787a5d0cbc5Sitojun hints.ai_socktype = SOCK_STREAM;
788ecf55737Sitojun hints.ai_protocol = IPPROTO_TCP;
789e5db40b6Sitojun error = getaddrinfo(hostp, portp, &hints, &res);
790e5db40b6Sitojun if (error) {
7912579358aSchristos return dprintf(src,
792e5db40b6Sitojun "501 EPRT: %s\r\n", gai_strerror(error));
793e5db40b6Sitojun }
794e5db40b6Sitojun if (res->ai_next) {
795522016beSitojun freeaddrinfo(res);
7962579358aSchristos return dprintf(src,
7972579358aSchristos "501 EPRT: %s resolved to multiple addresses\r\n",
7982579358aSchristos hostp);
799e5db40b6Sitojun }
800e5db40b6Sitojun
801e5db40b6Sitojun memcpy(&data6, res->ai_addr, res->ai_addrlen);
802e5db40b6Sitojun
803522016beSitojun freeaddrinfo(res);
804e5db40b6Sitojun goto sendport;
805e5db40b6Sitojun } else if (strcmp(cmd, "LPSV") == 0 && !param) {
806e5db40b6Sitojun /*
807e5db40b6Sitojun * LPSV -> PASV
808e5db40b6Sitojun */
809e5db40b6Sitojun nstate = LPSV;
810e5db40b6Sitojun
8114c1a6c87Schristos (void)close(wport4);
8124c1a6c87Schristos (void)close(wport6);
8134c1a6c87Schristos (void)close(port4);
8144c1a6c87Schristos (void)close(port6);
815e5db40b6Sitojun wport4 = wport6 = port4 = port6 = -1;
816e5db40b6Sitojun
817e5db40b6Sitojun if (epsvall) {
8182579358aSchristos return dprintf(src, "501 %s disallowed in EPSV ALL\r\n",
819e5db40b6Sitojun cmd);
820e5db40b6Sitojun }
821e5db40b6Sitojun
822e5db40b6Sitojun *state = LPSV;
823e5db40b6Sitojun passivemode = 0; /* to be set to 1 later */
8242579358aSchristos /* transmit PASV */
8252579358aSchristos return dprintf(dst, "PASV\r\n");
826e5db40b6Sitojun } else if (strcmp(cmd, "EPSV") == 0 && !param) {
827e5db40b6Sitojun /*
828e5db40b6Sitojun * EPSV -> PASV
829e5db40b6Sitojun */
8304c1a6c87Schristos (void)close(wport4);
8314c1a6c87Schristos (void)close(wport6);
8324c1a6c87Schristos (void)close(port4);
8334c1a6c87Schristos (void)close(port6);
834e5db40b6Sitojun wport4 = wport6 = port4 = port6 = -1;
835e5db40b6Sitojun
836e5db40b6Sitojun *state = EPSV;
837e5db40b6Sitojun passivemode = 0; /* to be set to 1 later */
8382579358aSchristos return dprintf(dst, "PASV\r\n");
8392579358aSchristos } else if (strcmp(cmd, "EPSV") == 0 && param &&
8402579358aSchristos strncasecmp(param, "ALL", 3) == 0 &&
8412579358aSchristos isspace((unsigned char)param[3])) {
842e5db40b6Sitojun /*
843e5db40b6Sitojun * EPSV ALL
844e5db40b6Sitojun */
845e5db40b6Sitojun epsvall = 1;
8462579358aSchristos return dprintf(src, "200 EPSV ALL command successful.\r\n");
847e5db40b6Sitojun } else if (strcmp(cmd, "PORT") == 0 || strcmp(cmd, "PASV") == 0) {
848e5db40b6Sitojun /*
849e5db40b6Sitojun * reject PORT/PASV
850e5db40b6Sitojun */
8512579358aSchristos return dprintf(src, "502 %s not implemented.\r\n", cmd);
852e5db40b6Sitojun } else if (passivemode
853e5db40b6Sitojun && (strcmp(cmd, "STOR") == 0
854e5db40b6Sitojun || strcmp(cmd, "STOU") == 0
855e5db40b6Sitojun || strcmp(cmd, "RETR") == 0
856e5db40b6Sitojun || strcmp(cmd, "LIST") == 0
857e5db40b6Sitojun || strcmp(cmd, "NLST") == 0
858e5db40b6Sitojun || strcmp(cmd, "APPE") == 0)) {
859e5db40b6Sitojun /*
860e5db40b6Sitojun * commands with data transfer. need to care about passive
861e5db40b6Sitojun * mode data connection.
862e5db40b6Sitojun */
863e5db40b6Sitojun
8642579358aSchristos *state = NONE;
865e5db40b6Sitojun if (ftp_passiveconn() < 0) {
8662579358aSchristos return dprintf(src,
867*749ce5c9Sandvar "425 Cannot open data connection\r\n");
868e5db40b6Sitojun } else {
869e5db40b6Sitojun /* simply relay the command */
8702579358aSchristos return write(dst, rbuf, (size_t)n);
871e5db40b6Sitojun }
872e5db40b6Sitojun } else {
873e5db40b6Sitojun /* simply relay it */
874e5db40b6Sitojun *state = NONE;
8752579358aSchristos return write(dst, rbuf, (size_t)n);
876e5db40b6Sitojun }
877e5db40b6Sitojun
878e5db40b6Sitojun bad:
879bc0d6cddSitojun exit_failure("%s", strerror(errno));
880e5db40b6Sitojun /*NOTREACHED*/
881e5db40b6Sitojun return 0; /* to make gcc happy */
882e5db40b6Sitojun }
883