March 2008

Who Broke Hoodwink'd?

Hoodwink’d is my favorite underground club, but right now it’s broken! Many are complaining, _why has been very silent and no one are there to help us. Since no one else wanted to investigate, it looks like I’ve got to do it.

First: The bugs

The first thing that is broken is the fresh sites at onslaught. Right now it looks like this:

But if you go to the satellite office, you’ll see that there are 19 more sites:

My first thought was that something went wrong when I added diskusjon.no, since if you tried to post on any of those sites you’ll get a dirty error:

ActiveRecord::StatementInvalid Mysql::Error: Column 'site_id' cannot be null: INSERT INTO hoodwinkd_posts (`permalink`, `site_id`, `title`, `last_wink_id`, `layer_id`, `wink_count`, `created_at`, `first_wink_id`) VALUES('/posts/haiku.rb.html', NULL, '', 0, 1431, 0, '2008-03-25 06:08:31', 0):

I was wrong. The same error appeared on many other sites too. You’ll get that error if you try to post on a page without any other winks. It also looks like I was the last to post on a page without any other winks:

Something happened after January 11. Someone broke Hoodwink’d!

Use the source!

Luckily for us, the source is freely available at why’s subversion repo. The error claims to be in /home/sites/hoodwink.d/lib/hoodwinkd/controllers.rb:120 and that 'site_id' cannot be null. Let’s check it out:

@post = Post.find_by_sql([<<-END, domain, @permalink]).first
  SELECT p.*, IFNULL(s.real_domain, s.domain) AS real_domain
  FROM hoodwinkd_posts p, hoodwinkd_layers l, hoodwinkd_sites s
  WHERE p.layer_id = l.id AND l.site_id = s.id
  AND s.domain = ? AND p.permalink = ?
END
if @post
  domain = @post.real_domain
else
  @layer =
    Site.find_by_domain(domain, :include => :layers).layers.detect do |l|
      @permalink =~ /#{ l.fullpost_url_match }/
    end
  # Line 120 below
  @post = Post.create :layer_id => @layer.id, :permalink => @permalink,
    :wink_count => 0
end

Post referrers to a page which has many Winks. First it tries to find a Post based on domain and permalink. If there isn’t such a post, it will create one. The problem is that the database wants site_id, while Post.create is only given layer_id, permalink and wiki_count.

What’s really weird is the schema. The Post-table shouldn’t have a site_id:

create_table :hoodwinkd_posts do |t|
  t.column :id,         :integer, :null => false
  t.column :layer_id, :integer, :null => false
  t.column :permalink,  :string,  :limit => 192, :null => false
  t.column :wink_count, :integer
  t.column :created_at, :datetime
  t.column :title,      :string,  :limit => 192
  t.column :first_wink_id, :integer
  t.column :last_wink_id,  :integer
end

Here’s what I think happened: Around January 11 why did some refactoring where he added @siteid@ to hoodwinkd_posts. In the refactoring he simply forgot to update line 120. I have no idea why he didn’t commit the changes to the repo.

Another option is that someone gained access to the database and added another column, but what kind of a hacker does that?

Why The Lucky Stiff, we require answers!