b1e8796607706a844cf9a33ecf1a46500acfebec
3 # Copyright (C) 2016 Ian Kelling
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # debian sets LANG=C when starting apache2.
19 # the envoding comment above fixes the internal encoding afaik,
21 # https://stackoverflow.com/questions/20521371/set-utf-8-as-default-for-ruby-1-9-3
22 # also note man ruby's -E arg.
23 Encoding
.default_external
= Encoding
::UTF_8
30 Dir
.chdir(File
.join(File
.dirname(__FILE__
), '..'))
40 x
= (<<EOF).split("\n")
43 Would you sleep better on a bed or a keyboard?
45 Are there more or less than one million people on the Earth?
47 Which of Lilly and Robert is more commonly a woman’s name?
49 Which word has fewer letters, adorable or fox?
51 What is the normal color of milk?
53 Which of Iceland and Turkey is an island nation?
55 Is a filesystem like a tree or a rose?
57 What character is a tilde?
59 Which is brighter, the moon or the sun?
61 Which is closer, the moon or the sun?
63 What language is this sentence written in?
65 What animal says "meow" and catches mice?
67 What animal quacks and has webbed feet?
69 Which are better fliers: worms or birds?
71 Which typically runs first: a kernel or a web browser?
81 captcha_q
= CAPTCHA
.sample
[0]
82 puts
"Content-type: text/html\n\n"
83 puts
skel("#{DN}/captcha", <<EOF, header: ' / <a href="/blog.html">blog</a> / captcha')
84 <p>Hello friend. I haven't read a post from #{IP}, and I only remember for a few months, so:</p>
87 <form action="/cgi/comment" method="post">
88 <input class="misc" type="text" name="url">
89 <input name="goto" type="hidden" value="#{GOTO}">
90 <input name="question" type="hidden" value="#{captcha_q}">
93 <textarea rows="10" name="comment" maxlength="1000">#{COMMENT_TXT}</textarea>
94 <input type="submit" value="Submit">
103 puts
"Content-type: text/plain\n\n"
112 puts
'Status: 302 Found'
113 puts
"Location: #{GOTO}#comment-section\n\n"
123 ###### begin error checking & arg parsing ######
127 if cgi
.has_key
?('goto')
134 if (cgi
.has_key
?('url') && cgi
['url'] != "") || ! cgi
.has_key
?('comment')
135 fail("comment not in form or url in form. cgi.params: #{cgi.params}")
138 COMMENT_TXT
= cgi
["comment"]
141 if COMMENT_TXT
.length
> 1000 or GOTO
.length
> 150
142 fail('length of comment or goto is too great')
147 if cgi
.has_key
?('answer') && cgi
.has_key
?('question')
148 if cgi
['answer'].downcase
!~
/^#{CAPTCHA.to_h[cgi['question']]}$/
157 Dir
.foreach('blog') do |entry
|
158 next if ['.','..'].any
? { |f
| f
== entry
}
159 if GOTO
== '/blog/' + entry
164 fail('goto entry not found') unless found
166 ######### end error checking & arg parsing ########
171 WHITELIST_CUTOFF
= NOW
- 4*DAY
174 ####### begin: state for ips we've seen before #######
177 [20, 60*60], # 60 min
178 [30, 60*60*24], # 1 day
179 [60, 60*60*24*7]] # 1 week
180 .each
do |max_posts
, date
|
182 if $db.execute(<<-SQL, [NOW - date])[0][0] > max_posts
183 select count(*) from c
184 where date > ? and ip = '#{IP}'
186 state
= 'rate_limited'
190 state
||= 'suspect' if $db.execute(<<-SQL)[0][0] > 0
191 select count(*) from c
192 where ip = '#{IP}' and (
194 state = 'rate_limited')
198 older_date
= NOW
- DAY
*2
199 last_moderated
= $db.execute(<<-SQL, [older_date])[-1]
201 where ip = '#{IP}' and (
202 state = 'moderated' or
203 (date < ? and (state = 'timed' or state = 'known')))
205 last_moderated
= last_moderated
[0] if last_moderated
206 last_good
= $db.execute(<<-SQL, [older_date])[-1]
208 where ip = '#{IP}' and (
210 (date < ? and (state = 'timed' or state = 'known')))
212 last_good
= last_good
[0] if last_good
213 if last_moderated
&& last_good
214 if last_good
> last_moderated
217 # these 2 waiting conditions are not actually needed,
218 # since waiting is the default, but meh.
227 ####### end: state for ips we've seen before #######
229 ####### begin: whitelist checking #########
230 glob
= "../blog/#{'?'*'YYYY-MM-DD-'.length}#{bn GOTO, '.*'}.md"
231 md_file
= Dir
[glob
][0]
234 post_date
= Time
.parse(b
[0..DATE_LEN
]).to_i
235 if post_date
> WHITELIST_CUTOFF
239 ###### end: whitelist checking ########
243 if state
!= 'known' && ! captchad
250 # # was posted a whitelist period, so automatically posted.
251 # # whitelist periods are per page times when legit comments are
252 # # much more likely than spam, so we automatically let comments through.
255 # # ip posted good comment before: either, one in picked state, or
256 # # a timed/known comment which is over 2 days old (I saw it and didn't remove
260 # # manually marked as a good comment, so publish it.
263 # # posting too much, consider them a spammer.
266 # # bad comment, but don't ban them
269 # # all comments from this ip dead, new comment's dont even go into the db.
272 # # waiting for manual moderation, get's posted automatically if there
273 # # is none in 24 hours
276 # # had a bad post in the past. does not
277 # # automatically get posted in time without moderation.
280 # any of the manual states
281 date
= $db.execute(<<-SQL)[0][0]
282 select max(date) from c where
283 state = 'moderated' or
288 # not the bad automatic states
290 select count(*) from c where
291 state != 'rate_limited' and
297 new_count
= $db.execute(query
+ 'and date > ?',date
)
299 new_count
= $db.execute(query
)
304 def send_email(opts
={})
305 opts
[:to] ||= ENV['USER']
306 opts
[:server] ||= 'localhost'
307 opts
[:from] ||= ENV['USER']
308 opts
[:from_alias] ||= ENV['USER']
309 opts
[:subject] ||= "test subject"
312 msg
= <<END_OF_MESSAGE
313 From: #{opts[:from_alias]} <#{opts[:from]}>
315 Subject: #{opts[:subject]}
320 Net
::SMTP.start(opts
[:server]) do |smtp
|
321 smtp
.send_message msg
, opts
[:from], opts
[:to]
324 send_email
:subject => 'new comments on iankelling.org'
327 $db.execute('insert into c values (NULL, ?, ?, ?, ?, ?)',