X-Git-Url: https://iankelling.org/git/?p=iankelling.org;a=blobdiff_plain;f=_site%2Fcgi%2Fcomment;fp=_site%2Fcgi%2Fcomment;h=b1e8796607706a844cf9a33ecf1a46500acfebec;hp=0000000000000000000000000000000000000000;hb=3c09af9cc854c716d9f772d1c8c1e568cacc92b9;hpb=d0a5b73d2a275bfff5957961f2360df8966bee22 diff --git a/_site/cgi/comment b/_site/cgi/comment new file mode 100755 index 0000000..b1e8796 --- /dev/null +++ b/_site/cgi/comment @@ -0,0 +1,336 @@ +#!/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 . + +# 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' + +Dir.chdir(File.join(File.dirname(__FILE__), '..')) + +require '../b' +include B + + +# constanty things +DEBUG = true +CAPTCHA = -> { + c = [] + x = (< 0 + c << x.pop(2) + end + c +}[] + +def do_captcha + captcha_q = CAPTCHA.sample[0] + puts "Content-type: text/html\n\n" + puts skel("#{DN}/captcha", <blog / captcha') +

Hello friend. I haven't read a post from #{IP}, and I only remember for a few months, so:

+

#{captcha_q}

+ +
+ + + + +
Your comment: + + +
+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 + 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] + select date from c + where ip = '#{IP}' and ( + state = 'moderated' or + (date < ? and (state = 'timed' or state = 'known'))) +SQL + last_moderated = last_moderated[0] if last_moderated + last_good = $db.execute(<<-SQL, [older_date])[-1] + select date from c + where ip = '#{IP}' and ( + state = 'picked' or + (date < ? and (state = 'timed' or state = 'known'))) +SQL + last_good = last_good[0] if last_good + 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 = < +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