]> git.sesse.net Git - betaftpd/blobdiff - ftpd.c
Fixed a security problem where the custom snprintf() would always be used. Thanks...
[betaftpd] / ftpd.c
diff --git a/ftpd.c b/ftpd.c
index 473624e53ba55e8a115e8fdb3798dc343b9fe448..f013777ec65dcb39a3660f5234baa79a143fe005 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
-    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,
 #include <config.h>
 #endif
 
-#if HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
 #if HAVE_ERRNO_H
 #include <errno.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
 #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_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
 #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_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_TCP_H
+#include <linux/tcp.h>
+#endif
+
 #if HAVE_MMAP
 #include <sys/mman.h>
 #endif
 #include <ascii.h>
 #endif
 
-/* Debug printing -- remove before 0.0.8 final */
-#if 1
-#define DPRINT(s)
-#else
-#define DPRINT(s) printf s 
+#if WANT_DCACHE
+#include <dcache.h>
 #endif
 
 #ifndef MAP_FAILED
@@ -187,6 +184,7 @@ struct ftran *first_ftran = NULL;
 #if WANT_DCACHE
 struct dcache *first_dcache = NULL;
 #endif
+char message_buf[512];
 
 #if HAVE_POLL
 unsigned int highest_fds = 0;
@@ -205,6 +203,10 @@ fd_set master_fds, master_send_fds;
 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
@@ -213,7 +215,7 @@ FILE *xferlog = NULL;
  */
 int time_to_check = 1;
 
