]> git.sesse.net Git - betaftpd/blobdiff - ftpd.c
Fixed a file transfer bug, where the initial "150 Opening connection (...)" message...
[betaftpd] / ftpd.c
diff --git a/ftpd.c b/ftpd.c
index 9cbf5e851507d3bd342bc0c070d2639585225312..19816bfd489801da7c80087166d1563831b2ad31 100644 (file)
--- a/ftpd.c
+++ b/ftpd.c
@@ -2,7 +2,7 @@
     Copyright (C) 1999-2000 Steinar H. Gunderson
 
     This program is is free software; you can redistribute it and/or modify
     Copyright (C) 1999-2000 Steinar H. Gunderson
 
     This program is is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License, version 2 if the
+    it under the terms of the GNU General Public License, version 2 of the
     License as published by the Free Software Foundation.
 
     This program is distributed in the hope that it will be useful,
     License as published by the Free Software Foundation.
 
     This program is distributed in the hope that it will be useful,
 #include <config.h>
 #endif
 
 #include <config.h>
 #endif
 
-#if HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
 #if HAVE_ERRNO_H
 #include <errno.h>
 #endif
 #if HAVE_ERRNO_H
 #include <errno.h>
 #endif
 #include <stropts.h>
 #endif
 
 #include <stropts.h>
 #endif
 
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
 #if HAVE_SYS_CONF_H
 #include <sys/conf.h>
 #endif
 #if HAVE_SYS_CONF_H
 #include <sys/conf.h>
 #endif
 #include <unistd.h>
 #endif
 
 #include <unistd.h>
 #endif
 
+#if HAVE_NETINET_IN_SYSTM_H
+#include <netinet/in_systm.h>
+#endif
+
 #if HAVE_NETINET_IN_H
 #include <netinet/in.h>
 #endif
 
 #if HAVE_NETINET_IN_H
 #include <netinet/in.h>
 #endif
 
+#if HAVE_NETINET_IP_H
+#include <netinet/ip.h>
+#endif
+
+#if HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+
 #if HAVE_ARPA_INET_H
 #include <arpa/inet.h>
 #endif
 #if HAVE_ARPA_INET_H
 #include <arpa/inet.h>
 #endif
 #include <sys/stat.h>
 #endif
 
 #include <sys/stat.h>
 #endif
 
-#if HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
-
 #if HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 #endif
 
 #if HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 #endif
 
-#if HAVE_NETINET_IN_SYSTM_H
-#include <netinet/in_systm.h>
-#endif
-
-#if HAVE_NETINET_IP_H
-#include <netinet/ip.h>
-#endif
-
-#if HAVE_NETINET_TCP_H
-#include <netinet/tcp.h>
-#endif
-
 #if HAVE_LINUX_SOCKET_H
 #include <linux/socket.h>
 #endif
 
 #if HAVE_LINUX_SOCKET_H
 #include <linux/socket.h>
 #endif
 
+#if HAVE_LINUX_TCP_H
+#include <linux/tcp.h>
+#endif
+
 #if HAVE_MMAP
 #include <sys/mman.h>
 #endif
 #if HAVE_MMAP
 #include <sys/mman.h>
 #endif
 #include <ascii.h>
 #endif
 
 #include <ascii.h>
 #endif
 
+#if WANT_DCACHE
+#include <dcache.h>
+#endif
+
 #ifndef MAP_FAILED
 #define MAP_FAILED -1
 #endif
 #ifndef MAP_FAILED
 #define MAP_FAILED -1
 #endif
@@ -180,6 +184,7 @@ struct ftran *first_ftran = NULL;
 #if WANT_DCACHE
 struct dcache *first_dcache = NULL;
 #endif
 #if WANT_DCACHE
 struct dcache *first_dcache = NULL;
 #endif
+char message_buf[512];
 
 #if HAVE_POLL
 unsigned int highest_fds = 0;
 
 #if HAVE_POLL
 unsigned int highest_fds = 0;
