#!/usr/bin/perl

#/*
#*  This program is free software; you can redistribute it and/or modify
#*  it under the terms of the GNU General Public License as published by
#*  the Free Software Foundation; either version 2 of the License, or
#*  (at your option) any later version.
#*
#*  This program is distributed in the hope that it will be useful,
#*  but WITHOUT ANY WARRANTY; without even the implied warranty of
#*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#*  GNU General Public License for more details.
#*
#*/


use strict;
use Fcntl ':mode';

my @sections;
my %strings;
my %version;
my $driver_name;
my $confdir = "/etc/ndiswrapper";
my $instdir;
my %fuzzlist;

#Backlist of files not to install.
my @copy_blacklist = ("prismusb.sys");

#Fixup list for parameters. 
my %param_fixlist = ("EnableRadio|0" => "EnableRadio|1",
                     "PrivacyMode|0" => "PrivacyMode|1"); 

print <<EOF;
WARNING:
This tool allows you to use a driver written for the Windows operating
system on Ubuntu. Please note that the use of such drivers is entirely
unsupportable by the Ubuntu team, and not recommended, even if it is
theoretically possible with this tool.

EOF

if(@ARGV < 1)
{
	usage();
	exit();
}

my $res;
if($ARGV[0] eq "-i" and @ARGV == 2)
{
	$res = install($ARGV[1]);
}
elsif($ARGV[0] eq "-e" and @ARGV == 2)
{
	$res = remove($ARGV[1]);
}
elsif($ARGV[0] eq "-l" and @ARGV == 1)
{
	$res = list();
}
elsif($ARGV[0] eq "-m" and @ARGV == 1)
{
	$res = modconf();
}
else
{
	usage();
	exit();
}

exit $res;

sub usage
{
	print "Usage: ndiswrapper OPTION\n".
	      "\n".
	      "Manage ndis drivers for ndiswrapper.\n".
	      "-i inffile   Install driver described by inffile\n".
	      "-e driver    Remove driver\n".
	      "-l           List installed drivers\n".
	      "-m           Write configuration for modprobe\n";
}


sub install
{
	my $inf = shift;
	$driver_name = lc($inf);
	$driver_name =~ s/\.inf//;
	$driver_name = `basename $driver_name`;
	$instdir = `dirname $inf`;
	chomp($instdir);
	chomp($driver_name);

	if(isInstalled($driver_name))
	{
		print "$driver_name is already installed. Use -e to remove it\n";
		return -1;
	}
	
	if(!opendir(DH, $confdir))
	{
		mkdir($confdir);
	}
	else
	{
		close(DH);
	}
	
	print "Installing $driver_name\n";
	if(!mkdir("$confdir/$driver_name"))
	{
		print "Unable to create directory $confdir/$driver_name. Make sure you are running as root\n";
		return -1;
	}
	
	loadinf($inf);
	initStrings(); 
	parseVersion();
	`cp -u $inf $confdir/$driver_name/$driver_name.inf`;	
	processPCIFuzz();
}



sub isInstalled
{
	my $installed;
	my $name = shift;
	open(LS, "ls -1 $confdir|");
	while(my $f = <LS>)
	{
		chomp($f);
		my $mode = (stat("$confdir/$f"))[2];
		if(S_ISDIR($mode) and $name eq $f)
		{
			$installed = 1;
		}
		
	}
	close(LS);	
	return $installed;
}


sub remove
{
	my $name = shift;
	if(!isInstalled($name))
	{
		print "Driver $name is not installed. Use -l to list installed drivers\n";
		return;
	}
	`rm -rf $confdir/$name`;
}

sub list
{
	my $s;

	my $cards = getPresentCards();

	if(!$cards)
	{
		print "WARNING: Cannot locate lspci. Unable to see if hardware is present.\n";
	}

	open(LS, "ls -1 $confdir|");
	while(my $f = <LS>)
	{
		chomp($f);
		my $mode = (stat("$confdir/$f"))[2];
		if(S_ISDIR($mode))
		{
			$s .= "$f\t".installStatus($cards, $f)."\n";
		}
	}
	if($s)
	{
		print "Installed ndis drivers:\n$s";
	}
	else
	{
		print "No drivers installed\n$s";
	}
	close(LS);	
}