-#ifndef HAVE_SPRINTF
+#ifndef HAVE_SNPRINTF
 /*
  * snprintf(): snprintf() replacement for systems that miss it. Note
  *             that this implementation does _not_ necessarily protect
@@ -262,7 +264,6 @@ int vsnprintf(char *str, size_t n, const char *format, va_list ap)
  */
 int add_fd(const int fd, const int events)
 {
-       DPRINT(("add_fd(%d, %x)\n", fd, events));
 #if HAVE_POLL
        if (fd >= FD_MAX) {
                printf("add_fd(%d, %x): failed\n", fd, events);
@@ -289,7 +290,6 @@ int add_fd(const int fd, const int events)
  */
 void del_fd(const int fd)
 {
-       DPRINT(("del_fd(%d)\n", fd));
 #if HAVE_POLL
        if (fd >= FD_MAX)
                return;
@@ -365,9 +365,8 @@ struct conn *alloc_new_conn(const int sock)
 {
        const unsigned int one = 1;
        struct conn *c = (struct conn *)(malloc(sizeof(struct conn)));
-       DPRINT(("alloc_new_conn(%d)\n", sock));
 
-       if (c == NULL) return c;
+       if (c == NULL) return NULL;
 
        if (sock != -1) {
                ioctl(sock, FIONBIO, &one);
@@ -392,6 +391,7 @@ struct conn *alloc_new_conn(const int sock)
 #if WANT_ASCII
        c->ascii_mode = 0;
 #endif
+       c->free_me = 0;
 
        /*
         * equals:
@@ -408,7 +408,7 @@ struct conn *alloc_new_conn(const int sock)
 
        time(&(c->last_transfer));
 
-       /*list_clients();*/
+       /* list_clients(); */
 
        return c;
 }
@@ -446,33 +446,12 @@ struct ftran *alloc_new_ftran(const int sock, const struct conn * const c)
 #endif
 
        f->dir_listing = 0;
+#if WANT_UPLOAD
+       f->upload = 0;
+#endif
        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
@@ -480,14 +459,11 @@ struct dcache *alloc_new_dcache()
  */
 void destroy_conn(struct conn * const c)
 {
-       DPRINT(("destroy_conn:\n"));
        if (c == NULL) return;
        del_fd(c->sock);
 
        destroy_ftran(c->transfer);
        remove_from_linked_list((struct list_element *)c);
-
-       DPRINT(("destroy_conn done.\n"));
 }
 
 /*
@@ -547,30 +523,9 @@ void destroy_ftran(struct ftran * const 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
+ *             Processes all the _control_ connections in active_clients
  *             (normally returned from a select(), there are at max
  *             NUM_AC active connections in the set), sending them
  *             through to the command parser if a command has been
@@ -585,8 +540,6 @@ int process_all_clients(const fd_set * const active_clients, const int num_ac)
        struct conn *c = NULL, *next = first_conn->next_conn;
        int checked_through = 0;
 
-       DPRINT(("process_all_clients: num_ac %d\n", num_ac));
-
        /* run through the linked list */
        while (next != NULL && checked_through < num_ac) {
                int bytes_avail;
@@ -594,14 +547,9 @@ int process_all_clients(const fd_set * const active_clients, const int num_ac)
                c = next;
                next = c->next_conn;
 #if HAVE_POLL
-               if (fds[c->sock].revents & (POLLERR|POLLHUP|POLLNVAL)) {
-                       destroy_conn(c);
-                       continue;
-               }
-               if (!fds[c->sock].revents & POLLIN) {
+               if ((fds[c->sock].revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL)) == 0) {
                        continue;
                }
-               DPRINT(("process_all_clients: fd %d has POLLIN set\n", c->sock));
 #else
                if (!FD_ISSET(c->sock, active_clients)) {
                        continue;
@@ -619,8 +567,10 @@ int process_all_clients(const fd_set * const active_clients, const int num_ac)
                         * client has closed the socket. If we get a return value
                         * of -1 (error), we close the socket ourselves.
                         *
-                        * Just to be safe, we include this code for poll() as
-                        * well.
+                        * We do the same for poll(), even though we actually have
+                        * bits that tell us what is happening (in case of new 
+                        * input AND error/hangup at the same time, we do an
+                        * explicit check at the bottom of the loop as well).
                         */
                        destroy_conn(c);
                        continue;
@@ -635,10 +585,42 @@ int process_all_clients(const fd_set * const active_clients, const int num_ac)
 
                c->buf_len += bytes_avail;
                parse_command(c);
+
+               if (c->free_me || (fds[c->sock].revents & (POLLERR|POLLHUP|POLLNVAL))) {
+                        destroy_conn(c);
+                }
        }
        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.
@@ -653,15 +635,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 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;
 
+#if WANT_UPLOAD
+               if ((f->upload == 1) && (fds[f->sock].revents & POLLHUP)) {
+                       finish_transfer(f);
+                       continue;
+               }
+#endif
+
 #if HAVE_POLL
-               if (fds[f->sock].revents & (POLLHUP|POLLERR|POLLNVAL)) {
+               if (fds[f->sock].revents & (POLLERR|POLLNVAL|POLLHUP)) {
                        destroy_ftran(f);
                        continue;
                }
@@ -669,7 +657,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
-               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
@@ -686,8 +674,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;
-                       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);
 
@@ -699,12 +686,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);
+                       
+                       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);
+                       
+                       flush_numeric(f->owner);
+                       if (f->owner->free_me) {
+                               destroy_conn(f->owner);
+                               continue;
+                       }
 #if WANT_UPLOAD
                        if (f->upload) continue;
 #endif
@@ -719,16 +719,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 */
-                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
@@ -741,7 +732,7 @@ int process_all_sendfiles(fd_set * const active_clients, const int num_ac)
 int do_upload(struct ftran *f)
 {
        char upload_buf[16384];
-       int avail, size;
+       int size;
 #if WANT_ASCII
        /* keep buffer size small in ascii transfers 
           to prevent process stalling while filtering
@@ -771,7 +762,7 @@ int do_upload(struct ftran *f)
 #endif
        if (size > 0 && (write(f->local_file, upload_buf, size) == size)) {
                return 1;
-       } else if (size == -1) {
+       } else if (size == -1 && errno != EAGAIN) {
                /* don't write xferlog... or? */
                numeric(f->owner, 426, strerror(errno));
                destroy_ftran(f);
@@ -798,17 +789,13 @@ int do_download(struct ftran *f)
 #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.
         */
-       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;
 
@@ -821,7 +808,7 @@ int do_download(struct ftran *f)
                }       
 #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
@@ -881,11 +868,11 @@ void write_xferlog(struct ftran *f)
 
        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
-       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
-       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,
@@ -893,7 +880,7 @@ void write_xferlog(struct ftran *f)
 #if WANT_UPLOAD
                (f->upload) ? 'i' : 'o',
 #endif
-               f->owner->username);
+               (f->owner->auth == 4) ? 'r' : 'a', f->owner->username);
                fflush(xferlog);
 
 #if 0
@@ -986,8 +973,8 @@ int main(void)
 #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);
@@ -1015,6 +1002,19 @@ int main(void)
        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
@@ -1029,7 +1029,6 @@ int main(void)
 
 #if HAVE_POLL
                i = poll(fds, highest_fds + 1, 60000);
-               DPRINT(("poll returns %d\n", i));
 #if 0
                {
                        int j;
@@ -1141,10 +1140,14 @@ void accept_new_client(int * const server_sock)
                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 (send(tempsock, hello, strlen(hello), 0) == -1 && errno == EPIPE)
+                               destroy_conn(c);
                }
        }
 }