@@ -198,6 +203,10 @@ fd_set master_fds, master_send_fds;
 FILE *xferlog = NULL;
 #endif
 
 FILE *xferlog = NULL;
 #endif
 
+#if HAVE_LINUX_SENDFILE || HAVE_BSD_SENDFILE
+int sendfile_supported = 1;
+#endif
+
 /*
  * This variable specifies if it's soon time to check for timed out
  * clients, and timed out directory listing cache entries. It is
 /*
  * This variable specifies if it's soon time to check for timed out
  * clients, and timed out directory listing cache entries. It is
@@ -357,7 +366,7 @@ struct conn *alloc_new_conn(const int sock)
        const unsigned int one = 1;
        struct conn *c = (struct conn *)(malloc(sizeof(struct conn)));
 
        const unsigned int one = 1;
        struct conn *c = (struct conn *)(malloc(sizeof(struct conn)));
 
-       if (c == NULL) return c;
+       if (c == NULL) return NULL;
 
        if (sock != -1) {
                ioctl(sock, FIONBIO, &one);
 
        if (sock != -1) {
                ioctl(sock, FIONBIO, &one);
@@ -382,6 +391,7 @@ struct conn *alloc_new_conn(const int sock)
 #if WANT_ASCII
        c->ascii_mode = 0;
 #endif
 #if WANT_ASCII
        c->ascii_mode = 0;
 #endif
+       c->free_me = 0;
 
        /*
         * equals:
 
        /*
         * equals:
@@ -398,7 +408,7 @@ struct conn *alloc_new_conn(const int sock)
 
        time(&(c->last_transfer));
 
 
        time(&(c->last_transfer));
 
-       /*list_clients();*/
+       /* list_clients(); */
 
        return c;
 }
 
        return c;
 }
@@ -439,30 +449,6 @@ struct ftran *alloc_new_ftran(const int sock, const struct conn * const c)
        return f;
 }
 
        return f;
 }
 