sub modconf
{
	my $alias = 0;
	my $err = 0;

	my @modprobe = ("/sbin/modprobe", "/usr/sbin/modprobe", "modprobe");
	my $ok = 0;
	for(my $i = 0; $i < @modprobe; $i++)
	{
		if(open(MODPROBE, "$modprobe[$i] -c|"))
		{
			$ok = 1;
			$i = @modprobe;
		}
	}
	if(!$ok)
	{
		return -1;
	}

	while(my $line = <MODPROBE>)
	{
		if($line =~ /^alias\s.+\sndiswrapper/)
		{
			print "modprobe config already contains alias directive\n\n";
			$alias = 1;
		}
		elsif($line =~ /^install\s.*ndiswrapper/)
		{
			print "You should not need an install directive in you modprobe config file.\n";
			modconf_err($line);
			$err = 1;
		}
		elsif($line =~ /^post-install\s+ndiswrapper/)
		{
			print "You should not need a post-install directive in you modprobe config file.\n";
			modconf_err($line);
			$err = 1;
		}
	}
	close(MODPROBE);

	if($alias)
	{
		return;
	}
	

	my $v =  `uname -r`;	


	$v =~ /(\d+)\.(\d+)\.(\d+)/;
	my $major = $1;
	my $minor = $2;
	my $rev = $3;	
	my $modconf;
	if($minor > 4)
	{
		if(-d "/etc/modprobe.d")
		{
			$modconf = "/etc/modprobe.d/ndiswrapper" 
		}
		else
		{
			$modconf = "/etc/modprobe.conf" 
		}
	}
	else
	{
		if(-d "/etc/modutils")
		{
			$modconf = "/etc/modutils/ndiswrapper";
		}
		else
		{
			$modconf = "/etc/modules.conf";
		}
	}
	

	print "Adding \"alias wlan0 ndiswrapper\" to $modconf\n";
	system("echo \"alias wlan0 ndiswrapper\" >>$modconf");
	
    	if(-x "/sbin/update-modules")
	{
		system("/sbin/update-modules");
	}
}

sub modconf_err
{
	my $line = shift;
	print "Please remove the line saying:\n\n";
	print "$line\n";
	print "unless you are 100% sure of what you are doing.\n";			
}


sub getPresentCards
{
#01:00.0 Class 0300: 1002:4c66 (rev 01)
#        Subsystem: 1043:1732
	my @cards;
	
	my @lspci = ("/sbin/lspci", "/usr/sbin/lspci", "lspci");
	for(my $i = 0; $i < @lspci; $i++)
	{
		if(open(LSPCI, "$lspci[$i] -vn|"))
		{
			my $card;
			while(my $line = <LSPCI>)
			{
				if($line =~ /^[0-9]+.*:\s(.{4}):(.{4}).*/)
				{
					my %c;
					$card = \%c;
					$card->{vendor} = $1;
					$card->{device} = $2;
				}
				if($line =~ /.+Subsystem:\s*(.{4}):(.{4}).*/)
				{
					$card->{subvendor} = $1;
					$card->{subdevice} = $2;

					push(@cards, $card);
				}
					
			}
			return \@cards;
		}
	}
}


sub installStatus
{
	my $cards = shift; 
	my $driver = shift;	

	if(!$cards)
	{
		return;
	}

	open(LS2, "ls -1 $confdir/$driver|");
	my $ret = "hardware NOT present";

	while(my $device = <LS2>)
	{
		chomp($device);
		my $d = $device;
		$d =~ s/.conf//;
		if($d =~ /(.{4}):(.{4}):(.{4}):(.{4})/)
		{
			for(my $i = 0; $$cards[$i]; $i++)
			{
				if($$cards[$i]->{vendor} == $1 and
				   $$cards[$i]->{device} == $2 and
				   $$cards[$i]->{subvendor} == $3 and
				   $$cards[$i]->{subdevice} == $4)
				{
					close(LS2);	
					return "hardware present";
				}
			}
			
		}
		elsif($d =~ /(.{4}):(.{4})/)
		{
			for(my $i = 0; $$cards[$i]; $i++)
			{
				if($$cards[$i]->{vendor} == $1 and
				   $$cards[$i]->{device} == $2)
				{
					my $mode = (lstat("$confdir/$driver/$device"))[2];
					if(S_ISLNK($mode))
					{
						$ret = "hardware present,fuzzy";
					}
					else
					{
						close(LS2);	
						return "hardware present";
					}
				}
			}
		}
	}
	close(LS2);	
	return $ret;
}



#
# Create symlink for PCI general device if not existing
#
sub processPCIFuzz
{
	my @devs = keys(%fuzzlist);
	for(my $i = 0; $i < @devs; $i++)
	{
		my $dev = $devs[$i];
		if($dev ne $fuzzlist{$dev})
		{
			`ln -s $confdir/$driver_name/$fuzzlist{$dev}.conf $confdir/$driver_name/$dev.conf`;
		}
	}
}

