From 088281c4e1376d665d3ae3c96b3a61c54d877a6e Mon Sep 17 00:00:00 2001 From: Henning Geinitz Date: Sun, 30 Mar 2003 19:07:18 +0000 Subject: [PATCH] Added support for IPv6. Updated manpages. Patch from Julien BLACHE . --- ChangeLog | 4 + acinclude.m4 | 40 +++ aclocal.m4 | 40 +++ backend/net.c | 485 ++++++++++++++++++++++++- backend/net.h | 9 + configure | 101 +++++- configure.in | 12 +- doc/sane-net.man | 38 +- doc/saned.man | 9 +- frontend/saned.c | 757 ++++++++++++++++++++++++++++++++++++++- include/sane/config.h.in | 9 + 11 files changed, 1467 insertions(+), 37 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1054c3b45..5e3248b24 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,10 @@ * doc/descriptions/unsupported.desc: Added Medion MD 6228, Microtek ScanPort 3000, and PIE Primefilm 1800u. + * acinclude.m4 aclocal.m4 configure configure.in backend/net.c + backend/net.h doc/sane-net.man doc/saned.man frontend/saned.c + include/sane/config.h.in: Added support for IPv6. Updated + manpages. Patch from Julien BLACHE . 2003-03-28 Oliver Schirrmeister diff --git a/acinclude.m4 b/acinclude.m4 index ee511adcf..49ee23abe 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -12,6 +12,7 @@ dnl JAPHAR_GREP_CFLAGS(flag, cmd_if_missing, cmd_if_present) dnl SANE_LINKER_RPATH dnl SANE_CHECK_U_TYPES dnl SANE_CHECK_GPHOTO2 +dnl SANE_CHECK_IPV6 dnl SANE_PROTOTYPES dnl AC_PROG_LIBTOOL dnl @@ -344,6 +345,45 @@ AC_DEFUN([SANE_CHECK_GPHOTO2], ]) fi ]) +# +# Check for AF_INET6, determines whether or not to enable IPv6 support +AC_DEFUN([SANE_CHECK_IPV6], +[ +AC_MSG_CHECKING([whether to enable IPv6]) +AC_ARG_ENABLE(ipv6, +[ --enable-ipv6 enable IPv6 (with IPv4) support + --disable-ipv6 disable IPv6 support], +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + ipv6=no + ;; + *) AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_IPV6], 1, [Define to 1 if the system supports IPv6]) + ipv6=yes + ;; + esac ], + + AC_TRY_COMPILE([ + #define INET6 + #include + #include ], [ + /* AF_INET6 available check */ + if (socket(AF_INET6, SOCK_STREAM, 0) < 0) + exit(1); + else + exit(0); +], + AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_IPV6], 1, [Define to 1 if the system supports IPv6]) + ipv6=yes, + AC_MSG_RESULT(no) + ipv6=no, + AC_MSG_RESULT(no) + ipv6=no +)) +]) + # # Generate prototypes for functions not available on the system AC_DEFUN([SANE_PROTOTYPES], diff --git a/aclocal.m4 b/aclocal.m4 index a375dd34d..bd1047f16 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -24,6 +24,7 @@ dnl JAPHAR_GREP_CFLAGS(flag, cmd_if_missing, cmd_if_present) dnl SANE_LINKER_RPATH dnl SANE_CHECK_U_TYPES dnl SANE_CHECK_GPHOTO2 +dnl SANE_CHECK_IPV6 dnl SANE_PROTOTYPES dnl AC_PROG_LIBTOOL dnl @@ -356,6 +357,45 @@ AC_DEFUN([SANE_CHECK_GPHOTO2], ]) fi ]) +# +# Check for AF_INET6, determines whether or not to enable IPv6 support +AC_DEFUN([SANE_CHECK_IPV6], +[ +AC_MSG_CHECKING([whether to enable IPv6]) +AC_ARG_ENABLE(ipv6, +[ --enable-ipv6 enable IPv6 (with IPv4) support + --disable-ipv6 disable IPv6 support], +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + ipv6=no + ;; + *) AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_IPV6], 1, [Define to 1 if the system supports IPv6]) + ipv6=yes + ;; + esac ], + + AC_TRY_COMPILE([ + #define INET6 + #include + #include ], [ + /* AF_INET6 available check */ + if (socket(AF_INET6, SOCK_STREAM, 0) < 0) + exit(1); + else + exit(0); +], + AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_IPV6], 1, [Define to 1 if the system supports IPv6]) + ipv6=yes, + AC_MSG_RESULT(no) + ipv6=no, + AC_MSG_RESULT(no) + ipv6=no +)) +]) + # # Generate prototypes for functions not available on the system AC_DEFUN([SANE_PROTOTYPES], diff --git a/backend/net.c b/backend/net.c index d0ce60d8a..f9ed0f618 100644 --- a/backend/net.c +++ b/backend/net.c @@ -1,5 +1,8 @@ /* sane - Scanner Access Now Easy. Copyright (C) 1997 David Mosberger-Tang + Copyright (C) 2003 Julien BLACHE + AF-independent code + IPv6 + This file is part of the SANE package. This program is free software; you can redistribute it and/or @@ -40,10 +43,6 @@ This file implements a SANE network-based meta backend. */ -/* Please increase version number with every change - (don't forget to update net.desc) */ -#define NET_VERSION "1.0.10" - #ifdef _AIX # include "../include/lalloca.h" /* MUST come first for AIX! */ #endif @@ -83,15 +82,34 @@ #include "../include/sane/sanei_config.h" #define NET_CONFIG_FILE "net.conf" +/* Please increase version number with every change + (don't forget to update net.desc) */ + +/* define the version string depending on which network code is used */ +#if defined (HAVE_GETADDRINFO) && defined (HAVE_GETNAMEINFO) +# define NET_USES_AF_INDEP +# ifdef ENABLE_IPV6 +# define NET_VERSION "1.0.11 (AF-indep+IPv6)" +# else +# define NET_VERSION "1.0.11 (AF-indep)" +# endif /* ENABLE_IPV6 */ +#else +# undef ENABLE_IPV6 +# define NET_VERSION "1.0.11" +#endif /* HAVE_GETADDRINFO && HAVE_GETNAMEINFO */ + static SANE_Auth_Callback auth_callback; static Net_Device *first_device; static Net_Scanner *first_handle; static const SANE_Device **devlist; -static int saned_port; static int client_big_endian; /* 1 == big endian; 0 == little endian */ static int server_big_endian; /* 1 == big endian; 0 == little endian */ static int depth; /* bits per pixel */ +#ifndef NET_USES_AF_INDEP +static int saned_port; +#endif /* !NET_USES_AF_INDEP */ + /* This variable is only needed, if the depth is 16bit/channel and client/server have different endianness. A value of -1 means, that there's no hang over; otherwise the value has to be casted to SANE_Byte. hang_over @@ -110,6 +128,99 @@ static int hang_over; */ static int left_over; + +#ifdef NET_USES_AF_INDEP +static SANE_Status +add_device (const char *name, Net_Device ** ndp) +{ + struct addrinfo hints; + struct addrinfo *res; + struct addrinfo *resp; + struct sockaddr_in *sin; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *sin6; +#endif /* ENABLE_IPV6 */ + + Net_Device *nd = NULL; + + int error; + short sane_port = htons (6566); + + DBG (1, "add_device: adding backend %s\n", name); + + memset (&hints, 0, sizeof(hints)); + +# ifdef ENABLE_IPV6 + hints.ai_family = PF_UNSPEC; +# else + hints.ai_family = PF_INET; +# endif /* ENABLE_IPV6 */ + + error = getaddrinfo (name, "sane", &hints, &res); + if (error) + { + error = getaddrinfo (name, NULL, &hints, &res); + if (error) + { + DBG (1, "add_device: error while getting address of host %s: %s\n", + name, gai_strerror (error)); + + return SANE_STATUS_IO_ERROR; + } + else + { + for (resp = res; resp != NULL; resp = resp->ai_next) + { + switch (resp->ai_family) + { + case AF_INET: + sin = (struct sockaddr_in *) resp->ai_addr; + sin->sin_port = sane_port; + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *) resp->ai_addr; + sin6->sin6_port = sane_port; + break; +#endif /* ENABLE_IPV6 */ + } + } + } + } + + nd = malloc (sizeof (Net_Device)); + if (!nd) + { + DBG (1, "add_device: not enough memory for Net_Device struct\n"); + + freeaddrinfo (res); + return SANE_STATUS_NO_MEM; + } + + memset (nd, 0, sizeof (Net_Device)); + nd->name = strdup (name); + if (!nd->name) + { + DBG (1, "add_device: not enough memory to duplicate name\n"); + free(nd); + return SANE_STATUS_NO_MEM; + } + + nd->addr = res; + nd->ctl = -1; + + nd->next = first_device; + + first_device = nd; + + if (ndp) + *ndp = nd; + DBG (2, "add_device: backend %s added\n", name); + return SANE_STATUS_GOOD; +} + +#else /* !NET_USES_AF_INDEP */ + static SANE_Status add_device (const char *name, Net_Device ** ndp) { @@ -161,6 +272,69 @@ add_device (const char *name, Net_Device ** ndp) DBG (2, "add_device: backend %s added\n", name); return SANE_STATUS_GOOD; } +#endif /* NET_USES_AF_INDEP */ + + +#ifdef NET_USES_AF_INDEP +static SANE_Status +connect_dev (Net_Device * dev) +{ + struct addrinfo *addrp; + + SANE_Word version_code; + SANE_Init_Reply reply; + SANE_Status status = SANE_STATUS_IO_ERROR; + SANE_Init_Req req; + SANE_Bool connected = SANE_FALSE; +#ifdef TCP_NODELAY + int on = 1; + int level = -1; +#endif + + int i; + + DBG (2, "connect_dev: trying to connect to %s\n", dev->name); + + for (addrp = dev->addr, i = 0; (addrp != NULL) && (connected == SANE_FALSE); addrp = addrp->ai_next, i++) + { +# ifdef ENABLE_IPV6 + if ((addrp->ai_family != AF_INET) && (addrp->ai_family != AF_INET6)) +# else /* !ENABLE_IPV6 */ + if (addrp->ai_family != AF_INET) +# endif /* ENABLE_IPV6 */ + { + DBG (1, "connect_dev: [%d] don't know how to deal with addr family %d\n", + i, addrp->ai_family); + break; + } + + dev->ctl = socket (addrp->ai_family, SOCK_STREAM, 0); + if (dev->ctl < 0) + { + DBG (1, "connect_dev: [%d] failed to obtain socket (%s)\n", + i, strerror (errno)); + dev->ctl = -1; + break; + } + + if (connect (dev->ctl, addrp->ai_addr, addrp->ai_addrlen) < 0) + { + DBG (1, "connect_dev: [%d] failed to connect (%s)\n", i, strerror (errno)); + dev->ctl = -1; + break; + } + DBG (3, "connect_dev: [%d] connection succeeded (%s)\n", i, (addrp->ai_family == AF_INET6) ? "IPv6" : "IPv4"); + dev->addr_used = addrp; + connected = SANE_TRUE; + } + + if (connected != SANE_TRUE) + { + DBG (1, "connect_dev: couldn't connect to host (see messages above)\n"); + return SANE_STATUS_IO_ERROR; + } + +#else /* !NET_USES_AF_INDEP */ static SANE_Status connect_dev (Net_Device * dev) @@ -202,6 +376,7 @@ connect_dev (Net_Device * dev) return SANE_STATUS_IO_ERROR; } DBG (3, "connect_dev: connection succeeded\n"); +#endif /* NET_USES_AF_INDEP */ #ifdef TCP_NODELAY # ifdef SOL_TCP @@ -288,6 +463,7 @@ fail: return status; } + static SANE_Status fetch_options (Net_Scanner * s) { @@ -444,13 +620,16 @@ SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) { char device_name[PATH_MAX]; - struct servent *serv; const char *env; size_t len; FILE *fp; short ns = 0x1234; unsigned char *p = (unsigned char *)(&ns); +#ifndef NET_USES_AF_INDEP + struct servent *serv; +#endif /* !NET_USES_AF_INDEP */ + DBG_INIT (); DBG (2, "sane_init: authorize = %p, version_code = %p\n", authorize, @@ -484,6 +663,7 @@ sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) DBG (3, "sane_init: Client has little endian byte order\n"); } +#ifndef NET_USES_AF_INDEP DBG (2, "sane_init: determining sane service port\n"); serv = getservbyname ("sane", "tcp"); @@ -498,6 +678,7 @@ sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) DBG (1, "sane_init: could not find `sane' service (%s); using default " "port %d\n", strerror (errno), ntohs (saned_port)); } +#endif /* !NET_USES_AF_INDEP */ DBG (2, "sane_init: searching for config file\n"); fp = sanei_config_open (NET_CONFIG_FILE); @@ -533,6 +714,30 @@ sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize) next = copy; while ((host = strsep (&next, ":"))) { +#ifdef ENABLE_IPV6 + if (host[0] == '[') + { + /* skip '[' (host[0]) */ + host++; + /* get the rest of the IPv6 addr (we're screwed if ] is missing) + * Is it worth checking for the matching ] ? Not for now. */ + strsep (&next, "]"); + /* add back the ":" that got removed by the strsep() */ + host[strlen (host)] = ':'; + /* host now holds the IPv6 address */ + + /* skip the ':' that could be after ] (avoids a call to strsep() */ + if (next[0] == ':') + next++; + } + + /* + * if the IPv6 is last in the list, the strsep() call in the while() + * will return a string with the first char being '\0'. Skip it. + */ + if (host[0] == '\0') + continue; +#endif /* ENABLE_IPV6 */ DBG (2, "sane_init: trying to add %s\n", host); add_device (host, 0); } @@ -580,6 +785,12 @@ sane_exit (void) } if (dev->name) free ((void *) dev->name); + +#ifdef NET_USES_AF_INDEP + if (dev->addr) + freeaddrinfo(dev->addr); +#endif /* NET_USES_AF_INDEP */ + free (dev); } if (devlist) @@ -693,11 +904,23 @@ sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) { SANE_Device *rdev; char *mem; +#ifdef ENABLE_IPV6 + SANE_Bool IPv6 = SANE_FALSE; +#endif /* ENABLE_IPV6 */ /* create a new device entry with a device name that is the sum of the backend name a colon and the backend's device name: */ len = strlen (dev->name) + 1 + strlen (reply.device_list[i]->name); + +#ifdef ENABLE_IPV6 + if (strchr (dev->name, ':') != NULL) + { + len += 2; + IPv6 = SANE_TRUE; + } +#endif /* ENABLE_IPV6 */ + mem = malloc (sizeof (*dev) + len + 1); if (!mem) { @@ -707,8 +930,22 @@ sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) &reply); return SANE_STATUS_NO_MEM; } + + memset (mem, 0, sizeof (*dev) + len); full_name = mem + sizeof (*dev); - strcpy (full_name, dev->name); + +#ifdef ENABLE_IPV6 + if (IPv6 == SANE_TRUE) + strcat (full_name, "["); +#endif /* ENABLE_IPV6 */ + + strcat (full_name, dev->name); + +#ifdef ENABLE_IPV6 + if (IPv6 == SANE_TRUE) + strcat (full_name, "]"); +#endif /* ENABLE_IPV6 */ + strcat (full_name, ":"); strcat (full_name, reply.device_list[i]->name); DBG (3, "sane_get_devices: got %s\n", full_name); @@ -723,11 +960,11 @@ sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) { DBG (1, "sane_get_devices: not enough free memory\n"); if (rdev->vendor) - free (rdev->vendor); + free ((void *) rdev->vendor); if (rdev->model) - free (rdev->model); + free ((void *) rdev->model); if (rdev->type) - free (rdev->type); + free ((void *) rdev->type); free (rdev); sanei_w_free (&dev->wire, (WireCodecFunc) sanei_w_get_devices_reply, @@ -756,6 +993,10 @@ sane_open (SANE_String_Const full_name, SANE_Handle * meta_handle) { SANE_Open_Reply reply; const char *dev_name; +#ifdef ENABLE_IPV6 + const char *tmp_name; + SANE_Bool v6addr = SANE_FALSE; +#endif /* ENABLE_IPV6 */ SANE_String nd_name; SANE_Status status; SANE_Word handle; @@ -764,12 +1005,46 @@ sane_open (SANE_String_Const full_name, SANE_Handle * meta_handle) int need_auth; DBG (3, "sane_open(\"%s\")\n", full_name); + +#ifdef ENABLE_IPV6 + /* + * Check whether a numerical IPv6 host was specified + * [2001:42:42::12] <== check for '[' as full_name[0] + * ex: [2001:42:42::12]:test:0 (syntax taken from Apache 2) + */ + if (full_name[0] == '[') + { + v6addr = SANE_TRUE; + tmp_name = strchr (full_name, ']'); + if (!tmp_name) + { + DBG (1, "sane_open: incorrect host address: missing matching ']'\n"); + return SANE_STATUS_INVAL; + } + } + else + tmp_name = full_name; + + dev_name = strchr (tmp_name, ':'); +#else /* !ENABLE_IPV6 */ + + dev_name = strchr (full_name, ':'); +#endif /* ENABLE_IPV6 */ - dev_name = strchr (full_name, ':'); if (dev_name) { #ifdef strndupa +# ifdef ENABLE_IPV6 + if (v6addr == SANE_TRUE) + nd_name = strndupa (full_name + 1, dev_name - full_name - 2); + else + nd_name = strndupa (full_name, dev_name - full_name); + +# else /* !ENABLE_IPV6 */ + nd_name = strndupa (full_name, dev_name - full_name); +# endif /* ENABLE_IPV6 */ + if (!nd_name) { DBG (1, "sane_open: not enough free memory\n"); @@ -778,14 +1053,41 @@ sane_open (SANE_String_Const full_name, SANE_Handle * meta_handle) #else char *tmp; +# ifdef ENABLE_IPV6 + if (v6addr == SANE_TRUE) + tmp = alloca (dev_name - full_name - 2 + 1); + else + tmp = alloca (dev_name - full_name + 1); + +# else /* !ENABLE_IPV6 */ + tmp = alloca (dev_name - full_name + 1); +# endif /* ENABLE_IPV6 */ + if (!tmp) { DBG (1, "sane_open: not enough free memory\n"); return SANE_STATUS_NO_MEM; } + +# ifdef ENABLE_IPV6 + if (v6addr == SANE_TRUE) + { + memcpy (tmp, full_name + 1, dev_name - full_name - 2); + tmp[dev_name - full_name - 2] = '\0'; + } + else + { + memcpy (tmp, full_name, dev_name - full_name); + tmp[dev_name - full_name] = '\0'; + } + +# else /* !ENABLE_IPV6 */ + memcpy (tmp, full_name, dev_name - full_name); tmp[dev_name - full_name] = '\0'; +# endif /* ENABLE_IPV6 */ + nd_name = tmp; #endif ++dev_name; /* skip colon */ @@ -795,7 +1097,26 @@ sane_open (SANE_String_Const full_name, SANE_Handle * meta_handle) /* if no colon interpret full_name as the host name; an empty device name will cause us to open the first device of that host. */ +#ifdef ENABLE_IPV6 + if (v6addr == SANE_TRUE) + { + nd_name = alloca (strlen (full_name) - 2 + 1); + if (!nd_name) + { + DBG (1, "sane_open: not enough free memory\n"); + return SANE_STATUS_NO_MEM; + } + memcpy (nd_name, full_name + 1, strlen (full_name) - 2); + nd_name[strlen (full_name) - 2] = '\0'; + } + else + nd_name = (char *) full_name; + +#else /* !ENABLE_IPV6 */ + nd_name = (char *) full_name; +#endif /* ENABLE_IPV6 */ + dev_name = ""; } DBG (2, "sane_open: host = %s, device = %s\n", nd_name, dev_name); @@ -1136,6 +1457,146 @@ sane_get_parameters (SANE_Handle handle, SANE_Parameters * params) return status; } +#ifdef NET_USES_AF_INDEP +SANE_Status +sane_start (SANE_Handle handle) +{ + Net_Scanner *s = handle; + SANE_Start_Reply reply; + struct sockaddr_in sin; + struct sockaddr *sa; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 sin6; +#endif /* ENABLE_IPV6 */ + SANE_Status status; + int fd, need_auth; + socklen_t len; + u_int16_t port; /* Internet-specific */ + + + DBG (3, "sane_start\n"); + + hang_over = -1; + left_over = -1; + + if (s->data >= 0) + { + DBG (2, "sane_start: data pipe already exists\n"); + return SANE_STATUS_INVAL; + } + + /* Do this ahead of time so in case anything fails, we can + recover gracefully (without hanging our server). */ + + switch (s->hw->addr_used->ai_family) + { + case AF_INET: + len = sizeof (sin); + sa = (struct sockaddr *) &sin; + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + len = sizeof (sin6); + sa = (struct sockaddr *) &sin6; + break; +#endif /* ENABLE_IPV6 */ + default: + DBG (1, "sane_start: unknown address family : %d\n", + s->hw->addr_used->ai_family); + return SANE_STATUS_INVAL; + } + + if (getpeername (s->hw->ctl, sa, &len) < 0) + { + DBG (1, "sane_start: getpeername() failed (%s)\n", strerror (errno)); + return SANE_STATUS_IO_ERROR; + } + + fd = socket (s->hw->addr_used->ai_family, SOCK_STREAM, 0); + if (fd < 0) + { + DBG (1, "sane_start: socket() failed (%s)\n", strerror (errno)); + return SANE_STATUS_IO_ERROR; + } + + DBG (3, "sane_start: remote start\n"); + sanei_w_call (&s->hw->wire, SANE_NET_START, + (WireCodecFunc) sanei_w_word, &s->handle, + (WireCodecFunc) sanei_w_start_reply, &reply); + do + { + status = reply.status; + port = reply.port; + if (reply.byte_order == 0x1234) + { + server_big_endian = 0; + DBG (1, "sane_start: server has little endian byte order\n"); + } + else + { + server_big_endian = 1; + DBG (1, "sane_start: server has big endian byte order\n"); + } + + need_auth = (reply.resource_to_authorize != 0); + if (need_auth) + { + DBG (3, "sane_start: auth required\n"); + do_authorization (s->hw, reply.resource_to_authorize); + + sanei_w_free (&s->hw->wire, + (WireCodecFunc) sanei_w_start_reply, &reply); + + sanei_w_set_dir (&s->hw->wire, WIRE_DECODE); + + sanei_w_start_reply (&s->hw->wire, &reply); + + continue; + } + sanei_w_free (&s->hw->wire, (WireCodecFunc) sanei_w_start_reply, + &reply); + if (need_auth && !s->hw->auth_active) + return SANE_STATUS_CANCELLED; + + if (status != SANE_STATUS_GOOD) + { + DBG (1, "sane_start: remote start failed (%s)\n", + sane_strstatus (status)); + close (fd); + return status; + } + } + while (need_auth); + DBG (3, "sane_start: remote start finished, data at port %hu\n", port); + + switch (s->hw->addr_used->ai_family) + { + case AF_INET: + sin.sin_port = htons (port); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sin6.sin6_port = htons (port); + break; +#endif /* ENABLE_IPV6 */ + } + + if (connect (fd, sa, len) < 0) + { + DBG (1, "sane_start: connect() failed (%s)\n", strerror (errno)); + close (fd); + return SANE_STATUS_IO_ERROR; + } + shutdown (fd, 1); + s->data = fd; + s->reclen_buf_offset = 0; + s->bytes_remaining = 0; + DBG (3, "sane_start: done (%s)\n", sane_strstatus (status)); + return status; +} + +#else /* !NET_USES_AF_INDEP */ + SANE_Status sane_start (SANE_Handle handle) { @@ -1240,6 +1701,8 @@ sane_start (SANE_Handle handle) DBG (3, "sane_start: done (%s)\n", sane_strstatus (status)); return status; } +#endif /* NET_USES_AF_INDEP */ + SANE_Status sane_read (SANE_Handle handle, SANE_Byte * data, SANE_Int max_length, diff --git a/backend/net.h b/backend/net.h index 69327421a..7d72fdd64 100644 --- a/backend/net.h +++ b/backend/net.h @@ -1,5 +1,8 @@ /* sane - Scanner Access Now Easy. Copyright (C) 1997 David Mosberger-Tang + Copyright (C) 2003 Julien BLACHE + AF-independent code + IPv6 + This file is part of the SANE package. This program is free software; you can redistribute it and/or @@ -44,12 +47,18 @@ #include #include "../include/sane/sanei_wire.h" +#include "../include/sane/config.h" typedef struct Net_Device { struct Net_Device *next; const char *name; +#if defined (HAVE_GETADDRINFO) && defined (HAVE_GETNAMEINFO) + struct addrinfo *addr; + struct addrinfo *addr_used; +#else struct sockaddr addr; +#endif /* HAVE_GETADDRINFO && HAVE_GETNAMEINFO */ int ctl; /* socket descriptor (or -1) */ Wire wire; int auth_active; diff --git a/configure b/configure index 161795029..16c2cb251 100755 --- a/configure +++ b/configure @@ -1011,6 +1011,8 @@ Optional Features: --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-warnings turn on tons of compiler warnings (GCC only) + --enable-ipv6 enable IPv6 (with IPv4) support + --disable-ipv6 disable IPv6 support --enable-static=PKGS build static libraries default=no --enable-shared=PKGS build shared libraries default=yes --enable-fast-install=PKGS optimize for fast installation default=yes @@ -8013,9 +8015,12 @@ rm -f conftest.mmap + + for ac_func in atexit inet_addr inet_aton inet_ntoa ioperm mkdir \ scsireq_enter strftime strstr strtod \ - cfmakeraw tcsendbreak strcasecmp strncasecmp _portaccess + cfmakeraw tcsendbreak strcasecmp strncasecmp _portaccess \ + getaddrinfo getnameinfo do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` echo "$as_me:$LINENO: checking for $ac_func" >&5 @@ -8298,6 +8303,90 @@ _ACEOF fi +if test "$ac_cv_func_getnameinfo" = "yes" && test "$ac_cv_func_getaddrinfo" = "yes" ; then + +echo "$as_me:$LINENO: checking whether to enable IPv6" >&5 +echo $ECHO_N "checking whether to enable IPv6... $ECHO_C" >&6 +# Check whether --enable-ipv6 or --disable-ipv6 was given. +if test "${enable_ipv6+set}" = set; then + enableval="$enable_ipv6" + case "$enableval" in + no) + echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6 + ipv6=no + ;; + *) echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6 + +cat >>confdefs.h <<\_ACEOF +#define ENABLE_IPV6 1 +_ACEOF + + ipv6=yes + ;; + esac +else + cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + + #define INET6 + #include + #include +int +main () +{ + + /* AF_INET6 available check */ + if (socket(AF_INET6, SOCK_STREAM, 0) < 0) + exit(1); + else + exit(0); + + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6 + +cat >>confdefs.h <<\_ACEOF +#define ENABLE_IPV6 1 +_ACEOF + + ipv6=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6 + ipv6=no +fi +rm -f conftest.$ac_objext conftest.$ac_ext +fi; + +else + ipv6="no" +fi + # Check whether --enable-static or --disable-static was given. if test "${enable_static+set}" = set; then @@ -9475,7 +9564,7 @@ test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes case $host in *-*-irix6*) # Find out which ABI we are using. - echo '#line 9478 "configure"' > conftest.$ac_ext + echo '#line 9567 "configure"' > conftest.$ac_ext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -10004,7 +10093,7 @@ chmod -w . save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -o out/conftest2.$ac_objext" compiler_c_o=no -if { (eval echo configure:10007: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.$ac_objext; then +if { (eval echo configure:10096: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.$ac_objext; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings if test -s out/conftest.err; then @@ -11836,7 +11925,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext < conftest.$ac_ext < /dev/null 2>&1 ; then AC_DEFINE(DISABLE_LINUX_SG_IO, 1, [Should we disable SCSI generic v3?]) fi +dnl check for IPv6 (can be overriden by --enable-ipv6) +if test "$ac_cv_func_getnameinfo" = "yes" && test "$ac_cv_func_getaddrinfo" = "yes" ; then + SANE_CHECK_IPV6 +else + ipv6="no" +fi + dnl *********************************************************************** dnl initialize libtool dnl *********************************************************************** @@ -433,6 +441,8 @@ echo "Configuration: `eval eval echo ${sysconfdir}`" echo "Libraries: `eval eval echo ${libdir}`" echo "Binaries: `eval eval echo ${bindir}` and `eval eval echo ${sbindir}`" echo "Manpages: `eval eval echo ${mandir}`" +echo "Network parameters:" +echo "IPv6 support: `eval eval echo ${ipv6}`" if test "$SANE_CONFIG_PATH" != "no" ; then SANE_INSTALLED_VERSION=`$SANE_CONFIG_PATH --version` diff --git a/doc/sane-net.man b/doc/sane-net.man index b247bc9df..7d3ac2a13 100644 --- a/doc/sane-net.man +++ b/doc/sane-net.man @@ -20,7 +20,7 @@ This backend expects device names of the form: .PP Where .I host -is the name of the (remote-) host and +is the name (or IP address) of the (remote-) host and .I device is the name of the device on this host that should be addressed. If the device name does not contain a colon (:), then the entire string @@ -28,35 +28,50 @@ is treated as the .I device string for the default host. The default host is the host listed last in the configuration file (see below). +.PP +An IPv6 address can be specified enclosed in square brackets: +.PP +.RS +.IR [::1] : device +.RE .SH CONFIGURATION The contents of the -.IR net.conf . -file is a list of host names that should be contacted for +.IR net.conf +file is a list of host names (or IP addresses) that should be contacted for scan requests. Empty lines and lines starting with a hash mark (#) are -ignored. A sample configuration file is shown below: +ignored. Note that IPv6 addresses in this file do not need to be enclosed +in square brackets. A sample configuration file is shown below: .PP .RS scan-server.somedomain.firm .br +192.168.0.1 +.br # this is a comment .br localhost +.br +::1 .RE .PP -The above list of host names can be extended at run-time using environment +The above list of hosts can be extended at run-time using environment variable .BR SANE_NET_HOSTS . -This environment variable is a colon-separated list of hostnames that -should be contacted in addition to the hosts mentioned in the -configuration file. For example, a user could set the environment +This environment variable is a colon-separated list of hostnames or IP +addresses that should be contacted in addition to the hosts mentioned in +the configuration file. For example, a user could set the environment variable to the string: .PP .RS -new.scanner.com:scanner.univ.edu +new.scanner.com:[::1]:192.168.0.2:scanner.univ.edu .RE .PP To request that hosts .I new.scanner.com +, +.I [::1] +, +.I 192.168.0.2 and .I scanner.univ.edu are contacted in addition to the hosts listed above. @@ -65,7 +80,7 @@ For this backend to function properly, it is also necessary to define the .B sane service in .IR /etc/services . -At present, the +The .B sane service should be defined using a line of the following form: .PP @@ -103,7 +118,8 @@ to "/tmp/config:" would result in directories "tmp/config", ".", and "@CONFIGDIR@" being searched (in this order). .TP .B SANE_NET_HOSTS -A colon-separated list of host names to be contacted by this backend. +A colon-separated list of host names or IP addresses to be contacted by this +backend. .TP .B SANE_DEBUG_NET If the library was compiled with debug support enabled, this diff --git a/doc/saned.man b/doc/saned.man index 9bead8f90..b1f7eef16 100644 --- a/doc/saned.man +++ b/doc/saned.man @@ -68,15 +68,22 @@ scan-client.somedomain.firm # this is a comment .br 192.168.0.1 +.br +::1 .RE .PP The case of the host names does not matter, so AHost.COM is considered -identical to ahost.com. +identical to ahost.com. IPv6 addresses should always be specified in their +compressed form. For .B saned to work properly, it is also necessary to add a configuration line to .IR /etc/inetd.conf . +Note that your inetd must support IPv6 if you +want to connect to saned over IPv6 ; xinetd and openbsd-inetd are known to +support IPv6, check the documentation for your inetd daemon. +.PP The configuration line normally looks like this: .PP .RS diff --git a/frontend/saned.c b/frontend/saned.c index b09b7a0a0..a219641b6 100644 --- a/frontend/saned.c +++ b/frontend/saned.c @@ -1,6 +1,9 @@ /* sane - Scanner Access Now Easy. Copyright (C) 1997 Andreas Beck Copyright (C) 2001, 2002 Henning Meier-Geinitz + Copyright (C) 2003 Julien BLACHE + AF-independent + IPv6 code + This file is part of the SANE package. SANE is free software; you can redistribute it and/or modify it under @@ -54,6 +57,7 @@ #include #include +#include #include #include #include @@ -74,12 +78,26 @@ # define IN_LOOPBACK(addr) (addr == 0x7f000001L) #endif +#ifdef ENABLE_IPV6 +# define SANE_IN6_IS_ADDR_LOOPBACK(a) \ + (((const uint32_t *) (a))[0] == 0 \ + && ((const uint32_t *) (a))[1] == 0 \ + && ((const uint32_t *) (a))[2] == 0 \ + && ((const uint32_t *) (a))[3] == htonl (1)) +#endif /* ENABLE_IPV6 */ + #ifndef MAXHOSTNAMELEN # define MAXHOSTNAMELEN 120 #endif #define SANED_CONFIG_FILE "saned.conf" +#if defined(HAVE_GETADDRINFO) && defined (HAVE_GETNAMEINFO) +# define SANED_USES_AF_INDEP +#else +# undef ENABLE_IPV6 +#endif /* HAVE_GETADDRINFO && HAVE_GETNAMEINFO */ + typedef struct { u_int inuse:1; /* is this handle in use? */ @@ -107,8 +125,14 @@ byte_order; 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_hostname; +static char *remote_ip; + +#ifdef SANED_USES_AF_INDEP +static struct sockaddr_storage 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" @@ -371,6 +395,300 @@ decode_handle (Wire * w, const char *op) } /* 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 + char *remote_ipv4 = NULL; /* in case we have an IPv4-mapped address (eg ::ffff:127.0.0.1) */ + struct addrinfo *remote_ipv4_addr = NULL; +#endif /* ENABLE_IPV6 */ + char config_line[1024]; + char hostname[MAXHOSTNAMELEN]; + +#ifdef ENABLE_IPV6 + SANE_Bool IPv4map = SANE_FALSE; +#endif /* ENABLE_IPV6 */ + + int len; + FILE *fp; + + /* Get address of remote host */ + remote_address_len = sizeof (remote_address); + if (getpeername (fd, (struct sockaddr *) &remote_address, (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 ((struct sockaddr *) &remote_address, 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 + if (strncmp (remote_ip, "::ffff:", 7) == 0) + { + 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 = (struct sockaddr_in *) &remote_address; +#ifdef ENABLE_IPV6 + sin6 = (struct sockaddr_in6 *) &remote_address; +#endif /* ENABLE_IPV6 */ + + switch (remote_address.ss_family) + { + case AF_INET: + if (IN_LOOPBACK (ntohl (sin->sin_addr.s_addr))) + { + DBG (DBG_MSG, + "check_host: remote host is IN_LOOPBACK: access granted\n"); + return SANE_STATUS_GOOD; + } + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + if (SANE_IN6_IS_ADDR_LOOPBACK (sin6->sin6_addr.s6_addr)) + { + DBG (DBG_MSG, + "check_host: remote host is IN6_LOOPBACK: access granted\n"); + return SANE_STATUS_GOOD; + } + break; +#endif /* ENABLE_IPV6 */ + default: + break; + } + + DBG (DBG_DBG, "check_host: remote host is not IN_LOOPBACK" +#ifdef ENABLE_IPV6 + " nor IN6_LOOPBACK" +#endif /* ENABLE_IPV6 */ + "\n"); + + + /* Get name of local host */ + if (gethostname (hostname, sizeof (hostname)) < 0) + { + DBG (DBG_ERR, "check_host: gethostname failed: %s\n", strerror (errno)); + return SANE_STATUS_INVAL; + } + DBG (DBG_DBG, "check_host: local hostname: %s\n", hostname); + + /* Get local addresses */ + memset (&hints, 0, sizeof (hints)); + hints.ai_flags = AI_CANONNAME; +#ifdef ENABLE_IPV6 + hints.ai_family = PF_UNSPEC; +#else + hints.ai_family = PF_INET; +#endif /* ENABLE_IPV6 */ + + err = getaddrinfo (hostname, NULL, &hints, &res); + if (err) + { + DBG (DBG_ERR, "check_host: getaddrinfo failed: %s\n", + gai_strerror (err)); + 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 ((strcmp (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); + + return SANE_STATUS_GOOD; + } + } + + freeaddrinfo (res); + + 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, + sizeof (config_line), fp)) + { + DBG (DBG_DBG, "check_host: config file line: `%s'\n", config_line); + if (config_line[0] == '#') /* ignore line comments */ + continue; + len = strlen (config_line); + + if (!len) + continue; /* ignore empty lines */ + + 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 (strcmp (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); + } +#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 ((strcmp (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); + } + } + } + } + + 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) { @@ -391,12 +709,13 @@ check_host (int fd) 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_hostname = strdup (r_hostname); + remote_ip = strdup (r_hostname); DBG (DBG_WARN, "check_host: access by remote host: %s\n", - remote_hostname); + remote_ip); /* Save remote address for check of control and data connections */ memcpy (&remote_address, &sin.sin_addr, sizeof (remote_address)); @@ -530,6 +849,8 @@ check_host (int fd) return SANE_STATUS_ACCESS_DENIED; } +#endif /* SANED_USES_AF_INDEP */ + static int init (Wire * w) { @@ -543,9 +864,11 @@ init (Wire * w) status = check_host (w->io.fd); if (status != SANE_STATUS_GOOD) { - DBG (DBG_WARN, "init: access by host %s denied\n", remote_hostname); + 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) @@ -583,8 +906,8 @@ init (Wire * w) reply.version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, SANEI_NET_PROTOCOL_VERSION); - DBG (DBG_WARN, "init: access by %s@%s accepted\n", - default_username, remote_hostname); + DBG (DBG_WARN, "init: access granted to %s@%s\n", + default_username, remote_ip); if (status == SANE_STATUS_GOOD) { @@ -612,6 +935,108 @@ init (Wire * w) return 0; } +#ifdef SANED_USES_AF_INDEP +static int +start_scan (Wire * w, int h, SANE_Start_Reply * reply) +{ + struct sockaddr_storage ss; + struct sockaddr_in *sin; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *sin6; +#endif /* ENABLE_IPV6 */ + SANE_Handle be_handle; + int fd, len; + + be_handle = handle[h].handle; + + len = sizeof (ss); + if (getsockname (w->io.fd, (struct sockaddr *) &ss, (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.ss_family, 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.ss_family) + { + case AF_INET: + sin = (struct sockaddr_in *) &ss; + sin->sin_port = 0; + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *) &ss; + sin6->sin6_port = 0; + break; +#endif /* ENABLE_IPV6 */ + default: + break; + } + + if (bind (fd, (struct sockaddr *) &ss, len) < 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 *) &ss, (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.ss_family) + { + case AF_INET: + sin = (struct sockaddr_in *) &ss; + reply->port = ntohs (sin->sin_port); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *) &ss; + 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) { @@ -677,6 +1102,7 @@ start_scan (Wire * w, int h, SANE_Start_Reply * reply) return fd; } +#endif /* SANED_USES_AF_INDEP */ static int store_reclen (SANE_Byte * buf, size_t buf_size, int i, size_t reclen) @@ -958,7 +1384,7 @@ process_request (Wire * w) } else { - DBG (DBG_MSG, "process_request: access to resource `%s' accepted\n", + DBG (DBG_MSG, "process_request: access to resource `%s' granted\n", resource); free (resource); memset (&reply, 0, sizeof (reply)); /* avoid leaking bits */ @@ -1089,6 +1515,51 @@ process_request (Wire * w) sanei_w_reply (w, (WireCodecFunc) sanei_w_start_reply, &reply); +#ifdef SANED_USES_AF_INDEP + if (reply.status == SANE_STATUS_GOOD) + { + struct sockaddr_storage ss; + char text_addr[64]; + int len; + int error; + + DBG (DBG_MSG, "process_request: waiting for data connection\n"); + data_fd = accept (fd, 0, 0); + close (fd); + + /* Get address of remote host */ + len = sizeof (ss); + if (getpeername (data_fd, (struct sockaddr *) &ss, (socklen_t *) &len) < 0) + { + DBG (DBG_ERR, "process_request: getpeername failed: %s\n", + strerror (errno)); + return; + } + + 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; + } + + 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; + quit (0); + } + +#else /* !SANED_USES_AF_INDEP */ + if (reply.status == SANE_STATUS_GOOD) { struct sockaddr_in sin; @@ -1126,6 +1597,7 @@ process_request (Wire * w) 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) { @@ -1169,6 +1641,274 @@ process_request (Wire * w) } } +#ifdef SANED_USES_AF_INDEP +int +main (int argc, char *argv[]) +{ + int fd = -1; + int on = 1; +#ifdef TCP_NODELAY + int level = -1; +#endif + + debug = DBG_WARN; + openlog ("saned", LOG_PID | LOG_CONS, LOG_DAEMON); + + prog_name = strrchr (argv[0], '/'); + if (prog_name) + ++prog_name; + else + prog_name = argv[0]; + + byte_order.w = 0; + byte_order.ch = 1; + + sanei_w_init (&wire, sanei_codec_bin_init); + wire.io.read = read; + wire.io.write = write; + + if (argc == 2 && + (strncmp (argv[1], "-d", 2) == 0 || strncmp (argv[1], "-s", 2) == 0)) + { + /* don't operate in daemon mode: wait for connection request: */ + struct addrinfo *res; + struct addrinfo *resp; + struct addrinfo hints; + struct sockaddr_in *sin; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *sin6; +#endif /* ENABLE_IPV6 */ + struct pollfd *fds = NULL; + struct pollfd *fdp = NULL; + int nfds; + int err; + int i; + short sane_port = htons (6566); + + if (argv[1][2]) + debug = atoi (argv[1] + 2); + if (strncmp (argv[1], "-d", 2) == 0) + log_to_syslog = SANE_FALSE; + + DBG (DBG_WARN, "main: starting debug mode (level %d)\n", debug); + + DBG (DBG_DBG, + "main: trying to get port for service `sane' (getaddrinfo)\n"); + + memset (&hints, 0, sizeof (struct addrinfo)); + + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + err = getaddrinfo (NULL, "sane", &hints, &res); + if (err) + { + /* + * You cannot pass (NULL, NULL, &hints, &res) to getaddrinfo, + * so request a good-old well known service, and change the port + * afterwards as a workaround + */ + err = getaddrinfo (NULL, "telnet", &hints, &res); + if (err) + { + DBG (DBG_ERR, "main: getaddrinfo() failed: %s\n", gai_strerror (err)); + exit (1); + } + else + { + DBG (DBG_WARN, "main: \"sane\" service unknown on your host; you should add\n"); + DBG (DBG_WARN, "main: sane 6566/tcp saned # SANE network scanner daemon\n"); + DBG (DBG_WARN, "main: to your /etc/services file (or equivalent). Proceeding anyway.\n"); + + for (resp = res; resp != NULL; resp = resp->ai_next) + { + switch (resp->ai_family) + { + case AF_INET: + sin = (struct sockaddr_in *) resp->ai_addr; + sin->sin_port = sane_port; + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + sin6 = (struct sockaddr_in6 *) resp->ai_addr; + sin6->sin6_port = sane_port; + break; +#endif /* ENABLE_IPV6 */ + } + } + } + } + + for (resp = res, nfds = 0; resp != NULL; resp = resp->ai_next, nfds++) + ; + + fds = malloc (nfds * sizeof (struct pollfd)); + + if (fds == NULL) + { + DBG (DBG_ERR, "main: not enough memory for fds\n"); + freeaddrinfo (res); + exit (1); + } + + for (resp = res, i = 0, fdp = fds; resp != NULL; resp = resp->ai_next, i++, fdp++) + { +#ifdef ENABLE_IPV6 + if ((resp->ai_family != AF_INET) && (resp->ai_family != AF_INET6)) +#else + if (resp->ai_family != AF_INET) +#endif /* ENABLE_IPV6 */ + { + fdp--; + nfds--; + continue; + } + + DBG (DBG_DBG, "main: [%d] socket ()\n", i); + fd = socket (resp->ai_family, SOCK_STREAM, 0); + + DBG (DBG_DBG, "main: [%d] setsockopt ()\n", i); + if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on))) + DBG (DBG_ERR, "main: [%d] failed to put socket in SO_REUSEADDR mode (%s)\n", + i, strerror (errno)); + + DBG (DBG_DBG, "main: [%d] bind ()\n", i); + if (bind (fd, resp->ai_addr, resp->ai_addrlen) < 0) + { + /* + * binding a socket may fail, eg if we already + * to the IPv6 addr returned by getaddrinfo (usually the first one), + * or if IPv6 isn't supported, but saned was built with IPv6 support + */ + DBG (DBG_ERR, "main: [%d] bind failed: %s\n", i, strerror (errno)); + + close (fd); + + nfds--; + fdp--; + continue; + } + + DBG (DBG_DBG, "main: [%d] listen ()\n", i); + if (listen (fd, 1) < 0) + { + DBG (DBG_ERR, "main: [%d] listen failed: %s\n", i, strerror (errno)); + exit (1); + } + + fdp->fd = fd; + fdp->events = POLLIN; + } + + resp = NULL; + freeaddrinfo (res); + + if (nfds <= 0) + { + DBG (DBG_ERR, "main: couldn't bind an address. Exiting.\n"); + exit (1); + } + + DBG (DBG_MSG, "main: waiting for control connection\n"); + + while (1) + { + if (poll (fds, nfds, -1) < 0) + { + if (errno == EINTR) + continue; + else + { + DBG (DBG_ERR, "main: poll failed: %s\n", strerror (errno)); + free (fds); + exit (1); + } + } + + for (i = 0, fdp = fds; i < nfds; i++, fdp++) + { + if (! (fdp->revents & POLLIN)) + continue; + + wire.io.fd = accept (fdp->fd, 0, 0); + if (wire.io.fd < 0) + { + DBG (DBG_ERR, "main: accept failed: %s", strerror (errno)); + free (fds); + exit (1); + } + + for (i = 0, fdp = fds; i < nfds; i++, fdp++) + close (fdp->fd); + + free (fds); + + break; + } + break; + } + } + else + /* use filedescriptor opened by inetd: */ +#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 (argc == 2) + { + wire.io.fd = _impsockhandle (atoi (argv[1]), 0); + if (wire.io.fd == -1) + perror ("impsockhandle"); + } + else +#endif /* HAVE_OS2_H */ + wire.io.fd = 1; + + 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, "main: 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, "main: failed to put socket in TCP_NODELAY mode (%s)", + strerror (errno)); +#endif /* !TCP_NODELAY */ + +/* define the version string depending on which network code is used */ +#ifdef ENABLE_IPV6 + DBG (DBG_WARN, "saned (AF-indep+IPv6) from %s ready\n", PACKAGE_STRING); +#else + DBG (DBG_WARN, "saned (AF-indep) from %s ready\n", PACKAGE_STRING); +#endif /* ENABLE_IPV6 */ + + if (init (&wire) < 0) + quit (0); + + while (1) + { + reset_watchdog (); + process_request (&wire); + } +} + +#else /* !SANED_USES_AF_INDEP */ + int main (int argc, char *argv[]) { @@ -1287,7 +2027,7 @@ main (int argc, char *argv[]) p = getprotobyname ("tcp"); if (p == 0) { - DBG (DBG_WARN, "main:: cannot look up `tcp' protocol number"); + DBG (DBG_WARN, "main: cannot look up `tcp' protocol number"); } else level = p->p_proto; @@ -1310,3 +2050,4 @@ main (int argc, char *argv[]) process_request (&wire); } } +#endif /* SANED_USES_AF_INDEP */ diff --git a/include/sane/config.h.in b/include/sane/config.h.in index 91fd25815..ffbc2a91f 100644 --- a/include/sane/config.h.in +++ b/include/sane/config.h.in @@ -11,6 +11,9 @@ /* Should we disable SCSI generic v3? */ #undef DISABLE_LINUX_SG_IO +/* Define to 1 if the system supports IPv6 */ +#undef ENABLE_IPV6 + /* Define to 1 if you have `alloca', as a function or macro. */ #undef HAVE_ALLOCA @@ -51,9 +54,15 @@ /* Define to 1 if you have the header file. */ #undef HAVE_FCNTL_H +/* Define to 1 if you have the `getaddrinfo' function. */ +#undef HAVE_GETADDRINFO + /* Define to 1 if you have the `getenv' function. */ #undef HAVE_GETENV +/* Define to 1 if you have the `getnameinfo' function. */ +#undef HAVE_GETNAMEINFO + /* Define to 1 if you have the `getpagesize' function. */ #undef HAVE_GETPAGESIZE