Coverage Report

Created: 2026-10-28 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/dnsquery.c
Line
Count
Source
1
/**
2
 * @file dnsquery.c DNS query API
3
 * @ingroup core
4
 */
5
6
/* purple
7
 *
8
 * Purple is the legal property of its developers, whose names are too numerous
9
 * to list here.  Please refer to the COPYRIGHT file distributed with this
10
 * source distribution.
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program; if not, write to the Free Software
24
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
25
 *
26
 */
27
#define _PURPLE_DNSQUERY_C_
28
29
#include "internal.h"
30
#include "debug.h"
31
#include "dnsquery.h"
32
#include "glibcompat.h"
33
#include "network.h"
34
#include "notify.h"
35
#include "prefs.h"
36
#include "util.h"
37
38
#ifndef _WIN32
39
#include <resolv.h>
40
#endif
41
42
0
#define MAX_ADDR_RESPONSE_LEN 1048576
43
44
#if (defined(__APPLE__) || defined (__unix__)) && !defined(__osf__)
45
#define PURPLE_DNSQUERY_USE_FORK
46
#endif
47
/**************************************************************************
48
 * DNS query API
49
 **************************************************************************/
50
51
static PurpleDnsQueryUiOps *dns_query_ui_ops = NULL;
52
53
typedef struct _PurpleDnsQueryResolverProcess PurpleDnsQueryResolverProcess;
54
55
struct _PurpleDnsQueryData {
56
  char *hostname;
57
  int port;
58
  PurpleDnsQueryConnectFunction callback;
59
  gpointer data;
60
  guint timeout;
61
  PurpleAccount *account;
62
63
#if defined(PURPLE_DNSQUERY_USE_FORK)
64
  PurpleDnsQueryResolverProcess *resolver;
65
#elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK  */
66
  GThread *resolver;
67
  GSList *hosts;
68
  gchar *error_message;
69
#endif
70
};
71
72
#if defined(PURPLE_DNSQUERY_USE_FORK)
73
74
0
#define MAX_DNS_CHILDREN 4
75
76
/*
77
 * This structure keeps a reference to a child resolver process.
78
 */
79
struct _PurpleDnsQueryResolverProcess {
80
  guint inpa;
81
  int fd_in, fd_out;
82
  pid_t dns_pid;
83
};
84
85
static GSList *free_dns_children = NULL;
86
/* TODO: Make me a GQueue when we require >= glib 2.4 */
87
static GSList *queued_requests = NULL;
88
89
static int number_of_dns_children = 0;
90
91
/*
92
 * This is a convenience struct used to pass data to
93
 * the child resolver process.
94
 */
95
typedef struct {
96
  char hostname[512];
97
  int port;
98
} dns_params_t;
99
#endif /* end PURPLE_DNSQUERY_USE_FORK */
100
101
static void
102
purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts)
103
0
{
104
0
  purple_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname);
105
0
  if (query_data->callback != NULL)
106
0
    query_data->callback(hosts, query_data->data, NULL);
107
0
  else
108
0
  {
109
    /*
110
     * Callback is a required parameter, but it can get set to
111
     * NULL if we cancel a thread-based DNS lookup.  So we need
112
     * to free hosts.
113
     *
114
     * The host's list is pairs of file descriptors and sockaddr_in's.
115
     */
116
0
    while (hosts != NULL)
117
0
    {
118
0
      hosts = g_slist_delete_link(hosts, hosts);
119
0
      g_free(hosts->data);
120
0
      hosts = g_slist_delete_link(hosts, hosts);
121
0
    }
122
0
  }
123
124
0
#ifdef PURPLE_DNSQUERY_USE_FORK
125
  /*
126
   * Add the resolver to the list of available resolvers, and set it
127
   * to NULL so that it doesn't get destroyed along with the query_data
128
   */
129
0
  if (query_data->resolver)
130
0
  {
131
0
    free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver);
132
0
    query_data->resolver = NULL;
133
0
  }
134
0
#endif /* PURPLE_DNSQUERY_USE_FORK */
135
136
0
  purple_dnsquery_destroy(query_data);
137
0
}
138
139
static void
140
purple_dnsquery_failed(PurpleDnsQueryData *query_data, const gchar *error_message)
141
0
{
142
0
  purple_debug_error("dnsquery", "%s\n", error_message);
143
0
  if (query_data->callback != NULL)
144
0
    query_data->callback(NULL, query_data->data, error_message);
145
0
  purple_dnsquery_destroy(query_data);
146
0
}
147
148
static gboolean
149
purple_dnsquery_ui_resolve(PurpleDnsQueryData *query_data)
150
0
{
151
0
  PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
152
153
0
  if (ops && ops->resolve_host)
154
0
    return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed);