#
# Collect a list of supported PCI-Id's so we can add fuzzy entries if needed.
#
sub addPCIFuzzEntry
{
	my $vendor = shift;
	my $device = shift;
	my $subvendor = shift;
	my $subdevice = shift;

	my $s = "$vendor:$device";

	if(!$subvendor or !$fuzzlist{$s})
	{
		my $s2 = $s;
		if($subvendor)
		{
			$s2 .= ":$subvendor:$subdevice";
		}
		$fuzzlist{$s} = $s2;
	}
}


sub parseVersion
{
  my $s = getSection("version");
  if(!$s)
    {
      return;
    }
  my @lines = split("\n", $s->{data});

  for(my $i = 0; $i < @lines; $i++)
    {
      (my $key, my $val) = getKeyVal($lines[$i]);
      if($key eq "Provider")
	{
	  $val =~ s/"(.+)"/$1/; 
	  $version{$key} = $val;
	}
      if($key eq "DriverVer")
	{
	  $val =~ s/"(.+)"/$1/; 
	  $version{$key} = $val;
	}
    }
  parseManu();
}

#
# Parse the [Manufacturer] section.
#
sub parseManu
{
#Examples:
#Vendor
#Vendor,ME,NT,NT.5.1
#Vendor.NTx86

	my $manu = getSection("manufacturer");
	if(!$manu)
	{
		return -1;
	}

	my @lines = split("\n", $manu->{data});
	for(my $i = 0; $i < @lines; $i++)
	{
		my $line = remComment($lines[$i]);
		(my $key, my $val) = getKeyVal($line, "=");

		if ($key eq $version{"Provider"})
		  {
		    $strings{$key} = trim($val);
		  }

		if($val)
		{
			my $section;
			my @flavours = split(",", $val);
			my $flavour = "";
			if(@flavours == 1)
			{
				#Vendor
				$section = $val;				
			}
			else
			{
				#Vendor,flavour1, flavour2 etc;
				for(my $i = 1; $i < @flavours; $i++)
				{
					my $flav = trim($flavours[$i]);
					$flav =~ s/\s*(\S+)\s*/$1/;
					if(uc($flav) eq "NT.5.1")
					{
						#This is the best (XP)
						$section = $flavours[0] . "." . $flav;
						$flavour = $flav;
					}
					elsif(substr(uc($flav),0,2) eq "NT" and $section eq "")
					{
						#This is the second best (win2k)
						$section = $flavours[0] . "." . $flav;
						$flavour = $flav;
					}
				}
				
			}
			my $res = parseVendor($flavour, $section);
			if(!$res)
			{
				return $res;
			}
		}
	}
}

#
# Parse a vendor section. This section contains the device-ids. Each poiting to a section with config info.
#	
sub parseVendor
{
	my $flavour = shift;
	my $vendor_name = shift;
	my $vend = getSection($vendor_name);

	if(!$vend)
	{
		print "no vendor\n";
		return -1;
	}

	my @lines = split("\n", $vend->{data});
	for(my $i = 0; $i < @lines; $i++)
	{
		my $line = remComment($lines[$i]);
		(my $name, my $val) = getKeyVal($line, "=");

		if($val)
		{
			(my $section, my $id) = split(",", $val);
			$section = trim($section);
			$id = trim($id);
			(my $bustype, my $vendor, my $device, my $subvendor, my $subdevice) = parseID($id);
			if($vendor)
			{
				parseDevice($flavour, $section, $bustype, $vendor, $device, $subvendor, $subdevice);
			}
		}
	}
}


