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
29 Dir
.chdir(File
.join(File
.dirname(__FILE__
), '..'))
39 x
= (<<EOF).split("\n")
42 Would you sleep better on a bed or a keyboard?
44 Are there more or less than one million people on the Earth?
46 Which of Lilly and Robert is more commonly a woman’s name?
48 Which word has fewer letters, adorable or fox?
50 What is the normal color of milk?
52 Which of Iceland and Turkey is an island nation?
54 Is a filesystem like a tree or a rose?
56 What character is a tilde?
58 Which is brighter, the moon or the sun?
60 Which is closer, the moon or the sun?
62 What language is this sentence written in?
64 What animal says "meow" and catches mice?
66 What animal quacks and has webbed feet?
68 Which are better fliers: worms or birds?
70 Which typically runs first: a kernel or a web browser?
80 captcha_q
= CAPTCHA
.sample
[0]
81 puts
"Content-type: text/html\n\n"
82 puts
skel("#{DN}/captcha", <<EOF, header: ' / <a href="/blog.html">blog</a> / captcha')
83 <p>Hello friend. I haven't read a post from #{IP}, and I only remember for a few months, so:</p>
86 <form action="/cgi/comment" method="post">
87 <input class="misc-comment-input" type="text" name="url">
88 <input name="goto" type="hidden" value="#{GOTO}">
89 <input name="question" type="hidden" value="#{captcha_q}">
92 <textarea rows="10" name="comment" maxlength="1000">#{COMMENT_TXT}</textarea>
93 <input type="submit" value="Submit">
102 puts
"Content-type: text/plain\n\n"
111 puts
'Status: 302 Found'
112 puts
"Location: #{GOTO}#comment-section\n\n"
122 ###### begin error checking & arg parsing ######
126 if cgi
.has_key
?('goto')
133 if (cgi
.has_key
?('url') && cgi
['url'] != "") || ! cgi
.has_key
?('comment')
134 fail("comment not in form or url in form. cgi.params: #{cgi.params}")
137 COMMENT_TXT
= cgi
["comment"]
140 if COMMENT_TXT
.length
> 1000 or GOTO
.length
> 150
141 fail('length of comment or goto is too great')
144 if COMMENT_TXT
.length
<= 2 or COMMENT_TXT
=~
/\A\s*\Z/
145 fail('not enough content in comment')
150 if cgi
.has_key
?('answer') && cgi
.has_key
?('question')
151 if cgi
['answer'].downcase
!~
/^#{CAPTCHA.to_h[cgi['question']]}$/
160 Dir
.foreach('blog') do |entry
|
161 next if ['.','..'].any
? { |f
| f
== entry
}
162 if GOTO
== '/blog/' + entry
167 fail('goto entry not found') unless found
169 ######### end error checking & arg parsing ########
174 WHITELIST_CUTOFF
= NOW
- 4*DAY
177 ####### begin: state for ips we've seen before #######
180 [20, 60*60], # 60 min
181 [30, 60*60*24], # 1 day
182 [60, 60*60*24*7]] # 1 week
183 .each
do |max_posts
, date
|
185 if $db.execute(<<-SQL, [NOW - date])[0][0] > max_posts
186 select count(*) from c
187 where date > ? and ip = '#{IP}'
189 state
= 'rate_limited'
193 state
||= 'suspect' if $db.execute(<<-SQL)[0][0] > 0
194 select count(*) from c
195 where ip = '#{IP}' and (
197 state = 'rate_limited')
201 older_date
= NOW
- DAY
*2
202 last_moderated
= $db.execute(<<-SQL, [older_date])[-1]
204 where ip = '#{IP}' and (
205 state = 'moderated' or
206 (date < ? and (state = 'timed' or state = 'known')))
208 last_moderated
= last_moderated
[0] if last_moderated
209 last_good
= $db.execute(<<-SQL, [older_date])[-1]
211 where ip = '#{IP}' and (
213 (date < ? and (state = 'timed' or state = 'known')))
215 last_good
= last_good
[0] if last_good
216 if last_moderated
&& last_good
217 if last_good
> last_moderated
220 # these 2 waiting conditions are not actually needed,
221 # since waiting is the default, but meh.
230 ####### end: state for ips we've seen before #######
232 ####### begin: whitelist checking #########
233 glob
= "../blog/#{'?'*'YYYY-MM-DD-'.length}#{bn GOTO, '.*'}.md"
234 md_file
= Dir
[glob
][0]
237 post_date
= Time
.parse(b
[0..DATE_LEN
]).to_i
238 if post_date
> WHITELIST_CUTOFF
242 ###### end: whitelist checking ########
246 if state
!= 'known' && ! captchad
253 # # was posted a whitelist period, so automatically posted.
254 # # whitelist periods are per page times when legit comments are
255 # # much more likely than spam, so we automatically let comments through.
258 # # ip posted good comment before: either, one in picked state, or
259 # # a timed/known comment which is over 2 days old (I saw it and didn't remove
263 # # manually marked as a good comment, so publish it.
266 # # posting too much, consider them a spammer.
269 # # bad comment, but don't ban them
272 # # all comments from this ip dead, new comment's dont even go into the db.
275 # # waiting for manual moderation, get's posted automatically if there
276 # # is none in 24 hours
279 # # had a bad post in the past. does not
280 # # automatically get posted in time without moderation.
283 # any of the manual states
284 date
= $db.execute(<<-SQL)[0][0]
285 select max(date) from c where
286 state = 'moderated' or
291 # not the bad automatic states
293 select count(*) from c where
294 state != 'rate_limited' and
298 $db.execute('insert into c values (NULL, ?, ?, ?, ?, ?)',
306 new_count
= $db.execute(query
+ 'and date > ?', date
)
308 new_count
= $db.execute(query
)
310 new_count
= new_count
[0][0]
315 from
= 'www-data@' + FQDN
317 msg
= <<END_OF_MESSAGE
320 Subject: "new comment on #{FQDN}"
324 Net
::SMTP.start(server
) do |smtp
|
325 smtp
.send_message msg
, from
, to