155
156
0
  return FALSE;
157
0
}
158
159
static gboolean
160
resolve_ip(PurpleDnsQueryData *query_data)
161
0
{
162
0
#if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST)
163
0
  struct addrinfo hints, *res;
164
0
  char servname[20];
165
166
0
  g_snprintf(servname, sizeof(servname), "%d", query_data->port);
167
0
  memset(&hints, 0, sizeof(hints));
168
0
  hints.ai_family = AF_UNSPEC;
169
0
  hints.ai_flags |= AI_NUMERICHOST;
170
171
0
  if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res))
172
0
  {
173
0
    GSList *hosts = NULL;
174
0
    hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen));
175
0
    hosts = g_slist_append(hosts, g_memdup2(res->ai_addr, res->ai_addrlen));
176
0
    purple_dnsquery_resolved(query_data, hosts);
177
178
0
    freeaddrinfo(res);
179
0
    return TRUE;
180
0
  }
181
#else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */
182
  struct sockaddr_in sin;
183
  if (inet_aton(query_data->hostname, &sin.sin_addr))
184
  {
185
    /*
186
     * The given "hostname" is actually an IP address, so we
187
     * don't need to do anything.
188
     */
189
    GSList *hosts = NULL;
190
    sin.sin_family = AF_INET;
191
    sin.sin_port = htons(query_data->port);
192
    hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
193
    hosts = g_slist_append(hosts, g_memdup2(&sin, sizeof(sin)));
194
    purple_dnsquery_resolved(query_data, hosts);
195
196
    return TRUE;
197
  }
198
#endif
199
200
0
  return FALSE;
201
0
}
202
203
#ifdef USE_IDN
204
static gboolean
205
dns_str_is_ascii(const char *name)
206
{
207
  guchar *c;
208
  for (c = (guchar *)name; c && *c; ++c) {
209
    if (*c > 0x7f)
210
      return FALSE;
211
  }
212
213
  return TRUE;
214
}
215
#endif
216
217
#if defined(PURPLE_DNSQUERY_USE_FORK)
218
219
/*
220
 * Unix!
221
 */
222
223
/*
224
 * Begin the DNS resolver child process functions.
225
 */
226
#ifdef HAVE_SIGNAL_H
227
G_GNUC_NORETURN static void
228
trap_gdb_bug(int sig)
229
0
{
230
0
  const char *message =
231
0
    "Purple's DNS child got a SIGTRAP signal.\n"
232
0
    "This can be caused by trying to run purple inside gdb.\n"
233
0
    "There is a known gdb bug which prevents this.  Supposedly purple\n"
234
0
    "should have detected you were using gdb and used an ugly hack,\n"
235
0
    "check cope_with_gdb_brokenness() in dnsquery.c.\n\n"
236
0
    "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n";
237
0
  fputs("\n* * *\n",stderr);
238
0
  fputs(message,stderr);
239
0
  fputs("* * *\n\n",stderr);
240
0
  execlp("xmessage","xmessage","-center", message, NULL);
241
0
  _exit(1);
242
0
}
243
#endif
244
245
static void
246
write_to_parent(int fd, const void *buf, size_t count)
247
0
{
248
0
  ssize_t written;
249
250
0
  written = write(fd, buf, count);
251
0
  if (written < 0 || (gsize)written != count) {
252
0
    if (written < 0)
253
0
      fprintf(stderr, "dns[%d]: Error writing data to "
254
0
          "parent: %s\n", getpid(), strerror(errno));
255
0
    else
256
0
      fprintf(stderr, "dns[%d]: Error: Tried to write %"
257
0
          G_GSIZE_FORMAT " bytes to parent but instead "
258
0
          "wrote %" G_GSIZE_FORMAT " bytes\n",
259
0
          getpid(), count, written);
260
0
  }
261
0
}
262
263
G_GNUC_NORETURN static void
264
purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
265
0
{
266
0
  dns_params_t dns_params;
267
0
  const size_t zero = 0;
268
0
  int rc;
269
0
#ifdef HAVE_GETADDRINFO
270
0
  struct addrinfo hints, *res, *tmp;
271
0
  char servname[20];
272
#else
273
  struct sockaddr_in sin;
274
  const size_t addrlen = sizeof(sin);
275
#endif
276
0
  char *hostname;
277
278
0
#ifdef HAVE_SIGNAL_H
279
0
  purple_restore_default_signal_handlers();
280
0
  signal(SIGTRAP, trap_gdb_bug);
281
0
#endif
282
283
  /*
284
   * We resolve 1 host name for each iteration of this
285
   * while loop.
286
   *
287
   * The top half of this reads in the hostname and port
288
   * number from the socket with our parent.  The bottom
289
   * half of this resolves the IP (blocking) and sends
290
   * the result back to our parent, when finished.
291
   */
292
0
  while (1) {
293
0
    fd_set fds;
294
0
    struct timeval tv = { .tv_sec = 20, .tv_usec = 0 };
295
0
    FD_ZERO(&fds);
296
0
    FD_SET(child_in, &fds);
297
0
    rc = select(child_in + 1, &fds, NULL, NULL, &tv);
298
0
    if (!rc) {
299
0
      if (show_debug)
300
0
        printf("dns[%d]: nobody needs me... =(\n", getpid());
301
0
      break;
302
0
    }
303
0
    rc = read(child_in, &dns_params, sizeof(dns_params_t));
304
0
    if (rc < 0) {
305
0
      fprintf(stderr, "dns[%d]: Error: Could not read dns_params: "
306
0
          "%s\n", getpid(), strerror(errno));
307
0
      break;
308
0
    }
309
0
    if (rc == 0) {
310
0
      if (show_debug)
311
0
        printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid());
312
0
      _exit(0);
313
0
    }
314
0
    if (dns_params.hostname[0] == '\0') {
315
0
      fprintf(stderr, "dns[%d]: Error: Parent requested resolution "
316
0
          "of an empty hostname (port = %d)!!!\n", getpid(),
317
0
          dns_params.port);
318
0
      _exit(1);
319
0
    }
320
321
#ifdef USE_IDN
322
    if (!dns_str_is_ascii(dns_params.hostname)) {
323
      rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname);
324
      if (rc != 0) {
325
        write_to_parent(child_out, &rc, sizeof(rc));
326
        if (show_debug)
327
          fprintf(stderr, "dns[%d] Error: IDN conversion returned "
328
              "%d\n", getpid(), rc);
329
        dns_params.hostname[0] = '\0';
330
        break;
331
      }
332
    } else /* intentional to execute the g_strdup */
