Ebay, always a bargain? 0

ludo, Saturday 24 July 2004

Russell learns that you don't always save any real money on Ebay. I am learning the same thing these days, luckily without spending as much money as Russell.

My P800 screen is broken, so I'm looking for a new phone since repairing the P800 will cost almost 200 euros. As much as I love my P800, I'm pretty tired of having to carry a brick in my pocket without even being able to read email or surf the web (my company's cellular subscription has GPRS and MMS disabled), and since I have lots of SonyEricsson gear (USB data and power cables, etc.) I am trying to buy a T600 (which is the smallest modern phone around), or a T610.

I have been tracking Ebay auctions for a couple of weeks, and I guess today I'll go to the store downstairs and buy a new T610. T600s are pretty rare, and with all the noob ebayers out there who start bidding as soon as the auction is up, and shippinh, they end up costing around 100 euro which is too much for a GPRS-less used phone.

T610s on Ebay go for 130-140 euros plus shipping, and since they're being discontinued for the T630, the shops that are still carrying them have lowered their prices to 170, which gets you a brand new phone with a 2-yr warranty without the risk of being scammed, and without having to wait for the seller to wake up and ship the item.

I guess Ebay is ok if you're looking for something special, or very rare (I just bougth a Swatch bag which isn't available to the public), otherwise I guess you'd make a better deal buying new from a store.

Simple Aggregator 1.0 0

ludo, Thursday 08 July 2004

Simple Aggregator

From time to time I receive an inquiry about the status of of my FeedOnFeeds rewrite, so today I took a few hours to add the main missing bits. For those of you who have never heard of FeedOnFeeds, Simple Aggregator is a web-based news aggregator. It supports Atom feeds, CDF, and the nine different versions of RSS thanks to Mark Pilgrim's Universal Feed Parser. There are still a few things missing (a frames view, OPML import/export, maybe categories), but it's quite usable and highly customizable if you know a bit of HTML and/or PHP.

It features a few improvements over its ancestor:

  • a better SQL schema
  • separation of code and presentation using HTML templates
  • cleaner code, the web interface is composed of 3 PHP scripts, 3 templates, and two PEAR-compliant PHP classes (DB access and templating)
  • a Python command line feeds loader making full use of Mark Pilgrim's Universal Feed Parser last-modified and etag support to reduce bandwidth usage
  • better looking (IMHO), and more user-friendly interface

It has few requirements:

  • a recent (2.2+) version of Python
  • the MySQLdb module
  • Mark Pilgrim's Universal Feed Parser
  • MySQL 4.0+ with InnoDB support
  • a recent PHP with the mysql extension
  • the PEAR base class in your PHP include_path

If you want to give it a try, version 1.0 is available for download. Please send me any comments, patches, or features suggestions.

Line Endings in Mail Messages 0

ludo, Thursday 01 July 2004

This entry expands on the subject of line endings in email (and news) messages, which I introduced in my previous entry on SMIME. In my (brief) experience working with mail and news messages, there are three different contexts involving line endings:

  • transmission over the wire by SMTP (or NNTP)
  • MIME canonicalization
  • local handling of messages (storage, mail applications, etc.)

Transmission by SMTP RFC2822 specifies CR+LF line endings for on-the-wire transmission by SMTP.

MIME canonicalization RFC2049 specifies that CR+LFs are used in the canonicalization of MIME body parts BEFORE applying transfer encoding. It further states that

The output of the encoders may have to pass through one or more additional steps prior to being transmitted as a message. As such, the output of the encoder may not be conformant with the formats specified by RFC 822. In particular, once again it may be appropriate for the converter's output to be expressed using local newline conventions rather than using the standard RFC 822 CRLF delimiters.

In this context then, the appropriate linefeeds for an email message depend on the steps to perform after MIME canonicalization, and the local line endings convention.

Local handling It is apparent from the previous excerpt from RFC2049 that local handling of mail messages has to follow the local operating system's line endings convention. The most typical examples of this context are storing messages (eg in mailbox files or Maildirs), and piping/passing messages to mail handling programs like qmail-inject or Openssl's smime command. Googling on this context reveals the complexity often surrounding the question of proper line endings in email and news messages, and produces a few interesting links such as Life With Qmail: G.11. Carriage Return/Linefeed (CRLF) line breaks don't work, PHP bug #15841, a thread on the ietf- 822 list, and a thread on the Usenet Article Standard Update mailing list.

