some tweaks
[rbot] / lib / rbot / auth.rb
1 module Irc
2
3   # globmask:: glob to test with
4   # netmask::  netmask to test against
5   # Compare a netmask with a standard IRC glob, e.g foo!bar@baz.com would
6   # match *!*@baz.com, foo!*@*, *!bar@*, etc.
7   def Irc.netmaskmatch(globmask, netmask)
8     regmask = globmask.gsub(/\*/, ".*?")
9     return true if(netmask =~ /#{regmask}/i)
10     return false
11   end
12
13   # check if a string is an actual IRC hostmask
14   def Irc.ismask(mask)
15     mask =~ /^.+!.+@.+$/
16   end
17
18   
19   # User-level authentication to allow/disallow access to bot commands based
20   # on hostmask and userlevel.
21   class IrcAuth
22     BotConfig.register BotConfigStringValue.new('auth.password',
23       :default => "rbotauth", :wizard => true,
24       :desc => "Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)")
25     
26     # create a new IrcAuth instance.
27     # bot:: associated bot class
28     def initialize(bot)
29       @bot = bot
30       @users = Hash.new(0)
31       @levels = Hash.new(0)
32       if(File.exist?("#{@bot.botclass}/users.rbot"))
33         IO.foreach("#{@bot.botclass}/users.rbot") do |line|
34           if(line =~ /\s*(\d+)\s*(\S+)/)
35             level = $1.to_i
36             mask = $2
37             @users[mask] = level
38           end
39         end
40       end
41       if(File.exist?("#{@bot.botclass}/levels.rbot"))
42         IO.foreach("#{@bot.botclass}/levels.rbot") do |line|
43           if(line =~ /\s*(\d+)\s*(\S+)/)
44             level = $1.to_i
45             command = $2
46             @levels[command] = level
47           end
48         end
49       end
50     end
51
52     # save current users and levels to files.
53     # levels are written to #{botclass}/levels.rbot
54     # users are written to #{botclass}/users.rbot
55     def save
56       Dir.mkdir("#{@bot.botclass}") if(!File.exist?("#{@bot.botclass}"))
57       File.open("#{@bot.botclass}/users.rbot", "w") do |file|
58         @users.each do |key, value|
59           file.puts "#{value} #{key}"
60         end
61       end
62       File.open("#{@bot.botclass}/levels.rbot", "w") do |file|
63         @levels.each do |key, value|
64           file.puts "#{value} #{key}"
65         end
66       end
67     end
68
69     # command:: command user wishes to perform
70     # mask::    hostmask of user
71     # tell::    optional recipient for "insufficient auth" message
72     #
73     # returns true if user with hostmask +mask+ is permitted to perform
74     # +command+ optionally pass tell as the target for the "insufficient auth"
75     # message, if the user is not authorised
76     def allow?(command, mask, tell=nil)
77       auth = userlevel(mask)
78       if(auth >= @levels[command])
79         return true
80       else
81         debug "#{mask} is not allowed to perform #{command}"
82         @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell
83         return false
84       end
85     end
86
87     # add user with hostmask matching +mask+ with initial auth level +level+
88     def useradd(mask, level)
89       if(Irc.ismask(mask))
90         @users[mask] = level
91       end
92     end
93     
94     # mask:: mask of user to remove
95     # remove user with mask +mask+
96     def userdel(mask)
97       if(Irc.ismask(mask))
98         @users.delete(mask)
99       end
100     end
101
102     # command:: command to adjust
103     # level::   new auth level for the command
104     # set required auth level of +command+ to +level+
105     def setlevel(command, level)
106       @levels[command] = level
107     end
108
109     # specific users.
110     # mask:: mask of user
111     # returns the authlevel of user with mask +mask+
112     # finds the matching user which has the highest authlevel (so you can have
113     # a default level of 5 for *!*@*, and yet still give higher levels to
114     def userlevel(mask)
115       # go through hostmask list, find match with _highest_ level (all users
116       # will match *!*@*)
117       level = 0
118       @users.each {|user,userlevel|
119         if(Irc.netmaskmatch(user, mask))
120           level = userlevel if userlevel > level
121         end
122       }
123       level
124     end
125
126     # return all currently defined commands (for which auth is required) and
127     # their required authlevels
128     def showlevels
129       reply = "Current levels are:"
130       @levels.sort.each {|a|
131         key = a[0]
132         value = a[1]
133         reply += " #{key}(#{value})"
134       }
135       reply
136     end
137
138     # return all currently defined users and their authlevels
139     def showusers
140       reply = "Current users are:"
141       @users.sort.each {|a|
142         key = a[0]
143         value = a[1]
144         reply += " #{key}(#{value})"
145       }
146       reply
147     end
148     
149     # module help
150     def help(topic="")
151       case topic
152         when "setlevel"
153           return "setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)"
154         when "useradd"
155           return "useradd <mask> <level> => Add user <mask> at level <level> (private addressing only)"
156         when "userdel"
157           return "userdel <mask> => Remove user <mask> (private addressing only)"
158         when "auth"
159           return "auth <masterpw> => Recognise your hostmask as bot master (private addressing only)"
160         when "levels"
161           return "levels => list commands and their required levels (private addressing only)"
162         when "users"
163           return "users => list users and their levels (private addressing only)"
164         else
165           return "Auth module (User authentication) topics: setlevel, useradd, userdel, auth, levels, users"
166       end
167     end
168
169     # privmsg handler
170     def privmsg(m)
171      if(m.address? && m.private?)
172       case m.message
173         when (/^setlevel\s+(\S+)\s+(\d+)$/)
174           if(@bot.auth.allow?("auth", m.source, m.replyto))
175             @bot.auth.setlevel($1, $2.to_i)
176             m.reply "level for #$1 set to #$2"
177           end
178         when (/^useradd\s+(\S+)\s+(\d+)/)
179           if(@bot.auth.allow?("auth", m.source, m.replyto))
180             @bot.auth.useradd($1, $2.to_i)
181             m.reply "added user #$1 at level #$2"
182           end
183         when (/^userdel\s+(\S+)/)
184           if(@bot.auth.allow?("auth", m.source, m.replyto))
185             @bot.auth.userdel($1)
186             m.reply "user #$1 is gone"
187           end
188         when (/^auth\s+(\S+)/)
189           if($1 == @bot.config["auth.password"])
190             @bot.auth.useradd(Regexp.escape(m.source), 1000)
191             m.reply "Identified, security level maxed out"
192           else
193             m.reply "incorrect password"
194           end
195         when ("levels")
196           m.reply @bot.auth.showlevels if(@bot.auth.allow?("config", m.source, m.replyto))
197         when ("users")
198           m.reply @bot.auth.showusers if(@bot.auth.allow?("config", m.source, m.replyto))
199       end
200      end
201     end
202   end
203 end