added basic ssl support to ircu
[ircu2.10.12-pk.git] / ircd / ircd_res.c
index 8e9f33ca3b038942f3f83143cb4293927e45218b..627e12e826fa813361d0a2737b7e8543aa383ec9 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * A rewrite of Darren Reeds original res.c As there is nothing
- * left of Darrens original code, this is now licensed by the hybrid group.
+ * A rewrite of Darren Reed's original res.c As there is nothing
+ * left of Darren's original code, this is now licensed by the hybrid group.
  * (Well, some of the function names are the same, and bits of the structs..)
  * You can use it where it is useful, free even. Buy us a beer and stuff.
  *
@@ -56,6 +56,10 @@ static struct Socket res_socket_v4;
 static struct Socket res_socket_v6;
 /** Next DNS lookup timeout. */
 static struct Timer res_timeout;
+/** Local address for IPv4 DNS lookups. */
+struct irc_sockaddr VirtualHost_dns_v4;
+/** Local address for IPv6 DNS lookups. */
+struct irc_sockaddr VirtualHost_dns_v6;
 /** Check for whether the resolver has been initialized yet. */
 #define resolver_started() (request_list.next != NULL)
 
@@ -115,17 +119,18 @@ struct reslist
   time_t timeout;          /**< When this request times out. */
   struct irc_in_addr addr; /**< Address for this request. */
   char *name;              /**< Hostname for this request. */
-  struct DNSQuery query;   /**< Query callback for this request. */
+  dns_callback_f callback; /**< Callback function on completion. */
+  void *callback_ctx;      /**< Context pointer for callback. */
 };
 
 /** Base of request list. */
 static struct dlink request_list;
 
 static void rem_request(struct reslist *request);