An illustrious victim of the line endings complexity I had to struggle with these past few days is Openssl's smime command, which has the nasty habit of outputting only the pkcs7 payload with CR+LF line endings when clear signing a message. The Python email package also seems to have a few problems with line endings, cf. bug #975330 submitted by Anders Hammarquist, who gave the very interesting Real-world email handling in python presentation at EuroPython 2004.

SMIME sucks 0

ludo, Thursday 24 June 2004

Well, not exactly..... SMIME is a very nice spec, it's widely supported, and there are SMIME libraries for most development environments. The problem is that the RFC822 SMTP-vs-local line- endings nastyness combines with MIME canonicalization and libraries/mail UAs idiosyncracies to make SMIME messages very fragile. In the rest of this entry, I briefly describe a couple of SMIME pitfalls I spent quite a few hours debugging recently.

If you want to experiment with SMIME signing, you can download the M2Crypto- based SMIME signing class which is the companion to the verifying class of my previous entry on SMIME.

line endings

Using OpenSSL (via M2Crypto) to cleartext sign a SMIME message, you get back a valid multipart/signed message that has one problem: the cleartext part of the message has CR+LF line endings, while the rest of the message (pkcs7 signature, SMIME headers) has LF line endings (cf this thread on mailing.openssl.dev). OpenSSL performs MIME canonicalization (ie it converts line endings to CR+LF -- SMIME_crlf_copy() in crypto/pkcs7/pk7_smime.c/PKCS7_sign()) on the message before signing it, as per the SMIME spec. The problem is that a message with a mix of CR+LF and bare LF is almost never what you need. If you send the message by SMTP directly after signing it, it should have CR+LF line endings as per RFC822. If you hand off the message to a program like qmail-inject, your message should respect local conventions, ie on Unix have bare LF line endings. This problem becomes apparent when you open a signed message in Outlook or Outlook Express, since the message appears tampered and cannot be verified. If you sign with the "binary" flag, you get no CR+LF line endings but the resulting message cannot be verified unless you perform the MIME canonicalization yourself before signing, getting the same output as in the above situation. A sample of the resulting message, with line endings prefixed to the actual lines (and long lines snipped):

'n'    MIME-Version: 1.0
'n'    Content-Type: multipart/signed; protocol="application/x-pkcs7-sig
'n'
'n'    This is an S/MIME signed message
'n'
'n'    ------526F05E052FA5F1DF695C4ABA3E3EF81
'rn'  prova prova prova
'rn'   prova 123
'rn'
'rn'  prova
'n'
'n'    ------526F05E052FA5F1DF695C4ABA3E3EF81
'n'    Content-Type: application/x-pkcs7-signature; name="smime.p7s"
'n'    Content-Transfer-Encoding: base64
'n'    Content-Disposition: attachment; filename="smime.p7s"
'n'
'n'    MIIGDAYJKoZIhvcNAQcCoIIF/TCCBfkCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3
'n'    DQEHAaCCA9gwggPUMIIDPaADAgECAgECMA0GCSqGSIb3DQEBBAUAMIGbMQswCQYD

outlook interoperability

If you sign SMIME messages with OpenSSL on Unix, you may discover that your messages are valid in Mozilla, but they appear as tampered in Outlook, Outlook Express, and programs using MS libraries to validate them. A search on Google Groups turns up quite a few threads on this topic, none of which unfortunately offer any practical help, apart from generic suggestions regarding line-ending conversions, which do not work. After quite a few hours spent debugging this problem, I could only come up with a practical solution with no theorical explanation: append a single linefeed (a bare LF) to the end of the payload before signing it. Mozilla and OpenSSL keep verifying the resulting SMIME messages, and Outlook stops complaining that the message has been tampered with. I still have to verify the implications of this workaround when you sign a message that includes a SMIME message as a MIME message/rfc822 attachment, since I've noticed that signing such a message often breaks the attachment validity.

Verifying SMIME email with M2Crypto 0

ludo, Monday 31 May 2004