333
#endif
334
0
    hostname = g_strdup(dns_params.hostname);
335
336
    /* We have the hostname and port, now resolve the IP */
337
338
0
#ifdef HAVE_GETADDRINFO
339
0
    g_snprintf(servname, sizeof(servname), "%d", dns_params.port);
340
0
    memset(&hints, 0, sizeof(hints));
341
342
    /* This is only used to convert a service
343
     * name to a port number. As we know we are
344
     * passing a number already, we know this
345
     * value will not be really used by the C
346
     * library.
347
     */
348
0
    hints.ai_socktype = SOCK_STREAM;
349
0
#ifdef AI_ADDRCONFIG
350
0
    hints.ai_flags |= AI_ADDRCONFIG;
351
0
#endif /* AI_ADDRCONFIG */
352
0
    rc = getaddrinfo(hostname, servname, &hints, &res);
353
0
    write_to_parent(child_out, &rc, sizeof(rc));
354
0
    if (rc != 0) {
355
0
      if (show_debug)
356
0
        printf("dns[%d] Error: getaddrinfo returned %d\n",
357
0
          getpid(), rc);
358
0
      dns_params.hostname[0] = '\0';
359
0
      g_free(hostname);
360
0
      hostname = NULL;
361
0
      break;
362
0
    }
363
0
    tmp = res;
364
0
    while (res) {
365
0
      size_t ai_addrlen = res->ai_addrlen;
366
0
      write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen));
367
0
      write_to_parent(child_out, res->ai_addr, res->ai_addrlen);
368
0
      res = res->ai_next;
369
0
    }
370
0
    freeaddrinfo(tmp);
371
#else
372
    struct hostent *hp;
373
    if (!(hp = gethostbyname(hostname))) {
374
      write_to_parent(child_out, &h_errno, sizeof(int));
375
      close(child_out);
376
      if (show_debug)
377
        printf("DNS Error: %d\n", h_errno);
378
      _exit(0);
379
    }
380
    memset(&sin, 0, sizeof(struct sockaddr_in));
381
    memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
382
    sin.sin_family = hp->h_addrtype;
383
384
    sin.sin_port = htons(dns_params.port);
385
    rc = 0;
386
    write_to_parent(child_out, &rc, sizeof(rc));
387
    write_to_parent(child_out, &addrlen, sizeof(addrlen));
388
    write_to_parent(child_out, &sin, addrlen);
389
#endif
390
0
    write_to_parent(child_out, &zero, sizeof(zero));
391
0
    dns_params.hostname[0] = '\0';
392
393
0
    g_free(hostname);
394
0
    hostname = NULL;
395
0
  }
396
397
0
  close(child_out);
398
0
  close(child_in);
