]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/auth.rb
Try calling the interpreter explicitly on restart if exec'ing /bin/bash fails
[user/henk/code/ruby/rbot.git] / lib / rbot / core / auth.rb
1 #-- vim:sw=2:et\r
2 #++\r
3 #\r
4 # :title: rbot auth management from IRC\r
5 #\r
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>\r
7 # Copyright:: (C) 2006,2007 Giuseppe Bilotta\r
8 # License:: GPL v2\r
9 \r
10 class AuthModule < CoreBotModule\r
11 \r
12   def initialize\r
13     super\r
14     load_array(:default, true)\r
15     debug "initialized auth. Botusers: #{@bot.auth.save_array.pretty_inspect}"\r
16   end\r
17 \r
18   def save\r
19     save_array\r
20   end\r
21 \r
22   def save_array(key=:default)\r
23     if @bot.auth.changed?\r
24       @registry[key] = @bot.auth.save_array\r
25       @bot.auth.reset_changed\r
26       debug "saved botusers (#{key}): #{@registry[key].pretty_inspect}"\r
27     end\r
28   end\r
29 \r
30   def load_array(key=:default, forced=false)\r
31     debug "loading botusers (#{key}): #{@registry[key].pretty_inspect}"\r
32     @bot.auth.load_array(@registry[key], forced) if @registry.has_key?(key)\r
33   end\r
34 \r
35   # The permission parameters accept arguments with the following syntax:\r
36   #   cmd_path... [on #chan .... | in here | in private]\r
37   # This auxiliary method scans the array _ar_ to see if it matches\r
38   # the given syntax: it expects + or - signs in front of _cmd_path_\r
39   # elements when _setting_ = true\r
40   #\r
41   # It returns an array whose first element is the array of cmd_path,\r
42   # the second element is an array of locations and third an array of\r
43   # warnings occurred while parsing the strings\r
44   #\r
45   def parse_args(ar, setting)\r
46     cmds = []\r
47     locs = []\r
48     warns = []\r
49     doing_cmds = true\r
50     next_must_be_chan = false\r
51     want_more = false\r
52     last_idx = 0\r
53     ar.each_with_index { |x, i|\r
54       if doing_cmds # parse cmd_path\r
55         # check if the list is done\r
56         if x == "on" or x == "in"\r
57           doing_cmds = false\r
58           next_must_be_chan = true if x == "on"\r
59           next\r
60         end\r
61         if "+-".include?(x[0])\r
62           warns << ArgumentError.new("please do not use + or - in front of command #{x} when resetting") unless setting\r
63         else\r
64           warns << ArgumentError.new("+ or - expected in front of #{x}") if setting\r
65         end\r
66         cmds << x\r
67       else # parse locations\r
68         if x[-1].chr == ','\r
69           want_more = true\r
70         else\r
71           want_more = false\r
72         end\r
73         case next_must_be_chan\r
74         when false\r
75           locs << x.gsub(/^here$/,'_').gsub(/^private$/,'?')\r
76         else\r
77           warns << ArgumentError("#{x} doesn't look like a channel name") unless @bot.server.supports[:chantypes].include?(x[0])\r
78           locs << x\r
79         end\r
80         unless want_more\r
81           last_idx = i\r
82           break\r
83         end\r
84       end\r
85     }\r
86     warns << "trailing comma" if want_more\r
87     warns << "you probably forgot a comma" unless last_idx == ar.length - 1\r
88     return cmds, locs, warns\r
89   end\r
90 \r
91   def auth_edit_perm(m, params)\r
92 \r
93     setting = m.message.split[1] == "set"\r
94     splits = params[:args]\r
95 \r
96     has_for = splits[-2] == "for"\r
97     return usage(m) unless has_for\r
98 \r
99     begin\r
100       user = @bot.auth.get_botuser(splits[-1].sub(/^all$/,"everyone"))\r
101     rescue\r
102       return m.reply("couldn't find botuser #{splits[-1]}")\r
103     end\r
104     return m.reply("you can't change permissions for #{user.username}") if user == @bot.auth.botowner\r
105     splits.slice!(-2,2) if has_for\r
106 \r
107     cmds, locs, warns = parse_args(splits, setting)\r
108     errs = warns.select { |w| w.kind_of?(Exception) }\r
109 \r
110     unless errs.empty?\r
111       m.reply "couldn't satisfy your request: #{errs.join(',')}"\r
112       return\r
113     end\r
114 \r
115     if locs.empty?\r
116       locs << "*"\r
117     end\r
118     begin\r
119       locs.each { |loc|\r
120         ch = loc\r
121         if m.private?\r
122           ch = "?" if loc == "_"\r
123         else\r
124           ch = m.target.to_s if loc == "_"\r
125         end\r
126         cmds.each { |setval|\r
127           if setting\r
128             val = setval[0].chr == '+'\r
129             cmd = setval[1..-1]\r
130             user.set_permission(cmd, val, ch)\r
131           else\r
132             cmd = setval\r
133             user.reset_permission(cmd, ch)\r
134           end\r
135         }\r
136       }\r
137     rescue => e\r
138       m.reply "something went wrong while trying to set the permissions"\r
139       raise\r
140     end\r
141     @bot.auth.set_changed\r
142     debug "user #{user} permissions changed"\r
143     m.okay\r
144   end\r
145 \r
146   def auth_view_perm(m, params)\r
147     begin\r
148       if params[:user].nil?\r
149         user = get_botusername_for(m.source)\r
150         return m.reply("you are owner, you can do anything") if user == @bot.auth.botwoner\r
151       else\r
152         user = @bot.auth.get_botuser(params[:user].sub(/^all$/,"everyone"))\r
153         return m.reply("owner can do anything") if user.username == "owner" \r
154       end\r
155     rescue\r
156       return m.reply("couldn't find botuser #{params[:user]}")\r
157     end\r
158     perm = user.perm\r
159     str = []\r
160     perm.each { |k, val|\r
161       next if val.perm.empty?\r
162       case k\r
163       when :*\r
164         str << "on any channel: "\r
165       when :"?"\r
166         str << "in private: "\r
167       else\r
168         str << "on #{k}: "\r
169       end\r
170       sub = []\r
171       val.perm.each { |cmd, bool|\r
172         sub << (bool ? "+" : "-")\r
173         sub.last << cmd.to_s\r
174       }\r
175       str.last << sub.join(', ')\r
176     }\r
177     if str.empty?\r
178       m.reply "no permissions set for #{user.username}"\r
179     else\r
180       m.reply "permissions for #{user.username}:: #{str.join('; ')}"\r
181     end\r
182   end\r
183 \r
184   def get_botuser_for(user)\r
185     @bot.auth.irc_to_botuser(user)\r
186   end\r
187 \r
188   def get_botusername_for(user)\r
189     get_botuser_for(user).username\r
190   end\r
191 \r
192   def welcome(user)\r
193     "welcome, #{get_botusername_for(user)}"\r
194   end\r
195 \r
196   def auth_auth(m, params)\r
197     params[:botuser] = 'owner'\r
198     auth_login(m,params)\r
199   end\r
200 \r
201   def auth_login(m, params)\r
202     begin\r
203       case @bot.auth.login(m.source, params[:botuser], params[:password])\r
204       when true\r
205         m.reply welcome(m.source)\r
206         @bot.auth.set_changed\r
207       else\r
208         m.reply "sorry, can't do"\r
209       end\r
210     rescue => e\r
211       m.reply "couldn't login: #{e}"\r
212       raise\r
213     end\r
214   end\r
215 \r
216   def auth_autologin(m, params)\r
217     u = do_autologin(m.source)\r
218     case u.username\r
219     when 'everyone'\r
220       m.reply "I couldn't find anything to let you login automatically"\r
221     else\r
222       m.reply welcome(m.source)\r
223     end\r
224   end\r
225 \r
226   def do_autologin(user)\r
227     @bot.auth.autologin(user)\r
228   end\r
229 \r
230   def auth_whoami(m, params)\r
231     rep = ""\r
232     # if m.public?\r
233     #   rep << m.source.nick << ", "\r
234     # end\r
235     rep << "you are "\r
236     rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")\r
237     m.reply rep\r
238   end\r
239 \r
240   def help(cmd, topic="")\r
241     case cmd\r
242     when "login"\r
243       return "login [<botuser>] [<pass>]: logs in to the bot as botuser <botuser> with password <pass>. When using the full form, you must contact the bot in private. <pass> can be omitted if <botuser> allows login-by-mask and your netmask is among the known ones. if <botuser> is omitted too autologin will be attempted"\r
244     when "whoami"\r
245       return "whoami: names the botuser you're linked to"\r
246     when /^permission/\r
247       case topic\r
248       when "syntax"\r
249         return "a permission is specified as module::path::to::cmd; when you want to enable it, prefix it with +; when you want to disable it, prefix it with -; when using the +reset+ command, do not use any prefix"\r
250       when "set", "reset", "[re]set", "(re)set"\r
251         return "permissions [re]set <permission> [in <channel>] for <user>: sets or resets the permissions for botuser <user> in channel <channel> (use ? to change the permissions for private addressing)"\r
252       when "view"\r
253         return "permissions view [for <user>]: display the permissions for user <user>"\r
254       else\r
255         return "permission topics: syntax, (re)set, view"\r
256       end\r
257     when "user"\r
258       case topic\r
259       when "show"\r
260         return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"\r
261       when /^(en|dis)able/\r
262         return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"\r
263       when "set"\r
264         return "user set password <blah> : sets the user password to <blah>; passwords can only contain upper and lowercase letters and numbers, and must be at least 4 characters long"\r
265       when "add", "rm"\r
266         return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"\r
267       when "reset"\r
268         return "user reset <what> : resets <what> to the default values. <what> can be +netmasks+ (the list will be emptied), +autologin+ or +login-by-mask+ (will be reset to the default value) or +password+ (a new one will be generated and you'll be told in private)"\r
269       when "tell"\r
270         return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"\r
271       when "create"\r
272         return "user create <name> <password> : create botuser named <name> with password <password>. The password can be omitted, in which case a random one will be generated. The <name> should only contain alphanumeric characters and the underscore (_)"\r
273       when "list"\r
274         return "user list : lists all the botusers"\r
275       when "destroy"\r
276         return "user destroy <botuser> <password> : destroys <botuser>; this function #{Bold}must#{Bold} be called in two steps. On the first call, no password must be specified: <botuser> is then queued for destruction. On the second call, you must specify the correct password for <botuser>, and it will be destroyed. If you want to cancel the destruction, issue the command +user cancel destroy <botuser>+"\r
277       else\r
278         return "user topics: show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"\r
279       end\r
280     when "auth"\r
281       return "auth <masterpassword>: log in as the bot owner; other commands: login, whoami, permission syntax, permissions [re]set, permissions view, user"\r
282     else\r
283       return "auth commands: auth, login, whoami, permission[s], user"\r
284     end\r
285   end\r
286 \r
287   def need_args(cmd)\r
288     "sorry, I need more arguments to #{cmd}"\r
289   end\r
290 \r
291   def not_args(cmd, *stuff)\r
292     "I can only #{cmd} these: #{stuff.join(', ')}"\r
293   end\r
294 \r
295   def set_prop(botuser, prop, val)\r
296     k = prop.to_s.gsub("-","_")\r
297     botuser.send( (k + "=").to_sym, val)\r
298   end\r
299 \r
300   def reset_prop(botuser, prop)\r
301     k = prop.to_s.gsub("-","_")\r
302     botuser.send( ("reset_"+k).to_sym)\r
303   end\r
304 \r
305   def ask_bool_prop(botuser, prop)\r
306     k = prop.to_s.gsub("-","_")\r
307     botuser.send( (k + "?").to_sym)\r
308   end\r
309 \r
310   def auth_manage_user(m, params)\r
311     splits = params[:data]\r
312 \r
313     cmd = splits.first\r
314     return auth_whoami(m, params) if cmd.nil?\r
315 \r
316     botuser = get_botuser_for(m.source)\r
317     # By default, we do stuff on the botuser the irc user is bound to\r
318     butarget = botuser\r
319 \r
320     has_for = splits[-2] == "for"\r
321     butarget = @bot.auth.get_botuser(splits[-1]) if has_for\r
322     return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget\r
323     splits.slice!(-2,2) if has_for\r
324 \r
325     bools = [:autologin, :"login-by-mask"]\r
326     can_set = [:password]\r
327     can_addrm = [:netmasks]\r
328     can_reset = bools + can_set + can_addrm\r
329     can_show = can_reset + ["perms"]\r
330 \r
331     case cmd.to_sym\r
332 \r
333     when :show\r
334       return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")\r
335 \r
336       case splits[1]\r
337       when nil, "all"\r
338         props = can_reset\r
339       when "password"\r
340         if botuser != butarget\r
341           return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner\r
342           return m.reply("you can't ask for someone else's password")\r
343         end\r
344         return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?\r
345         return m.reply("the password for #{butarget.username} is #{butarget.password}")\r
346       else\r
347         props = splits[1..-1]\r
348       end\r
349 \r
350       str = []\r
351 \r
352       props.each { |arg|\r
353         k = arg.to_sym\r
354         next if k == :password\r
355         case k\r
356         when *bools\r
357           str << "can"\r
358           str.last << "not" unless ask_bool_prop(butarget, k)\r
359           str.last << " #{k}"\r
360         when :netmasks\r
361           str << "knows "\r
362           if butarget.netmasks.empty?\r
363             str.last << "no netmasks"\r
364           else\r
365             str.last << butarget.netmasks.join(", ")\r
366           end\r
367         end\r
368       }\r
369       return m.reply("#{butarget.username} #{str.join('; ')}")\r
370 \r
371     when :enable, :disable\r
372       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")\r
373       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
374 \r
375       return m.reply(need_args(cmd)) unless splits[1]\r
376       things = []\r
377       skipped = []\r
378       splits[1..-1].each { |a|\r
379         arg = a.to_sym\r
380         if bools.include?(arg)\r
381           set_prop(butarget, arg, cmd.to_sym == :enable)\r
382           things << a\r
383         else\r
384           skipped << a\r
385         end\r
386       }\r
387 \r
388       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?\r
389       if things.empty?\r
390         m.reply "I haven't changed anything"\r
391       else\r
392         @bot.auth.set_changed\r
393         return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })\r
394       end\r
395 \r
396     when :set\r
397       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
398       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
399 \r
400       return m.reply(need_args(cmd)) unless splits[1]\r
401       arg = splits[1].to_sym\r
402       return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)\r
403       argarg = splits[2]\r
404       return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg\r
405       if arg == :password && m.public?\r
406         return m.reply("is that a joke? setting the password in public?")\r
407       end\r
408       set_prop(butarget, arg, argarg)\r
409       @bot.auth.set_changed\r
410       auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })\r
411 \r
412     when :reset\r
413       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
414       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
415 \r
416       return m.reply(need_args(cmd)) unless splits[1]\r
417       things = []\r
418       skipped = []\r
419       splits[1..-1].each { |a|\r
420         arg = a.to_sym\r
421         if can_reset.include?(arg)\r
422           reset_prop(butarget, arg)\r
423           things << a\r
424         else\r
425           skipped << a\r
426         end\r
427       }\r
428 \r
429       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?\r
430       if things.empty?\r
431         m.reply "I haven't changed anything"\r
432       else\r
433         @bot.auth.set_changed\r
434         @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")\r
435         return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})\r
436       end\r
437 \r
438     when :add, :rm, :remove, :del, :delete\r
439       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
440       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
441 \r
442       arg = splits[1]\r
443       if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?\r
444         return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")\r
445       end\r
446 \r
447       method = cmd.to_sym == :add ? :add_netmask : :delete_netmask\r
448 \r
449       failed = []\r
450 \r
451       splits[2..-1].each { |mask|\r
452         begin\r
453           butarget.send(method, mask.to_irc_netmask(:server => @bot.server))\r
454         rescue\r
455           failed << mask\r
456         end\r
457       }\r
458       m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?\r
459       @bot.auth.set_changed\r
460       return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })\r
461 \r
462     else\r
463       m.reply "sorry, I don't know how to #{m.message}"\r
464     end\r
465   end\r
466 \r
467   def auth_tell_password(m, params)\r
468     user = params[:user]\r
469     begin\r
470       botuser = @bot.auth.get_botuser(params[:botuser])\r
471     rescue\r
472       return m.reply("coudln't find botuser #{params[:botuser]})")\r
473     end\r
474     m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner\r
475     msg = "the password for botuser #{botuser.username} is #{botuser.password}"\r
476     @bot.say user, msg\r
477     @bot.say m.source, "I told #{user} that " + msg\r
478   end\r
479 \r
480   def auth_create_user(m, params)\r
481     name = params[:name]\r
482     password = params[:password]\r
483     return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?\r
484     begin\r
485       bu = @bot.auth.create_botuser(name, password)\r
486       @bot.auth.set_changed\r
487     rescue => e\r
488       m.reply "failed to create #{name}: #{e}"\r
489       debug e.inspect + "\n" + e.backtrace.join("\n")\r
490       return\r
491     end\r
492     m.reply "created botuser #{bu.username}"\r
493   end\r
494 \r
495   def auth_list_users(m, params)\r
496     # TODO name regexp to filter results\r
497     list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']\r
498     if defined?(@destroy_q)\r
499       list.map! { |x|\r
500         @destroy_q.include?(x) ? x + " (queued for destruction)" : x\r
501       }\r
502     end\r
503     return m.reply("I have no botusers other than the default ones") if list.empty?\r
504     return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")\r
505   end\r
506 \r
507   def auth_destroy_user(m, params)\r
508     @destroy_q = [] unless defined?(@destroy_q)\r
509     buname = params[:name]\r
510     return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)\r
511     cancel = m.message.split[1] == 'cancel'\r
512     password = params[:password]\r
513 \r
514     buser_array = @bot.auth.save_array\r
515     buser_hash = buser_array.inject({}) { |h, u|\r
516       h[u[:username]] = u\r
517       h\r
518     }\r
519 \r
520     return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)\r
521 \r
522     if cancel\r
523       if @destroy_q.include?(buname)\r
524         @destroy_q.delete(buname)\r
525         m.reply "#{buname} removed from the destruction queue"\r
526       else\r
527         m.reply "#{buname} was not queued for destruction"\r
528       end\r
529       return\r
530     end\r
531 \r
532     if password.nil?\r
533       if @destroy_q.include?(buname)\r
534         rep = "#{buname} already queued for destruction"\r
535       else\r
536         @destroy_q << buname\r
537         rep = "#{buname} queued for destruction"\r
538       end\r
539       return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")\r
540     else\r
541       begin\r
542         return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)\r
543         return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password\r
544         buser_array.delete_if { |u|\r
545           u[:username] == buname\r
546         }\r
547         @destroy_q.delete(buname)\r
548         @bot.auth.load_array(buser_array, true)\r
549         @bot.auth.set_changed\r
550       rescue => e\r
551         return m.reply("failed: #{e}")\r
552       end\r
553       return m.reply("botuser #{buname} destroyed")\r
554     end\r
555 \r
556   end\r
557 \r
558   def auth_copy_ren_user(m, params)\r
559     source = Auth::BotUser.sanitize_username(params[:source])\r
560     dest = Auth::BotUser.sanitize_username(params[:dest])\r
561     return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4\r
562 \r
563     buser_array = @bot.auth.save_array\r
564     buser_hash = buser_array.inject({}) { |h, u|\r
565       h[u[:username]] = u\r
566       h\r
567     }\r
568 \r
569     return m.reply("no such botuser #{source}") unless buser_hash.keys.include?(source)\r
570     return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)\r
571 \r
572     copying = m.message.split[1] == "copy"\r
573     begin\r
574       if copying\r
575         h = {}\r
576         buser_hash[source].each { |k, val|\r
577           h[k] = val.dup\r
578         }\r
579       else\r
580         h = buser_hash[source]\r
581       end\r
582       h[:username] = dest\r
583       buser_array << h if copying\r
584 \r
585       @bot.auth.load_array(buser_array, true)\r
586       @bot.auth.set_changed\r
587     rescue => e\r
588       return m.reply("failed: #{e}")\r
589     end\r
590     return m.reply("botuser #{source} copied to #{dest}") if copying\r
591     return m.reply("botuser #{source} renamed to #{dest}")\r
592 \r
593   end\r
594 \r
595   def auth_export(m, params)\r
596 \r
597     exportfile = "#{@bot.botclass}/new-auth.users"\r
598 \r
599     what = params[:things]\r
600 \r
601     has_to = what[-2] == "to"\r
602     if has_to\r
603       exportfile = "#{@bot.botclass}/#{what[-1]}"\r
604       what.slice!(-2,2)\r
605     end\r
606 \r
607     what.delete("all")\r
608 \r
609     m.reply "selecting data to export ..."\r
610 \r
611     buser_array = @bot.auth.save_array\r
612     buser_hash = buser_array.inject({}) { |h, u|\r
613       h[u[:username]] = u\r
614       h\r
615     }\r
616 \r
617     if what.empty?\r
618       we_want = buser_hash\r
619     else\r
620       we_want = buser_hash.delete_if { |key, val|\r
621         not what.include?(key)\r
622       }\r
623     end\r
624 \r
625     m.reply "preparing data for export ..."\r
626     begin\r
627       yaml_hash = {}\r
628       we_want.each { |k, val|\r
629         yaml_hash[k] = {}\r
630         val.each { |kk, v|\r
631           case kk\r
632           when :username\r
633             next\r
634           when :netmasks\r
635             yaml_hash[k][kk] = []\r
636             v.each { |nm|\r
637               yaml_hash[k][kk] << {\r
638                 :fullform => nm.fullform,\r
639                 :casemap => nm.casemap.to_s\r
640               }\r
641             }\r
642           else\r
643             yaml_hash[k][kk] = v\r
644           end\r
645         }\r
646       }\r
647     rescue => e\r
648       m.reply "failed to prepare data: #{e}"\r
649       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
650       return\r
651     end\r
652 \r
653     m.reply "exporting to #{exportfile} ..."\r
654     begin\r
655       # m.reply yaml_hash.inspect\r
656       File.open(exportfile, "w") do |file|\r
657         file.puts YAML::dump(yaml_hash)\r
658       end\r
659     rescue => e\r
660       m.reply "failed to export users: #{e}"\r
661       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
662       return\r
663     end\r
664     m.reply "done"\r
665   end\r
666 \r
667   def auth_import(m, params)\r
668 \r
669     importfile = "#{@bot.botclass}/new-auth.users"\r
670 \r
671     what = params[:things]\r
672 \r
673     has_from = what[-2] == "from"\r
674     if has_from\r
675       importfile = "#{@bot.botclass}/#{what[-1]}"\r
676       what.slice!(-2,2)\r
677     end\r
678 \r
679     what.delete("all")\r
680 \r
681     m.reply "reading #{importfile} ..."\r
682     begin\r
683       yaml_hash = YAML::load_file(importfile)\r
684     rescue => e\r
685       m.reply "failed to import from: #{e}"\r
686       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
687       return\r
688     end\r
689 \r
690     # m.reply yaml_hash.inspect\r
691 \r
692     m.reply "selecting data to import ..."\r
693 \r
694     if what.empty?\r
695       we_want = yaml_hash\r
696     else\r
697       we_want = yaml_hash.delete_if { |key, val|\r
698         not what.include?(key)\r
699       }\r
700     end\r
701 \r
702     m.reply "parsing data from import ..."\r
703 \r
704     buser_hash = {}\r
705 \r
706     begin\r
707       yaml_hash.each { |k, val|\r
708         buser_hash[k] = { :username => k }\r
709         val.each { |kk, v|\r
710           case kk\r
711           when :netmasks\r
712             buser_hash[k][kk] = []\r
713             v.each { |nm|\r
714               buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)\r
715             }\r
716           else\r
717             buser_hash[k][kk] = v\r
718           end\r
719         }\r
720       }\r
721     rescue => e\r
722       m.reply "failed to parse data: #{e}"\r
723       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
724       return\r
725     end\r
726 \r
727     # m.reply buser_hash.inspect\r
728 \r
729     org_buser_array = @bot.auth.save_array\r
730     org_buser_hash = org_buser_array.inject({}) { |h, u|\r
731       h[u[:username]] = u\r
732       h\r
733     }\r
734 \r
735     # TODO we may want to do a(n optional) key-by-key merge\r
736     #\r
737     org_buser_hash.merge!(buser_hash)\r
738     new_buser_array = org_buser_hash.values\r
739     @bot.auth.load_array(new_buser_array, true)\r
740     @bot.auth.set_changed\r
741 \r
742     m.reply "done"\r
743   end\r
744 \r
745 end\r
746 \r
747 auth = AuthModule.new\r
748 \r
749 auth.map "user export *things",\r
750   :action => 'auth_export',\r
751   :defaults => { :things => ['all'] },\r
752   :auth_path => ':manage:fedex:'\r
753 \r
754 auth.map "user import *things",\r
755  :action => 'auth_import',\r
756  :auth_path => ':manage:fedex:'\r
757 \r
758 auth.map "user create :name :password",\r
759   :action => 'auth_create_user',\r
760   :defaults => {:password => nil},\r
761   :auth_path => ':manage:'\r
762 \r
763 auth.map "user [cancel] destroy :name :password",\r
764   :action => 'auth_destroy_user',\r
765   :defaults => { :password => nil },\r
766   :auth_path => ':manage::destroy:'\r
767 \r
768 auth.map "user copy :source [to] :dest",\r
769   :action => 'auth_copy_ren_user',\r
770   :auth_path => ':manage:'\r
771 \r
772 auth.map "user rename :source [to] :dest",\r
773   :action => 'auth_copy_ren_user',\r
774   :auth_path => ':manage:'\r
775 \r
776 auth.default_auth("user::manage", false)\r
777 \r
778 auth.map "user tell :user the password for :botuser",\r
779   :action => 'auth_tell_password',\r
780   :auth_path => '::'\r
781 \r
782 auth.map "user list",\r
783   :action => 'auth_list_users',\r
784   :auth_path => '::'\r
785 \r
786 auth.map "user *data",\r
787   :action => 'auth_manage_user'\r
788 \r
789 auth.default_auth("user", true)\r
790 auth.default_auth("edit::other", false)\r
791 \r
792 auth.map "whoami",\r
793   :action => 'auth_whoami',\r
794   :auth_path => '!*!'\r
795 \r
796 auth.map "auth :password",\r
797   :action => 'auth_auth',\r
798   :public => false,\r
799   :auth_path => '!login!'\r
800 \r
801 auth.map "login :botuser :password",\r
802   :action => 'auth_login',\r
803   :public => false,\r
804   :defaults => { :password => nil },\r
805   :auth_path => '!login!'\r
806 \r
807 auth.map "login :botuser",\r
808   :action => 'auth_login',\r
809   :auth_path => '!login!'\r
810 \r
811 auth.map "login",\r
812   :action => 'auth_autologin',\r
813   :auth_path => '!login!'\r
814 \r
815 auth.map "permissions set *args",\r
816   :action => 'auth_edit_perm',\r
817   :auth_path => ':edit::set:'\r
818 \r
819 auth.map "permissions reset *args",\r
820   :action => 'auth_edit_perm',\r
821   :auth_path => ':edit::reset:'\r
822 \r
823 auth.map "permissions view [for :user]",\r
824   :action => 'auth_view_perm',\r
825   :auth_path => '::'\r
826 \r
827 auth.default_auth('*', false)\r
828 \r