In my spare time, I'm working on a project where I have to sign and verify SMIME mail using M2Crypto which works quite well, but lacks a bit in documentation especially on SMIME functions. The Programming S/MIME in Python with M2Crypto howto is enough to point you in the right direction, and the source has a few SMIME examples. What is missing is a recipe to verify signed SMIME messages if you don't have the signer's certificate, which is what usually happens when you have to verify Internet email.

Openssl's smime command is able to do that, so there should be a way to accomplish the same thing from Python using M2Crypto. After a bit of fiddling around and looking at openssl's source, I have found out a way which seems to work (update: content check done against the output of SMIME.smime_load_pkcs7_bio instead of using email.message_from_string, return a list of certificates on succesful verification, show content diff if verification fails):

#!/usr/bin/python
"""
Simple class to verify SMIME signed email messages
without having to know the signer's certificate.
The signer's certificate(s) is extracted from
the signed message, and returned on successful
verification.
A unified diff of the cleartext content against
the one resulting from verification is returned
as exception value if the content has been tampered
with.

Use at your own risk, send comments and fixes. May 30, 2004 Ludovico Magnocavallo <[email protected]> """

import os, base64 from M2Crypto import BIO, SMIME, m2, X509 from difflib import unified_diff

class VerifierError(Exception): pass

class Verifier(object): """ accepts an email payload and verifies it with SMIME """ def __init__(self, certstore): """ certstore - path to the file used to store CA certificates eg /etc/apache/ssl.crt/ca-bundle.crt >>> v = Verifier('/etc/dummy.crt') >>> v.verify('pippo') Traceback (most recent call last): File "/usr/lib/python2.3/doctest.py", line 442, in _run_examples_inner compileflags, 1) in globs File "<string>", line 1, in ? File "verifier.py", line 46, in verify self._setup() File "verifier.py", line 36, in _setup raise VerifierError, "cannot access %s" % self._certstore VerifierError: cannot access /etc/dummy.crt >>> """ self._certstore = certstore self._smime = None def _setup(self): """ sets up the SMIME.SMIME instance and loads the CA certificates store """ smime = SMIME.SMIME() st = X509.X509_Store() if not os.access(self._certstore, os.R_OK): raise VerifierError, "cannot access %s" % self._certstore st.load_info(self._certstore) smime.set_x509_store(st) self._smime = smime def verify(self, text): """ verifies a signed SMIME email returns a list of certificates used to sign the SMIME message on success

text - string containing the SMIME signed message

>>> v = Verifier('/etc/apache/ssl.crt/ca-bundle.crt') >>> v.verify('pippo') Traceback (most recent call last): File "<stdin>", line 1, in ? File "signer.py", line 23, in __init__ raise VerifierError, e VerifierError: cannot extract payloads from message >>> >>> certs = v.verify(test_email) >>> isinstance(certs, list) and len(certs) > 0 True >>> """ if self._smime is None: self._setup() buf = BIO.MemoryBuffer(text) try: p7, data_bio = SMIME.smime_load_pkcs7_bio(buf) except SystemError: # uncaught exception in M2Crypto raise VerifierError, "cannot extract payloads from message" if data_bio is not None: data = data_bio.read() data_bio = BIO.MemoryBuffer(data) sk3 = p7.get0_signers(X509.X509_Stack()) if len(sk3) == 0: raise VerifierError, "no certificates found in message" signer_certs = [] for cert in sk3: signer_certs.append( "-----BEGIN CERTIFICATE-----n%s-----END CERTIFICATE-----" % base64.encodestring(sk3[0].as_der())) self._smime.set_x509_stack(sk3) try: if data_bio is not None: v = self._smime.verify(p7, data_bio) else: v = self._smime.verify(p7) except SMIME.SMIME_Error, e: raise VerifierError, "message verification failed: %s" % e if data_bio is not None and data != v: raise VerifierError, "message verification failed: payload vs SMIME.verify output diffn%s" % 'n'.join(list(unified_diff(data.split('n'), v.split('n'), n = 1))) return signer_certs

test_email = """put your test SMIME signed email here"""

def _test(): import doctest return doctest.testmod()

if __name__ == "__main__": _test()

Customizing logging.LogRecord 0

ludo, Tuesday 18 May 2004

