kopia lustrzana https://gitlab.com/sane-project/backends
				
				
				
			
		
			
				
	
	
		
			3530 wiersze
		
	
	
		
			83 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			3530 wiersze
		
	
	
		
			83 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, 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>
 | |
| 
 | |
| #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 */
 | |
| 
 | |
| #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
 | |
| # 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 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;
 | |
| 
 | |
| #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");
 | |
| 	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;
 | |
| 
 | |
|   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 (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);
 | |
| 
 | |
| #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;
 | |
| 	    }
 | |
| 
 | |
| 	  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"
 | |
|        "  -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'},
 | |
|   {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:", 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 '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;
 | |
| }
 |