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