autumn 3.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. data/AUTHORS +11 -0
  2. data/CHANGELOG +567 -0
  3. data/MANIFEST +110 -0
  4. data/README +1114 -0
  5. data/README.textile +1153 -0
  6. data/Rakefile +75 -0
  7. data/autumn.gemspec +44 -0
  8. data/bin/autumn +11 -0
  9. data/lib/autumn.rb +8 -0
  10. data/lib/autumn/authentication.rb +238 -0
  11. data/lib/autumn/channel_leaf.rb +107 -0
  12. data/lib/autumn/coder.rb +166 -0
  13. data/lib/autumn/console_boot.rb +10 -0
  14. data/lib/autumn/ctcp.rb +250 -0
  15. data/lib/autumn/daemon.rb +207 -0
  16. data/lib/autumn/datamapper_hacks.rb +290 -0
  17. data/lib/autumn/foliater.rb +231 -0
  18. data/lib/autumn/formatting.rb +236 -0
  19. data/lib/autumn/generator.rb +231 -0
  20. data/lib/autumn/genesis.rb +190 -0
  21. data/lib/autumn/inheritable_attributes.rb +162 -0
  22. data/lib/autumn/leaf.rb +738 -0
  23. data/lib/autumn/log_facade.rb +49 -0
  24. data/lib/autumn/misc.rb +87 -0
  25. data/lib/autumn/resources/daemons/Anothernet.yml +3 -0
  26. data/lib/autumn/resources/daemons/AustHex.yml +29 -0
  27. data/lib/autumn/resources/daemons/Bahamut.yml +67 -0
  28. data/lib/autumn/resources/daemons/Dancer.yml +3 -0
  29. data/lib/autumn/resources/daemons/GameSurge.yml +3 -0
  30. data/lib/autumn/resources/daemons/IRCnet.yml +3 -0
  31. data/lib/autumn/resources/daemons/Ithildin.yml +7 -0
  32. data/lib/autumn/resources/daemons/KineIRCd.yml +56 -0
  33. data/lib/autumn/resources/daemons/PTlink.yml +6 -0
  34. data/lib/autumn/resources/daemons/QuakeNet.yml +20 -0
  35. data/lib/autumn/resources/daemons/RFC1459.yml +158 -0
  36. data/lib/autumn/resources/daemons/RFC2811.yml +16 -0
  37. data/lib/autumn/resources/daemons/RFC2812.yml +36 -0
  38. data/lib/autumn/resources/daemons/RatBox.yml +25 -0
  39. data/lib/autumn/resources/daemons/Ultimate.yml +24 -0
  40. data/lib/autumn/resources/daemons/Undernet.yml +6 -0
  41. data/lib/autumn/resources/daemons/Unreal.yml +110 -0
  42. data/lib/autumn/resources/daemons/_Other.yml +7 -0
  43. data/lib/autumn/resources/daemons/aircd.yml +33 -0
  44. data/lib/autumn/resources/daemons/bdq-ircd.yml +3 -0
  45. data/lib/autumn/resources/daemons/hybrid.yml +38 -0
  46. data/lib/autumn/resources/daemons/ircu.yml +67 -0
  47. data/lib/autumn/resources/daemons/tr-ircd.yml +8 -0
  48. data/lib/autumn/script.rb +74 -0
  49. data/lib/autumn/speciator.rb +165 -0
  50. data/lib/autumn/stem.rb +919 -0
  51. data/lib/autumn/stem_facade.rb +176 -0
  52. data/lib/autumn/tool/bin.rb +301 -0
  53. data/lib/autumn/tool/create.rb +48 -0
  54. data/lib/autumn/tool/project_creator.rb +110 -0
  55. data/lib/autumn/version.rb +3 -0
  56. data/lib/skel/Rakefile +163 -0
  57. data/lib/skel/config/global.yml +2 -0
  58. data/lib/skel/config/seasons/testing/database.yml +4 -0
  59. data/lib/skel/config/seasons/testing/leaves.yml +9 -0
  60. data/lib/skel/config/seasons/testing/season.yml +2 -0
  61. data/lib/skel/config/seasons/testing/stems.yml +10 -0
  62. data/lib/skel/leaves/administrator/README +20 -0
  63. data/lib/skel/leaves/administrator/controller.rb +67 -0
  64. data/lib/skel/leaves/administrator/views/autumn.txt.erb +1 -0
  65. data/lib/skel/leaves/administrator/views/reload.txt.erb +11 -0
  66. data/lib/skel/leaves/insulter/README +17 -0
  67. data/lib/skel/leaves/insulter/controller.rb +65 -0
  68. data/lib/skel/leaves/insulter/views/about.txt.erb +1 -0
  69. data/lib/skel/leaves/insulter/views/help.txt.erb +1 -0
  70. data/lib/skel/leaves/insulter/views/insult.txt.erb +1 -0
  71. data/lib/skel/leaves/scorekeeper/README +34 -0
  72. data/lib/skel/leaves/scorekeeper/config.yml +2 -0
  73. data/lib/skel/leaves/scorekeeper/controller.rb +104 -0
  74. data/lib/skel/leaves/scorekeeper/helpers/general.rb +64 -0
  75. data/lib/skel/leaves/scorekeeper/models/channel.rb +12 -0
  76. data/lib/skel/leaves/scorekeeper/models/person.rb +14 -0
  77. data/lib/skel/leaves/scorekeeper/models/pseudonym.rb +11 -0
  78. data/lib/skel/leaves/scorekeeper/models/score.rb +14 -0
  79. data/lib/skel/leaves/scorekeeper/tasks/stats.rake +17 -0
  80. data/lib/skel/leaves/scorekeeper/views/about.txt.erb +1 -0
  81. data/lib/skel/leaves/scorekeeper/views/change.txt.erb +5 -0
  82. data/lib/skel/leaves/scorekeeper/views/history.txt.erb +11 -0
  83. data/lib/skel/leaves/scorekeeper/views/points.txt.erb +5 -0
  84. data/lib/skel/leaves/scorekeeper/views/usage.txt.erb +1 -0
  85. data/lib/skel/log/README +1 -0
  86. data/lib/skel/script/console +28 -0
  87. data/lib/skel/script/destroy +48 -0
  88. data/lib/skel/script/generate +48 -0
  89. data/lib/skel/shared/README +1 -0
  90. data/lib/skel/tmp/README +1 -0
  91. data/spec/authentication_spec.rb +328 -0
  92. data/spec/channel_leaf_spec.rb +142 -0
  93. data/spec/coder_spec.rb +146 -0
  94. data/spec/ctcp_spec.rb +222 -0
  95. data/spec/daemon_spec.rb +202 -0
  96. data/spec/datamapper_hacks_spec.rb +164 -0
  97. data/tasks/authors.rake +30 -0
  98. data/tasks/changelog.rake +18 -0
  99. data/tasks/copyright.rake +21 -0
  100. data/tasks/doc.rake +7 -0
  101. data/tasks/gem.rake +23 -0
  102. data/tasks/gem_installer.rake +76 -0
  103. data/tasks/install_dependencies.rake +6 -0
  104. data/tasks/manifest.rake +4 -0
  105. data/tasks/rcov.rake +23 -0
  106. data/tasks/release.rake +52 -0
  107. data/tasks/reversion.rake +8 -0
  108. data/tasks/setup.rake +24 -0
  109. data/tasks/spec.rake +7 -0
  110. data/tasks/yard.rake +4 -0
  111. metadata +188 -0