399
400
0
  _exit(0);
401
0
}
402
/*
403
 * End the DNS resolver child process functions.
404
 */
405
406
/*
407
 * Begin the functions for dealing with the DNS child processes.
408
 */
409
static void
410
cope_with_gdb_brokenness(void)
411
0
{
412
0
#ifdef __linux__
413
0
  static gboolean already_done = FALSE;
414
0
  char s[256], e[512];
415
0
  int n;
416
0
  pid_t ppid;
417
418
0
  if(already_done)
419
0
    return;
420
0
  already_done = TRUE;
421
0
  ppid = getppid();
422
0
  g_snprintf(s, sizeof(s), "/proc/%d/exe", ppid);
423
0
  n = readlink(s, e, sizeof(e));
424
0
  if(n < 0)
425
0
    return;
426
427
0
  e[MIN((gsize)n,sizeof(e)-1)] = '\0';
428
429
0
  if(strstr(e,"gdb")) {
430
0
    purple_debug_info("dns",
431
0
           "Debugger detected, performing useless query...\n");
432
0
    gethostbyname("x.x.x.x.x");
433
0
  }
434
0
#endif
435
0
}
436
437
static void
438
purple_dnsquery_resolver_destroy(PurpleDnsQueryResolverProcess *resolver)
439
0
{
440
0
  g_return_if_fail(resolver != NULL);
441
442
  /* Keep this before the kill() call below. */
443
0
  if (resolver->inpa != 0) {
444
0
    purple_input_remove(resolver->inpa);
445
0
    resolver->inpa = 0;
446
0
  }
447
448
  /*
449
   * We might as well attempt to kill our child process.  It really
450
   * doesn't matter if this fails, because children will expire on
451
   * their own after a few seconds.
452
   */
453
0
  if (resolver->dns_pid > 0)
454
0
    kill(resolver->dns_pid, SIGKILL);
455
456
0
  close(resolver->fd_in);
457
0
  close(resolver->fd_out);
458
459
0
  g_free(resolver);
460
461
0
  number_of_dns_children--;
462
0
}
463
464
static PurpleDnsQueryResolverProcess *
465
purple_dnsquery_resolver_new(gboolean show_debug)
466
0
{
467
0
  PurpleDnsQueryResolverProcess *resolver;
468
0
  int child_out[2], child_in[2];
469
470
  /* Create pipes for communicating with the child process */
471
0
  if (pipe(child_out) || pipe(child_in)) {
472
0
    purple_debug_error("dns",
473
0
           "Could not create pipes: %s\n", g_strerror(errno));
474
0
    return NULL;
475
0
  }
476
477
0
  resolver = g_new0(PurpleDnsQueryResolverProcess, 1);
478
479
0
  cope_with_gdb_brokenness();
480
481
  /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */
482
0
  resolver->dns_pid = fork();
483
484
  /* If we are the child process... */
485
0
  if (resolver->dns_pid == 0) {
486
    /* We should not access the parent's side of the pipes, so close them */
487
0
    close(child_out[0]);
488
0
    close(child_in[1]);
489
490
0
    purple_dnsquery_resolver_run(child_out[1], child_in[0], show_debug);
491
    /* The thread calls _exit() rather than returning, so we never get here */
492
0
  }
493
494
  /* We should not access the child's side of the pipes, so close them */
495
0
  close(child_out[1]);
496
0
  close(child_in[0]);
497
0
  if (resolver->dns_pid == -1) {
498
0
    purple_debug_error("dns",
499
0
           "Could not create child process for DNS: %s\n",
500
0
           g_strerror(errno));
501
0
    purple_dnsquery_resolver_destroy(resolver);
502
0
    return NULL;
503
0
  }
504
505
0
  resolver->fd_out = child_out[0];
506
0
  resolver->fd_in = child_in[1];
507
0
  number_of_dns_children++;
508
0
  purple_debug_info("dns",
509
0
         "Created new DNS child %d, there are now %d children.\n",
510
0
         resolver->dns_pid, number_of_dns_children);
511
512
0
  return resolver;
513
0
}
514
515
/**
516
 * @return TRUE if the request was sent succesfully.  FALSE
517
 *         if the request could not be sent.  This isn't
518
 *         necessarily an error.  If the child has expired,
519
 *         for example, we won't be able to send the message.
520
 */
521
static gboolean
522
send_dns_request_to_child(PurpleDnsQueryData *query_data,
523
    PurpleDnsQueryResolverProcess *resolver)