#
# This section contains pointers to registry sections and copyfiles sections. 
#
sub parseDevice
{
	my $flavour = shift;
	my $device_sect = shift;
	my $bustype = shift;
	my $device = shift;
	my $vendor = shift;
	my $subvendor = shift;
	my $subdevice = shift;

	my $dev = getSection("$device_sect.$flavour");
	if(!$dev)
	{
		$dev = getSection("$device_sect.NT");
	}
	if(!$dev)
	{
		$dev = getSection("$device_sect");
	}

	if(!$dev)
	{
		print "no dev $device_sect $flavour\n";
		return -1;
	}

#	print "$device:$vendor:$subvendor:$subdevice  $flavour\n";

	my $copyfiles;
	my $addreg;
	my @lines = split("\n", $dev->{data});

	for(my $i = 0; $i < @lines; $i++)
	{
		my $line = remComment($lines[$i]);
		(my $key, my $val) = getKeyVal($line, "=");
		if($key)
		{
			if(lc($key) eq "addreg")
			{
				$addreg = $val;
			}
			if(lc($key) eq "copyfiles")
			{
				$copyfiles = $val;
			}
		}
	}		

	my $filename = "$device:$vendor";
	if($subvendor)
	{
		$filename .= ":$subvendor:$subdevice"
	}

	$filename .= ".conf";	
	
	if($bustype == 5)
	{
		addPCIFuzzEntry($device, $vendor, $subvendor, $subdevice);
	}

	if(!open(CONF, ">$confdir/$driver_name/$filename"))
	{
		print "Unable to create file $filename";
		return -1;
	}

	printf CONF "NdisVersion|0x50001\n";
	printf CONF "Environment|1\n";
 	printf CONF "BusType|$bustype\n";
	my $ver=$version{"DriverVer"};
	my $provider=$version{"Provider"};
	my $providerstring = stripquotes(substStr(trim($provider)));
	printf CONF "ndis_version|$providerstring,$ver\n\n";

	my @addregs = split(",", $addreg);
	for(my $i = 0; $i < @addregs; $i++)
	{
		my $reg = trim($addregs[$i]);
		addReg($reg);
	}
	
	my @copyfiles = split(",", $copyfiles);
	for(my $i = 0; $i < @copyfiles; $i++)
	{
		my $file = trim($copyfiles[$i]);
		copyfiles($file);
	}

	close(CONF);
}


#
# Process a copyfiles section.
#
sub copyfiles
{
	my $copy_name = shift;
	my $copy = getSection($copy_name);
	if(!$copy)
	{
		printf "Parse error in inf. Unable to find section $copy_name\n";
		return -1;
	}

	my @lines = split("\n", $copy->{data});

	for(my $i = 0; $i < @lines; $i++)
	{
		my $line = trim(remComment($lines[$i]));

		if($line)
		{
			$line =~ s/,+.*//;
			$line = trim($line);
			my $nocopy = 0;
			for(my $j = 0; $j < @copy_blacklist; $j++)
			{
				if($copy_blacklist[$j] eq lc($line))
				{
					$nocopy = 1;
				}
			}

			my $dir = finddir($line);
			if($dir)
			{
				$dir = findfile("", $dir);
			}

			my $realname = findfile($dir, $line);

			if($realname)
			{
				my $newname = lc($realname);
				if($dir)
				{
					$realname = "$dir/$realname";
				}
				if(!$nocopy)
				{
					`cp -u $instdir/$realname $confdir/$driver_name/$newname`;	
					`chmod 644 $confdir/$driver_name/$newname`;	
				}
				
			}
			else
			{
				print STDERR "Warning: Cannot locate $line\n";
			}
			
		}
	}
}

sub finddir
{
	my $filename = shift;
	my $sourcedisksfiles = getSection("sourcedisksfiles");
	if(!$sourcedisksfiles)
	{
		return "";
	}
	my @lines = split("\n", $sourcedisksfiles->{data});
	for(my $i = 0; $i < @lines; $i++)
	{
		my $line = trim(remComment($lines[$i]));
		$line =~ /(.+)=.+,+(.*)/;
		my $file = trim($1);
		my $dir = trim($2);
		if($file and $dir and lc($filename) eq lc($file))
		{
			return $dir;
		}
	}
	return "";
}

#
# Find a file in a case-insensitive way.
#
sub findfile
{
	my $dir = shift;
	my $file = shift;

	if(!opendir(DIR, "$instdir/$dir"))
	{
		print "Unable to open $instdir\n";
		return "";
	}
	
	my @allfiles = readdir(DIR);
	for(my $i = 0; $i < @allfiles; $i++)
	{
		if(lc($allfiles[$i]) eq lc($file))
		{
			closedir(DIR);	
			return $allfiles[$i]; 
		}
	}
	closedir(DIR);	
	return "";
}


