mirror of
				https://github.com/xcat2/xcat-core.git
				synced 2025-11-04 05:12:30 +00:00 
			
		
		
		
	git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@419 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
		
			
				
	
	
		
			464 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
 | 
						|
package xCAT_plugin::dhcp;
 | 
						|
use xCAT::Table;
 | 
						|
use Data::Dumper;
 | 
						|
use MIME::Base64;
 | 
						|
use Getopt::Long;
 | 
						|
Getopt::Long::Configure("bundling");
 | 
						|
Getopt::Long::Configure("pass_through");
 | 
						|
use Socket;
 | 
						|
use Sys::Syslog;
 | 
						|
use IPC::Open2;
 | 
						|
use xCAT::Utils;
 | 
						|
 | 
						|
my @dhcpconf; #Hold DHCP config file contents to be written back.
 | 
						|
my @nrn; # To hold output of netstat -rn to be consulted throughout process
 | 
						|
my $domain;
 | 
						|
my $omshell;
 | 
						|
my $statements; #Hold custom statements to be slipped into host declarations
 | 
						|
my $callback;
 | 
						|
 | 
						|
 | 
						|
sub handled_commands {
 | 
						|
  return {
 | 
						|
    makedhcp => "dhcp",
 | 
						|
  }
 | 
						|
}
 | 
						|
  
 | 
						|
