bf3cf5bad4d46fe1e6cb9fe579bda9f0af384a00
3 # Copyright (C) 2019 Ian Kelling
4 # SPDX-License-Identifier: AGPL-3.0-or-later
6 # debian sets LANG=C when starting apache2.
7 # the envoding comment above fixes the internal encoding afaik,
9 # https://stackoverflow.com/questions/20521371/set-utf-8-as-default-for-ruby-1-9-3
10 # also note man ruby's -E arg.
11 Encoding
.default_external
= Encoding
::UTF_8
17 Dir
.chdir(File
.join(File
.dirname(__FILE__
), '..'))
27 x
= (<<EOF).split("\n")
30 Would you sleep better on a bed or a keyboard?
32 Are there more or less than one million people on the Earth?
34 Which of Lilly and Robert is more commonly a woman’s name?
36 Which word has fewer letters, adorable or fox?
38 What is the normal color of milk?
40 Which of Iceland and Turkey is an island nation?
42 Is a filesystem like a tree or a rose?
44 What character is a tilde?
46 Which is brighter, the moon or the sun?
48 Which is closer, the moon or the sun?
50 What language is this sentence written in?
52 What animal says "meow" and catches mice?
54 What animal quacks and has webbed feet?
56 Which are better fliers: worms or birds?
58 Which typically runs first: a kernel or a web browser?
68 captcha_q
= CAPTCHA
.sample
[0]
69 puts
"Content-type: text/html\n\n"
70 puts
skel("#{DN}/captcha", <<EOF, header: ' / <a href="/blog.html">blog</a> / captcha')
71 <p>Hello friend. I haven't read a post from #{IP}, and I only remember for a few months, so:</p>
74 <form action="/cgi/comment" method="post">
75 <input class="misc-comment-input" type="text" name="url">
76 <input name="goto" type="hidden" value="#{GOTO}">
77 <input name="question" type="hidden" value="#{captcha_q}">
80 <textarea rows="10" name="comment" maxlength="1000">#{COMMENT_TXT}</textarea>
81 <input type="submit" value="Submit">
90 puts
"Content-type: text/plain\n\n"
99 puts
'Status: 302 Found'
100 puts
"Location: #{GOTO}#comment-section\n\n"
110 ###### begin error checking & arg parsing ######
114 if cgi
.has_key
?('goto')
121 if (cgi
.has_key
?('url') && cgi
['url'] != "") || ! cgi
.has_key
?('comment')
122 fail("comment not in form or url in form. cgi.params: #{cgi.params}")
125 COMMENT_TXT
= cgi
["comment"]
128 if COMMENT_TXT
.length
> 1000 or GOTO
.length
> 150
129 fail('length of comment or goto is too great')
132 if COMMENT_TXT
.length
<= 2 or COMMENT_TXT
=~
/\A\s*\Z/
133 fail('not enough content in comment')
138 if cgi
.has_key
?('answer') && cgi
.has_key
?('question')
139 if cgi
['answer'].downcase
!~
/^#{CAPTCHA.to_h[cgi['question']]}$/
148 Dir
.foreach('blog') do |entry
|
149 next if ['.','..'].any
? { |f
| f
== entry
}
150 if GOTO
== '/blog/' + entry
155 fail('goto entry not found') unless found
157 ######### end error checking & arg parsing ########
162 WHITELIST_CUTOFF
= NOW
- 4*DAY
165 ####### begin: state for ips we've seen before #######
168 [20, 60*60], # 60 min
169 [30, 60*60*24], # 1 day
170 [60, 60*60*24*7]] # 1 week
171 .each
do |max_posts
, date
|
173 if $db.execute(<<-SQL, [NOW - date])[0][0] > max_posts
174 select count(*) from c
175 where date > ? and ip = '#{IP}'
177 state
= 'rate_limited'
181 state
||= 'suspect' if $db.execute(<<-SQL)[0][0] > 0
182 select count(*) from c
183 where ip = '#{IP}' and (
185 state = 'rate_limited')
189 older_date
= NOW
- DAY
*2
190 last_moderated
= $db.execute(<<-SQL, [older_date])[-1]
192 where ip = '#{IP}' and (
193 state = 'moderated' or
194 (date < ? and (state = 'timed' or state = 'known')))
196 last_moderated
= last_moderated
[0] if last_moderated
197 last_good
= $db.execute(<<-SQL, [older_date])[-1]
199 where ip = '#{IP}' and (
201 (date < ? and (state = 'timed' or state = 'known')))
203 last_good
= last_good
[0] if last_good
204 if last_moderated
&& last_good
205 if last_good
> last_moderated
208 # these 2 waiting conditions are not actually needed,
209 # since waiting is the default, but meh.
218 ####### end: state for ips we've seen before #######
220 ####### begin: whitelist checking #########
221 glob
= "../blog/#{'?'*'YYYY-MM-DD-'.length}#{bn GOTO, '.*'}.md"
222 md_file
= Dir
[glob
][0]
225 post_date
= Time
.parse(b
[0..DATE_LEN
]).to_i
226 if post_date
> WHITELIST_CUTOFF
230 ###### end: whitelist checking ########
234 if state
!= 'known' && ! captchad
241 # # was posted a whitelist period, so automatically posted.
242 # # whitelist periods are per page times when legit comments are
243 # # much more likely than spam, so we automatically let comments through.
246 # # ip posted good comment before: either, one in picked state, or
247 # # a timed/known comment which is over 2 days old (I saw it and didn't remove
251 # # manually marked as a good comment, so publish it.
254 # # posting too much, consider them a spammer.
257 # # bad comment, but don't ban them
260 # # all comments from this ip dead, new comment's dont even go into the db.
263 # # waiting for manual moderation, get's posted automatically if there
264 # # is none in 24 hours
267 # # had a bad post in the past. does not
268 # # automatically get posted in time without moderation.
271 # any of the manual states
272 date
= $db.execute(<<-SQL)[0][0]
273 select max(date) from c where
274 state = 'moderated' or
279 # not the bad automatic states
281 select count(*) from c where
282 state != 'rate_limited' and
286 $db.execute('insert into c values (NULL, ?, ?, ?, ?, ?)',
294 new_count
= $db.execute(query
+ 'and date > ?', date
)
296 new_count
= $db.execute(query
)
298 new_count
= new_count
[0][0]
302 to
= 'root@localhost'
303 from
= 'www-data@' + FQDN
305 msg
= <<END_OF_MESSAGE
308 Subject: new comment on #{FQDN}
312 Net
::SMTP.start(server
) do |smtp
|
313 smtp
.send_message msg
, from
, to