+++ /dev/null
-#!/usr/bin/env ruby
-# encoding: utf-8
-# Copyright (C) 2016 Ian Kelling
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# debian sets LANG=C when starting apache2.
-# the envoding comment above fixes the internal encoding afaik,
-# Found this at
-# https://stackoverflow.com/questions/20521371/set-utf-8-as-default-for-ruby-1-9-3
-# also note man ruby's -E arg.
-Encoding.default_external = Encoding::UTF_8
-
-require 'cgi'
-require 'fileutils'
-require 'time'
-require 'sqlite3'
-
-require_relative '../b'
-include B
-
-# constanty things
-DEBUG = true
-CAPTCHA = -> {
- c = []
- x = (<<EOF).split("\n")
-What does a dog wag?
-tail
-Would you sleep better on a bed or a keyboard?
-bed
-Are there more or less than one million people on the Earth?
-more
-Which of Lilly and Robert is more commonly a woman’s name?
-lilly
-Which word has fewer letters, adorable or fox?
-fox
-What is the normal color of milk?
-white
-Which of Iceland and Turkey is an island nation?
-iceland
-Is a filesystem like a tree or a rose?
-tree
-What character is a tilde?
-~
-Which is brighter, the moon or the sun?
-(the )?sun
-Which is closer, the moon or the sun?
-(the )?moon
-What language is this sentence written in?
-english
-What animal says "meow" and catches mice?
-cat
-What animal quacks and has webbed feet?
-duck
-Which are better fliers: worms or birds?
-birds
-Which typically runs first: a kernel or a web browser?
-kernel
-EOF
- while x.length > 0
- c << x.pop(2)
- end
- c
-}[]
-
-def do_captcha
- captcha_q = CAPTCHA.sample[0]
- puts "Content-type: text/html\n\n"
- puts skel('comment.rb', "#{DN}/captcha", <<EOF, ' / <a href="/blog.html">blog</a> / comment-captcha')
-<p>Hello friend. I haven't read a post from #{IP}, and I only remember for a few months, so:</p>
-<p>#{captcha_q}</p>
-
-<form action="/comment.rb" method="post">
- <input class="misc" type="text" name="url">
- <input name="goto" type="hidden" value="#{GOTO}">
- <input name="question" type="hidden" value="#{captcha_q}">
- <input name="answer">
-<br>Your comment:
- <textarea rows="10" name="comment" maxlength="1000">#{COMMENT_TXT}</textarea>
- <input type="submit" value="Submit">
-</form>
-EOF
- exit 0
-end
-
-
-def fail(msg)
- if DEBUG and msg
- puts "Content-type: text/plain\n\n"
- puts msg
- else
- redir
- end
- exit 0
-end
-
-def redir
- File.write('/tmp/x', GOTO)
- puts 'Status: 302 Found'
- puts "Location: #{GOTO}#comment-section\n\n"
- exit(0)
-end
-
-
-def bn(*args)
- File.basename *args
-end
-
-
-###### begin error checking & arg parsing ######
-cgi = CGI.new
-IP = cgi.remote_addr
-
-if cgi.has_key?('goto')
- GOTO = cgi['goto']
-else
- GOTO = '/'
- fail['redir to /']
-end
-
-if (cgi.has_key?('url') && cgi['url'] != "") || ! cgi.has_key?('comment')
- fail["comment not in form or url in form. cgi.params: #{cgi.params}"]
-end
-
-COMMENT_TXT = cgi["comment"]
-
-
-if COMMENT_TXT.length > 1000 or GOTO.length > 150
- fail['length of comment or goto is too great']
-end
-
-
-captchad = false
-if cgi.has_key?('answer') && cgi.has_key?('question')
- if cgi['answer'].downcase !~ /^#{CAPTCHA.to_h[cgi['question']]}$/
- do_captcha
- end
- captchad = true
-end
-
-
--> {
- found = false
- Dir.foreach('blog') do |entry|
- next if ['.','..'].any? { |f| f == entry }
- if GOTO == 'blog/' + entry
- found = true
- break
- end
- end
- fail['goto entry not found'] unless found
-}[]
-######### end error checking & arg parsing ########
-
-
-$db = db_init
-state = nil
-WHITELIST_CUTOFF = NOW - 4*DAY
-
-
-####### begin: state for ips we've seen before #######
-[[5, 60], # 1 min
- [10, 60*5], # 5 min
- [20, 60*60], # 60 min
- [30, 60*60*24], # 1 day
- [60, 60*60*24*7]] # 1 week
- .each do |max_posts, date|
-
- if $db.execute(<<-SQL, [NOW - date])[0][0] > max_posts
- select count(*) from c
- where date > ? and ip = '#{IP}'
-SQL
- state = 'rate_limited'
- end
-end
-
-state ||= 'suspect' if $db.execute(<<-SQL)[0][0] > 0
- select count(*) from c
- where ip = '#{IP}' and (
-state = 'banned' or
-state = 'rate_limited')
-SQL
-
-unless state
- older_date = NOW - DAY*2
- last_moderated = $db.execute(<<-SQL, [older_date])[-1][0]
- select date from c
- where ip = '#{IP}' and (
- state = 'moderated' or
- (date < ? and (state = 'timed' or state = 'known')))
-SQL
- last_good = $db.execute(<<-SQL, [older_date])[-1][0]
- select date from c
- where ip = '#{IP}' and (
- state = 'picked' or
- (date < ? and (state = 'timed' or state = 'known')))
-SQL
- if last_moderated && last_good
- if last_good > last_moderated
- state = 'known'
- else
- # these 2 waiting conditions are not actually needed,
- # since waiting is the default, but meh.
- state = 'waiting'
- end
- elsif last_moderated
- state = 'waiting'
- elsif last_good
- state = 'known'
- end
-end
-####### end: state for ips we've seen before #######
-
-####### begin: whitelist checking #########
-glob = "../blog/#{'?'*'YYYY-MM-DD-'.length}#{bn GOTO, '.*'}.md"
-md_file = Dir[glob][0]
-unless state
- b = bn(md_file,'.*')
- post_date = Time.parse(b[0..DATE_LEN]).to_i
- if post_date > WHITELIST_CUTOFF
- state = 'timed'
- end
-end
-###### end: whitelist checking ########
-
-state ||= 'waiting'
-
-if state != 'known' && ! captchad
- do_captcha
-end
-
-
-# states:
-# timed
-# # was posted a whitelist period, so automatically posted.
-# # whitelist periods are per page times when legit comments are
-# # much more likely than spam, so we automatically let comments through.
-
-# known
-# # ip posted good comment before: either, one in picked state, or
-# # a timed/known comment which is over 2 days old (I saw it and didn't remove
-# # it)
-
-# picked
-# # manually marked as a good comment, so publish it.
-
-# rate_limited
-# # posting too much, consider them a spammer.
-
-# moderated
-# # bad comment, but don't ban them
-
-# banned
-# # all comments from this ip dead, new comment's dont even go into the db.
-
-# waiting
-# # waiting for manual moderation, get's posted automatically if there
-# # is none in 24 hours
-
-# suspect
-# # had a bad post in the past. does not
-# # automatically get posted in time without moderation.
-
-
-# any of the manual states
-date = $db.execute(<<-SQL)[0][0]
-select max(date) from c where
-state = 'moderated' or
-state = 'banned' or
-state = 'picked'
-SQL
-
-# not the bad automatic states
-query = <<-SQL
-select count(*) from c where
-state != 'rate_limited' and
-state != 'suspect'
-SQL
-
-
-if date
- new_count = $db.execute(query + 'and date > ?',date)
-else
- new_count = $db.execute(query)
-end
-
-if new_count == 1
- require 'net/smtp'
- def send_email(opts={})
- opts[:to] ||= ENV['USER']
- opts[:server] ||= 'localhost'
- opts[:from] ||= ENV['USER']
- opts[:from_alias] ||= ENV['USER']
- opts[:subject] ||= "test subject"
- opts[:body] ||= ""
-
- msg = <<END_OF_MESSAGE
-From: #{opts[:from_alias]} <#{opts[:from]}>
-To: <#{opts[:to]}>
-Subject: #{opts[:subject]}
-
-#{opts[:body]}
-END_OF_MESSAGE
-
- Net::SMTP.start(opts[:server]) do |smtp|
- smtp.send_message msg, opts[:from], opts[:to]
- end
- end
- send_email :subject => 'new comments on iankelling.org'
-end
-
-$db.execute('insert into c values (NULL, ?, ?, ?, ?, ?)',
- [state,
- IP,
- NOW,
- GOTO,
- COMMENT_TXT])
-
-post(md_file)
-
-redir