-static struct reslist *make_request(const struct DNSQuery *query);
-static void do_query_name(const struct DNSQuery *query,
+static struct reslist *make_request(dns_callback_f callback, void *ctx);
+static void do_query_name(dns_callback_f callback, void *ctx,
                           const char* name, struct reslist *request, int);
-static void do_query_number(const struct DNSQuery *query,
+static void do_query_number(dns_callback_f callback, void *ctx,
                             const struct irc_in_addr *,
                             struct reslist *request);
 static void query_name(const char *name, int query_class, int query_type,
@@ -134,7 +139,6 @@ static int send_res_msg(const char *buf, int len, int count);
 static void resend_query(struct reslist *request);
 static int proc_answer(struct reslist *request, HEADER *header, char *, char *);
 static struct reslist *find_id(int id);
-static struct DNSReply *make_dnsreply(struct reslist *request);
 static void res_readreply(struct Event *ev);
 static void timeout_resolver(struct Event *notused);
 
@@ -142,6 +146,16 @@ extern struct irc_sockaddr irc_nsaddr_list[IRCD_MAXNS];
 extern int irc_nscount;
 extern char irc_domain[HOSTLEN];
 
+/** Prepare the resolver library to (optionally) accept a list of
+ * DNS servers through add_dns_server().
+ */
+void clear_nameservers(void)
+{
+  irc_nscount = 0;
+  memset(&VirtualHost_dns_v4, 0, sizeof(VirtualHost_dns_v4));
+  memset(&VirtualHost_dns_v6, 0, sizeof(VirtualHost_dns_v6));
+}
+
 /** Check whether \a inp is a nameserver we use.
  * @param[in] inp Nameserver address.
  * @return Non-zero if we trust \a inp; zero if not.
@@ -166,26 +180,43 @@ res_ourserver(const struct irc_sockaddr *inp)
 void
 restart_resolver(void)
 {
+  int need_v4;
+  int need_v6;
+  int ns;
+
   irc_res_init();
 
   if (!request_list.next)
     request_list.next = request_list.prev = &request_list;
 
-  if (!s_active(&res_socket_v4))
+  /* Check which address family (or families) our nameservers use. */
+  for (need_v4 = need_v6 = ns = 0; ns < irc_nscount; ns++)
+  {
+    if (irc_in_addr_is_ipv4(&irc_nsaddr_list[ns].addr))
+      need_v4 = 1;
+    else
+      need_v6 = 1;
+  }
+
+  /* If we need an IPv4 socket, and don't have one, open it. */
+  if (need_v4 && !s_active(&res_socket_v4))
   {
-    int fd = os_socket(&VirtualHost_v4, SOCK_DGRAM, "Resolver UDPv4 socket");
+    int fd = os_socket(&VirtualHost_dns_v4, SOCK_DGRAM, "Resolver UDPv4 socket", AF_INET);
     if (fd >= 0)
       socket_add(&res_socket_v4, res_readreply, NULL,
                  SS_DATAGRAM, SOCK_EVENT_READABLE, fd);
   }
 
-  if (!s_active(&res_socket_v6))
+#ifdef AF_INET6
+  /* If we need an IPv6 socket, and don't have one, open it. */
+  if (need_v6 && !s_active(&res_socket_v6))
   {
-    int fd = os_socket(&VirtualHost_v6, SOCK_DGRAM, "Resolver UDPv6 socket");
+    int fd = os_socket(&VirtualHost_dns_v6, SOCK_DGRAM, "Resolver UDPv6 socket", AF_INET6);
     if (fd >= 0)
       socket_add(&res_socket_v6, res_readreply, NULL,
                  SS_DATAGRAM, SOCK_EVENT_READABLE, fd);
   }
+#endif
 
   if (s_active(&res_socket_v4) || s_active(&res_socket_v6))
     timer_init(&res_timeout);
@@ -248,7 +279,7 @@ rem_request(struct reslist *request)
  * @return Newly allocated and linked-in reslist.
  */
 static struct reslist *
-make_request(const struct DNSQuery* query)
+make_request(dns_callback_f callback, void *ctx)
 {
   struct reslist *request;
 
@@ -264,7 +295,8 @@ make_request(const struct DNSQuery* query)
   request->resend  = 1;
   request->timeout = feature_int(FEAT_IRCD_RES_TIMEOUT);
   memset(&request->addr, 0, sizeof(request->addr));
-  memcpy(&request->query, query, sizeof(request->query));
+  request->callback = callback;
+  request->callback_ctx = ctx;
 
   add_dlink(&request->node, &request_list);
   return(request);
@@ -278,14 +310,18 @@ check_resolver_timeout(time_t when)
 {
   if (when > CurrentTime + AR_TTL)
     when = CurrentTime + AR_TTL;
-  if (t_onqueue(&res_timeout))
+  /* TODO after 2.10.12: Rewrite the timer API because there should be
+   * no need for clients to know this kind of implementation detail. */
+  if (when > t_expire(&res_timeout))
+    /* do nothing */;
+  else if (t_onqueue(&res_timeout) && !(res_timeout.t_header.gh_flags & GEN_MARKED))
     timer_chg(&res_timeout, TT_ABSOLUTE, when);
   else
     timer_add(&res_timeout, timeout_resolver, NULL, TT_ABSOLUTE, when);
 }
 
 /** Drop pending DNS lookups which have timed out.
- * @param[in] notused Timer event data (ignored).
+ * @param[in] ev Timer event data (ignored).
  */
 static void
 timeout_resolver(struct Event *ev)
@@ -309,7 +345,7 @@ timeout_resolver(struct Event *ev)
       if (--request->retries <= 0)
       {
         Debug((DEBUG_DNS, "Request %p out of retries; destroying", request));
-        (*request->query.callback)(request->query.vptr, 0);
+        (*request->callback)(request->callback_ctx, NULL, NULL);
         rem_request(request);
         continue;
       }
@@ -348,7 +384,7 @@ delete_resolver_queries(const void *vptr)
     {
       next_ptr = ptr->next;
       request = (struct reslist*)ptr;
-      if (vptr == request->query.vptr) {
+      if (vptr == request->callback_ctx) {
         Debug((DEBUG_DNS, "Removing request %p with vptr %p", request, vptr));
         rem_request(request);
       }
@@ -413,9 +449,9 @@ find_id(int id)
  * @param[in] query Callback information.
  */
 void
-gethost_byname(const char *name, const struct DNSQuery *query)
+gethost_byname(const char *name, dns_callback_f callback, void *ctx)
 {
-  do_query_name(query, name, NULL, T_AAAA);
+  do_query_name(callback, ctx, name, NULL, T_AAAA);
 }
 
 /** Try to look up hostname for an address.
@@ -423,9 +459,9 @@ gethost_byname(const char *name, const struct DNSQuery *query)
  * @param[in] query Callback information.
  */
 void
-gethost_byaddr(const struct irc_in_addr *addr, const struct DNSQuery *query)
+gethost_byaddr(const struct irc_in_addr *addr, dns_callback_f callback, void *ctx)
 {
-  do_query_number(query, addr, NULL);
+  do_query_number(callback, ctx, addr, NULL);
 }
 
 /** Send a query to look up the address for a name.
@@ -435,7 +471,7 @@ gethost_byaddr(const struct irc_in_addr *addr, const struct DNSQuery *query)
  * @param[in] type Preferred request type.
  */
 static void
-do_query_name(const struct DNSQuery *query, const char *name,
+do_query_name(dns_callback_f callback, void *ctx, const char *name,
               struct reslist *request, int type)
 {
   char host_name[HOSTLEN + 1];
@@ -445,7 +481,7 @@ do_query_name(const struct DNSQuery *query, const char *name,
 
   if (request == NULL)
   {
-    request       = make_request(query);
+    request       = make_request(callback, ctx);
     DupString(request->name, host_name);
 #ifdef IPV6
     if (type != T_A)
@@ -466,7 +502,7 @@ do_query_name(const struct DNSQuery *query, const char *name,
  * @param[in] request DNS lookup structure (may be NULL).
  */
 static void
-do_query_number(const struct DNSQuery *query, const struct irc_in_addr *addr,
+do_query_number(dns_callback_f callback, void *ctx, const struct irc_in_addr *addr,
                 struct reslist *request)
 {
   char ipbuf[128];
@@ -511,7 +547,7 @@ do_query_number(const struct DNSQuery *query, const struct irc_in_addr *addr,
   }
   if (request == NULL)
   {
-    request       = make_request(query);
+    request       = make_request(callback, ctx);
     request->state= REQ_PTR;
     request->type = T_PTR;
     memcpy(&request->addr, addr, sizeof(request->addr));
@@ -571,15 +607,15 @@ resend_query(struct reslist *request)
   switch(request->type)
   {
     case T_PTR:
-      do_query_number(NULL, &request->addr, request);
+      do_query_number(NULL, NULL, &request->addr, request);
       break;
     case T_A:
-      do_query_name(NULL, request->name, request, request->type);
+      do_query_name(NULL, NULL, request->name, request, request->type);
       break;
     case T_AAAA:
       /* didn't work, try A */
       if (request->state == REQ_AAAA)
-        do_query_name(NULL, request->name, request, T_A);
+        do_query_name(NULL, NULL, request->name, request, T_A);
     default:
       break;
   }
@@ -597,7 +633,6 @@ proc_answer(struct reslist *request, HEADER* header, char* buf, char* eob)
 {
   char hostbuf[HOSTLEN + 100]; /* working buffer */
   unsigned char *current;      /* current position in buf */
-  int query_class;             /* answer class */
   int type;                    /* answer type */
   int n;                       /* temp count */
   int rd_length;
@@ -651,9 +686,8 @@ proc_answer(struct reslist *request, HEADER* header, char* buf, char* eob)
     type = irc_ns_get16(current);
     current += TYPE_SIZE;
 
-    query_class = irc_ns_get16(current);
+    /* We do not use the class or TTL values. */
     current += CLASS_SIZE;
-
     current += TTL_SIZE;
 
     rd_length = irc_ns_get16(current);
@@ -701,9 +735,6 @@ proc_answer(struct reslist *request, HEADER* header, char* buf, char* eob)
         break;
       case T_CNAME: /* first check we already haven't started looking
                        into a cname */
-        if (request->type != T_PTR)
-          return(0);
-
         if (request->state == REQ_CNAME)
         {
           n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob,
@@ -723,7 +754,13 @@ proc_answer(struct reslist *request, HEADER* header, char* buf, char* eob)
          * but its possible its just a broken nameserver with still
          * valid answers. But lets do some rudimentary logging for now...
          */
-          log_write(LS_RESOLVER, L_ERROR, 0, "irc_res.c bogus type %d", type);
+        log_write(LS_RESOLVER, L_ERROR, 0, "irc_res.c bogus type %d", type);
+
+        if ((char*)current + rd_length >= (char*)current)
+          current += rd_length;
+        else
+          return(0);
+
         break;
     }
   }
@@ -742,7 +779,6 @@ res_readreply(struct Event *ev)
   char buf[sizeof(HEADER) + MAXPACKET];
   HEADER *header;
   struct reslist *request = NULL;
-  struct DNSReply *reply  = NULL;
   unsigned int rc;
   int answer_count;
 
@@ -753,6 +789,12 @@ res_readreply(struct Event *ev)
       || (rc <= sizeof(HEADER)))
     return;
 
+  /*
+   * check against possibly fake replies
+   */
+  if (!res_ourserver(&lsin))
+    return;
+
   /*
    * convert DNS reply reader from Network byte order to CPU byte order.
    */
@@ -769,16 +811,18 @@ res_readreply(struct Event *ev)
   if (0 == (request = find_id(header->id)))
     return;
 
-  /*
-   * check against possibly fake replies
-   */
-  if (!res_ourserver(&lsin))
-    return;
-
   if ((header->rcode != NO_ERRORS) || (header->ancount == 0))
   {
-    if (SERVFAIL == header->rcode)
-      resend_query(request);
+    if (SERVFAIL == header->rcode || NXDOMAIN == header->rcode)
+    {
+        /*
+         * If a bad error was returned, we stop here and don't send
+         * send any more (no retries granted).
+         */
+        Debug((DEBUG_DNS, "Request %p has bad response (state %d type %d rcode %d)", request, request->state, request->type, header->rcode));
+        (*request->callback)(request->callback_ctx, NULL, NULL);
+       rem_request(request);
+    }
     else
     {
       /*
@@ -798,16 +842,6 @@ res_readreply(struct Event *ev)
         request->timeout += feature_int(FEAT_IRCD_RES_TIMEOUT);
         resend_query(request);
       }
-      else
-      {
-        /*
-         * If a bad error was returned, we stop here and don't send
-         * send any more (no retries granted).
-         */
-        Debug((DEBUG_DNS, "Request %p has bad response (state %d type %d rcode %d)", request, request->state, request->type, header->rcode));
-        (*request->query.callback)(request->query.vptr, 0);
-       rem_request(request);
-      }
     }
 
     return;
@@ -829,7 +863,7 @@ res_readreply(struct Event *ev)
          * don't bother trying again, the client address doesn't resolve
          */
         Debug((DEBUG_DNS, "Request %p PTR had empty name", request));
-        (*request->query.callback)(request->query.vptr, reply);
+        (*request->callback)(request->callback_ctx, NULL, NULL);
         rem_request(request);
         return;
       }
@@ -840,10 +874,10 @@ res_readreply(struct Event *ev)
        */
 #ifdef IPV6
       if (!irc_in_addr_is_ipv4(&request->addr))
-        do_query_name(&request->query, request->name, NULL, T_AAAA);
+        do_query_name(request->callback, request->callback_ctx, request->name, NULL, T_AAAA);
       else
 #endif
-      do_query_name(&request->query, request->name, NULL, T_A);
+      do_query_name(request->callback, request->callback_ctx, request->name, NULL, T_A);
       Debug((DEBUG_DNS, "Request %p switching to forward resolution", request));
       rem_request(request);
     }
@@ -852,8 +886,7 @@ res_readreply(struct Event *ev)
       /*
        * got a name and address response, client resolved
        */
-      reply = make_dnsreply(request);
-      (*request->query.callback)(request->query.vptr, (reply) ? reply : 0);
+      (*request->callback)(request->callback_ctx, &request->addr, request->name);
       Debug((DEBUG_DNS, "Request %p got forward resolution", request));
       rem_request(request);
     }
@@ -871,23 +904,6 @@ res_readreply(struct Event *ev)
   }
 }
 
-/** Build a DNSReply for a completed request.
- * @param[in] request Completed DNS request.
- * @return Newly allocated DNSReply containing host name and address.
- */
-static struct DNSReply *
-make_dnsreply(struct reslist *request)
-{
-  struct DNSReply *cp;
-  assert(request != 0);
-
-  cp = (struct DNSReply *)MyMalloc(sizeof(struct DNSReply));
-
-  DupString(cp->h_name, request->name);
-  memcpy(&cp->addr, &request->addr, sizeof(cp->addr));
-  return(cp);
-}
-
 /** Statistics callback to list DNS servers.
  * @param[in] source_p Client requesting statistics.
  * @param[in] sd Stats descriptor for request (ignored).