I haven't posted anything in a long while since I've been pretty busy working on a complex project in my spare time (let's call it project X), and I've not had Internet access outside office hours for the past couple of months. I hope to start writing again frequently soon, since I'm learning interesting things for project X which involves SMIME and SMTP mail (using OpenSSL, M2Crypto and the email package).

This brief note, done mainly so that I can use it as a reference in the future, regards customizing logging.LogRecord. One of the requirements of project X is that all operations for a single transaction are logged with a unique timestamp, representing the start of the transaction. Since I'm lazy, and I don't trust myself too much when writing code (especially at late hours), I did not want to have to carry around this value everywhere, and remember to pass it to every logging call.

So last night I had a look into the logging internals, to see how to extend logging.LogRecord to carry an additional argument representing my unique timestamp. Apart from being very useful, the logging package is very well architected, so it turns out subclassing LogRecord is not too difficult. Let's see one way of doing it, which is not necessarily the best one so please send me any improvements/suggestions.

LogRecord instances are

created every time something is logged. They contain all the information pertinent to the event being logged. [...]

LogRecord has no methods; it's just a repository for information about the logging event. The only reason it's a class rather than a dictionary is to facilitate extension.

LogRecord objects are created by makeRecord, a factory method of the logging.Logger class. To use a custom subclass of LogRecord, you have to subclass logging.Logger and override makeRecord, then set your subclassed Logger class as logging's default which is not too hard.

When you call logging.getLogger(name) to get a new logger instance, the getLogger function calls Logger.manager.getLogger(name). Manager.getLogger (Logger.manager is Manager(Logger.root)) does a bunch of stuff, then if no loggers with the same name have been already created, it returns a new instance of _loggerClass for the given name. logging._loggerClass is set by default as _loggerClass = Logger. So to set your Logger subclass as logging's default, just call:

logging.setLoggerClass(CustomLogger)

Now that we know what to subclass and how to set logging to use it, we still have to decide how to pass the extra argument to LogRecord. To pass it to our CustomLogger class as an additional __init__ argument we would have to subclass logging.Manager and override getLogger, which seems a bit too much work. Another option would be to directly set the extra parameter in every newly obtained CustomLogger instance, but then you would have to remember to set it every time, since logging raises a KeyError exception if you'd try to use the missing parameter as a string format (you could also set it to a default value in CustomLogging.__init__, but then you'd get a default value in your logs which IMHO is even worse). The way I choose to do it is to set the parameter in logging.root so that my extra argument is available to all loggers since they share a reference to the root logger. To make the root logger emit CustomLogRecord instances, I redefined logging.RootLogger.MakeRecord to match CustomLogger.MakeRecord.

This solution obviously won't work if you need to set different extra arguments for different loggers, and my custom classes manage only a single extra argument, which is exactly what I need for this project but may not be enough for other cases.

#!/usr/bin/python
import logging
from time import strftime, gmtime

class CustomLogRecord(logging.LogRecord): """ LogRecord subclass that stores a unique -- transaction -- time as an additional attribute """ def __init__(self, name, level, pathname, lineno, msg, args, exc_info, trans_time): logging.LogRecord.__init__(self, name, level, pathname, lineno, msg, args, exc_info) self.trans_time = trans_time

def makeRecord(self, name, level, fn, lno, msg, args, exc_info): return CustomLogRecord(name, level, fn, lno, msg, args, exc_info, self.root.trans_time)

class CustomLogger(logging.Logger): """ Logger subclass that uses CustomLogRecord as its LogRecord class """ def __init__(self, name, level=logging.NOTSET): logging.Logger.__init__(self, name, level) makeRecord = makeRecord

# setup logging.setLoggerClass(CustomLogger) logging.root.trans_time = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) logging.RootLogger.makeRecord = makeRecord

if __name__ == '__main__': # get logger logger = logging.getLogger('modified logger') logger.propagate = False hdlr = logging.StreamHandler() hdlr.setFormatter(logging.Formatter( '%(trans_time)s [%(asctime)s] %(levelname)s %(process)d %(message)s')) logger.addHandler(hdlr) logger.setLevel(logging.DEBUG) logging.root.addHandler(hdlr) logger.debug('test') from time import sleep sleep(1) logger.debug('a second test, with (hopefully) the same date as the previous log record') logging.warn('root logging')

