/* ftpd.c: dkBetaFTPD main 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 License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Special note: this file has been overwritten by another (0-byte) file, been * through the dead, and restored (with the help of dd, grep, gpm, vi and less) * with a sucess rate of 99.9%. Show it a little respect -- don't add junk * to it. :-) */ #define _GNU_SOURCE #if HAVE_CONFIG_H #include #endif #if HAVE_ERRNO_H #include #endif #if HAVE_STROPTS_H #include #endif #if HAVE_SYS_CONF_H #include #endif #if HAVE_FCNTL_H #include #endif #if HAVE_STDIO_H #include #endif #if HAVE_ASSERT_H #include #endif #if HAVE_STRING_H #include #endif #if HAVE_STRINGS_H #include #endif #if HAVE_STDARG_H #include #endif #if HAVE_STDLIB_H #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_ARPA_INET_H #include #endif #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_IOCTL_H #include #endif #if HAVE_NETINET_IN_SYSTM_H #include #endif #if HAVE_NETINET_IP_H #include #endif #if HAVE_NETINET_TCP_H #include #endif #if HAVE_LINUX_SOCKET_H #include #endif #if HAVE_LINUX_TCP_H #include #endif #if HAVE_MMAP #include #endif #if HAVE_TIME_H #include #endif #if HAVE_SYS_TIME_H #include #endif #if HAVE_SYS_TIME_H #include #endif #if HAVE_SYS_FILIO_H #include #endif #if HAVE_NETDB_H #include #endif #if HAVE_SIGNAL_H #include #endif #if HAVE_GLOB_H #include #endif #if HAVE_SYS_SIGNAL_H #include #endif #if HAVE_SYS_POLL_H #include #endif #if HAVE_SYS_SENDFILE_H #include #endif /* * does not export this to glibc2 systems, and it isn't * always defined anywhere else. */ #if !defined(TCP_CORK) && defined(__linux__) #define TCP_CORK 3 #endif #include "ftpd.h" #include "conn.h" #if WANT_ASCII #include "ascii.h" #endif #if WANT_DCACHE #include "dcache.h" #endif #include #ifndef MAP_FAILED #define MAP_FAILED -1 #endif struct ftran *first_ftran = NULL; #if WANT_DCACHE struct dcache *first_dcache = NULL; #endif FtpServer ftpd; Poller_poll poller; #if WANT_XFERLOG FILE *xferlog = NULL; #endif #if HAVE_LINUX_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 * set to 1 by a signal handler every minute, and set to 0 when the * checking has been performed. */ int time_to_check = 1; #ifndef HAVE_SPRINTF /* * snprintf(): snprintf() replacement for systems that miss it. Note * that this implementation does _not_ necessarily protect * against all buffer overflows. Get a real snprintf() in * your C library. That being said, the 8k limit is * substantially larger than any other string in BetaFTPD, * which should make such an attack harder. */ int snprintf(char *str, size_t n, const char *format, ...) { char buf[8192]; va_list args; int err; va_start(args, format); err = vsprintf(buf, format, args); va_end(args); buf[(int)n] = 0; strcpy(str, buf); return err; } #endif #ifndef HAVE_VSNPRINTF /* * vsnprintf: vsnprintf() replacement for systems that miss it. Please * see snprintf (above) for more information. */ int vsnprintf(char *str, size_t n, const char *format, va_list ap) { char buf[8192]; int err; err = vsprintf(buf, format, ap); buf[(int)n] = 0; strcpy(str, buf); return err; } #endif /* * add_to_linked_list(): * Inserts an element (conn, ftran or dcache) into its linked list. * The list is placed at the beginning, right after the (bogus) * first element of the list. */ void add_to_linked_list(struct list_element * const first, struct list_element * const elem) { elem->prev = first; if (first) { elem->next = first->next; if (elem->next) elem->next->prev = elem; first->next = elem; } else { /* this is the bogus head of the list */ elem->next = NULL; } } /* * remove_from_linked_list(): * Removes an element (conn, ftran or dcache) from its linked list, */ void remove_from_linked_list(struct list_element * const elem) { if (elem->prev != NULL) elem->prev->next = elem->next; if (elem->next != NULL) elem->next->prev = elem->prev; } /* * alloc_new_ftran(): * Allocates a new data connection (type `struct ftran'), and * adds it to the linked list. The connection operates on the * socket SOCK, and has the control connection C as its parent. */ struct ftran *alloc_new_ftran(const int sock, const struct conn * const c) { struct ftran *f = new struct ftran; if (f == NULL) return f; if (c == NULL) { /* this is the bogus head of the list */ f->next_ftran = NULL; f->prev_ftran = NULL; } else { add_to_linked_list((struct list_element *)first_ftran, (struct list_element *)f); } #if HAVE_MMAP f->file_data = NULL; #endif f->owner = (struct conn * const)c; f->sock = sock; f->state = 0; f->local_file = -1; #if WANT_DCACHE f->dir_cache = NULL; #endif f->dir_listing = 0; return f; } /* * destroy_ftran(): * Destroy a data connection, remove it from the linked list, * and clean up after it. * * For some reason, TCP_CORK (Linux 2.2.x-only) doesn't flush * even _after_ the socket is closed, so we zero it just before * closing. We also zero just before sending the last packet, * as it seems to be needed on some systems. * * If you wonder why I check for `defined(SOL_TCP)' and don't * provide an alternative, see the comments on init_file_transfer(). */ void destroy_ftran(struct ftran * const f) { const unsigned int zero = 0; if (f == NULL) return; #if defined(TCP_CORK) && defined(SOL_TCP) setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); #endif poller.del(f->sock); close(f->sock); #if WANT_DCACHE if (f->dir_cache) { time(&(f->dir_cache->last_used)); f->dir_cache->use_count--; f->dir_cache = NULL; } else #endif #if HAVE_MMAP if (f->file_data) { if (f->dir_listing) { free(f->file_data); f->file_data = NULL; } else { munmap(f->file_data, f->size); } } if (!f->dir_listing) #endif if (f->local_file != -1) close(f->local_file); #if !HAVE_MMAP if (f->dir_listing) unlink(f->filename); #endif f->owner->c_transfer = NULL; #if WANT_DCACHE if (f->dir_cache != NULL) f->dir_cache->use_count--; #endif remove_from_linked_list((struct list_element *)f); delete f; } int ftran::notifyPollEvent(Poller::PollEvent *e) { if (e->revents & (POLLHUP|POLLERR|POLLNVAL)) { destroy_ftran(this); return 0; } /* state = 2: incoming PASV, state >3: send file */ if ((this->state < 2) || (this->state == 3) || (e->revents & (POLLIN|POLLOUT)) == 0) return 0; if (this->state == 2) { /* incoming PASV */ const unsigned int one = 1; struct sockaddr tempaddr; socklen_t tempaddr_len = sizeof(tempaddr); const int tempsock = accept(this->sock, (struct sockaddr *)&tempaddr, &tempaddr_len); poller.del(this->sock); close(this->sock); if (tempsock == -1) { destroy_ftran(this); return 1; } this->sock = tempsock; ioctl(this->sock, FIONBIO, &one); init_file_transfer(this); #if WANT_UPLOAD if (this->upload) return 0; #endif } if (this->state < 5) { init_file_transfer(this); #if WANT_UPLOAD if (this->upload) return 0; #endif } /* for download, we send the first packets right away */ #if WANT_UPLOAD if (this->upload) { if (do_upload(this)) return 0; } else #endif if (do_download(this)) return 0; /* do_{upload,download} returned 0, the transfer is complete */ this->owner->numeric(226, "Transfer complete."); time(&(this->owner->c_last_transfer)); #if WANT_XFERLOG if (!this->dir_listing) { write_xferlog(this); } #endif destroy_ftran(this); #if WANT_FULLSCREEN update_display(ftpd.m_first_conn); #endif return 0; } #if WANT_UPLOAD int do_upload(struct ftran *f) { char upload_buf[16384]; int size; #if WANT_ASCII /* keep buffer size small in ascii transfers to prevent process stalling while filtering data on slower computers */ /* * This isn't a big problem, since we won't get * packets this big anyway, the biggest I've seen * was 12kB on 100mbit (but that was from a Windows * machine), so I've reduced the buffer from 64 kB * to 16 kB :-) --Steinar */ const int maxlen = (f->ascii_mode == 1) ? 4096 : 16384; #else const int maxlen = 16384; #endif errno = 0; size = recv(f->sock, upload_buf, maxlen, 0); if (size >= 0) { f->pos += size; } #if WANT_ASCII if (size > 0 && f->ascii_mode == 1) { size = ascii_uploadfilter(upload_buf, size); } #endif if (size > 0 && (write(f->local_file, upload_buf, size) == size)) { return 1; } else if (size == -1) { /* don't write xferlog... or? */ f->owner->numeric(426, strerror(errno)); destroy_ftran(f); return 1; } return 0; } #endif int do_download(struct ftran *f) { #if defined(TCP_CORK) && defined(SOL_TCP) unsigned int zero = 0; #endif char *sendfrom_buf; int bytes_to_send; int more_to_send = 0; #if !HAVE_MMAP char buf[MAX_BLOCK_SIZE]; #endif #if WANT_ASCII char buf2[MAX_BLOCK_SIZE * 2]; #endif int size; #if HAVE_LINUX_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 (sendfile_supported == 1 && f->dir_listing == 0) { int err; size = f->size - f->pos; if (size > f->block_size) size = f->block_size; if (size < 0) size = 0; #ifdef TCP_CORK if (size != f->block_size) { setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); } #endif err = sendfile(f->sock, f->local_file, &f->pos, size); return (f->pos < f->size) && (err > -1); } #endif #if HAVE_MMAP size = f->size - f->pos; if (size > f->block_size) size = f->block_size; if (size < 0) size = 0; bytes_to_send = size; sendfrom_buf = f->file_data + f->pos; #else bytes_to_send = read(f->local_file, buf, f->block_size); sendfrom_buf = buf; #endif if (bytes_to_send == f->block_size) more_to_send = 1; #if WANT_ASCII if (f->ascii_mode == 1) { bytes_to_send = ascii_downloadfilter(sendfrom_buf, buf2, bytes_to_send); sendfrom_buf = buf2; } #endif /* WANT_ASCII */ #if defined(TCP_CORK) && defined(SOL_TCP) /* if we believe this is the last packet, unset TCP_CORK */ if (more_to_send == 0) { setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); } #endif size = send(f->sock, sendfrom_buf, bytes_to_send, 0); if (size < bytes_to_send) more_to_send = 1; #if WANT_ASCII if (f->ascii_mode == 1 && size < bytes_to_send && size > 0) { size = ascii_findlength(sendfrom_buf, size); } #endif #if HAVE_MMAP if (size > 0) f->pos += size; #endif return more_to_send; } #if WANT_XFERLOG void write_xferlog(struct ftran *f) { char temp[256]; time_t now = time(NULL); struct tm *t = localtime(&now); if (xferlog == NULL) return; 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", #else fprintf(xferlog, "%s %u %s %lu %s b _ o a %s ftp 0 *\n", #endif temp, (int)(difftime(now, f->tran_start)), inet_ntoa(f->sin.sin_addr), f->size, f->filename, #if WANT_UPLOAD (f->upload) ? 'i' : 'o', #endif f->owner->username); fflush(xferlog); #if 0 /* vim needs this to work properly :-( */ ) #endif } #endif /* * main(): Main function. Does the initialization, and contains * the main server loop. Takes no command-line arguments * (see README for justification). */ int main(void) { /*setlinebuf(stdout);*/ setvbuf(stdout, (char *)NULL, _IOLBF, 0); signal(SIGPIPE, SIG_IGN); printf("dkbetaftpd version %s\n", VERSION); puts("\ copyright (C) 1999-2000 Steinar H. Gunderson, 2001 Dan Kegel\n\ dkBetaFTPD comes with ABSOLUTELY NO WARRANTY; for details see the file\n\ COPYING. This is free software, and you are welcome to redistribute it\n\ under certain conditions; again see the file COPYING for details.\n\ "); /* we don't need stdin */ close(0); poller.init(); ftpd.m_server_sock = create_server_socket(); #if WANT_FULLSCREEN printf("%cc", (char)27); /* reset and clear the screen */ #endif /* init dummy first connection */ ftpd.m_first_conn = new conn; ftpd.m_first_conn->init(&ftpd, -1); first_ftran = alloc_new_ftran(0, NULL); #if WANT_DCACHE first_dcache = alloc_new_dcache(); #endif #if WANT_XFERLOG #if WANT_NONROOT #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+"); if (xferlog != NULL) { fseek(xferlog, 0L, SEEK_END); } #endif #endif #if WANT_FORK switch (fork()) { case -1: perror("fork()"); puts("fork() failed, exiting"); exit(0); case 0: break; default: puts("dkBetaFTPD forked into the background"); exit(0); } #else puts("dkBetaFTPD active"); #endif /* set timeout alarm here (after the fork) */ alarm(60); signal(SIGALRM, handle_alarm); #if HAVE_LINUX_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; sendfile(out_fd, in_fd, &offset, size); if (errno == ENOSYS) sendfile_supported = 0; } #endif for ( ;; ) { #if WANT_FULLSCREEN update_display(ftpd.m_first_conn); #endif int err; Poller::PollEvent event; printf("calling waitForEvents\n"); poller.waitForEvents(1000); // Pump any network traffic into the appropriate Clients for (;;) { err = poller.getNextEvent(&event); if (err) { if (err != EWOULDBLOCK) { printf("waitAndDispatchEvents: getNextEvent() returned %d\n", err); return err; } break; } printf("fd %d; calling %p->notifyPollEvent\n", event.fd, event.client); err = event.client->notifyPollEvent(&event); printf("notifyPollEvent returned %d\n", err); if (err) { printf("waitAndDispatchEvents: %p->notifyPollEvent(fd %d) returned %d, deleting\n", event.client, event.fd, err); poller.del(event.fd); } } /* remove any timed out sockets */ if (time_to_check) { time_out_sockets(); #if WANT_DCACHE time_out_dcache(); #endif time_to_check = 0; } } } /* * accept_new_client(): * Open a socket for the new client, say hello and put it in * among the others. */ int FtpServer::notifyPollEvent(Poller::PollEvent *e) { struct sockaddr_in tempaddr; socklen_t tempaddr_len = sizeof(tempaddr); const int tempsock = accept(m_server_sock, (struct sockaddr *)&tempaddr, &tempaddr_len); static int num_err = 0; if (tempsock < 0) { #ifndef WANT_FORK perror("accept()"); #endif } else { struct conn * const c = new conn; c->init(this, tempsock); num_err = 0; if (c != NULL) { c->numeric(220, "dkBetaFTPD " VERSION " ready."); #if WANT_STAT memcpy(&(c->c_addr), &tempaddr, sizeof(struct sockaddr)); #endif } } return 0; } /* * time_out_sockets(): * Times out any socket that has not had any transfer * in the last 15 minutes (delay not customizable by FTP * user -- you must change it in ftpd.h). * * Note that RFC959 explicitly states that there are no * `spontaneous' error replies, yet we have to do it to * get the message through at all. * * If we check this list for every accept() call, it's * actually eating a lot of CPU time, so we only check * it every minute. We used to do a time() call here, * but we've changed to do use an alarm() call and set * the time_to_check_flag in the SIGALRM handler. */ RETSIGTYPE handle_alarm(int signum) { time_to_check = 1; alarm(60); /* for libc5 */ signal(SIGALRM, handle_alarm); } /* FIXME: should be method of conn? */ void time_out_sockets() { struct conn *next = (struct conn *)ftpd.m_first_conn->c_next_conn; time_t now = time(NULL); /* run through the linked list */ while (next != NULL) { struct conn *c = next; next = (struct conn *)c->c_next_conn; if ((c->c_transfer == NULL || c->c_transfer->state != 5) && (now - c->c_last_transfer > TIMEOUT_SECS)) { /* RFC violation? */ c->numeric(421, "Timeout (%u minutes): Closing control connection.", TIMEOUT_SECS/60); c->shutdown(); } } } /* * init_file_transfer(): * Initiate a data connection for sending. This does not open * any files etc., just does whatever is needed for the socket, * if needed. It does, however, send the 150 reply to the client, * and mmap()s if needed. * * Linux systems (others?) define SOL_TCP right away, which saves us * some grief and code size. Perhaps using getprotoent() is the `right' * way, but it's bigger :-) (Optionally, we could figure it out at * configure time, of course...) * * For optimal speed, we use the Linux 2.2.x-only TCP_CORK flag if * possible. Note that this is only defined in the first `arm' -- * we silently assume that Linux is the only OS supporting this * flag. This might be an over-generalization, but I it looks like * we'll have to depend on it other places as well, so we might * just as well be evil here. */ void init_file_transfer(struct ftran * const f) { struct linger ling; const int mode = IPTOS_THROUGHPUT, zero = 0, one = 1; struct stat buf; int events; #ifdef SOL_TCP /* we want max throughput */ setsockopt(f->sock, SOL_IP, IP_TOS, (void *)&mode, sizeof(mode)); setsockopt(f->sock, SOL_TCP, TCP_NODELAY, (void *)&zero, sizeof(zero)); #ifdef TCP_CORK setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&one, sizeof(one)); #endif #else /* should these pointers be freed afterwards? */ { getprotoent(); /* legal? */ { const struct protoent * const pe_ip = getprotobyname("ip"); const struct protoent * const pe_tcp = getprotobyname("tcp"); setsockopt(f->sock, pe_ip->p_proto, IP_TOS, (void *)&mode, sizeof(mode)); setsockopt(f->sock, pe_tcp->p_proto, TCP_NODELAY, (void *)&zero, sizeof(zero)); } endprotoent(); } #endif if (f->dir_listing) { f->block_size = MAX_BLOCK_SIZE; } else { #if WANT_ASCII f->ascii_mode = f->owner->ascii_mode; #endif /* find the preferred block size */ f->block_size = MAX_BLOCK_SIZE; if (fstat(f->local_file, &buf) != -1 && buf.st_blksize < MAX_BLOCK_SIZE) { f->block_size = buf.st_blksize; } } f->state = 5; events = POLLOUT; #if WANT_UPLOAD if (f->upload) { events = POLLIN; } #endif /* WANT_UPLOAD */ printf("poller.add(fd %d, f %p)\n", f->sock, f); if (poller.add(f->sock, f, events)) { f->owner->numeric(500, strerror(errno)); return; } ling.l_onoff = 0; ling.l_linger = 0; setsockopt(f->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); time(&(f->owner->c_last_transfer)); if (f->dir_listing) { /* include size? */ f->owner->numeric(150, "Opening ASCII mode data connection for directory listing."); } else { /* * slightly kludged -- perhaps we should kill the second arm, * at the expense of code size? Or perhaps we could collapse * the two possible replies into one? */ #if WANT_ASCII if (f->ascii_mode #if WANT_UPLOAD || f->upload #endif /* WANT_UPLOAD */ ) { f->owner->numeric(150, "Opening %s mode data connection for '%s'", (f->ascii_mode) ? "ASCII" : "BINARY", f->filename); } else { f->owner->numeric(150, "Opening %s mode data connection for '%s' (%u bytes)", (f->ascii_mode) ? "ASCII" : "BINARY", f->filename, f->size); } #else /* !WANT_ASCII */ #if WANT_UPLOAD if (f->upload) { f->owner->numeric(150, "Opening BINARY mode data connection for '%s'", f->filename); } else #endif /* WANT_UPLOAD */ f->owner->numeric(150, "Opening BINARY mode data connection for '%s' (%u bytes)", f->filename, f->size); #endif /* !WANT_ASCII */ } /* * This section _could_ in theory be more optimized, but it's * much easier this way, and hopefully, the compiler will be * intelligent enough to optimize most of this away. The idea * is, some modes _require_ use of mmap (or not). The preferred * thing is using mmap() when we don't have sendfile(), and not * using mmap() when we have sendfile(). */ #if HAVE_MMAP if (f->dir_listing == 0) { #if HAVE_LINUX_SENDFILE int do_mmap = (sendfile_supported) ? 0 : 1; #else int do_mmap = 1; #endif #if WANT_ASCII if (f->ascii_mode == 1) do_mmap = 1; #endif #if WANT_UPLOAD if (f->upload == 1) do_mmap = 0; #endif if (do_mmap == 1) { f->file_data = (char *)mmap(NULL, f->size, PROT_READ, MAP_SHARED, f->local_file, 0); if (f->file_data == MAP_FAILED) f->file_data = NULL; } else { f->file_data = NULL; } f->pos = f->owner->c_rest_pos; } #else /* !HAVE_MMAP */ lseek(f->local_file, f->owner->c_rest_pos, SEEK_SET); #endif } /* * create_server_socket(): * Create and bind a server socket, that we can use to * listen to new clients on. */ int create_server_socket() { int server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); const unsigned int one = 1; struct sockaddr_in addr; int err; /* * In the `perfect' world, if an address was in use, we could * just wait for the kernel to clear everything up, and everybody * would be happy. But when you just found out your server socket * was invalid, it has to be `re-made', and 3000 users are trying * to access your fileserver, I think it's nice that it comes * up right away... hence this option. */ setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); ioctl(server_sock, FIONBIO, &one); /* just in case */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(FTP_PORT); do { err = bind(server_sock, (struct sockaddr *)&addr, sizeof(struct sockaddr)); if (err == -1) { perror("bind()"); /* try to recover from recoverable errors... */ if (errno == ENOMEM || errno == EADDRINUSE) { puts("Waiting 1 sec before trying again..."); sleep(1); } else { puts("Giving up."); exit(1); } } } while (err == -1); listen(server_sock, 20); printf("poller.add(fd %d, ftpd %p)\n", server_sock, &ftpd); err = poller.add(server_sock, &ftpd, POLLIN); if (err) { perror("poller.add"); return -1; } return server_sock; } #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)... 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[5121]; char *ptr = outbuf + 4; int i, j = -1; const int dumpfile = open(filename, O_RDONLY); if (dumpfile == -1) return; i = read(dumpfile, buf, 1024); if (i <= 0) { close(dumpfile); return; } sprintf(outbuf, "%03u-", num); while (++j < i) { *ptr++ = buf[j]; if (buf[j] == '\n') { sprintf(ptr, "%03u-", num); ptr += 4; } } *ptr++ = '\n'; send(c->c_sock, outbuf, ptr - outbuf, 0); close(dumpfile); } /* * list_readme(): * Lists all README file in the current (ie. OS current) * directory, in a 250- message. */ void list_readmes(struct conn * const c) { glob_t pglob; const time_t now = time(NULL); int i; if (glob("README*", 0, NULL, &pglob) != 0) return; for (i = 0; i < pglob.gl_pathc; i++) { struct stat buf; char str[256]; char *tm; if (stat(pglob.gl_pathv[i], &buf) == -1) continue; /* remove trailing LF */ tm = ctime(&buf.st_mtime); tm[strlen(tm) - 1] = 0; 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->c_sock, str, strlen(str), 0); } globfree(&pglob); } #endif