gresolver-0.0.5/ 0000755 0000764 0000764 00000000000 10367512230 012525 5 ustar gavin gavin gresolver-0.0.5/gresolver.pl 0000644 0000764 0000764 00000043526 10367476362 015123 0 ustar gavin gavin #!/usr/bin/perl
# Copyright (c) 2004 Gavin Brown. This program is free software, you can use it
# and/or modify it under the same terms as Perl itself.
#
# $Id: gresolver.pl,v 1.12 2006/01/30 20:54:10 gavin Exp $
use English;
use Gtk2 -init;
use Gtk2::GladeXML;
use Gtk2::Helper;
use File::Temp qw(tmpnam);
use POSIX qw(setlocale);
use Net::IPv6Addr;
use strict;
### variables:
my $NAME = 'GResolver';
my $SNAME = lc($NAME);
my $PREFIX = '@PREFIX@';
my $LOCALE_DIR = (-d $PREFIX ? "$PREFIX/share/locale" : get_current_dir().'/locale');
my $LOCALE = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG});
### do locale stuff here, so that we can have a translated error message below:
if ($OSNAME ne 'MSWin32') {
require Locale::gettext;
eval {
use POSIX;
setlocale(LC_ALL, $LOCALE);
bindtextdomain($SNAME, $LOCALE_DIR);
textdomain($SNAME);
};
} else {
eval {
sub gettext { shift }
};
}
my $GLADE_FILE = (-d $PREFIX ? "$PREFIX/share/$SNAME" : get_current_dir())."/$SNAME.glade";
my $ICON_FILE = (-d $PREFIX ? "$PREFIX/share/pixmaps" : get_current_dir())."/$SNAME.png";
my $VERSION = '0.0.5';
my $RCFILE = sprintf('%s/.%src', Glib::get_home_dir(), $SNAME);
my $OPTIONS = load_options();
my @SERVER_HIST = split(/\|/, $OPTIONS->{server_hist});
my @QUERY_HIST = split(/\|/, $OPTIONS->{query_hist});
my $glade = Gtk2::GladeXML->new($GLADE_FILE);
$glade->signal_autoconnect_from_package('main');
if ($OSNAME eq 'MSWin32' && !-e $OPTIONS->{dig}) {
my $filter = Gtk2::FileFilter->new;
$filter->add_pattern('dig.exe');
$filter->set_name('dig.exe');
$glade->get_widget('dig_locator_chooser')->add_filter($filter);
$glade->get_widget('dig_locator')->set_icon(Gtk2::Gdk::Pixbuf->new_from_file($ICON_FILE));
$glade->get_widget('dig_locator')->signal_connect('response', \&on_dig_locator_response, $glade);
$glade->get_widget('dig_locator')->run;
}
my $DIG;
if ($OSNAME eq 'MSWin32') {
$DIG = $OPTIONS->{dig};
} else {
chomp($DIG = `which dig 2>/dev/null`);
}
### we need an executable dig, pop up an error dialog if one can't be found:
if (!-x $DIG) {
my $dialog = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'ok', gettext("Can't find the 'dig' program!"));
$dialog->signal_connect('response', sub { exit 1 });
$dialog->signal_connect('delete_event', sub { exit 1 });
$dialog->run;
}
my @DIG_VERSION = get_dig_version();
my $DIG_VERSION = join('.', @DIG_VERSION);
my $RECURS_PARAM= recursive_param();
# these correspond to rows in the type combobox, which are populated by glade.
# They will break if the order of the rows is changed.
my $ANY_ROW_ID = 4;
my $PTR_ROW_ID = 6;
my $ABOUT_DIALOG_MARKUP = <<"END";
$NAME
v$VERSION, using DiG v$DIG_VERSION
Copyright (c) 2004 Gavin Brown. This program is free software, you can use it and/or modify it under the same terms as Perl itself.
END
my $OVERWRITE_DIALOG_MARKUP = sprintf('%s', gettext('A file named "%s" already exists.'));
my $normal = Gtk2::Gdk::Cursor->new('left_ptr');
my $busy = Gtk2::Gdk::Cursor->new('watch');
### build the UI:
my $glade = Gtk2::GladeXML->new($GLADE_FILE);
$glade->signal_autoconnect_from_package('main');
### the application icon:
my $ICON = Gtk2::Gdk::Pixbuf->new_from_file($ICON_FILE);
### set up the about dialog:
$glade->get_widget('about_icon')->set_from_pixbuf($ICON);
$glade->get_widget('about_label')->set_markup($ABOUT_DIALOG_MARKUP);
$glade->get_widget('about_dialog')->set_icon($ICON);
### set up the file overwrite dialog:
my $SAVE_TARGET = '';
$glade->get_widget('overwrite_dialog')->set_icon($ICON);
$glade->get_widget('overwrite_dialog')->signal_connect('close', sub { $glade->get_widget('overwrite_dialog')->hide_all ; return 1 });
$glade->get_widget('overwrite_dialog')->signal_connect('delete_event', sub { $glade->get_widget('overwrite_dialog')->hide_all ; return 1 });
$glade->get_widget('overwrite_dialog')->signal_connect('response', sub { $glade->get_widget('overwrite_dialog')->hide_all ; write_output($SAVE_TARGET) if ($_[1] eq 'ok') ; return 1 });
### set up the output treeview:
my @cols = qw(Host TTL Class Type Answer);
my $store = Gtk2::TreeStore->new(qw/Glib::String Glib::String Glib::String Glib::String Glib::String/);
$glade->get_widget('output')->set_model($store);
for (0..4) {
$glade->get_widget('output')->insert_column_with_attributes(
$_,
gettext($cols[$_]),
Gtk2::CellRendererText->new,
text => $_,
);
}
foreach my $col ($glade->get_widget('output')->get_columns) {
$col->set_resizable(1);
}
$glade->get_widget('output')->signal_connect('button_release_event', sub { context_menu() if ($_[1]->button == 3) });
### setup the raw output textview:
$glade->get_widget('raw_response')->get_buffer->create_tag(
'monospace',
font => $OPTIONS->{font},
wrap_mode => 'none',
);
$glade->get_widget('query_combo')->disable_activate;
$glade->get_widget('server_combo')->disable_activate;
### apply user settings:
$glade->get_widget('main_window')->set_position('center');
$glade->get_widget('main_window')->set_icon($ICON);
$glade->get_widget('query_combo')->set_popdown_strings('', @QUERY_HIST);
$glade->get_widget('server_combo')->set_popdown_strings('', @SERVER_HIST);
$glade->get_widget('recursive_checkbutton')->set_active( $OPTIONS->{recursive_on} == 1);
$glade->get_widget('additional_checkbutton')->set_active( $OPTIONS->{additional_on} == 1);
$glade->get_widget('authoritative_checkbutton')->set_active( $OPTIONS->{authoritative_on} == 1);
$glade->get_widget('trace_checkbutton')->set_active( $OPTIONS->{trace_on} == 1);
$glade->get_widget('options_expander')->set_expanded( $OPTIONS->{options_visible} == 1);
$glade->get_widget('notebook')->set_current_page( $OPTIONS->{current_page});
$glade->get_widget('type_combo')->set_active( $OPTIONS->{last_type});
$glade->get_widget('query_menuitem')->set_sensitive(undef);
$glade->get_widget('query_button')->set_sensitive(undef);
$glade->get_widget('main_window')->show_all;
Gtk2->main;
exit 0;
### gather user settings and save config:
sub close_program {
$OPTIONS->{recursive_on} = ($glade->get_widget('recursive_checkbutton')->get_active ? 1 : 0);
$OPTIONS->{additional_on} = ($glade->get_widget('additional_checkbutton')->get_active ? 1 : 0);
$OPTIONS->{authoritative_on} = ($glade->get_widget('authoritative_checkbutton')->get_active ? 1 : 0);
$OPTIONS->{trace_on} = ($glade->get_widget('trace_checkbutton')->get_active ? 1 : 0);
$OPTIONS->{options_visible} = ($glade->get_widget('options_expander')->get_expanded ? 1 : 0);
$OPTIONS->{current_page} = $glade->get_widget('notebook')->get_current_page;
$OPTIONS->{last_type} = $glade->get_widget('type_combo')->get_active;
$OPTIONS->{query_hist} = join('|', uniq(@QUERY_HIST));
$OPTIONS->{server_hist} = join('|', uniq(@SERVER_HIST));
save_options($OPTIONS);
exit 0;
}
### make a DNS query:
sub make_query {
### query options:
my $type = $glade->get_widget('type_combo')->get_model->get($glade->get_widget('type_combo')->get_active_iter, 0);
my $query = $glade->get_widget('query_combo')->entry->get_text;
my $server = $glade->get_widget('server_combo')->entry->get_text;
my $recursive = $glade->get_widget('recursive_checkbutton')->get_active;
my $additional = $glade->get_widget('additional_checkbutton')->get_active;
my $authoritative = $glade->get_widget('authoritative_checkbutton')->get_active;
my $trace = $glade->get_widget('trace_checkbutton')->get_active;
return unless ($query ne '');
unshift(@QUERY_HIST, $query);
@QUERY_HIST = uniq(@QUERY_HIST);
unshift(@SERVER_HIST, $server) if ($server ne '');
@SERVER_HIST = uniq(@SERVER_HIST);
$glade->get_widget('query_combo')->set_popdown_strings(@QUERY_HIST);
$glade->get_widget('server_combo')->set_popdown_strings(@SERVER_HIST) if ($server ne '');
$glade->get_widget('query_button')->set_sensitive(undef);
$glade->get_widget('menu')->set_sensitive(undef);
set_status(gettext('Sending query...'));
### if we're doing a PTR query and the query string is a dotted-quad IP address,
### then turn it into an in-addr.arpa domain:
if ($type eq 'PTR') {
if (is_ipv4addr($query)) {
$query = join('.', reverse(split(/\./, $query))).'.in-addr.arpa';
$glade->get_widget('query_combo')->entry->set_text($query);
} elsif (is_ipv6addr($query)) {
$glade->get_widget('query_combo')->entry->set_text(lc(Net::IPv6Addr->new($query)->to_string_ip6_int));
}
}
### build the dig query:
my $cmd = join(' ', (
"\"$DIG\"",
'+'.($recursive == 1 ? '' : 'no').$RECURS_PARAM,
'+'.($additional == 1 ? '' : 'no').'additional',
'+'.($authoritative == 1 ? '' : 'no').'authority',
'+'.($trace == 1 ? '' : 'no').'trace',
$type,
$query,
($server ne '' ? sprintf('@%s', $server) : ''),
));
$glade->get_widget('main_window')->window->set_cursor($busy);
$store->clear;
$glade->get_widget('raw_response')->get_buffer->set_text('');
### read dig's STDOUT, pipe STDERR to a file so we can display it
### if dig exits with an error:
my $tmpfile = tmpnam();
if (!open(DIG, "$cmd 2>$tmpfile|")) {
$glade->get_widget('main_window')->window->set_cursor($normal);
$glade->get_widget('menu')->set_sensitive(1);
$glade->get_widget('query_button')->set_sensitive(1);
my $dialog = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'ok', sprintf(gettex('Cannot pipe from dig: %s'), $!));
$dialog->signal_connect('response', sub { $dialog->destroy });
set_status(gettext(''));
$dialog->run;
} else {
my ($tag, $buffer);
set_status(gettext('Receiving response...'));
$tag = Gtk2::Helper->add_watch(fileno(DIG), 'in', sub {
if (eof(DIG)) {
close(DIG);
Gtk2::Helper->remove_watch($tag);
display($buffer);
$glade->get_widget('main_window')->window->set_cursor($normal);
$glade->get_widget('menu')->set_sensitive(1);
$glade->get_widget('query_button')->set_sensitive(1);
set_status(gettext(''));
if ($? > 0) {
if (!open(TMPFILE, $tmpfile)) {
print STDERR "Error: couldn't open temporary file '$tmpfile' to tell you about an error from dig: $!\n";
exit 1;
} else {
my $err;
while () {
$err .= $_;
}
close(TMPFILE);
my $dialog = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'ok', $err);
$dialog->signal_connect('response', sub { $dialog->destroy });
$dialog->run;
}
} else {
unlink($tmpfile);
}
} else {
$buffer .= ;
}
});
}
return 1;
}
sub display {
my $buffer = shift;
my @lines = split(/\n{1}/, $buffer);
my $parent = undef;
$glade->get_widget('raw_response')->get_buffer->insert_with_tags_by_name(
$glade->get_widget('raw_response')->get_buffer->get_start_iter,
$buffer,
'monospace',
);
foreach my $line (@lines) {
if ($line =~ /;; ANSWER SECTION/) {
my $iter = $store->append(undef);
$store->set($iter, 0, 'Answer:');
$parent = $iter;
} elsif ($line =~ /;; ADDITIONAL SECTION/) {
my $iter = $store->append(undef);
$store->set($iter, 0, 'Additional:');
$parent = $iter;
} elsif ($line =~ /;; AUTHORITY SECTION/) {
my $iter = $store->append(undef);
$store->set($iter, 0, 'Authority:');
$parent = $iter;
} elsif ($line !~ /^\s*$/ && $line !~ /^\s*;/) {
my $iter = $store->append($parent);
my @parts = split(/\s+/, $line, 5);
for (0..4) {
$store->set($iter, $_, $parts[$_]);
}
}
}
$glade->get_widget('output')->expand_all;
return 1;
}
sub load_options {
my $hashref = {
font => 'Courier 10',
recursive_on => 1,
additional_on => 1,
authoritative_on => 1,
trace_on => 0,
options_visible => 0,
current_page => 0,
last_type => 0,
};
if (open(RCFILE, $RCFILE)) {
while () {
chomp;
my ($name, $value) = split(/\s*=\s*/, $_, 2);
$hashref->{lc($name)} = $value;
}
close(RCFILE);
}
return $hashref;
}
sub save_options {
my $hashref = shift;
if (!open(RCFILE, ">$RCFILE")) {
print STDERR "Error opening '$RCFILE': $!\n";
return undef;
} else {
foreach my $name (keys(%{$hashref})) {
printf(RCFILE "%s=%s\n", $name, $hashref->{$name});
}
close(RCFILE);
return 1;
}
}
sub uniq {
my @array = @_;
my @new;
my %map;
foreach my $member (@array) {
$map{$member}++;
}
foreach my $member (@array) {
if ($map{$member} > 0) {
push(@new, $member);
$map{$member} = 0;
}
}
return @new;
}
sub show_about_dialog {
$glade->get_widget('about_dialog')->set_position('center');
$glade->get_widget('about_dialog')->show_all;
return 1;
}
sub hide_about_dialog {
$glade->get_widget('about_dialog')->hide_all;
return 1;
}
sub changed {
my $bool = ($glade->get_widget('query_combo')->entry->get_text ne '');
$glade->get_widget('query_menuitem')->set_sensitive($bool);
$glade->get_widget('query_button')->set_sensitive($bool);
return 1;
}
### return an array containing the major, minor and micro version numbers of the dig program:
sub get_dig_version {
my $version;
# an un-argumented call to dig returns the root hints from the default server;
if (!open(DIG, "\"$DIG\"|")) {
print STDERR "Cannot pipe from '$DIG': $!\n";
exit 1;
} else {
# ignore the first line:
;
# capture the next line:
my $line = ;
close(DIG);
if ($line =~ /DiG ([\d\.]+)/) {
$version = $1;
} else {
print STDERR "Error parsing version output from dig, got:\n\t$line\n";
exit 1;
}
}
return split(/\./, $version, 3);
}
### the +[no]recurs(iv)e parameter changed between 9.2.x and 9.3.x. This subroutine examines the
### dig version and returns a string containing the correct parameter:
sub recursive_param {
return ($DIG_VERSION[0] >= 9 && $DIG_VERSION[1] >= 3 && $DIG_VERSION[2] >= 0 ? 'recurse' : 'recursive');
}
sub is_ipv4addr {
return ($_[0] =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
}
### this is about as good as we can get here, since IPv6 addresses can have all different
### kinds of format:
sub is_ipv6addr {
return ($_[0] =~ /^[A-F0-9:]+$/);
}
sub context_menu {
my ($model, $iter) = $glade->get_widget('output')->get_selection->get_selected;
return 1 if (!defined($model) && !defined($iter));
my ($host, $ttl, $class, $type, $answer) = $model->get($iter);
$type = uc($type);
$answer =~ s/\.$//;
return 1 if ($type eq 'SOA' || $answer eq '');
my $menu = Gtk2::Menu->new;
my ($label, $callback);
if ($type eq 'A') {
$label = sprintf(gettext("Do a PTR query on %s"), $answer);
$callback = sub {
$glade->get_widget('query_combo')->entry->set_text($answer);
$glade->get_widget('type_combo')->set_active($PTR_ROW_ID);
$glade->get_widget('query_button')->clicked;
}
} elsif ($type eq 'MX') {
my ($priority, $exchanger) = split(/\s+/, $answer, 2);
if (is_ipv4addr($exchanger)) {
$label = sprintf(gettext("Do a PTR query on '%s'"), $exchanger);
$callback = sub {
$glade->get_widget('query_combo')->entry->set_text($exchanger);
$glade->get_widget('type_combo')->set_active($PTR_ROW_ID);
$glade->get_widget('query_button')->clicked;
};
} else {
$label = sprintf(gettext("Put '%s' into Query box"), $exchanger);
$callback = sub {
$glade->get_widget('query_combo')->entry->set_text($exchanger);
$glade->get_widget('type_combo')->set_active($ANY_ROW_ID);
};
}
} elsif ($type eq 'AAAA') {
$label = sprintf(gettext("Do a PTR query on '%s'"), $answer);
$callback = sub {
$glade->get_widget('query_combo')->entry->set_text($answer);
$glade->get_widget('type_combo')->set_active($PTR_ROW_ID);
$glade->get_widget('query_button')->clicked;
};
} elsif ($type eq 'NS') {
$label = sprintf(gettext("Put '%s' into Server box"), $answer);
$callback = sub {
$glade->get_widget('server_combo')->entry->set_text($answer);
};
} else {
$label = sprintf(gettext("Put '%s' into Query box"), $answer);
$callback = sub {
$glade->get_widget('query_combo')->entry->set_text($answer);
$glade->get_widget('type_combo')->set_active($ANY_ROW_ID);
};
}
my $item = Gtk2::ImageMenuItem->new_with_label($label);
$item->set_image(Gtk2::Image->new_from_pixbuf($glade->get_widget('main_window')->render_icon('gtk-jump-to', 'menu')));
$item->signal_connect('activate', $callback) if (defined($callback));
$menu->append($item);
$menu->show_all;
$menu->popup(undef, undef, undef, undef, 3, undef);
return 1;
}
sub save_output {
my $chooser = Gtk2::FileChooserDialog->new(
gettext('Save as...'),
$glade->get_widget('main_window'),
'save',
'gtk-cancel' => 'cancel',
'gtk-ok' => 'ok',
);
$chooser->set_modal(1);
$chooser->signal_connect('response', sub {
my $file = $chooser->get_filename;
$chooser->destroy;
if ($_[1] eq 'ok') {
$SAVE_TARGET = $file;
if (-e $file) {
$glade->get_widget('overwrite_dialog_title_label')->set_markup(sprintf($OVERWRITE_DIALOG_MARKUP, $file));
$glade->get_widget('overwrite_dialog')->show_all;
} else {
write_output($SAVE_TARGET);
}
}
});
$chooser->show_all;
return 1;
}
sub set_status {
my $text = shift;
$glade->get_widget('status')->push($glade->get_widget('status')->get_context_id('default'), $text);
}
sub write_output {
my $file = shift;
if (!open(FILE, ">$file")) {
my $dialog = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'ok', sprintf(gettext("Error opening '%s': %s", $file, $!)));
$dialog->signal_connect('response', sub { $dialog->destroy });
$dialog->run;
} else {
print FILE $glade->get_widget('raw_response')->get_buffer->get_text(
$glade->get_widget('raw_response')->get_buffer->get_start_iter,
$glade->get_widget('raw_response')->get_buffer->get_end_iter,
0
);
close(FILE);
}
return 1;
}
sub get_current_dir {
if ($OSNAME eq 'MSWin32') {
my $dir;
eval {
require Win32;
$dir = Win32::GetCwd();
};
return ($@ ? undef : $dir);
} else {
return $ENV{PWD};
}
}
sub on_dig_locator_close {
exit 0;
}
sub on_dig_locator_response {
my ($dialog, $response, $glade) = @_;
if ($response ne 'ok') {
exit 0;
} else {
my $dig = $glade->get_widget('dig_locator_chooser')->get_filename;
if (!-e $dig) {
$dialog->run;
} else {
$glade->get_widget('dig_locator')->hide;
$OPTIONS->{dig} = $dig;
save_options();
return 1;
}
}
}
gresolver-0.0.5/ChangeLog 0000644 0000764 0000764 00000001404 10367477334 014315 0 ustar gavin gavin ChangeLog
=========
2006-01-30 - v0.0.5
* This release works on Windows.
2005-06-20 - v0.0.4
* This release introduces some user-interface improvements, and
allows the user to save their queries.
2004-11-03 - v0.0.3
* This release improves support for IPv6, allowing for RFC 1886 style
reverse lookups, as well as the A6 and DNAME record types. You can
now also right-click on most records in the output window.
2004-10-24 - v0.0.2
* This release fixes a problem caused by a change in dig's command
line syntax between 9.2 and 9.3, that broke the recursion option.
Thanks to Jean-Yves Lefort for reporting this
bug.
2004-10-17 - v0.0.1
* Initial release.
--
$Id: ChangeLog,v 1.5 2006/01/30 21:02:20 gavin Exp $
gresolver-0.0.5/gresolver.ico 0000644 0000764 0000764 00000010276 10367476400 015247 0 ustar gavin gavin ( @
&