#!/usr/bin/perl -w #Copyright (c) 2000 # Randy Smith # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer as # the first lines of this file unmodified. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY Randy Smith ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL Randy Smith BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =head1 NAME rodopi-event-handler.pl - Rodopi Event Handler =head1 Introduction Handle the events from Rodopi. Rodopi is going to upload a file to the admin server. This script will then go read all of those files and do what it needs to, for each one. The data files will be uploaded to a seperate directory depending on what the purpose of the file is. Rodopi takes care of making sure that the file names are unique in the following way. =over 4 =item 1 Downloads a template file via FTP. (For this example, I'll call it F) =item 2 Rodopi then replaces various fields with the appropriate and then uploads the new file to the B directory with a unique name. The file will be named something like F. =back This script will go read those files and make the chages to the mail server. The data files are deleted when the changes are completed I. If there was a problem with the data, the program will email them to the appropriate person and will I delete the file. =cut ######################################## # # Configuration Section # ######################################## # Turn on debugging. my $debug = 0; # Data Directories and file name # Rodopi deals with five events. The files for these events may be in # different directories and have different file names. # What is the base directory for the event directories? # $base_dir is provided for convenience. It does not have to be used. # It is here to allow the other directories to be based off of this one. my $base_dir = '/home/events'; # Active file. # This tells the script if it should be making updates. # If the file does not exist, then the update files will be deleted. my $active_file = "$base_dir/active"; # What is the field delimiter? # This is the character (or regex) that delimits the fields in the data files. # If you want to use different a delimiter for an event, you will need to modify # the %events hash below. my $delimiter = '\|'; # The \ is needed to escape the | since | means stuff in a regex. my $vuser = '/usr/local/sbin/vuser'; my $ruser = '/usr/local/sbin/ruser'; ######################################## # # Code Stuff # ######################################## use strict; # Forward references to subs used in the hash described below; # Domain Events sub DomainInserted (\%); # Handles the Domain inserted event sub DomainDeleted (\%); # Handles the Domain deleted event sub DomainUpdated (\%); # Handles the Domain updated event sub DomainSuspended (\%); # Handles the Domain suspended event sub DomainReleased (\%); # Handles the Domain released event # Email Events sub EmailInserted (\%); # Handles the email inserted event sub EmailDeleted (\%); # Handles the email deleted event sub EmailUpdated (\%); # Handles the email updated event sub EmailSuspended (\%); # Handles the email suspended event sub EmailReleased (\%); # Handles the email released event # List Events sub ListInserted (\%); # Handles the list inserted event sub ListDeleted (\%); # Handles the list deleted event sub ListUpdated (\%); # Handles the list updated event sub ListSuspended (\%); # Handles the list suspended event sub ListReleased (\%); # Handles the list released event # Dial-Up Events sub DialupInserted (\%); # Handles the dial-up inserted event sub DialupDeleted (\%); # Handles the dial-up deleted event sub DialupUpdated (\%); # Handles the dial-up updated event sub DialupSuspended (\%); # Handles the dial-up suspended event sub DialupReleased (\%); # Handles the dial-up released event # Other Events sub PushUsers (\%); # Handle the radius users file upload # Other functions. See the actual definitions for more detailed info. sub ErrorHandler ($); # The error handler sub GetFiles ($$); # Gets the list of data files for the event handlers =head1 The %events Hash I<%events> is a list of information for each event. The keys of this hash should be the event name and the value is a hash reference with the following keys: =over 4 =item dir The directory the data files are stored in. =item delimiter The field delimiter in the flat files =item file_prefix the prefix for the datafiles (ex. F) Remember Rodopi will use this as the base for the datafile names as file_prefix_uniqueNumber =item fields This is a reference to an array of the fields in the data file. The fields should be in the same order as the in the file. =item handler This is a reference to the function that deals with this event. The function should be defined before it is put into the hash. The subs will take a hash reference as the only parameter. That reference is the value for this event key in I<%events>. i.e. C<$events{'eventname'}> =back =cut my %events = ('DomainInserted' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'domain_inserted.dat', 'fields' => ['New_DomainName', 'New_DomainOrg', 'New_DomainDescription', 'New_Status'], 'handler' => \&DomainInserted}, 'DomainDeleted' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'domain_deleted.dat', 'fields' => ['Old_DomainName', 'Old_DomainOrg', 'Old_DomainDescription', 'Old_Status'], 'handler' => \&DomainDeleted}, 'DomainUpdated' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'domain_updated.dat', 'fields' => ['Old_DomainName', 'Old_DomainOrg', 'Old_DomainDescription', 'Old_Status', 'New_DomainName', 'New_DomainOrg', 'New_DomainDescription', 'New_Status'], 'handler' => \&DomainUpdated}, 'DomainSuspended' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'domain_suspended.dat', 'fields' => ['Old_DomainName', 'Old_DomainOrg', 'Old_DomainDescription', 'Old_Status'], 'handler' => \&DomainSuspended}, 'DomainReleased' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'domain_released.dat', 'fields' => ['Old_DomainName', 'Old_DomainOrg', 'Old_DomainDescription', 'Old_Status'], 'handler' => \&DomainReleased}, 'EmailInserted' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'email_inserted.dat', 'fields' => ['New_EmailAddress', 'New_Password', 'New_RealName', 'New_PopName'], 'handler' => \&EmailInserted }, 'EmailDeleted' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'email_deleted.dat', 'fields' => ['Old_EmailAddress', 'Old_Password', 'Old_RealName', 'Old_PopName'], 'handler' => \&EmailDeleted}, 'EmailUpdated' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'email_updated.dat', 'fields' => ['New_EmailAddress', 'New_Password', 'New_RealName', 'New_PopName', 'Old_EmailAddress', 'Old_Password', 'Old_RealName', 'Old_PopName'], 'handler' => \&EmailUpdated}, 'EmailSuspended' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'email_suspended.dat', 'fields' => ['Old_EmailAddress', 'Old_Password', 'Old_RealName', 'Old_PopName'], 'handler' => \&EmailSuspended}, 'EmailReleased' => {'dir' => "$base_dir", 'delimiter' => "$delimiter", 'file_prefix' => 'email_released.dat', 'fields' => ['Old_EmailAddress', 'Old_Password', 'Old_RealName', 'Old_PopName'], 'handler' => \&EmailReleased}, 'ListInserted' => {'dir' => $base_dir, 'delimiter' => $delimiter, 'file_prefix' => 'list_inserted.dat', 'fields' => ['New_list_name', 'New_EmailAddress', 'New_list_manager', 'New_Password'], handler => \&ListInserted,}, 'ListDeleted' => {'dir' => $base_dir, 'delimiter' => $delimiter, 'file_prefix' => 'list_deleted.dat', 'fields' => ['Old_list_name', 'Old_EmailAddress', 'Old_list_manager', 'Old_Password'], handler => \&ListDeleted}, 'ListUpdated' => {'dir' => $base_dir, 'delimiter' => $delimiter, 'file_prefix' => 'list_updated.dat', 'fields' => ['New_list_name', 'New_EmailAddress', 'New_list_manager', 'New_Password', 'Old_list_name', 'Old_EmailAddress', 'Old_list_manager', 'Old_Password'], handler => \&ListUpdated}, 'DialupInserted' => {'dir' => $base_dir, 'delimiter' => $delimiter, 'file_prefix' => 'dialup_inserted.dat', 'fields' => ['New_UserName', 'New_Password'], handler => \&DialupInserted}, 'DialupDeleted' => {dir => $base_dir, delimiter => $delimiter, file_prefix => 'dialup_deleted.dat', fields => ['Old_UserName', 'Old_Password'], handler => \&DialupDeleted}, 'DialupUpdated' => {dir => $base_dir, delimiter => $delimiter, file_prefix => 'dialup_updated.dat', fields => ['New_UserName', 'New_Password', 'Old_UserName', 'Old_Password'], handler => \&DialupUpdated}, 'DialupSuspended' => {dir => $base_dir, delimiter => $delimiter, file_prefix => 'dialup_suspended.dat', fields => ['Old_UserName', 'Old_Password'], handler => \&DialupSuspended}, 'DialupReleased' => {dir => $base_dir, delimiter => $delimiter, file_prefix => 'dialup_released.dat', fields => ['Old_UserName', 'Old_Password'], handler => \&DialupReleased}, 'PushUsers' => {'dir' => '/usr/home/rodopi/radius', 'delimiter' => '', 'file_prefix' => 'users', 'fields' => [], 'handler' => \&PushUsers} ); # This is the list of events that we are actually going to handle. The event names # are the keys of %events. my @handled_events = qw(DomainInserted DomainUpdated EmailDeleted EmailInserted EmailUpdated EmailSuspended EmailReleased DomainDeleted ListInserted ListDeleted ListUpdated DialupDeleted DialupInserted DialupUpdated DialupSuspended DialupReleased ); # Do everything needed for each event. # Basically, we're going to call the handler method and let it do # all of the work. Any special modules needed in the handlers # should be 'use'd there. # Use die to get out of a handler on a critical error. Otherwise # just call ErrorHandler from the event handler. foreach my $event (@handled_events) { if (-e $active_file) { # Call the event handler with the hash reference for this event eval { &{$events{$event}{'handler'}}($events{$event}); }; ErrorHandler "$@: $event" if ($@); } else { #`rm $events{$event}{'file_prefix'}*`; ErrorHandler "Not active"; } } =head1 Event Handler Functions These are the functions that do all of the work. The fuctions all take a hash reference as their only parameter. That hash ref is the value from I<%events> described earlier. These functions should be modified to do whatever you need to make your system work. =head2 EmailInserted (\%) Handles the Email Inserted event. This function should be modified to work with your mail server and your specific needs. =cut sub EmailInserted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } $fields{New_RealName} = "''" if not $fields{New_RealName} or $fields{New_RealName} =~ /^\s*$/; # $fields{New_RealName} =~ s/[^\\]'/\\'/; # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($vuser, 'add', $fields{New_EmailAddress}, '-n', "\"$fields{New_RealName}\"", '-p', "'$fields{New_Password}'" ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("EmailInserted ($file): Can't add account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 EmailDeleted (\%) Handles the Email Deleted event. This function should be modified to work with your mail server and your specific needs. =cut sub EmailDeleted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailDeleted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->'fields' better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($vuser, 'del', $fields{Old_EmailAddress}); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("EmailDeleted ($file): Can't del account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 EmailUpdated (\%) Handles the Email Updated event. This function should be modified to work with your mail server and your specific needs. =cut sub EmailUpdated (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailUpdated: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->'fields' better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } $fields{New_RealName} = "''" if not $fields{New_RealName} or $fields{New_RealName} =~ /^\s*$/; my @cmd = ($vuser, 'mod', $fields{Old_EmailAddress}, '-n', "\"$fields{New_RealName}\"", '-p', "'$fields{New_Password}'" ); if ($fields{Old_EmailAddress} ne $fields{New_EmailAddress}) { push @cmd, ('-a', $fields{New_EmailAddress}); } if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("EmailUpdated ($file): Can't mod account: $output"); rename $file, "error_$file"; } } # OK. Now we have the data out of the file. Let's update the server. # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 EmailSuspended (\%) Handles the Email Suspended event. This function should be modified to work with your mail server and your specific needs. =cut sub EmailSuspended (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailSuspended: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->'fields' better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. # To disable the account, we'll change the password. my @cmd = ($vuser, 'mod', $fields{Old_EmailAddress}, '-p', generate_password(10) ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("EmailSuspended ($file): Can't suspend account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 EmailReleased (\%) Handles the Email Released event. This function should be modified to work with your mail server and your specific needs. =cut sub EmailReleased (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailReleased: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->'fields' better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. # To enable the account, we'll reset the password. my @cmd = ($vuser, 'mod', $fields{Old_EmailAddress}, '-p', "'$fields{Old_Password}'" ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("EmailReleased ($file): Can't release account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DomainInserted (\%) Handles the Domain Inserted event. This function should be modified to work with your mail server and your specific needs. =cut sub DomainInserted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DomainInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the servers. # We need to add this domain to the mail server so we can add email # accounts to it. my @cmd = ($vuser, 'domain', 'add', $fields{New_DomainName}); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DomainInserted ($file): Can't add domain: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DomainUpdated (\%) Handles the Domain Updated event. This function should be modified to work with your mail server and your specific needs. =cut sub DomainUpdated (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DomainInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the servers. # We need to add this domain to the mail server so we can add email # accounts to it. my @cmd = ($vuser, 'domain', 'mod', $fields{Old_DomainName}); if ($fields{Old_DomainName} ne $fields{New_DomainName}) { push @cmd, '-d', $fields{New_DomainName}; push @cmd, '-r'; } if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DomainUpdated ($file): Can't update domain: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DomainDeleted (\%) Handles the Domain Deleted event. This function should be modified to work with your mail server and your specific needs. =cut sub DomainDeleted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DomainDeleted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the servers. my @cmd = ($vuser, 'domain', 'del', $fields{New_DomainName}); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DomainInserted ($file): Can't add domain: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 ListInserted (\%) Handles the List Inserted event. This function should be modified to work with your mail server and your specific needs. =cut sub ListInserted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($vuser, 'list', 'add', $fields{New_list_name}, $fields{New_EmailAddress}, $fields{New_list_manager}); push (@cmd, ('-p', "'$fields{New_Password}'")) if $fields{New_Password}; if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("ListInserted ($file): Can't add list: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 ListDeleted (\%) Handles the List Inserted event. This function should be modified to work with your mail server and your specific needs. =cut sub ListDeleted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($vuser, 'list', 'del', $fields{Old_list_name}, $fields{Old_EmailAddress}, '-r' ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("ListDeleted ($file): Can't delete list: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 ListUpdated (\%) Handles the List Updated event. This function should be modified to work with your mail server and your specific needs. =cut sub ListUpdated (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "EmailInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($vuser, 'list', 'mod', $fields{Old_list_name}, $fields{Old_EmailAddress} ); if ($fields{New_EmailAddress} ne $fields{Old_EmailAddress}) { push (@cmd, '-a', $fields{New_EmailAddress}); } if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("ListDeleted ($file): Can't update list: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DialupInserted (\%) Handles the Dial-up Inserted event. This function should be modified to work with your radius server and your specific needs. =cut sub DialupInserted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DialupInserted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($ruser, 'add', $fields{New_UserName}, '-p', "'$fields{New_Password}'" ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DialupInserted ($file): Can't add account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DialupDeleted (\%) Handles the Dial-up Deleted event. This function should be modified to work with your radius server and your specific needs. =cut sub DialupDeleted (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DialupDeleted: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($ruser, 'del', $fields{Old_UserName} ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DialupDeleted ($file): Can't delete account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DialupUpdated (\%) Handles the DialupUpdated event. This function should be modified to work with your radius server and your specific needs. =cut sub DialupUpdated (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DialupUpdated: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($ruser, 'mod', $fields{Old_UserName}, '-p', "'$fields{New_Password}'" ); push (@cmd, '-u', $fields{New_UserName}) if $fields{Old_UserName} ne $fields{New_UserName}; if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DialupUpdated ($file): Can't update account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DialupSuspended (\%) Handles the Email Inserted event. This function should be modified to work with your mail server and your specific needs. =cut sub DialupSuspended (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DialupSuspended: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($ruser, 'mod', $fields{Old_UserName}, '-a', 'n' ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DialupSuspended ($file): Can't suspend account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 DialupReleased (\%) Handles the Email Inserted event. This function should be modified to work with your mail server and your specific needs. =cut sub DialupReleased (\%) { my $props = shift; # get the properties for this event my @dataFiles = GetFiles $props->{'dir'}, $props->{'file_prefix'}; # Let's get out of here if there's nothing to do. return if scalar @dataFiles == 0; FILE: foreach my $file (@dataFiles) { unless (open FILE, $file) { ErrorHandler "DialupReleased: Unable to open file: $!"; next FILE; } # This works because Rodopi uses one file per change and # we've defined each file to have only one line. # However, @data and $props->{'fields'} better have the # same number of elements. my @data = split ($props->{'delimiter'}, ); chomp @data; close FILE; my %fields = (); foreach my $i (0 .. $#data) { $fields{$props->{'fields'}[$i]} = $data[$i]; } # OK. Now we have the data out of the file. Let's update the server. my @cmd = ($ruser, 'mod', $fields{Old_UserName}, '-a', 'y' ); if ($debug) { print "@cmd\n"; } else { my $output = `@cmd 2>&1`; my $rc = (0xffff & $?) >> 8; if ($rc != 0) { ErrorHandler ("DialupReleased ($file): Can't release account: $output"); rename $file, "error_$file"; } } # If we're still here, then everything worked. Let's remove the file unlink $file; } } =head2 PushUsers (\%) Handles the F file upload. This function should be modified to work with your server and your specific needs. =cut sub PushUsers (\%) { my $props = shift; my $usersFile = "$props->{'dir'}/$props->{'file_prefix'}"; return unless -e $usersFile; # don't go on if there is no users file #use File::Copy; #copy $usersFile, '/etc/raddb/users'; # copy the file # delete the backup users files). `rm $props->{'dir'}/users_*`; } =head1 Other Functions These functions provide basic functionality that I needed in all of the event handlers. =head2 ErrorHandler ($) ErrorHandler SCALAR This is called if one of the event handlers has issues. ErrorHandler takes the error string as it only parameter. You can have it do whatever you need it to. I choose to have it email me when there's and error. =cut sub ErrorHandler ($) { use Mail::Sendmail; # An email module my $err = shift; # Who are we going to send errors to? my $error_rcpt = 'randys@amigo.net'; # What is the name of the server to use to send mail? my $smtp_server = 'smtp.amigo.net'; my %mail = ('smtp' => $smtp_server, 'To' => $error_rcpt, 'From' => 'Event Handler ', 'Subject' => 'Error in Rodopi Event Handler', 'Message' => "$err" ); print STDERR "$err\n"; sendmail (%mail) if not $debug; 1; } =head2 GetFiles ($$) GetFiles SCALAR, SCALAR The first scalar is the directory name and the second is the file prefix. =cut sub GetFiles ($$) { my $dir = shift; my $prefix = shift; (opendir (DIR, "$dir") and chdir $dir) or die "Unable to open directory $dir: $!"; # Read all of the files that match the file prefix except that file my @dataFiles = (); #my @dataFiles = grep /^$prefix\_/, readdir DIR; #or die "Unable to read directory $dir: $!"; while (my $file = readdir DIR) { next if $file !~ /^$prefix\_/; # get only prefix_ files unlink "$dir/$file" if -z "$dir/$file"; # delete 0 length files push (@dataFiles, $file); } closedir DIR; return @dataFiles; } sub generate_password { my $len = shift || 10; my @valid = (0..9, 'a'..'z', 'A'..'Z', '@', '#', '%', '^', '*'); my $password = ''; for (1 .. $len) { $password .= $valid[int (rand $#valid)]; } return $password; } =head1 Licensing Information Copyright (c) 2000 Randy Smith, Colorado 81101. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY Randy Smith ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Randy Smith BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =head1 Author Randy Smith =cut