/* * Copyright (c) 1985, 1989 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef lint static char Version[] = "@(#)send.c e07@nikhef.nl (Eric Wassenaar) 930209"; #endif #include <stdio.h> #include <errno.h> #include <setjmp.h> #include <signal.h> #include <sys/time.h> #include <sys/param.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/nameser.h> #include <resolv.h> #define input /* read-only input parameter */ #define output /* modified output parameter */ #if defined(apollo) typedef int sig_t; #else typedef void sig_t; #endif #define bitset(a,b) (((a) & (b)) != 0) #ifdef ULTRIX_RESOLV #define nslist(i) _res.ns_list[i].addr #else #define nslist(i) _res.nsaddr_list[i] #endif extern int errno; extern struct state _res; /* defined in res_init.c */ static int timeout; /* connection read timeout */ extern char *inet_ntoa(); /* send.c */ int res_send(); #ifndef lint void _res_close(); #endif static int send_stream(); static int send_dgram(); static sig_t timer(); int _res_connect(); int _res_write(); int _res_read(); static int recvsock(); /* ** RES_SEND -- Send nameserver query and retrieve answer ** ----------------------------------------------------- ** ** Returns: ** Length of nameserver answer buffer, if obtained. ** -1 if an error occurred (errno set appropriately). ** ** This is a simplified version of the BIND 4.8.3 res_send(). ** - Always use connected datagrams to get proper error messages. ** - Do not only return ETIMEDOUT or ECONNREFUSED in datagram mode. ** - Never leave a connection open after we got an answer. ** - No special ECONNRESET handling when using virtual circuits. */ int res_send(query, querylen, answer, anslen) input char *query; /* address of formatted query buffer */ input int querylen; /* length of query buffer */ output char *answer; /* address of buffer to store answer */ input int anslen; /* maximum size of answer buffer */ { HEADER *bp = (HEADER *)answer; struct sockaddr_in *sin; int v_circuit; /* virtual circuit or datagram switch */ register int try, ns; register int n; /* make sure resolver has been initialized */ if (!bitset(RES_INIT, _res.options) && res_init() == -1) return(-1); if (bitset(RES_DEBUG, _res.options)) { printf("res_send()\n"); p_query(query); } /* use virtual circuit if requested or if necessary */ v_circuit = bitset(RES_USEVC, _res.options) || (querylen > PACKETSZ); /* * Do _res.retry attempts for each of the _res.nscount addresses. */ for (try = 0; try < _res.retry; try++) { for (ns = 0; ns < _res.nscount; ns++) { sin = &nslist(ns); retry: if (bitset(RES_DEBUG, _res.options)) printf("Querying server (# %d) %s address = %s\n", ns+1, v_circuit ? "tcp" : "udp", inet_ntoa(sin->sin_addr)); if (v_circuit) { /* at most one attempt per server */ try = _res.retry; /* connect via virtual circuit */ n = send_stream(sin, query, querylen, answer, anslen); } else { /* set datagram read timeout for recvsock */ timeout = (_res.retrans << try); if (try > 0) timeout /= _res.nscount; if (timeout <= 0) timeout = 1; /* connect via datagram */ n = send_dgram(sin, query, querylen, answer, anslen); /* check truncation; use v_circuit with same server */ if (n > 0 && bp->tc) { if (bitset(RES_DEBUG, _res.options)) (void) fprintf(stderr, "truncated answer\n"); if (!bitset(RES_IGNTC, _res.options)) { v_circuit = 1; goto retry; } } } if (n <= 0) continue; if (bitset(RES_DEBUG, _res.options)) { printf("got answer:\n"); p_query(answer); } /* we have an answer; clear possible error condition */ errno = 0; return(n); } } return(-1); } /* * Note that this private version of res_send() is not only called * directly by 'host' but also indirectly by gethostbyname() or by * gethostbyaddr() via their resolver interface routines. */ /* * Provide dummy routine to prevent the real res_send() to be loaded. * This one is actually only called by endhostent() to close a socket * that was requested to stay open. But in this version sockets are * always closed after use. */ #ifndef lint void _res_close() { } #endif /* ** SEND_STREAM -- Query nameserver via virtual circuit ** --------------------------------------------------- ** ** Returns: ** Length of nameserver answer buffer, if obtained. ** -1 if an error occurred. ** ** Note that connect() is the call that is allowed to fail ** under normal circumstances. All other failures generate ** an unconditional error message. */ static int send_stream(addr, query, querylen, answer, anslen) input struct sockaddr_in *addr; /* the server address to connect to */ input char *query; /* address of formatted query buffer */ input int querylen; /* length of query buffer */ output char *answer; /* address of buffer to store answer */ input int anslen; /* maximum size of answer buffer */ { int sock; register int n; /* * Setup a virtual circuit connection. */ sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return(-1); } if (_res_connect(sock, addr, sizeof(*addr)) < 0) { if (bitset(RES_DEBUG, _res.options)) perror("connect"); (void) close(sock); return(-1); } if (bitset(RES_DEBUG, _res.options)) printf("connected to %s\n", inet_ntoa(addr->sin_addr)); /* * Send the query buffer. */ if (_res_write(sock, query, querylen) < 0) { (void) close(sock); return(-1); } /* * Read the answer buffer. */ n = _res_read(sock, answer, anslen); if (n <= 0) { (void) close(sock); return(-1); } /* * Never leave the socket open. */ (void) close(sock); return(n); } /* ** SEND_DGRAM -- Query nameserver via datagram ** ------------------------------------------- ** ** Returns: ** Length of nameserver answer buffer, if obtained. ** -1 if an error occurred. ** ** Inputs: ** The global variable timeout should have been ** set with the desired timeout value in seconds. ** ** Sending to a nameserver datagram port with no nameserver running ** will cause an ICMP port unreachable message to be returned. If the ** socket is connected, we get an ECONNREFUSED error on the next socket ** operation, and select returns if the error message is received. ** Also, we get ENETUNREACH or EHOSTUNREACH errors if appropriate. ** We thus get a proper error status before timing out. ** This method usually works only if BSD >= 43. ** ** Note that send() and recv() are now the calls that are allowed ** to fail under normal circumstances. All other failures generate ** an unconditional error message. */ static int send_dgram(addr, query, querylen, answer, anslen) input struct sockaddr_in *addr; /* the server address to connect to */ input char *query; /* address of formatted query buffer */ input int querylen; /* length of query buffer */ output char *answer; /* address of buffer to store answer */ input int anslen; /* maximum size of answer buffer */ { HEADER *qp = (HEADER *)query; HEADER *bp = (HEADER *)answer; int sock; register int n; /* * Setup a connected datagram socket. */ sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return(-1); } if (connect(sock, (struct sockaddr *)addr, sizeof(*addr)) < 0) { perror("connect"); (void) close(sock); return(-1); } /* * Send the query buffer. */ if (send(sock, query, querylen, 0) != querylen) { if (bitset(RES_DEBUG, _res.options)) perror("send"); (void) close(sock); return(-1); } /* * Wait for the arrival of a reply, timeout, or error message. */ wait: n = recvsock(sock, answer, anslen); if (n <= 0) { if (bitset(RES_DEBUG, _res.options)) perror("recvfrom"); (void) close(sock); return(-1); } /* * Make sure it is the proper response by checking the packet id. */ if (qp->id != bp->id) { if (bitset(RES_DEBUG, _res.options)) { printf("old answer:\n"); p_query(answer); } goto wait; } /* * Never leave the socket open. */ (void) close(sock); return(n); } /* ** _RES_CONNECT -- Connect to a stream (virtual circuit) socket ** ------------------------------------------------------------ ** ** Returns: ** 0 if successfully connected. ** -1 in case of failure or timeout. ** ** Note that we use _res.retrans to override the default ** connect timeout value. */ static jmp_buf timer_buf; static sig_t /*ARGSUSED*/ timer(sig) int sig; { longjmp(timer_buf, 1); } int _res_connect(sock, addr, addrlen) input int sock; input struct sockaddr_in *addr; /* the server address to connect to */ input int addrlen; { if (setjmp(timer_buf) != 0) { errno = ETIMEDOUT; (void) alarm((u_int)0); return(-1); } (void) signal(SIGALRM, timer); (void) alarm((u_int)_res.retrans); if (connect(sock, (struct sockaddr *)addr, addrlen) < 0) { if (errno == EINTR) errno = ETIMEDOUT; (void) alarm((u_int)0); return(-1); } (void) alarm((u_int)0); return(0); } /* ** _RES_WRITE -- Write the query buffer via a stream socket ** -------------------------------------------------------- ** ** Returns: ** Length of buffer if successfully transmitted. ** -1 in case of failure (error message is issued). ** ** The query is sent in two steps: first a single word with ** the length of the buffer, followed by the buffer itself. */ int _res_write(sock, buf, bufsize) input int sock; input char *buf; /* address of formatted query buffer */ input int bufsize; /* length of query buffer */ { u_short len; /* * Write the length of the query buffer. */ len = htons(bufsize); if (write(sock, (char *)&len, sizeof(len)) != sizeof(len)) { perror("write query length"); return(-1); } /* * Write the query buffer itself. */ if (write(sock, buf, bufsize) != bufsize) { perror("write query"); return(-1); } return(bufsize); } /* ** _RES_READ -- Read the answer buffer via a stream socket ** ------------------------------------------------------- ** ** Returns: ** Length of buffer if successfully received. ** -1 in case of failure (error message is issued). ** ** The answer is read in two steps: first a single word which ** gives the length of the buffer, followed by the buffer itself. ** If the answer is too long to fit into the supplied buffer, ** only the portion that fits will be stored, the residu will be ** flushed, and the truncation flag will be set. */ int _res_read(sock, buf, bufsize) input int sock; output char *buf; /* address of buffer to store answer */ input int bufsize; /* maximum size of answer buffer */ { u_short len; char *buffer; int buflen; int reslen; register int n; /* set stream timeout for recvsock */ timeout = 60; /* * Read the length of answer buffer. */ buffer = (char *)&len; buflen = sizeof(len); while (buflen > 0 && (n = recvsock(sock, buffer, buflen)) > 0) { buffer += n; buflen -= n; } if (buflen != 0) { perror("read answer length"); return(-1); } /* * Terminate if length is zero. */ len = ntohs(len); if (len == 0) return(0); /* * Check for truncation. */ reslen = 0; if ((int)len > bufsize) { reslen = len - bufsize; len = bufsize; } /* * Read the answer buffer itself. */ buffer = buf; buflen = len; while (buflen > 0 && (n = recvsock(sock, buffer, buflen)) > 0) { buffer += n; buflen -= n; } if (buflen != 0) { perror("read answer"); return(-1); } /* * Discard the residu to keep connection in sync. */ if (reslen > 0) { HEADER *bp = (HEADER *)buf; char resbuf[PACKETSZ]; buffer = resbuf; buflen = reslen < sizeof(resbuf) ? reslen : sizeof(resbuf); while (reslen > 0 && (n = recvsock(sock, buffer, buflen)) > 0) { reslen -= n; buflen = reslen < sizeof(resbuf) ? reslen : sizeof(resbuf); } if (reslen != 0) { perror("read residu"); return(-1); } if (bitset(RES_DEBUG, _res.options)) (void) fprintf(stderr, "response truncated\n"); /* set truncation flag */ bp->tc = 1; } return(len); } /* ** RECVSOCK -- Read from stream or datagram socket with timeout ** ------------------------------------------------------------ ** ** Returns: ** Length of buffer if successfully received. ** -1 in case of failure or timeout. ** ** Inputs: ** The global variable timeout should have been ** set with the desired timeout value in seconds. */ static int recvsock(sock, buffer, buflen) input int sock; output char *buffer; /* current buffer address */ input int buflen; /* remaining buffer size */ { fd_set fds; struct timeval wait; register int n; wait.tv_sec = timeout; wait.tv_usec = 0; /* FD_ZERO(&fds); */ bzero((char *)&fds, sizeof(fds)); FD_SET(sock, &fds); /* * Wait for the arrival of data, or timeout. */ n = select(FD_SETSIZE, &fds, (fd_set *)NULL, (fd_set *)NULL, &wait); if (n <= 0) { if (n == 0) errno = ETIMEDOUT; return(-1); } /* * Fake an error if nothing was actually read. */ n = recv(sock, buffer, buflen, 0); if (n == 0) errno = ECONNRESET; return(n); }