summaryrefslogtreecommitdiff
path: root/lib/rbot
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbot')
-rw-r--r--lib/rbot/auth.rb263
-rw-r--r--lib/rbot/ircbot.rb4
-rw-r--r--lib/rbot/plugins.rb12
3 files changed, 179 insertions, 100 deletions
diff --git a/lib/rbot/auth.rb b/lib/rbot/auth.rb
index 16f092a5..9868e6ea 100644
--- a/lib/rbot/auth.rb
+++ b/lib/rbot/auth.rb
@@ -4,39 +4,45 @@ module Irc
# netmask:: netmask to test against
# Compare a netmask with a standard IRC glob, e.g foo!bar@baz.com would
# match *!*@baz.com, foo!*@*, *!bar@*, etc.
- def Irc.netmaskmatch(globmask, netmask)
- regmask = globmask.gsub(/\*/, ".*?")
+ def Irc.netmaskmatch( globmask, netmask )
+ regmask = Regexp.escape( globmask )
+ regmask.gsub!( /\\\*/, '.*' )
return true if(netmask =~ /#{regmask}/i)
return false
end
# check if a string is an actual IRC hostmask
- def Irc.ismask(mask)
+ def Irc.ismask?(mask)
mask =~ /^.+!.+@.+$/
end
-
+ Struct.new( 'UserData', :level, :password, :hostmasks )
+
# User-level authentication to allow/disallow access to bot commands based
# on hostmask and userlevel.
class IrcAuth
- BotConfig.register BotConfigStringValue.new('auth.password',
- :default => "rbotauth", :wizard => true,
- :desc => "Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)")
-
+ BotConfig.register BotConfigStringValue.new( 'auth.password',
+ :default => 'rbotauth', :wizard => true,
+ :desc => 'Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)' )
+ BotConfig.register BotConfigIntegerValue.new( 'auth.default_level',
+ :default => 10, :wizard => true,
+ :desc => 'The default level for new/unknown users' )
+
# create a new IrcAuth instance.
# bot:: associated bot class
def initialize(bot)
@bot = bot
- @users = Hash.new(0)
+ @users = Hash.new do
+ Struct::UserData.new(@bot.config['auth.default_level'], '', [])
+ end
@levels = Hash.new(0)
- if(File.exist?("#{@bot.botclass}/users.rbot"))
- IO.foreach("#{@bot.botclass}/users.rbot") do |line|
- if(line =~ /\s*(\d+)\s*(\S+)/)
- level = $1.to_i
- mask = $2
- @users[mask] = level
- end
- end
+ @currentUsers = Hash.new( nil )
+ if( File.exist?( "#{@bot.botclass}/users.yaml" ) )
+ File.open( "#{@bot.botclass}/users.yaml" ) { |file|
+ # work around YAML not maintaining the default proc
+ @loadedusers = YAML::parse(file).transform
+ @users.merge(@loadedusers)
+ }
end
if(File.exist?("#{@bot.botclass}/levels.rbot"))
IO.foreach("#{@bot.botclass}/levels.rbot") do |line|
@@ -51,15 +57,13 @@ module Irc
# save current users and levels to files.
# levels are written to #{botclass}/levels.rbot
- # users are written to #{botclass}/users.rbot
+ # users are written to #{botclass}/users.yaml
def save
Dir.mkdir("#{@bot.botclass}") if(!File.exist?("#{@bot.botclass}"))
- File.open("#{@bot.botclass}/users.rbot", "w") do |file|
- @users.each do |key, value|
- file.puts "#{value} #{key}"
- end
+ File.open("#{@bot.botclass}/users.yaml", 'w') do |file|
+ file.puts @users.to_yaml
end
- File.open("#{@bot.botclass}/levels.rbot", "w") do |file|
+ File.open("#{@bot.botclass}/levels.rbot", 'w') do |file|
@levels.each do |key, value|
file.puts "#{value} #{key}"
end
@@ -73,30 +77,58 @@ module Irc
# returns true if user with hostmask +mask+ is permitted to perform
# +command+ optionally pass tell as the target for the "insufficient auth"
# message, if the user is not authorised
- def allow?(command, mask, tell=nil)
- auth = userlevel(mask)
- if(auth >= @levels[command])
- return true
- else
- debug "#{mask} is not allowed to perform #{command}"
- @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell
- return false
- end
+ def allow?( command, mask, tell=nil )
+ auth = @users[matchingUser(mask)].level # Directly using @users[] is possible, because UserData has a default setting
+ if( auth >= @levels[command] )
+ return true
+ else
+ debug "#{mask} is not allowed to perform #{command}"
+ @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell
+ return false
+ end
end
# add user with hostmask matching +mask+ with initial auth level +level+
- def useradd(mask, level)
- if(Irc.ismask(mask))
- @users[mask] = level
- end
+ def useradd( username, level=@bot.config['auth.default_level'], password='', hostmask='*!*@*' )
+ @users[username] = Struct::UserData.new( level, password, [hostmask] ) if ! @users.has_key? username
end
-
+
# mask:: mask of user to remove
# remove user with mask +mask+
- def userdel(mask)
- if(Irc.ismask(mask))
- @users.delete(mask)
+ def userdel( username )
+ @users.delete( username ) if @users.has_key? username
+ end
+
+ def usermod( username, item, value=nil )
+ if @users.has_key?( username )
+ case item
+ when 'hostmask'
+ if Irc.ismask?( value )
+ @users[username].hostmasks = [ value ]
+ return true
+ end
+ when '+hostmask'
+ if Irc.ismask?( value )
+ @users[username].hostmasks += [ value ]
+ return true
+ end
+ when '-hostmask'
+ if Irc.ismask?( value )
+ @users[username].hostmasks -= [ value ]
+ return true
+ end
+ when 'password'
+ @users[username].password = value
+ return true
+ when 'level'
+ @users[username].level = value.to_i
+ return true
+ else
+ debug "usermod: Tried to modify unknown item #{item}"
+ @bot.say tell, "Unknown item #{item}" if tell
+ end
end
+ return false
end
# command:: command to adjust
@@ -106,30 +138,32 @@ module Irc
@levels[command] = level
end
- # specific users.
- # mask:: mask of user
- # returns the authlevel of user with mask +mask+
- # finds the matching user which has the highest authlevel (so you can have
- # a default level of 5 for *!*@*, and yet still give higher levels to
- def userlevel(mask)
- # go through hostmask list, find match with _highest_ level (all users
- # will match *!*@*)
- level = 0
- @users.each {|user,userlevel|
- if(Irc.netmaskmatch(user, mask))
- level = userlevel if userlevel > level
+ def matchingUser( mask )
+ currentUser = nil
+ currentLevel = 0
+ @users.each { |user, data| # TODO Will get easier if YPaths are used...
+ if data.level > currentLevel
+ data.hostmasks.each { |hostmask|
+ if Irc.netmaskmatch( hostmask, mask )
+ currentUser = user
+ currentLevel = data.level
+ end
+ }
end
}
- level
+ currentUser
+ end
+
+ def identify( mask, username, password )
+ usermod( username, '+hostmask', mask ) if @users.has_key? username && @users[username].password == password
+ debug "User identified: #{username}"
end
# return all currently defined commands (for which auth is required) and
# their required authlevels
def showlevels
- reply = "Current levels are:"
- @levels.sort.each {|a|
- key = a[0]
- value = a[1]
+ reply = 'Current levels are:'
+ @levels.sort.each { |key, value|
reply += " #{key}(#{value})"
}
reply
@@ -137,32 +171,46 @@ module Irc
# return all currently defined users and their authlevels
def showusers
- reply = "Current users are:"
- @users.sort.each {|a|
- key = a[0]
- value = a[1]
- reply += " #{key}(#{value})"
+ reply = 'Current users are:'
+ @users.sort.each { |key, value|
+ reply += " #{key}(#{value.level})"
}
reply
end
-
+
+ def showdetails( username )
+ if @users.has_key? username
+ reply = "#{username}(#{@users[username].level}):"
+ @users[username].hostmasks.each { |hostmask|
+ reply += " #{hostmask}"
+ }
+ end
+ reply
+ end
+
# module help
- def help(topic="")
+ def help(topic='')
case topic
- when "setlevel"
- return "setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)"
- when "useradd"
- return "useradd <mask> <level> => Add user <mask> at level <level> (private addressing only)"
- when "userdel"
- return "userdel <mask> => Remove user <mask> (private addressing only)"
- when "auth"
- return "auth <masterpw> => Recognise your hostmask as bot master (private addressing only)"
- when "levels"
- return "levels => list commands and their required levels (private addressing only)"
- when "users"
- return "users => list users and their levels (private addressing only)"
+ when 'setlevel'
+ return 'setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)'
+ when 'useradd'
+ return 'useradd <username> => Add user <mask>, you still need to set him up correctly (private addressing only)'
+ when 'userdel'
+ return 'userdel <username> => Remove user <username> (private addressing only)'
+ when 'usermod'
+ return 'usermod <username> <item> <value> => Modify <username>s settings. Valid <item>s are: hostmask, (+|-)hostmask, password, level (private addressing only)'
+ when 'auth'
+ return 'auth <masterpw> => Create a user with your hostmask and master password as bot master (private addressing only)'
+ when 'levels'
+ return 'levels => list commands and their required levels (private addressing only)'
+ when 'users'
+ return 'users [<username>]=> list users and their levels or details about <username> (private addressing only)'
+ when 'whoami'
+ return 'whoami => Show as whom you are recognized (private addressing only)'
+ when 'identify'
+ return 'identify <username> <password> => Identify your hostmask as belonging to <username> (private addressing only)'
else
- return "Auth module (User authentication) topics: setlevel, useradd, userdel, auth, levels, users"
+ return 'Auth module (User authentication) topics: setlevel, useradd, userdel, usermod, auth, levels, users, whoami, identify'
end
end
@@ -171,31 +219,58 @@ module Irc
if(m.address? && m.private?)
case m.message
when (/^setlevel\s+(\S+)\s+(\d+)$/)
- if(@bot.auth.allow?("auth", m.source, m.replyto))
- @bot.auth.setlevel($1, $2.to_i)
+ if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
+ @bot.auth.setlevel( $1, $2.to_i )
m.reply "level for #$1 set to #$2"
end
- when (/^useradd\s+(\S+)\s+(\d+)/)
- if(@bot.auth.allow?("auth", m.source, m.replyto))
- @bot.auth.useradd($1, $2.to_i)
- m.reply "added user #$1 at level #$2"
+ when( /^useradd\s+(\S+)/ ) # FIXME Needs review!!! (\s+(\S+)(\s+(\S+)(\s+(\S+))?)?)? Should this part be added to make complete useradds possible?
+ if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
+ @bot.auth.useradd( $1 )
+ m.reply "added user #$1, please set him up correctly"
end
- when (/^userdel\s+(\S+)/)
- if(@bot.auth.allow?("auth", m.source, m.replyto))
- @bot.auth.userdel($1)
+ when( /^userdel\s+(\S+)/ )
+ if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
+ @bot.auth.userdel( $1 )
m.reply "user #$1 is gone"
end
+ when( /^usermod\s+(\S+)\s+(\S+)\s+(\S+)/ )
+ if( @bot.auth.allow?('auth', m.source, m.replyto ) )
+ if( @bot.auth.usermod( $1, $2, $3 ) )
+ m.reply "Set #$2 of #$1 to #$3"
+ else
+ m.reply "Failed to set #$2 of #$1 to #$3"
+ end
+ end
when (/^auth\s+(\S+)/)
- if($1 == @bot.config["auth.password"])
- @bot.auth.useradd(Regexp.escape(m.source), 1000)
- m.reply "Identified, security level maxed out"
+ if( $1 == @bot.config['auth.password'] )
+ if ! @users.has_key? 'master'
+ @bot.auth.useradd( 'master', 1000, @bot.config['auth.password'], m.source )
+ else
+ @bot.usermod( 'master', '+hostmask', m.source )
+ end
+ m.reply 'Identified, security level maxed out'
+ else
+ m.reply 'Incorrect password'
+ end
+ when( /^identify\s+(\S+)\s+(\S+)/ )
+ if( @bot.auth.identify( m.source, $1, $2 ) )
+ m.reply "Identified as #$1(#{@users[$1].level($1)}"
+ else
+ m.reply 'Incorrect username/password'
+ end
+ when( 'whoami' )
+ user = @bot.auth.matchingUser( m.source )
+ if user
+ m.reply "I recognize you as #{user}(#{@users[user].level})"
else
- m.reply "incorrect password"
+ m.reply 'You don\'t belong to any user.'
end
- when ("levels")
- m.reply @bot.auth.showlevels if(@bot.auth.allow?("config", m.source, m.replyto))
- when ("users")
- m.reply @bot.auth.showusers if(@bot.auth.allow?("config", m.source, m.replyto))
+ when( /^users\s+(\S+)/ )
+ m.reply @bot.auth.showdetails( $1 ) if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
+ when ( 'levels' )
+ m.reply @bot.auth.showlevels if( @bot.auth.allow?( 'config', m.source, m.replyto ) )
+ when ( 'users' )
+ m.reply @bot.auth.showusers if( @bot.auth.allow?( 'users', m.source, m.replyto ) )
end
end
end
diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb
index 271d0d70..435c6284 100644
--- a/lib/rbot/ircbot.rb
+++ b/lib/rbot/ircbot.rb
@@ -329,8 +329,8 @@ class IrcBot
def mainloop
while true
begin
- connect
- @timer.start
+ connect
+ @timer.start
while true
if @socket.select
diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb
index 893bb414..f45e44f4 100644
--- a/lib/rbot/plugins.rb
+++ b/lib/rbot/plugins.rb
@@ -200,7 +200,8 @@ module Plugins
debug "loading plugin #{tmpfilename}"
plugin_module.module_eval(plugin_string)
processed << file
- rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
+ rescue Exception => err
+ # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
puts "warning: plugin #{tmpfilename} load failed: " + err
puts err.backtrace.join("\n")
end
@@ -250,7 +251,8 @@ module Plugins
if(@@plugins.has_key?(key))
begin
return @@plugins[key].help(key, params)
- rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
puts "plugin #{@@plugins[key].name} help() failed: " + err
puts err.backtrace.join("\n")
end
@@ -267,7 +269,8 @@ module Plugins
if(p.respond_to? method)
begin
p.send method, *args
- rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
puts "plugin #{p.name} #{method}() failed: " + err
puts err.backtrace.join("\n")
end
@@ -284,7 +287,8 @@ module Plugins
@@bot.auth.allow?(m.plugin, m.source, m.replyto))
begin
@@plugins[m.plugin].privmsg(m)
- rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
puts "plugin #{@@plugins[m.plugin].name} privmsg() failed: " + err
puts err.backtrace.join("\n")
end