/*
* $Id: ftp_session.c,v 1.47 2004/03/25 20:46:35 shane Exp $
*/
#include <config.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <errno.h>
#include <pthread.h>
#include <sys/utsname.h>
#include <netdb.h>
#include <syslog.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include "daemon_assert.h"
#include "telnet_session.h"
#include "ftp_command.h"
#include "file_list.h"
#include "ftp_session.h"
#include "watchdog.h"
#include "oftpd.h"
#include "af_portability.h"
/* space requirements */
#define ADDRPORT_STRLEN 58
/* prototypes */
static int invariant(const ftp_session_t *f);
static void reply(ftp_session_t *f, int code, const char *fmt, ...);
static void change_dir(ftp_session_t *f, const char *new_dir);
static int open_connection(ftp_session_t *f);
static int write_fully(int fd, const char *buf, int buflen);
static void init_passive_port();
static int get_passive_port();
static int convert_newlines(char *dst, const char *src, int srclen);
static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz);
static void send_readme(const ftp_session_t *f, int code);
static void netscape_hack(int fd);
static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port);
static int set_pasv(ftp_session_t *f, sockaddr_storage_t *host_port);
static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b);
static void get_absolute_fname(char *fname,
int fname_len,
const char *dir,
const char *file);
/* command handlers */
static void do_user(ftp_session_t *f, const ftp_command_t *cmd);
static void do_pass(ftp_session_t *f, const ftp_command_t *cmd);
static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd);
static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd);
static void do_quit(ftp_session_t *f, const ftp_command_t *cmd);
static void do_port(ftp_session_t *f, const ftp_command_t *cmd);
static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd);
static void do_type(ftp_session_t *f, const ftp_command_t *cmd);
static void do_stru(ftp_session_t *f, const ftp_command_t *cmd);
static void do_mode(ftp_session_t *f, const ftp_command_t *cmd);
static void do_retr(ftp_session_t *f, const ftp_command_t *cmd);
static void do_stor(ftp_session_t *f, const ftp_command_t *cmd);
static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd);
static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd);
static void do_list(ftp_session_t *f, const ftp_command_t *cmd);
static void do_syst(ftp_session_t *f, const ftp_command_t *cmd);
static void do_noop(ftp_session_t *f, const ftp_command_t *cmd);
static void do_rest(ftp_session_t *f, const ftp_command_t *cmd);
static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd);
static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd);
static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd);
static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd);
static void do_size(ftp_session_t *f, const ftp_command_t *cmd);
static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd);
static struct {
char *name;
void (*func)(ftp_session_t *f, const ftp_command_t *cmd);
} command_func[] = {
{ "USER", do_user },
{ "PASS", do_pass },
{ "CWD", do_cwd },
{ "CDUP", do_cdup },
{ "QUIT", do_quit },
{ "PORT", do_port },
{ "PASV", do_pasv },
{ "LPRT", do_lprt },
{ "LPSV", do_lpsv },
{ "EPRT", do_eprt },
{ "EPSV", do_epsv },
{ "TYPE", do_type },
{ "STRU", do_stru },
{ "MODE", do_mode },
{ "RETR", do_retr },
{ "STOR", do_stor },
{ "PWD", do_pwd },
{ "NLST", do_nlst },
{ "LIST", do_list },
{ "SYST", do_syst },
{ "NOOP", do_noop },
{ "REST", do_rest },
{ "SIZE", do_size },
{ "MDTM", do_mdtm }
};
#define NUM_COMMAND_FUNC (sizeof(command_func) / sizeof(command_func[0]))
int ftp_session_init(ftp_session_t *f,
const sockaddr_storage_t *client_addr,
const sockaddr_storage_t *server_addr,
telnet_session_t *t,
const char *dir,
error_t *err)
{
daemon_assert(f != NULL);
daemon_assert(client_addr != NULL);
daemon_assert(server_addr != NULL);
daemon_assert(t != NULL);
daemon_assert(dir != NULL);
daemon_assert(strlen(dir) <= PATH_MAX);
daemon_assert(err != NULL);
#ifdef INET6
/* if the control connection is on IPv6, we need to get an IPv4 address */
/* to bind the socket to */
if (SSFAM(server_addr) == AF_INET6) {
struct addrinfo hints;
struct addrinfo *res;
int errcode;
/* getaddrinfo() does the job nicely */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(NULL, "ftp", &hints, &res) != 0) {
error_init(err, 0, "unable to determing IPv4 address; %s",
gai_strerror(errcode));
return 0;
}
/* let's sanity check */
daemon_assert(res != NULL);
daemon_assert(sizeof(f->server_ipv4_addr) >= res->ai_addrlen);
daemon_assert(SSFAM(host_port) == AF_INET);
/* copy the result and free memory as necessary */
memcpy(&f->server_ipv4_addr, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
} else {
daemon_assert(SSFAM(host_port) == AF_INET);
f->server_ipv4_addr = *server_addr;
}
#else
f->server_ipv4_addr = *server_addr;
#endif
f->session_active = 1;
f->command_number = 0;
f->data_type = TYPE_ASCII;
f->file_structure = STRU_FILE;
f->file_offset = 0;
f->file_offset_command_number = ULONG_MAX;
f->epsv_all_set = 0;
f->client_addr = *client_addr;
get_addr_str(client_addr, f->client_addr_str, sizeof(f->client_addr_str));
f->server_addr = *server_addr;
f->telnet_session = t;
daemon_assert(strlen(dir) < sizeof(f->dir));
strcpy(f->dir, dir);
f->data_channel = DATA_PORT;
f->data_port = *client_addr;
f->server_fd = -1;
daemon_assert(invariant(f));
return 1;
}
void ftp_session_drop(ftp_session_t *f, const char *reason)
{
daemon_assert(invariant(f));
daemon_assert(reason != NULL);
/* say goodbye */
reply(f, 421, "%s.", reason);
daemon_assert(invariant(f));
}
void ftp_session_run(ftp_session_t *f, watched_t *watched)
{
char buf[2048];
int len;
ftp_command_t cmd;
int i;
daemon_assert(invariant(f));
daemon_assert(watched != NULL);
/* record our watchdog */
f->watched = watched;
/* say hello */
send_readme(f, 220);
reply(f, 220, "Service ready for new user.");
/* process commands */
while (f->session_active &&
telnet_session_readln(f->telnet_session, buf, sizeof(buf)))
{
/* delay our timeout based on this input */
watchdog_defer_watched(f->watched);
/* increase our command count */
if (f->command_number == ULONG_MAX) {
f->command_number = 0;
} else {
f->command_number++;
}
/* make sure we read a whole line */
len = strlen(buf);
if (buf[len-1] != '\n') {
reply(f, 500, "Command line too long.");
while (telnet_session_readln(f->telnet_session, buf, sizeof(buf))) {
len = strlen(buf);
if (buf[len-1] == '\n') {
break;
}
}
goto next_command;
}
syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
/* parse the line */
if (!ftp_command_parse(buf, &cmd)) {
reply(f, 500, "Syntax error, command unrecognized.");
goto next_command;
}
/* dispatch the command */
for (i=0; i<NUM_COMMAND_FUNC; i++) {
if (strcmp(cmd.command, command_func[i].name) == 0) {
(command_func[i].func)(f, &cmd);
goto next_command;
}
}
/* oops, we don't have this command (shouldn't happen - shrug) */
reply(f, 502, "Command not implemented.");
next_command: {}
}
daemon_assert(invariant(f));
}
void ftp_session_destroy(ftp_session_t *f)
{
daemon_assert(invariant(f));
if (f->server_fd != -1) {
close(f->server_fd);
f->server_fd = -1;
}
}
#ifndef NDEBUG
static int invariant(const ftp_session_t *f)
{
int len;
if (f == NULL) {
return 0;
}
if ((f->session_active != 0) && (f->session_active != 1)) {
return 0;
}
if ((f->data_type != TYPE_ASCII) && (f->data_type != TYPE_IMAGE)) {
return 0;
}
if ((f->file_structure != STRU_FILE) && (f->file_structure != STRU_RECORD)){
return 0;
}
if (f->file_offset < 0) {
return 0;
}
if ((f->epsv_all_set != 0) && (f->epsv_all_set != 1)) {
return 0;
}
len = strlen(f->client_addr_str);
if ((len <= 0) || (len >= ADDRPORT_STRLEN)) {
return 0;
}
if (f->telnet_session == NULL) {
return 0;
}
switch (f->data_channel) {
case DATA_PORT:
/* If the client specifies a port, verify that it is from the */
/* host the client connected from. This prevents a client from */
/* using the server to open connections to arbritrary hosts. */
if (!ip_equal(&f->client_addr, &f->data_port)) {
return 0;
}
if (f->server_fd != -1) {
return 0;
}
break;
case DATA_PASSIVE:
if (f->server_fd < 0) {
return 0;
}
break;
default:
return 0;
}
return 1;
}
#endif /* NDEBUG */
static void reply(ftp_session_t *f, int code, const char *fmt, ...)
{
char buf[256];
va_list ap;
daemon_assert(invariant(f));
daemon_assert(code >= 100);
daemon_assert(code <= 559);
daemon_assert(fmt != NULL);
/* prepend our code to the buffer */
sprintf(buf, "%d", code);
buf[3] = ' ';
/* add the formatted output of the caller to the buffer */
va_start(ap, fmt);
vsnprintf(buf+4, sizeof(buf)-4, fmt, ap);
va_end(ap);
/* log our reply */
syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
/* send the output to the other side */
telnet_session_println(f->telnet_session, buf);
daemon_assert(invariant(f));
}
static void do_user(ftp_session_t *f, const ftp_command_t *cmd)
{
const char *user;
char addr_port[ADDRPORT_STRLEN];
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
user = cmd->arg[0].string;
if (strcasecmp(user, "ftp") && strcasecmp(user, "anonymous")) {
syslog(LOG_WARNING, "%s attempted to log in as \"%s\"",
f->client_addr_str, user);
reply(f, 530, "Only anonymous FTP supported.");
} else {
reply(f, 331, "Send e-mail address as password.");
}
daemon_assert(invariant(f));
}
static void do_pass(ftp_session_t *f, const ftp_command_t *cmd)
{
const char *password;
char addr_port[ADDRPORT_STRLEN];
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
password = cmd->arg[0].string;
syslog(LOG_INFO, "%s reports e-mail address \"%s\"",
f->client_addr_str, password);
reply(f, 230, "User logged in, proceed.");
daemon_assert(invariant(f));
}
#ifdef INET6
static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
{
int port;
int error;
int len;
daemon_assert(s != NULL);
daemon_assert(buf != NULL);
/* buf must be able to contain (at least) a string representing an
* ipv4 addr, followed by the string " port " (6 chars) and the port
* number (which is 5 chars max), plus the '\0' character. */
daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
error = getnameinfo(client_addr, sizeof(sockaddr_storage_t), buf,
bufsiz, NULL, 0, NI_NUMERICHOST);
/* getnameinfo() should never fail when called with NI_NUMERICHOST */
daemon_assert(error == 0);
len = strlen(buf);
daemon_assert(bufsiz >= len+12);
snprintf(buf+len, bufsiz-len, " port %d", ntohs(SINPORT(&f->client_addr)));
}
#else
static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
{
unsigned int addr;
int port;
daemon_assert(s != NULL);
daemon_assert(buf != NULL);
/* buf must be able to contain (at least) a string representing an
* ipv4 addr, followed by the string " port " (6 chars) and the port
* number (which is 5 chars max), plus the '\0' character. */
daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
addr = ntohl(s->sin_addr.s_addr);
port = ntohs(s->sin_port);
snprintf(buf, bufsiz, "%d.%d.%d.%d port %d",
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
addr & 0xff,
port);
}
#endif
static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd)
{
const char *new_dir;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
new_dir = cmd->arg[0].string;
change_dir(f, new_dir);
daemon_assert(invariant(f));
}
static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
change_dir(f, "..");
daemon_assert(invariant(f));
}
static void change_dir(ftp_session_t *f, const char *new_dir)
{
char target[PATH_MAX+1];
const char *p, *n;
int len;
char *prev_dir;
char *target_end;
struct stat stat_buf;
int dir_okay;
daemon_assert(invariant(f));
daemon_assert(new_dir != NULL);
daemon_assert(strlen(new_dir) <= PATH_MAX);
/* set up our "base" directory that we build from */
p = new_dir;
if (*p == '/') {
/* if this starts with a '/' it is an absolute path */
strcpy(target, "/");
do {
p++;
} while (*p == '/');
} else {
/* otherwise it's a relative path */
daemon_assert(strlen(f->dir) < sizeof(target));
strcpy(target, f->dir);
}
/* add on each directory, handling "." and ".." */
while (*p != '\0') {
/* find the end of the next directory (either at '/' or '\0') */
n = strchr(p, '/');
if (n == NULL) {
n = strchr(p, '\0');
}
len = n - p;
if ((len == 1) && (p[0] == '.')) {
/* do nothing with "." */
} else if ((len == 2) && (p[0] == '.') && (p[1] == '.')) {
/* change to previous directory with ".." */
prev_dir = strrchr(target, '/');
daemon_assert(prev_dir != NULL);
*prev_dir = '\0';
if (prev_dir == target) {
strcpy(target, "/");
}
} else {
/* otherwise add to current directory */
if ((strlen(target) + 1 + len) > PATH_MAX) {
reply(f, 550, "Error changing directory; path is too long.");
return;
}
/* append a '/' unless we were at the root directory */
target_end = strchr(target, '\0');
if (target_end != target+1) {
*target_end++ = '/';
}
/* add the directory itself */
while (p != n) {
*target_end++ = *p++;
}
*target_end = '\0';
}
/* advance to next directory to check */
p = n;
/* skip '/' characters */
while (*p == '/') {
p++;
}
}
/* see if this is a directory we can change into */
dir_okay = 0;
if (stat(target, &stat_buf) == 0) {
#ifndef STAT_MACROS_BROKEN
if (!S_ISDIR(stat_buf.st_mode)) {
#else
if (S_ISDIR(stat_buf.st_mode)) {
#endif
reply(f, 550,"Directory change failed; target is not a directory.");
} else {
if (S_IXOTH & stat_buf.st_mode) {
dir_okay = 1;
} else if ((stat_buf.st_gid == getegid()) &&
(S_IXGRP & stat_buf.st_mode))
{
dir_okay = 1;
} else if ((stat_buf.st_uid == geteuid()) &&
(S_IXUSR & stat_buf.st_mode))
{
dir_okay = 1;
} else {
reply(f, 550, "Directory change failed; permission denied.");
}
}
} else {
reply(f, 550, "Directory change failed; directory does not exist.");
}
/* if everything is okay, change into the directory */
if (dir_okay) {
daemon_assert(strlen(target) < sizeof(f->dir));
/* send a readme unless we changed to our current directory */
if (strcmp(f->dir, target) != 0) {
strcpy(f->dir, target);
send_readme(f, 250);
} else {
strcpy(f->dir, target);
}
reply(f, 250, "Directory change successful.");
}
daemon_assert(invariant(f));
}
static void do_quit(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
reply(f, 221, "Service closing control connection.");
f->session_active = 0;
daemon_assert(invariant(f));
}
/* support for the various port setting functions */
static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port)
{
daemon_assert(invariant(f));
daemon_assert(host_port != NULL);
if (f->epsv_all_set) {
reply(f, 500, "After EPSV ALL, only EPSV allowed.");
} else if (!ip_equal(&f->client_addr, host_port)) {
reply(f, 500, "Port must be on command channel IP.");
} else if (ntohs(SINPORT(host_port)) < IPPORT_RESERVED) {
reply(f, 500, "Port may not be less than 1024, which is reserved.");
} else {
/* close any outstanding PASSIVE port */
if (f->data_channel == DATA_PASSIVE) {
close(f->server_fd);
f->server_fd = -1;
}
f->data_channel = DATA_PORT;
f->data_port = *host_port;
reply(f, 200, "Command okay.");
}
daemon_assert(invariant(f));
}
/* set IP and port for client to receive data on */
static void do_port(ftp_session_t *f, const ftp_command_t *cmd)
{
const sockaddr_storage_t *host_port;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
host_port = &cmd->arg[0].host_port;
daemon_assert(SSFAM(host_port) == AF_INET);
set_port(f, host_port);
daemon_assert(invariant(f));
}
/* set IP and port for client to receive data on, transport independent */
static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd)
{
const sockaddr_storage_t *host_port;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
host_port = &cmd->arg[0].host_port;
#ifdef INET6
if ((SSFAM(host_port) != AF_INET) && (SSFAM(host_port) != AF_INET6)) {
reply(f, 521, "Only IPv4 and IPv6 supported, address families (4,6)");
}
#else
if (SSFAM(host_port) != AF_INET) {
reply(f, 521, "Only IPv4 supported, address family (4)");
}
#endif
set_port(f, host_port);
daemon_assert(invariant(f));
}
/* set IP and port for the client to receive data on, IPv6 extension */
/* */
/* RFC 2428 specifies that if the data connection is going to be on */
/* the same IP as the control connection, EPSV must be used. Since */
/* that is the only mode of transfer we support, we reject all EPRT */
/* requests. */
static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd)
{
const sockaddr_storage_t *host_port;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
reply(f, 500, "EPRT not supported, use EPSV.");
daemon_assert(invariant(f));
}
/* support for the various pasv setting functions */
/* returns the file descriptor of the bound port, or -1 on error */
/* note: the "host_port" parameter will be modified, having its port set */
static int set_pasv(ftp_session_t *f, sockaddr_storage_t *bind_addr)
{
int socket_fd;
int port;
daemon_assert(invariant(f));
daemon_assert(bind_addr != NULL);
socket_fd = socket(SSFAM(bind_addr), SOCK_STREAM, 0);
if (socket_fd == -1) {
reply(f, 500, "Error creating server socket; %s.", strerror(errno));
return -1;
}
for (;;) {
port = get_passive_port();
SINPORT(bind_addr) = htons(port);
if (bind(socket_fd, (struct sockaddr *)bind_addr,
sizeof(struct sockaddr)) == 0)
{
break;
}
if (errno != EADDRINUSE) {
reply(f, 500, "Error binding server port; %s.", strerror(errno));
close(socket_fd);
return -1;
}
}
if (listen(socket_fd, 1) != 0) {
reply(f, 500, "Error listening on server port; %s.", strerror(errno));
close(socket_fd);
return -1;
}
return socket_fd;
}
/* pick a server port to listen for connection on */
static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd)
{
int socket_fd;
unsigned int addr;
int port;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
if (f->epsv_all_set) {
reply(f, 500, "After EPSV ALL, only EPSV allowed.");
goto exit_pasv;
}
socket_fd = set_pasv(f, &f->server_ipv4_addr);
if (socket_fd == -1) {
goto exit_pasv;
}
/* report port to client */
addr = ntohl(f->server_ipv4_addr.sin_addr.s_addr);
port = ntohs(f->server_ipv4_addr.sin_port);
reply(f, 227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d).",
addr >> 24,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
addr & 0xff,
port >> 8,
port & 0xff);
/* close any outstanding PASSIVE port */
if (f->data_channel == DATA_PASSIVE) {
close(f->server_fd);
}
f->data_channel = DATA_PASSIVE;
f->server_fd = socket_fd;
exit_pasv:
daemon_assert(invariant(f));
}
/* pick a server port to listen for connection on, including IPv6 */
static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd)
{
int socket_fd;
char addr[80];
uint8_t *a;
uint8_t *p;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
if (f->epsv_all_set) {
reply(f, 500, "After EPSV ALL, only EPSV allowed.");
goto exit_lpsv;
}
socket_fd = set_pasv(f, &f->server_addr);
if (socket_fd == -1) {
goto exit_lpsv;
}
/* report address and port to client */
#ifdef INET6
if (SSFAM(&f->server_addr) == AF_INET6) {
a = (uint8_t *)&SIN6ADDR(&f->server_addr);
p = (uint8_t *)&SIN6PORT(&f->server_addr);
snprintf(addr, sizeof(addr),
"(6,16,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,2,%d,%d)",
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8],
a[9], a[10], a[11], a[12], a[13], a[14], a[15], p[0], p[1]);
} else
#endif
{
a = (uint8_t *)&SIN4ADDR(&f->server_addr);
p = (uint8_t *)&SIN4PORT(&f->server_addr);
snprintf(addr, sizeof(addr), "(4,4,%d,%d,%d,%d,2,%d,%d)",
a[0], a[1], a[2], a[3], p[0], p[1]);
}
reply(f, 228, "Entering Long Passive Mode %s", addr);
/* close any outstanding PASSIVE port */
if (f->data_channel == DATA_PASSIVE) {
close(f->server_fd);
}
f->data_channel = DATA_PASSIVE;
f->server_fd = socket_fd;
exit_lpsv:
daemon_assert(invariant(f));
}
/* pick a server port to listen for connection on, new IPv6 method */
static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd)
{
int socket_fd;
sockaddr_storage_t *addr;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
/* check our argument, if any, and use the appropriate address */
if (cmd->num_arg == 0) {
addr = &f->server_addr;
} else {
switch (cmd->arg[0].num) {
/* EPSV_ALL is a special number indicating the client sent */
/* the command "EPSV ALL" - this is not a request to assign */
/* a new passive port, but rather to deny all future port */
/* assignment requests other than EPSV */
case EPSV_ALL:
f->epsv_all_set = 1;
reply(f, 200, "EPSV ALL command successful.");
goto exit_epsv;
case 1:
addr = (sockaddr_storage_t *)&f->server_ipv4_addr;
break;
#ifdef INET6
case 2:
addr = &f->server_addr;
break;
default:
reply(f, 522, "Only IPv4 and IPv6 supported, use (1,2)");
goto exit_epsv;
#else
default:
reply(f, 522, "Only IPv4 supported, use (1)");
goto exit_epsv;
#endif
}
}
/* bind port and so on */
socket_fd = set_pasv(f, addr);
if (socket_fd == -1) {
goto exit_epsv;
}
/* report port to client */
reply(f, 229, "Entering Extended Passive Mode (|||%d|)",
ntohs(SINPORT(&f->server_addr)));
/* close any outstanding PASSIVE port */
if (f->data_channel == DATA_PASSIVE) {
close(f->server_fd);
}
f->data_channel = DATA_PASSIVE;
f->server_fd = socket_fd;
exit_epsv:
daemon_assert(invariant(f));
}
/* seed the random number generator used to pick a port */
static void init_passive_port()
{
struct timeval tv;
unsigned short int seed[3];
gettimeofday(&tv, NULL);
seed[0] = (tv.tv_sec >> 16) & 0xFFFF;
seed[1] = tv.tv_sec & 0xFFFF;
seed[2] = tv.tv_usec & 0xFFFF;
seed48(seed);
}
/* pick a port to try to bind() for passive FTP connections */
static int get_passive_port()
{
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int port;
/* initialize the random number generator the first time we're called */
pthread_once(&once_control, init_passive_port);
/* pick a random port between 1024 and 65535, inclusive */
pthread_mutex_lock(&mutex);
port = (lrand48() % 64512) + 1024;
pthread_mutex_unlock(&mutex);
return port;
}
static void do_type(ftp_session_t *f, const ftp_command_t *cmd)
{
char type;
char form;
int cmd_okay;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg >= 1);
daemon_assert(cmd->num_arg <= 2);
type = cmd->arg[0].string[0];
if (cmd->num_arg == 2) {
form = cmd->arg[1].string[0];
} else {
form = 0;
}
cmd_okay = 0;
if (type == 'A') {
if ((cmd->num_arg == 1) || ((cmd->num_arg == 2) && (form == 'N'))) {
f->data_type = TYPE_ASCII;
cmd_okay = 1;
}
} else if (type == 'I') {
f->data_type = TYPE_IMAGE;
cmd_okay = 1;
}
if (cmd_okay) {
reply(f, 200, "Command okay.");
} else {
reply(f, 504, "Command not implemented for that parameter.");
}
daemon_assert(invariant(f));
}
static void do_stru(ftp_session_t *f, const ftp_command_t *cmd)
{
char structure;
int cmd_okay;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
structure = cmd->arg[0].string[0];
cmd_okay = 0;
if (structure == 'F') {
f->file_structure = STRU_FILE;
cmd_okay = 1;
} else if (structure == 'R') {
f->file_structure = STRU_RECORD;
cmd_okay = 1;
}
if (cmd_okay) {
reply(f, 200, "Command okay.");
} else {
reply(f, 504, "Command not implemented for that parameter.");
}
daemon_assert(invariant(f));
}
static void do_mode(ftp_session_t *f, const ftp_command_t *cmd)
{
char mode;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
mode = cmd->arg[0].string[0];
if (mode == 'S') {
reply(f, 200, "Command okay.");
} else {
reply(f, 504, "Command not implemented for that parameter.");
}
daemon_assert(invariant(f));
}
/* convert the user-entered file name into a full path on our local drive */
static void get_absolute_fname(char *fname,
int fname_len,
const char *dir,
const char *file)
{
daemon_assert(fname != NULL);
daemon_assert(dir != NULL);
daemon_assert(file != NULL);
if (*file == '/') {
/* absolute path, use as input */
daemon_assert(strlen(file) < fname_len);
strcpy(fname, file);
} else {
/* construct a file name based on our current directory */
daemon_assert(strlen(dir) + 1 + strlen(file) < fname_len);
strcpy(fname, dir);
/* add a seperating '/' if we're not at the root */
if (fname[1] != '\0') {
strcat(fname, "/");
}
/* and of course the actual file name */
strcat(fname, file);
}
}
static void do_retr(ftp_session_t *f, const ftp_command_t *cmd)
{
const char *file_name;
char full_path[PATH_MAX+1+MAX_STRING_LEN];
int file_fd;
struct stat stat_buf;
int socket_fd;
int read_ret;
char buf[4096];
char converted_buf[8192];
int converted_buflen;
char addr_port[ADDRPORT_STRLEN];
struct timeval start_timestamp;
struct timeval end_timestamp;
struct timeval transfer_time;
off_t file_size;
off_t offset;
off_t amt_to_send;
int sendfile_ret;
off_t amt_sent;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
/* set up for exit */
file_fd = -1;
socket_fd = -1;
/* create an absolute name for our file */
file_name = cmd->arg[0].string;
get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
/* open file */
file_fd = open(full_path, O_RDONLY);
if (file_fd == -1) {
reply(f, 550, "Error opening file; %s.", strerror(errno));
goto exit_retr;
}
if (fstat(file_fd, &stat_buf) != 0) {
reply(f, 550, "Error getting file information; %s.", strerror(errno));
goto exit_retr;
}
#ifndef STATS_MACRO_BROKEN
if (S_ISDIR(stat_buf.st_mode)) {
#else
if (!S_ISDIR(stat_buf.st_mode)) {
#endif
reply(f, 550, "Error, file is a directory.");
goto exit_retr;
}
/* if the last command was a REST command, restart at the */
/* requested position in the file */
if ((f->file_offset_command_number == (f->command_number - 1)) &&
(f->file_offset > 0))
{
if (lseek(file_fd, f->file_offset, SEEK_SET) == -1) {
reply(f, 550, "Error seeking to restart position; %s.",
strerror(errno));
goto exit_retr;
}
}
/* ready to transfer */
reply(f, 150, "About to open data connection.");
/* mark start time */
gettimeofday(&start_timestamp, NULL);
/* open data path */
socket_fd = open_connection(f);
if (socket_fd == -1) {
goto exit_retr;
}
/* we're golden, send the file */
file_size = 0;
if (f->data_type == TYPE_ASCII) {
for (;;) {
read_ret = read(file_fd, buf, sizeof(buf));
if (read_ret == -1) {
reply(f, 550, "Error reading from file; %s.", strerror(errno));
goto exit_retr;
}
if (read_ret == 0) {
break;
}
converted_buflen = convert_newlines(converted_buf, buf, read_ret);
if (write_fully(socket_fd, converted_buf, converted_buflen) == -1) {
reply(f, 550, "Error writing to data connection; %s.",
strerror(errno));
goto exit_retr;
}
file_size += converted_buflen;
}
} else {
daemon_assert(f->data_type == TYPE_IMAGE);
/* for sendfile(), we still have to use a loop to avoid
having our watchdog time us out on large files - it does
allow us to avoid an extra copy to/from user space */
/* #ifdef HAVE_SENDFILE
offset = f->file_offset;
file_size = stat_buf.st_size - offset;
while (offset < stat_buf.st_size) {
amt_to_send = stat_buf.st_size - offset;
if (amt_to_send > 65536) {
amt_to_send = 65536;
}
#ifdef HAVE_LINUX_SENDFILE
sendfile_ret = sendfile(socket_fd,
file_fd,
&offset,
amt_to_send);
if (sendfile_ret != amt_to_send) {
reply(f, 550, "Error sending file; %s.", strerror(errno));
goto exit_retr;
}
#elif HAVE_FREEBSD_SENDFILE
sendfile_ret = sendfile(file_fd,
socket_fd,
offset,
amt_to_send,
NULL,
&amt_sent,
0);
if (sendfile_ret != 0) {
reply(f, 550, "Error sending file; %s.", strerror(errno));
goto exit_retr;
}
offset += amt_sent;
#endif
watchdog_defer_watched(f->watched);
}
#else */
for (;;) {
read_ret = read(file_fd, buf, sizeof(buf));
if (read_ret == -1) {
reply(f, 550, "Error reading from file; %s.", strerror(errno));
goto exit_retr;
}
if (read_ret == 0) {
break;
}
if (write_fully(socket_fd, buf, read_ret) == -1) {
reply(f, 550, "Error writing to data connection; %s.",
strerror(errno));
goto exit_retr;
}
file_size += read_ret;
watchdog_defer_watched(f->watched);
}
/* #endif */ /* HAVE_SENDFILE */
}
/* disconnect */
close(socket_fd);
socket_fd = -1;
/* hey, it worked, let the other side know */
reply(f, 226, "File transfer complete.");
/* mark end time */
gettimeofday(&end_timestamp, NULL);
/* calculate transfer rate */
transfer_time.tv_sec = end_timestamp.tv_sec - start_timestamp.tv_sec;
transfer_time.tv_usec = end_timestamp.tv_usec - start_timestamp.tv_usec;
while (transfer_time.tv_usec >= 1000000) {
transfer_time.tv_sec++;
transfer_time.tv_usec -= 1000000;
}
while (transfer_time.tv_usec < 0) {
transfer_time.tv_sec--;
transfer_time.tv_usec += 1000000;
}
/* note the transfer */
syslog(LOG_INFO,
"%s retrieved \"%s\", %ld bytes in %d.%06d seconds",
f->client_addr_str,
full_path,
file_size,
transfer_time.tv_sec,
transfer_time.tv_usec);
exit_retr:
f->file_offset = 0;
if (socket_fd != -1) {
close(socket_fd);
}
if (file_fd != -1) {
close(file_fd);
}
daemon_assert(invariant(f));
}
static void do_stor(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
reply(f, 553, "Server will not store files.");
daemon_assert(invariant(f));
}
static int open_connection(ftp_session_t *f)
{
int socket_fd;
struct sockaddr_in addr;
unsigned addr_len;
daemon_assert((f->data_channel == DATA_PORT) ||
(f->data_channel == DATA_PASSIVE));
if (f->data_channel == DATA_PORT) {
socket_fd = socket(SSFAM(&f->data_port), SOCK_STREAM, 0);
if (socket_fd == -1) {
reply(f, 425, "Error creating socket; %s.", strerror(errno));
return -1;
}
if (connect(socket_fd, (struct sockaddr *)&f->data_port,
sizeof(sockaddr_storage_t)) != 0)
{
reply(f, 425, "Error connecting; %s.", strerror(errno));
close(socket_fd);
return -1;
}
} else {
daemon_assert(f->data_channel == DATA_PASSIVE);
addr_len = sizeof(struct sockaddr_in);
socket_fd = accept(f->server_fd, (struct sockaddr *)&addr, &addr_len);
if (socket_fd == -1) {
reply(f, 425, "Error accepting connection; %s.", strerror(errno));
return -1;
}
#ifdef INET6
/* in IPv6, the client can connect to a channel using a different */
/* protocol - in that case, we'll just blindly let the connection */
/* through, otherwise verify addresses match */
if (SAFAM(addr) == SSFAM(&f->client_addr)) {
if (memcmp(&SINADDR(&f->client_addr), &SINADDR(&addr),
sizeof(SINADDR(&addr))))
{
reply(f, 425,
"Error accepting connection; connection from invalid IP.");
close(socket_fd);
return -1;
}
}
#else
if (memcmp(&f->client_addr.sin_addr,
&addr.sin_addr, sizeof(struct in_addr)))
{
reply(f, 425,
"Error accepting connection; connection from invalid IP.");
close(socket_fd);
return -1;
}
#endif
}
return socket_fd;
}
/* convert any '\n' to '\r\n' */
/* destination should be twice the size of the source for safety */
static int convert_newlines(char *dst, const char *src, int srclen)
{
int i;
int dstlen;
daemon_assert(dst != NULL);
daemon_assert(src != NULL);
dstlen = 0;
for (i=0; i<srclen; i++) {
if (src[i] == '\n') {
dst[dstlen++] = '\r';
}
dst[dstlen++] = src[i];
}
return dstlen;
}
static int write_fully(int fd, const char *buf, int buflen)
{
int amt_written;
int write_ret;
amt_written = 0;
while (amt_written < buflen) {
write_ret = write(fd, buf+amt_written, buflen-amt_written);
if (write_ret <= 0) {
return -1;
}
amt_written += write_ret;
}
return amt_written;
}
static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
reply(f, 257, "\"%s\" is current directory.", f->dir);
daemon_assert(invariant(f));
}
#if 0
/*
because oftpd uses glob(), it is possible for users to launch a
denial-of-service attack by sending certain wildcard expressions that
create extremely large lists of files, e.g. "*/../*/../*/../*/../*/../*"
in order to prevent this, a user may pass wildcards, or paths, but not
both as arguments to LIST or NLST - at most all the files from a single
directory will be returned
*/
#endif
/* check if a filespec has a wildcard in it */
static int filespec_has_wildcard(const char *filespec)
{
daemon_assert(filespec != NULL);
/* check each character for wildcard */
while (*filespec != '\0') {
switch (*filespec) {
/* wildcards */
case '*':
case '?':
case '[':
return 1;
/* backslash escapes next character unless at end of string */
case '\\':
if (*(filespec+1) != '\0') {
filespec++;
}
break;
}
filespec++;
}
return 0;
}
/* filespec includes path separator, i.e. '/' */
static int filespec_has_path_separator(const char *filespec)
{
daemon_assert(filespec != NULL);
/* check each character for path separator */
if (strchr(filespec, '/') != NULL) {
return 1;
} else {
return 0;
}
}
/* returns whether filespec is legal or not */
static int filespec_is_legal(const char *filespec)
{
daemon_assert(filespec != NULL);
if (filespec_has_wildcard(filespec)) {
if (filespec_has_path_separator(filespec)) {
return 0;
}
}
return 1;
}
static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd)
{
int fd;
const char *param;
int send_ok;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
/* set up for exit */
fd = -1;
/* figure out what parameters to use */
if (cmd->num_arg == 0) {
param = "*";
} else {
daemon_assert(cmd->num_arg == 1);
/* ignore attempts to send options to "ls" by silently dropping */
if (cmd->arg[0].string[0] == '-') {
param = "*";
} else {
param = cmd->arg[0].string;
}
}
/* check spec passed */
if (!filespec_is_legal(param)) {
reply(f, 550, "Illegal filename passed.");
goto exit_nlst;
}
/* ready to list */
reply(f, 150, "About to send name list.");
/* open our data connection */
fd = open_connection(f);
if (fd == -1) {
goto exit_nlst;
}
/* send any files */
send_ok = file_nlst(fd, f->dir, param);
/* strange handshake for Netscape's benefit */
netscape_hack(fd);
if (send_ok) {
reply(f, 226, "Transfer complete.");
} else {
reply(f, 451, "Error sending name list.");
}
/* clean up and exit */
exit_nlst:
if (fd != -1) {
close(fd);
}
daemon_assert(invariant(f));
}
static void do_list(ftp_session_t *f, const ftp_command_t *cmd)
{
int fd;
const char *param;
int send_ok;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
/* set up for exit */
fd = -1;
/* figure out what parameters to use */
if (cmd->num_arg == 0) {
param = "*";
} else {
daemon_assert(cmd->num_arg == 1);
/* ignore attempts to send options to "ls" by silently dropping */
if (cmd->arg[0].string[0] == '-') {
param = "*";
} else {
param = cmd->arg[0].string;
}
}
/* check spec passed */
if (!filespec_is_legal(param)) {
reply(f, 550, "Illegal filename passed.");
goto exit_list;
}
/* ready to list */
reply(f, 150, "About to send file list.");
/* open our data connection */
fd = open_connection(f);
if (fd == -1) {
goto exit_list;
}
/* send any files */
send_ok = file_list(fd, f->dir, param);
/* strange handshake for Netscape's benefit */
netscape_hack(fd);
if (send_ok) {
reply(f, 226, "Transfer complete.");
} else {
reply(f, 451, "Error sending file list.");
}
/* clean up and exit */
exit_list:
if (fd != -1) {
close(fd);
}
daemon_assert(invariant(f));
}
static void do_syst(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
reply(f, 215, "UNIX");
daemon_assert(invariant(f));
}
static void do_noop(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 0);
reply(f, 200, "Command okay.");
daemon_assert(invariant(f));
}
static void do_rest(ftp_session_t *f, const ftp_command_t *cmd)
{
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
if (f->data_type != TYPE_IMAGE) {
reply(f, 555, "Restart not possible in ASCII mode.");
} else if (f->file_structure != STRU_FILE) {
reply(f, 555, "Restart only possible with FILE structure.");
} else {
f->file_offset = cmd->arg[0].offset;
f->file_offset_command_number = f->command_number;
reply(f, 350, "Restart okay, awaiting file retrieval request.");
}
daemon_assert(invariant(f));
}
static void do_size(ftp_session_t *f, const ftp_command_t *cmd)
{
const char *file_name;
char full_path[PATH_MAX+1+MAX_STRING_LEN];
struct stat stat_buf;
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
if (f->data_type != TYPE_IMAGE) {
reply(f, 550, "Size cannot be determined in ASCII mode.");
} else if (f->file_structure != STRU_FILE) {
reply(f, 550, "Size cannot be determined with FILE structure.");
} else {
/* create an absolute name for our file */
file_name = cmd->arg[0].string;
get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
/* get the file information */
if (stat(full_path, &stat_buf) != 0) {
reply(f, 550, "Error getting file status; %s.", strerror(errno));
} else {
/* verify that the file is not a directory */
if (S_ISDIR(stat_buf.st_mode)) {
reply(f, 550, "File is a directory, SIZE command not valid.");
} else {
/* output the size */
if (sizeof(off_t) == 8) {
reply(f, 213, "%llu", stat_buf.st_size);
} else {
reply(f, 213, "%lu", stat_buf.st_size);
}
}
}
}
daemon_assert(invariant(f));
}
/* if no gmtime_r() is available, provide one */
#ifndef HAVE_GMTIME_R
struct tm *gmtime_r(const time_t *timep, struct tm *timeptr)
{
static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&time_lock);
*timeptr = *(gmtime(timep));
pthread_mutex_unlock(&time_lock);
return timeptr;
}
#endif /* HAVE_GMTIME_R */
static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd)
{
const char *file_name;
char full_path[PATH_MAX+1+MAX_STRING_LEN];
struct stat stat_buf;
struct tm mtime;
char time_buf[16];
daemon_assert(invariant(f));
daemon_assert(cmd != NULL);
daemon_assert(cmd->num_arg == 1);
/* create an absolute name for our file */
file_name = cmd->arg[0].string;
get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
/* get the file information */
if (stat(full_path, &stat_buf) != 0) {
reply(f, 550, "Error getting file status; %s.", strerror(errno));
} else {
gmtime_r(&stat_buf.st_mtime, &mtime);
strftime(time_buf, sizeof(time_buf), "%Y%m%d%H%M%S", &mtime);
reply(f, 213, time_buf);
}
daemon_assert(invariant(f));
}
static void send_readme(const ftp_session_t *f, int code)
{
char file_name[PATH_MAX+1];
int dir_len;
struct stat stat_buf;
int fd;
int read_ret;
char buf[4096];
char code_str[8];
char *p;
int len;
char *nl;
int line_len;
daemon_assert(invariant(f));
daemon_assert(code >= 100);
daemon_assert(code <= 559);
/* set up for early exit */
fd = -1;
/* verify our README wouldn't be too long */
dir_len = strlen(f->dir);
if ((dir_len + 1 + sizeof(README_FILE_NAME)) > sizeof(file_name)) {
goto exit_send_readme;
}
/* create a README file name */
strcpy(file_name, f->dir);
strcat(file_name, "/");
strcat(file_name, README_FILE_NAME);
/* open our file */
fd = open(file_name, O_RDONLY);
if (fd == -1) {
goto exit_send_readme;
}
/* verify this isn't a directory */
if (fstat(fd, &stat_buf) != 0) {
goto exit_send_readme;
}
#ifndef STATS_MACRO_BROKEN
if (S_ISDIR(stat_buf.st_mode)) {
#else
if (!S_ISDIR(stat_buf.st_mode)) {
#endif
goto exit_send_readme;
}
/* convert our code to a buffer */
daemon_assert(code >= 100);
daemon_assert(code <= 999);
sprintf(code_str, "%03d-", code);
/* read and send */
read_ret = read(fd, buf, sizeof(buf));
if (read_ret > 0) {
telnet_session_print(f->telnet_session, code_str);
while (read_ret > 0) {
p = buf;
len = read_ret;
nl = memchr(p, '\n', len);
while ((len > 0) && (nl != NULL)) {
*nl = '\0';
telnet_session_println(f->telnet_session, p);
line_len = nl - p;
len -= line_len + 1;
if (len > 0) {
telnet_session_print(f->telnet_session, code_str);
}
p = nl+1;
nl = memchr(p, '\n', len);
}
if (len > 0) {
telnet_session_print(f->telnet_session, p);
}
read_ret = read(fd, buf, sizeof(buf));
}
}
/* cleanup and exit */
exit_send_readme:
if (fd != -1) {
close(fd);
}
daemon_assert(invariant(f));
}
/* hack which prevents Netscape error in file list */
static void netscape_hack(int fd)
{
fd_set readfds;
struct timeval timeout;
int select_ret;
char c;
daemon_assert(fd >= 0);
shutdown(fd, 1);
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 15;
timeout.tv_usec = 0;
select_ret = select(fd+1, &readfds, NULL, NULL, &timeout);
if (select_ret > 0) {
read(fd, &c, 1);
}
}
/* compare two addresses to see if they contain the same IP address */
static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b)
{
daemon_assert(a != NULL);
daemon_assert(b != NULL);
daemon_assert((SSFAM(a) == AF_INET) || (SSFAM(a) == AF_INET6));
daemon_assert((SSFAM(b) == AF_INET) || (SSFAM(b) == AF_INET6));
if (SSFAM(a) != SSFAM(b)) {
return 0;
}
if (memcmp(&SINADDR(a), &SINADDR(b), sizeof(SINADDR(a))) != 0) {
return 0;
}
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1