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
+#if WANT_DCACHE
+#include <dcache.h>
+#endif
+
#ifndef MAP_FAILED
#define MAP_FAILED -1
#endif
#if WANT_DCACHE
struct dcache *first_dcache = NULL;
#endif
+char message_buf[512];
#if HAVE_POLL
unsigned int highest_fds = 0;
FILE *xferlog = NULL;
#endif
-#if HAVE_LINUX_SENDFILE
+#if HAVE_LINUX_SENDFILE || HAVE_BSD_SENDFILE
int sendfile_supported = 1;
#endif
*/
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
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 WANT_ASCII
c->ascii_mode = 0;
#endif
+ c->free_me = 0;
/*
* equals:
time(&(c->last_transfer));
- /*list_clients();*/
+ /* list_clients(); */
return 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
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
c->buf_len += bytes_avail;
parse_command(c);
- if (fds[c->sock].revents & (POLLERR|POLLHUP|POLLNVAL)) {
+ 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.
{
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;
}
/* 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
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);
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
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
#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);
#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',
}
#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
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,
#if WANT_UPLOAD
(f->upload) ? 'i' : 'o',
#endif
- f->owner->username);
+ (f->owner->auth == 4) ? 'r' : 'a', f->owner->username);
fflush(xferlog);
#if 0
#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);
alarm(60);
signal(SIGALRM, handle_alarm);
-#if HAVE_LINUX_SENDFILE
+#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;
size_t size = 1024;
errno = 0;
- sendfile(out_fd, in_fd, &offset, size);
+ mysendfile(out_fd, in_fd, &offset, size);
if (errno == ENOSYS) sendfile_supported = 0;
}
#endif
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);
}
}
}
}
}
-#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
* 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);
+}
- 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;
}
/*
*/
#if HAVE_MMAP
if (f->dir_listing == 0) {
-#if HAVE_LINUX_SENDFILE
+#if HAVE_LINUX_SENDFILE || HAVE_BSD_SENDFILE
int do_mmap = (sendfile_supported) ? 0 : 1;
#else
int do_mmap = 1;
}
#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