sub delnode {
 | 
						|
  my $node = shift;
 | 
						|
  my $inetn = inet_aton($node);
 | 
						|
 | 
						|
  my $mactab = xCAT::Table->new('mac');
 | 
						|
  if ($mactab) { $ent = $mactab->getNodeAttribs($node,[qw(mac)]); }
 | 
						|
  if ($ent and $ent->{mac}) {
 | 
						|
  my @macs = split(/\|/,$ent->{mac});
 | 
						|
  my $mace;
 | 
						|
  foreach $mace (@macs) {
 | 
						|
	my $mac;
 | 
						|
	my $hname;
 | 
						|
	($mac,$hname) = split (/!/,$mace);	
 | 
						|
	unless ($hname) { $hname = $node; }
 | 
						|
  	print $omshell "new host\n";
 | 
						|
  	print $omshell "set name = \"$hname\"\n"; #Find and destroy conflict name
 | 
						|
  	print $omshell "open\n";
 | 
						|
  	print $omshell "remove\n";
 | 
						|
  	print $omshell "close\n";
 | 
						|
  	if ($inetn) {
 | 
						|
  	  my $ip = inet_ntoa(inet_aton($hname));;
 | 
						|
  	  unless ($ip) { return; }
 | 
						|
  	  print $omshell "new host\n";
 | 
						|
  	  print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
 | 
						|
  	  print $omshell "open\n";
 | 
						|
  	  print $omshell "remove\n";
 | 
						|
  	  print $omshell "close\n";
 | 
						|
  	}
 | 
						|
  }
 | 
						|
  }
 | 
						|
  print $omshell "new host\n";
 | 
						|
  print $omshell "set name = \"$node\"\n"; #Find and destroy conflict name
 | 
						|
  print $omshell "open\n";
 | 
						|
  print $omshell "remove\n";
 | 
						|
  print $omshell "close\n";
 | 
						|
  if ($inetn) {
 | 
						|
    my $ip = inet_ntoa(inet_aton($node));;
 | 
						|
    unless ($ip) { return; }
 | 
						|
    print $omshell "new host\n";
 | 
						|
    print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
 | 
						|
    print $omshell "open\n";
 | 
						|
    print $omshell "remove\n";
 | 
						|
    print $omshell "close\n";
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
sub addnode {
 | 
						|
#Use omshell to add the node.
 | 
						|
#the process used is blind typing commands that should work
 | 
						|
#it tries to delet any conflicting entries matched by name and 
 | 
						|
#hardware address and ip address before creating a brand now one
 | 
						|
#unfortunate side effect: dhcpd.leases can look ugly over time, when
 | 
						|
#doing updates would keep it cleaner, good news, dhcpd restart cleans
 | 
						|
#up the lease file the way we would want anyway.
 | 
						|
  my $node = shift;
 | 
						|
  my $ent;
 | 
						|
  my $nrtab = xCAT::Table->new('noderes');
 | 
						|
  if ($nrtab) {
 | 
						|
     my $ent;
 | 
						|
     $ent = $nrtab->getNodeAttribs($node,['tftpserver']);
 | 
						|
     if ($ent and $ent->{tftpserver}) {
 | 
						|
        $statements = 'next-server '.inet_ntoa(inet_aton($ent->{tftpserver})).';'.$statements;
 | 
						|
     } else {
 | 
						|
        my $nxtsrv = xCAT::Utils->my_ip_facing($node);
 | 
						|
        if ($nxtsrv) {
 | 
						|
           $statements = "next-server $nxtsrv;$statements";
 | 
						|
        }
 | 
						|
     }
 | 
						|
     #else {
 | 
						|
       # $ent = $nrtab->getNodeAttribs($node,['servicenode']);
 | 
						|
       # if ($ent and $ent->{servicenode}) {
 | 
						|
       #  $statements = 'next-server  = \"'.inet_ntoa(inet_aton($ent->{servicenode})).'\";'.$statements;
 | 
						|
       # }
 | 
						|
     #}
 | 
						|
  }
 | 
						|
  my $mactab = xCAT::Table->new('mac');
 | 
						|
  unless ($mactab) { 
 | 
						|
     $callback->({error=>["Unable to open mac table, it may not exist yet"],errorcode=>[1]});
 | 
						|
     return; 
 | 
						|
  } 
 | 
						|
  $ent = $mactab->getNodeAttribs($node,[qw(mac)]);
 | 
						|
  unless ($ent and $ent->{mac}) {
 | 
						|
    $callback->({error=>["Unable to find mac address for $node"],errorcode=>[1]});
 | 
						|
    return; 
 | 
						|
  }
 | 
						|
  my @macs = split(/\|/,$ent->{mac});
 | 
						|
  my $mace;
 | 
						|
  foreach $mace (@macs) {
 | 
						|
	my $mac;
 | 
						|
	my $hname;
 | 
						|
	($mac,$hname) = split (/!/,$mace);	
 | 
						|
	unless ($hname) { $hname = $node; } #Default to hostname equal to nodename
 | 
						|
        unless ($mac) { next; } #Skip corrupt format
 | 
						|
  	my $inetn = inet_aton($hname);
 | 
						|
  	unless ($inetn) {
 | 
						|
    		syslog("local1|err","xCAT DHCP plugin unable to resolve IP for $hname (for $node)");
 | 
						|
    		return;
 | 
						|
  	}
 | 
						|
        my $ip = inet_ntoa(inet_aton($hname));;
 | 
						|
  	syslog("local4|err","Setting $node ($hname|$ip) to ".$mac);
 | 
						|
  	print $omshell "new host\n";
 | 
						|
  	print $omshell "set name = \"$hname\"\n"; #Find and destroy conflict name
 | 
						|
  	print $omshell "open\n";
 | 
						|
  	print $omshell "remove\n";
 | 
						|
  	print $omshell "close\n";
 | 
						|
  	print $omshell "new host\n";
 | 
						|
  	print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
 | 
						|
  	print $omshell "open\n";
 | 
						|
  	print $omshell "remove\n";
 | 
						|
  	print $omshell "close\n";
 | 
						|
  	print $omshell "new host\n";
 | 
						|
  	print $omshell "set hardware-address = ".$mac."\n"; #find and destroy mac conflict
 | 
						|
  	print $omshell "open\n";
 | 
						|
  	print $omshell "remove\n";
 | 
						|
  	print $omshell "close\n";
 | 
						|
  	print $omshell "new host\n";
 | 
						|
  	print $omshell "set name = \"$hname\"\n";
 | 
						|
  	print $omshell "set hardware-address = ".$mac."\n";
 | 
						|
  	print $omshell "set hardware-type = 1\n";
 | 
						|
  	print $omshell "set ip-address = $ip\n";
 | 
						|
  	if ($statements) {
 | 
						|
  	    print $omshell "set statements = \"$statements\"\n";
 | 
						|
  	 }
 | 
						|
 | 
						|
  	print $omshell "create\n";
 | 
						|
  	print $omshell "close\n";
 | 
						|
  	unless (grep /#definition for host $node aka host $hname/,@dhcpconf) {
 | 
						|
  	  push @dhcpconf,"#definition for host $node aka host $hname can be found in the dhcpd.leases file\n";
 | 
						|
  	}
 | 
						|
   }
 | 
						|
}	
 | 
						|
sub preprocess_request {
 | 
						|
   my $req = shift;
 | 
						|
   $callback = shift;
 | 
						|
   if ($req->{_xcatdest}) { return [$req]; } #Exit if the packet has been preprocessed in its history
 | 
						|
   my @requests = ({%$req}); #Start with a straight copy to reflect local instance
 | 
						|
   my $sitetab = xCAT::Table->new('site');
 | 
						|
   (my $ent) = $sitetab->getAttribs({key=>'xcatservers'},'value');
 | 
						|
   $sitetab->close;
 | 
						|
   if ($ent and $ent->{value}) {
 | 
						|
      foreach (split /,/,$ent->{value}) {
 | 
						|
         if (xCAT::Utils->thishostisnot($_)) {
 | 
						|
            my $reqcopy = {%$req};
 | 
						|
            $reqcopy->{'_xcatdest'} = $_;
 | 
						|
            push @requests,$reqcopy;
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
   if (scalar(@requests) > 1) { #hierarchy detected, enforce more rigorous sanity
 | 
						|
      my $ntab = xCAT::Table->new('networks');
 | 
						|
      if ($ntab) {
 | 
						|
         foreach (@{$ntab->getAllEntries()}) {
 | 
						|
            if ($_->{dynamicrange} and not $_->{dhcpserver}) {
 | 
						|
               $callback->({error=>["In a hierachy, network entries with a dynamic range must be explicit in the dhcpserver field"],errorcode=>[1]});
 | 
						|
               return [];
 | 
						|
            }
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return \@requests;
 | 
						|
}
 | 
						|
 | 
						|
sub process_request {
 | 
						|
  my $req = shift;
 | 
						|
  $callback = shift;
 | 
						|
  my $sitetab = xCAT::Table->new('site');
 | 
						|
  my %activenics;
 | 
						|
  my $querynics=1;
 | 
						|
  if ($sitetab) {
 | 
						|
    my $href;
 | 
						|
    ($href) = $sitetab->getAttribs({key=>'dhcpinterfaces'},'value');
 | 
						|
    unless ($href and $href->{value}) { #LEGACY: singular keyname for old style site value
 | 
						|
      ($href) = $sitetab->getAttribs({key=>'dhcpinterface'},'value');
 | 
						|
    }
 | 
						|
    if ($href and $href->{value}) {
 | 
						|
      foreach (split /[,\s]+/,$href->{value}) {
 | 
						|
        $activenics{$_} = 1;
 | 
						|
        $querynics=0;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    ($href) = $sitetab->getAttribs({key=>'domain'},'value');
 | 
						|
    unless ($href and $href->{value}) {
 | 
						|
      $callback->({error=>["No domain defined in site tabe"],errorcode=>[1]});
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    $domain = $href->{value};
 | 
						|
  }
 | 
						|
 | 
						|
  @dhcpconf = ();
 | 
						|
  unless ($req->{arg} or $req->{node}) {
 | 
						|
      $callback->({data=>["Usage: makedhcp <-n> <noderange>"]});
 | 
						|
      return;
 | 
						|
  }
 | 
						|
  if (grep /-n/,@{$req->{arg}}) {
 | 
						|
    if (-e "/etc/dhcpd.conf") {
 | 
						|
      my $bakname = "/etc/dhcpd.conf.xcatbak";
 | 
						|
      while (-e $bakname) { #look for unused backup name..
 | 
						|
        $bakname .= "~";
 | 
						|
      }
 | 
						|
      rename("/etc/dhcpd.conf",$bakname);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    open($rconf,"/etc/dhcpd.conf"); # Read file into memory
 | 
						|
    if ($rconf) {
 | 
						|
      while (<$rconf>) {
 | 
						|
        push @dhcpconf,$_;
 | 
						|
      }
 | 
						|
      close($rconf);
 | 
						|
    }
 | 
						|
    unless ($dhcpconf[0] =~ /^#xCAT/) { #Discard file if not xCAT originated, like 1.x did
 | 
						|
      @dhcpconf = ();
 | 
						|
    }
 | 
						|
  }
 | 
						|
  @nrn = split /\n/,`/bin/netstat -rn`;
 | 
						|
  splice @nrn,0,2; #get rid of header
 | 
						|
  if ($querynics) { #Use netstat to determine activenics only when no site ent.
 | 
						|
    foreach (@nrn) {
 | 
						|
      my @ent = split /\s+/;
 | 
						|
      if ($ent[7] =~ m/(ipoib|ib|vlan|bond|eth|myri|man|wlan)/) { #Mask out many types of interfaces, like xCAT 1.x
 | 
						|
        $activenics{$ent[7]} = 1;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  unless ($dhcpconf[0]) { #populate an empty config with some starter data...
 | 
						|
    newconfig();
 | 
						|
  }
 | 
						|
  foreach (keys %activenics) {
 | 
						|
    addnic($_);
 | 
						|
  }
 | 
						|
  if ($req->{node}) {
 | 
						|
    @ARGV = @{$req->{arg}};
 | 
						|
    $statements="";
 | 
						|
    GetOptions(
 | 
						|
       's|statements=s' => \$statements
 | 
						|
    );
 | 
						|
 | 
						|
        
 | 
						|
        
 | 
						|
    my $passtab = xCAT::Table->new('passwd');
 | 
						|
    my $ent;
 | 
						|
    ($ent) = $passtab->getAttribs({key=>"omapi"},qw(username password));
 | 
						|
    unless ($ent->{username} and $ent->{password}) { return; } # TODO sane err
 | 
						|
    #Have nodes to update
 | 
						|
    #open2($omshellout,$omshell,"/usr/bin/omshell");
 | 
						|
    open($omshell,"|/usr/bin/omshell > /dev/null");
 | 
						|
    
 | 
						|
    print $omshell "key ".$ent->{username}." \"".$ent->{password}."\"\n";
 | 
						|
    print $omshell "connect\n";
 | 
						|
    foreach(@{$req->{node}}) {
 | 
						|
      if (grep /^-d$/,@{$req->{arg}}) {
 | 
						|
        delnode $_;
 | 
						|
      } else { 
 | 
						|
        unless (xCAT::Utils->nodeonmynet($_)) {
 | 
						|
         next;
 | 
						|
        }
 | 
						|
        addnode $_;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    close($omshell);
 | 
						|
  }
 | 
						|
  foreach (@nrn) {
 | 
						|
    my @line = split /\s+/;
 | 
						|
    if ($activenics{$line[7]} and $line[3] !~ /G/) {
 | 
						|
      addnet($line[0],$line[2]);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  writeout();
 | 
						|
}
 | 
						|
 | 
						|
sub addnet {
 | 
						|
  my $net = shift;
 | 
						|
  my $mask = shift;
 | 
						|
  my $nic;
 | 
						|
  unless (grep /\} # $net\/$mask subnet_end/,@dhcpconf) {
 | 
						|
    foreach (@nrn) { # search for relevant NIC
 | 
						|
      my @ent = split /\s+/;
 | 
						|
      if  ($ent[0] eq $net and $ent[2] eq $mask) {
 | 
						|
        $nic=$ent[7];
 | 
						|
      }
 | 
						|
    }
 | 
						|
    print "Need to add $net $mask under $nic\n";
 | 
						|
    my $idx=0;
 | 
						|
    while ($idx <= $#dhcpconf) {
 | 
						|
      if ($dhcpconf[$idx] =~ /\} # $nic nic_end\n/) {
 | 
						|
        last;
 | 
						|
      }
 | 
						|
      $idx++;
 | 
						|
    }
 | 
						|
    unless ($dhcpconf[$idx] =~ /\} # $nic nic_end\n/) {
 | 
						|
      return 1; #TODO: this is an error condition
 | 
						|
    }
 | 
						|
    # if here, means we found the idx before which to insert
 | 
						|
    my $nettab = xCAT::Table->new("networks");
 | 
						|
    my $nameservers;
 | 
						|
    my $gateway;
 | 
						|
    my $tftp;
 | 
						|
    my $range;
 | 
						|
    if ($nettab) {
 | 
						|
      my ($ent) = $nettab->getAttribs({net=>$net,mask=>$mask},qw(tftpserver nameservers gateway dynamicrange dhcpserver));
 | 
						|
      if ($ent and $ent->{nameservers}) {
 | 
						|
        $nameservers = $ent->{nameservers};
 | 
						|
      } else {
 | 
						|
         $callback->({warning=>["No $net specific entry for nameservers, and dhcp plugin not sourcing from site yet (TODO)"]});
 | 
						|
      }
 | 
						|
      if ($ent and $ent->{tftpserver}) {
 | 
						|
        $tftp = $ent->{tftpserver};
 | 
						|
      } else { #presume myself to be it, dhcp no longer does this for us
 | 
						|
         $tftp = xCAT::Utils->my_ip_facing($net);
 | 
						|
      }
 | 
						|
      if ($ent and $ent->{gateway}) {
 | 
						|
        $gateway = $ent->{gateway};
 | 
						|
      }
 | 
						|
      if ($ent and $ent->{dynamicrange}) {
 | 
						|
        unless ($ent->{dhcpserver} and xCAT::Utils->thishostisnot($ent->{dhcpserver}))  { #If specific, only one dhcp server gets a dynamic range
 | 
						|
         $range = $ent->{dynamicrange};
 | 
						|
         $range =~ s/[,-]/ /g;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
         $callback->({warning=>["No dynamic range specified for $net, unknown systems on this network will not receive an address"]});
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
       $callback->({error=>["Unable to open networks table, please run makenetworks"],errorcode=>[1]});
 | 
						|
       return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    my @netent;
 | 
						|
    @netent = (
 | 
						|
      "  subnet $net netmask $mask {\n",
 | 
						|
      "    max-lease-time 43200;\n",
 | 
						|
      "    min-lease-time 43200;\n",
 | 
						|
      "    default-lease-time 43200;\n"
 | 
						|
      );
 | 
						|
    if ($gateway) {
 | 
						|
      push @netent,"    option routers  $gateway;\n";
 | 
						|
    }
 | 
						|
    if ($tftp) {
 | 
						|
      push @netent,"    next-server  $tftp;\n";
 | 
						|
    }
 | 
						|
    push @netent,"    option domain-name \"$domain\";\n";
 | 
						|
    if ($nameservers) {
 | 
						|
      push @netent,"    option domain-name-servers  $nameservers;\n";
 | 
						|
    }
 | 
						|
    push @netent,"    if option client-architecture = 00:00  { #x86\n";
 | 
						|
    push @netent,"      filename \"pxelinux.0\";\n";
 | 
						|
    push @netent,"    } else if option client-architecture = 00:02 { #ia64\n ";
 | 
						|
    push @netent,"      filename \"elilo.efi\";\n";
 | 
						|
    push @netent,"    } else if substring(filename,0,1) = null { #otherwise, provide yaboot if the client isn't specific\n ";
 | 
						|
    push @netent,"      filename \"/yaboot\";\n";
 | 
						|
    push @netent,"    }\n";
 | 
						|
    if ($range) { push @netent,"    range dynamic-bootp $range;\n" };
 | 
						|
    push @netent,"  } # $net\/$mask subnet_end\n";
 | 
						|
    splice(@dhcpconf,$idx,0,@netent);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
sub addnic {
 | 
						|
  my $nic = shift;
 | 
						|
  my $firstindex=0;
 | 
						|
  my $lastindex=0;
 | 
						|
  unless (grep /} # $nic nic_end/,@dhcpconf) { #add a section if not there
 | 
						|
    print "Adding NIC $nic\n";
 | 
						|
    push @dhcpconf,"shared-network $nic {\n";
 | 
						|
    push @dhcpconf,"\} # $nic nic_end\n";
 | 
						|
  }
 | 
						|
    #return; #Don't touch it, it should already be fine..
 | 
						|
    #my $idx=0;
 | 
						|
    #while ($idx <= $#dhcpconf) {
 | 
						|
    #  if ($dhcpconf[$idx] =~ /^shared-network $nic {/) {
 | 
						|
    #    $firstindex = $idx; # found the first place to chop...
 | 
						|
    #  } elsif ($dhcpconf[$idx] =~ /} # $nic network_end/) {
 | 
						|
    #    $lastindex=$idx;
 | 
						|
    #  }
 | 
						|
    #  $idx++;
 | 
						|
    #}
 | 
						|
    #print Dumper(\@dhcpconf);
 | 
						|
    #if ($firstindex and $lastindex) {
 | 
						|
    #  splice @dhcpconf,$firstindex,($lastindex-$firstindex+1);
 | 
						|
    #}
 | 
						|
    #print Dumper(\@dhcpconf);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
sub writeout {
 | 
						|
  my $targ;
 | 
						|
  open($targ,'>',"/etc/dhcpd.conf");
 | 
						|
  foreach (@dhcpconf) {
 | 
						|
    print $targ $_;
 | 
						|
  }
 | 
						|
  close($targ)
 | 
						|
}
 | 
						|
 | 
						|
sub newconfig {
 | 
						|
# This function puts a standard header in and enough to make omapi work.
 | 
						|
  my $passtab = xCAT::Table->new('passwd',-create=>1);
 | 
						|
  push @dhcpconf,"#xCAT generated dhcp configuration\n";
 | 
						|
  push @dhcpconf,"\n";
 | 
						|
  push @dhcpconf,"authoritative;\n";
 | 
						|
  push @dhcpconf,"ddns-update-style none;\n";
 | 
						|
  push @dhcpconf,"option client-architecture code 93 = unsigned integer 16;\n";
 | 
						|
  push @dhcpconf,"\n";
 | 
						|
  push @dhcpconf,"omapi-port 7911;\n"; #Enable omapi...
 | 
						|
  push @dhcpconf,"key xcat_key {\n";
 | 
						|
  push @dhcpconf,"  algorithm hmac-md5;\n";
 | 
						|
  (my $passent) = $passtab->getAttribs({key=>omapi,username=>'xcat_key'},'password');
 | 
						|
  my $secret = encode_base64(genpassword(32)); #Random from set of  62^32 
 | 
						|
  chomp $secret;
 | 
						|
  if ($passent->{password}) { $secret = $passent->{password}; } else {
 | 
						|
     $callback->({data=>["The dhcp server must be restarted for OMAPI function to work"]});
 | 
						|
     $passtab->setAttribs({key=>omapi},{username=>'xcat_key',password=>$secret});
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  push @dhcpconf,"  secret \"".$secret."\";\n";
 | 
						|
  push @dhcpconf,"};\n";
 | 
						|
  push @dhcpconf,"omapi-key xcat_key;\n";
 | 
						|
}
 | 
						|
 | 
						|
sub genpassword {
 | 
						|
#Generate a pseudo-random password of specified length
 | 
						|
  my $length = shift;
 | 
						|
  my $password='';
 | 
						|
  my $characters= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
 | 
						|
  srand; #have to reseed, rand is not rand otherwise
 | 
						|
  while (length($password) < $length) {
 | 
						|
    $password .= substr($characters,int(rand 63),1);
 | 
						|
  }
 | 
						|
  return $password;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
1;
 |