1740 lines
42 KiB
Perl
Executable File
1740 lines
42 KiB
Perl
Executable File
#!/usr/bin/perl
|
||
|
||
use v5.10.1;
|
||
use strict;
|
||
use warnings;
|
||
|
||
use Digest::SHA1;
|
||
use IPC::Cmd qw[run];
|
||
use MIME::Base64;
|
||
use Net::LDAPS;
|
||
use Net::LDAP::Util qw(ldap_error_text);
|
||
use Pod::Usage;
|
||
use Term::ANSIColor qw(:constants);
|
||
use Term::ReadKey;
|
||
use Quota;
|
||
|
||
#use Cwd 'abs_path';
|
||
#use File::Basename;
|
||
#use File::Find;
|
||
|
||
BEGIN {
|
||
push @INC, "../";
|
||
}
|
||
|
||
use ACU::LDAP;
|
||
use ACU::Log;
|
||
|
||
###########################################################
|
||
# #
|
||
# Global variables #
|
||
# #
|
||
###########################################################
|
||
|
||
my $wksHomePrefix = "/home/";
|
||
my $nfsHomePrefix = "/srv/nfs/accounts/";
|
||
|
||
my $shellValid = "/bin/zsh";
|
||
|
||
my $colorize = defined($ENV{'ENABLE_COLOR'});
|
||
|
||
my %dev_quota = ( home => "/dev/mapper/acu-nfs--accounts",
|
||
sgoinfre => "/dev/mapper/acu-nfs--sgoinfre" );
|
||
my %def_quota = ( block => { home => 2306866, sgoinfre => 5242880 },
|
||
file => { home => 50000, sgoinfre => 60000 } );
|
||
|
||
###########################################################
|
||
# #
|
||
# Main Program #
|
||
# #
|
||
###########################################################
|
||
|
||
my $dbh;
|
||
|
||
my %cmds =
|
||
(
|
||
"account" => \&cmd_account,
|
||
"group" => \&cmd_group,
|
||
"help" => \&cmd_help,
|
||
"list" => \&cmd_list,
|
||
);
|
||
|
||
my %cmds_account =
|
||
(
|
||
"alias" => \&cmd_account_alias,
|
||
"close" => \&cmd_account_close,
|
||
"cn" => \&cmd_account_cn,
|
||
"create" => \&cmd_account_create,
|
||
"finger" => \&cmd_account_view,
|
||
"mail" => \&cmd_account_mail,
|
||
"name" => \&cmd_account_cn,
|
||
"nopass" => \&cmd_account_nopass,
|
||
"password" => \&cmd_account_password,
|
||
"passgen" => \&cmd_account_passgen,
|
||
"photo" => \&cmd_account_photo,
|
||
"quota" => \&cmd_account_quota,
|
||
"reopen" => \&cmd_account_reopen,
|
||
"rights" => \&cmd_account_rights,
|
||
"services" => \&cmd_account_services,
|
||
"shell" => \&cmd_account_shell,
|
||
"view" => \&cmd_account_view,
|
||
"view" => \&cmd_account_view,
|
||
|
||
"grant-intra" => \&cmd_account_grantintra,
|
||
"grant-lab" => \&cmd_account_grantlab,
|
||
"grant-mail" => \&cmd_account_grantmail,
|
||
);
|
||
|
||
my %cmds_group =
|
||
(
|
||
"list" => \&cmd_group_list,
|
||
"add" => \&cmd_group_add,
|
||
"remove" => \&cmd_group_remove,
|
||
"create" => \&cmd_group_create,
|
||
"delete" => \&cmd_group_delete
|
||
);
|
||
|
||
my %cmds_list =
|
||
(
|
||
"accounts" => \&cmd_list_accounts,
|
||
"groups" => \&cmd_list_groups,
|
||
"roles" => \&cmd_list_roles,
|
||
);
|
||
|
||
|
||
######################################
|
||
# #
|
||
# UTILITY FUNCTIONS #
|
||
# #
|
||
######################################
|
||
|
||
sub ldap_get_password()
|
||
{
|
||
my $bindsecret;
|
||
if (defined($ENV{'LDAP_PASSWORD'}) && $ENV{'LDAP_PASSWORD'} ne "") {
|
||
return $ENV{'LDAP_PASSWORD'};
|
||
}
|
||
|
||
say "To avoid typing password everytime, set LDAP_PASSWORD in your env.";
|
||
say "Do not do this in your shell configuration file!";
|
||
say "Use a command like:\n";
|
||
say ' $ echo -n "LDAP password: "; read -s LDAP_PASSWORD; echo';
|
||
say ' $ LDAP_PASSWORD=$LDAP_PASSWORD lpt ...';
|
||
say "The last line prevent you from exporting the LDAP password to all commands but lpt!";
|
||
say "";
|
||
|
||
ReadMode("noecho");
|
||
print BOLD, "Need LDAP password: ", RESET;
|
||
$bindsecret = <STDIN>;
|
||
ReadMode("restore");
|
||
print "\n";
|
||
|
||
chomp $bindsecret;
|
||
return $bindsecret;
|
||
}
|
||
|
||
$LDAP::binddn = "cn=admin,dc=acu,dc=epita,dc=fr";
|
||
$LDAP::secret_search = \&ldap_get_password;
|
||
|
||
######################################
|
||
# #
|
||
# ACCOUNT BLOCK #
|
||
# #
|
||
######################################
|
||
|
||
sub cmd_account(@)
|
||
{
|
||
my $login = shift;
|
||
|
||
if (! $login) {
|
||
log(USAGE, "lpt account <login> <command> [arguments ...]");
|
||
return 1;
|
||
}
|
||
|
||
my $subcmd = shift // "view";
|
||
|
||
if (! $subcmd) {
|
||
pod2usage(-verbose => 99,
|
||
-sections => [ 'ACCOUNT COMMANDS' ] );
|
||
}
|
||
elsif (! exists $cmds_account{$subcmd}) {
|
||
log(USAGE, "Unknown command for account: ". $subcmd);
|
||
return 1;
|
||
}
|
||
|
||
return $cmds_account{$subcmd}($login, @_);
|
||
}
|
||
|
||
sub cmd_account_alias($@)
|
||
{
|
||
return cmd_account_multiple_vieworchange('mailAlias', 'alias', @_);
|
||
}
|
||
|
||
sub cmd_account_close($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
if ($#_ > -1) {
|
||
log(USAGE, "<lpt> account <login> close");
|
||
return -1;
|
||
}
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => ['objectClass', 'userPassword', 'loginShell'],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found or multiple presence");
|
||
}
|
||
|
||
if (grep { "epitaAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
log(INFO, "Invalidating password for $login ...");
|
||
|
||
my $passwd = $mesg->entry(0)->get_value("userPassword");
|
||
$passwd =~ s/^(\{[^\}]+\})/$1!/ if ($passwd !~ /^\{[^\}]+\}!/);
|
||
|
||
$mesg->entry(0)->replace("userPassword" => $passwd);
|
||
$mesg->entry(0)->update($ldap);
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
if (grep { "posixAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
log(DEBUG, "Setting shell for $login ...");
|
||
cmd_account_shell($login, "/bin/false");
|
||
}
|
||
|
||
log(WARN, "Done. Don't forget to restart nscd on servers and workstations!");
|
||
return 0;
|
||
}
|
||
|
||
sub cmd_account_cn($@)
|
||
{
|
||
return cmd_account_vieworchange('cn', 'name', @_);
|
||
}
|
||
|
||
sub cmd_account_create($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
if ($#_ < 3) {
|
||
log(USAGE, "lpt account <login> create <year> <uid> <pr<70>nom> <nom> [nopass|passgen|password]");
|
||
return 1;
|
||
}
|
||
|
||
my $group = shift;
|
||
|
||
log(DEBUG, "Adding dn: uid=$login,ou=$group,ou=users,dc=acu,dc=epita,dc=fr ...");
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
my $mesg = $ldap->add( "uid=$login,ou=$group,ou=users,dc=acu,dc=epita,dc=fr",
|
||
attrs => [
|
||
objectclass => [ "top", "epitaAccount" ],
|
||
uidNumber => shift,
|
||
cn => shift(@_)." ".shift(@_),
|
||
mail => "$login\@epita.fr",
|
||
uid => $login,
|
||
]
|
||
);
|
||
|
||
#$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
if ($mesg->code == 0) {
|
||
log(INFO, "Account added: $login");
|
||
my $pass = shift;
|
||
return cmd_account($login, $pass) if ($pass ne "nopass");
|
||
return 0;
|
||
}
|
||
else {
|
||
log(ERROR, "Unable to add: $login: ", RESET, $mesg->error);
|
||
}
|
||
}
|
||
|
||
sub cmd_account_grantintra($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $dn = LDAP::search_dn($ldap, "ou=users", "uid=$login");
|
||
|
||
LDAP::add_attribute($ldap, $dn, "objectClass", "intraAccount");
|
||
|
||
log(INFO, "$login now grants to use the intranet.");
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
}
|
||
|
||
sub cmd_account_grantlab($@)
|
||
{
|
||
my $login = shift;
|
||
my $group = shift;
|
||
|
||
if ($group ne "acu" && $group ne "yaka") {
|
||
log(USAGE, "lpt account <login> grantlab <acu|yaka>");
|
||
return 1;
|
||
}
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $dn = LDAP::search_dn($ldap, "ou=users", "uid=$login");
|
||
|
||
if (!LDAP::get_attribute($ldap, $dn, "mail")) {
|
||
LDAP::add_attribute($ldap, $dn, "mail", "$login\@epita.fr");
|
||
}
|
||
|
||
LDAP::add_attribute($ldap, $dn, "mailAlias", "$login\@$group.epita.fr");
|
||
LDAP::update_attribute($ldap, $dn, "mailAccountActive", "yes");
|
||
LDAP::add_attribute($ldap, $dn, "objectClass", "MailAccount");
|
||
LDAP::add_attribute($ldap, $dn, "objectClass", "labAccount");
|
||
|
||
log(INFO, "$login now grants to receive e-mail and connect in laboratory.");
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
}
|
||
|
||
sub cmd_account_grantmail($)
|
||
{
|
||
my $login = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $dn = LDAP::search_dn($ldap, "ou=users", "uid=$login");
|
||
|
||
my $entry = LDAP::get_dn($ldap, $dn, "mailAccountActive", "objectClass");
|
||
|
||
my @oc = $entry->get_value("objectClass");
|
||
push @oc, "MailAccount";
|
||
|
||
$entry->replace("objectClass" => \@oc);
|
||
$entry->replace("mailAccountActive" => [ "yes" ]);
|
||
|
||
my $mesg = $entry->update($ldap) or die $!;
|
||
if ($mesg->code != 0) { log(WARN, $mesg->error); return 0; }
|
||
else { log(INFO, "$login now grants to receive e-mail. Remember to add some aliases!"); }
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
}
|
||
|
||
sub cmd_account_mail(@)
|
||
{
|
||
return cmd_account_vieworchange('mail', 'mail', @_);
|
||
}
|
||
|
||
sub cmd_account_nopass($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => ['userPassword'],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found");
|
||
}
|
||
|
||
my $pass = $mesg->entry(0)->get_value("userPassword");
|
||
|
||
if (! $pass || $pass eq "{crypt}!toto") {
|
||
$mesg = $ldap->unbind;
|
||
log(WARN, "Password already empty");
|
||
return 2;
|
||
}
|
||
else {
|
||
printf(STDERR "Are you sure you want to reset password for $login? [y/N] ");
|
||
if (getc(STDIN) ne "y") {
|
||
log(DEBUG, "y response expected to continue; leaving.");
|
||
log(WARN, "Password unchanged for $login.");
|
||
return 2;
|
||
}
|
||
|
||
$mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => ['userPassword'],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found");
|
||
}
|
||
|
||
$mesg->entry(0)->replace("userPassword" => "{crypt}!toto");
|
||
$mesg->entry(0)->update($ldap);
|
||
|
||
log(INFO, "$login have no more password.");
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
sub cmd_account_passgen($@)
|
||
{
|
||
my $login = shift;
|
||
my $nb_char = shift // 10;
|
||
|
||
if ($nb_char < 10) {
|
||
log(USAGE, "lpt account <login> passgen [nb_char>=10]");
|
||
return 1;
|
||
}
|
||
|
||
#printf(STDERR "Are you sure you want to change password for $login? [y/N] ");
|
||
# my $go = <STDIN>;
|
||
# chomp $go;
|
||
# if ($go ne "y" and $go ne "yes") {
|
||
# log(DEBUG, "y response expected to continue, leaving.");
|
||
# log(WARN, "Password unchanged for $login.");
|
||
# return 2;
|
||
# }
|
||
#
|
||
|
||
log(DEBUG, "Generating a $nb_char chars password...");
|
||
my $pass = "";
|
||
open (HANDLE, "pwgen -s -n -c -y -1 $nb_char 1 |");
|
||
while(<HANDLE>) {
|
||
$pass = $_;
|
||
}
|
||
close(HANDLE);
|
||
chomp($pass);
|
||
|
||
log(DEBUG, "Setting $pass password to $login...");
|
||
if (cmd_account_password($login, $pass)) {
|
||
return 3;
|
||
}
|
||
else {
|
||
say "$login:$pass";
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
sub cmd_account_password($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
if ($#_ > 0) {
|
||
log(USAGE, "lpt account <login> password [new_password]");
|
||
return 1;
|
||
}
|
||
my $pass = shift;
|
||
|
||
if (! $pass) {
|
||
say "Changing password for $login.";
|
||
|
||
ReadMode("noecho");
|
||
print "new password: "; my $pass1 = <STDIN>;
|
||
print "\nretype new password: "; my $pass2 = <STDIN>;
|
||
ReadMode("restore");
|
||
print "\n";
|
||
|
||
log(DEBUG, "Read passwords: $pass1 and $pass2");
|
||
|
||
$pass1 eq $pass2 || log(ERROR, "Passwords did not match.");
|
||
$pass = $pass1;
|
||
}
|
||
|
||
if ($pass eq "") {
|
||
log(ERROR, "Empty password refused.");
|
||
}
|
||
|
||
chomp($pass);
|
||
my $salt = join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64, rand 64, rand 64];
|
||
|
||
my $ctx = Digest::SHA1->new;
|
||
$ctx->add($pass);
|
||
$ctx->add($salt);
|
||
|
||
my $enc_password = "{SSHA}" . encode_base64($ctx->digest . $salt ,'');
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => ['userPassword'],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found");
|
||
}
|
||
|
||
$mesg->entry(0)->replace("userPassword" => $enc_password);
|
||
$mesg->entry(0)->update($ldap);
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
return 0;
|
||
}
|
||
|
||
sub cmd_account_photo($@)
|
||
{
|
||
return cmd_account_vieworchange('photoURI', 'photo', @_);
|
||
}
|
||
|
||
sub cmd_account_reopen(@)
|
||
{
|
||
my $login = shift;
|
||
|
||
if ($#_ != -1) {
|
||
log(USAGE, "<lpt> account <login> reopen");
|
||
return 1;
|
||
}
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => ['objectClass', 'cn', 'userPassword', 'loginShell'],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found or multiple presence");
|
||
}
|
||
|
||
if (grep { "epitaAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
# update password
|
||
my $passwd = $mesg->entry(0)->get_value("userPassword");
|
||
if ($passwd =~ /^\{[^\}]+\}!/) {
|
||
log(INFO, "Restoring password for $login ...");
|
||
|
||
$passwd =~ s/^(\{[^\}]+\})!/$1/;
|
||
|
||
$mesg->entry(0)->replace("userPassword" => $passwd);
|
||
$mesg->entry(0)->update($ldap);
|
||
}
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
if (grep { "posixAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
log(DEBUG, "Setting shell for $login ...");
|
||
cmd_account_shell($login, $shellValid);
|
||
}
|
||
|
||
log(WARN, "Done. Don't forget to restart nscd on servers and workstations!");
|
||
return 0;
|
||
}
|
||
|
||
sub cmd_account_rights($@)
|
||
{
|
||
return cmd_account_multiple_vieworchange("intraRight", "right", @_);
|
||
}
|
||
|
||
sub cmd_account_services($@)
|
||
{
|
||
return cmd_account_multiple_vieworchange("labService", "laboratory_service", @_);
|
||
}
|
||
|
||
sub cmd_account_shell($@)
|
||
{
|
||
return cmd_account_vieworchange("loginShell", "shell", @_);
|
||
}
|
||
|
||
sub cmd_account_multiple_vieworchange($$$@)
|
||
{
|
||
my $type = shift;
|
||
my $typeName = shift;
|
||
my $login = shift;
|
||
my $action = shift // "list";
|
||
my $change = shift;
|
||
|
||
if (($action ne "list" and $action ne "add" and $action ne "del" and $action ne "flush") or (!$change and $action ne "list" and $action ne "flush")) {
|
||
log(USAGE, "<lpt> account <login> $typeName [list|add|del|flush] [string]");
|
||
return 1;
|
||
}
|
||
|
||
my $ldap;
|
||
$ldap = LDAP::ldap_connect() if ($action ne "list");
|
||
$ldap = LDAP::ldap_connect_anon() if ($action eq "list");
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => [ $type ],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found or multiple presence");
|
||
}
|
||
|
||
if ($action eq "add") {
|
||
log(INFO, "Adding $change as ".$typeName."s for $login ...");
|
||
|
||
my @data = $mesg->entry(0)->get_value($type);
|
||
|
||
if (! grep(/^$change$/, @data)) {
|
||
push @data, $change;
|
||
$mesg->entry(0)->replace($type => \@data) or die $!;
|
||
$mesg->entry(0)->update($ldap) or die $!;
|
||
|
||
log(INFO, "Done!");
|
||
}
|
||
else {
|
||
log(WARN, "$login has already $change $typeName.");
|
||
}
|
||
}
|
||
elsif ($action eq "del") {
|
||
log(INFO, "Checking if $change is a ".$typeName."s of $login ...");
|
||
my @data = $mesg->entry(0)->get_value($type);
|
||
if (grep(/^$change$/, @data)) {
|
||
log(INFO, "Deleting $change as $typeName for $login ...");
|
||
|
||
@data = grep(!/$change$/, @data);
|
||
|
||
$mesg->entry(0)->replace($type => \@data) or die $!;
|
||
$mesg->entry(0)->update($ldap) or die $!;
|
||
|
||
log(INFO, "Done!");
|
||
}
|
||
else {
|
||
log(WARN, "$change is not a $typeName for $login.");
|
||
}
|
||
}
|
||
elsif ($action eq "flush") {
|
||
$ldap->modify($mesg->entry(0)->dn, delete => [$type]);
|
||
log(INFO, "$login have no more $typeName.");
|
||
}
|
||
else {
|
||
if ($mesg->entry(0)->get_value($type)) {
|
||
log(INFO, $login."'s ".$typeName."s are:");
|
||
for my $val ($mesg->entry(0)->get_value($type)) {
|
||
say " - $val";
|
||
}
|
||
}
|
||
else {
|
||
log(INFO, "$login have no $typeName.");
|
||
}
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
return 0;
|
||
}
|
||
|
||
sub cmd_account_vieworchange($$@)
|
||
{
|
||
my $type = shift;
|
||
my $typeName = shift;
|
||
my $login = shift;
|
||
|
||
if ($#_ > 0) {
|
||
log(USAGE, "<lpt> account <login> $typeName [new_string]");
|
||
return 1;
|
||
}
|
||
|
||
my $change = shift;
|
||
|
||
my $ldap;
|
||
$ldap = LDAP::ldap_connect() if ($change);
|
||
$ldap = LDAP::ldap_connect_anon() if (!$change);
|
||
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => [ $type ],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) {
|
||
log(ERROR, $mesg->error);
|
||
}
|
||
if ($mesg->count != 1) {
|
||
log(ERROR, "User $login not found or multiple presence");
|
||
}
|
||
|
||
if ($change) {
|
||
log(INFO, "Setting $typeName to $change for $login ...");
|
||
|
||
$mesg->entry(0)->replace($type => $change) or die $!;
|
||
$mesg->entry(0)->update($ldap) or die $!;
|
||
|
||
log(INFO, "Done!");
|
||
}
|
||
elsif ($mesg->entry(0)->get_value($type)) {
|
||
log(INFO, $login."'s $typeName is ".$mesg->entry(0)->get_value($type).".");
|
||
}
|
||
else {
|
||
log(INFO, $login."'s has no $typeName.");
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
return 0;
|
||
}
|
||
|
||
sub cmd_account_view($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
|
||
my $mesg = $ldap->search(base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => ['objectClass']);
|
||
|
||
$mesg->code && log(ERROR, $mesg->error);
|
||
if ($mesg->count <= 0) {
|
||
log(ERROR, "No such account!");
|
||
}
|
||
|
||
log(DEBUG, "objectClasses:\t" . join(', ', $mesg->entry(0)->get_value("objectClass")));
|
||
|
||
my @attrs = ['dn', 'ou'];
|
||
if ($#_ >= 0) {
|
||
push @attrs, @_;
|
||
}
|
||
else {
|
||
if (grep { "epitaAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
push @attrs, 'uid', 'cn', 'mail', 'uidNumber';
|
||
}
|
||
if (grep { "posixAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
push @attrs, 'gecos', 'loginShell', 'homeDirectory', 'gidNumber';
|
||
}
|
||
if (grep { "labAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
push @attrs, 'labService', 'quotaHomeBlock', 'quotaHomeFile', 'quotaSgoinfreBlock', 'quotaSgoinfreFile';
|
||
}
|
||
if (grep { "intraAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
push @attrs, 'intraRight';
|
||
}
|
||
if (grep { "MailAccount" } $mesg->entry(0)->get_value("objectClass")) {
|
||
push @attrs, 'mailAlias';
|
||
}
|
||
}
|
||
|
||
log(DEBUG, "attrs to get: " . join(', ', @attrs));
|
||
$mesg = $ldap->search(base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => \@attrs);
|
||
$mesg->code && die $mesg->error;
|
||
|
||
shift @attrs; # Remove dn
|
||
my $nb = 0;
|
||
for my $entry ($mesg->entries)
|
||
{
|
||
if ($nb > 0) {
|
||
say "==";
|
||
}
|
||
say BOLD, YELLOW, "dn: ", RESET, YELLOW, $entry->dn, RESET;
|
||
|
||
for my $attr (@attrs) {
|
||
if ($#attrs < 3) {
|
||
for my $entry ($entry->get_value($attr)) {
|
||
say CYAN, "$attr: ", RESET , $entry;
|
||
}
|
||
}
|
||
else {
|
||
say CYAN, "$attr: ", RESET , join(', ', $entry->get_value($attr));
|
||
}
|
||
}
|
||
|
||
$nb++;
|
||
}
|
||
|
||
if ($nb > 1) {
|
||
say "\n$nb users displayed";
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
return 0;
|
||
}
|
||
|
||
|
||
######################################
|
||
# #
|
||
# GROUP BLOCKS #
|
||
# #
|
||
######################################
|
||
|
||
sub cmd_group(@)
|
||
{
|
||
my $gname = shift;
|
||
|
||
if (! $gname) {
|
||
log(USAGE, "lpt group <group-name> <command> [arguments ...]");
|
||
return 1;
|
||
}
|
||
|
||
my $subcmd = shift // "view";
|
||
|
||
if (! $subcmd) {
|
||
pod2usage(-verbose => 99,
|
||
-sections => [ 'GROUP COMMANDS' ] );
|
||
}
|
||
elsif (! exists $cmds_group{$subcmd}) {
|
||
log(USAGE, "Unknown command for group: ". $subcmd);
|
||
return 1;
|
||
}
|
||
|
||
return $cmds_group{$subcmd}($gname, @_);
|
||
}
|
||
|
||
sub cmd_group_list(@)
|
||
{
|
||
if ($#ARGV > 0)
|
||
{
|
||
log(USAGE, "<lpt> group list [group]");
|
||
exit(1);
|
||
}
|
||
|
||
my $group = $ARGV[0];
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
if ($#ARGV == 0)
|
||
{
|
||
my $mesg = $ldap->search( # search a group
|
||
base => "cn=$group,ou=groups,dc=acu,dc=epita,dc=fr",
|
||
filter => "objectClass=posixGroup",
|
||
attrs => ['memberUid']
|
||
);
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
foreach my $entry ($mesg->sorted('memberUid'))
|
||
{
|
||
foreach my $user ($entry->get_value("memberUid"))
|
||
{
|
||
print "$user\n";
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
my $mesg = $ldap->search( # list groups
|
||
base => "ou=groups,dc=acu,dc=epita,dc=fr",
|
||
filter => "objectClass=posixGroup",
|
||
attrs => ['cn', 'gidNumber']
|
||
);
|
||
|
||
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
foreach my $entry ($mesg->sorted('gidNumber'))
|
||
{
|
||
print $entry->get_value("cn")." --->";
|
||
print $entry->get_value("gidNumber")."\n";
|
||
}
|
||
}
|
||
|
||
$ldap->unbind; # take down session
|
||
}
|
||
|
||
sub cmd_group_add(@)
|
||
{
|
||
my $group = shift;
|
||
|
||
if ($#_ < 0)
|
||
{
|
||
log(USAGE, "<lpt> group <group-name> add <login>");
|
||
exit(1);
|
||
}
|
||
|
||
my $login = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->search( # search a group
|
||
base => "cn=$group,ou=system,ou=groups,dc=acu,dc=epita,dc=fr",
|
||
filter => "objectClass=posixGroup",
|
||
attrs => ['memberUid']
|
||
) or die $!;
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
foreach my $entry ($mesg->entries)
|
||
{
|
||
my @mem = $entry->get_value("memberUid");
|
||
|
||
foreach my $member (@mem)
|
||
{
|
||
if ($member eq $login)
|
||
{
|
||
log WARN, "$login est d<EFBFBD>j<EFBFBD> dans le groupe $group";
|
||
$ldap->unbind;
|
||
exit 1;
|
||
}
|
||
}
|
||
|
||
push @mem, $login;
|
||
$entry->replace("memberUid" => \@mem);
|
||
$entry->update($ldap);
|
||
|
||
log INFO, "$login ajout<EFBFBD> au groupe $group avec succ<EFBFBD>s.";
|
||
}
|
||
$ldap->unbind; # take down session
|
||
}
|
||
|
||
sub cmd_group_remove(@)
|
||
{
|
||
if ($#ARGV < 1)
|
||
{
|
||
log(USAGE, "<lpt> group remove <group> <login>");
|
||
exit(1);
|
||
}
|
||
|
||
my $group = $ARGV[0];
|
||
my $login = $ARGV[1];
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->search( # search a group
|
||
base => "cn=$group,ou=groups,dc=acu,dc=epita,dc=fr",
|
||
filter => "objectClass=posixGroup",
|
||
attrs => ['memberUid']
|
||
);
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
foreach my $entry ($mesg->sorted('memberUid'))
|
||
{
|
||
my @mem = $entry->get_value("memberUid");
|
||
my $found = 0;
|
||
foreach my $user (@mem)
|
||
{
|
||
if ($user eq $login)
|
||
{
|
||
$found = 1;
|
||
}
|
||
}
|
||
|
||
if ($found)
|
||
{
|
||
@mem = grep(!/$login$/, @mem);
|
||
$entry->replace("memberUid" => [@mem]);
|
||
$entry->update($ldap);
|
||
}
|
||
else
|
||
{
|
||
print "$login n'est pas dans le groupe $group\n";
|
||
}
|
||
|
||
print "Nouvelle liste des membres de $group :\n";
|
||
foreach my $user (@mem)
|
||
{
|
||
print "$user\n";
|
||
}
|
||
|
||
}
|
||
$ldap->unbind; # take down session
|
||
|
||
system('service nscd restart');
|
||
}
|
||
|
||
sub cmd_group_create($$)
|
||
{
|
||
if ($#_ != 1)
|
||
{
|
||
log(USAGE, "<lpt> group create <yaka|acu> <year>");
|
||
exit(1);
|
||
}
|
||
|
||
my $type = shift;
|
||
my $year = shift;
|
||
my $cn = $type . $year;
|
||
my $gid;
|
||
if ($type eq "acu") {
|
||
$gid = $year;
|
||
}
|
||
elsif ($type eq "yaka") {
|
||
$gid = $year - 1000;
|
||
}
|
||
else {
|
||
log(ERROR, "Error: type must be acu or yaka!");
|
||
}
|
||
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
my $mesg = $ldap->add( "cn=$cn,ou=groups,dc=acu,dc=epita,dc=fr",
|
||
attrs => [
|
||
objectclass => "posixGroup",
|
||
gidNumber => $gid,
|
||
cn => $cn,
|
||
]
|
||
);
|
||
if ($mesg->code != 0) { die $mesg->error; }
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
log(INFO, "group added: $cn");
|
||
}
|
||
|
||
sub cmd_group_delete(@)
|
||
{
|
||
if ($#ARGV != 1)
|
||
{
|
||
log(USAGE, "<lpt> group delete <yaka|acu> <year>");
|
||
exit(1);
|
||
}
|
||
|
||
print "TODO!";
|
||
print "hint: ldapdelete -v -h ldap.acu.epita.fr -x -w \$LDAP_PASSWD -D 'cn=admin,dc=acu,dc=epita,dc=fr' 'cn=yaka2042,ou=groups,dc=acu,dc=epita,dc=fr'";
|
||
exit(1);
|
||
}
|
||
|
||
|
||
######################################
|
||
# #
|
||
# LIST BLOCK #
|
||
# #
|
||
######################################
|
||
|
||
sub cmd_list(@)
|
||
{
|
||
my $subcmd = shift;
|
||
|
||
if (! $subcmd) {
|
||
pod2usage(-verbose => 99,
|
||
-sections => [ 'LIST COMMANDS' ] );
|
||
}
|
||
elsif (! exists $cmds_list{$subcmd}) {
|
||
log(USAGE, "Unknown command for list: ". $subcmd);
|
||
return 1;
|
||
}
|
||
|
||
return $cmds_list{$subcmd}(@_);
|
||
}
|
||
|
||
sub cmd_list_accounts(@)
|
||
{
|
||
if ($#_ > 1)
|
||
{
|
||
log(USAGE, "<lpt> list account [open|close|services]");
|
||
exit(1);
|
||
}
|
||
my $action = shift // "open";
|
||
|
||
my $shellFalse = "/bin/false";
|
||
my $ldap = LDAP::ldap_connect();
|
||
|
||
if ($action eq "open")
|
||
{
|
||
my $mesg = $ldap->search(base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "&(!(loginShell=$shellFalse))(|(objectClass=posixAccount)(objectClass=epitaAccount))",
|
||
attrs => [ 'dn', 'userPassword' ]);
|
||
$mesg->code && die $mesg->error;
|
||
if ($mesg->count == 0) {
|
||
log(WARN, "No account found");
|
||
}
|
||
else {
|
||
for my $entry ($mesg->entries) {
|
||
if (! $entry->get_value("userPassword") or $entry->get_value("userPassword") =~ /^\{[^\}]\}!/) {
|
||
print YELLOW, "Partially closed:\t", RESET;
|
||
} else {
|
||
print CYAN, "Opened:\t", RESET;
|
||
}
|
||
say $entry->dn;
|
||
}
|
||
}
|
||
}
|
||
elsif ($action eq "close")
|
||
{
|
||
my $mesg = $ldap->search(base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "&(loginShell=$shellFalse)(|(objectClass=posixAccount)(objectClass=epitaAccount))",
|
||
attrs => [ 'userPassword' ]);
|
||
$mesg->code && die $mesg->error;
|
||
if ($mesg->count == 0) {
|
||
log(WARN, "No account found");
|
||
}
|
||
else {
|
||
for my $entry ($mesg->entries) {
|
||
if ($entry->get_value("userPassword") =~ /^\{[^\}]\}!/) {
|
||
print YELLOW, "Partially closed:\t", RESET;
|
||
} else {
|
||
print RED, "Closed:\t", RESET;
|
||
}
|
||
say $entry->dn;
|
||
}
|
||
}
|
||
}
|
||
elsif ($action eq "services")
|
||
{
|
||
my $service = shift // "*";
|
||
|
||
my $mesg = $ldap->search(base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "&(labService=$service)(|(objectClass=posixAccount)(objectClass=epitaAccount))",
|
||
attrs => [ 'uid', 'labService' ]);
|
||
$mesg->code && die $mesg->error;
|
||
if ($mesg->count == 0) {
|
||
log(WARN, "No account found!");
|
||
}
|
||
else {
|
||
for my $entry ($mesg->entries) {
|
||
say YELLOW, $entry->get_value("uid"), "\t", RESET, join(", ", $entry->get_value("labService"));
|
||
}
|
||
}
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
return 0;
|
||
}
|
||
|
||
|
||
######################################
|
||
# #
|
||
# QUOTA COMMAND #
|
||
# #
|
||
######################################
|
||
|
||
sub cmd_account_quota($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
my $action = shift;
|
||
|
||
if ($#_ >= 0) {
|
||
cmd_account_quota_set($login, $action, @_);
|
||
}
|
||
elsif ($action eq "sync") {
|
||
cmd_account_quota_sync($login, @_);
|
||
}
|
||
else {
|
||
cmd_account_quota_view($login, @_);
|
||
}
|
||
}
|
||
|
||
sub cmd_account_quota_view($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
my $mesg = $ldap->search(
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => [ 'quotaHomeBlock', 'quotaHomeFile', 'quotaSgoinfreBlock', 'quotaSgoinfreFile' ]
|
||
);
|
||
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
my $nb = 0;
|
||
foreach my $entry ($mesg->entries)
|
||
{
|
||
if ($nb > 0) {
|
||
say "==";
|
||
}
|
||
say BOLD, YELLOW, "dn: ", RESET, YELLOW, $entry->dn, ":", RESET;
|
||
say " - ", BLUE, "Home blocks:\t\t", RESET, ($entry->get_value("quotaHomeBlock") or "(standard)");
|
||
say " - ", BLUE, "Home files:\t\t", RESET, ($entry->get_value("quotaHomeFile") or "(standard)");
|
||
say " - ", BLUE, "Sgoinfre blocks:\t", RESET, ($entry->get_value("quotaSgoinfreBlock") or "(standard)");
|
||
say " - ", BLUE, "Sgoinfre files:\t", RESET, ($entry->get_value("quotaSgoinfreFile") or "(standard)");
|
||
|
||
$nb++;
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
}
|
||
|
||
sub cmd_account_quota_set($@)
|
||
{
|
||
my $login = shift;
|
||
|
||
if ($#_ > 2)
|
||
{
|
||
log(USAGE, "<lpt> account <login> quota <volume> <type> <value>");
|
||
return 1;
|
||
}
|
||
|
||
my $volume = shift;
|
||
my $type = shift;
|
||
my $value = shift;
|
||
|
||
# check args
|
||
if (!($volume eq "home" || $volume eq "sgoinfre")) {
|
||
log(ERROR, "Volume must be home or sgoinfre; given: $volume");
|
||
}
|
||
if (!($type eq "file" || $type eq "block")) {
|
||
log(ERROR, "Type must be file or block; given: $type");
|
||
}
|
||
|
||
# generate quotaName
|
||
my $quotaName = "quota";
|
||
$quotaName .= "Home" if ($volume eq "home");
|
||
$quotaName .= "Sgoinfre" if ($volume eq "sgoinfre");
|
||
$quotaName .= "File" if ($type eq "file");
|
||
$quotaName .= "Block" if ($type eq "block");
|
||
|
||
my $ldap;
|
||
$ldap = LDAP::ldap_connect() if ($value);
|
||
$ldap = LDAP::ldap_connect_anon() if (!$value);
|
||
my $mesg = $ldap->search( # search
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => [ $quotaName ],
|
||
scope => "sub"
|
||
);
|
||
if ($mesg->code != 0) { log(ERROR, $mesg->error); }
|
||
if ($mesg->count != 1) { log(ERROR, "user $login not found or multiple presence"); }
|
||
|
||
my $old_value = $mesg->entry(0)->get_value($quotaName);
|
||
if (!$old_value) {
|
||
$old_value = $def_quota{$type}{$volume};
|
||
}
|
||
|
||
if (!$value) {
|
||
say YELLOW, "dn: ", $mesg->entry(0)->dn, RESET;
|
||
say BLUE, $quotaName, ": ", RESET, $old_value;
|
||
return 0;
|
||
}
|
||
|
||
if ($value =~ '^\+([0-9]+)([MKGTmkgt]?)$') {
|
||
my $t = $1;
|
||
$t *= 1024 if ($2 eq "K" or $2 eq "k");
|
||
$t *= 1048576 if ($2 eq "M" or $2 eq "m");
|
||
$t *= 1073741824 if ($2 eq "G" or $2 eq "g");
|
||
$t *= 1099511627776 if ($2 eq "T" or $2 eq "t");
|
||
$value = $old_value + $t;
|
||
}
|
||
elsif ($value =~ '^-([0-9]+)([MKGTmkgt]?)$') {
|
||
my $t = $1;
|
||
$t *= 1024 if ($2 eq "K" or $2 eq "k");
|
||
$t *= 1048576 if ($2 eq "M" or $2 eq "m");
|
||
$t *= 1073741824 if ($2 eq "G" or $2 eq "g");
|
||
$t *= 1099511627776 if ($2 eq "T" or $2 eq "t");
|
||
$value = $old_value - $t;
|
||
}
|
||
elsif ($value !~ /^[0-9]+$/) {
|
||
log(ERROR, "Value must be an integer or +i or -i");
|
||
}
|
||
|
||
log(INFO, "Changing quota of $quotaName of $login to $value...");
|
||
|
||
$mesg->entry(0)->replace($quotaName => $value) or die $!;
|
||
$mesg->entry(0)->update($ldap) or die $!;
|
||
|
||
$ldap->unbind;
|
||
|
||
log(INFO, "Done!");
|
||
}
|
||
|
||
sub cmd_account_quota_sync($;$)
|
||
{
|
||
my $login = shift;
|
||
my $nosync = shift;
|
||
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
my $mesg = $ldap->search(
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "(&(uid=$login)(objectClass=labAccount))",
|
||
attrs => [ 'uid', 'uidNumber',
|
||
'quotaHomeBlock', 'quotaHomeFile',
|
||
'quotaSgoinfreBlock', 'quotaSgoinfreFile' ]
|
||
);
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count == 1 || log(ERROR, "User $login not found or multiple presence");
|
||
|
||
my $quotaHomeBlock = $mesg->entry(0)->get_value("quotaHomeBlock") // $def_quota{block}{home};
|
||
my $quotaHomeFile = $mesg->entry(0)->get_value("quotaHomeFile") // $def_quota{file}{home};
|
||
my $quotaSgoinfreBlock = $mesg->entry(0)->get_value("quotaSgoinfreBlock") // $def_quota{block}{sgoinfre};
|
||
my $quotaSgoinfreFile = $mesg->entry(0)->get_value("quotaSgoinfreFile") // $def_quota{file}{sgoinfre};
|
||
|
||
if (Quota::setqlim($dev_quota{home}, $mesg->entry(0)->get_value("uidNumber"), int(0.9 * $quotaHomeBlock), $quotaHomeBlock, int(0.9 * $quotaHomeFile), $quotaHomeFile, 1, 0) == 0 and
|
||
Quota::setqlim($dev_quota{sgoinfre}, $mesg->entry(0)->get_value("uidNumber"), int(0.9 * $quotaHomeBlock), $quotaHomeBlock, int(0.9 * $quotaHomeFile), $quotaHomeFile, 1, 0) == 0) {
|
||
log(INFO, $login."'s quota synchronized!");
|
||
}
|
||
else {
|
||
log(ERROR, "An error occurs during quota synchronization:");
|
||
Quota::strerr();
|
||
return 2;
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
if (!$nosync) {
|
||
Quota::sync($dev_quota{home});
|
||
Quota::sync($dev_quota{sgoinfre});
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
sub cmd_sync_quota(@)
|
||
{
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
my $mesg = $ldap->search(
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "(objectClass=labAccount)",
|
||
attrs => [ 'uid' ]
|
||
);
|
||
$mesg->code && die $mesg->error;
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
for my $entry ($mesg->entries) {
|
||
cmd_account_quota_sync($entry->get_value("uid"), 1);
|
||
}
|
||
}
|
||
|
||
|
||
######################################
|
||
# #
|
||
# QUOTA COMMAND #
|
||
# #
|
||
######################################
|
||
|
||
sub get_ssh_keys_unprotected()
|
||
{
|
||
my %keys_unprotected = qw();
|
||
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
my $mesg = $ldap->search(
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "(objectClass=posixAccount)",
|
||
attrs => ['uid','cn', 'homeDirectory']
|
||
);
|
||
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
foreach my $entry ($mesg->sorted('uid'))
|
||
{
|
||
my $home = $entry->get_value("homeDirectory");
|
||
$home =~ s#^$wksHomePrefix#$nfsHomePrefix#;
|
||
my $sshDir = $home . "/.ssh";
|
||
my $login = $entry->get_value("uid");
|
||
|
||
if (-d $sshDir)
|
||
{
|
||
my $process_file = sub() {
|
||
my $file = $_;
|
||
if (-f $file) {
|
||
open my $fh, '<', $file or die $!;
|
||
my @lines = <$fh>;
|
||
close $fh;
|
||
if ( grep { chomp; $_ =~ /PRIVATE KEY/ } @lines )
|
||
{
|
||
if (! grep { chomp; $_ =~ /ENCRYPTED/ } @lines )
|
||
{
|
||
if (!exists $keys_unprotected{$login})
|
||
{
|
||
$keys_unprotected{$login} = [$file];
|
||
}
|
||
else
|
||
{
|
||
push(@{$keys_unprotected{$login}}, $file);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
find({ wanted => \&$process_file, no_chdir => 1 }, $sshDir);
|
||
}
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
|
||
return %keys_unprotected;
|
||
}
|
||
|
||
sub cmd_ssh_keys_without_passphrase_generic(@)
|
||
{
|
||
my $func = shift;
|
||
|
||
my %keys_unprotected = get_ssh_keys_unprotected();
|
||
my $ldap = LDAP::ldap_connect_anon();
|
||
|
||
foreach my $login (keys %keys_unprotected)
|
||
{
|
||
my $mesg = $ldap->search(
|
||
base => "ou=users,dc=acu,dc=epita,dc=fr",
|
||
filter => "uid=$login",
|
||
attrs => [ 'uid', 'cn', 'mailAlias' ]
|
||
);
|
||
|
||
$mesg->code && die $mesg->error;
|
||
$mesg->count > 0 || return -1;
|
||
|
||
my $entry = $mesg->entry(0);
|
||
|
||
# Apply func
|
||
&$func($entry, \@{$keys_unprotected{$login}});
|
||
}
|
||
|
||
$ldap->unbind or die ("couldn't disconnect correctly");
|
||
}
|
||
|
||
# list unprotected keys
|
||
sub cmd_ssh_keys_without_passphrase_show(@)
|
||
{
|
||
my $process = sub() {
|
||
my $entry = shift;
|
||
my $keys = shift;
|
||
|
||
# Display
|
||
print $entry->get_value("cn").":\n";
|
||
foreach my $key (@$keys)
|
||
{
|
||
print " * $key\n";
|
||
}
|
||
print "\n";
|
||
};
|
||
|
||
cmd_ssh_keys_without_passphrase_generic(\&$process);
|
||
}
|
||
|
||
# warn about unprotected keys
|
||
sub cmd_ssh_keys_without_passphrase_warn(@)
|
||
{
|
||
my $process = sub() {
|
||
my $entry = shift;
|
||
my $keys = shift;
|
||
|
||
# Display
|
||
print $entry->get_value("uid")."\n";
|
||
|
||
# create the message
|
||
use Email::MIME;
|
||
my $body = "Bonjour ".$entry->get_value("cn").",
|
||
|
||
Un outil automatique a d<>couvert une cl<63> sans passphrase sur votre compte
|
||
du laboratoire. Il est imp<6D>ratif de mettre une passphrase chiffrant votre
|
||
cl<EFBFBD> pour des raisons de s<>curit<69>.
|
||
|
||
Les clefs non prot<6F>g<EFBFBD>es sont les suivantes :\n";
|
||
foreach my $key (@$keys)
|
||
{
|
||
$key =~ s#^$nfsHomePrefix#$wksHomePrefix#;
|
||
$body .= " - $key\n";
|
||
}
|
||
$body .= "\nPour mettre une passphrase :
|
||
\$ ssh-keygen -p -f CHEMIN_VERS_LA_CLE_PRIVEE
|
||
|
||
Merci de rectifier la situation au plus vite ou votre cl<63> sera supprim<69>e et
|
||
votre compte sera mis en suspens.
|
||
|
||
Cordialement,
|
||
|
||
PS: Ce message est g<>n<EFBFBD>r<EFBFBD> automatiquement, les roots sont en copie.
|
||
Pour toute demande, merci de faire un ticket <20> admin\@acu.epita.fr
|
||
|
||
--
|
||
Les roots ACU";
|
||
my $message = Email::MIME->create(
|
||
header_str => [
|
||
From => 'root@acu.epita.fr',
|
||
To => $entry->get_value("mailAlias"),
|
||
Cc => 'root@acu.epita.fr',
|
||
Subject => '[LAB][SSH-PASSPHRASE] Clef SSH non prot<EFBFBD>g<EFBFBD>e',
|
||
],
|
||
attributes => {
|
||
encoding => 'quoted-printable',
|
||
charset => 'UTF-8',
|
||
},
|
||
body_str => $body,
|
||
);
|
||
|
||
# send the message
|
||
use Email::Sender::Simple qw(sendmail);
|
||
sendmail($message);
|
||
};
|
||
|
||
cmd_ssh_keys_without_passphrase_generic(\&$process);
|
||
}
|
||
|
||
# remove unprotected keys
|
||
sub cmd_ssh_keys_without_passphrase_remove(@)
|
||
{
|
||
my $process = sub() {
|
||
my $entry = shift;
|
||
my $keys = shift;
|
||
|
||
# Display
|
||
print $entry->get_value("uid")."\n";
|
||
|
||
# create the message
|
||
use Email::MIME;
|
||
my $body = "Bonjour ".$entry->get_value("cn").",
|
||
|
||
Un outil automatique a d<>couvert une clef sans passphrase sur votre
|
||
compte du laboratoire.
|
||
|
||
N'ayant pas corrig<EFBFBD> votre situation apr<EFBFBD>s plusieurs relances, nous avons
|
||
d<EFBFBD>sactiv<EFBFBD> votre compte et supprim<EFBFBD> le(s) clef(s) incrimin<EFBFBD>es.
|
||
|
||
Pour information, voici l'empreinte de chacune des clefs supprim<69>e :\n";
|
||
foreach my $key (@$keys)
|
||
{
|
||
open (FNGR, "ssh-keygen -l -f '$key' | cut -d ' ' -f 2");
|
||
my $fingerprint = <FNGR>;
|
||
chomp $fingerprint;
|
||
close (FNGR);
|
||
|
||
unlink($key);
|
||
|
||
$key =~ s#^$nfsHomePrefix#$wksHomePrefix#;
|
||
$body .= " * $key: $fingerprint\n";
|
||
}
|
||
$body .= "\n
|
||
Contacter les roots pour faire reouvrir votre compte.
|
||
|
||
Cordialement,
|
||
|
||
PS: Ce message est g<>n<EFBFBD>r<EFBFBD> automatiquement, les roots sont en copie.
|
||
Pour toute demande, merci de faire un ticket <20> admin\@acu.epita.fr
|
||
|
||
--
|
||
Les roots ACU";
|
||
my $message = Email::MIME->create(
|
||
header_str => [
|
||
From => 'root@acu.epita.fr',
|
||
To => $entry->get_value("aliasmail"),
|
||
Cc => 'root@acu.epita.fr',
|
||
Subject => '[LAB][SSH-PASSPHRASE] Cl<EFBFBD> SSH non prot<EFBFBD>g<EFBFBD>e supprim<EFBFBD>e',
|
||
],
|
||
attributes => {
|
||
encoding => 'quoted-printable',
|
||
charset => 'UTF-8',
|
||
},
|
||
body_str => $body,
|
||
);
|
||
|
||
# send the message
|
||
use Email::Sender::Simple qw(sendmail);
|
||
sendmail($message);
|
||
};
|
||
|
||
cmd_ssh_keys_without_passphrase_generic(\&$process);
|
||
}
|
||
|
||
|
||
######################################
|
||
# #
|
||
# MAIN CORE #
|
||
# #
|
||
######################################
|
||
|
||
sub cmd_help
|
||
{
|
||
pod2usage(-exitval => 1, -verbose => 2);
|
||
}
|
||
|
||
if ($#ARGV == -1) {
|
||
cmd_help();
|
||
exit(1);
|
||
}
|
||
|
||
my $cmd = shift;
|
||
|
||
if ($cmd eq "-v" or $cmd eq "--verbose" or $cmd eq "--debug") {
|
||
$ACU::Log::display_level = 8;
|
||
$cmd = shift;
|
||
}
|
||
elsif ($cmd eq "-q" or $cmd eq "--quiet") {
|
||
$ACU::Log::display_level = 6;
|
||
$cmd = shift;
|
||
}
|
||
|
||
$ACU::Log::fatal_error = 1;
|
||
$ACU::Log::fatal_warn = 0;
|
||
|
||
if (! exists $cmds{$cmd})
|
||
{
|
||
say BOLD, "Usage: ", RESET, "$0 ", GREEN, "command", RESET, " <arguments>";
|
||
log(ERROR, "Uknown command : $cmd");
|
||
}
|
||
|
||
exit ($cmds{$cmd}(@ARGV));
|
||
|
||
__END__
|
||
=head1 NAME
|
||
|
||
lpt - Lab Power Tool
|
||
|
||
=head1 SYNOPSIS
|
||
|
||
B<lpt> I<command> [arguments]
|
||
|
||
I<command> can be:
|
||
|
||
B<lpt> I<account> <login> [arguments]
|
||
|
||
Manage the account <login>.
|
||
|
||
B<lpt> I<group> <group-name> [arguments]
|
||
|
||
Manage the group <group-name>
|
||
|
||
B<lpt> I<help>
|
||
|
||
Display this screen.
|
||
|
||
B<lpt> I<year> [year]
|
||
|
||
Set or display the current year.
|
||
|
||
|
||
=head1 ACCOUNT COMMANDS
|
||
|
||
B<lpt account> <login> [I<view>]
|
||
|
||
Display information about <login>.
|
||
|
||
<login> can be a globbing string.
|
||
|
||
B<lpt account> <login> I<create> <promo> <uid> <Prenom> <Nom> [nopass|password|passgen]
|
||
|
||
This is used to create a new Epita account, base for intra and/or lab account.
|
||
|
||
Promo for professor are professors, other people are guests.
|
||
|
||
B<lpt account> <login> I<nopass>
|
||
|
||
This is used to erase the userPassword.
|
||
|
||
B<lpt account> <login> I<close>
|
||
|
||
This is used to close an existing account.
|
||
|
||
B<lpt account> <login> I<reopen>
|
||
|
||
This is used to reopen a previously closed account.
|
||
|
||
B<lpt account> <login> I<shell> <shell path>
|
||
|
||
This is used to change default shell for an existing accout.
|
||
|
||
B<lpt account> <login> I<passgen> [nb_char]
|
||
|
||
This is used to set user password. Generated by pwgen.
|
||
|
||
nb_char must be at least egal to 10.
|
||
|
||
B<lpt account> <login> I<password> [password]
|
||
|
||
This is used to set user password. Interactively asked if not given.
|
||
|
||
B<lpt account> I<mail> <login> [new]
|
||
|
||
This is used to get user email (to which are forwarded his emails) if
|
||
'new' is empty, and to change it if 'new' is given.
|
||
|
||
B<lpt account> I<list> <open | close | service>
|
||
|
||
List accounts: with access to the PILA, without, with access to
|
||
services.
|
||
|
||
B<lpt account> I<finger> <login>
|
||
|
||
Display information about a login.
|
||
|
||
B<lpt account> I<service_flush> <login>
|
||
|
||
Remove all services associated to a login.
|
||
|
||
|
||
=head1 GROUP COMMANDS
|
||
|
||
B<lpt group> I<list> [group]
|
||
|
||
This is used to list groups available on the PIL or to list the members
|
||
of the specified group.
|
||
|
||
B<lpt group> I<add> <group> <login>
|
||
|
||
This is used to add a user to a posix group.
|
||
|
||
B<lpt group> I<create> <yaka | acu> <year>
|
||
|
||
This is used to create a posix group.
|
||
|
||
B<lpt group> I<remove> <group> <login>
|
||
|
||
This is used to remove a user from a posix group.
|
||
|
||
B<lpt group> I<delete> <yaka | acu> <year>
|
||
|
||
This is used to delete a posix group.
|
||
|
||
|
||
=head1 QUOTA COMMANDS
|
||
|
||
B<lpt quota> I<show> <login>
|
||
|
||
Display the quota of everyone or someone.
|
||
|
||
B<lpt quota> I<set> <login> <volume> <type> <value>
|
||
|
||
Set the quota of someone. Volume is home/sgoinfre and type is
|
||
block/file.
|
||
|
||
=head1 SERVICE COMMANDS
|
||
|
||
B<lpt service> I<add> <login> <name>
|
||
|
||
This is used to add a service to a user.
|
||
|
||
B<lpt service> I<remove> <login> <name>
|
||
|
||
This is used to remove a service from a user.
|
||
|
||
|
||
=head1 SSH_KEYS_WITHOUT_PASSPHRASE COMMANDS
|
||
|
||
B<lpt ssh> I<keys-without-passphrase> <show | warn | remove>
|
||
|
||
Search for users with SSH keys without passphrase. Warn the users and
|
||
remove them if requested.
|
||
|
||
|
||
=head1 DESCRIPTION
|
||
|
||
B<lpt> is a tool developed to replace ancient perl scripts used to manage
|
||
accounts, and some other stuff.
|
||
The goal was to give an unique tool with meaningful commands to perform
|
||
usual operations. lpt is born from ipt.
|
||
|
||
=head1 AUTHORS
|
||
|
||
Project started by : Adnan Aita <I<ski@epita.fr>>, root@acu 2006
|
||
|
||
Modified by Laroche Emeric <I<laroch_e@epita.fr>>, root@acu 2007
|
||
|
||
Modified by Sterckeman Julien <I<sterck_j@epita.fr>>, root@acu 2008
|
||
|
||
Modified by Sebastien Luttringer <I<seblu@epita.fr>>, root@acu 2008
|
||
|
||
Modified by Vincent Nguyen <I<nguyen_v@epita.fr>>, root@acu 2010
|
||
|
||
Modified by JB et Antoine <I<root@acu.epita.fr>>, root@acu 2012
|
||
|
||
Modified by megra <I<j@marguerie.org>>, root@acu 2013 : added tons of features :)
|
||
|
||
Strongly modified by nemunaire & nicolas, root@acu 2014
|
||
|
||
=head1 VERSION
|
||
|
||
This is B<lpt> version 1.1.
|
||
|
||
=head1 TODO
|
||
|
||
Tons of stuff :
|
||
* delete account
|
||
* group delete
|
||
* ...
|
||
|
||
=head1 BUGS
|
||
|
||
No bug, just features.
|
||
|
||
=cut
|