Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Re^4: Adding WlanConnect() to Win32::Wlan::API

by tomsell (Acolyte)
on Jun 27, 2012 at 08:32 UTC ( [id://978583]=note: print w/replies, xml ) Need Help??


in reply to Re^3: Adding WlanConnect() to Win32::Wlan::API
in thread Adding WlanConnect() to Win32::Wlan::API

The GUID is obtained the same way the stock Wlan::API does it and it is used all over the place, e.g in the (added by me) WlanSetProfile(), which creates a new profile from the required XML structures like thus:

sub WlanSetProfile { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid, $xmlref) = @_; my $reason = Zero; $API{ WlanSetProfile }->Call($handle, $guuid, 0, $$xmlref, 0, 1, 0 +, $reason) == 0 or die "$^E"; };

Background: I want to automate establishing an ad-hoc network. On XP, insecure WEP is the only available encryption method. So, in order to make things a bit more secure, before connecting I create a new profile with a new WEP key, effectively creating a session key.

WlanSetProfile() succeeds and I can use the new profile to connect manually. So, the GUID is good. Also, the UTF16LE profilename is good, because before creating a new profile, I do some housekeeping and delete the previously used profile with the (added by me) WlanDeleteProfile()

sub WlanDeleteProfile { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid, $profilename) = @_; $API{ WlanDeleteProfile }->Call($handle, $guuid, $profilename, 0) +== 0 or die "$^E"; };

Deleting a profile works by supplying its name in the required fashion. WlanDeleteProfile() succeeds.

Unfortunately, using S for P in the signature

['WlanConnect' => 'IPSI' => 'I']

makes no difference.

I think my best bet is to follow Corion's suggestion and investigate pack()ing some string.