-#if WANT_DCACHE
-/*
- * alloc_new_dcache():
- *             Allocates a new directory cache entry (type `struct dcache'),
- *             and adds it to the linked list.
- */
-struct dcache *alloc_new_dcache()
-{
-       struct dcache *d = (struct dcache *)(malloc(sizeof(struct dcache)));
-
-       if (d == NULL) return d;
-
-       d->use_count = 0;
-       d->last_used = 0;
-       strcpy(d->dir_name, "");
-       d->dir_data = NULL;
-
-       add_to_linked_list((struct list_element *)first_dcache,
-                          (struct list_element *)d);
-
-       return d;
-}
-#endif
-
 /*
  * destroy_conn():
  *             Destroy a control connection, remove it from the linked
 /*
  * destroy_conn():
  *             Destroy a control connection, remove it from the linked
@@ -534,27 +520,6 @@ void destroy_ftran(struct ftran * const f)
        remove_from_linked_list((struct list_element *)f);
 }
 
        remove_from_linked_list((struct list_element *)f);
 }
 
-#if WANT_DCACHE
-/*
- * destroy_dcache():
- *             Destroy a directory listing cache entry, remove it from the
- *             linked list, and clean up after it.
- *
- *             If you free a cache entry that is in use (use_count > 0),
- *             BetaFTPD will most likely crash (later). The thing you're supposed
- *             to do when you're done with a dcache entry, is to decrement
- *             its use_count, and let the timeout functions do the destroying
- *             when it's time to do so.
- */
-void destroy_dcache(struct dcache * const d)
-{
-        if (d == NULL) return;
-
-       if (d->dir_data != NULL) free(d->dir_data);
-       remove_from_linked_list((struct list_element *)d);
-}
-#endif
-
 /*
  * process_all_clients():
  *             Processes all the _control_ connections in active_clients
 /*
  * process_all_clients():
  *             Processes all the _control_ connections in active_clients
@@ -625,6 +590,34 @@ int process_all_clients(const fd_set * const active_clients, const int num_ac)
        return checked_through;
 }
 
        return checked_through;
 }
 
+/*
+ * finish_transfer():
+ *             Send a message that the transfer is completed, write xferlog
+ *             entry (optional), and update the last_transfer record in the
+ *             file transfer object. Goes for both uploads and downloads.
+ */
+void finish_transfer(struct ftran * const f)
+{
+       char finished[] = "226 Transfer complete.\r\n";
+               if (send(f->owner->sock, finished, strlen(finished), 0) == -1 && errno == EPIPE) {
+               destroy_conn(f->owner);
+               return;
+       }
+       
+       time(&(f->owner->last_transfer));
+
+#if WANT_XFERLOG
+       if (!f->dir_listing) {
+               write_xferlog(f);
+       }
+#endif
+
+       destroy_ftran(f);
+#if WANT_FULLSCREEN
+       update_display(first_conn);
+#endif
+}
+
 /*
  * process_all_sendfiles():
  *             Sends data to all clients that are ready to receive it.
 /*
  * process_all_sendfiles():
  *             Sends data to all clients that are ready to receive it.
@@ -639,15 +632,21 @@ int process_all_sendfiles(fd_set * const active_clients, const int num_ac)
 {
        struct ftran *f = NULL, *next = first_ftran->next_ftran;
        int checked_through = 0;
 {
        struct ftran *f = NULL, *next = first_ftran->next_ftran;
        int checked_through = 0;
-       struct sockaddr tempaddr;
-       int tempaddr_len = sizeof(tempaddr);
+       int tempaddr_len = sizeof(struct sockaddr_in);
  
        while (next != NULL && checked_through < num_ac) {
                f = next;
                next = f->next_ftran;
 
  
        while (next != NULL && checked_through < num_ac) {
                f = next;
                next = f->next_ftran;
 
+#if WANT_UPLOAD
+               if ((f->upload == 1) && (fds[f->sock].revents & POLLHUP)) {
+                       finish_transfer(f);
+                       continue;
+               }
+#endif
+
 #if HAVE_POLL
 #if HAVE_POLL
-               if (fds[f->sock].revents & (POLLHUP|POLLERR|POLLNVAL)) {
+               if (fds[f->sock].revents & (POLLERR|POLLNVAL|POLLHUP)) {
                        destroy_ftran(f);
                        continue;
                }
                        destroy_ftran(f);
                        continue;
                }
@@ -655,7 +654,7 @@ int process_all_sendfiles(fd_set * const active_clients, const int num_ac)
 
                /* state = 2: incoming PASV, state >3: send file */
 #if HAVE_POLL
 
                /* state = 2: incoming PASV, state >3: send file */
 #if HAVE_POLL