524
0
{
525
0
  pid_t pid;
526
0
  dns_params_t dns_params;
527
0
  ssize_t rc;
528
529
  /* This waitpid might return the child's PID if it has recently
530
   * exited, or it might return an error if it exited "long
531
   * enough" ago that it has already been reaped; in either
532
   * instance, we can't use it. */
533
0
  pid = waitpid(resolver->dns_pid, NULL, WNOHANG);
534
0
  if (pid > 0) {
535
0
    purple_debug_warning("dns", "DNS child %d no longer exists\n",
536
0
        resolver->dns_pid);
537
0
    purple_dnsquery_resolver_destroy(resolver);
538
0
    return FALSE;
539
0
  } else if (pid < 0) {
540
0
    purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n",
541
0
        resolver->dns_pid, g_strerror(errno));
542
0
    purple_dnsquery_resolver_destroy(resolver);
543
0
    return FALSE;
544
0
  }
545
546
  /* Copy the hostname and port into a single data structure */
547
0
  strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1);
548
0
  dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0';
549
0
  dns_params.port = query_data->port;
550
551
  /* Send the data structure to the child */
552
0
  rc = write(resolver->fd_in, &dns_params, sizeof(dns_params));
553
0
  if (rc < 0) {
554
0
    purple_debug_error("dns", "Unable to write to DNS child %d: %s\n",
555
0
        resolver->dns_pid, g_strerror(errno));
556
0
    purple_dnsquery_resolver_destroy(resolver);
557
0
    return FALSE;
558
0
  }
559
0
  if ((gsize)rc < sizeof(dns_params)) {
560
0
    purple_debug_error("dns", "Tried to write %" G_GSSIZE_FORMAT
561
0
        " bytes to child but only wrote %" G_GSSIZE_FORMAT "\n",
562
0
        sizeof(dns_params), rc);
563
0
    purple_dnsquery_resolver_destroy(resolver);
564
0
    return FALSE;
565
0
  }
566
567
0
  purple_debug_info("dns",
568
0
      "Successfully sent DNS request to child %d\n",
569
0
      resolver->dns_pid);
570
571
0
  query_data->resolver = resolver;
572
573
0
  return TRUE;
574
0
}
575
576
static void host_resolved(gpointer data, gint source, PurpleInputCondition cond);
577
578
static void
579
handle_next_queued_request(void)
580
0
{
581
0
  PurpleDnsQueryData *query_data;
582
0
  PurpleDnsQueryResolverProcess *resolver;
583
584
0
  if (queued_requests == NULL)
585
    /* No more DNS queries, yay! */
586
0
    return;
587
588
0
  query_data = queued_requests->data;
589
0
  queued_requests = g_slist_delete_link(queued_requests, queued_requests);
590
591
  /*
592
   * If we have any children, attempt to have them perform the DNS
593
   * query.  If we're able to send the query then resolver will be
594
   * set to the PurpleDnsQueryResolverProcess.  Otherwise, resolver
595
   * will be NULL and we'll need to create a new DNS request child.
596
   */
597
0
  while (free_dns_children != NULL)
598
0
  {
599
0
    resolver = free_dns_children->data;
600
0
    free_dns_children = g_slist_remove(free_dns_children, resolver);
601
602
0
    if (send_dns_request_to_child(query_data, resolver))
603
      /* We found an acceptable child, yay */
604
0
      break;
605
0
  }
606
607
  /* We need to create a new DNS request child */
608
0
  if (query_data->resolver == NULL)
609
0
  {
610
0
    if (number_of_dns_children >= MAX_DNS_CHILDREN)
611
0
    {
612
      /* Apparently all our children are busy */
613
0
      queued_requests = g_slist_prepend(queued_requests, query_data);
614
0
      return;
615
0
    }
616
617
0
    resolver = purple_dnsquery_resolver_new(purple_debug_is_enabled());
618
0
    if (resolver == NULL)
619
0
    {
620
0
      purple_dnsquery_failed(query_data, _("Unable to create new resolver process\n"));
621
0
      return;
622
0
    }
623
0
    if (!send_dns_request_to_child(query_data, resolver))
624
0
    {
625
0
      purple_dnsquery_failed(query_data, _("Unable to send request to resolver process\n"));
626
0
      return;
627
0
    }
628
0
  }
629
630
0
  query_data->resolver->inpa = purple_input_add(query_data->resolver->fd_out,
631
0
      PURPLE_INPUT_READ, host_resolved, query_data);
632
0
}
633
634
/*
635
 * End the functions for dealing with the DNS child processes.
636
 */
