sane-project-backends/frontend/saned.c

3565 wiersze
84 KiB
C

/* sane - Scanner Access Now Easy.
Copyright (C) 1997 Andreas Beck
Copyright (C) 2001 - 2004 Henning Meier-Geinitz
Copyright (C) 2003, 2008 Julien BLACHE <jb@jblache.org>
AF-independent + IPv6 code, standalone mode
This file is part of the SANE package.
SANE is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.
SANE 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 sane; see the file COPYING.
If not, see <https://www.gnu.org/licenses/>.
The SANE network daemon. This is the counterpart to the NET
backend.
*/
#ifdef _AIX
# include "../include/lalloca.h" /* MUST come first for AIX! */
#endif
#include "../include/sane/config.h"
#include "../include/lalloca.h"
#include <sys/types.h>
#if defined(HAVE_GETADDRINFO) && defined (HAVE_GETNAMEINFO)
# define SANED_USES_AF_INDEP
# ifdef HAS_SS_FAMILY
# define SS_FAMILY(ss) ss.ss_family
# elif defined(HAS___SS_FAMILY)
# define SS_FAMILY(ss) ss.__ss_family
# else /* fallback to the old, IPv4-only code */
# undef SANED_USES_AF_INDEP
# undef ENABLE_IPV6
# endif
#else
# undef ENABLE_IPV6
#endif /* HAVE_GETADDRINFO && HAVE_GETNAMEINFO */
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
#ifdef HAVE_LIBC_H
# include <libc.h> /* NeXTStep/OpenStep */
#endif
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <netinet/in.h>
#include <stdarg.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>
#include "lgetopt.h"
#if defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL)
# include <sys/poll.h>
#else
/*
* This replacement poll() using select() is only designed to cover
* our needs in run_standalone(). It should probably be extended...
*/
struct pollfd
{
int fd;
short events;
short revents;
};
#define POLLIN 0x0001
#define POLLERR 0x0002
int
poll (struct pollfd *ufds, unsigned int nfds, int timeout);
int
poll (struct pollfd *ufds, unsigned int nfds, int timeout)
{
struct pollfd *fdp;
fd_set rfds;
fd_set efds;
struct timeval tv;
int maxfd = 0;
unsigned int i;
int ret;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000;
FD_ZERO (&rfds);
FD_ZERO (&efds);
for (i = 0, fdp = ufds; i < nfds; i++, fdp++)
{
fdp->revents = 0;
if (fdp->events & POLLIN)
FD_SET (fdp->fd, &rfds);
FD_SET (fdp->fd, &efds);
maxfd = (fdp->fd > maxfd) ? fdp->fd : maxfd;
}
maxfd++;
ret = select (maxfd, &rfds, NULL, &efds, &tv);
if (ret < 0)
return ret;
for (i = 0, fdp = ufds; i < nfds; i++, fdp++)
{
if (fdp->events & POLLIN)
if (FD_ISSET (fdp->fd, &rfds))
fdp->revents |= POLLIN;
if (FD_ISSET (fdp->fd, &efds))
fdp->revents |= POLLERR;
}
return ret;
}
#endif /* HAVE_SYS_POLL_H && HAVE_POLL */
#if WITH_AVAHI
# include <avahi-client/client.h>
# include <avahi-client/publish.h>
# include <avahi-common/alternative.h>
# include <avahi-common/simple-watch.h>
# include <avahi-common/malloc.h>
# include <avahi-common/error.h>
# define SANED_SERVICE_DNS "_sane-port._tcp"
# define SANED_NAME "saned"
pid_t avahi_pid = -1;
char *avahi_svc_name;
static AvahiClient *avahi_client = NULL;
static AvahiSimplePoll *avahi_poll = NULL;
static AvahiEntryGroup *avahi_group = NULL;
#endif /* WITH_AVAHI */
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/sanei_net.h"
#include "../include/sane/sanei_codec_bin.h"
#include "../include/sane/sanei_config.h"
#include "../include/sane/sanei_auth.h"
#ifndef EXIT_SUCCESS
# define EXIT_SUCCESS 0
#endif
#ifndef IN_LOOPBACK
# define IN_LOOPBACK(addr) (addr == 0x7f000001L)
#endif
#ifdef ENABLE_IPV6
# ifndef IN6_IS_ADDR_LOOPBACK
# define IN6_IS_ADDR_LOOPBACK(a) \
(((const uint32_t *) (a))[0] == 0 \
&& ((const uint32_t *) (a))[1] == 0 \
&& ((const uint32_t *) (a))[2] == 0 \
&& ((const uint32_t *) (a))[3] == htonl (1))
# endif
# ifndef IN6_IS_ADDR_V4MAPPED
# define IN6_IS_ADDR_V4MAPPED(a) \
((((const uint32_t *) (a))[0] == 0) \
&& (((const uint32_t *) (a))[1] == 0) \
&& (((const uint32_t *) (a))[2] == htonl (0xffff)))
# endif
#endif /* ENABLE_IPV6 */
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN 120
#endif
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
struct saned_child {
pid_t pid;
struct saned_child *next;
};
struct saned_child *children;
int numchildren;
#define SANED_CONFIG_FILE "saned.conf"
#define SANED_PID_FILE "/var/run/saned.pid"
#define SANED_SERVICE_NAME "sane-port"
#define SANED_SERVICE_PORT 6566
#define SANED_SERVICE_PORT_S "6566"
typedef struct
{
u_int inuse:1; /* is this handle in use? */
u_int scanning:1; /* are we scanning? */
u_int docancel:1; /* cancel the current scan */
SANE_Handle handle; /* backends handle */
}
Handle;
static SANE_Net_Procedure_Number current_request;
static const char *prog_name;
static int can_authorize;
static Wire wire;
static int num_handles;
static int debug;
static int run_mode;
static int run_foreground;
static int run_once;
static int data_connect_timeout = 4000;
static Handle *handle;
static char *bind_addr;
static short bind_port = -1;
static union
{
int w;
u_char ch;
}
byte_order;
/* The default-user name. This is not used to imply any rights. All
it does is save a remote user some work by reducing the amount of
text s/he has to type when authentication is requested. */
static const char *default_username = "saned-user";
static char *remote_ip;
/* data port range */
static in_port_t data_port_lo;
static in_port_t data_port_hi;
#ifdef SANED_USES_AF_INDEP
static union {
struct sockaddr_storage ss;
struct sockaddr sa;
struct sockaddr_in sin;
#ifdef ENABLE_IPV6
struct sockaddr_in6 sin6;
#endif
} remote_address;
static int remote_address_len;
#else
static struct in_addr remote_address;
#endif /* SANED_USES_AF_INDEP */
#ifndef _PATH_HEQUIV
# define _PATH_HEQUIV "/etc/hosts.equiv"
#endif
static const char *config_file_names[] = {
_PATH_HEQUIV, SANED_CONFIG_FILE
};
static SANE_Bool log_to_syslog = SANE_TRUE;
/* forward declarations: */
static int process_request (Wire * w);
#define SANED_RUN_INETD 0
#define SANED_RUN_ALONE 1
#define DBG_ERR 1
#define DBG_WARN 2
#define DBG_MSG 3
#define DBG_INFO 4
#define DBG_DBG 5
#define DBG saned_debug_call
static void
saned_debug_call (int level, const char *fmt, ...)
{
#ifndef NDEBUG
va_list ap;
va_start (ap, fmt);
if (debug >= level)
{
if (log_to_syslog)
{
/* print to syslog */
vsyslog (LOG_DEBUG, fmt, ap);
}
else
{
/* print to stderr */
fprintf (stderr, "[saned] ");
vfprintf (stderr, fmt, ap);
}
}
va_end (ap);
#endif
}
static void
reset_watchdog (void)
{
if (!debug)
alarm (3600);
}
static void
auth_callback (SANE_String_Const res,
SANE_Char *username,
SANE_Char *password)
{
SANE_Net_Procedure_Number procnum;
SANE_Authorization_Req req;
SANE_Word word, ack = 0;
memset (username, 0, SANE_MAX_USERNAME_LEN);
memset (password, 0, SANE_MAX_PASSWORD_LEN);
if (!can_authorize)
{
DBG (DBG_WARN,
"auth_callback: called during non-authorizable RPC (resource=%s)\n",
res);
return;
}
if (wire.status)
{
DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
return;
}
switch (current_request)
{
case SANE_NET_OPEN:
{
SANE_Open_Reply reply;
memset (&reply, 0, sizeof (reply));
reply.resource_to_authorize = (char *) res;
sanei_w_reply (&wire, (WireCodecFunc) sanei_w_open_reply, &reply);
}
break;
case SANE_NET_CONTROL_OPTION:
{
SANE_Control_Option_Reply reply;
memset (&reply, 0, sizeof (reply));
reply.resource_to_authorize = (char *) res;
sanei_w_reply (&wire,
(WireCodecFunc) sanei_w_control_option_reply, &reply);
}
break;
case SANE_NET_START:
{
SANE_Start_Reply reply;
memset (&reply, 0, sizeof (reply));
reply.resource_to_authorize = (char *) res;
sanei_w_reply (&wire, (WireCodecFunc) sanei_w_start_reply, &reply);
}
break;
default:
DBG (DBG_WARN,
"auth_callback: called for unexpected request %d (resource=%s)\n",
current_request, res);
break;
}
if (wire.status)
{
DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
return;
}
reset_watchdog ();
sanei_w_set_dir (&wire, WIRE_DECODE);
sanei_w_word (&wire, &word);
if (wire.status)
{
DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
return;
}
procnum = word;
if (procnum != SANE_NET_AUTHORIZE)
{
DBG (DBG_WARN,
"auth_callback: bad procedure number %d "
"(expected: %d, resource=%s)\n", procnum, SANE_NET_AUTHORIZE,
res);
return;
}
sanei_w_authorization_req (&wire, &req);
if (wire.status)
{
DBG(DBG_ERR, "auth_callback: bad status %d\n", wire.status);
return;
}
if (req.username)
strcpy (username, req.username);
if (req.password)
strcpy (password, req.password);
if (!req.resource || strcmp (req.resource, res) != 0)
{
DBG (DBG_MSG,
"auth_callback: got auth for resource %s (expected resource=%s)\n",
res, req.resource);
}
sanei_w_free (&wire, (WireCodecFunc) sanei_w_authorization_req, &req);
sanei_w_reply (&wire, (WireCodecFunc) sanei_w_word, &ack);
}
static void
quit (int signum)
{
static int running = 0;
int i;
if (signum)
DBG (DBG_ERR, "quit: received signal %d\n", signum);
if (running)
{
DBG (DBG_ERR, "quit: already active, returning\n");
return;
}
running = 1;
for (i = 0; i < num_handles; ++i)
if (handle[i].inuse)
sane_close (handle[i].handle);
sane_exit ();
sanei_w_exit (&wire);
if (handle)
free (handle);
DBG (DBG_WARN, "quit: exiting\n");
if (log_to_syslog)
closelog ();
exit (EXIT_SUCCESS); /* This is a nowait-daemon. */
}
static SANE_Word
get_free_handle (void)
{
# define ALLOC_INCREMENT 16
static int h, last_handle_checked = -1;
if (num_handles > 0)
{
h = last_handle_checked + 1;
do
{
if (h >= num_handles)
h = 0;
if (!handle[h].inuse)
{
last_handle_checked = h;
memset (handle + h, 0, sizeof (handle[0]));
handle[h].inuse = 1;
return h;
}
++h;
}
while (h != last_handle_checked);
}
/* we're out of handles---alloc some more: */
last_handle_checked = num_handles - 1;
num_handles += ALLOC_INCREMENT;
if (handle)
handle = realloc (handle, num_handles * sizeof (handle[0]));
else
handle = malloc (num_handles * sizeof (handle[0]));
if (!handle)
return -1;
memset (handle + last_handle_checked + 1, 0,
ALLOC_INCREMENT * sizeof (handle[0]));
return get_free_handle ();
# undef ALLOC_INCREMENT
}
static void
close_handle (int h)
{
if (h >= 0 && handle[h].inuse)
{
sane_close (handle[h].handle);
handle[h].inuse = 0;
}
}
static SANE_Word
decode_handle (Wire * w, const char *op)
{
SANE_Word h;
sanei_w_word (w, &h);
if (w->status || (unsigned) h >= (unsigned) num_handles || !handle[h].inuse)
{
DBG (DBG_ERR,
"decode_handle: %s: error while decoding handle argument "
"(h=%d, %s)\n", op, h, strerror (w->status));
return -1;
}
return h;
}
/* Convert a number of bits to an 8-bit bitmask */
static unsigned int cidrtomask[9] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0,
0xF8, 0xFC, 0xFE, 0xFF };
#ifdef SANED_USES_AF_INDEP
static SANE_Bool
check_v4_in_range (struct sockaddr_in *sin, char *base_ip, char *netmask)
{
int cidr;
int i, err;
char *end;
uint32_t mask;
struct sockaddr_in *base;
struct addrinfo hints;
struct addrinfo *res;
SANE_Bool ret = SANE_FALSE;
cidr = -1;
cidr = strtol (netmask, &end, 10);
/* Sanity check on the cidr value */
if ((cidr < 0) || (cidr > 32) || (end == netmask))
{
DBG (DBG_ERR, "check_v4_in_range: invalid CIDR value (%s) !\n", netmask);
return SANE_FALSE;
}
mask = 0;
cidr -= 8;
/* Build a bitmask out of the CIDR value */
for (i = 3; cidr >= 0; i--)
{
mask |= (0xff << (8 * i));
cidr -= 8;
}
if (cidr < 0)
mask |= (cidrtomask[cidr + 8] << (8 * i));
mask = htonl (mask);
/* get a sockaddr_in representing the base IP address */
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_INET;
err = getaddrinfo (base_ip, NULL, &hints, &res);
if (err)
{
DBG (DBG_DBG, "check_v4_in_range: getaddrinfo() failed: %s\n", gai_strerror (err));
return SANE_FALSE;
}
base = (struct sockaddr_in *) res->ai_addr;
/*
* Check that the address belongs to the specified subnet, using the bitmask.
* The address is represented by a 32bit integer.
*/
if ((base->sin_addr.s_addr & mask) == (sin->sin_addr.s_addr & mask))
ret = SANE_TRUE;
freeaddrinfo (res);
return ret;
}
# ifdef ENABLE_IPV6
static SANE_Bool
check_v6_in_range (struct sockaddr_in6 *sin6, char *base_ip, char *netmask)
{
int cidr;
int i, err;
unsigned int mask[16];
char *end;
struct sockaddr_in6 *base;
struct addrinfo hints;
struct addrinfo *res;
SANE_Bool ret = SANE_TRUE;
cidr = -1;
cidr = strtol (netmask, &end, 10);
/* Sanity check on the cidr value */
if ((cidr < 0) || (cidr > 128) || (end == netmask))
{
DBG (DBG_ERR, "check_v6_in_range: invalid CIDR value (%s) !\n", netmask);
return SANE_FALSE;
}
memset (mask, 0, (16 * sizeof (unsigned int)));
cidr -= 8;
/* Build a bitmask out of the CIDR value */
for (i = 0; cidr >= 0; i++)
{
mask[i] = 0xff;
cidr -= 8;
}
if (cidr < 0)
mask[i] = cidrtomask[cidr + 8];
/* get a sockaddr_in6 representing the base IP address */
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_INET6;
err = getaddrinfo (base_ip, NULL, &hints, &res);
if (err)
{
DBG (DBG_DBG, "check_v6_in_range: getaddrinfo() failed: %s\n", gai_strerror (err));
return SANE_FALSE;
}
base = (struct sockaddr_in6 *) res->ai_addr;
/*
* Check that the address belongs to the specified subnet.
* The address is reprensented by an array of 16 8bit integers.
*/
for (i = 0; i < 16; i++)
{
if ((base->sin6_addr.s6_addr[i] & mask[i]) != (sin6->sin6_addr.s6_addr[i] & mask[i]))
{
ret = SANE_FALSE;
break;
}
}
freeaddrinfo (res);
return ret;
}
# endif /* ENABLE_IPV6 */
#else /* !SANED_USES_AF_INDEP */
static SANE_Bool
check_v4_in_range (struct in_addr *inaddr, struct in_addr *base, char *netmask)
{
int cidr;
int i;
char *end;
uint32_t mask;
SANE_Bool ret = SANE_FALSE;
cidr = -1;
cidr = strtol (netmask, &end, 10);
/* sanity check on the cidr value */
if ((cidr < 0) || (cidr > 32) || (end == netmask))
{
DBG (DBG_ERR, "check_v4_in_range: invalid CIDR value (%s) !\n", netmask);
return SANE_FALSE;
}
mask = 0;
cidr -= 8;
/* Build a bitmask out of the CIDR value */
for (i = 3; cidr >= 0; i--)
{
mask |= (0xff << (8 * i));
cidr -= 8;
}
if (cidr < 0)
mask |= (cidrtomask[cidr + 8] << (8 * i));
mask = htonl (mask);
/*
* Check that the address belongs to the specified subnet, using the bitmask.
* The address is represented by a 32bit integer.
*/
if ((base->s_addr & mask) == (inaddr->s_addr & mask))
ret = SANE_TRUE;
return ret;
}
#endif /* SANED_USES_AF_INDEP */
/* Access control */
#ifdef SANED_USES_AF_INDEP
static SANE_Status
check_host (int fd)
{
struct sockaddr_in *sin = NULL;
#ifdef ENABLE_IPV6
struct sockaddr_in6 *sin6;
#endif /* ENABLE_IPV6 */
struct addrinfo hints;
struct addrinfo *res;
struct addrinfo *resp;
int j, access_ok = 0;
int err;
char text_addr[64];
#ifdef ENABLE_IPV6
SANE_Bool IPv4map = SANE_FALSE;
char *remote_ipv4 = NULL; /* in case we have an IPv4-mapped address (eg ::ffff:127.0.0.1) */
char *tmp;
struct addrinfo *remote_ipv4_addr = NULL;
#endif /* ENABLE_IPV6 */
char config_line_buf[1024];
char *config_line;
char *netmask;
char hostname[MAXHOSTNAMELEN];
int len;
FILE *fp;
/* Get address of remote host */
remote_address_len = sizeof (remote_address.ss);
if (getpeername (fd, &remote_address.sa, (socklen_t *) &remote_address_len) < 0)
{
DBG (DBG_ERR, "check_host: getpeername failed: %s\n", strerror (errno));
remote_ip = strdup ("[error]");
return SANE_STATUS_INVAL;
}
err = getnameinfo (&remote_address.sa, remote_address_len,
hostname, sizeof (hostname), NULL, 0, NI_NUMERICHOST);
if (err)
{
DBG (DBG_DBG, "check_host: getnameinfo failed: %s\n", gai_strerror(err));
remote_ip = strdup ("[error]");
return SANE_STATUS_INVAL;
}
else
remote_ip = strdup (hostname);
#ifdef ENABLE_IPV6
sin6 = &remote_address.sin6;
if (IN6_IS_ADDR_V4MAPPED ((struct in6_addr *)sin6->sin6_addr.s6_addr))
{
DBG (DBG_DBG, "check_host: detected an IPv4-mapped address\n");
remote_ipv4 = remote_ip + 7;
IPv4map = SANE_TRUE;
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_INET;
err = getaddrinfo (remote_ipv4, NULL, &hints, &res);
if (err)
{
DBG (DBG_DBG, "check_host: getaddrinfo() failed: %s\n", gai_strerror (err));
IPv4map = SANE_FALSE; /* we failed, remote_ipv4_addr points to nothing */
}
else
{
remote_ipv4_addr = res;
sin = (struct sockaddr_in *)res->ai_addr;
}
}
#endif /* ENABLE_IPV6 */
DBG (DBG_WARN, "check_host: access by remote host: %s\n", remote_ip);
/* Always allow access from local host. Do it here to avoid DNS lookups
and reading saned.conf. */
#ifdef ENABLE_IPV6
if (IPv4map == SANE_TRUE)
{
if (IN_LOOPBACK (ntohl (sin->sin_addr.s_addr)))
{
DBG (DBG_MSG,
"check_host: remote host is IN_LOOPBACK: access granted\n");
freeaddrinfo (remote_ipv4_addr);
return SANE_STATUS_GOOD;
}
freeaddrinfo (remote_ipv4_addr);
}
#endif /* ENABLE_IPV6 */
sin = &remote_address.sin;
switch (SS_FAMILY(remote_address.ss))
{
case AF_INET:
if (IN_LOOPBACK (ntohl (sin->sin_addr.s_addr)))
{
DBG (DBG_MSG,
"check_host: remote host is IN_LOOPBACK: access granted\n");
return SANE_STATUS_GOOD;
}
break;
#ifdef ENABLE_IPV6
case AF_INET6:
if (IN6_IS_ADDR_LOOPBACK ((struct in6_addr *)sin6->sin6_addr.s6_addr))
{
DBG (DBG_MSG,
"check_host: remote host is IN6_LOOPBACK: access granted\n");
return SANE_STATUS_GOOD;
}
break;
#endif /* ENABLE_IPV6 */
default:
break;
}
DBG (DBG_DBG, "check_host: remote host is not IN_LOOPBACK"
#ifdef ENABLE_IPV6
" nor IN6_LOOPBACK"
#endif /* ENABLE_IPV6 */
"\n");
/* Get name of local host */
if (gethostname (hostname, sizeof (hostname)) < 0)
{
DBG (DBG_ERR, "check_host: gethostname failed: %s\n", strerror (errno));
return SANE_STATUS_INVAL;
}
DBG (DBG_DBG, "check_host: local hostname: %s\n", hostname);
/* Get local addresses */
memset (&hints, 0, sizeof (hints));
hints.ai_flags = AI_CANONNAME;
#ifdef ENABLE_IPV6
hints.ai_family = PF_UNSPEC;
#else
hints.ai_family = PF_INET;
#endif /* ENABLE_IPV6 */
err = getaddrinfo (hostname, NULL, &hints, &res);
if (err)
{
DBG (DBG_ERR, "check_host: getaddrinfo for local hostname failed: %s\n",
gai_strerror (err));
/* Proceed even if the local hostname does not resolve */
if (err != EAI_NONAME)
return SANE_STATUS_INVAL;
}
else
{
for (resp = res; resp != NULL; resp = resp->ai_next)
{
DBG (DBG_DBG, "check_host: local hostname(s) (from DNS): %s\n",
resp->ai_canonname);
err = getnameinfo (resp->ai_addr, resp->ai_addrlen, text_addr,
sizeof (text_addr), NULL, 0, NI_NUMERICHOST);
if (err)
strncpy (text_addr, "[error]", 8);
#ifdef ENABLE_IPV6
if ((strcasecmp (text_addr, remote_ip) == 0) ||
((IPv4map == SANE_TRUE) && (strcmp (text_addr, remote_ipv4) == 0)))
#else
if (strcmp (text_addr, remote_ip) == 0)
#endif /* ENABLE_IPV6 */
{
DBG (DBG_MSG, "check_host: remote host has same addr as local: access granted\n");
freeaddrinfo (res);
res = NULL;
return SANE_STATUS_GOOD;
}
}
freeaddrinfo (res);
res = NULL;
DBG (DBG_DBG,
"check_host: remote host doesn't have same addr as local\n");
}
/* must be a remote host: check contents of PATH_NET_CONFIG or
/etc/hosts.equiv if former doesn't exist: */
for (j = 0; j < NELEMS (config_file_names); ++j)
{
DBG (DBG_DBG, "check_host: opening config file: %s\n",
config_file_names[j]);
if (config_file_names[j][0] == '/')
fp = fopen (config_file_names[j], "r");
else
fp = sanei_config_open (config_file_names[j]);
if (!fp)
{
DBG (DBG_MSG,
"check_host: can't open config file: %s (%s)\n",
config_file_names[j], strerror (errno));
continue;
}
while (!access_ok && sanei_config_read (config_line_buf,
sizeof (config_line_buf), fp))
{
config_line = config_line_buf; /* from now on, use a pointer */
DBG (DBG_DBG, "check_host: config file line: `%s'\n", config_line);
if (config_line[0] == '#')
continue; /* ignore comments */
if (strchr (config_line, '='))
continue; /* ignore lines with an = sign */
len = strlen (config_line);
if (!len)
continue; /* ignore empty lines */
/* look for a subnet specification */
netmask = strchr (config_line, '/');
if (netmask != NULL)
{
*netmask = '\0';
netmask++;
DBG (DBG_DBG, "check_host: subnet with base IP = %s, CIDR netmask = %s\n",
config_line, netmask);
}
#ifdef ENABLE_IPV6
/* IPv6 addresses are enclosed in [] */
if (*config_line == '[')
{
config_line++;
tmp = strchr (config_line, ']');
if (tmp == NULL)
{
DBG (DBG_ERR,
"check_host: malformed IPv6 address in config file, skipping: [%s\n",
config_line);
continue;
}
*tmp = '\0';
}
#endif /* ENABLE_IPV6 */
if (strcmp (config_line, "+") == 0)
{
access_ok = 1;
DBG (DBG_DBG,
"check_host: access granted from any host (`+')\n");
}
/* compare remote_ip (remote IP address) to the config_line */
else if (strcasecmp (config_line, remote_ip) == 0)
{
access_ok = 1;
DBG (DBG_DBG,
"check_host: access granted from IP address %s\n", remote_ip);
}
#ifdef ENABLE_IPV6
else if ((IPv4map == SANE_TRUE) && (strcmp (config_line, remote_ipv4) == 0))
{
access_ok = 1;
DBG (DBG_DBG,
"check_host: access granted from IP address %s (IPv4-mapped)\n", remote_ip);
}
/* handle IP ranges, take care of the IPv4map stuff */
else if (netmask != NULL)
{
if (strchr (config_line, ':') != NULL) /* is a v6 address */
{
if (SS_FAMILY(remote_address.ss) == AF_INET6)
{
if (check_v6_in_range (sin6, config_line, netmask))
{
access_ok = 1;
DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet [%s]/%s)\n",
remote_ip, config_line, netmask);
}
}
}
else /* is a v4 address */
{
if (IPv4map == SANE_TRUE)
{
/* get a sockaddr_in representing the v4-mapped IP address */
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_INET;
err = getaddrinfo (remote_ipv4, NULL, &hints, &res);
if (err)
DBG (DBG_DBG, "check_host: getaddrinfo() failed: %s\n", gai_strerror (err));
else
sin = (struct sockaddr_in *)res->ai_addr;
}
if ((SS_FAMILY(remote_address.ss) == AF_INET) ||
(IPv4map == SANE_TRUE))
{
if (check_v4_in_range (sin, config_line, netmask))
{
DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet %s/%s)\n",
((IPv4map == SANE_TRUE) ? remote_ipv4 : remote_ip), config_line, netmask);
access_ok = 1;
}
else
{
/* restore the old sin pointer */
sin = &remote_address.sin;
}
if (res != NULL)
{
freeaddrinfo (res);
res = NULL;
}
}
}
}
#else /* !ENABLE_IPV6 */
/* handle IP ranges */
else if (netmask != NULL)
{
if (check_v4_in_range (sin, config_line, netmask))
{
access_ok = 1;
DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet %s/%s)\n",
remote_ip, config_line, netmask);
}
}
#endif /* ENABLE_IPV6 */
else
{
memset (&hints, 0, sizeof (hints));
hints.ai_flags = AI_CANONNAME;
#ifdef ENABLE_IPV6
hints.ai_family = PF_UNSPEC;
#else
hints.ai_family = PF_INET;
#endif /* ENABLE_IPV6 */
err = getaddrinfo (config_line, NULL, &hints, &res);
if (err)
{
DBG (DBG_DBG,
"check_host: getaddrinfo for `%s' failed: %s\n",
config_line, gai_strerror (err));
DBG (DBG_MSG, "check_host: entry isn't an IP address "
"and can't be found in DNS\n");
continue;
}
else
{
for (resp = res; resp != NULL; resp = resp->ai_next)
{
err = getnameinfo (resp->ai_addr, resp->ai_addrlen, text_addr,
sizeof (text_addr), NULL, 0, NI_NUMERICHOST);
if (err)
strncpy (text_addr, "[error]", 8);
DBG (DBG_MSG,
"check_host: DNS lookup returns IP address: %s\n",
text_addr);
#ifdef ENABLE_IPV6
if ((strcasecmp (text_addr, remote_ip) == 0) ||
((IPv4map == SANE_TRUE) && (strcmp (text_addr, remote_ipv4) == 0)))
#else
if (strcmp (text_addr, remote_ip) == 0)
#endif /* ENABLE_IPV6 */
access_ok = 1;
if (access_ok)
break;
}
freeaddrinfo (res);
res = NULL;
}
}
}
fclose (fp);
}
if (access_ok)
return SANE_STATUS_GOOD;
return SANE_STATUS_ACCESS_DENIED;
}
#else /* !SANED_USES_AF_INDEP */
static SANE_Status
check_host (int fd)
{
struct sockaddr_in sin;
int j, access_ok = 0;
struct hostent *he;
char text_addr[64];
char config_line_buf[1024];
char *config_line;
char *netmask;
char hostname[MAXHOSTNAMELEN];
char *r_hostname;
static struct in_addr config_line_address;
int len;
FILE *fp;
/* Get address of remote host */
len = sizeof (sin);
if (getpeername (fd, (struct sockaddr *) &sin, (socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "check_host: getpeername failed: %s\n", strerror (errno));
remote_ip = strdup ("[error]");
return SANE_STATUS_INVAL;
}
r_hostname = inet_ntoa (sin.sin_addr);
remote_ip = strdup (r_hostname);
DBG (DBG_WARN, "check_host: access by remote host: %s\n",
remote_ip);
/* Save remote address for check of control and data connections */
memcpy (&remote_address, &sin.sin_addr, sizeof (remote_address));
/* Always allow access from local host. Do it here to avoid DNS lookups
and reading saned.conf. */
if (IN_LOOPBACK (ntohl (sin.sin_addr.s_addr)))
{
DBG (DBG_MSG,
"check_host: remote host is IN_LOOPBACK: access accepted\n");
return SANE_STATUS_GOOD;
}
DBG (DBG_DBG, "check_host: remote host is not IN_LOOPBACK\n");
/* Get name of local host */
if (gethostname (hostname, sizeof (hostname)) < 0)
{
DBG (DBG_ERR, "check_host: gethostname failed: %s\n", strerror (errno));
return SANE_STATUS_INVAL;
}
DBG (DBG_DBG, "check_host: local hostname: %s\n", hostname);
/* Get local address */
he = gethostbyname (hostname);
if (!he)
{
DBG (DBG_ERR, "check_host: gethostbyname for local hostname failed: %s\n",
hstrerror (h_errno));
/* Proceed even if the local hostname doesn't resolve */
if (h_errno != HOST_NOT_FOUND)
return SANE_STATUS_INVAL;
}
else
{
DBG (DBG_DBG, "check_host: local hostname (from DNS): %s\n",
he->h_name);
if ((he->h_length == 4) || (he->h_addrtype == AF_INET))
{
if (!inet_ntop (he->h_addrtype, he->h_addr_list[0], text_addr,
sizeof (text_addr)))
strcpy (text_addr, "[error]");
DBG (DBG_DBG, "check_host: local host address (from DNS): %s\n",
text_addr);
if (memcmp (he->h_addr_list[0], &remote_address.s_addr, 4) == 0)
{
DBG (DBG_MSG,
"check_host: remote host has same addr as local: "
"access accepted\n");
return SANE_STATUS_GOOD;
}
}
else
{
DBG (DBG_ERR, "check_host: can't get local address "
"(only IPv4 is supported)\n");
}
DBG (DBG_DBG,
"check_host: remote host doesn't have same addr as local\n");
}
/* must be a remote host: check contents of PATH_NET_CONFIG or
/etc/hosts.equiv if former doesn't exist: */
for (j = 0; j < NELEMS (config_file_names); ++j)
{
DBG (DBG_DBG, "check_host: opening config file: %s\n",
config_file_names[j]);
if (config_file_names[j][0] == '/')
fp = fopen (config_file_names[j], "r");
else
fp = sanei_config_open (config_file_names[j]);
if (!fp)
{
DBG (DBG_MSG,
"check_host: can't open config file: %s (%s)\n",
config_file_names[j], strerror (errno));
continue;
}
while (!access_ok && sanei_config_read (config_line_buf,
sizeof (config_line_buf), fp))
{
config_line = config_line_buf; /* from now on, use a pointer */
DBG (DBG_DBG, "check_host: config file line: `%s'\n", config_line);
if (config_line[0] == '#')
continue; /* ignore comments */
if (strchr (config_line, '='))
continue; /* ignore lines with an = sign */
len = strlen (config_line);
if (!len)
continue; /* ignore empty lines */
/* look for a subnet specification */
netmask = strchr (config_line, '/');
if (netmask != NULL)
{
*netmask = '\0';
netmask++;
DBG (DBG_DBG, "check_host: subnet with base IP = %s, CIDR netmask = %s\n",
config_line, netmask);
}
if (strcmp (config_line, "+") == 0)
{
access_ok = 1;
DBG (DBG_DBG,
"check_host: access accepted from any host (`+')\n");
}
else
{
if (inet_pton (AF_INET, config_line, &config_line_address) > 0)
{
if (memcmp (&remote_address.s_addr,
&config_line_address.s_addr, 4) == 0)
access_ok = 1;
else if (netmask != NULL)
{
if (check_v4_in_range (&remote_address, &config_line_address, netmask))
{
access_ok = 1;
DBG (DBG_DBG, "check_host: access granted from IP address %s (in subnet %s/%s)\n",
remote_ip, config_line, netmask);
}
}
}
else
{
DBG (DBG_DBG,
"check_host: inet_pton for `%s' failed\n",
config_line);
he = gethostbyname (config_line);
if (!he)
{
DBG (DBG_DBG,
"check_host: gethostbyname for `%s' failed: %s\n",
config_line, hstrerror (h_errno));
DBG (DBG_MSG, "check_host: entry isn't an IP address "
"and can't be found in DNS\n");
continue;
}
if (!inet_ntop (he->h_addrtype, he->h_addr_list[0],
text_addr, sizeof (text_addr)))
strcpy (text_addr, "[error]");
DBG (DBG_MSG,
"check_host: DNS lookup returns IP address: %s\n",
text_addr);
if (memcmp (&remote_address.s_addr,
he->h_addr_list[0], 4) == 0)
access_ok = 1;
}
}
}
fclose (fp);
if (access_ok)
return SANE_STATUS_GOOD;
}
return SANE_STATUS_ACCESS_DENIED;
}
#endif /* SANED_USES_AF_INDEP */
static int
init (Wire * w)
{
SANE_Word word, be_version_code;
SANE_Init_Reply reply;
SANE_Status status;
SANE_Init_Req req;
reset_watchdog ();
status = check_host (w->io.fd);
if (status != SANE_STATUS_GOOD)
{
DBG (DBG_WARN, "init: access by host %s denied\n", remote_ip);
return -1;
}
else
DBG (DBG_MSG, "init: access granted\n");
sanei_w_set_dir (w, WIRE_DECODE);
if (w->status)
{
DBG (DBG_ERR, "init: bad status after sanei_w_set_dir: %d\n", w->status);
return -1;
}
sanei_w_word (w, &word); /* decode procedure number */
if (w->status || word != SANE_NET_INIT)
{
DBG (DBG_ERR, "init: bad status=%d or procnum=%d\n",
w->status, word);
return -1;
}
sanei_w_init_req (w, &req);
if (w->status)
{
DBG (DBG_ERR, "init: bad status after sanei_w_init_req: %d\n", w->status);
return -1;
}
w->version = SANEI_NET_PROTOCOL_VERSION;
if (req.username)
default_username = strdup (req.username);
sanei_w_free (w, (WireCodecFunc) sanei_w_init_req, &req);
if (w->status)
{
DBG (DBG_ERR, "init: bad status after sanei_w_free: %d\n", w->status);
return -1;
}
reply.version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR,
SANEI_NET_PROTOCOL_VERSION);
DBG (DBG_WARN, "init: access granted to %s@%s\n",
default_username, remote_ip);
if (status == SANE_STATUS_GOOD)
{
status = sane_init (&be_version_code, auth_callback);
if (status != SANE_STATUS_GOOD)
DBG (DBG_ERR, "init: failed to initialize backend (%s)\n",
sane_strstatus (status));
if (SANE_VERSION_MAJOR (be_version_code) != V_MAJOR)
{
DBG (DBG_ERR,
"init: unexpected backend major version %d (expected %d)\n",
SANE_VERSION_MAJOR (be_version_code), V_MAJOR);
status = SANE_STATUS_INVAL;
}
}
reply.status = status;
if (status != SANE_STATUS_GOOD)
reply.version_code = 0;
sanei_w_reply (w, (WireCodecFunc) sanei_w_init_reply, &reply);
if (w->status || status != SANE_STATUS_GOOD)
return -1;
return 0;
}
#ifdef SANED_USES_AF_INDEP
static int
start_scan (Wire * w, int h, SANE_Start_Reply * reply)
{
union {
struct sockaddr_storage ss;
struct sockaddr sa;
struct sockaddr_in sin;
#ifdef ENABLE_IPV6
struct sockaddr_in6 sin6;
#endif /* ENABLE_IPV6 */
} data_addr;
struct sockaddr_in *sin;
#ifdef ENABLE_IPV6
struct sockaddr_in6 *sin6;
#endif /* ENABLE_IPV6 */
SANE_Handle be_handle;
int fd, len;
in_port_t data_port;
int ret = -1;
be_handle = handle[h].handle;
len = sizeof (data_addr.ss);
if (getsockname (w->io.fd, &data_addr.sa, (socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
fd = socket (SS_FAMILY(data_addr.ss), SOCK_STREAM, 0);
if (fd < 0)
{
DBG (DBG_ERR, "start_scan: failed to obtain data socket (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
switch (SS_FAMILY(data_addr.ss))
{
case AF_INET:
sin = &data_addr.sin;
break;
#ifdef ENABLE_IPV6
case AF_INET6:
sin6 = &data_addr.sin6;
break;
#endif /* ENABLE_IPV6 */
default:
break;
}
/* Try to bind a port between data_port_lo and data_port_hi for the data connection */
for (data_port = data_port_lo; data_port <= data_port_hi; data_port++)
{
switch (SS_FAMILY(data_addr.ss))
{
case AF_INET:
sin->sin_port = htons(data_port);
break;
#ifdef ENABLE_IPV6
case AF_INET6:
sin6->sin6_port = htons(data_port);
break;
#endif /* ENABLE_IPV6 */
default:
break;
}
DBG (DBG_INFO, "start_scan: trying to bind data port %d\n", data_port);
ret = bind (fd, &data_addr.sa, len);
if (ret == 0)
break;
}
if (ret < 0)
{
DBG (DBG_ERR, "start_scan: failed to bind address (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
if (listen (fd, 1) < 0)
{
DBG (DBG_ERR, "start_scan: failed to make socket listen (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
if (getsockname (fd, &data_addr.sa, (socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
switch (SS_FAMILY(data_addr.ss))
{
case AF_INET:
sin = &data_addr.sin;
reply->port = ntohs (sin->sin_port);
break;
#ifdef ENABLE_IPV6
case AF_INET6:
sin6 = &data_addr.sin6;
reply->port = ntohs (sin6->sin6_port);
break;
#endif /* ENABLE_IPV6 */
default:
break;
}
DBG (DBG_MSG, "start_scan: using port %d for data\n", reply->port);
reply->status = sane_start (be_handle);
if (reply->status == SANE_STATUS_GOOD)
{
handle[h].scanning = 1;
handle[h].docancel = 0;
}
return fd;
}
#else /* !SANED_USES_AF_INDEP */
static int
start_scan (Wire * w, int h, SANE_Start_Reply * reply)
{
struct sockaddr_in sin;
SANE_Handle be_handle;
int fd, len;
in_port_t data_port;
int ret;
be_handle = handle[h].handle;
len = sizeof (sin);
if (getsockname (w->io.fd, (struct sockaddr *) &sin, (socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
fd = socket (AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
DBG (DBG_ERR, "start_scan: failed to obtain data socket (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
/* Try to bind a port between data_port_lo and data_port_hi for the data connection */
for (data_port = data_port_lo; data_port <= data_port_hi; data_port++)
{
sin.sin_port = htons(data_port);
DBG(DBG_INFO, "start_scan: trying to bind data port %d\n", data_port);
ret = bind (fd, (struct sockaddr *) &sin, len);
if (ret == 0)
break;
}
if (ret < 0)
{
DBG (DBG_ERR, "start_scan: failed to bind address (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
if (listen (fd, 1) < 0)
{
DBG (DBG_ERR, "start_scan: failed to make socket listen (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
if (getsockname (fd, (struct sockaddr *) &sin, (socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "start_scan: failed to obtain socket address (%s)\n",
strerror (errno));
reply->status = SANE_STATUS_IO_ERROR;
return -1;
}
reply->port = ntohs (sin.sin_port);
DBG (DBG_MSG, "start_scan: using port %d for data\n", reply->port);
reply->status = sane_start (be_handle);
if (reply->status == SANE_STATUS_GOOD)
{
handle[h].scanning = 1;
handle[h].docancel = 0;
}
return fd;
}
#endif /* SANED_USES_AF_INDEP */
static int
store_reclen (SANE_Byte * buf, size_t buf_size, int i, size_t reclen)
{
buf[i++] = (reclen >> 24) & 0xff;
if (i >= (int) buf_size)
i = 0;
buf[i++] = (reclen >> 16) & 0xff;
if (i >= (int) buf_size)
i = 0;
buf[i++] = (reclen >> 8) & 0xff;
if (i >= (int) buf_size)
i = 0;
buf[i++] = (reclen >> 0) & 0xff;
if (i >= (int) buf_size)
i = 0;
return i;
}
static void
do_scan (Wire * w, int h, int data_fd)
{
int num_fds, be_fd = -1, reader, writer, bytes_in_buf, status_dirty = 0;
SANE_Handle be_handle = handle[h].handle;
struct timeval tv, *timeout = 0;
fd_set rd_set, rd_mask, wr_set, wr_mask;
SANE_Byte buf[8192];
SANE_Status status;
long int nwritten;
SANE_Int length;
size_t nbytes;
DBG (3, "do_scan: start\n");
FD_ZERO (&rd_mask);
FD_SET (w->io.fd, &rd_mask);
num_fds = w->io.fd + 1;
FD_ZERO (&wr_mask);
FD_SET (data_fd, &wr_mask);
if (data_fd >= num_fds)
num_fds = data_fd + 1;
sane_set_io_mode (be_handle, SANE_TRUE);
if (sane_get_select_fd (be_handle, &be_fd) == SANE_STATUS_GOOD)
{
FD_SET (be_fd, &rd_mask);
if (be_fd >= num_fds)
num_fds = be_fd + 1;
}
else
{
memset (&tv, 0, sizeof (tv));
timeout = &tv;
}
status = SANE_STATUS_GOOD;
reader = writer = bytes_in_buf = 0;
do
{
rd_set = rd_mask;
wr_set = wr_mask;
if (select (num_fds, &rd_set, &wr_set, 0, timeout) < 0)
{
if (be_fd >= 0 && errno == EBADF)
{
/* This normally happens when a backend closes a select
filedescriptor when reaching the end of file. So
pass back this status to the client: */
FD_CLR (be_fd, &rd_mask);
be_fd = -1;
/* only set status_dirty if EOF hasn't been already detected */
if (status == SANE_STATUS_GOOD)
status_dirty = 1;
status = SANE_STATUS_EOF;
DBG (DBG_INFO, "do_scan: select_fd was closed --> EOF\n");
continue;
}
else
{
status = SANE_STATUS_IO_ERROR;
DBG (DBG_ERR, "do_scan: select failed (%s)\n", strerror (errno));
break;
}
}
if (bytes_in_buf)
{
if (FD_ISSET (data_fd, &wr_set))
{
if (bytes_in_buf > 0)
{
/* write more input data */
nbytes = bytes_in_buf;
if (writer + nbytes > sizeof (buf))
nbytes = sizeof (buf) - writer;
DBG (DBG_INFO,
"do_scan: trying to write %d bytes to client\n",
nbytes);
nwritten = write (data_fd, buf + writer, nbytes);
DBG (DBG_INFO,
"do_scan: wrote %ld bytes to client\n", nwritten);
if (nwritten < 0)
{
DBG (DBG_ERR, "do_scan: write failed (%s)\n",
strerror (errno));
status = SANE_STATUS_CANCELLED;
handle[h].docancel = 1;
break;
}
bytes_in_buf -= nwritten;
writer += nwritten;
if (writer == sizeof (buf))
writer = 0;
}
}
}
else if (status == SANE_STATUS_GOOD
&& (timeout || FD_ISSET (be_fd, &rd_set)))
{
int i;
/* get more input data */
/* reserve 4 bytes to store the length of the data record: */
i = reader;
reader += 4;
if (reader >= (int) sizeof (buf))
reader -= sizeof(buf);
assert (bytes_in_buf == 0);
nbytes = sizeof (buf) - 4;
if (reader + nbytes > sizeof (buf))
nbytes = sizeof (buf) - reader;
DBG (DBG_INFO,
"do_scan: trying to read %d bytes from scanner\n", nbytes);
status = sane_read (be_handle, buf + reader, nbytes, &length);
DBG (DBG_INFO,
"do_scan: read %d bytes from scanner\n", length);
reset_watchdog ();
reader += length;
if (reader >= (int) sizeof (buf))
reader = 0;
bytes_in_buf += length + 4;
if (status != SANE_STATUS_GOOD)
{
reader = i; /* restore reader index */
status_dirty = 1;
DBG (DBG_MSG,
"do_scan: status = `%s'\n", sane_strstatus(status));
}
else
store_reclen (buf, sizeof (buf), i, length);
}
if (status_dirty && sizeof (buf) - bytes_in_buf >= 5)
{
status_dirty = 0;
reader = store_reclen (buf, sizeof (buf), reader, 0xffffffff);
buf[reader] = status;
bytes_in_buf += 5;
DBG (DBG_MSG, "do_scan: statuscode `%s' was added to buffer\n",
sane_strstatus(status));
}
if (FD_ISSET (w->io.fd, &rd_set))
{
DBG (DBG_MSG,
"do_scan: processing RPC request on fd %d\n", w->io.fd);
if(process_request (w) < 0)
handle[h].docancel = 1;
if (handle[h].docancel)
break;
}
}
while (status == SANE_STATUS_GOOD || bytes_in_buf > 0 || status_dirty);
DBG (DBG_MSG, "do_scan: done, status=%s\n", sane_strstatus (status));
if(handle[h].docancel)
sane_cancel (handle[h].handle);
handle[h].docancel = 0;
handle[h].scanning = 0;
}
static int
process_request (Wire * w)
{
SANE_Handle be_handle;
SANE_Word h, word;
int i;
DBG (DBG_DBG, "process_request: waiting for request\n");
sanei_w_set_dir (w, WIRE_DECODE);
sanei_w_word (w, &word); /* decode procedure number */
if (w->status)
{
DBG (DBG_ERR,
"process_request: bad status %d\n", w->status);
return -1;
}
current_request = word;
DBG (DBG_MSG, "process_request: got request %d\n", current_request);
switch (current_request)
{
case SANE_NET_GET_DEVICES:
{
SANE_Get_Devices_Reply reply;
reply.status =
sane_get_devices ((const SANE_Device ***) &reply.device_list,
SANE_TRUE);
sanei_w_reply (w, (WireCodecFunc) sanei_w_get_devices_reply, &reply);
}
break;
case SANE_NET_OPEN:
{
SANE_Open_Reply reply;
SANE_Handle be_handle;
SANE_String name, resource;
sanei_w_string (w, &name);
if (w->status)
{
DBG (DBG_ERR,
"process_request: (open) error while decoding args (%s)\n",
strerror (w->status));
return 1;
}
if (!name)
{
DBG (DBG_ERR, "process_request: (open) device_name == NULL\n");
reply.status = SANE_STATUS_INVAL;
sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
return 1;
}
can_authorize = 1;
resource = strdup (name);
if (strlen(resource) == 0) {
const SANE_Device **device_list;
DBG(DBG_DBG, "process_request: (open) strlen(resource) == 0\n");
free (resource);
if ((i = sane_get_devices (&device_list, SANE_TRUE)) !=
SANE_STATUS_GOOD)
{
DBG(DBG_ERR, "process_request: (open) sane_get_devices failed\n");
memset (&reply, 0, sizeof (reply));
reply.status = i;
sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
break;
}
if ((device_list == NULL) || (device_list[0] == NULL))
{
DBG(DBG_ERR, "process_request: (open) device_list[0] == 0\n");
memset (&reply, 0, sizeof (reply));
reply.status = SANE_STATUS_INVAL;
sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
break;
}
resource = strdup (device_list[0]->name);
}
if (strchr (resource, ':'))
*(strchr (resource, ':')) = 0;
if (sanei_authorize (resource, "saned", auth_callback) !=
SANE_STATUS_GOOD)
{
DBG (DBG_ERR, "process_request: access to resource `%s' denied\n",
resource);
free (resource);
memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
reply.status = SANE_STATUS_ACCESS_DENIED;
}
else
{
DBG (DBG_MSG, "process_request: access to resource `%s' granted\n",
resource);
free (resource);
memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
reply.status = sane_open (name, &be_handle);
DBG (DBG_MSG, "process_request: sane_open returned: %s\n",
sane_strstatus (reply.status));
}
if (reply.status == SANE_STATUS_GOOD)
{
h = get_free_handle ();
if (h < 0)
reply.status = SANE_STATUS_NO_MEM;
else
{
handle[h].handle = be_handle;
reply.handle = h;
}
}
can_authorize = 0;
sanei_w_reply (w, (WireCodecFunc) sanei_w_open_reply, &reply);
sanei_w_free (w, (WireCodecFunc) sanei_w_string, &name);
}
break;
case SANE_NET_CLOSE:
{
SANE_Word ack = 0;
h = decode_handle (w, "close");
close_handle (h);
sanei_w_reply (w, (WireCodecFunc) sanei_w_word, &ack);
}
break;
case SANE_NET_GET_OPTION_DESCRIPTORS:
{
SANE_Option_Descriptor_Array opt;
h = decode_handle (w, "get_option_descriptors");
if (h < 0)
return 1;
be_handle = handle[h].handle;
sane_control_option (be_handle, 0, SANE_ACTION_GET_VALUE,
&opt.num_options, 0);
opt.desc = malloc (opt.num_options * sizeof (opt.desc[0]));
for (i = 0; i < opt.num_options; ++i)
opt.desc[i] = (SANE_Option_Descriptor *)
sane_get_option_descriptor (be_handle, i);
sanei_w_reply (w,(WireCodecFunc) sanei_w_option_descriptor_array,
&opt);
free (opt.desc);
}
break;
case SANE_NET_CONTROL_OPTION:
{
SANE_Control_Option_Req req;
SANE_Control_Option_Reply reply;
sanei_w_control_option_req (w, &req);
if (w->status || (unsigned) req.handle >= (unsigned) num_handles
|| !handle[req.handle].inuse)
{
DBG (DBG_ERR,
"process_request: (control_option) "
"error while decoding args h=%d (%s)\n"
, req.handle, strerror (w->status));
return 1;
}
/* Addresses CVE-2017-6318 (#315576, Debian BTS #853804) */
/* This is done here (rather than in sanei/sanei_wire.c where
* it should be done) to minimize scope of impact and amount
* of code change.
*/
if (w->direction == WIRE_DECODE
&& req.value_type == SANE_TYPE_STRING
&& req.action == SANE_ACTION_GET_VALUE)
{
if (req.value)
{
/* FIXME: If req.value contains embedded NUL
* characters, this is wrong but we do not have
* access to the amount of memory allocated in
* sanei/sanei_wire.c at this point.
*/
w->allocated_memory -= (1 + strlen (req.value));
free (req.value);
}
req.value = malloc (req.value_size);
if (!req.value)
{
w->status = ENOMEM;
DBG (DBG_ERR,
"process_request: (control_option) "
"h=%d (%s)\n", req.handle, strerror (w->status));
return 1;
}
memset (req.value, 0, req.value_size);
w->allocated_memory += req.value_size;
}
can_authorize = 1;
memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
be_handle = handle[req.handle].handle;
reply.status = sane_control_option (be_handle, req.option,
req.action, req.value,
&reply.info);
reply.value_type = req.value_type;
reply.value_size = req.value_size;
reply.value = req.value;
can_authorize = 0;
sanei_w_reply (w, (WireCodecFunc) sanei_w_control_option_reply,
&reply);
sanei_w_free (w, (WireCodecFunc) sanei_w_control_option_req, &req);
}
break;
case SANE_NET_GET_PARAMETERS:
{
SANE_Get_Parameters_Reply reply;
h = decode_handle (w, "get_parameters");
if (h < 0)
return 1;
be_handle = handle[h].handle;
reply.status = sane_get_parameters (be_handle, &reply.params);
sanei_w_reply (w, (WireCodecFunc) sanei_w_get_parameters_reply,
&reply);
}
break;
case SANE_NET_START:
{
SANE_Start_Reply reply;
int fd = -1, data_fd = -1;
h = decode_handle (w, "start");
if (h < 0)
return 1;
memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */
reply.byte_order = SANE_NET_LITTLE_ENDIAN;
if (byte_order.w != 1)
reply.byte_order = SANE_NET_BIG_ENDIAN;
if (handle[h].scanning)
reply.status = SANE_STATUS_DEVICE_BUSY;
else
fd = start_scan (w, h, &reply);
sanei_w_reply (w, (WireCodecFunc) sanei_w_start_reply, &reply);
#ifdef SANED_USES_AF_INDEP
if (reply.status == SANE_STATUS_GOOD)
{
struct sockaddr_storage ss;
char text_addr[64];
int len;
int error;
struct pollfd fds[1];
int ret;
fds->fd = fd;
fds->events = POLLIN;
DBG (DBG_MSG, "process_request: waiting 4s for data connection\n");
if(data_connect_timeout)
{
while (1)
{
ret = poll (fds, 1, data_connect_timeout);
if (ret < 0)
{
if (errno == EINTR)
continue;
else
{
DBG (DBG_ERR, "run_standalone: poll failed: %s\n",
strerror (errno));
}
break;
}
break;
}
}
else
ret = 0;
if(ret >= 0)
data_fd = accept (fd, 0, 0);
close (fd);
/* Get address of remote host */
len = sizeof (ss);
if (getpeername (data_fd, (struct sockaddr *) &ss, (socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "process_request: getpeername failed: %s\n",
strerror (errno));
return 1;
}
error = getnameinfo ((struct sockaddr *) &ss, len, text_addr,
sizeof (text_addr), NULL, 0, NI_NUMERICHOST);
if (error)
{
DBG (DBG_ERR, "process_request: getnameinfo failed: %s\n",
gai_strerror (error));
return 1;
}
DBG (DBG_MSG, "process_request: access to data port from %s\n",
text_addr);
if (strcmp (text_addr, remote_ip) != 0)
{
DBG (DBG_ERR, "process_request: however, only %s is authorized\n",
text_addr);
DBG (DBG_ERR, "process_request: configuration problem or attack?\n");
close (data_fd);
data_fd = -1;
return -1;
}
#else /* !SANED_USES_AF_INDEP */
if (reply.status == SANE_STATUS_GOOD)
{
struct sockaddr_in sin;
int len;
int ret;
struct pollfd fds[1];
fds->fd = fd;
fds->events = POLLIN;
DBG (DBG_MSG, "process_request: waiting for data connection\n");
if(data_connect_timeout)
{
while (1)
{
ret = poll (fds, 1, data_connect_timeout);
if (ret < 0)
{
if (errno == EINTR)
continue;
else
{
DBG (DBG_ERR, "run_standalone: poll failed: %s\n", strerror (errno));
}
break;
}
break;
}
}
else
ret = 0;
if(ret >= 0)
data_fd = accept (fd, 0, 0);
close (fd);
/* Get address of remote host */
len = sizeof (sin);
if (getpeername (data_fd, (struct sockaddr *) &sin,
(socklen_t *) &len) < 0)
{
DBG (DBG_ERR, "process_request: getpeername failed: %s\n",
strerror (errno));
return 1;
}
if (memcmp (&remote_address, &sin.sin_addr,
sizeof (remote_address)) != 0)
{
DBG (DBG_ERR,
"process_request: access to data port from %s\n",
inet_ntoa (sin.sin_addr));
DBG (DBG_ERR,
"process_request: however, only %s is authorized\n",
inet_ntoa (remote_address));
DBG (DBG_ERR,
"process_request: configuration problem or attack?\n");
close (data_fd);
data_fd = -1;
return -1;
}
else
DBG (DBG_MSG, "process_request: access to data port from %s\n",
inet_ntoa (sin.sin_addr));
#endif /* SANED_USES_AF_INDEP */
if (data_fd < 0)
{
sane_cancel (handle[h].handle);
handle[h].scanning = 0;
handle[h].docancel = 0;
DBG (DBG_ERR, "process_request: accept failed! (%s)\n",
strerror (errno));
return 1;
}
fcntl (data_fd, F_SETFL, 1); /* set non-blocking */
shutdown (data_fd, 0);
do_scan (w, h, data_fd);
close (data_fd);
}
}
break;
case SANE_NET_CANCEL:
{
SANE_Word ack = 0;
h = decode_handle (w, "cancel");
if (h >= 0)
{
sane_cancel (handle[h].handle);
handle[h].docancel = 1;
}
sanei_w_reply (w, (WireCodecFunc) sanei_w_word, &ack);
}
break;
case SANE_NET_EXIT:
return -1;
break;
case SANE_NET_INIT:
case SANE_NET_AUTHORIZE:
default:
DBG (DBG_ERR,
"process_request: received unexpected procedure number %d\n",
current_request);
return -1;
}
return 0;
}
static int
wait_child (pid_t pid, int *status, int options)
{
struct saned_child *c;
struct saned_child *p = NULL;
int ret;
ret = waitpid(pid, status, options);
if (ret <= 0)
return ret;
#if WITH_AVAHI
if ((avahi_pid > 0) && (ret == avahi_pid))
{
avahi_pid = -1;
numchildren--;
return ret;
}
#endif /* WITH_AVAHI */
for (c = children; (c != NULL) && (c->next != NULL); p = c, c = c->next)
{
if (c->pid == ret)
{
if (c == children)
children = c->next;
else if (p != NULL)
p->next = c->next;
free(c);
numchildren--;
break;
}
}
return ret;
}
static int
add_child (pid_t pid)
{
struct saned_child *c;
c = (struct saned_child *) malloc (sizeof(struct saned_child));
if (c == NULL)
{
DBG (DBG_ERR, "add_child: out of memory\n");
return -1;
}
c->pid = pid;
c->next = children;
children = c;
return 0;
}
static void
handle_connection (int fd)
{
#ifdef TCP_NODELAY
int on = 1;
int level = -1;
#endif
DBG (DBG_DBG, "handle_connection: processing client connection\n");
wire.io.fd = fd;
signal (SIGALRM, quit);
signal (SIGPIPE, quit);
#ifdef TCP_NODELAY
# ifdef SOL_TCP
level = SOL_TCP;
# else /* !SOL_TCP */
/* Look up the protocol level in the protocols database. */
{
struct protoent *p;
p = getprotobyname ("tcp");
if (p == 0)
{
DBG (DBG_WARN, "handle_connection: cannot look up `tcp' protocol number");
}
else
level = p->p_proto;
}
# endif /* SOL_TCP */
if (level == -1
|| setsockopt (wire.io.fd, level, TCP_NODELAY, &on, sizeof (on)))
DBG (DBG_WARN, "handle_connection: failed to put socket in TCP_NODELAY mode (%s)",
strerror (errno));
#endif /* !TCP_NODELAY */
if (init (&wire) < 0)
return;
while (1)
{
reset_watchdog ();
if (process_request (&wire) < 0)
break;
}
}
static void
handle_client (int fd)
{
pid_t pid;
int i;
DBG (DBG_DBG, "handle_client: spawning child process\n");
pid = fork ();
if (pid == 0)
{
/* child */
if (log_to_syslog)
closelog();
for (i = 3; i < fd; i++)
close(i);
if (log_to_syslog)
openlog ("saned", LOG_PID | LOG_CONS, LOG_DAEMON);
handle_connection (fd);
quit (0);
}
else if (pid > 0)
{
/* parent */
add_child (pid);
close(fd);
}
else
{
/* FAILED */
DBG (DBG_ERR, "handle_client: fork() failed: %s\n", strerror (errno));
close(fd);
}
}
static void
bail_out (int error)
{
DBG (DBG_ERR, "%sbailing out, waiting for children...\n", (error) ? "FATAL ERROR; " : "");
#if WITH_AVAHI
if (avahi_pid > 0)
kill (avahi_pid, SIGTERM);
#endif /* WITH_AVAHI */
while (numchildren > 0)
wait_child (-1, NULL, 0);
DBG (DBG_ERR, "bail_out: all children exited\n");
exit ((error) ? 1 : 0);
}
void
sig_int_term_handler (int signum);
void
sig_int_term_handler (int signum)
{
/* unused */
(void) signum;
signal (SIGINT, NULL);
signal (SIGTERM, NULL);
bail_out (0);
}
#if WITH_AVAHI
static void
saned_avahi (struct pollfd *fds, int nfds);
static void
saned_create_avahi_services (AvahiClient *c);
static void
saned_avahi_callback (AvahiClient *c, AvahiClientState state, void *userdata);
static void
saned_avahi_group_callback (AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata);
static void
saned_avahi (struct pollfd *fds, int nfds)
{
struct pollfd *fdp = NULL;
int error;
avahi_pid = fork ();
if (avahi_pid > 0)
{
numchildren++;
return;
}
else if (avahi_pid < 0)
{
DBG (DBG_ERR, "saned_avahi: could not spawn Avahi process: %s\n", strerror (errno));
return;
}
signal (SIGINT, NULL);
signal (SIGTERM, NULL);
/* Close network fds */
for (fdp = fds; nfds > 0; nfds--, fdp++)
close (fdp->fd);
free(fds);
avahi_svc_name = avahi_strdup(SANED_NAME);
avahi_poll = avahi_simple_poll_new ();
if (avahi_poll == NULL)
{
DBG (DBG_ERR, "saned_avahi: failed to create simple poll object\n");
goto fail;
}
avahi_client = avahi_client_new (avahi_simple_poll_get (avahi_poll), AVAHI_CLIENT_NO_FAIL, saned_avahi_callback, NULL, &error);
if (avahi_client == NULL)
{
DBG (DBG_ERR, "saned_avahi: failed to create client: %s\n", avahi_strerror (error));
goto fail;
}
avahi_simple_poll_loop (avahi_poll);
DBG (DBG_INFO, "saned_avahi: poll loop exited\n");
exit(EXIT_SUCCESS);
/* NOT REACHED */
return;
fail:
if (avahi_client)
avahi_client_free (avahi_client);
if (avahi_poll)
avahi_simple_poll_free (avahi_poll);
avahi_free (avahi_svc_name);
exit(EXIT_FAILURE);
}
static void
saned_avahi_group_callback (AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)
{
char *n;
/* unused */
(void) userdata;
if ((!g) || (g != avahi_group))
return;
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
/* The entry group has been established successfully */
DBG (DBG_INFO, "saned_avahi_group_callback: service '%s' successfully established\n", avahi_svc_name);
break;
case AVAHI_ENTRY_GROUP_COLLISION:
/* A service name collision with a remote service
* happened. Let's pick a new name */
n = avahi_alternative_service_name (avahi_svc_name);
avahi_free (avahi_svc_name);
avahi_svc_name = n;
DBG (DBG_WARN, "saned_avahi_group_callback: service name collision, renaming service to '%s'\n", avahi_svc_name);
/* And recreate the services */
saned_create_avahi_services (avahi_entry_group_get_client (g));
break;
case AVAHI_ENTRY_GROUP_FAILURE :
DBG (DBG_ERR, "saned_avahi_group_callback: entry group failure: %s\n", avahi_strerror (avahi_client_errno (avahi_entry_group_get_client (g))));
/* Some kind of failure happened while we were registering our services */
avahi_simple_poll_quit (avahi_poll);
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
break;
}
}
static void
saned_create_avahi_services (AvahiClient *c)
{
char *n;
char txt[32];
AvahiProtocol proto;
int ret;
if (!c)
return;
if (!avahi_group)
{
avahi_group = avahi_entry_group_new (c, saned_avahi_group_callback, NULL);
if (avahi_group == NULL)
{
DBG (DBG_ERR, "saned_create_avahi_services: avahi_entry_group_new() failed: %s\n", avahi_strerror (avahi_client_errno (c)));
goto fail;
}
}
if (avahi_entry_group_is_empty (avahi_group))
{
DBG (DBG_INFO, "saned_create_avahi_services: adding service '%s'\n", avahi_svc_name);
snprintf(txt, sizeof (txt), "protovers=%x", SANE_VERSION_CODE (V_MAJOR, V_MINOR, SANEI_NET_PROTOCOL_VERSION));
#ifdef ENABLE_IPV6
proto = AVAHI_PROTO_UNSPEC;
#else
proto = AVAHI_PROTO_INET;
#endif /* ENABLE_IPV6 */
ret = avahi_entry_group_add_service (avahi_group, AVAHI_IF_UNSPEC, proto, 0, avahi_svc_name, SANED_SERVICE_DNS, NULL, NULL, SANED_SERVICE_PORT, txt, NULL);
if (ret < 0)
{
if (ret == AVAHI_ERR_COLLISION)
{
n = avahi_alternative_service_name (avahi_svc_name);
avahi_free (avahi_svc_name);
avahi_svc_name = n;
DBG (DBG_WARN, "saned_create_avahi_services: service name collision, renaming service to '%s'\n", avahi_svc_name);
avahi_entry_group_reset (avahi_group);
saned_create_avahi_services (c);
return;
}
DBG (DBG_ERR, "saned_create_avahi_services: failed to add %s service: %s\n", SANED_SERVICE_DNS, avahi_strerror (ret));
goto fail;
}
/* Tell the server to register the service */
ret = avahi_entry_group_commit (avahi_group);
if (ret < 0)
{
DBG (DBG_ERR, "saned_create_avahi_services: failed to commit entry group: %s\n", avahi_strerror (ret));
goto fail;
}
}
return;
fail:
avahi_simple_poll_quit (avahi_poll);
}
static void
saned_avahi_callback (AvahiClient *c, AvahiClientState state, void *userdata)
{
int error;
/* unused */
(void) userdata;
if (!c)
return;
switch (state)
{
case AVAHI_CLIENT_CONNECTING:
DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_CONNECTING\n");
break;
case AVAHI_CLIENT_S_RUNNING:
DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_S_RUNNING\n");
saned_create_avahi_services (c);
break;
case AVAHI_CLIENT_S_COLLISION:
DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_S_COLLISION\n");
if (avahi_group)
avahi_entry_group_reset (avahi_group);
break;
case AVAHI_CLIENT_S_REGISTERING:
DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_S_REGISTERING\n");
if (avahi_group)
avahi_entry_group_reset (avahi_group);
break;
case AVAHI_CLIENT_FAILURE:
DBG (DBG_INFO, "saned_avahi_callback: AVAHI_CLIENT_FAILURE\n");
error = avahi_client_errno (c);
if (error == AVAHI_ERR_DISCONNECTED)
{
DBG (DBG_INFO, "saned_avahi_callback: AVAHI_ERR_DISCONNECTED\n");
/* Server disappeared - try to reconnect */
avahi_client_free (avahi_client);
avahi_client = NULL;
avahi_group = NULL;
avahi_client = avahi_client_new (avahi_simple_poll_get (avahi_poll), AVAHI_CLIENT_NO_FAIL, saned_avahi_callback, NULL, &error);
if (avahi_client == NULL)
{
DBG (DBG_ERR, "saned_avahi_callback: failed to create client: %s\n", avahi_strerror (error));
avahi_simple_poll_quit (avahi_poll);
}
}
else
{
/* Another error happened - game over */
DBG (DBG_ERR, "saned_avahi_callback: client failure: %s\n", avahi_strerror (error));
avahi_simple_poll_quit (avahi_poll);
}
break;
}
}
#endif /* WITH_AVAHI */
static void
read_config (void)
{
char config_line[PATH_MAX];
const char *optval;
char *endval;
long val;
FILE *fp;
int len;
DBG (DBG_INFO, "read_config: searching for config file\n");
fp = sanei_config_open (SANED_CONFIG_FILE);
if (fp)
{
while (sanei_config_read (config_line, sizeof (config_line), fp))
{
if (config_line[0] == '#')
continue; /* ignore line comments */
optval = strchr (config_line, '=');
if (optval == NULL)
continue; /* only interested in options, skip hosts */
len = strlen (config_line);
if (!len)
continue; /* ignore empty lines */
/*
* Check for saned options.
* Anything that isn't an option is a client.
*/
if (strstr(config_line, "data_portrange") != NULL)
{
optval = sanei_config_skip_whitespace (++optval);
if ((optval != NULL) && (*optval != '\0'))
{
val = strtol (optval, &endval, 10);
if (optval == endval)
{
DBG (DBG_ERR, "read_config: invalid value for data_portrange\n");
continue;
}
else if ((val < 0) || (val > 65535))
{
DBG (DBG_ERR, "read_config: data_portrange start port is invalid\n");
continue;
}
optval = strchr (endval, '-');
if (optval == NULL)
{
DBG (DBG_ERR, "read_config: no end port value for data_portrange\n");
continue;
}
optval = sanei_config_skip_whitespace (++optval);
data_port_lo = val;
val = strtol (optval, &endval, 10);
if (optval == endval)
{
DBG (DBG_ERR, "read_config: invalid value for data_portrange\n");
data_port_lo = 0;
continue;
}
else if ((val < 0) || (val > 65535))
{
DBG (DBG_ERR, "read_config: data_portrange end port is invalid\n");
data_port_lo = 0;
continue;
}
else if (val < data_port_lo)
{
DBG (DBG_ERR, "read_config: data_portrange end port is less than start port\n");
data_port_lo = 0;
continue;
}
data_port_hi = val;
DBG (DBG_INFO, "read_config: data port range: %d - %d\n", data_port_lo, data_port_hi);
}
}
else if(strstr(config_line, "data_connect_timeout") != NULL)
{
optval = sanei_config_skip_whitespace (++optval);
if ((optval != NULL) && (*optval != '\0'))
{
val = strtol (optval, &endval, 10);
if (optval == endval)
{
DBG (DBG_ERR, "read_config: invalid value for data_connect_timeout\n");
continue;
}
else if ((val < 0) || (val > 65535))
{
DBG (DBG_ERR, "read_config: data_connect_timeout is invalid\n");
continue;
}
data_connect_timeout = val;
DBG (DBG_INFO, "read_config: data connect timeout: %d\n", data_connect_timeout);
}
}
}
fclose (fp);
DBG (DBG_INFO, "read_config: done reading config\n");
}
else
DBG (DBG_ERR, "read_config: could not open config file (%s): %s\n",
SANED_CONFIG_FILE, strerror (errno));
}
#ifdef SANED_USES_AF_INDEP
static void
do_bindings_family (int family, int *nfds, struct pollfd **fds, struct addrinfo *res)
{
struct addrinfo *resp;
struct pollfd *fdp;
short sane_port;
int fd = -1;
int on = 1;
int i;
sane_port = bind_port;
fdp = *fds;
for (resp = res, i = 0; resp != NULL; resp = resp->ai_next, i++)
{
/* We're not interested */
if (resp->ai_family != family)
continue;
if (resp->ai_family == AF_INET)
{
if (sane_port != -1)
((struct sockaddr_in *) resp->ai_addr)->sin_port = htons(sane_port);
else
sane_port = ntohs(((struct sockaddr_in *) resp->ai_addr)->sin_port);
}
#ifdef ENABLE_IPV6
else if (resp->ai_family == AF_INET6)
{
if (sane_port != -1)
((struct sockaddr_in6 *) resp->ai_addr)->sin6_port = htons(sane_port);
else
sane_port = ntohs (((struct sockaddr_in6 *) resp->ai_addr)->sin6_port);
}
#endif /* ENABLE_IPV6 */
else
continue;
DBG (DBG_DBG, "do_bindings: [%d] socket () using IPv%d\n", i, (family == AF_INET) ? 4 : 6);
if ((fd = socket (resp->ai_family, SOCK_STREAM, 0)) < 0)
{
DBG (DBG_ERR, "do_bindings: [%d] socket failed: %s\n", i, strerror (errno));
continue;
}
DBG (DBG_DBG, "do_bindings: [%d] setsockopt ()\n", i);
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)))
DBG (DBG_ERR, "do_bindings: [%d] failed to put socket in SO_REUSEADDR mode (%s)\n", i, strerror (errno));
DBG (DBG_DBG, "do_bindings: [%d] bind () to port %d\n", i, sane_port);
if (bind (fd, resp->ai_addr, resp->ai_addrlen) < 0)
{
/*
* Binding a socket may fail with EADDRINUSE if we already bound
* to an IPv6 addr returned by getaddrinfo (usually the first ones)
* and we're trying to bind to an IPv4 addr now.
* It can also fail because we're trying to bind an IPv6 socket and IPv6
* is not functional on this machine.
* In any case, a bind() call returning an error is not necessarily fatal.
*/
DBG (DBG_WARN, "do_bindings: [%d] bind failed: %s\n", i, strerror (errno));
close (fd);
continue;
}
DBG (DBG_DBG, "do_bindings: [%d] listen ()\n", i);
if (listen (fd, 1) < 0)
{
DBG (DBG_ERR, "do_bindings: [%d] listen failed: %s\n", i, strerror (errno));
close (fd);
continue;
}
if (sane_port == 0)
{
/* sane was asked to bind to an ephemeral port, log it */
socklen_t len = sizeof (*resp->ai_addr);
if (getsockname(fd, resp->ai_addr, &len) != -1)
{
if (resp->ai_family == AF_INET)
{
DBG (DBG_INFO, "do_bindings: [%d] selected ephemeral port: %d\n", i, ntohs(((struct sockaddr_in *) resp->ai_addr)->sin_port));
}
#ifdef ENABLE_IPV6
if (resp->ai_family == AF_INET6)
{
DBG (DBG_INFO, "do_bindings: [%d] selected ephemeral port: %d\n", i, ntohs(((struct sockaddr_in6 *) resp->ai_addr)->sin6_port));
}
#endif /* ENABLE_IPV6 */
}
}
fdp->fd = fd;
fdp->events = POLLIN;
(*nfds)++;
fdp++;
}
*fds = fdp;
}
static void
do_bindings (int *nfds, struct pollfd **fds)
{
struct addrinfo *res;
struct addrinfo *resp;
struct addrinfo hints;
struct pollfd *fdp;
int err;
DBG (DBG_DBG, "do_bindings: trying to get port for service \"%s\" (getaddrinfo)\n", SANED_SERVICE_NAME);
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_family = PF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
err = getaddrinfo (bind_addr, SANED_SERVICE_NAME, &hints, &res);
if (err)
{
DBG (DBG_WARN, "do_bindings: \" %s \" service unknown on your host; you should add\n", SANED_SERVICE_NAME);
DBG (DBG_WARN, "do_bindings: %s %d/tcp saned # SANE network scanner daemon\n", SANED_SERVICE_NAME, SANED_SERVICE_PORT);
DBG (DBG_WARN, "do_bindings: to your /etc/services file (or equivalent). Proceeding anyway.\n");
err = getaddrinfo (bind_addr, SANED_SERVICE_PORT_S, &hints, &res);
if (err)
{
DBG (DBG_ERR, "do_bindings: getaddrinfo() failed even with numeric port: %s\n", gai_strerror (err));
bail_out (1);
}
}
for (resp = res, *nfds = 0; resp != NULL; resp = resp->ai_next, (*nfds)++)
;
*fds = malloc (*nfds * sizeof (struct pollfd));
if (fds == NULL)
{
DBG (DBG_ERR, "do_bindings: not enough memory for fds\n");
freeaddrinfo (res);
bail_out (1);
}
fdp = *fds;
*nfds = 0;
/* bind IPv6 first, IPv4 second */
#ifdef ENABLE_IPV6
do_bindings_family (AF_INET6, nfds, &fdp, res);
#endif
do_bindings_family (AF_INET, nfds, &fdp, res);
resp = NULL;
freeaddrinfo (res);
if (*nfds <= 0)
{
DBG (DBG_ERR, "do_bindings: couldn't bind an address. Exiting.\n");
bail_out (1);
}
}
#else /* !SANED_USES_AF_INDEP */
static void
do_bindings (int *nfds, struct pollfd **fds)
{
struct sockaddr_in sin;
struct servent *serv;
short port;
int fd = -1;
int on = 1;
DBG (DBG_DBG, "do_bindings: trying to get port for service \"%s\" (getservbyname)\n", SANED_SERVICE_NAME);
serv = getservbyname (SANED_SERVICE_NAME, "tcp");
if (serv)
{
port = serv->s_port;
DBG (DBG_MSG, "do_bindings: port is %d\n", ntohs (port));
}
else
{
port = htons (SANED_SERVICE_PORT);
DBG (DBG_WARN, "do_bindings: \"%s\" service unknown on your host; you should add\n", SANED_SERVICE_NAME);
DBG (DBG_WARN, "do_bindings: %s %d/tcp saned # SANE network scanner daemon\n", SANED_SERVICE_NAME, SANED_SERVICE_PORT);
DBG (DBG_WARN, "do_bindings: to your /etc/services file (or equivalent). Proceeding anyway.\n");
}
*nfds = 1;
*fds = malloc (*nfds * sizeof (struct pollfd));
if (fds == NULL)
{
DBG (DBG_ERR, "do_bindings: not enough memory for fds\n");
bail_out (1);
}
memset (&sin, 0, sizeof (sin));
sin.sin_family = AF_INET;
if(bind_addr)
sin.sin_addr.s_addr = inet_addr(bind_addr);
else
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = port;
DBG (DBG_DBG, "do_bindings: socket ()\n");
fd = socket (AF_INET, SOCK_STREAM, 0);
DBG (DBG_DBG, "do_bindings: setsockopt ()\n");
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)))
DBG (DBG_ERR, "do_bindings: failed to put socket in SO_REUSEADDR mode (%s)", strerror (errno));
DBG (DBG_DBG, "do_bindings: bind ()\n");
if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0)
{
DBG (DBG_ERR, "do_bindings: bind failed: %s", strerror (errno));
bail_out (1);
}
DBG (DBG_DBG, "do_bindings: listen ()\n");
if (listen (fd, 1) < 0)
{
DBG (DBG_ERR, "do_bindings: listen failed: %s", strerror (errno));
bail_out (1);
}
(*fds)->fd = fd;
(*fds)->events = POLLIN;
}
#endif /* SANED_USES_AF_INDEP */
static void
runas_user (char *user)
{
uid_t runas_uid = 0;
gid_t runas_gid = 0;
struct passwd *pwent;
gid_t *grplist = NULL;
struct group *grp;
int ngroups = 0;
int ret;
pwent = getpwnam(user);
if (pwent == NULL)
{
DBG (DBG_ERR, "FATAL ERROR: user %s not found on system\n", user);
bail_out (1);
}
runas_uid = pwent->pw_uid;
runas_gid = pwent->pw_gid;
/* Get group list for runas_uid */
grplist = (gid_t *)malloc(sizeof(gid_t));
if (grplist == NULL)
{
DBG (DBG_ERR, "FATAL ERROR: cannot allocate memory for group list\n");
exit (1);
}
ngroups = 1;
grplist[0] = runas_gid;
setgrent();
while ((grp = getgrent()) != NULL)
{
int i = 0;
/* Already added current group */
if (grp->gr_gid == runas_gid)
continue;
while (grp->gr_mem[i])
{
if (strcmp(grp->gr_mem[i], user) == 0)
{
int need_to_add = 1, j;
/* Make sure its not already in list */
for (j = 0; j < ngroups; j++)
{
if (grp->gr_gid == grplist[i])
need_to_add = 0;
}
if (need_to_add)
{
grplist = (gid_t *)realloc(grplist,
sizeof(gid_t)*ngroups+1);
if (grplist == NULL)
{
DBG (DBG_ERR, "FATAL ERROR: cannot reallocate memory for group list\n");
exit (1);
}
grplist[ngroups++] = grp->gr_gid;
}
}
i++;
}
}
endgrent();
/* Drop privileges if requested */
if (runas_uid > 0)
{
ret = setgroups(ngroups, grplist);
if (ret < 0)
{
DBG (DBG_ERR, "FATAL ERROR: could not set group list: %s\n", strerror(errno));
exit (1);
}
free(grplist);
ret = setegid (runas_gid);
if (ret < 0)
{
DBG (DBG_ERR, "FATAL ERROR: setegid to gid %d failed: %s\n", runas_gid, strerror (errno));
exit (1);
}
ret = seteuid (runas_uid);
if (ret < 0)
{
DBG (DBG_ERR, "FATAL ERROR: seteuid to uid %d failed: %s\n", runas_uid, strerror (errno));
exit (1);
}
DBG (DBG_WARN, "Dropped privileges to uid %d gid %d\n", runas_uid, runas_gid);
}
}
static void
run_standalone (char *user)
{
struct pollfd *fds = NULL;
struct pollfd *fdp = NULL;
int nfds;
int fd = -1;
int i;
int ret;
FILE *pidfile;
do_bindings (&nfds, &fds);
if (run_foreground == SANE_FALSE)
{
DBG (DBG_MSG, "run_standalone: daemonizing now\n");
fd = open ("/dev/null", O_RDWR);
if (fd < 0)
{
DBG (DBG_ERR, "FATAL ERROR: cannot open /dev/null: %s\n", strerror (errno));
exit (1);
}
ret = fork ();
if (ret > 0)
{
_exit (0);
}
else if (ret < 0)
{
DBG (DBG_ERR, "FATAL ERROR: fork failed: %s\n", strerror (errno));
exit (1);
}
DBG (DBG_WARN, "Now daemonized\n");
/* Write out PID file */
pidfile = fopen (SANED_PID_FILE, "w");
if (pidfile)
{
fprintf (pidfile, "%d", getpid());
fclose (pidfile);
}
else
DBG (DBG_ERR, "Could not write PID file: %s\n", strerror (errno));
chdir ("/");
dup2 (fd, STDIN_FILENO);
dup2 (fd, STDOUT_FILENO);
dup2 (fd, STDERR_FILENO);
close (fd);
setsid ();
signal(SIGINT, sig_int_term_handler);
signal(SIGTERM, sig_int_term_handler);
}
if (user)
runas_user(user);
#if WITH_AVAHI
DBG (DBG_INFO, "run_standalone: spawning Avahi process\n");
saned_avahi (fds, nfds);
/* NOT REACHED (Avahi process) */
#endif /* WITH_AVAHI */
DBG (DBG_MSG, "run_standalone: waiting for control connection\n");
while (1)
{
ret = poll (fds, nfds, 500);
if (ret < 0)
{
if (errno == EINTR)
continue;
else
{
DBG (DBG_ERR, "run_standalone: poll failed: %s\n", strerror (errno));
free (fds);
bail_out (1);
}
}
/* Wait for children */
while (wait_child (-1, NULL, WNOHANG) > 0)
;
if (ret == 0)
continue;
for (i = 0, fdp = fds; i < nfds; i++, fdp++)
{
/* Error on an fd */
if (fdp->revents & (POLLERR | POLLHUP | POLLNVAL))
{
for (i = 0, fdp = fds; i < nfds; i++, fdp++)
close (fdp->fd);
free (fds);
DBG (DBG_WARN, "run_standalone: invalid fd in set, attempting to re-bind\n");
/* Reopen sockets */
do_bindings (&nfds, &fds);
break;
}
else if (! (fdp->revents & POLLIN))
continue;
fd = accept (fdp->fd, 0, 0);
if (fd < 0)
{
DBG (DBG_ERR, "run_standalone: accept failed: %s", strerror (errno));
continue;
}
handle_client (fd);
if (run_once == SANE_TRUE)
break; /* We have handled the only connection we're going to handle */
}
if (run_once == SANE_TRUE)
break;
}
for (i = 0, fdp = fds; i < nfds; i++, fdp++)
close (fdp->fd);
free (fds);
}
static void
run_inetd (char __sane_unused__ *sock)
{
int fd = -1;
#ifdef HAVE_SYSTEMD
int n;
n = sd_listen_fds(0);
if (n > 1)
{
DBG (DBG_ERR, "run_inetd: Too many file descriptors (sockets) received from systemd!\n");
return;
}
if (n == 1)
{
fd = SD_LISTEN_FDS_START + 0;
DBG (DBG_INFO, "run_inetd: Using systemd socket %d!\n", fd);
}
#endif
if (fd == -1)
{
int dave_null;
/* Some backends really can't keep their dirty fingers off
* stdin/stdout/stderr; we work around them here so they don't
* mess up the network dialog and crash the remote net backend
* by messing with the inetd socket.
* For systemd this not an issue as systemd uses fd >= 3 for the
* socket and can even redirect stdout and stderr to syslog.
* We can even use this to get the debug logging
*/
do
{
/* get new fd for the inetd socket */
fd = dup (1);
if (fd == -1)
{
DBG (DBG_ERR, "run_inetd: duplicating fd failed: %s", strerror (errno));
return;
}
}
while (fd < 3);
/* Our good'ole friend Dave Null to the rescue */
dave_null = open ("/dev/null", O_RDWR);
if (dave_null < 0)
{
DBG (DBG_ERR, "run_inetd: could not open /dev/null: %s", strerror (errno));
return;
}
close (STDIN_FILENO);
close (STDOUT_FILENO);
close (STDERR_FILENO);
dup2 (dave_null, STDIN_FILENO);
dup2 (dave_null, STDOUT_FILENO);
dup2 (dave_null, STDERR_FILENO);
close (dave_null);
}
#ifdef HAVE_OS2_H
/* under OS/2, the socket handle is passed as argument on the command
line; the socket handle is relative to IBM TCP/IP, so a call
to impsockethandle() is required to add it to the EMX runtime */
if (sock)
{
fd = _impsockhandle (atoi (sock), 0);
if (fd == -1)
perror ("impsockhandle");
}
#endif /* HAVE_OS2_H */
handle_connection(fd);
}
static void usage(char *me, int err)
{
fprintf (stderr,
"Usage: %s [OPTIONS]\n\n"
" Options:\n\n"
" -a, --alone[=user] equal to `-l -D -u user'\n"
" -l, --listen run in standalone mode (listen for connection)\n"
" -u, --user=user run as `user'\n"
" -D, --daemonize run in background\n"
" -o, --once exit after first client disconnects\n"
" -d, --debug=level set debug level `level' (default is 2)\n"
" -e, --stderr output to stderr\n"
" -b, --bind=addr bind address `addr' (default all interfaces)\n"
" -p, --port=port bind port `port` (default sane-port or 6566)\n"
" -h, --help show this help message and exit\n", me);
exit(err);
}
static int debug;
static struct option long_options[] =
{
/* These options set a flag. */
{"help", no_argument, 0, 'h'},
{"alone", optional_argument, 0, 'a'},
{"listen", no_argument, 0, 'l'},
{"user", required_argument, 0, 'u'},
{"daemonize", no_argument, 0, 'D'},
{"once", no_argument, 0, 'o'},
{"debug", required_argument, 0, 'd'},
{"stderr", no_argument, 0, 'e'},
{"bind", required_argument, 0, 'b'},
{"port", required_argument, 0, 'p'},
{0, 0, 0, 0 }
};
int
main (int argc, char *argv[])
{
char options[64] = "";
char *user = NULL;
char *sock = NULL;
int c;
int long_index = 0;
debug = DBG_WARN;
prog_name = strrchr (argv[0], '/');
if (prog_name)
++prog_name;
else
prog_name = argv[0];
numchildren = 0;
run_mode = SANED_RUN_INETD;
run_foreground = SANE_TRUE;
run_once = SANE_FALSE;
while((c = getopt_long(argc, argv,"ha::lu:Dod:eb:p:", long_options, &long_index )) != -1)
{
switch(c) {
case 'a':
run_mode = SANED_RUN_ALONE;
run_foreground = SANE_FALSE;
if (optarg)
user = optarg;
break;
case 'l':
run_mode = SANED_RUN_ALONE;
break;
case 'u':
user = optarg;
break;
case 'D':
run_foreground = SANE_FALSE;
break;
case 'o':
run_once = SANE_TRUE;
break;
case 'd':
debug = atoi(optarg);
break;
case 'e':
log_to_syslog = SANE_FALSE;
break;
case 'b':
bind_addr = optarg;
break;
case 'p':
bind_port = atoi(optarg);
break;
case 'h':
usage(argv[0], EXIT_SUCCESS);
break;
default:
usage(argv[0], EXIT_FAILURE);
break;
}
}
if (log_to_syslog)
openlog ("saned", LOG_PID | LOG_CONS, LOG_DAEMON);
read_config ();
byte_order.w = 0;
byte_order.ch = 1;
sanei_w_init (&wire, sanei_codec_bin_init);
wire.io.read = read;
wire.io.write = write;
#ifdef SANED_USES_AF_INDEP
strcat(options, "AF-indep");
# ifdef ENABLE_IPV6
strcat(options, "+IPv6");
#endif
#else
strcat(options, "IPv4 only");
#endif
#ifdef HAVE_SYSTEMD
if (sd_listen_fds(0) > 0)
{
strcat(options, "+systemd");
}
#endif
if (strlen(options) > 0)
{
DBG (DBG_WARN, "saned (%s) from %s starting up\n", options, PACKAGE_STRING);
}
else
{
DBG (DBG_WARN, "saned from %s ready\n", PACKAGE_STRING);
}
if (run_mode == SANED_RUN_ALONE)
{
run_standalone(user);
}
else
{
#ifdef HAVE_OS2_H
if (argc == 2)
sock = argv[1];
#endif
run_inetd(sock);
}
DBG (DBG_WARN, "saned exiting\n");
return 0;
}