-               if ((f->state < 2) || (f->state == 3) ||  (fds[f->sock].revents & (POLLIN|POLLOUT)) == 0) {
+               if ((f->state < 2) || (f->state == 3) || (fds[f->sock].revents & (POLLIN|POLLOUT)) == 0) {
 #else
                if ((f->state < 2) || (f->state == 3) || !FD_ISSET(f->sock, active_clients)) {
 #endif
 #else
                if ((f->state < 2) || (f->state == 3) || !FD_ISSET(f->sock, active_clients)) {
 #endif
@@ -672,8 +671,7 @@ int process_all_sendfiles(fd_set * const active_clients, const int num_ac)
 
                if (f->state == 2) {            /* incoming PASV */
                        const unsigned int one = 1;
 
                if (f->state == 2) {            /* incoming PASV */
                        const unsigned int one = 1;
-                       const int tempsock = accept(f->sock, (struct sockaddr *)&tempaddr,
-                                                       &tempaddr_len);
+                       const int tempsock = accept(f->sock, &(f->sin), &tempaddr_len);
 
                        del_fd(f->sock);
 
 
                        del_fd(f->sock);
 
@@ -685,12 +683,25 @@ int process_all_sendfiles(fd_set * const active_clients, const int num_ac)
                        f->sock = tempsock;
                        ioctl(f->sock, FIONBIO, &one);
                        init_file_transfer(f);
                        f->sock = tempsock;
                        ioctl(f->sock, FIONBIO, &one);
                        init_file_transfer(f);
+                       
+                       flush_numeric(f->owner);
+                       if (f->owner->free_me) {
+                               destroy_conn(f->owner);
+                               continue;
+                       }
+       
 #if WANT_UPLOAD
                        if (f->upload) continue;
 #endif
                }
                if (f->state < 5) {
                        init_file_transfer(f);
 #if WANT_UPLOAD
                        if (f->upload) continue;
 #endif
                }
                if (f->state < 5) {
                        init_file_transfer(f);
+                       
+                       flush_numeric(f->owner);
+                       if (f->owner->free_me) {
+                               destroy_conn(f->owner);
+                               continue;
+                       }
 #if WANT_UPLOAD
                        if (f->upload) continue;
 #endif
 #if WANT_UPLOAD
                        if (f->upload) continue;
 #endif
@@ -705,16 +716,7 @@ int process_all_sendfiles(fd_set * const active_clients, const int num_ac)
                        if (do_download(f)) continue;
 
                /* do_{upload,download} returned 0, the transfer is complete */
                        if (do_download(f)) continue;
 
                /* do_{upload,download} returned 0, the transfer is complete */
-                numeric(f->owner, 226, "Transfer complete.");
-                time(&(f->owner->last_transfer));
-
-#if WANT_XFERLOG
-                if (!f->dir_listing) {
-                       write_xferlog(f);
-               }
-#endif
-
-               destroy_ftran(f);
+               finish_transfer(f);
 #if WANT_FULLSCREEN
                 update_display(first_conn);
 #endif
 #if WANT_FULLSCREEN
                 update_display(first_conn);
 #endif
@@ -784,17 +786,13 @@ int do_download(struct ftran *f)
 #endif
        int size;
 
 #endif
        int size;
 
-#if HAVE_LINUX_SENDFILE
+#if HAVE_LINUX_SENDFILE || HAVE_BSD_SENDFILE
        /*
         * We handle the optimal case first, which is sendfile().
         * Here we use a rather simplified sending `algorithm',
         * leaving most of the quirks to the system calls.
         */
        /*
         * We handle the optimal case first, which is sendfile().
         * Here we use a rather simplified sending `algorithm',
         * leaving most of the quirks to the system calls.
         */
-       if (f->dir_listing == 0
-#if WANT_UPLOAD
-               && f->upload == 0
-#endif
-       ) {
+       if (sendfile_supported == 1 && f->dir_listing == 0) {
                int err;
                size = f->size - f->pos;
 
                int err;
                size = f->size - f->pos;
 
@@ -807,7 +805,7 @@ int do_download(struct ftran *f)
                }       
 #endif
 
                }       
 #endif
 
-                       err = sendfile(f->sock, f->local_file, &f->pos, size);
+                       err = mysendfile(f->sock, f->local_file, &f->pos, size);
                return (f->pos < f->size) && (err > -1);
        }
 #endif
                return (f->pos < f->size) && (err > -1);
        }
 #endif
@@ -867,11 +865,11 @@ void write_xferlog(struct ftran *f)
 
        if (xferlog == NULL) return;
 
 
        if (xferlog == NULL) return;
 
-       strftime(temp, 256, "%a %b %d %H:%M:%S %Y", t);
+       strftime(temp, 256, "%a  %b %d %H:%M:%S %Y", t);
 #if WANT_UPLOAD
 #if WANT_UPLOAD
-       fprintf(xferlog, "%s %u %s %lu %s b _ %c a %s ftp 0 * \n",
+       fprintf(xferlog, "%s %u %s %lu %s b _ %c %c %s ftp 0 *\n",
 #else
 #else
-       fprintf(xferlog, "%s %u %s %lu %s b _ o a %s ftp 0 *\n",
+       fprintf(xferlog, "%s %u %s %lu %s b _ o %c %s ftp 0 *\n",
 #endif
                temp, (int)(difftime(now, f->tran_start)),
                inet_ntoa(f->sin.sin_addr), f->size,
 #endif
                temp, (int)(difftime(now, f->tran_start)),
                inet_ntoa(f->sin.sin_addr), f->size,
@@ -879,7 +877,7 @@ void write_xferlog(struct ftran *f)
 #if WANT_UPLOAD
                (f->upload) ? 'i' : 'o',
 #endif
 #if WANT_UPLOAD
                (f->upload) ? 'i' : 'o',
 #endif
-               f->owner->username);
+               (f->owner->auth == 4) ? 'r' : 'a', f->owner->username);
                fflush(xferlog);
 
 #if 0
                fflush(xferlog);
 
 #if 0
@@ -972,8 +970,8 @@ int main(void)
 #warning No xferlog support for nonroot yet
 #else
        /* open xferlog */
 #warning No xferlog support for nonroot yet
 #else
        /* open xferlog */
-       xferlog = fopen("/var/log/xferlog", "r+");
-       if (xferlog == NULL) xferlog = fopen("/usr/adm/xferlog", "r+");
+       xferlog = fopen("/var/log/xferlog", "a");
+       if (xferlog == NULL) xferlog = fopen("/usr/adm/xferlog", "a");
 
        if (xferlog != NULL) {
                  fseek(xferlog, 0L, SEEK_END);
 
        if (xferlog != NULL) {
                  fseek(xferlog, 0L, SEEK_END);
@@ -1001,6 +999,19 @@ int main(void)
        alarm(60);
        signal(SIGALRM, handle_alarm);
 
        alarm(60);
        signal(SIGALRM, handle_alarm);
 
+#if HAVE_LINUX_SENDFILE || HAVE_BSD_SENDFILE
+       /* check that sendfile() is really implemented (same check as configure does) */
+       {
+               int out_fd = 1, in_fd = 0;
+               off_t offset = 0;
+               size_t size = 1024;
+
+               errno = 0;
+               mysendfile(out_fd, in_fd, &offset, size);
+               if (errno == ENOSYS) sendfile_supported = 0;
+       }
+#endif
+
        for ( ;; ) {
                int i;
 #ifndef HAVE_POLL
        for ( ;; ) {
                int i;
 #ifndef HAVE_POLL
@@ -1126,10 +1137,14 @@ void accept_new_client(int * const server_sock)
                struct conn * const c = alloc_new_conn(tempsock);
                num_err = 0;
                if (c != NULL) {
                struct conn * const c = alloc_new_conn(tempsock);
                num_err = 0;
                if (c != NULL) {
-                       numeric(c, 220, "BetaFTPD " VERSION " ready.");
+                       char hello[] = "220 BetaFTPD " VERSION " ready.\r\n";
+                       
 #if WANT_STAT
                        memcpy(&(c->addr), &tempaddr, sizeof(struct sockaddr));
 #endif
 #if WANT_STAT
                        memcpy(&(c->addr), &tempaddr, sizeof(struct sockaddr));
 #endif
+
+                       if (send(tempsock, hello, strlen(hello), 0) == -1 && errno == EPIPE)
+                               destroy_conn(c);
                }
        }
 }
                }
        }
 }
@@ -1178,29 +1193,6 @@ void time_out_sockets()
        }
 }
 
        }
 }
 
-#if WANT_DCACHE
-/*
- * time_out_dcache():
- *             Time out expired directory listing cache entries.
- *             Uses much of the same code as time_out_sockets().
- */
-void time_out_dcache()
-{
-       struct dcache *d = NULL, *next = first_dcache->next_dcache;
-       time_t now = time(NULL);        
-
-       /* run through the linked list */
-       while (next != NULL) {
-               d = next;
-               next = d->next_dcache;
-
-               if (d->use_count == 0 && (now - d->last_used > 900)) {
-                       destroy_dcache(d);
-               }
-       }
-}
-#endif
-
 /*
  * remove_bytes():
  *             Remove some bytes from the incoming buffer. This gives
 /*
  * remove_bytes():
  *             Remove some bytes from the incoming buffer. This gives
@@ -1224,23 +1216,36 @@ void remove_bytes(struct conn * const c, const int num)
  *             you can use this command much the same way as you
  *             would use a printf() (with all the normal %s, %d,
  *             etc.), since it actually uses printf() internally.
  *             you can use this command much the same way as you
  *             would use a printf() (with all the normal %s, %d,
  *             etc.), since it actually uses printf() internally.
+ *
+ *             This command doesn't actually SEND the data -- it
+ *             just puts it in a buffer which is sent after the
+ *             command handler has completed. The reasons for this
+ *             are simple -- it makes error checking and cleanup
+ *             MUCH cleaner, so we won't have to check for errors
+ *             in every single little branch of the code.
  */
 void numeric(struct conn * const c, const int numeric, const char * const format, ...)
 {
  */
 void numeric(struct conn * const c, const int numeric, const char * const format, ...)
 {
-       char buf[256], fmt[256];
+       char fmt[256];
        va_list args;
        int i, err;
        va_list args;
        int i, err;
+       int in_buf = strlen(message_buf);
 
        snprintf(fmt, 256, "%03u %s\r\n", numeric, format);
 
        va_start(args, format);
 
        snprintf(fmt, 256, "%03u %s\r\n", numeric, format);
 
        va_start(args, format);
-       i = vsnprintf(buf, 256, fmt, args);
+       i = vsnprintf(message_buf + in_buf, 512 - in_buf, fmt, args);
        va_end(args);
        va_end(args);
+}
 
 
-       err = send(c->sock, buf, i, 0);
-       if (err == -1 && errno == EPIPE) {
-               destroy_conn(c);
-       }
+/* flush_numeric():
+ *             Actually flushes the buffer written by numeric() -- but does
+ *             NOT erase it. If an error, sets the "free_me" flag in the socket.
+ */
+void flush_numeric(struct conn * const c)
+{
+       if (send(c->sock, message_buf, strlen(message_buf), 0) == -1 && errno == EPIPE)
+               c->free_me = 1;
 }
 
 /*
 }
 
 /*
@@ -1376,8 +1381,8 @@ void init_file_transfer(struct ftran * const f)
         */
 #if HAVE_MMAP
        if (f->dir_listing == 0) {
         */
 #if HAVE_MMAP
        if (f->dir_listing == 0) {
-#if HAVE_LINUX_SENDFILE
-               int do_mmap = 0;
+#if HAVE_LINUX_SENDFILE || HAVE_BSD_SENDFILE
+               int do_mmap = (sendfile_supported) ? 0 : 1;
 #else
                int do_mmap = 1;
 #endif
 #else
                int do_mmap = 1;
 #endif
@@ -1519,6 +1524,24 @@ void clear_bad_fds(int * const server_sock)
 }
 #endif
 
 }
 #endif
 
+#if HAVE_BSD_SENDFILE || HAVE_LINUX_SENDFILE
+int mysendfile(int sock, int fd, off_t *offset, size_t count)
+{
+#if HAVE_BSD_SENDFILE
+       int err;
+       off_t ssize = 0;
+       
+       err = sendfile(fd, sock, *offset, count, NULL, &ssize, 0);
+       if (ssize > 0) *offset += ssize;
+#else /* !HAVE_BSD_SENDFILE */
+#if HAVE_LINUX_SENDFILE
+       return sendfile(sock, fd, offset, count);
+#endif /* HAVE_LINUX_SENDFILE */
+#endif /* !HAVE_BSD_SENDFILE */
+}
+#endif /* HAVE_BSD_SENDFILE || HAVE_LINUX_SENDFILE */
+
+
 #if WANT_MESSAGE
 /*
  * dump_file(): Dumps a file on the control connection. Used for
 #if WANT_MESSAGE
 /*
  * dump_file(): Dumps a file on the control connection. Used for