#
# This section contains pointers to the section with the parameters to be
# added to the registry.
#
sub addReg
{
	my $reg_name = shift;
	my $reg = getSection($reg_name);
	if(!$reg)
	{
		printf "Parse error in inf. Unable to find section $reg_name\n";
		return -1;
	}

	my $param;
	my $type;
	my $val;
	my $found;
	my $gotParam = 0;
	
	my @lines = split("\n", $reg->{data});
	for(my $i = 0; $i < @lines; $i++)
	{
		my $line = trim(remComment($lines[$i]));
		if($line)
		{
			$line =~ /([^,]*),([^,]*),([^,]*),([^,]*),(.*)/; 
			my $hkr = trim($1);
			my $p1 = stripquotes(substStr(trim($2)));
			my $p2 = stripquotes(substStr(trim($3)));
			my $p3 = stripquotes(substStr(trim($4)));
			my $p4 = stripquotes(substStr(trim($5)));

			if($p1)
			{
				if($p1 =~ /ndi\\params\\(.+)/i)
				{
					$1 =~ /(.+)\\.*/;
					
					if($1 ne $param)
					{
						$found = 0;
						$param = $1;
						$type = "";
						$val = "";
					}
					if(lc($p2) eq "type")
					{
						$found++;
						$type = $p4;
					}
					elsif(lc($p2) eq "default")
					{
						$found++;
						$val = $p4;
					}

					if($found == 2)
					{
						$gotParam = 1;
					}
				}
			}
			else
			{
				#print "type 2: $reg_name '$p1', '$p2', '$p3', '$p4':'$line'\n";		
				$param = $p2;
				$val = $p4;
				$gotParam = 1;
			}

			
			if($gotParam and $param ne "BusType")
			{
				my $s = "$param|$val";
				if($param_fixlist{"$s"})
				{
					my $sOld = $s;
					$s = $param_fixlist{"$s"};
					print "Forcing parameter $sOld to $s\n"; 
				}
				
				print CONF "$s\n";
				$param = "";
				$gotParam = 0;
			}
		}
	}
}

sub substStr
{
	my $s = shift;
	if($s =~ /^\%(.+)$\%/)
	{
		return getString($1);
	}
	return $s;
}


#
# Parse a device-id line.
#
sub parseID
{
	my $s = uc(shift);
	if($s =~ /PCI\\VEN_(\w+)&DEV_(\w+)&SUBSYS_(\w{4})(\S{4})/)
	{
		return (5, $1, $2, $4, $3);
	}
	elsif($s =~ /PCI\\VEN_(\w+)&DEV_(\w+)/)
	{
		return (5, $1, $2);
	}
	elsif($s =~ /USB\\VID_(\w+)&PID_(\w+)/)
	{
		return (0, $1, $2);
	}
}

#
# remove whitsepace at front and end.
#
sub trim
{
	my $s = shift;
	$s =~ s/^\s*//;
	$s =~ s/\s*$//;
	return $s;
}


sub stripquotes
{
	my $s = shift;
	$s =~ s/"(.*)"/$1/;
	return $s;
}


sub getKeyVal
{
	my $line = shift;

	$line = remComment($line);
	(my $key, my $val) = split("=", $line);
	if($line =~ /(.+)=(.+)/)
	{
		return (trim($1), trim($2));
	}
}


sub remComment
{
	my $s = shift;
	$s=~ s/([^;]*);.*/$1/;
	return $s;
}

#
# Initialize the string symbol table
#
sub initStrings
{
	my $s = getSection("strings");
	if(!$s)
	{
		return;
	}
	my @lines = split("\n", $s->{data});

	for(my $i = 0; $i < @lines; $i++)
	{
		(my $key, my $val) = getKeyVal($lines[$i]);
		if($key)
		{
			$val =~ s/"(.+)"/$1/; 
			$strings{$key} = $val;
#			print "$key=$val\n";
		}
	}
}


#
# fetch a string from the symbol table
#
sub getString
{
	my $s = shift;
	return $strings{$s};
}


#
# Loacate a section.
#
sub getSection
{
	my $needle = shift;

	for(my $i = 0; $i < @sections;  $i++)
	{
#		print "Searching: '" . lc($sections[$i]->{name}) . "' == '" . lc($needle) . "'\n";

		if( lc($sections[$i]->{name}) eq lc($needle))
		{
#			print "found!\n\n";
			return $sections[$i];
		}
	}
#	print "not found!\n\n";
	return 0;
}


#
# Load inf and split into different sections.
#
sub loadinf
{
	my $filename = shift;
	my %def_section;
	my $section = \%def_section;
	
	if(!open(INF, $filename))
	{
		return -1;
	}

	my $i = 0;
	$section->{name} = "none";
	while(my $s = <INF>)
	{
		#Convert from unicode
		$s =~ s/\xff\xfe//;
		$s =~ s/\0//g;

		$s =~ s/\s*$//;	#Remove trailing whitespace and \r
		$s .= "\n";
		if($s =~ /^\[(.+)\]\s*/)
		{
			$sections[$i++] = $section;		
			my %new_section;
			$section = \%new_section;
			$section->{name} = $1;
		}
		else
		{
			$section->{data} .= $s;
		}
	}
	
	$sections[$i++] = $section;  
	close(INF);


#	print "Sections:\n";
#	for(my $i = 0; $i < @sections; $i++)
#	{
#		print $sections[$i]->{name} . "\n";		
#	}
#	print "\n";
}