637
638
static void
639
host_resolved(gpointer data, gint source, PurpleInputCondition cond)
640
0
{
641
0
  PurpleDnsQueryData *query_data;
642
0
  int rc, err;
643
0
  GSList *hosts = NULL;
644
0
  struct sockaddr *addr = NULL;
645
0
  size_t addrlen;
646
0
  char message[1024];
647
648
0
  query_data = data;
649
650
0
  purple_debug_info("dns", "Got response for '%s'\n", query_data->hostname);
651
0
  purple_input_remove(query_data->resolver->inpa);
652
0
  query_data->resolver->inpa = 0;
653
654
0
  rc = read(query_data->resolver->fd_out, &err, sizeof(err));
655
0
  if ((rc == 4) && (err != 0))
656
0
  {
657
0
#ifdef HAVE_GETADDRINFO
658
0
    g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"),
659
0
        query_data->hostname, purple_gai_strerror(err));
660
#else
661
    g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
662
        query_data->hostname, err);
663
#endif
664
    /* Re-read resolv.conf and friends in case DNS servers have changed */
665
0
    res_init();
666
667
0
    purple_dnsquery_failed(query_data, message);
668
0
  } else if (rc > 0) {
669
    /* Success! */
670
0
    while (rc > 0) {
671
0
      rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen));
672
0
      if (rc > 0 && addrlen > 0 && addrlen < MAX_ADDR_RESPONSE_LEN) {
673
0
        addr = g_malloc(addrlen);
674
0
        rc = read(query_data->resolver->fd_out, addr, addrlen);
675
0
        hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen));
676
0
        hosts = g_slist_append(hosts, addr);
677
0
      } else {
678
0
        break;
679
0
      }
680
0
    }
681
    /*  wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */
682
0
    purple_dnsquery_resolved(query_data, hosts);
683
684
0
  } else if (rc == -1) {
685
0
    g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno));
686
0
    purple_dnsquery_failed(query_data, message);
687
688
0
  } else if (rc == 0) {
689
0
    g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request"));
690
0
    purple_dnsquery_failed(query_data, message);
691
0
  }
692
693
0
  handle_next_queued_request();
694
0
}
695
696
static void
697
resolve_host(PurpleDnsQueryData *query_data)
698
0
{
699
0
  queued_requests = g_slist_append(queued_requests, query_data);
700
701
0
  handle_next_queued_request();
702
0
}
703
704
#elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK  */
705
706
/*
707
 * Windows!
708
 */
709
710
static gboolean
711
dns_main_thread_cb(gpointer data)
712
{
713
  PurpleDnsQueryData *query_data = data;
714
715
  /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */
716
  query_data->resolver = NULL;
717
718
  if (query_data->error_message != NULL)
719
    purple_dnsquery_failed(query_data, query_data->error_message);
720
  else
721
  {
722
    GSList *hosts;
723
724
    /* We don't want purple_dns_query_resolved() to free(hosts) */
725
    hosts = query_data->hosts;
726
    query_data->hosts = NULL;
727
    purple_dnsquery_resolved(query_data, hosts);
728
  }
729
730
  return FALSE;
731
}
732
733
static gpointer
734
dns_thread(gpointer data)
735
{
736
  PurpleDnsQueryData *query_data;
737
#ifdef HAVE_GETADDRINFO
738
  int rc;
739
  struct addrinfo hints, *res, *tmp;
740
  char servname[20];
741
#else
742
  struct sockaddr_in sin;
743
  struct hostent *hp;
744
#endif
745
  char *hostname;
746
747
  query_data = data;
748
749
#ifdef USE_IDN
750
  if (!dns_str_is_ascii(query_data->hostname)) {
751
    rc = purple_network_convert_idn_to_ascii(query_data->hostname, &hostname);
752
    if (rc != 0) {
753
      query_data->error_message = g_strdup_printf(_("Error converting %s "
754
          "to punycode: %d"), query_data->hostname, rc);
755
      /* back to main thread */
756
      purple_timeout_add(0, dns_main_thread_cb, query_data);
757
      return 0;
758
    }
759
  } else /* intentional fallthru */
760
#endif
761
  hostname = g_strdup(query_data->hostname);
762
763
#ifdef HAVE_GETADDRINFO
764
  g_snprintf(servname, sizeof(servname), "%d", query_data->port);
765
  memset(&hints,0,sizeof(hints));
766
767
  /*
768
   * This is only used to convert a service
769
   * name to a port number. As we know we are
770
   * passing a number already, we know this
771
   * value will not be really used by the C
772
   * library.
773
   */
774
  hints.ai_socktype = SOCK_STREAM;
775
#ifdef AI_ADDRCONFIG
776
  hints.ai_flags |= AI_ADDRCONFIG;
777
#endif /* AI_ADDRCONFIG */
778
  if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) {
779
    tmp = res;
780
    while(res) {
781
      query_data->hosts = g_slist_append(query_data->hosts,
782
        GSIZE_TO_POINTER(res->ai_addrlen));
783
      query_data->hosts = g_slist_append(query_data->hosts,
784
        g_memdup2(res->ai_addr, res->ai_addrlen));
785
      res = res->ai_next;
786
    }
787
    freeaddrinfo(tmp);
788
  } else {
789
    query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc));
