summaryrefslogtreecommitdiff
path: root/frontend/saned.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/saned.c')
-rw-r--r--frontend/saned.c3355
1 files changed, 3355 insertions, 0 deletions
diff --git a/frontend/saned.c b/frontend/saned.c
new file mode 100644
index 0000000..108512d
--- /dev/null
+++ b/frontend/saned.c
@@ -0,0 +1,3355 @@
+/* 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, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ 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>
+
+#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 */
+
+#ifdef 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
+# define SANE_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))
+
+#define SANE_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 /* 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 Handle *handle;
+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_DEBUG 1
+#define SANED_RUN_ALONE 2
+
+
+#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 (SANE_IN6_IS_ADDR_V4MAPPED (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 (SANE_IN6_IS_ADDR_LOOPBACK (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;
+
+ 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;
+ 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);
+ process_request (w);
+ 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));
+ 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;
+ }
+
+ 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;
+
+ 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;
+
+ DBG (DBG_MSG, "process_request: waiting for data connection\n");
+ 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;
+
+ DBG (DBG_MSG, "process_request: waiting for data connection\n");
+ 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;
+
+#ifdef 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; " : "");
+
+#ifdef 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 */
+ signum = signum;
+
+ signal (SIGINT, NULL);
+ signal (SIGTERM, NULL);
+
+ bail_out (0);
+}
+
+
+#ifdef 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 */
+ userdata = 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 */
+ userdata = 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");
+
+ 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);
+ }
+ }
+ }
+ 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;
+
+ 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)
+ {
+ sane_port = ntohs (((struct sockaddr_in *) resp->ai_addr)->sin_port);
+ }
+#ifdef ENABLE_IPV6
+ else if (resp->ai_family == AF_INET6)
+ {
+ 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;
+ }
+
+ 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 (NULL, 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 (NULL, 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;
+ 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
+run_standalone (int argc, char **argv)
+{
+ struct pollfd *fds = NULL;
+ struct pollfd *fdp = NULL;
+ int nfds;
+ int fd = -1;
+ int i;
+ int ret;
+
+ uid_t runas_uid = 0;
+ gid_t runas_gid = 0;
+ struct passwd *pwent;
+ gid_t *grplist = NULL;
+ struct group *grp;
+ int ngroups = 0;
+ FILE *pidfile;
+
+ do_bindings (&nfds, &fds);
+
+ if (run_mode != SANED_RUN_DEBUG)
+ {
+ if (argc > 2)
+ {
+ pwent = getpwnam(argv[2]);
+
+ if (pwent == NULL)
+ {
+ DBG (DBG_ERR, "FATAL ERROR: user %s not found on system\n", argv[2]);
+ 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], argv[2]) == 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();
+ }
+
+ 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 ();
+
+ /* 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);
+ }
+
+ signal(SIGINT, sig_int_term_handler);
+ signal(SIGTERM, sig_int_term_handler);
+ }
+
+#ifdef 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;
+ }
+
+ if (run_mode == SANED_RUN_DEBUG)
+ break; /* We have the only connection we're going to handle */
+ else
+ handle_client (fd);
+ }
+
+ if (run_mode == SANED_RUN_DEBUG)
+ break;
+ }
+
+ for (i = 0, fdp = fds; i < nfds; i++, fdp++)
+ close (fdp->fd);
+
+ free (fds);
+
+ if (run_mode == SANED_RUN_DEBUG)
+ {
+ if (fd > 0)
+ handle_connection (fd);
+
+ bail_out(0);
+ }
+}
+
+
+static void
+run_inetd (int argc, char **argv)
+{
+
+ 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);
+ }
+#ifndef HAVE_OS2_H
+ /* Unused in this function */
+ argc = argc;
+ argv = argv;
+
+#else
+ /* 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 (argc == 2)
+ {
+ fd = _impsockhandle (atoi (argv[1]), 0);
+ if (fd == -1)
+ perror ("impsockhandle");
+ }
+#endif /* HAVE_OS2_H */
+
+ handle_connection(fd);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ char options[64] = "";
+ 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;
+
+ if (argc >= 2)
+ {
+ if (strncmp (argv[1], "-a", 2) == 0)
+ run_mode = SANED_RUN_ALONE;
+ else if (strncmp (argv[1], "-d", 2) == 0)
+ {
+ run_mode = SANED_RUN_DEBUG;
+ log_to_syslog = SANE_FALSE;
+ }
+ else if (strncmp (argv[1], "-s", 2) == 0)
+ run_mode = SANED_RUN_DEBUG;
+ else
+ {
+ printf ("Usage: saned [ -a [ username ] | -d [ n ] | -s [ n ] ] | -h\n");
+ if ((strncmp (argv[1], "-h", 2) == 0) ||
+ (strncmp (argv[1], "--help", 6) == 0))
+ exit (EXIT_SUCCESS);
+ else
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (run_mode == SANED_RUN_DEBUG)
+ {
+ if (argv[1][2])
+ debug = atoi (argv[1] + 2);
+
+ DBG (DBG_WARN, "main: starting debug mode (level %d)\n", debug);
+ }
+
+ 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_mode == SANED_RUN_DEBUG))
+ {
+ run_standalone(argc, argv);
+ }
+ else
+ {
+ run_inetd(argc, argv);
+ }
+
+ DBG (DBG_WARN, "saned exiting\n");
+
+ return 0;
+}