#!/usr/bin/env perl
# Written by Ray Lai <ray@raylai.com>.
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org>

use File::Temp 'tempfile';
use JSON::PP;
use OpenBSD::Pledge;
use strict;
use warnings;

my $ifconfig = '/sbin/ifconfig';
my $head = "-chan -bssid -wpakey\n";
my $tail = "dhcp\n";

sub slurp
{
	my $file = shift;
	open F, '<', $file or die "Can't read $file: $!";
	local $/;	# enable slurp mode, locally.
	my $data = <F>;
	close F;
	$data;
}

sub write_hostname_if
{
	my ($if, $ap) = @_;
	my $hostname_if = "/etc/hostname.$if";
	my $hostname_orig = "$hostname_if.orig";

	my ($tmp_fh, $tmp_file) = tempfile('wifind.XXXXXXXXXX', DIR => '/etc');
	print {$tmp_fh} $head;
	# set nwid, bssid, chan, lladdr
	printf {$tmp_fh} 'nwid "%s"', $ap->{nwid};
	printf {$tmp_fh} ' bssid "%s"', $ap->{bssid} if $ap->{bssid};
	printf {$tmp_fh} ' chan "%s"', $ap->{chan} if $ap->{chan};
	printf {$tmp_fh} ' lladdr "%s"', $ap->{lladdr} if $ap->{lladdr};
	print {$tmp_fh} "\n";
	# wpa needs to be set after nwid
	printf {$tmp_fh} "wpakey \"%s\"\n", $ap->{wpakey} if $ap->{wpakey};
	print {$tmp_fh} $tail;
	close $tmp_fh;

	if (-e $hostname_orig) {
		unlink "$hostname_if.orig" or die "unlink failed: $!";
	}
	link $hostname_if, "$hostname_if.orig" or die "link failed: $!";
	rename $tmp_file, $hostname_if or die "rename failed: $!";

	warn "found $ap->{nwid}, wrote $hostname_if\n";
}

pledge(qw( rpath wpath cpath fattr flock proc exec )) || die "Unable to pledge: $!";

my $conf = decode_json(slurp '/etc/wifind.conf');
my $wlan = $conf->{wlan};
my $if = $conf->{if};

# initial scan
open L, '-|', $ifconfig, $if, 'scan' or die "Can't open pipe: $!";

pledge(qw( rpath wpath cpath fattr flock exec )) || die "Unable to pledge: $!";
for (<L>) {
	if (/^\s+nwid (.+) chan (\d+) bssid ([0-9a-f:]+) (-\d+)dBm ([\w-]+) ([\w,-]+)\s*$/) {
		my ($nwid, $chan, $bssid, $dbm, $mystery, $csv) =
		    ($1, $2, $3, $4, $5, $6);
		my %cap = map { $_ => 1 } split(/,/, $csv);

		# remove quotes from nwid, if any
		$nwid =~ s/^"(.*)"$/$1/;

		# reject hostile characters
		if ($nwid =~ /["\\\$]/) {
			warn "malformed nwid: $nwid\n";
			next;
		}
		# check for recognized access points
		# i assume we will match the strongest signal first
		for my $ap (@$wlan) {
			next if $ap->{nwid} ne $nwid ||
			    ($ap->{bssid} && $ap->{bssid} ne $bssid) ||
			    ($ap->{chan} && $ap->{chan} ne $chan) ||
			    ($ap->{wpakey} && !$cap{wpa2});

			# reject hostile characters
			if ($ap->{wpakey} =~ /["\\\$]/) {
				warn "malformed wpakey\n";
				next;
			}

			write_hostname_if $if, $ap;
			exec '/bin/sh', '/etc/netstart', $if
			    or die "exec failed: $!";
		}
	}
}

warn "no network found\n";
exit 1;