790
  }
791
#else
792
  if ((hp = gethostbyname(hostname))) {
793
    memset(&sin, 0, sizeof(struct sockaddr_in));
794
    memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
795
    sin.sin_family = hp->h_addrtype;
796
    sin.sin_port = htons(query_data->port);
797
798
    query_data->hosts = g_slist_append(query_data->hosts,
799
        GSIZE_TO_POINTER(sizeof(sin)));
800
    query_data->hosts = g_slist_append(query_data->hosts,
801
        g_memdup2(&sin, sizeof(sin)));
802
  } else {
803
    query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno);
804
  }
805
#endif
806
  g_free(hostname);
807
808
  /* back to main thread */
809
  purple_timeout_add(0, dns_main_thread_cb, query_data);
810
811
  return 0;
812
}
813
814
static void
815
resolve_host(PurpleDnsQueryData *query_data)
816
{
817
  GError *err = NULL;
818
819
  /*
820
   * Spin off a separate thread to perform the DNS lookup so
821
   * that we don't block the UI.
822
   */
823
  query_data->resolver = g_thread_create(dns_thread,
824
      query_data, FALSE, &err);
825
  if (query_data->resolver == NULL)
826
  {
827
    char message[1024];
828
    g_snprintf(message, sizeof(message), _("Thread creation failure: %s"),
829
        (err && err->message) ? err->message : _("Unknown reason"));
830
    g_error_free(err);
831
    purple_dnsquery_failed(query_data, message);
832
  }
833
}
834
835
#else /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
836
837
/*
838
 * We weren't able to do anything fancier above, so use the
839
 * fail-safe name resolution code, which is blocking.
840
 */
841
842
static void
843
resolve_host(PurpleDnsQueryData *query_data)
844
{
845
  struct sockaddr_in sin;
846
  GSList *hosts = NULL;
847
  struct hostent *hp;
848
  gchar *hostname;
849
#ifdef USE_IDN
850
  if (!dns_str_is_ascii(query_data->hostname)) {
851
    int ret = purple_network_convert_idn_to_ascii(query_data->hostname,
852
        &hostname);
853
    if (ret != 0) {
854
      char message[1024];
855
      g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
856
          query_data->hostname, ret);
857
      purple_dnsquery_failed(query_data, message);
858
      return;
859
    }
860
  } else /* fallthrough is intentional to the g_strdup */
861
#endif
862
  hostname = g_strdup(query_data->hostname);
863
864
  if(!(hp = gethostbyname(hostname))) {
865
    char message[1024];
866
    g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
867
        query_data->hostname, h_errno);
868
    purple_dnsquery_failed(query_data, message);
869
    g_free(hostname);
870
    return;
871
  }
872
  memset(&sin, 0, sizeof(struct sockaddr_in));
873
  memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
874
  sin.sin_family = hp->h_addrtype;
875
  g_free(hostname);
876
  sin.sin_port = htons(query_data->port);
877
878
  hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
879
  hosts = g_slist_append(hosts, g_memdup2(&sin, sizeof(sin)));
880
881
  purple_dnsquery_resolved(query_data, hosts);
882
}
883
884
#endif /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
885
886
static gboolean
887
initiate_resolving(gpointer data)
888
0
{
889
0
  PurpleDnsQueryData *query_data;
890
0
  PurpleProxyType proxy_type;
891
892
0
  query_data = data;
893
0
  query_data->timeout = 0;
894
895
0
  if (resolve_ip(query_data))
896
    /* resolve_ip calls purple_dnsquery_resolved */
897
0
    return FALSE;
898
899
0
  proxy_type = purple_proxy_info_get_type(
900
0
    purple_proxy_get_setup(query_data->account));
901
0
  if (proxy_type == PURPLE_PROXY_TOR) {
902
0
    purple_dnsquery_failed(query_data,
903
0
      _("Aborting DNS lookup in Tor Proxy mode."));
904
0
    return FALSE;
905
0
  }
906
907
0
  if (purple_dnsquery_ui_resolve(query_data))
908
    /* The UI is handling the resolve; we're done */
909
0
    return FALSE;
910
911
0
  resolve_host(query_data);
912
913
0
  return FALSE;
914
0
}
915
916
PurpleDnsQueryData *
917
purple_dnsquery_a_account(PurpleAccount *account, const char *hostname, int port,
918
        PurpleDnsQueryConnectFunction callback, gpointer data)
