Archive for April, 2005

Disallowing TRACE but putting internal hostnames into replies …

Wednesday, April 20th, 2005

… can be considered stupid.

c0ldcut:~ md$ socat – tcp:www.lufthansa.de:80,crlf
TRACE http://portal.lufthansa.com/online/portal HTTP/1.0

HTTP/1.1 403 Forbidden by rule.
Cache-Control: no-cache
Pragma: no-cache
Expires: Tue, 19 Apr 2005 20:51:28 GMT
Last-Modified: Tue, 19 Apr 2005 20:51:28 GMT
Content-Length: 226
Content-Type: text/html; charset=IBM-850
Accept-Ranges: bytes
Connection: close
Location: http://portal.lufthansa.com/online/portal
Date: Tue, 19 Apr 2005 20:51:28 GMT
Server: IBM-PROXY-WTE/5.1.1

<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 403</H1>Forbidden by rule.<P><HR><ADDRESS><A HREF=”http://defrqnd020lhax6/”>Edge Components Caching Proxy – International English Edition 5.1.1</A></ADDRESS></BODY></HTML>

Convinient Web Application Testing

Wednesday, April 13th, 2005

Many WebApps I’m testing want to be installed at the top level directory. That’s often a hassle. Solution: use wildcard DNS and “Name Based Virtual Hosting” with Apache. I have set up a domain localhost.23.nu. where every hostname points to 127.0.0.1:

$ host abc.localhost.23.nu
abc.localhost.23.nu     A       127.0.0.1
$ host def.localhost.23.nu
def.localhost.23.nu     A       127.0.0.1

Now using a little bit of Apache magic I can add virtual hosts by just creating a directory:

NameVirtualHost 127.0.0.1:80

<VirtualHost 127.0.0.1:80>
    ServerAdmin root@localhost.localdomain
    # path to your web application goes here
    VirtualDocumentRoot /Users/md/Sites/%1
</VirtualHost>

not citrusdb.localhost.23.nu will point to /Users/md/Sites/, phpnuke.localhost.23.nu /Users/md/Sites/phpnuke, etc. Very convinient.

POPing mails into Rails

Sunday, April 10th, 2005

There are countless ways of Getting Mails into a Ruby on Rails application. This is my take:

Running a Mailserver nowadays is a burden. So I like to outsource it. I get all mail to *@example.com delivered to a POP3 account. I use getmail to fetch the mail via POP3 and stuff it into ActionMailer. I also keep a kopy of each Mail in a so called maildir because I don’t trust this Web-Database-Thingies.

I have a dedicated user, let’s say called ‘project’. So I created ~/.getmail/getmailrc:

[retriever]
type = MultidropPOP3Retriever
server = pop3.example.com
username = something
password = secret
envelope_recipient = x-envelope-to:1

[destination]
type = MultiDestination
destinations = ("[maildir]", "[rails]")

[maildir]
# keep local copies. Don't forget
# mkdir ~/Maildir ~/Maildir/tmp ~Maildir/new ~Maildir/cur
type = Maildir
path = /usr/local/project/Maildir/