The output of the above script should be something like:

Tue, 18 May 2004 12:53:04 +0000 [2004-05-18 14:53:04,898] DEBUG 31268 test
Tue, 18 May 2004 12:53:04 +0000 [2004-05-18 14:53:05,898] DEBUG 31268 a second test,
with (hopefully) the same date as the previous log record
Tue, 18 May 2004 12:53:04 +0000 [2004-05-18 14:53:05,898] WARNING 31268 root logging

Urban Headphones 0

ludo, Wednesday 21 January 2004

Koss 'The Plug'

As I mentioned in a previous entry I am now the happy owner of a Sony Ericsson P800 which I use to listen to mp3 audiobooks while going to/from work by subway and bus, which are unfortunately very noisy. After a while I got fed up with trying to shield my ears from the noise, and started investigating possible solutions to this problem.

My first thought was to look for a headphone amplifier to raise the volume of the P800 audio output. It turns out that pocket headphone amplifiers are not too common. Apart from a DIY project which I have no skills (and no patience) to complete, the only one I was able to find (via Dan's Data) is the Boostaroo, which looks like a nice little piece of hardware. Unfortunately the Boostaroo isn't available in Italy, making it a bit too expensive. And of course, what's the point of having a smart phone if you have to carry other stuff around to use it?

My next thought was to find a pair of portable noise-reducing headphones. After reading a nice review on cheap headphones on HeadRoom I decided that a pair of Shure E2C would be perfect to shield my ears from the surrounding noise, knowing perfectly well though that I would never be able to find them in stock somewhere around here.

Sennheiser PX200

So today when I got out from work I went shopping, hoping to find the right pair of headphones for my needs. My first stop was a well known HI-FI store, where despite the snobbish clerk (apparently you should enter that store only if you're interested -- ie buying -- esoteric and very expensive HI-FI components) I bought a pair of Sennheiser PX 200. Then on my way home I stopped at FNAC to buy a 3.5-2.5 jack (while I wait for my PC-Mobile SP8AA adapter to get here from Hong Kong) and, surprise surprise, they have a cheap version of the E2Cs, the Koss "The Plug" earbuds. Obviously I couldn't resist and I bought them too.

My impression so far is that the Koss earbuds are perfect for my needs: they're (relatively, at least in Italy) cheap, very light, and they block most of the outside noise. The Sennheisers on the other hand are a bit too big to carry around comfortably (they fold in an eyeglasses- size case), but they're beautiful, very very comfortable, and a wonderful piece of design. So I'm glad I bought both, and I guess I will use the Koss earbuds with the P800 while the Sennheisers will go in my laptop bag, or come handy at home when E. is working in the same room where I am (she needs total silence, except when she feels like chatting which is very often). =)

update: it turns out that the Koss earplugs are not as comfortable as I thought, inserting them well is pretty hard, and they are almost painful; on the other hand the Sennheisers are wonderful, very very comfortable and with very good external noise reduction, so I'm using them as my day-to-day headphones.

What a waste 0

ludo, Saturday 17 January 2004

I skimmed over the Return of the King movie today, and though I did not think it was possible, it's even worse than the previous two episodes. The characters are all wrong (Denetor for instance is too young and looks like a butcher not like a high noble and the lore master of Gondor, Aragorn looks like a biker, etc.), the architecture and the costumes are all wrong (the palace of Gondor is a romanesque church, Gandalf's staff looks like an art deco object, Pipin should have worn Gondor's uniform with a silver winged helmet and a black vest with the silver tree, the cloak of the Nazguls' Captain -- who used to be a king and is Sauron's most powerful servant -- is in tatters, etc.), the music is awful, the directing is pompous and empty (no more birds-eye shots, please).

What a waste, thinking that these will be remembered as the Lord of the Rings movies. I may be culturally biased here, but having seen the first two Harry Potter movies, I think the LoTR should have been produced in the UK, with a british director and british staff. Or they should have hired a real director, like Ermanno Olmi (maybe a bit too gloomy to be successful, but have a look at The Profession of Arms to see what I mean). The Jackson movies look too much like a long, boring, and badly made rock videoclip.

Luckily there's the beautiful unabridged Audiobook narrated by Rob Inglis which I am listening to these days.