@@ -0,0 +1,75 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
2
+
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/gempackagetask'
6
+ require 'time'
7
+ require 'date'
8
+
9
+ PROJECT_SPECS = FileList[
10
+ 'spec/*.rb',
11
+ 'lib/skel/spec/*.rb'
12
+ ]
13
+
14
+ PROJECT_MODULE = 'Autumn'
15
+ PROJECT_README = 'README.markdown'
16
+ #PROJECT_RUBYFORGE_GROUP_ID = 3034
17
+ PROJECT_COPYRIGHT = [
18
+ "# Copyright (c) #{Time.now.year} Tim Morgan riscfuture@gmail.com",
19
+ "# Distributed under the same terms as the Ruby license.",
20
+ "# Portions of this code are copyright ©2004 David Heinemeier Hansson; ",
21
+ "# please see libs/inheritable_attributes.rb for more information.",
22
+ "# Autumn's package tasks are copyright 2009 Michael Fellinger m.fellinger@gmail.com;",
23
+ "# Distributed under the terms of the Ramaze license"
24
+ ]
25
+
26
+ # To release the monthly version do:
27
+ # $ PROJECT_VERSION=2009.03 rake release
28
+ IGNORE_FILES = [/\.gitignore/]
29
+
30
+ GEMSPEC = Gem::Specification.new{|s|
31
+ s.name = 'autumn'
32
+ s.author = "Tim 'riscfuture' Morgan"
33
+ s.summary = "Autumn is a simple and modular irc framework"
34
+ s.description = s.summary
35
+ s.email = 'bougy.man@gmail.com'
36
+ s.homepage = 'http://github.com/bougyman/autumn'
37
+ s.platform = Gem::Platform::RUBY
38
+ s.version = (ENV['PROJECT_VERSION'] || Date.today.strftime("%Y.%m.%d"))
39
+ s.files = `git ls-files`.split("\n").sort.reject { |f| IGNORE_FILES.detect { |exp| f.match(exp) } }
40
+ s.has_rdoc = true
41
+ s.require_path = 'lib'
42
+ s.bindir = "bin"
43
+ s.executables = ["autumn"]
44
+ s.rubyforge_project = "pastr"
45
+
46
+ s.add_dependency('facets')
47
+ s.add_dependency('anise')
48
+
49
+ s.post_install_message = <<MESSAGE.strip
50
+ ============================================================
51
+
52
+ Thank you for installing Autumn!
53
+ You can now create a new bot:
54
+ # autumn create yourbot
55
+
56
+ ============================================================
57
+ MESSAGE
58
+ }
59
+
60
+ Dir['tasks/*.rake'].each{|f| import(f) }
61
+
62
+ task :default => [:spec]
63
+
64
+ CLEAN.include %w[
65
+ **/.*.sw?
66
+ *.gem
67
+ .config
68
+ **/*~
69
+ **/{data.db,cache.yaml}
70
+ *.yaml
71
+ pkg
72
+ rdoc
73
+ ydoc
74
+ *coverage*
75
+ ]
@@ -0,0 +1,44 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{autumn}
5
+ s.version = "3.1.8"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Tim 'riscfuture' Morgan"]
9
+ s.date = %q{2009-05-03}
10
+ s.default_executable = %q{autumn}
11
+ s.description = %q{Autumn is a simple and modular irc framework}
12
+ s.email = %q{bougy.man@gmail.com}
13
+ s.executables = ["autumn"]
14
+ s.files = ["AUTHORS", "CHANGELOG", "MANIFEST", "README", "README.textile", "Rakefile", "autumn.gemspec", "bin/autumn", "lib/autumn.rb", "lib/autumn/authentication.rb", "lib/autumn/channel_leaf.rb", "lib/autumn/coder.rb", "lib/autumn/console_boot.rb", "lib/autumn/ctcp.rb", "lib/autumn/daemon.rb", "lib/autumn/datamapper_hacks.rb", "lib/autumn/foliater.rb", "lib/autumn/formatting.rb", "lib/autumn/generator.rb", "lib/autumn/genesis.rb", "lib/autumn/inheritable_attributes.rb", "lib/autumn/leaf.rb", "lib/autumn/log_facade.rb", "lib/autumn/misc.rb", "lib/autumn/resources/daemons/Anothernet.yml", "lib/autumn/resources/daemons/AustHex.yml", "lib/autumn/resources/daemons/Bahamut.yml", "lib/autumn/resources/daemons/Dancer.yml", "lib/autumn/resources/daemons/GameSurge.yml", "lib/autumn/resources/daemons/IRCnet.yml", "lib/autumn/resources/daemons/Ithildin.yml", "lib/autumn/resources/daemons/KineIRCd.yml", "lib/autumn/resources/daemons/PTlink.yml", "lib/autumn/resources/daemons/QuakeNet.yml", "lib/autumn/resources/daemons/RFC1459.yml", "lib/autumn/resources/daemons/RFC2811.yml", "lib/autumn/resources/daemons/RFC2812.yml", "lib/autumn/resources/daemons/RatBox.yml", "lib/autumn/resources/daemons/Ultimate.yml", "lib/autumn/resources/daemons/Undernet.yml", "lib/autumn/resources/daemons/Unreal.yml", "lib/autumn/resources/daemons/_Other.yml", "lib/autumn/resources/daemons/aircd.yml", "lib/autumn/resources/daemons/bdq-ircd.yml", "lib/autumn/resources/daemons/hybrid.yml", "lib/autumn/resources/daemons/ircu.yml", "lib/autumn/resources/daemons/tr-ircd.yml", "lib/autumn/script.rb", "lib/autumn/speciator.rb", "lib/autumn/stem.rb", "lib/autumn/stem_facade.rb", "lib/autumn/tool/bin.rb", "lib/autumn/tool/create.rb", "lib/autumn/tool/project_creator.rb", "lib/autumn/version.rb", "lib/skel/Rakefile", "lib/skel/config/global.yml", "lib/skel/config/seasons/testing/database.yml", "lib/skel/config/seasons/testing/leaves.yml", "lib/skel/config/seasons/testing/season.yml", "lib/skel/config/seasons/testing/stems.yml", "lib/skel/leaves/administrator/README", "lib/skel/leaves/administrator/controller.rb", "lib/skel/leaves/administrator/views/autumn.txt.erb", "lib/skel/leaves/administrator/views/reload.txt.erb", "lib/skel/leaves/insulter/README", "lib/skel/leaves/insulter/controller.rb", "lib/skel/leaves/insulter/views/about.txt.erb", "lib/skel/leaves/insulter/views/help.txt.erb", "lib/skel/leaves/insulter/views/insult.txt.erb", "lib/skel/leaves/scorekeeper/README", "lib/skel/leaves/scorekeeper/config.yml", "lib/skel/leaves/scorekeeper/controller.rb", "lib/skel/leaves/scorekeeper/helpers/general.rb", "lib/skel/leaves/scorekeeper/models/channel.rb", "lib/skel/leaves/scorekeeper/models/person.rb", "lib/skel/leaves/scorekeeper/models/pseudonym.rb", "lib/skel/leaves/scorekeeper/models/score.rb", "lib/skel/leaves/scorekeeper/tasks/stats.rake", "lib/skel/leaves/scorekeeper/views/about.txt.erb", "lib/skel/leaves/scorekeeper/views/change.txt.erb", "lib/skel/leaves/scorekeeper/views/history.txt.erb", "lib/skel/leaves/scorekeeper/views/points.txt.erb", "lib/skel/leaves/scorekeeper/views/usage.txt.erb", "lib/skel/log/README", "lib/skel/script/console", "lib/skel/script/destroy", "lib/skel/script/generate", "lib/skel/shared/README", "lib/skel/tmp/README", "spec/authentication_spec.rb", "spec/channel_leaf_spec.rb", "spec/coder_spec.rb", "spec/ctcp_spec.rb", "spec/daemon_spec.rb", "spec/datamapper_hacks_spec.rb", "tasks/authors.rake", "tasks/changelog.rake", "tasks/copyright.rake", "tasks/doc.rake", "tasks/gem.rake", "tasks/gem_installer.rake", "tasks/install_dependencies.rake", "tasks/manifest.rake", "tasks/rcov.rake", "tasks/release.rake", "tasks/reversion.rake", "tasks/setup.rake", "tasks/spec.rake", "tasks/yard.rake"]
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://github.com/bougyman/autumn}
17
+ s.post_install_message = %q{============================================================
18
+
19
+ Thank you for installing Autumn!
20
+ You can now create a new bot:
21
+ # autumn create yourbot
22
+
23
+ ============================================================}
24
+ s.require_paths = ["lib"]
25
+ s.rubyforge_project = %q{pastr}
26
+ s.rubygems_version = %q{1.3.1}
27
+ s.summary = %q{Autumn is a simple and modular irc framework}
28
+
29
+ if s.respond_to? :specification_version then
30
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
31
+ s.specification_version = 2
32
+
33
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
34
+ s.add_runtime_dependency(%q<facets>, [">= 0"])
35
+ s.add_runtime_dependency(%q<anise>, [">= 0"])
36
+ else
37
+ s.add_dependency(%q<facets>, [">= 0"])
38
+ s.add_dependency(%q<anise>, [">= 0"])
39
+ end
40
+ else
41
+ s.add_dependency(%q<facets>, [">= 0"])
42
+ s.add_dependency(%q<anise>, [">= 0"])
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require "pathname"
4
+ require Pathname.new(__FILE__).expand_path.dirname.join("..", "lib", "autumn")
5
+ require "autumn/tool/bin"
6
+ rescue LoadError
7
+ require "rubygems"
8
+ require "autumn"
9
+ require "autumn/tool/bin"
10
+ end
11
+ Autumn::Tool::Bin::Cmd.run(ARGV)
@@ -0,0 +1,8 @@
1
+ require "pathname"
2
+ module Autumn
3
+ unless Autumn.const_defined?("ROOT")
4
+ ROOT = Pathname.new(__FILE__).dirname.expand_path
5
+ end
6
+ end
7
+ $LOAD_PATH.unshift Autumn::ROOT
8
+ require "autumn/version"
@@ -0,0 +1,238 @@
1
+ # Defines the Autumn::Authentication class, which includes different
2
+ # authentication strategies available to leaves.
3
+
4
+ module Autumn
5
+
6
+ # Defines classes which each encapsulate a different strategy for
7
+ # authentication. When the +authentication+ option is specified (see the
8
+ # Autumn::Leaf class), the options given are used to choose the correct class
9
+ # within this module to serve as the authenticator for that leaf.
10
+ #
11
+ # These authentication strategies are used to ensure only authorized users
12
+ # have access to protected commands. Leaf authors can designate certain
13
+ # commands as protected.
14
+ #
15
+ # = Writing Your Own Authenticators
16
+ #
17
+ # When the Autumn::Leaf#authenticate method is called, it converts the symbol
18
+ # from snake_case to CamelCase, and looks for a class in this model. Thus, a
19
+ # call to <tt>authenticate :hostname</tt> would look for a class
20
+ # Autumn::Authentication::Hostname.
21
+ #
22
+ # To define your own authenticator, subclass Autumn::Authentication::Base as a
23
+ # new class in the Autumn::Authentication module. Implement the methods
24
+ # defined in the Autumn::Authentication::Base class docs, then adjust your
25
+ # configuration to use your new authenticator.
26
+
27
+ module Authentication
28
+
29
+ # The basic subclass for all authenticators. If you wish to write your own
30
+ # authenticator, you must subclass this class. You must at a minimum
31
+ # override the authenticate method. You should also override the initialize
32
+ # method if you need to store any options or other data for later use.
33
+ #
34
+ # The authentication module will become a stem listener, so see
35
+ # Autumn::Stem#add_listener for information on other methods you can
36
+ # implement.
37
+
38
+ class Base
39
+
40
+ # Stores the options for this authenticator and configures it for use.
41
+
42
+ def initialize(options={})
43
+ raise "You can only instantiate subclasses of this class."
44
+ end
45
+
46
+ # Returns true if the user is authorized, false if not. +sender+ is a
47
+ # sender hash as defined in the Autumn::Stem docs.
48
+
49
+ def authenticate(stem, channel, sender, leaf)
50
+ raise "Subclasses must override the Autumn::Authentication::Base#authenticate method."
51
+ end
52
+
53
+ # Returns a string to be displayed to a user who is not authorized to
54
+ # perform a command. Override this method to provide more specific hints
55
+ # to a user on what he can do to authorize himself (e.g., "Tell me your
56
+ # password").
57
+
58
+ def unauthorized
59
+ "You must be an administrator for this bot to do that."
60
+ end
61
+ end
62
+
63
+ # Authenticates users by their privilege level in the channel they ran the
64
+ # command in.
65
+ #
66
+ # This is a quick, configuration-free way of protecting your leaf, so long
67
+ # as you trust the ops in your channel.
68
+
69
+ class Op < Base
70
+
71
+ # Creates a new authenticator. Pass a list of allowed privileges (as
72
+ # symbols) for the +privileges+ option. By default this class accepts ops,
73
+ # admins, and channel owners/founders as authorized.
74
+
75
+ def initialize(options={})
76
+ @privileges = options[:privileges]
77
+ @privileges ||= [ :operator, :oper, :op, :admin, :founder, :channel_owner ]
78
+ end
79
+
80
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
81
+ # Returns true if the sender has any of the privileges listed below
82
+ not (@privileges & [ stem.privilege(channel, sender) ].flatten).empty?
83
+ end
84
+
85
+ def unauthorized # :nodoc:
86
+ "You must be an op to do that."
87
+ end
88
+ end
89
+
90
+ # Authenticates by IRC nick. A list of allowed nicks is built on
91
+ # initialization, and anyone with that nick is allowed to use restricted
92
+ # commands.
93
+ #
94
+ # This is the most obvious approach to authentication, but it is hardly
95
+ # secure. Anyone can change their nick to an admin's nick once that admin
96
+ # logs out.
97
+
98
+ class Nick < Base
99
+
100
+ # Creates a new authenticator. Pass a single nick for the +nick+ option or
101
+ # an array of allowed nicks for the +nicks+ option. If neither option is
102
+ # set, an exception is raised.
103
+
104
+ def initialize(options={})
105
+ @nicks = options[:nick]
106
+ @nicks ||= options[:nicks]
107
+ raise "You must give the nick of an administrator to use nick-based authentication." if @nicks.nil?
108
+ @nicks = [ @nicks ] if @nicks.kind_of? String
109
+ end
110
+
111
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
112
+ @nicks.include? sender[:nick]
113
+ end
114
+ end
115
+
116
+ # Authenticates by the host portion of an IRC message. A hostmask is used to
117
+ # match the relevant portion of the address with a whitelist of accepted
118
+ # host addresses.
119
+ #
120
+ # This method can be a secure way of preventing unauthorized access if you
121
+ # choose an appropriately narrow hostmask. However, you must configure in
122
+ # advance the computers you may want to administrate your leaves from.
123
+
124
+ class Hostname < Base
125
+
126
+ # Creates a new authenticator. You provide a hostmask via the +hostmask+
127
+ # option -- either a Regexp with one capture (that captures the portion of
128
+ # the hostmask you are interested in), or a Proc, which takes a host as an
129
+ # argument and returns true if the host is authorized, false if not. If
130
+ # the +hostmask+ option is not provided, a standard hostmask regexp will
131
+ # be used. This regexp strips everything left of the first period; for the
132
+ # example hostmask "wsd1.ca.widgetcom.net", it would return
133
+ # "ca.widgetcom.net" to be used for comparison.
134
+ #
135
+ # You also provide an authorized host with the +host+ option, or a list of
136
+ # such hosts with the +hosts+ option. If neither is given, an exception is
137
+ # raised.
138
+
139
+ def initialize(options={})
140
+ @hostmask = options[:hostmask]
141
+ @hostmask ||= /^.+?\.(.+)$/
142
+ @hostmask = @hostmask.to_rx(false) if @hostmask.kind_of? String
143
+ if @hostmask.kind_of? Regexp then
144
+ mask = @hostmask
145
+ @hostmask = lambda do |host|
146
+ if matches = host.match(mask) then matches[1] else nil end
147
+ end
148
+ end
149
+
150
+ @hosts = options[:host]
151
+ @hosts ||= options[:hosts]
152
+ raise "You must give the host address of an administrator to use nick-based authentication." unless @hosts
153
+ @hosts = [ @hosts ] unless @hosts.kind_of? Array
154
+ end
155
+
156
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
157
+ @hosts.include? @hostmask.call(sender[:host])
158
+ end
159
+ end
160
+
161
+ # Authenticates by a password provided in secret. When a user PRIVMSG's the
162
+ # leaf the correct password, the leaf adds that user's nick to a list of
163
+ # authorized nicks. These credentials expire when the person changes his
164
+ # nick, logs out, leaves the channel, etc. They also expire if a certain
165
+ # amount of time passes without running any protected commands.
166
+
167
+ class Password < Base
168
+ # The default period of time that must occur with no use of protected
169
+ # commands after which a user's credentials expire.
170
+ DEFAULT_EXPIRE_TIME = 5*60
171
+
172
+ # Creates a new authenticator. You provide a valid password with the
173
+ # +password+ option. If that option is not provided, an exception is
174
+ # raised. You can pass a number of seconds to the +expire_time+ option;
175
+ # this is the amount of time that must pass with no protected commands for
176
+ # a nick's authorization to expire. If the +expire_time+ option is not
177
+ # given, a default value of five minutes is used.
178
+
179
+ def initialize(options={})
180
+ @password = options[:password]
181
+ @expire_time = options[:expire_time]
182
+ @expire_time ||= DEFAULT_EXPIRE_TIME
183
+ raise "You must provide a password to use password-based authentication" unless @password
184
+ @authorized_nicks = Hash.new { |hsh, key| hsh[key] = Set.new }
185
+ @last_protected_action = Hash.new { |hsh, key| hsh[key] = Hash.new(Time.at(0)) }
186
+ @an_lock = Mutex.new
187
+ end
188
+
189
+ def irc_privmsg_event(stem, sender, arguments) # :nodoc:
190
+ if arguments[:recipient] and arguments[:message] == @password then
191
+ @an_lock.synchronize do
192
+ @authorized_nicks[stem] << sender[:nick]
193
+ @last_protected_action[stem][sender[:nick]] = Time.now
194
+ #TODO values are not always deleted; this hash has the possibility to slowly grow and consume more memory
195
+ end
196
+ stem.message "Your password has been accepted, and you are now authorized.", sender[:nick]
197
+ end
198
+ end
199
+
200
+ def irc_nick_event(stem, sender, arguments) # :nodoc:
201
+ @an_lock.synchronize do
202
+ revoke stem, sender[:nick]
203
+ revoke stem, arguments[:nick]
204
+ end
205
+ end
206
+
207
+ def irc_kick_event(stem, sender, arguments) # :nodoc:
208
+ @an_lock.synchronize { revoke stem, arguments[:nick] }
209
+ end
210
+
211
+ def irc_quit_event(stem, sender, arguments) # :nodoc:
212
+ @an_lock.synchronize { revoke stem, sender[:nick] }
213
+ end
214
+
215
+ def authenticate(stem, channel, sender, leaf) # :nodoc:
216
+ @an_lock.synchronize do
217
+ if Time.now - @last_protected_action[stem][sender[:nick]] > @expire_time then
218
+ revoke stem, sender[:nick]
219
+ else
220
+ @last_protected_action[stem][sender[:nick]] = Time.now
221
+ end
222
+ @authorized_nicks[stem].include? sender[:nick]
223
+ end
224
+ end
225
+
226
+ def unauthorized # :nodoc:
227
+ "You must authenticate with an administrator password to do that."
228
+ end
229
+
230
+ private
231
+
232
+ def revoke(stem, nick)
233
+ @authorized_nicks[stem].delete nick
234
+ @last_protected_action[stem].delete nick
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,107 @@
1
+ # Defines the Autumn::ChannelLeaf class, a subclass of Autumn::Leaf that
2
+ # selectively ignores channels.
3
+
4
+ module Autumn
5
+
6
+ # A special kind of leaf that only responds to messages sent to certain
7
+ # channels. Leaves that subclass ChannelLeaf can, in their config, specify a
8
+ # +channels+ option that narrows down which channels the leaf listens to. The
9
+ # leaf will not invoke the hook methods nor the <tt>*_command</tt> methods for
10
+ # IRC events that are not associated with those channels. It will respond to
11
+ # global, non-channel-specific events as well.
12
+ #
13
+ # You can combine multiple ChannelLeaf subclasses in one Stem to allow you to
14
+ # run two leaves off of one nick, but have the nick running different leaves
15
+ # in different channels.
16
+ #
17
+ # The +channels+ option should be a list of stems, and for each stem, a valid
18
+ # channel. For example, if you ran your leaf on two servers, your stems.yml
19
+ # file might look like:
20
+ #
21
+ # GamingServer:
22
+ # channels: fishinggames, games, drivinggames
23
+ # [...]
24
+ # FishingServer:
25
+ # channels: fishinggames, flyfishing
26
+ # [...]
27
+ #
28
+ # Now let's say you had a trivia leaf that asked questions about fishing
29
+ # games. You'd want to run that leaf on the "#fishinggames" channel of each
30
+ # server, and the "#games" channel of the GamingServer, but not the other
31
+ # channels. (Perhaps your Stem was also running other leaves relevant to those
32
+ # channels.) You'd set up your leaves.yml file like so:
33
+ #
34
+ # FishingGamesTrivia:
35
+ # channels:
36
+ # GamingServer:
37
+ # - fishinggames
38
+ # - games
39
+ # FishingServer: fishinggames
40
+ # [...]
41
+ #
42
+ # Now your leaf will only respond to messages relevant to the specified server
43
+ # channels (as well as global messages).
44
+ #
45
+ # Interception and filtering of messages is done at the _leaf_ level, not the
46
+ # _stem_ level. Therefore, for instance, if you override
47
+ # +someone_did_join_channel+, it will only be called for the appropriate
48
+ # channels; however, if you implement +irc_join_event+, it will still be
49
+ # called for all channels the stem is in.
50
+
51
+ class ChannelLeaf < Leaf
52
+ # The IRC channels that this leaf is responding to, mapped to server names.
53
+ attr :channels
54
+
55
+ # Creates a new instance. (See the Leaf class for more information.)
56
+
57
+ def will_start_up
58
+ @channels = Hash.new
59
+ @options[:channels] ||= Hash.new
60
+ @options[:channels].each do |server, chans|
61
+ stem = Foliater.instance.stems[server]
62
+ raise "Unknown stem #{server}" unless stem
63
+ chans = [ chans ] if chans.kind_of? String
64
+ @channels[stem] = chans.map { |chan| stem.normalized_channel_name chan }
65
+ end
66
+ super
67
+ end
68
+
69
+ def irc_privmsg_event(stem, sender, arguments) # :nodoc:
70
+ super if arguments[:channel].nil? or listening?(stem, arguments[:channel])
71
+ end
72
+
73
+ def irc_join_event(stem, sender, arguments) # :nodoc:
74
+ super if listening?(stem, arguments[:channel])
75
+ end
76
+
77
+ def irc_part_event(stem, sender, arguments) # :nodoc:
78
+ super if listening?(stem, arguments[:channel])
79
+ end
80
+
81
+ def irc_mode_event(stem, sender, arguments) # :nodoc:
82
+ super if arguments[:channel].nil? or listening?(stem, arguments[:channel])
83
+ end
84
+
85
+ def irc_topic_event(stem, sender, arguments) # :nodoc:
86
+ super if listening?(stem, arguments[:channel])
87
+ end
88
+
89
+ def irc_invite_event(stem, sender, arguments) # :nodoc:
90
+ super if listening?(stem, arguments[:channel]) or not stem.channels.include? arguments[:channel]
91
+ end
92
+
93
+ def irc_kick_event(stem, sender, arguments) # :nodoc:
94
+ super if listening?(stem, arguments[:channel])
95
+ end
96
+
97
+ def irc_notice_event(stem, sender, arguments) # :nodoc:
98
+ super if arguments[:channel].nil? or listening?(stem, arguments[:channel])
99
+ end
100
+
101
+ private
102
+
103
+ def listening?(stem, channel)
104
+ @channels.include? stem and @channels[stem].include? channel
105
+ end
106
+ end
107
+ end