зеркало из https://github.com/mozilla/pjs.git
1760 строки
43 KiB
C
1760 строки
43 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||
*
|
||
* The contents of this file are subject to the Netscape Public License
|
||
* Version 1.0 (the "NPL"); you may not use this file except in
|
||
* compliance with the NPL. You may obtain a copy of the NPL at
|
||
* http://www.mozilla.org/NPL/
|
||
*
|
||
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
||
* for the specific language governing rights and limitations under the
|
||
* NPL.
|
||
*
|
||
* The Initial Developer of this code under the NPL is Netscape
|
||
* Communications Corporation. Portions created by Netscape are
|
||
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
||
* Reserved.
|
||
*/
|
||
|
||
/*
|
||
* unix-dns.c --- portable nonblocking DNS for Unix
|
||
* Created: Jamie Zawinski <jwz@netscape.com>, 19-Dec-96.
|
||
*/
|
||
|
||
#if defined(XP_UNIX) && defined(UNIX_ASYNC_DNS)
|
||
|
||
static void dns_socks_kludge(void);
|
||
|
||
/* Todo:
|
||
|
||
= Should we actually use the CHANGING_ARGV_WORKS code, on systems where
|
||
that is true, or just always do the exec() hack? If we use it, it
|
||
needs to be debugged more, and the exact set of systems on which it
|
||
works needs to be determined. (Works on: SunOS 4, Linux, AIX, OSF;
|
||
doesn't work on: Solaris, Irix, UnixWare, HPUX.) (But HPUX has an
|
||
undocumented ioctl that lets you change a proc's name.) (Unix sucks
|
||
a lot.)
|
||
*/
|
||
|
||
|
||
/* Compile-time options:
|
||
-DDNS_EXPLICITLY_FLUSH_PIPES
|
||
to do ioctls() to flush out the pipes after each write().
|
||
at one point I thought I needed this, but it doesn't
|
||
actually seem to be necessary.
|
||
|
||
-DGETHOSTBYNAME_DELAY=N
|
||
to insert an artificial delay of N seconds before each
|
||
call to gethostbyname (in order to simulate DNS lossage.)
|
||
|
||
-DNO_SOCKS_NS_KLUDGE
|
||
Set this to *disable* the $SOCKS_NS kludge. Otherwise,
|
||
that environment variable will be consulted for use as an
|
||
alternate DNS root. It's historical; don't ask me...
|
||
|
||
-DRANDOMIZE_ADDRESSES
|
||
This code only deals with one IP address per host; if this
|
||
is set, then that host will be randomly selected from among
|
||
all the addresses of the host; otherwise, it will be the
|
||
first address.
|
||
|
||
-DCHANGING_ARGV_WORKS
|
||
(This option isn't fully implemented yet, and can't work
|
||
on some systems anyway.)
|
||
|
||
The code in this file wants to fork(), and then change the
|
||
name of the second process as reported by `ps'. Some systems
|
||
let you do that by overwriting argv[0], but some do not.
|
||
|
||
If this isn't defined, we effect this change by re-execing
|
||
after the fork (since you can pass a new name that way.)
|
||
This depends on exec(argv[0] ... ) working, which it might
|
||
not if argv[0] has a non-absolute path in it, and the user
|
||
has played stupid games with $PATH or something. Those
|
||
users probably deserve to lose.
|
||
|
||
-DSTANDALONE
|
||
to include a main() for interactive command-line testing.
|
||
|
||
-DPROC3_DEBUG_PRINT
|
||
to cause proc3 (the gethostbyname() processes) to print
|
||
diagnostics to stderr.
|
||
|
||
-DPROC2_DEBUG_PRINT
|
||
to cause proc2 (the looping, dispatching process) to print
|
||
diagnostics to stderr.
|
||
|
||
-DPROC1_DEBUG_PRINT
|
||
to cause proc1 (the main thread) to print diagnostics to
|
||
stderr. */
|
||
|
||
|
||
#include "unix-dns.h"
|
||
|
||
#include <stdlib.h>
|
||
#include <unistd.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <assert.h>
|
||
#include <netdb.h> /* for gethostbyname() */
|
||
#include <signal.h> /* for kill() */
|
||
#include <sys/param.h> /* for MAXHOSTNAMELEN */
|
||
#include <sys/wait.h> /* for waitpid() */
|
||
#include <sys/time.h> /* for gettimeofday() */
|
||
#include <signal.h> /* for signal() and the signal names */
|
||
|
||
#ifdef AIX
|
||
#include <sys/select.h> /* for fd_set */
|
||
#endif
|
||
|
||
#ifndef NO_SOCKS_NS_KLUDGE
|
||
# include <netinet/in.h> /* for sockaddr_in (from inet.h) */
|
||
# include <arpa/inet.h> /* for in_addr (from nameser.h) */
|
||
# include <arpa/nameser.h> /* for MAXDNAME (from resolv.h) */
|
||
# include <resolv.h> /* for res_init() and _res */
|
||
#endif
|
||
|
||
#if !defined(__irix)
|
||
/* Looks like Irix is the only one that has getdtablehi()? */
|
||
# define getdtablehi() getdtablesize()
|
||
#endif
|
||
|
||
#if !defined(__irix)
|
||
/* Looks like Irix and Solaris 5.5 are the only ones that have
|
||
getdtablesize()... but since Solaris 5.4 doesn't have it,
|
||
Solaris is out. (If you find a system that doesn't even
|
||
have FD_SETSIZE, just grab your ankles and set it to 255.)
|
||
*/
|
||
# define getdtablesize() (FD_SETSIZE)
|
||
#endif
|
||
|
||
|
||
#ifdef DNS_EXPLICITLY_FLUSH_PIPES
|
||
# include <stropts.h> /* for I_FLUSH and FLUSHRW */
|
||
#endif
|
||
|
||
/* Rumor has it that some systems lie about this value, and actually accept
|
||
host names longer than sys/param.h claims. But it's definitely the case
|
||
that some systems (and again I don't know which) crash if you pass a
|
||
too-long hostname into gethostbyname(). So better safe than sorry.
|
||
*/
|
||
#ifndef MAXHOSTNAMELEN
|
||
# define MAXHOSTNAMELEN 64
|
||
#endif
|
||
|
||
#define ASSERT(x) assert(x)
|
||
|
||
#ifdef DNS_EXPLICITLY_FLUSH_PIPES
|
||
# define FLUSH(fd) ioctl(fd, I_FLUSH, FLUSHRW)
|
||
#else
|
||
# define FLUSH(x)
|
||
#endif
|
||
|
||
/* We use fdopen() to get the more convenient stdio interface on the pipes
|
||
we use; but we don't want the stdio library to provide *any* buffering;
|
||
just use the buffering that the pipe itself provides, so that when
|
||
select() says there is no input available, fgetc() agrees.
|
||
*/
|
||
#define SET_BUFFERING(file) setvbuf((file), NULL, _IONBF, 0)
|
||
|
||
|
||
#ifdef RANDOMIZE_ADDRESSES
|
||
/* Unix is Random. */
|
||
# if defined(UNIXWARE) || defined(_INCLUDE_HPUX_SOURCE) || (defined(__sun) && defined(__svr4__))
|
||
# define RANDOM rand
|
||
# define SRANDOM srand
|
||
# else /* !BSD-incompatible-Unixes */
|
||
# define RANDOM random
|
||
# define SRANDOM srandom
|
||
# endif /* !BSD-incompatible-Unixes */
|
||
#endif /* RANDOMIZE_ADDRESSES */
|
||
|
||
|
||
/* Way kludgy debugging/logging interface, since gdb's support
|
||
for debugging fork'ed processes is pathetic.
|
||
*/
|
||
#define LOG_PROCn(PROC,PREFIX,BUF,SUFFIX,QL) do{\
|
||
fprintf(stderr, \
|
||
"\t" PROC " (%lu): " PREFIX ": (ql=%ld) %s" SUFFIX, \
|
||
((unsigned long) getpid()), QL, BUF); \
|
||
} while(0)
|
||
|
||
#ifdef PROC3_DEBUG_PRINT
|
||
# define LOG_PROC3(PREFIX,BUF,SUFFIX) LOG_PROCn("proc3",PREFIX,BUF,SUFFIX,0L)
|
||
#else
|
||
# define LOG_PROC3(PREFIX,BUF,SUFFIX)
|
||
#endif
|
||
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
# define LOG_PROC2(PREFIX,BUF,SUFFIX) LOG_PROCn("proc2",PREFIX,BUF,SUFFIX,\
|
||
proc2_queue_length())
|
||
#else
|
||
# define LOG_PROC2(PREFIX,BUF,SUFFIX)
|
||
#endif
|
||
|
||
#ifdef PROC1_DEBUG_PRINT
|
||
# define LOG_PROC1(PREFIX,BUF,SUFFIX) LOG_PROCn("proc1",PREFIX,BUF,SUFFIX,\
|
||
proc1_queue_length())
|
||
#else
|
||
# define LOG_PROC1(PREFIX,BUF,SUFFIX)
|
||
#endif
|
||
|
||
|
||
/* The internal status codes that are used; these follow the basic
|
||
SMTP/NNTP model of three-digit codes.
|
||
*/
|
||
#define DNS_STATUS_GETHOSTBYNAME_OK 101 /* proc3 -> proc2 */
|
||
#define DNS_STATUS_LOOKUP_OK 102 /* proc2 -> proc1 */
|
||
#define DNS_STATUS_KILLED_OK 103 /* proc2 -> proc1 */
|
||
#define DNS_STATUS_LOOKUP_STARTED 201 /* proc2 -> proc1 */
|
||
#define DNS_STATUS_GETHOSTBYNAME_FAILED 501 /* proc3 -> proc2 */
|
||
#define DNS_STATUS_LOOKUP_FAILED 502 /* proc2 -> proc1 */
|
||
#define DNS_STATUS_LOOKUP_NOT_STARTED 503 /* proc2 -> proc1 */
|
||
#define DNS_STATUS_KILL_FAILED 504 /* proc2 -> proc1 */
|
||
#define DNS_STATUS_UNIMPLEMENTED 601 /* unimplemented (ha ha) */
|
||
#define DNS_STATUS_INTERNAL_ERROR 602 /* assertion failure */
|
||
|
||
#define DNS_PROC2_NAME "(dns helper)"
|
||
|
||
|
||
static char *
|
||
string_trim(char *s)
|
||
{
|
||
char *s2;
|
||
if (!s) return 0;
|
||
s2 = s + strlen(s) - 1;
|
||
while (s2 > s && (*s2 == '\n' || *s2 == '\r' || *s2 == ' ' || *s2 == '\t'))
|
||
*s2-- = 0;
|
||
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
|
||
s++;
|
||
return s;
|
||
}
|
||
|
||
|
||
/* Process 3.
|
||
Inherits a string (hostname) from the spawner.
|
||
Writes "1xx: a.b.c.d\n" or "5xx: error msg\n".
|
||
*/
|
||
|
||
static void
|
||
blocking_gethostbyname (const char *name, int out_fd,
|
||
unsigned long random_number)
|
||
{
|
||
char buf[MAXHOSTNAMELEN + 100];
|
||
struct hostent *h;
|
||
unsigned long which_addr = 0;
|
||
|
||
static int firstTime=1;
|
||
if (firstTime) {
|
||
firstTime=0;
|
||
dns_socks_kludge();
|
||
}
|
||
|
||
#if GETHOSTBYNAME_DELAY
|
||
{
|
||
int i = GETHOSTBYNAME_DELAY;
|
||
LOG_PROC3("sleeping","","\n");
|
||
sleep(i);
|
||
}
|
||
#endif /* GETHOSTBYNAME_DELAY */
|
||
|
||
LOG_PROC3("gethostbyname",name,"\n");
|
||
|
||
h = gethostbyname(name);
|
||
|
||
#ifdef RANDOMIZE_ADDRESSES
|
||
if (h)
|
||
{
|
||
unsigned long n_addrs;
|
||
for (n_addrs = 0; h->h_addr_list[n_addrs]; n_addrs++)
|
||
;
|
||
if (n_addrs > 0)
|
||
which_addr = (random_number % n_addrs);
|
||
else
|
||
h = 0;
|
||
}
|
||
#endif /* RANDOMIZE_ADDRESSES */
|
||
|
||
if (h)
|
||
sprintf(buf, "%d: %d.%d.%d.%d\n",
|
||
DNS_STATUS_GETHOSTBYNAME_OK,
|
||
((unsigned char *) h->h_addr_list[which_addr])[0],
|
||
((unsigned char *) h->h_addr_list[which_addr])[1],
|
||
((unsigned char *) h->h_addr_list[which_addr])[2],
|
||
((unsigned char *) h->h_addr_list[which_addr])[3]);
|
||
else
|
||
sprintf(buf, "%d: host %s not found\n",
|
||
DNS_STATUS_GETHOSTBYNAME_FAILED, name);
|
||
|
||
LOG_PROC3("writing response",buf,"");
|
||
|
||
write(out_fd, buf, strlen(buf));
|
||
FLUSH(out_fd);
|
||
}
|
||
|
||
|
||
|
||
/* Process 2.
|
||
Loops forever.
|
||
|
||
Reads "lookup: hostname\n".
|
||
Writes "2xx: id\n" or "5xx: error msg\n".
|
||
Some time later, writes "1xx: id: a.b.c.d\n" or "5xx: id: error msg\n".
|
||
|
||
-or-
|
||
|
||
Reads "kill: id\n".
|
||
Writes "1xx: killed" or "5xx: kill failed"
|
||
*/
|
||
|
||
typedef struct dns_lookup {
|
||
long id;
|
||
pid_t pid;
|
||
FILE *fd;
|
||
char *name;
|
||
struct dns_lookup *next, *prev;
|
||
} dns_lookup;
|
||
|
||
static long dns_id_tick;
|
||
static dns_lookup *proc2_queue = 0;
|
||
|
||
|
||
static long
|
||
proc2_queue_length(void)
|
||
{
|
||
dns_lookup *obj;
|
||
long i = 0;
|
||
for (obj = proc2_queue; obj; obj = obj->next) i++;
|
||
return i;
|
||
}
|
||
|
||
|
||
static dns_lookup *
|
||
new_lookup_object (const char *name)
|
||
{
|
||
dns_lookup *obj;
|
||
char *n2;
|
||
ASSERT(name);
|
||
if (!name) return 0;
|
||
|
||
n2 = strdup(name);
|
||
if (!n2) return 0; /* MK_OUT_OF_MEMORY */
|
||
|
||
obj = (dns_lookup *) malloc(sizeof(*obj));
|
||
if (!obj) return 0;
|
||
memset(obj, 0, sizeof(*obj));
|
||
obj->id = ++dns_id_tick;
|
||
obj->name = n2;
|
||
obj->fd = 0;
|
||
|
||
ASSERT(!proc2_queue || proc2_queue->prev == 0);
|
||
obj->next = proc2_queue;
|
||
if (proc2_queue)
|
||
proc2_queue->prev = obj;
|
||
proc2_queue = obj;
|
||
|
||
return obj;
|
||
}
|
||
|
||
|
||
/* Frees the object associated with a host lookup on which proc2 is waiting,
|
||
and writes the result to proc1. Returns 0 if everything went ok, negative
|
||
otherwise. Status should be 0 (for "ok") or negative (the inverse of one
|
||
of the three-digit response codes that *proc1* is expecting (not the codes
|
||
that *proc3* returns.)n
|
||
*/
|
||
static int
|
||
free_lookup_object (dns_lookup *obj,
|
||
int out_fd,
|
||
int status, const char *error_msg,
|
||
const unsigned char ip_addr[4])
|
||
{
|
||
char buf[MAXHOSTNAMELEN + 100];
|
||
|
||
ASSERT(status == 0 || (status < -100 && status > -700));
|
||
|
||
ASSERT(obj);
|
||
if (!obj) return -1;
|
||
|
||
if (status >= 0 && ip_addr)
|
||
{
|
||
sprintf(buf, "%d: %lu: %d.%d.%d.%d\n",
|
||
DNS_STATUS_LOOKUP_OK,
|
||
obj->id, ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
|
||
}
|
||
else
|
||
{
|
||
ASSERT(error_msg);
|
||
sprintf(buf, "%d: %lu: %s\n",
|
||
((status < -100) ? -status : DNS_STATUS_INTERNAL_ERROR),
|
||
obj->id, (error_msg ? error_msg : "???"));
|
||
}
|
||
|
||
LOG_PROC2("writing response",buf,"");
|
||
|
||
write(out_fd, buf, strlen(buf));
|
||
FLUSH(out_fd);
|
||
|
||
if (obj->fd)
|
||
{
|
||
fclose(obj->fd);
|
||
obj->fd = 0;
|
||
}
|
||
if (obj->prev)
|
||
{
|
||
ASSERT(obj->prev->next != obj->next);
|
||
obj->prev->next = obj->next;
|
||
}
|
||
if (obj->next)
|
||
{
|
||
ASSERT(obj->next->prev != obj->prev);
|
||
obj->next->prev = obj->prev;
|
||
}
|
||
if (proc2_queue == obj)
|
||
proc2_queue = obj->next;
|
||
|
||
memset(obj, ~0, sizeof(obj));
|
||
free(obj);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
static dns_lookup *
|
||
spawn_lookup_process (const char *name, int out_fd)
|
||
{
|
||
dns_lookup *obj = new_lookup_object(name);
|
||
pid_t forked;
|
||
int fds[2]; /* one reads from [0] and writes to [1]. */
|
||
unsigned long random_number = 0;
|
||
|
||
if (pipe (fds))
|
||
{
|
||
free_lookup_object(obj, out_fd,
|
||
-DNS_STATUS_LOOKUP_NOT_STARTED,
|
||
"can't make pipe", 0);
|
||
obj = 0;
|
||
return 0;
|
||
}
|
||
|
||
/* Save the fd from which we should read from the forked proc. */
|
||
obj->fd = fdopen(fds[0], "r");
|
||
if (!obj->fd)
|
||
{
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
free_lookup_object(obj, out_fd,
|
||
-DNS_STATUS_LOOKUP_NOT_STARTED,
|
||
"out of memory", 0);
|
||
obj = 0;
|
||
return 0;
|
||
}
|
||
SET_BUFFERING(obj->fd);
|
||
|
||
#ifdef RANDOMIZE_ADDRESSES
|
||
/* Generate the random numbers in proc2 for use by proc3, so that the
|
||
PRNG actually gets permuted. */
|
||
random_number = (unsigned long) RANDOM();
|
||
#endif /* RANDOMIZE_ADDRESSES */
|
||
|
||
|
||
switch (forked = fork ())
|
||
{
|
||
case -1:
|
||
{
|
||
close (fds[1]);
|
||
free_lookup_object(obj, out_fd,
|
||
-DNS_STATUS_LOOKUP_NOT_STARTED,
|
||
"can't fork", 0);
|
||
obj = 0;
|
||
return 0;
|
||
}
|
||
break;
|
||
|
||
case 0: /* This is the forked process. */
|
||
{
|
||
/* Close the other side of the pipe (it's used by the main fork.) */
|
||
close (fds[0]);
|
||
|
||
/* Call gethostbyname, then write the result down the pipe. */
|
||
blocking_gethostbyname(name, fds[1], random_number);
|
||
|
||
/* Now exit this process. */
|
||
close (fds[1]);
|
||
exit(0);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
{
|
||
/* This is the "old" process (subproc pid is in `forked'.) */
|
||
obj->pid = forked;
|
||
|
||
/* This is the file descriptor we created for the benefit
|
||
of the child process - we don't need it in the parent. */
|
||
close (fds[1]);
|
||
|
||
return obj; /* ok. */
|
||
}
|
||
break;
|
||
}
|
||
|
||
/* shouldn't get here. */
|
||
ASSERT(0);
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Given a line which is an asynchronous response from proc3, find the
|
||
associated lookup object, feed the response on to proc1, and free
|
||
the dns_lookup object.
|
||
*/
|
||
static void
|
||
handle_subproc_response(dns_lookup *obj, const char *line, int out_fd)
|
||
{
|
||
int iip[4];
|
||
char *s;
|
||
int code = 0;
|
||
int i;
|
||
|
||
i = sscanf(line, "%d: %d.%d.%d.%d\n",
|
||
&code, &iip[0], &iip[1], &iip[2], &iip[3]);
|
||
if (i != 5)
|
||
{
|
||
i = sscanf(line, "%d:", &code);
|
||
if (i != 1 || code <= 100)
|
||
code = DNS_STATUS_INTERNAL_ERROR;
|
||
}
|
||
|
||
switch (code)
|
||
{
|
||
case DNS_STATUS_GETHOSTBYNAME_OK:
|
||
{
|
||
unsigned char ip[5];
|
||
ip[0] = iip[0]; ip[1] = iip[1]; ip[2] = iip[2]; ip[3] = iip[3];
|
||
ip[4] = 0; /* just in case */
|
||
free_lookup_object (obj, out_fd, 0, 0, ip);
|
||
obj = 0;
|
||
break;
|
||
}
|
||
|
||
case DNS_STATUS_GETHOSTBYNAME_FAILED:
|
||
default:
|
||
{
|
||
ASSERT(code == DNS_STATUS_GETHOSTBYNAME_FAILED);
|
||
if (code == DNS_STATUS_GETHOSTBYNAME_FAILED)
|
||
code = DNS_STATUS_LOOKUP_FAILED;
|
||
s = strchr(line, ':');
|
||
if (s) s++;
|
||
free_lookup_object (obj, out_fd,
|
||
(code > 100
|
||
? -code
|
||
: -DNS_STATUS_INTERNAL_ERROR),
|
||
(s ? string_trim(s) : "???"), 0);
|
||
obj = 0;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
static void
|
||
cancel_lookup(long id, int out_fd)
|
||
{
|
||
dns_lookup *obj = 0;
|
||
|
||
if (proc2_queue)
|
||
for (obj = proc2_queue; obj; obj = obj->next)
|
||
if (obj->id == id) break;
|
||
|
||
if (obj)
|
||
{
|
||
if (obj->pid)
|
||
{
|
||
pid_t pid2;
|
||
|
||
/*
|
||
* SIGKILL causes the browser to hang if the user clicks on the stop
|
||
* button while a long/bogus dns lookup is in progress. According
|
||
* to signal guru asharma, we should use SIGQUIT instead. -re
|
||
*/
|
||
/* kill(obj->pid, SIGKILL); */
|
||
kill(obj->pid, SIGQUIT);
|
||
pid2 = waitpid(obj->pid, 0, 0);
|
||
ASSERT(obj->pid == pid2);
|
||
}
|
||
free_lookup_object(obj, out_fd, -DNS_STATUS_KILLED_OK, "cancelled", 0);
|
||
obj = 0;
|
||
}
|
||
else
|
||
{
|
||
char buf[200];
|
||
sprintf (buf, "%d: %lu: unable to cancel\n", DNS_STATUS_KILL_FAILED, id);
|
||
LOG_PROC2("writing (kill) response",buf,"");
|
||
write(out_fd, buf, strlen(buf));
|
||
}
|
||
|
||
FLUSH(out_fd);
|
||
}
|
||
|
||
|
||
static void
|
||
dns_socks_kludge(void)
|
||
{
|
||
#ifndef NO_SOCKS_NS_KLUDGE
|
||
/* Gross historical kludge.
|
||
If the environment variable $SOCKS_NS is defined, stomp on the host that
|
||
the DNS code uses for host lookup to be a specific ip address.
|
||
*/
|
||
char *ns = getenv("SOCKS_NS");
|
||
if (ns && *ns)
|
||
{
|
||
/* Gross hack added to Gross historical kludge - need to
|
||
* initialize resolv.h structure first with low-cost call
|
||
* to gethostbyname() before hacking _res. Subsequent call
|
||
* to gethostbyname() will then pick up $SOCKS_NS address.
|
||
*/
|
||
gethostbyname("localhost");
|
||
|
||
res_init();
|
||
_res.nsaddr_list[0].sin_addr.s_addr = inet_addr(ns);
|
||
_res.nscount = 1;
|
||
}
|
||
#endif /* !NO_SOCKS_NS_KLUDGE */
|
||
}
|
||
|
||
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
static void
|
||
dns_signal_handler_dfl (int sig)
|
||
{
|
||
char buf[100];
|
||
signal (sig, SIG_DFL);
|
||
sprintf(buf,"caught signal %d!\n",sig);
|
||
LOG_PROC2("dns_signal_handler",buf,"");
|
||
kill (getpid(), sig);
|
||
}
|
||
#endif /* PROC2_DEBUG_PRINT */
|
||
|
||
static void
|
||
dns_signal_handler_ign (int sig)
|
||
{
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
char buf[100];
|
||
sprintf(buf,"caught signal %d -- ignoring.\n",sig);
|
||
LOG_PROC2("dns_signal_handler",buf,"");
|
||
#endif /* PROC2_DEBUG_PRINT */
|
||
signal (sig, dns_signal_handler_ign);
|
||
}
|
||
|
||
|
||
static void
|
||
dns_catch_signals(void)
|
||
{
|
||
#ifndef SIG_ERR
|
||
# define SIG_ERR -1
|
||
#endif
|
||
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
int sig;
|
||
char buf[255];
|
||
LOG_PROC2("dns_catch_signals","plugging in...\n","");
|
||
# define CATCH_SIGNAL_DFL(SIG) sig = SIG; \
|
||
if (((int)signal(SIG, dns_signal_handler_dfl)) == ((int)SIG_ERR)) \
|
||
goto FAIL
|
||
# define CATCH_SIGNAL_IGN(SIG) sig = SIG; \
|
||
if (((int)signal(SIG, dns_signal_handler_ign)) == ((int)SIG_ERR)) \
|
||
goto FAIL
|
||
#else /* !PROC2_DEBUG_PRINT */
|
||
# define CATCH_SIGNAL_DFL(SIG) \
|
||
if ((int)signal(SIG,SIG_DFL) == ((int)SIG_ERR)) goto FAIL
|
||
# define CATCH_SIGNAL_IGN(SIG) \
|
||
if ((int)signal(SIG,SIG_IGN) == ((int)SIG_ERR)) goto FAIL
|
||
#endif /* !PROC2_DEBUG_PRINT */
|
||
|
||
CATCH_SIGNAL_IGN(SIGHUP);
|
||
CATCH_SIGNAL_IGN(SIGINT);
|
||
CATCH_SIGNAL_IGN(SIGQUIT);
|
||
CATCH_SIGNAL_DFL(SIGILL);
|
||
CATCH_SIGNAL_DFL(SIGTRAP);
|
||
CATCH_SIGNAL_DFL(SIGIOT);
|
||
CATCH_SIGNAL_DFL(SIGABRT);
|
||
# ifdef SIGEMT
|
||
CATCH_SIGNAL_DFL(SIGEMT);
|
||
# endif
|
||
CATCH_SIGNAL_DFL(SIGFPE);
|
||
CATCH_SIGNAL_DFL(SIGBUS);
|
||
CATCH_SIGNAL_DFL(SIGSEGV);
|
||
# ifdef SIGSYS
|
||
CATCH_SIGNAL_DFL(SIGSYS);
|
||
# endif
|
||
CATCH_SIGNAL_DFL(SIGPIPE);
|
||
CATCH_SIGNAL_DFL(SIGTERM); /* ignore this one? hmm... */
|
||
CATCH_SIGNAL_IGN(SIGUSR1);
|
||
CATCH_SIGNAL_IGN(SIGUSR2);
|
||
# ifdef SIGXCPU
|
||
CATCH_SIGNAL_DFL(SIGXCPU);
|
||
# endif
|
||
# ifdef SIGDANGER
|
||
CATCH_SIGNAL_DFL(SIGDANGER);
|
||
# endif
|
||
# undef CATCH_SIGNAL
|
||
|
||
return;
|
||
|
||
FAIL:
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
sprintf(buf,"\tproc2 (%lu): error handling signal %d",
|
||
(unsigned long) getpid(), sig);
|
||
perror(buf);
|
||
#endif /* PROC2_DEBUG_PRINT */
|
||
exit(1);
|
||
}
|
||
|
||
|
||
static void dns_driver_init(int argc, char **argv, int in_fd, int out_fd);
|
||
static void dns_driver_main_loop(int in_fd, int out_fd);
|
||
|
||
/* Essentially, this is the main() of the forked dns-helper process.
|
||
*/
|
||
static void
|
||
dns_driver(int argc, char **argv, int in_fd, int out_fd)
|
||
{
|
||
/* Might not return, if we fork-and-exec. */
|
||
dns_driver_init(argc, argv, in_fd, out_fd);
|
||
|
||
/* Shouldn't return until proc1 has closed its end of the pipe. */
|
||
dns_driver_main_loop(in_fd, out_fd);
|
||
}
|
||
|
||
|
||
static void
|
||
dns_driver_init(int argc, char **argv, int in_fd, int out_fd)
|
||
{
|
||
#ifdef CHANGING_ARGV_WORKS
|
||
{
|
||
LOG_PROC2("launched","","\n");
|
||
ERROR! Write me! (This should overwrite argv.)
|
||
}
|
||
#else /* !CHANGING_ARGV_WORKS */
|
||
{
|
||
char proc2_name[] = DNS_PROC2_NAME "\000";
|
||
|
||
/* If argv[0] is already the proc2 name, then we're already in proc2,
|
||
so there's nothing else to do.
|
||
|
||
Otherwise, re-exec the running program with a different name.
|
||
Kludge, kludge, kludge.
|
||
*/
|
||
if (!strcmp(argv[0], proc2_name))
|
||
{
|
||
LOG_PROC2("dns_driver_init","not respawning.\n","");
|
||
}
|
||
else
|
||
{
|
||
char *new_argv[2];
|
||
new_argv[0] = proc2_name;
|
||
new_argv[1] = 0;
|
||
|
||
LOG_PROC2("dns_driver_init",
|
||
"execvp(... \"" DNS_PROC2_NAME "\")\n", "");
|
||
|
||
/* Copy in_fd and out_fd onto stdin/stdout, in order to pass them
|
||
to the newly-exec'ed process. */
|
||
dup2(in_fd, fileno(stdin));
|
||
dup2(out_fd, fileno(stdout));
|
||
|
||
execvp(argv[0], new_argv);
|
||
|
||
fprintf(stderr,
|
||
"\nMozilla: execvp(\"%s\") failed!\n"
|
||
"\tThis means that we were unable to fork() the dns-helper process,\n"
|
||
"\tand so host-name lookups will happen in the foreground instead\n"
|
||
"\tof in the background (and therefore won't be interruptible.)\n\n",
|
||
argv[0]);
|
||
exit(0);
|
||
}
|
||
}
|
||
#endif /* !CHANGING_ARGV_WORKS */
|
||
|
||
|
||
/* Close any streams we're not using.
|
||
*/
|
||
if (fileno(stdin) != in_fd && fileno(stdin) != out_fd)
|
||
fclose(stdin);
|
||
if (fileno(stdout) != in_fd && fileno(stdout) != out_fd)
|
||
fclose(stdout);
|
||
/* Never close stderr -- proc2 needs it in case the execvp() fails. */
|
||
|
||
dns_socks_kludge();
|
||
dns_catch_signals();
|
||
|
||
#ifdef RANDOMIZE_ADDRESSES
|
||
{
|
||
struct timeval tp;
|
||
struct timezone tzp;
|
||
int seed;
|
||
gettimeofday(&tp, &tzp);
|
||
seed = (999*tp.tv_sec) + (1001*tp.tv_usec) + (1003 * getpid());
|
||
SRANDOM(seed);
|
||
}
|
||
#endif /* RANDOMIZE_ADDRESSES */
|
||
|
||
}
|
||
|
||
|
||
static void
|
||
dns_driver_main_loop(int in_fd, int out_fd)
|
||
{
|
||
int status = 0;
|
||
FILE *in = fdopen(in_fd, "r");
|
||
ASSERT(in);
|
||
if (!in) return; /* out of memory *already*? We lost real bad... */
|
||
SET_BUFFERING(in);
|
||
|
||
while (1)
|
||
{
|
||
fd_set fdset;
|
||
char line[MAXHOSTNAMELEN + 100];
|
||
dns_lookup *obj = 0;
|
||
dns_lookup *next = 0;
|
||
|
||
FD_ZERO(&fdset);
|
||
|
||
/* Select on our parent's input stream. */
|
||
FD_SET(in_fd, &fdset);
|
||
|
||
/* And select on the pipe associated with each child process. */
|
||
if (proc2_queue)
|
||
for (obj = proc2_queue; obj; obj = obj->next)
|
||
{
|
||
int fd;
|
||
ASSERT(obj->fd);
|
||
if (!obj->fd) continue;
|
||
fd = fileno(obj->fd);
|
||
ASSERT(fd >= 0 &&
|
||
fd != in_fd &&
|
||
fd != out_fd &&
|
||
!FD_ISSET(fd, &fdset));
|
||
FD_SET(fd, &fdset);
|
||
}
|
||
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
{
|
||
int i = 0, j = 0;
|
||
fprintf(stderr, "\tproc2 (%lu): entering select(",
|
||
(unsigned long) getpid());
|
||
for (i = 0; i < getdtablehi(); i++)
|
||
if (FD_ISSET(i, &fdset))
|
||
{
|
||
fprintf(stderr, "%s%d", (j == 0 ? "" : ", "), i);
|
||
j++;
|
||
}
|
||
fprintf(stderr, ")\n");
|
||
}
|
||
#endif /* PROC2_DEBUG_PRINT */
|
||
|
||
status = select(getdtablehi(), &fdset, 0, 0, 0);
|
||
|
||
LOG_PROC2("select returned","","\n");
|
||
|
||
ASSERT(status > 0);
|
||
if (status <= 0) continue;
|
||
|
||
|
||
/* Handle input from our parent.
|
||
*/
|
||
|
||
if (FD_ISSET (in_fd, &fdset))
|
||
{
|
||
/* Read one line from the parent (blocking if necessary, which
|
||
it shouldn't be except for a trivial amount of time.) */
|
||
*line = 0;
|
||
fgets(line, sizeof(line)-1, in);
|
||
|
||
if (!*line)
|
||
{
|
||
LOG_PROC2("got EOF from proc1; exiting","","\n");
|
||
return;
|
||
}
|
||
|
||
LOG_PROC2("read command",line,"");
|
||
|
||
string_trim(line);
|
||
|
||
if (!strncmp(line, "kill: ", 6))
|
||
{
|
||
long id = 0;
|
||
status = sscanf(line+6, "%ld\n", &id);
|
||
ASSERT(status == 1);
|
||
if (id)
|
||
cancel_lookup(id, out_fd);
|
||
}
|
||
else if (!strncmp(line, "lookup: ", 8))
|
||
{
|
||
char *name = line + 8;
|
||
obj = spawn_lookup_process(name, out_fd);
|
||
/* Write a response to the parent immediately (to assign an id.)
|
||
*/
|
||
if (obj)
|
||
sprintf(line, "%d: %lu\n",
|
||
DNS_STATUS_LOOKUP_STARTED,
|
||
obj->id);
|
||
else
|
||
sprintf(line, "%d: %lu\n",
|
||
DNS_STATUS_LOOKUP_NOT_STARTED,
|
||
obj->id);
|
||
|
||
LOG_PROC2("writing initial",line,"");
|
||
write(out_fd, line, strlen(line));
|
||
FLUSH(out_fd);
|
||
}
|
||
}
|
||
|
||
|
||
/* Handle input from our kids.
|
||
*/
|
||
|
||
for (obj = proc2_queue; obj; obj = next)
|
||
{
|
||
next=obj->next;
|
||
if (FD_ISSET(fileno(obj->fd), &fdset))
|
||
{
|
||
pid_t pid = obj->pid;
|
||
pid_t pid2;
|
||
|
||
/* Read one line from the subprocess (blocking if necessary,
|
||
which it shouldn't be except for a trivial amount of time.) */
|
||
*line = 0;
|
||
fgets(line, sizeof(line)-1, obj->fd);
|
||
|
||
#ifdef PROC2_DEBUG_PRINT
|
||
fprintf(stderr, "\tproc2 (%lu): read from proc3 (%ld): %s",
|
||
(unsigned long) getpid(), obj->id, line);
|
||
#endif /* PROC2_DEBUG_PRINT */
|
||
|
||
handle_subproc_response(obj, line, out_fd);
|
||
|
||
/* `obj' has now been freed. After having written one line, the
|
||
process should exit. Wait for it now (we saved `pid' before
|
||
`obj' got freed.)
|
||
|
||
The thing that confuses me is, if I *don't* do the waitpid()
|
||
here, zombies don't seem to get left around. I expected them
|
||
to, and don't know why they don't. (On Irix 6.2, at least.)
|
||
*/
|
||
pid2 = waitpid(pid, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
ASSERT(0); /* not reached */
|
||
}
|
||
|
||
|
||
|
||
/* Process 1 (the user-called code.)
|
||
*/
|
||
|
||
|
||
static FILE *dns_in_fd = 0;
|
||
static FILE *dns_out_fd = 0;
|
||
|
||
|
||
typedef struct queued_response {
|
||
long id;
|
||
int (*cb) (void *id, void *closure, int status, const char *result);
|
||
void *closure;
|
||
struct queued_response *next, *prev;
|
||
} queued_response;
|
||
|
||
static queued_response *proc1_queue = 0;
|
||
|
||
|
||
static long
|
||
proc1_queue_length(void)
|
||
{
|
||
queued_response *obj;
|
||
long i = 0;
|
||
for (obj = proc1_queue; obj; obj = obj->next) i++;
|
||
return i;
|
||
}
|
||
|
||
|
||
/* Call this from main() to initialize the async DNS library.
|
||
Returns a file descriptor that should be selected for, or
|
||
negative if something went wrong. Pass it the argc/argv
|
||
that your `main' was called with (it needs these pointers
|
||
in order to give its forked subprocesses sensible names.)
|
||
*/
|
||
int
|
||
DNS_SpawnProcess(int argc, char **argv)
|
||
{
|
||
static int already_spawned_p = 0;
|
||
pid_t forked;
|
||
int infds[2]; /* "in" and "out" are from the p.o.v. of proc2. */
|
||
int outfds[2]; /* one reads from [0] and writes to [1]. */
|
||
|
||
ASSERT(!already_spawned_p);
|
||
if (already_spawned_p) return -1;
|
||
already_spawned_p = 1;
|
||
|
||
#ifndef CHANGING_ARGV_WORKS
|
||
if (!strcmp(argv[0], DNS_PROC2_NAME))
|
||
{
|
||
/* Oh look, we're already in proc2, so don't fork again.
|
||
Assume our side of the pipe is on stdin/stdout.
|
||
*/
|
||
LOG_PROC2("DNS_SpawnProcess","reconnecting to stdin/stdout.\n","");
|
||
|
||
/* doesn't return until the other side closes the pipe. */
|
||
dns_driver(argc, argv, fileno(stdin), fileno(stdout));
|
||
exit(0);
|
||
}
|
||
#endif /* !CHANGING_ARGV_WORKS */
|
||
|
||
LOG_PROC1("DNS_SpawnProcess","forking proc2.\n","");
|
||
|
||
if (pipe (infds))
|
||
{
|
||
return -1; /* pipe error: return better error code? */
|
||
}
|
||
if (pipe (outfds))
|
||
{
|
||
close(infds[0]);
|
||
close(infds[1]);
|
||
return -1; /* pipe error: return better error code? */
|
||
}
|
||
|
||
switch (forked = fork ())
|
||
{
|
||
case -1:
|
||
{
|
||
close (infds[0]);
|
||
close (infds[1]);
|
||
close (outfds[0]);
|
||
close (outfds[1]);
|
||
return -1; /* fork error: return better error code? */
|
||
}
|
||
break;
|
||
|
||
case 0: /* This is the forked process. */
|
||
{
|
||
/* Close the other sides of the pipes (used by the main fork.) */
|
||
close (infds[1]);
|
||
close (outfds[0]);
|
||
|
||
/* doesn't return until the other side closes the pipe. */
|
||
dns_driver(argc, argv, infds[0], outfds[1]);
|
||
|
||
close (infds[0]);
|
||
close (outfds[1]);
|
||
exit(-1);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
{
|
||
/* This is the "old" process (subproc pid is in `forked'.)
|
||
(Save that somewhere?)
|
||
*/
|
||
|
||
/* This is the file descriptor we created for the benefit
|
||
of the child process - we don't need it in the parent. */
|
||
close (infds[0]);
|
||
close (outfds[1]);
|
||
|
||
/* Set up FILE objects for the proc3 side of the pipes.
|
||
(Note that the words "out" in `dns_out_fd' and `outfds'
|
||
have opposite senses here.)
|
||
*/
|
||
dns_out_fd = fdopen(infds[1], "w");
|
||
dns_in_fd = fdopen(outfds[0], "r");
|
||
|
||
SET_BUFFERING(dns_out_fd);
|
||
SET_BUFFERING(dns_in_fd);
|
||
|
||
return fileno(dns_in_fd); /* ok! */
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
static queued_response *
|
||
new_queued_response (int (*cb) (void *, void *closure, int, const char *),
|
||
void *closure)
|
||
{
|
||
queued_response *obj;
|
||
|
||
obj = (queued_response *) malloc(sizeof(*obj));
|
||
if (!obj) return 0;
|
||
memset(obj, 0, sizeof(*obj));
|
||
obj->id = 0;
|
||
obj->cb = cb;
|
||
obj->closure = closure;
|
||
|
||
ASSERT(!proc1_queue || proc1_queue->prev == 0);
|
||
obj->next = proc1_queue;
|
||
if (proc1_queue)
|
||
proc1_queue->prev = obj;
|
||
proc1_queue = obj;
|
||
|
||
return obj;
|
||
}
|
||
|
||
|
||
/* Frees the object associated with a host lookup on which proc1 is waiting,
|
||
and runs the callback function. The returned value is that of the callback.
|
||
*/
|
||
static int
|
||
free_queued_response (queued_response *obj, int status, const char *result)
|
||
{
|
||
long id = obj->id;
|
||
int (*cb) (void *id, void *closure, int status, const char *result)
|
||
= obj->cb;
|
||
void *closure = obj->closure;
|
||
|
||
if( -1 == id ) return 0;
|
||
|
||
if (obj->prev)
|
||
{
|
||
ASSERT(obj->prev->next != obj->next);
|
||
obj->prev->next = obj->next;
|
||
}
|
||
if (obj->next)
|
||
{
|
||
ASSERT(obj->next->prev != obj->prev);
|
||
obj->next->prev = obj->prev;
|
||
}
|
||
if (proc1_queue == obj)
|
||
proc1_queue = obj->next;
|
||
|
||
ASSERT(closure != (void *) obj);
|
||
memset(obj, ~0, sizeof(*obj));
|
||
free(obj);
|
||
|
||
ASSERT(status < -100 && status > -700);
|
||
if (status == -DNS_STATUS_LOOKUP_OK) status = 1;
|
||
else if (status == -DNS_STATUS_KILL_FAILED) status = 0;
|
||
|
||
if (cb)
|
||
return cb((void *) id, closure, status, result);
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Given a line which is an asynchronous response from proc2, find the
|
||
associated lookup object, run the callback, return the status that
|
||
the callback returned, and free the queued_response object.
|
||
*/
|
||
static int
|
||
handle_async_response(char *line)
|
||
{
|
||
int i;
|
||
int code = 0;
|
||
queued_response *obj = 0;
|
||
long id = 0;
|
||
int iip[4];
|
||
|
||
ASSERT(line);
|
||
if (!line || !*line) return -1;
|
||
|
||
i = sscanf(line, "%d: %lu: %d.%d.%d.%d\n",
|
||
&code, &id,
|
||
&iip[0], &iip[1], &iip[2], &iip[3]);
|
||
if (i != 6)
|
||
{
|
||
i = sscanf(line, "%d: %lu:", &code, &id);
|
||
if (i != 2)
|
||
{
|
||
i = sscanf(line, "%d:", &code);
|
||
if (i != 1)
|
||
code = DNS_STATUS_INTERNAL_ERROR;
|
||
}
|
||
}
|
||
|
||
if (id)
|
||
for (obj = proc1_queue; obj; obj = obj->next)
|
||
if (obj->id == id) break;
|
||
|
||
if (!obj) /* got an id that we don't know about? */
|
||
return -1;
|
||
|
||
switch (code)
|
||
{
|
||
case DNS_STATUS_LOOKUP_OK: /* "1xx: id: a.b.c.d\n" */
|
||
{
|
||
unsigned char ip[5];
|
||
ip[0] = iip[0]; ip[1] = iip[1]; ip[2] = iip[2]; ip[3] = iip[3];
|
||
ip[4] = 0; /* just in case */
|
||
return free_queued_response(obj, -DNS_STATUS_LOOKUP_OK, (char *) ip);
|
||
break;
|
||
}
|
||
case DNS_STATUS_LOOKUP_FAILED: /* "5xx: id: host not found\n" */
|
||
default:
|
||
{
|
||
char *msg = 0;
|
||
ASSERT(code == DNS_STATUS_LOOKUP_FAILED);
|
||
msg = strchr(line, ':');
|
||
if (msg)
|
||
{
|
||
char *s = strchr(msg, ':');
|
||
if (s) msg = s+1;
|
||
}
|
||
else
|
||
{
|
||
msg = line;
|
||
}
|
||
|
||
code = (code > 100 ? -code : -DNS_STATUS_INTERNAL_ERROR);
|
||
free_queued_response(obj, code, string_trim(msg));
|
||
return code;
|
||
break;
|
||
}
|
||
}
|
||
ASSERT(0); /* not reached */
|
||
}
|
||
|
||
|
||
|
||
/* Kick off an async DNS lookup;
|
||
The returned value is an id representing this transaction;
|
||
the result_callback will be run (in the main process) when we
|
||
have a result. Returns negative if something went wrong.
|
||
If `status' is negative,`result' is an error message.
|
||
If `status' is positive, `result' is a 4-character string of
|
||
the IP address.
|
||
If `status' is 0, then the lookup was prematurely aborted
|
||
via a call to DNS_AbortHostLookup().
|
||
*/
|
||
int
|
||
DNS_AsyncLookupHost(const char *name,
|
||
int (*result_callback) (void *id,
|
||
void *closure,
|
||
int status,
|
||
const char *result),
|
||
void *closure,
|
||
void **id_return)
|
||
{
|
||
char buf[MAXHOSTNAMELEN + 100];
|
||
queued_response *obj = 0;
|
||
int code = 0;
|
||
int i;
|
||
char *s;
|
||
|
||
ASSERT(result_callback);
|
||
if (!result_callback) return -1;
|
||
ASSERT(id_return);
|
||
if (!id_return) return -1;
|
||
*id_return = 0;
|
||
|
||
ASSERT(name);
|
||
if (!name || !*name) return -1; /* invalid host name: better error code? */
|
||
if (strchr(name, '\n')) return -1; /* ditto */
|
||
if (strchr(name, '\r')) return -1;
|
||
|
||
if (strlen(name) > MAXHOSTNAMELEN)
|
||
return -1; /* host name too long: return better error code? */
|
||
|
||
obj = new_queued_response(result_callback, closure);
|
||
if (!obj) return -1; /* out of memory: return better error code? */
|
||
|
||
|
||
LOG_PROC1("writing: lookup",name,"\n");
|
||
fprintf(dns_out_fd, "lookup: %s\n", name);
|
||
fflush(dns_out_fd);
|
||
FLUSH(fileno(dns_out_fd));
|
||
|
||
/* We have just written a "lookup:" command to proc2.
|
||
As soon as this command is received, proc2 will write a 2xx response
|
||
back to us, ACKing it, and giving us an ID. We must wait for that
|
||
response in order to return the ID to our caller; we block waiting
|
||
for it, knowing that it won't be long in coming.
|
||
|
||
However, there could be other data in the pipe already -- the final
|
||
results of previously launched commands (1xx responses or 5xx responses.)
|
||
So we read commands, and process them, until we find the one we're
|
||
looking for.
|
||
|
||
Under no circumstances can there be more than one 2xx response in the
|
||
pipe at a time (since this is the only place a command is issued that
|
||
can cause a 2xx response to be generated, and each call waits for it.)
|
||
*/
|
||
|
||
AGAIN:
|
||
|
||
*buf = 0;
|
||
fgets(buf, sizeof(buf)-1, dns_in_fd);
|
||
LOG_PROC1("read (lookup)",buf,"");
|
||
|
||
obj->id = 0;
|
||
|
||
if (!*buf) /* EOF, perhaps? */
|
||
{
|
||
code = DNS_STATUS_INTERNAL_ERROR;
|
||
}
|
||
else
|
||
{
|
||
i = sscanf(buf, "%d:", &code);
|
||
ASSERT(i == 1);
|
||
if (code <= 100) code = DNS_STATUS_INTERNAL_ERROR;
|
||
}
|
||
|
||
switch (code)
|
||
{
|
||
case DNS_STATUS_LOOKUP_STARTED: /* 2xx: id\n" */
|
||
i = sscanf(buf, "%d: %lu\n", &code, &obj->id);
|
||
ASSERT(i == 2);
|
||
*id_return = (void *) obj->id;
|
||
return 0;
|
||
break;
|
||
|
||
case DNS_STATUS_LOOKUP_OK: /* 1xx or 5xx which is an */
|
||
case DNS_STATUS_LOOKUP_FAILED: /* async response (not for us) */
|
||
handle_async_response(buf);
|
||
goto AGAIN;
|
||
break;
|
||
|
||
case DNS_STATUS_LOOKUP_NOT_STARTED:
|
||
default: /* 5xx: error msg\n"
|
||
Presumably this is for us.
|
||
(If not, there's a bug...) */
|
||
ASSERT(code == DNS_STATUS_LOOKUP_NOT_STARTED);
|
||
s = strchr(buf, ':');
|
||
if (s) s++;
|
||
code = (code > 100 ? -code : -DNS_STATUS_INTERNAL_ERROR);
|
||
code = free_queued_response(obj, code, (s ? string_trim(s) : "???"));
|
||
obj = 0;
|
||
return code;
|
||
break;
|
||
}
|
||
ASSERT(0); /* not reached */
|
||
return -1;
|
||
}
|
||
|
||
|
||
/* Prematurely give up on the given host-lookup transaction.
|
||
The `token' is what was returned by DNS_AsyncLookupHost.
|
||
This causes the result_callback to be called with a negative
|
||
status.
|
||
*/
|
||
int
|
||
DNS_AbortHostLookup(void *token)
|
||
{
|
||
char buf[MAXHOSTNAMELEN + 100];
|
||
int code = 0;
|
||
int i;
|
||
long id = (int) token;
|
||
long id2 = 0;
|
||
queued_response *obj = 0;
|
||
char *s;
|
||
|
||
for (obj = proc1_queue; obj; obj = obj->next)
|
||
if (obj->id == id) break;
|
||
|
||
if (!obj) return -1;
|
||
|
||
#ifdef PROC1_DEBUG_PRINT
|
||
fprintf(stderr, "\tproc1 (%lu): writing: kill: %lu (ql=%ld)\n",
|
||
(unsigned long) getpid(), id, proc1_queue_length());
|
||
#endif /* PROC1_DEBUG_PRINT */
|
||
|
||
fprintf(dns_out_fd, "kill: %lu\n", id);
|
||
fflush(dns_out_fd);
|
||
FLUSH(fileno(dns_out_fd));
|
||
|
||
|
||
/* We have just written a "kill:" command to proc2.
|
||
As soon as this command is received, proc2 will write a 1xx or 5xx
|
||
response back to us, ACKing it. We must wait for that response in
|
||
order to return an error code to our caller; we block waiting for it,
|
||
knowing that it won't be long in coming.
|
||
|
||
However, there could be other data in the pipe already -- the final
|
||
results of previously launched commands (1xx responses or 5xx responses.)
|
||
So we read commands, and process them, until we find the one we're
|
||
looking for.
|
||
|
||
Under no circumstances can there be more than one kill-response in the
|
||
pipe at a time (since this is the only place a command is issued that
|
||
can cause a kill-response response to be generated, and each call waits
|
||
for it.)
|
||
*/
|
||
|
||
AGAIN:
|
||
|
||
*buf = 0;
|
||
fgets(buf, sizeof(buf)-1, dns_in_fd);
|
||
LOG_PROC1("read (kill)",buf,"");
|
||
|
||
i = sscanf(buf, "%d:", &code);
|
||
ASSERT(i == 1);
|
||
if (code <= 100) code = DNS_STATUS_INTERNAL_ERROR;
|
||
|
||
switch (code)
|
||
{
|
||
case DNS_STATUS_KILLED_OK:
|
||
return free_queued_response(obj, -DNS_STATUS_KILLED_OK, "killed");
|
||
break;
|
||
|
||
case DNS_STATUS_LOOKUP_OK: /* 1xx or 5xx which is an */
|
||
case DNS_STATUS_LOOKUP_FAILED: /* async response (not for us) */
|
||
|
||
/* Actually, it might be for us -- if we've issued a kill, but the
|
||
success/failure for this same code raced in before the response
|
||
to the kill (a kill-failure, presumably) then ignore this response
|
||
(wait for the kill-failure instead.) */
|
||
id2 = 0;
|
||
i = sscanf(buf, "%d: %lu:", &code, &id2);
|
||
ASSERT(i == 2);
|
||
if (i == 2 && id2 == id) {
|
||
LOG_PROC1("Bug #87736 (1)",buf,"");
|
||
handle_async_response(buf);
|
||
goto AGAIN;
|
||
}
|
||
|
||
/* Not for us -- let it through. */
|
||
handle_async_response(buf);
|
||
goto AGAIN;
|
||
break;
|
||
|
||
case DNS_STATUS_KILL_FAILED:
|
||
default: /* 5xx: error msg\n"
|
||
Presumably this is for us.
|
||
(If not, there's a bug...) */
|
||
ASSERT(code == DNS_STATUS_KILL_FAILED);
|
||
s = strchr(buf, ':');
|
||
if (s)
|
||
{
|
||
char *s2 = strchr(++s, ':');
|
||
if (s2) s = s2+1;
|
||
}
|
||
code = (code > 100 ? -code : -DNS_STATUS_INTERNAL_ERROR);
|
||
free_queued_response(obj, code, (s ? string_trim(s) : "???"));
|
||
LOG_PROC1("Bug #87736 (2)",buf,"");
|
||
return code;
|
||
break;
|
||
}
|
||
ASSERT(0); /* not reached */
|
||
return -1;
|
||
}
|
||
|
||
|
||
|
||
/* The main select() loop of your program should call this when the fd
|
||
that was returned by DNS_SpawnProcess comes active. This may cause
|
||
some of the result_callback functions to run.
|
||
|
||
If this returns negative, then a fatal error has happened, and you
|
||
should close `fd' and not select() for it again. Call gethostbyname()
|
||
in the foreground or something.
|
||
*/
|
||
int
|
||
DNS_ServiceProcess(int fd)
|
||
{
|
||
char buf[MAXHOSTNAMELEN + 100];
|
||
char *s = 0;
|
||
int status = 0;
|
||
int final_status = 0;
|
||
|
||
AGAIN:
|
||
|
||
ASSERT(fd == fileno(dns_in_fd));
|
||
if (fd != fileno(dns_in_fd)) return -1;
|
||
|
||
ASSERT(final_status <= 0);
|
||
|
||
/* Poll to see if input is available on fd.
|
||
NOTE: This only works because we've marked the FILE* object wrapped
|
||
around this fd as being fully unbuffered!!
|
||
*/
|
||
{
|
||
fd_set fdset;
|
||
struct timeval t = { 0L, 0L, };
|
||
FD_ZERO(&fdset);
|
||
FD_SET(fd, &fdset);
|
||
status = select(fd+1, &fdset, 0, 0, &t);
|
||
if (status < 0 && final_status == 0)
|
||
final_status = status;
|
||
if (status <= 0) /* error, or no input ready */
|
||
return final_status;
|
||
}
|
||
|
||
*buf = 0;
|
||
s = fgets(buf, sizeof(buf)-1, dns_in_fd);
|
||
|
||
if (s && *s)
|
||
{
|
||
LOG_PROC1("read (async)",buf,"");
|
||
}
|
||
else
|
||
{
|
||
/* #### If there's nothing to read, the socket must be closed? */
|
||
LOG_PROC1("read (async)","nothing -- returning error\n","");
|
||
return (final_status < 0 ? final_status : -1);
|
||
}
|
||
|
||
/* call all pending procs, but remember the first error code that
|
||
one of them returned.
|
||
*/
|
||
status = handle_async_response(buf);
|
||
status = 0; /* #### */
|
||
if (status < 0 && final_status == 0)
|
||
final_status = status;
|
||
|
||
/* see if there is more to read. */
|
||
goto AGAIN;
|
||
}
|
||
|
||
|
||
|
||
#ifdef DEBUG
|
||
void
|
||
print_fdset(fd_set *fdset)
|
||
{
|
||
int i;
|
||
int any = 0;
|
||
if (!fdset) return;
|
||
for (i = 0; i < getdtablesize(); i++)
|
||
{
|
||
if (FD_ISSET(i, fdset))
|
||
{
|
||
if (any) fprintf(stderr, ", ");
|
||
any = 1;
|
||
fprintf(stderr, "%d", i);
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
print_fd(int fd)
|
||
{
|
||
int status;
|
||
fd_set fdset;
|
||
struct timeval t = { 0L, 0L, };
|
||
|
||
fprintf(stderr, " %d:\n", fd);
|
||
|
||
FD_ZERO(&fdset);
|
||
FD_SET(fd, &fdset);
|
||
status = select(fd+1, &fdset, 0, 0, &t);
|
||
if (status == 0)
|
||
fprintf(stderr, " readable (no input)\n");
|
||
else if (status == 1)
|
||
fprintf(stderr, " readable (pending)\n");
|
||
else
|
||
fprintf(stderr, " unreadable (error %d)\n", status);
|
||
|
||
FD_ZERO(&fdset);
|
||
FD_SET(fd, &fdset);
|
||
status = select(fd+1, 0, &fdset, 0, &t);
|
||
if (status == 0)
|
||
fprintf(stderr, " writeable (no input)\n");
|
||
else if (status == 1)
|
||
fprintf(stderr, " writeable (pending)\n");
|
||
else
|
||
fprintf(stderr, " unwriteable (error %d)\n", status);
|
||
|
||
FD_ZERO(&fdset);
|
||
FD_SET(fd, &fdset);
|
||
status = select(fd+1, 0, 0, &fdset, &t);
|
||
if (status == 0)
|
||
fprintf(stderr, " exceptionable (no input)\n");
|
||
else if (status == 1)
|
||
fprintf(stderr, " exceptionable (pending)\n");
|
||
else
|
||
fprintf(stderr, " unexceptionable (error %d)\n", status);
|
||
}
|
||
|
||
|
||
void
|
||
print_fdsets(fd_set *read, fd_set *write, fd_set *except)
|
||
{
|
||
int i;
|
||
fd_set all;
|
||
FD_ZERO(&all);
|
||
|
||
fprintf(stderr, "rd: "); print_fdset(read);
|
||
fprintf(stderr, "\nwr: "); print_fdset(write);
|
||
fprintf(stderr, "\nex: "); print_fdset(except);
|
||
fprintf(stderr, "\n");
|
||
|
||
if (read)
|
||
for (i = 0; i < getdtablesize(); i++)
|
||
if (FD_ISSET(i, read)) FD_SET(i, &all);
|
||
if (write)
|
||
for (i = 0; i < getdtablesize(); i++)
|
||
if (FD_ISSET(i, write)) FD_SET(i, &all);
|
||
if (except)
|
||
for (i = 0; i < getdtablesize(); i++)
|
||
if (FD_ISSET(i, except)) FD_SET(i, &all);
|
||
|
||
for (i = 0; i < getdtablesize(); i++)
|
||
if (FD_ISSET(i, &all)) print_fd(i);
|
||
}
|
||
|
||
#endif /* DEBUG */
|
||
|
||
|
||
|
||
|
||
#ifdef STANDALONE
|
||
|
||
typedef struct test_id_cons {
|
||
char *name;
|
||
void *id;
|
||
struct test_id_cons *next;
|
||
} test_id_cons;
|
||
static test_id_cons *test_list = 0;
|
||
|
||
static int
|
||
test_cb (void *id, void *closure, int status, const char *result)
|
||
{
|
||
const char *argv0 = (char *) closure;
|
||
test_id_cons *cons = test_list;
|
||
|
||
while(cons)
|
||
{
|
||
if (cons->id == id) break;
|
||
cons = cons->next;
|
||
ASSERT(cons);
|
||
}
|
||
|
||
if (status == 1)
|
||
{
|
||
unsigned char *ip = (unsigned char *) result;
|
||
fprintf(stderr, "%s: %s = %d.%d.%d.%d\n",
|
||
argv0, cons->name, ip[0], ip[1], ip[2], ip[3]);
|
||
}
|
||
else
|
||
{
|
||
fprintf(stderr, "%s: %s: error %d: %s\n",
|
||
argv0, cons->name, status, result);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int
|
||
main (int argc, char **argv)
|
||
{
|
||
char buf[MAXHOSTNAMELEN + 100];
|
||
int fd = DNS_SpawnProcess(argc, argv);
|
||
|
||
if (fd <= 0)
|
||
{
|
||
fprintf(stderr, "%s: dns init failed\n", argv[0]);
|
||
exit(-1);
|
||
}
|
||
|
||
fprintf(stderr,
|
||
"Type host names to spawn lookups.\n"
|
||
"Type `abort ID' to kill one off.\n"
|
||
"Type `quit' when done.\n\n");
|
||
|
||
while(1)
|
||
{
|
||
int status = 0;
|
||
fd_set fdset;
|
||
|
||
FD_ZERO(&fdset);
|
||
FD_SET(fd, &fdset);
|
||
FD_SET(fileno(stdin), &fdset);
|
||
|
||
status = select(getdtablehi(), &fdset, 0, 0, 0);
|
||
if (status <= 0)
|
||
{
|
||
fprintf(stderr, "%s: select() returned %d\n", argv[0], status);
|
||
exit(-1);
|
||
}
|
||
|
||
if (!FD_ISSET(fd, &fdset) &&
|
||
!FD_ISSET(fileno(stdin), &fdset))
|
||
{
|
||
fprintf(stderr, "%s: neither fd is set?\n", argv[0]);
|
||
exit(-1);
|
||
}
|
||
|
||
if (FD_ISSET(fd, &fdset))
|
||
{
|
||
status = DNS_ServiceProcess(fd);
|
||
if (status < 0)
|
||
{
|
||
fprintf(stderr, "%s: DNS_ServiceProcess() returned %d\n",
|
||
argv[0], status);
|
||
exit(-1);
|
||
}
|
||
}
|
||
|
||
if (FD_ISSET(fileno(stdin), &fdset))
|
||
{
|
||
/* Read a line from the user. */
|
||
char *line = fgets(buf, sizeof(buf)-1, stdin);
|
||
|
||
line = string_trim(line);
|
||
|
||
if (!strcmp(line, "quit") || !strcmp(line, "exit"))
|
||
{
|
||
fprintf(stderr, "buh bye now\n");
|
||
exit(0);
|
||
}
|
||
else if (!strncmp(line, "abort ", 6))
|
||
{
|
||
long id;
|
||
status = sscanf(line+6, "%lu\n", &id);
|
||
if (status != 1)
|
||
{
|
||
test_id_cons *cons = test_list;
|
||
while(cons)
|
||
{
|
||
if (!strcmp(cons->name, line+6)) break;
|
||
cons = cons->next;
|
||
}
|
||
if (cons)
|
||
id = (long) cons->id;
|
||
else
|
||
{
|
||
fprintf(stderr, "%s: %s is not a known host or id.\n",
|
||
argv[0], line+6);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
status = DNS_AbortHostLookup((void *)id);
|
||
if (status < 0)
|
||
fprintf(stderr, "%s: DNS_AbortHostLookup(%ld) returned %d\n",
|
||
argv[0], id, status);
|
||
}
|
||
else if (strchr(line, ' ') || strchr(line, '\t'))
|
||
{
|
||
fprintf(stderr, "%s: unrecognized command %s.\n",
|
||
argv[0], line);
|
||
}
|
||
else
|
||
{
|
||
void *id = 0;
|
||
test_id_cons *cons = 0;
|
||
|
||
fprintf(stderr, "%s: looking up %s...", argv[0], line);
|
||
status = DNS_AsyncLookupHost(line, test_cb, argv[0], &id);
|
||
if (status == 0)
|
||
fprintf(stderr, " id = %lu.\n", (long)id);
|
||
else
|
||
fprintf(stderr,
|
||
"\n%s: DNS_AsyncLookupHost(%s) returned %d (id = %lu)\n",
|
||
argv[0], line, status, (long)id);
|
||
|
||
cons = (test_id_cons *) malloc(sizeof(*cons));
|
||
cons->name = strdup(line);
|
||
cons->id = id;
|
||
cons->next = test_list;
|
||
test_list = cons;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif /* STANDALONE */
|
||
#endif /* UNIX_ASYNC_DNS && XP_UNIX */
|