@@ -1193,29 +1196,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
@@ -1239,24 +1219,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.
+ *
+ *             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, ...)
 {
-       char buf[256], fmt[256];
+       char fmt[256];
        va_list args;
-       int i, err;
+       int i;
+       int in_buf = strlen(message_buf);
 
        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);
+}
 
-       DPRINT((buf));
-       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;
 }
 
 /*
@@ -1392,8 +1384,8 @@ void init_file_transfer(struct ftran * const f)
         */
 #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
@@ -1535,16 +1527,35 @@ void clear_bad_fds(int * const server_sock)
 }
 #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
  *             welcome messages and the likes. Note that outbuf
  *             is so big, to prevent any crashing from users creating
- *             weird .message files (like 1024 LFs)...
+ *             weird .message files (like 1024 LFs)... The size of
+ *             the file is limited to 1024 bytes (by truncation).
  */
 void dump_file(struct conn * const c, const int num, const char * const filename)
 {
-       char buf[1024], outbuf[8192];
+       char buf[1024], outbuf[5121];
        char *ptr = outbuf + 4;
        int i, j = -1;
 
@@ -1583,28 +1594,26 @@ void list_readmes(struct conn * const c)
        const time_t now = time(NULL);
        int i;
 
-       if (glob("README*", 0, NULL, &pglob) == 0) {
-               for (i = 0; i < pglob.gl_pathc; i++) {
-                       const char * const temp = pglob.gl_pathv[i];
-                       struct stat buf;
-                       char str[2048];
+       if (glob("README*", 0, NULL, &pglob) != 0) return;
 
-                       char *tm;
+       for (i = 0; i < pglob.gl_pathc; i++) {
+               struct stat buf;
+               char str[256];
+               char *tm;
 
-                       if (stat(temp, &buf) == -1) continue;
+               if (stat(pglob.gl_pathv[i], &buf) == -1) continue;
 
-                       /* remove trailing LF */
-                       tm = ctime(&buf.st_mtime);
-                       tm[strlen(tm) - 1] = 0;
+               /* remove trailing LF */
+               tm = ctime(&buf.st_mtime);
+               tm[strlen(tm) - 1] = 0;
 
-                       sprintf(str, "250-Please read the file %s\r\n"
-                                    "250-\tIt was last modified %s - %ld days ago\r\n",
-                               temp, tm,
-                               (now - buf.st_mtime) / 86400);
-                       send(c->sock, str, strlen(str), 0);
-               }
-               globfree(&pglob);
-       }       
+               snprintf(str, 256, "250-Please read the file %s\r\n"
+                                  "250-\tIt was last modified %s - %ld days ago\r\n",
+                       pglob.gl_pathv[i], tm,
+                       (now - buf.st_mtime) / 86400);
+               send(c->sock, str, strlen(str), 0);
+       }
+       globfree(&pglob);
 }
 #endif