diff --git a/opentrafficshaper/plugins/configmanager.pm b/opentrafficshaper/plugins/configmanager.pm index dc0454a3f19702b3839bd0a86f35235a0c9bdd77..eb92e761376c7f82d439b112144ead918423753f 100644 --- a/opentrafficshaper/plugins/configmanager.pm +++ b/opentrafficshaper/plugins/configmanager.pm @@ -1,6 +1,6 @@ # OpenTrafficShaper configuration manager # Copyright (C) 2007-2013, AllWorldIT -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -59,19 +59,57 @@ use constant { # How often our config check ticks TICK_PERIOD => 5, + }; +# Mandatory config attributes +sub CONFIG_ATTRIBUTES { + qw( + Username IP + GroupID ClassID + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + Expires Status + Source + ) +} + +# Changeset attributes - things that can be changed on the fly +sub CHANGESET_ATTRIBUTES { + qw( + GroupID ClassID + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + Expires + ) +} + +# Persistent attributes supported +sub PERSISTENT_ATTRIBUTES { + qw( + Username IP + GroupID ClassID + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + Expires Created + Source + ) +} + +# Override attributes supported +sub OVERRIDE_ATTRIBUTES { + qw( + Username IP + GroupID ClassID + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + ) +} + # Plugin info our $pluginInfo = { Name => "Config Manager", Version => VERSION, - + Init => \&plugin_init, Start => \&plugin_start, - - # Signals - signal_SIGHUP => \&handle_SIGHUP, }; @@ -79,30 +117,35 @@ our $pluginInfo = { my $globals; my $logger; -# Our own config stuff -my $groups = { - 1 => 'Default' -}; -my $classes = { - 1 => 'Default' +# Configuration for this plugin +my $config = { + # Use default pool for unclassified traffic + 'use_default_pool' => 0, + 'default_pool_txrate' => undef, + 'default_pool_rxrate' => undef, + 'default_pool_priority' => 10, + # Traffic groups + 'groups' => { + 1 => 'Default' + }, + # Traffic classes + 'classes' => { + 1 => 'Default' + }, + # State file + 'statefile' => '/var/lib/opentrafficshaper/configmanager.state', }; -# TODO: move to $config -# Use default pool for unclassified traffic -my $use_default_pool = 0; -my $default_pool_txrate; -my $default_pool_rxrate; -my $default_pool_priority = 10; - # Pending changes my $changeQueue = { }; -# Limits + +# Main variables handling our limits my $limits = { }; my $limitIPMap = { }; my $limitIDMap = { }; my $limitIDCounter = 1; - +my $overrides = { }; # Initialize plugin @@ -119,12 +162,12 @@ sub plugin_init # Split off groups to load $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading traffic groups..."); # Check if we loaded an array or just text - my @groups = ref($globals->{'file.config'}->{'shaping'}->{'group'}) eq "ARRAY" ? @{$globals->{'file.config'}->{'shaping'}->{'group'}} : + my @groups = ref($globals->{'file.config'}->{'shaping'}->{'group'}) eq "ARRAY" ? @{$globals->{'file.config'}->{'shaping'}->{'group'}} : ( $globals->{'file.config'}->{'shaping'}->{'group'} ); # Loop with groups foreach my $group (@groups) { - # Skip comments - next if ($group =~ /^\s*#/); + # Skip comments + next if ($group =~ /^\s*#/); # Split off group ID and group name my ($groupID,$groupName) = split(/:/,$group); if (!defined($groupID) || int($groupID) < 1) { @@ -135,7 +178,7 @@ sub plugin_init $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load traffic group definition '$group': Name is invalid"); next; } - $groups->{$groupID} = $groupName; + $config->{'groups'}->{$groupID} = $groupName; $logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic group '$groupName' with ID $groupID."); } $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading traffic groups completed."); @@ -143,12 +186,12 @@ sub plugin_init # Split off traffic classes $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading traffic classes..."); # Check if we loaded an array or just text - my @classes = ref($globals->{'file.config'}->{'shaping'}->{'class'}) eq "ARRAY" ? @{$globals->{'file.config'}->{'shaping'}->{'class'}} : + my @classes = ref($globals->{'file.config'}->{'shaping'}->{'class'}) eq "ARRAY" ? @{$globals->{'file.config'}->{'shaping'}->{'class'}} : ( $globals->{'file.config'}->{'shaping'}->{'class'} ); # Loop with classes foreach my $class (@classes) { - # Skip comments - next if ($class =~ /^\s*#/); + # Skip comments + next if ($class =~ /^\s*#/); # Split off class ID and class name my ($classID,$className) = split(/:/,$class); if (!defined($classID) || int($classID) < 1) { @@ -159,7 +202,7 @@ sub plugin_init $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load traffic class definition '$class': Name is invalid"); next; } - $classes->{$classID} = $className; + $config->{'classes'}->{$classID} = $className; $logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic class '$className' with ID $classID."); } $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading traffic classes completed."); @@ -167,37 +210,45 @@ sub plugin_init # Check if we using a default pool or not if (defined(my $dp = booleanize($globals->{'file.config'}->{'shaping'}->{'use_default_pool'}))) { # If we are using the default pool, load the limits - if ($use_default_pool = $dp) { + if ($config->{'use_default_pool'} = $dp) { # Pull in both config items if (defined(my $txir = $globals->{'file.config'}->{'shaping'}->{'default_pool_txrate'})) { $logger->log(LOG_INFO,"[CONFIGMANAGER] Set default_pool_txrate to '$txir'"); - $default_pool_txrate = isNumber($txir); + $config->{'default_pool_txrate'} = isNumber($txir); } else { $logger->log(LOG_WARN,"[CONFIGMANAGER] There is a problem with default_pool_txrate, config item use_default_pool disabled"); } if (defined(my $rxir = $globals->{'file.config'}->{'shaping'}->{'default_pool_rxrate'})) { $logger->log(LOG_INFO,"[CONFIGMANAGER] Set default_pool_rxrate to '$rxir'"); - $default_pool_rxrate = isNumber($rxir); + $config->{'default_pool_rxrate'} = isNumber($rxir); } else { $logger->log(LOG_WARN,"[CONFIGMANAGER] There is a problem with default_pool_rxrate, config item use_default_pool disabled"); } # Check we have both items configured, if not deconfigure - if (!defined($default_pool_txrate) || !defined($default_pool_rxrate)) { - $use_default_pool = 0; - $default_pool_txrate = undef; - $default_pool_rxrate = undef; + if (!defined($config->{'default_pool_txrate'}) || !defined($config->{'default_pool_rxrate'})) { + $config->{'use_default_pool'} = 0; } } } - $logger->log(LOG_INFO,"[CONFIGMANAGER] Using of default pool ". ( $use_default_pool ? - "ENABLED with rates $default_pool_txrate/$default_pool_rxrate" : "DISABLED" ) ); + $logger->log(LOG_INFO,"[CONFIGMANAGER] Using of default pool ". ( $config->{'use_default_pool'} ? + "ENABLED with rates $config->{'default_pool_txrate'}/$config->{'default_pool_rxrate'}" : "DISABLED" ) ); + + # Check if we have a state file + if (defined(my $statefile = $globals->{'file.config'}->{'system'}->{'statefile'})) { + $config->{'statefile'} = $statefile; + $logger->log(LOG_INFO,"[CONFIGMANAGER] Set statefile to '$statefile'"); + } # This is our configuration processing session POE::Session->create( inline_states => { - _start => \&session_init, + _start => \&session_start, + _stop => \&session_stop, + tick => \&session_tick, process_change => \&process_change, + + handle_SIGHUP => \&handle_SIGHUP, } ); } @@ -206,26 +257,62 @@ sub plugin_init # Start the plugin sub plugin_start { - $logger->log(LOG_INFO,"[CONFIGMANAGER] Started"); + $logger->log(LOG_INFO,"[CONFIGMANAGER] Started with ".( keys %{$changeQueue} )." queued items"); } # Initialize config manager -sub session_init { - my $kernel = $_[KERNEL]; +sub session_start +{ + my ($kernel,$heap) = @_[KERNEL,HEAP]; # Set our alias $kernel->alias_set("configmanager"); + # Load config + if (-f $config->{'statefile'}) { + _load_statefile(); + } else { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Statefile '$config->{'statefile'}' cannot be opened: $!"); + } + # Set delay on config updates $kernel->delay(tick => TICK_PERIOD); + $kernel->sig('HUP', 'handle_SIGHUP'); + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Initialized"); } +# Stop the session +sub session_stop +{ + my ($kernel,$heap) = @_[KERNEL,HEAP]; + + + $logger->log(LOG_NOTICE,"[CONFIGMANAGER] Shutting down, saving configuration..."); + + _write_statefile(); + + # Blow away all data + $globals = undef; + $changeQueue = { }; + $limits = { }; + $limitIPMap = { }; + $limitIDMap = { }; + $limitIDCounter = 1; + $overrides = { }; + # XXX: Blow away rest? config? + + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Shutdown"); + + $logger = undef; +} + + # Time ticker for processing changes sub session_tick { my $kernel = $_[KERNEL]; @@ -258,7 +345,7 @@ sub session_tick { $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit already live but new state provided?"); # Get the changes we made and push them to the shaper - if (my $changes = processChanges($glimit,$climit)) { + if (my $changes = _getChangeset($glimit,$climit)) { # Post to shaper $kernel->post("shaper" => "change" => $lid => $changes); } @@ -271,7 +358,7 @@ sub session_tick { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit still online"); # Get the changes we made and push them to the shaper - if (my $changes = processChanges($glimit,$climit)) { + if (my $changes = _getChangeset($glimit,$climit)) { # Post to shaper $kernel->post("shaper" => "change" => $lid => $changes); } @@ -283,7 +370,7 @@ sub session_tick { } elsif ($climit->{'Status'} eq "offline") { # We first check if this update was received some time ago, and if it exceeds our expire time - # We don't want to immediately remove a limit, only for him to come back on a few seconds later, the cost in exec()'s + # We don't want to immediately remove a limit, only for him to come back on a few seconds later, the cost in exec()'s # would be pretty high if ($now - $climit->{'LastUpdate'} > TIMEOUT_EXPIRE_OFFLINE) { @@ -296,7 +383,7 @@ sub session_tick { # Remove from change queue delete($changeQueue->{$lid}); # Set this UID as no longer using this IP - # NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value + # NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value # to be totally gone, which means we not tracking this limit using this IP anymore, not easily solved!! delete($limitIPMap->{$glimit->{'IP'}}->{$lid}); # Check if we can delete the IP too @@ -324,8 +411,7 @@ sub session_tick { # Update the limit data $glimit->{'Status'} = $climit->{'Status'}; $glimit->{'LastUpdate'} = $climit->{'LastUpdate'}; - # This item is optional - $glimit->{'Expires'} = $climit->{'Expires'} if (defined($climit->{'Expires'})); + $glimit->{'Expires'} = $climit->{'Expires'}; # # LIMIT NOT IN LIST @@ -341,7 +427,7 @@ sub session_tick { # If there is already an entry and its not us ... ( @ipLimits == 1 && !defined($limitIPMap->{$climit->{'IP'}}->{$lid}) ) # Or if there is more than 1 entry... - || @ipLimits > 1 + || @ipLimits > 1 ) { # We not going to post this to the shaper, but we are going to override the status $climit->{'Status'} = 'conflict'; @@ -392,7 +478,7 @@ sub session_tick { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Ignoring limit '$climit->{'Username'}' [$lid] state '$climit->{'Status'}', not in our". " global list"); } - + # Remove from change queue delete($changeQueue->{$lid}); } @@ -407,8 +493,13 @@ sub session_tick { # Global limit my $glimit = $limits->{$lid}; + +if (!defined($glimit->{'Expires'})) { + use Data::Dumper; warn "UNDEFINED: ".Dumper($glimit,$lid); +} + # Check for expired limits - if ($glimit->{'Expires'} != 0 && $glimit->{'Expires'} < $now) { + if ($glimit->{'Expires'} && $glimit->{'Expires'} < $now) { $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$glimit->{'Username'}' has expired, marking offline"); # Looks like this limit has expired? my $climit = { @@ -428,119 +519,11 @@ sub session_tick { # Process shaper change -# Supoprted user attributes: -# -# Username -# - Users username -# IP -# - Users IP -# GroupID -# - Group ID -# ClassID -# - Class ID -# TrafficLimitTx -# - Traffic limit in kbps -# TrafficLimitRx -# - Traffic limit in kbps -# TrafficLimitTxBurst -# - Traffic bursting limit in kbps -# TrafficLimitRxBurst -# - Traffic bursting limit in kbps -# Expires -# - Unix timestamp when this entry expires, 0 if never -# Status -# - new -# - offline -# - online -# - unknown -# Source -# - This is the source of the limit, typically plugin.ModuleName -sub process_change { - my ($kernel, $limit) = @_[KERNEL, ARG0]; - - - - # Create a unique limit identifier - my $limitUniq = $limit->{'Username'} . "/" . $limit->{'IP'}; - - # If we've not seen it - my $lid; - if (!defined($lid = $limitIDMap->{$limitUniq})) { - # Give it the next limitID in the list - $limitIDMap->{$limitUniq} = $lid = ++$limitIDCounter; - } - - # We start off blank so we only pull in whats supported - my $limitChange; - if (!($limitChange->{'Username'} = $limit->{'Username'})) { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change as username is invalid."); - } - $limitChange->{'Username'} = $limit->{'Username'}; - $limitChange->{'IP'} = $limit->{'IP'}; - # Check group is OK - if (!($limitChange->{'GroupID'} = checkGroupID($limit->{'GroupID'}))) { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the GroupID is invalid."); - } - # Check class is OK - if (!($limitChange->{'ClassID'} = checkClassID($limit->{'ClassID'}))) { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the ClassID is invalid."); - } - $limitChange->{'TrafficLimitTx'} = $limit->{'TrafficLimitTx'}; - $limitChange->{'TrafficLimitRx'} = $limit->{'TrafficLimitRx'}; - # Take base limits if we don't have any burst values set - $limitChange->{'TrafficLimitTxBurst'} = $limit->{'TrafficLimitTxBurst'}; - $limitChange->{'TrafficLimitRxBurst'} = $limit->{'TrafficLimitRxBurst'}; - - # If we don't have burst limits, set them to the traffic limit, and reset the limit to 25% - if (!defined($limitChange->{'TrafficLimitTxBurst'})) { - $limitChange->{'TrafficLimitTxBurst'} = $limitChange->{'TrafficLimitTx'}; - $limitChange->{'TrafficLimitTx'} = int($limitChange->{'TrafficLimitTxBurst'}/4); - } - if (!defined($limitChange->{'TrafficLimitRxBurst'})) { - $limitChange->{'TrafficLimitRxBurst'} = $limitChange->{'TrafficLimitRx'}; - $limitChange->{'TrafficLimitRx'} = int($limitChange->{'TrafficLimitRxBurst'}/4); - } - - - # Optional priority, we default to 5 - $limitChange->{'TrafficPriority'} = defined($limit->{'TrafficPriority'}) ? $limit->{'TrafficPriority'} : 5; - - # Set when this entry expires - $limitChange->{'Expires'} = defined($limit->{'Expires'}) ? $limit->{'Expires'} : 0; - - # Check status is OK - if (!($limitChange->{'Status'} = checkStatus($limit->{'Status'}))) { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change for '".$limit->{'Username'}."' as the Status is invalid."); - } - - $limitChange->{'Source'} = $limit->{'Source'}; - - # Set the user ID before we post to the change queue - $limitChange->{'ID'} = $lid; - $limitChange->{'LastUpdate'} = time(); - - # Push change to change queue - $changeQueue->{$lid} = $limitChange; -} - - -# Function to compute the changes between two users -sub processChanges +sub process_change { - my ($orig,$new) = @_; - - my $res; - - # Loop through what can change - foreach my $item ('GroupID','ClassID','TrafficLimitTx','TrafficLimitRx','TrafficLimitTxBurst','TrafficLimitRxBurst') { - # Check if its first set, if it is, check if its changed - if (defined($new->{$item}) && $orig->{$item} ne $new->{$item}) { - # If so record it & make the change - $res->{$item} = $orig->{$item} = $new->{$item}; - } - } + my ($kernel, $limit) = @_[KERNEL, ARG0]; - return $res; + _process_change($limit); } @@ -548,22 +531,24 @@ sub processChanges sub checkGroupID { my $gid = shift; - if (defined($groups->{$gid})) { + if (defined($config->{'groups'}->{$gid})) { return $gid; } return; } + # Function to check the class ID exists sub checkClassID { my $cid = shift; - if (defined($classes->{$cid})) { + if (defined($config->{'classes'}->{$cid})) { return $cid; } return; } + # Function to check if the status is ok sub checkStatus { @@ -574,6 +559,7 @@ sub checkStatus return; } + # Function to return a limit username sub getLimitUsername { @@ -584,7 +570,8 @@ sub getLimitUsername return; } -# Function to return a limit + +# Function to return a limit sub getLimit { my $lid = shift; @@ -596,6 +583,7 @@ sub getLimit return; } + # Function to return a list of limit ID's sub getLimits { @@ -615,6 +603,7 @@ sub setLimitAttribute return; } + # Function to get a limit attribute sub getLimitAttribute { @@ -628,6 +617,7 @@ sub getLimitAttribute return; } + # Function to set shaper state on a limit sub setShaperState { @@ -638,6 +628,7 @@ sub setShaperState } } + # Function to get shaper state on a limit sub getShaperState { @@ -648,25 +639,287 @@ sub getShaperState return; } + # Function to get traffic classes sub getTrafficClasses { - return $classes; + my %classes = %{$config->{'classes'}}; + + return \%classes; } + # Function to get priority name sub getPriorityName { my $prio = shift; - return $classes->{$prio}; + return $config->{'classes'}->{$prio}; } # Handle SIGHUP sub handle_SIGHUP { + my ($kernel, $heap, $signal_name) = @_[KERNEL, HEAP, ARG0]; + $logger->log(LOG_WARN,"[CONFIGMANAGER] Got SIGHUP, ignoring for now"); } + + +# +# Internal functions +# + +# Function to compute the changes between two users +sub _getChangeset +{ + my ($orig,$new) = @_; + + my $res; + + # Loop through what can change + foreach my $item (CHANGESET_ATTRIBUTES) { + # Check if its first set, if it is, check if its changed + if (defined($new->{$item}) && $orig->{$item} ne $new->{$item}) { + # If so record it & make the change + $res->{$item} = $orig->{$item} = $new->{$item}; + } + } + + return $res; +} + + +# This is the real function +# Supoprted user attributes: +# +# Username +# - Users username +# IP +# - Users IP +# GroupID +# - Group ID +# ClassID +# - Class ID +# TrafficLimitTx +# - Traffic limit in kbps +# TrafficLimitRx +# - Traffic limit in kbps +# TrafficLimitTxBurst +# - Traffic bursting limit in kbps +# TrafficLimitRxBurst +# - Traffic bursting limit in kbps +# Expires +# - Unix timestamp when this entry expires, 0 if never +# Status +# - new +# - offline +# - online +# - unknown +# Source +# - This is the source of the limit, typically plugin.ModuleName +sub _process_change +{ + my $limit = shift; + + + # We start off blank so we only pull in whats supported + my $limitChange; + if (!($limitChange->{'Username'} = $limit->{'Username'})) { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change as username is invalid."); + return; + } + $limitChange->{'Username'} = $limit->{'Username'}; + $limitChange->{'IP'} = $limit->{'IP'}; + # Check group is OK + if (!($limitChange->{'GroupID'} = checkGroupID($limit->{'GroupID'}))) { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the GroupID is invalid."); + return; + } + # Check class is OK + if (!($limitChange->{'ClassID'} = checkClassID($limit->{'ClassID'}))) { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the ClassID is invalid."); + return; + } + $limitChange->{'TrafficLimitTx'} = $limit->{'TrafficLimitTx'}; + $limitChange->{'TrafficLimitRx'} = $limit->{'TrafficLimitRx'}; + # Take base limits if we don't have any burst values set + $limitChange->{'TrafficLimitTxBurst'} = $limit->{'TrafficLimitTxBurst'}; + $limitChange->{'TrafficLimitRxBurst'} = $limit->{'TrafficLimitRxBurst'}; + + # If we don't have burst limits, set them to the traffic limit, and reset the limit to 25% + if (!defined($limitChange->{'TrafficLimitTxBurst'})) { + $limitChange->{'TrafficLimitTxBurst'} = $limitChange->{'TrafficLimitTx'}; + $limitChange->{'TrafficLimitTx'} = int($limitChange->{'TrafficLimitTxBurst'}/4); + } + if (!defined($limitChange->{'TrafficLimitRxBurst'})) { + $limitChange->{'TrafficLimitRxBurst'} = $limitChange->{'TrafficLimitRx'}; + $limitChange->{'TrafficLimitRx'} = int($limitChange->{'TrafficLimitRxBurst'}/4); + } + + + # Optional priority, we default to 5 + $limitChange->{'TrafficPriority'} = defined($limit->{'TrafficPriority'}) ? $limit->{'TrafficPriority'} : 5; + + # Set when this entry expires + $limitChange->{'Expires'} = defined($limit->{'Expires'}) ? $limit->{'Expires'} : 0; + + # Set when this entry was created + $limitChange->{'Created'} = defined($limit->{'Created'}) ? $limit->{'Created'} : time(); + + # Check status is OK + if (!($limitChange->{'Status'} = checkStatus($limit->{'Status'}))) { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change for '".$limit->{'Username'}."' as the Status is invalid."); + return; + } + + $limitChange->{'Source'} = $limit->{'Source'}; + + # Create a unique limit identifier + my $limitUniq = $limit->{'Username'} . "/" . $limit->{'IP'}; + # If we've not seen it + my $lid; + if (!defined($lid = $limitIDMap->{$limitUniq})) { + # Give it the next limitID in the list + $limitIDMap->{$limitUniq} = $lid = ++$limitIDCounter; + } + + # Set the user ID before we post to the change queue + $limitChange->{'ID'} = $lid; + $limitChange->{'LastUpdate'} = time(); + + # Push change to change queue + $changeQueue->{$lid} = $limitChange; +} + + +# Load our statefile +sub _load_statefile +{ + # Check if the state file exists first of all + if (! -e $config->{'statefile'}) { + $logger->log(LOG_INFO,"[CONFIGMANAGER] Statefile '".$config->{'statefile'}."' doesn't exist"); + return; + } + + # Pull in a hash for our statefile + my %stateHash; + if (! tie %stateHash, 'Config::IniFiles', ( -file => $config->{'statefile'} )) { + my $reason = $1 || "Config file blank?"; + $logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to open statefile '".$config->{'statefile'}."': $reason"); + # NK: Breaks load on blank file + # Set it to undef so we don't overwrite it... + #$config->{'statefile'} = undef; + return; + } + # Grab the object handle + my $state = tied( %stateHash ); + + # Loop with user overrides + foreach my $section ($state->GroupMembers('override')) { + my $override = $stateHash{$section}; + + # Our user override + my $ouser; + foreach my $attr (OVERRIDE_ATTRIBUTES) { + if (defined($override->{$attr})) { + $ouser->{$attr} = $override->{$attr}; + } + } + + # Check username & IP are defined + if (!defined($ouser->{'Username'})) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load user override with no username '$section'"); + next; + } + + $overrides->{$ouser->{'Username'}} = $ouser; + } + + # Loop with persistent users + foreach my $section ($state->GroupMembers('persist')) { + my $user = $stateHash{$section}; + + # User to push through to process change + my $cuser; + foreach my $attr (PERSISTENT_ATTRIBUTES) { + if (defined($user->{$attr})) { + $cuser->{$attr} = $user->{$attr}; + } + } + # This is a new entry + $cuser->{'Status'} = 'new'; + + # Check username & IP are defined + if (!defined($cuser->{'Username'}) || !defined($cuser->{'IP'})) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load persistent user with no username or no IP '$section'"); + next; + } + + # Process this user + _process_change($cuser); + } +} + + +# Write out statefile +sub _write_statefile +{ + # Check if the state file exists first of all + if (!defined($config->{'statefile'})) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] No statefile defined. Possible initial load error?"); + return; + } + + # Create new state file object + my $state = new Config::IniFiles(); + + # Loop with persistent users, these are users with expires = 0 + foreach my $lid (keys %{$limits}) { + # Skip over expiring entries, we only want persistent ones + # XXX: Should we not just save all of them? load? + next if ($limits->{$lid}->{'Expires'}); + # Pull in the section name + my $section = "persist " . $limits->{$lid}->{'Username'}; + + # Add a new section for this user + $state->AddSection($section); + # Items we want for persistent entries + foreach my $pItem (PERSISTENT_ATTRIBUTES) { + # Set items up + if (defined(my $value = $limits->{$lid}->{$pItem})) { + $state->newval($section,$pItem,$value); + } + } + } + + # Loop with overrides + foreach my $username (keys %{$overrides}) { + # Pull in the section name + my $section = "override " . $username; + + # Add a new section for this user + $state->AddSection($section); + # Items we want for override entries + foreach my $pItem (OVERRIDE_ATTRIBUTES) { + # Set items up + if (defined(my $value = $overrides->{$username}->{$pItem})) { + $state->newval($section,$pItem,$value); + } + } + + } + + # Check for an error + if (!defined($state->WriteConfig($config->{'statefile'}))) { + $logger->log(LOG_ERR,"[CONFIGMANAGER] Failed to write statefile '".$config->{'statefile'}."': $!"); + return; + } + + $logger->log(LOG_NOTICE,"[CONFIGMANAGER] Configuration saved"); +} + + 1; # vim: ts=4