Replies are listed 'Best First'.
Re^5: Adding WlanConnect() to Win32::Wlan::API
by bulk88 (Priest) on Jun 27, 2012 at 13:48 UTC
    Can you post your whole modified Win32 Wlan module, or atleast api.pm, wlan.pm, and something that uses your WlanConnect? I'll obviously have to slightly modify it to work with my SSID. If its a bug with Win32::API I'm willing to fix it. Since the GUIDs are known to work, and aren't hard coded, I know they are fine.

    Update: I decided to try and see if its a Win32::API or user problem in using Win32::API or a MS API problem. The following code, with the IPPI signature. generated
    hClientHandle 0x00000001 void * + pInterfaceGuid 0x00a55324 {04030201-0605-0807-0910-11121314151 +6} const _GUID * - pConnectionParameters 0x00a21594 {wlanConnectionMode=862873943 + strProfile=0x413a3a32 <Bad Ptr> pDot11Ssid=0x3a3a4950 {uSSIDLength=? +?? ucSSID=0x3a3a4954 <Bad Ptr> } ...} _WLAN_CONNECTION_PARAMETERS +* const wlanConnectionMode 862873943 _WLAN_CONNECTION_MODE - strProfile 0x413a3a32 <Bad Ptr> const unsigned short * CXX0030: Error: expression cannot be evaluated const unsign +ed short - pDot11Ssid 0x3a3a4950 {uSSIDLength=??? ucSSID=0x3a3a4954 <Bad +Ptr> } _DOT11_SSID * uSSIDLength CXX0030: Error: expression cannot be evaluated u +nsigned long + ucSSID 0x3a3a4954 <Bad Ptr> unsigned char [32] - pDesiredBssidList 0x75727453 {Header={Type=??? Revision=??? Si +ze=??? } uNumOfEntries=??? uTotalNumOfEntries=??? ...} DOT11_BSSID +_LIST * + Header {Type=??? Revision=??? Size=??? } _NDIS_OBJECT_HEADE +R uNumOfEntries CXX0030: Error: expression cannot be evaluated + unsigned long uTotalNumOfEntries CXX0030: Error: expression cannot be evaluat +ed unsigned long + BSSIDs 0x7572745f unsigned char [1][6] dot11BssType 1211987043 _DOT11_BSS_TYPE dwFlags 675828545 unsigned long pReserved 0x00000000 void *
    in C. pConnectionParameters is gibberish, and its exactly what I thought it would be.
    0x00A21594 57 69 6e 33 32 3a 3a 41 50 49 3a 3a 53 74 72 75 63 74 3d 4 +8 41 53 48 28 30 78 61 34 64 Win32::API::Struct=HASH(0xa4d 0x00A215B1 61 62 63 29 00 00 00 00 26 94 22 14 0d 02 00 00 00 ad ba a +b ab ab ab ab ab ab ab ee 04 abc)....&&#148;"......н║ллллллллю.
    Now I changed the signature to IPSI, and, I don't know how you ran it, but Win32::API errored out and died on me.
    Use of uninitialized value $type in string eq at C:/perl512/site/lib/W +in32/API/S truct.pm line 205. Use of uninitialized value $type in pattern match (m//) at C:/perl512/ +site/lib/W in32/API/Struct.pm line 216. Use of uninitialized value $type in concatenation (.) or string at C:/ +perl512/si te/lib/Win32/API/Struct.pm line 221. Use of uninitialized value $type in string eq at C:/perl512/site/lib/W +in32/API/S truct.pm line 223. Use of uninitialized value $name in hash element at C:/perl512/site/li +b/Win32/AP I/Struct.pm line 228. Use of uninitialized value $self in string eq at C:/perl512/site/lib/W +in32/API/T ype.pm line 161. Use of uninitialized value $type in exists at C:/perl512/site/lib/Win3 +2/API/Type .pm line 167. Use of uninitialized value $type in pattern match (m//) at C:/perl512/ +site/lib/W in32/API/Type.pm line 173. Use of uninitialized value $type in substitution (s///) at C:/perl512/ +site/lib/W in32/API/Type.pm line 180. Use of uninitialized value $type in exists at C:/perl512/site/lib/Win3 +2/API/Type .pm line 182. Use of uninitialized value $packing in pattern match (m//) at C:/perl5 +12/site/li b/Win32/API/Type.pm line 142. Use of uninitialized value $packing in hash element at C:/perl512/site +/lib/Win32 /API/Type.pm line 146. Use of uninitialized value $type_size in addition (+) at C:/perl512/si +te/lib/Win 32/API/Struct.pm line 232. Use of uninitialized value $type_size in modulus (%) at C:/perl512/sit +e/lib/Win3 2/API/Struct.pm line 232. Illegal modulus zero at C:/perl512/site/lib/Win32/API/Struct.pm line 2 +32.

      Thanks. I only modified Win32::Wlan::API by adding wrappers for these native functions:

      ['WlanScan' => 'IPPPI' => 'I'], ['WlanGetProfileList' => 'IPIP' => 'I'], ['WlanDeleteProfile' => 'IPPI' => 'I'], ['WlanSetProfile' => 'IPIPPIIP' => 'I'], ['WlanConnect' => 'IPSI' => 'I']

      To test WlanConnect() using an existing profile on XP or Win7, run wlanconnect.pl PROFILENAME SSID. A list of UTF16 profile names can be obtained thusly: my @profilenames = WlanGetProfileList($handle, $guid).

      # wlanconnect.pl -- connect to a WLAN network on Windows XP SP3 and ab +ove. # Args: PROFILENAME to use and SSID to connect to use strict; use warnings; use Encode qw(encode); use Win32::Wlan::API qw< WlanOpenHandle WlanCloseHandle WlanQueryCurrentConnection WlanEnumInterfaces WlanGetAvailableNetworkList $wlan_available WlanDeleteProfile WlanSetProfile WlanScan WlanConnect WlanGetProfileList >; main(); sub main { die "USAGE: $0 PROFILENAME SSID\n" unless($ARGV[1]); my $profilename = shift; my $ssid = shift; my $wlan_handle = WlanOpenHandle(); my @interfaces = WlanEnumInterfaces($wlan_handle); my $wlan_guuid = $interfaces[0]->{guuid}; $profilename = encode('UTF-16LE', $profilename); WlanConnect($wlan_handle, $wlan_guuid, $profilename, $ssid); }

      modified Wlan::API.pm follows

      ###################################################################### # modified Wlan::API.pm follows # added: # ['WlanScan' => 'IPPPI' => 'I'], # ['WlanGetProfileList' => 'IPIP' => 'I'], # ['WlanDeleteProfile' => 'IPPI' => 'I'], # ['WlanSetProfile' => 'IPIPPIIP' => 'I'], # ['WlanConnect' => 'IPSI' => 'I'] ###################################################################### package Win32::Wlan::API; use strict; use Carp qw(croak); use Encode qw(decode); use Exporter 'import'; use vars qw($VERSION $wlan_available %API @signatures @EXPORT_OK); $VERSION = '0.06'; sub Zero() { "\0\0\0\0" }; # just in case we ever get a 64bit Win32::API # Zero will have to return 8 bytes of zeroes BEGIN { @signatures = ( ['WlanOpenHandle' => 'IIPP' => 'I'], ['WlanCloseHandle' => 'II' => 'I'], ['WlanFreeMemory' => 'I' => 'I'], ['WlanEnumInterfaces' => 'IIP' => 'I'], ['WlanQueryInterface' => 'IPIIPPI' => 'I'], ['WlanGetAvailableNetworkList' => 'IPIIP' => 'I'], ['WlanScan' => 'IPPPI' => 'I'], ['WlanGetProfileList' => 'IPIP' => 'I'], ['WlanDeleteProfile' => 'IPPI' => 'I'], ['WlanSetProfile' => 'IPIPPIIP' => 'I'], ['WlanConnect' => 'IPSI' => 'I'] ); @EXPORT_OK = (qw<$wlan_available WlanQueryCurrentConnection>, map +{ $_->[0] } @signatures); }; use constant { not_ready => 0, connected => 1, ad_hoc_network_formed => 2, disconnecting => 3, disconnected => 4, associating => 5, discovering => 6, authenticating => 7 }; if (! load_functions()) { # Wlan functions are not available $wlan_available = 0; } else { $wlan_available = 1; }; sub unpack_struct { # Unpacks a string into a hash # according to a key/unpack template structure my $desc = shift; my @keys; my $template = ''; for (0..$#{$desc}) { if ($_ % 2) { $template .= $desc->[ $_ ] } elsif ($desc->[ $_ ] ne '') { push @keys, $desc->[ $_ ] }; }; my %res; @res{ @keys } = unpack $template, shift; %res } sub WlanOpenHandle { croak "Wlan functions are not available" unless $wlan_available; my $version = Zero; my $handle = Zero; $API{ WlanOpenHandle }->Call(2,0,$version,$handle) == 0 or croak $^E; my $h = unpack "V", $handle; $h }; sub WlanCloseHandle { croak "Wlan functions are not available" unless $wlan_available; my ($handle) = @_; $API{ WlanCloseHandle }->Call($handle,0) == 0 or croak $^E; }; sub WlanFreeMemory { croak "Wlan functions are not available" unless $wlan_available; my ($block) = @_; $API{ WlanFreeMemory }->Call($block); }; sub _unpack_counted_array { my ($pointer,$template,$size) = @_; my $info = unpack 'P8', $pointer; my ($count,$curr) = unpack 'VV', $info; my $data = unpack "P" . (8+$count*$size), $pointer; my @items = unpack "x8 ($template)$count", $data; my @res; if ($count) { my $elements_per_item = @items / $count; while (@items) { push @res, [splice @items, 0, $elements_per_item ] }; }; @res }; sub WlanEnumInterfaces { croak "Wlan functions are not available" unless $wlan_available; my ($handle) = @_; my $interfaces = Zero; $API{ WlanEnumInterfaces }->Call($handle,0,$interfaces) == 0 or croak $^E; my @items = _unpack_counted_array($interfaces,'a16 a512 V',16+512+ +4); @items = map { # First element is the GUUID of the interface # Name is in 16bit UTF $_->[1] = decode('UTF-16LE' => $_->[1]); $_->[1] =~ s/\0+$//; # The third element is the status of the interface +{ guuid => $_->[0], name => $_->[1], status => $_->[2], }; } @items; $interfaces = unpack 'V', $interfaces; WlanFreeMemory($interfaces); @items }; sub WlanQueryInterface { croak "Wlan functions are not available" unless $wlan_available; my ($handle,$interface,$op) = @_; my $size = Zero; my $data = Zero; $API{ WlanQueryInterface }->Call($handle, $interface, $op, 0, $siz +e, $data, 0) == 0 or return; $size = unpack 'V', $size; my $payload = unpack "P$size", $data; $data = unpack 'V', $data; WlanFreeMemory($data); $payload }; =head2 C<< WlanCurrentConnection( $handle, $interface ) >> Returns a hashref containing the following keys =over 4 =item * C<< state >> - state of the interface One of the following Win32::Wlan::API::not_ready => 0, Win32::Wlan::API::connected => 1, Win32::Wlan::API::ad_hoc_network_formed => 2, Win32::Wlan::API::disconnecting => 3, Win32::Wlan::API::disconnected => 4, Win32::Wlan::API::associating => 5, Win32::Wlan::API::discovering => 6, Win32::Wlan::API::authenticating => 7 =item * C<< mode >> =item * C<< profile_name >> C<< bss_type >> infrastructure = 1, independent = 2, any = 3 =item * auth_algorithm DOT11_AUTH_ALGO_80211_OPEN = 1, DOT11_AUTH_ALGO_80211_SHARED_KEY = 2, DOT11_AUTH_ALGO_WPA = 3, DOT11_AUTH_ALGO_WPA_PSK = 4, DOT11_AUTH_ALGO_WPA_NONE = 5, DOT11_AUTH_ALGO_RSNA = 6, # wpa2 DOT11_AUTH_ALGO_RSNA_PSK = 7, # wpa2 DOT11_AUTH_ALGO_IHV_START = 0x80000000, DOT11_AUTH_ALGO_IHV_END = 0xffffffff =item * cipher_algorithm DOT11_CIPHER_ALGO_NONE = 0x00, DOT11_CIPHER_ALGO_WEP40 = 0x01, DOT11_CIPHER_ALGO_TKIP = 0x02, DOT11_CIPHER_ALGO_CCMP = 0x04, DOT11_CIPHER_ALGO_WEP104 = 0x05, DOT11_CIPHER_ALGO_WPA_USE_GROUP = 0x100, DOT11_CIPHER_ALGO_RSN_USE_GROUP = 0x100, DOT11_CIPHER_ALGO_WEP = 0x101, DOT11_CIPHER_ALGO_IHV_START = 0x80000000, DOT11_CIPHER_ALGO_IHV_END = 0xffffffff =back =cut sub WlanQueryCurrentConnection { my ($handle,$interface) = @_; my $info = WlanQueryInterface($handle,$interface,7) || ''; my @WLAN_CONNECTION_ATTRIBUTES = ( state => 'V', mode => 'V', profile_name => 'a512', # WLAN_ASSOCIATION_ATTRIBUTES ssid_len => 'V', ssid => 'a32', bss_type => 'V', mac_address => 'a6', dummy => 'a2', # ??? phy_type => 'V', phy_index => 'V', signal_quality => 'V', rx_rate => 'V', tx_rate => 'V', security_enabled => 'V', # BOOL onex_enabled => 'V', # BOOL auth_algorithm => 'V', cipher_algorithm => 'V', ); my %res = unpack_struct(\@WLAN_CONNECTION_ATTRIBUTES, $info); $res{ profile_name } = decode('UTF-16LE', $res{ profile_name }) || + ''; $res{ profile_name } =~ s/\0+$//; $res{ ssid } = substr $res{ ssid }, 0, $res{ ssid_len }; $res{ mac_address } = sprintf "%02x:%02x:%02x:%02x:%02x:%02x", unp +ack 'C*', $res{ mac_address }; %res } sub WlanGetAvailableNetworkList { my ($handle,$interface,$flags) = @_; $flags ||= 0; my $list = Zero; $API{ WlanGetAvailableNetworkList }->Call($handle,$interface,$flag +s,0,$list) == 0 or croak $^E; # name ssid_len ssid b +ss bssids connectable my @items = _unpack_counted_array($list, join( '', 'a512', # name 'V', # ssid_len 'a32', # ssid 'V', # bss 'V', # bssids 'V', # connectable 'V', # notConnectableReason, 'V', # PhysTypes 'V8', # PhysType elements 'V', # More PhysTypes 'V', # wlanSignalQuality from 0=-100dbm to 100=-50dbm, line +ar 'V', # bSecurityEnabled; 'V', # dot11DefaultAuthAlgorithm; 'V', # dot11DefaultCipherAlgorithm; 'V', # dwFlags 'V', # dwReserved; ), 512+4+32+20*4); for (@items) { my %info; @info{qw( name ssid_len ssid bss bssids connectable notConnect +ableReason phystype_count )} = splice @$_, 0, 8; $info{ phystypes }= [splice @$_, 0, 8]; @info{qw( has_more_phystypes signal_quality security_enabled default_auth_algorithm default_cipher_algorithm flags reserved )} = @$_; # Decode the elements $info{ ssid } = substr( $info{ ssid }, 0, $info{ ssid_len }); $info{ name } = decode('UTF-16LE', $info{ name }); $info{ name } =~ s/\0+$//; splice @{$info{ phystypes }}, $info{ phystype_count }; $_ = \%info; }; $list = unpack 'V', $list; WlanFreeMemory($list); @items } sub WlanScan { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid) = @_; $API{ WlanScan }->Call($handle, $guuid, 0, 0, 0) == 0 or die "$^E"; }; sub WlanDeleteProfile { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid, $profilename) = @_; $API{ WlanDeleteProfile }->Call($handle, $guuid, $profilename, 0) +== 0 or die "$^E"; }; Win32::API::Struct->typedef('WLAN_CONNECTION_PARAMETERS', qw( WLAN_CONNECTION_MODE wlanConnectionMode; LPCWSTR strProfile; PDOT11_SSID pDot11Ssid; PDOT11_BSSID_LIST pDesiredBssidList; DOT11_BSS_TYPE dot11BssType; DWORD dwFlags; )); Win32::API::Struct->typedef ('DOT11_SSID', qw( ULONG uSSIDLength; UCHAR ucSSID; )); # unused Win32::API::Struct->typedef('WLAN_CONNECTION_MODE', qw( wlan_connection_mode_profile = 0, wlan_connection_mode_temporary_profile, wlan_connection_mode_discovery_secure, wlan_connection_mode_discovery_unsecure, wlan_connection_mode_auto, wlan_connection_mode_invalid )); sub WlanConnect { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid, $profilename, $ssid) = @_; my $pDot11Ssid = Win32::API::Struct->new('DOT11_SSID'); $pDot11Ssid->{uSSIDLength} = length $ssid; $pDot11Ssid->{ucSSID} = $ssid; my $Wlan_connection_parameters = Win32::API::Struct->new('WLAN_CON +NECTION_PARAMETERS'); $Wlan_connection_parameters->{wlanConnectionMode} = 0; $Wlan_connection_parameters->{strProfile} = $profilename; $Wlan_connection_parameters->{pDot11Ssid} = $pDot11Ssid; $Wlan_connection_parameters->{pDesiredBssidList} = 0; $Wlan_connection_parameters->{dot11BssType} = 3; $Wlan_connection_parameters->{dwFlags} = 0; $API{ WlanConnect }->Call($handle, $guuid, $Wlan_connection_parame +ters, 0) == 0 or die "$^E"; }; sub WlanSetProfile { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid, $xmlref) = @_; my $reason = Zero; $API{ WlanSetProfile }->Call($handle, $guuid, 0, $$xmlref, 0, 1, 0 +, $reason) == 0 or die "$^E"; }; sub WlanGetProfileList { croak "Wlan functions are not available" unless $wlan_available; my ($handle, $guuid) = @_; my $profilelist = Zero; $API{ WlanGetProfileList }->Call($handle, $guuid, 0, $profilelist) + == 0 or die "$^E"; my @items = _unpack_counted_array($profilelist, join( '', 'a512', # profile name 'V' # dwFlags ), 512+4); my @profilenames = map { @$_[0] } @items; WlanFreeMemory($profilelist); @profilenames; }; sub load_functions { my $ok = eval { require Win32::API; 1 }; return if ! $ok; for my $sig (@signatures) { $API{ $sig->[0] } = eval { Win32::API->new( 'wlanapi.dll', @$sig ); }; if (! $API{ $sig->[0] }) { return }; }; 1 }; 1; __END__ =head1 NAME Win32::Wlan::API - Access to the Win32 WLAN API =head1 SYNOPSIS use Win32::Wlan::API qw(WlanOpenHandle WlanEnumInterfaces WlanQuer +yCurrentConnection); if ($Win32::Wlan::available) { my $handle = WlanOpenHandle(); my @interfaces = WlanEnumInterfaces($handle); my $ih = $interfaces[0]->{guuid}; # Network adapters are identified by guuid print $interfaces[0]->{name}; my $info = WlanQueryCurrentConnection($handle,$ih); print "Connected to $info{ profile_name }\n"; } else { print "No Wlan detected (or switched off)\n"; }; =head1 SEE ALSO Windows Native Wifi Reference L<http://msdn.microsoft.com/en-us/library/ms706274%28v=VS.85%29.aspx> =head1 REPOSITORY The public repository of this module is L<http://github.com/Corion/Win32-Wlan>. =head1 SUPPORT The public support forum of this module is L<http://perlmonks.org/>. =head1 BUG TRACKER Please report bugs in this module via the RT CPAN bug queue at L<https://rt.cpan.org/Public/Dist/Display.html?Name=Win32-Wlan> or via mail to L<win32-wlan-Bugs@rt.cpan.org>. =head1 AUTHOR Max Maischein C<corion@cpan.org> =head1 COPYRIGHT (c) Copyright 2011-2011 by Max Maischein C<corion@cpan.org>. =head1 LICENSE This module is released under the same terms as Perl itself. =cut
        Win32::API::Struct->typedef('WLAN_CONNECTION_PARAMETERS', qw( WLAN_CONNECTION_MODE wlanConnectionMode; LPCWSTR strProfile; PDOT11_SSID pDot11Ssid; PDOT11_BSSID_LIST pDesiredBssidList; DOT11_BSS_TYPE dot11BssType; DWORD dwFlags; )); Win32::API::Struct->typedef ('DOT11_SSID', qw( ULONG uSSIDLength; UCHAR ucSSID; ));
        "WLAN_CONNECTION_MODE wlanConnectionMode;", "PDOT11_SSID pDot11Ssid;", "PDOT11_BSSID_LIST pDesiredBssidList;", "DOT11_BSS_TYPE dot11BssType;" as types have never been registered earlier with Win32::API::Struct. Some of those are enums and not pointers to structs, which leads me to the next issue
        # unused Win32::API::Struct->typedef('WLAN_CONNECTION_MODE', qw( wlan_connection_mode_profile = 0, wlan_connection_mode_temporary_profile, wlan_connection_mode_discovery_secure, wlan_connection_mode_discovery_unsecure, wlan_connection_mode_auto, wlan_connection_mode_invalid ));
        Win32::API::Struct does NOT support enums. On Windows, enum is a "int", which always 32 bits, regardless of platform (32/64).

        Win32::API::Struct can do a better job giving error messages when encountering unknown types.

        update, your script doesn't run,
        C:\Documents and Settings\Owner\Desktop\cpan libs\Win32-Wlan-0.06>perl + wlan.pl Win32::API::Struct::typedef: unknown member type="WLAN_CONNECTION_MODE +", name="w lanConnectionMode" at C:/perl512/site/lib/Win32/API/Struct.pm line 46. Win32::API::Struct::typedef: unknown member type="wlan_connection_mode +_profile", name="=" at C:/perl512/site/lib/Win32/API/Struct.pm line 46. Unknown Win32::API::Struct 'WLAN_CONNECTION_PARAMETERS' at C:/perl512/ +site/lib/W in32/Wlan/API.pm line 373 Can't call method "Pack" on unblessed reference at C:/perl512/site/lib +/Win32/Wla n/API.pm line 381. C:\Documents and Settings\Owner\Desktop\cpan libs\Win32-Wlan-0.06>

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://978583]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (5)
As of 2024-04-18 21:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found