919
0
{
920
0
  PurpleDnsQueryData *query_data;
921
922
0
  g_return_val_if_fail(hostname != NULL, NULL);
923
0
  g_return_val_if_fail(port != 0, NULL);
924
0
  g_return_val_if_fail(callback != NULL, NULL);
925
926
0
  purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname);
927
928
0
  query_data = g_new0(PurpleDnsQueryData, 1);
929
0
  query_data->hostname = g_strdup(hostname);
930
0
  g_strstrip(query_data->hostname);
931
0
  query_data->port = port;
932
0
  query_data->callback = callback;
933
0
  query_data->data = data;
934
0
  query_data->account = account;
935
936
0
  if (*query_data->hostname == '\0')
937
0
  {
938
0
    purple_dnsquery_destroy(query_data);
939
0
    g_return_val_if_reached(NULL);
940
0
  }
941
942
0
  query_data->timeout = purple_timeout_add(0, initiate_resolving, query_data);
943
944
0
  return query_data;
945
0
}
946
947
PurpleDnsQueryData *
948
purple_dnsquery_a(const char *hostname, int port,
949
        PurpleDnsQueryConnectFunction callback, gpointer data)
950
0
{
951
0
  return purple_dnsquery_a_account(NULL, hostname, port, callback, data);
952
0
}
953
954
void
955
purple_dnsquery_destroy(PurpleDnsQueryData *query_data)
956
0
{
957
0
  PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
958
959
0
  if (ops && ops->destroy)
960
0
    ops->destroy(query_data);
961
962
0
#if defined(PURPLE_DNSQUERY_USE_FORK)
963
0
  queued_requests = g_slist_remove(queued_requests, query_data);
964
965
0
  if (query_data->resolver != NULL)
966
    /*
967
     * This is only non-NULL when we're cancelling an in-progress
968
     * query.  Ideally we would tell our resolver child to stop
969
     * resolving shit and then we would add it back to the
970
     * free_dns_children linked list.  However, it's hard to tell
971
     * children stuff, they just don't listen.  So we'll just
972
     * kill the process and allow a new child to be started if we
973
     * have more stuff to resolve.
974
     */
975
0
    purple_dnsquery_resolver_destroy(query_data->resolver);
976
#elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
977
  if (query_data->resolver != NULL)
978
  {
979
    /*
980
     * It's not really possible to kill a thread.  So instead we
981
     * just set the callback to NULL and let the DNS lookup
982
     * finish.
983
     */
984
    query_data->callback = NULL;
985
    return;
986
  }
987
988
  while (query_data->hosts != NULL)
989
  {
990
    /* Discard the length... */
991
    query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
992
    /* Free the address... */
993
    g_free(query_data->hosts->data);
994
    query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
995
  }
996
  g_free(query_data->error_message);
997
#endif /* end _WIN32 */
998
999
0
  if (query_data->timeout > 0)
1000
0
    purple_timeout_remove(query_data->timeout);
1001
1002
0
  g_free(query_data->hostname);
1003
0
  g_free(query_data);
1004
0
}
1005
1006
char *
1007
purple_dnsquery_get_host(PurpleDnsQueryData *query_data)
1008
0
{
1009
0
  g_return_val_if_fail(query_data != NULL, NULL);
1010
1011
0
  return query_data->hostname;
1012
0
}
1013
1014
unsigned short
1015
purple_dnsquery_get_port(PurpleDnsQueryData *query_data)
1016
0
{
1017
0
  g_return_val_if_fail(query_data != NULL, 0);
1018
1019
0
  return query_data->port;
1020
0
}
1021
1022
void
1023
purple_dnsquery_set_ui_ops(PurpleDnsQueryUiOps *ops)
1024
0
{
1025
0
  dns_query_ui_ops = ops;
1026
0
}
1027
1028
PurpleDnsQueryUiOps *
1029
purple_dnsquery_get_ui_ops(void)
1030
0
{
1031
  /* It is perfectly acceptable for dns_query_ui_ops to be NULL; this just
1032
   * means that the default platform-specific implementation will be used.
1033
   */
1034
0
  return dns_query_ui_ops;
1035
0
}
1036
1037
void
1038
purple_dnsquery_init(void)
1039
0
{
1040
0
}
1041
1042
void
1043
purple_dnsquery_uninit(void)
1044
0
{
1045
0
#if defined(PURPLE_DNSQUERY_USE_FORK)
1046
0
  while (free_dns_children != NULL)
1047
0
  {
1048
0
    purple_dnsquery_resolver_destroy(free_dns_children->data);
1049
0
    free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data);
1050
0
  }
1051
0
#endif /* end PURPLE_DNSQUERY_USE_FORK */
1052
0
}