Lets keep our version of opftpd in the CVS
[oftpd.git] / src / ftp_listener.c
1 /*
2
3 $Id$
4  
5 This is the code that waits for client connections and creates the
6 threads that handle them.  When ftp_listener_init() is called, it binds
7 to the appropriate socket and sets up the other values for the
8 structure.  Then, when ftp_listener_start() is called, a thread is
9 started dedicatd to accepting connections.
10
11 This thread waits for input on two file descriptors.  If input arrives
12 on the socket, then it accepts the connection and creates a thread to
13 handle it.  If input arrives on the shutdown_request pipe, then the
14 thread terminates.  This is how ftp_listener_stop() signals the listener
15 to end.
16
17 */
18
19 #include <config.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <netinet/in.h>
23 #include <arpa/inet.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <pthread.h>
28 #include <stdlib.h>
29 #include <limits.h>
30 #include <syslog.h>
31 #include <stdio.h>
32 #include <netdb.h>
33 #include <netinet/tcp.h>
34 #include <fcntl.h>
35
36 #if TIME_WITH_SYS_TIME
37 # include <sys/time.h>
38 # include <time.h>
39 #else
40 # if HAVE_SYS_TIME_H
41 #  include <sys/time.h>
42 # else
43 #  include <time.h>
44 # endif
45 #endif
46
47 #include "daemon_assert.h"
48 #include "telnet_session.h"
49 #include "ftp_session.h"
50 #include "ftp_listener.h"
51 #include "watchdog.h"
52 #include "af_portability.h"
53
54
55
56 /* maximum number of consecutive errors in accept()
57    before we terminate listener                     */
58 #define MAX_ACCEPT_ERROR 10
59
60 /* buffer to hold address string */
61 #define ADDR_BUF_LEN 100
62
63 /* information for a specific connection */
64 typedef struct connection_info {
65     ftp_listener_t *ftp_listener;
66     telnet_session_t telnet_session;
67     ftp_session_t ftp_session;
68     watched_t watched;
69     
70     struct connection_info *next;
71 } connection_info_t;
72
73 /* prototypes */
74 static int invariant(const ftp_listener_t *f);
75 static void *connection_acceptor(ftp_listener_t *f);
76 static void addr_to_string(const sockaddr_storage_t *s, char *addr);
77 static void *connection_handler(connection_info_t *info);
78 static void connection_handler_cleanup(connection_info_t *info);
79
80 /* initialize an FTP listener */
81 int ftp_listener_init(ftp_listener_t *f, 
82                       char *address, 
83                       int port, 
84                       int max_connections,
85                       int inactivity_timeout,
86                       error_t *err)
87 {
88     sockaddr_storage_t sock_addr;
89     int fd;
90     int flags;
91     int pipefds[2];
92     int reuseaddr;
93     char dir[PATH_MAX+1];
94     char buf[ADDR_BUF_LEN+1];
95     const char *inet_ntop_ret;
96
97     daemon_assert(f != NULL);
98     daemon_assert(port >= 0);
99     daemon_assert(port < 65536);
100     daemon_assert(max_connections > 0);
101     daemon_assert(err != NULL);
102
103     /* get our current directory */
104     if (getcwd(dir, sizeof(dir)) == NULL) {
105         error_init(err, errno, "error getting current directory; %s",
106                    strerror(errno));
107         return 0;
108     }
109
110     /* set up our socket address */
111     memset(&sock_addr, 0, sizeof(sockaddr_storage_t));
112
113     if (address == NULL) {
114 #ifdef INET6
115         SAFAM(&sock_addr) = AF_INET6;
116         memcpy(&SIN6ADDR(&sock_addr), &in6addr_any, sizeof(struct in6_addr));
117 #else
118         SAFAM(&sock_addr) = AF_INET;
119         sock_addr.sin_addr.s_addr = INADDR_ANY;
120 #endif
121     } else {
122         int gai_err;
123         struct addrinfo hints;
124         struct addrinfo *res;
125
126         memset(&hints, 0, sizeof(hints));
127
128 /* - This code should be able to parse both IPv4 and IPv6 internet
129  * addresses and put them in the sock_addr variable.
130  * - Much neater now.
131  * - Bug: Can't handle hostnames, only IP addresses.  Not sure
132  * exactly why.  But then again, I'm not sure exactly why
133  * there is a man page for getipnodebyname (which getaddrinfo
134  * says it uses) but no corresponding function in libc.
135  * -- Matthew Danish [3/20/2001]
136  */
137
138 #ifdef INET6
139         hints.ai_family = AF_INET6;
140 #else
141         hints.ai_family = AF_INET;
142 #endif
143
144         hints.ai_flags  = AI_PASSIVE;
145
146         gai_err = getaddrinfo(address, NULL, &hints, &res);
147         if (gai_err != 0) {
148             error_init(err, gai_err, "error parsing server socket address; %s", 
149                        gai_strerror(gai_err));
150             return 0;
151         }
152
153         memcpy(&sock_addr, res->ai_addr, res->ai_addrlen);
154         freeaddrinfo(res);
155     } 
156
157     if (port == 0) {
158         SINPORT(&sock_addr) = htons(DEFAULT_FTP_PORT);
159     } else {
160         SINPORT(&sock_addr) = htons(port);
161     }
162
163
164     inet_ntop_ret = inet_ntop(SAFAM(&sock_addr), 
165                               (void *)&SINADDR(&sock_addr), 
166                               buf, 
167                               sizeof(buf));
168     if (inet_ntop_ret == NULL) {
169         error_init(err, errno, "error converting server address to ASCII; %s", 
170                    strerror(errno));
171         return 0;
172     }
173     
174     /* Put some information in syslog */
175     syslog(LOG_INFO, "Binding interface '%s', port %d, max clients %d\n", buf, 
176         ntohs(SINPORT(&sock_addr)), max_connections);    
177     
178
179     /* okay, finally do some socket manipulation */
180     fd = socket(AF_INET, SOCK_STREAM, 0);
181     reopen_syslog_hack (fd);
182     if (fd == -1) {
183         error_init(err, errno, "error creating socket; %s", strerror(errno));
184         return 0;
185     }
186
187     reuseaddr = 1;
188     if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuseaddr, 
189                    sizeof(int)) !=0) 
190     {
191         close(fd);
192         error_init(err, errno, "error setting socket to reuse address; %s", 
193                    strerror(errno));
194         return 0;
195     }
196
197     if (bind(fd, (struct sockaddr *)&sock_addr, 
198              sizeof(struct sockaddr_in)) != 0) 
199     {
200         close(fd);
201         error_init(err, errno, "error binding address; %s", strerror(errno));
202         return 0;
203     }
204
205     if (listen(fd, SOMAXCONN) != 0) {
206         close(fd);
207         error_init(err, errno, "error setting socket to listen; %s", 
208                    strerror(errno));
209         return 0;
210     }
211
212     /* prevent socket from blocking on accept() */
213     flags = fcntl(fd, F_GETFL);
214     if (flags == -1) {
215         close(fd);
216         error_init(err, errno, "error getting flags on socket; %s", 
217                    strerror(errno));
218         return 0;
219     }
220     if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
221         close(fd);
222         error_init(err, errno, "error setting socket to non-blocking; %s", 
223                    strerror(errno));
224         return 0;
225     }
226
227     /* create a pipe to wake up our listening thread */
228     if (pipe(pipefds) != 0) {
229         close(fd);
230         error_init(err, errno, "error creating pipe for internal use; %s", 
231                    strerror(errno));
232         return 0;
233     }
234
235     /* now load the values into the structure, since we can't fail from
236        here */
237     f->fd = fd;
238     f->max_connections = max_connections;
239     f->num_connections = 0;
240     f->inactivity_timeout = inactivity_timeout;
241     pthread_mutex_init(&f->mutex, NULL);
242
243     daemon_assert(strlen(dir) < sizeof(f->dir));
244     strcpy(f->dir, dir);
245     f->listener_running = 0;
246
247     f->shutdown_request_send_fd = pipefds[1];
248     f->shutdown_request_recv_fd = pipefds[0];
249     pthread_cond_init(&f->shutdown_cond, NULL);
250
251     daemon_assert(invariant(f));
252     return 1;
253 }
254
255 /* receive connections */
256 int ftp_listener_start(ftp_listener_t *f, error_t *err)
257 {
258     pthread_t thread_id;
259     int ret_val;
260     int error_code;
261
262     daemon_assert(invariant(f));
263     daemon_assert(err != NULL);
264
265     error_code = pthread_create(&thread_id, 
266                                 NULL, 
267                                 (void *(*)())connection_acceptor, 
268                                 f);
269
270     if (error_code == 0) {
271         f->listener_running = 1;
272         f->listener_thread = thread_id;
273         ret_val = 1;
274     } else {
275         error_init(err, error_code, "unable to create thread");
276         ret_val = 0;
277     }
278
279     daemon_assert(invariant(f));
280
281     return ret_val;
282 }
283
284
285 #ifndef NDEBUG
286 static int invariant(const ftp_listener_t *f) 
287 {
288     int dir_len;
289
290     if (f == NULL) {
291         return 0;
292     }
293     if (f->fd < 0) {
294         return 0;
295     }
296     if (f->max_connections <= 0) {
297         return 0;
298     }
299     if ((f->num_connections < 0) || (f->num_connections > f->max_connections)) {
300         return 0;
301     }
302     dir_len = strlen(f->dir);
303     if ((dir_len <= 0) || (dir_len > PATH_MAX)) {
304         return 0;
305     }
306     if (f->shutdown_request_send_fd < 0) {
307         return 0;
308     }
309     if (f->shutdown_request_recv_fd < 0) {
310         return 0;
311     }
312     return 1;
313 }
314 #endif /* NDEBUG */
315
316 /* handle incoming connections */
317 static void *connection_acceptor(ftp_listener_t *f)
318 {
319     error_t err;
320     int num_error;
321
322     int fd;
323     int tcp_nodelay;
324     sockaddr_storage_t client_addr;
325     sockaddr_storage_t server_addr;
326     unsigned addr_len;
327     
328     connection_info_t *info;
329     pthread_t thread_id;
330     int error_code;
331
332     fd_set readfds;
333
334     daemon_assert(invariant(f));
335
336     if (!watchdog_init(&f->watchdog, f->inactivity_timeout, &err)) {
337         syslog(LOG_ERR, "Error initializing watchdog thread; %s", 
338             error_get_desc(&err));
339         return NULL;
340     }
341
342     num_error = 0;
343     for (;;) {
344
345          /* wait for something to happen */
346          FD_ZERO(&readfds);
347          FD_SET(f->fd, &readfds);
348          FD_SET(f->shutdown_request_recv_fd, &readfds);
349          select(FD_SETSIZE, &readfds, NULL, NULL, NULL);
350
351          /* if data arrived on our pipe, we've been asked to exit */
352          if (FD_ISSET(f->shutdown_request_recv_fd, &readfds)) {
353              close(f->fd);
354              syslog(LOG_INFO, "listener no longer accepting connections");
355              pthread_exit(NULL);
356          }
357
358          /* otherwise accept our pending connection (if any) */
359          addr_len = sizeof(sockaddr_storage_t);
360          fd = accept(f->fd, (struct sockaddr *)&client_addr, &addr_len);
361          reopen_syslog_hack (fd);
362          if (fd >= 0) {
363
364              tcp_nodelay = 1;
365              if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&tcp_nodelay,
366                  sizeof(int)) != 0)
367              {
368                  syslog(LOG_ERR,
369                    "error in setsockopt(), FTP server dropping connection; %s",
370                    strerror(errno));
371                  close(fd);
372                  continue;
373              }
374
375              addr_len = sizeof(sockaddr_storage_t);
376              if (getsockname(fd, (struct sockaddr *)&server_addr, 
377                  &addr_len) == -1) 
378              {
379                  syslog(LOG_ERR, 
380                    "error in getsockname(), FTP server dropping connection; %s",
381                    strerror(errno));
382                  close(fd);
383                  continue;
384              }
385
386              info = (connection_info_t *)malloc(sizeof(connection_info_t));
387              if (info == NULL) {
388                  syslog(LOG_CRIT, 
389                      "out of memory, FTP server dropping connection");
390                  close(fd);
391                  continue;
392              }
393              info->ftp_listener = f;
394
395              telnet_session_init(&info->telnet_session, fd, fd);
396
397              if (!ftp_session_init(&info->ftp_session, 
398                                    &client_addr, 
399                                    &server_addr, 
400                                    &info->telnet_session,  
401                                    f->dir, 
402                                    &err)) 
403              {
404                  syslog(LOG_ERR, 
405                      "error initializing FTP session, FTP server exiting; %s",
406                      error_get_desc(&err));
407                  close(fd);
408                  telnet_session_destroy(&info->telnet_session);
409                  free(info);
410                  continue;
411              }
412
413
414              error_code = pthread_create(&thread_id, 
415                           NULL, 
416                           (void *(*)())connection_handler, 
417                           info);
418
419              if (error_code != 0) {
420                  syslog(LOG_ERR, "error creating new thread; %d", error_code);
421                  close(fd);
422                  telnet_session_destroy(&info->telnet_session);
423                  free(info);
424              }
425
426              num_error = 0;
427          } else {
428              if ((errno == ECONNABORTED) || (errno == ECONNRESET)) {
429                  syslog(LOG_NOTICE, 
430                      "interruption accepting FTP connection; %s", 
431                      strerror(errno));
432              } else {
433                  syslog(LOG_WARNING, 
434                      "error accepting FTP connection; %s", 
435                      strerror(errno));
436                  ++num_error;
437              }
438              if (num_error >= MAX_ACCEPT_ERROR) {
439                  syslog(LOG_ERR, 
440                    "too many consecutive errors, FTP server exiting");
441                  return NULL;
442              }
443          }
444     }
445 }
446
447 /* convert an address to a printable string */
448 /* NOT THREADSAFE - wrap with a mutex before calling! */
449 static char *addr2string(const sockaddr_storage_t *s)
450 {
451     static char addr[IP_ADDRSTRLEN+1];
452     int error;
453     char *ret_val;
454
455     daemon_assert(s != NULL);
456
457 #ifdef INET6
458     error = getnameinfo((struct sockaddr *)s, 
459                          sizeof(sockaddr_storage_t),
460                          addr, 
461                          sizeof(addr), 
462                          NULL,
463                          0, 
464                          NI_NUMERICHOST);
465     if (error != 0) {
466         syslog(LOG_WARN, "getnameinfo error; %s", gai_strerror(error));
467         ret_val = "Unknown IP";
468     } else {
469         ret_val = addr;
470     }
471 #else
472     ret_val = inet_ntoa(s->sin_addr);
473 #endif
474
475     return ret_val;
476 }
477
478
479 static void *connection_handler(connection_info_t *info) 
480 {
481     ftp_listener_t *f;
482     int num_connections;
483     char drop_reason[80];
484
485     /* for ease of use only */
486     f = info->ftp_listener;
487
488     /* don't save state for pthread_join() */
489     pthread_detach(pthread_self());
490
491     /* set up our watchdog */
492     pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
493     watchdog_add_watched(&f->watchdog, &info->watched);
494
495     /* set up our cleanup handler */
496     pthread_cleanup_push((void (*)())connection_handler_cleanup, info);
497
498     /* process global data */
499     pthread_mutex_lock(&f->mutex);
500     num_connections = ++f->num_connections;
501     syslog(LOG_INFO, "%s port %d connection", 
502         addr2string(&info->ftp_session.client_addr), 
503         ntohs(SINPORT(&info->ftp_session.client_addr)));
504     pthread_mutex_unlock(&f->mutex);
505
506     /* handle the session */
507     if (num_connections <= f->max_connections) {
508
509         ftp_session_run(&info->ftp_session, &info->watched);
510
511     } else {
512
513         /* too many users */
514         sprintf(drop_reason, 
515           "Too many users logged in, dropping connection (%d logins maximum)", 
516           f->max_connections);
517         ftp_session_drop(&info->ftp_session, drop_reason);
518
519         /* log the rejection */
520         pthread_mutex_lock(&f->mutex);
521         syslog(LOG_WARNING, 
522           "%s port %d exceeds max users (%d), dropping connection",
523           addr2string(&info->ftp_session.client_addr), 
524           ntohs(SINPORT(&info->ftp_session.client_addr)),
525           num_connections);
526         pthread_mutex_unlock(&f->mutex);
527
528     }
529
530     /* exunt (pop calls cleanup function) */
531     pthread_cleanup_pop(1);
532
533     /* return for form :) */
534     return NULL;
535 }
536
537 /* clean up a connection */
538 static void connection_handler_cleanup(connection_info_t *info) 
539 {
540     ftp_listener_t *f;
541
542     f = info->ftp_listener;
543
544     watchdog_remove_watched(&info->watched);
545
546     pthread_mutex_lock(&f->mutex);
547
548     f->num_connections--;
549     pthread_cond_signal(&f->shutdown_cond);
550
551     syslog(LOG_INFO, 
552       "%s port %d disconnected", 
553       addr2string(&info->ftp_session.client_addr),
554       ntohs(SINPORT(&info->ftp_session.client_addr)));
555
556     pthread_mutex_unlock(&f->mutex);
557
558     ftp_session_destroy(&info->ftp_session);
559     telnet_session_destroy(&info->telnet_session);
560
561     free(info);
562 }
563
564 void ftp_listener_stop(ftp_listener_t *f)
565 {
566     daemon_assert(invariant(f));
567
568     /* write a byte to the listening thread - this will wake it up */
569     write(f->shutdown_request_send_fd, "", 1);
570
571     /* wait for client connections to complete */
572     pthread_mutex_lock(&f->mutex);
573     while (f->num_connections > 0) {
574         pthread_cond_wait(&f->shutdown_cond, &f->mutex);
575     }
576     pthread_mutex_unlock(&f->mutex);
577 }
578