[rails]
type = MDA_external
path = /usr/bin/env
arguments = ("RAILS_ENV=production",
 "sh", "-c",
 "cd /usr/local/project/rails; /usr/local/bin/ruby
 script/runner 'Incoming.receive(STDIN.read)'")

[options]
# delete mails on server
delete = On

The call to runner is somewhat fancy to ensure it gets the environment right.

With tis setup I can use ontime mailadresses ensuring that any reply to mails my application had sent out can be easily connected to the related data. So when sending a mail about issue 345 to customer 12 I use something like service-345-12@example.com. When the customer replies to that mail I can parse the adress and extract the imformation. No further reason to bother customers with tracking numbers and the like.

Alert Mails with Rails

Saturday, April 9th, 2005

The Ruby on Rails Wiki explains Howto Send Email When Rails Throws An Exception. It’s a bit brief.

app/controllers/application.rb has

  # send out emails each time we get an exception
  def log_error(exception)
    if RAILS_ENV != 'development'
      super(exception)
      InternalNotifier.deliver_snapshot(exception,
                                        clean_backtrace(exception),
                                        @session.instance_variable_get("@data"),
                                        @params,
                                        @request.env)
    end
  end

Then do something like ruby scripts generate mailer InternalNotifier. app/models/internal_notifier.rb:

  # this is called for every exception in production mode
  def snapshot(exception,
               backtrace,
               session_data,
               params,
               env)
    # Email header info MUST be added here
    @recipients = 'XXX@example.com'
    @from = "#{MAILSENDERNAME} "
    @subject = "CRM crashed"  

    # Email body substitutions go here
    @body["exception"] = exception
    @body["backtrace"] = backtrace
    @body["session_data"] = session_data
    @body["params"] = params
    @body["env"] = env
  end

app/views/internal_notifier/snapshot.rhtml:

CRASH!
--- 

---- 

---- 

--- 

--- 

I didn’t know 0.0.0.0 was a valid destination address.

Wednesday, April 6th, 2005
$ ping 0.0.0.0
PING 0.0.0.0 (0.0.0.0): 56 data bytes
64 bytes from 172.30.14.1: icmp_seq=0 ttl=64 time=1.843 ms
64 bytes from 172.30.14.1: icmp_seq=1 ttl=64 time=1.774 ms
64 bytes from 172.30.14.1: icmp_seq=2 ttl=64 time=1.748 ms
^C
--- 0.0.0.0 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.748/1.788/1.843 ms

Doing HTTP-Auth with Ruby on Rails.

Wednesday, April 6th, 2005

The Ruby on Rails bunch seems Web coder centric – no surprise there. Seems Web people prefer form/session based authentication while Unix weenies prefer HTTP-Authentication. Ever tried to get past form based authentication in a script? It’s a pain.

There are some issues with HTTP-Authentication in most Web Application environments but usually you can hack it in.

For Ruby on rails you don’t even need to patch. Create a User Model with a username and a passwd row somehow like this:

require 'digest/sha1' 

class User < ActiveRecord::Base
  def passwd=(str)
    write_attribute("passwd", Digest::SHA1.hexdigest(str))
  end 

  def passwd
    "*****"
  end 

  def self.authenticate(username, passwd)
    find_first([ "username = ? AND passwd =?",
               username,
               Digest::SHA1.hexdigest(passwd) ])
  end
end

Then add this to your app/controllers/application.rb:

  def authorize(realm='Web Password', errormessage="Could't authenticate you")
    username, passwd = get_auth_data
    # check if authorized
    # try to get user
    if user = User.authenticate(username, passwd)
      # user exists and password is correct ... horray!
      if user.methods.include? 'lastlogin'
        # note last login
        @session['lastlogin'] = user.lastlogin
        user.last.login = Time.now
        user.save()
      @session["User.id"] = user.id
      end
    else
      # the user does not exist or the password was wrong
      @response.headers["Status"] = "Unauthorized"
      @response.headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
      render_text(errormessage, 401)
    end
  end 

  private
  def get_auth_data
    user, pass = '', ''
    # extract authorisation credentials
    if request.env.has_key? 'X-HTTP_AUTHORIZATION'
      # try to get it where mod_rewrite might have put it
      authdata = @request.env['X-HTTP_AUTHORIZATION'].to_s.split
    elsif request.env.has_key? 'HTTP_AUTHORIZATION'
      # this is the regular location
      authdata = @request.env['HTTP_AUTHORIZATION'].to_s.split
    end 

    # at the moment we only support basic authentication
    if authdata and authdata[0] == 'Basic'
      user, pass = Base64.decode64(authdata[1]).split(':')[0..1]
    end
    return [user, pass]
  end

Now you can add before_filter :authorize to all of your controllers which need protection.

Tested with Webrick and lighttpd/FastCGI